@@ -25,7 +25,7 @@ class MemPool(util.LoggedClass):
25
25
26
26
To that end we maintain the following maps:
27
27
28
- tx_hash -> (txin_pairs, txout_pairs)
28
+ tx_hash -> (txin_pairs, txout_pairs, tx_fee, tx_size )
29
29
hashX -> set of all tx hashes in which the hashX appears
30
30
31
31
A pair is a (hashX, value) tuple. tx hashes are hex strings.
@@ -42,6 +42,9 @@ def __init__(self, bp, controller):
42
42
self .txs = {}
43
43
self .hashXs = defaultdict (set ) # None can be a key
44
44
self .synchronized_event = asyncio .Event ()
45
+ self .fee_histogram = defaultdict (int )
46
+ self .compact_fee_histogram = []
47
+ self .histogram_time = 0
45
48
46
49
def _resync_daemon_hashes (self , unprocessed , unfetched ):
47
50
'''Re-sync self.txs with the list of hashes in the daemon's mempool.
@@ -52,6 +55,7 @@ def _resync_daemon_hashes(self, unprocessed, unfetched):
52
55
txs = self .txs
53
56
hashXs = self .hashXs
54
57
touched = self .touched
58
+ fee_hist = self .fee_histogram
55
59
56
60
hashes = self .daemon .cached_mempool_hashes ()
57
61
gone = set (txs ).difference (hashes )
@@ -60,7 +64,11 @@ def _resync_daemon_hashes(self, unprocessed, unfetched):
60
64
unprocessed .pop (hex_hash , None )
61
65
item = txs .pop (hex_hash )
62
66
if item :
63
- txin_pairs , txout_pairs = item
67
+ txin_pairs , txout_pairs , tx_fee , tx_size = item
68
+ fee_rate = tx_fee // tx_size
69
+ fee_hist [fee_rate ] -= tx_size
70
+ if fee_hist [fee_rate ] == 0 :
71
+ fee_hist .pop (fee_rate )
64
72
tx_hashXs = set (hashX for hashX , value in txin_pairs )
65
73
tx_hashXs .update (hashX for hashX , value in txout_pairs )
66
74
for hashX in tx_hashXs :
@@ -138,6 +146,7 @@ async def main_loop(self):
138
146
def _async_process_some (self , limit ):
139
147
pending = []
140
148
txs = self .txs
149
+ fee_hist = self .fee_histogram
141
150
142
151
async def process (unprocessed ):
143
152
nonlocal pending
@@ -160,10 +169,13 @@ async def process(unprocessed):
160
169
pending .extend (deferred )
161
170
hashXs = self .hashXs
162
171
touched = self .touched
163
- for hex_hash , in_out_pairs in result .items ():
172
+ for hex_hash , item in result .items ():
164
173
if hex_hash in txs :
165
- txs [hex_hash ] = in_out_pairs
166
- for hashX , value in itertools .chain (* in_out_pairs ):
174
+ txs [hex_hash ] = item
175
+ txin_pairs , txout_pairs , tx_fee , tx_size = item
176
+ fee_rate = tx_fee // tx_size
177
+ fee_hist [fee_rate ] += tx_size
178
+ for hashX , value in itertools .chain (txin_pairs , txout_pairs ):
167
179
touched .add (hashX )
168
180
hashXs [hashX ].add (hex_hash )
169
181
@@ -209,7 +221,7 @@ def process_raw_txs(self, raw_tx_map, pending):
209
221
for tx_hash , raw_tx in raw_tx_map .items ():
210
222
if tx_hash not in txs :
211
223
continue
212
- tx = deserializer (raw_tx ).read_tx ()
224
+ tx , tx_size = deserializer (raw_tx ).read_tx_and_vsize ()
213
225
214
226
# Convert the tx outputs into (hashX, value) pairs
215
227
txout_pairs = [(script_hashX (txout .pk_script ), txout .value )
@@ -219,7 +231,7 @@ def process_raw_txs(self, raw_tx_map, pending):
219
231
txin_pairs = [(hash_to_str (txin .prev_hash ), txin .prev_idx )
220
232
for txin in tx .inputs ]
221
233
222
- pending .append ((tx_hash , txin_pairs , txout_pairs ))
234
+ pending .append ((tx_hash , txin_pairs , txout_pairs , tx_size ))
223
235
224
236
# Now process what we can
225
237
result = {}
@@ -229,7 +241,7 @@ def process_raw_txs(self, raw_tx_map, pending):
229
241
if self .stop :
230
242
break
231
243
232
- tx_hash , old_txin_pairs , txout_pairs = item
244
+ tx_hash , old_txin_pairs , txout_pairs , tx_size = item
233
245
if tx_hash not in txs :
234
246
continue
235
247
@@ -259,7 +271,10 @@ def process_raw_txs(self, raw_tx_map, pending):
259
271
if mempool_missing :
260
272
deferred .append (item )
261
273
else :
262
- result [tx_hash ] = (txin_pairs , txout_pairs )
274
+ # Compute fee
275
+ tx_fee = (sum (v for hashX , v in txin_pairs ) -
276
+ sum (v for hashX , v in txout_pairs ))
277
+ result [tx_hash ] = (txin_pairs , txout_pairs , tx_fee , tx_size )
263
278
264
279
return result , deferred
265
280
@@ -290,9 +305,7 @@ async def transactions(self, hashX):
290
305
item = self .txs .get (hex_hash )
291
306
if not item or not raw_tx :
292
307
continue
293
- txin_pairs , txout_pairs = item
294
- tx_fee = (sum (v for hashX , v in txin_pairs ) -
295
- sum (v for hashX , v in txout_pairs ))
308
+ txin_pairs , txout_pairs , tx_fee , tx_size = item
296
309
tx = deserializer (raw_tx ).read_tx ()
297
310
unconfirmed = any (hash_to_str (txin .prev_hash ) in self .txs
298
311
for txin in tx .inputs )
@@ -325,7 +338,36 @@ def value(self, hashX):
325
338
# hashXs is a defaultdict
326
339
if hashX in self .hashXs :
327
340
for hex_hash in self .hashXs [hashX ]:
328
- txin_pairs , txout_pairs = self .txs [hex_hash ]
341
+ txin_pairs , txout_pairs , tx_fee , tx_size = self .txs [hex_hash ]
329
342
value -= sum (v for h168 , v in txin_pairs if h168 == hashX )
330
343
value += sum (v for h168 , v in txout_pairs if h168 == hashX )
331
344
return value
345
+
346
+ def get_fee_histogram (self ):
347
+ now = time .time ()
348
+ if now > self .histogram_time + 30 :
349
+ self .update_compact_histogram ()
350
+ self .histogram_time = now
351
+ return self .compact_fee_histogram
352
+
353
+ def update_compact_histogram (self ):
354
+ # For efficiency, get_fees returns a compact histogram with
355
+ # variable bin size. The compact histogram is an array of
356
+ # (fee, vsize) values. vsize_n is the cumulative virtual size
357
+ # of mempool transactions with a fee rate in the interval
358
+ # [fee_(n-1), fee_n)], and fee_(n-1) > fee_n. Fee intervals
359
+ # are chosen so as to create tranches that contain at least
360
+ # 100kb of transactions
361
+ l = list (reversed (sorted (self .fee_histogram .items ())))
362
+ out = []
363
+ size = 0
364
+ r = 0
365
+ binsize = 100000
366
+ for fee , s in l :
367
+ size += s
368
+ if size + r > binsize :
369
+ out .append ((fee , size ))
370
+ r += size - binsize
371
+ size = 0
372
+ binsize *= 1.1
373
+ self .compact_fee_histogram = out
0 commit comments