Skip to content

Commit 830db33

Browse files
committed
core/txpool, eth: add GetRLP to transaction pool
Currently, when answering GetPooledTransaction request, txpool.Get() is used. When the requested hash is blob transaction, blobpool.Get() is called. This function loads the RLP-encoded transaction from limbo then decodes and returns. Later, in answerGetPooledTransactions, we need to RLP encode again. This decode then encode is wasteful. This commit adds GetRLP to transaction pool interface so that answerGetPooledTransactions can use the RLP-encoded from limbo directly.
1 parent ebff2f4 commit 830db33

File tree

9 files changed

+188
-23
lines changed

9 files changed

+188
-23
lines changed

core/txpool/blobpool/blobpool.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -1189,8 +1189,7 @@ func (p *BlobPool) Has(hash common.Hash) bool {
11891189
return p.lookup.exists(hash)
11901190
}
11911191

1192-
// Get returns a transaction if it is contained in the pool, or nil otherwise.
1193-
func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
1192+
func (p *BlobPool) getRLP(hash common.Hash) []byte {
11941193
// Track the amount of time waiting to retrieve a fully resolved blob tx from
11951194
// the pool and the amount of time actually spent on pulling the data from disk.
11961195
getStart := time.Now()
@@ -1212,14 +1211,32 @@ func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
12121211
log.Error("Tracked blob transaction missing from store", "hash", hash, "id", id, "err", err)
12131212
return nil
12141213
}
1214+
return data
1215+
}
1216+
1217+
// Get returns a transaction if it is contained in the pool, or nil otherwise.
1218+
func (p *BlobPool) Get(hash common.Hash) *types.Transaction {
1219+
data := p.getRLP(hash)
1220+
if len(data) == 0 {
1221+
return nil
1222+
}
1223+
12151224
item := new(types.Transaction)
1216-
if err = rlp.DecodeBytes(data, item); err != nil {
1217-
log.Error("Blobs corrupted for traced transaction", "hash", hash, "id", id, "err", err)
1225+
if err := rlp.DecodeBytes(data, item); err != nil {
1226+
id, _ := p.lookup.storeidOfTx(hash)
1227+
1228+
log.Error("Blobs corrupted for traced transaction",
1229+
"hash", hash, "id", id, "err", err)
12181230
return nil
12191231
}
12201232
return item
12211233
}
12221234

1235+
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
1236+
func (pool *BlobPool) GetRLP(hash common.Hash) ([]byte, error) {
1237+
return pool.getRLP(hash), nil
1238+
}
1239+
12231240
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
12241241
// This is a utility method for the engine API, enabling consensus clients to
12251242
// retrieve blobs from the pools directly instead of the network.

core/txpool/legacypool/legacypool.go

+15
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/ethereum/go-ethereum/log"
4141
"github.com/ethereum/go-ethereum/metrics"
4242
"github.com/ethereum/go-ethereum/params"
43+
"github.com/ethereum/go-ethereum/rlp"
4344
"github.com/holiman/uint256"
4445
)
4546

@@ -1010,6 +1011,20 @@ func (pool *LegacyPool) get(hash common.Hash) *types.Transaction {
10101011
return pool.all.Get(hash)
10111012
}
10121013

