diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9834fc6fcc..d261180c5e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,7 +2,7 @@ name: "Push multi-arch docker image to GAR" on: push: - branches: [taiko] + branches: [taiko, is_forced_inclusion] tags: - "v*" diff --git a/build/checksums.txt b/build/checksums.txt index 06e44ea2b0..b5f12967f1 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -3,7 +3,7 @@ # version:spec-tests pectra-devnet-6@v1.0.0 # https://github.com/ethereum/execution-spec-tests/releases # https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/fixtures_pectra-devnet-6.tar.gz -b69211752a3029083c020dc635fe12156ca1a6725a08559da540a0337586a77e fixtures_pectra-devnet-6.tar.gz +5ed3bec40a02776a4096c17b84b59fb20fd99fbabba8101fdac3d35a001cf82a fixtures_pectra-devnet-6.tar.gz # version:golang 1.24.1 # https://go.dev/dl/ diff --git a/core/rawdb/taiko_l1_origin.go b/core/rawdb/taiko_l1_origin.go index 83d6c181e3..8c6421443d 100644 --- a/core/rawdb/taiko_l1_origin.go +++ b/core/rawdb/taiko_l1_origin.go @@ -29,6 +29,17 @@ func l1OriginKey(blockID *big.Int) []byte { // L1Origin represents a L1Origin of a L2 block. type L1Origin struct { + BlockID *big.Int `json:"blockID" gencodec:"required"` + L2BlockHash common.Hash `json:"l2BlockHash"` + L1BlockHeight *big.Int `json:"l1BlockHeight" rlp:"optional"` + L1BlockHash common.Hash `json:"l1BlockHash" rlp:"optional"` + IsForcedInclusion bool `json:"isForcedInclusion" rlp:"optional"` + Signature [65]byte `json:"signature" rlp:"optional"` // signature of the envelope via p2p + BuildPayloadArgsID [8]byte `json:"buildPayloadArgsID" rlp:"optional"` +} + +// L1OriginLegacyTwo represents a second legacy L1Origin of a L2 block. +type L1OriginLegacyTwo struct { BlockID *big.Int `json:"blockID" gencodec:"required"` L2BlockHash common.Hash `json:"l2BlockHash"` L1BlockHeight *big.Int `json:"l1BlockHeight" rlp:"optional"` @@ -70,32 +81,49 @@ func WriteL1Origin(db ethdb.KeyValueWriter, blockID *big.Int, l1Origin *L1Origin // ReadL1Origin retrieves the given L2 block's L1Origin from database. func ReadL1Origin(db ethdb.KeyValueReader, blockID *big.Int) (*L1Origin, error) { data, _ := db.Get(l1OriginKey(blockID)) + if len(data) == 0 { return nil, nil } - // First try to decode the new version (with new fields). - l1Origin := new(L1Origin) - if err := rlp.Decode(bytes.NewReader(data), l1Origin); err != nil { - // If decoding the new version fails, try to decode the legacy version (without new fields). - l1OriginLegacy := new(L1OriginLegacy) - if err := rlp.Decode(bytes.NewReader(data), &l1OriginLegacy); err != nil { - return nil, fmt.Errorf("invalid legacy L1Origin RLP bytes: %w", err) - } - - // If decoding legacy version succeeds, manually - // construct the new L1Origin with default values for the new fields. - l1Origin = &L1Origin{ - BlockID: l1OriginLegacy.BlockID, - L2BlockHash: l1OriginLegacy.L2BlockHash, - L1BlockHeight: l1OriginLegacy.L1BlockHeight, - L1BlockHash: l1OriginLegacy.L1BlockHash, - // Set BuildPayloadArgsID to an empty hash as the intended default for legacy L1Origin conversions. - BuildPayloadArgsID: [8]byte{}, - } + // try to decode standard L1Origin first + var o L1Origin + if err := rlp.Decode(bytes.NewReader(data), &o); err == nil { + return &o, nil } - return l1Origin, nil + // 3) try second legacy version, which includes BuildPayloadArgsID + var o2 L1OriginLegacyTwo + if err := rlp.Decode(bytes.NewReader(data), &o2); err == nil { + return &L1Origin{ + BlockID: o2.BlockID, + L2BlockHash: o2.L2BlockHash, + L1BlockHeight: o2.L1BlockHeight, + L1BlockHash: o2.L1BlockHash, + BuildPayloadArgsID: o2.BuildPayloadArgsID, + // new fields default to zero/false: + IsForcedInclusion: false, + Signature: [65]byte{}, + }, nil + } + + // 4) try original legacy version (no BuildPayloadArgsID, IsForcedInclusio nor Signature) + var o1 L1OriginLegacy + if err := rlp.Decode(bytes.NewReader(data), &o1); err == nil { + return &L1Origin{ + BlockID: o1.BlockID, + L2BlockHash: o1.L2BlockHash, + L1BlockHeight: o1.L1BlockHeight, + L1BlockHash: o1.L1BlockHash, + // new fields default to zero/false: + IsForcedInclusion: false, + Signature: [65]byte{}, + BuildPayloadArgsID: [8]byte{}, + }, nil + } + + // 5) nothing worked + return nil, fmt.Errorf("invalid L1Origin RLP bytes: failed new, legacyTwo and legacyOne decodes") } // WriteHeadL1Origin stores the given L1Origin as the last L1Origin. diff --git a/core/rawdb/taiko_l1_origin_test.go b/core/rawdb/taiko_l1_origin_test.go index 7bffa2f6a9..7b7b4978d1 100644 --- a/core/rawdb/taiko_l1_origin_test.go +++ b/core/rawdb/taiko_l1_origin_test.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -39,6 +40,8 @@ func TestL1Origin(t *testing.T) { L1BlockHeight: nil, L1BlockHash: randomHash(), BuildPayloadArgsID: [8]byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + IsForcedInclusion: true, + Signature: [65]byte{0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10}, } WriteL1Origin(db, testL1Origin.BlockID, testL1Origin) l1Origin, err := ReadL1Origin(db, testL1Origin.BlockID) @@ -49,6 +52,8 @@ func TestL1Origin(t *testing.T) { assert.True(t, l1Origin.L1BlockHeight.Cmp(common.Big0) == 0) assert.Equal(t, testL1Origin.L1BlockHash, l1Origin.L1BlockHash) assert.Equal(t, testL1Origin.BuildPayloadArgsID, l1Origin.BuildPayloadArgsID) + assert.Equal(t, testL1Origin.IsForcedInclusion, l1Origin.IsForcedInclusion) + assert.Equal(t, testL1Origin.Signature, l1Origin.Signature) } func TestHeadL1Origin(t *testing.T) { @@ -60,3 +65,78 @@ func TestHeadL1Origin(t *testing.T) { require.NotNil(t, blockID) assert.Equal(t, testBlockID, blockID) } + +func TestReadL1OriginFallbacks(t *testing.T) { + db := NewMemoryDatabase() + + t.Run("LegacyTwo → L1Origin", func(t *testing.T) { + // prepare a second‐legacy L1Origin + blockID := randomBigInt() + height := randomBigInt() + l2Hash := randomHash() + l1Hash := randomHash() + buildID := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} + + legacyTwo := &L1OriginLegacyTwo{ + BlockID: blockID, + L2BlockHash: l2Hash, + L1BlockHeight: height, + L1BlockHash: l1Hash, + BuildPayloadArgsID: buildID, + } + + // encode & write raw RLP + data, err := rlp.EncodeToBytes(legacyTwo) + require.NoError(t, err) + require.NoError(t, db.Put(l1OriginKey(blockID), data)) + + // read back via our helper + got, err := ReadL1Origin(db, blockID) + require.NoError(t, err) + require.NotNil(t, got) + + // verify fields + assert.Equal(t, blockID, got.BlockID) + assert.Equal(t, l2Hash, got.L2BlockHash) + assert.True(t, got.L1BlockHeight.Cmp(height) == 0) + assert.Equal(t, l1Hash, got.L1BlockHash) + assert.Equal(t, buildID, got.BuildPayloadArgsID) + assert.False(t, got.IsForcedInclusion) + assert.Equal(t, [65]byte{}, got.Signature) + }) + + t.Run("LegacyOne → L1Origin", func(t *testing.T) { + // prepare the original legacy L1Origin + blockID := randomBigInt() + height := randomBigInt() + l2Hash := randomHash() + l1Hash := randomHash() + + legacyOne := &L1OriginLegacy{ + BlockID: blockID, + L2BlockHash: l2Hash, + L1BlockHeight: height, + L1BlockHash: l1Hash, + } + + // encode & write raw RLP + data, err := rlp.EncodeToBytes(legacyOne) + require.NoError(t, err) + require.NoError(t, db.Put(l1OriginKey(blockID), data)) + + // read back via our helper + got, err := ReadL1Origin(db, blockID) + require.NoError(t, err) + require.NotNil(t, got) + + // verify fields + assert.Equal(t, blockID, got.BlockID) + assert.Equal(t, l2Hash, got.L2BlockHash) + assert.True(t, got.L1BlockHeight.Cmp(height) == 0) + assert.Equal(t, l1Hash, got.L1BlockHash) + // new fields should be zero-default + assert.Equal(t, [8]byte{}, got.BuildPayloadArgsID) + assert.False(t, got.IsForcedInclusion) + assert.Equal(t, [65]byte{}, got.Signature) + }) +} diff --git a/eth/taiko_api_backend.go b/eth/taiko_api_backend.go index cc6ca6bed6..15f4c5019d 100644 --- a/eth/taiko_api_backend.go +++ b/eth/taiko_api_backend.go @@ -87,6 +87,18 @@ func (a *TaikoAuthAPIBackend) UpdateL1Origin(l1Origin *rawdb.L1Origin) *rawdb.L1 return l1Origin } +func (a *TaikoAuthAPIBackend) SetL1OriginSignature(blockID *big.Int, signature [65]byte) (*rawdb.L1Origin, error) { + l1Origin, err := rawdb.ReadL1Origin(a.eth.ChainDb(), blockID) + if err != nil { + return nil, err + } + + l1Origin.Signature = signature + rawdb.WriteL1Origin(a.eth.ChainDb(), blockID, l1Origin) + + return l1Origin, nil +} + // TxPoolContent retrieves the transaction pool content with the given upper limits. func (a *TaikoAuthAPIBackend) TxPoolContent( beneficiary common.Address,