Skip to content

Commit dca926a

Browse files
minh-bqrjl493456442
authored andcommitted
core/txpool, eth: add GetRLP to transaction pool (ethereum#31307)
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. --------- Co-authored-by: Gary Rong <[email protected]>
1 parent 26a18f5 commit dca926a

File tree

9 files changed

+184
-23
lines changed

9 files changed

+184
-23
lines changed

core/txpool/blobpool/blobpool.go

+20-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,31 @@ 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+
}
12151223
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)
1224+
if err := rlp.DecodeBytes(data, item); err != nil {
1225+
id, _ := p.lookup.storeidOfTx(hash)
1226+
1227+
log.Error("Blobs corrupted for traced transaction",
1228+
"hash", hash, "id", id, "err", err)
12181229
return nil
12191230
}
12201231
return item
12211232
}
12221233

1234+
// GetRLP returns a RLP-encoded transaction if it is contained in the pool.
1235+
func (p *BlobPool) GetRLP(hash common.Hash) []byte {
1236+
return p.getRLP(hash)
1237+
}
1238+
12231239
// GetBlobs returns a number of blobs are proofs for the given versioned hashes.
12241240
// This is a utility method for the engine API, enabling consensus clients to
12251241
// 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 {
1016+
tx := pool.all.Get(hash)
1017+
if tx == nil {
1018+
return nil
1019+
}
1020+
encoded, err := rlp.EncodeToBytes(tx)
1021+
if err != nil {
1022+
log.Error("Failed to encoded transaction in legacy pool", "hash", hash, "err", err)
1023+
return nil
1024+
}
1025+
return encoded
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
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 {
314+
for _, subpool := range p.subpools {
315+
encoded := subpool.GetRLP(hash)
316+
if len(encoded) != 0 {
317+
return encoded
318+
}
319+
}
320+
return 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
73+
7074
// Add should add the given transactions to the pool.
7175
Add(txs []*types.Transaction, sync bool) []error
7276

eth/handler_test.go

+15
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,20 @@ 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 {
85+
p.lock.Lock()
86+
defer p.lock.Unlock()
87+
88+
tx := p.pool[hash]
89+
if tx != nil {
90+
blob, _ := rlp.EncodeToBytes(tx)
91+
return blob
92+
}
93+
return nil
94+
}
95+
8196
// Add appends a batch of transactions to the pool, and notifies any
8297
// listeners if the addition channel is non nil
8398
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
8993
}
9094

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

eth/protocols/eth/handler_test.go

+107-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,12 @@ 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(blocks int, shanghai bool, cancun bool, generator func(int, *core.BlockGen)) *testBackend {
7176
var (
7277
// Create a database pre-initialize with a genesis block
7378
db = rawdb.NewMemoryDatabase()
@@ -99,9 +104,21 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int,
99104
}
100105
}
101106

107+
if cancun {
108+
config.CancunTime = u64(0)
109+
config.BlobScheduleConfig = &params.BlobScheduleConfig{
110+
Cancun: &params.BlobConfig{
111+
Target: 3,
112+
Max: 6,
113+
UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction,
114+
},
115+
}
116+
}
117+
102118
gspec := &core.Genesis{
103-
Config: config,
104-
Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}},
119+
Config: config,
120+
Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(100_000_000_000_000_000)}},
121+
Difficulty: common.Big0,
105122
}
106123
chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil)
107124

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

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

121142
return &testBackend{
122143
db: db,
@@ -351,7 +372,7 @@ func testGetBlockBodies(t *testing.T, protocol uint) {
351372
}
352373
}
353374

354-
backend := newTestBackendWithGenerator(maxBodiesServe+15, true, gen)
375+
backend := newTestBackendWithGenerator(maxBodiesServe+15, true, false, gen)
355376
defer backend.close()
356377

357378
peer, _ := newTestPeer("peer", protocol, backend)
@@ -471,7 +492,7 @@ func testGetBlockReceipts(t *testing.T, protocol uint) {
471492
}
472493
}
473494
// Assemble the test environment
474-
backend := newTestBackendWithGenerator(4, false, generator)
495+
backend := newTestBackendWithGenerator(4, false, false, generator)
475496
defer backend.close()
476497

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

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 := backend.TxPool().GetRLP(hash)
401+
if 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)