def setUp(self) -> None: self.source_identity = Entity() self.multi_sig_identity = Entity() self.multi_sig_board = [Entity() for _ in range(4)] self.target_identity = Entity() self.tx = TokenTxFactory.transfer(self.source_identity, Identity(self.target_identity), 500, 500, [self.source_identity]) self.mstx = TokenTxFactory.transfer(self.multi_sig_identity, Identity(self.target_identity), 500, 500, self.multi_sig_board)
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 test_handle_tx_signing_fetchai(self): """Test tx signing for fetchai.""" tx = TokenTxFactory.transfer( FetchaiAddress("v3sZs7gKKz9xmoTo9yzRkfHkjYuX42MzXaq4eVjGHxrX9qu3U"), FetchaiAddress("2bzQNV4TTjMAiKZe85EyLUttoFpHHuksRzUUBYB1brt98pMXKK"), 1, 1, [], ) signing_dialogues = SigningDialogues("agent1") signing_msg = SigningMessage( performative=SigningMessage.Performative.SIGN_TRANSACTION, dialogue_reference=signing_dialogues.new_self_initiated_dialogue_reference(), skill_callback_ids=(str(PublicId("author", "a_skill", "0.1.0")),), skill_callback_info={}, terms=Terms( ledger_id=FETCHAI, sender_address="pk1", counterparty_address="pk2", amount_by_currency_id={"FET": -1}, is_sender_payable_tx_fee=True, quantities_by_good_id={"good_id": 10}, nonce="transaction nonce", ), raw_transaction=RawTransaction(FETCHAI, tx), ) signing_msg.counterparty = "decision_maker" self.decision_maker.message_in_queue.put_nowait(signing_msg) signing_msg_response = self.decision_maker.message_out_queue.get(timeout=2) assert ( signing_msg_response.performative == SigningMessage.Performative.SIGNED_TRANSACTION ) assert signing_msg_response.skill_callback_ids == signing_msg.skill_callback_ids assert type(signing_msg_response.signed_transaction.body) == FetchaiTransaction
def run_transfer(args): from getpass import getpass from fetchai.ledger.crypto import Address from fetchai.ledger.api.token import TokenTxFactory from pocketbook.address_book import AddressBook from pocketbook.key_store import KeyStore from pocketbook.utils import create_api, from_canonical, token_amount address_book = AddressBook() key_store = KeyStore() # choose the destination destination_name = '{}:'.format(args.destination) if args.destination in address_book.keys(): destination = address_book.lookup_address(args.destination) else: destination = key_store.lookup_address(args.destination) if destination is None: destination = Address(args.destination) destination_name = '' # convert the amount amount = args.amount charge_rate = args.charge_rate computed_amount = from_canonical(amount) # check all the signers make sense for signer in args.signers: if signer not in key_store.list_keys(): raise RuntimeError('Unknown key: {}'.format(signer)) # determine the from account from_address_name = None if len(args.signers) == 1 and args.from_address is None: from_address_name = args.signers[0] elif len(args.signers) >= 1 and args.from_address is not None: present = args.from_address in key_store.list_keys() or args.from_address in address_book.keys() from_address_name = args.from_address if not present: raise RuntimeError('Unknown from address: {}'.format(args.from_address)) else: raise RuntimeError('Unable to determine from account') required_ops = len(args.signers) fee = required_ops * charge_rate computed_fee = from_canonical(fee) computed_total = computed_amount + computed_fee computed_charge_rate = from_canonical(charge_rate) print('Network....:', args.network) print('From.......:', str(from_address_name)) print('Signer(s)..:', ','.join(args.signers)) print('Destination:', destination_name, str(destination)) print('Amount.....:', token_amount(computed_amount)) print('Fee........:', token_amount(computed_fee)) # only display extended fee information if something other than the default it selected if charge_rate != 1: print(' : {} ops @ {}'.format(required_ops, token_amount(computed_charge_rate))) print('Total......:', token_amount(computed_total), '(Amount + Fee)') print() input('Press enter to continue') api = create_api(args.network) # start unsealing the private keys entities = {} for signer in args.signers: entity = key_store.load_key(signer, getpass('Enter password for key {}: '.format(signer))) entities[signer] = entity from_address = None if from_address_name in entities: from_address = Address(entities[from_address_name]) elif from_address_name in address_book.keys(): from_address = Address(address_book.lookup_address(from_address_name)) # cache the signers signers = list(entities.values()) # build up the basic transaction information tx = TokenTxFactory.transfer(Address(from_address), destination, amount, 0, signers) tx.charge_rate = charge_rate tx.charge_limit = required_ops api.set_validity_period(tx) for entity in signers: tx.sign(entity) tx_digest = api.submit_signed_tx(tx) print('TX: 0x{} submitted'.format(tx_digest)) # submit the transaction print('Waiting for transaction to be confirmed...') api.sync(tx_digest) print('Waiting for transaction to be confirmed...complete') # determine if there is a block explorer link to be printed explorer_link = None if args.network == 'mainnet': explorer_link = 'https://explore.fetch.ai/transactions/0x{}'.format(tx_digest) elif args.network == 'testnet': explorer_link = 'https://explore-testnet.fetch.ai/transactions/0x{}'.format(tx_digest) if explorer_link is not None: print() print('See {} for more details'.format(explorer_link))
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))
def test_compare(self): tx = TokenTxFactory.transfer(self.source_identity, Identity(self.target_identity), 500, 500, [self.source_identity]) # encode the transaction so that copies can be made afterwards encoded_tx = tx.encode_partial() # Test comparison fails if any data changed _, tx2 = Transaction.decode_partial(encoded_tx) tx2.from_address = Entity() self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2.add_transfer(Entity(), 500) self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2.valid_from = 999 self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2.valid_until = 999 self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2.charge_rate = 999 self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2.charge_limit = 999 self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2._contract_address = Address(Entity()) self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2._chain_code = 'changed' self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2._action = 'changed' self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2._shard_mask = BitVector(size=16) self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2.data = b'changed' self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2.add_signer(Entity()) self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2._counter = 999 self.assertNotEqual(tx, tx2) _, tx2 = Transaction.decode_partial(encoded_tx) tx2._is_synergetic = True self.assertNotEqual(tx, tx2)
def main(): # create the APIs api = LedgerApi(HOST, PORT) # 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. # we use keys for accounts which already have funds. board = [] board.append( Entity.from_hex( "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8") ) board.append( Entity.from_hex( "4083a476c4872f25cb40839ac8d994924bcef12d83e2ba4bd3ed6c9705959860") ) board.append( Entity.from_hex( "20293422c4b5faefba3422ed436427f2d37f310673681e98ac8637b04e756de3") ) board.append( 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( "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8") print('Original balance of multi_sig_identity:', api.tokens.balance(multi_sig_identity)) # transfers can happen normally without a deed print('\nSubmitting pre-deed transfer with original signature...') api.sync(api.tokens.transfer(multi_sig_identity, other_identity, 250, 20)) print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # submit the original deed print("\nCreating deed...") deed = Deed() for sig, weight in voting_weights.items(): deed.set_signee(sig, weight) # set our initial voting thresholds deed.set_operation(Operation.transfer, 2) deed.set_operation(Operation.amend, 4) api.sync(api.tokens.deed(multi_sig_identity, deed, 6000)) # original address can no longer validate transfers print("\nTransfer with original signature should fail...") try: api.sync( api.tokens.transfer(multi_sig_identity, other_identity, 250, 20)) except RuntimeError as e: print("Transaction failed as expected") else: print("Transaction succeeded, it shouldn't have") # sufficient voting power required to sign transfers print("\nSubmitting transfer with two signatures with total 2 votes...") print_signing_votes(voting_weights, board[:2]) # since we now want to create a transaction which has only been signed by a subset of the board, we must use # the factory interface in order to build out the transaction we are after tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, board[:2]) tx.valid_until = api.tokens.current_block_number() + 100 # the signatories to sign the transaction for signatory in board[:2]: tx.sign(signatory) api.sync(api.submit_signed_tx(tx)) print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # some entities may have more voting power print("\nSubmitting transfer with single signature with 2 votes...") print_signing_votes(voting_weights, board[3]) tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, [board[3]]) tx.valid_until = api.tokens.current_block_number() + 100 tx.sign(board[3]) api.sync(api.submit_signed_tx(tx)) print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # amend the deed print("\nAmending deed to increase transfer threshold to 3 votes...") deed.set_operation(Operation.transfer, 3) tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board) tx.valid_until = api.tokens.current_block_number() + 100 for member in board: tx.sign(member) api.sync(api.submit_signed_tx(tx)) # single member no longer has enough voting power print("\nSingle member transfer with 2 votes should no longer succeed...") try: print_signing_votes(voting_weights, board[3]) tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, [board[3]]) tx.valid_until = api.tokens.current_block_number() + 100 tx.sign(board[3]) api.sync(api.submit_signed_tx(tx)) except RuntimeError as e: print("Transaction failed as expected") else: print("Transaction succeeded, it shouldn't have") # correct number of signatory votes print("\nSuccesful transaction with sufficient voting weight...") print_signing_votes(voting_weights, board[1:]) tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, board[1:]) tx.valid_until = api.tokens.current_block_number() + 100 for member in board[1:]: tx.sign(member) api.sync(api.submit_signed_tx(tx)) print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # warning: if no amend threshold is set, future amendments are impossible print("\nAmending deed to remove threshold...") deed.remove_operation(Operation.amend) deed.require_amend = False tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board) tx.valid_until = api.tokens.current_block_number() + 100 for member in board: tx.sign(member) api.sync(api.submit_signed_tx(tx)) deed.set_operation(Operation.amend, 1) print("\nExpecting further amendment to fail...") try: tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board) tx.valid_until = api.tokens.current_block_number() + 100 for member in board: tx.sign(member) api.sync(api.submit_signed_tx(tx)) except RuntimeError as e: print("Transaction failed as expected") else: print("Transaction succeeded, it shouldn't have")