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) +}