Skip to content

Commit 51f3723

Browse files
rjl493456442cp-wjhan
authored andcommitted
core: store genesis allocation and recommit them if necessary (ethereum#24460)
* core: store genesis allocation and recommit them if necessary * core: recover predefined genesis allocation if possible
1 parent ee04a25 commit 51f3723

File tree

6 files changed

+145
-15
lines changed

6 files changed

+145
-15
lines changed

core/blockchain.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,19 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
543543
}
544544
}
545545
if beyondRoot || newHeadBlock.NumberU64() == 0 {
546+
if newHeadBlock.NumberU64() == 0 {
547+
// Recommit the genesis state into disk in case the rewinding destination
548+
// is genesis block and the relevant state is gone. In the future this
549+
// rewinding destination can be the earliest block stored in the chain
550+
// if the historical chain pruning is enabled. In that case the logic
551+
// needs to be improved here.
552+
if !bc.HasState(bc.genesisBlock.Root()) {
553+
if err := CommitGenesisState(bc.db, bc.genesisBlock.Hash()); err != nil {
554+
log.Crit("Failed to commit genesis state", "err", err)
555+
}
556+
log.Debug("Recommitted genesis state to disk")
557+
}
558+
}
546559
log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash())
547560
break
548561
}

core/genesis.go

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,81 @@ func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error {
8383
return nil
8484
}
8585

86+
// flush adds allocated genesis accounts into a fresh new statedb and
87+
// commit the state changes into the given database handler.
88+
func (ga *GenesisAlloc) flush(db ethdb.Database) (common.Hash, error) {
89+
statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
90+
if err != nil {
91+
return common.Hash{}, err
92+
}
93+
for addr, account := range *ga {
94+
statedb.AddBalance(addr, account.Balance)
95+
statedb.SetCode(addr, account.Code)
96+
statedb.SetNonce(addr, account.Nonce)
97+
for key, value := range account.Storage {
98+
statedb.SetState(addr, key, value)
99+
}
100+
}
101+
root, err := statedb.Commit(false)
102+
if err != nil {
103+
return common.Hash{}, err
104+
}
105+
err = statedb.Database().TrieDB().Commit(root, true, nil)
106+
if err != nil {
107+
return common.Hash{}, err
108+
}
109+
return root, nil
110+
}
111+
112+
// write writes the json marshaled genesis state into database
113+
// with the given block hash as the unique identifier.
114+
func (ga *GenesisAlloc) write(db ethdb.KeyValueWriter, hash common.Hash) error {
115+
blob, err := json.Marshal(ga)
116+
if err != nil {
117+
return err
118+
}
119+
rawdb.WriteGenesisState(db, hash, blob)
120+
return nil
121+
}
122+
123+
// CommitGenesisState loads the stored genesis state with the given block
124+
// hash and commits them into the given database handler.
125+
func CommitGenesisState(db ethdb.Database, hash common.Hash) error {
126+
var alloc GenesisAlloc
127+
blob := rawdb.ReadGenesisState(db, hash)
128+
if len(blob) != 0 {
129+
if err := alloc.UnmarshalJSON(blob); err != nil {
130+
return err
131+
}
132+
} else {
133+
// Genesis allocation is missing and there are several possibilities:
134+
// the node is legacy which doesn't persist the genesis allocation or
135+
// the persisted allocation is just lost.
136+
// - supported networks(mainnet, testnets), recover with defined allocations
137+
// - private network, can't recover
138+
var genesis *Genesis
139+
switch hash {
140+
case params.MainnetGenesisHash:
141+
genesis = DefaultGenesisBlock()
142+
case params.RopstenGenesisHash:
143+
genesis = DefaultRopstenGenesisBlock()
144+
case params.RinkebyGenesisHash:
145+
genesis = DefaultRinkebyGenesisBlock()
146+
case params.GoerliGenesisHash:
147+
genesis = DefaultGoerliGenesisBlock()
148+
case params.SepoliaGenesisHash:
149+
genesis = DefaultSepoliaGenesisBlock()
150+
}
151+
if genesis != nil {
152+
alloc = genesis.Alloc
153+
} else {
154+
return errors.New("not found")
155+
}
156+
}
157+
_, err := alloc.flush(db)
158+
return err
159+
}
160+
86161
// GenesisAccount is an account in the state of the genesis block.
87162
type GenesisAccount struct {
88163
Code []byte `json:"code,omitempty"`
@@ -274,19 +349,10 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
274349
if db == nil {
275350
db = rawdb.NewMemoryDatabase()
276351
}
277-
statedb, err := state.New(common.Hash{}, state.NewDatabase(db), nil)
352+
root, err := g.Alloc.flush(db)
278353
if err != nil {
279354
panic(err)
280355
}
281-
for addr, account := range g.Alloc {
282-
statedb.AddBalance(addr, account.Balance)
283-
statedb.SetCode(addr, account.Code)
284-
statedb.SetNonce(addr, account.Nonce)
285-
for key, value := range account.Storage {
286-
statedb.SetState(addr, key, value)
287-
}
288-
}
289-
root := statedb.IntermediateRoot(false)
290356
head := &types.Header{
291357
Number: new(big.Int).SetUint64(g.Number),
292358
Nonce: types.EncodeNonce(g.Nonce),
@@ -314,9 +380,6 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
314380
head.BaseFee = new(big.Int).SetUint64(params.InitialBaseFee)
315381
}
316382
}
317-
statedb.Commit(false)
318-
statedb.Database().TrieDB().Commit(root, true, nil)
319-
320383
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil))
321384
}
322385

