@@ -200,11 +200,11 @@ type TxPool struct {
200
200
locals * accountSet // Set of local transaction to exempt from eviction rules
201
201
journal * txJournal // Journal of local transaction to back up to disk
202
202
203
- pending map [common.Address ]* txList // All currently processable transactions
204
- queue map [common.Address ]* txList // Queued but non-processable transactions
205
- beats map [common.Address ]time.Time // Last heartbeat from each known account
206
- all map [common. Hash ] * types. Transaction // All transactions to allow lookups
207
- priced * txPricedList // All transactions sorted by price
203
+ pending map [common.Address ]* txList // All currently processable transactions
204
+ queue map [common.Address ]* txList // Queued but non-processable transactions
205
+ beats map [common.Address ]time.Time // Last heartbeat from each known account
206
+ all * txLookup // All transactions to allow lookups
207
+ priced * txPricedList // All transactions sorted by price
208
208
209
209
wg sync.WaitGroup // for shutdown sync
210
210
@@ -226,12 +226,12 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
226
226
pending : make (map [common.Address ]* txList ),
227
227
queue : make (map [common.Address ]* txList ),
228
228
beats : make (map [common.Address ]time.Time ),
229
- all : make ( map [common. Hash ] * types. Transaction ),
229
+ all : newTxLookup ( ),
230
230
chainHeadCh : make (chan ChainHeadEvent , chainHeadChanSize ),
231
231
gasPrice : new (big.Int ).SetUint64 (config .PriceLimit ),
232
232
}
233
233
pool .locals = newAccountSet (pool .signer )
234
- pool .priced = newTxPricedList (& pool .all )
234
+ pool .priced = newTxPricedList (pool .all )
235
235
pool .reset (nil , chain .CurrentBlock ().Header ())
236
236
237
237
// If local transactions and journaling is enabled, load from disk
@@ -605,7 +605,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
605
605
func (pool * TxPool ) add (tx * types.Transaction , local bool ) (bool , error ) {
606
606
// If the transaction is already known, discard it
607
607
hash := tx .Hash ()
608
- if pool .all [ hash ] != nil {
608
+ if pool .all . Get ( hash ) != nil {
609
609
log .Trace ("Discarding already known transaction" , "hash" , hash )
610
610
return false , fmt .Errorf ("known transaction: %x" , hash )
611
611
}
@@ -616,15 +616,15 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
616
616
return false , err
617
617
}
618
618
// If the transaction pool is full, discard underpriced transactions
619
- if uint64 (len ( pool .all )) >= pool .config .GlobalSlots + pool .config .GlobalQueue {
619
+ if uint64 (pool .all . Count ( )) >= pool .config .GlobalSlots + pool .config .GlobalQueue {
620
620
// If the new transaction is underpriced, don't accept it
621
621
if ! local && pool .priced .Underpriced (tx , pool .locals ) {
622
622
log .Trace ("Discarding underpriced transaction" , "hash" , hash , "price" , tx .GasPrice ())
623
623
underpricedTxCounter .Inc (1 )
624
624
return false , ErrUnderpriced
625
625
}
626
626
// New transaction is better than our worse ones, make room for it
627
- drop := pool .priced .Discard (len ( pool .all )- int (pool .config .GlobalSlots + pool .config .GlobalQueue - 1 ), pool .locals )
627
+ drop := pool .priced .Discard (pool .all . Count ( )- int (pool .config .GlobalSlots + pool .config .GlobalQueue - 1 ), pool .locals )
628
628
for _ , tx := range drop {
629
629
log .Trace ("Discarding freshly underpriced transaction" , "hash" , tx .Hash (), "price" , tx .GasPrice ())
630
630
underpricedTxCounter .Inc (1 )
@@ -642,11 +642,11 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
642
642
}
643
643
// New transaction is better, replace old one
644
644
if old != nil {
645
- delete ( pool .all , old .Hash ())
645
+ pool .all . Remove ( old .Hash ())
646
646
pool .priced .Removed ()
647
647
pendingReplaceCounter .Inc (1 )
648
648
}
649
- pool .all [ tx . Hash ()] = tx
649
+ pool .all . Add ( tx )
650
650
pool .priced .Put (tx )
651
651
pool .journalTx (from , tx )
652
652
@@ -689,12 +689,12 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er
689
689
}
690
690
// Discard any previous transaction and mark this
691
691
if old != nil {
692
- delete ( pool .all , old .Hash ())
692
+ pool .all . Remove ( old .Hash ())
693
693
pool .priced .Removed ()
694
694
queuedReplaceCounter .Inc (1 )
695
695
}
696
- if pool .all [ hash ] == nil {
697
- pool .all [ hash ] = tx
696
+ if pool .all . Get ( hash ) == nil {
697
+ pool .all . Add ( tx )
698
698
pool .priced .Put (tx )
699
699
}
700
700
return old != nil , nil
@@ -726,22 +726,22 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
726
726
inserted , old := list .Add (tx , pool .config .PriceBump )
727
727
if ! inserted {
728
728
// An older transaction was better, discard this
729
- delete ( pool .all , hash )
729
+ pool .all . Remove ( hash )
730
730
pool .priced .Removed ()
731
731
732
732
pendingDiscardCounter .Inc (1 )
733
733
return false
734
734
}
735
735
// Otherwise discard any previous transaction and mark this
736
736
if old != nil {
737
- delete ( pool .all , old .Hash ())
737
+ pool .all . Remove ( old .Hash ())
738
738
pool .priced .Removed ()
739
739
740
740
pendingReplaceCounter .Inc (1 )
741
741
}
742
742
// Failsafe to work around direct pending inserts (tests)
743
- if pool .all [ hash ] == nil {
744
- pool .all [ hash ] = tx
743
+ if pool .all . Get ( hash ) == nil {
744
+ pool .all . Add ( tx )
745
745
pool .priced .Put (tx )
746
746
}
747
747
// Set the potentially new pending nonce and notify any subsystems of the new tx
@@ -840,7 +840,7 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
840
840
841
841
status := make ([]TxStatus , len (hashes ))
842
842
for i , hash := range hashes {
843
- if tx := pool .all [ hash ] ; tx != nil {
843
+ if tx := pool .all . Get ( hash ) ; tx != nil {
844
844
from , _ := types .Sender (pool .signer , tx ) // already validated
845
845
if pool .pending [from ] != nil && pool .pending [from ].txs .items [tx .Nonce ()] != nil {
846
846
status [i ] = TxStatusPending
@@ -855,24 +855,21 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
855
855
// Get returns a transaction if it is contained in the pool
856
856
// and nil otherwise.
857
857
func (pool * TxPool ) Get (hash common.Hash ) * types.Transaction {
858
- pool .mu .RLock ()
859
- defer pool .mu .RUnlock ()
860
-
861
- return pool .all [hash ]
858
+ return pool .all .Get (hash )
862
859
}
863
860
864
861
// removeTx removes a single transaction from the queue, moving all subsequent
865
862
// transactions back to the future queue.
866
863
func (pool * TxPool ) removeTx (hash common.Hash , outofbound bool ) {
867
864
// Fetch the transaction we wish to delete
868
- tx , ok := pool .all [ hash ]
869
- if ! ok {
865
+ tx := pool .all . Get ( hash )
866
+ if tx == nil {
870
867
return
871
868
}
872
869
addr , _ := types .Sender (pool .signer , tx ) // already validated during insertion
873
870
874
871
// Remove it from the list of known transactions
875
- delete ( pool .all , hash )
872
+ pool .all . Remove ( hash )
876
873
if outofbound {
877
874
pool .priced .Removed ()
878
875
}
@@ -928,15 +925,15 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
928
925
for _ , tx := range list .Forward (pool .currentState .GetNonce (addr )) {
929
926
hash := tx .Hash ()
930
927
log .Trace ("Removed old queued transaction" , "hash" , hash )
931
- delete ( pool .all , hash )
928
+ pool .all . Remove ( hash )
932
929
pool .priced .Removed ()
933
930
}
934
931
// Drop all transactions that are too costly (low balance or out of gas)
935
932
drops , _ := list .Filter (pool .currentState .GetBalance (addr ), pool .currentMaxGas )
936
933
for _ , tx := range drops {
937
934
hash := tx .Hash ()
938
935
log .Trace ("Removed unpayable queued transaction" , "hash" , hash )
939
- delete ( pool .all , hash )
936
+ pool .all . Remove ( hash )
940
937
pool .priced .Removed ()
941
938
queuedNofundsCounter .Inc (1 )
942
939
}
@@ -952,7 +949,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
952
949
if ! pool .locals .contains (addr ) {
953
950
for _ , tx := range list .Cap (int (pool .config .AccountQueue )) {
954
951
hash := tx .Hash ()
955
- delete ( pool .all , hash )
952
+ pool .all . Remove ( hash )
956
953
pool .priced .Removed ()
957
954
queuedRateLimitCounter .Inc (1 )
958
955
log .Trace ("Removed cap-exceeding queued transaction" , "hash" , hash )
@@ -1001,7 +998,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
1001
998
for _ , tx := range list .Cap (list .Len () - 1 ) {
1002
999
// Drop the transaction from the global pools too
1003
1000
hash := tx .Hash ()
1004
- delete ( pool .all , hash )
1001
+ pool .all . Remove ( hash )
1005
1002
pool .priced .Removed ()
1006
1003
1007
1004
// Update the account nonce to the dropped transaction
@@ -1023,7 +1020,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
1023
1020
for _ , tx := range list .Cap (list .Len () - 1 ) {
1024
1021
// Drop the transaction from the global pools too
1025
1022
hash := tx .Hash ()
1026
- delete ( pool .all , hash )
1023
+ pool .all . Remove ( hash )
1027
1024
pool .priced .Removed ()
1028
1025
1029
1026
// Update the account nonce to the dropped transaction
@@ -1092,15 +1089,15 @@ func (pool *TxPool) demoteUnexecutables() {
1092
1089
for _ , tx := range list .Forward (nonce ) {
1093
1090
hash := tx .Hash ()
1094
1091
log .Trace ("Removed old pending transaction" , "hash" , hash )
1095
- delete ( pool .all , hash )
1092
+ pool .all . Remove ( hash )
1096
1093
pool .priced .Removed ()
1097
1094
}
1098
1095
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
1099
1096
drops , invalids := list .Filter (pool .currentState .GetBalance (addr ), pool .currentMaxGas )
1100
1097
for _ , tx := range drops {
1101
1098
hash := tx .Hash ()
1102
1099
log .Trace ("Removed unpayable pending transaction" , "hash" , hash )
1103
- delete ( pool .all , hash )
1100
+ pool .all . Remove ( hash )
1104
1101
pool .priced .Removed ()
1105
1102
pendingNofundsCounter .Inc (1 )
1106
1103
}
@@ -1172,3 +1169,68 @@ func (as *accountSet) containsTx(tx *types.Transaction) bool {
1172
1169
func (as * accountSet ) add (addr common.Address ) {
1173
1170
as .accounts [addr ] = struct {}{}
1174
1171
}
1172
+
1173
+ // txLookup is used internally by TxPool to track transactions while allowing lookup without
1174
+ // mutex contention.
1175
+ //
1176
+ // Note, although this type is properly protected against concurrent access, it
1177
+ // is **not** a type that should ever be mutated or even exposed outside of the
1178
+ // transaction pool, since its internal state is tightly coupled with the pools
1179
+ // internal mechanisms. The sole purpose of the type is to permit out-of-bound
1180
+ // peeking into the pool in TxPool.Get without having to acquire the widely scoped
1181
+ // TxPool.mu mutex.
1182
+ type txLookup struct {
1183
+ all map [common.Hash ]* types.Transaction
1184
+ lock sync.RWMutex
1185
+ }
1186
+
1187
+ // newTxLookup returns a new txLookup structure.
1188
+ func newTxLookup () * txLookup {
1189
+ return & txLookup {
1190
+ all : make (map [common.Hash ]* types.Transaction ),
1191
+ }
1192
+ }
1193
+
1194
+ // Range calls f on each key and value present in the map.
1195
+ func (t * txLookup ) Range (f func (hash common.Hash , tx * types.Transaction ) bool ) {
1196
+ t .lock .RLock ()
1197
+ defer t .lock .RUnlock ()
1198
+
1199
+ for key , value := range t .all {
1200
+ if ! f (key , value ) {
1201
+ break
1202
+ }
1203
+ }
1204
+ }
1205
+
1206
+ // Get returns a transaction if it exists in the lookup, or nil if not found.
1207
+ func (t * txLookup ) Get (hash common.Hash ) * types.Transaction {
1208
+ t .lock .RLock ()
1209
+ defer t .lock .RUnlock ()
1210
+
1211
+ return t .all [hash ]
1212
+ }
1213
+
1214
+ // Count returns the current number of items in the lookup.
1215
+ func (t * txLookup ) Count () int {
1216
+ t .lock .RLock ()
1217
+ defer t .lock .RUnlock ()
1218
+
1219
+ return len (t .all )
1220
+ }
1221
+
1222
+ // Add adds a transaction to the lookup.
1223
+ func (t * txLookup ) Add (tx * types.Transaction ) {
1224
+ t .lock .Lock ()
1225
+ defer t .lock .Unlock ()
1226
+
1227
+ t .all [tx .Hash ()] = tx
1228
+ }
1229
+
1230
+ // Remove removes a transaction from the lookup.
1231
+ func (t * txLookup ) Remove (hash common.Hash ) {
1232
+ t .lock .Lock ()
1233
+ defer t .lock .Unlock ()
1234
+
1235
+ delete (t .all , hash )
1236
+ }
0 commit comments