1014+
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
1015+
func (pool *LegacyPool) GetRLP(hash common.Hash) ([]byte, error) {
1016+
tx := pool.all.Get(hash)
1017+
if tx != nil {
1018+
encoded, err := rlp.EncodeToBytes(tx)
1019+
if err != nil {
1020+
log.Error("Failed to encoded transaction in legacy pool", "err", err)
1021+
return nil, err
1022+
}
1023+
return encoded, nil
1024+
}
1025+
return nil, nil
1026+
}
1027+
10131028
// GetBlobs is not supported by the legacy transaction pool, it is just here to
10141029
// implement the txpool.SubPool interface.
10151030
func (pool *LegacyPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Proof) {

core/txpool/subpool.go

+3
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ type SubPool interface {
124124
// Get returns a transaction if it is contained in the pool, or nil otherwise.
125125
Get(hash common.Hash) *types.Transaction
126126

127+
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
128+
GetRLP(hash common.Hash) ([]byte, error)
129+
127130
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
128131
// This is a utility method for the engine API, enabling consensus clients to
129132
// retrieve blobs from the pools directly instead of the network.

core/txpool/txpool.go

+11
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,17 @@ func (p *TxPool) Get(hash common.Hash) *types.Transaction {
309309
return nil
310310
}
311311

312+
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
313+
func (p *TxPool) GetRLP(hash common.Hash) ([]byte, error) {
314+
for _, subpool := range p.subpools {
315+
encoded, err := subpool.GetRLP(hash)
316+
if err != nil || len(encoded) != 0 {
317+
return encoded, err
318+
}
319+
}
320+
return nil, nil
321+
}
322+
312323
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
313324
// This is a utility method for the engine API, enabling consensus clients to
314325
// retrieve blobs from the pools directly instead of the network.

eth/handler.go

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ type txPool interface {
6767
// tx hash.
6868
Get(hash common.Hash) *types.Transaction
6969

70+
// GetRLP retrieves the RLP-encoded transaction from local txpool
71+
// with given tx hash.
72+
GetRLP(hash common.Hash) ([]byte, error)
73+
7074
// Add should add the given transactions to the pool.
7175
Add(txs []*types.Transaction, sync bool) []error
7276

eth/handler_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/ethereum/go-ethereum/ethdb"
3434
"github.com/ethereum/go-ethereum/event"
3535
"github.com/ethereum/go-ethereum/params"
36+
"github.com/ethereum/go-ethereum/rlp"
3637
"github.com/holiman/uint256"
3738
)
3839

@@ -78,6 +79,19 @@ func (p *testTxPool) Get(hash common.Hash) *types.Transaction {
7879
return p.pool[hash]
7980
}
8081

82+
// Get retrieves the transaction from local txpool with given
83+
// tx hash.
84+
func (p *testTxPool) GetRLP(hash common.Hash) ([]byte, error) {
85+
p.lock.Lock()
86+
defer p.lock.Unlock()
87+
88+
tx := p.pool[hash]
89+
if tx != nil {
90+
return rlp.EncodeToBytes(tx)
91+
}
92+
return nil, nil
93+
}
94+
8195
// Add appends a batch of transactions to the pool, and notifies any
8296
// listeners if the addition channel is non nil
8397
func (p *testTxPool) Add(txs []*types.Transaction, sync bool) []error {

eth/protocols/eth/handler.go

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ type Backend interface {
8686
type TxPool interface {
8787
// Get retrieves the transaction from the local txpool with the given hash.
8888
Get(hash common.Hash) *types.Transaction
89+
90+
// GetRLP retrieves the RLP-encoded transaction from the local txpool with
91+
// the given hash.
92+
GetRLP(hash common.Hash) ([]byte, error)
8993
}
9094

9195
// MakeProtocols constructs the P2P protocol definitions for `eth`.

eth/protocols/eth/handler_test.go

+111-9
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ package eth
1818

1919
import (
2020
"bytes"
21+
"crypto/sha256"
2122
"math"
2223
"math/big"
2324
"math/rand"
25+
"os"
2426
"testing"
2527
"time"
2628

@@ -30,15 +32,18 @@ import (
3032
"github.com/ethereum/go-ethereum/core"
3133
"github.com/ethereum/go-ethereum/core/rawdb"
3234
"github.com/ethereum/go-ethereum/core/txpool"
35+
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
3336
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
3437
"github.com/ethereum/go-ethereum/core/types"
3538
"github.com/ethereum/go-ethereum/core/vm"
3639
"github.com/ethereum/go-ethereum/crypto"
40+
"github.com/ethereum/go-ethereum/crypto/kzg4844"
3741
"github.com/ethereum/go-ethereum/ethdb"
3842
"github.com/ethereum/go-ethereum/p2p"
3943
"github.com/ethereum/go-ethereum/p2p/enode"
4044
"github.com/ethereum/go-ethereum/params"
4145
"github.com/ethereum/go-ethereum/rlp"
46+
"github.com/holiman/uint256"
4247
)
4348

4449
var (
@@ -62,12 +67,15 @@ type testBackend struct {
6267

6368
// newTestBackend creates an empty chain and wraps it into a mock backend.
6469
func newTestBackend(blocks int) *testBackend {
65-
return newTestBackendWithGenerator(blocks, false, nil)
70+
return newTestBackendWithGenerator(blocks, false, false, nil)
6671
}
6772

6873
// newTestBackendWithGenerator creates a chain with a number of explicitly defined blocks and
6974
// wraps it into a mock backend.
70-
func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, *core.BlockGen)) *testBackend {
75+
func newTestBackendWithGenerator(
76+
blocks int, shanghai bool, cancun bool,
77+
generator func(int, *core.BlockGen),
78+
) *testBackend {
7179
var (
7280
// Create a database pre-initialize with a genesis block
7381
db = rawdb.NewMemoryDatabase()
@@ -99,9 +107,21 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int,
99107
}
100108
}
101109

110+
if cancun {
111+
config.CancunTime = u64(0)
112+
config.BlobScheduleConfig = &params.BlobScheduleConfig{
113+
Cancun: &params.BlobConfig{
114+
Target: 3,
115+
Max: 6,
116+
UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction,
117+
},
118+
}
119+
}
120+
102121
gspec := &core.Genesis{
103-
Config: config,
104-
Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}},
122+
Config: config,
123+
Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}},
124+
Difficulty: common.Big0,
105125
}
106126
chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil)
107127

@@ -115,8 +135,12 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int,
115135
txconfig := legacypool.DefaultConfig
116136
txconfig.Journal = "" // Don't litter the disk with test journals
117137

118-
pool := legacypool.New(txconfig, chain)
119-
txpool, _ := txpool.New(txconfig.PriceLimit, chain, []txpool.SubPool{pool})
138+
storage, _ := os.MkdirTemp("", "blobpool-")
139+
defer os.RemoveAll(storage)
140+
141+
blobPool := blobpool.New(blobpool.Config{Datadir: storage}, chain)
142+
legacyPool := legacypool.New(txconfig, chain)
143+
txpool, _ := txpool.New(txconfig.PriceLimit, chain, []txpool.SubPool{legacyPool, blobPool})
120144

121145
return &testBackend{
122146
db: db,
@@ -351,7 +375,7 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
351375
}
352376
}
353377

354-
backend := newTestBackendWithGenerator(maxBodiesServe+15, true, gen)
378+
backend := newTestBackendWithGenerator(maxBodiesServe+15, true, false, gen)
355379
defer backend.close()
356380

357381
peer, _ := newTestPeer("peer", protocol, backend)
@@ -471,7 +495,7 @@ func testGetBlockReceipts(t *testing.T, protocol uint) {
471495
}
472496
}
473497
// Assemble the test environment
474-
backend := newTestBackendWithGenerator(4, false, generator)
498+
backend := newTestBackendWithGenerator(4, false, false, generator)
475499
defer backend.close()
476500

477501
peer, _ := newTestPeer("peer", protocol, backend)
@@ -548,7 +572,7 @@ func setup() (*testBackend, *testPeer) {
548572
block.SetExtra([]byte("yeehaw"))
549573
}
550574
}
551-
backend := newTestBackendWithGenerator(maxBodiesServe+15, true, gen)
575+
backend := newTestBackendWithGenerator(maxBodiesServe+15, true, false, gen)
552576
peer, _ := newTestPeer("peer", ETH68, backend)
553577
// Discard all messages
554578
go func() {
@@ -573,3 +597,81 @@ func FuzzEthProtocolHandlers(f *testing.F) {
573597
handler(backend, decoder{msg: msg}, peer.Peer)
574598
})
575599
}
600+
601+
func TestGetPooledTransaction(t *testing.T) {
602+
t.Run("blobTx", func(t *testing.T) {
603+
testGetPooledTransaction(t, true)
604+
})
605+
t.Run("legacyTx", func(t *testing.T) {
606+
testGetPooledTransaction(t, false)
607+
})
608+
}
609+
610+
func testGetPooledTransaction(t *testing.T, blobTx bool) {
611+
var (
612+
emptyBlob = kzg4844.Blob{}
613+
emptyBlobs = []kzg4844.Blob{emptyBlob}
614+
emptyBlobCommit, _ = kzg4844.BlobToCommitment(&emptyBlob)
615+
emptyBlobProof, _ = kzg4844.ComputeBlobProof(&emptyBlob, emptyBlobCommit)
616+
emptyBlobHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
617+
)
618+
backend := newTestBackendWithGenerator(0, true, true, nil)
619+
defer backend.close()
620+
621+
peer, _ := newTestPeer("peer", ETH68, backend)
622+
defer peer.close()
623+
624+
signer := types.NewCancunSigner(params.TestChainConfig.ChainID)
625+
var (
626+
tx *types.Transaction
627+
err error
628+
)
629+
630+
if blobTx {
631+
tx, err = types.SignNewTx(testKey, signer, &types.BlobTx{
632+
ChainID: uint256.MustFromBig(params.TestChainConfig.ChainID),
633+
Nonce: 0,
634+
GasTipCap: uint256.NewInt(20_000_000_000),
635+
GasFeeCap: uint256.NewInt(21_000_000_000),
636+
Gas: 21000,
637+
To: testAddr,
638+
BlobHashes: []common.Hash{emptyBlobHash},
639+
BlobFeeCap: uint256.MustFromBig(common.Big1),
640+
Sidecar: &types.BlobTxSidecar{
641+
Blobs: emptyBlobs,
642+
Commitments: []kzg4844.Commitment{emptyBlobCommit},
643+
Proofs: []kzg4844.Proof{emptyBlobProof},
644+
},
645+
})
646+
if err != nil {
647+
t.Fatal(err)
648+
}
649+
} else {
650+
tx, err = types.SignTx(
651+
types.NewTransaction(0, testAddr, big.NewInt(10_000), params.TxGas, big.NewInt(1_000_000_000), nil),
652+
signer,
653+
testKey,
654+
)
655+
if err != nil {
656+
t.Fatal(err)
657+
}
658+
}
659+
errs := backend.txpool.Add([]*types.Transaction{tx}, true)
660+
for _, err := range errs {
661+
if err != nil {
662+
t.Fatal(err)
663+
}
664+
}
665+
666+
// Send the hash request and verify the response
667+
p2p.Send(peer.app, GetPooledTransactionsMsg, GetPooledTransactionsPacket{
668+
RequestId: 123,
669+
GetPooledTransactionsRequest: []common.Hash{tx.Hash()},
670+
})
671+
if err := p2p.ExpectMsg(peer.app, PooledTransactionsMsg, PooledTransactionsPacket{
672+
RequestId: 123,
673+
PooledTransactionsResponse: []*types.Transaction{tx},
674+
}); err != nil {
675+
t.Errorf("pooled transaction mismatch: %v", err)
676+
}
677+
}

eth/protocols/eth/handlers.go

+5-10
Original file line numberDiff line numberDiff line change
@@ -397,18 +397,13 @@ func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsReq
397397
break
398398
}
399399
// Retrieve the requested transaction, skipping if unknown to us
400-
tx := backend.TxPool().Get(hash)
401-
if tx == nil {
400+
encoded, err := backend.TxPool().GetRLP(hash)
401+
if err != nil || len(encoded) == 0 {
402402
continue
403403
}
404-
// If known, encode and queue for response packet
405-
if encoded, err := rlp.EncodeToBytes(tx); err != nil {
406-
log.Error("Failed to encode transaction", "err", err)
407-
} else {
408-
hashes = append(hashes, hash)
409-
txs = append(txs, encoded)
410-
bytes += len(encoded)
411-
}
404+
hashes = append(hashes, hash)
405+
txs = append(txs, encoded)
406+
bytes += len(encoded)
412407
}
413408
return hashes, txs
414409
}

0 commit comments

Comments
 (0)