Skip to content

Commit a4741be

Browse files
rjl493456442jagdeep sidhu
authored andcommitted
core: recover state when beacon sets canonical head if it's missing (ethereum#24613)
* core: recover the state in SetChainHead if the head state is missing * core: disable test logging * core: address comment from martin * core: improve log level in case state is recovered * core, eth, les, light: rename SetChainHead to SetCanonical
1 parent ade44e0 commit a4741be

File tree

7 files changed

+102
-14
lines changed

7 files changed

+102
-14
lines changed

core/blockchain.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,7 +2077,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
20772077
// InsertBlockWithoutSetHead executes the block, runs the necessary verification
20782078
// upon it and then persist the block and the associate state into the database.
20792079
// The key difference between the InsertChain is it won't do the canonical chain
2080-
// updating. It relies on the additional SetChainHead call to finalize the entire
2080+
// updating. It relies on the additional SetCanonical call to finalize the entire
20812081
// procedure.
20822082
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error {
20832083
if !bc.chainmu.TryLock() {
@@ -2089,16 +2089,22 @@ func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error {
20892089
return err
20902090
}
20912091

2092-
// SetChainHead rewinds the chain to set the new head block as the specified
2093-
// block. It's possible that after the reorg the relevant state of head
2094-
// is missing. It can be fixed by inserting a new block which triggers
2095-
// the re-execution.
2096-
func (bc *BlockChain) SetChainHead(head *types.Block) error {
2092+
// SetCanonical rewinds the chain to set the new head block as the specified
2093+
// block. It's possible that the state of the new head is missing, and it will
2094+
// be recovered in this function as well.
2095+
func (bc *BlockChain) SetCanonical(head *types.Block) error {
20972096
if !bc.chainmu.TryLock() {
20982097
return errChainStopped
20992098
}
21002099
defer bc.chainmu.Unlock()
21012100

2101+
// Re-execute the reorged chain in case the head state is missing.
2102+
if !bc.HasState(head.Root()) {
2103+
if err := bc.recoverAncestors(head); err != nil {
2104+
return err
2105+
}
2106+
log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash())
2107+
}
21022108
// Run the reorg if necessary and set the given block as new head.
21032109
start := time.Now()
21042110
if head.ParentHash() != bc.CurrentBlock().Hash() {

core/blockchain_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3676,3 +3676,85 @@ func TestEIP1559Transition(t *testing.T) {
36763676
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
36773677
}
36783678
}
3679+
3680+
// Tests the scenario the chain is requested to another point with the missing state.
3681+
// It expects the state is recovered and all relevant chain markers are set correctly.
3682+
func TestSetCanonical(t *testing.T) {
3683+
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
3684+
3685+
var (
3686+
db = rawdb.NewMemoryDatabase()
3687+
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
3688+
address = crypto.PubkeyToAddress(key.PublicKey)
3689+
funds = big.NewInt(100000000000000000)
3690+
gspec = &Genesis{
3691+
Config: params.TestChainConfig,
3692+
Alloc: GenesisAlloc{address: {Balance: funds}},
3693+
BaseFee: big.NewInt(params.InitialBaseFee),
3694+
}
3695+
genesis = gspec.MustCommit(db)
3696+
signer = types.LatestSigner(gspec.Config)
3697+
engine = ethash.NewFaker()
3698+
)
3699+
// Generate and import the canonical chain
3700+
canon, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, func(i int, gen *BlockGen) {
3701+
tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key)
3702+
if err != nil {
3703+
panic(err)
3704+
}
3705+
gen.AddTx(tx)
3706+
})
3707+
diskdb := rawdb.NewMemoryDatabase()
3708+
gspec.MustCommit(diskdb)
3709+
3710+
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
3711+
if err != nil {
3712+
t.Fatalf("failed to create tester chain: %v", err)
3713+
}
3714+
if n, err := chain.InsertChain(canon); err != nil {
3715+
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
3716+
}
3717+
3718+
// Generate the side chain and import them
3719+
side, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, func(i int, gen *BlockGen) {
3720+
tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1), params.TxGas, gen.header.BaseFee, nil), signer, key)
3721+
if err != nil {
3722+
panic(err)
3723+
}
3724+
gen.AddTx(tx)
3725+
})
3726+
for _, block := range side {
3727+
err := chain.InsertBlockWithoutSetHead(block)
3728+
if err != nil {
3729+
t.Fatalf("Failed to insert into chain: %v", err)
3730+
}
3731+
}
3732+
for _, block := range side {
3733+
got := chain.GetBlockByHash(block.Hash())
3734+
if got == nil {
3735+
t.Fatalf("Lost the inserted block")
3736+
}
3737+
}
3738+
3739+
// Set the chain head to the side chain, ensure all the relevant markers are updated.
3740+
verify := func(head *types.Block) {
3741+
if chain.CurrentBlock().Hash() != head.Hash() {
3742+
t.Fatalf("Unexpected block hash, want %x, got %x", head.Hash(), chain.CurrentBlock().Hash())
3743+
}
3744+
if chain.CurrentFastBlock().Hash() != head.Hash() {
3745+
t.Fatalf("Unexpected fast block hash, want %x, got %x", head.Hash(), chain.CurrentFastBlock().Hash())
3746+
}
3747+
if chain.CurrentHeader().Hash() != head.Hash() {
3748+
t.Fatalf("Unexpected head header, want %x, got %x", head.Hash(), chain.CurrentHeader().Hash())
3749+
}
3750+
if !chain.HasState(head.Root()) {
3751+
t.Fatalf("Lost block state %v %x", head.Number(), head.Hash())
3752+
}
3753+
}
3754+
chain.SetCanonical(side[len(side)-1])
3755+
verify(side[len(side)-1])
3756+
3757+
// Reset the chain head to original chain
3758+
chain.SetCanonical(canon[TriesInMemory-1])
3759+
verify(canon[TriesInMemory-1])
3760+
}