@@ -337,6 +400,9 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
337400
if config.Clique != nil && len(block.Extra()) == 0 {
338401
return nil, errors.New("can't start clique chain without signers")
339402
}
403+
if err := g.Alloc.write(db, block.Hash()); err != nil {
404+
return nil, err
405+
}
340406
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty())
341407
rawdb.WriteBlock(db, block)
342408
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil)

core/genesis_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,33 @@ func TestGenesis_Commit(t *testing.T) {
213213
t.Errorf("inequal difficulty; stored: %v, genesisBlock: %v", stored, genesisBlock.Difficulty())
214214
}
215215
}
216+
217+
func TestReadWriteGenesisAlloc(t *testing.T) {
218+
var (
219+
db = rawdb.NewMemoryDatabase()
220+
alloc = &GenesisAlloc{
221+
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
222+
{2}: {Balance: big.NewInt(2), Storage: map[common.Hash]common.Hash{{2}: {2}}},
223+
}
224+
hash = common.HexToHash("0xdeadbeef")
225+
)
226+
alloc.write(db, hash)
227+
228+
var reload GenesisAlloc
229+
err := reload.UnmarshalJSON(rawdb.ReadGenesisState(db, hash))
230+
if err != nil {
231+
t.Fatalf("Failed to load genesis state %v", err)
232+
}
233+
if len(reload) != len(*alloc) {
234+
t.Fatal("Unexpected genesis allocation")
235+
}
236+
for addr, account := range reload {
237+
want, ok := (*alloc)[addr]
238+
if !ok {
239+
t.Fatal("Account is not found")
240+
}
241+
if !reflect.DeepEqual(want, account) {
242+
t.Fatal("Unexpected account")
243+
}
244+
}
245+
}

core/rawdb/accessors_metadata.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,19 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha
8181
}
8282
}
8383

84+
// ReadGenesisState retrieves the genesis state based on the given genesis hash.
85+
func ReadGenesisState(db ethdb.KeyValueReader, hash common.Hash) []byte {
86+
data, _ := db.Get(genesisKey(hash))
87+
return data
88+
}
89+
90+
// WriteGenesisState writes the genesis state into the disk.
91+
func WriteGenesisState(db ethdb.KeyValueWriter, hash common.Hash, data []byte) {
92+
if err := db.Put(genesisKey(hash), data); err != nil {
93+
log.Crit("Failed to store genesis state", "err", err)
94+
}
95+
}
96+
8497
// crashList is a list of unclean-shutdown-markers, for rlp-encoding to the
8598
// database
8699
type crashList struct {

core/rawdb/database.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
455455
preimages.Add(size)
456456
case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength):
457457
metadata.Add(size)
458+
case bytes.HasPrefix(key, genesisPrefix) && len(key) == (len(genesisPrefix)+common.HashLength):
459+
metadata.Add(size)
458460
case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength):
459461
bloomBits.Add(size)
460462
case bytes.HasPrefix(key, BloomBitsIndexPrefix):

core/rawdb/schema.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@ var (
9797
CodePrefix = []byte("c") // CodePrefix + code hash -> account code
9898
skeletonHeaderPrefix = []byte("S") // skeletonHeaderPrefix + num (uint64 big endian) -> header
9999

100-
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
101-
configPrefix = []byte("ethereum-config-") // config prefix for the db
100+
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
101+
configPrefix = []byte("ethereum-config-") // config prefix for the db
102+
genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db
102103

103104
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
104105
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) {
242243
func configKey(hash common.Hash) []byte {
243244
return append(configPrefix, hash.Bytes()...)
244245
}
246+
247+
// genesisKey = genesisPrefix + hash
248+
func genesisKey(hash common.Hash) []byte {
249+
return append(genesisPrefix, hash.Bytes()...)
250+
}

0 commit comments

Comments
 (0)