Skip to content

Commit 7ff98c0

Browse files
MariusVanDerWijdenrjl493456442
authored andcommitted
core/types: reduce allocs in transaction signing (ethereum#31258)
This PR roughly halves the number of allocations needed to compute the sigHash for a transaction. This sigHash is used whenever we recover a signature of a transaction, so quite often. During a recent benchmark full syncing on Holesky, roughly 2.8% of all allocations were happening here because the fields from the transaction would be copied multiple times. ``` 66168733 153175654 (flat, cum) 2.80% of Total . . 368:func (s londonSigner) Hash(tx *Transaction) common.Hash { . . 369: if tx.Type() != DynamicFeeTxType { . . 370: return s.eip2930Signer.Hash(tx) . . 371: } . 19169966 372: return prefixedRlpHash( . . 373: tx.Type(), 26442187 26442187 374: []interface{}{ . . 375: s.chainId, 6848616 6848616 376: tx.Nonce(), . 19694077 377: tx.GasTipCap(), . 18956774 378: tx.GasFeeCap(), 6357089 6357089 379: tx.Gas(), . 12321050 380: tx.To(), . 16865054 381: tx.Value(), 13435187 13435187 382: tx.Data(), 13085654 13085654 383: tx.AccessList(), . . 384: }) . . 385:} ``` This PR reduces the allocations and speeds up the computation of the sigHash by ~22%, which is quite significantly given that this operation involves a call to Keccak ``` // BenchmarkHash-8 440082 2639 ns/op 384 B/op 13 allocs/op // BenchmarkHash-8 493566 2033 ns/op 240 B/op 6 allocs/op ``` ``` Hash-8 2.691µ ± 8% 2.097µ ± 9% -22.07% (p=0.000 n=10) ``` It also kinda cleans up stuff in my opinion, since the transaction should itself know best how to compute the sighash ![Screenshot_2025-02-25_13-52-41](https://github.com/user-attachments/assets/e2b268aa-e137-417d-926b-f3619daef748) --------- Co-authored-by: Gary Rong <[email protected]>
1 parent dca926a commit 7ff98c0

8 files changed

+105
-64
lines changed

core/types/transaction.go

+3
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ type TxData interface {
100100

101101
encode(*bytes.Buffer) error
102102
decode([]byte) error
103+
104+
// sigHash returns the hash of the transaction that is ought to be signed
105+
sigHash(*big.Int) common.Hash
103106
}
104107

105108
// EncodeRLP implements rlp.Encoder

core/types/transaction_signing.go

+6-64
Original file line numberDiff line numberDiff line change
@@ -233,20 +233,7 @@ func (s pragueSigner) Hash(tx *Transaction) common.Hash {
233233
if tx.Type() != SetCodeTxType {
234234
return s.cancunSigner.Hash(tx)
235235
}
236-
return prefixedRlpHash(
237-
tx.Type(),
238-
[]interface{}{
239-
s.chainId,
240-
tx.Nonce(),
241-
tx.GasTipCap(),
242-
tx.GasFeeCap(),
243-
tx.Gas(),
244-
tx.To(),
245-
tx.Value(),
246-
tx.Data(),
247-
tx.AccessList(),
248-
tx.SetCodeAuthorizations(),
249-
})
236+
return tx.inner.sigHash(s.chainId)
250237
}
251238

252239
type cancunSigner struct{ londonSigner }
@@ -301,21 +288,7 @@ func (s cancunSigner) Hash(tx *Transaction) common.Hash {
301288
if tx.Type() != BlobTxType {
302289
return s.londonSigner.Hash(tx)
303290
}
304-
return prefixedRlpHash(
305-
tx.Type(),
306-
[]interface{}{
307-
s.chainId,
308-
tx.Nonce(),
309-
tx.GasTipCap(),
310-
tx.GasFeeCap(),
311-
tx.Gas(),
312-
tx.To(),
313-
tx.Value(),
314-
tx.Data(),
315-
tx.AccessList(),
316-
tx.BlobGasFeeCap(),
317-
tx.BlobHashes(),
318-
})
291+
return tx.inner.sigHash(s.chainId)
319292
}
320293

321294
type londonSigner struct{ eip2930Signer }
@@ -369,19 +342,7 @@ func (s londonSigner) Hash(tx *Transaction) common.Hash {
369342
if tx.Type() != DynamicFeeTxType {
370343
return s.eip2930Signer.Hash(tx)
371344
}
372-
return prefixedRlpHash(
373-
tx.Type(),
374-
[]interface{}{
375-
s.chainId,
376-
tx.Nonce(),
377-
tx.GasTipCap(),
378-
tx.GasFeeCap(),
379-
tx.Gas(),
380-
tx.To(),
381-
tx.Value(),
382-
tx.Data(),
383-
tx.AccessList(),
384-
})
345+
return tx.inner.sigHash(s.chainId)
385346
}
386347

387348
type eip2930Signer struct{ EIP155Signer }
@@ -444,18 +405,7 @@ func (s eip2930Signer) Hash(tx *Transaction) common.Hash {
444405
case LegacyTxType:
445406
return s.EIP155Signer.Hash(tx)
446407
case AccessListTxType:
447-
return prefixedRlpHash(
448-
tx.Type(),
449-
[]interface{}{
450-
s.chainId,
451-
tx.Nonce(),
452-
tx.GasPrice(),
453-
tx.Gas(),
454-
tx.To(),
455-
tx.Value(),
456-
tx.Data(),
457-
tx.AccessList(),
458-
})
408+
return tx.inner.sigHash(s.chainId)
459409
default:
460410
// This _should_ not happen, but in case someone sends in a bad
461411
// json struct via RPC, it's probably more prudent to return an
@@ -525,15 +475,7 @@ func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big
525475
// Hash returns the hash to be signed by the sender.
526476
// It does not uniquely identify the transaction.
527477
func (s EIP155Signer) Hash(tx *Transaction) common.Hash {
528-
return rlpHash([]interface{}{
529-
tx.Nonce(),
530-
tx.GasPrice(),
531-
tx.Gas(),
532-
tx.To(),
533-
tx.Value(),
534-
tx.Data(),
535-
s.chainId, uint(0), uint(0),
536-
})
478+
return tx.inner.sigHash(s.chainId)
537479
}
538480

539481
// HomesteadSigner implements Signer interface using the
@@ -597,7 +539,7 @@ func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *
597539
// Hash returns the hash to be signed by the sender.
598540
// It does not uniquely identify the transaction.
599541
func (fs FrontierSigner) Hash(tx *Transaction) common.Hash {
600-
return rlpHash([]interface{}{
542+
return rlpHash([]any{
601543
tx.Nonce(),
602544
tx.GasPrice(),
603545
tx.Gas(),

core/types/transaction_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -576,3 +576,20 @@ func TestYParityJSONUnmarshalling(t *testing.T) {
576576
}
577577
}
578578
}
579+
580+
func BenchmarkHash(b *testing.B) {
581+
signer := NewLondonSigner(big.NewInt(1))
582+
to := common.Address{}
583+
tx := NewTx(&DynamicFeeTx{
584+
ChainID: big.NewInt(123),
585+
Nonce: 1,
586+
Gas: 1000000,
587+
To: &to,
588+
Value: big.NewInt(1),
589+
GasTipCap: big.NewInt(500),
590+
GasFeeCap: big.NewInt(500),
591+
})
592+
for i := 0; i < b.N; i++ {
593+
signer.Hash(tx)
594+
}
595+
}

core/types/tx_access_list.go

+15
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,18 @@ func (tx *AccessListTx) encode(b *bytes.Buffer) error {
127127
func (tx *AccessListTx) decode(input []byte) error {
128128
return rlp.DecodeBytes(input, tx)
129129
}
130+
131+
func (tx *AccessListTx) sigHash(chainID *big.Int) common.Hash {
132+
return prefixedRlpHash(
133+
AccessListTxType,
134+
[]any{
135+
chainID,
136+
tx.Nonce,
137+
tx.GasPrice,
138+
tx.Gas,
139+
tx.To,
140+
tx.Value,
141+
tx.Data,
142+
tx.AccessList,
143+
})
144+
}

core/types/tx_blob.go

+18
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,21 @@ func (tx *BlobTx) decode(input []byte) error {
259259
}
260260
return nil
261261
}
262+
263+
func (tx *BlobTx) sigHash(chainID *big.Int) common.Hash {
264+
return prefixedRlpHash(
265+
BlobTxType,
266+
[]any{
267+
chainID,
268+
tx.Nonce,
269+
tx.GasTipCap,
270+
tx.GasFeeCap,
271+
tx.Gas,
272+
tx.To,
273+
tx.Value,
274+
tx.Data,
275+
tx.AccessList,
276+
tx.BlobFeeCap,
277+
tx.BlobHashes,
278+
})
279+
}

core/types/tx_dynamic_fee.go

+16
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,19 @@ func (tx *DynamicFeeTx) encode(b *bytes.Buffer) error {
123123
func (tx *DynamicFeeTx) decode(input []byte) error {
124124
return rlp.DecodeBytes(input, tx)
125125
}
126+
127+
func (tx *DynamicFeeTx) sigHash(chainID *big.Int) common.Hash {
128+
return prefixedRlpHash(
129+
DynamicFeeTxType,
130+
[]any{
131+
chainID,
132+
tx.Nonce,
133+
tx.GasTipCap,
134+
tx.GasFeeCap,
135+
tx.Gas,
136+
tx.To,
137+
tx.Value,
138+
tx.Data,
139+
tx.AccessList,
140+
})
141+
}

core/types/tx_legacy.go

+13
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,16 @@ func (tx *LegacyTx) encode(*bytes.Buffer) error {
123123
func (tx *LegacyTx) decode([]byte) error {
124124
panic("decode called on LegacyTx)")
125125
}
126+
127+
// OBS: This is the post-EIP155 hash, the pre-EIP155 does not contain a chainID.
128+
func (tx *LegacyTx) sigHash(chainID *big.Int) common.Hash {
129+
return rlpHash([]any{
130+
tx.Nonce,
131+
tx.GasPrice,
132+
tx.Gas,
133+
tx.To,
134+
tx.Value,
135+
tx.Data,
136+
chainID, uint(0), uint(0),
137+
})
138+
}

core/types/tx_setcode.go

+17
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,20 @@ func (tx *SetCodeTx) encode(b *bytes.Buffer) error {
223223
func (tx *SetCodeTx) decode(input []byte) error {
224224
return rlp.DecodeBytes(input, tx)
225225
}
226+
227+
func (tx *SetCodeTx) sigHash(chainID *big.Int) common.Hash {
228+
return prefixedRlpHash(
229+
SetCodeTxType,
230+
[]any{
231+
chainID,
232+
tx.Nonce,
233+
tx.GasTipCap,
234+
tx.GasFeeCap,
235+
tx.Gas,
236+
tx.To,
237+
tx.Value,
238+
tx.Data,
239+
tx.AccessList,
240+
tx.AuthList,
241+
})
242+
}

0 commit comments

Comments
 (0)