@@ -20,6 +20,7 @@ import (
2020 "bytes"
2121 "errors"
2222 "fmt"
23+ "math"
2324 mrand "math/rand"
2425 "sort"
2526 "time"
@@ -105,6 +106,14 @@ var (
105106type txAnnounce struct {
106107 origin string // Identifier of the peer originating the notification
107108 hashes []common.Hash // Batch of transaction hashes being announced
109+ metas []* txMetadata // Batch of metadatas associated with the hashes (nil before eth/68)
110+ }
111+
112+ // txMetadata is a set of extra data transmitted along the announcement for better
113+ // fetch scheduling.
114+ type txMetadata struct {
115+ kind byte // Transaction consensus type
116+ size uint32 // Transaction size in bytes
108117}
109118
110119// txRequest represents an in-flight transaction retrieval request destined to
@@ -120,6 +129,7 @@ type txRequest struct {
120129type txDelivery struct {
121130 origin string // Identifier of the peer originating the notification
122131 hashes []common.Hash // Batch of transaction hashes having been delivered
132+ metas []txMetadata // Batch of metadatas associated with the delivered hashes
123133 direct bool // Whether this is a direct reply or a broadcast
124134}
125135
@@ -155,14 +165,14 @@ type TxFetcher struct {
155165
156166 // Stage 1: Waiting lists for newly discovered transactions that might be
157167 // broadcast without needing explicit request/reply round trips.
158- waitlist map [common.Hash ]map [string ]struct {} // Transactions waiting for an potential broadcast
159- waittime map [common.Hash ]mclock.AbsTime // Timestamps when transactions were added to the waitlist
160- waitslots map [string ]map [common.Hash ]struct {} // Waiting announcements grouped by peer (DoS protection)
168+ waitlist map [common.Hash ]map [string ]struct {} // Transactions waiting for an potential broadcast
169+ waittime map [common.Hash ]mclock.AbsTime // Timestamps when transactions were added to the waitlist
170+ waitslots map [string ]map [common.Hash ]* txMetadata // Waiting announcements grouped by peer (DoS protection)
161171
162172 // Stage 2: Queue of transactions that waiting to be allocated to some peer
163173 // to be retrieved directly.
164- announces map [string ]map [common.Hash ]struct {} // Set of announced transactions, grouped by origin peer
165- announced map [common.Hash ]map [string ]struct {} // Set of download locations, grouped by transaction hash
174+ announces map [string ]map [common.Hash ]* txMetadata // Set of announced transactions, grouped by origin peer
175+ announced map [common.Hash ]map [string ]struct {} // Set of download locations, grouped by transaction hash
166176
167177 // Stage 3: Set of transactions currently being retrieved, some which may be
168178 // fulfilled and some rescheduled. Note, this step shares 'announces' from the
@@ -175,6 +185,7 @@ type TxFetcher struct {
175185 hasTx func (common.Hash ) bool // Retrieves a tx from the local txpool
176186 addTxs func ([]* types.Transaction ) []error // Insert a batch of transactions into local txpool
177187 fetchTxs func (string , []common.Hash ) error // Retrieves a set of txs from a remote peer
188+ dropPeer func (string ) // Drops a peer in case of announcement violation
178189
179190 step chan struct {} // Notification channel when the fetcher loop iterates
180191 clock mclock.Clock // Time wrapper to simulate in tests
@@ -183,14 +194,14 @@ type TxFetcher struct {
183194
184195// NewTxFetcher creates a transaction fetcher to retrieve transaction
185196// based on hash announcements.
186- func NewTxFetcher (hasTx func (common.Hash ) bool , addTxs func ([]* types.Transaction ) []error , fetchTxs func (string , []common.Hash ) error ) * TxFetcher {
187- return NewTxFetcherForTests (hasTx , addTxs , fetchTxs , mclock.System {}, nil )
197+ func NewTxFetcher (hasTx func (common.Hash ) bool , addTxs func ([]* types.Transaction ) []error , fetchTxs func (string , []common.Hash ) error , dropPeer func ( string ) ) * TxFetcher {
198+ return NewTxFetcherForTests (hasTx , addTxs , fetchTxs , dropPeer , mclock.System {}, nil )
188199}
189200
190201// NewTxFetcherForTests is a testing method to mock out the realtime clock with
191202// a simulated version and the internal randomness with a deterministic one.
192203func NewTxFetcherForTests (
193- hasTx func (common.Hash ) bool , addTxs func ([]* types.Transaction ) []error , fetchTxs func (string , []common.Hash ) error ,
204+ hasTx func (common.Hash ) bool , addTxs func ([]* types.Transaction ) []error , fetchTxs func (string , []common.Hash ) error , dropPeer func ( string ),
194205 clock mclock.Clock , rand * mrand.Rand ) * TxFetcher {
195206 return & TxFetcher {
196207 notify : make (chan * txAnnounce ),
@@ -199,8 +210,8 @@ func NewTxFetcherForTests(
199210 quit : make (chan struct {}),
200211 waitlist : make (map [common.Hash ]map [string ]struct {}),
201212 waittime : make (map [common.Hash ]mclock.AbsTime ),
202- waitslots : make (map [string ]map [common.Hash ]struct {} ),
203- announces : make (map [string ]map [common.Hash ]struct {} ),
213+ waitslots : make (map [string ]map [common.Hash ]* txMetadata ),
214+ announces : make (map [string ]map [common.Hash ]* txMetadata ),
204215 announced : make (map [common.Hash ]map [string ]struct {}),
205216 fetching : make (map [common.Hash ]string ),
206217 requests : make (map [string ]* txRequest ),
@@ -209,14 +220,15 @@ func NewTxFetcherForTests(
209220 hasTx : hasTx ,
210221 addTxs : addTxs ,
211222 fetchTxs : fetchTxs ,
223+ dropPeer : dropPeer ,
212224 clock : clock ,
213225 rand : rand ,
214226 }
215227}
216228
217229// Notify announces the fetcher of the potential availability of a new batch of
218230// transactions in the network.
219- func (f * TxFetcher ) Notify (peer string , hashes []common.Hash ) error {
231+ func (f * TxFetcher ) Notify (peer string , types [] byte , sizes [] uint32 , hashes []common.Hash ) error {
220232 // Keep track of all the announced transactions
221233 txAnnounceInMeter .Mark (int64 (len (hashes )))
222234
@@ -226,28 +238,35 @@ func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error {
226238 // still valuable to check here because it runs concurrent to the internal
227239 // loop, so anything caught here is time saved internally.
228240 var (
229- unknowns = make ([]common.Hash , 0 , len (hashes ))
241+ unknownHashes = make ([]common.Hash , 0 , len (hashes ))
242+ unknownMetas = make ([]* txMetadata , 0 , len (hashes ))
243+
230244 duplicate int64
231245 underpriced int64
232246 )
233- for _ , hash := range hashes {
247+ for i , hash := range hashes {
234248 switch {
235249 case f .hasTx (hash ):
236250 duplicate ++
237251 case f .isKnownUnderpriced (hash ):
238252 underpriced ++
239253 default :
240- unknowns = append (unknowns , hash )
254+ unknownHashes = append (unknownHashes , hash )
255+ if types == nil {
256+ unknownMetas = append (unknownMetas , nil )
257+ } else {
258+ unknownMetas = append (unknownMetas , & txMetadata {kind : types [i ], size : sizes [i ]})
259+ }
241260 }
242261 }
243262 txAnnounceKnownMeter .Mark (duplicate )
244263 txAnnounceUnderpricedMeter .Mark (underpriced )
245264
246265 // If anything's left to announce, push it into the internal loop
247- if len (unknowns ) == 0 {
266+ if len (unknownHashes ) == 0 {
248267 return nil
249268 }
250- announce := & txAnnounce {origin : peer , hashes : unknowns }
269+ announce := & txAnnounce {origin : peer , hashes : unknownHashes , metas : unknownMetas }
251270 select {
252271 case f .notify <- announce :
253272 return nil
@@ -290,6 +309,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
290309 // re-requesting them and dropping the peer in case of malicious transfers.
291310 var (
292311 added = make ([]common.Hash , 0 , len (txs ))
312+ metas = make ([]txMetadata , 0 , len (txs ))
293313 )
294314 // proceed in batches
295315 for i := 0 ; i < len (txs ); i += 128 {
@@ -325,6 +345,10 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
325345 otherreject ++
326346 }
327347 added = append (added , batch [j ].Hash ())
348+ metas = append (metas , txMetadata {
349+ kind : batch [j ].Type (),
350+ size : uint32 (batch [j ].Size ()),
351+ })
328352 }
329353 knownMeter .Mark (duplicate )
330354 underpricedMeter .Mark (underpriced )
@@ -337,7 +361,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
337361 }
338362 }
339363 select {
340- case f .cleanup <- & txDelivery {origin : peer , hashes : added , direct : direct }:
364+ case f .cleanup <- & txDelivery {origin : peer , hashes : added , metas : metas , direct : direct }:
341365 return nil
342366 case <- f .quit :
343367 return errTerminated
@@ -394,13 +418,15 @@ func (f *TxFetcher) loop() {
394418 want := used + len (ann .hashes )
395419 if want > maxTxAnnounces {
396420 txAnnounceDOSMeter .Mark (int64 (want - maxTxAnnounces ))
421+
397422 ann .hashes = ann .hashes [:want - maxTxAnnounces ]
423+ ann .metas = ann .metas [:want - maxTxAnnounces ]
398424 }
399425 // All is well, schedule the remainder of the transactions
400426 idleWait := len (f .waittime ) == 0
401427 _ , oldPeer := f .announces [ann .origin ]
402428
403- for _ , hash := range ann .hashes {
429+ for i , hash := range ann .hashes {
404430 // If the transaction is already downloading, add it to the list
405431 // of possible alternates (in case the current retrieval fails) and
406432 // also account it for the peer.
@@ -409,9 +435,9 @@ func (f *TxFetcher) loop() {
409435
410436 // Stage 2 and 3 share the set of origins per tx
411437 if announces := f .announces [ann .origin ]; announces != nil {
412- announces [hash ] = struct {}{}
438+ announces [hash ] = ann . metas [ i ]
413439 } else {
414- f .announces [ann .origin ] = map [common.Hash ]struct {}{ hash : {} }
440+ f .announces [ann .origin ] = map [common.Hash ]* txMetadata { hash : ann . metas [ i ] }
415441 }
416442 continue
417443 }
@@ -422,22 +448,28 @@ func (f *TxFetcher) loop() {
422448
423449 // Stage 2 and 3 share the set of origins per tx
424450 if announces := f .announces [ann .origin ]; announces != nil {
425- announces [hash ] = struct {}{}
451+ announces [hash ] = ann . metas [ i ]
426452 } else {
427- f .announces [ann .origin ] = map [common.Hash ]struct {}{ hash : {} }
453+ f .announces [ann .origin ] = map [common.Hash ]* txMetadata { hash : ann . metas [ i ] }
428454 }
429455 continue
430456 }
431457 // If the transaction is already known to the fetcher, but not
432458 // yet downloading, add the peer as an alternate origin in the
433459 // waiting list.
434460 if f .waitlist [hash ] != nil {
461+ // Ignore double announcements from the same peer. This is
462+ // especially important if metadata is also passed along to
463+ // prevent malicious peers flip-flopping good/bad values.
464+ if _ , ok := f.waitlist [hash ][ann.origin ]; ok {
465+ continue
466+ }
435467 f.waitlist [hash ][ann.origin ] = struct {}{}
436468
437469 if waitslots := f .waitslots [ann .origin ]; waitslots != nil {
438- waitslots [hash ] = struct {}{}
470+ waitslots [hash ] = ann . metas [ i ]
439471 } else {
440- f .waitslots [ann .origin ] = map [common.Hash ]struct {}{ hash : {} }
472+ f .waitslots [ann .origin ] = map [common.Hash ]* txMetadata { hash : ann . metas [ i ] }
441473 }
442474 continue
443475 }
@@ -446,9 +478,9 @@ func (f *TxFetcher) loop() {
446478 f .waittime [hash ] = f .clock .Now ()
447479
448480 if waitslots := f .waitslots [ann .origin ]; waitslots != nil {
449- waitslots [hash ] = struct {}{}
481+ waitslots [hash ] = ann . metas [ i ]
450482 } else {
451- f .waitslots [ann .origin ] = map [common.Hash ]struct {}{ hash : {} }
483+ f .waitslots [ann .origin ] = map [common.Hash ]* txMetadata { hash : ann . metas [ i ] }
452484 }
453485 }
454486 // If a new item was added to the waitlist, schedule it into the fetcher
@@ -474,9 +506,9 @@ func (f *TxFetcher) loop() {
474506 f .announced [hash ] = f .waitlist [hash ]
475507 for peer := range f .waitlist [hash ] {
476508 if announces := f .announces [peer ]; announces != nil {
477- announces [hash ] = struct {}{}
509+ announces [hash ] = f. waitslots [ peer ][ hash ]
478510 } else {
479- f .announces [peer ] = map [common.Hash ]struct {}{ hash : {} }
511+ f .announces [peer ] = map [common.Hash ]* txMetadata { hash : f. waitslots [ peer ][ hash ] }
480512 }
481513 delete (f .waitslots [peer ], hash )
482514 if len (f .waitslots [peer ]) == 0 {
@@ -545,10 +577,27 @@ func (f *TxFetcher) loop() {
545577
546578 case delivery := <- f .cleanup :
547579 // Independent if the delivery was direct or broadcast, remove all
548- // traces of the hash from internal trackers
549- for _ , hash := range delivery .hashes {
580+ // traces of the hash from internal trackers. That said, compare any
581+ // advertised metadata with the real ones and drop bad peers.
582+ for i , hash := range delivery .hashes {
550583 if _ , ok := f .waitlist [hash ]; ok {
551584 for peer , txset := range f .waitslots {
585+ if meta := txset [hash ]; meta != nil {
586+ if delivery .metas [i ].kind != meta .kind {
587+ log .Warn ("Announced transaction type mismatch" , "peer" , peer , "tx" , hash , "type" , delivery .metas [i ].kind , "ann" , meta .kind )
588+ f .dropPeer (peer )
589+ } else if delivery .metas [i ].size != meta .size {
590+ log .Warn ("Announced transaction size mismatch" , "peer" , peer , "tx" , hash , "size" , delivery .metas [i ].size , "ann" , meta .size )
591+ if math .Abs (float64 (delivery .metas [i ].size )- float64 (meta .size )) > 8 {
592+ // Normally we should drop a peer considering this is a protocol violation.
593+ // However, due to the RLP vs consensus format messyness, allow a few bytes
594+ // wiggle-room where we only warn, but don't drop.
595+ //
596+ // TODO(karalabe): Get rid of this relaxation when clients are proven stable.
597+ f .dropPeer (peer )
598+ }
599+ }
600+ }
552601 delete (txset , hash )
553602 if len (txset ) == 0 {
554603 delete (f .waitslots , peer )
@@ -558,6 +607,22 @@ func (f *TxFetcher) loop() {
558607 delete (f .waittime , hash )
559608 } else {
560609 for peer , txset := range f .announces {
610+ if meta := txset [hash ]; meta != nil {
611+ if delivery .metas [i ].kind != meta .kind {
612+ log .Warn ("Announced transaction type mismatch" , "peer" , peer , "tx" , hash , "type" , delivery .metas [i ].kind , "ann" , meta .kind )
613+ f .dropPeer (peer )
614+ } else if delivery .metas [i ].size != meta .size {
615+ log .Warn ("Announced transaction size mismatch" , "peer" , peer , "tx" , hash , "size" , delivery .metas [i ].size , "ann" , meta .size )
616+ if math .Abs (float64 (delivery .metas [i ].size )- float64 (meta .size )) > 8 {
617+ // Normally we should drop a peer considering this is a protocol violation.
618+ // However, due to the RLP vs consensus format messyness, allow a few bytes
619+ // wiggle-room where we only warn, but don't drop.
620+ //
621+ // TODO(karalabe): Get rid of this relaxation when clients are proven stable.
622+ f .dropPeer (peer )
623+ }
624+ }
625+ }
561626 delete (txset , hash )
562627 if len (txset ) == 0 {
563628 delete (f .announces , peer )
@@ -859,7 +924,7 @@ func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string))
859924
860925// forEachHash does a range loop over a map of hashes in production, but during
861926// testing it does a deterministic sorted random to allow reproducing issues.
862- func (f * TxFetcher ) forEachHash (hashes map [common.Hash ]struct {} , do func (hash common.Hash ) bool ) {
927+ func (f * TxFetcher ) forEachHash (hashes map [common.Hash ]* txMetadata , do func (hash common.Hash ) bool ) {
863928 // If we're running production, use whatever Go's map gives us
864929 if f .rand == nil {
865930 for hash := range hashes {
0 commit comments