core: store genesis allocation and recommit them if necessary (#24460)
* core: store genesis allocation and recommit them if necessary * core: recover predefined genesis allocation if possible
This commit is contained in:
parent
34501ed235
commit
7ae6c4a790
@ -542,6 +542,19 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if beyondRoot || newHeadBlock.NumberU64() == 0 {
|
if beyondRoot || newHeadBlock.NumberU64() == 0 {
|
||||||
|
if newHeadBlock.NumberU64() == 0 {
|
||||||
|
// Recommit the genesis state into disk in case the rewinding destination
|
||||||
|
// is genesis block and the relevant state is gone. In the future this
|
||||||
|
// rewinding destination can be the earliest block stored in the chain
|
||||||
|
// if the historical chain pruning is enabled. In that case the logic
|
||||||
|
// needs to be improved here.
|
||||||
|
if !bc.HasState(bc.genesisBlock.Root()) {
|
||||||
|
if err := CommitGenesisState(bc.db, bc.genesisBlock.Hash()); err != nil {
|
||||||
|
log.Crit("Failed to commit genesis state", "err", err)
|
||||||
|
}
|
||||||
|
log.Debug("Recommitted genesis state to disk")
|
||||||
|
}
|
||||||
|
}
|
||||||
log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
|
log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,81 @@ func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flush adds allocated genesis accounts into a fresh new statedb and
|
||||||
|
// commit the state changes into the given database handler.
|
||||||
|
func (ga *GenesisAlloc) flush(db ethdb.Database) (common.Hash, error) {
|
||||||
|
statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
for addr, account := range *ga {
|
||||||
|
statedb.AddBalance(addr, account.Balance)
|
||||||
|
statedb.SetCode(addr, account.Code)
|
||||||
|
statedb.SetNonce(addr, account.Nonce)
|
||||||
|
for key, value := range account.Storage {
|
||||||
|
statedb.SetState(addr, key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root, err := statedb.Commit(false)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
err = statedb.Database().TrieDB().Commit(root, true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// write writes the json marshaled genesis state into database
|
||||||
|
// with the given block hash as the unique identifier.
|
||||||
|
func (ga *GenesisAlloc) write(db ethdb.KeyValueWriter, hash common.Hash) error {
|
||||||
|
blob, err := json.Marshal(ga)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawdb.WriteGenesisState(db, hash, blob)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitGenesisState loads the stored genesis state with the given block
|
||||||
|
// hash and commits them into the given database handler.
|
||||||
|
func CommitGenesisState(db ethdb.Database, hash common.Hash) error {
|
||||||
|
var alloc GenesisAlloc
|
||||||
|
blob := rawdb.ReadGenesisState(db, hash)
|
||||||
|
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 hash {
|
||||||
|
case params.MainnetGenesisHash:
|
||||||
|
genesis = DefaultGenesisBlock()
|
||||||
|
case params.RopstenGenesisHash:
|
||||||
|
genesis = DefaultRopstenGenesisBlock()
|
||||||
|
case params.RinkebyGenesisHash:
|
||||||
|
genesis = DefaultRinkebyGenesisBlock()
|
||||||
|
case params.GoerliGenesisHash:
|
||||||
|
genesis = DefaultGoerliGenesisBlock()
|
||||||
|
case params.SepoliaGenesisHash:
|
||||||
|
genesis = DefaultSepoliaGenesisBlock()
|
||||||
|
}
|
||||||
|
if genesis != nil {
|
||||||
|
alloc = genesis.Alloc
|
||||||
|
} else {
|
||||||
|
return errors.New("not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := alloc.flush(db)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// GenesisAccount is an account in the state of the genesis block.
|
// GenesisAccount is an account in the state of the genesis block.
|
||||||
type GenesisAccount struct {
|
type GenesisAccount struct {
|
||||||
Code []byte `json:"code,omitempty"`
|
Code []byte `json:"code,omitempty"`
|
||||||
@ -267,19 +342,10 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
|
|||||||
if db == nil {
|
if db == nil {
|
||||||
db = rawdb.NewMemoryDatabase()
|
db = rawdb.NewMemoryDatabase()
|
||||||
}
|
}
|
||||||
statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
|
root, err := g.Alloc.flush(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
for addr, account := range g.Alloc {
|
|
||||||
statedb.AddBalance(addr, account.Balance)
|
|
||||||
statedb.SetCode(addr, account.Code)
|
|
||||||
statedb.SetNonce(addr, account.Nonce)
|
|
||||||
for key, value := range account.Storage {
|
|
||||||
statedb.SetState(addr, key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
root := statedb.IntermediateRoot(false)
|
|
||||||
head := &types.Header{
|
head := &types.Header{
|
||||||
Number: new(big.Int).SetUint64(g.Number),
|
Number: new(big.Int).SetUint64(g.Number),
|
||||||
Nonce: types.EncodeNonce(g.Nonce),
|
Nonce: types.EncodeNonce(g.Nonce),
|
||||||
@ -307,9 +373,6 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
|
|||||||
head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee)
|
head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statedb.Commit(false)
|
|
||||||
statedb.Database().TrieDB().Commit(root, true, nil)
|
|
||||||
|
|
||||||
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
|
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,6 +393,9 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
|
|||||||
if config.Clique != nil && len(block.Extra()) == 0 {
|
if config.Clique != nil && len(block.Extra()) == 0 {
|
||||||
return nil, errors.New("can't start clique chain without signers")
|
return nil, errors.New("can't start clique chain without signers")
|
||||||
}
|
}
|
||||||
|
if err := g.Alloc.write(db, block.Hash()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty())
|
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty())
|
||||||
rawdb.WriteBlock(db, block)
|
rawdb.WriteBlock(db, block)
|
||||||
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil)
|
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil)
|
||||||
|
@ -213,3 +213,33 @@ func TestGenesis_Commit(t *testing.T) {
|
|||||||
t.Errorf("inequal difficulty; stored: %v, genesisBlock: %v", stored, genesisBlock.Difficulty())
|
t.Errorf("inequal difficulty; stored: %v, genesisBlock: %v", stored, genesisBlock.Difficulty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadWriteGenesisAlloc(t *testing.T) {
|
||||||
|
var (
|
||||||
|
db = rawdb.NewMemoryDatabase()
|
||||||
|
alloc = &GenesisAlloc{
|
||||||
|
{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 = common.HexToHash("0xdeadbeef")
|
||||||
|
)
|
||||||
|
alloc.write(db, hash)
|
||||||
|
|
||||||
|
var reload GenesisAlloc
|
||||||
|
err := reload.UnmarshalJSON(rawdb.ReadGenesisState(db, hash))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load genesis state %v", err)
|
||||||
|
}
|
||||||
|
if len(reload) != len(*alloc) {
|
||||||
|
t.Fatal("Unexpected genesis allocation")
|
||||||
|
}
|
||||||
|
for addr, account := range reload {
|
||||||
|
want, ok := (*alloc)[addr]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Account is not found")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(want, account) {
|
||||||
|
t.Fatal("Unexpected account")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -81,6 +81,19 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadGenesisState retrieves the genesis state based on the given genesis hash.
|
||||||
|
func ReadGenesisState(db ethdb.KeyValueReader, hash common.Hash) []byte {
|
||||||
|
data, _ := db.Get(genesisKey(hash))
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteGenesisState writes the genesis state into the disk.
|
||||||
|
func WriteGenesisState(db ethdb.KeyValueWriter, hash common.Hash, data []byte) {
|
||||||
|
if err := db.Put(genesisKey(hash), data); err != nil {
|
||||||
|
log.Crit("Failed to store genesis state", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// crashList is a list of unclean-shutdown-markers, for rlp-encoding to the
|
// crashList is a list of unclean-shutdown-markers, for rlp-encoding to the
|
||||||
// database
|
// database
|
||||||
type crashList struct {
|
type crashList struct {
|
||||||
|
@ -386,6 +386,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
|||||||
preimages.Add(size)
|
preimages.Add(size)
|
||||||
case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength):
|
case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength):
|
||||||
metadata.Add(size)
|
metadata.Add(size)
|
||||||
|
case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength):
|
||||||
|
metadata.Add(size)
|
||||||
case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength):
|
case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength):
|
||||||
bloomBits.Add(size)
|
bloomBits.Add(size)
|
||||||
case bytes.HasPrefix(key, BloomBitsIndexPrefix):
|
case bytes.HasPrefix(key, BloomBitsIndexPrefix):
|
||||||
|
@ -99,6 +99,7 @@ var (
|
|||||||
|
|
||||||
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
|
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
|
||||||
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
||||||
|
genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db
|
||||||
|
|
||||||
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
|
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
|
||||||
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
|
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
|
||||||
@ -242,3 +243,8 @@ func IsCodeKey(key []byte) (bool, []byte) {
|
|||||||
func configKey(hash common.Hash) []byte {
|
func configKey(hash common.Hash) []byte {
|
||||||
return append(configPrefix, hash.Bytes()...)
|
return append(configPrefix, hash.Bytes()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// genesisKey = genesisPrefix + hash
|
||||||
|
func genesisKey(hash common.Hash) []byte {
|
||||||
|
return append(genesisPrefix, hash.Bytes()...)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user