Create MemPoolAPI and use it
This commit is contained in:
parent
4329724b98
commit
90dcf87536
@ -14,7 +14,7 @@ from electrumx.lib.server_base import ServerBase
|
|||||||
from electrumx.lib.util import version_string
|
from electrumx.lib.util import version_string
|
||||||
from electrumx.server.chain_state import ChainState
|
from electrumx.server.chain_state import ChainState
|
||||||
from electrumx.server.db import DB
|
from electrumx.server.db import DB
|
||||||
from electrumx.server.mempool import MemPool
|
from electrumx.server.mempool import MemPool, MemPoolAPI
|
||||||
from electrumx.server.session import SessionManager
|
from electrumx.server.session import SessionManager
|
||||||
|
|
||||||
|
|
||||||
@ -97,8 +97,18 @@ class Controller(ServerBase):
|
|||||||
db = DB(env)
|
db = DB(env)
|
||||||
BlockProcessor = env.coin.BLOCK_PROCESSOR
|
BlockProcessor = env.coin.BLOCK_PROCESSOR
|
||||||
bp = BlockProcessor(env, db, daemon, notifications)
|
bp = BlockProcessor(env, db, daemon, notifications)
|
||||||
mempool = MemPool(env.coin, daemon, notifications, db.lookup_utxos)
|
|
||||||
chain_state = ChainState(env, db, daemon, bp)
|
chain_state = ChainState(env, db, daemon, bp)
|
||||||
|
|
||||||
|
# Set ourselves up to implement the MemPoolAPI
|
||||||
|
self.height = daemon.height
|
||||||
|
self.cached_height = daemon.cached_height
|
||||||
|
self.mempool_hashes = daemon.mempool_hashes
|
||||||
|
self.raw_transactions = daemon.getrawtransactions
|
||||||
|
self.lookup_utxos = db.lookup_utxos
|
||||||
|
self.on_mempool = notifications.on_mempool
|
||||||
|
MemPoolAPI.register(Controller)
|
||||||
|
mempool = MemPool(env.coin, self)
|
||||||
|
|
||||||
session_mgr = SessionManager(env, chain_state, mempool,
|
session_mgr = SessionManager(env, chain_state, mempool,
|
||||||
notifications, shutdown_event)
|
notifications, shutdown_event)
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import itertools
|
import itertools
|
||||||
import time
|
import time
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
@ -30,9 +31,53 @@ class MemPoolTx(object):
|
|||||||
size = attr.ib()
|
size = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
|
class MemPoolAPI(ABC):
|
||||||
|
'''A concrete instance of this class is passed to the MemPool object
|
||||||
|
and used by it to query DB and blockchain state.'''
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def height(self):
|
||||||
|
'''Query bitcoind for its height.'''
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def cached_height(self):
|
||||||
|
'''Return the height of bitcoind the last time it was queried,
|
||||||
|
for any reason, without actually querying it.
|
||||||
|
'''
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def mempool_hashes(self):
|
||||||
|
'''Query bitcoind for the hashes of all transactions in its
|
||||||
|
mempool, returned as a list.'''
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def raw_transactions(self, hex_hashes):
|
||||||
|
'''Query bitcoind for the serialized raw transactions with the given
|
||||||
|
hashes. Missing transactions are returned as None.
|
||||||
|
|
||||||
|
hex_hashes is an iterable of hexadecimal hash strings.'''
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def lookup_utxos(self, prevouts):
|
||||||
|
'''Return a list of (hashX, value) pairs each prevout if unspent,
|
||||||
|
otherwise return None if spent or not found.
|
||||||
|
|
||||||
|
prevouts - an iterable of (hash, index) pairs
|
||||||
|
'''
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def on_mempool(self, touched, height):
|
||||||
|
'''Called each time the mempool is synchronized. touched is a set of
|
||||||
|
hashXs touched since the previous call. height is the
|
||||||
|
daemon's height at the time the mempool was obtained.'''
|
||||||
|
|
||||||
|
|
||||||
class MemPool(object):
|
class MemPool(object):
|
||||||
'''Representation of the daemon's mempool.
|
'''Representation of the daemon's mempool.
|
||||||
|
|
||||||
|
coin - a coin class from coins.py
|
||||||
|
api - an object implementing MemPoolAPI
|
||||||
|
|
||||||
Updated regularly in caught-up state. Goal is to enable efficient
|
Updated regularly in caught-up state. Goal is to enable efficient
|
||||||
response to the calls in the external interface. To that end we
|
response to the calls in the external interface. To that end we
|
||||||
maintain the following maps:
|
maintain the following maps:
|
||||||
@ -41,12 +86,11 @@ class MemPool(object):
|
|||||||
hashXs: hashX -> set of all hashes of txs touching the hashX
|
hashXs: hashX -> set of all hashes of txs touching the hashX
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, coin, daemon, notifications, lookup_utxos):
|
def __init__(self, coin, api):
|
||||||
self.logger = class_logger(__name__, self.__class__.__name__)
|
assert isinstance(api, MemPoolAPI)
|
||||||
self.coin = coin
|
self.coin = coin
|
||||||
self.lookup_utxos = lookup_utxos
|
self.api = api
|
||||||
self.daemon = daemon
|
self.logger = class_logger(__name__, self.__class__.__name__)
|
||||||
self.notifications = notifications
|
|
||||||
self.txs = {}
|
self.txs = {}
|
||||||
self.hashXs = defaultdict(set) # None can be a key
|
self.hashXs = defaultdict(set) # None can be a key
|
||||||
self.cached_compact_histogram = []
|
self.cached_compact_histogram = []
|
||||||
@ -132,14 +176,14 @@ class MemPool(object):
|
|||||||
sleep = 5
|
sleep = 5
|
||||||
histogram_refresh = self.coin.MEMPOOL_HISTOGRAM_REFRESH_SECS // sleep
|
histogram_refresh = self.coin.MEMPOOL_HISTOGRAM_REFRESH_SECS // sleep
|
||||||
for loop_count in itertools.count():
|
for loop_count in itertools.count():
|
||||||
height = self.daemon.cached_height()
|
height = self.api.cached_height()
|
||||||
hex_hashes = await self.daemon.mempool_hashes()
|
hex_hashes = await self.api.mempool_hashes()
|
||||||
if height != await self.daemon.height():
|
if height != await self.api.height():
|
||||||
continue
|
continue
|
||||||
hashes = set(hex_str_to_hash(hh) for hh in hex_hashes)
|
hashes = set(hex_str_to_hash(hh) for hh in hex_hashes)
|
||||||
touched = await self._process_mempool(hashes)
|
touched = await self._process_mempool(hashes)
|
||||||
synchronized_event.set()
|
synchronized_event.set()
|
||||||
await self.notifications.on_mempool(touched, height)
|
await self.api.on_mempool(touched, height)
|
||||||
# Thread mempool histogram refreshes - they can be expensive
|
# Thread mempool histogram refreshes - they can be expensive
|
||||||
if loop_count % histogram_refresh == 0:
|
if loop_count % histogram_refresh == 0:
|
||||||
await run_in_thread(self._update_histogram)
|
await run_in_thread(self._update_histogram)
|
||||||
@ -193,7 +237,7 @@ class MemPool(object):
|
|||||||
async def _fetch_and_accept(self, hashes, all_hashes, touched):
|
async def _fetch_and_accept(self, hashes, all_hashes, touched):
|
||||||
'''Fetch a list of mempool transactions.'''
|
'''Fetch a list of mempool transactions.'''
|
||||||
hex_hashes_iter = (hash_to_hex_str(hash) for hash in hashes)
|
hex_hashes_iter = (hash_to_hex_str(hash) for hash in hashes)
|
||||||
raw_txs = await self.daemon.getrawtransactions(hex_hashes_iter)
|
raw_txs = await self.api.raw_transactions(hex_hashes_iter)
|
||||||
|
|
||||||
def deserialize_txs(): # This function is pure
|
def deserialize_txs(): # This function is pure
|
||||||
to_hashX = self.coin.hashX_from_script
|
to_hashX = self.coin.hashX_from_script
|
||||||
@ -225,7 +269,7 @@ class MemPool(object):
|
|||||||
prevouts = tuple(prevout for tx in tx_map.values()
|
prevouts = tuple(prevout for tx in tx_map.values()
|
||||||
for prevout in tx.prevouts
|
for prevout in tx.prevouts
|
||||||
if prevout[0] not in all_hashes)
|
if prevout[0] not in all_hashes)
|
||||||
utxos = await self.lookup_utxos(prevouts)
|
utxos = await self.api.lookup_utxos(prevouts)
|
||||||
utxo_map = {prevout: utxo for prevout, utxo in zip(prevouts, utxos)}
|
utxo_map = {prevout: utxo for prevout, utxo in zip(prevouts, utxos)}
|
||||||
|
|
||||||
return self._accept_transactions(tx_map, utxo_map, touched)
|
return self._accept_transactions(tx_map, utxo_map, touched)
|
||||||
@ -271,7 +315,8 @@ class MemPool(object):
|
|||||||
'''Return a set of (prev_hash, prev_idx) pairs from mempool
|
'''Return a set of (prev_hash, prev_idx) pairs from mempool
|
||||||
transactions that touch hashX.
|
transactions that touch hashX.
|
||||||
|
|
||||||
None, some or all of these may be spends of the hashX.
|
None, some or all of these may be spends of the hashX, but all
|
||||||
|
actual spends of it (in the DB or mempool) will be included.
|
||||||
'''
|
'''
|
||||||
result = set()
|
result = set()
|
||||||
for tx_hash in self.hashXs.get(hashX, ()):
|
for tx_hash in self.hashXs.get(hashX, ()):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user