|
|
@ -50,9 +50,7 @@ class MemPool(object): |
|
|
|
self.notifications = notifications |
|
|
|
self.txs = {} |
|
|
|
self.hashXs = defaultdict(set) # None can be a key |
|
|
|
self.fee_histogram = defaultdict(int) |
|
|
|
self.cached_compact_histogram = [] |
|
|
|
self.histogram_time = 0 |
|
|
|
|
|
|
|
async def _log_stats(self): |
|
|
|
while True: |
|
|
@ -60,6 +58,34 @@ class MemPool(object): |
|
|
|
f'touching {len(self.hashXs):,d} addresses') |
|
|
|
await asyncio.sleep(120) |
|
|
|
|
|
|
|
def _update_histogram(self): |
|
|
|
# Build a histogram by fee rate |
|
|
|
histogram = defaultdict(int) |
|
|
|
for tx in self.txs.values(): |
|
|
|
histogram[tx.fee // tx.size] += tx.size |
|
|
|
|
|
|
|
# Now compact it. For efficiency, get_fees returns a |
|
|
|
# compact histogram with variable bin size. The compact |
|
|
|
# histogram is an array of (fee_rate, vsize) values. |
|
|
|
# vsize_n is the cumulative virtual size of mempool |
|
|
|
# transactions with a fee rate in the interval |
|
|
|
# [rate_(n-1), rate_n)], and rate_(n-1) > rate_n. |
|
|
|
# Intervals are chosen to create tranches containing at |
|
|
|
# least 100kb of transactions |
|
|
|
compact = [] |
|
|
|
cum_size = 0 |
|
|
|
r = 0 # ? |
|
|
|
bin_size = 100 * 1000 |
|
|
|
for fee_rate, size in sorted(histogram.items(), reverse=True): |
|
|
|
cum_size += size |
|
|
|
if cum_size + r > bin_size: |
|
|
|
compact.append((fee_rate, cum_size)) |
|
|
|
r += cum_size - bin_size |
|
|
|
cum_size = 0 |
|
|
|
bin_size *= 1.1 |
|
|
|
self.logger.info(f'compact fee histogram: {compact}') |
|
|
|
self.cached_compact_histogram = compact |
|
|
|
|
|
|
|
def _accept_transactions(self, tx_map, utxo_map, touched): |
|
|
|
'''Accept transactions in tx_map to the mempool if all their inputs |
|
|
|
can be found in the existing mempool or a utxo_map from the |
|
|
@ -69,7 +95,6 @@ class MemPool(object): |
|
|
|
''' |
|
|
|
hashXs = self.hashXs |
|
|
|
txs = self.txs |
|
|
|
fee_hist = self.fee_histogram |
|
|
|
init_count = len(utxo_map) |
|
|
|
|
|
|
|
deferred = {} |
|
|
@ -97,8 +122,6 @@ class MemPool(object): |
|
|
|
# Compute fee |
|
|
|
tx_fee = (sum(v for hashX, v in tx.in_pairs) - |
|
|
|
sum(v for hashX, v in tx.out_pairs)) |
|
|
|
fee_rate = tx.fee // tx.size |
|
|
|
fee_hist[fee_rate] += tx.size |
|
|
|
txs[hash] = tx |
|
|
|
for hashX, value in itertools.chain(tx.in_pairs, tx.out_pairs): |
|
|
|
touched.add(hashX) |
|
|
@ -111,7 +134,7 @@ class MemPool(object): |
|
|
|
are for.''' |
|
|
|
refresh_event = asyncio.Event() |
|
|
|
loop = self.tasks.loop |
|
|
|
while True: |
|
|
|
for loop_count in itertools.count(): |
|
|
|
height = self.daemon.cached_height() |
|
|
|
hex_hashes = await self.daemon.mempool_hashes() |
|
|
|
if height != await self.daemon.height(): |
|
|
@ -120,6 +143,10 @@ class MemPool(object): |
|
|
|
hashes = set(hex_str_to_hash(hh) for hh in hex_hashes) |
|
|
|
touched = await self._process_mempool(hashes) |
|
|
|
await self.notifications.on_mempool(touched, height) |
|
|
|
# Refresh the cached histogram periodically. Thread it as it |
|
|
|
# can be expensive. |
|
|
|
if loop_count % 100 == 0: |
|
|
|
await self.tasks.run_in_thread(self._update_histogram) |
|
|
|
if single_pass: |
|
|
|
return |
|
|
|
await refresh_event.wait() |
|
|
@ -130,15 +157,10 @@ class MemPool(object): |
|
|
|
txs = self.txs |
|
|
|
hashXs = self.hashXs |
|
|
|
touched = set() |
|
|
|
fee_hist = self.fee_histogram |
|
|
|
|
|
|
|
# First handle txs that have disappeared |
|
|
|
for tx_hash in set(txs).difference(all_hashes): |
|
|
|
tx = txs.pop(tx_hash) |
|
|
|
fee_rate = tx.fee // tx.size |
|
|
|
fee_hist[fee_rate] -= tx.size |
|
|
|
if fee_hist[fee_rate] == 0: |
|
|
|
fee_hist.pop(fee_rate) |
|
|
|
tx_hashXs = set(hashX for hashX, value in tx.in_pairs) |
|
|
|
tx_hashXs.update(hashX for hashX, value in tx.out_pairs) |
|
|
|
for hashX in tx_hashXs: |
|
|
@ -234,27 +256,6 @@ class MemPool(object): |
|
|
|
raw_txs = await self.daemon.getrawtransactions(hex_hashes) |
|
|
|
return zip(hashes, raw_txs) |
|
|
|
|
|
|
|
def _calc_compact_histogram(self): |
|
|
|
# For efficiency, get_fees returns a compact histogram with |
|
|
|
# variable bin size. The compact histogram is an array of |
|
|
|
# (fee, vsize) values. vsize_n is the cumulative virtual size |
|
|
|
# of mempool transactions with a fee rate in the interval |
|
|
|
# [fee_(n-1), fee_n)], and fee_(n-1) > fee_n. Fee intervals |
|
|
|
# are chosen so as to create tranches that contain at least |
|
|
|
# 100kb of transactions |
|
|
|
out = [] |
|
|
|
size = 0 |
|
|
|
r = 0 |
|
|
|
binsize = 100000 |
|
|
|
for fee, s in sorted(self.fee_histogram.items(), reverse=True): |
|
|
|
size += s |
|
|
|
if size + r > binsize: |
|
|
|
out.append((fee, size)) |
|
|
|
r += size - binsize |
|
|
|
size = 0 |
|
|
|
binsize *= 1.1 |
|
|
|
return out |
|
|
|
|
|
|
|
# External interface |
|
|
|
async def start_and_wait_for_sync(self): |
|
|
|
'''Starts the mempool synchronizer. |
|
|
@ -286,10 +287,6 @@ class MemPool(object): |
|
|
|
|
|
|
|
async def compact_fee_histogram(self): |
|
|
|
'''Return a compact fee histogram of the current mempool.''' |
|
|
|
now = time.time() |
|
|
|
if now > self.histogram_time: |
|
|
|
self.histogram_time = now + 30 |
|
|
|
self.cached_compact_histogram = self._calc_compact_histogram() |
|
|
|
return self.cached_compact_histogram |
|
|
|
|
|
|
|
async def potential_spends(self, hashX): |
|
|
|