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
This commit is contained in:
parent
13d1d425ac
commit
eeb5dc3ccf
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue