mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 01:41:36 +00:00
fix: re-org checks
This commit is contained in:
parent
2d5da60371
commit
c3f0049a21
2 changed files with 77 additions and 4 deletions
|
|
@ -81,6 +81,7 @@ var (
|
||||||
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"}
|
||||||
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"}
|
||||||
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
|
UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"}
|
||||||
|
TooDeepReorg = &EngineAPIError{code: -38006, msg: "Too deep reorg"}
|
||||||
|
|
||||||
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
|
||||||
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,10 @@ const (
|
||||||
// beaconUpdateWarnFrequency is the frequency at which to warn the user that
|
// beaconUpdateWarnFrequency is the frequency at which to warn the user that
|
||||||
// the beacon client is offline.
|
// the beacon client is offline.
|
||||||
beaconUpdateWarnFrequency = 5 * time.Minute
|
beaconUpdateWarnFrequency = 5 * time.Minute
|
||||||
|
|
||||||
|
// maxReorgDepth is the maximum number of blocks the client is willing to
|
||||||
|
// reorg. Reorg requests deeper than this will be rejected with -38006.
|
||||||
|
maxReorgDepth = 90000
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConsensusAPI struct {
|
type ConsensusAPI struct {
|
||||||
|
|
@ -313,6 +317,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo
|
||||||
}
|
}
|
||||||
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
|
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
|
||||||
// Block is not canonical, set head.
|
// Block is not canonical, set head.
|
||||||
|
// Before reorging, check if the reorg depth exceeds our supported limit.
|
||||||
|
if err := api.checkReorgDepth(block); err != nil {
|
||||||
|
return engine.ForkChoiceResponse{}, err
|
||||||
|
}
|
||||||
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
|
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
|
||||||
return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
|
return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
|
||||||
}
|
}
|
||||||
|
|
@ -321,10 +329,20 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo
|
||||||
// generating the payload. It's a special corner case that a few slots are
|
// generating the payload. It's a special corner case that a few slots are
|
||||||
// missing and we are requested to generate the payload in slot.
|
// missing and we are requested to generate the payload in slot.
|
||||||
} else {
|
} else {
|
||||||
// If the head block is already in our canonical chain, the beacon client is
|
// Block is canonical but not the current head. Per spec, the no-reorg
|
||||||
// probably resyncing. Ignore the update.
|
// skip is only valid when headBlockHash is an ancestor of the last
|
||||||
log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number)
|
// known finalized block.
|
||||||
return valid(nil), nil
|
if api.isAncestorOfFinalized(block) {
|
||||||
|
log.Info("Ignoring beacon update to old head (ancestor of finalized)", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number)
|
||||||
|
return valid(nil), nil
|
||||||
|
}
|
||||||
|
// Head is canonical but beyond finalized - must reorg (set head).
|
||||||
|
if err := api.checkReorgDepth(block); err != nil {
|
||||||
|
return engine.ForkChoiceResponse{}, err
|
||||||
|
}
|
||||||
|
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
|
||||||
|
return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
api.eth.SetSynced()
|
api.eth.SetSynced()
|
||||||
|
|
||||||
|
|
@ -388,6 +406,60 @@ func (api *ConsensusAPI) forkchoiceUpdated(ctx context.Context, update engine.Fo
|
||||||
return valid(nil), nil
|
return valid(nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkReorgDepth verifies that the reorg required to switch to the given block
|
||||||
|
// does not exceed maxReorgDepth. It walks back from the target block until it
|
||||||
|
// finds a canonical ancestor, then computes the depth as the number of canonical
|
||||||
|
// blocks that would be unwound.
|
||||||
|
func (api *ConsensusAPI) checkReorgDepth(target *types.Block) error {
|
||||||
|
currentHead := api.eth.BlockChain().CurrentBlock()
|
||||||
|
if currentHead == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.NumberU64() >= currentHead.Number.Uint64() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ancestor := target
|
||||||
|
for ancestor != nil && ancestor.NumberU64() > 0 {
|
||||||
|
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), ancestor.NumberU64()) == ancestor.Hash() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if currentHead.Number.Uint64()-ancestor.NumberU64() > maxReorgDepth {
|
||||||
|
return engine.TooDeepReorg.With(fmt.Errorf("reorg depth exceeds maximum supported depth %d", maxReorgDepth))
|
||||||
|
}
|
||||||
|
ancestor = api.eth.BlockChain().GetBlock(ancestor.ParentHash(), ancestor.NumberU64()-1)
|
||||||
|
}
|
||||||
|
if ancestor == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
depth := currentHead.Number.Uint64() - ancestor.NumberU64()
|
||||||
|
if depth > maxReorgDepth {
|
||||||
|
log.Warn("Reorg depth exceeds supported limit", "depth", depth, "max", maxReorgDepth, "target", target.Hash(), "ancestor", ancestor.Hash())
|
||||||
|
return engine.TooDeepReorg.With(fmt.Errorf("reorg depth %d exceeds maximum supported depth %d", depth, maxReorgDepth))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ConsensusAPI) isAncestorOfFinalized(block *types.Block) bool {
|
||||||
|
finalHeader := api.eth.BlockChain().CurrentFinalBlock()
|
||||||
|
if finalHeader == nil {
|
||||||
|
return false // no finalized block known
|
||||||
|
}
|
||||||
|
if block.Hash() == finalHeader.Hash() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if block.NumberU64() >= finalHeader.Number.Uint64() {
|
||||||
|
return false // block is at or beyond finalized
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk finalized ancestry back to block's height and check hash
|
||||||
|
cursor := api.eth.BlockChain().GetHeaderByHash(finalHeader.Hash())
|
||||||
|
for cursor != nil && cursor.Number.Uint64() > block.NumberU64() {
|
||||||
|
cursor = api.eth.BlockChain().GetHeaderByHash(cursor.ParentHash)
|
||||||
|
}
|
||||||
|
return cursor != nil && cursor.Hash() == block.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
// ExchangeTransitionConfigurationV1 checks the given configuration against
|
// ExchangeTransitionConfigurationV1 checks the given configuration against
|
||||||
// the configuration of the node.
|
// the configuration of the node.
|
||||||
func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.TransitionConfigurationV1) (*engine.TransitionConfigurationV1, error) {
|
func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.TransitionConfigurationV1) (*engine.TransitionConfigurationV1, error) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue