feat: Add support for L1 MPT (#1104)

* Allow using MPT

* Load leaves and compare in-memory

* Update api.go

* remove checker

* chore: auto version bump [bot]

---------

Co-authored-by: omerfirmak <omerfirmak@users.noreply.github.com>
This commit is contained in:
Ömer Faruk Irmak 2025-02-05 16:57:40 +03:00 committed by GitHub
parent 8ecaeec8b8
commit 4bdf6d096c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 872 additions and 110 deletions

View file

@ -151,6 +151,7 @@ var (
utils.ScrollAlphaFlag, utils.ScrollAlphaFlag,
utils.ScrollSepoliaFlag, utils.ScrollSepoliaFlag,
utils.ScrollFlag, utils.ScrollFlag,
utils.ScrollMPTFlag,
utils.VMEnableDebugFlag, utils.VMEnableDebugFlag,
utils.NetworkIdFlag, utils.NetworkIdFlag,
utils.EthStatsURLFlag, utils.EthStatsURLFlag,

View file

@ -50,6 +50,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.ScrollAlphaFlag, utils.ScrollAlphaFlag,
utils.ScrollSepoliaFlag, utils.ScrollSepoliaFlag,
utils.ScrollFlag, utils.ScrollFlag,
utils.ScrollMPTFlag,
utils.SyncModeFlag, utils.SyncModeFlag,
utils.ExitWhenSyncedFlag, utils.ExitWhenSyncedFlag,
utils.GCModeFlag, utils.GCModeFlag,

View file

@ -183,6 +183,10 @@ var (
Name: "scroll", Name: "scroll",
Usage: "Scroll mainnet", Usage: "Scroll mainnet",
} }
ScrollMPTFlag = cli.BoolFlag{
Name: "scroll-mpt",
Usage: "Use MPT trie for state storage",
}
DeveloperFlag = cli.BoolFlag{ DeveloperFlag = cli.BoolFlag{
Name: "dev", Name: "dev",
Usage: "Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled", Usage: "Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled",
@ -1887,12 +1891,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
stack.Config().L1Confirmations = rpc.FinalizedBlockNumber stack.Config().L1Confirmations = rpc.FinalizedBlockNumber
log.Info("Setting flag", "--l1.sync.startblock", "4038000") log.Info("Setting flag", "--l1.sync.startblock", "4038000")
stack.Config().L1DeploymentBlock = 4038000 stack.Config().L1DeploymentBlock = 4038000
// disable pruning cfg.Genesis.Config.Scroll.UseZktrie = !ctx.GlobalBool(ScrollMPTFlag.Name)
if ctx.GlobalString(GCModeFlag.Name) != GCModeArchive { if cfg.Genesis.Config.Scroll.UseZktrie {
log.Crit("Must use --gcmode=archive") // disable pruning
if ctx.GlobalString(GCModeFlag.Name) != GCModeArchive {
log.Crit("Must use --gcmode=archive")
}
log.Info("Pruning disabled")
cfg.NoPruning = true
} }
log.Info("Pruning disabled")
cfg.NoPruning = true
case ctx.GlobalBool(ScrollFlag.Name): case ctx.GlobalBool(ScrollFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) { if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 534352 cfg.NetworkId = 534352
@ -1903,12 +1910,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
stack.Config().L1Confirmations = rpc.FinalizedBlockNumber stack.Config().L1Confirmations = rpc.FinalizedBlockNumber
log.Info("Setting flag", "--l1.sync.startblock", "18306000") log.Info("Setting flag", "--l1.sync.startblock", "18306000")
stack.Config().L1DeploymentBlock = 18306000 stack.Config().L1DeploymentBlock = 18306000
// disable pruning cfg.Genesis.Config.Scroll.UseZktrie = !ctx.GlobalBool(ScrollMPTFlag.Name)
if ctx.GlobalString(GCModeFlag.Name) != GCModeArchive { if cfg.Genesis.Config.Scroll.UseZktrie {
log.Crit("Must use --gcmode=archive") // disable pruning
if ctx.GlobalString(GCModeFlag.Name) != GCModeArchive {
log.Crit("Must use --gcmode=archive")
}
log.Info("Pruning disabled")
cfg.NoPruning = true
} }
log.Info("Pruning disabled")
cfg.NoPruning = true
case ctx.GlobalBool(DeveloperFlag.Name): case ctx.GlobalBool(DeveloperFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) { if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 1337 cfg.NetworkId = 1337

View file

@ -226,7 +226,8 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
} }
// Validate the state root against the received state root and throw // Validate the state root against the received state root and throw
// an error if they don't match. // an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { shouldValidateStateRoot := v.config.Scroll.UseZktrie != v.config.IsEuclid(header.Time)
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); shouldValidateStateRoot && header.Root != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root) return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root)
} }
return nil return nil

View file

@ -1318,6 +1318,9 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
return NonStatTy, err return NonStatTy, err
} }
triedb := bc.stateCache.TrieDB() triedb := bc.stateCache.TrieDB()
if block.Root() != root {
rawdb.WriteDiskStateRoot(bc.db, block.Root(), root)
}
// If we're running an archive node, always flush // If we're running an archive node, always flush
if bc.cacheConfig.TrieDirtyDisabled { if bc.cacheConfig.TrieDirtyDisabled {
@ -1677,7 +1680,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
} }
// Enable prefetching to pull in trie node paths while processing transactions // Enable prefetching to pull in trie node paths while processing transactions
statedb.StartPrefetcher("chain") statedb.StartPrefetcher("chain", nil)
activeState = statedb activeState = statedb
// If we have a followup block, run that against the current state to pre-cache // If we have a followup block, run that against the current state to pre-cache
@ -1814,7 +1817,7 @@ func (bc *BlockChain) BuildAndWriteBlock(parentBlock *types.Block, header *types
return NonStatTy, err return NonStatTy, err
} }
statedb.StartPrefetcher("l1sync") statedb.StartPrefetcher("l1sync", nil)
defer statedb.StopPrefetcher() defer statedb.StopPrefetcher()
header.ParentHash = parentBlock.Hash() header.ParentHash = parentBlock.Hash()

View file

@ -17,7 +17,6 @@
package core package core
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -3032,15 +3031,16 @@ func TestPoseidonCodeHash(t *testing.T) {
var callCreate2Code = common.Hex2Bytes("f4754f660000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005c6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220707985753fcb6578098bb16f3709cf6d012993cba6dd3712661cf8f57bbc0d4d64736f6c6343000807003300000000") var callCreate2Code = common.Hex2Bytes("f4754f660000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005c6080604052348015600f57600080fd5b50603f80601d6000396000f3fe6080604052600080fdfea2646970667358221220707985753fcb6578098bb16f3709cf6d012993cba6dd3712661cf8f57bbc0d4d64736f6c6343000807003300000000")
var ( var (
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr1 = crypto.PubkeyToAddress(key1.PublicKey)
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} gspec = &Genesis{Config: params.TestChainConfig.Clone(), Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}}
genesis = gspec.MustCommit(db) signer = types.LatestSigner(gspec.Config)
signer = types.LatestSigner(gspec.Config) engine = ethash.NewFaker()
engine = ethash.NewFaker()
blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil)
) )
gspec.Config.Scroll.UseZktrie = true
genesis := gspec.MustCommit(db)
blockchain, _ := NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil)
defer blockchain.Stop() defer blockchain.Stop()
@ -3053,7 +3053,7 @@ func TestPoseidonCodeHash(t *testing.T) {
assert.Equal(t, common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), keccakCodeHash, "code hash mismatch") assert.Equal(t, common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), keccakCodeHash, "code hash mismatch")
// deploy contract through transaction // deploy contract through transaction
chain, receipts := GenerateChain(params.TestChainConfig, genesis, engine, db, 1, func(i int, gen *BlockGen) { chain, receipts := GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, gen *BlockGen) {
tx, _ := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, deployCode), signer, key1) tx, _ := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), 1000000, gen.header.BaseFee, deployCode), signer, key1)
gen.AddTx(tx) gen.AddTx(tx)
}) })
@ -3074,7 +3074,7 @@ func TestPoseidonCodeHash(t *testing.T) {
assert.Equal(t, common.HexToHash("0x089bfd332dfa6117cbc20756f31801ce4f5a175eb258e46bf8123317da54cd96"), keccakCodeHash, "code hash mismatch") assert.Equal(t, common.HexToHash("0x089bfd332dfa6117cbc20756f31801ce4f5a175eb258e46bf8123317da54cd96"), keccakCodeHash, "code hash mismatch")
// deploy contract through another contract (CREATE and CREATE2) // deploy contract through another contract (CREATE and CREATE2)
chain, receipts = GenerateChain(params.TestChainConfig, blockchain.CurrentBlock(), engine, db, 1, func(i int, gen *BlockGen) { chain, receipts = GenerateChain(gspec.Config, blockchain.CurrentBlock(), engine, db, 1, func(i int, gen *BlockGen) {
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), contractAddress, new(big.Int), 1000000, gen.header.BaseFee, callCreateCode), signer, key1) tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), contractAddress, new(big.Int), 1000000, gen.header.BaseFee, callCreateCode), signer, key1)
gen.AddTx(tx) gen.AddTx(tx)
@ -3718,12 +3718,11 @@ func TestTransientStorageReset(t *testing.T) {
func TestCurieTransition(t *testing.T) { func TestCurieTransition(t *testing.T) {
// Set fork blocks in config // Set fork blocks in config
// (we make a deep copy to avoid interference with other tests) // (we make a deep copy to avoid interference with other tests)
var config *params.ChainConfig config := params.AllEthashProtocolChanges.Clone()
b, _ := json.Marshal(params.AllEthashProtocolChanges)
json.Unmarshal(b, &config)
config.CurieBlock = big.NewInt(2) config.CurieBlock = big.NewInt(2)
config.DarwinTime = nil config.DarwinTime = nil
config.DarwinV2Time = nil config.DarwinV2Time = nil
config.Scroll.UseZktrie = true
var ( var (
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
@ -3748,7 +3747,7 @@ func TestCurieTransition(t *testing.T) {
number := block.Number().Uint64() number := block.Number().Uint64()
baseFee := block.BaseFee() baseFee := block.BaseFee()
statedb, _ := state.New(block.Root(), state.NewDatabase(db), nil) statedb, _ := state.New(block.Root(), state.NewDatabaseWithConfig(db, &trie.Config{Zktrie: gspec.Config.Scroll.UseZktrie}), nil)
code := statedb.GetCode(rcfg.L1GasPriceOracleAddress) code := statedb.GetCode(rcfg.L1GasPriceOracleAddress)
codeSize := statedb.GetCodeSize(rcfg.L1GasPriceOracleAddress) codeSize := statedb.GetCodeSize(rcfg.L1GasPriceOracleAddress)

View file

@ -29,6 +29,7 @@ import (
"github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/params"
"github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/rollup/fees"
"github.com/scroll-tech/go-ethereum/trie"
) )
// BlockGen creates blocks for testing. // BlockGen creates blocks for testing.
@ -220,7 +221,7 @@ func (b *BlockGen) OffsetTime(seconds int64) {
// a similar non-validating proof of work implementation. // a similar non-validating proof of work implementation.
func GenerateChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) { func GenerateChain(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 { if config == nil {
config = params.TestChainConfig config = params.TestChainConfig.Clone()
} }
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n) blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
chainreader := &fakeChainReader{config: config} chainreader := &fakeChainReader{config: config}
@ -264,7 +265,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
return nil, nil return nil, nil
} }
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), state.NewDatabase(db), nil) statedb, err := state.New(parent.Root(), state.NewDatabaseWithConfig(db, &trie.Config{Zktrie: config.Scroll.ZktrieEnabled()}), nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -322,7 +322,10 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
} }
statedb.Commit(false) statedb.Commit(false)
statedb.Database().TrieDB().Commit(root, true, nil) statedb.Database().TrieDB().Commit(root, true, nil)
if g.Config != nil && g.Config.Scroll.GenesisStateRoot != nil {
head.Root = *g.Config.Scroll.GenesisStateRoot
rawdb.WriteDiskStateRoot(db, head.Root, root)
}
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)) return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
} }

