Skip to content

Commit 40cdcf8

Browse files
zsfelfoldikaralabe
authored andcommitted
les, light: implement ODR transaction lookup by hash (#19069)
* les, light: implement ODR transaction lookup by hash * les: delete useless file * internal/ethapi: always use backend to find transaction * les, eth, internal/ethapi: renamed GetCanonicalTransaction to GetTransaction * light: add canonical header verification to GetTransaction
1 parent f4fb1a1 commit 40cdcf8

File tree

16 files changed

+182
-51
lines changed

16 files changed

+182
-51
lines changed

eth/api_backend.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/ethereum/go-ethereum/common/math"
2727
"github.com/ethereum/go-ethereum/core"
2828
"github.com/ethereum/go-ethereum/core/bloombits"
29+
"github.com/ethereum/go-ethereum/core/rawdb"
2930
"github.com/ethereum/go-ethereum/core/state"
3031
"github.com/ethereum/go-ethereum/core/types"
3132
"github.com/ethereum/go-ethereum/core/vm"
@@ -178,6 +179,11 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction
178179
return b.eth.txPool.Get(hash)
179180
}
180181

182+
func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
183+
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.eth.ChainDb(), txHash)
184+
return tx, blockHash, blockNumber, index, nil
185+
}
186+
181187
func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
182188
return b.eth.txPool.State().GetNonce(addr), nil
183189
}

internal/ethapi/api.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,25 +1155,32 @@ func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr
11551155
}
11561156

11571157
// GetTransactionByHash returns the transaction for the given hash
1158-
func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) *RPCTransaction {
1158+
func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
11591159
// Try to return an already finalized transaction
1160-
if tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash); tx != nil {
1161-
return newRPCTransaction(tx, blockHash, blockNumber, index)
1160+
tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash)
1161+
if err != nil {
1162+
return nil, err
1163+
}
1164+
if tx != nil {
1165+
return newRPCTransaction(tx, blockHash, blockNumber, index), nil
11621166
}
11631167
// No finalized transaction, try to retrieve it from the pool
11641168
if tx := s.b.GetPoolTransaction(hash); tx != nil {
1165-
return newRPCPendingTransaction(tx)
1169+
return newRPCPendingTransaction(tx), nil
11661170
}
1171+
11671172
// Transaction unknown, return as such
1168-
return nil
1173+
return nil, nil
11691174
}
11701175

