core: recover state when beacon sets canonical head if it's missing (#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
This commit is contained in:
		
							parent
							
								
									11b56ace2a
								
							
						
					
					
						commit
						7f6f01d46f
					
				| @ -2075,7 +2075,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { | ||||
| // InsertBlockWithoutSetHead executes the block, runs the necessary verification
 | ||||
| // upon it and then persist the block and the associate state into the database.
 | ||||
| // The key difference between the InsertChain is it won't do the canonical chain
 | ||||
| // updating. It relies on the additional SetChainHead call to finalize the entire
 | ||||
| // updating. It relies on the additional SetCanonical call to finalize the entire
 | ||||
| // procedure.
 | ||||
| func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error { | ||||
| 	if !bc.chainmu.TryLock() { | ||||
| @ -2087,16 +2087,22 @@ func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error { | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // SetChainHead rewinds the chain to set the new head block as the specified
 | ||||
| // block. It's possible that after the reorg the relevant state of head
 | ||||
| // is missing. It can be fixed by inserting a new block which triggers
 | ||||
| // the re-execution.
 | ||||
| func (bc *BlockChain) SetChainHead(head *types.Block) error { | ||||
| // SetCanonical rewinds the chain to set the new head block as the specified
 | ||||
| // block. It's possible that the state of the new head is missing, and it will
 | ||||
| // be recovered in this function as well.
 | ||||
| func (bc *BlockChain) SetCanonical(head *types.Block) error { | ||||
| 	if !bc.chainmu.TryLock() { | ||||
| 		return errChainStopped | ||||
| 	} | ||||
| 	defer bc.chainmu.Unlock() | ||||
| 
 | ||||
| 	// Re-execute the reorged chain in case the head state is missing.
 | ||||
| 	if !bc.HasState(head.Root()) { | ||||
| 		if err := bc.recoverAncestors(head); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		log.Info("Recovered head state", "number", head.Number(), "hash", head.Hash()) | ||||
| 	} | ||||
| 	// Run the reorg if necessary and set the given block as new head.
 | ||||
| 	start := time.Now() | ||||
| 	if head.ParentHash() != bc.CurrentBlock().Hash() { | ||||
|  | ||||
| @ -3676,3 +3676,85 @@ func TestEIP1559Transition(t *testing.T) { | ||||
| 		t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Tests the scenario the chain is requested to another point with the missing state.
 | ||||
| // It expects the state is recovered and all relevant chain markers are set correctly.
 | ||||
| func TestSetCanonical(t *testing.T) { | ||||
| 	//log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
 | ||||
| 
 | ||||
| 	var ( | ||||
| 		db      = rawdb.NewMemoryDatabase() | ||||
| 		key, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") | ||||
| 		address = crypto.PubkeyToAddress(key.PublicKey) | ||||
| 		funds   = big.NewInt(100000000000000000) | ||||
| 		gspec   = &Genesis{ | ||||
| 			Config:  params.TestChainConfig, | ||||
| 			Alloc:   GenesisAlloc{address: {Balance: funds}}, | ||||
| 			BaseFee: big.NewInt(params.InitialBaseFee), | ||||
| 		} | ||||
| 		genesis = gspec.MustCommit(db) | ||||
| 		signer  = types.LatestSigner(gspec.Config) | ||||
| 		engine  = ethash.NewFaker() | ||||
| 	) | ||||
| 	// Generate and import the canonical chain
 | ||||
| 	canon, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, func(i int, gen *BlockGen) { | ||||
| 		tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		gen.AddTx(tx) | ||||
| 	}) | ||||
| 	diskdb := rawdb.NewMemoryDatabase() | ||||
| 	gspec.MustCommit(diskdb) | ||||
| 
 | ||||
| 	chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to create tester chain: %v", err) | ||||
| 	} | ||||
| 	if n, err := chain.InsertChain(canon); err != nil { | ||||
| 		t.Fatalf("block %d: failed to insert into chain: %v", n, err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Generate the side chain and import them
 | ||||
| 	side, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, func(i int, gen *BlockGen) { | ||||
| 		tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1), params.TxGas, gen.header.BaseFee, nil), signer, key) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		gen.AddTx(tx) | ||||
| 	}) | ||||
| 	for _, block := range side { | ||||
| 		err := chain.InsertBlockWithoutSetHead(block) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("Failed to insert into chain: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	for _, block := range side { | ||||
| 		got := chain.GetBlockByHash(block.Hash()) | ||||
| 		if got == nil { | ||||
| 			t.Fatalf("Lost the inserted block") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Set the chain head to the side chain, ensure all the relevant markers are updated.
 | ||||
| 	verify := func(head *types.Block) { | ||||
| 		if chain.CurrentBlock().Hash() != head.Hash() { | ||||
| 			t.Fatalf("Unexpected block hash, want %x, got %x", head.Hash(), chain.CurrentBlock().Hash()) | ||||
| 		} | ||||
| 		if chain.CurrentFastBlock().Hash() != head.Hash() { | ||||
| 			t.Fatalf("Unexpected fast block hash, want %x, got %x", head.Hash(), chain.CurrentFastBlock().Hash()) | ||||
| 		} | ||||
| 		if chain.CurrentHeader().Hash() != head.Hash() { | ||||
| 			t.Fatalf("Unexpected head header, want %x, got %x", head.Hash(), chain.CurrentHeader().Hash()) | ||||
| 		} | ||||
| 		if !chain.HasState(head.Root()) { | ||||
| 			t.Fatalf("Lost block state %v %x", head.Number(), head.Hash()) | ||||
| 		} | ||||
| 	} | ||||
| 	chain.SetCanonical(side[len(side)-1]) | ||||
| 	verify(side[len(side)-1]) | ||||
| 
 | ||||
| 	// Reset the chain head to original chain
 | ||||
| 	chain.SetCanonical(canon[TriesInMemory-1]) | ||||
| 	verify(canon[TriesInMemory-1]) | ||||
| } | ||||
|  | ||||
| @ -204,7 +204,7 @@ func (hc *HeaderChain) Reorg(headers []*types.Header) error { | ||||
| 
 | ||||
| // WriteHeaders writes a chain of headers into the local chain, given that the
 | ||||
| // parents are already known. The chain head header won't be updated in this
 | ||||
| // function, the additional setChainHead is expected in order to finish the entire
 | ||||
| // function, the additional SetCanonical is expected in order to finish the entire
 | ||||
| // procedure.
 | ||||
| func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) { | ||||
| 	if len(headers) == 0 { | ||||
|  | ||||
| @ -139,7 +139,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa | ||||
| 
 | ||||
| 	if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash { | ||||
| 		// Block is not canonical, set head.
 | ||||
| 		if err := api.eth.BlockChain().SetChainHead(block); err != nil { | ||||
| 		if err := api.eth.BlockChain().SetCanonical(block); err != nil { | ||||
| 			return beacon.STATUS_INVALID, err | ||||
| 		} | ||||
| 	} else { | ||||
|  | ||||
| @ -87,7 +87,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, pay | ||||
| 		} | ||||
| 	} | ||||
| 	// SetHead
 | ||||
| 	if err := api.setHead(heads.HeadBlockHash); err != nil { | ||||
| 	if err := api.setCanonical(heads.HeadBlockHash); err != nil { | ||||
| 		return beacon.STATUS_INVALID, err | ||||
| 	} | ||||
| 	if payloadAttributes != nil { | ||||
| @ -166,8 +166,8 @@ func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // setHead is called to perform a force choice.
 | ||||
| func (api *ConsensusAPI) setHead(newHead common.Hash) error { | ||||
| // setCanonical is called to perform a force choice.
 | ||||
| func (api *ConsensusAPI) setCanonical(newHead common.Hash) error { | ||||
| 	log.Info("Setting head", "head", newHead) | ||||
| 
 | ||||
| 	headHeader := api.les.BlockChain().CurrentHeader() | ||||
| @ -178,7 +178,7 @@ func (api *ConsensusAPI) setHead(newHead common.Hash) error { | ||||
| 	if newHeadHeader == nil { | ||||
| 		return &beacon.GenericServerError | ||||
| 	} | ||||
| 	if err := api.les.BlockChain().SetChainHead(newHeadHeader); err != nil { | ||||
| 	if err := api.les.BlockChain().SetCanonical(newHeadHeader); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// Trigger the transition if it's the first `NewHead` event.
 | ||||
|  | ||||
| @ -194,7 +194,7 @@ func TestEth2DeepReorg(t *testing.T) { | ||||
| 			if ethservice.BlockChain().CurrentBlock().NumberU64() != head { | ||||
| 				t.Fatalf("Chain head shouldn't be updated") | ||||
| 			} | ||||
| 			if err := api.setHead(block.Hash()); err != nil { | ||||
| 			if err := api.setCanonical(block.Hash()); err != nil { | ||||
| 				t.Fatalf("Failed to set head: %v", err) | ||||
| 			} | ||||
| 			if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() { | ||||
|  | ||||
| @ -389,7 +389,7 @@ func (lc *LightChain) InsertHeader(header *types.Header) error { | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (lc *LightChain) SetChainHead(header *types.Header) error { | ||||
| func (lc *LightChain) SetCanonical(header *types.Header) error { | ||||
| 	lc.chainmu.Lock() | ||||
| 	defer lc.chainmu.Unlock() | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user