def _handle_tx_message_for_signing(self, tx_message: TransactionMessage) -> None: """ Handle a transaction message for signing. :param tx_message: the transaction message :return: None """ if self._is_acceptable_for_signing(tx_message): if self._is_valid_message(tx_message): tx_signature = self._sign_tx_hash(tx_message) tx_message_response = TransactionMessage.respond_signing( tx_message, performative=TransactionMessage.Performative.SUCCESSFUL_SIGNING, signed_payload={"tx_signature": tx_signature}, ) if self._is_valid_tx(tx_message): tx_signed = self._sign_ledger_tx(tx_message) tx_message_response = TransactionMessage.respond_signing( tx_message, performative=TransactionMessage.Performative.SUCCESSFUL_SIGNING, signed_payload={"tx_signed": tx_signed}, ) else: tx_message_response = TransactionMessage.respond_signing( tx_message, performative=TransactionMessage.Performative.REJECTED_SIGNING, ) self.message_out_queue.put(tx_message_response)
def _handle_tx_message(self, tx_message: TransactionMessage) -> None: """ Handle a transaction message. :param tx_message: the transaction message :return: None """ # if not self.goal_pursuit_readiness.is_ready: # logger.warning("[{}]: Preferences and ownership state not initialized. Refusing to process transaction!".format(self._agent_name)) # return # TODO: reintroduce above check # check if the transaction is acceptable and process it accordingly if self._is_acceptable_tx(tx_message): tx_digest = self._settle_tx(tx_message) if tx_digest is not None: tx_message_response = TransactionMessage.respond_with( tx_message, performative=TransactionMessage.Performative.ACCEPT, transaction_digest=tx_digest) else: tx_message_response = TransactionMessage.respond_with( tx_message, performative=TransactionMessage.Performative.REJECT) else: tx_message_response = TransactionMessage.respond_with( tx_message, performative=TransactionMessage.Performative.REJECT) self.message_out_queue.put(tx_message_response)
def _is_acceptable_tx(self, tx_message: TransactionMessage) -> bool: """ Check if the tx is acceptable. :param tx_message: the transaction message :return: whether the transaction is acceptable or not """ if tx_message.get("ledger_id") is not None: amount = cast(int, tx_message.get("amount")) counterparty_tx_fee = cast(int, tx_message.get("counterparty_tx_fee")) sender_tx_fee = cast(int, tx_message.get("sender_tx_fee")) # adjust payment amount to reflect transaction fee split amount -= counterparty_tx_fee tx_fee = counterparty_tx_fee + sender_tx_fee payable = amount + tx_fee is_correct_format = isinstance(payable, int) crypto_object = self._wallet.crypto_objects.get( tx_message.get("ledger_id")) balance = self.ledger_apis.token_balance( crypto_object.identifier, cast(str, crypto_object.address)) is_affordable = payable <= balance # TODO check against preferences and other constraints is_acceptable = is_correct_format and is_affordable else: is_acceptable = self.preferences.get_score_diff_from_transaction( self.ownership_state, tx_message) >= 0.0 return is_acceptable
def test_respond_message(self): tx_hash = Web3.keccak(text="some_bytes") tx_signature = Web3.keccak(text="tx_signature") tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE_FOR_SIGNING, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id=self.tx_id, tx_sender_addr=self.tx_sender_addr, tx_counterparty_addr=self.tx_counterparty_addr, tx_amount_by_currency_id={"FET": -20}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 0}, ledger_id=self.ledger_id, info=self.info, signing_payload={"tx_hash": tx_hash}, ) tx_message_response = TransactionMessage.respond_signing( tx_message, performative=TransactionMessage.Performative.SUCCESSFUL_SIGNING, signed_payload={"tx_signature": tx_signature}, ) assert tx_message_response.signed_payload.get( "tx_signature") == tx_signature
def _handle_tx_message_for_settlement(self, tx_message) -> None: """ Handle a transaction message for settlement. :param tx_message: the transaction message :return: None """ if self._is_acceptable_for_settlement(tx_message): tx_digest = self._settle_tx(tx_message) if tx_digest is not None: tx_message_response = TransactionMessage.respond_settlement( tx_message, performative=TransactionMessage.Performative.SUCCESSFUL_SETTLEMENT, tx_digest=tx_digest, ) else: tx_message_response = TransactionMessage.respond_settlement( tx_message, performative=TransactionMessage.Performative.FAILED_SETTLEMENT, ) else: tx_message_response = TransactionMessage.respond_settlement( tx_message, performative=TransactionMessage.Performative.REJECTED_SETTLEMENT, ) self.message_out_queue.put(tx_message_response)
def test_decision_maker_handle_tx_message_not_ready(self): """Test that the decision maker is not ready to pursuit the goals.Cannot handle the message.""" tx_message = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id=self.tx_id, tx_sender_addr=self.tx_sender_addr, tx_counterparty_addr=self.tx_counterparty_addr, tx_amount_by_currency_id={"FET": -2}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, info=self.info, ledger_id=self.ledger_id, tx_nonce="Transaction nonce", ) with mock.patch.object( self.decision_maker_handler.context.ledger_apis, "token_balance", return_value=1000000, ): with mock.patch.object( self.decision_maker_handler.context.ledger_apis, "transfer", return_value="This is a test digest", ): with mock.patch( "aea.decision_maker.default.GoalPursuitReadiness.Status" ) as mocked_status: mocked_status.READY.value = False self.decision_maker.handle(tx_message) assert (not self.decision_maker_handler.context. goal_pursuit_readiness.is_ready) self.decision_maker.message_out_queue.get() tx_message = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id=self.tx_id, tx_sender_addr=self.tx_sender_addr, tx_counterparty_addr=self.tx_counterparty_addr, tx_amount_by_currency_id={"FET": -2}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, info=self.info, ledger_id=self.ledger_id, tx_nonce="transaction nonce", ) self.decision_maker.handle(tx_message) assert not self.decision_maker.message_out_queue.empty() self.decision_maker.message_out_queue.get()
def test_transaction_is_affordable_there_is_no_wealth(): """Reject the transaction when there is no wealth exchange.""" currency_endowment = {"FET": 0} good_endowment = {"good_id": 0} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id="transaction0", tx_sender_addr="agent_1", tx_counterparty_addr="pk", tx_amount_by_currency_id={"FET": 0}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 0}, info={"some_info_key": "some_info_value"}, ledger_id="fetchai", tx_nonce="transaction nonce", ) assert not ownership_state.is_affordable_transaction( tx_message=tx_message), "We must reject the transaction."
def _handle_match_accept(self, msg: FipaMessage, dialogue: Dialogue) -> None: """ Handle the match accept. :param msg: the message :param dialogue: the dialogue object :return: None """ strategy = cast(Strategy, self.context.strategy) if strategy.is_ledger_tx: self.context.logger.info( "[{}]: received MATCH_ACCEPT_W_INFORM from sender={}".format( self.context.agent_name, msg.counterparty[-5:])) info = msg.info address = cast(str, info.get("address")) proposal = cast(Description, dialogue.proposal) strategy = cast(Strategy, self.context.strategy) tx_msg = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[ PublicId("fetchai", "carpark_client", "0.1.0") ], tx_id="transaction0", tx_sender_addr=self.context.agent_addresses["fetchai"], tx_counterparty_addr=address, tx_amount_by_currency_id={ proposal.values["currency_id"]: -proposal.values["price"] }, tx_sender_fee=strategy.max_buyer_tx_fee, tx_counterparty_fee=proposal.values["seller_tx_fee"], tx_quantities_by_good_id={}, ledger_id=proposal.values["ledger_id"], info={"dialogue_label": dialogue.dialogue_label.json}, ) self.context.decision_maker_message_queue.put_nowait(tx_msg) self.context.logger.info( "[{}]: proposing the transaction to the decision maker. Waiting for confirmation ..." .format(self.context.agent_name)) else: new_message_id = msg.message_id + 1 new_target = msg.message_id inform_msg = FipaMessage( message_id=new_message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=new_target, performative=FipaMessage.Performative.INFORM, info={"Done": "Sending payment via bank transfer"}, ) dialogue.outgoing_extend(inform_msg) self.context.outbox.put_message( to=msg.counterparty, sender=self.context.agent_address, protocol_id=FipaMessage.protocol_id, message=FipaSerializer().encode(inform_msg), ) self.context.logger.info( "[{}]: informing counterparty={} of payment.".format( self.context.agent_name, msg.counterparty[-5:]))
def _handle_match_accept(self, msg: FIPAMessage, sender: str, message_id: int, dialogue: Dialogue) -> None: """ Handle the match accept. :param msg: the message :param sender: the sender :param message_id: the message id :param dialogue: the dialogue object :return: None """ logger.info( "[{}]: received MATCH_ACCEPT_W_ADDRESS from sender={}".format( self.context.agent_name, sender[-5:])) address = cast(str, msg.get("address")) proposal = cast(Description, dialogue.proposal) tx_msg = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE, skill_id="carpark_client", transaction_id="transaction0", sender=self.context.agent_public_keys['fetchai'], counterparty=address, is_sender_buyer=True, currency_pbk="FET", amount=proposal.values['price'], sender_tx_fee=0, counterparty_tx_fee=0, quantities_by_good_pbk={}, dialogue_label=dialogue.dialogue_label.json, ledger_id='fetchai') self.context.decision_maker_message_queue.put_nowait(tx_msg) logger.info( "[{}]: proposing the transaction to the decision maker. Waiting for confirmation ..." .format(self.context.agent_name))
def handle(self, message: Message, sender: str) -> None: """ Implement the reaction to a message. :param message: the message :param sender: the sender :return: None """ tx_msg_response = cast(TransactionMessage, message) if tx_msg_response is not None and \ TransactionMessage.Performative(tx_msg_response.get("performative")) == TransactionMessage.Performative.ACCEPT: logger.info("[{}]: transaction was successful.".format(self.context.agent_name)) json_data = {'transaction_digest': tx_msg_response.get("transaction_digest")} dialogue_label = DialogueLabel.from_json(cast(Dict[str, str], tx_msg_response.get("dialogue_label"))) dialogues = cast(Dialogues, self.context.dialogues) dialogue = dialogues.dialogues[dialogue_label] fipa_msg = cast(FIPAMessage, dialogue.last_incoming_message) new_message_id = cast(int, fipa_msg.get("message_id")) + 1 new_target_id = cast(int, fipa_msg.get("target")) + 1 counterparty_pbk = dialogue.dialogue_label.dialogue_opponent_pbk inform_msg = FIPAMessage(message_id=new_message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=new_target_id, performative=FIPAMessage.Performative.INFORM, json_data=json_data) dialogue.outgoing_extend(inform_msg) self.context.outbox.put_message(to=counterparty_pbk, sender=self.context.agent_public_key, protocol_id=FIPAMessage.protocol_id, message=FIPASerializer().encode(inform_msg)) logger.info("[{}]: informing counterparty={} of transaction digest.".format(self.context.agent_name, counterparty_pbk[-5:])) self._received_tx_message = True else: logger.info("[{}]: transaction was not successful.".format(self.context.agent_name))
def test_decision_maker_handle_unknown_tx_message(self): """Test the handle tx message method.""" patch_logger_error = mock.patch.object( aea.decision_maker.default.logger, "error") mocked_logger_error = patch_logger_error.__enter__() with mock.patch( "aea.decision_maker.messages.transaction.TransactionMessage._is_consistent", return_value=True, ): tx_message = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id=self.tx_id, tx_sender_addr=self.tx_sender_addr, tx_counterparty_addr=self.tx_counterparty_addr, tx_amount_by_currency_id={"FET": -2}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, info=self.info, ledger_id="bitcoin", ) self.decision_maker.handle(tx_message) mocked_logger_error.assert_called_with( "[test]: ledger_id=bitcoin is not supported")
def test_transaction_is_affordable_agent_is_buyer(): """Check if the agent has the money to cover the sender_amount (the agent=sender is the buyer).""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 20} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id="transaction0", tx_sender_addr="agent_1", tx_counterparty_addr="pk", tx_amount_by_currency_id={"FET": -1}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, info={"some_info_key": "some_info_value"}, ledger_id="fetchai", tx_nonce="transaction nonce", ) assert ownership_state.is_affordable_transaction( tx_message=tx_message), "We should have the money for the transaction!"
def test_transaction_update_receive(): """Test the transaction update when receiving tokens.""" currency_endowment = {"FET": 75} good_endowment = {"good_id": 30} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) assert ownership_state.amount_by_currency_id == currency_endowment assert ownership_state.quantities_by_good_id == good_endowment tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id="transaction0", tx_sender_addr="agent_1", tx_counterparty_addr="pk", tx_amount_by_currency_id={"FET": 20}, tx_sender_fee=5, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": -10}, info={"some_info_key": "some_info_value"}, ledger_id="fetchai", tx_nonce="transaction nonce", ) ownership_state._update(tx_message=tx_message) expected_amount_by_currency_id = {"FET": 90} expected_quantities_by_good_id = {"good_id": 20} assert ownership_state.amount_by_currency_id == expected_amount_by_currency_id assert ownership_state.quantities_by_good_id == expected_quantities_by_good_id
def test_apply(): """Test the apply function.""" currency_endowment = {"FET": 100} good_endowment = {"good_id": 2} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id="transaction0", tx_sender_addr="agent_1", tx_counterparty_addr="pk", tx_amount_by_currency_id={"FET": -20}, tx_sender_fee=5, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, info={"some_info_key": "some_info_value"}, ledger_id="fetchai", tx_nonce="transaction nonce", ) list_of_transactions = [tx_message] state = ownership_state new_state = ownership_state.apply_transactions( transactions=list_of_transactions) assert ( state != new_state ), "after applying a list_of_transactions must have a different state!"
def tests_transaction_is_affordable_else_statement(): """Check that the function returns false if we cannot satisfy any if/elif statements.""" currency_endowment = {"FET": 0} good_endowment = {"good_id": 0} ownership_state = OwnershipState() ownership_state.set( amount_by_currency_id=currency_endowment, quantities_by_good_id=good_endowment, ) tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id="transaction0", tx_sender_addr="agent_1", tx_counterparty_addr="pk", tx_amount_by_currency_id={"FET": 10}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 50}, info={"some_info_key": "some_info_value"}, ledger_id="fetchai", tx_nonce="transaction nonce", ) assert not ownership_state.is_affordable_transaction( tx_message=tx_message), "We must reject the transaction."
def test_decision_maker_execute(self): """Test the execute method.""" tx_message = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id=self.tx_id, tx_sender_addr=self.tx_sender_addr, tx_counterparty_addr=self.tx_counterparty_addr, tx_amount_by_currency_id={"FET": -20}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, info=self.info, ledger_id=self.ledger_id, tx_nonce="Transaction nonce", ) self.decision_maker.message_in_queue.put_nowait(tx_message) # test that after a while the queue has been consumed. time.sleep(0.5) assert self.decision_maker.message_in_queue.empty() time.sleep(0.5) assert not self.decision_maker.message_out_queue.empty() # TODO test the content of the response. response = self.decision_maker.message_out_queue.get() # noqa
def test_decision_maker_handle_tx_message(self): """Test the handle tx message method.""" assert self.decision_maker.message_out_queue.empty() tx_message = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id=self.tx_id, tx_sender_addr=self.tx_sender_addr, tx_counterparty_addr=self.tx_counterparty_addr, tx_amount_by_currency_id={"FET": -2}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, info=self.info, ledger_id=self.ledger_id, tx_nonce="Transaction nonce", ) with mock.patch.object( self.decision_maker_handler.context.ledger_apis, "token_balance", return_value=1000000, ): with mock.patch.object( self.decision_maker_handler.context.ledger_apis, "transfer", return_value="This is a test digest", ): self.decision_maker.handle(tx_message) assert not self.decision_maker.message_out_queue.empty() self.decision_maker.message_out_queue.get()
def handle(self, message: Message, sender: str) -> None: """ Dispatch message to relevant handler and respond. :param message: the message :param sender: the sender :return: None """ tx_message = cast(TransactionMessage, message) if TransactionMessage.Performative(tx_message.get( "performative")) == TransactionMessage.Performative.ACCEPT: logger.info( "[{}]: transaction confirmed by decision maker, sending to controller." .format(self.context.agent_name)) game = cast(Game, self.context.game) msg = TACMessage( type=TACMessage.Type.TRANSACTION, transaction_id=tx_message.get("transaction_digest"), counterparty=tx_message.get("counterparty"), amount_by_currency={ tx_message.get("currency"): tx_message.get("amount") }, sender_tx_fee=tx_message.get("sender_tx_fee"), counterparty_tx_fee=tx_message.get("counterparty_tx_fee"), quantities_by_good_pbk=tx_message.get( "quantities_by_good_pbk")) self.context.outbox.put_message( to=game.configuration.controller_pbk, sender=self.context.agent_public_key, protocol_id=TACMessage.protocol_id, message=TACSerializer().encode(msg)) else: logger.info("[{}]: transaction was not successful.".format( self.context.agent_name))
def _settle_tx(self, tx_message: TransactionMessage) -> Optional[str]: """ Settle the tx. :param tx_message: the transaction message :return: the transaction digest """ if tx_message.ledger_id == OFF_CHAIN: logger.info( "[{}]: Cannot settle transaction, settlement happens off chain!".format( self._agent_name ) ) tx_digest = OFF_CHAIN_SETTLEMENT_DIGEST else: logger.info("[{}]: Settling transaction on chain!".format(self._agent_name)) crypto_object = self.wallet.crypto_objects.get(tx_message.ledger_id) tx_digest = self.ledger_apis.transfer( crypto_object, tx_message.tx_counterparty_addr, tx_message.counterparty_amount, tx_message.fees, info=tx_message.info, tx_nonce=cast(str, tx_message.get("tx_nonce")), ) return tx_digest
def test_settle_tx_known_chain(self): """Test the off_chain message.""" tx_message = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id=self.tx_id, tx_sender_addr=self.tx_sender_addr, tx_counterparty_addr=self.tx_counterparty_addr, tx_amount_by_currency_id={"FET": -20}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, ledger_id=self.ledger_id, info=self.info, tx_nonce="Transaction nonce", ) with mock.patch.object( self.decision_maker_handler.context.ledger_apis, "transfer", return_value="tx_digest", ): tx_digest = self.decision_maker_handler._settle_tx(tx_message) assert tx_digest == "tx_digest"
def test_handle_internal_messages(self): """Test that the internal messages are handled.""" t = TransactionMessage( performative=TransactionMessage.Performative.SUCCESSFUL_SETTLEMENT, tx_id="transaction0", skill_callback_ids=[PublicId("dummy_author", "dummy", "0.1.0")], tx_sender_addr="pk1", tx_counterparty_addr="pk2", tx_amount_by_currency_id={"FET": 2}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"Unknown": 10}, ledger_id="fetchai", info={}, tx_digest="some_tx_digest", ) self.aea.decision_maker.message_out_queue.put(t) self.aea._filter.handle_internal_messages() internal_handlers_list = self.aea.resources.get_handlers( PublicId("fetchai", "internal", "0.1.0")) assert len(internal_handlers_list) == 1 internal_handler = internal_handlers_list[0] assert len(internal_handler.handled_internal_messages) == 1 self.aea.teardown()
def test_is_affordable_ledger_state_proxy(self): """Test that the tx_message is affordable with initialized ledger_state_proxy.""" tx_message = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id=self.tx_id, tx_sender_addr=self.tx_sender_addr, tx_counterparty_addr=self.tx_counterparty_addr, tx_amount_by_currency_id={"FET": -20}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, ledger_id=self.ledger_id, info=self.info, tx_nonce="Transaction nonce", ) with mock.patch.object( self.decision_maker_handler, "_is_acceptable_for_settlement", return_value=True, ): with mock.patch.object(self.decision_maker_handler, "_settle_tx", return_value="tx_digest"): self.decision_maker_handler._is_affordable(tx_message)
def test_decision_maker_tx_message_is_not_acceptable_for_settlement(self): """Test that a tx_message is not acceptable for settlement.""" tx_message = TransactionMessage( performative=TransactionMessage.Performative. PROPOSE_FOR_SETTLEMENT, skill_callback_ids=[PublicId(AUTHOR, "a_skill", "0.1.0")], tx_id=self.tx_id, tx_sender_addr=self.tx_sender_addr, tx_counterparty_addr=self.tx_counterparty_addr, tx_amount_by_currency_id={"FET": -2}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"good_id": 10}, ledger_id=self.ledger_id, info=self.info, tx_nonce="Transaction nonce", ) with mock.patch.object( self.decision_maker_handler, "_is_acceptable_for_settlement", return_value=True, ): with mock.patch.object(self.decision_maker_handler, "_settle_tx", return_value=None): self.decision_maker.handle(tx_message) assert not self.decision_maker.message_out_queue.empty() self.decision_maker.message_out_queue.get()
def check_transaction_is_consistent( self, tx_message: TransactionMessage) -> bool: """ Check if the transaction is consistent. E.g. check that the agent state has enough money if it is a buyer or enough holdings if it is a seller. :return: True if the transaction is legal wrt the current state, false otherwise. """ currency_pbk = cast(str, tx_message.get("currency_pbk")) if tx_message.get("is_sender_buyer"): # check if we have the money to cover amount and tx fee. result = self.amount_by_currency[currency_pbk] >= cast( int, tx_message.get("amount")) + cast( int, tx_message.get("sender_tx_fee")) else: # check if we have the goods. result = True quantities_by_good_pbk = cast( Dict[str, int], tx_message.get("quantities_by_good_pbk")) for good_pbk, quantity in quantities_by_good_pbk.items(): result = result and (self.quantities_by_good_pbk[good_pbk] >= quantity) # check if we have the money to cover tx fee. result = self.amount_by_currency[currency_pbk] + cast( int, tx_message.get("amount")) >= cast( int, tx_message.get("sender_tx_fee")) return result
def _settle_tx(self, tx_message: TransactionMessage) -> Optional[str]: """ Settle the tx. :param tx_message: the transaction message :return: the transaction digest """ logger.info("[{}]: Settling transaction!".format(self._agent_name)) if tx_message.get("ledger_id") is not None: amount = cast(int, tx_message.get("amount")) counterparty_tx_fee = cast(int, tx_message.get("counterparty_tx_fee")) sender_tx_fee = cast(int, tx_message.get("sender_tx_fee")) counterparty_address = cast(str, tx_message.get("counterparty")) # adjust payment amount to reflect transaction fee split amount -= counterparty_tx_fee tx_fee = counterparty_tx_fee + sender_tx_fee crypto_object = self._wallet.crypto_objects.get( tx_message.get("ledger_id")) tx_digest = self.ledger_apis.transfer(crypto_object.identifier, crypto_object, counterparty_address, amount, tx_fee) else: tx_digest = cast(str, tx_message.get("transaction_id")) return tx_digest
def test_message_consistency(self): """Test for an error in consistency of a message.""" assert TransactionMessage( performative=TransactionMessage.Performative.SUCCESSFUL_SETTLEMENT, skill_callback_ids=[PublicId.from_str("author/skill:0.1.0")], tx_id="transaction0", tx_sender_addr="pk1", tx_counterparty_addr="pk2", tx_amount_by_currency_id={"FET": -2}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"GOOD_ID": 10}, ledger_id="fetchai", info={"some_string": [1, 2]}, tx_digest="some_string", ) with pytest.raises(AssertionError): TransactionMessage( performative=TransactionMessage.Performative.SUCCESSFUL_SETTLEMENT, skill_callback_ids=[PublicId.from_str("author/skill:0.1.0")], tx_id="transaction0", tx_sender_addr="pk1", tx_counterparty_addr="pk2", tx_amount_by_currency_id={"FET": -2}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"GOOD_ID": 10}, ledger_id="ethereum", info={"some_string": [1, 2]}, tx_digest="some_string", ) with pytest.raises(AssertionError): TransactionMessage( performative=TransactionMessage.Performative.SUCCESSFUL_SETTLEMENT, skill_callback_ids=[PublicId.from_str("author/skill:0.1.0")], tx_id="transaction0", tx_sender_addr="pk", tx_counterparty_addr="pk", tx_amount_by_currency_id={"Unknown": 2}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={"Unknown": 10}, ledger_id="fetchai", info={"info": "info_value"}, )
def get_mint_single_transaction_msg( self, deployer_address: Address, recipient_address: Address, token_id: int, mint_quantity: int, ledger_api: LedgerApi, skill_callback_id: ContractId, transaction_id: str = Performative.CONTRACT_MINT_SINGLE.value, info: Optional[Dict[str, Any]] = None, ) -> TransactionMessage: """ Get the transaction message containing the transaction to mint a batch of tokens. :param deployer_address: the deployer_address :param recipient_address: the recipient_address :param token_id: the token id :param mint_quantity: the mint_quantity of each token :param ledger_api: the ledger api :param skill_callback_id: the skill callback id :param transaction_id: the transaction id :param info: the optional info payload for the transaction message :return: the transaction message for the decision maker """ tx = self.get_mint_single_transaction( deployer_address=deployer_address, recipient_address=recipient_address, token_id=token_id, mint_quantity=mint_quantity, ledger_api=ledger_api, ) logger.debug( "get_mint_single_tx: deployer_address={}, recipient_address={}, token_id={}, mint_quantity={}, tx={}" .format( deployer_address, recipient_address, token_id, mint_quantity, tx, )) tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE_FOR_SIGNING, skill_callback_ids=[skill_callback_id], tx_id=transaction_id, tx_sender_addr=deployer_address, tx_counterparty_addr=recipient_address, tx_amount_by_currency_id={ETHEREUM_CURRENCY: 0}, tx_sender_fee=0, tx_counterparty_fee=0, tx_quantities_by_good_id={str(token_id): mint_quantity}, info=info if info is not None else {}, ledger_id=EthereumCrypto.identifier, signing_payload={"tx": tx}, ) return tx_message
def test_transaction_update(self): """Test the tranasction update.""" currency_endowment = {"FET": 100} good_endowment = {"good_pbk": 2} self.ownership_state.init(amount_by_currency=currency_endowment, quantities_by_good_pbk=good_endowment) tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE, skill_id="default", transaction_id="transaction0", sender="agent_1", counterparty="pk", is_sender_buyer=True, currency_pbk="FET", amount=20, sender_tx_fee=5, counterparty_tx_fee=0, quantities_by_good_pbk={"good_pbk": 10}, ledger_id="fetchai") cur_holdings = self.ownership_state.amount_by_currency['FET'] self.ownership_state.update(tx_message=tx_message) assert self.ownership_state.amount_by_currency['FET'] < cur_holdings tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE, skill_id="default", transaction_id="transaction0", sender="agent_1", counterparty="pk", is_sender_buyer=False, currency_pbk="FET", amount=20, sender_tx_fee=5, counterparty_tx_fee=0, quantities_by_good_pbk={"good_pbk": 10}, ledger_id="fetchai") cur_holdings = self.ownership_state.amount_by_currency['FET'] self.ownership_state.update(tx_message=tx_message) assert self.ownership_state.amount_by_currency['FET'] > cur_holdings
def _handle_tx_message_for_signing(self, tx_message: TransactionMessage) -> None: """ Handle a transaction message for signing. :param tx_message: the transaction message :return: None """ if self._is_acceptable_for_signing(tx_message): tx_signature = self._sign_tx(tx_message) tx_message_response = TransactionMessage.respond_signing( tx_message, performative=TransactionMessage.Performative. SUCCESSFUL_SIGNING, tx_signature=tx_signature, ) else: tx_message_response = TransactionMessage.respond_signing( tx_message, performative=TransactionMessage.Performative.REJECTED_SIGNING, ) self.message_out_queue.put(tx_message_response)
def test_transaction_is_consistent(self): """Test the consistency of the transaction message.""" currency_endowment = {"FET": 100} good_endowment = {"good_pbk": 2} self.ownership_state.init(amount_by_currency=currency_endowment, quantities_by_good_pbk=good_endowment) tx_message = TransactionMessage( performative=TransactionMessage.Performative.ACCEPT, skill_id="default", transaction_id="transaction0", sender="agent_1", counterparty="pk", is_sender_buyer=True, currency_pbk="FET", amount=1, sender_tx_fee=0, counterparty_tx_fee=0, quantities_by_good_pbk={"good_pbk": 10}, ledger_id="fetchai") assert self.ownership_state.check_transaction_is_consistent(tx_message=tx_message),\ "We should have the money for the transaction!" tx_message = TransactionMessage( performative=TransactionMessage.Performative.PROPOSE, skill_id="default", transaction_id="transaction0", sender="agent_1", counterparty="pk", is_sender_buyer=False, currency_pbk="FET", amount=1, sender_tx_fee=0, counterparty_tx_fee=0, quantities_by_good_pbk={"good_pbk": 10}, ledger_id="fetchai") assert self.ownership_state.check_transaction_is_consistent(tx_message=tx_message), \ "We should have the money for the transaction!"