Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth/catalyst: reset to current header if chain is rewound (in dev mode) #27992

Merged
merged 8 commits into from
Aug 25, 2023
74 changes: 53 additions & 21 deletions eth/catalyst/simulated_beacon.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package catalyst

import (
"errors"
"fmt"
"sync"
"time"

Expand Down Expand Up @@ -144,40 +143,49 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
feeRecipient := c.feeRecipient
c.feeRecipientLock.Unlock()

// Reset to CurrentBlock in case of the chain was rewound
if header := c.eth.BlockChain().CurrentBlock(); c.curForkchoiceState.HeadBlockHash != header.Hash() {
finalizedHash := c.finalizedBlockHash(header.Number.Uint64())
c.setCurrentState(header.Hash(), *finalizedHash)
Comment on lines +148 to +149
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

finalizedHash can be nil, should have nil check here.

Copy link
Contributor

@jwasinger jwasinger Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it could happen in theory but should be extremely unlikely: a setHead which rolls back to before the latest finalized block would have to occur between the execution of line 147 and 148.

}

fcResponse, err := c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, &engine.PayloadAttributes{
Timestamp: tstamp,
SuggestedFeeRecipient: feeRecipient,
Withdrawals: withdrawals,
})
if err != nil {
return fmt.Errorf("error calling forkchoice update: %v", err)
return err
}
if fcResponse == engine.STATUS_SYNCING {
return errors.New("chain rewind prevented invocation of payload creation")
}

envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true)
if err != nil {
return fmt.Errorf("error retrieving payload: %v", err)
return err
}
payload := envelope.ExecutionPayload

var finalizedHash common.Hash
if payload.Number%devEpochLength == 0 {
finalizedHash = payload.BlockHash
} else {
finalizedHash = c.eth.BlockChain().GetBlockByNumber((payload.Number - 1) / devEpochLength * devEpochLength).Hash()
if fh := c.finalizedBlockHash(payload.Number); fh == nil {
return errors.New("chain rewind interrupted calculation of finalized block hash")
} else {
finalizedHash = *fh
}
}

// mark the payload as canon
// Mark the payload as canon
if _, err = c.engineAPI.NewPayloadV2(*payload); err != nil {
return fmt.Errorf("failed to mark payload as canonical: %v", err)
}
c.curForkchoiceState = engine.ForkchoiceStateV1{
HeadBlockHash: payload.BlockHash,
SafeBlockHash: payload.BlockHash,
FinalizedBlockHash: finalizedHash,
return err
}
// mark the block containing the payload as canonical
c.setCurrentState(payload.BlockHash, finalizedHash)
// Mark the block containing the payload as canonical
if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil {
return fmt.Errorf("failed to mark block as canonical: %v", err)
return err
}
c.lastBlockTime = payload.Timestamp
return nil
Expand All @@ -198,20 +206,18 @@ func (c *SimulatedBeacon) loopOnDemand() {
case w := <-c.withdrawals.pending:
withdrawals := append(c.withdrawals.gatherPending(9), w)
if err := c.sealBlock(withdrawals); err != nil {
log.Error("Error performing sealing-work", "err", err)
return
log.Warn("Error performing sealing work", "err", err)
}
case <-newTxs:
withdrawals := c.withdrawals.gatherPending(10)
if err := c.sealBlock(withdrawals); err != nil {
log.Error("Error performing sealing-work", "err", err)
return
log.Warn("Error performing sealing work", "err", err)
}
}
}
}

// loopOnDemand runs the block production loop for non-zero period configuration
// loop runs the block production loop for non-zero period configuration
func (c *SimulatedBeacon) loop() {
timer := time.NewTimer(0)
for {
Expand All @@ -221,14 +227,40 @@ func (c *SimulatedBeacon) loop() {
case <-timer.C:
withdrawals := c.withdrawals.gatherPending(10)
if err := c.sealBlock(withdrawals); err != nil {
log.Error("Error performing sealing-work", "err", err)
return
log.Warn("Error performing sealing work", "err", err)
} else {
timer.Reset(time.Second * time.Duration(c.period))
}
timer.Reset(time.Second * time.Duration(c.period))
}
}
}

// finalizedBlockHash returns the block hash of the finalized block corresponding to the given number
// or nil if doesn't exist in the chain.
func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
var finalizedNumber uint64
if number%devEpochLength == 0 {
finalizedNumber = number
} else {
finalizedNumber = (number - 1) / devEpochLength * devEpochLength
}

if finalizedBlock := c.eth.BlockChain().GetBlockByNumber(finalizedNumber); finalizedBlock != nil {
fh := finalizedBlock.Hash()
return &fh
}
return nil
}

// setCurrentState sets the current forkchoice state
func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) {
c.curForkchoiceState = engine.ForkchoiceStateV1{
HeadBlockHash: headHash,
SafeBlockHash: headHash,
FinalizedBlockHash: finalizedHash,
}
}

func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) {
stack.RegisterAPIs([]rpc.API{
{
Expand Down