Ejemplo n.º 1
0
class FetchAIApi(LedgerApi, FetchAIHelper):
    """Class to interact with the Fetch ledger APIs."""

    identifier = _FETCHAI

    def __init__(self, **kwargs):
        """
        Initialize the Fetch.AI ledger APIs.

        :param kwargs: key word arguments (expects either a pair of 'host' and 'port' or a 'network')
        """
        if not ("host" in kwargs and "port" in kwargs):
            network = kwargs.pop("network", DEFAULT_NETWORK)
            kwargs["network"] = network
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            self._api = FetchaiLedgerApi(**kwargs)

    @property
    def api(self) -> FetchaiLedgerApi:
        """Get the underlying API object."""
        return self._api

    def get_balance(self, address: Address) -> Optional[int]:
        """
        Get the balance of a given account.

        :param address: the address for which to retrieve the balance.
        :return: the balance, if retrivable, otherwise None
        """
        balance = self._try_get_balance(address)
        return balance

    @try_decorator("Unable to retrieve balance: {}", logger_method="debug")
    def _try_get_balance(self, address: Address) -> Optional[int]:
        """Try get the balance."""
        return self._api.tokens.balance(FetchaiAddress(address))

    def get_transfer_transaction(  # pylint: disable=arguments-differ
        self,
        sender_address: Address,
        destination_address: Address,
        amount: int,
        tx_fee: int,
        tx_nonce: str,
        **kwargs,
    ) -> Optional[Any]:
        """
        Submit a transfer transaction to the ledger.

        :param sender_address: the sender address of the payer.
        :param destination_address: the destination address of the payee.
        :param amount: the amount of wealth to be transferred.
        :param tx_fee: the transaction fee.
        :param tx_nonce: verifies the authenticity of the tx
        :return: the transfer transaction
        """
        tx = TokenTxFactory.transfer(
            FetchaiAddress(sender_address),
            FetchaiAddress(destination_address),
            amount,
            tx_fee,
            [],  # we don't add signer here as we would need the public key for this
        )
        self._api.set_validity_period(tx)
        return tx

    def send_signed_transaction(self, tx_signed: Any) -> Optional[str]:
        """
        Send a signed transaction and wait for confirmation.

        :param tx_signed: the signed transaction
        """
        encoded_tx = transaction.encode_transaction(tx_signed)
        endpoint = "transfer" if tx_signed.transfers is not None else "create"
        return self.api.tokens._post_tx_json(  # pylint: disable=protected-access
            encoded_tx, endpoint)

    def get_transaction_receipt(self, tx_digest: str) -> Optional[Any]:
        """
        Get the transaction receipt for a transaction digest (non-blocking).

        :param tx_digest: the digest associated to the transaction.
        :return: the tx receipt, if present
        """
        tx_receipt = self._try_get_transaction_receipt(tx_digest)
        return tx_receipt

    @try_decorator("Error when attempting getting tx receipt: {}",
                   logger_method="debug")
    def _try_get_transaction_receipt(self, tx_digest: str) -> Optional[Any]:
        """
        Get the transaction receipt (non-blocking).

        :param tx_digest: the transaction digest.
        :return: the transaction receipt, if found
        """
        return self._api.tx.status(tx_digest)

    def get_transaction(self, tx_digest: str) -> Optional[Any]:
        """
        Get the transaction for a transaction digest.

        :param tx_digest: the digest associated to the transaction.
        :return: the tx, if present
        """
        tx = self._try_get_transaction(tx_digest)
        return tx

    @try_decorator("Error when attempting getting tx: {}",
                   logger_method="debug")
    def _try_get_transaction(self, tx_digest: str) -> Optional[TxContents]:
        """
        Try get the transaction (non-blocking).

        :param tx_digest: the transaction digest.
        :return: the tx, if found
        """
        return cast(TxContents, self._api.tx.contents(tx_digest))

    def get_contract_instance(self,
                              contract_interface: Dict[str, str],
                              contract_address: Optional[str] = None) -> Any:
        """
        Get the instance of a contract.

        :param contract_interface: the contract interface.
        :param contract_address: the contract address.
        :return: the contract instance
        """
        raise NotImplementedError

    def get_deploy_transaction(
        self,
        contract_interface: Dict[str, str],
        deployer_address: Address,
        **kwargs,
    ) -> Dict[str, Any]:
        """
        Get the transaction to deploy the smart contract.

        :param contract_interface: the contract interface.
        :param deployer_address: The address that will deploy the contract.
        :returns tx: the transaction dictionary.
        """
        raise NotImplementedError
