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 16c3be53bd2e..3f1082bc223c 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -156,6 +156,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 38f690f17576..a0fa632a3d6b 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -229,6 +229,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 52554fbe5f3d..2d2907693eef 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -784,6 +784,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 @@ -1465,7 +1470,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 { @@ -1603,6 +1612,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 ff372870dd75..da67364eb6d8 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -236,6 +236,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(), @@ -377,7 +378,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. @@ -1607,7 +1608,22 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er // 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 80d07eb30ab0..f0c0ce3d76a7 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -157,7 +157,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 b113c0d1be9d..fe407311c44e 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) } @@ -250,6 +259,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 37cc96fe6bdf..d67524635fcb 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() } @@ -261,7 +265,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) } @@ -303,6 +311,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)) } @@ -354,6 +365,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 360f1eb47c2b..85d524b1dd1e 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -85,6 +85,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"` } // field type overrides for gencodec @@ -331,6 +334,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 61dbd5007adb..5166e3e29105 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 4315750baa6e..d3453238a9dc 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 3913da757222..38c7656535e9 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -79,7 +79,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 c855f01004ff..bce99f353a55 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 77e868c2bf4f..b809d707216d 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. @@ -694,6 +697,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) { @@ -768,18 +772,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 { @@ -842,7 +846,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 @@ -1037,6 +1046,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() { 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") +}