chainqueue

Blockchain transaction queue control
Log | Files | Refs | LICENSE

commit 1dd403b4a2839ca3a1647a19e2665689fdd96b01
parent 88098e356b8ce256d53516dd7defe8863cd4e2f4
Author: nolash <dev@holbrook.no>
Date:   Fri, 27 Aug 2021 10:52:05 +0200

WIP docstrings

Diffstat:
Mchainqueue/sql/backend.py | 107++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mchainqueue/sql/query.py | 25+++++++++++++------------
Mchainqueue/sql/state.py | 2+-
Mchainqueue/sql/tx.py | 1+
Mchainqueue/unittest/db.py | 9+++++++++
Msetup.cfg | 2+-
Mtests/test_basic.py | 1+
7 files changed, 129 insertions(+), 18 deletions(-)

diff --git a/chainqueue/sql/backend.py b/chainqueue/sql/backend.py @@ -38,15 +38,50 @@ logg = logging.getLogger(__name__) class SQLBackend: - - def __init__(self, conn_spec, error_parser=None, *args, **kwargs): - SessionBase.connect(conn_spec, pool_size=kwargs.get('poolsize', 0), debug=kwargs.get('debug', False)) + """SQL flavor of the chainqueue backend implementation. + + :param conn_spec: Backend-dependent connection specification string. See chainqueue.db.models.base.SessionBase.connect + :type conn_spec: str + :param error_parser: Error parser to use for RPC calls within the backend component + :type error_parser: Object implementing chainlib.error.DefaultErrorParser + :param pool_size: Connection pool size for pool-capable sql engines. See chainqueue.db.models.base.SessionBase.connect + :type pool_size: int + :param debug: Activate SQL engine level debug. See chainqueue.db.models.base.SessionBase.connect + :type debug: bool + :todo: define a backend abstract interface class + """ + + #def __init__(self, conn_spec, error_parser=None, *args, **kwargs): + def __init__(self, conn_spec, error_parser=None, pool_size=0, debug=False, *args, **kwargs): + #SessionBase.connect(conn_spec, pool_size=kwargs.get('poolsize', 0), debug=kwargs.get('debug', False)) + SessionBase.connect(conn_spec, pool_size=pool_size, debug=debug) if error_parser == None: error_parser = DefaultErrorParser() self.error_parser = error_parser def create(self, chain_spec, nonce, holder_address, tx_hash, signed_tx, obsolete_predecessors=True, session=None): + """Create a new transaction record in backend. + + The nonce field is provided as a convenience to avoid needless resources spent on decoding the transaction data to retrieve it. However, this means that no check will be performed to verify that nonce matches the nonce encoded in the transaction, and thus it is the caller's responsibility to ensure that it is correct. + + :param chain_spec: Chain spec to add record for + :type chain_spec: chainlib.chain.ChainSpec + :param nonce: Transaction nonce + :type nonce: int + :param holder_address: Address of transaction sender + :type holder_address: str + :param tx_hash: Transaction hash + :type tx_hash: str + :param signed_tx: Signed transaction data + :type signed_tx: str + :param obsolete_predecessors: If set, will mark older transactions with same nonce from holder_address as obsolete + :type obsolete_predecessors: bool + :param session: Sqlalchemy database session + :type session: sqlalchemy.orm.Session + :rtype: int + :returns: 0 if successfully added + """ try: queue_create(chain_spec, nonce, holder_address, tx_hash, signed_tx, obsolete_predecessors=True, session=session) except IntegrityError as e: @@ -57,16 +92,61 @@ class SQLBackend: def cache(self, tx, session=None): + """Create a new cache record for existing outgoing transaction in backend. + + :param tx: Transaction dict representation + :type tx: dict + :param session: Sqlalchemy database session + :type session: sqlalchemy.orm.Session + :rtype: int + :returns: 0 if successful + """ (tx, txc_id) = cache_tx_dict(tx, session=session) logg.debug('cached {} db insert id {}'.format(tx, txc_id)) return 0 - def get_tx(self, chain_spec, tx_hash, session=None): + def get_otx(self, chain_spec, tx_hash, session=None): + """Retrieve a single otx summary dictionary by transaction hash. + + Alias of chainqueue.sql.query.get_tx + + :param chain_spec: Chain spec context to look up transaction with + :type chain_spec: chainlib.chain.ChainSpec + :param tx_hash: Transaction hash + :type tx_hash: str + :param session: Sqlalchemy database session + :type session: sqlalchemy.orm.Session + :rtype: dict + :returns: otx record summary + """ return backend_get_tx(chain_spec, tx_hash, session=session) def get(self, chain_spec, decoder, session=None, requeue=False, *args, **kwargs): + """Gets transaction lists based on given criteria. + + Calls chainqueue.sql.query.get_upcoming_tx. If requeue is True, the QUEUED status bit will be set on all matched transactions. + + :param chain_spec: Chain spec context to look up transactions for + :type chain_spec: chainlib.chain.ChainSpec + :param decoder: Decoder instance to parse values from serialized transaction data in record + :type decoder: Function taking serialized tx as parameter + :param session: Sqlalchemy database session + :type session: sqlalchemy.orm.Session + :param status: Only match transaction that have the given bits set + :type status: int + :param not_status: Only match transactions that have none of the given bits set + :type not_status: int + :param recipient: Only match transactions that has the given address as recipient + :type recipient: str + :param before: Only match tranaactions that were last checked before the given time + :type before: datetime.datetime + :param limit: Return at most given number of transaction. If 0, will return all matched transactions. + :type limit: int + :rtype: dict + :returns: key value pairs of transaction hash and signed transaction data for all matching transactions + """ txs = get_upcoming_tx(chain_spec, status=kwargs.get('status'), decoder=decoder, not_status=kwargs.get('not_status', 0), recipient=kwargs.get('recipient'), before=kwargs.get('before'), limit=kwargs.get('limit', 0)) if requeue: for tx_hash in txs.keys(): @@ -75,6 +155,21 @@ class SQLBackend: def dispatch(self, chain_spec, rpc, tx_hash, payload, session=None): + """Send a single queued transaction. + + :param chain_spec: Chain spec context for network send + :type chain_spec: chainlib.chain.ChainSpec + :param rpc: RPC connection to use for send + :type rpc: chainlib.connection.RPCConnection + :param tx_hash: Transaction hash of transaction to send + :type tx_hash: str + :param payload: Prepared RPC query to send + :type payload: any + :param session: Sqlalchemy database session + :type session: sqlalchemy.orm.Session + :rtype: int + :returns: 0 if no error + """ set_reserved(chain_spec, tx_hash, session=session) fail = False r = 1 @@ -102,8 +197,12 @@ class SQLBackend: def create_session(self, session=None): + """Alias for chainqueue.db.models.base.SessionBase.bind_session + """ return SessionBase.bind_session(session=session) def release_session(self, session): + """Alias for chainqueue.db.models.base.SessionBase.release_session + """ return SessionBase.release_session(session=session) diff --git a/chainqueue/sql/query.py b/chainqueue/sql/query.py @@ -1,3 +1,5 @@ +# TODO: this module could require better naming to discern what type of data the different methods return. Currently it's a mix of otx summary, otx objects, tx cache objects, tx representation dicts and hash/signedtx kv pairs + # standard imports import logging import time @@ -127,6 +129,7 @@ def get_nonce_tx_cache(chain_spec, nonce, sender, decoder=None, session=None): :type decoder: TODO - define transaction decoder :param session: Backend state integrity session :type session: varies + :raises CacheIntegrityError: Cached data does not match intepreted data. :returns: Transactions :rtype: dict, with transaction hash as key, signed raw transaction as value """ @@ -160,17 +163,15 @@ def get_paused_tx_cache(chain_spec, status=None, sender=None, session=None, deco :type chain_spec: chainlib.chain.ChainSpec :param status: If set, will return transactions with this local queue status only :type status: cic_eth.db.enum.StatusEnum - :param recipient: Recipient address to return transactions for - :type recipient: str, 0x-hex - :param chain_id: Numeric chain id to use to parse signed transaction data - :type chain_id: number - :param decoder: Transaction decoder - :type decoder: TODO - define transaction decoder + :param sender: Sender address to return transactions for + :type sender: str :param session: Backend state integrity session :type session: varies + :param decoder: Transaction decoder + :type decoder: Function accepting signed transaction data as input :raises ValueError: Status is finalized, sent or never attempted sent - :returns: Transactions - :rtype: dict, with transaction hash as key, signed raw transaction as value + :returns: Key value pairs with transaction hash and signed raw transaction + :rtype: dict """ session = SessionBase.bind_session(session) q = session.query(Otx) @@ -465,13 +466,13 @@ def get_account_tx(chain_spec, address, as_sender=True, as_recipient=True, count return txs -def count_tx(chain_spec, address=None, status=None, status_target=None, session=None): - """ +def count_tx(chain_spec, sender=None, status=None, status_target=None, session=None): + """Count transaction records matching the given criteria. :param chain_spec: Chain spec for transaction network :type chain_spec: chainlib.chain.ChainSpec - :param address: Address to count transactions for - :type address: str + :param sender: Sender address to count transactions for + :type sender: str :param status: Status to count transactions for :type status: chainqueue.enum.StatusEnum :param status_target: If set, will match status argument exactly against the given value diff --git a/chainqueue/sql/state.py b/chainqueue/sql/state.py @@ -379,7 +379,7 @@ def obsolete_by_cache(chain_spec, tx_hash, final, session=None): :param tx_hash: Transaction hash of record to modify, in hex :type tx_hash: str :param final: Transaction hash superseding record, in hex - :type tx_hash: str + :type final: str :param session: Backend state integrity session :type session: varies :raises TxStateChangeError: Transaction is not obsoletable diff --git a/chainqueue/sql/tx.py b/chainqueue/sql/tx.py @@ -38,6 +38,7 @@ def create(chain_spec, nonce, holder_address, tx_hash, signed_tx, obsolete_prede :type obsolete_predecessors: bool :param session: Backend state integrity session :type session: varies + :raises TxStateChangeError: Transaction obsoletion failed :returns: transaction hash, in hex :rtype: str """ diff --git a/chainqueue/unittest/db.py b/chainqueue/unittest/db.py @@ -21,6 +21,11 @@ db_config = { class ChainQueueDb: + """SQLITE database setup for unit tests + + :param debug: Activate sql level debug (outputs sql statements) + :type debug: bool + """ base = SessionBase @@ -48,8 +53,12 @@ class ChainQueueDb: def bind_session(self, session=None): + """Create session using underlying session base + """ return self.base.bind_session(session) def release_session(self, session=None): + """Release session using underlying session base + """ return self.base.release_session(session) diff --git a/setup.cfg b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = chainqueue -version = 0.0.4a6 +version = 0.0.4a7 description = Generic blockchain transaction queue control author = Louis Holbrook author_email = dev@holbrook.no diff --git a/tests/test_basic.py b/tests/test_basic.py @@ -39,6 +39,7 @@ class TestBasic(TestBase): tx = add_0x(os.urandom(128).hex()) nonce = 42 otx = Otx(nonce, tx_hash, tx) + otx.block = 1024 self.session.add(otx) alice = add_0x(os.urandom(20).hex())