Skip to content

Commit caa5ec3

Browse files
committed
updates
1 parent b9890aa commit caa5ec3

File tree

5 files changed

+706
-60
lines changed

5 files changed

+706
-60
lines changed

execution/evm/engine_geth.go

Lines changed: 128 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package evm
22

33
import (
44
"context"
5+
"crypto/sha256"
56
"encoding/binary"
67
"errors"
78
"fmt"
@@ -16,15 +17,18 @@ import (
1617
"github.com/ethereum/go-ethereum/core"
1718
"github.com/ethereum/go-ethereum/core/rawdb"
1819
"github.com/ethereum/go-ethereum/core/state"
20+
"github.com/ethereum/go-ethereum/core/tracing"
1921
"github.com/ethereum/go-ethereum/core/txpool"
2022
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
2123
"github.com/ethereum/go-ethereum/core/types"
2224
"github.com/ethereum/go-ethereum/core/vm"
25+
"github.com/ethereum/go-ethereum/crypto"
2326
"github.com/ethereum/go-ethereum/ethdb"
2427
"github.com/ethereum/go-ethereum/params"
2528
"github.com/ethereum/go-ethereum/rpc"
2629
"github.com/ethereum/go-ethereum/trie"
2730
"github.com/ethereum/go-ethereum/triedb"
31+
"github.com/holiman/uint256"
2832
ds "github.com/ipfs/go-datastore"
2933
"github.com/rs/zerolog"
3034
)
@@ -37,6 +41,12 @@ var (
3741
// baseFeeChangeDenominator is the EIP-1559 base fee change denominator.
3842
const baseFeeChangeDenominator = 8
3943

44+
// payloadTTL is how long a payload can remain in the map before being cleaned up.
45+
const payloadTTL = 60 * time.Second
46+
47+
// maxPayloads is the maximum number of payloads to keep in memory.
48+
const maxPayloads = 10
49+
4050
// GethBackend holds the in-process geth components.
4151
type GethBackend struct {
4252
db ethdb.Database
@@ -67,8 +77,14 @@ type payloadBuildState struct {
6777
transactions [][]byte
6878
gasLimit uint64
6979

80+
// createdAt tracks when this payload was created for TTL cleanup
81+
createdAt time.Time
82+
7083
// built payload (populated after getPayload)
7184
payload *engine.ExecutableData
85+
86+
// buildErr stores any error that occurred during payload build
87+
buildErr error
7288
}
7389

7490
// gethEngineClient implements EngineRPCClient using in-process geth.
@@ -302,10 +318,26 @@ func (g *gethEngineClient) ForkchoiceUpdated(ctx context.Context, fcState engine
302318
return nil, fmt.Errorf("failed to parse payload attributes: %w", err)
303319
}
304320

305-
// Generate payload ID deterministically from attributes
306-
g.backend.nextPayloadID++
307-
var payloadID engine.PayloadID
308-
binary.BigEndian.PutUint64(payloadID[:], g.backend.nextPayloadID)
321+
// Generate deterministic payload ID from attributes
322+
payloadID := g.generatePayloadID(payloadState)
323+
324+
// Check if we already have this payload (idempotency)
325+
if existing, ok := g.backend.payloads[payloadID]; ok {
326+
// Reuse existing payload if it hasn't errored
327+
if existing.buildErr == nil {
328+
response.PayloadID = &payloadID
329+
g.logger.Debug().
330+
Str("payload_id", payloadID.String()).
331+
Msg("reusing existing payload")
332+
return response, nil
333+
}
334+
// Previous build failed, remove it and try again
335+
delete(g.backend.payloads, payloadID)
336+
}
337+
338+
// Clean up old payloads before adding new one
339+
g.cleanupStalePayloads()
340+
309341
g.backend.payloads[payloadID] = payloadState
310342
response.PayloadID = &payloadID
311343

@@ -321,11 +353,73 @@ func (g *gethEngineClient) ForkchoiceUpdated(ctx context.Context, fcState engine
321353
return response, nil
322354
}
323355

356+
// generatePayloadID creates a deterministic payload ID from the payload attributes.
357+
// This ensures that the same attributes always produce the same ID for idempotency.
358+
func (g *gethEngineClient) generatePayloadID(ps *payloadBuildState) engine.PayloadID {
359+
h := sha256.New()
360+
h.Write(ps.parentHash[:])
361+
binary.Write(h, binary.BigEndian, ps.timestamp)
362+
h.Write(ps.prevRandao[:])
363+
h.Write(ps.feeRecipient[:])
364+
binary.Write(h, binary.BigEndian, ps.gasLimit)
365+
// Include transaction count and first tx hash for uniqueness
366+
binary.Write(h, binary.BigEndian, uint64(len(ps.transactions)))
367+
for _, tx := range ps.transactions {
368+
h.Write(tx)
369+
}
370+
sum := h.Sum(nil)
371+
var id engine.PayloadID
372+
copy(id[:], sum[:8])
373+
return id
374+
}
375+
376+
// cleanupStalePayloads removes payloads that have exceeded their TTL or when we have too many.
377+
func (g *gethEngineClient) cleanupStalePayloads() {
378+
now := time.Now()
379+
var staleIDs []engine.PayloadID
380+
381+
// Find stale payloads
382+
for id, ps := range g.backend.payloads {
383+
if now.Sub(ps.createdAt) > payloadTTL {
384+
staleIDs = append(staleIDs, id)
385+
}
386+
}
387+
388+
// Remove stale payloads
389+
for _, id := range staleIDs {
390+
delete(g.backend.payloads, id)
391+
g.logger.Debug().
392+
Str("payload_id", id.String()).
393+
Msg("cleaned up stale payload")
394+
}
395+
396+
// If still too many payloads, remove oldest ones
397+
for len(g.backend.payloads) >= maxPayloads {
398+
var oldestID engine.PayloadID
399+
var oldestTime time.Time
400+
first := true
401+
for id, ps := range g.backend.payloads {
402+
if first || ps.createdAt.Before(oldestTime) {
403+
oldestID = id
404+
oldestTime = ps.createdAt
405+
first = false
406+
}
407+
}
408+
if !first {
409+
delete(g.backend.payloads, oldestID)
410+
g.logger.Debug().
411+
Str("payload_id", oldestID.String()).
412+
Msg("evicted oldest payload due to limit")
413+
}
414+
}
415+
}
416+
324417
// parsePayloadAttributes extracts payload attributes from the map format.
325418
func (g *gethEngineClient) parsePayloadAttributes(parentHash common.Hash, attrs map[string]any) (*payloadBuildState, error) {
326419
ps := &payloadBuildState{
327420
parentHash: parentHash,
328421
withdrawals: []*types.Withdrawal{},
422+
createdAt: time.Now(),
329423
}
330424

331425
// Parse timestamp (required)
@@ -439,11 +533,19 @@ func (g *gethEngineClient) GetPayload(ctx context.Context, payloadID engine.Payl
439533
return nil, fmt.Errorf("unknown payload ID: %s", payloadID.String())
440534
}
441535

442-
// Build the block if not already built
536+
// Return cached error if previous build failed
537+
if payloadState.buildErr != nil {
538+
delete(g.backend.payloads, payloadID)
539+
return nil, fmt.Errorf("payload build previously failed: %w", payloadState.buildErr)
540+
}
541+
542+
// Build the payload if not already built
443543
if payloadState.payload == nil {
444-
startTime := time.Now()
544+
buildStartTime := time.Now()
445545
payload, err := g.buildPayload(ctx, payloadState)
446546
if err != nil {
547+
// Cache the error so we don't retry on the same payload
548+
payloadState.buildErr = err
447549
return nil, fmt.Errorf("failed to build payload: %w", err)
448550
}
449551
payloadState.payload = payload
@@ -454,11 +556,11 @@ func (g *gethEngineClient) GetPayload(ctx context.Context, payloadID engine.Payl
454556
Str("block_hash", payload.BlockHash.Hex()).
455557
Int("tx_count", len(payload.Transactions)).
456558
Uint64("gas_used", payload.GasUsed).
457-
Dur("build_time", time.Since(startTime)).
559+
Dur("build_time", time.Since(buildStartTime)).
458560
Msg("built payload")
459561
}
460562

461-
// Remove the payload from pending after retrieval
563+
// Remove the payload from pending after retrieval - caller has it now
462564
delete(g.backend.payloads, payloadID)
463565

464566
return &engine.ExecutionPayloadEnvelope{
@@ -593,6 +695,21 @@ func (g *gethEngineClient) buildPayload(ctx context.Context, ps *payloadBuildSta
593695
Msg("transaction execution summary")
594696
}
595697

698+
// Process withdrawals (EIP-4895) - credit ETH to withdrawal recipients
699+
// Withdrawals are processed after all transactions, crediting the specified
700+
// amount (in Gwei) to each recipient address.
701+
if len(ps.withdrawals) > 0 {
702+
for _, withdrawal := range ps.withdrawals {
703+
// Withdrawal amount is in Gwei, convert to Wei (multiply by 1e9)
704+
amount := new(big.Int).SetUint64(withdrawal.Amount)
705+
amount.Mul(amount, big.NewInt(params.GWei))
706+
stateDB.AddBalance(withdrawal.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal)
707+
}
708+
g.logger.Debug().
709+
Int("count", len(ps.withdrawals)).
710+
Msg("processed withdrawals")
711+
}
712+
596713
// Finalize state
597714
header.GasUsed = gasUsed
598715
header.Root = stateDB.IntermediateRoot(g.backend.chainConfig.IsEIP158(header.Number))
@@ -659,7 +776,7 @@ func (g *gethEngineClient) NewPayload(ctx context.Context, payload *engine.Execu
659776
g.backend.mu.Lock()
660777
defer g.backend.mu.Unlock()
661778

662-
startTime := time.Now()
779+
validationStart := time.Now()
663780

664781
// Validate payload
665782
if payload == nil {
@@ -803,7 +920,7 @@ func (g *gethEngineClient) NewPayload(ctx context.Context, payload *engine.Execu
803920
Str("parent_hash", payload.ParentHash.Hex()).
804921
Int("tx_count", len(txs)).
805922
Uint64("gas_used", payload.GasUsed).
806-
Dur("process_time", time.Since(startTime)).
923+
Dur("process_time", time.Since(validationStart)).
807924
Msg("new payload validated and inserted")
808925

809926
return &engine.PayloadStatusV1{
@@ -975,7 +1092,7 @@ func applyTransaction(
9751092

9761093
// Set contract address if this was a contract creation
9771094
if msg.To == nil {
978-
receipt.ContractAddress = evmInstance.Origin
1095+
receipt.ContractAddress = crypto.CreateAddress(msg.From, tx.Nonce())
9791096
}
9801097

9811098
return receipt, nil

execution/evm/engine_geth_consensus.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,23 @@ import (
88
"github.com/ethereum/go-ethereum/consensus"
99
"github.com/ethereum/go-ethereum/consensus/beacon"
1010
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
11+
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
1112
"github.com/ethereum/go-ethereum/core/types"
1213
"github.com/ethereum/go-ethereum/params"
1314
)
1415

16+
const (
17+
// maxExtraDataSize is the maximum allowed size for block extra data (32 bytes).
18+
maxExtraDataSize = 32
19+
20+
// gasLimitBoundDivisor is the bound divisor for gas limit changes between blocks.
21+
// Gas limit can only change by 1/1024 per block.
22+
gasLimitBoundDivisor = 1024
23+
24+
// minGasLimit is the minimum gas limit allowed for blocks.
25+
minGasLimit = 5000
26+
)
27+
1528
// sovereignBeacon wraps the standard beacon consensus engine but allows
1629
// equal timestamps (timestamp >= parent.timestamp) instead of requiring
1730
// strictly increasing timestamps (timestamp > parent.timestamp).
@@ -57,9 +70,28 @@ func (sb *sovereignBeacon) VerifyHeader(chain consensus.ChainHeaderReader, heade
5770
return errors.New("invalid uncle hash: must be empty for PoS")
5871
}
5972

73+
// Verify extra data size limit
74+
if len(header.Extra) > maxExtraDataSize {
75+
return fmt.Errorf("invalid extra data size: have %d, max %d", len(header.Extra), maxExtraDataSize)
76+
}
77+
78+
// Verify gas limit bounds
6079
if header.GasLimit > params.MaxGasLimit {
6180
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
6281
}
82+
if header.GasLimit < minGasLimit {
83+
return fmt.Errorf("invalid gasLimit: have %v, min %v", header.GasLimit, minGasLimit)
84+
}
85+
86+
// Verify gas limit change is within bounds (can only change by 1/1024 per block)
87+
diff := int64(header.GasLimit) - int64(parent.GasLimit)
88+
if diff < 0 {
89+
diff = -diff
90+
}
91+
limit := parent.GasLimit / gasLimitBoundDivisor
92+
if uint64(diff) >= limit {
93+
return fmt.Errorf("invalid gas limit: have %d, want %d ± %d", header.GasLimit, parent.GasLimit, limit-1)
94+
}
6395

6496
// Verify that the gasUsed is <= gasLimit
6597
if header.GasUsed > header.GasLimit {
@@ -71,6 +103,14 @@ func (sb *sovereignBeacon) VerifyHeader(chain consensus.ChainHeaderReader, heade
71103
return err
72104
}
73105

106+
// Verify EIP-4844 blob gas fields if Cancun is active
107+
config := chain.Config()
108+
if config.IsCancun(header.Number, header.Time) {
109+
if err := eip4844.VerifyEIP4844Header(config, parent, header); err != nil {
110+
return err
111+
}
112+
}
113+
74114
return nil
75115
}
76116

0 commit comments

Comments
 (0)