View file

@ -41,7 +41,7 @@ func TestInvalidCliqueConfig(t *testing.T) {
func TestSetupGenesis(t *testing.T) { func TestSetupGenesis(t *testing.T) {
var ( var (
customghash = common.HexToHash("0x700380ab70d789c462c4e8f0db082842095321f390d0a3f25f400f0746db32bc") customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50")
customg = Genesis{ customg = Genesis{
Config: &params.ChainConfig{HomesteadBlock: big.NewInt(3)}, Config: &params.ChainConfig{HomesteadBlock: big.NewInt(3)},
Alloc: GenesisAlloc{ Alloc: GenesisAlloc{

View file

@ -94,3 +94,17 @@ func DeleteTrieNode(db ethdb.KeyValueWriter, hash common.Hash) {
log.Crit("Failed to delete trie node", "err", err) log.Crit("Failed to delete trie node", "err", err)
} }
} }
func WriteDiskStateRoot(db ethdb.KeyValueWriter, headerRoot, diskRoot common.Hash) {
if err := db.Put(diskStateRootKey(headerRoot), diskRoot.Bytes()); err != nil {
log.Crit("Failed to store disk state root", "err", err)
}
}
func ReadDiskStateRoot(db ethdb.KeyValueReader, headerRoot common.Hash) (common.Hash, error) {
data, err := db.Get(diskStateRootKey(headerRoot))
if err != nil {
return common.Hash{}, err
}
return common.BytesToHash(data), nil
}

View file

@ -127,6 +127,8 @@ var (
// Scroll da syncer store // Scroll da syncer store
daSyncedL1BlockNumberKey = []byte("LastDASyncedL1BlockNumber") daSyncedL1BlockNumberKey = []byte("LastDASyncedL1BlockNumber")
diskStateRootPrefix = []byte("disk-state-root")
) )
// Use the updated "L1" prefix on all new networks // Use the updated "L1" prefix on all new networks
@ -312,3 +314,7 @@ func batchMetaKey(batchIndex uint64) []byte {
func committedBatchMetaKey(batchIndex uint64) []byte { func committedBatchMetaKey(batchIndex uint64) []byte {
return append(committedBatchMetaPrefix, encodeBigEndian(batchIndex)...) return append(committedBatchMetaPrefix, encodeBigEndian(batchIndex)...)
} }
func diskStateRootKey(headerRoot common.Hash) []byte {
return append(diskStateRootPrefix, headerRoot.Bytes()...)
}

View file

@ -105,6 +105,9 @@ type Trie interface {
// nodes of the longest existing prefix of the key (at least the root), ending // nodes of the longest existing prefix of the key (at least the root), ending
// with the node that proves the absence of the key. // with the node that proves the absence of the key.
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error
// Witness returns a set containing all trie nodes that have been accessed.
Witness() map[string]struct{}
} }
// NewDatabase creates a backing store for state. The returned database is safe for // NewDatabase creates a backing store for state. The returned database is safe for
@ -136,6 +139,9 @@ type cachingDB struct {
// OpenTrie opens the main account trie at a specific root hash. // OpenTrie opens the main account trie at a specific root hash.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
if diskRoot, err := rawdb.ReadDiskStateRoot(db.db.DiskDB(), root); err == nil {
root = diskRoot
}
if db.zktrie { if db.zktrie {
tr, err := trie.NewZkTrie(root, trie.NewZktrieDatabaseFromTriedb(db.db)) tr, err := trie.NewZkTrie(root, trie.NewZktrieDatabaseFromTriedb(db.db))
if err != nil { if err != nil {

View file

@ -33,8 +33,8 @@ type Account struct {
Balance *big.Int Balance *big.Int
Root []byte Root []byte
KeccakCodeHash []byte KeccakCodeHash []byte
PoseidonCodeHash []byte PoseidonCodeHash []byte `rlp:"-"`
CodeSize uint64 CodeSize uint64 `rlp:"-"`
} }
// SlimAccount converts a state.Account content into a slim snapshot account // SlimAccount converts a state.Account content into a slim snapshot account

View file

@ -618,8 +618,8 @@ func (dl *diskLayer) generate(stats *generatorStats) {
Balance *big.Int Balance *big.Int
Root common.Hash Root common.Hash
KeccakCodeHash []byte KeccakCodeHash []byte
PoseidonCodeHash []byte PoseidonCodeHash []byte `rlp:"-"`
CodeSize uint64 CodeSize uint64 `rlp:"-"`
} }
if err := rlp.DecodeBytes(val, &acc); err != nil { if err := rlp.DecodeBytes(val, &acc); err != nil {
log.Crit("Invalid account encountered during snapshot creation", "err", err) log.Crit("Invalid account encountered during snapshot creation", "err", err)

View file

@ -500,8 +500,18 @@ func (s *stateObject) Code(db Database) []byte {
// CodeSize returns the size of the contract code associated with this object, // CodeSize returns the size of the contract code associated with this object,
// or zero if none. This method is an almost mirror of Code, but uses a cache // or zero if none. This method is an almost mirror of Code, but uses a cache
// inside the database to avoid loading codes seen recently. // inside the database to avoid loading codes seen recently.
func (s *stateObject) CodeSize() uint64 { func (s *stateObject) CodeSize(db Database) uint64 {
return s.data.CodeSize if s.code != nil {
return uint64(len(s.code))
}
if bytes.Equal(s.KeccakCodeHash(), emptyKeccakCodeHash) {
return 0
}
size, err := db.ContractCodeSize(s.addrHash, common.BytesToHash(s.KeccakCodeHash()))
if err != nil {
s.setError(fmt.Errorf("can't load code size %x: %v", s.KeccakCodeHash(), err))
}
return uint64(size)
} }
func (s *stateObject) SetCode(code []byte) { func (s *stateObject) SetCode(code []byte) {
@ -534,6 +544,9 @@ func (s *stateObject) setNonce(nonce uint64) {
} }
func (s *stateObject) PoseidonCodeHash() []byte { func (s *stateObject) PoseidonCodeHash() []byte {
if !s.db.IsZktrie() {
panic("PoseidonCodeHash is only available in zktrie mode")
}
return s.data.PoseidonCodeHash return s.data.PoseidonCodeHash
} }

View file

@ -155,7 +155,8 @@ func TestSnapshotEmpty(t *testing.T) {
} }
func TestSnapshot2(t *testing.T) { func TestSnapshot2(t *testing.T) {
state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) stateDb := NewDatabase(rawdb.NewMemoryDatabase())
state, _ := New(common.Hash{}, stateDb, nil)
stateobjaddr0 := common.BytesToAddress([]byte("so0")) stateobjaddr0 := common.BytesToAddress([]byte("so0"))
stateobjaddr1 := common.BytesToAddress([]byte("so1")) stateobjaddr1 := common.BytesToAddress([]byte("so1"))
@ -201,7 +202,7 @@ func TestSnapshot2(t *testing.T) {
so0Restored.GetState(state.db, storageaddr) so0Restored.GetState(state.db, storageaddr)
so0Restored.Code(state.db) so0Restored.Code(state.db)
// non-deleted is equal (restored) // non-deleted is equal (restored)
compareStateObjects(so0Restored, so0, t) compareStateObjects(so0Restored, so0, stateDb, t)
// deleted should be nil, both before and after restore of state copy // deleted should be nil, both before and after restore of state copy
so1Restored := state.getStateObject(stateobjaddr1) so1Restored := state.getStateObject(stateobjaddr1)
@ -210,7 +211,7 @@ func TestSnapshot2(t *testing.T) {
} }
} }
func compareStateObjects(so0, so1 *stateObject, t *testing.T) { func compareStateObjects(so0, so1 *stateObject, db Database, t *testing.T) {
if so0.Address() != so1.Address() { if so0.Address() != so1.Address() {
t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address) t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address)
} }
@ -229,8 +230,8 @@ func compareStateObjects(so0, so1 *stateObject, t *testing.T) {
if !bytes.Equal(so0.PoseidonCodeHash(), so1.PoseidonCodeHash()) { if !bytes.Equal(so0.PoseidonCodeHash(), so1.PoseidonCodeHash()) {
t.Fatalf("PoseidonCodeHash mismatch: have %v, want %v", so0.PoseidonCodeHash(), so1.PoseidonCodeHash()) t.Fatalf("PoseidonCodeHash mismatch: have %v, want %v", so0.PoseidonCodeHash(), so1.PoseidonCodeHash())
} }
if so0.CodeSize() != so1.CodeSize() { if so0.CodeSize(db) != so1.CodeSize(db) {
t.Fatalf("CodeSize mismatch: have %v, want %v", so0.CodeSize(), so1.CodeSize()) t.Fatalf("CodeSize mismatch: have %v, want %v", so0.CodeSize(db), so1.CodeSize(db))
} }
if !bytes.Equal(so0.code, so1.code) { if !bytes.Equal(so0.code, so1.code) {
t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code) t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code)

View file

@ -29,6 +29,7 @@ import (
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/state/snapshot" "github.com/scroll-tech/go-ethereum/core/state/snapshot"
"github.com/scroll-tech/go-ethereum/core/stateless"
"github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/log"
@ -106,6 +107,9 @@ type StateDB struct {
validRevisions []revision validRevisions []revision
nextRevisionId int nextRevisionId int
// State witness if cross validation is needed
witness *stateless.Witness
// Measurements gathered during execution for debugging purposes // Measurements gathered during execution for debugging purposes
AccountReads time.Duration AccountReads time.Duration
AccountHashes time.Duration AccountHashes time.Duration
@ -159,11 +163,15 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the // state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot. // commit phase, most of the needed data is already hot.
func (s *StateDB) StartPrefetcher(namespace string) { func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) {
if s.prefetcher != nil { if s.prefetcher != nil {
s.prefetcher.close() s.prefetcher.close()
s.prefetcher = nil s.prefetcher = nil
} }
// Enable witness collection if requested
s.witness = witness
if s.snap != nil { if s.snap != nil {
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace) s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
} }
@ -289,6 +297,9 @@ func (s *StateDB) TxIndex() int {
func (s *StateDB) GetCode(addr common.Address) []byte { func (s *StateDB) GetCode(addr common.Address) []byte {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
if s.witness != nil {
s.witness.AddCode(stateObject.Code(s.db))
}
return stateObject.Code(s.db) return stateObject.Code(s.db)
} }
return nil return nil
@ -297,7 +308,10 @@ func (s *StateDB) GetCode(addr common.Address) []byte {
func (s *StateDB) GetCodeSize(addr common.Address) uint64 { func (s *StateDB) GetCodeSize(addr common.Address) uint64 {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)
if stateObject != nil { if stateObject != nil {
return stateObject.CodeSize() if s.witness != nil {
s.witness.AddCode(stateObject.Code(s.db))
}
return stateObject.CodeSize(s.db)
} }
return 0 return 0
} }
@ -725,6 +739,9 @@ func (s *StateDB) Copy() *StateDB {
journal: newJournal(), journal: newJournal(),
hasher: crypto.NewKeccakState(), hasher: crypto.NewKeccakState(),
} }
if s.witness != nil {
state.witness = s.witness.Copy()
}
// Copy the dirty states, logs, and preimages // Copy the dirty states, logs, and preimages
for addr := range s.journal.dirties { for addr := range s.journal.dirties {
// As documented [here](https://github.com/scroll-tech/go-ethereum/pull/16485#issuecomment-380438527), // As documented [here](https://github.com/scroll-tech/go-ethereum/pull/16485#issuecomment-380438527),
@ -913,7 +930,33 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// to pull useful data from disk. // to pull useful data from disk.
for addr := range s.stateObjectsPending { for addr := range s.stateObjectsPending {
if obj := s.stateObjects[addr]; !obj.deleted { if obj := s.stateObjects[addr]; !obj.deleted {
// If witness building is enabled and the state object has a trie,
// gather the witnesses for its specific storage trie
if s.witness != nil && obj.trie != nil {
s.witness.AddState(obj.trie.Witness())
}
obj.updateRoot(s.db) obj.updateRoot(s.db)
// If witness building is enabled and the state object has a trie,
// gather the witnesses for its specific storage trie
if s.witness != nil && obj.trie != nil {
s.witness.AddState(obj.trie.Witness())
}
}
}
if s.witness != nil {
// If witness building is enabled, gather the account trie witness for read-only operations
for _, obj := range s.stateObjects {
if len(obj.originStorage) == 0 {
continue
}
if trie := obj.getTrie(s.db); trie != nil {
s.witness.AddState(trie.Witness())
}
} }
} }
// Now we're about to start to write changes to the trie. The trie is so far // Now we're about to start to write changes to the trie. The trie is so far
@ -945,7 +988,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
if metrics.EnabledExpensive { if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
} }
return s.trie.Hash()
hash := s.trie.Hash()
// If witness building is enabled, gather the account trie witness
if s.witness != nil {
s.witness.AddState(s.trie.Witness())
}
return hash
} }
// SetTxContext sets the current transaction hash and index which are // SetTxContext sets the current transaction hash and index which are

View file

@ -0,0 +1,67 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package stateless
import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/ethdb"
)
// MakeHashDB imports tries, codes and block hashes from a witness into a new
// hash-based memory db. We could eventually rewrite this into a pathdb, but
// simple is better for now.
//
// Note, this hashdb approach is quite strictly self-validating:
// - Headers are persisted keyed by hash, so blockhash will error on junk
// - Codes are persisted keyed by hash, so bytecode lookup will error on junk
// - Trie nodes are persisted keyed by hash, so trie expansion will error on junk
//
// Acceleration structures built would need to explicitly validate the witness.
func (w *Witness) MakeHashDB() ethdb.Database {
var (
memdb = rawdb.NewMemoryDatabase()
hasher = crypto.NewKeccakState()
hash = make([]byte, 32)
)
// Inject all the "block hashes" (i.e. headers) into the ephemeral database
for _, header := range w.Headers {
rawdb.WriteHeader(memdb, header)
}
// Inject all the bytecodes into the ephemeral database
for code := range w.Codes {
blob := []byte(code)
hasher.Reset()
hasher.Write(blob)
hasher.Read(hash)
rawdb.WriteCode(memdb, common.BytesToHash(hash), blob)
}
// Inject all the MPT trie nodes into the ephemeral database
for node := range w.State {
blob := []byte(node)
hasher.Reset()
hasher.Write(blob)
hasher.Read(hash)
rawdb.WriteTrieNode(memdb, common.BytesToHash(hash), blob)
}
return memdb
}

View file

@ -0,0 +1,76 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package stateless
import (
"io"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/rlp"
)
// toExtWitness converts our internal witness representation to the consensus one.
func (w *Witness) toExtWitness() *extWitness {
ext := &extWitness{
Headers: w.Headers,
}
ext.Codes = make([][]byte, 0, len(w.Codes))
for code := range w.Codes {
ext.Codes = append(ext.Codes, []byte(code))
}
ext.State = make([][]byte, 0, len(w.State))
for node := range w.State {
ext.State = append(ext.State, []byte(node))
}
return ext
}
// fromExtWitness converts the consensus witness format into our internal one.
func (w *Witness) fromExtWitness(ext *extWitness) error {
w.Headers = ext.Headers
w.Codes = make(map[string]struct{}, len(ext.Codes))
for _, code := range ext.Codes {
w.Codes[string(code)] = struct{}{}
}
w.State = make(map[string]struct{}, len(ext.State))
for _, node := range ext.State {
w.State[string(node)] = struct{}{}
}
return nil
}
// EncodeRLP serializes a witness as RLP.
func (w *Witness) EncodeRLP(wr io.Writer) error {
return rlp.Encode(wr, w.toExtWitness())
}
// DecodeRLP decodes a witness from RLP.
func (w *Witness) DecodeRLP(s *rlp.Stream) error {
var ext extWitness
if err := s.Decode(&ext); err != nil {
return err
}
return w.fromExtWitness(&ext)
}
// extWitness is a witness RLP encoding for transferring across clients.
type extWitness struct {
Headers []*types.Header
Codes [][]byte
State [][]byte
}

122
core/stateless/witness.go Normal file
View file

@ -0,0 +1,122 @@
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
package stateless
import (
"errors"
"maps"
"slices"
"sync"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
)
// HeaderReader is an interface to pull in headers in place of block hashes for
// the witness.
type HeaderReader interface {
// GetHeader retrieves a block header from the database by hash and number,
GetHeader(hash common.Hash, number uint64) *types.Header
}
// Witness encompasses the state required to apply a set of transactions and
// derive a post state/receipt root.
type Witness struct {
context *types.Header // Header to which this witness belongs to, with rootHash and receiptHash zeroed out
Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set.
Codes map[string]struct{} // Set of bytecodes ran or accessed
State map[string]struct{} // Set of MPT state trie nodes (account and storage together)
chain HeaderReader // Chain reader to convert block hash ops to header proofs
lock sync.Mutex // Lock to allow concurrent state insertions
}
// NewWitness creates an empty witness ready for population.
func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
// When building witnesses, retrieve the parent header, which will *always*
// be included to act as a trustless pre-root hash container
var headers []*types.Header
if chain != nil {
parent := chain.GetHeader(context.ParentHash, context.Number.Uint64()-1)
if parent == nil {
return nil, errors.New("failed to retrieve parent header")
}
headers = append(headers, parent)
}
// Create the wtness with a reconstructed gutted out block
return &Witness{
context: context,
Headers: headers,
Codes: make(map[string]struct{}),
State: make(map[string]struct{}),
chain: chain,
}, nil
}
// AddBlockHash adds a "blockhash" to the witness with the designated offset from
// chain head. Under the hood, this method actually pulls in enough headers from
// the chain to cover the block being added.
func (w *Witness) AddBlockHash(number uint64) {
// Keep pulling in headers until this hash is populated
for int(w.context.Number.Uint64()-number) > len(w.Headers) {
tail := w.Headers[len(w.Headers)-1]
w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1))
}
}
// AddCode adds a bytecode blob to the witness.
func (w *Witness) AddCode(code []byte) {
if len(code) == 0 {
return
}
w.Codes[string(code)] = struct{}{}
}
// AddState inserts a batch of MPT trie nodes into the witness.
func (w *Witness) AddState(nodes map[string]struct{}) {
if len(nodes) == 0 {
return
}
w.lock.Lock()
defer w.lock.Unlock()
maps.Copy(w.State, nodes)
}
// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it
// is never mutated by Witness
func (w *Witness) Copy() *Witness {
cpy := &Witness{
Headers: slices.Clone(w.Headers),
Codes: maps.Clone(w.Codes),
State: maps.Clone(w.State),
chain: w.chain,
}
if w.context != nil {
cpy.context = types.CopyHeader(w.context)
}
return cpy
}
// Root returns the pre-state root from the first header.
//
// Note, this method will panic in case of a bad witness (but RLP decoding will
// sanitize it and fail before that).
func (w *Witness) Root() common.Hash {
return w.Headers[0].Root
}

View file

@ -31,6 +31,6 @@ type StateAccount struct {
KeccakCodeHash []byte KeccakCodeHash []byte
// StateAccount Scroll extensions // StateAccount Scroll extensions
PoseidonCodeHash []byte PoseidonCodeHash []byte `rlp:"-"`
CodeSize uint64 CodeSize uint64 `rlp:"-"`
} }

View file

@ -34,11 +34,14 @@ import (
"github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core"
"github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/stateless"
"github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/internal/ethapi" "github.com/scroll-tech/go-ethereum/internal/ethapi"
"github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rlp" "github.com/scroll-tech/go-ethereum/rlp"
"github.com/scroll-tech/go-ethereum/rollup/ccc" "github.com/scroll-tech/go-ethereum/rollup/ccc"
"github.com/scroll-tech/go-ethereum/rollup/rcfg"
"github.com/scroll-tech/go-ethereum/rpc" "github.com/scroll-tech/go-ethereum/rpc"
"github.com/scroll-tech/go-ethereum/trie" "github.com/scroll-tech/go-ethereum/trie"
) )
@ -321,6 +324,109 @@ func (api *PublicDebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error
return stateDb.RawDump(opts), nil return stateDb.RawDump(opts), nil
} }
func (api *PublicDebugAPI) ExecutionWitness(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*ExecutionWitness, error) {
block, err := api.eth.APIBackend.BlockByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return nil, fmt.Errorf("failed to retrieve block: %w", err)
}
if block == nil {
return nil, fmt.Errorf("block not found: %s", blockNrOrHash.String())
}
witness, err := generateWitness(api.eth.blockchain, block)
return ToExecutionWitness(witness), err
}
func generateWitness(blockchain *core.BlockChain, block *types.Block) (*stateless.Witness, error) {
witness, err := stateless.NewWitness(block.Header(), blockchain)
if err != nil {
return nil, fmt.Errorf("failed to create witness: %w", err)
}
parentHeader := witness.Headers[0]
statedb, err := blockchain.StateAt(parentHeader.Root)
if err != nil {
return nil, fmt.Errorf("failed to retrieve parent state: %w", err)
}
// Collect storage locations that prover needs but sequencer might not touch necessarily
statedb.GetState(rcfg.L2MessageQueueAddress, rcfg.WithdrawTrieRootSlot)
statedb.StartPrefetcher("debug_execution_witness", witness)
defer statedb.StopPrefetcher()
receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb, *blockchain.GetVMConfig())
if err != nil {
return nil, fmt.Errorf("failed to process block %d: %w", block.Number(), err)
}
if err := blockchain.Validator().ValidateState(block, statedb, receipts, usedGas); err != nil {
return nil, fmt.Errorf("failed to validate block %d: %w", block.Number(), err)
}
return witness, testWitness(blockchain, block, witness)
}
func testWitness(blockchain *core.BlockChain, block *types.Block, witness *stateless.Witness) error {
stateRoot := witness.Root()
if diskRoot, _ := rawdb.ReadDiskStateRoot(blockchain.Database(), stateRoot); diskRoot != (common.Hash{}) {
stateRoot = diskRoot
}
// Create and populate the state database to serve as the stateless backend
statedb, err := state.New(stateRoot, state.NewDatabase(witness.MakeHashDB()), nil)
if err != nil {
return fmt.Errorf("failed to create state database: %w", err)
}
receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb, *blockchain.GetVMConfig())
if err != nil {
return fmt.Errorf("failed to process block %d: %w", block.Number(), err)
}
if err := blockchain.Validator().ValidateState(block, statedb, receipts, usedGas); err != nil {
return fmt.Errorf("failed to validate block %d: %w", block.Number(), err)
}
postStateRoot := block.Root()
if diskRoot, _ := rawdb.ReadDiskStateRoot(blockchain.Database(), postStateRoot); diskRoot != (common.Hash{}) {
postStateRoot = diskRoot
}
if statedb.GetRootHash() != postStateRoot {
return fmt.Errorf("failed to commit statelessly %d: %w", block.Number(), err)
}
return nil
}
// ExecutionWitness is a witness json encoding for transferring across the network.
// In the future, we'll probably consider using the extWitness format instead for less overhead if performance becomes an issue.
// Currently using this format for ease of reading, parsing and compatibility across clients.
type ExecutionWitness struct {
Headers []*types.Header `json:"headers"`
Codes map[string]string `json:"codes"`
State map[string]string `json:"state"`
}
func transformMap(in map[string]struct{}) map[string]string {
out := make(map[string]string, len(in))
for item := range in {
bytes := []byte(item)
key := crypto.Keccak256Hash(bytes).Hex()
out[key] = hexutil.Encode(bytes)
}
return out
}
// ToExecutionWitness converts a witness to an execution witness format that is compatible with reth.
// keccak(node) => node
// keccak(bytecodes) => bytecodes
func ToExecutionWitness(w *stateless.Witness) *ExecutionWitness {
return &ExecutionWitness{
Headers: w.Headers,
Codes: transformMap(w.Codes),
State: transformMap(w.State),
}
}
// PrivateDebugAPI is the collection of Ethereum full node APIs exposed over // PrivateDebugAPI is the collection of Ethereum full node APIs exposed over
// the private debugging endpoint. // the private debugging endpoint.
type PrivateDebugAPI struct { type PrivateDebugAPI struct {
@ -859,3 +965,30 @@ func (api *ScrollAPI) CalculateRowConsumptionByBlockNumber(ctx context.Context,
asyncChecker.Wait() asyncChecker.Wait()
return rawdb.ReadBlockRowConsumption(api.eth.ChainDb(), block.Hash()), checkErr return rawdb.ReadBlockRowConsumption(api.eth.ChainDb(), block.Hash()), checkErr
} }
type DiskAndHeaderRoot struct {
DiskRoot common.Hash `json:"diskRoot"`
HeaderRoot common.Hash `json:"headerRoot"`
}
// DiskRoot
func (api *ScrollAPI) DiskRoot(ctx context.Context, blockNrOrHash *rpc.BlockNumberOrHash) (DiskAndHeaderRoot, error) {
block, err := api.eth.APIBackend.BlockByNumberOrHash(ctx, *blockNrOrHash)
if err != nil {
return DiskAndHeaderRoot{}, fmt.Errorf("failed to retrieve block: %w", err)
}
if block == nil {
return DiskAndHeaderRoot{}, fmt.Errorf("block not found: %s", blockNrOrHash.String())
}
if diskRoot, _ := rawdb.ReadDiskStateRoot(api.eth.ChainDb(), block.Root()); diskRoot != (common.Hash{}) {
return DiskAndHeaderRoot{
DiskRoot: diskRoot,
HeaderRoot: block.Root(),
}, nil
}
return DiskAndHeaderRoot{
DiskRoot: block.Root(),
HeaderRoot: block.Root(),
}, nil
}

View file

@ -482,6 +482,13 @@ web3._extend({
params: 2, params: 2,
inputFormatter:[web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter], inputFormatter:[web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter],
}), }),
new web3._extend.Method({
name: 'executionWitness',
call: 'debug_executionWitness',
params: 1,
inputFormatter: [null]
}),
], ],
properties: [] properties: []
}); });
@ -942,6 +949,13 @@ web3._extend({
params: 1, params: 1,
inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter]
}), }),
new web3._extend.Method({
name: 'diskRoot',
call: 'scroll_diskRoot',
params: 1,
inputFormatter: [web3._extend.formatters.inputDefaultBlockNumberFormatter],
}),
], ],
properties: properties:
[ [

View file

@ -181,6 +181,11 @@ func (t *odrTrie) do(key []byte, fn func() error) error {
} }
} }
// Witness returns a set containing all trie nodes that have been accessed.
func (t *odrTrie) Witness() map[string]struct{} {
panic("not implemented")
}
type nodeIterator struct { type nodeIterator struct {
trie.NodeIterator trie.NodeIterator
t *odrTrie t *odrTrie

View file

@ -372,7 +372,7 @@ func (w *worker) mainLoop() {
select { select {
case <-w.startCh: case <-w.startCh:
idleTimer.UpdateSince(idleStart) idleTimer.UpdateSince(idleStart)
if w.isRunning() { if w.isRunning() && w.chainConfig.Scroll.UseZktrie {
if err := w.checkHeadRowConsumption(); err != nil { if err := w.checkHeadRowConsumption(); err != nil {
log.Error("failed to start head checkers", "err", err) log.Error("failed to start head checkers", "err", err)
return return
@ -490,9 +490,10 @@ func (w *worker) newWork(now time.Time, parentHash common.Hash, reorging bool, r
vmConfig := *w.chain.GetVMConfig() vmConfig := *w.chain.GetVMConfig()
cccLogger := ccc.NewLogger() cccLogger := ccc.NewLogger()
vmConfig.Debug = true if w.chainConfig.Scroll.UseZktrie {
vmConfig.Tracer = cccLogger vmConfig.Debug = true
vmConfig.Tracer = cccLogger
}
deadline := time.Unix(int64(header.Time), 0) deadline := time.Unix(int64(header.Time), 0)
if w.chainConfig.Clique != nil && w.chainConfig.Clique.RelaxedPeriod { if w.chainConfig.Clique != nil && w.chainConfig.Clique.RelaxedPeriod {
// clique with relaxed period uses time.Now() as the header.Time, calculate the deadline // clique with relaxed period uses time.Now() as the header.Time, calculate the deadline
@ -566,6 +567,11 @@ func (w *worker) handleForks() (bool, error) {
misc.ApplyCurieHardFork(w.current.state) misc.ApplyCurieHardFork(w.current.state)
return true, nil return true, nil
} }
if w.chainConfig.IsEuclid(w.current.header.Time) {
parent := w.chain.GetBlockByHash(w.current.header.ParentHash)
return parent != nil && !w.chainConfig.IsEuclid(parent.Time()), nil
}
return false, nil return false, nil
} }
@ -809,7 +815,10 @@ func (w *worker) commit() (common.Hash, error) {
}(time.Now()) }(time.Now())
w.updateSnapshot() w.updateSnapshot()
if !w.isRunning() && !w.current.reorging { // Since clocks of mpt-sequencer and zktrie-sequencer can be slightly out of sync,
// this might result in a reorg at the Euclid fork block. But it will be resolved shortly after.
canCommitState := w.chainConfig.Scroll.UseZktrie != w.chainConfig.IsEuclid(w.current.header.Time)
if !canCommitState || (!w.isRunning() && !w.current.reorging) {
return common.Hash{}, nil return common.Hash{}, nil
} }
@ -886,7 +895,7 @@ func (w *worker) commit() (common.Hash, error) {
currentHeight := w.current.header.Number.Uint64() currentHeight := w.current.header.Number.Uint64()
maxReorgDepth := uint64(w.config.CCCMaxWorkers + 1) maxReorgDepth := uint64(w.config.CCCMaxWorkers + 1)
if !w.current.reorging && currentHeight > maxReorgDepth { if w.chainConfig.Scroll.UseZktrie && !w.current.reorging && currentHeight > maxReorgDepth {
ancestorHeight := currentHeight - maxReorgDepth ancestorHeight := currentHeight - maxReorgDepth
ancestorHash := w.chain.GetHeaderByNumber(ancestorHeight).Hash() ancestorHash := w.chain.GetHeaderByNumber(ancestorHeight).Hash()
if rawdb.ReadBlockRowConsumption(w.chain.Database(), ancestorHash) == nil { if rawdb.ReadBlockRowConsumption(w.chain.Database(), ancestorHash) == nil {
@ -914,8 +923,10 @@ func (w *worker) commit() (common.Hash, error) {
w.mux.Post(core.NewMinedBlockEvent{Block: block}) w.mux.Post(core.NewMinedBlockEvent{Block: block})
checkStart := time.Now() checkStart := time.Now()
if err = w.asyncChecker.Check(block); err != nil { if w.chainConfig.Scroll.UseZktrie {
log.Error("failed to launch CCC background task", "err", err) if err = w.asyncChecker.Check(block); err != nil {
log.Error("failed to launch CCC background task", "err", err)
}
} }
cccStallTimer.UpdateSince(checkStart) cccStallTimer.UpdateSince(checkStart)

View file

@ -224,14 +224,15 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
) )
if isClique { if isClique {
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
engine = clique.New(chainConfig.Clique, db) engine = clique.New(chainConfig.Clique, db)
} else { } else {
chainConfig = params.AllEthashProtocolChanges chainConfig = params.AllEthashProtocolChanges.Clone()
engine = ethash.NewFaker() engine = ethash.NewFaker()
} }
chainConfig.Scroll.FeeVaultAddress = &common.Address{} chainConfig.Scroll.FeeVaultAddress = &common.Address{}
chainConfig.Scroll.UseZktrie = true
chainConfig.LondonBlock = big.NewInt(0) chainConfig.LondonBlock = big.NewInt(0)
w, b := newTestWorker(t, chainConfig, engine, db, 0) w, b := newTestWorker(t, chainConfig, engine, db, 0)
@ -285,17 +286,18 @@ func testGenerateBlockWithL1Msg(t *testing.T, isClique bool) {
rawdb.WriteL1Messages(db, msgs) rawdb.WriteL1Messages(db, msgs)
if isClique { if isClique {
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
engine = clique.New(chainConfig.Clique, db) engine = clique.New(chainConfig.Clique, db)
} else { } else {
chainConfig = params.AllEthashProtocolChanges chainConfig = params.AllEthashProtocolChanges.Clone()
engine = ethash.NewFaker() engine = ethash.NewFaker()
} }
chainConfig.Scroll.L1Config = &params.L1Config{ chainConfig.Scroll.L1Config = &params.L1Config{
NumL1MessagesPerBlock: 1, NumL1MessagesPerBlock: 1,
} }
chainConfig.Scroll.FeeVaultAddress = &common.Address{} chainConfig.Scroll.FeeVaultAddress = &common.Address{}
chainConfig.Scroll.UseZktrie = true
chainConfig.LondonBlock = big.NewInt(0) chainConfig.LondonBlock = big.NewInt(0)
w, b := newTestWorker(t, chainConfig, engine, db, 0) w, b := newTestWorker(t, chainConfig, engine, db, 0)
@ -341,9 +343,10 @@ func TestAcceptableTxlimit(t *testing.T) {
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
) )
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
chainConfig.Scroll.FeeVaultAddress = &common.Address{} chainConfig.Scroll.FeeVaultAddress = &common.Address{}
chainConfig.Scroll.UseZktrie = true
engine = clique.New(chainConfig.Clique, db) engine = clique.New(chainConfig.Clique, db)
// Set maxTxPerBlock = 4, which >= non-l1msg + non-skipped l1msg txs // Set maxTxPerBlock = 4, which >= non-l1msg + non-skipped l1msg txs
@ -401,9 +404,10 @@ func TestUnacceptableTxlimit(t *testing.T) {
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
) )
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
chainConfig.Scroll.FeeVaultAddress = &common.Address{} chainConfig.Scroll.FeeVaultAddress = &common.Address{}
chainConfig.Scroll.UseZktrie = true
engine = clique.New(chainConfig.Clique, db) engine = clique.New(chainConfig.Clique, db)
// Set maxTxPerBlock = 3, which < non-l1msg + l1msg txs // Set maxTxPerBlock = 3, which < non-l1msg + l1msg txs
@ -460,9 +464,10 @@ func TestL1MsgCorrectOrder(t *testing.T) {
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
) )
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
chainConfig.Scroll.FeeVaultAddress = &common.Address{} chainConfig.Scroll.FeeVaultAddress = &common.Address{}
chainConfig.Scroll.UseZktrie = true
engine = clique.New(chainConfig.Clique, db) engine = clique.New(chainConfig.Clique, db)
maxTxPerBlock := 4 maxTxPerBlock := 4
@ -523,8 +528,9 @@ func l1MessageTest(t *testing.T, msgs []types.L1MessageTx, withL2Tx bool, callba
) )
rawdb.WriteL1Messages(db, msgs) rawdb.WriteL1Messages(db, msgs)
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
chainConfig.Scroll.UseZktrie = true
engine = clique.New(chainConfig.Clique, db) engine = clique.New(chainConfig.Clique, db)
maxTxPerBlock := 4 maxTxPerBlock := 4
chainConfig.Scroll.MaxTxPerBlock = &maxTxPerBlock chainConfig.Scroll.MaxTxPerBlock = &maxTxPerBlock
@ -872,13 +878,14 @@ func TestPrioritizeOverflowTx(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
var ( var (
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
) )
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
chainConfig.LondonBlock = big.NewInt(0) chainConfig.LondonBlock = big.NewInt(0)
chainConfig.Scroll.FeeVaultAddress = &common.Address{} chainConfig.Scroll.FeeVaultAddress = &common.Address{}
chainConfig.Scroll.UseZktrie = true
engine := clique.New(chainConfig.Clique, db) engine := clique.New(chainConfig.Clique, db)
w, b := newTestWorker(t, chainConfig, engine, db, 0) w, b := newTestWorker(t, chainConfig, engine, db, 0)
@ -1033,9 +1040,10 @@ func TestPending(t *testing.T) {
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
) )
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
chainConfig.Scroll.FeeVaultAddress = &common.Address{} chainConfig.Scroll.FeeVaultAddress = &common.Address{}
chainConfig.Scroll.UseZktrie = true
engine = clique.New(chainConfig.Clique, db) engine = clique.New(chainConfig.Clique, db)
w, b := newTestWorker(t, chainConfig, engine, db, 0) w, b := newTestWorker(t, chainConfig, engine, db, 0)
defer w.close() defer w.close()
@ -1077,9 +1085,10 @@ func TestReorg(t *testing.T) {
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
) )
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000, RelaxedPeriod: true} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000, RelaxedPeriod: true}
chainConfig.Scroll.FeeVaultAddress = &common.Address{} chainConfig.Scroll.FeeVaultAddress = &common.Address{}
chainConfig.Scroll.UseZktrie = true
engine = clique.New(chainConfig.Clique, db) engine = clique.New(chainConfig.Clique, db)
maxTxPerBlock := 2 maxTxPerBlock := 2
@ -1191,9 +1200,10 @@ func TestRestartHeadCCC(t *testing.T) {
chainConfig *params.ChainConfig chainConfig *params.ChainConfig
db = rawdb.NewMemoryDatabase() db = rawdb.NewMemoryDatabase()
) )
chainConfig = params.AllCliqueProtocolChanges chainConfig = params.AllCliqueProtocolChanges.Clone()
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000, RelaxedPeriod: true} chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000, RelaxedPeriod: true}
chainConfig.Scroll.FeeVaultAddress = &common.Address{} chainConfig.Scroll.FeeVaultAddress = &common.Address{}
chainConfig.Scroll.UseZktrie = true
engine = clique.New(chainConfig.Clique, db) engine = clique.New(chainConfig.Clique, db)
maxTxPerBlock := 2 maxTxPerBlock := 2

View file

@ -18,6 +18,7 @@ package params
import ( import (
"encoding/binary" "encoding/binary"
"encoding/json"
"fmt" "fmt"
"math/big" "math/big"
@ -29,14 +30,16 @@ import (
// Genesis hashes to enforce below configs on. // Genesis hashes to enforce below configs on.
var ( var (
MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
SepoliaGenesisHash = common.HexToHash("0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9") SepoliaGenesisHash = common.HexToHash("0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9")
RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a")
ScrollAlphaGenesisHash = common.HexToHash("0xa4fc62b9b0643e345bdcebe457b3ae898bef59c7203c3db269200055e037afda") ScrollAlphaGenesisHash = common.HexToHash("0xa4fc62b9b0643e345bdcebe457b3ae898bef59c7203c3db269200055e037afda")
ScrollSepoliaGenesisHash = common.HexToHash("0xaa62d1a8b2bffa9e5d2368b63aae0d98d54928bd713125e3fd9e5c896c68592c") ScrollSepoliaGenesisHash = common.HexToHash("0xaa62d1a8b2bffa9e5d2368b63aae0d98d54928bd713125e3fd9e5c896c68592c")
ScrollMainnetGenesisHash = common.HexToHash("0xbbc05efd412b7cd47a2ed0e5ddfcf87af251e414ea4c801d78b6784513180a80") ScrollMainnetGenesisHash = common.HexToHash("0xbbc05efd412b7cd47a2ed0e5ddfcf87af251e414ea4c801d78b6784513180a80")
ScrollSepoliaGenesisState = common.HexToHash("0x20695989e9038823e35f0e88fbc44659ffdbfa1fe89fbeb2689b43f15fa64cb5")
ScrollMainnetGenesisState = common.HexToHash("0x08d535cc60f40af5dd3b31e0998d7567c2d568b224bed2ba26070aeb078d1339")
) )
func newUint64(val uint64) *uint64 { return &val } func newUint64(val uint64) *uint64 { return &val }
@ -340,6 +343,7 @@ var (
NumL1MessagesPerBlock: 10, NumL1MessagesPerBlock: 10,
ScrollChainAddress: common.HexToAddress("0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"), ScrollChainAddress: common.HexToAddress("0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"),
}, },
GenesisStateRoot: &ScrollSepoliaGenesisState,
}, },
} }
@ -380,6 +384,7 @@ var (
NumL1MessagesPerBlock: 10, NumL1MessagesPerBlock: 10,
ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"), ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"),
}, },
GenesisStateRoot: &ScrollMainnetGenesisState,
}, },
} }
@ -633,6 +638,7 @@ type ChainConfig struct {
CurieBlock *big.Int `json:"curieBlock,omitempty"` // Curie switch block (nil = no fork, 0 = already on curie) CurieBlock *big.Int `json:"curieBlock,omitempty"` // Curie switch block (nil = no fork, 0 = already on curie)
DarwinTime *uint64 `json:"darwinTime,omitempty"` // Darwin switch time (nil = no fork, 0 = already on darwin) DarwinTime *uint64 `json:"darwinTime,omitempty"` // Darwin switch time (nil = no fork, 0 = already on darwin)
DarwinV2Time *uint64 `json:"darwinv2Time,omitempty"` // DarwinV2 switch time (nil = no fork, 0 = already on darwinv2) DarwinV2Time *uint64 `json:"darwinv2Time,omitempty"` // DarwinV2 switch time (nil = no fork, 0 = already on darwinv2)
EuclidTime *uint64 `json:"euclidTime,omitempty"` // Euclid switch time (nil = no fork, 0 = already on euclid)
// TerminalTotalDifficulty is the amount of total difficulty reached by // TerminalTotalDifficulty is the amount of total difficulty reached by
// the network that triggers the consensus upgrade. // the network that triggers the consensus upgrade.
@ -646,6 +652,18 @@ type ChainConfig struct {
Scroll ScrollConfig `json:"scroll,omitempty"` Scroll ScrollConfig `json:"scroll,omitempty"`
} }
func (c *ChainConfig) Clone() *ChainConfig {
var clone ChainConfig
j, err := json.Marshal(c)
if err != nil {
panic(err)
}
if err = json.Unmarshal(j, &clone); err != nil {
panic(err)
}
return &clone
}
type ScrollConfig struct { type ScrollConfig struct {
// Use zktrie [optional] // Use zktrie [optional]
UseZktrie bool `json:"useZktrie,omitempty"` UseZktrie bool `json:"useZktrie,omitempty"`
@ -661,6 +679,9 @@ type ScrollConfig struct {
// L1 config // L1 config
L1Config *L1Config `json:"l1Config,omitempty"` L1Config *L1Config `json:"l1Config,omitempty"`
// Genesis State Root for MPT clients
GenesisStateRoot *common.Hash `json:"genesisStateRoot,omitempty"`
} }
// L1Config contains the l1 parameters needed to sync l1 contract events (e.g., l1 messages, commit/revert/finalize batches) in the sequencer // L1Config contains the l1 parameters needed to sync l1 contract events (e.g., l1 messages, commit/revert/finalize batches) in the sequencer
@ -888,6 +909,11 @@ func (c *ChainConfig) IsDarwinV2(now uint64) bool {
return isForkedTime(now, c.DarwinV2Time) return isForkedTime(now, c.DarwinV2Time)
} }
// IsEuclid returns whether num is either equal to the Darwin fork block or greater.
func (c *ChainConfig) IsEuclid(now uint64) bool {
return isForkedTime(now, c.EuclidTime)
}
// IsTerminalPoWBlock returns whether the given block is the last block of PoW stage. // IsTerminalPoWBlock returns whether the given block is the last block of PoW stage.
func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *big.Int) bool { func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *big.Int) bool {
if c.TerminalTotalDifficulty == nil { if c.TerminalTotalDifficulty == nil {
@ -1100,7 +1126,7 @@ type Rules struct {
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon, IsArchimedes, IsShanghai bool IsBerlin, IsLondon, IsArchimedes, IsShanghai bool
IsBernoulli, IsCurie, IsDarwin bool IsBernoulli, IsCurie, IsDarwin, IsEuclid bool
} }
// Rules ensures c's ChainID is not nil. // Rules ensures c's ChainID is not nil.
@ -1126,5 +1152,6 @@ func (c *ChainConfig) Rules(num *big.Int, time uint64) Rules {
IsBernoulli: c.IsBernoulli(num), IsBernoulli: c.IsBernoulli(num),
IsCurie: c.IsCurie(num), IsCurie: c.IsCurie(num),
IsDarwin: c.IsDarwin(time), IsDarwin: c.IsDarwin(time),
IsEuclid: c.IsEuclid(time),
} }
} }

