types: remove heap allocations from GetEffectiveGasTip#20808
types: remove heap allocations from GetEffectiveGasTip#20808Sahil-4555 wants to merge 2 commits intoerigontech:mainfrom
Conversation
453e746 to
dd83d25
Compare
Block Building Benchmarkpackage execmodule
import (
"fmt"
"testing"
"github.com/holiman/uint256"
"github.com/erigontech/erigon/execution/types"
)
// buildTotalFeesFixture creates a block with `n` dynamic-fee transactions
// and corresponding receipts.
func buildTotalFeesFixture(n int) (*types.BlockWithReceipts, *uint256.Int) {
baseFee := uint256.NewInt(1_000_000_000) // 1 gwei
txs := make([]types.Transaction, n)
receipts := make(types.Receipts, n)
for i := 0; i < n; i++ {
gasFeeCap := uint256.NewInt(3_000_000_000)
gasTipCap := uint256.NewInt(2_000_000_000)
tx := &types.DynamicFeeTransaction{
CommonTx: types.CommonTx{
Nonce: uint64(i),
GasLimit: 21000,
},
ChainID: *uint256.NewInt(1),
FeeCap: *gasFeeCap,
TipCap: *gasTipCap,
}
txs[i] = tx
receipts[i] = &types.Receipt{GasUsed: 21000}
}
header := &types.Header{
Number: *uint256.NewInt(1),
GasLimit: 30_000_000,
BaseFee: baseFee,
}
block := types.NewBlock(header, txs, nil, receipts, nil)
return &types.BlockWithReceipts{
Block: block,
Receipts: receipts,
}, baseFee
}
func BenchmarkTotalFees(b *testing.B) {
for _, n := range []int{1, 50, 200} {
br, baseFee := buildTotalFeesFixture(n)
b.Run(fmt.Sprintf("txs=%d", n), func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = blockValue(br, baseFee)
}
})
}
} |
Otter Scan API Benchpackage jsonrpc
import (
"context"
"fmt"
"testing"
"github.com/holiman/uint256"
"github.com/erigontech/erigon/common"
"github.com/erigontech/erigon/execution/chain"
"github.com/erigontech/erigon/execution/types"
)
func buildDelegateBlockFeesFixture(n int) (*types.Block, []common.Address, *chain.Config, types.Receipts) {
baseFee := uint256.NewInt(1_000_000_000)
txs := make([]types.Transaction, n)
receipts := make(types.Receipts, n)
senders := make([]common.Address, n)
for i := 0; i < n; i++ {
gasFeeCap := uint256.NewInt(3_000_000_000)
gasTipCap := uint256.NewInt(2_000_000_000)
tx := &types.DynamicFeeTransaction{
CommonTx: types.CommonTx{
Nonce: uint64(i),
GasLimit: 21000,
},
ChainID: *uint256.NewInt(1),
FeeCap: *gasFeeCap,
TipCap: *gasTipCap,
}
txs[i] = tx
receipts[i] = &types.Receipt{GasUsed: 21000, TransactionIndex: uint(i)}
}
header := &types.Header{
Number: *uint256.NewInt(1),
GasLimit: 30_000_000,
BaseFee: baseFee,
}
block := types.NewBlock(header, txs, nil, receipts, nil)
zero := uint64(0)
chainConfig := &chain.Config{LondonBlock: &zero}
return block, senders, chainConfig, receipts
}
func BenchmarkDelegateBlockFees(b *testing.B) {
ctx := context.Background()
for _, n := range []int{1, 50, 200} {
block, senders, chainConfig, receipts := buildDelegateBlockFeesFixture(n)
b.Run(fmt.Sprintf("txs=%d", n), func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = delegateBlockFees(ctx, nil, block, senders, chainConfig, receipts)
}
})
}
} |
There was a problem hiding this comment.
Pull request overview
This PR optimizes EIP-1559 effective-tip calculations by changing types.Transaction.GetEffectiveGasTip to return a uint256.Int by value, avoiding per-call heap allocations in hot loops (block assembly, gas price oracle, receipt marshaling).
Changes:
- Updated
Transactioninterface andCalcEffectiveGasTipto returnuint256.Intby value (no escaping pointers). - Refactored key call sites (RPC, execmodule, AA gas, tracing/tests) to use the new value-returning API and reduce intermediate allocations.
- Adjusted gas price oracle sorting/precomputation to store tips by value and only allocate when returning pointer slices.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| rpc/jsonrpc/otterscan_api.go | Switch block fee aggregation to uint256.Int temporaries and value-based effective tip usage. |
| rpc/gasprice/gasprice.go | Store precomputed tips by value; update sort comparator and output append logic. |
| execution/types/transaction.go | Change GetEffectiveGasTip to return by value; refactor CalcEffectiveGasTip to avoid heap allocations. |
| execution/types/legacy_tx.go | Update legacy tx implementation to match new GetEffectiveGasTip signature. |
| execution/types/ethutils/receipt.go | Adjust effective gas price marshaling to use value-returning effective tip. |
| execution/types/dynamic_fee_tx.go | Update dynamic fee tx implementation to match new GetEffectiveGasTip signature. |
| execution/types/blob_tx_wrapper.go | Update wrapper to forward new value-returning GetEffectiveGasTip. |
| execution/types/aa_transaction.go | Update AA tx implementation to match new GetEffectiveGasTip signature. |
| execution/tracing/tracers/internal/tracetest/calltrace_test.go | Update tracer benchmark to use value-returning GetEffectiveGasTip. |
| execution/tests/blockchain_test.go | Update test to handle value return when reading Uint64(). |
| execution/protocol/aa/aa_gas.go | Update AA gas accounting to add baseFee + effectiveTip using value return. |
| execution/execmodule/block_building.go | Rework block value calculation to reuse uint256.Int temporaries and value-returning tips. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
yperbasis
left a comment
There was a problem hiding this comment.
The "zero allocations" claim is overstated for gasprice.go
In rpc/gasprice/gasprice.go:323, the new code adds:
tipCopy := new(uint256.Int).Set(&item.tip)
*out = append(*out, tipCopy)
This is a heap allocation per kept item. It's better than before (only items passing the coinbase filter allocate, vs. all of them), but not zero. Update the PR description to clarify "drastically reduced" rather than "flatlines to zero" for this caller.
This PR optimizes the
GetEffectiveGasTipcalculation to drastically reduce heap allocations during block assembly and RPC fee processing loops.Previously,
GetEffectiveGasTipreturned a pointer (*uint256.Int), forcing Go's escape analysis to allocate dynamically computed effective tips onto the heap for every single transaction. This was a massive garbage collection bottleneck in loops processing hundreds of transactions per block.Instead of patching callers with repetitive inline math logic, this PR fixes the root cause by updating the core
Transactioninterface to returnuint256.Intby value.Because
uint256.Intis a fixed 32-byte struct, passing it by value is extremely cheap and completely bypasses the heap. For most callers, this flatlines loop allocations to exactly zero. For callers that must persist the result (likegasprice.go), allocations are drastically reduced from O(N) (every transaction) to O(K) (only the kept items).