core/headerchain.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ func (hc *HeaderChain) Reorg(headers []*types.Header) error {
214214

215215
// WriteHeaders writes a chain of headers into the local chain, given that the
216216
// parents are already known. The chain head header won't be updated in this
217-
// function, the additional setChainHead is expected in order to finish the entire
217+
// function, the additional SetCanonical is expected in order to finish the entire
218218
// procedure.
219219
func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) {
220220
if len(headers) == 0 {

eth/catalyst/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
139139

140140
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
141141
// Block is not canonical, set head.
142-
if err := api.eth.BlockChain().SetChainHead(block); err != nil {
142+
if err := api.eth.BlockChain().SetCanonical(block); err != nil {
143143
return beacon.STATUS_INVALID, err
144144
}
145145
} else {

les/catalyst/api.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, pay
8787
}
8888
}
8989
// SetHead
90-
if err := api.setHead(heads.HeadBlockHash); err != nil {
90+
if err := api.setCanonical(heads.HeadBlockHash); err != nil {
9191
return beacon.STATUS_INVALID, err
9292
}
9393
if payloadAttributes != nil {
@@ -166,8 +166,8 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
166166
return nil
167167
}
168168

169-
// setHead is called to perform a force choice.
170-
func (api *ConsensusAPI) setHead(newHead common.Hash) error {
169+
// setCanonical is called to perform a force choice.
170+
func (api *ConsensusAPI) setCanonical(newHead common.Hash) error {
171171
log.Info("Setting head", "head", newHead)
172172

173173
headHeader := api.les.BlockChain().CurrentHeader()
@@ -178,7 +178,7 @@ func (api *ConsensusAPI) setHead(newHead common.Hash) error {
178178
if newHeadHeader == nil {
179179
return &beacon.GenericServerError
180180
}
181-
if err := api.les.BlockChain().SetChainHead(newHeadHeader); err != nil {
181+
if err := api.les.BlockChain().SetCanonical(newHeadHeader); err != nil {
182182
return err
183183
}
184184
// Trigger the transition if it's the first `NewHead` event.

les/catalyst/api_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func TestEth2DeepReorg(t *testing.T) {
194194
if ethservice.BlockChain().CurrentBlock().NumberU64() != head {
195195
t.Fatalf("Chain head shouldn't be updated")
196196
}
197-
if err := api.setHead(block.Hash()); err != nil {
197+
if err := api.setCanonical(block.Hash()); err != nil {
198198
t.Fatalf("Failed to set head: %v", err)
199199
}
200200
if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() {

light/lightchain.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ func (lc *LightChain) InsertHeader(header *types.Header) error {
389389
return err
390390
}
391391

392-
func (lc *LightChain) SetChainHead(header *types.Header) error {
392+
func (lc *LightChain) SetCanonical(header *types.Header) error {
393393
lc.chainmu.Lock()
394394
defer lc.chainmu.Unlock()
395395

0 commit comments

Comments
 (0)