Skip to content

fix(taiko-client): fix an occasional engine.SYNCING error when receiving P2P preconf blocks #19262

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

Merged
merged 8 commits into from
Apr 14, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,29 @@ func createExecutionPayloadsAndSetHead(
meta *createExecutionPayloadsMetaData,
txListBytes []byte,
) (payloadData *engine.ExecutableData, err error) {
// Before updating the fork choice, we need to check if the parent block is in the canonical chain,
// if the parent is in a frok, we need to set the fork choice to the parent block at first.
parent, err := rpc.L2.HeaderByNumber(ctx, new(big.Int).SetUint64(meta.BlockID.Uint64()-1))
if err != nil && !errors.Is(err, ethereum.NotFound) {
return nil, fmt.Errorf("failed to get parent block: %w", err)
}
if parent == nil || parent.Hash() != meta.ParentHash {
log.Info(
"Parent block not in canonical chain, setting fork choice to parent",
"blockID", meta.BlockID,
"parentNumber", meta.BlockID.Uint64()-1,
"parentHash", meta.ParentHash,
)

fcRes, err := rpc.L2Engine.ForkchoiceUpdate(ctx, &engine.ForkchoiceStateV1{HeadBlockHash: meta.ParentHash}, nil)
if err != nil {
return nil, err
}
if fcRes.PayloadStatus.Status != engine.VALID {
return nil, fmt.Errorf("unexpected ForkchoiceUpdate response status: %s", fcRes.PayloadStatus.Status)
}
}

// Create a new execution payload.
payload, err := createExecutionPayloads(ctx, rpc, meta, txListBytes)
if err != nil {
Expand Down
126 changes: 126 additions & 0 deletions packages/taiko-client/driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,132 @@ func (s *DriverTestSuite) TestOnUnsafeL2PayloadWithInvalidPayload() {
s.Equal(l2Head1.Hash(), l2Head2.Hash())
}

func (s *DriverTestSuite) TestGossipMessagesRandomReorgs() {
s.ForkIntoPacaya(s.p, s.d.ChainSyncer().BlobSyncer())
s.ProposeAndInsertEmptyBlocks(s.p, s.d.ChainSyncer().BlobSyncer())

l1Head, err := s.d.rpc.L1.HeaderByNumber(context.Background(), nil)
s.Nil(err)
l2Head1, err := s.d.rpc.L2.HeaderByNumber(context.Background(), nil)
s.Nil(err)

headL1Origin, err := s.RPCClient.L2.HeadL1Origin(context.Background())
s.Nil(err)
s.Equal(l2Head1.Number.Uint64(), headL1Origin.BlockID.Uint64())

snapshotID := s.SetL1Snapshot()

var (
lenForkA = rand.Intn(6) + 3
forkA = make([]*types.Block, 0)
lenForkB = lenForkA + rand.Intn(3) + 1
forkB = make([]*types.Block, 0)
)

for i := 0; i < lenForkA; i++ {
s.ProposeAndInsertValidBlock(s.p, s.d.ChainSyncer().BlobSyncer())
}

l2Head2, err := s.d.rpc.L2.HeaderByNumber(context.Background(), nil)
s.Nil(err)
s.Greater(l2Head2.Number.Uint64(), l2Head1.Number.Uint64())

for i := l2Head1.Number.Uint64() + 1; i <= l2Head2.Number.Uint64(); i++ {
block, err := s.RPCClient.L2.BlockByNumber(context.Background(), new(big.Int).SetUint64(i))
s.Nil(err)
forkA = append(forkA, block)
}
s.Equal(l2Head2.Number.Uint64()-l2Head1.Number.Uint64(), uint64(len(forkA)))

s.RevertL1Snapshot(snapshotID)
s.L1Mine()
s.Nil(rpc.SetHead(context.Background(), s.RPCClient.L2, l2Head1.Number))
_, err = s.RPCClient.L2Engine.SetHeadL1Origin(context.Background(), headL1Origin.BlockID)
s.Nil(err)
s.d.state.SetL1Current(l1Head)
s.Nil(s.d.ChainSyncer().Sync())
s.InitProposer()

snapshotID = s.SetL1Snapshot()

for i := 0; i < lenForkB; i++ {
s.ProposeAndInsertEmptyBlocks(s.p, s.d.ChainSyncer().BlobSyncer())
}

l2Head3, err := s.d.rpc.L2.HeaderByNumber(context.Background(), nil)
s.Nil(err)
s.Greater(l2Head3.Number.Uint64(), l2Head2.Number.Uint64())

for i := l2Head1.Number.Uint64() + 1; i <= l2Head3.Number.Uint64(); i++ {
block, err := s.RPCClient.L2.BlockByNumber(context.Background(), new(big.Int).SetUint64(i))
s.Nil(err)
forkB = append(forkB, block)
}
s.Equal(l2Head3.Number.Uint64()-l2Head1.Number.Uint64(), uint64(len(forkB)))

s.RevertL1Snapshot(snapshotID)
s.L1Mine()
s.Nil(rpc.SetHead(context.Background(), s.RPCClient.L2, l2Head1.Number))
_, err = s.RPCClient.L2Engine.SetHeadL1Origin(context.Background(), headL1Origin.BlockID)
s.Nil(err)
s.d.state.SetL1Current(l1Head)
s.Nil(s.d.ChainSyncer().Sync())
s.InitProposer()

headL1Origin, err = s.RPCClient.L2.HeadL1Origin(context.Background())
s.Nil(err)
s.Equal(l2Head1.Number.Uint64(), headL1Origin.BlockID.Uint64())

l2Head4, err := s.d.rpc.L2.HeaderByNumber(context.Background(), nil)
s.Nil(err)
s.Equal(l2Head1.Number.Uint64(), l2Head4.Number.Uint64())

// Randomly gossip preconfirmation messages based on the blocks
// in the forkA and forkB
blocks := append(forkA, forkB...)
for i := len(blocks) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
blocks[i], blocks[j] = blocks[j], blocks[i]
}

for _, block := range blocks {
baseFee, overflow := uint256.FromBig(block.BaseFee())
s.False(overflow)

b, err := utils.EncodeAndCompressTxList(block.Transactions())
s.Nil(err)
s.GreaterOrEqual(len(block.Transactions()), 1)

s.Nil(s.d.preconfBlockServer.OnUnsafeL2Payload(
context.Background(),
peer.ID(testutils.RandomBytes(32)),
&eth.ExecutionPayloadEnvelope{ExecutionPayload: &eth.ExecutionPayload{
BlockHash: block.Hash(),
ParentHash: block.ParentHash(),
FeeRecipient: block.Coinbase(),
PrevRandao: eth.Bytes32(block.MixDigest()),
BlockNumber: eth.Uint64Quantity(block.Number().Uint64()),
GasLimit: eth.Uint64Quantity(block.GasLimit()),
Timestamp: eth.Uint64Quantity(block.Time()),
ExtraData: block.Extra(),
BaseFeePerGas: eth.Uint256Quantity(*baseFee),
Transactions: []eth.Data{b},
Withdrawals: &types.Withdrawals{},
}},
))
}

l2Head5, err := s.d.rpc.L2.BlockByNumber(context.Background(), nil)
s.Nil(err)

log.Info("Current L2 head", "number", l2Head5.Number().Uint64(), "expected", l2Head3.Number.Uint64())
s.Equal(l2Head3.Number.Uint64(), l2Head5.Number().Uint64())

headL1Origin, err = s.RPCClient.L2.HeadL1Origin(context.Background())
s.Nil(err)
s.Equal(l2Head1.Number.Uint64(), headL1Origin.BlockID.Uint64())
}

func (s *DriverTestSuite) TestOnUnsafeL2PayloadWithMissingAncients() {
s.ForkIntoPacaya(s.p, s.d.ChainSyncer().BlobSyncer())
// Propose some valid L2 blocks
Expand Down
4 changes: 4 additions & 0 deletions packages/taiko-client/internal/testutils/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ func (s *ClientTestSuite) IncreaseTime(time uint64) {
s.NotNil(result)
}

func (s *ClientTestSuite) L1Mine() {
s.Nil(s.RPCClient.L1.CallContext(context.Background(), nil, "evm_mine"))
}

func (s *ClientTestSuite) SetNextBlockTimestamp(time uint64) {
var result uint64
s.Nil(s.RPCClient.L1.CallContext(context.Background(), &result, "evm_setNextBlockTimestamp", time))
Expand Down
Loading