4cfc1b4765
-adds HashXStatusPrefixRow and HashXMempoolStatusPrefixRow column families
110 lines
4.4 KiB
Python
110 lines
4.4 KiB
Python
import itertools
|
|
import attr
|
|
import typing
|
|
from collections import defaultdict
|
|
from scribe.blockchain.transaction.deserializer import Deserializer
|
|
|
|
if typing.TYPE_CHECKING:
|
|
from scribe.db import HubDB
|
|
|
|
|
|
@attr.s(slots=True)
|
|
class MemPoolTx:
|
|
prevouts = attr.ib()
|
|
# A pair is a (hashX, value) tuple
|
|
in_pairs = attr.ib()
|
|
out_pairs = attr.ib()
|
|
fee = attr.ib()
|
|
size = attr.ib()
|
|
raw_tx = attr.ib()
|
|
|
|
|
|
@attr.s(slots=True)
|
|
class MemPoolTxSummary:
|
|
hash = attr.ib()
|
|
fee = attr.ib()
|
|
has_unconfirmed_inputs = attr.ib()
|
|
|
|
|
|
class MemPool:
|
|
def __init__(self, coin, db: 'HubDB'):
|
|
self.coin = coin
|
|
self._db = db
|
|
self.txs = {}
|
|
self.touched_hashXs: typing.DefaultDict[bytes, typing.Set[bytes]] = defaultdict(set) # None can be a key
|
|
|
|
def mempool_history(self, hashX: bytes) -> str:
|
|
result = ''
|
|
for tx_hash in self.touched_hashXs.get(hashX, ()):
|
|
if tx_hash not in self.txs:
|
|
continue # the tx hash for the touched address is an input that isn't in mempool anymore
|
|
result += f'{tx_hash[::-1].hex()}:{-any(_hash in self.txs for _hash, idx in self.txs[tx_hash].in_pairs):d}:'
|
|
return result
|
|
|
|
def remove(self, to_remove: typing.Dict[bytes, bytes]):
|
|
# Remove txs that aren't in mempool anymore
|
|
for tx_hash in set(self.txs).intersection(to_remove.keys()):
|
|
tx = self.txs.pop(tx_hash)
|
|
tx_hashXs = {hashX for hashX, value in tx.in_pairs}.union({hashX for hashX, value in tx.out_pairs})
|
|
for hashX in tx_hashXs:
|
|
if hashX in self.touched_hashXs and tx_hash in self.touched_hashXs[hashX]:
|
|
self.touched_hashXs[hashX].remove(tx_hash)
|
|
if not self.touched_hashXs[hashX]:
|
|
self.touched_hashXs.pop(hashX)
|
|
|
|
def update_mempool(self, to_add: typing.List[typing.Tuple[bytes, bytes]]) -> typing.Set[bytes]:
|
|
prefix_db = self._db.prefix_db
|
|
touched_hashXs = set()
|
|
|
|
# Re-sync with the new set of hashes
|
|
tx_map = {}
|
|
for tx_hash, raw_tx in to_add:
|
|
if tx_hash in self.txs:
|
|
continue
|
|
tx, tx_size = Deserializer(raw_tx).read_tx_and_vsize()
|
|
# Convert the inputs and outputs into (hashX, value) pairs
|
|
# Drop generation-like inputs from MemPoolTx.prevouts
|
|
txin_pairs = tuple((txin.prev_hash, txin.prev_idx)
|
|
for txin in tx.inputs
|
|
if not txin.is_generation())
|
|
txout_pairs = tuple((self.coin.hashX_from_txo(txout), txout.value)
|
|
for txout in tx.outputs if txout.pk_script)
|
|
tx_map[tx_hash] = MemPoolTx(None, txin_pairs, txout_pairs, 0, tx_size, raw_tx)
|
|
|
|
for tx_hash, tx in tx_map.items():
|
|
prevouts = []
|
|
# Look up the prevouts
|
|
for prev_hash, prev_index in tx.in_pairs:
|
|
if prev_hash in self.txs: # accepted mempool
|
|
utxo = self.txs[prev_hash].out_pairs[prev_index]
|
|
elif prev_hash in tx_map: # this set of changes
|
|
utxo = tx_map[prev_hash].out_pairs[prev_index]
|
|
else: # get it from the db
|
|
prev_tx_num = prefix_db.tx_num.get(prev_hash)
|
|
if not prev_tx_num:
|
|
continue
|
|
prev_tx_num = prev_tx_num.tx_num
|
|
hashX_val = prefix_db.hashX_utxo.get(prev_hash[:4], prev_tx_num, prev_index)
|
|
if not hashX_val:
|
|
continue
|
|
hashX = hashX_val.hashX
|
|
utxo_value = prefix_db.utxo.get(hashX, prev_tx_num, prev_index)
|
|
utxo = (hashX, utxo_value.amount)
|
|
prevouts.append(utxo)
|
|
|
|
# Save the prevouts, compute the fee and accept the TX
|
|
tx.prevouts = tuple(prevouts)
|
|
# Avoid negative fees if dealing with generation-like transactions
|
|
# because some in_parts would be missing
|
|
tx.fee = max(0, (sum(v for _, v in tx.prevouts) -
|
|
sum(v for _, v in tx.out_pairs)))
|
|
self.txs[tx_hash] = tx
|
|
for hashX, value in itertools.chain(tx.prevouts, tx.out_pairs):
|
|
self.touched_hashXs[hashX].add(tx_hash)
|
|
touched_hashXs.add(hashX)
|
|
|
|
return touched_hashXs
|
|
|
|
def clear(self):
|
|
self.txs.clear()
|
|
self.touched_hashXs.clear()
|