From eeb5dc3ccf69cfdb3a9f66685a2ed3bb50f06a86 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 11 Oct 2023 16:27:44 +0800 Subject: [PATCH] cmd, core: resolve scheme from a read-write database (#28313) * cmd, core: resolve scheme from a read-write database * cmd, core, eth: move the scheme check in the ethereum constructor * cmd/geth: dump should in ro mode * cmd: reverts --- cmd/geth/chaincmd.go | 2 +- cmd/utils/flags.go | 50 +++--------------------------------- core/genesis.go | 45 +++++--------------------------- core/genesis_test.go | 2 +- core/rawdb/accessors_trie.go | 35 +++++++++++++++++++++++++ eth/backend.go | 8 ++++-- eth/ethconfig/config.go | 8 +++--- 7 files changed, 58 insertions(+), 92 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index aebcc29eb..a6bb2c2d2 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -474,7 +474,7 @@ func dump(ctx *cli.Context) error { if err != nil { return err } - triedb := utils.MakeTrieDatabase(ctx, db, true, false) // always enable preimage lookup + triedb := utils.MakeTrieDatabase(ctx, db, true, true) // always enable preimage lookup defer triedb.Close() state, err := state.New(root, state.NewDatabaseWithNodeDB(db, triedb), nil) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 9743a7b9c..f24ca6658 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -268,7 +268,6 @@ var ( StateSchemeFlag = &cli.StringFlag{ Name: "state.scheme", Usage: "Scheme to use for storing ethereum state ('hash' or 'path')", - Value: rawdb.HashScheme, Category: flags.StateCategory, } StateHistoryFlag = &cli.Uint64Flag{ @@ -1721,15 +1720,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(StateHistoryFlag.Name) { cfg.StateHistory = ctx.Uint64(StateHistoryFlag.Name) } - // Parse state scheme, abort the process if it's not compatible. - chaindb := tryMakeReadOnlyDatabase(ctx, stack) - scheme, err := ParseStateScheme(ctx, chaindb) - chaindb.Close() - if err != nil { - Fatalf("%v", err) + if ctx.IsSet(StateSchemeFlag.Name) { + cfg.StateScheme = ctx.String(StateSchemeFlag.Name) } - cfg.StateScheme = scheme - // Parse transaction history flag, if user is still using legacy config // file with 'TxLookupLimit' configured, copy the value to 'TransactionHistory'. if cfg.TransactionHistory == ethconfig.Defaults.TransactionHistory && cfg.TxLookupLimit != ethconfig.Defaults.TxLookupLimit { @@ -2165,7 +2158,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh if gcmode := ctx.String(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" { Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name) } - scheme, err := ParseStateScheme(ctx, chainDb) + scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), chainDb) if err != nil { Fatalf("%v", err) } @@ -2224,47 +2217,12 @@ func MakeConsolePreloads(ctx *cli.Context) []string { return preloads } -// ParseStateScheme resolves scheme identifier from CLI flag. If the provided -// state scheme is not compatible with the one of persistent scheme, an error -// will be returned. -// -// - none: use the scheme consistent with persistent state, or fallback -// to hash-based scheme if state is empty. -// - hash: use hash-based scheme or error out if not compatible with -// persistent state scheme. -// - path: use path-based scheme or error out if not compatible with -// persistent state scheme. -func ParseStateScheme(ctx *cli.Context, disk ethdb.Database) (string, error) { - // If state scheme is not specified, use the scheme consistent - // with persistent state, or fallback to hash mode if database - // is empty. - stored := rawdb.ReadStateScheme(disk) - if !ctx.IsSet(StateSchemeFlag.Name) { - if stored == "" { - // use default scheme for empty database, flip it when - // path mode is chosen as default - log.Info("State schema set to default", "scheme", "hash") - return rawdb.HashScheme, nil - } - log.Info("State scheme set to already existing", "scheme", stored) - return stored, nil // reuse scheme of persistent scheme - } - // If state scheme is specified, ensure it's compatible with - // persistent state. - scheme := ctx.String(StateSchemeFlag.Name) - if stored == "" || scheme == stored { - log.Info("State scheme set by user", "scheme", scheme) - return scheme, nil - } - return "", fmt.Errorf("incompatible state scheme, stored: %s, provided: %s", stored, scheme) -} - // MakeTrieDatabase constructs a trie database based on the configured scheme. func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, readOnly bool) *trie.Database { config := &trie.Config{ Preimages: preimage, } - scheme, err := ParseStateScheme(ctx, disk) + scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), disk) if err != nil { Fatalf("%v", err) } diff --git a/core/genesis.go b/core/genesis.go index baace3f99..0f1e8baf4 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -120,8 +120,8 @@ func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error { return nil } -// deriveHash computes the state root according to the genesis specification. -func (ga *GenesisAlloc) deriveHash() (common.Hash, error) { +// hash computes the state root according to the genesis specification. +func (ga *GenesisAlloc) hash() (common.Hash, error) { // Create an ephemeral in-memory database for computing hash, // all the derived states will be discarded to not pollute disk. db := state.NewDatabase(rawdb.NewMemoryDatabase()) @@ -142,9 +142,9 @@ func (ga *GenesisAlloc) deriveHash() (common.Hash, error) { return statedb.Commit(0, false) } -// flush is very similar with deriveHash, but the main difference is -// all the generated states will be persisted into the given database. -// Also, the genesis state specification will be flushed as well. +// flush is very similar with hash, but the main difference is all the generated +// states will be persisted into the given database. Also, the genesis state +// specification will be flushed as well. func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error { statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseWithNodeDB(db, triedb), nil) if err != nil { @@ -179,39 +179,6 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas return nil } -// CommitGenesisState loads the stored genesis state with the given block -// hash and commits it into the provided trie database. -func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error { - var alloc GenesisAlloc - blob := rawdb.ReadGenesisStateSpec(db, blockhash) - if len(blob) != 0 { - if err := alloc.UnmarshalJSON(blob); err != nil { - return err - } - } else { - // Genesis allocation is missing and there are several possibilities: - // the node is legacy which doesn't persist the genesis allocation or - // the persisted allocation is just lost. - // - supported networks(mainnet, testnets), recover with defined allocations - // - private network, can't recover - var genesis *Genesis - switch blockhash { - case params.MainnetGenesisHash: - genesis = DefaultGenesisBlock() - case params.GoerliGenesisHash: - genesis = DefaultGoerliGenesisBlock() - case params.SepoliaGenesisHash: - genesis = DefaultSepoliaGenesisBlock() - } - if genesis != nil { - alloc = genesis.Alloc - } else { - return errors.New("not found") - } - } - return alloc.flush(db, triedb, blockhash) -} - // GenesisAccount is an account in the state of the genesis block. type GenesisAccount struct { Code []byte `json:"code,omitempty"` @@ -444,7 +411,7 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { // ToBlock returns the genesis block according to genesis specification. func (g *Genesis) ToBlock() *types.Block { - root, err := g.Alloc.deriveHash() + root, err := g.Alloc.hash() if err != nil { panic(err) } diff --git a/core/genesis_test.go b/core/genesis_test.go index 6a0f2df08..fac88ff37 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -231,7 +231,7 @@ func TestReadWriteGenesisAlloc(t *testing.T) { {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, {2}: {Balance: big.NewInt(2), Storage: map[common.Hash]common.Hash{{2}: {2}}}, } - hash, _ = alloc.deriveHash() + hash, _ = alloc.hash() ) blob, _ := json.Marshal(alloc) rawdb.WriteGenesisStateSpec(db, hash, blob) diff --git a/core/rawdb/accessors_trie.go b/core/rawdb/accessors_trie.go index ea437b811..78f1a70b1 100644 --- a/core/rawdb/accessors_trie.go +++ b/core/rawdb/accessors_trie.go @@ -305,3 +305,38 @@ func ReadStateScheme(db ethdb.Reader) string { } return HashScheme } + +// ParseStateScheme checks if the specified state scheme is compatible with +// the stored state. +// +// - If the provided scheme is none, use the scheme consistent with persistent +// state, or fallback to hash-based scheme if state is empty. +// +// - If the provided scheme is hash, use hash-based scheme or error out if not +// compatible with persistent state scheme. +// +// - If the provided scheme is path: use path-based scheme or error out if not +// compatible with persistent state scheme. +func ParseStateScheme(provided string, disk ethdb.Database) (string, error) { + // If state scheme is not specified, use the scheme consistent + // with persistent state, or fallback to hash mode if database + // is empty. + stored := ReadStateScheme(disk) + if provided == "" { + if stored == "" { + // use default scheme for empty database, flip it when + // path mode is chosen as default + log.Info("State schema set to default", "scheme", "hash") + return HashScheme, nil + } + log.Info("State scheme set to already existing", "scheme", stored) + return stored, nil // reuse scheme of persistent scheme + } + // If state scheme is specified, ensure it's compatible with + // persistent state. + if stored == "" || provided == stored { + log.Info("State scheme set by user", "scheme", provided) + return provided, nil + } + return "", fmt.Errorf("incompatible state scheme, stored: %s, provided: %s", stored, provided) +} diff --git a/eth/backend.go b/eth/backend.go index af0351779..c6787870c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -133,8 +133,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } + scheme, err := rawdb.ParseStateScheme(config.StateScheme, chainDb) + if err != nil { + return nil, err + } // Try to recover offline state pruning only in hash-based. - if config.StateScheme == rawdb.HashScheme { + if scheme == rawdb.HashScheme { if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb); err != nil { log.Error("Failed to recover state", "error", err) } @@ -194,7 +198,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { SnapshotLimit: config.SnapshotCache, Preimages: config.Preimages, StateHistory: config.StateHistory, - StateScheme: config.StateScheme, + StateScheme: scheme, } ) // Override the chain config with provided settings. diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 342ff3da9..bfb1df3fb 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -27,7 +27,6 @@ import ( "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/txpool/blobpool" "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/eth/downloader" @@ -64,7 +63,6 @@ var Defaults = Config{ TxLookupLimit: 2350000, TransactionHistory: 2350000, StateHistory: params.FullImmutabilityThreshold, - StateScheme: rawdb.HashScheme, LightPeers: 100, DatabaseCache: 512, TrieCleanCache: 154, @@ -105,7 +103,11 @@ type Config struct { TxLookupLimit uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. TransactionHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. StateHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose state histories are reserved. - StateScheme string `toml:",omitempty"` // State scheme used to store ethereum state and merkle trie nodes on top + + // State scheme represents the scheme used to store ethereum states and trie + // nodes on top. It can be 'hash', 'path', or none which means use the scheme + // consistent with persistent state. + StateScheme string `toml:",omitempty"` // RequiredBlocks is a set of block number -> hash mappings which must be in the // canonical chain of all remote peers. Setting the option makes geth verify the