Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions eth/catalyst/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ type assembleBlockParamsMarshaling struct {
type AssembleL2BlockParams struct {
Number uint64 `json:"number" gencodec:"required"`
Transactions [][]byte `json:"transactions"`
Timestamp *uint64 `json:"timestamp"`
}
Comment on lines 41 to 45
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

gencodec go-ethereum pointer hexutil override examples

💡 Result:

Summary + minimal example — show how to use gencodec field overrides to make pointer fields marshal/unmarshal with go-ethereum's hexutil types.

Key points

  • gencodec supports a "field override" struct: put the alternate (marshal/unmarshal) types there; generated code converts between original and override types. [1]
  • Use hexutil types (hexutil.Bytes, hexutil.Big, hexutil.Uint64, etc.) in the override to get 0x-prefixed hex JSON encoding. [2]
  • Generated code will use pointer override types for required pointer fields (example of generated code using hexutil shown below). [3]

Minimal example

  1. Original type (in package types.go)
    package mypkg

import "math/big"

type MyStruct struct {
Data []byte json:"data" gencodec:"required"
Amount *big.Int json:"amount" gencodec:"required"
Nonce uint64 json:"nonce" gencodec:"required"
}

  1. Override type + go:generate (in same package)
    //go:generate gencodec -type MyStruct -field-override mystruct_marshaling -out mystruct_json.go
    type mystruct_marshaling struct {
    Data hexutil.Bytes // overrides []byte -> hexutil.Bytes
    Amount *hexutil.Big // overrides *big.Int -> *hexutil.Big
    Nonce hexutil.Uint64 json:"nonce" // overrides uint64 -> hexutil.Uint64
    }

  2. Run generator
    go generate ./...

What gencodec generates (conceptual)

  • MarshalJSON will build an enc struct with the override types and assign:
    enc.Data = hexutil.Bytes(m.Data)
    enc.Amount = (*hexutil.Big)(m.Amount) // (conversion code is generated)
    enc.Nonce = hexutil.Uint64(m.Nonce)
    then json.Marshal(&enc)
  • UnmarshalJSON will decode into pointer override fields (so missing required pointer fields are detected) and convert back into original types. See generated example using hexutil in real projects. [3]

References

  • gencodec documentation (field-override behavior, examples). [1]
  • go-ethereum hexutil types and behavior (hex marshaling types: Bytes, Big, Uint64). [2]
  • Example generated code using hexutil (shows pointer override conversions in generated Marshal/Unmarshal). [3]

🏁 Script executed:

# Search for examples of pointer fields with hexutil in overrides in the repo
rg 'type.*Marshaling\s*struct' -A 10 | head -100

Repository: morph-l2/go-ethereum

Length of output: 5492


🏁 Script executed:

# Look for BaseFee patterns in the codebase to see how pointer big.Int fields are overridden
rg 'BaseFee.*\*big.Int' -B 2 -A 5

Repository: morph-l2/go-ethereum

Length of output: 15345


🏁 Script executed:

# Find the gen_l2blockparams.go file to see the current generated code
fd 'gen_l2blockparams.go' -x cat {}

Repository: morph-l2/go-ethereum

Length of output: 1650


🏁 Script executed:

sed -n '35,60p' eth/catalyst/api_types.go

Repository: morph-l2/go-ethereum

Length of output: 1127


🏁 Script executed:

# Also check the override struct to see all override definitions
fd 'api_types.go' -x cat {} | head -100

Repository: morph-l2/go-ethereum

Length of output: 4240


🏁 Script executed:

head -80 eth/catalyst/api_types.go

Repository: morph-l2/go-ethereum

Length of output: 3321


🏁 Script executed:

# Find the second struct around lines 48-52
sed -n '40,80p' eth/catalyst/api_types.go

Repository: morph-l2/go-ethereum

Length of output: 1745


🏁 Script executed:

sed -n '40,80p' eth/catalyst/api_types.go

Repository: morph-l2/go-ethereum

Length of output: 1745


🏁 Script executed:

# Look for all override structs
rg 'type.*Marshaling struct' -A 8 eth/catalyst/api_types.go

Repository: morph-l2/go-ethereum

Length of output: 1440


🏁 Script executed:

# Get the full content of api_types.go to see both structs
cat eth/catalyst/api_types.go

Repository: morph-l2/go-ethereum

Length of output: 5814


Fix pointer/override type mismatch for AssembleL2BlockParams.Timestamp

AssembleL2BlockParams declares Timestamp *uint64 but the override type is non-pointer hexutil.Uint64. gencodec requires matching pointer signatures; this mismatch causes the override to be ignored and timestamp to encode as a plain JSON number instead of a hex quantity like number and other numeric Engine API fields.

Change the override to pointer-typed:

 type assembleL2BlockParamsMarshaling struct {
 	Number       hexutil.Uint64
 	Transactions []hexutil.Bytes
-	Timestamp    hexutil.Uint64
+	Timestamp    *hexutil.Uint64
 }

Then regenerate with go generate ./... so gen_l2blockparams.go properly encodes/decodes timestamp as a hex quantity.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In eth/catalyst/api_types.go around lines 41 to 45, the
AssembleL2BlockParams.Timestamp field is declared as *uint64 but the gencodec
override used elsewhere is a non-pointer hexutil.Uint64 which causes the
override to be ignored; update the override type to a pointer form (i.e.
*hexutil.Uint64) so it matches the *uint64 field, then run go generate ./... to
regenerate gen_l2blockparams.go so timestamp is encoded/decoded as a hex
quantity.


// JSON type overrides for assembleL2BlockParams.
type assembleL2BlockParamsMarshaling struct {
Number hexutil.Uint64
Transactions []hexutil.Bytes
Timestamp hexutil.Uint64
}

//go:generate go run github.com/fjl/gencodec -type executableData -field-override executableDataMarshaling -out gen_ed.go
Expand Down
4 changes: 4 additions & 0 deletions eth/catalyst/gen_l2blockparams.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion eth/catalyst/l2_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ func (api *l2ConsensusAPI) AssembleL2Block(params AssembleL2BlockParams) (*Execu
}

start := time.Now()
newBlockResult, err := api.eth.Miner().BuildBlock(parent.Hash(), time.Now(), transactions)
timestamp := uint64(time.Now().Unix())
if params.Timestamp != nil {
timestamp = *params.Timestamp
}
newBlockResult, err := api.eth.Miner().BuildBlock(parent.Hash(), timestamp, transactions)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions eth/catalyst/l2_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func TestValidateL2Block(t *testing.T) {
// generic case
err = sendTransfer(config, ethService)
require.NoError(t, err)
ret, err := ethService.Miner().BuildBlock(ethService.BlockChain().CurrentHeader().Hash(), time.Now(), nil)
ret, err := ethService.Miner().BuildBlock(ethService.BlockChain().CurrentHeader().Hash(), uint64(time.Now().Unix()), nil)
require.NoError(t, err)
block := ret.Block
l2Data := ExecutableL2Data{
Expand Down Expand Up @@ -170,7 +170,7 @@ func TestNewL2Block(t *testing.T) {

err := sendTransfer(config, ethService)
require.NoError(t, err)
ret, err := ethService.Miner().BuildBlock(ethService.BlockChain().CurrentHeader().Hash(), time.Now(), nil)
ret, err := ethService.Miner().BuildBlock(ethService.BlockChain().CurrentHeader().Hash(), uint64(time.Now().Unix()), nil)
block := ret.Block
require.NoError(t, err)
l2Data := ExecutableL2Data{
Expand Down Expand Up @@ -223,7 +223,7 @@ func TestNewSafeL2Block(t *testing.T) {

err := sendTransfer(config, ethService)
require.NoError(t, err)
ret, err := ethService.Miner().BuildBlock(ethService.BlockChain().CurrentHeader().Hash(), time.Now(), nil)
ret, err := ethService.Miner().BuildBlock(ethService.BlockChain().CurrentHeader().Hash(), uint64(time.Now().Unix()), nil)
require.NoError(t, err)
block := ret.Block
l2Data := SafeL2Data{
Expand Down
3 changes: 2 additions & 1 deletion ethclient/authclient/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

// AssembleL2Block assembles L2 Block used for L2 sequencer to propose a block in L2 consensus progress
func (ec *Client) AssembleL2Block(ctx context.Context, number *big.Int, transactions types.Transactions) (*catalyst.ExecutableL2Data, error) {
func (ec *Client) AssembleL2Block(ctx context.Context, timeStamp *uint64, number *big.Int, transactions types.Transactions) (*catalyst.ExecutableL2Data, error) {
txs := make([][]byte, 0, len(transactions))
for i, tx := range transactions {
bz, err := tx.MarshalBinary()
Expand All @@ -25,6 +25,7 @@ func (ec *Client) AssembleL2Block(ctx context.Context, number *big.Int, transact
err := ec.c.CallContext(ctx, &result, "engine_assembleL2Block", &catalyst.AssembleL2BlockParams{
Number: number.Uint64(),
Transactions: txs,
Timestamp: timeStamp,
})
return &result, err
}
Expand Down
4 changes: 2 additions & 2 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ func (miner *Miner) getSealingBlockAndState(params *generateParams) (*NewBlockRe
}
}

func (miner *Miner) BuildBlock(parentHash common.Hash, timestamp time.Time, transactions types.Transactions) (*NewBlockResult, error) {
func (miner *Miner) BuildBlock(parentHash common.Hash, timestamp uint64, transactions types.Transactions) (*NewBlockResult, error) {
return miner.getSealingBlockAndState(&generateParams{
timestamp: uint64(timestamp.Unix()),
timestamp: timestamp,
parentHash: parentHash,
transactions: transactions,
timeout: miner.newBlockTimeout,
Expand Down