11711176
// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
11721177
func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
1173-
var tx *types.Transaction
1174-
11751178
// Retrieve a finalized transaction, or a pooled otherwise
1176-
if tx, _, _, _ = rawdb.ReadTransaction(s.b.ChainDb(), hash); tx == nil {
1179+
tx, _, _, _, err := s.b.GetTransaction(ctx, hash)
1180+
if err != nil {
1181+
return nil, err
1182+
}
1183+
if tx == nil {
11771184
if tx = s.b.GetPoolTransaction(hash); tx == nil {
11781185
// Transaction not found anywhere, abort
11791186
return nil, nil

internal/ethapi/backend.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ type Backend interface {
6262

6363
// TxPool API
6464
SendTx(ctx context.Context, signedTx *types.Transaction) error
65+
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
6566
GetPoolTransactions() (types.Transactions, error)
6667
GetPoolTransaction(txHash common.Hash) *types.Transaction
6768
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)

les/api_backend.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ func (b *LesApiBackend) GetPoolTransaction(txHash common.Hash) *types.Transactio
132132
return b.eth.txPool.GetTransaction(txHash)
133133
}
134134

135+
func (b *LesApiBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
136+
return light.GetTransaction(ctx, b.eth.odr, txHash)
137+
}
138+
135139
func (b *LesApiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
136140
return b.eth.txPool.GetNonce(ctx, addr)
137141
}

les/backend.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
114114
if leth.config.ULC != nil {
115115
trustedNodes = leth.config.ULC.TrustedServers
116116
}
117-
leth.relay = NewLesTxRelay(peers, leth.reqDist)
118117
leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg, trustedNodes)
119118
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
119+
leth.relay = NewLesTxRelay(peers, leth.retriever)
120120

121121
leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever)
122122
leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations)
@@ -271,6 +271,7 @@ func (s *LightEthereum) Start(srvr *p2p.Server) error {
271271
// Ethereum protocol.
272272
func (s *LightEthereum) Stop() error {
273273
s.odr.Stop()
274+
s.relay.Stop()
274275
s.bloomIndexer.Close()
275276
s.chtIndexer.Close()
276277
s.blockchain.Stop()

les/handler.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
979979
return errResp(ErrRequestRejected, "")
980980
}
981981
go func() {
982-
stats := make([]txStatus, len(req.Txs))
982+
stats := make([]light.TxStatus, len(req.Txs))
983983
for i, tx := range req.Txs {
984984
if i != 0 && !task.waitOrStop() {
985985
return
@@ -1014,7 +1014,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
10141014
return errResp(ErrRequestRejected, "")
10151015
}
10161016
go func() {
1017-
stats := make([]txStatus, len(req.Hashes))
1017+
stats := make([]light.TxStatus, len(req.Hashes))
10181018
for i, hash := range req.Hashes {
10191019
if i != 0 && !task.waitOrStop() {
10201020
return
@@ -1032,14 +1032,21 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
10321032
p.Log().Trace("Received tx status response")
10331033
var resp struct {
10341034
ReqID, BV uint64
1035-
Status []txStatus
1035+
Status []light.TxStatus
10361036
}
10371037
if err := msg.Decode(&resp); err != nil {
10381038
return errResp(ErrDecode, "msg %v: %v", msg, err)
10391039
}
10401040

10411041
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
10421042

1043+
p.Log().Trace("Received helper trie proof response")
1044+
deliverMsg = &Msg{
1045+
MsgType: MsgTxStatus,
1046+
ReqID: resp.ReqID,
1047+
Obj: resp.Status,
1048+
}
1049+
10431050
default:
10441051
p.Log().Trace("Received unknown message", "code", msg.Code)
10451052
return errResp(ErrInvalidMsgCode, "%v", msg.Code)
@@ -1097,8 +1104,8 @@ func (pm *ProtocolManager) getHelperTrieAuxData(req HelperTrieReq) []byte {
10971104
return nil
10981105
}
10991106

1100-
func (pm *ProtocolManager) txStatus(hash common.Hash) txStatus {
1101-
var stat txStatus
1107+
func (pm *ProtocolManager) txStatus(hash common.Hash) light.TxStatus {
1108+
var stat light.TxStatus
11021109
stat.Status = pm.txpool.Status([]common.Hash{hash})[0]
11031110
// If the transaction is unknown to the pool, try looking it up locally
11041111
if stat.Status == core.TxStatusUnknown {

les/handler_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ func TestTransactionStatusLes2(t *testing.T) {
443443

444444
var reqID uint64
445445

446-
test := func(tx *types.Transaction, send bool, expStatus txStatus) {
446+
test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) {
447447
reqID++
448448
if send {
449449
cost := peer.GetRequestCost(SendTxV2Msg, 1)
@@ -452,7 +452,7 @@ func TestTransactionStatusLes2(t *testing.T) {
452452
cost := peer.GetRequestCost(GetTxStatusMsg, 1)
453453
sendRequest(peer.app, GetTxStatusMsg, reqID, cost, []common.Hash{tx.Hash()})
454454
}
455-
if err := expectResponse(peer.app, TxStatusMsg, reqID, testBufLimit, []txStatus{expStatus}); err != nil {
455+
if err := expectResponse(peer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil {
456456
t.Errorf("transaction status mismatch")
457457
}
458458
}
@@ -461,20 +461,20 @@ func TestTransactionStatusLes2(t *testing.T) {
461461

462462
// test error status by sending an underpriced transaction
463463
tx0, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey)
464-
test(tx0, true, txStatus{Status: core.TxStatusUnknown, Error: core.ErrUnderpriced.Error()})
464+
test(tx0, true, light.TxStatus{Status: core.TxStatusUnknown, Error: core.ErrUnderpriced.Error()})
465465

466466
tx1, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, testBankKey)
467-
test(tx1, false, txStatus{Status: core.TxStatusUnknown}) // query before sending, should be unknown
468-
test(tx1, true, txStatus{Status: core.TxStatusPending}) // send valid processable tx, should return pending
469-
test(tx1, true, txStatus{Status: core.TxStatusPending}) // adding it again should not return an error
467+
test(tx1, false, light.TxStatus{Status: core.TxStatusUnknown}) // query before sending, should be unknown
468+
test(tx1, true, light.TxStatus{Status: core.TxStatusPending}) // send valid processable tx, should return pending
469+
test(tx1, true, light.TxStatus{Status: core.TxStatusPending}) // adding it again should not return an error
470470

471471
tx2, _ := types.SignTx(types.NewTransaction(1, acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, testBankKey)
472472
tx3, _ := types.SignTx(types.NewTransaction(2, acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, testBankKey)
473473
// send transactions in the wrong order, tx3 should be queued
474-
test(tx3, true, txStatus{Status: core.TxStatusQueued})
475-
test(tx2, true, txStatus{Status: core.TxStatusPending})
474+
test(tx3, true, light.TxStatus{Status: core.TxStatusQueued})
475+
test(tx2, true, light.TxStatus{Status: core.TxStatusPending})
476476
// query again, now tx3 should be pending too
477-
test(tx3, false, txStatus{Status: core.TxStatusPending})
477+
test(tx3, false, light.TxStatus{Status: core.TxStatusPending})
478478

479479
// generate and add a block with tx1 and tx2 included
480480
gchain, _ := core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), db, 1, func(i int, block *core.BlockGen) {
@@ -497,8 +497,8 @@ func TestTransactionStatusLes2(t *testing.T) {
497497

498498
// check if their status is included now
499499
block1hash := rawdb.ReadCanonicalHash(db, 1)
500-
test(tx1, false, txStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}})
501-
test(tx2, false, txStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}})
500+
test(tx1, false, light.TxStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}})
501+
test(tx2, false, light.TxStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}})
502502

503503
// create a reorg that rolls them back
504504
gchain, _ = core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), db, 2, func(i int, block *core.BlockGen) {})
@@ -516,6 +516,6 @@ func TestTransactionStatusLes2(t *testing.T) {
516516
t.Fatalf("pending count mismatch: have %d, want 3", pending)
517517
}
518518
// check if their status is pending again
519-
test(tx1, false, txStatus{Status: core.TxStatusPending})
520-
test(tx2, false, txStatus{Status: core.TxStatusPending})
519+
test(tx1, false, light.TxStatus{Status: core.TxStatusPending})
520+
test(tx2, false, light.TxStatus{Status: core.TxStatusPending})
521521
}

les/helper_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
148148
}
149149
genesis = gspec.MustCommit(db)
150150
chain BlockChain
151+
pool txPool
151152
)
152153
if peers == nil {
153154
peers = newPeerSet()
@@ -162,13 +163,14 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
162163
panic(err)
163164
}
164165
chain = blockchain
166+
pool = core.NewTxPool(core.DefaultTxPoolConfig, gspec.Config, blockchain)
165167
}
166168

167169
indexConfig := light.TestServerIndexerConfig
168170
if lightSync {
169171
indexConfig = light.TestClientIndexerConfig
170172
}
171-
pm, err := NewProtocolManager(gspec.Config, indexConfig, lightSync, NetworkId, evmux, engine, peers, chain, nil, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup), ulcConfig)
173+
pm, err := NewProtocolManager(gspec.Config, indexConfig, lightSync, NetworkId, evmux, engine, peers, chain, pool, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup), ulcConfig)
172174
if err != nil {
173175
return nil, err
174176
}

