From 162780515aff8bf38c883c9e5879c678ab8b0242 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Thu, 6 May 2021 18:58:39 +0200 Subject: [PATCH] all: implement EIP-compliant verkle trees verkle: Implement Trie, NodeIterator and Database ifs Fix crash in TestDump Fix TestDump Fix TrieCopy remove unnecessary traces fix: Error() returned errIteratorEnd in verkle node iterator rewrite the iterator and change the signature of OpenStorageTrie add the adapter to reuse the account trie for storage don't try to deserialize a storage leaf into an account Fix statedb unit tests (#14) * debug code * Fix more unit tests * remove traces * Go back to the full range One tree to rule them all remove updateRoot, there is no root to update store code inside the account leaf fix build save current state for Sina Update go-verkle to latest Charge WITNESS_*_COST gas on storage loads Add witness costs for SSTORE as well Charge witness gas in the case of code execution corresponding code deletion add a --verkle flag to separate verkle experiments from regular geth operations use the snapshot to get data stateless execution from block witness AccessWitness functions Add block generation test + genesis snapshot generation test stateless block execution (#18) * test stateless block execution * Force tree resolution before generating the proof increased coverage in stateless test execution (#19) * test stateless block execution * Force tree resolution before generating the proof * increase coverage in stateless test execution ensure geth compiles fix issues in tests with verkle trees deactivated Ensure stateless data is available when executing statelessly (#20) * Ensure stateless data is available when executing statelessly * Actual execution of a statless block * bugfixes in stateless block execution * code cleanup - Reduce PR footprint by reverting NewEVM to its original signature - Move the access witness to the block context - prepare for a change in AW semantics Need to store the initial values. - Use the touch helper function, DRY * revert the signature of MustCommit to its original form (#21) fix leaf proofs in stateless execution (#22) * Fixes in witness pre-state * Add the recipient's nonce to the witness * reduce PR footprint and investigate issue in root state calculation * quick build fix cleanup: Remove extra parameter in ToBlock revert ToBlock to its older signature fix import cycle in vm tests fix linter issue fix appveyor build fix nil pointers in tests Add indices, yis and Cis to the block's Verkle proof upgrade geth dependency to drop geth's common dep fix cmd/devp2p tests fix rebase issues quell an appveyor warning fix address touching in SLOAD and SSTORE fix access witness for code size touch target account data before calling make sure the proper locations get touched in (ext)codecopy touch all code pages in execution add pushdata to witness remove useless code in genesis snapshot generation testnet: fix some of the rebase/drift issues Fix verkle proof generation in block fix an issue occuring when chunking past the code size fix: ensure the code copy doesn't extend past the code size --- accounts/abi/bind/bind_test.go | 2 +- cmd/geth/chaincmd.go | 5 + cmd/geth/main.go | 1 + cmd/geth/snapshot.go | 4 +- cmd/geth/usage.go | 1 + cmd/utils/flags.go | 16 ++ core/blockchain.go | 20 ++- core/blockchain_test.go | 2 +- core/chain_makers.go | 95 +++++++++++- core/evm.go | 1 + core/genesis.go | 29 +++- core/state/database.go | 75 +++++++++ core/state/iterator.go | 11 +- core/state/pruner/pruner.go | 4 +- core/state/snapshot/snapshot.go | 14 +- core/state/state_object.go | 36 ++++- core/state/statedb.go | 199 ++++++++++++++++++++++-- core/state/statedb_test.go | 7 +- core/state/sync_test.go | 5 +- core/state_processor.go | 53 ++++++- core/state_processor_test.go | 52 +++++++ core/state_transition.go | 25 ++- core/types.go | 5 +- core/types/access_witness.go | 131 ++++++++++++++++ core/types/block.go | 7 + core/vm/contract.go | 6 +- core/vm/evm.go | 19 +++ core/vm/gas_table.go | 113 +++++++++++++- core/vm/instructions.go | 128 +++++++++++++++- core/vm/interpreter.go | 49 +++++- core/vm/jump_table.go | 2 + eth/catalyst/api.go | 2 +- eth/state_accessor.go | 2 +- light/trie.go | 2 + miner/worker.go | 29 +++- params/config.go | 19 +++ params/protocol_params.go | 4 + tests/state_test_util.go | 2 +- trie/database.go | 1 + trie/secure_trie.go | 4 + trie/utils/verkle.go | 103 +++++++++++++ trie/verkle.go | 261 ++++++++++++++++++++++++++++++++ trie/verkle_iterator.go | 251 ++++++++++++++++++++++++++++++ 43 files changed, 1731 insertions(+), 66 deletions(-) create mode 100644 core/types/access_witness.go create mode 100644 trie/utils/verkle.go create mode 100644 trie/verkle.go create mode 100644 trie/verkle_iterator.go diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 5a436607ce45..7392d10ff2a7 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1983,7 +1983,7 @@ func TestGolangBindings(t *testing.T) { t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) } // Test the entire package and report any failures - cmd := exec.Command(gocmd, "test", "-v", "-count", "1") + cmd := exec.Command(gocmd, "test", "-tags=bignum_kilic", "-v", "-count", "1") cmd.Dir = pkg if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("failed to run binding test: %v\n%s", err, out) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 6077c43cc003..2f2175973682 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -49,6 +49,7 @@ var ( ArgsUsage: "", Flags: []cli.Flag{ utils.DataDirFlag, + utils.VerkleFlag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` @@ -205,6 +206,10 @@ func initGenesis(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() + if ctx.GlobalBool(utils.VerkleFlag.Name) { + genesis.Config.UseVerkle = true + } + for _, name := range []string{"chaindata", "lightchaindata"} { chaindb, err := stack.OpenDatabase(name, 0, 0, "", false) if err != nil { diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 07e870a3b0aa..8f79c1f4d46e 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -158,6 +158,7 @@ var ( utils.MinerNotifyFullFlag, configFileFlag, utils.CatalystFlag, + utils.VerkleFlag, } rpcFlags = []cli.Flag{ diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index bd2c2443a68f..6de27b7feea5 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 @@ -472,7 +472,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/cmd/geth/usage.go b/cmd/geth/usage.go index c63c62fd3820..0bcab2a81ee2 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -230,6 +230,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{ utils.BloomFilterSizeFlag, cli.HelpFlag, utils.CatalystFlag, + utils.VerkleFlag, }, }, } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ffff2c92cb84..9084cd71ebeb 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -794,6 +794,11 @@ var ( Name: "catalyst", Usage: "Catalyst mode (eth2 integration testing)", } + + VerkleFlag = cli.BoolFlag{ + Name: "verkle", + Usage: "Enable geth with verkle trees (EXPERIMENTAL)", + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1475,7 +1480,11 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags +<<<<<<< HEAD CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, SepoliaFlag) +======= + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RopstenFlag, RinkebyFlag, GoerliFlag, VerkleFlag) +>>>>>>> 00ed1c501 (all: implement EIP-compliant verkle trees) CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer if ctx.GlobalString(GCModeFlag.Name) == "archive" && ctx.GlobalUint64(TxLookupLimitFlag.Name) != 0 { @@ -1613,6 +1622,13 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } cfg.Genesis = core.DefaultGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) + case ctx.GlobalBool(VerkleFlag.Name): + if !ctx.GlobalIsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 86 // 'V' + } + cfg.Genesis = core.DefaultVerkleGenesisBlock() + cfg.Genesis.Config.UseVerkle = true + SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) case ctx.GlobalBool(RopstenFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 3 diff --git a/core/blockchain.go b/core/blockchain.go index 68388ec40a61..9dd7cf133597 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -234,6 +234,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par Cache: cacheConfig.TrieCleanLimit, Journal: cacheConfig.TrieCleanJournal, Preimages: cacheConfig.Preimages, + UseVerkle: chainConfig.UseVerkle, }), quit: make(chan struct{}), chainmu: syncx.NewClosableMutex(), @@ -375,7 +376,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, bc.Config().UseVerkle) } // Start future block processor. @@ -1592,7 +1593,22 @@ 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 + ) + if len(block.Header().VerkleProof) == 0 { + receipts, logs, _, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig) + } else { + var leaves map[common.Hash]common.Hash + _, _, _, leaves, err = trie.DeserializeAndVerifyVerkleProof(block.Header().VerkleProof) + if err != nil { + return it.index, err + } + statedb.SetStateless(leaves) + receipts, logs, usedGas, err = bc.processor.ProcessStateless(block, statedb, bc.vmConfig, leaves) + } if err != nil { bc.reportBlock(block, receipts, err) atomic.StoreUint32(&followupInterrupt, 1) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 6e542fe2f47b..a886951ee539 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -160,7 +160,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { if err != nil { return err } - receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) + receipts, _, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) if err != nil { blockchain.reportBlock(block, receipts, err) return err diff --git a/core/chain_makers.go b/core/chain_makers.go index c7bf60a4b06e..f65c54cfa3d1 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -28,6 +28,7 @@ import ( "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. @@ -43,6 +44,7 @@ type BlockGen struct { txs []*types.Transaction receipts []*types.Receipt uncles []*types.Header + witness *types.AccessWitness config *params.ChainConfig engine consensus.Engine @@ -103,10 +105,17 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { b.SetCoinbase(common.Address{}) } b.statedb.Prepare(tx.Hash(), len(b.txs)) - receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) + receipt, accesses, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) if err != nil { panic(err) } + if accesses != nil { + if b.witness != nil { + b.witness.Merge(accesses) + } else { + b.witness = accesses + } + } b.txs = append(b.txs, tx) b.receipts = append(b.receipts, receipt) } @@ -284,6 +293,90 @@ 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, witness: types.NewAccessWitness()} + b.header = makeHeader(chainreader, parent, statedb, b.engine) + + // 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 + if tr := statedb.GetTrie(); tr.IsVerkle() { + vtr := tr.(*trie.VerkleTrie) + // Generate the proof if we are using a verkle tree + // WORKAROUND: make sure all keys are resolved + // before building the proof. Ultimately, node + // resolution can be done with a prefetcher or + // from GetCommitmentsAlongPath. + keys := b.witness.Keys() + for _, key := range keys { + out, err := vtr.TryGet(key) + if err != nil { + panic(err) + } + if len(out) == 0 { + panic(fmt.Sprintf("%x should be present in the tree", key)) + } + } + vtr.Hash() + _, err := vtr.ProveAndSerialize(keys, b.witness.KeyVals()) + //block.SetVerkleProof(p) + if err != nil { + panic(err) + } + } + return block, b.receipts + } + return nil, nil + } + for i := 0; i < n; i++ { + statedb, err := state.New(parent.Root(), state.NewDatabaseWithConfig(db, &trie.Config{UseVerkle: true}), nil) + if err != nil { + panic(err) + } + block, receipt := genblock(i, parent, statedb) + blocks[i] = block + receipts[i] = receipt + parent = block + } + 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/evm.go b/core/evm.go index 6c67fc43762c..141d730cacba 100644 --- a/core/evm.go +++ b/core/evm.go @@ -69,6 +69,7 @@ func NewEVMTxContext(msg Message) vm.TxContext { return vm.TxContext{ Origin: msg.From(), GasPrice: new(big.Int).Set(msg.GasPrice()), + Accesses: types.NewAccessWitness(), } } diff --git a/core/genesis.go b/core/genesis.go index 557440d08aa1..77fc78c9a9bb 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -180,7 +180,11 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override // 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 { + var cfg *trie.Config = nil + if genesis.Config.UseVerkle { + cfg = &trie.Config{UseVerkle: true} + } + if _, err := state.New(header.Root, state.NewDatabaseWithConfig(db, cfg), nil); err != nil { if genesis == nil { genesis = DefaultGenesisBlock() } @@ -264,7 +268,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.UseVerkle} + } + statedb, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(db, trieCfg), nil) if err != nil { panic(err) } @@ -306,6 +314,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)) } @@ -357,6 +368,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..ee7d2c9a37a0 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,67 @@ 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) { + panic("need to merge #31 for this to work") +} + +// 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 37772ca35c55..016bea90f3df 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 6ee6b06bb5f2..8ddcbfb94bd2 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 138fcbdecde8..8af0f2f2e665 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -28,6 +28,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) @@ -239,9 +241,13 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has if metrics.EnabledExpensive { meter = &s.db.StorageReads } - if enc, err = s.getTrie(db).TryGet(key.Bytes()); err != nil { - s.setError(err) - return common.Hash{} + if !s.db.trie.IsVerkle() { + if enc, err = s.getTrie(db).TryGet(key.Bytes()); err != nil { + s.setError(err) + return common.Hash{} + } + } else { + panic("verkle trees use the snapshot") } } var value common.Hash @@ -332,7 +338,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)) @@ -345,12 +356,25 @@ 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, v)) + } s.db.StorageUpdated += 1 } // If state snapshotting is active, cache the data til commit diff --git a/core/state/statedb.go b/core/state/statedb.go index e3541339eaa5..94263e95df4d 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,9 @@ type StateDB struct { // Per-transaction access list accessList *accessList + // Stateless locations for this block + stateless map[common.Hash]common.Hash + // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. journal *journal @@ -144,6 +150,12 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) accessList: newAccessList(), hasher: crypto.NewKeccakState(), } + if sdb.snaps == nil && tr.IsVerkle() { + 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{}) @@ -411,6 +423,12 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) { } } +// SetStateless sets the vales recovered from the execution of a stateless +// block. +func (s *StateDB) SetStateless(leaves map[common.Hash]common.Hash) { + s.stateless = leaves +} + func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { @@ -452,6 +470,46 @@ func (s *StateDB) Suicide(addr common.Address) bool { // Setting, updating & deleting state object methods. // +func (s *StateDB) updateStatelessStateObject(obj *stateObject) { + addr := obj.Address() + + var ( + ok bool + n common.Hash + v common.Hash + b common.Hash + cs common.Hash + ch common.Hash + ) + + versionKey := common.BytesToHash(trieUtils.GetTreeKeyVersion(addr[:])) + if v, ok = s.stateless[versionKey]; ok { + nonceKey := common.BytesToHash(trieUtils.GetTreeKeyNonce(addr[:])) + if n, ok = s.stateless[nonceKey]; ok { + balanceKey := common.BytesToHash(trieUtils.GetTreeKeyBalance(addr[:])) + if b, ok = s.stateless[balanceKey]; ok { + codeHashKey := common.BytesToHash(trieUtils.GetTreeKeyCodeKeccak(addr[:])) + if _, ok = s.stateless[codeHashKey]; ok { + v[0] = byte(0) + binary.BigEndian.PutUint64(n[:], obj.data.Nonce) + copy(ch[:], obj.data.CodeHash[:]) + copy(b[:], obj.data.Balance.Bytes()) + binary.BigEndian.PutUint64(cs[:], uint64(len(obj.code))) + + // TODO(@gballet) stateless tree update + // i.e. perform a "delta" update on all + // commitments. go-verkle currently has + // no support for these. + } + } + } + } + + if !ok { + s.setError(fmt.Errorf("updateStatelessStateObject (%x) missing", addr[:])) + } +} + // updateStateObject writes the given object to the trie. func (s *StateDB) updateStateObject(obj *stateObject) { // Track the amount of time wasted on updating the account from the trie @@ -460,8 +518,22 @@ func (s *StateDB) updateStateObject(obj *stateObject) { } // Encode the account and update the account trie addr := obj.Address() + + // bypass the snapshot and writing to tree if in stateless mode + if s.stateless != nil { + s.updateStatelessStateObject(obj) + return + } + 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 len(obj.code) > 0 && s.trie.IsVerkle() { + 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 state snapshotting is active, cache the data til commit. Note, this @@ -473,16 +545,34 @@ func (s *StateDB) updateStateObject(obj *stateObject) { } } +func (s *StateDB) deleteStatelessStateObject(obj *stateObject) { + // unsupported + panic("not currently supported") +} + // deleteStateObject removes the given object from the state trie. func (s *StateDB) deleteStateObject(obj *stateObject) { // Track the amount of time wasted on deleting the account from the trie if metrics.EnabledExpensive { defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now()) } + if s.stateless != nil { + s.deleteStatelessStateObject(obj) + return + } + // 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)) + 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)) + } + } else { + for i := byte(0); i <= 255; i++ { + if err := s.trie.TryDelete(trieUtils.GetTreeKeyAccountLeaf(obj.Address().Bytes(), i)); err != nil { + s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", obj.Address(), err)) + } + } } } @@ -496,6 +586,48 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { return nil } +func (s *StateDB) getStatelessDeletedStateObject(addr common.Address) *stateObject { + // Check that it is present in the witness, if running + // in stateless execution mode. + chunk := trieUtils.GetTreeKeyNonce(addr[:]) + nb, ok := s.stateless[common.BytesToHash(chunk)] + if !ok { + log.Error("Failed to decode state object", "addr", addr) + s.setError(fmt.Errorf("could not find nonce chunk in proof: %x", chunk)) + // TODO(gballet) remove after debug, and check the issue is found + panic("inivalid chunk") + return nil + } + chunk = trieUtils.GetTreeKeyBalance(addr[:]) + bb, ok := s.stateless[common.BytesToHash(chunk)] + if !ok { + log.Error("Failed to decode state object", "addr", addr) + s.setError(fmt.Errorf("could not find balance chunk in proof: %x", chunk)) + // TODO(gballet) remove after debug, and check the issue is found + panic("inivalid chunk") + return nil + } + chunk = trieUtils.GetTreeKeyCodeKeccak(addr[:]) + cb, ok := s.stateless[common.BytesToHash(chunk)] + if !ok { + // Assume that this is an externally-owned account, and that + // the code has not been accessed. + // TODO(gballet) write this down, just like deletions, so + // that an error can be triggered if trying to access the + // account code. + copy(cb[:], emptyCodeHash) + } + data := &types.StateAccount{ + Nonce: binary.BigEndian.Uint64(nb[:8]), + Balance: big.NewInt(0).SetBytes(bb[:]), + CodeHash: cb[:], + } + // Insert into the live set + obj := newObject(s, addr, *data) + s.setStateObject(obj) + return obj +} + // getDeletedStateObject is similar to getStateObject, but instead of returning // nil for a deleted state object, it returns the actual object with the deleted // flag set. This is needed by the state journal to revert to the correct s- @@ -510,6 +642,10 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { data *types.StateAccount err error ) + // if executing statelessly, bypass the snapshot and the db. + if s.stateless != nil { + return s.getStatelessDeletedStateObject(addr) + } if s.snap != nil { if metrics.EnabledExpensive { defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now()) @@ -532,6 +668,14 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { data.Root = emptyRoot } } + + // NOTE: Do not touch the addresses here, kick the can down the + // road. That is because I don't want to change the interface + // to getDeletedStateObject at this stage, as the PR would then + // have a huge footprint. + // The alternative is to make accesses available via the state + // db instead of the evm. This requires a significant rewrite, + // that isn't currently warranted. } // If snapshot unavailable or reading from it failed, load from the database if s.snap == nil || err != nil { @@ -708,6 +852,13 @@ func (s *StateDB) Copy() *StateDB { // to not blow up if we ever decide copy it in the middle of a transaction state.accessList = s.accessList.Copy() + if s.stateless != nil { + state.stateless = make(map[common.Hash]common.Hash, len(s.stateless)) + for addr, value := range s.stateless { + state.stateless[addr] = value + } + } + // If there's a prefetcher running, make an inactive copy of it that can // only access data but does not actively preload (since the user will not // know that they need to explicitly terminate an active copy). @@ -845,7 +996,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 @@ -896,6 +1051,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 { @@ -909,17 +1078,27 @@ 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(addr, obj.code); err == nil { + for i, chunk := range chunks { + s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(uint64(i))), chunk[:]) + } + } else { + s.setError(err) + } + } else { + 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 beb8fcfd9c46..de43fdb6b3cb 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -70,7 +70,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..0310ea16902a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -56,7 +56,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { +func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, *types.AccessWitness, uint64, error) { var ( receipts types.Receipts usedGas = new(uint64) @@ -65,6 +65,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg blockNumber = block.Number() allLogs []*types.Log gp = new(GasPool).AddGas(block.GasLimit()) + accesses = types.NewAccessWitness() ) // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { @@ -73,13 +74,51 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg blockContext := NewEVMBlockContext(header, p.bc, nil) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) // Iterate over and process the individual transactions + for i, tx := range block.Transactions() { + msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee) + if err != nil { + return nil, nil, accesses, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + } + statedb.Prepare(tx.Hash(), i) + receipt, acc, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) + if err != nil { + return nil, nil, accesses, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + } + receipts = append(receipts, receipt) + allLogs = append(allLogs, receipt.Logs...) + accesses.Merge(acc) + } + // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) + p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles()) + + return receipts, allLogs, accesses, *usedGas, nil +} + +func (p *StateProcessor) ProcessStateless(block *types.Block, statedb *state.StateDB, cfg vm.Config, leaves map[common.Hash]common.Hash) (types.Receipts, []*types.Log, uint64, error) { + var ( + receipts types.Receipts + usedGas = new(uint64) + header = block.Header() + blockHash = block.Hash() + blockNumber = block.Number() + allLogs []*types.Log + gp = new(GasPool).AddGas(block.GasLimit()) + ) + // Mutate the block and state according to any hard-fork specs + if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { + misc.ApplyDAOHardFork(statedb) + } + blockContext := NewEVMBlockContext(header, p.bc, nil) + blockContext.StatelessAccesses = leaves + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) + // Iterate over and process the individual transactions for i, tx := range block.Transactions() { msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee) if err != nil { return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } statedb.Prepare(tx.Hash(), i) - receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) + receipt, _, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) if err != nil { return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } @@ -92,7 +131,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return receipts, allLogs, *usedGas, nil } -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) { +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, *types.AccessWitness, error) { // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) @@ -100,7 +139,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon // Apply the transaction to the current state (included in the env). result, err := ApplyMessage(evm, msg, gp) if err != nil { - return nil, err + return nil, txContext.Accesses, err } // Update the state with pending changes. @@ -134,17 +173,17 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon receipt.BlockHash = blockHash receipt.BlockNumber = blockNumber receipt.TransactionIndex = uint(statedb.TxIndex()) - return receipt, err + return receipt, txContext.Accesses, err } // ApplyTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { +func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, *types.AccessWitness, error) { msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee) if err != nil { - return nil, err + return nil, nil, err } // Create a new context to be used in the EVM environment blockContext := NewEVMBlockContext(header, bc, author) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index aa8e4bebf9d4..8547fbe78516 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -340,3 +340,55 @@ 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)) } + +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), + UseVerkle: true, + } + 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() + chain, _ := GenerateVerkleChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(_ int, gen *BlockGen) { + tx, _ := types.SignTx(types.NewTransaction(0, common.Address{1, 2, 3}, big.NewInt(999), params.TxGas, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + tx, _ = types.SignTx(types.NewTransaction(1, common.Address{}, big.NewInt(999), params.TxGas, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + tx, _ = types.SignTx(types.NewTransaction(2, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + + }) + + _, err := blockchain.InsertChain(chain) + if err != nil { + t.Fatalf("block imported with error: %v", err) + } +} diff --git a/core/state_transition.go b/core/state_transition.go index 135a9c6dbe85..393e620ad860 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "encoding/binary" "fmt" "math" "math/big" @@ -27,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + trieUtils "github.com/ethereum/go-ethereum/trie/utils" ) var emptyCodeHash = crypto.Keccak256Hash(nil) @@ -115,7 +117,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 { @@ -302,6 +304,27 @@ 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.TxContext.Accesses != nil { + if msg.To() != nil { + toBalance := trieUtils.GetTreeKeyBalance(msg.To().Bytes()) + pre := st.state.GetBalance(*msg.To()) + gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(toBalance, pre.Bytes()) + + // NOTE: Nonce also needs to be charged, because it is needed for execution + // on the statless side. + var preTN [8]byte + fromNonce := trieUtils.GetTreeKeyNonce(msg.To().Bytes()) + binary.BigEndian.PutUint64(preTN[:], st.state.GetNonce(*msg.To())) + gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce, preTN[:]) + } + fromBalance := trieUtils.GetTreeKeyBalance(msg.From().Bytes()) + preFB := st.state.GetBalance(msg.From()).Bytes() + fromNonce := trieUtils.GetTreeKeyNonce(msg.From().Bytes()) + var preFN [8]byte + binary.BigEndian.PutUint64(preFN[:], st.state.GetNonce(msg.From())) + gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce, preFN[:]) + gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromBalance, preFB[:]) + } st.gas -= gas // Check clause 6 diff --git a/core/types.go b/core/types.go index 4c5b74a49865..6ec0a4ac17c3 100644 --- a/core/types.go +++ b/core/types.go @@ -17,6 +17,7 @@ package core import ( + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -47,5 +48,7 @@ type Processor interface { // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. - Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) + Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, *types.AccessWitness, uint64, error) + + ProcessStateless(block *types.Block, statedb *state.StateDB, cfg vm.Config, accesses map[common.Hash]common.Hash) (types.Receipts, []*types.Log, uint64, error) } diff --git a/core/types/access_witness.go b/core/types/access_witness.go new file mode 100644 index 000000000000..6f040ea90c10 --- /dev/null +++ b/core/types/access_witness.go @@ -0,0 +1,131 @@ +// 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 ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" +) + +// 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[[31]byte]struct{} + + // Chunks contains the initial value of each address + Chunks map[common.Hash][]byte + + // The initial value isn't always available at the time an + // address is touched, this map references addresses that + // were touched but can not yet be put in Chunks. + Undefined map[common.Hash]struct{} +} + +func NewAccessWitness() *AccessWitness { + return &AccessWitness{ + Branches: make(map[[31]byte]struct{}), + Chunks: make(map[common.Hash][]byte), + Undefined: make(map[common.Hash]struct{}), + } +} + +// 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, value []byte) (bool, bool) { + var ( + stem [31]byte + newStem bool + newSelector bool + ) + copy(stem[:], addr[:31]) + + // Check for the presence of the stem + if _, newStem := aw.Branches[stem]; !newStem { + aw.Branches[stem] = struct{}{} + } + + // Check for the presence of the selector + if _, newSelector := aw.Chunks[common.BytesToHash(addr)]; !newSelector { + if value == nil { + aw.Undefined[common.BytesToHash(addr)] = struct{}{} + } else { + if _, ok := aw.Undefined[common.BytesToHash(addr)]; !ok { + delete(aw.Undefined, common.BytesToHash(addr)) + } + aw.Chunks[common.BytesToHash(addr)] = value + } + } + + return newStem, newSelector +} + +// TouchAddressAndChargeGas checks if a location has already been touched in +// the current witness, and charge extra gas if that isn't the case. This is +// meant to only be called on a tx-context access witness (i.e. before it is +// merged), not a block-context witness: witness costs are charged per tx. +func (aw *AccessWitness) TouchAddressAndChargeGas(addr, value []byte) uint64 { + var gas uint64 + + nstem, nsel := aw.TouchAddress(addr, value) + if nstem { + gas += params.WitnessBranchCost + } + if nsel { + gas += params.WitnessChunkCost + } + return gas +} + +// 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) { + // catch unresolved touched addresses + if len(other.Undefined) != 0 { + panic("undefined value in witness") + } + + for k := range other.Branches { + if _, ok := aw.Branches[k]; !ok { + aw.Branches[k] = struct{}{} + } + } + + for k, chunk := range other.Chunks { + if _, ok := aw.Chunks[k]; !ok { + aw.Chunks[k] = chunk + } + } +} + +// 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[common.Hash][]byte { + return aw.Chunks +} diff --git a/core/types/block.go b/core/types/block.go index 92e5cb77275a..33ea6f8468f0 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -86,6 +86,9 @@ 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"` + /* TODO (MariusVanDerWijden) Add this field once needed // Random was added during the merge and contains the BeaconState randomness @@ -337,6 +340,10 @@ func (b *Block) SanityCheck() error { return b.header.SanityCheck() } +func (b *Block) SetVerkleProof(vp []byte) { + b.header.VerkleProof = vp +} + type writeCounter common.StorageSize func (c *writeCounter) Write(b []byte) (int, error) { diff --git a/core/vm/contract.go b/core/vm/contract.go index bb0902969ec7..d3f069db888f 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -93,12 +93,12 @@ 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) diff --git a/core/vm/evm.go b/core/vm/evm.go index 618bbcf176cb..298f066657aa 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -17,13 +17,16 @@ package vm import ( + "encoding/binary" "math/big" "sync/atomic" "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/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" ) @@ -75,6 +78,8 @@ type BlockContext struct { Time *big.Int // Provides information for TIME Difficulty *big.Int // Provides information for DIFFICULTY BaseFee *big.Int // Provides information for BASEFEE + + StatelessAccesses map[common.Hash]common.Hash } // TxContext provides the EVM with information about a transaction. @@ -83,6 +88,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 @@ -120,6 +127,8 @@ type EVM struct { // available gas is calculated in gasCall* according to the 63/64 rule and later // applied in opCall*. callGasTemp uint64 + + accesses map[common.Hash]common.Hash } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -222,6 +231,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if len(code) == 0 { ret, err = nil, nil // gas is unchanged } else { + // Touch the account data + var data [32]byte + evm.Accesses.TouchAddress(utils.GetTreeKeyVersion(addr.Bytes()), data[:]) + binary.BigEndian.PutUint64(data[:], evm.StateDB.GetNonce(addr)) + evm.Accesses.TouchAddress(utils.GetTreeKeyNonce(addr[:]), data[:]) + evm.Accesses.TouchAddress(utils.GetTreeKeyBalance(addr[:]), evm.StateDB.GetBalance(addr).Bytes()) + binary.BigEndian.PutUint64(data[:], uint64(len(code))) + evm.Accesses.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), data[:]) + evm.Accesses.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes()) + addrCopy := addr // If the account has no code, we can abort here // The depth-check is already done, and precompiles handled above diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 19d2198af6bd..c42f0e61152d 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -22,6 +22,8 @@ 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" + "github.com/holiman/uint256" ) // memoryGasCost calculates the quadratic gas for memory expansion. It does so @@ -86,14 +88,102 @@ func memoryCopierGas(stackpos int) gasFunc { } } +func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + usedGas := uint64(0) + slot := stack.Back(0) + if evm.accesses != nil { + index := trieUtils.GetTreeKeyCodeSize(slot.Bytes()) + usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + } + + return usedGas, nil +} + 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 gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var statelessGas uint64 + if evm.accesses != nil { + var ( + codeOffset = stack.Back(1) + length = stack.Back(2) + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow() + if overflow { + uint64CodeEnd = 0xffffffffffffffff + } + addr := contract.Address() + chunk := uint64CodeOffset / 31 + endChunk := uint64CodeEnd / 31 + // XXX uint64 overflow in condition check + for ; chunk < endChunk; chunk++ { + + // TODO make a version of GetTreeKeyCodeChunk without the bigint + index := trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk)) + statelessGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + } + + } + 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.accesses != nil { + var ( + a = stack.Back(0) + codeOffset = stack.Back(2) + length = stack.Back(3) + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow() + if overflow { + uint64CodeEnd = 0xffffffffffffffff + } + addr := common.Address(a.Bytes20()) + chunk := uint64CodeOffset / 31 + endChunk := uint64CodeEnd / 31 + // XXX uint64 overflow in condition check + for ; chunk < endChunk; chunk++ { + // TODO(@gballet) make a version of GetTreeKeyCodeChunk without the bigint + index := trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk)) + statelessGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + } + + } + 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.accesses != nil { + where := stack.Back(0) + addr := contract.Address() + index := trieUtils.GetTreeKeyStorageSlot(addr[:], where) + usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + } + + 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,14 +199,15 @@ 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): // // 1. If current value equals new value (this is a no-op), 200 gas is deducted. @@ -331,6 +422,14 @@ 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.accesses != nil { + // Charge witness costs + for i := trieUtils.VersionLeafKey; i <= trieUtils.CodeSizeLeafKey; i++ { + index := trieUtils.GetTreeKeyAccountLeaf(address[:], byte(i)) + gas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + } + } + if evm.chainRules.IsEIP158 { if transfersValue && evm.StateDB.Empty(address) { gas += params.CallNewAccountGas diff --git a/core/vm/instructions.go b/core/vm/instructions.go index bda480f083d4..4eec1d3014eb 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + trieUtils "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" "golang.org/x/crypto/sha3" ) @@ -341,7 +342,12 @@ 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.accesses != nil { + index := trieUtils.GetTreeKeyCodeSize(slot.Bytes()) + interpreter.evm.TxContext.Accesses.TouchAddress(index, uint256.NewInt(cs).Bytes()) + } + slot.SetUint64(cs) return nil, nil } @@ -362,12 +368,64 @@ 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) + uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow() + if overflow { + uint64CodeEnd = 0xffffffffffffffff + } + if interpreter.evm.accesses != nil { + copyCodeFromAccesses(scope.Contract.Address(), uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope) + } else { + codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + + touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm) + } return nil, nil } +// Helper function to touch every chunk in a code range +func touchEachChunks(start, end uint64, code []byte, contract *Contract, evm *EVM) { + for chunk := start / 31; chunk <= end/31 && chunk <= uint64(len(code))/31; chunk++ { + index := trieUtils.GetTreeKeyCodeChunk(contract.Address().Bytes(), uint256.NewInt(chunk)) + count := uint64(0) + // Look for the first code byte (i.e. no pushdata) + for ; count < 31 && !contract.IsCode(chunk*31+count); count++ { + } + var value [32]byte + value[0] = byte(count) + end := (chunk + 1) * 31 + if end > uint64(len(code)) { + end = uint64(len(code)) + } + copy(value[1:], code[chunk*31:end]) + evm.Accesses.TouchAddress(index, value[:]) + } +} + +// copyCodeFromAccesses perform codecopy from the witness, not from the db. +func copyCodeFromAccesses(addr common.Address, codeOffset, codeEnd, memOffset uint64, in *EVMInterpreter, scope *ScopeContext) { + chunk := codeOffset / 31 + endChunk := codeEnd / 31 + start := codeOffset % 31 // start inside the first code chunk + offset := uint64(0) // memory offset to write to + // XXX uint64 overflow in condition check + for end := uint64(31); chunk < endChunk; chunk, start = chunk+1, 0 { + // case of the last chunk: figure out how many bytes need to + // be extracted from the last chunk. + if chunk+1 == endChunk { + end = codeEnd % 31 + } + + // TODO make a version of GetTreeKeyCodeChunk without the bigint + index := common.BytesToHash(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk))) + h := in.evm.accesses[index] + //in.evm.Accesses.TouchAddress(index.Bytes(), h[1+start:1+end]) + scope.Memory.Set(memOffset+offset, end-start, h[1+start:end]) + offset += 31 - start + } +} + func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( stack = scope.Stack @@ -380,9 +438,19 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) if overflow { uint64CodeOffset = 0xffffffffffffffff } + uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow() + if overflow { + uint64CodeEnd = 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.accesses != nil { + copyCodeFromAccesses(addr, uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope) + } else { + codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) + scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + + touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm) + } return nil, nil } @@ -510,6 +578,10 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by hash := common.Hash(loc.Bytes32()) val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) loc.SetBytes(val.Bytes()) + // Get the initial value as it might not be present + + index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc) + interpreter.evm.TxContext.Accesses.TouchAddress(index, val.Bytes()) return nil, nil } @@ -834,6 +906,25 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by *pc += 1 if *pc < codeLen { scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + // touch next chunk if PUSH1 is at the boundary. if so, *pc has + // advanced past this boundary. + if *pc%31 == 0 { + // 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)) + interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + } } else { scope.Stack.push(integer.Clear()) } @@ -859,6 +950,33 @@ func makePush(size uint64, pushByteSize int) executionFunc { scope.Stack.push(integer.SetBytes(common.RightPadBytes( scope.Contract.Code[startMin:endMin], pushByteSize))) + // touch push data by adding the last byte of the pushdata + var value [32]byte + chunk := uint64(endMin-1) / 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) + copy(value[1:], scope.Contract.Code[chunk*31:endMin]) + index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk)) + interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + + // in the case of PUSH32, the end data might be two chunks away, + // so also get the middle chunk. + if pushByteSize == 32 { + chunk = uint64(endMin-2) / 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) + copy(value[1:], scope.Contract.Code[chunk*31:(chunk+1)*31]) + index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk)) + interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + + } + *pc += size return nil, nil } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index afa3c0f12118..82fd6a367547 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -17,12 +17,15 @@ package vm import ( + "errors" "hash" "sync/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" + trieUtils "github.com/ethereum/go-ethereum/trie/utils" + "github.com/holiman/uint256" ) // Config are the configuration options for the Interpreter @@ -191,9 +194,53 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged, pcCopy, gasCopy = false, pc, contract.Gas } + // if the PC ends up in a new "page" of verkleized code, charge the + // associated witness costs. + inWitness := false + var codePage common.Hash + if in.evm.ChainConfig().UseVerkle { + index := trieUtils.GetTreeKeyCodeChunk(contract.Address().Bytes(), uint256.NewInt(pc/31)) + + var value [32]byte + if in.evm.accesses != nil { + codePage, inWitness = in.evm.accesses[common.BytesToHash(index)] + // Return an error if we're in stateless mode + // and the code isn't in the witness. It means + // that if code is read beyond the actual code + // size, pages of 0s need to be added to the + // witness. + if !inWitness { + return nil, errors.New("code chunk missing from proof") + } + copy(value[:], codePage[:]) + } else { + // Calculate the chunk + chunk := pc / 31 + end := (chunk + 1) * 31 + if end >= uint64(len(contract.Code)) { + end = uint64(len(contract.Code)) + } + count := uint64(0) + // Look for the first code byte (i.e. no pushdata) + for ; chunk*31+count < end && count < 31 && !contract.IsCode(chunk*31+count); count++ { + } + value[0] = byte(count) + copy(value[1:], contract.Code[chunk*31:end]) + } + contract.Gas -= in.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, value[:]) + } + + if inWitness { + // Get the op from the tree, skipping the header byte + op = OpCode(codePage[1+pc%31]) + } else { + // If we are in witness mode, then raise an error + op = contract.GetOp(pc) + + } + // Get the operation from the jump table and validate the stack to ensure there are // enough stack items available to perform the operation. - op = contract.GetOp(pc) operation := in.cfg.JumpTable[op] if operation == nil { return nil, &ErrInvalidOpCode{opcode: op} diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 329ad77cbf83..15250f15a8d0 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -433,6 +433,7 @@ func newFrontierInstructionSet() JumpTable { EXTCODESIZE: { execute: opExtCodeSize, constantGas: params.ExtcodeSizeGasFrontier, + dynamicGas: gasExtCodeSize, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, @@ -513,6 +514,7 @@ func newFrontierInstructionSet() JumpTable { SLOAD: { execute: opSload, constantGas: params.SloadGasFrontier, + dynamicGas: gasSLoad, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 059df3670286..fb18e020d92f 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -131,7 +131,7 @@ type blockExecutionEnv struct { func (env *blockExecutionEnv) commitTransaction(tx *types.Transaction, coinbase common.Address) error { vmconfig := *env.chain.GetVMConfig() snap := env.state.Snapshot() - receipt, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig) + receipt, _, err := core.ApplyTransaction(env.chain.Config(), env.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, vmconfig) if err != nil { env.state.RevertToSnapshot(snap) return err diff --git a/eth/state_accessor.go b/eth/state_accessor.go index f01db93a6785..d8aa78c0d40e 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -131,7 +131,7 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state if current = eth.blockchain.GetBlockByNumber(next); current == nil { return nil, fmt.Errorf("block #%d not found", next) } - _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}) + _, _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}) if err != nil { return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) } 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 54932a474deb..86a12ada2d88 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -91,6 +91,9 @@ type environment struct { header *types.Header txs []*types.Transaction receipts []*types.Receipt + + // list of all locations touched by the transactions in this block + witness *types.AccessWitness } // task contains all information for consensus engine sealing and result submitting. @@ -696,6 +699,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { family: mapset.NewSet(), uncles: mapset.NewSet(), header: header, + witness: types.NewAccessWitness(), } // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { @@ -770,18 +774,18 @@ func (w *worker) updateSnapshot() { w.snapshotState = w.current.state.Copy() } -func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { +func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, *types.AccessWitness, error) { snap := w.current.state.Snapshot() - receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig()) + receipt, accesses, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig()) if err != nil { w.current.state.RevertToSnapshot(snap) - return nil, err + return nil, accesses, err } w.current.txs = append(w.current.txs, tx) w.current.receipts = append(w.current.receipts, receipt) - return receipt.Logs, nil + return receipt.Logs, accesses, nil } func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool { @@ -844,7 +848,12 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin // Start executing the transaction w.current.state.Prepare(tx.Hash(), w.current.tcount) - logs, err := w.commitTransaction(tx, coinbase) + logs, accs, err := w.commitTransaction(tx, coinbase) + if w.current.witness == nil { + w.current.witness = accs + } else { + w.current.witness.Merge(accs) + } switch { case errors.Is(err, core.ErrGasLimitReached): // Pop the current out-of-gas transaction without shifting in the next from the account @@ -1039,7 +1048,15 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st if err != nil { return err } - + if tr := s.GetTrie(); tr.IsVerkle() { + vtr := tr.(*trie.VerkleTrie) + // Generate the proof if we are using a verkle tree + p, err := vtr.ProveAndSerialize(w.current.witness.Keys(), w.current.witness.KeyVals()) + w.current.header.VerkleProof = p + if err != nil { + return err + } + } if w.isRunning() && !w.merger.TDDReached() { if interval != nil { interval() diff --git a/params/config.go b/params/config.go index f767c1c4b92b..2f0ac139ddd1 100644 --- a/params/config.go +++ b/params/config.go @@ -75,6 +75,25 @@ 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), + Ethash: new(EthashConfig), + } + // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ SectionIndex: 413, diff --git a/params/protocol_params.go b/params/protocol_params.go index 7abb2441bf30..fc0e83a18a01 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -156,6 +156,10 @@ 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 + WitnessBranchCost = uint64(1900) + WitnessChunkCost = uint64(200) ) // 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 f7fb08bfbc8d..0be9be3446d9 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -261,7 +261,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 58ca4e6f3caa..b7c679d47930 100644 --- a/trie/database.go +++ b/trie/database.go @@ -277,6 +277,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..46de70fca289 --- /dev/null +++ b/trie/utils/verkle.go @@ -0,0 +1,103 @@ +// 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 ( + "crypto/sha256" + + "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(8) + codeStorageDelta = uint256.NewInt(0).Sub(HeaderStorageOffset, CodeOffset) +) + +func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { + digest := sha256.New() + digest.Write(address) + treeIndexBytes := treeIndex.Bytes() + var payload [32]byte + copy(payload[:len(treeIndexBytes)], treeIndexBytes) + digest.Write(payload[:]) + h := digest.Sum(nil) + h[31] = subIndex + return h +} + +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 { + treeIndex := storageKey.Clone() + if storageKey.Cmp(codeStorageDelta) < 0 { + treeIndex.Add(HeaderStorageOffset, storageKey) + } else { + treeIndex.Add(MainStorageOffset, storageKey) + } + treeIndex.Div(treeIndex, VerkleNodeWidth) + subIndexMod := new(uint256.Int).Mod(treeIndex, VerkleNodeWidth).Bytes() + var subIndex byte + if len(subIndexMod) != 0 { + subIndex = subIndexMod[0] + } + return GetTreeKey(address, treeIndex, subIndex) +} diff --git a/trie/verkle.go b/trie/verkle.go new file mode 100644 index 000000000000..2e5fdfbb95d4 --- /dev/null +++ b/trie/verkle.go @@ -0,0 +1,261 @@ +// 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/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/gballet/go-verkle" + "github.com/protolambda/go-kzg/bls" +) + +// 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.BigEndian.PutUint64(nonce[:], acc.Nonce) + if err = t.TryUpdate(utils.GetTreeKeyNonce(key), nonce[:]); err != nil { + return fmt.Errorf("updateStateObject (%x) error: %v", key, err) + } + if err = t.TryUpdate(utils.GetTreeKeyBalance(key), acc.Balance.Bytes()); 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 { + // TODO cache this value + rootC := trie.root.ComputeCommitment() + return bls.FrTo32(rootC) +} + +func nodeToDBKey(n verkle.VerkleNode) []byte { + ret := bls.FrTo32(n.ComputeCommitment()) + 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) { + 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 +} + +type KeyValuePair struct { + Key []byte + Value []byte +} + +type verkleproof struct { + D *bls.G1Point + Y *bls.Fr + Σ *bls.G1Point + + Cis []*bls.G1Point + Indices []uint + Yis []*bls.Fr + + Leaves []KeyValuePair +} + +func (trie *VerkleTrie) ProveAndSerialize(keys [][]byte, kv map[common.Hash][]byte) ([]byte, error) { + d, y, σ, cis, indices, yis := verkle.MakeVerkleMultiProof(trie.root, keys) + vp := verkleproof{ + D: d, + Y: y, + Σ: σ, + Cis: cis, + Indices: indices, + Yis: yis, + } + for key, val := range kv { + var k [32]byte + copy(k[:], key[:]) + vp.Leaves = append(vp.Leaves, KeyValuePair{ + Key: k[:], + Value: val, + }) + } + return rlp.EncodeToBytes(vp) +} + +func DeserializeAndVerifyVerkleProof(proof []byte) (*bls.G1Point, *bls.Fr, *bls.G1Point, map[common.Hash]common.Hash, error) { + d, y, σ, cis, indices, yis, leaves, err := deserializeVerkleProof(proof) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("could not deserialize proof: %w", err) + } + if !verkle.VerifyVerkleProof(d, σ, y, cis, indices, yis, verkle.GetKZGConfig()) { + return nil, nil, nil, nil, errInvalidProof + } + + return d, y, σ, leaves, nil +} + +func deserializeVerkleProof(proof []byte) (*bls.G1Point, *bls.Fr, *bls.G1Point, []*bls.G1Point, []uint, []*bls.Fr, map[common.Hash]common.Hash, error) { + var vp verkleproof + err := rlp.DecodeBytes(proof, &vp) + if err != nil { + return nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("verkle proof deserialization error: %w", err) + } + leaves := make(map[common.Hash]common.Hash, len(vp.Leaves)) + for _, kvp := range vp.Leaves { + leaves[common.BytesToHash(kvp.Key)] = common.BytesToHash(kvp.Value) + } + return vp.D, vp.Y, vp.Σ, vp.Cis, vp.Indices, vp.Yis, leaves, nil +} + +// Copy the values here so as to avoid an import cycle +const ( + PUSH1 = 0x60 + PUSH32 = 0x71 +) + +func ChunkifyCode(addr common.Address, code []byte) ([][32]byte, error) { + lastOffset := byte(0) + chunkCount := len(code) / 31 + if len(code)%31 != 0 { + chunkCount++ + } + chunks := make([][32]byte, chunkCount) + for i, chunk := range chunks { + end := 31 * (i + 1) + if len(code) < end { + end = len(code) + } + copy(chunk[1:], code[31*i:end]) + for j := lastOffset; int(j) < len(code[31*i:end]); j++ { + if code[j] >= byte(PUSH1) && code[j] <= byte(PUSH32) { + j += code[j] - byte(PUSH1) + 1 + lastOffset = (j + 1) % 31 + } + } + chunk[0] = lastOffset + } + + return chunks, nil +} diff --git a/trie/verkle_iterator.go b/trie/verkle_iterator.go new file mode 100644 index 000000000000..67cdf9c641ba --- /dev/null +++ b/trie/verkle_iterator.go @@ -0,0 +1,251 @@ +// 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 ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/protolambda/go-kzg/bls" + + //"github.com/ethereum/go-ethereum/rlp" + "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 + 1; i < 256; i++ { + if node.Value(i) != nil { + it.stack[len(it.stack)-1].Index = i + 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, 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: + fmt.Println(node) + 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 bls.FrTo32(it.current.ComputeCommitment()) +} + +// 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 bls.FrTo32(it.stack[len(it.stack)-1].Node.ComputeCommitment()) +} + +// 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) +} + +// 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) +} + +// 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") +}