17
17
package fetcher
18
18
19
19
import (
20
- "bytes"
21
20
"errors"
22
21
"fmt"
23
22
"math"
@@ -35,7 +34,7 @@ import (
35
34
)
36
35
37
36
const (
38
- // maxTxAnnounces is the maximum number of unique transaction a peer
37
+ // maxTxAnnounces is the maximum number of unique transactions a peer
39
38
// can announce in a short time.
40
39
maxTxAnnounces = 4096
41
40
@@ -114,16 +113,23 @@ var errTerminated = errors.New("terminated")
114
113
type txAnnounce struct {
115
114
origin string // Identifier of the peer originating the notification
116
115
hashes []common.Hash // Batch of transaction hashes being announced
117
- metas []* txMetadata // Batch of metadata associated with the hashes
116
+ metas []txMetadata // Batch of metadata associated with the hashes
118
117
}
119
118
120
- // txMetadata is a set of extra data transmitted along the announcement for better
121
- // fetch scheduling.
119
+ // txMetadata provides the extra data transmitted along with the announcement
120
+ // for better fetch scheduling.
122
121
type txMetadata struct {
123
122
kind byte // Transaction consensus type
124
123
size uint32 // Transaction size in bytes
125
124
}
126
125
126
+ // txMetadataWithSeq is a wrapper of transaction metadata with an extra field
127
+ // tracking the transaction sequence number.
128
+ type txMetadataWithSeq struct {
129
+ txMetadata
130
+ seq uint64
131
+ }
132
+
127
133
// txRequest represents an in-flight transaction retrieval request destined to
128
134
// a specific peers.
129
135
type txRequest struct {
@@ -159,7 +165,7 @@ type txDrop struct {
159
165
// The invariants of the fetcher are:
160
166
// - Each tracked transaction (hash) must only be present in one of the
161
167
// three stages. This ensures that the fetcher operates akin to a finite
162
- // state automata and there's do data leak.
168
+ // state automata and there's no data leak.
163
169
// - Each peer that announced transactions may be scheduled retrievals, but
164
170
// only ever one concurrently. This ensures we can immediately know what is
165
171
// missing from a reply and reschedule it.
@@ -169,18 +175,19 @@ type TxFetcher struct {
169
175
drop chan * txDrop
170
176
quit chan struct {}
171
177
178
+ txSeq uint64 // Unique transaction sequence number
172
179
underpriced * lru.Cache [common.Hash , time.Time ] // Transactions discarded as too cheap (don't re-fetch)
173
180
174
181
// Stage 1: Waiting lists for newly discovered transactions that might be
175
182
// broadcast without needing explicit request/reply round trips.
176
- waitlist map [common.Hash ]map [string ]struct {} // Transactions waiting for an potential broadcast
177
- waittime map [common.Hash ]mclock.AbsTime // Timestamps when transactions were added to the waitlist
178
- waitslots map [string ]map [common.Hash ]* txMetadata // Waiting announcements grouped by peer (DoS protection)
183
+ waitlist map [common.Hash ]map [string ]struct {} // Transactions waiting for an potential broadcast
184
+ waittime map [common.Hash ]mclock.AbsTime // Timestamps when transactions were added to the waitlist
185
+ waitslots map [string ]map [common.Hash ]* txMetadataWithSeq // Waiting announcements grouped by peer (DoS protection)
179
186
180
187
// Stage 2: Queue of transactions that waiting to be allocated to some peer
181
188
// to be retrieved directly.
182
- announces map [string ]map [common.Hash ]* txMetadata // Set of announced transactions, grouped by origin peer
183
- announced map [common.Hash ]map [string ]struct {} // Set of download locations, grouped by transaction hash
189
+ announces map [string ]map [common.Hash ]* txMetadataWithSeq // Set of announced transactions, grouped by origin peer
190
+ announced map [common.Hash ]map [string ]struct {} // Set of download locations, grouped by transaction hash
184
191
185
192
// Stage 3: Set of transactions currently being retrieved, some which may be
186
193
// fulfilled and some rescheduled. Note, this step shares 'announces' from the
@@ -218,8 +225,8 @@ func NewTxFetcherForTests(
218
225
quit : make (chan struct {}),
219
226
waitlist : make (map [common.Hash ]map [string ]struct {}),
220
227
waittime : make (map [common.Hash ]mclock.AbsTime ),
221
- waitslots : make (map [string ]map [common.Hash ]* txMetadata ),
222
- announces : make (map [string ]map [common.Hash ]* txMetadata ),
228
+ waitslots : make (map [string ]map [common.Hash ]* txMetadataWithSeq ),
229
+ announces : make (map [string ]map [common.Hash ]* txMetadataWithSeq ),
223
230
announced : make (map [common.Hash ]map [string ]struct {}),
224
231
fetching : make (map [common.Hash ]string ),
225
232
requests : make (map [string ]* txRequest ),
@@ -247,7 +254,7 @@ func (f *TxFetcher) Notify(peer string, types []byte, sizes []uint32, hashes []c
247
254
// loop, so anything caught here is time saved internally.
248
255
var (
249
256
unknownHashes = make ([]common.Hash , 0 , len (hashes ))
250
- unknownMetas = make ([]* txMetadata , 0 , len (hashes ))
257
+ unknownMetas = make ([]txMetadata , 0 , len (hashes ))
251
258
252
259
duplicate int64
253
260
underpriced int64
@@ -264,7 +271,7 @@ func (f *TxFetcher) Notify(peer string, types []byte, sizes []uint32, hashes []c
264
271
// Transaction metadata has been available since eth68, and all
265
272
// legacy eth protocols (prior to eth68) have been deprecated.
266
273
// Therefore, metadata is always expected in the announcement.
267
- unknownMetas = append (unknownMetas , & txMetadata {kind : types [i ], size : sizes [i ]})
274
+ unknownMetas = append (unknownMetas , txMetadata {kind : types [i ], size : sizes [i ]})
268
275
}
269
276
}
270
277
txAnnounceKnownMeter .Mark (duplicate )
@@ -431,9 +438,19 @@ func (f *TxFetcher) loop() {
431
438
ann .metas = ann .metas [:want - maxTxAnnounces ]
432
439
}
433
440
// All is well, schedule the remainder of the transactions
434
- idleWait := len (f .waittime ) == 0
435
- _ , oldPeer := f .announces [ann .origin ]
436
-
441
+ var (
442
+ idleWait = len (f .waittime ) == 0
443
+ _ , oldPeer = f .announces [ann .origin ]
444
+ hasBlob bool
445
+
446
+ // nextSeq returns the next available sequence number for tagging
447
+ // transaction announcement and also bump it internally.
448
+ nextSeq = func () uint64 {
449
+ seq := f .txSeq
450
+ f .txSeq ++
451
+ return seq
452
+ }
453
+ )
437
454
for i , hash := range ann .hashes {
438
455
// If the transaction is already downloading, add it to the list
439
456
// of possible alternates (in case the current retrieval fails) and
@@ -443,9 +460,17 @@ func (f *TxFetcher) loop() {
443
460
444
461
// Stage 2 and 3 share the set of origins per tx
445
462
if announces := f .announces [ann .origin ]; announces != nil {
446
- announces [hash ] = ann .metas [i ]
463
+ announces [hash ] = & txMetadataWithSeq {
464
+ txMetadata : ann .metas [i ],
465
+ seq : nextSeq (),
466
+ }
447
467
} else {
448
- f .announces [ann .origin ] = map [common.Hash ]* txMetadata {hash : ann .metas [i ]}
468
+ f .announces [ann .origin ] = map [common.Hash ]* txMetadataWithSeq {
469
+ hash : {
470
+ txMetadata : ann .metas [i ],
471
+ seq : nextSeq (),
472
+ },
473
+ }
449
474
}
450
475
continue
451
476
}
@@ -456,9 +481,17 @@ func (f *TxFetcher) loop() {
456
481
457
482
// Stage 2 and 3 share the set of origins per tx
458
483
if announces := f .announces [ann .origin ]; announces != nil {
459
- announces [hash ] = ann .metas [i ]
484
+ announces [hash ] = & txMetadataWithSeq {
485
+ txMetadata : ann .metas [i ],
486
+ seq : nextSeq (),
487
+ }
460
488
} else {
461
- f .announces [ann .origin ] = map [common.Hash ]* txMetadata {hash : ann .metas [i ]}
489
+ f .announces [ann .origin ] = map [common.Hash ]* txMetadataWithSeq {
490
+ hash : {
491
+ txMetadata : ann .metas [i ],
492
+ seq : nextSeq (),
493
+ },
494
+ }
462
495
}
463
496
continue
464
497
}
@@ -475,24 +508,47 @@ func (f *TxFetcher) loop() {
475
508
f.waitlist [hash ][ann.origin ] = struct {}{}
476
509
477
510
if waitslots := f .waitslots [ann .origin ]; waitslots != nil {
478
- waitslots [hash ] = ann .metas [i ]
511
+ waitslots [hash ] = & txMetadataWithSeq {
512
+ txMetadata : ann .metas [i ],
513
+ seq : nextSeq (),
514
+ }
479
515
} else {
480
- f .waitslots [ann .origin ] = map [common.Hash ]* txMetadata {hash : ann .metas [i ]}
516
+ f .waitslots [ann .origin ] = map [common.Hash ]* txMetadataWithSeq {
517
+ hash : {
518
+ txMetadata : ann .metas [i ],
519
+ seq : nextSeq (),
520
+ },
521
+ }
481
522
}
482
523
continue
483
524
}
484
525
// Transaction unknown to the fetcher, insert it into the waiting list
485
526
f .waitlist [hash ] = map [string ]struct {}{ann .origin : {}}
486
- f .waittime [hash ] = f .clock .Now ()
487
527
528
+ // Assign the current timestamp as the wait time, but for blob transactions,
529
+ // skip the wait time since they are only announced.
530
+ if ann .metas [i ].kind != types .BlobTxType {
531
+ f .waittime [hash ] = f .clock .Now ()
532
+ } else {
533
+ hasBlob = true
534
+ f .waittime [hash ] = f .clock .Now () - mclock .AbsTime (txArriveTimeout )
535
+ }
488
536
if waitslots := f .waitslots [ann .origin ]; waitslots != nil {
489
- waitslots [hash ] = ann .metas [i ]
537
+ waitslots [hash ] = & txMetadataWithSeq {
538
+ txMetadata : ann .metas [i ],
539
+ seq : nextSeq (),
540
+ }
490
541
} else {
491
- f .waitslots [ann .origin ] = map [common.Hash ]* txMetadata {hash : ann .metas [i ]}
542
+ f .waitslots [ann .origin ] = map [common.Hash ]* txMetadataWithSeq {
543
+ hash : {
544
+ txMetadata : ann .metas [i ],
545
+ seq : nextSeq (),
546
+ },
547
+ }
492
548
}
493
549
}
494
550
// If a new item was added to the waitlist, schedule it into the fetcher
495
- if idleWait && len (f .waittime ) > 0 {
551
+ if hasBlob || ( idleWait && len (f .waittime ) > 0 ) {
496
552
f .rescheduleWait (waitTimer , waitTrigger )
497
553
}
498
554
// If this peer is new and announced something already queued, maybe
@@ -516,7 +572,7 @@ func (f *TxFetcher) loop() {
516
572
if announces := f .announces [peer ]; announces != nil {
517
573
announces [hash ] = f.waitslots [peer ][hash ]
518
574
} else {
519
- f .announces [peer ] = map [common.Hash ]* txMetadata {hash : f.waitslots [peer ][hash ]}
575
+ f .announces [peer ] = map [common.Hash ]* txMetadataWithSeq {hash : f.waitslots [peer ][hash ]}
520
576
}
521
577
delete (f .waitslots [peer ], hash )
522
578
if len (f .waitslots [peer ]) == 0 {
@@ -873,7 +929,7 @@ func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{},
873
929
hashes = make ([]common.Hash , 0 , maxTxRetrievals )
874
930
bytes uint64
875
931
)
876
- f .forEachAnnounce (f .announces [peer ], func (hash common.Hash , meta * txMetadata ) bool {
932
+ f .forEachAnnounce (f .announces [peer ], func (hash common.Hash , meta txMetadata ) bool {
877
933
// If the transaction is already fetching, skip to the next one
878
934
if _ , ok := f .fetching [hash ]; ok {
879
935
return true
@@ -938,28 +994,26 @@ func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string))
938
994
}
939
995
}
940
996
941
- // forEachAnnounce does a range loop over a map of announcements in production,
942
- // but during testing it does a deterministic sorted random to allow reproducing
943
- // issues.
944
- func (f * TxFetcher ) forEachAnnounce (announces map [common.Hash ]* txMetadata , do func (hash common.Hash , meta * txMetadata ) bool ) {
945
- // If we're running production, use whatever Go's map gives us
946
- if f .rand == nil {
947
- for hash , meta := range announces {
948
- if ! do (hash , meta ) {
949
- return
950
- }
951
- }
952
- return
997
+ // forEachAnnounce loops over the given announcements in arrival order, invoking
998
+ // the do function for each until it returns false. We enforce an arrival
999
+ // ordering to minimize the chances of transaction nonce-gaps, which result in
1000
+ // transactions being rejected by the txpool.
1001
+ func (f * TxFetcher ) forEachAnnounce (announces map [common.Hash ]* txMetadataWithSeq , do func (hash common.Hash , meta txMetadata ) bool ) {
1002
+ type announcement struct {
1003
+ hash common.Hash
1004
+ meta txMetadata
1005
+ seq uint64
953
1006
}
954
- // We're running the test suite, make iteration deterministic
955
- list := make ([]common. Hash , 0 , len (announces ))
956
- for hash := range announces {
957
- list = append (list , hash )
1007
+ // Process announcements by their arrival order
1008
+ list := make ([]announcement , 0 , len (announces ))
1009
+ for hash , entry := range announces {
1010
+ list = append (list , announcement { hash : hash , meta : entry . txMetadata , seq : entry . seq } )
958
1011
}
959
- sortHashes (list )
960
- rotateHashes (list , f .rand .Intn (len (list )))
961
- for _ , hash := range list {
962
- if ! do (hash , announces [hash ]) {
1012
+ sort .Slice (list , func (i , j int ) bool {
1013
+ return list [i ].seq < list [j ].seq
1014
+ })
1015
+ for i := range list {
1016
+ if ! do (list [i ].hash , list [i ].meta ) {
963
1017
return
964
1018
}
965
1019
}
@@ -975,26 +1029,3 @@ func rotateStrings(slice []string, n int) {
975
1029
slice [i ] = orig [(i + n )% len (orig )]
976
1030
}
977
1031
}
978
-
979
- // sortHashes sorts a slice of hashes. This method is only used in tests in order
980
- // to simulate random map iteration but keep it deterministic.
981
- func sortHashes (slice []common.Hash ) {
982
- for i := 0 ; i < len (slice ); i ++ {
983
- for j := i + 1 ; j < len (slice ); j ++ {
984
- if bytes .Compare (slice [i ][:], slice [j ][:]) > 0 {
985
- slice [i ], slice [j ] = slice [j ], slice [i ]
986
- }
987
- }
988
- }
989
- }
990
-
991
- // rotateHashes rotates the contents of a slice by n steps. This method is only
992
- // used in tests to simulate random map iteration but keep it deterministic.
993
- func rotateHashes (slice []common.Hash , n int ) {
994
- orig := make ([]common.Hash , len (slice ))
995
- copy (orig , slice )
996
-
997
- for i := 0 ; i < len (orig ); i ++ {
998
- slice [i ] = orig [(i + n )% len (orig )]
999
- }
1000
- }
0 commit comments