les/odr.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const (
8686
MsgReceipts
8787
MsgProofsV2
8888
MsgHelperTrieProofs
89+
MsgTxStatus
8990
)
9091

9192
// Msg encodes a LES message that delivers reply data for a request

les/odr_requests.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ func LesRequest(req light.OdrRequest) LesOdrRequest {
6666
return (*ChtRequest)(r)
6767
case *light.BloomRequest:
6868
return (*BloomRequest)(r)
69+
case *light.TxStatusRequest:
70+
return (*TxStatusRequest)(r)
6971
default:
7072
return nil
7173
}
@@ -471,6 +473,44 @@ func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error {
471473
return nil
472474
}
473475

476+
// TxStatusRequest is the ODR request type for transaction status
477+
type TxStatusRequest light.TxStatusRequest
478+
479+
// GetCost returns the cost of the given ODR request according to the serving
480+
// peer's cost table (implementation of LesOdrRequest)
481+
func (r *TxStatusRequest) GetCost(peer *peer) uint64 {
482+
return peer.GetRequestCost(GetTxStatusMsg, len(r.Hashes))
483+
}
484+
485+
// CanSend tells if a certain peer is suitable for serving the given request
486+
func (r *TxStatusRequest) CanSend(peer *peer) bool {
487+
return peer.version >= lpv2
488+
}
489+
490+
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
491+
func (r *TxStatusRequest) Request(reqID uint64, peer *peer) error {
492+
peer.Log().Debug("Requesting transaction status", "count", len(r.Hashes))
493+
return peer.RequestTxStatus(reqID, r.GetCost(peer), r.Hashes)
494+
}
495+
496+
// Valid processes an ODR request reply message from the LES network
497+
// returns true and stores results in memory if the message was a valid reply
498+
// to the request (implementation of LesOdrRequest)
499+
func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error {
500+
log.Debug("Validating transaction status", "count", len(r.Hashes))
501+
502+
// Ensure we have a correct message with a single block body
503+
if msg.MsgType != MsgTxStatus {
504+
return errInvalidMessageType
505+
}
506+
status := msg.Obj.([]light.TxStatus)
507+
if len(status) != len(r.Hashes) {
508+
return errInvalidEntryCount
509+
}
510+
r.Status = status
511+
return nil
512+
}
513+
474514
// readTraceDB stores the keys of database reads. We use this to check that received node
475515
// sets contain only the trie nodes necessary to make proofs pass.
476516
type readTraceDB struct {

0 commit comments

Comments
 (0)