feat(core): add optional isForcedInclusion and Signature to L1Origin (#434)

* add isForcedInclusion to L1Origin

* test

* update file fash

* add signature

* use [65]byte

* add helper method

* build

* comment

* fix legacy, add test

* return nil if empty
This commit is contained in:
jeff 2025-07-13 21:22:08 -07:00 committed by GitHub
parent 17fbc6b74c
commit a1fe1d8309
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 142 additions and 22 deletions

View file

@ -2,7 +2,7 @@ name: "Push multi-arch docker image to GAR"
on: on:
push: push:
branches: [taiko] branches: [taiko, is_forced_inclusion]
tags: tags:
- "v*" - "v*"

View file

@ -3,7 +3,7 @@
# version:spec-tests pectra-devnet-6@v1.0.0 # 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
# https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-6%40v1.0.0/fixtures_pectra-devnet-6.tar.gz # 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 # version:golang 1.24.1
# https://go.dev/dl/ # https://go.dev/dl/

View file

@ -29,6 +29,17 @@ func l1OriginKey(blockID *big.Int) []byte {
// L1Origin represents a L1Origin of a L2 block. // L1Origin represents a L1Origin of a L2 block.
type L1Origin struct { 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"` BlockID *big.Int `json:"blockID" gencodec:"required"`
L2BlockHash common.Hash `json:"l2BlockHash"` L2BlockHash common.Hash `json:"l2BlockHash"`
L1BlockHeight *big.Int `json:"l1BlockHeight" rlp:"optional"` 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. // ReadL1Origin retrieves the given L2 block's L1Origin from database.
func ReadL1Origin(db ethdb.KeyValueReader, blockID *big.Int) (*L1Origin, error) { func ReadL1Origin(db ethdb.KeyValueReader, blockID *big.Int) (*L1Origin, error) {
data, _ := db.Get(l1OriginKey(blockID)) data, _ := db.Get(l1OriginKey(blockID))
if len(data) == 0 { if len(data) == 0 {
return nil, nil return nil, nil
} }
// First try to decode the new version (with new fields). // try to decode standard L1Origin first
l1Origin := new(L1Origin) var o L1Origin
if err := rlp.Decode(bytes.NewReader(data), l1Origin); err != nil { if err := rlp.Decode(bytes.NewReader(data), &o); err == nil {
// If decoding the new version fails, try to decode the legacy version (without new fields). return &o, nil
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{},
}
} }
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. // WriteHeadL1Origin stores the given L1Origin as the last L1Origin.

View file

@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -39,6 +40,8 @@ func TestL1Origin(t *testing.T) {
L1BlockHeight: nil, L1BlockHeight: nil,
L1BlockHash: randomHash(), L1BlockHash: randomHash(),
BuildPayloadArgsID: [8]byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, 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) WriteL1Origin(db, testL1Origin.BlockID, testL1Origin)
l1Origin, err := ReadL1Origin(db, testL1Origin.BlockID) 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.True(t, l1Origin.L1BlockHeight.Cmp(common.Big0) == 0)
assert.Equal(t, testL1Origin.L1BlockHash, l1Origin.L1BlockHash) assert.Equal(t, testL1Origin.L1BlockHash, l1Origin.L1BlockHash)
assert.Equal(t, testL1Origin.BuildPayloadArgsID, l1Origin.BuildPayloadArgsID) 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) { func TestHeadL1Origin(t *testing.T) {
@ -60,3 +65,78 @@ func TestHeadL1Origin(t *testing.T) {
require.NotNil(t, blockID) require.NotNil(t, blockID)
assert.Equal(t, testBlockID, blockID) assert.Equal(t, testBlockID, blockID)
} }
func TestReadL1OriginFallbacks(t *testing.T) {
db := NewMemoryDatabase()
t.Run("LegacyTwo → L1Origin", func(t *testing.T) {
// prepare a secondlegacy 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)
})
}

View file

@ -87,6 +87,18 @@ func (a *TaikoAuthAPIBackend) UpdateL1Origin(l1Origin *rawdb.L1Origin) *rawdb.L1
return l1Origin 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. // TxPoolContent retrieves the transaction pool content with the given upper limits.
func (a *TaikoAuthAPIBackend) TxPoolContent( func (a *TaikoAuthAPIBackend) TxPoolContent(
beneficiary common.Address, beneficiary common.Address,