diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 000000000000..f5340d8cc2a7
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,45 @@
+# Use the latest 2.1 version of CircleCI pipeline process engine.
+# See: https://circleci.com/docs/2.0/configuration-reference
+version: 2.1
+
+# Define a job to be invoked later in a workflow.
+# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
+jobs:
+ build:
+ working_directory: ~/repo
+ # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
+ # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
+ docker:
+ - image: circleci/golang:1.16.10
+ # Add steps to the job
+ # See: https://circleci.com/docs/2.0/configuration-reference/#steps
+ steps:
+ - checkout
+ - restore_cache:
+ keys:
+ - go-mod-v4-{{ checksum "go.sum" }}
+ - run:
+ name: Install Dependencies
+ command: go mod download
+ - save_cache:
+ key: go-mod-v4-{{ checksum "go.sum" }}
+ paths:
+ - "/go/pkg/mod"
+ #- run:
+ # name: Run linter
+ # command: |
+ # go run build/ci.go lint
+ - run:
+ name: Run tests
+ command: |
+ go run build/ci.go test -coverage
+ - store_test_results:
+ path: /tmp/test-reports
+
+# Invoke jobs via workflows
+# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
+workflows:
+ sample: # This is the name of the workflow, feel free to change it to better match your workflow.
+ # Inside the workflow, you define the jobs you want to run.
+ jobs:
+ - build
diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go
index d0539eeff74c..812a6bfa4d34 100644
--- a/cmd/geth/snapshot.go
+++ b/cmd/geth/snapshot.go
@@ -220,7 +220,7 @@ func verifyState(ctx *cli.Context) error {
log.Error("Failed to load head block")
return errors.New("no head block")
}
- snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false)
+ snaptree, err := snapshot.New(chaindb, trie.NewDatabase(chaindb), 256, headBlock.Root(), false, false, false, false)
if err != nil {
log.Error("Failed to open snapshot tree", "err", err)
return err
@@ -468,7 +468,7 @@ func dumpState(ctx *cli.Context) error {
if err != nil {
return err
}
- snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, root, false, false, false)
+ snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, root, false, false, false, false)
if err != nil {
return err
}
diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go
index 7dec436a2601..1b7531f916bd 100644
--- a/consensus/ethash/consensus.go
+++ b/consensus/ethash/consensus.go
@@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
+ "github.com/ethereum/go-ethereum/trie/utils"
"golang.org/x/crypto/sha3"
)
@@ -659,10 +660,21 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)
+
+ if config.IsCancun(header.Number) {
+ uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes())
+ state.Witness().TouchAddressOnReadAndComputeGas(uncleCoinbase)
+ state.Witness().SetLeafValue(uncleCoinbase, state.GetBalance(uncle.Coinbase).Bytes())
+ }
state.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, big32)
reward.Add(reward, r)
}
+ if config.IsCancun(header.Number) {
+ coinbase := utils.GetTreeKeyBalance(header.Coinbase.Bytes())
+ state.Witness().TouchAddressOnReadAndComputeGas(coinbase)
+ state.Witness().SetLeafValue(coinbase, state.GetBalance(header.Coinbase).Bytes())
+ }
state.AddBalance(header.Coinbase, reward)
}
diff --git a/core/blockchain.go b/core/blockchain.go
index bc6422a701c4..7328684495c6 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -226,15 +226,10 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
futureBlocks, _ := lru.New(maxFutureBlocks)
bc := &BlockChain{
- chainConfig: chainConfig,
- cacheConfig: cacheConfig,
- db: db,
- triegc: prque.New(nil),
- stateCache: state.NewDatabaseWithConfig(db, &trie.Config{
- Cache: cacheConfig.TrieCleanLimit,
- Journal: cacheConfig.TrieCleanJournal,
- Preimages: cacheConfig.Preimages,
- }),
+ chainConfig: chainConfig,
+ cacheConfig: cacheConfig,
+ db: db,
+ triegc: prque.New(nil),
quit: make(chan struct{}),
chainmu: syncx.NewClosableMutex(),
bodyCache: bodyCache,
@@ -283,6 +278,13 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
// Make sure the state associated with the block is available
head := bc.CurrentBlock()
+ bc.stateCache = state.NewDatabaseWithConfig(db, &trie.Config{
+ Cache: cacheConfig.TrieCleanLimit,
+ Journal: cacheConfig.TrieCleanJournal,
+ Preimages: cacheConfig.Preimages,
+ UseVerkle: chainConfig.IsCancun(head.Header().Number),
+ })
+
if _, err := state.New(head.Root(), bc.stateCache, bc.snaps); err != nil {
// Head state is missing, before the state recovery, find out the
// disk layer point of snapshot(if it's enabled). Make sure the
@@ -375,7 +377,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
log.Warn("Enabling snapshot recovery", "chainhead", head.NumberU64(), "diskbase", *layer)
recover = true
}
- bc.snaps, _ = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, true, recover)
+ bc.snaps, _ = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, head.Root(), !bc.cacheConfig.SnapshotWait, true, recover, chainConfig.IsCancun(head.Header().Number))
}
// Start future block processor.
@@ -1591,7 +1593,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
// Process block using the parent state as reference point
substart := time.Now()
- receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
+ var (
+ usedGas uint64
+ receipts types.Receipts
+ logs []*types.Log
+ )
+ receipts, logs, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig)
if err != nil {
bc.reportBlock(block, receipts, err)
atomic.StoreUint32(&followupInterrupt, 1)
diff --git a/core/chain_makers.go b/core/chain_makers.go
index c7bf60a4b06e..495e32074666 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -24,10 +24,12 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/trie"
)
// BlockGen creates blocks for testing.
@@ -284,6 +286,106 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
return blocks, receipts
}
+func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
+ if config == nil {
+ config = params.TestChainConfig
+ }
+ blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
+ chainreader := &fakeChainReader{config: config}
+ genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
+ b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine}
+ b.header = makeHeader(chainreader, parent, statedb, b.engine)
+ preState := statedb.Copy()
+
+ // Mutate the state and block according to any hard-fork specs
+ if daoBlock := config.DAOForkBlock; daoBlock != nil {
+ limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
+ if b.header.Number.Cmp(daoBlock) >= 0 && b.header.Number.Cmp(limit) < 0 {
+ if config.DAOForkSupport {
+ b.header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
+ }
+ }
+ }
+ if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
+ misc.ApplyDAOHardFork(statedb)
+ }
+ // Execute any user modifications to the block
+ if gen != nil {
+ gen(i, b)
+ }
+ if b.engine != nil {
+ // Finalize and seal the block
+ block, err := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)
+ if err != nil {
+ panic(err)
+ }
+
+ // Write state changes to db
+ root, err := statedb.Commit(config.IsEIP158(b.header.Number))
+ if err != nil {
+ panic(fmt.Sprintf("state write error: %v", err))
+ }
+ if err := statedb.Database().TrieDB().Commit(root, false, nil); err != nil {
+ panic(fmt.Sprintf("trie write error: %v", err))
+ }
+
+ // Generate an associated verkle proof
+ tr := preState.GetTrie()
+ if !tr.IsVerkle() {
+ panic("tree should be verkle")
+ }
+
+ vtr := tr.(*trie.VerkleTrie)
+ // Make sure all keys are resolved before
+ // building the proof. Ultimately, node
+ // resolution can be done with a prefetcher
+ // or from GetCommitmentsAlongPath.
+ kvs := statedb.Witness().KeyVals()
+ keys := statedb.Witness().Keys()
+ for _, key := range keys {
+ _, err := vtr.TryGet(key)
+ if err != nil {
+ panic(err)
+ }
+
+ // Sanity check: ensure all flagged addresses have an associated
+ // value: keys is built from Chunks and kvs from InitialValue.
+ if _, exists := kvs[string(key)]; !exists {
+ panic(fmt.Sprintf("address not in access witness: %x", key))
+ }
+ }
+
+ // sanity check: ensure all values correspond to a flagged key by
+ // comparing the lengths of both structures: they should be equal
+ if len(kvs) != len(keys) {
+ panic("keys without a value in witness")
+ }
+
+ vtr.Hash()
+ p, k, err := vtr.ProveAndSerialize(keys, kvs)
+ block.SetVerkleProof(p, k)
+ if err != nil {
+ panic(err)
+ }
+ return block, b.receipts
+ }
+ return nil, nil
+ }
+ var snaps *snapshot.Tree
+ for i := 0; i < n; i++ {
+ statedb, err := state.New(parent.Root(), state.NewDatabaseWithConfig(db, &trie.Config{UseVerkle: true}), snaps)
+ if err != nil {
+ panic(err)
+ }
+ block, receipt := genblock(i, parent, statedb)
+ blocks[i] = block
+ receipts[i] = receipt
+ parent = block
+ snaps = statedb.Snaps()
+ }
+ return blocks, receipts
+}
+
func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
var time uint64
if parent.Time() == 0 {
diff --git a/core/error.go b/core/error.go
index 51ebefc137bc..238d881d14e7 100644
--- a/core/error.go
+++ b/core/error.go
@@ -63,6 +63,11 @@ var (
// have enough funds for transfer(topmost call only).
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
+ // ErrInsufficientBalanceWitness is returned if the transaction sender has enough
+ // funds to cover the transfer, but not enough to pay for witness access/modification
+ // costs for the transaction
+ ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction")
+
// ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
diff --git a/core/genesis.go b/core/genesis.go
index b773b903cccf..eb487bf6d1bd 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -162,6 +162,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
if genesis != nil && genesis.Config == nil {
return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
}
+
// Just commit the new block if there is no stored genesis block.
stored := rawdb.ReadCanonicalHash(db, 0)
if (stored == common.Hash{}) {
@@ -177,13 +178,29 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
}
return genesis.Config, block.Hash(), nil
}
+
// We have the genesis block in database(perhaps in ancient database)
// but the corresponding state is missing.
header := rawdb.ReadHeader(db, stored, 0)
- if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, nil), nil); err != nil {
- if genesis == nil {
- genesis = DefaultGenesisBlock()
+
+ var trieCfg *trie.Config
+
+ if genesis == nil {
+ storedcfg := rawdb.ReadChainConfig(db, stored)
+ if storedcfg == nil {
+ panic("this should never be reached: if genesis is nil, the config is already present or 'geth init' is being called which created it (in the code above, which means genesis != nil)")
}
+
+ if storedcfg.CancunBlock != nil {
+ if storedcfg.CancunBlock.Cmp(big.NewInt(0)) != 0 {
+ panic("cancun block must be 0")
+ }
+
+ trieCfg = &trie.Config{UseVerkle: storedcfg.IsCancun(big.NewInt(header.Number.Int64()))}
+ }
+ }
+
+ if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, trieCfg), nil); err != nil {
// Ensure the stored genesis matches with the given one.
hash := genesis.ToBlock(nil).Hash()
if hash != stored {
@@ -267,7 +284,11 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
if db == nil {
db = rawdb.NewMemoryDatabase()
}
- statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
+ var trieCfg *trie.Config
+ if g.Config != nil {
+ trieCfg = &trie.Config{UseVerkle: g.Config.IsCancun(big.NewInt(int64(g.Number)))}
+ }
+ statedb, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(db, trieCfg), nil)
if err != nil {
panic(err)
}
@@ -309,6 +330,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
}
statedb.Commit(false)
statedb.Database().TrieDB().Commit(root, true, nil)
+ if err := statedb.Cap(root); err != nil {
+ panic(err)
+ }
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
}
@@ -360,6 +384,20 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big
return g.MustCommit(db)
}
+func DefaultVerkleGenesisBlock() *Genesis {
+ return &Genesis{
+ Config: params.VerkleChainConfig,
+ Nonce: 86,
+ GasLimit: 0x2fefd8,
+ Difficulty: big.NewInt(1),
+ Alloc: map[common.Address]GenesisAccount{
+ common.BytesToAddress([]byte{97, 118, 97, 209, 72, 165, 43, 239, 81, 162, 104, 199, 40, 179, 162, 27, 88, 249, 67, 6}): {
+ Balance: big.NewInt(0).Lsh(big.NewInt(1), 27),
+ },
+ },
+ }
+}
+
// DefaultGenesisBlock returns the Ethereum main net genesis block.
func DefaultGenesisBlock() *Genesis {
return &Genesis{
diff --git a/core/state/database.go b/core/state/database.go
index bbcd2358e5b8..f29e85474745 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
+ "github.com/gballet/go-verkle"
lru "github.com/hashicorp/golang-lru"
)
@@ -104,6 +105,9 @@ type Trie interface {
// nodes of the longest existing prefix of the key (at least the root), ending
// with the node that proves the absence of the key.
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error
+
+ // IsVerkle returns true if the trie is verkle-tree based
+ IsVerkle() bool
}
// NewDatabase creates a backing store for state. The returned database is safe for
@@ -118,6 +122,13 @@ func NewDatabase(db ethdb.Database) Database {
// large memory cache.
func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database {
csc, _ := lru.New(codeSizeCacheSize)
+ if config != nil && config.UseVerkle {
+ return &VerkleDB{
+ db: trie.NewDatabaseWithConfig(db, config),
+ codeSizeCache: csc,
+ codeCache: fastcache.New(codeCacheSize),
+ }
+ }
return &cachingDB{
db: trie.NewDatabaseWithConfig(db, config),
codeSizeCache: csc,
@@ -202,3 +213,76 @@ func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, erro
func (db *cachingDB) TrieDB() *trie.Database {
return db.db
}
+
+// VerkleDB implements state.Database for a verkle tree
+type VerkleDB struct {
+ db *trie.Database
+ codeSizeCache *lru.Cache
+ codeCache *fastcache.Cache
+}
+
+// OpenTrie opens the main account trie.
+func (db *VerkleDB) OpenTrie(root common.Hash) (Trie, error) {
+ if root == (common.Hash{}) || root == emptyRoot {
+ return trie.NewVerkleTrie(verkle.New(), db.db), nil
+ }
+ payload, err := db.db.DiskDB().Get(root[:])
+ if err != nil {
+ return nil, err
+ }
+
+ r, err := verkle.ParseNode(payload, 0)
+ if err != nil {
+ panic(err)
+ }
+ return trie.NewVerkleTrie(r, db.db), err
+}
+
+// OpenStorageTrie opens the storage trie of an account.
+func (db *VerkleDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
+ // alternatively, return accTrie
+ panic("should not be called")
+}
+
+// CopyTrie returns an independent copy of the given trie.
+func (db *VerkleDB) CopyTrie(tr Trie) Trie {
+ t, ok := tr.(*trie.VerkleTrie)
+ if ok {
+ return t.Copy(db.db)
+ }
+
+ panic("invalid tree type != VerkleTrie")
+}
+
+// ContractCode retrieves a particular contract's code.
+func (db *VerkleDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
+ if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
+ return code, nil
+ }
+ code := rawdb.ReadCode(db.db.DiskDB(), codeHash)
+ if len(code) > 0 {
+ db.codeCache.Set(codeHash.Bytes(), code)
+ db.codeSizeCache.Add(codeHash, len(code))
+ return code, nil
+ }
+ return nil, errors.New("not found")
+}
+
+// ContractCodeSize retrieves a particular contracts code's size.
+func (db *VerkleDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
+ if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
+ return len(code), nil
+ }
+ code := rawdb.ReadCode(db.db.DiskDB(), codeHash)
+ if len(code) > 0 {
+ db.codeCache.Set(codeHash.Bytes(), code)
+ db.codeSizeCache.Add(codeHash, len(code))
+ return len(code), nil
+ }
+ return 0, nil
+}
+
+// TrieDB retrieves the low level trie database used for data storage.
+func (db *VerkleDB) TrieDB() *trie.Database {
+ return db.db
+}
diff --git a/core/state/iterator.go b/core/state/iterator.go
index 611df52431eb..aa8e455a23e7 100644
--- a/core/state/iterator.go
+++ b/core/state/iterator.go
@@ -76,6 +76,14 @@ func (it *NodeIterator) step() error {
// Initialize the iterator if we've just started
if it.stateIt == nil {
it.stateIt = it.state.trie.NodeIterator(nil)
+
+ // If the trie is a verkle trie, then the data and state
+ // are the same tree, and as a result both iterators are
+ // the same. This is a hack meant for both tree types to
+ // work.
+ if _, ok := it.state.trie.(*trie.VerkleTrie); ok {
+ it.dataIt = it.stateIt
+ }
}
// If we had data nodes previously, we surely have at least state nodes
if it.dataIt != nil {
@@ -100,10 +108,11 @@ func (it *NodeIterator) step() error {
it.state, it.stateIt = nil, nil
return nil
}
- // If the state trie node is an internal entry, leave as is
+ // If the state trie node is an internal entry, leave as is.
if !it.stateIt.Leaf() {
return nil
}
+
// Otherwise we've reached an account node, initiate data iteration
var account types.StateAccount
if err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob()), &account); err != nil {
diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go
index 4e3daac669b4..038d65be1670 100644
--- a/core/state/pruner/pruner.go
+++ b/core/state/pruner/pruner.go
@@ -89,7 +89,7 @@ func NewPruner(db ethdb.Database, datadir, trieCachePath string, bloomSize uint6
if headBlock == nil {
return nil, errors.New("Failed to load head block")
}
- snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, false)
+ snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, false, false)
if err != nil {
return nil, err // The relevant snapshot(s) might not exist
}
@@ -362,7 +362,7 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err
// - The state HEAD is rewound already because of multiple incomplete `prune-state`
// In this case, even the state HEAD is not exactly matched with snapshot, it
// still feasible to recover the pruning correctly.
- snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, true)
+ snaptree, err := snapshot.New(db, trie.NewDatabase(db), 256, headBlock.Root(), false, false, true, false)
if err != nil {
return err // The relevant snapshot(s) might not exist
}
diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go
index 76200851e469..5ad832694f8f 100644
--- a/core/state/snapshot/snapshot.go
+++ b/core/state/snapshot/snapshot.go
@@ -24,6 +24,7 @@ import (
"sync"
"sync/atomic"
+ "github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
@@ -183,7 +184,7 @@ type Tree struct {
// This case happens when the snapshot is 'ahead' of the state trie.
// - otherwise, the entire snapshot is considered invalid and will be recreated on
// a background thread.
-func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, rebuild bool, recovery bool) (*Tree, error) {
+func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool, rebuild bool, recovery bool, useVerkle bool) (*Tree, error) {
// Create a new, empty snapshot tree
snap := &Tree{
diskdb: diskdb,
@@ -202,6 +203,17 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm
}
if err != nil {
if rebuild {
+ if useVerkle {
+ snap.layers = map[common.Hash]snapshot{
+ root: &diskLayer{
+ diskdb: diskdb,
+ triedb: triedb,
+ root: root,
+ cache: fastcache.New(cache * 1024 * 1024),
+ },
+ }
+ return snap, nil
+ }
log.Warn("Failed to load snapshot, regenerating", "err", err)
snap.Rebuild(root)
return snap, nil
diff --git a/core/state/state_object.go b/core/state/state_object.go
index bcb6dca4f56b..79aa3ec8d8d4 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -18,6 +18,7 @@ package state
import (
"bytes"
+ "encoding/binary"
"fmt"
"io"
"math/big"
@@ -28,6 +29,8 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
+ trieUtils "github.com/ethereum/go-ethereum/trie/utils"
+ "github.com/holiman/uint256"
)
var emptyCodeHash = crypto.Keccak256(nil)
@@ -100,6 +103,24 @@ func (s *stateObject) empty() bool {
// newObject creates a state object.
func newObject(db *StateDB, address common.Address, data types.StateAccount) *stateObject {
+ if db.trie.IsVerkle() {
+ var nonce, balance, version []byte
+
+ // preserve nil as a balance value, it means it's not in the tree
+ // use is as a heuristic for the nonce being null as well
+ if data.Balance != nil {
+ nonce = make([]byte, 32)
+ balance = make([]byte, 32)
+ version = make([]byte, 32)
+ for i, b := range data.Balance.Bytes() {
+ balance[len(data.Balance.Bytes())-1-i] = b
+ }
+
+ binary.LittleEndian.PutUint64(nonce[:8], data.Nonce)
+ }
+ db.witness.SetGetObjectTouchedLeaves(address.Bytes(), version, balance[:], nonce[:], data.CodeHash)
+ }
+
if data.Balance == nil {
data.Balance = new(big.Int)
}
@@ -201,6 +222,17 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
enc []byte
err error
)
+ readStart := time.Now()
+ if metrics.EnabledExpensive {
+ // If the snap is 'under construction', the first lookup may fail. If that
+ // happens, we don't want to double-count the time elapsed. Thus this
+ // dance with the metering.
+ defer func() {
+ if meter != nil {
+ *meter += time.Since(readStart)
+ }
+ }()
+ }
if s.db.snap != nil {
// If the object was destructed in *this* block (and potentially resurrected),
// the storage has been cleared out, and we should *not* consult the previous
@@ -219,6 +251,9 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
}
// If the snapshot is unavailable or reading from it fails, load from the database.
if s.db.snap == nil || err != nil {
+ if s.db.GetTrie().IsVerkle() {
+ panic("verkle trees use the snapshot")
+ }
start := time.Now()
enc, err = s.getTrie(db).TryGet(key.Bytes())
if metrics.EnabledExpensive {
@@ -237,6 +272,22 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
}
value.SetBytes(content)
}
+
+ // Capture the initial value of the location in the verkle proof witness
+ if s.db.GetTrie().IsVerkle() {
+ if err != nil {
+ return common.Hash{}
+ }
+ addr := s.Address()
+ loc := new(uint256.Int).SetBytes(key[:])
+ index := trieUtils.GetTreeKeyStorageSlot(addr[:], loc)
+ if len(enc) > 0 {
+ s.db.Witness().SetLeafValue(index, value.Bytes())
+ } else {
+ s.db.Witness().SetLeafValue(index, nil)
+ }
+ }
+
s.originStorage[key] = value
return value
}
@@ -317,7 +368,12 @@ func (s *stateObject) updateTrie(db Database) Trie {
// The snapshot storage map for the object
var storage map[common.Hash][]byte
// Insert all the pending updates into the trie
- tr := s.getTrie(db)
+ var tr Trie
+ if s.db.trie.IsVerkle() {
+ tr = s.db.trie
+ } else {
+ tr = s.getTrie(db)
+ }
hasher := s.db.hasher
usedStorage := make([][]byte, 0, len(s.pendingStorage))
@@ -330,12 +386,24 @@ func (s *stateObject) updateTrie(db Database) Trie {
var v []byte
if (value == common.Hash{}) {
- s.setError(tr.TryDelete(key[:]))
+ if tr.IsVerkle() {
+ k := trieUtils.GetTreeKeyStorageSlot(s.address[:], new(uint256.Int).SetBytes(key[:]))
+ s.setError(tr.TryDelete(k))
+ //s.db.db.TrieDB().DiskDB().Delete(append(s.address[:], key[:]...))
+ } else {
+ s.setError(tr.TryDelete(key[:]))
+ }
s.db.StorageDeleted += 1
} else {
// Encoding []byte cannot fail, ok to ignore the error.
v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:]))
- s.setError(tr.TryUpdate(key[:], v))
+ if !tr.IsVerkle() {
+ s.setError(tr.TryUpdate(key[:], v))
+ } else {
+ k := trieUtils.GetTreeKeyStorageSlot(s.address[:], new(uint256.Int).SetBytes(key[:]))
+ // Update the trie, with v as a value
+ s.setError(tr.TryUpdate(k, value[:]))
+ }
s.db.StorageUpdated += 1
}
// If state snapshotting is active, cache the data til commit
@@ -459,12 +527,21 @@ func (s *stateObject) Code(db Database) []byte {
return s.code
}
if bytes.Equal(s.CodeHash(), emptyCodeHash) {
+ if s.db.GetTrie().IsVerkle() {
+ // Mark the code size and code hash as empty
+ s.db.witness.SetObjectCodeTouchedLeaves(s.address.Bytes(), nil, nil)
+ }
return nil
}
code, err := db.ContractCode(s.addrHash, common.BytesToHash(s.CodeHash()))
if err != nil {
s.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
}
+ if s.db.GetTrie().IsVerkle() {
+ var cs [32]byte
+ binary.LittleEndian.PutUint64(cs[:8], uint64(len(code)))
+ s.db.witness.SetObjectCodeTouchedLeaves(s.address.Bytes(), cs[:], s.CodeHash())
+ }
s.code = code
return code
}
@@ -477,12 +554,21 @@ func (s *stateObject) CodeSize(db Database) int {
return len(s.code)
}
if bytes.Equal(s.CodeHash(), emptyCodeHash) {
+ if s.db.trie.IsVerkle() {
+ var sz [32]byte
+ s.db.witness.SetLeafValuesMessageCall(s.address.Bytes(), sz[:])
+ }
return 0
}
size, err := db.ContractCodeSize(s.addrHash, common.BytesToHash(s.CodeHash()))
if err != nil {
s.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
}
+ if s.db.trie.IsVerkle() {
+ var sz [32]byte
+ binary.LittleEndian.PutUint64(sz[:8], uint64(size))
+ s.db.witness.SetLeafValuesMessageCall(s.address.Bytes(), sz[:])
+ }
return size
}
@@ -522,10 +608,25 @@ func (s *stateObject) Balance() *big.Int {
return s.data.Balance
}
+func (s *stateObject) BalanceLE() []byte {
+ var out [32]byte
+ for i, b := range s.data.Balance.Bytes() {
+ out[len(s.data.Balance.Bytes())-1-i] = b
+ }
+
+ return out[:]
+}
+
func (s *stateObject) Nonce() uint64 {
return s.data.Nonce
}
+func (s *stateObject) NonceLE() []byte {
+ var out [32]byte
+ binary.LittleEndian.PutUint64(out[:8], s.data.Nonce)
+ return out[:]
+}
+
// Never called, but must be present to allow stateObject to be used
// as a vm.Account interface that also satisfies the vm.ContractRef
// interface. Interfaces are awesome.
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 1d31cf470be0..7056a2ca0dbc 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -18,6 +18,7 @@
package state
import (
+ "encoding/binary"
"errors"
"fmt"
"math/big"
@@ -33,6 +34,8 @@ import (
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
+ trieUtils "github.com/ethereum/go-ethereum/trie/utils"
+ "github.com/holiman/uint256"
)
type revision struct {
@@ -99,6 +102,8 @@ type StateDB struct {
// Per-transaction access list
accessList *accessList
+ witness *types.AccessWitness
+
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal *journal
@@ -144,6 +149,15 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
accessList: newAccessList(),
hasher: crypto.NewKeccakState(),
}
+ if tr.IsVerkle() {
+ sdb.witness = types.NewAccessWitness()
+ if sdb.snaps == nil {
+ sdb.snaps, err = snapshot.New(db.TrieDB().DiskDB(), db.TrieDB(), 1, root, false, true, false, true)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
if sdb.snaps != nil {
if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
sdb.snapDestructs = make(map[common.Hash]struct{})
@@ -154,6 +168,18 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
return sdb, nil
}
+func (s *StateDB) Snaps() *snapshot.Tree {
+ return s.snaps
+}
+
+func (s *StateDB) Witness() *types.AccessWitness {
+ return s.witness
+}
+
+func (s *StateDB) SetWitness(aw *types.AccessWitness) {
+ s.witness = aw
+}
+
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot.
@@ -162,7 +188,7 @@ func (s *StateDB) StartPrefetcher(namespace string) {
s.prefetcher.close()
s.prefetcher = nil
}
- if s.snap != nil {
+ if s.snap != nil && !s.trie.IsVerkle() {
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
}
}
@@ -266,6 +292,24 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int {
return common.Big0
}
+func (s *StateDB) GetNonceLittleEndian(address common.Address) []byte {
+ var nonceBytes [8]byte
+ binary.LittleEndian.PutUint64(nonceBytes[:], s.GetNonce(address))
+ return nonceBytes[:]
+}
+
+func (s *StateDB) GetBalanceLittleEndian(address common.Address) []byte {
+ var paddedBalance [32]byte
+ balanceBytes := s.GetBalance(address).Bytes()
+ // swap to little-endian
+ for i, j := 0, len(balanceBytes)-1; i < j; i, j = i+1, j-1 {
+ balanceBytes[i], balanceBytes[j] = balanceBytes[j], balanceBytes[i]
+ }
+
+ copy(paddedBalance[:len(balanceBytes)], balanceBytes)
+ return paddedBalance[:len(balanceBytes)]
+}
+
func (s *StateDB) GetNonce(addr common.Address) uint64 {
stateObject := s.getStateObject(addr)
if stateObject != nil {
@@ -460,8 +504,33 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
}
// Encode the account and update the account trie
addr := obj.Address()
+
if err := s.trie.TryUpdateAccount(addr[:], &obj.data); err != nil {
- s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
+ s.setError(fmt.Errorf("updateStateObject (%x) error: %w", addr[:], err))
+ }
+ if s.trie.IsVerkle() {
+ if len(obj.code) > 0 {
+ cs := make([]byte, 32)
+ binary.BigEndian.PutUint64(cs, uint64(len(obj.code)))
+ if err := s.trie.TryUpdate(trieUtils.GetTreeKeyCodeSize(addr[:]), cs); err != nil {
+ s.setError(fmt.Errorf("updateStateObject (%x) error: %w", addr[:], err))
+ }
+
+ if obj.dirtyCode {
+ if chunks, err := trie.ChunkifyCode(obj.code); err == nil {
+ for i := range chunks {
+ s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(uint64(i))), chunks[i][:])
+ }
+ } else {
+ s.setError(err)
+ }
+ }
+ } else {
+ cs := []byte{0}
+ if err := s.trie.TryUpdate(trieUtils.GetTreeKeyCodeSize(addr[:]), cs); err != nil {
+ s.setError(fmt.Errorf("updateStateObject (%x) error: %w", addr[:], err))
+ }
+ }
}
// If state snapshotting is active, cache the data til commit. Note, this
@@ -479,10 +548,15 @@ func (s *StateDB) deleteStateObject(obj *stateObject) {
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
}
+
// Delete the account from the trie
- addr := obj.Address()
- if err := s.trie.TryDelete(addr[:]); err != nil {
- s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
+ // Post-verkle, the only case where this can occur is a static call
+ // to a non-existent account which creates an empty stateObject
+ if !s.trie.IsVerkle() {
+ addr := obj.Address()
+ if err := s.trie.TryDelete(addr[:]); err != nil {
+ s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
+ }
}
}
@@ -530,6 +604,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
data.Root = emptyRoot
}
}
+
}
// If snapshot unavailable or reading from it failed, load from the database
if data == nil {
@@ -658,6 +733,9 @@ func (s *StateDB) Copy() *StateDB {
journal: newJournal(),
hasher: crypto.NewKeccakState(),
}
+ if s.witness != nil {
+ state.witness = s.witness.Copy()
+ }
// Copy the dirty states, logs, and preimages
for addr := range s.journal.dirties {
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527),
@@ -844,7 +922,11 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// to pull useful data from disk.
for addr := range s.stateObjectsPending {
if obj := s.stateObjects[addr]; !obj.deleted {
- obj.updateRoot(s.db)
+ if s.trie.IsVerkle() {
+ obj.updateTrie(s.db)
+ } else {
+ obj.updateRoot(s.db)
+ }
}
}
// Now we're about to start to write changes to the trie. The trie is so far
@@ -894,6 +976,20 @@ func (s *StateDB) clearJournalAndRefund() {
s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entires
}
+// GetTrie returns the account trie.
+func (s *StateDB) GetTrie() Trie {
+ return s.trie
+}
+
+func (s *StateDB) Cap(root common.Hash) error {
+ if s.snaps != nil {
+ return s.snaps.Cap(root, 0)
+ }
+ // pre-verkle path: noop if s.snaps hasn't been
+ // initialized.
+ return nil
+}
+
// Commit writes the state to the underlying in-memory trie database.
func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
if s.dbErr != nil {
@@ -907,17 +1003,26 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
codeWriter := s.db.TrieDB().DiskDB().NewBatch()
for addr := range s.stateObjectsDirty {
if obj := s.stateObjects[addr]; !obj.deleted {
- // Write any contract code associated with the state object
- if obj.code != nil && obj.dirtyCode {
- rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
- obj.dirtyCode = false
- }
// Write any storage changes in the state object to its storage trie
committed, err := obj.CommitTrie(s.db)
if err != nil {
return common.Hash{}, err
}
storageCommitted += committed
+ // Write any contract code associated with the state object
+ if obj.code != nil && obj.dirtyCode {
+ if s.trie.IsVerkle() {
+ if chunks, err := trie.ChunkifyCode(obj.code); err == nil {
+ for i := range chunks {
+ s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(uint64(i))), chunks[i][:])
+ }
+ } else {
+ s.setError(err)
+ }
+ }
+ rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
+ obj.dirtyCode = false
+ }
}
}
if len(s.stateObjectsDirty) > 0 {
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index e9576d4dc44d..98d76a6de778 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -704,7 +704,10 @@ func TestMissingTrieNodes(t *testing.T) {
memDb := rawdb.NewMemoryDatabase()
db := NewDatabase(memDb)
var root common.Hash
- state, _ := New(common.Hash{}, db, nil)
+ state, err := New(common.Hash{}, db, nil)
+ if err != nil {
+ panic("nil stte")
+ }
addr := common.BytesToAddress([]byte("so"))
{
state.SetBalance(addr, big.NewInt(1))
@@ -736,7 +739,7 @@ func TestMissingTrieNodes(t *testing.T) {
}
// Modify the state
state.SetBalance(addr, big.NewInt(2))
- root, err := state.Commit(false)
+ root, err = state.Commit(false)
if err == nil {
t.Fatalf("expected error, got root :%x", root)
}
diff --git a/core/state/sync_test.go b/core/state/sync_test.go
index 007590c76d9c..3dbb7d7b2d30 100644
--- a/core/state/sync_test.go
+++ b/core/state/sync_test.go
@@ -69,7 +69,10 @@ func makeTestState() (Database, common.Hash, []*testAccount) {
state.updateStateObject(obj)
accounts = append(accounts, acc)
}
- root, _ := state.Commit(false)
+ root, err := state.Commit(false)
+ if err != nil {
+ panic(err)
+ }
// Return the generated state
return db, root, accounts
diff --git a/core/state_processor.go b/core/state_processor.go
index d4c77ae41042..bdb21ac68664 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -95,6 +95,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
+ if config.IsCancun(blockNumber) {
+ txContext.Accesses = types.NewAccessWitness()
+ }
evm.Reset(txContext, statedb)
// Apply the transaction to the current state (included in the env).
@@ -128,6 +131,10 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
}
+ if config.IsCancun(blockNumber) {
+ statedb.Witness().Merge(txContext.Accesses)
+ }
+
// Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash)
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
diff --git a/core/state_processor_test.go b/core/state_processor_test.go
index aa8e4bebf9d4..27b30395a7c4 100644
--- a/core/state_processor_test.go
+++ b/core/state_processor_test.go
@@ -17,8 +17,11 @@
package core
import (
+ //"bytes"
"crypto/ecdsa"
+ //"fmt"
"math/big"
+ //"os"
"testing"
"github.com/ethereum/go-ethereum/common"
@@ -31,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
+ //"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"golang.org/x/crypto/sha3"
)
@@ -340,3 +344,100 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
// Assemble and return the final block for sealing
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
}
+
+// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
+// will not contain that copied data.
+// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
+var (
+ codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
+ codeWithExtCodeCopyGas = uint64(192372)
+)
+
+func TestProcessStateless(t *testing.T) {
+ var (
+ config = ¶ms.ChainConfig{
+ ChainID: big.NewInt(1),
+ HomesteadBlock: big.NewInt(0),
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ Ethash: new(params.EthashConfig),
+ CancunBlock: big.NewInt(0),
+ }
+ signer = types.LatestSigner(config)
+ testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ db = rawdb.NewMemoryDatabase()
+ gspec = &Genesis{
+ Config: config,
+ Alloc: GenesisAlloc{
+ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{
+ Balance: big.NewInt(1000000000000000000), // 1 ether
+ Nonce: 0,
+ },
+ },
+ }
+ )
+ // Verkle trees use the snapshot, which must be enabled before the
+ // data is saved into the tree+database.
+ genesis := gspec.MustCommit(db)
+ blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
+ defer blockchain.Stop()
+
+ code := common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
+ txCost1 := params.WitnessBranchWriteCost*2 + params.WitnessBranchReadCost*2 + params.WitnessChunkWriteCost*3 + params.WitnessChunkReadCost*10 + params.TxGas
+ txCost2 := params.WitnessBranchWriteCost + params.WitnessBranchReadCost*2 + params.WitnessChunkWriteCost*2 + params.WitnessChunkReadCost*10 + params.TxGas
+ intrinsic, _ := IntrinsicGas(code, nil, true, true, true)
+ contractCreationCost := intrinsic + (2*params.WitnessChunkReadCost+params.WitnessChunkWriteCost)*10 + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + uint64(3639) // standard cost of executing the contract
+ blockGasUsagesExpected := []uint64{
+ txCost1*2 + txCost2,
+ txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
+ }
+ chain, _ := GenerateVerkleChain(gspec.Config, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
+ // TODO need to check that the tx cost provided is the exact amount used (no remaining left-over)
+ tx, _ := types.SignTx(types.NewTransaction(uint64(i)*3, common.Address{byte(i), 2, 3}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey)
+ gen.AddTx(tx)
+ tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+1, common.Address{}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey)
+ gen.AddTx(tx)
+ tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+2, common.Address{}, big.NewInt(0), txCost2, big.NewInt(875000000), nil), signer, testKey)
+ gen.AddTx(tx)
+
+ // Add two contract creations in block #2
+ if i == 1 {
+ tx, _ = types.SignTx(types.NewContractCreation(6, big.NewInt(16), 3000000, big.NewInt(875000000), code), signer, testKey)
+ gen.AddTx(tx)
+
+ tx, _ = types.SignTx(types.NewContractCreation(7, big.NewInt(32), 3000000, big.NewInt(875000000), codeWithExtCodeCopy), signer, testKey)
+ gen.AddTx(tx)
+ }
+ })
+
+ // Uncomment to extract block #2
+ //f, _ := os.Create("block2.rlp")
+ //defer f.Close()
+ //var buf bytes.Buffer
+ //rlp.Encode(&buf, chain[1])
+ //f.Write(buf.Bytes())
+ //fmt.Printf("root= %x\n", chain[0].Root())
+
+ _, err := blockchain.InsertChain(chain)
+ if err != nil {
+ t.Fatalf("block imported with error: %v", err)
+ }
+
+ for i := 0; i < 2; i++ {
+ b := blockchain.GetBlockByNumber(uint64(i) + 1)
+ if b == nil {
+ t.Fatalf("expected block %d to be present in chain", i+1)
+ }
+ if b.GasUsed() != blockGasUsagesExpected[i] {
+ t.Fatalf("expected block txs to use %d, got %d\n", blockGasUsagesExpected[i], b.GasUsed())
+ }
+ }
+}
diff --git a/core/state_transition.go b/core/state_transition.go
index 05d5633075b9..684b2330c41f 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -115,7 +115,7 @@ func (result *ExecutionResult) Revert() []byte {
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
-func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) {
+func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
@@ -259,6 +259,19 @@ func (st *StateTransition) preCheck() error {
return st.buyGas()
}
+// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool
+// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false
+// otherwise, do the subtraction setting the result in gasPool and return true
+func tryConsumeGas(gasPool *uint64, gas uint64) bool {
+ if *gasPool < gas {
+ *gasPool = 0
+ return false
+ }
+
+ *gasPool -= gas
+ return true
+}
+
// TransitionDb will transition the state by applying the current message and
// returning the evm execution result with following fields.
//
@@ -302,6 +315,40 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st.gas < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas)
}
+ if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber) {
+ var originBalance, originNonceBytes []byte
+
+ targetAddr := msg.To()
+ originAddr := msg.From()
+
+ statelessGasOrigin := st.evm.Accesses.TouchTxOriginAndComputeGas(originAddr.Bytes(), msg.Value().Sign() != 0)
+ if !tryConsumeGas(&st.gas, statelessGasOrigin) {
+ return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gas, gas)
+ }
+ originBalance = st.evm.StateDB.GetBalanceLittleEndian(originAddr)
+ originNonce := st.evm.StateDB.GetNonce(originAddr)
+ originNonceBytes = st.evm.StateDB.GetNonceLittleEndian(originAddr)
+ st.evm.Accesses.SetTxOriginTouchedLeaves(originAddr.Bytes(), originBalance, originNonceBytes, st.evm.StateDB.GetCodeSize(originAddr))
+
+ if msg.To() != nil {
+ statelessGasDest := st.evm.Accesses.TouchTxExistingAndComputeGas(targetAddr.Bytes(), msg.Value().Sign() != 0)
+ if !tryConsumeGas(&st.gas, statelessGasDest) {
+ return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gas, gas)
+ }
+
+ // ensure the code size ends up in the access witness
+ st.evm.StateDB.GetCodeSize(*targetAddr)
+ } else {
+ contractAddr := crypto.CreateAddress(originAddr, originNonce)
+ if !tryConsumeGas(&st.gas, st.evm.Accesses.TouchAndChargeContractCreateInit(contractAddr.Bytes(), msg.Value().Sign() != 0)) {
+ return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gas, gas)
+ }
+ }
+
+ if st.gas < gas {
+ return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gas, gas)
+ }
+ }
st.gas -= gas
// Check clause 6
diff --git a/core/types/access_witness.go b/core/types/access_witness.go
new file mode 100644
index 000000000000..81802bc429a8
--- /dev/null
+++ b/core/types/access_witness.go
@@ -0,0 +1,352 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package types
+
+import (
+ "encoding/binary"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/trie/utils"
+)
+
+type VerkleStem [31]byte
+
+// Mode specifies how a tree location has been accessed
+// for the byte value:
+// the first bit is set if the branch has been edited
+// the second bit is set if the branch has been read
+type Mode byte
+
+const (
+ AccessWitnessReadFlag = Mode(1)
+ AccessWitnessWriteFlag = Mode(2)
+)
+
+// AccessWitness lists the locations of the state that are being accessed
+// during the production of a block.
+// TODO(@gballet) this doesn't fully support deletions
+type AccessWitness struct {
+ // Branches flags if a given branch has been loaded
+ Branches map[VerkleStem]Mode
+
+ // Chunks contains the initial value of each address
+ Chunks map[common.Hash]Mode
+
+ // InitialValue contains either `nil` if the location
+ // didn't exist before it was accessed, or the value
+ // that a location had before the execution of this
+ // block.
+ InitialValue map[string][]byte
+}
+
+func NewAccessWitness() *AccessWitness {
+ return &AccessWitness{
+ Branches: make(map[VerkleStem]Mode),
+ Chunks: make(map[common.Hash]Mode),
+ InitialValue: make(map[string][]byte),
+ }
+}
+
+func (aw *AccessWitness) SetLeafValue(addr []byte, value []byte) {
+ var stem [31]byte
+ copy(stem[:], addr[:31])
+
+ // Sanity check: ensure that the location has been declared
+ if _, exist := aw.InitialValue[string(addr)]; !exist {
+ if len(value) == 32 || len(value) == 0 {
+ aw.InitialValue[string(addr)] = value
+ } else {
+ var aligned [32]byte
+ copy(aligned[:len(value)], value)
+
+ aw.InitialValue[string(addr)] = aligned[:]
+ }
+ }
+}
+
+func (aw *AccessWitness) touchAddressOnWrite(addr []byte) (bool, bool, bool) {
+ var stem VerkleStem
+ var stemWrite, chunkWrite, chunkFill bool
+ copy(stem[:], addr[:31])
+
+ // NOTE: stem, selector access flags already exist in their
+ // respective maps because this function is called at the end of
+ // processing a read access event
+
+ if (aw.Branches[stem] & AccessWitnessWriteFlag) == 0 {
+ stemWrite = true
+ aw.Branches[stem] |= AccessWitnessWriteFlag
+ }
+
+ chunkValue := aw.Chunks[common.BytesToHash(addr)]
+ // if chunkValue.mode XOR AccessWitnessWriteFlag
+ if ((chunkValue & AccessWitnessWriteFlag) == 0) && ((chunkValue | AccessWitnessWriteFlag) != 0) {
+ chunkWrite = true
+ chunkValue |= AccessWitnessWriteFlag
+ aw.Chunks[common.BytesToHash(addr)] = chunkValue
+ }
+
+ // TODO charge chunk filling costs if the leaf was previously empty in the state
+ /*
+ if chunkWrite {
+ if _, err := verkleDb.TryGet(addr); err != nil {
+ chunkFill = true
+ }
+ }
+ */
+
+ return stemWrite, chunkWrite, chunkFill
+}
+
+// TouchAddress adds any missing addr to the witness and returns respectively
+// true if the stem or the stub weren't arleady present.
+func (aw *AccessWitness) touchAddress(addr []byte, isWrite bool) (bool, bool, bool, bool, bool) {
+ var (
+ stem [31]byte
+ stemRead, selectorRead bool
+ stemWrite, selectorWrite, chunkFill bool
+ )
+ copy(stem[:], addr[:31])
+
+ // Check for the presence of the stem
+ if _, hasStem := aw.Branches[stem]; !hasStem {
+ stemRead = true
+ aw.Branches[stem] = AccessWitnessReadFlag
+ }
+
+ // Check for the presence of the leaf selector
+ if _, hasSelector := aw.Chunks[common.BytesToHash(addr)]; !hasSelector {
+ selectorRead = true
+ aw.Chunks[common.BytesToHash(addr)] = AccessWitnessReadFlag
+ }
+
+ if isWrite {
+ stemWrite, selectorWrite, chunkFill = aw.touchAddressOnWrite(addr)
+ }
+
+ return stemRead, selectorRead, stemWrite, selectorWrite, chunkFill
+}
+
+func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, isWrite bool) uint64 {
+ var gas uint64
+
+ stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.touchAddress(addr, isWrite)
+
+ if stemRead {
+ gas += params.WitnessBranchReadCost
+ }
+ if selectorRead {
+ gas += params.WitnessChunkReadCost
+ }
+ if stemWrite {
+ gas += params.WitnessBranchWriteCost
+ }
+ if selectorWrite {
+ gas += params.WitnessChunkWriteCost
+ }
+ if selectorFill {
+ gas += params.WitnessChunkFillCost
+ }
+
+ return gas
+}
+
+func (aw *AccessWitness) TouchAddressOnWriteAndComputeGas(addr []byte) uint64 {
+ return aw.touchAddressAndChargeGas(addr, true)
+}
+
+func (aw *AccessWitness) TouchAddressOnReadAndComputeGas(addr []byte) uint64 {
+ return aw.touchAddressAndChargeGas(addr, false)
+}
+
+// Merge is used to merge the witness that got generated during the execution
+// of a tx, with the accumulation of witnesses that were generated during the
+// execution of all the txs preceding this one in a given block.
+func (aw *AccessWitness) Merge(other *AccessWitness) {
+ for k := range other.Branches {
+ if _, ok := aw.Branches[k]; !ok {
+ aw.Branches[k] = other.Branches[k]
+ }
+ }
+
+ for k, chunk := range other.Chunks {
+ if _, ok := aw.Chunks[k]; !ok {
+ aw.Chunks[k] = chunk
+ }
+ }
+
+ for k, v := range other.InitialValue {
+ if _, ok := aw.InitialValue[k]; !ok {
+ aw.InitialValue[k] = v
+ }
+ }
+}
+
+// Key returns, predictably, the list of keys that were touched during the
+// buildup of the access witness.
+func (aw *AccessWitness) Keys() [][]byte {
+ keys := make([][]byte, 0, len(aw.Chunks))
+ for key := range aw.Chunks {
+ var k [32]byte
+ copy(k[:], key[:])
+ keys = append(keys, k[:])
+ }
+ return keys
+}
+
+func (aw *AccessWitness) KeyVals() map[string][]byte {
+ result := make(map[string][]byte)
+ for k, v := range aw.InitialValue {
+ result[k] = v
+ }
+ return result
+}
+
+func (aw *AccessWitness) Copy() *AccessWitness {
+ naw := &AccessWitness{
+ Branches: make(map[VerkleStem]Mode),
+ Chunks: make(map[common.Hash]Mode),
+ InitialValue: make(map[string][]byte),
+ }
+
+ naw.Merge(aw)
+
+ return naw
+}
+
+// TODO TouchAndCharge + SetLeafValue* does redundant calls to GetTreeKey*
+
+func (aw *AccessWitness) TouchAndChargeProofOfAbsence(addr []byte) uint64 {
+ var gas uint64
+ gas += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyVersion(addr[:]))
+ gas += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyBalance(addr[:]))
+ gas += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyCodeSize(addr[:]))
+ gas += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyCodeKeccak(addr[:]))
+ gas += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyNonce(addr[:]))
+ return gas
+}
+
+func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 {
+ var gas uint64
+ gas += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyVersion(addr[:]))
+ gas += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyCodeSize(addr[:]))
+ return gas
+}
+
+func (aw *AccessWitness) SetLeafValuesMessageCall(addr, codeSize []byte) {
+ var data [32]byte
+ aw.SetLeafValue(utils.GetTreeKeyVersion(addr[:]), data[:])
+ aw.SetLeafValue(utils.GetTreeKeyCodeSize(addr[:]), codeSize[:])
+}
+
+func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 {
+ var gas uint64
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyBalance(callerAddr[:]))
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyBalance(targetAddr[:]))
+ return gas
+}
+
+// TouchAndChargeContractCreateInit charges access costs to initiate
+// a contract creation
+func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 {
+ var gas uint64
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyVersion(addr[:]))
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyNonce(addr[:]))
+ if createSendsValue {
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyBalance(addr[:]))
+ }
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyCodeKeccak(addr[:]))
+ return gas
+}
+
+// TouchAndChargeContractCreateCompleted charges access access costs after
+// the completion of a contract creation to populate the created account in
+// the tree
+func (aw *AccessWitness) TouchAndChargeContractCreateCompleted(addr []byte, withValue bool) uint64 {
+ var gas uint64
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyVersion(addr[:]))
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyNonce(addr[:]))
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyBalance(addr[:]))
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyCodeSize(addr[:]))
+ gas += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyCodeKeccak(addr[:]))
+ return gas
+}
+
+func (aw *AccessWitness) SetLeafValuesContractCreateCompleted(addr, codeSize, codeKeccak []byte) {
+ aw.SetLeafValue(utils.GetTreeKeyCodeSize(addr[:]), codeSize)
+ aw.SetLeafValue(utils.GetTreeKeyCodeKeccak(addr[:]), codeKeccak)
+}
+
+func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte, sendsValue bool) uint64 {
+ var gasUsed uint64
+ gasUsed += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyVersion(originAddr[:]))
+ gasUsed += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyCodeKeccak(originAddr[:]))
+ gasUsed += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyCodeSize(originAddr[:]))
+ gasUsed += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyNonce(originAddr[:]))
+ gasUsed += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyBalance(originAddr[:]))
+
+ if sendsValue {
+ gasUsed += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyBalance(originAddr[:]))
+ }
+ return gasUsed
+}
+
+func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 {
+ var gasUsed uint64
+ gasUsed += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyVersion(targetAddr[:]))
+ gasUsed += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyBalance(targetAddr[:]))
+ gasUsed += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyNonce(targetAddr[:]))
+ gasUsed += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyCodeSize(targetAddr[:]))
+ gasUsed += aw.TouchAddressOnReadAndComputeGas(utils.GetTreeKeyCodeKeccak(targetAddr[:]))
+
+ if sendsValue {
+ gasUsed += aw.TouchAddressOnWriteAndComputeGas(utils.GetTreeKeyBalance(targetAddr[:]))
+ }
+ return gasUsed
+}
+
+func (aw *AccessWitness) SetTxOriginTouchedLeaves(originAddr, originBalance, originNonce []byte, codeSize int) {
+ var version [32]byte
+ aw.SetLeafValue(utils.GetTreeKeyVersion(originAddr[:]), version[:])
+ aw.SetLeafValue(utils.GetTreeKeyBalance(originAddr[:]), originBalance)
+ aw.SetLeafValue(utils.GetTreeKeyNonce(originAddr[:]), originNonce)
+ var cs [32]byte
+ binary.LittleEndian.PutUint64(cs[:8], uint64(codeSize))
+ aw.SetLeafValue(utils.GetTreeKeyCodeSize(originAddr[:]), cs[:])
+}
+
+func (aw *AccessWitness) SetTxExistingTouchedLeaves(targetAddr, targetBalance, targetNonce, targetCodeSize, targetCodeHash []byte) {
+ var version [32]byte
+ aw.SetLeafValue(utils.GetTreeKeyVersion(targetAddr[:]), version[:])
+ aw.SetLeafValue(utils.GetTreeKeyBalance(targetAddr[:]), targetBalance)
+ aw.SetLeafValue(utils.GetTreeKeyNonce(targetAddr[:]), targetNonce)
+ aw.SetLeafValue(utils.GetTreeKeyCodeSize(targetAddr[:]), targetCodeSize)
+ aw.SetLeafValue(utils.GetTreeKeyCodeKeccak(targetAddr[:]), targetCodeHash)
+}
+
+func (aw *AccessWitness) SetGetObjectTouchedLeaves(targetAddr, version, targetBalance, targetNonce, targetCodeHash []byte) {
+ aw.SetLeafValue(utils.GetTreeKeyVersion(targetAddr[:]), version[:])
+ aw.SetLeafValue(utils.GetTreeKeyBalance(targetAddr[:]), targetBalance)
+ aw.SetLeafValue(utils.GetTreeKeyNonce(targetAddr[:]), targetNonce)
+ aw.SetLeafValue(utils.GetTreeKeyCodeKeccak(targetAddr[:]), targetCodeHash)
+}
+
+func (aw *AccessWitness) SetObjectCodeTouchedLeaves(addr, cs, ch []byte) {
+ aw.SetLeafValue(utils.GetTreeKeyCodeSize(addr[:]), cs)
+ aw.SetLeafValue(utils.GetTreeKeyCodeKeccak(addr[:]), ch)
+}
diff --git a/core/types/block.go b/core/types/block.go
index 314990dc99af..9a1ddb6355d6 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
+ "github.com/gballet/go-verkle"
)
var (
@@ -87,6 +88,10 @@ type Header struct {
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
+ // The verkle proof is ignored in legacy headers
+ VerkleProof []byte `json:"verkleProof" rlp:"optional"`
+ VerkleKeyVals []verkle.KeyValuePair `json:"verkleKeyVals" rlp:"optional"`
+
/*
TODO (MariusVanDerWijden) Add this field once needed
// Random was added during the merge and contains the BeaconState randomness
@@ -338,6 +343,11 @@ func (b *Block) SanityCheck() error {
return b.header.SanityCheck()
}
+func (b *Block) SetVerkleProof(vp []byte, kv []verkle.KeyValuePair) {
+ b.header.VerkleProof = vp
+ b.header.VerkleKeyVals = kv
+}
+
type writeCounter common.StorageSize
func (c *writeCounter) Write(b []byte) (int, error) {
diff --git a/core/vm/common.go b/core/vm/common.go
index 90ba4a4ad15b..ba75950e370b 100644
--- a/core/vm/common.go
+++ b/core/vm/common.go
@@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte {
return common.RightPadBytes(data[start:end], int(size))
}
+func getDataAndAdjustedBounds(data []byte, start uint64, size uint64) (codeCopyPadded []byte, actualStart uint64, sizeNonPadded uint64) {
+ length := uint64(len(data))
+ if start > length {
+ start = length
+ }
+ end := start + size
+ if end > length {
+ end = length
+ }
+ return common.RightPadBytes(data[start:end], int(size)), start, end - start
+}
+
// toWordSize returns the ceiled word size required for memory expansion.
func toWordSize(size uint64) uint64 {
if size > math.MaxUint64-31 {
diff --git a/core/vm/contract.go b/core/vm/contract.go
index bb0902969ec7..eaa4cb7c65b6 100644
--- a/core/vm/contract.go
+++ b/core/vm/contract.go
@@ -28,6 +28,13 @@ type ContractRef interface {
Address() common.Address
}
+// AnalyzedContract is an interface for a piece of contract
+// code that has undegone jumpdest analysis, and whose bytecode
+// can be queried to determine if it is "contract code"
+type AnalyzedContract interface {
+ IsCode(dest uint64) bool
+}
+
// AccountRef implements ContractRef.
//
// Account references are used during EVM initialisation and
@@ -58,6 +65,9 @@ type Contract struct {
CodeAddr *common.Address
Input []byte
+ // is the execution frame represented by this object a contract deployment
+ IsDeployment bool
+
Gas uint64
value *big.Int
}
@@ -93,15 +103,15 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool {
if OpCode(c.Code[udest]) != JUMPDEST {
return false
}
- return c.isCode(udest)
+ return c.IsCode(udest)
}
-// isCode returns true if the provided PC location is an actual opcode, as
+// IsCode returns true if the provided PC location is an actual opcode, as
// opposed to a data-segment following a PUSHN operation.
-func (c *Contract) isCode(udest uint64) bool {
+func (c *Contract) IsCode(udest uint64) bool {
// Do we already have an analysis laying around?
if c.analysis != nil {
- return c.analysis.codeSegment(udest)
+ return c.analysis.IsCode(udest)
}
// Do we have a contract hash already?
// If we do have a hash, that means it's a 'regular' contract. For regular
@@ -117,7 +127,7 @@ func (c *Contract) isCode(udest uint64) bool {
}
// Also stash it in current contract for faster access
c.analysis = analysis
- return analysis.codeSegment(udest)
+ return analysis.IsCode(udest)
}
// We don't have the code hash, most likely a piece of initcode not already
// in state trie. In that case, we do an analysis, and save it locally, so
@@ -126,7 +136,7 @@ func (c *Contract) isCode(udest uint64) bool {
if c.analysis == nil {
c.analysis = codeBitmap(c.Code)
}
- return c.analysis.codeSegment(udest)
+ return c.analysis.IsCode(udest)
}
// AsDelegate sets the contract to be a delegate call and returns the current
diff --git a/core/vm/evm.go b/core/vm/evm.go
index dd55618bf812..05391775aa23 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -22,6 +22,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
@@ -84,6 +85,8 @@ type TxContext struct {
// Message information
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE
+
+ Accesses *types.AccessWitness
}
// EVM is the Ethereum Virtual Machine base object and provides
@@ -126,6 +129,9 @@ type EVM struct {
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
// only ever be used *once*.
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM {
+ if txCtx.Accesses == nil && chainConfig.IsCancun(blockCtx.BlockNumber) {
+ txCtx.Accesses = types.NewAccessWitness()
+ }
evm := &EVM{
Context: blockCtx,
TxContext: txCtx,
@@ -141,6 +147,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
// Reset resets the EVM with a new transaction context.Reset
// This is not threadsafe and should only be done very cautiously.
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
+ if txCtx.Accesses == nil && evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ txCtx.Accesses = types.NewAccessWitness()
+ }
evm.TxContext = txCtx
evm.StateDB = statedb
}
@@ -161,6 +170,19 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
return evm.interpreter
}
+// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool
+// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false
+// otherwise, do the subtraction setting the result in gasPool and return true
+func tryConsumeGas(gasPool *uint64, gas uint64) bool {
+ if *gasPool < gas {
+ *gasPool = 0
+ return false
+ }
+
+ *gasPool -= gas
+ return true
+}
+
// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
@@ -179,6 +201,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ // proof of absence
+ tryConsumeGas(&gas, evm.Accesses.TouchAndChargeProofOfAbsence(caller.Address().Bytes()))
+ }
// Calling a non existing account, don't do anything, but ping the tracer
if evm.Config.Debug {
if evm.depth == 0 {
@@ -217,6 +243,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
code := evm.StateDB.GetCode(addr)
+
if len(code) == 0 {
ret, err = nil, nil // gas is unchanged
} else {
@@ -225,6 +252,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// The depth-check is already done, and precompiles handled above
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
+ contract.IsDeployment = true
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
}
@@ -404,6 +432,8 @@ func (c *codeAndHash) Hash() common.Hash {
// create creates a new contract using code as deployment code.
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
+ var zeroVerkleLeaf [32]byte
+
// Depth check execution. Fail if we're trying to execute above the
// limit.
if evm.depth > int(params.CallCreateDepth) {
@@ -433,12 +463,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.chainRules.IsEIP158 {
evm.StateDB.SetNonce(address, 1)
}
+
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash)
+ contract.IsDeployment = true
if evm.Config.Debug {
if evm.depth == 0 {
@@ -485,6 +517,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
}
}
+ if err == nil && evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateCompleted(address.Bytes()[:], value.Sign() != 0)) {
+ evm.StateDB.RevertToSnapshot(snapshot)
+ err = ErrOutOfGas
+ } else {
+ evm.Accesses.SetLeafValuesContractCreateCompleted(address.Bytes()[:], zeroVerkleLeaf[:], zeroVerkleLeaf[:])
+ }
+ }
+
if evm.Config.Debug {
if evm.depth == 0 {
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go
index 4c2cb3e5cf79..b7374a63be2b 100644
--- a/core/vm/gas_table.go
+++ b/core/vm/gas_table.go
@@ -21,7 +21,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
+ trieUtils "github.com/ethereum/go-ethereum/trie/utils"
)
// memoryGasCost calculates the quadratic gas for memory expansion. It does so
@@ -87,13 +89,89 @@ func memoryCopierGas(stackpos int) gasFunc {
}
var (
- gasCallDataCopy = memoryCopierGas(2)
- gasCodeCopy = memoryCopierGas(2)
- gasExtCodeCopy = memoryCopierGas(3)
- gasReturnDataCopy = memoryCopierGas(2)
+ gasCallDataCopy = memoryCopierGas(2)
+ gasCodeCopyStateful = memoryCopierGas(2)
+ gasExtCodeCopyStateful = memoryCopierGas(3)
+ gasReturnDataCopy = memoryCopierGas(2)
)
+func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ usedGas := uint64(0)
+ slot := stack.Back(0)
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
+ usedGas += evm.TxContext.Accesses.TouchAddressOnReadAndComputeGas(index)
+ }
+
+ return usedGas, nil
+}
+
+func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ var statelessGas uint64
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ var (
+ codeOffset = stack.Back(1)
+ length = stack.Back(2)
+ )
+ uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
+ if overflow {
+ uint64CodeOffset = 0xffffffffffffffff
+ }
+ uint64Length, overflow := length.Uint64WithOverflow()
+ if overflow {
+ uint64Length = 0xffffffffffffffff
+ }
+ _, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length)
+ statelessGas = touchEachChunksOnReadAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], nil, nil, evm.Accesses, contract.IsDeployment)
+ }
+ usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize)
+ return usedGas + statelessGas, err
+}
+
+func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ var statelessGas uint64
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ var (
+ codeOffset = stack.Back(2)
+ length = stack.Back(3)
+ targetAddr = stack.Back(0).Bytes20()
+ )
+ uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
+ if overflow {
+ uint64CodeOffset = 0xffffffffffffffff
+ }
+ uint64Length, overflow := length.Uint64WithOverflow()
+ if overflow {
+ uint64Length = 0xffffffffffffffff
+ }
+ // note: we must charge witness costs for the specified range regardless of whether it
+ // is in-bounds of the actual target account code. This is because we must charge the cost
+ // before hitting the db to be able to now what the actual code size is. This is different
+ // behavior from CODECOPY which only charges witness access costs for the part of the range
+ // which overlaps in the account code. TODO: clarify this is desired behavior and amend the
+ // spec.
+ statelessGas = touchEachChunksOnReadAndChargeGas(uint64CodeOffset, uint64Length, targetAddr[:], nil, nil, evm.Accesses, contract.IsDeployment)
+ }
+ usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize)
+ return usedGas + statelessGas, err
+}
+
+func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ usedGas := uint64(0)
+
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ where := stack.Back(0)
+ addr := contract.Address()
+ index := trieUtils.GetTreeKeyStorageSlot(addr[:], where)
+ usedGas += evm.Accesses.TouchAddressOnReadAndComputeGas(index)
+ }
+
+ return usedGas, nil
+}
+
func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ // Apply the witness access costs, err is nil
+ accessGas, _ := gasSLoad(evm, contract, stack, mem, memorySize)
var (
y, x = stack.Back(1), stack.Back(0)
current = evm.StateDB.GetState(contract.Address(), x.Bytes32())
@@ -109,12 +187,12 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi
// 3. From a non-zero to a non-zero (CHANGE)
switch {
case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0
- return params.SstoreSetGas, nil
+ return params.SstoreSetGas + accessGas, nil
case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0
evm.StateDB.AddRefund(params.SstoreRefundGas)
- return params.SstoreClearGas, nil
+ return params.SstoreClearGas + accessGas, nil
default: // non 0 => non 0 (or 0 => 0)
- return params.SstoreResetGas, nil
+ return params.SstoreResetGas + accessGas, nil
}
}
// The new gas metering is based on net gas costs (EIP-1283):
@@ -331,6 +409,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
transfersValue = !stack.Back(2).IsZero()
address = common.Address(stack.Back(1).Bytes20())
)
+
if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
gas += params.CallNewAccountGas
@@ -357,6 +436,21 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ if _, isPrecompile := evm.precompile(address); !isPrecompile {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()[:]))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ if transfersValue {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:]))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
+
return gas, nil
}
@@ -382,6 +476,15 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ address := common.Address(stack.Back(1).Bytes20())
+ if _, isPrecompile := evm.precompile(address); !isPrecompile {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
return gas, nil
}
@@ -398,6 +501,15 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ address := common.Address(stack.Back(1).Bytes20())
+ if _, isPrecompile := evm.precompile(address); !isPrecompile {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
return gas, nil
}
@@ -414,6 +526,15 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ address := common.Address(stack.Back(1).Bytes20())
+ if _, isPrecompile := evm.precompile(address); !isPrecompile {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
return gas, nil
}
@@ -434,6 +555,12 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
}
}
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ // TODO turn this into a panic (when we are sure this method
+ // will never execute when verkle is enabled)
+ log.Warn("verkle witness accumulation not supported for selfdestruct")
+ }
+
if !evm.StateDB.HasSuicided(contract.Address()) {
evm.StateDB.AddRefund(params.SelfdestructRefundGas)
}
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index db507c481100..71d8d0cfed02 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -21,7 +21,9 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
+ trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
)
@@ -343,7 +345,13 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
slot := scope.Stack.peek()
- slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
+ cs := uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))
+ if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
+ index := trieUtils.GetTreeKeyCodeSize(slot.Bytes())
+ statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(index)
+ scope.Contract.UseGas(statelessGas)
+ }
+ slot.SetUint64(cs)
return nil, nil
}
@@ -364,12 +372,80 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
if overflow {
uint64CodeOffset = 0xffffffffffffffff
}
- codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
- scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
+ paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64())
+ if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
+ touchEachChunksOnReadAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses, scope.Contract.IsDeployment)
+ }
+ scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy)
return nil, nil
}
+// touchEachChunksAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
+func touchEachChunksOnReadAndChargeGas(offset, size uint64, address []byte, code []byte, contract AnalyzedContract, accesses *types.AccessWitness, deployment bool) uint64 {
+ // note that in the case where the copied code is outside the range of the
+ // contract code but touches the last leaf with contract code in it,
+ // we don't include the last leaf of code in the AccessWitness. The
+ // reason that we do not need the last leaf is the account's code size
+ // is already in the AccessWitness so a stateless verifier can see that
+ // the code from the last leaf is not needed.
+ if contract != nil && (size == 0 || offset > uint64(len(code))) {
+ return 0
+ }
+ var (
+ statelessGasCharged uint64
+ endLeafOffset uint64
+ startOffset uint64
+ endOffset uint64
+ numLeaves uint64
+ )
+ // startLeafOffset, endLeafOffset is the evm code offset of the first byte in the first leaf touched
+ // and the evm code offset of the last byte in the last leaf touched
+ startOffset = offset - (offset % 31)
+ if contract != nil && startOffset+size > uint64(len(code)) {
+ endOffset = uint64(len(code))
+ } else {
+ endOffset = startOffset + size
+ }
+ endLeafOffset = endOffset + (endOffset % 31)
+ numLeaves = (endLeafOffset - startOffset + 30) / 31
+
+ for i := 0; i < int(numLeaves); i++ {
+ index := trieUtils.GetTreeKeyCodeChunk(address, uint256.NewInt(uint64(i)+startOffset/31))
+
+ // TODO safe-add here to catch overflow
+ statelessGasCharged += accesses.TouchAddressOnReadAndComputeGas(index)
+ var value []byte
+ if code != nil && len(code) > 0 {
+ // the offset into the leaf that the first PUSH occurs
+ var firstPushOffset uint64 = 0
+ // Look for the first code byte (i.e. no pushdata)
+ for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ {
+ }
+ curEnd := (uint64(i) + 1) * 31
+ if curEnd > endOffset {
+ curEnd = endOffset
+ }
+ valueSize := curEnd - (uint64(i) * 31)
+ value = make([]byte, 32, 32)
+ value[0] = byte(firstPushOffset)
+
+ copy(value[1:valueSize+1], code[i*31:curEnd])
+ if valueSize < 31 {
+ padding := make([]byte, 31-valueSize, 31-valueSize)
+ copy(value[valueSize+1:], padding)
+ }
+ if deployment {
+ accesses.SetLeafValue(index[:], nil)
+ } else {
+ accesses.SetLeafValue(index[:], value)
+ }
+ }
+ }
+
+ return statelessGasCharged
+}
+
func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
stack = scope.Stack
@@ -383,8 +459,16 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
uint64CodeOffset = 0xffffffffffffffff
}
addr := common.Address(a.Bytes20())
- codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
- scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
+ if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
+ code := interpreter.evm.StateDB.GetCode(addr)
+ paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
+ cb := codeBitmap(code)
+ touchEachChunksOnReadAndChargeGas(copyOffset, nonPaddedCopyLength, addr[:], code, &cb, interpreter.evm.Accesses, false)
+ scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
+ } else {
+ codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
+ scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
+ }
return nil, nil
}
@@ -517,6 +601,7 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
loc := scope.Stack.peek()
hash := common.Hash(loc.Bytes32())
val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash)
+
loc.SetBytes(val.Bytes())
return nil, nil
}
@@ -587,6 +672,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas
)
+ if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
+ contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address()))
+ statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], value.Sign() != 0)
+ if !tryConsumeGas(&gas, statelessGas) {
+ return nil, ErrExecutionReverted
+ }
+ }
if interpreter.evm.chainRules.IsEIP150 {
gas -= gas / 64
}
@@ -634,6 +726,14 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas
)
+ if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
+ codeAndHash := &codeAndHash{code: input}
+ contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
+ statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], endowment.Sign() != 0)
+ if !tryConsumeGas(&gas, statelessGas) {
+ return nil, ErrExecutionReverted
+ }
+ }
// Apply EIP150
gas -= gas / 64
@@ -877,6 +977,29 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
*pc += 1
if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
+
+ if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) && *pc%31 == 0 {
+ // touch next chunk if PUSH1 is at the boundary. if so, *pc has
+ // advanced past this boundary.
+
+ // touch push data by adding the last byte of the pushdata
+ var value [32]byte
+ chunk := *pc / 31
+ count := uint64(0)
+ // Look for the first code byte (i.e. no pushdata)
+ for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ {
+ }
+ value[0] = byte(count)
+ endMin := (chunk + 1) * 31
+ if endMin > uint64(len(scope.Contract.Code)) {
+ endMin = uint64(len(scope.Contract.Code))
+ }
+ copy(value[1:], scope.Contract.Code[chunk*31:endMin])
+ index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk))
+ statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(index)
+ interpreter.evm.Accesses.SetLeafValue(index, value[:])
+ scope.Contract.UseGas(statelessGas)
+ }
} else {
scope.Stack.push(integer.Clear())
}
@@ -898,6 +1021,11 @@ func makePush(size uint64, pushByteSize int) executionFunc {
endMin = startMin + pushByteSize
}
+ if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
+ statelessGas := touchEachChunksOnReadAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses, scope.Contract.IsDeployment)
+ scope.Contract.UseGas(statelessGas)
+ }
+
integer := new(uint256.Int)
scope.Stack.push(integer.SetBytes(common.RightPadBytes(
scope.Contract.Code[startMin:endMin], pushByteSize)))
diff --git a/core/vm/interface.go b/core/vm/interface.go
index ad9b05d666a8..37a7eab63912 100644
--- a/core/vm/interface.go
+++ b/core/vm/interface.go
@@ -47,6 +47,9 @@ type StateDB interface {
GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash)
+ GetBalanceLittleEndian(address common.Address) []byte
+ GetNonceLittleEndian(address common.Address) []byte
+
Suicide(common.Address) bool
HasSuicided(common.Address) bool
@@ -74,6 +77,9 @@ type StateDB interface {
AddPreimage(common.Hash, []byte)
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
+
+ Witness() *types.AccessWitness
+ SetWitness(*types.AccessWitness)
}
// CallContext provides a basic interface for the EVM calling conventions. The EVM
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 21e3c914e139..76526e8c3074 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -183,8 +183,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas
}
+ if in.evm.ChainConfig().IsCancun(in.evm.Context.BlockNumber) && !contract.IsDeployment {
+ // if the PC ends up in a new "page" of verkleized code, charge the
+ // associated witness costs.
+ contract.Gas -= touchEachChunksOnReadAndChargeGas(pc, 1, contract.Address().Bytes()[:], contract.Code, contract, in.evm.TxContext.Accesses, contract.IsDeployment)
+ }
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
+ // TODO how can we tell if we are in stateless mode here and need to get the op from the witness
+ // If we are in witness mode, then raise an error
+
op = contract.GetOp(pc)
operation := in.cfg.JumpTable[op]
cost = operation.constantGas // For tracing
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index eef3b53d8c66..4fdc7ec2cc05 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -452,6 +452,7 @@ func newFrontierInstructionSet() JumpTable {
EXTCODESIZE: {
execute: opExtCodeSize,
constantGas: params.ExtcodeSizeGasFrontier,
+ dynamicGas: gasExtCodeSize,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
},
@@ -532,6 +533,7 @@ func newFrontierInstructionSet() JumpTable {
SLOAD: {
execute: opSload,
constantGas: params.SloadGasFrontier,
+ dynamicGas: gasSLoad,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
},
diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go
index 483226eefad8..16b195eb901a 100644
--- a/core/vm/operations_acl.go
+++ b/core/vm/operations_acl.go
@@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
+ trieUtils "github.com/ethereum/go-ethereum/trie/utils"
)
func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
@@ -51,6 +52,12 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
}
value := common.Hash(y.Bytes32())
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ addr := contract.Address()
+ index := trieUtils.GetTreeKeyStorageSlot(addr[:], x)
+ cost += evm.Accesses.TouchAddressOnWriteAndComputeGas(index)
+ }
+
if current == value { // noop (1)
// EIP 2200 original clause:
// return params.SloadGasEIP2200, nil
@@ -103,14 +110,23 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
loc := stack.peek()
slot := common.Hash(loc.Bytes32())
+ var gasUsed uint64
+
+ if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
+ where := stack.Back(0)
+ addr := contract.Address()
+ index := trieUtils.GetTreeKeyStorageSlot(addr[:], where)
+ gasUsed += evm.Accesses.TouchAddressOnReadAndComputeGas(index)
+ }
+
// Check slot presence in the access list
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
// If the caller cannot afford the cost, this change will be rolled back
// If he does afford it, we can skip checking the same thing later on, during execution
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
- return params.ColdSloadCostEIP2929, nil
+ return gasUsed + params.ColdSloadCostEIP2929, nil
}
- return params.WarmStorageReadCostEIP2929, nil
+ return gasUsed + params.WarmStorageReadCostEIP2929, nil
}
// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929
diff --git a/eth/backend.go b/eth/backend.go
index 4e6d71523531..b1879dd9412c 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -57,6 +57,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
+ "github.com/gballet/go-verkle"
)
// Config contains the configuration options of the ETH protocol.
@@ -142,6 +143,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}
log.Info("Initialised chain configuration", "config", chainConfig)
+ // Start the precomputation of Lagrange points
+ // if this config supports verkle trees.
+ if chainConfig.CancunBlock != nil {
+ log.Info("Detected the use of verkle trees, rebuilding the cache")
+ verkle.GetConfig()
+ }
+
if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil {
log.Error("Failed to recover state", "error", err)
}
diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go
index cf0a4aa8284f..afb7a50c6d73 100644
--- a/eth/tracers/js/tracer_test.go
+++ b/eth/tracers/js/tracer_test.go
@@ -197,7 +197,7 @@ func TestNoStepExec(t *testing.T) {
}
func TestIsPrecompile(t *testing.T) {
- chaincfg := ¶ms.ChainConfig{ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP150Hash: common.Hash{}, EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0), BerlinBlock: big.NewInt(300), LondonBlock: big.NewInt(0), TerminalTotalDifficulty: nil, Ethash: new(params.EthashConfig), Clique: nil}
+ chaincfg := ¶ms.ChainConfig{ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP150Hash: common.Hash{}, EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0), BerlinBlock: big.NewInt(300), LondonBlock: big.NewInt(0), TerminalTotalDifficulty: nil, CancunBlock: nil, Ethash: new(params.EthashConfig), Clique: nil}
chaincfg.ByzantiumBlock = big.NewInt(100)
chaincfg.IstanbulBlock = big.NewInt(200)
chaincfg.BerlinBlock = big.NewInt(300)
@@ -235,14 +235,17 @@ func TestEnterExit(t *testing.T) {
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context)); err != nil {
t.Fatal(err)
}
+
// test that the enter and exit method are correctly invoked and the values passed
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context))
if err != nil {
t.Fatal(err)
}
+
scope := &vm.ScopeContext{
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0),
}
+
tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int))
tracer.CaptureExit([]byte{}, 400, nil)
diff --git a/go.mod b/go.mod
index 7d0c3abdf862..4ebc359b8547 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
github.com/cespare/cp v0.1.0
github.com/cloudflare/cloudflare-go v0.14.0
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f
+ github.com/crate-crypto/go-ipa v0.0.0-20220309173511-816621cb2ec4
github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set v1.8.0
github.com/deepmap/oapi-codegen v1.8.2 // indirect
@@ -23,6 +24,7 @@ require (
github.com/fatih/color v1.7.0
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
+ github.com/gballet/go-verkle v0.0.0-20220309192943-d9f613553c79
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-stack/stack v1.8.0
github.com/golang-jwt/jwt/v4 v4.3.0
@@ -61,8 +63,9 @@ require (
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
+ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
- golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912
+ golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7
golang.org/x/text v0.3.7
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
golang.org/x/tools v0.1.0
diff --git a/go.sum b/go.sum
index 95ff558f6c4c..e6f7e6ce0671 100644
--- a/go.sum
+++ b/go.sum
@@ -83,6 +83,17 @@ github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f h1:C43yEtQ6NIf4ftFXD/V55gnGFgPbMQobd//YlnLjUJ8=
github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/crate-crypto/go-ipa v0.0.0-20211107182441-1aeb67f49de7/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
+github.com/crate-crypto/go-ipa v0.0.0-20211211194659-8730b6787450 h1:FW8SSFr58vgCbvNAWHUgigf6BYG1uIcwgZA8z539RLE=
+github.com/crate-crypto/go-ipa v0.0.0-20211211194659-8730b6787450/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
+github.com/crate-crypto/go-ipa v0.0.0-20211223165939-ab3f49447206 h1:nMTTM1b+4WtWrb43nmTUV/FJz7M0M2g5fioLmd4QzUQ=
+github.com/crate-crypto/go-ipa v0.0.0-20211223165939-ab3f49447206/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
+github.com/crate-crypto/go-ipa v0.0.0-20220114181434-991b62f9b1da h1:2luwsOSyUPVE67DmD7s2nKM+JbxaRuOCu6Y82qBTdnI=
+github.com/crate-crypto/go-ipa v0.0.0-20220114181434-991b62f9b1da/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
+github.com/crate-crypto/go-ipa v0.0.0-20220120174240-fe21866d2ad5 h1:BsLconJDsODyRO72xjBHhxZKPCuY/kBgZdPeV4GFNlU=
+github.com/crate-crypto/go-ipa v0.0.0-20220120174240-fe21866d2ad5/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
+github.com/crate-crypto/go-ipa v0.0.0-20220309173511-816621cb2ec4 h1:6KIFkDoBRVfE2I4cUt2wUuejBs+ReEe0cNQnTLs8AwE=
+github.com/crate-crypto/go-ipa v0.0.0-20220309173511-816621cb2ec4/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
@@ -126,6 +137,32 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
+github.com/gballet/go-verkle v0.0.0-20211221174846-e7cddd97495c h1:d34wNrUBGD6s2OCWQd31nqjkPWt+YbXUm8FAJ+7+tHM=
+github.com/gballet/go-verkle v0.0.0-20211221174846-e7cddd97495c/go.mod h1:6hL9IP6wLByFGqj1VDbruVAjfa8DghWD96o8kuNMDCA=
+github.com/gballet/go-verkle v0.0.0-20220113152809-0c00dbeef550 h1:uyv9x+hIyWF6Jrlz9pg/PtIhQS/lPGwVl5Rkp04m/Ik=
+github.com/gballet/go-verkle v0.0.0-20220113152809-0c00dbeef550/go.mod h1:2ObZwG6QCVAzvnUrsezVfjEL28O+e1cSA9xV/PB7IME=
+github.com/gballet/go-verkle v0.0.0-20220119102938-379d70afb801 h1:XoZ7eOVOdDXeVz05t3hO4JtRWXYvZa8lGm9zoPhSyEc=
+github.com/gballet/go-verkle v0.0.0-20220119102938-379d70afb801/go.mod h1:e1m+0UuY3AFFTubxZZ3ePf4yO/rHMAeTb7udhUAJduk=
+github.com/gballet/go-verkle v0.0.0-20220119112812-69e1c35fb0e0 h1:OQ2MGbAcc9X/51/llMp1CozPwc3ryRyXSQ19E2mbI2k=
+github.com/gballet/go-verkle v0.0.0-20220119112812-69e1c35fb0e0/go.mod h1:e1m+0UuY3AFFTubxZZ3ePf4yO/rHMAeTb7udhUAJduk=
+github.com/gballet/go-verkle v0.0.0-20220119205306-b466d7bff5ba h1:iOhT1XFktDNN72Ly2GUI/UTj2dnA1qI7+kyYqdpSCCk=
+github.com/gballet/go-verkle v0.0.0-20220119205306-b466d7bff5ba/go.mod h1:e1m+0UuY3AFFTubxZZ3ePf4yO/rHMAeTb7udhUAJduk=
+github.com/gballet/go-verkle v0.0.0-20220121105610-351986d619a8 h1:6fW03c2tXGttQv1b3mt9zmcXw4/srV62DltEER5mp/U=
+github.com/gballet/go-verkle v0.0.0-20220121105610-351986d619a8/go.mod h1:e1m+0UuY3AFFTubxZZ3ePf4yO/rHMAeTb7udhUAJduk=
+github.com/gballet/go-verkle v0.0.0-20220127081734-1fca5295178c h1:EoP3VKIxU+29cA4coL9TJfD8lPuaUURxbW5nHoK1joM=
+github.com/gballet/go-verkle v0.0.0-20220127081734-1fca5295178c/go.mod h1:NhFaKv4U1IBG8QkKCsKh4xBMmdori0B0eZjdywcrktE=
+github.com/gballet/go-verkle v0.0.0-20220128155149-95499dfcd74a h1:0GpdT+zyEoa8OTBtJk5EdZm7nF4HXzU+blG3io0pm5A=
+github.com/gballet/go-verkle v0.0.0-20220128155149-95499dfcd74a/go.mod h1:NhFaKv4U1IBG8QkKCsKh4xBMmdori0B0eZjdywcrktE=
+github.com/gballet/go-verkle v0.0.0-20220208130546-d368c16aca2f h1:E/dsnBev33ojJlYjBVnh/uJ2tvFB5juYKyOP854fwTE=
+github.com/gballet/go-verkle v0.0.0-20220208130546-d368c16aca2f/go.mod h1:NhFaKv4U1IBG8QkKCsKh4xBMmdori0B0eZjdywcrktE=
+github.com/gballet/go-verkle v0.0.0-20220209120303-88a3fa537cf1 h1:mkoUQjsrx/6hH3b3/tCfNhe1IJyErvNqG5k4XxWpuNs=
+github.com/gballet/go-verkle v0.0.0-20220209120303-88a3fa537cf1/go.mod h1:NhFaKv4U1IBG8QkKCsKh4xBMmdori0B0eZjdywcrktE=
+github.com/gballet/go-verkle v0.0.0-20220217102726-664ad58b43cd h1:kvZpVdKgiVfxi+8Wvlm9FknyA4b+T5bp3ajZEMlp6N0=
+github.com/gballet/go-verkle v0.0.0-20220217102726-664ad58b43cd/go.mod h1:NhFaKv4U1IBG8QkKCsKh4xBMmdori0B0eZjdywcrktE=
+github.com/gballet/go-verkle v0.0.0-20220228192133-5773319d4d56 h1:Zucd5Snqq+pzzpOuuj675QgOKvfWxpnouTqug4trGIA=
+github.com/gballet/go-verkle v0.0.0-20220228192133-5773319d4d56/go.mod h1:mxWdGqfhWGZ0ckAEg+zHM8u4p1Tsw9mAsbSBtXb9AQE=
+github.com/gballet/go-verkle v0.0.0-20220309192943-d9f613553c79 h1:IGryvoVakS5ANetCp33JpA/3eW9A9/k6Ucbqr1rA5uU=
+github.com/gballet/go-verkle v0.0.0-20220309192943-d9f613553c79/go.mod h1:S2TbrZxLyGqCqwtl2IA09xxun6oretK6byC1lHY+sAk=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -516,8 +553,25 @@ golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220207234003-57398862261d h1:Bm7BNOQt2Qv7ZqysjeLjgCBanX+88Z/OtdvsrEv1Djc=
+golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 h1:BXxu8t6QN0G1uff4bzZzSkpsax8+ALqTGUtz08QrV00=
+golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE=
+golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 366d5714175d..0a53c7f12029 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -1068,7 +1068,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap)
if err != nil {
- if errors.Is(err, core.ErrIntrinsicGas) {
+ if errors.Is(err, core.ErrIntrinsicGas) || errors.Is(err, core.ErrInsufficientBalanceWitness) {
return true, nil, nil // Special case, raise gas limit
}
return true, nil, err // Bail out
diff --git a/light/trie.go b/light/trie.go
index 4ab6f4ace075..db09e52965fc 100644
--- a/light/trie.go
+++ b/light/trie.go
@@ -163,6 +163,8 @@ func (t *odrTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter
return errors.New("not implemented, needs client/server interface split")
}
+func (t *odrTrie) IsVerkle() bool { return false }
+
// do tries and retries to execute a function until it returns with no error or
// an error type other than MissingNodeError
func (t *odrTrie) do(key []byte, fn func() error) error {
diff --git a/miner/worker.go b/miner/worker.go
index c6927a1ca1e8..5c156eb034e4 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -83,6 +83,7 @@ type environment struct {
signer types.Signer
state *state.StateDB // apply state changes here
+ original *state.StateDB // verkle: keep the orignal data to prove the pre-state
ancestors mapset.Set // ancestor set (used for checking uncle parent validity)
family mapset.Set // family set (used for checking uncle invalidity)
tcount int // tx count in cycle
@@ -766,11 +767,11 @@ func (w *worker) makeEnv(parent *types.Block, header *types.Header, coinbase com
}
state.StartPrefetcher("miner")
- // Note the passed coinbase may be different with header.Coinbase.
env := &environment{
signer: types.MakeSigner(w.chainConfig, header.Number),
state: state,
coinbase: coinbase,
+ original: state.Copy(),
ancestors: mapset.NewSet(),
family: mapset.NewSet(),
header: header,
diff --git a/miner/worker_test.go b/miner/worker_test.go
index dd029433b8bf..916bcc843f9b 100644
--- a/miner/worker_test.go
+++ b/miner/worker_test.go
@@ -118,7 +118,7 @@ type testWorkerBackend struct {
uncleBlock *types.Block
}
-func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend {
+func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int, isVerkle bool) *testWorkerBackend {
var gspec = core.Genesis{
Config: chainConfig,
Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}},
@@ -153,9 +153,17 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine
if n > 0 {
parent = chain.GetBlockByHash(chain.CurrentBlock().ParentHash())
}
- blocks, _ := core.GenerateChain(chainConfig, parent, engine, db, 1, func(i int, gen *core.BlockGen) {
- gen.SetCoinbase(testUserAddress)
- })
+ var blocks []*types.Block
+
+ if isVerkle {
+ blocks, _ = core.GenerateVerkleChain(chainConfig, parent, engine, db, 1, func(i int, gen *core.BlockGen) {
+ gen.SetCoinbase(testUserAddress)
+ })
+ } else {
+ blocks, _ = core.GenerateChain(chainConfig, parent, engine, db, 1, func(i int, gen *core.BlockGen) {
+ gen.SetCoinbase(testUserAddress)
+ })
+ }
return &testWorkerBackend{
db: db,
@@ -188,6 +196,22 @@ func (b *testWorkerBackend) newRandomUncle() *types.Block {
return blocks[0]
}
+func (b *testWorkerBackend) newRandomVerkleUncle() *types.Block {
+ var parent *types.Block
+ cur := b.chain.CurrentBlock()
+ if cur.NumberU64() == 0 {
+ parent = b.chain.Genesis()
+ } else {
+ parent = b.chain.GetBlockByHash(b.chain.CurrentBlock().ParentHash())
+ }
+ blocks, _ := core.GenerateVerkleChain(b.chain.Config(), parent, b.chain.Engine(), b.db, 1, func(i int, gen *core.BlockGen) {
+ var addr = make([]byte, common.AddressLength)
+ rand.Read(addr)
+ gen.SetCoinbase(common.BytesToAddress(addr))
+ })
+ return blocks[0]
+}
+
func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
var tx *types.Transaction
gasPrice := big.NewInt(10 * params.InitialBaseFee)
@@ -199,8 +223,8 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
return tx
}
-func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) {
- backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
+func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int, isVerkle bool) (*worker, *testWorkerBackend) {
+ backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks, isVerkle)
backend.txPool.AddLocals(pendingTxs)
w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false)
w.setEtherbase(testBankAddress)
@@ -231,7 +255,7 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
}
chainConfig.LondonBlock = big.NewInt(0)
- w, b := newTestWorker(t, chainConfig, engine, db, 0)
+ w, b := newTestWorker(t, chainConfig, engine, db, 0, false)
defer w.close()
// This test chain imports the mined blocks.
@@ -270,6 +294,60 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
}
}
+func TestGenerateBlocksAndImportVerkle(t *testing.T) {
+ t.Skip("Skipping due to a faulty testing infrastructure, see gballet/go-verkle/issues/180 for more information.")
+ var (
+ engine consensus.Engine
+ chainConfig *params.ChainConfig
+ db = rawdb.NewMemoryDatabase()
+ )
+ chainConfig = params.VerkleChainConfig
+ engine = ethash.NewFaker()
+
+ w, b := newTestWorker(t, chainConfig, engine, db, 0, true)
+ defer w.close()
+
+ // This test chain imports the mined blocks.
+ db2 := rawdb.NewMemoryDatabase()
+ b.genesis.MustCommit(db2)
+ chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil, nil)
+ defer chain.Stop()
+
+ // Ignore empty commit here for less noise.
+ /*
+ w.skipSealHook = func(task *task) bool {
+ return len(task.receipts) == 0
+ }
+ */
+
+ // Wait for mined blocks.
+ sub := w.mux.Subscribe(core.NewMinedBlockEvent{})
+ defer sub.Unsubscribe()
+
+ // Start mining!
+ w.start()
+
+ for i := 0; i < 5; i++ {
+ b.txPool.AddLocal(b.newRandomTx(true))
+ b.txPool.AddLocal(b.newRandomTx(false))
+ w.postSideBlock(core.ChainSideEvent{Block: b.newRandomVerkleUncle()})
+ w.postSideBlock(core.ChainSideEvent{Block: b.newRandomVerkleUncle()})
+
+ select {
+ case ev := <-sub.Chan():
+ block := ev.Data.(core.NewMinedBlockEvent).Block
+ if block.Header().VerkleProof == nil {
+ t.Fatalf("expected Verkle proof in mined block header")
+ }
+ if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
+ t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err)
+ }
+ case <-time.After(3 * time.Second): // Worker needs 1s to include new changes.
+ t.Fatalf("timeout")
+ }
+ }
+}
+
func TestEmptyWorkEthash(t *testing.T) {
testEmptyWork(t, ethashChainConfig, ethash.NewFaker())
}
@@ -280,7 +358,7 @@ func TestEmptyWorkClique(t *testing.T) {
func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
defer engine.Close()
- w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
+ w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, false)
defer w.close()
var (
@@ -326,7 +404,7 @@ func TestStreamUncleBlock(t *testing.T) {
ethash := ethash.NewFaker()
defer ethash.Close()
- w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1)
+ w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1, false)
defer w.close()
var taskCh = make(chan struct{})
@@ -384,7 +462,7 @@ func TestRegenerateMiningBlockClique(t *testing.T) {
func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
defer engine.Close()
- w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
+ w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, false)
defer w.close()
var taskCh = make(chan struct{}, 3)
@@ -444,7 +522,7 @@ func TestAdjustIntervalClique(t *testing.T) {
func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
defer engine.Close()
- w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0)
+ w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0, false)
defer w.close()
w.skipSealHook = func(task *task) bool {
diff --git a/params/config.go b/params/config.go
index aee5b7e1c855..0a34c4b27415 100644
--- a/params/config.go
+++ b/params/config.go
@@ -76,6 +76,26 @@ var (
Ethash: new(EthashConfig),
}
+ VerkleChainConfig = &ChainConfig{
+ ChainID: big.NewInt(86),
+ HomesteadBlock: big.NewInt(0),
+ DAOForkBlock: big.NewInt(0),
+ DAOForkSupport: true,
+ EIP150Block: big.NewInt(0),
+ EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ CancunBlock: big.NewInt(0),
+ Ethash: new(EthashConfig),
+ }
+
// MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network.
MainnetTrustedCheckpoint = &TrustedCheckpoint{
SectionIndex: 413,
@@ -348,6 +368,7 @@ type ChainConfig struct {
LondonBlock *big.Int `json:"londonBlock,omitempty"` // London switch block (nil = no fork, 0 = already on london)
ArrowGlacierBlock *big.Int `json:"arrowGlacierBlock,omitempty"` // Eip-4345 (bomb delay) switch block (nil = no fork, 0 = already activated)
MergeForkBlock *big.Int `json:"mergeForkBlock,omitempty"` // EIP-3675 (TheMerge) switch block (nil = no fork, 0 = already in merge proceedings)
+ CancunBlock *big.Int `json:"cancunBlock,omitempty"`
// TerminalTotalDifficulty is the amount of total difficulty reached by
// the network that triggers the consensus upgrade.
@@ -388,7 +409,7 @@ func (c *ChainConfig) String() string {
default:
engine = "unknown"
}
- return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, MergeFork: %v, Terminal TD: %v, Engine: %v}",
+ return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, MergeFork: %v, Terminal TD: %v, Cancun: %v, Engine: %v}",
c.ChainID,
c.HomesteadBlock,
c.DAOForkBlock,
@@ -406,6 +427,7 @@ func (c *ChainConfig) String() string {
c.ArrowGlacierBlock,
c.MergeForkBlock,
c.TerminalTotalDifficulty,
+ c.CancunBlock,
engine,
)
}
@@ -450,6 +472,10 @@ func (c *ChainConfig) IsMuirGlacier(num *big.Int) bool {
return isForked(c.MuirGlacierBlock, num)
}
+func (c *ChainConfig) IsCancun(num *big.Int) bool {
+ return isForked(c.CancunBlock, num)
+}
+
// IsPetersburg returns whether num is either
// - equal to or greater than the PetersburgBlock fork block,
// - OR is nil, and Constantinople is active
@@ -669,8 +695,8 @@ type Rules struct {
ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
- IsBerlin, IsLondon bool
- IsMerge bool
+ IsBerlin, IsLondon, IsCancun, bool
+ IsMerge bool
}
// Rules ensures c's ChainID is not nil.
@@ -692,5 +718,6 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool) Rules {
IsBerlin: c.IsBerlin(num),
IsLondon: c.IsLondon(num),
IsMerge: isMerge,
+ IsCancun: c.IsCancun(num),
}
}
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 5f154597a7fa..70cd72a0916f 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -157,6 +157,13 @@ const (
// up to half the consumed gas could be refunded. Redefined as 1/5th in EIP-3529
RefundQuotient uint64 = 2
RefundQuotientEIP3529 uint64 = 5
+
+ // Verkle tree EIP: costs associated to witness accesses
+ WitnessBranchReadCost = uint64(1900)
+ WitnessChunkReadCost = uint64(200)
+ WitnessBranchWriteCost = uint64(3000)
+ WitnessChunkWriteCost = uint64(500)
+ WitnessChunkFillCost = uint64(6200)
)
// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
diff --git a/tests/state_test_util.go b/tests/state_test_util.go
index 4fd3cf76b210..f8d9b93a9323 100644
--- a/tests/state_test_util.go
+++ b/tests/state_test_util.go
@@ -267,7 +267,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo
var snaps *snapshot.Tree
if snapshotter {
- snaps, _ = snapshot.New(db, sdb.TrieDB(), 1, root, false, true, false)
+ snaps, _ = snapshot.New(db, sdb.TrieDB(), 1, root, false, true, false, false)
}
statedb, _ = state.New(root, sdb, snaps)
return snaps, statedb
diff --git a/trie/database.go b/trie/database.go
index d71abeee476a..ed72b1ec9b5e 100644
--- a/trie/database.go
+++ b/trie/database.go
@@ -266,6 +266,7 @@ type Config struct {
Cache int // Memory allowance (MB) to use for caching trie nodes in memory
Journal string // Journal of clean cache to survive node restarts
Preimages bool // Flag whether the preimage of trie key is recorded
+ UseVerkle bool // Flag whether the data is stored in a verkle trie
}
// NewDatabase creates a new trie database to store ephemeral trie content before
diff --git a/trie/secure_trie.go b/trie/secure_trie.go
index 18be12d34a48..bf78bc061e00 100644
--- a/trie/secure_trie.go
+++ b/trie/secure_trie.go
@@ -217,3 +217,7 @@ func (t *SecureTrie) getSecKeyCache() map[string][]byte {
}
return t.secKeyCache
}
+
+func (t *SecureTrie) IsVerkle() bool {
+ return false
+}
diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go
new file mode 100644
index 000000000000..c2e228f89ce3
--- /dev/null
+++ b/trie/utils/verkle.go
@@ -0,0 +1,122 @@
+// Copyright 2021 go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package utils
+
+import (
+ "github.com/crate-crypto/go-ipa/bandersnatch/fr"
+ "github.com/gballet/go-verkle"
+
+ "github.com/holiman/uint256"
+)
+
+const (
+ VersionLeafKey = 0
+ BalanceLeafKey = 1
+ NonceLeafKey = 2
+ CodeKeccakLeafKey = 3
+ CodeSizeLeafKey = 4
+)
+
+var (
+ zero = uint256.NewInt(0)
+ HeaderStorageOffset = uint256.NewInt(64)
+ CodeOffset = uint256.NewInt(128)
+ MainStorageOffset = new(uint256.Int).Lsh(uint256.NewInt(256), 31)
+ VerkleNodeWidth = uint256.NewInt(256)
+ codeStorageDelta = uint256.NewInt(0).Sub(CodeOffset, HeaderStorageOffset)
+)
+
+func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte {
+ var poly [256]fr.Element
+ verkle.FromLEBytes(&poly[0], []byte{2, 64}) // little endian, 64 bytes
+ verkle.FromLEBytes(&poly[1], address[:16])
+ verkle.FromLEBytes(&poly[2], address[16:])
+ // little-endian, 32-byte aligned treeIndex
+ var index [32]byte
+ for i, b := range treeIndex.Bytes() {
+ index[len(treeIndex.Bytes())-1-i] = b
+ }
+ verkle.FromLEBytes(&poly[3], index[:16])
+ verkle.FromLEBytes(&poly[4], index[16:])
+ for i := 5; i < len(poly); i++ {
+ verkle.CopyFr(&poly[i], &verkle.FrZero)
+ }
+
+ cfg, _ := verkle.GetConfig()
+ ret := cfg.CommitToPoly(poly[:], 0)
+ retb := ret.Bytes()
+ retb[31] = subIndex
+ return retb[:]
+
+}
+
+func GetTreeKeyAccountLeaf(address []byte, leaf byte) []byte {
+ return GetTreeKey(address, zero, leaf)
+}
+
+func GetTreeKeyVersion(address []byte) []byte {
+ return GetTreeKey(address, zero, VersionLeafKey)
+}
+
+func GetTreeKeyBalance(address []byte) []byte {
+ return GetTreeKey(address, zero, BalanceLeafKey)
+}
+
+func GetTreeKeyNonce(address []byte) []byte {
+ return GetTreeKey(address, zero, NonceLeafKey)
+}
+
+func GetTreeKeyCodeKeccak(address []byte) []byte {
+ return GetTreeKey(address, zero, CodeKeccakLeafKey)
+}
+
+func GetTreeKeyCodeSize(address []byte) []byte {
+ return GetTreeKey(address, zero, CodeSizeLeafKey)
+}
+
+func GetTreeKeyCodeChunk(address []byte, chunk *uint256.Int) []byte {
+ chunkOffset := new(uint256.Int).Add(CodeOffset, chunk)
+ treeIndex := new(uint256.Int).Div(chunkOffset, VerkleNodeWidth)
+ subIndexMod := new(uint256.Int).Mod(chunkOffset, VerkleNodeWidth).Bytes()
+ var subIndex byte
+ if len(subIndexMod) != 0 {
+ subIndex = subIndexMod[0]
+ }
+ return GetTreeKey(address, treeIndex, subIndex)
+}
+
+func GetTreeKeyStorageSlot(address []byte, storageKey *uint256.Int) []byte {
+ pos := storageKey.Clone()
+ if storageKey.Cmp(codeStorageDelta) < 0 {
+ pos.Add(HeaderStorageOffset, storageKey)
+ } else {
+ pos.Add(MainStorageOffset, storageKey)
+ }
+ treeIndex := new(uint256.Int).Div(pos, VerkleNodeWidth)
+
+ // calculate the sub_index, i.e. the index in the stem tree.
+ // Because the modulus is 256, it's the last byte of treeIndex
+ subIndexMod := new(uint256.Int).Mod(pos, VerkleNodeWidth).Bytes()
+ var subIndex byte
+ if len(subIndexMod) != 0 {
+ // uint256 is broken into 4 little-endian quads,
+ // each with native endianness. Extract the least
+ // significant byte.
+ subIndex = byte(subIndexMod[0] & 0xFF)
+ }
+ return GetTreeKey(address, treeIndex, subIndex)
+}
diff --git a/trie/verkle.go b/trie/verkle.go
new file mode 100644
index 000000000000..ad73ebd08769
--- /dev/null
+++ b/trie/verkle.go
@@ -0,0 +1,362 @@
+// Copyright 2021 go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package trie
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+
+ "github.com/crate-crypto/go-ipa/bandersnatch/fr"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/trie/utils"
+ "github.com/gballet/go-verkle"
+)
+
+// VerkleTrie is a wrapper around VerkleNode that implements the trie.Trie
+// interface so that Verkle trees can be reused verbatim.
+type VerkleTrie struct {
+ root verkle.VerkleNode
+ db *Database
+}
+
+func (vt *VerkleTrie) ToDot() string {
+ return verkle.ToDot(vt.root)
+}
+
+func NewVerkleTrie(root verkle.VerkleNode, db *Database) *VerkleTrie {
+ return &VerkleTrie{
+ root: root,
+ db: db,
+ }
+}
+
+var errInvalidProof = errors.New("invalid proof")
+
+// GetKey returns the sha3 preimage of a hashed key that was previously used
+// to store a value.
+func (trie *VerkleTrie) GetKey(key []byte) []byte {
+ return key
+}
+
+// TryGet returns the value for key stored in the trie. The value bytes must
+// not be modified by the caller. If a node was not found in the database, a
+// trie.MissingNodeError is returned.
+func (trie *VerkleTrie) TryGet(key []byte) ([]byte, error) {
+ return trie.root.Get(key, trie.db.DiskDB().Get)
+}
+
+func (t *VerkleTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error {
+
+ var err error
+ if err = t.TryUpdate(utils.GetTreeKeyVersion(key), []byte{0}); err != nil {
+ return fmt.Errorf("updateStateObject (%x) error: %v", key, err)
+ }
+ var nonce [32]byte
+ binary.LittleEndian.PutUint64(nonce[:], acc.Nonce)
+ if err = t.TryUpdate(utils.GetTreeKeyNonce(key), nonce[:]); err != nil {
+ return fmt.Errorf("updateStateObject (%x) error: %v", key, err)
+ }
+ var balance [32]byte
+ bbytes := acc.Balance.Bytes()
+ if len(bbytes) > 0 {
+ for i, b := range bbytes {
+ balance[len(bbytes)-i-1] = b
+ }
+ }
+ if err = t.TryUpdate(utils.GetTreeKeyBalance(key), balance[:]); err != nil {
+ return fmt.Errorf("updateStateObject (%x) error: %v", key, err)
+ }
+ if err = t.TryUpdate(utils.GetTreeKeyCodeKeccak(key), acc.CodeHash); err != nil {
+ return fmt.Errorf("updateStateObject (%x) error: %v", key, err)
+ }
+
+ return nil
+}
+
+// TryUpdate associates key with value in the trie. If value has length zero, any
+// existing value is deleted from the trie. The value bytes must not be modified
+// by the caller while they are stored in the trie. If a node was not found in the
+// database, a trie.MissingNodeError is returned.
+func (trie *VerkleTrie) TryUpdate(key, value []byte) error {
+ return trie.root.Insert(key, value, func(h []byte) ([]byte, error) {
+ return trie.db.DiskDB().Get(h)
+ })
+}
+
+// TryDelete removes any existing value for key from the trie. If a node was not
+// found in the database, a trie.MissingNodeError is returned.
+func (trie *VerkleTrie) TryDelete(key []byte) error {
+ return trie.root.Delete(key)
+}
+
+// Hash returns the root hash of the trie. It does not write to the database and
+// can be used even if the trie doesn't have one.
+func (trie *VerkleTrie) Hash() common.Hash {
+ return trie.root.ComputeCommitment().Bytes()
+}
+
+func nodeToDBKey(n verkle.VerkleNode) []byte {
+ ret := n.ComputeCommitment().Bytes()
+ return ret[:]
+}
+
+// Commit writes all nodes to the trie's memory database, tracking the internal
+// and external (for account tries) references.
+func (trie *VerkleTrie) Commit(onleaf LeafCallback) (common.Hash, int, error) {
+ flush := make(chan verkle.VerkleNode)
+ go func() {
+ trie.root.(*verkle.InternalNode).Flush(func(n verkle.VerkleNode) {
+ if onleaf != nil {
+ if leaf, isLeaf := n.(*verkle.LeafNode); isLeaf {
+ for i := 0; i < verkle.NodeWidth; i++ {
+ if leaf.Value(i) != nil {
+ comm := n.ComputeCommitment().Bytes()
+ onleaf(nil, nil, leaf.Value(i), common.BytesToHash(comm[:]))
+ }
+ }
+ }
+ }
+ flush <- n
+ })
+ close(flush)
+ }()
+ var commitCount int
+ for n := range flush {
+ commitCount += 1
+ value, err := n.Serialize()
+ if err != nil {
+ panic(err)
+ }
+
+ if err := trie.db.DiskDB().Put(nodeToDBKey(n), value); err != nil {
+ return common.Hash{}, commitCount, err
+ }
+ }
+
+ return trie.Hash(), commitCount, nil
+}
+
+// NodeIterator returns an iterator that returns nodes of the trie. Iteration
+// starts at the key after the given start key.
+func (trie *VerkleTrie) NodeIterator(startKey []byte) NodeIterator {
+ return newVerkleNodeIterator(trie, nil)
+}
+
+// Prove constructs a Merkle proof for key. The result contains all encoded nodes
+// on the path to the value at key. The value itself is also included in the last
+// node and can be retrieved by verifying the proof.
+//
+// If the trie does not contain a value for key, the returned proof contains all
+// nodes of the longest existing prefix of the key (at least the root), ending
+// with the node that proves the absence of the key.
+func (trie *VerkleTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
+ panic("not implemented")
+}
+
+func (trie *VerkleTrie) Copy(db *Database) *VerkleTrie {
+ return &VerkleTrie{
+ root: trie.root.Copy(),
+ db: db,
+ }
+}
+func (trie *VerkleTrie) IsVerkle() bool {
+ return true
+}
+
+func (trie *VerkleTrie) ProveAndSerialize(keys [][]byte, kv map[string][]byte) ([]byte, []verkle.KeyValuePair, error) {
+ proof, _, _, _ := verkle.MakeVerkleMultiProof(trie.root, keys, kv)
+ p, kvps, err := verkle.SerializeProof(proof)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return p, kvps, nil
+}
+
+type set = map[string]struct{}
+
+func hasKey(s set, key []byte) bool {
+ _, ok := s[string(key)]
+ return ok
+}
+
+func addKey(s set, key []byte) {
+ s[string(key)] = struct{}{}
+}
+
+func DeserializeAndVerifyVerkleProof(serialized []byte) (map[common.Hash]common.Hash, error) {
+ proof, cis, indices, yis, leaves, err := deserializeVerkleProof(serialized)
+ if err != nil {
+ return nil, fmt.Errorf("could not deserialize proof: %w", err)
+ }
+ cfg, err := verkle.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+ if !verkle.VerifyVerkleProof(proof, cis, indices, yis, cfg) {
+ return nil, errInvalidProof
+ }
+
+ return leaves, nil
+}
+
+func deserializeVerkleProof(serialized []byte) (*verkle.Proof, []*verkle.Point, []byte, []*verkle.Fr, map[common.Hash]common.Hash, error) {
+ var (
+ indices []byte // List of zis
+ yis []*verkle.Fr // List of yis
+ seenIdx, seenComm set // Mark when a zi/yi has already been seen in deserialization
+ others set // Mark when an "other" stem has been seen
+ )
+
+ proof, err := verkle.DeserializeProof(serialized)
+ if err != nil {
+ return nil, nil, nil, nil, nil, fmt.Errorf("verkle proof deserialization error: %w", err)
+ }
+
+ for _, stem := range proof.PoaStems {
+ addKey(others, stem)
+ }
+
+ keyvals := make(map[common.Hash]common.Hash)
+ for i, key := range proof.Keys {
+ keyvals[common.BytesToHash(key)] = common.BytesToHash(proof.Values[i])
+ }
+
+ if len(proof.Keys) != len(proof.Values) {
+ return nil, nil, nil, nil, nil, fmt.Errorf("keys and values are of different length %d != %d", len(proof.Keys), len(proof.Values))
+ }
+ if len(proof.Keys) != len(proof.ExtStatus) {
+ return nil, nil, nil, nil, nil, fmt.Errorf("keys and values are of different length %d != %d", len(proof.Keys), len(proof.Values))
+ }
+
+ // Rebuild the tree, creating nodes in the lexicographic order of their path
+ lastcomm, lastpoa := 0, 0
+ root := verkle.NewStateless()
+ for i, es := range proof.ExtStatus {
+ depth := es & 0x1F
+ status := es >> 5
+ node := root
+ stem := proof.Keys[i]
+
+ // go over the stem's bytes, in order to rebuild the internal nodes
+ for j := byte(0); j < depth; j++ {
+ // Recurse into the tree that is being rebuilt
+ if node.Children()[stem[j]] == nil {
+ node.SetChild(int(stem[j]), verkle.NewStatelessWithCommitment(proof.Cs[lastcomm]))
+ lastcomm++
+ }
+
+ node = node.Children()[stem[j]].(*verkle.StatelessNode)
+
+ // if that zi hasn't been encountered yet, add it to
+ // the list of zis sorted by path.
+ if !hasKey(seenIdx, stem[:j]) {
+ addKey(seenIdx, stem[:j])
+ indices = append(indices, stem[j])
+ }
+
+ // same thing with a yi
+ if !hasKey(seenComm, stem[:j]) {
+ addKey(seenComm, stem[:j])
+ var yi fr.Element
+ bytes := node.ComputeCommitment().Bytes()
+ yi.SetBytesLE(bytes[:])
+ yis = append(yis, &yi)
+ }
+ }
+
+ // Reached the end, add the extension-and-suffix tree
+ switch status {
+ case 0:
+ // missing stem, leave it as is
+ break
+ case 1:
+ // another stem is found, build it
+ node.SetStem(proof.PoaStems[lastpoa])
+ lastpoa++
+ break
+ case 2:
+ // stem is present
+ node.SetStem(stem[:31])
+ break
+ default:
+ return nil, nil, nil, nil, nil, fmt.Errorf("verkle proof deserialization error: invalid extension status %d", status)
+ }
+
+ }
+
+ return proof, proof.Cs, indices, yis, keyvals, nil
+}
+
+// Copy the values here so as to avoid an import cycle
+const (
+ PUSH1 = 0x60
+ PUSH32 = 0x7f
+)
+
+func ChunkifyCode(code []byte) ([][32]byte, error) {
+ var (
+ chunkOffset = 0 // offset in the chunk
+ chunkCount = len(code) / 31
+ codeOffset = 0 // offset in the code
+ )
+ if len(code)%31 != 0 {
+ chunkCount++
+ }
+ chunks := make([][32]byte, chunkCount)
+ for i := range chunks {
+ // number of bytes to copy, 31 unless
+ // the end of the code has been reached.
+ end := 31 * (i + 1)
+ if len(code) < end {
+ end = len(code)
+ }
+
+ // Copy the code itself
+ copy(chunks[i][1:], code[31*i:end])
+
+ // chunk offset = taken from the
+ // last chunk.
+ if chunkOffset > 31 {
+ // skip offset calculation if push
+ // data covers the whole chunk
+ chunks[i][0] = 31
+ chunkOffset = 1
+ continue
+ }
+ chunks[i][0] = byte(chunkOffset)
+ chunkOffset = 0
+
+ // Check each instruction and update the offset
+ // it should be 0 unless a PUSHn overflows.
+ for ; codeOffset < end; codeOffset++ {
+ if code[codeOffset] >= PUSH1 && code[codeOffset] <= PUSH32 {
+ codeOffset += int(code[codeOffset]) - PUSH1 + 1
+ if codeOffset+1 >= 31*(i+1) {
+ codeOffset++
+ chunkOffset = codeOffset - 31*(i+1)
+ break
+ }
+ }
+ }
+ }
+
+ return chunks, nil
+}
diff --git a/trie/verkle_iterator.go b/trie/verkle_iterator.go
new file mode 100644
index 000000000000..dbf919e8d248
--- /dev/null
+++ b/trie/verkle_iterator.go
@@ -0,0 +1,247 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package trie
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethdb"
+
+ "github.com/gballet/go-verkle"
+)
+
+type verkleNodeIteratorState struct {
+ Node verkle.VerkleNode
+ Index int
+}
+
+type verkleNodeIterator struct {
+ trie *VerkleTrie
+ current verkle.VerkleNode
+ lastErr error
+
+ stack []verkleNodeIteratorState
+}
+
+func newVerkleNodeIterator(trie *VerkleTrie, start []byte) NodeIterator {
+ if trie.Hash() == emptyState {
+ return new(nodeIterator)
+ }
+ it := &verkleNodeIterator{trie: trie, current: trie.root}
+ //it.err = it.seek(start)
+ return it
+}
+
+// Next moves the iterator to the next node. If the parameter is false, any child
+// nodes will be skipped.
+func (it *verkleNodeIterator) Next(descend bool) bool {
+ if it.lastErr == errIteratorEnd {
+ it.lastErr = errIteratorEnd
+ return false
+ }
+
+ if len(it.stack) == 0 {
+ it.stack = append(it.stack, verkleNodeIteratorState{Node: it.trie.root, Index: 0})
+ it.current = it.trie.root
+
+ return true
+ }
+
+ switch node := it.current.(type) {
+ case *verkle.InternalNode:
+ context := &it.stack[len(it.stack)-1]
+
+ // Look for the next non-empty child
+ children := node.Children()
+ for ; context.Index < len(children); context.Index++ {
+ if _, ok := children[context.Index].(verkle.Empty); !ok {
+ it.stack = append(it.stack, verkleNodeIteratorState{Node: children[context.Index], Index: 0})
+ it.current = children[context.Index]
+ return it.Next(descend)
+ }
+ }
+
+ // Reached the end of this node, go back to the parent, if
+ // this isn't root.
+ if len(it.stack) == 1 {
+ it.lastErr = errIteratorEnd
+ return false
+ }
+ it.stack = it.stack[:len(it.stack)-1]
+ it.current = it.stack[len(it.stack)-1].Node
+ it.stack[len(it.stack)-1].Index++
+ return it.Next(descend)
+ case *verkle.LeafNode:
+ // Look for the next non-empty value
+ for i := it.stack[len(it.stack)-1].Index; i < 256; i++ {
+ if node.Value(i) != nil {
+ it.stack[len(it.stack)-1].Index = i + 1
+ return true
+ }
+ }
+
+ // go back to parent to get the next leaf
+ it.stack = it.stack[:len(it.stack)-1]
+ it.current = it.stack[len(it.stack)-1].Node
+ it.stack[len(it.stack)-1].Index++
+ return it.Next(descend)
+ case *verkle.HashedNode:
+ // resolve the node
+ data, err := it.trie.db.diskdb.Get(nodeToDBKey(node))
+ if err != nil {
+ panic(err)
+ }
+ it.current, err = verkle.ParseNode(data, byte(len(it.stack)-1))
+ if err != nil {
+ panic(err)
+ }
+
+ // update the stack and parent with the resolved node
+ it.stack[len(it.stack)-1].Node = it.current
+ parent := &it.stack[len(it.stack)-2]
+ parent.Node.(*verkle.InternalNode).SetChild(parent.Index, it.current)
+ return true
+ default:
+ panic("invalid node type")
+ }
+}
+
+// Error returns the error status of the iterator.
+func (it *verkleNodeIterator) Error() error {
+ if it.lastErr == errIteratorEnd {
+ return nil
+ }
+ return it.lastErr
+}
+
+// Hash returns the hash of the current node.
+func (it *verkleNodeIterator) Hash() common.Hash {
+ return it.current.ComputeCommitment().Bytes()
+}
+
+// Parent returns the hash of the parent of the current node. The hash may be the one
+// grandparent if the immediate parent is an internal node with no hash.
+func (it *verkleNodeIterator) Parent() common.Hash {
+ return it.stack[len(it.stack)-1].Node.ComputeCommitment().Bytes()
+}
+
+// Path returns the hex-encoded path to the current node.
+// Callers must not retain references to the return value after calling Next.
+// For leaf nodes, the last element of the path is the 'terminator symbol' 0x10.
+func (it *verkleNodeIterator) Path() []byte {
+
+ panic("not completely implemented")
+}
+
+// Leaf returns true iff the current node is a leaf node.
+func (it *verkleNodeIterator) Leaf() bool {
+ _, ok := it.current.(*verkle.LeafNode)
+ return ok
+}
+
+// LeafKey returns the key of the leaf. The method panics if the iterator is not
+// positioned at a leaf. Callers must not retain references to the value after
+// calling Next.
+func (it *verkleNodeIterator) LeafKey() []byte {
+ leaf, ok := it.current.(*verkle.LeafNode)
+ if !ok {
+ panic("Leaf() called on an verkle node iterator not at a leaf location")
+ }
+
+ return leaf.Key(it.stack[len(it.stack)-1].Index - 1)
+}
+
+// LeafBlob returns the content of the leaf. The method panics if the iterator
+// is not positioned at a leaf. Callers must not retain references to the value
+// after calling Next.
+func (it *verkleNodeIterator) LeafBlob() []byte {
+ leaf, ok := it.current.(*verkle.LeafNode)
+ if !ok {
+ panic("LeafBlob() called on an verkle node iterator not at a leaf location")
+ }
+
+ return leaf.Value(it.stack[len(it.stack)-1].Index - 1)
+}
+
+// LeafProof returns the Merkle proof of the leaf. The method panics if the
+// iterator is not positioned at a leaf. Callers must not retain references
+// to the value after calling Next.
+func (it *verkleNodeIterator) LeafProof() [][]byte {
+ _, ok := it.current.(*verkle.LeafNode)
+ if !ok {
+ panic("LeafProof() called on an verkle node iterator not at a leaf location")
+ }
+
+ //return it.trie.Prove(leaf.Key())
+ panic("not completely implemented")
+}
+
+// AddResolver sets an intermediate database to use for looking up trie nodes
+// before reaching into the real persistent layer.
+//
+// This is not required for normal operation, rather is an optimization for
+// cases where trie nodes can be recovered from some external mechanism without
+// reading from disk. In those cases, this resolver allows short circuiting
+// accesses and returning them from memory.
+//
+// Before adding a similar mechanism to any other place in Geth, consider
+// making trie.Database an interface and wrapping at that level. It's a huge
+// refactor, but it could be worth it if another occurrence arises.
+func (it *verkleNodeIterator) AddResolver(ethdb.KeyValueStore) {
+ panic("not completely implemented")
+}
+
+type dummy struct{}
+
+func (it dummy) Next(descend bool) bool {
+ return false
+}
+
+func (it dummy) Error() error {
+ return nil
+}
+
+func (it dummy) Hash() common.Hash {
+ panic("should not be called")
+}
+
+func (it dummy) Leaf() bool {
+ return false
+}
+
+func (it dummy) LeafKey() []byte {
+ return nil
+}
+
+func (it dummy) LeafProof() [][]byte {
+ return nil
+}
+
+func (it dummy) LeafBlob() []byte {
+ return nil
+}
+
+func (it dummy) Parent() common.Hash {
+ return common.Hash{}
+}
+
+func (it dummy) Path() []byte {
+ return nil
+}
+
+func (it dummy) AddResolver(ethdb.KeyValueStore) {
+ panic("not completely implemented")
+}
diff --git a/trie/verkle_test.go b/trie/verkle_test.go
new file mode 100644
index 000000000000..de77bcc1db73
--- /dev/null
+++ b/trie/verkle_test.go
@@ -0,0 +1,257 @@
+// Copyright 2021 go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package trie
+
+import (
+ "encoding/hex"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/gballet/go-verkle"
+)
+
+func TestReproduceTree(t *testing.T) {
+ presentKeys := [][]byte{
+ common.Hex2Bytes("318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d01"),
+ common.Hex2Bytes("e6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526400"),
+ common.Hex2Bytes("18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a02"),
+ common.Hex2Bytes("318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d02"),
+ common.Hex2Bytes("18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a04"),
+ common.Hex2Bytes("e6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526402"),
+ common.Hex2Bytes("e6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526403"),
+ common.Hex2Bytes("18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a00"),
+ common.Hex2Bytes("18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a03"),
+ common.Hex2Bytes("e6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526401"),
+ common.Hex2Bytes("e6ed6c222e3985050b4fc574b136b0a42c63538e9ab970995cd418ba8e526404"),
+ common.Hex2Bytes("318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d00"),
+ common.Hex2Bytes("18fb432d3b859ec3a1803854e8cceea75d092e52d0d4a4398d13022496745a01"),
+ }
+
+ absentKeys := [][]byte{
+ common.Hex2Bytes("318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d03"),
+ common.Hex2Bytes("318dea512b6f3237a2d4763cf49bf26de3b617fb0cabe38a97807a5549df4d04"),
+ }
+
+ values := [][]byte{
+ common.Hex2Bytes("320122e8584be00d000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("0300000000000000000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"),
+ common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"),
+ common.Hex2Bytes("1bc176f2790c91e6000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000"),
+ common.Hex2Bytes("e703000000000000000000000000000000000000000000000000000000000000"),
+ }
+
+ root := verkle.New()
+ kv := make(map[string][]byte)
+
+ for i, key := range presentKeys {
+ root.Insert(key, values[i], nil)
+ kv[string(key)] = values[i]
+ }
+
+ proof, Cs, zis, yis := verkle.MakeVerkleMultiProof(root, append(presentKeys, absentKeys...), kv)
+ cfg, _ := verkle.GetConfig()
+ if !verkle.VerifyVerkleProof(proof, Cs, zis, yis, cfg) {
+ t.Fatal("could not verify proof")
+ }
+
+ t.Log("commitments returned by proof:")
+ for i, c := range Cs {
+ t.Logf("%d %x", i, c.Bytes())
+ }
+
+ p, _, err := verkle.SerializeProof(proof)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("serialized: %x", p)
+ t.Logf("tree: %s\n%x\n", verkle.ToDot(root), root.ComputeCommitment().Bytes())
+}
+
+func TestChunkifyCodeTestnet(t *testing.T) {
+ code, _ := hex.DecodeString("6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea264697066735822122000382db0489577c1646ea2147a05f92f13f32336a32f1f82c6fb10b63e19f04064736f6c63430008070033")
+ chunks, err := ChunkifyCode(code)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(chunks) != (len(code)+30)/31 {
+ t.Fatalf("invalid length %d", len(chunks))
+ }
+ if chunks[0][0] != 0 {
+ t.Fatalf("invalid offset in first chunk %d != 0", chunks[0][0])
+ }
+ t.Logf("%x\n", chunks[0])
+ for i, chunk := range chunks[1:] {
+ if chunk[0] != 0 && i != 4 {
+ t.Fatalf("invalid offset in chunk #%d %d != 0", i+1, chunk[0])
+ }
+ if i == 4 && chunk[0] != 12 {
+ t.Fatalf("invalid offset in chunk #%d %d != 0", i+1, chunk[0])
+ }
+ }
+ t.Logf("code=%x, chunks=%x\n", code, chunks)
+
+ code, _ = hex.DecodeString("608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220d8add45a339f741a94b4fe7f22e101b560dc8a5874cbd957a884d8c9239df86264736f6c63430008070033")
+ chunks, err = ChunkifyCode(code)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(chunks) != (len(code)+30)/31 {
+ t.Fatalf("invalid length %d", len(chunks))
+ }
+ if chunks[0][0] != 0 {
+ t.Fatalf("invalid offset in first chunk %d != 0", chunks[0][0])
+ }
+ t.Logf("%x\n", chunks[0])
+ expected := []byte{0, 1, 0, 13, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3}
+ for i, chunk := range chunks[1:] {
+ if chunk[0] != expected[i] {
+ t.Fatalf("invalid offset in chunk #%d %d != %d", i+1, chunk[0], expected[i])
+ }
+ }
+ t.Logf("code=%x, chunks=%x\n", code, chunks)
+
+ code, _ = hex.DecodeString("6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea2646970667358221220163c79eab5630c3dbe22f7cc7692da08575198dda76698ae8ee2e3bfe62af3de64736f6c63430008070033")
+ chunks, err = ChunkifyCode(code)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(chunks) != (len(code)+30)/31 {
+ t.Fatalf("invalid length %d", len(chunks))
+ }
+ if chunks[0][0] != 0 {
+ t.Fatalf("invalid offset in first chunk %d != 0", chunks[0][0])
+ }
+ expected = []byte{0, 0, 0, 0, 13}
+ for i, chunk := range chunks[1:] {
+ if chunk[0] != expected[i] {
+ t.Fatalf("invalid offset in chunk #%d %d != %d", i+1, chunk[0], expected[i])
+ }
+ }
+ t.Logf("code=%x, chunks=%x\n", code, chunks)
+}
+
+func TestChunkifyCodeSimple(t *testing.T) {
+ code := []byte{
+ 0, byte(vm.PUSH4), 1, 2, 3, 4, byte(vm.PUSH3), 58, 68, 12, byte(vm.PUSH21), 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+ // Second 31 bytes
+ 0, byte(vm.PUSH21), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ byte(vm.PUSH7), 1, 2, 3, 4, 5, 6, 7,
+ // Third 31 bytes
+ byte(vm.PUSH30), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, 26, 27, 28, 29, 30,
+ }
+ t.Logf("code=%x", code)
+ chunks, err := ChunkifyCode(code)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(chunks) != 3 {
+ t.Fatalf("invalid length %d", len(chunks))
+ }
+ if chunks[0][0] != 0 {
+ t.Fatalf("invalid offset in first chunk %d != 0", chunks[0][0])
+ }
+ if chunks[1][0] != 1 {
+ t.Fatalf("invalid offset in second chunk %d != 1, chunk=%x", chunks[1][0], chunks[1])
+ }
+ if chunks[2][0] != 0 {
+ t.Fatalf("invalid offset in third chunk %d != 0", chunks[2][0])
+ }
+ t.Logf("code=%x, chunks=%x\n", code, chunks)
+}
+
+func TestChunkifyCodeFuzz(t *testing.T) {
+ code := []byte{
+ 3, PUSH32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ }
+ chunks, err := ChunkifyCode(code)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(chunks) != 1 {
+ t.Fatalf("invalid length %d", len(chunks))
+ }
+ if chunks[0][0] != 0 {
+ t.Fatalf("invalid offset in first chunk %d != 0", chunks[0][0])
+ }
+ t.Logf("code=%x, chunks=%x\n", code, chunks)
+
+ code = []byte{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, PUSH32,
+ }
+ chunks, err = ChunkifyCode(code)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(chunks) != 1 {
+ t.Fatalf("invalid length %d", len(chunks))
+ }
+ if chunks[0][0] != 0 {
+ t.Fatalf("invalid offset in first chunk %d != 0", chunks[0][0])
+ }
+ t.Logf("code=%x, chunks=%x\n", code, chunks)
+
+ code = []byte{
+ byte(vm.PUSH4), PUSH32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }
+ chunks, err = ChunkifyCode(code)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(chunks) != 2 {
+ t.Fatalf("invalid length %d", len(chunks))
+ }
+ if chunks[0][0] != 0 {
+ t.Fatalf("invalid offset in first chunk %d != 0", chunks[0][0])
+ }
+ if chunks[1][0] != 0 {
+ t.Fatalf("invalid offset in second chunk %d != 0, chunk=%x", chunks[1][0], chunks[1])
+ }
+ t.Logf("code=%x, chunks=%x\n", code, chunks)
+
+ code = []byte{
+ byte(vm.PUSH4), PUSH32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ }
+ chunks, err = ChunkifyCode(code)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(chunks) != 2 {
+ t.Fatalf("invalid length %d", len(chunks))
+ }
+ if chunks[0][0] != 0 {
+ t.Fatalf("invalid offset in first chunk %d != 0", chunks[0][0])
+ }
+ if chunks[1][0] != 0 {
+ t.Fatalf("invalid offset in second chunk %d != 0, chunk=%x", chunks[1][0], chunks[1])
+ }
+ t.Logf("code=%x, chunks=%x\n", code, chunks)
+}