View file

@ -24,7 +24,7 @@ import (
const ( const (
VersionMajor = 5 // Major version component of the current release VersionMajor = 5 // Major version component of the current release
VersionMinor = 8 // Minor version component of the current release VersionMinor = 8 // Minor version component of the current release
VersionPatch = 3 // Patch version component of the current release VersionPatch = 4 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string VersionMeta = "mainnet" // Version metadata to append to the version string
) )

View file

@ -98,6 +98,11 @@ func (c *AsyncChecker) Wait() {
// Check spawns an async CCC verification task. // Check spawns an async CCC verification task.
func (c *AsyncChecker) Check(block *types.Block) error { func (c *AsyncChecker) Check(block *types.Block) error {
if c.bc.Config().IsEuclid(block.Time()) {
// Euclid blocks use MPT and CCC doesn't support them
return nil
}
if block.NumberU64() > c.currentHead.Number.Uint64()+1 { if block.NumberU64() > c.currentHead.Number.Uint64()+1 {
log.Warn("non continuous chain observed in AsyncChecker", "prev", c.currentHead, "got", block.Header()) log.Warn("non continuous chain observed in AsyncChecker", "prev", c.currentHead, "got", block.Header())
} }

View file

@ -25,18 +25,20 @@ func TestAsyncChecker(t *testing.T) {
// Create a database pre-initialize with a genesis block // Create a database pre-initialize with a genesis block
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
chainConfig := params.TestChainConfig.Clone()
chainConfig.Scroll.UseZktrie = true
(&core.Genesis{ (&core.Genesis{
Config: params.TestChainConfig, Config: chainConfig,
Alloc: core.GenesisAlloc{testAddr: {Balance: new(big.Int).Mul(big.NewInt(1000), big.NewInt(params.Ether))}}, Alloc: core.GenesisAlloc{testAddr: {Balance: new(big.Int).Mul(big.NewInt(1000), big.NewInt(params.Ether))}},
}).MustCommit(db) }).MustCommit(db)
chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) chain, _ := core.NewBlockChain(db, nil, chainConfig, ethash.NewFaker(), vm.Config{}, nil, nil)
asyncChecker := NewAsyncChecker(chain, 1, false) asyncChecker := NewAsyncChecker(chain, 1, false)
chain.Validator().WithAsyncValidator(asyncChecker.Check) chain.Validator().WithAsyncValidator(asyncChecker.Check)
bs, _ := core.GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, 100, func(i int, block *core.BlockGen) { bs, _ := core.GenerateChain(chainConfig, chain.Genesis(), ethash.NewFaker(), db, 100, func(i int, block *core.BlockGen) {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
signer := types.MakeSigner(params.TestChainConfig, block.Number()) signer := types.MakeSigner(chainConfig, block.Number())
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), testAddr, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), testAddr, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey)
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -228,7 +228,7 @@ func sendCancellable[T any, C comparable](resCh chan T, msg T, cancelCh <-chan C
} }
func (p *Pipeline) traceAndApplyStage(txsIn <-chan *types.Transaction) (<-chan error, <-chan *BlockCandidate, error) { func (p *Pipeline) traceAndApplyStage(txsIn <-chan *types.Transaction) (<-chan error, <-chan *BlockCandidate, error) {
p.state.StartPrefetcher("miner") p.state.StartPrefetcher("miner", nil)
downstreamCh := make(chan *BlockCandidate, p.downstreamChCapacity()) downstreamCh := make(chan *BlockCandidate, p.downstreamChCapacity())
resCh := make(chan error) resCh := make(chan error)
p.wg.Add(1) p.wg.Add(1)

View file

@ -667,6 +667,9 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H
} }
batch.Reset() batch.Reset()
if diskRoot, err := rawdb.ReadDiskStateRoot(db.diskdb, node); err == nil {
node = diskRoot
}
if (node == common.Hash{}) { if (node == common.Hash{}) {
return nil return nil
} }
@ -782,7 +785,7 @@ func (c *cleaner) Put(key []byte, rlp []byte) error {
delete(c.db.dirties, hash) delete(c.db.dirties, hash)
c.db.dirtiesSize -= common.StorageSize(common.HashLength + int(node.size)) c.db.dirtiesSize -= common.StorageSize(common.HashLength + int(node.size))
if node.children != nil { if node.children != nil {
c.db.dirtiesSize -= common.StorageSize(cachedNodeChildrenSize + len(node.children)*(common.HashLength+2)) c.db.childrenSize -= common.StorageSize(cachedNodeChildrenSize + len(node.children)*(common.HashLength+2))
} }
// Move the flushed node into the clean cache to prevent insta-reloads // Move the flushed node into the clean cache to prevent insta-reloads
if c.db.cleans != nil { if c.db.cleans != nil {

View file

@ -296,7 +296,7 @@ func TestUnionIterator(t *testing.T) {
} }
func TestIteratorNoDups(t *testing.T) { func TestIteratorNoDups(t *testing.T) {
var tr Trie tr := newEmpty()
for _, val := range testdata1 { for _, val := range testdata1 {
tr.Update([]byte(val.k), []byte(val.v)) tr.Update([]byte(val.k), []byte(val.v))
} }
@ -530,7 +530,7 @@ func TestNodeIteratorLargeTrie(t *testing.T) {
trie.NodeIterator(common.FromHex("0x77667766776677766778855885885885")) trie.NodeIterator(common.FromHex("0x77667766776677766778855885885885"))
// master: 24 get operations // master: 24 get operations
// this pr: 5 get operations // this pr: 5 get operations
if have, want := logDb.getCount, uint64(5); have != want { if have, want := logDb.getCount, uint64(10); have != want {
t.Fatalf("Too many lookups during seek, have %d want %d", have, want) t.Fatalf("Too many lookups during seek, have %d want %d", have, want)
} }
} }

View file

@ -559,7 +559,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key
} }
// Rebuild the trie with the leaf stream, the shape of trie // Rebuild the trie with the leaf stream, the shape of trie
// should be same with the original one. // should be same with the original one.
tr := &Trie{root: root, db: NewDatabase(memorydb.New())} tr := &Trie{root: root, db: NewDatabase(memorydb.New()), tracer: newTracer()}
if empty { if empty {
tr.root = nil tr.root = nil
} }

View file

@ -79,7 +79,7 @@ func TestProof(t *testing.T) {
} }
func TestOneElementProof(t *testing.T) { func TestOneElementProof(t *testing.T) {
trie := new(Trie) trie := newEmpty()
updateString(trie, "k", "v") updateString(trie, "k", "v")
for i, prover := range makeProvers(trie) { for i, prover := range makeProvers(trie) {
proof := prover([]byte("k")) proof := prover([]byte("k"))
@ -130,7 +130,7 @@ func TestBadProof(t *testing.T) {
// Tests that missing keys can also be proven. The test explicitly uses a single // Tests that missing keys can also be proven. The test explicitly uses a single
// entry trie and checks for missing keys both before and after the single entry. // entry trie and checks for missing keys both before and after the single entry.
func TestMissingKeyProof(t *testing.T) { func TestMissingKeyProof(t *testing.T) {
trie := new(Trie) trie := newEmpty()
updateString(trie, "k", "v") updateString(trie, "k", "v")
for i, key := range []string{"a", "j", "l", "z"} { for i, key := range []string{"a", "j", "l", "z"} {
@ -386,7 +386,7 @@ func TestOneElementRangeProof(t *testing.T) {
} }
// Test the mini trie with only a single element. // Test the mini trie with only a single element.
tinyTrie := new(Trie) tinyTrie := newEmpty()
entry := &kv{randBytes(32), randBytes(20), false} entry := &kv{randBytes(32), randBytes(20), false}
tinyTrie.Update(entry.k, entry.v) tinyTrie.Update(entry.k, entry.v)
@ -458,7 +458,7 @@ func TestAllElementsProof(t *testing.T) {
// TestSingleSideRangeProof tests the range starts from zero. // TestSingleSideRangeProof tests the range starts from zero.
func TestSingleSideRangeProof(t *testing.T) { func TestSingleSideRangeProof(t *testing.T) {
for i := 0; i < 64; i++ { for i := 0; i < 64; i++ {
trie := new(Trie) trie := newEmpty()
var entries entrySlice var entries entrySlice
for i := 0; i < 4096; i++ { for i := 0; i < 4096; i++ {
value := &kv{randBytes(32), randBytes(20), false} value := &kv{randBytes(32), randBytes(20), false}
@ -493,7 +493,7 @@ func TestSingleSideRangeProof(t *testing.T) {
// TestReverseSingleSideRangeProof tests the range ends with 0xffff...fff. // TestReverseSingleSideRangeProof tests the range ends with 0xffff...fff.
func TestReverseSingleSideRangeProof(t *testing.T) { func TestReverseSingleSideRangeProof(t *testing.T) {
for i := 0; i < 64; i++ { for i := 0; i < 64; i++ {
trie := new(Trie) trie := newEmpty()
var entries entrySlice var entries entrySlice
for i := 0; i < 4096; i++ { for i := 0; i < 4096; i++ {
value := &kv{randBytes(32), randBytes(20), false} value := &kv{randBytes(32), randBytes(20), false}
@ -600,7 +600,7 @@ func TestBadRangeProof(t *testing.T) {
// TestGappedRangeProof focuses on the small trie with embedded nodes. // TestGappedRangeProof focuses on the small trie with embedded nodes.
// If the gapped node is embedded in the trie, it should be detected too. // If the gapped node is embedded in the trie, it should be detected too.
func TestGappedRangeProof(t *testing.T) { func TestGappedRangeProof(t *testing.T) {
trie := new(Trie) trie := newEmpty()
var entries []*kv // Sorted entries var entries []*kv // Sorted entries
for i := byte(0); i < 10; i++ { for i := byte(0); i < 10; i++ {
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
@ -674,7 +674,7 @@ func TestSameSideProofs(t *testing.T) {
} }
func TestHasRightElement(t *testing.T) { func TestHasRightElement(t *testing.T) {
trie := new(Trie) trie := newEmpty()
var entries entrySlice var entries entrySlice
for i := 0; i < 4096; i++ { for i := 0; i < 4096; i++ {
value := &kv{randBytes(32), randBytes(20), false} value := &kv{randBytes(32), randBytes(20), false}
@ -1027,7 +1027,7 @@ func benchmarkVerifyRangeNoProof(b *testing.B, size int) {
} }
func randomTrie(n int) (*Trie, map[string]*kv) { func randomTrie(n int) (*Trie, map[string]*kv) {
trie := new(Trie) trie := newEmpty()
vals := make(map[string]*kv) vals := make(map[string]*kv)
for i := byte(0); i < 100; i++ { for i := byte(0); i < 100; i++ {
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
@ -1052,7 +1052,7 @@ func randBytes(n int) []byte {
} }
func nonRandomTrie(n int) (*Trie, map[string]*kv) { func nonRandomTrie(n int) (*Trie, map[string]*kv) {
trie := new(Trie) trie := newEmpty()
vals := make(map[string]*kv) vals := make(map[string]*kv)
max := uint64(0xffffffffffffffff) max := uint64(0xffffffffffffffff)
for i := uint64(0); i < uint64(n); i++ { for i := uint64(0); i < uint64(n); i++ {

View file

@ -190,6 +190,7 @@ func (t *SecureTrie) Hash() common.Hash {
// Copy returns a copy of SecureTrie. // Copy returns a copy of SecureTrie.
func (t *SecureTrie) Copy() *SecureTrie { func (t *SecureTrie) Copy() *SecureTrie {
cpy := *t cpy := *t
cpy.trie.tracer = t.trie.tracer.copy()
return &cpy return &cpy
} }
@ -221,3 +222,8 @@ func (t *SecureTrie) getSecKeyCache() map[string][]byte {
} }
return t.secKeyCache return t.secKeyCache
} }
// Witness returns a set containing all trie nodes that have been accessed.
func (t *SecureTrie) Witness() map[string]struct{} {
return t.trie.Witness()
}

View file

@ -112,8 +112,7 @@ func TestSecureTrieConcurrency(t *testing.T) {
threads := runtime.NumCPU() threads := runtime.NumCPU()
tries := make([]*SecureTrie, threads) tries := make([]*SecureTrie, threads)
for i := 0; i < threads; i++ { for i := 0; i < threads; i++ {
cpy := *trie tries[i] = trie.Copy()
tries[i] = &cpy
} }
// Start a batch of goroutines interactng with the trie // Start a batch of goroutines interactng with the trie
pend := new(sync.WaitGroup) pend := new(sync.WaitGroup)

122
trie/tracer.go Normal file
View file

@ -0,0 +1,122 @@
// Copyright 2022 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 <http://www.gnu.org/licenses/>.
package trie
import (
"maps"
"github.com/scroll-tech/go-ethereum/common"
)
// tracer tracks the changes of trie nodes. During the trie operations,
// some nodes can be deleted from the trie, while these deleted nodes
// won't be captured by trie.Hasher or trie.Committer. Thus, these deleted
// nodes won't be removed from the disk at all. Tracer is an auxiliary tool
// used to track all insert and delete operations of trie and capture all
// deleted nodes eventually.
//
// The changed nodes can be mainly divided into two categories: the leaf
// node and intermediate node. The former is inserted/deleted by callers
// while the latter is inserted/deleted in order to follow the rule of trie.
// This tool can track all of them no matter the node is embedded in its
// parent or not, but valueNode is never tracked.
//
// Besides, it's also used for recording the original value of the nodes
// when they are resolved from the disk. The pre-value of the nodes will
// be used to construct trie history in the future.
//
// Note tracer is not thread-safe, callers should be responsible for handling
// the concurrency issues by themselves.
type tracer struct {
inserts map[string]struct{}
deletes map[string]struct{}
accessList map[string][]byte
}
// newTracer initializes the tracer for capturing trie changes.
func newTracer() *tracer {
return &tracer{
inserts: make(map[string]struct{}),
deletes: make(map[string]struct{}),
accessList: make(map[string][]byte),
}
}
// onRead tracks the newly loaded trie node and caches the rlp-encoded
// blob internally. Don't change the value outside of function since
// it's not deep-copied.
func (t *tracer) onRead(path []byte, val []byte) {
t.accessList[string(path)] = val
}
// onInsert tracks the newly inserted trie node. If it's already
// in the deletion set (resurrected node), then just wipe it from
// the deletion set as it's "untouched".
func (t *tracer) onInsert(path []byte) {
if _, present := t.deletes[string(path)]; present {
delete(t.deletes, string(path))
return
}
t.inserts[string(path)] = struct{}{}
}
// onDelete tracks the newly deleted trie node. If it's already
// in the addition set, then just wipe it from the addition set
// as it's untouched.
func (t *tracer) onDelete(path []byte) {
if _, present := t.inserts[string(path)]; present {
delete(t.inserts, string(path))
return
}
t.deletes[string(path)] = struct{}{}
}
// reset clears the content tracked by tracer.
func (t *tracer) reset() {
t.inserts = make(map[string]struct{})
t.deletes = make(map[string]struct{})
t.accessList = make(map[string][]byte)
}
// copy returns a deep copied tracer instance.
func (t *tracer) copy() *tracer {
accessList := make(map[string][]byte, len(t.accessList))
for path, blob := range t.accessList {
accessList[path] = common.CopyBytes(blob)
}
return &tracer{
inserts: maps.Clone(t.inserts),
deletes: maps.Clone(t.deletes),
accessList: accessList,
}
}
// deletedNodes returns a list of node paths which are deleted from the trie.
func (t *tracer) deletedNodes() []string {
var paths []string
for path := range t.deletes {
// It's possible a few deleted nodes were embedded
// in their parent before, the deletions can be no
// effect by deleting nothing, filter them out.
_, ok := t.accessList[path]
if !ok {
continue
}
paths = append(paths, path)
}
return paths
}

View file

@ -62,6 +62,9 @@ type Trie struct {
// hashing operation. This number will not directly map to the number of // hashing operation. This number will not directly map to the number of
// actually unhashed nodes // actually unhashed nodes
unhashed int unhashed int
// tracer is the tool to track the trie changes.
tracer *tracer
} }
// newFlag returns the cache flag value for a newly created node. // newFlag returns the cache flag value for a newly created node.
@ -80,7 +83,8 @@ func New(root common.Hash, db *Database) (*Trie, error) {
panic("trie.New called without a database") panic("trie.New called without a database")
} }
trie := &Trie{ trie := &Trie{
db: db, db: db,
tracer: newTracer(),
} }
if root != (common.Hash{}) && root != emptyRoot { if root != (common.Hash{}) && root != emptyRoot {
rootnode, err := trie.resolveHash(root[:], nil) rootnode, err := trie.resolveHash(root[:], nil)
@ -313,6 +317,11 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
if matchlen == 0 { if matchlen == 0 {
return true, branch, nil return true, branch, nil
} }
// New branch node is created as a child of the original short node.
// Track the newly inserted node in the tracer. The node identifier
// passed is the path from the root node.
t.tracer.onInsert(append(prefix, key[:matchlen]...))
// Otherwise, replace it with a short node leading up to the branch. // Otherwise, replace it with a short node leading up to the branch.
return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil
@ -327,6 +336,11 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
return true, n, nil return true, n, nil
case nil: case nil:
// New short node is created and track it in the tracer. The node identifier
// passed is the path from the root node. Note the valueNode won't be tracked
// since it's always embedded in its parent.
t.tracer.onInsert(prefix)
return true, &shortNode{key, value, t.newFlag()}, nil return true, &shortNode{key, value, t.newFlag()}, nil
case hashNode: case hashNode:
@ -379,6 +393,11 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
return false, n, nil // don't replace n on mismatch return false, n, nil // don't replace n on mismatch
} }
if matchlen == len(key) { if matchlen == len(key) {
// The matched short node is deleted entirely and track
// it in the deletion set. The same the valueNode doesn't
// need to be tracked at all since it's always embedded.
t.tracer.onDelete(prefix)
return true, nil, nil // remove n entirely for whole matches return true, nil, nil // remove n entirely for whole matches
} }
// The key is longer than n.Key. Remove the remaining suffix // The key is longer than n.Key. Remove the remaining suffix
@ -391,6 +410,10 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
} }
switch child := child.(type) { switch child := child.(type) {
case *shortNode: case *shortNode:
// The child shortNode is merged into its parent, track
// is deleted as well.
t.tracer.onDelete(append(prefix, n.Key...))
// Deleting from the subtrie reduced it to another // Deleting from the subtrie reduced it to another
// short node. Merge the nodes to avoid creating a // short node. Merge the nodes to avoid creating a
// shortNode{..., shortNode{...}}. Use concat (which // shortNode{..., shortNode{...}}. Use concat (which
@ -452,6 +475,11 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
return false, nil, err return false, nil, err
} }
if cnode, ok := cnode.(*shortNode); ok { if cnode, ok := cnode.(*shortNode); ok {
// Replace the entire full node with the short node.
// Mark the original short node as deleted since the
// value is embedded into the parent now.
t.tracer.onDelete(append(prefix, byte(pos)))
k := append([]byte{byte(pos)}, cnode.Key...) k := append([]byte{byte(pos)}, cnode.Key...)
return true, &shortNode{k, cnode.Val, t.newFlag()}, nil return true, &shortNode{k, cnode.Val, t.newFlag()}, nil
} }
@ -505,6 +533,11 @@ func (t *Trie) resolve(n node, prefix []byte) (node, error) {
func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
hash := common.BytesToHash(n) hash := common.BytesToHash(n)
if node := t.db.node(hash); node != nil { if node := t.db.node(hash); node != nil {
rlp, err := t.db.Node(hash)
if err != nil {
return nil, err
}
t.tracer.onRead(prefix, rlp)
return node, nil return node, nil
} }
return nil, &MissingNodeError{NodeHash: hash, Path: prefix} return nil, &MissingNodeError{NodeHash: hash, Path: prefix}
@ -582,4 +615,18 @@ func (t *Trie) hashRoot() (node, node, error) {
func (t *Trie) Reset() { func (t *Trie) Reset() {
t.root = nil t.root = nil
t.unhashed = 0 t.unhashed = 0
t.tracer.reset()
}
// Witness returns a set containing all trie nodes that have been accessed.
func (t *Trie) Witness() map[string]struct{} {
if len(t.tracer.accessList) == 0 {
return nil
}
witness := make(map[string]struct{}, len(t.tracer.accessList))
for _, node := range t.tracer.accessList {
witness[string(node)] = struct{}{}
}
return witness
} }

View file

@ -64,7 +64,7 @@ func TestEmptyTrie(t *testing.T) {
} }
func TestNull(t *testing.T) { func TestNull(t *testing.T) {
var trie Trie trie := newEmpty()
key := make([]byte, 32) key := make([]byte, 32)
value := []byte("test") value := []byte("test")
trie.Update(key, value) trie.Update(key, value)
@ -593,15 +593,15 @@ func TestTinyTrie(t *testing.T) {
_, accounts := makeAccounts(5) _, accounts := makeAccounts(5)
trie := newEmpty() trie := newEmpty()
trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3]) trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3])
if exp, root := common.HexToHash("fc516c51c03bf9f1a0eec6ed6f6f5da743c2745dcd5670007519e6ec056f95a8"), trie.Hash(); exp != root { if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root {
t.Errorf("1: got %x, exp %x", root, exp) t.Errorf("1: got %x, exp %x", root, exp)
} }
trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001338"), accounts[4]) trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001338"), accounts[4])
if exp, root := common.HexToHash("5070d3f144546fd13589ad90cd153954643fa4ca6c1a5f08683cbfbbf76e960c"), trie.Hash(); exp != root { if exp, root := common.HexToHash("ec63b967e98a5720e7f720482151963982890d82c9093c0d486b7eb8883a66b1"), trie.Hash(); exp != root {
t.Errorf("2: got %x, exp %x", root, exp) t.Errorf("2: got %x, exp %x", root, exp)
} }
trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001339"), accounts[4]) trie.Update(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001339"), accounts[4])
if exp, root := common.HexToHash("aa3fba77e50f6e931d8aacde70912be5bff04c7862f518ae06f3418dd4d37be3"), trie.Hash(); exp != root { if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root {
t.Errorf("3: got %x, exp %x", root, exp) t.Errorf("3: got %x, exp %x", root, exp)
} }
checktr, _ := New(common.Hash{}, trie.db) checktr, _ := New(common.Hash{}, trie.db)
@ -625,7 +625,7 @@ func TestCommitAfterHash(t *testing.T) {
trie.Hash() trie.Hash()
trie.Commit(nil) trie.Commit(nil)
root := trie.Hash() root := trie.Hash()
exp := common.HexToHash("f0c0681648c93b347479cd58c61995557f01294425bd031ce1943c2799bbd4ec") exp := common.HexToHash("72f9d3f3fe1e1dd7b8936442e7642aef76371472d94319900790053c493f3fe6")
if exp != root { if exp != root {
t.Errorf("got %x, exp %x", root, exp) t.Errorf("got %x, exp %x", root, exp)
} }
@ -725,12 +725,12 @@ func TestCommitSequence(t *testing.T) {
expWriteSeqHash []byte expWriteSeqHash []byte
expCallbackSeqHash []byte expCallbackSeqHash []byte
}{ }{
{20, common.FromHex("7b908cce3bc16abb3eac5dff6c136856526f15225f74ce860a2bec47912a5492"), {20, common.FromHex("873c78df73d60e59d4a2bcf3716e8bfe14554549fea2fc147cb54129382a8066"),
common.FromHex("fac65cd2ad5e301083d0310dd701b5faaff1364cbe01cdbfaf4ec3609bb4149e")}, common.FromHex("ff00f91ac05df53b82d7f178d77ada54fd0dca64526f537034a5dbe41b17df2a")},
{200, common.FromHex("55791f6ec2f83fee512a2d3d4b505784fdefaea89974e10440d01d62a18a298a"), {200, common.FromHex("ba03d891bb15408c940eea5ee3d54d419595102648d02774a0268d892add9c8e"),
common.FromHex("5ab775b64d86a8058bb71c3c765d0f2158c14bbeb9cb32a65eda793a7e95e30f")}, common.FromHex("f3cd509064c8d319bbdd1c68f511850a902ad275e6ed5bea11547e23d492a926")},
{2000, common.FromHex("ccb464abf67804538908c62431b3a6788e8dc6dee62aff9bfe6b10136acfceac"), {2000, common.FromHex("f7a184f20df01c94f09537401d11e68d97ad0c00115233107f51b9c287ce60c7"),
common.FromHex("b908adff17a5aa9d6787324c39014a74b04cef7fba6a92aeb730f48da1ca665d")}, common.FromHex("ff795ea898ba1e4cfed4a33b4cf5535a347a02cf931f88d88719faf810f9a1c9")},
} { } {
addresses, accounts := makeAccounts(tc.count) addresses, accounts := makeAccounts(tc.count)
// This spongeDb is used to check the sequence of disk-db-writes // This spongeDb is used to check the sequence of disk-db-writes

View file

@ -233,3 +233,8 @@ func VerifyProofSMT(rootHash common.Hash, key []byte, proofDb ethdb.KeyValueRead
return nil, fmt.Errorf("bad proof node %v", proof) return nil, fmt.Errorf("bad proof node %v", proof)
} }
} }
// Witness returns a set containing all trie nodes that have been accessed.
func (t *ZkTrie) Witness() map[string]struct{} {
panic("not implemented")
}