mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 22:56:43 +00:00
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:
parent
8ecaeec8b8
commit
4bdf6d096c
41 changed files with 872 additions and 110 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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: ¶ms.ChainConfig{HomesteadBlock: big.NewInt(3)},
|
Config: ¶ms.ChainConfig{HomesteadBlock: big.NewInt(3)},
|
||||||
Alloc: GenesisAlloc{
|
Alloc: GenesisAlloc{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()...)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
67
core/stateless/database.go
Normal file
67
core/stateless/database.go
Normal 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
|
||||||
|
}
|
||||||
76
core/stateless/encoding.go
Normal file
76
core/stateless/encoding.go
Normal 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
122
core/stateless/witness.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
133
eth/api.go
133
eth/api.go
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
chainConfig.Clique = ¶ms.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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
chainConfig.Clique = ¶ms.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 = ¶ms.L1Config{
|
chainConfig.Scroll.L1Config = ¶ms.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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
chainConfig.Clique = ¶ms.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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
chainConfig.Clique = ¶ms.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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
chainConfig.Clique = ¶ms.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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
chainConfig.Clique = ¶ms.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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
chainConfig.Clique = ¶ms.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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000}
|
chainConfig.Clique = ¶ms.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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000, RelaxedPeriod: true}
|
chainConfig.Clique = ¶ms.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 = ¶ms.CliqueConfig{Period: 1, Epoch: 30000, RelaxedPeriod: true}
|
chainConfig.Clique = ¶ms.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
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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++ {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
122
trie/tracer.go
Normal 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
|
||||||
|
}
|
||||||
49
trie/trie.go
49
trie/trie.go
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue