Skip to content

Commit 6ea3b8d

Browse files
committed
shot4
1 parent f327423 commit 6ea3b8d

5 files changed

Lines changed: 193 additions & 33 deletions

File tree

block/internal/executing/executor.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ type Executor struct {
9090
cachedPubKey crypto.PubKey
9191
cachedValidatorHash types.Hash
9292
signerInfoCached bool
93+
94+
// cachedChainID avoids per-block allocation in RetrieveBatch
95+
cachedChainID []byte
9396
}
9497

9598
// NewExecutor creates a new block executor.
@@ -152,6 +155,7 @@ func NewExecutor(
152155
txNotifyCh: make(chan struct{}, 1),
153156
errorCh: errorCh,
154157
logger: logger.With().Str("component", "executor").Logger(),
158+
cachedChainID: []byte(genesis.ChainID),
155159
}
156160
e.blockProducer = e
157161
return e, nil
@@ -608,7 +612,7 @@ func (e *Executor) ProduceBlock(ctx context.Context) error {
608612
// RetrieveBatch gets the next batch of transactions from the sequencer.
609613
func (e *Executor) RetrieveBatch(ctx context.Context) (*BatchData, error) {
610614
req := coresequencer.GetNextBatchRequest{
611-
Id: []byte(e.genesis.ChainID),
615+
Id: e.cachedChainID,
612616
MaxBytes: common.DefaultMaxBlobSize,
613617
LastBatchData: [][]byte{}, // Can be populated if needed for sequencer context
614618
}

execution/evm/engine_rpc_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func NewEngineRPCClient(client *rpc.Client) EngineRPCClient {
1919
return &engineRPCClient{client: client}
2020
}
2121

22-
func (e *engineRPCClient) ForkchoiceUpdated(ctx context.Context, state engine.ForkchoiceStateV1, args map[string]any) (*engine.ForkChoiceResponse, error) {
22+
func (e *engineRPCClient) ForkchoiceUpdated(ctx context.Context, state engine.ForkchoiceStateV1, args interface{}) (*engine.ForkChoiceResponse, error) {
2323
var result engine.ForkChoiceResponse
2424
err := e.client.CallContext(ctx, &result, "engine_forkchoiceUpdatedV3", state, args)
2525
if err != nil {

execution/evm/engine_rpc_tracing.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func withTracingEngineRPCClient(inner EngineRPCClient) EngineRPCClient {
2626
}
2727
}
2828

29-
func (t *tracedEngineRPCClient) ForkchoiceUpdated(ctx context.Context, state engine.ForkchoiceStateV1, args map[string]any) (*engine.ForkChoiceResponse, error) {
29+
func (t *tracedEngineRPCClient) ForkchoiceUpdated(ctx context.Context, state engine.ForkchoiceStateV1, args interface{}) (*engine.ForkChoiceResponse, error) {
3030
ctx, span := t.tracer.Start(ctx, "Engine.ForkchoiceUpdated",
3131
trace.WithAttributes(
3232
attribute.String("method", "engine_forkchoiceUpdatedV3"),

execution/evm/execution.go

Lines changed: 85 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func retryWithBackoffOnPayloadStatus(ctx context.Context, fn func() error, maxRe
152152
// EngineRPCClient abstracts Engine API RPC calls for tracing and testing.
153153
type EngineRPCClient interface {
154154
// ForkchoiceUpdated updates the forkchoice state and optionally starts payload building.
155-
ForkchoiceUpdated(ctx context.Context, state engine.ForkchoiceStateV1, args map[string]any) (*engine.ForkChoiceResponse, error)
155+
ForkchoiceUpdated(ctx context.Context, state engine.ForkchoiceStateV1, args interface{}) (*engine.ForkChoiceResponse, error)
156156

157157
// GetPayload retrieves a previously requested execution payload.
158158
GetPayload(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error)
@@ -396,7 +396,16 @@ func (c *EngineClient) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight
396396
}
397397

398398
// 2. Prepare payload attributes
399-
txsPayload := c.filterTransactions(txs)
399+
// Use zero-alloc struct instead of map to avoid reflection overhead
400+
evPayloadAttrs := PayloadAttributesV3{
401+
Timestamp: timestamp.Unix(),
402+
PrevRandao: c.derivePrevRandao(blockHeight).Hex(),
403+
SuggestedFeeRecipient: c.feeRecipient.Hex(),
404+
Withdrawals: emptyWithdrawals,
405+
ParentBeaconBlockRoot: zeroHashHex,
406+
Transactions: PayloadTransactions(txs),
407+
GasLimit: prevGasLimit,
408+
}
400409

401410
// Cache parent block hash for safe-block lookups.
402411
c.cacheBlockHash(blockHeight-1, prevBlockHash)
@@ -411,20 +420,6 @@ func (c *EngineClient) ExecuteTxs(ctx context.Context, txs [][]byte, blockHeight
411420
}
412421
c.mu.Unlock()
413422

414-
// update forkchoice to get the next payload id
415-
evPayloadAttrs := map[string]any{
416-
// Standard Ethereum payload attributes (flattened) - using camelCase as expected by JSON
417-
"timestamp": timestamp.Unix(),
418-
"prevRandao": c.derivePrevRandao(blockHeight),
419-
"suggestedFeeRecipient": c.feeRecipient,
420-
"withdrawals": emptyWithdrawals,
421-
// V3 requires parentBeaconBlockRoot
422-
"parentBeaconBlockRoot": zeroHashHex, // Use zero hash for evolve
423-
// evolve-specific fields
424-
"transactions": txsPayload,
425-
"gasLimit": prevGasLimit, // Use camelCase to match JSON conventions
426-
}
427-
428423
c.logger.Debug().
429424
Uint64("height", blockHeight).
430425
Int("tx_count", len(txs)).
@@ -874,20 +869,6 @@ func (c *EngineClient) reconcileExecutionAtHeight(ctx context.Context, height ui
874869
return nil, nil, false, nil
875870
}
876871

877-
// filterTransactions formats transactions for the payload.
878-
// DA transactions should already be filtered via FilterTxs before reaching here.
879-
// Mempool transactions are already validated when added to mempool.
880-
func (c *EngineClient) filterTransactions(txs [][]byte) []string {
881-
validTxs := make([]string, 0, len(txs))
882-
for _, tx := range txs {
883-
if len(tx) == 0 {
884-
continue
885-
}
886-
validTxs = append(validTxs, "0x"+hex.EncodeToString(tx))
887-
}
888-
return validTxs
889-
}
890-
891872
// GetExecutionInfo returns current execution layer parameters.
892873
func (c *EngineClient) GetExecutionInfo(ctx context.Context) (execution.ExecutionInfo, error) {
893874
if cached := c.cachedExecutionInfo.Load(); cached != nil {
@@ -1170,3 +1151,77 @@ func getAuthToken(jwtSecret []byte) (string, error) {
11701151
}
11711152
return authToken, nil
11721153
}
1154+
1155+
// PayloadAttributesV3 replaces the untyped map for better performance (no reflection)
1156+
type PayloadAttributesV3 struct {
1157+
Timestamp int64 `json:"timestamp"`
1158+
PrevRandao string `json:"prevRandao"`
1159+
SuggestedFeeRecipient string `json:"suggestedFeeRecipient"`
1160+
Withdrawals []*types.Withdrawal `json:"withdrawals"`
1161+
ParentBeaconBlockRoot string `json:"parentBeaconBlockRoot"`
1162+
Transactions PayloadTransactions `json:"transactions"`
1163+
GasLimit uint64 `json:"gasLimit"`
1164+
}
1165+
1166+
// PayloadTransactions implements custom JSON marshaling to avoid intermediate string allocations.
1167+
type PayloadTransactions [][]byte
1168+
1169+
// MarshalJSON encodes transactions as a JSON array of hex strings directly to bytes.
1170+
func (txs PayloadTransactions) MarshalJSON() ([]byte, error) {
1171+
if len(txs) == 0 {
1172+
return []byte("[]"), nil
1173+
}
1174+
1175+
// Pre-calculate full size to perform a single allocation:
1176+
// 2 bytes for [] + (len(txs)-1) commas + per tx: 2 quotes + 2 prefix + hex len
1177+
size := 2
1178+
count := 0
1179+
for _, tx := range txs {
1180+
if len(tx) == 0 {
1181+
continue
1182+
}
1183+
// comma
1184+
if count > 0 {
1185+
size++
1186+
}
1187+
// "0x..."
1188+
size += 4 + hex.EncodedLen(len(tx))
1189+
count++
1190+
}
1191+
1192+
if count == 0 {
1193+
return []byte("[]"), nil
1194+
}
1195+
1196+
buf := make([]byte, 0, size)
1197+
buf = append(buf, '[')
1198+
written := 0
1199+
for _, tx := range txs {
1200+
if len(tx) == 0 {
1201+
continue
1202+
}
1203+
if written > 0 {
1204+
buf = append(buf, ',')
1205+
}
1206+
buf = append(buf, '"', '0', 'x')
1207+
1208+
// Append encoded hex directly
1209+
n := hex.EncodedLen(len(tx))
1210+
start := len(buf)
1211+
// Ensure capacity
1212+
if cap(buf) < start+n {
1213+
// grow logic if needed, but make() with exact size should cover it
1214+
newBuf := make([]byte, len(buf), start+n*2)
1215+
copy(newBuf, buf)
1216+
buf = newBuf
1217+
}
1218+
// Expand length
1219+
buf = buf[:start+n]
1220+
hex.Encode(buf[start:], tx)
1221+
1222+
buf = append(buf, '"')
1223+
written++
1224+
}
1225+
buf = append(buf, ']')
1226+
return buf, nil
1227+
}

execution/evm/payload_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package evm
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/ethereum/go-ethereum/common/hexutil"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestPayloadTransactionsMarshalJSON(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
input [][]byte
15+
expected string
16+
}{
17+
{
18+
name: "empty",
19+
input: [][]byte{},
20+
expected: "[]",
21+
},
22+
{
23+
name: "nil",
24+
input: nil,
25+
expected: "[]",
26+
},
27+
{
28+
name: "single tx",
29+
input: [][]byte{
30+
{0x01, 0x02, 0x03},
31+
},
32+
expected: `["0x010203"]`,
33+
},
34+
{
35+
name: "multiple txs",
36+
input: [][]byte{
37+
{0xaa, 0xbb},
38+
{0xcc, 0xdd},
39+
},
40+
expected: `["0xaabb","0xccdd"]`,
41+
},
42+
{
43+
name: "skip empty tx",
44+
input: [][]byte{
45+
{0x01},
46+
{},
47+
{0x02},
48+
},
49+
expected: `["0x01","0x02"]`,
50+
},
51+
{
52+
name: "all empty",
53+
input: [][]byte{
54+
{},
55+
{},
56+
},
57+
expected: "[]",
58+
},
59+
}
60+
61+
for _, tt := range tests {
62+
t.Run(tt.name, func(t *testing.T) {
63+
pt := PayloadTransactions(tt.input)
64+
b, err := pt.MarshalJSON()
65+
require.NoError(t, err)
66+
require.Equal(t, tt.expected, string(b))
67+
})
68+
}
69+
}
70+
71+
func BenchmarkPayloadTransactionsMarshalJSON(b *testing.B) {
72+
// Setup large payload
73+
txs := make([][]byte, 1000)
74+
for i := range txs {
75+
txs[i] = make([]byte, 1024) // 1KB tx
76+
}
77+
pt := PayloadTransactions(txs)
78+
79+
b.ResetTimer()
80+
for i := 0; i < b.N; i++ {
81+
_, _ = pt.MarshalJSON()
82+
}
83+
}
84+
85+
func BenchmarkStandardJSONMarshal(b *testing.B) {
86+
// Setup large payload
87+
txs := make([][]byte, 1000)
88+
for i := range txs {
89+
txs[i] = make([]byte, 1024) // 1KB tx
90+
}
91+
92+
b.ResetTimer()
93+
for i := 0; i < b.N; i++ {
94+
// Simulate old behavior: allocate strings + json marshal
95+
validTxs := make([]string, 0, len(txs))
96+
for _, tx := range txs {
97+
validTxs = append(validTxs, hexutil.Encode(tx))
98+
}
99+
_, _ = json.Marshal(validTxs)
100+
}
101+
}

0 commit comments

Comments
 (0)