Skip to content

Commit 3d921ed

Browse files
committed
les, light: resend if the transaction is not retrieved back
1 parent 8f03e3b commit 3d921ed

File tree

7 files changed

+117
-15
lines changed

7 files changed

+117
-15
lines changed

les/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
122122
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.getTimeout)
123123
leth.relay = newLesTxRelay(peers, leth.retriever)
124124

125-
leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.retriever)
125+
leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever)
126126
leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune)
127127
leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune)
128128
leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer)

les/odr.go

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package les
1818

1919
import (
2020
"context"
21+
"sort"
2122
"time"
2223

2324
"github.com/ethereum/go-ethereum/common/mclock"
@@ -31,14 +32,16 @@ type LesOdr struct {
3132
db ethdb.Database
3233
indexerConfig *light.IndexerConfig
3334
chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer
35+
peers *serverPeerSet
3436
retriever *retrieveManager
3537
stop chan struct{}
3638
}
3739

38-
func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, retriever *retrieveManager) *LesOdr {
40+
func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, peers *serverPeerSet, retriever *retrieveManager) *LesOdr {
3941
return &LesOdr{
4042
db: db,
4143
indexerConfig: config,
44+
peers: peers,
4245
retriever: retriever,
4346
stop: make(chan struct{}),
4447
}
@@ -98,7 +101,101 @@ type Msg struct {
98101
Obj interface{}
99102
}
100103

101-
// Retrieve tries to fetch an object from the LES network.
104+
// peerByTxHistory is a heap.Interface implementation which can sort
105+
// the peerset by transaction history.
106+
type peerByTxHistory []*serverPeer
107+
108+
func (h peerByTxHistory) Len() int { return len(h) }
109+
func (h peerByTxHistory) Less(i, j int) bool {
110+
if h[i].txHistory == txIndexUnlimited {
111+
return false
112+
}
113+
if h[j].txHistory == txIndexUnlimited {
114+
return true
115+
}
116+
return h[i].txHistory < h[j].txHistory
117+
}
118+
func (h peerByTxHistory) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
119+
120+
const (
121+
maxTxStatusRetry = 3 // The maximum retrys will be made for tx status request.
122+
maxTxStatusCandidates = 5 // The maximum les servers the tx status requests will be sent to.
123+
)
124+
125+
// RetrieveTxStatus retrieves the transaction status from the LES network.
126+
// There is no guarantee in the LES protocol that the mined transaction will
127+
// be retrieved back for sure because of different reasons(the transaction
128+
// is unindexed, the malicous server doesn't reply it deliberately, etc).
129+
// Therefore, unretrieved transactions(UNKNOWN) will receive a certain number
130+
// of retrys, thus giving a weak guarantee.
131+
func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequest) error {
132+
// Sort according to the transaction history supported by the peer and
133+
// select the peers with longest history.
134+
var (
135+
retrys int
136+
peers []*serverPeer
137+
missing = len(req.Hashes)
138+
result = make([]light.TxStatus, len(req.Hashes))
139+
canSend = make(map[string]bool)
140+
)
141+
for _, peer := range odr.peers.allPeers() {
142+
if peer.txHistory == txIndexDisabled {
143+
continue
144+
}
145+
peers = append(peers, peer)
146+
}
147+
sort.Sort(sort.Reverse(peerByTxHistory(peers)))
148+
for i := 0; i < maxTxStatusCandidates && i < len(peers); i++ {
149+
canSend[peers[i].id] = true
150+
}
151+
// Send out the request and assemble the result.
152+
for {
153+
if retrys >= maxTxStatusRetry {
154+
break
155+
}
156+
var (
157+
// Deep copy the request, so that the partial result won't be mixed.
158+
req = &TxStatusRequest{Hashes: req.Hashes}
159+
id = genReqID()
160+
distreq = &distReq{
161+
getCost: func(dp distPeer) uint64 { return req.GetCost(dp.(*serverPeer)) },
162+
canSend: func(dp distPeer) bool { return canSend[dp.(*serverPeer).id] },
163+
request: func(dp distPeer) func() {
164+
p := dp.(*serverPeer)
165+
p.fcServer.QueuedRequest(id, req.GetCost(p))
166+
delete(canSend, p.id)
167+
return func() { req.Request(id, p) }
168+
},
169+
}
170+
)
171+
if err := odr.retriever.retrieve(ctx, id, distreq, func(p distPeer, msg *Msg) error { return req.Validate(odr.db, msg) }, odr.stop); err != nil {
172+
return err
173+
}
174+
// Collect the response and assemble them to the final result.
175+
// All the response is not verifiable, so always pick the first
176+
// one we get.
177+
for index, status := range req.Status {
178+
if result[index].Status != core.TxStatusUnknown {
179+
continue
180+
}
181+
if status.Status == core.TxStatusUnknown {
182+
continue
183+
}
184+
result[index], missing = status, missing-1
185+
}
186+
// Abort the procedure if all the status are retrieved
187+
if missing == 0 {
188+
break
189+
}
190+
retrys += 1
191+
}
192+
req.Status = result
193+
return nil
194+
}
195+
196+
// Retrieve tries to fetch an object from the LES network. It's a common API
197+
// for most of the LES requests except for the TxStatusRequest which needs
198+
// the addtional retry mechanism.
102199
// If the network retrieval was successful, it stores the object in local db.
103200
func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) {
104201
lreq := LesRequest(req)

les/odr_requests.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 {
487487

488488
// CanSend tells if a certain peer is suitable for serving the given request
489489
func (r *TxStatusRequest) CanSend(peer *serverPeer) bool {
490-
return peer.serveTxLookup
490+
return true
491491
}
492492

493493
// Request sends an ODR request to the LES network (implementation of LesOdrRequest)
@@ -496,13 +496,12 @@ func (r *TxStatusRequest) Request(reqID uint64, peer *serverPeer) error {
496496
return peer.requestTxStatus(reqID, r.Hashes)
497497
}
498498

499-
// Valid processes an ODR request reply message from the LES network
499+
// Validate processes an ODR request reply message from the LES network
500500
// returns true and stores results in memory if the message was a valid reply
501501
// to the request (implementation of LesOdrRequest)
502502
func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error {
503503
log.Debug("Validating transaction status", "count", len(r.Hashes))
504504

505-
// Ensure we have a correct message with a single block body
506505
if msg.MsgType != MsgTxStatus {
507506
return errInvalidMessageType
508507
}

les/peer.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ type serverPeer struct {
341341
onlyAnnounce bool // The flag whether the server sends announcement only.
342342
chainSince, chainRecent uint64 // The range of chain server peer can serve.
343343
stateSince, stateRecent uint64 // The range of state server peer can serve.
344-
serveTxLookup bool // The server peer can serve tx lookups.
344+
txHistory uint64 // The length of available tx history, 0 means all, 1 means disabled
345345

346346
// Advertised checkpoint fields
347347
checkpointNumber uint64 // The block height which the checkpoint is registered.
@@ -634,13 +634,13 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter
634634
if err := recv.get("recentTxLookup", &recentTx); err != nil {
635635
return err
636636
}
637-
// Note: in the current version we only consider the tx index service useful
638-
// if it is unlimited. This can be made configurable in the future.
639-
p.serveTxLookup = recentTx == txIndexUnlimited
637+
p.txHistory = uint64(recentTx)
640638
} else {
641-
p.serveTxLookup = true
639+
// The weak assumption is held here that legacy les server(les2,3)
640+
// has unlimited transaction history. The les serving in these legacy
641+
// versions is disabled if the transaction is unindexed.
642+
p.txHistory = txIndexUnlimited
642643
}
643-
644644
if p.onlyAnnounce && !p.trusted {
645645
return errResp(ErrUselessPeer, "peer cannot serve requests")
646646
}

les/test_helper.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ func newClientServerEnv(t *testing.T, blocks int, protocol int, callback indexer
523523
}
524524
dist := newRequestDistributor(speers, clock)
525525
rm := newRetrieveManager(speers, dist, func() time.Duration { return time.Millisecond * 500 })
526-
odr := NewLesOdr(cdb, light.TestClientIndexerConfig, rm)
526+
odr := NewLesOdr(cdb, light.TestClientIndexerConfig, speers, rm)
527527

528528
sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig, true)
529529
cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig, disablePruning)

light/odr.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type OdrBackend interface {
4242
BloomTrieIndexer() *core.ChainIndexer
4343
BloomIndexer() *core.ChainIndexer
4444
Retrieve(ctx context.Context, req OdrRequest) error
45+
RetrieveTxStatus(ctx context.Context, req *TxStatusRequest) error
4546
IndexerConfig() *IndexerConfig
4647
}
4748

light/odr_util.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,15 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint
269269
return result, nil
270270
}
271271

272-
// GetTransaction retrieves a canonical transaction by hash and also returns its position in the chain
272+
// GetTransaction retrieves a canonical transaction by hash and also returns
273+
// its position in the chain. There is no guarantee in the LES protocol that
274+
// the mined transaction will be retrieved back for sure because of different
275+
// reasons(the transaction is unindexed, the malicous server doesn't reply it
276+
// deliberately, etc). Therefore, unretrieved transactions will receive a certain
277+
// number of retrys, thus giving a weak guarantee.
273278
func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
274279
r := &TxStatusRequest{Hashes: []common.Hash{txHash}}
275-
if err := odr.Retrieve(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded {
280+
if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != core.TxStatusIncluded {
276281
return nil, common.Hash{}, 0, 0, err
277282
}
278283
pos := r.Status[0].Lookup

0 commit comments

Comments
 (0)