Source code for graphenecommon.aio.chain

# -*- coding: utf-8 -*-
import logging

from ..chain import AbstractGrapheneChain as SyncAbstractGrapheneChain

log = logging.getLogger(__name__)


class AbstractGrapheneChain(SyncAbstractGrapheneChain):
    def __init__(self, *args, **kwargs):
        # Initialize parent without connecting and wallet init to avoid calling async methods
        super().__init__(skip_wallet_init=True, offline=True, *args, **kwargs)

        # Remember kwargs to use them to connect()
        self._kwargs = kwargs

        self.notifications = None

    async def connect(self):
        """ Connect to blockchain network

            Async version does wallet initialization after connect because
            wallet depends on prefix which is available after connection only,
            and we want to keep __init__() synchronous.
        """
        node = self._kwargs.get("node", None)
        rpcuser = self._kwargs.get("rpcuser", None)
        rpcpassword = self._kwargs.get("rpcpassword", None)

        if not node:
            if "node" in self.config:
                node = self.config["node"]
            else:
                raise ValueError("A Blockchain node needs to be provided!")

        if not rpcuser and "rpcuser" in self.config:
            rpcuser = self.config["rpcuser"]

        if not rpcpassword and "rpcpassword" in self.config:
            rpcpassword = self.config["rpcpassword"]

        self.rpc = self.rpc_class(node, rpcuser, rpcpassword, **self._kwargs)
        await self.rpc.connect()

        self.wallet = self._kwargs.get(
            "wallet", self.wallet_class(blockchain_instance=self, **self._kwargs)
        )

        # Make a shortcut for notifications queue
        self.notifications = self.rpc.connection.notifications

    async def info(self):
        """ Returns the global properties
        """
        return await self.rpc.get_dynamic_global_properties()

    async def finalizeOp(self, ops, account, permission, **kwargs):
        """ This method obtains the required private keys if present in
            the wallet, finalizes the transaction, signs it and
            broadacasts it

            :param operation ops: The operation (or list of operaions) to
                broadcast
            :param operation account: The account that authorizes the
                operation
            :param string permission: The required permission for
                signing (active, owner, posting)
            :param object append_to: This allows to provide an instance of
                ProposalsBuilder (see :func:`new_proposal`) or
                TransactionBuilder (see :func:`new_tx()`) to specify
                where to put a specific operation.

            ... note:: ``append_to`` is exposed to every method used in the
                this class

            ... note::

                If ``ops`` is a list of operation, they all need to be
                signable by the same key! Thus, you cannot combine ops
                that require active permission with ops that require
                posting permission. Neither can you use different
                accounts for different operations!

            ... note:: This uses ``txbuffer`` as instance of
                :class:`transactionbuilder.TransactionBuilder`.
                You may want to use your own txbuffer
        """
        if "append_to" in kwargs and kwargs["append_to"]:
            if self.proposer:
                log.warning(
                    "You may not use append_to and self.proposer at "
                    "the same time. Append new_proposal(..) instead"
                )
            # Append to the append_to and return
            append_to = kwargs["append_to"]
            parent = append_to.get_parent()
            assert isinstance(
                append_to, (self.transactionbuilder_class, self.proposalbuilder_class)
            )
            append_to.appendOps(ops)
            # Add the signer to the buffer so we sign the tx properly
            if isinstance(append_to, self.proposalbuilder_class):
                await parent.appendSigner(append_to.proposer, permission)
            else:
                await parent.appendSigner(account, permission)
            # This returns as we used append_to, it does NOT broadcast, or sign
            return append_to.get_parent()
        elif self.proposer:
            # Legacy proposer mode!
            proposal = self.proposal()
            proposal.set_proposer(self.proposer)
            proposal.set_expiration(self.proposal_expiration)
            proposal.set_review(self.proposal_review)
            proposal.appendOps(ops)
            # Go forward to see what the other options do ...
        else:
            # Append tot he default buffer
            self.txbuffer.appendOps(ops)

        # The API that obtains the fee only allows to specify one particular
        # fee asset for all operations in that transaction even though the
        # blockchain itself could allow to pay multiple operations with
        # different fee assets.
        if "fee_asset" in kwargs and kwargs["fee_asset"]:
            self.txbuffer.set_fee_asset(kwargs["fee_asset"])

        # Add signing information, signer, sign and optionally broadcast
        if self.unsigned:
            # In case we don't want to sign anything
            self.txbuffer.addSigningInformation(account, permission)
            return self.txbuffer
        elif self.bundle:
            # In case we want to add more ops to the tx (bundle)
            self.txbuffer.appendSigner(account, permission)
            return self.txbuffer.json()
        else:
            # default behavior: sign + broadcast
            await self.txbuffer.appendSigner(account, permission)
            await self.txbuffer.sign()
            return await self.txbuffer.broadcast()

    async def sign(self, tx=None, wifs=[]):
        """ Sign a provided transaction witht he provided key(s)

            :param dict tx: The transaction to be signed and returned
            :param string wifs: One or many wif keys to use for signing
                a transaction. If not present, the keys will be loaded
                from the wallet as defined in "missing_signatures" key
                of the transactions.
        """
        if tx:
            txbuffer = self.transactionbuilder_class(tx, blockchain_instance=self)
        else:
            txbuffer = self.txbuffer
        txbuffer.appendWif(wifs)
        txbuffer.appendMissingSignatures()
        await txbuffer.sign()
        return await txbuffer.json()

    async def broadcast(self, tx=None):
        """ Broadcast a transaction to the Blockchain

            :param tx tx: Signed transaction to broadcast
        """
        if tx:
            # If tx is provided, we broadcast the tx
            return await self.transactionbuilder_class(
                tx, blockchain_instance=self
            ).broadcast()
        else:
            return await self.txbuffer.broadcast()