Ejemplo n.º 2
0
def main():
    # create the APIs
    api = LedgerApi(HOST, PORT)

    # we generate an identity from a known key, which contains funds.
    multi_sig_identity = Entity.from_hex(
        "6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b")

    # generate a board to control multi-sig account, with variable voting weights
    board = [
        Entity.from_hex(
            "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8"
        ),
        Entity.from_hex(
            "4083a476c4872f25cb40839ac8d994924bcef12d83e2ba4bd3ed6c9705959860"
        ),
        Entity.from_hex(
            "20293422c4b5faefba3422ed436427f2d37f310673681e98ac8637b04e756de3"
        ),
        Entity.from_hex(
            "d5f10ad865fff147ae7fcfdc98b755452a27a345975c8b9b3433ff16f23495fb"
        ),
    ]

    voting_weights = {
        board[0]: 1,
        board[1]: 1,
        board[2]: 1,
        board[3]: 2,
    }

    # generate another entity as a target for transfers
    other_identity = Entity.from_hex(
        "7da0e3fa62a916238decd4f54d43301c809595d66dd469f82f29e076752b155c")

    # submit deed
    print("\nCreating deed...")
    deed = Deed()
    for sig, weight in voting_weights.items():
        deed.set_signee(sig, weight)
    deed.set_operation(Operation.amend, 4)
    deed.set_operation(Operation.transfer, 3)

    api.sync(api.tokens.deed(multi_sig_identity, deed, 500))

    # display balance before
    print("\nBefore remote-multisig transfer")
    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))
    print()

    # scatter/gather example
    print("Generating transaction and distributing to signers...")

    # add intended signers to transaction
    ref_tx = TokenTxFactory.transfer(multi_sig_identity,
                                     other_identity,
                                     250,
                                     20,
                                     signatories=board)
    api.set_validity_period(ref_tx)

    # make a reference payload that can be used in this script for validation
    reference_payload = ref_tx.encode_payload()

    # have signers individually sign transaction
    signed_txs = []
    for signer in board:
        # signer builds their own transaction to compare to note that each of the signers will need to agree on all
        # parts of the message including the validity period and the counter
        signer_tx = TokenTxFactory.transfer(multi_sig_identity,
                                            other_identity,
                                            250,
                                            20,
                                            signatories=board)
        signer_tx.counter = ref_tx.counter
        signer_tx.valid_until = ref_tx.valid_until
        signer_tx.valid_from = ref_tx.valid_from

        # sanity check each of the signers payload should match the reference payload
        assert signer_tx.encode_payload() == reference_payload

        # signers locally sign there version of the transaction
        signer_tx.sign(signer)

        # simulate distribution of signed partial transactions
        signed_txs.append(signer_tx.encode_partial())

    # gather and encode final transaction - this step in theory can be done by all the signers provided they are
    # received all the signature shares
    print("Gathering and combining signed transactions...")
    partial_txs = [Transaction.decode_partial(s)[1] for s in signed_txs]

    # merge them together into one fully signed transaction
    success, tx = Transaction.merge(partial_txs)
    assert success  # this indicates that all the signatures have been merged and that the transaction now validates

    # submit the transaction
    api.sync(api.submit_signed_tx(tx))

    print("\nAfter remote multisig-transfer")
    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # round-robin example
    print("\nGenerating transaction and sending down the line of signers...")

    # create the basis for the transaction
    tx = TokenTxFactory.transfer(multi_sig_identity,
                                 other_identity,
                                 250,
                                 20,
                                 signatories=board)
    api.set_validity_period(tx)

    # serialize and send to be signed
    tx_payload = tx.encode_payload()

    # have signers individually sign transaction and pass on to next signer
    for signer in board:
        # build the target transaction
        signer_tx = Transaction.decode_payload(tx_payload)

        # Signer decodes payload to inspect transaction
        signer_tx.sign(signer)

        # ensure that when we merge the signers signature into the payload that it is correct
        assert tx.merge_signatures(signer_tx)

    # once all the partial signatures have been merged then it makes sense
    print("Collecting final signed transaction...")
    assert tx.is_valid()
    api.sync(api.submit_signed_tx(tx))

    print("\nAfter remote multisig-transfer")
    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))