From 71660132c36231e5db640243098cb5c61e152879 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Fri, 1 Mar 2019 12:14:13 +0100 Subject: [PATCH 01/33] Change logic for transaction serialization/deserialization (WIP) --- aeternity/hashing.py | 9 ++++ aeternity/transactions.py | 100 ++++++++++++++++++++++++++++---------- 2 files changed, 82 insertions(+), 27 deletions(-) diff --git a/aeternity/hashing.py b/aeternity/hashing.py index 773f4038..caa9a2b2 100644 --- a/aeternity/hashing.py +++ b/aeternity/hashing.py @@ -145,6 +145,15 @@ def _int(val: int, byte_length: int = None) -> bytes: return val.to_bytes(size, byteorder='big') +def _int_decode(data: bytes) -> int: + """ + Interpret a byte array to an integer (big endian) + """ + if len(data) == 0: + return 0 + return int.from_bytes(data, "big") + + def _binary(val): """ Encode a value to bytes. diff --git a/aeternity/transactions.py b/aeternity/transactions.py index d6745e47..65012ec8 100644 --- a/aeternity/transactions.py +++ b/aeternity/transactions.py @@ -1,9 +1,11 @@ -from aeternity.hashing import _int, _binary, _id, encode, decode, encode_rlp, decode_rlp, hash_encode, contract_id +from aeternity.hashing import _int, _int_decode, _binary, _id, encode, decode, encode_rlp, decode_rlp, hash_encode, contract_id from aeternity.openapi import OpenAPICli from aeternity.config import ORACLE_DEFAULT_TTL_TYPE_DELTA from aeternity import identifiers as idf +from aeternity.exceptions import UnsupportedTransactionType import rlp import math +import namedtupled BASE_GAS = 15000 GAS_PER_BYTE = 20 @@ -34,18 +36,43 @@ def sign_encode_transaction(self, tx): :return: encoded_signed_tx, encoded_signature, tx_hash """ # decode the transaction if not in native mode - transaction = decode(tx.tx) if hasattr(tx, idf.TRANSACTION) else decode(tx) + transaction = _tx_native(op=UNPACK_TX, tx=tx.tx if hasattr(tx, "tx") else tx) + # get the transaction as byte list + tx_raw = decode(transaction.tx) # sign the transaction - signature = self.account.sign(_binary(self.network_id) + transaction) + signature = self.account.sign(_binary(self.network_id) + tx_raw) # encode the transaction - encoded_signed_tx, encoded_signature = self.encode_signed_transaction(transaction, signature) + encoded_signed_tx, encoded_signature = self.encode_signed_transaction(tx_raw, signature) # compute the hash tx_hash = TxBuilder.compute_tx_hash(encoded_signed_tx) - # return the - return encoded_signed_tx, encoded_signature, tx_hash + # return the object + tx = dict( + data=transaction.data, + tx=encoded_signed_tx, + hash=tx_hash, + signature=encoded_signature, + network_id=self.network_id, + ) + return namedtupled.map(tx, _nt_name="TxObject") + + +class TxObject: + def __init__(self, tx_data, tx, hash): + self.data = tx_data + self.tx = tx + self.hash = hash + self._as_bytes = decode(self.tx) + + def set_properties(self, **kwargs): + for k, v in kwargs.items(): + self.__setattr__(k, v) -def _tx_native(tag: int, vsn: int, op: int=1, **kwargs): +PACK_TX = 1 +UNPACK_TX = 0 + + +def _tx_native(op, **kwargs): def std_fee(tx_raw, fee_idx, base_gas_multiplier=1): # calculates the standard minimum transaction fee @@ -66,8 +93,29 @@ def oracle_fee(tx_raw, fee_idx, relative_ttl): fee += math.ceil(32000 * relative_ttl / math.floor(60 * 24 * 365 / KEY_BLOCK_INTERVAL)) return fee * GAS_PRICE + def build_tx_object(tx_data, tx_raw, fee_idx, min_fee): + if tx_data.get("fee") < min_fee: + tx_native[fee_idx] = _int(min_fee) + tx_data["fee"] = min_fee + tx_encoded = encode_rlp(idf.TRANSACTION, tx_native) + tx = dict( + data=tx_data, + tx=tx_encoded, + hash=TxBuilder.compute_tx_hash(tx_encoded), + ) + return namedtupled.map(tx, _nt_name="TxObject") + + if op == PACK_TX: + tag = kwargs.get("tag", 0) + vsn = kwargs.get("vsn", 1) + else: + tx_native = decode_rlp(kwargs.get("tx", [])) + tag = int.from_bytes(tx_native[0], "big") + if tag == idf.OBJECT_TAG_SPEND_TRANSACTION: - if op == 1: + tx = {} + tx_field_fee_index = 5 + if op == PACK_TX: # pack transaction tx_native = [ _int(tag), _int(vsn), @@ -79,23 +127,23 @@ def oracle_fee(tx_raw, fee_idx, relative_ttl): _int(kwargs.get("nonce")), _binary(kwargs.get("payload")) ] - tx_field_fee_index = 5 min_fee = std_fee(tx_native, tx_field_fee_index) - else: + tx = build_tx_object(kwargs, tx_native, tx_field_fee_index, min_fee) + else: # unpack transaction tx_native = decode_rlp(kwargs.get("tx")) - body = dict( - type="SpendTx", - vsn=int.from_bytes(tx_native[1], "big"), + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), sender_id=encode(idf.ACCOUNT_ID, tx_native[2]), recipient_id=encode(idf.ACCOUNT_ID, tx_native[3]), - amount=int.from_bytes(tx_native[4], "big"), - fee=int.from_bytes(tx_native[5], "big"), - ttl=int.from_bytes(tx_native[6], "big"), - nonce=int.from_bytes(tx_native[7], "big"), + amount=_int_decode(tx_native[4]), + fee=_int_decode(tx_native[5]), + ttl=_int_decode(tx_native[6]), + nonce=_int_decode(tx_native[7]), payload=tx_native[8].hex(), ) - return body - + tx = build_tx_object(tx_data, tx_native, tx_field_fee_index, tx_data.get("fee")) + return tx elif tag == idf.OBJECT_TAG_NAME_SERVICE_PRECLAIM_TRANSACTION: tx_native = [ _int(tag), @@ -409,10 +457,8 @@ def pointer_tag(pointer): ] tx_field_fee_index = 6 min_fee = oracle_fee(tx_native, tx_field_fee_index, oracle_ttl.get("value")) - # set the correct fee if fee is empty - if kwargs.get("fee") < min_fee: - tx_native[tx_field_fee_index] = min_fee - return encode_rlp(idf.TRANSACTION, tx_native), min_fee + else: + raise UnsupportedTransactionType(f"Unusupported transaction tag {tag}") class TxBuilder: @@ -420,7 +466,7 @@ class TxBuilder: TxBuilder is used to build and post transactions to the chain. """ - def __init__(self, native=None, api=None): + def __init__(self): pass @staticmethod @@ -445,6 +491,8 @@ def tx_spend(self, account_id, recipient_id, amount, payload, fee, ttl, nonce)-> """ # use internal endpoints transaction body = { + "tag": idf.OBJECT_TAG_SPEND_TRANSACTION, + "vsn": idf.VSN, "recipient_id": recipient_id, "amount": amount, "fee": fee, @@ -453,9 +501,7 @@ def tx_spend(self, account_id, recipient_id, amount, payload, fee, ttl, nonce)-> "ttl": ttl, "nonce": nonce, } - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_SPEND_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # return self.api.post_spend(body=body).tx # NAMING # From 615f652a0d4299ea77f60a5ebfe8b04f43525591 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 19:15:21 +0100 Subject: [PATCH 02/33] New logic for transaction packing/unpacking --- aeternity/transactions.py | 683 +++++++++++++++++++++++++------------- 1 file changed, 448 insertions(+), 235 deletions(-) diff --git a/aeternity/transactions.py b/aeternity/transactions.py index 65012ec8..017b6a45 100644 --- a/aeternity/transactions.py +++ b/aeternity/transactions.py @@ -1,6 +1,6 @@ -from aeternity.hashing import _int, _int_decode, _binary, _id, encode, decode, encode_rlp, decode_rlp, hash_encode, contract_id +from aeternity.hashing import _int, _int_decode, _binary, _binary_decode, _id, encode, decode, encode_rlp, decode_rlp, hash_encode from aeternity.openapi import OpenAPICli -from aeternity.config import ORACLE_DEFAULT_TTL_TYPE_DELTA +from aeternity.config import ORACLE_DEFAULT_TTL_TYPE_DELTA, ORACLE_DEFAULT_TTL_TYPE_BLOCK from aeternity import identifiers as idf from aeternity.exceptions import UnsupportedTransactionType import rlp @@ -12,6 +12,9 @@ GAS_PRICE = 1000000000 KEY_BLOCK_INTERVAL = 3 +PACK_TX = 1 +UNPACK_TX = 0 + class TxSigner: """ @@ -47,31 +50,15 @@ def sign_encode_transaction(self, tx): tx_hash = TxBuilder.compute_tx_hash(encoded_signed_tx) # return the object tx = dict( - data=transaction.data, - tx=encoded_signed_tx, - hash=tx_hash, - signature=encoded_signature, - network_id=self.network_id, + data=transaction.data, + tx=encoded_signed_tx, + hash=tx_hash, + signature=encoded_signature, + network_id=self.network_id, ) return namedtupled.map(tx, _nt_name="TxObject") -class TxObject: - def __init__(self, tx_data, tx, hash): - self.data = tx_data - self.tx = tx - self.hash = hash - self._as_bytes = decode(self.tx) - - def set_properties(self, **kwargs): - for k, v in kwargs.items(): - self.__setattr__(k, v) - - -PACK_TX = 1 -UNPACK_TX = 0 - - def _tx_native(op, **kwargs): def std_fee(tx_raw, fee_idx, base_gas_multiplier=1): @@ -105,15 +92,20 @@ def build_tx_object(tx_data, tx_raw, fee_idx, min_fee): ) return namedtupled.map(tx, _nt_name="TxObject") + tx = {} + # prepare tag and version if op == PACK_TX: tag = kwargs.get("tag", 0) vsn = kwargs.get("vsn", 1) - else: + tx_data = kwargs + elif op == UNPACK_TX: tx_native = decode_rlp(kwargs.get("tx", [])) - tag = int.from_bytes(tx_native[0], "big") + tag = _int_decode(tx_native[0]) + else: + raise Exception("Invalid operation") + # check the tags if tag == idf.OBJECT_TAG_SPEND_TRANSACTION: - tx = {} tx_field_fee_index = 5 if op == PACK_TX: # pack transaction tx_native = [ @@ -128,9 +120,7 @@ def build_tx_object(tx_data, tx_raw, fee_idx, min_fee): _binary(kwargs.get("payload")) ] min_fee = std_fee(tx_native, tx_field_fee_index) - tx = build_tx_object(kwargs, tx_native, tx_field_fee_index, min_fee) - else: # unpack transaction - tx_native = decode_rlp(kwargs.get("tx")) + elif op == UNPACK_TX: # unpack transaction tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), @@ -140,121 +130,254 @@ def build_tx_object(tx_data, tx_raw, fee_idx, min_fee): fee=_int_decode(tx_native[5]), ttl=_int_decode(tx_native[6]), nonce=_int_decode(tx_native[7]), - payload=tx_native[8].hex(), + payload=_binary_decode(tx_native[8]), ) - tx = build_tx_object(tx_data, tx_native, tx_field_fee_index, tx_data.get("fee")) - return tx + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) elif tag == idf.OBJECT_TAG_NAME_SERVICE_PRECLAIM_TRANSACTION: - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), - _int(kwargs.get("nonce")), - _id(idf.ID_TAG_COMMITMENT, kwargs.get("commitment_id")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")) - ] tx_field_fee_index = 5 - min_fee = std_fee(tx_native, tx_field_fee_index) + if op == PACK_TX: # pack transaction + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), + _int(kwargs.get("nonce")), + _id(idf.ID_TAG_COMMITMENT, kwargs.get("commitment_id")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")) + ] + min_fee = std_fee(tx_native, tx_field_fee_index) + elif op == UNPACK_TX: # unpack transaction + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + commitment_id=encode(idf.COMMITMENT, tx_native[4]), + fee=_int_decode(tx_native[5]), + ttl=_int_decode(tx_native[6]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_NAME_SERVICE_CLAIM_TRANSACTION: - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), - _int(kwargs.get("nonce")), - decode(kwargs.get("name")), - _binary(kwargs.get("name_salt")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")) - ] tx_field_fee_index = 6 - min_fee = std_fee(tx_native, tx_field_fee_index) + if op == PACK_TX: # pack transaction + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), + _int(kwargs.get("nonce")), + decode(kwargs.get("name")), + _binary(kwargs.get("name_salt")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")) + ] + min_fee = std_fee(tx_native, tx_field_fee_index) + elif op == UNPACK_TX: # unpack transaction + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + name=_binary_decode(tx_native[4], str), + name_salt=_binary_decode(tx_native[5], int), + fee=_int_decode(tx_native[6]), + ttl=_int_decode(tx_native[7]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_NAME_SERVICE_UPDATE_TRANSACTION: - # first asseble the pointers - def pointer_tag(pointer): - return { - "account_pubkey": idf.ID_TAG_ACCOUNT, - "oracle_pubkey": idf.ID_TAG_ORACLE, - "contract_pubkey": idf.ID_TAG_CONTRACT, - "channel_pubkey": idf.ID_TAG_CHANNEL - }.get(pointer.get("key")) - ptrs = [[_binary(p.get("key")), _id(pointer_tag(p), p.get("id"))] for p in kwargs.get("pointers", [])] - # then build the transaction - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), - _int(kwargs.get("nonce")), - _id(idf.ID_TAG_NAME, kwargs.get("name_id")), - _int(kwargs.get("name_ttl")), - ptrs, - _int(kwargs.get("client_ttl")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")) - ] tx_field_fee_index = 8 - min_fee = std_fee(tx_native, tx_field_fee_index) + if op == PACK_TX: # pack transaction + # first asseble the pointers + def pointer_tag(pointer): + return { + "account_pubkey": idf.ID_TAG_ACCOUNT, + "oracle_pubkey": idf.ID_TAG_ORACLE, + "contract_pubkey": idf.ID_TAG_CONTRACT, + "channel_pubkey": idf.ID_TAG_CHANNEL + }.get(pointer.get("key")) + ptrs = [[_binary(p.get("key")), _id(pointer_tag(p), p.get("id"))] for p in kwargs.get("pointers", [])] + # then build the transaction + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), + _int(kwargs.get("nonce")), + _id(idf.ID_TAG_NAME, kwargs.get("name_id")), + _int(kwargs.get("name_ttl")), + ptrs, + _int(kwargs.get("client_ttl")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")) + ] + min_fee = std_fee(tx_native, tx_field_fee_index) + elif op == UNPACK_TX: # unpack transaction + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + name=encode(idf.NAME, tx_native[4]), + name_ttl=_int_decode(tx_native[5]), + pointers=[], # TODO: decode pointers + client_ttl=_int_decode(tx_native[7]), + fee=_int_decode(tx_native[8]), + ttl=_int_decode(tx_native[9]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_NAME_SERVICE_TRANSFER_TRANSACTION: - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), - _int(kwargs.get("nonce")), - _id(idf.ID_TAG_NAME, kwargs.get("name_id")), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("recipient_id")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")), - ] tx_field_fee_index = 6 - min_fee = std_fee(tx_native, tx_field_fee_index) + if op == PACK_TX: # pack transaction + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), + _int(kwargs.get("nonce")), + _id(idf.ID_TAG_NAME, kwargs.get("name_id")), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("recipient_id")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")), + ] + min_fee = std_fee(tx_native, tx_field_fee_index) + elif op == UNPACK_TX: # unpack transaction + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + name=encode(idf.NAME, tx_native[4]), + recipient_id=encode(idf.ACCOUNT_ID, tx_native[5]), + fee=_int_decode(tx_native[6]), + ttl=_int_decode(tx_native[7]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_NAME_SERVICE_REVOKE_TRANSACTION: - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), - _int(kwargs.get("nonce")), - _id(idf.ID_TAG_NAME, kwargs.get("name_id")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")), - ] tx_field_fee_index = 5 - min_fee = std_fee(tx_native, tx_field_fee_index) + if op == PACK_TX: # pack transaction + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), + _int(kwargs.get("nonce")), + _id(idf.ID_TAG_NAME, kwargs.get("name_id")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")), + ] + min_fee = std_fee(tx_native, tx_field_fee_index) + elif op == UNPACK_TX: # unpack transaction + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + name=encode(idf.NAME, tx_native[4]), + fee=_int_decode(tx_native[5]), + ttl=_int_decode(tx_native[6]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_CONTRACT_CREATE_TRANSACTION: - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("owner_id")), - _int(kwargs.get("nonce")), - _binary(decode(kwargs.get("code"))), - _int(kwargs.get("vm_version")) + _int(kwargs.get("abi_version"), 2), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")), - _int(kwargs.get("deposit")), - _int(kwargs.get("amount")), - _int(kwargs.get("gas")), - _int(kwargs.get("gas_price")), - _binary(decode(kwargs.get("call_data"))), - ] tx_field_fee_index = 6 - # TODO: verify the fee caluclation for the contract - min_fee = contract_fee(tx_native, tx_field_fee_index, kwargs.get("gas"), base_gas_multiplier=5) + if op == PACK_TX: # pack transaction + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("owner_id")), + _int(kwargs.get("nonce")), + _binary(decode(kwargs.get("code"))), + _int(kwargs.get("vm_version")) + _int(kwargs.get("abi_version"), 2), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")), + _int(kwargs.get("deposit")), + _int(kwargs.get("amount")), + _int(kwargs.get("gas")), + _int(kwargs.get("gas_price")), + _binary(decode(kwargs.get("call_data"))), + ] + # TODO: verify the fee caluclation for the contract + min_fee = contract_fee(tx_native, tx_field_fee_index, kwargs.get("gas"), base_gas_multiplier=5) + elif op == UNPACK_TX: # unpack transaction + vml = len(tx_native[5]) # this is used to extract the abi and vm version from the 5th field + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + owner_id=encode(idf.ACCOUNT_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + code=encode(idf.BYTECODE, tx_native[4]), + vm_version=_int_decode(tx_native[5][0:vml - 2]), + abi_version=_int_decode(tx_native[5][vml - 2:]), + fee=_int_decode(tx_native[6]), + ttl=_int_decode(tx_native[7]), + deposit=_int_decode(tx_native[8]), + amount=_int_decode(tx_native[9]), + gas=_int_decode(tx_native[10]), + gas_price=_int_decode(tx_native[11]), + call_data=encode(idf.BYTECODE, tx_native[12]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_CONTRACT_CALL_TRANSACTION: - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("caller_id")), - _int(kwargs.get("nonce")), - _id(idf.ID_TAG_CONTRACT, kwargs.get("contract_id")), - _int(kwargs.get("abi_version")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")), - _int(kwargs.get("amount")), - _int(kwargs.get("gas")), - _int(kwargs.get("gas_price")), - _binary(decode(kwargs.get("call_data"))), - ] tx_field_fee_index = 6 - min_fee = contract_fee(tx_native, tx_field_fee_index, kwargs.get("gas"), base_gas_multiplier=30) + if op == PACK_TX: # pack transaction + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("caller_id")), + _int(kwargs.get("nonce")), + _id(idf.ID_TAG_CONTRACT, kwargs.get("contract_id")), + _int(kwargs.get("abi_version")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")), + _int(kwargs.get("amount")), + _int(kwargs.get("gas")), + _int(kwargs.get("gas_price")), + _binary(decode(kwargs.get("call_data"))), + ] + min_fee = contract_fee(tx_native, tx_field_fee_index, kwargs.get("gas"), base_gas_multiplier=30) + elif op == UNPACK_TX: # unpack transaction + vml = len(tx_native[5]) # this is used to extract the abi and vm version from the 5th field + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + caller_id=encode(idf.ACCOUNT_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + contract_id=encode(idf.CONTRACT_ID, tx_native[4]), + abi_version=_int_decode(tx_native[5]), + fee=_int_decode(tx_native[6]), + ttl=_int_decode(tx_native[7]), + amount=_int_decode(tx_native[8]), + gas=_int_decode(tx_native[9]), + gas_price=_int_decode(tx_native[10]), + call_data=encode(idf.BYTECODE, tx_native[11]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_CHANNEL_CREATE_TRANSACTION: tx_native = [ _int(tag), @@ -390,73 +513,165 @@ def pointer_tag(pointer): tx_field_fee_index = 9 min_fee = std_fee(tx_native, tx_field_fee_index) elif tag == idf.OBJECT_TAG_ORACLE_REGISTER_TRANSACTION: - oracle_ttl = kwargs.get("oracle_ttl", {}) - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), - _int(kwargs.get("nonce")), - _binary(kwargs.get("query_format")), - _binary(kwargs.get("response_format")), - _int(kwargs.get("query_fee")), - _int(0 if oracle_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), - _int(oracle_ttl.get("value")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")), - _int(kwargs.get("vm_version")), - ] tx_field_fee_index = 9 - min_fee = oracle_fee(tx_native, tx_field_fee_index, oracle_ttl.get("value")) + if op == PACK_TX: # pack transaction + oracle_ttl = kwargs.get("oracle_ttl", {}) + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("account_id")), + _int(kwargs.get("nonce")), + _binary(kwargs.get("query_format")), + _binary(kwargs.get("response_format")), + _int(kwargs.get("query_fee")), + _int(0 if oracle_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(oracle_ttl.get("value")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")), + _int(kwargs.get("vm_version")), + ] + min_fee = oracle_fee(tx_native, tx_field_fee_index, oracle_ttl.get("value")) + elif op == UNPACK_TX: # unpack transaction + vml = len(tx_native[5]) # this is used to extract the abi and vm version from the 5th field + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + query_format=_binary_decode(tx_native[4]), + response_format=_binary_decode(tx_native[5]), + query_fee=_int_decode(tx_native[6]), + oracle_ttl=dict( + type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[7]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + value=_int_decode(tx_native[8]), + ), + fee=_int_decode(tx_native[9]), + ttl=_int_decode(tx_native[10]), + vm_version=_int_decode(tx_native[11]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_ORACLE_QUERY_TRANSACTION: - query_ttl = kwargs.get("query_ttl", {}) - response_ttl = kwargs.get("response_ttl", {}) - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ACCOUNT, kwargs.get("sender_id")), - _int(kwargs.get("nonce")), - _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")), - _binary(kwargs.get("query")), - _int(kwargs.get("query_fee")), - _int(0 if query_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), - _int(query_ttl.get("value")), - _int(0 if response_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), - _int(response_ttl.get("value")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")), - ] tx_field_fee_index = 11 - min_fee = oracle_fee(tx_native, tx_field_fee_index, query_ttl.get("value")) + if op == PACK_TX: # pack transaction + query_ttl = kwargs.get("query_ttl", {}) + response_ttl = kwargs.get("response_ttl", {}) + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ACCOUNT, kwargs.get("sender_id")), + _int(kwargs.get("nonce")), + _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")), + _binary(kwargs.get("query")), + _int(kwargs.get("query_fee")), + _int(0 if query_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(query_ttl.get("value")), + _int(0 if response_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(response_ttl.get("value")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")), + ] + min_fee = oracle_fee(tx_native, tx_field_fee_index, query_ttl.get("value")) + elif op == UNPACK_TX: # unpack transaction + vml = len(tx_native[5]) # this is used to extract the abi and vm version from the 5th field + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + sender_id=encode(idf.ACCOUNT_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + oracle_id=encode(idf.ORACLE_ID, tx_native[4]), + query=_binary_decode(tx_native[5]), + query_fee=_int_decode(tx_native[6]), + query_ttl=dict( + type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[7]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + value=_int_decode(tx_native[8]), + ), + response_ttl=dict( + type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[9]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + value=_int_decode(tx_native[10]), + ), + fee=_int_decode(tx_native[11]), + ttl=_int_decode(tx_native[12]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_ORACLE_RESPONSE_TRANSACTION: - response_ttl = kwargs.get("response_ttl", {}) - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")), - _int(kwargs.get("nonce")), - decode(kwargs.get("query_id")), - _binary(kwargs.get("response")), - _int(0 if response_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), - _int(response_ttl.get("value")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")), - ] tx_field_fee_index = 8 - min_fee = oracle_fee(tx_native, tx_field_fee_index, response_ttl.get("value")) + if op == PACK_TX: # pack transaction + response_ttl = kwargs.get("response_ttl", {}) + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")), + _int(kwargs.get("nonce")), + decode(kwargs.get("query_id")), + _binary(kwargs.get("response")), + _int(0 if response_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(response_ttl.get("value")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")), + ] + min_fee = oracle_fee(tx_native, tx_field_fee_index, response_ttl.get("value")) + elif op == UNPACK_TX: # unpack transaction + vml = len(tx_native[5]) # this is used to extract the abi and vm version from the 5th field + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + oracle_id=encode(idf.ORACLE_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + query_id=encode(idf.ORACLE_QUERY_ID, tx_native[4]), + response=_binary(tx_native[5]), + response_ttl=dict( + type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[6]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + value=_int_decode(tx_native[7]), + ), + fee=_int_decode(tx_native[8]), + ttl=_int_decode(tx_native[9]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) + elif tag == idf.OBJECT_TAG_ORACLE_EXTEND_TRANSACTION: - oracle_ttl = kwargs.get("oracle_ttl", {}) - tx_native = [ - _int(tag), - _int(vsn), - _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")), - _int(kwargs.get("nonce")), - _int(0 if oracle_ttl.get("type", {}) == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), - _int(oracle_ttl.get("value")), - _int(kwargs.get("fee")), - _int(kwargs.get("ttl")), - ] tx_field_fee_index = 6 - min_fee = oracle_fee(tx_native, tx_field_fee_index, oracle_ttl.get("value")) + if op == PACK_TX: # pack transaction + oracle_ttl = kwargs.get("oracle_ttl", {}) + tx_native = [ + _int(tag), + _int(vsn), + _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")), + _int(kwargs.get("nonce")), + _int(0 if oracle_ttl.get("type", {}) == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(oracle_ttl.get("value")), + _int(kwargs.get("fee")), + _int(kwargs.get("ttl")), + ] + min_fee = oracle_fee(tx_native, tx_field_fee_index, oracle_ttl.get("value")) + elif op == UNPACK_TX: # unpack transaction + vml = len(tx_native[5]) # this is used to extract the abi and vm version from the 5th field + tx_data = dict( + tag=tag, + vsn=_int_decode(tx_native[1]), + oracle_id=encode(idf.ORACLE_ID, tx_native[2]), + nonce=_int_decode(tx_native[3]), + oracle_ttl=dict( + type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[4]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + value=_int_decode(tx_native[5]), + ), + fee=_int_decode(tx_native[6]), + ttl=_int_decode(tx_native[7]), + ) + min_fee = tx_data.get("fee") + else: + raise Exception("Invalid operation") + return build_tx_object(tx_data, tx_native, tx_field_fee_index, min_fee) else: raise UnsupportedTransactionType(f"Unusupported transaction tag {tag}") @@ -490,17 +705,17 @@ def tx_spend(self, account_id, recipient_id, amount, payload, fee, ttl, nonce)-> :param nonce: the nonce of the transaction """ # use internal endpoints transaction - body = { - "tag": idf.OBJECT_TAG_SPEND_TRANSACTION, - "vsn": idf.VSN, - "recipient_id": recipient_id, - "amount": amount, - "fee": fee, - "sender_id": account_id, - "payload": payload, - "ttl": ttl, - "nonce": nonce, - } + body = dict( + tag=idf.OBJECT_TAG_SPEND_TRANSACTION, + vsn=idf.VSN, + recipient_id=recipient_id, + amount=amount, + fee=fee, + sender_id=account_id, + payload=payload, + ttl=ttl, + nonce=nonce, + ) return _tx_native(op=PACK_TX, **body) # return self.api.post_spend(body=body).tx @@ -516,15 +731,15 @@ def tx_name_preclaim(self, account_id, commitment_id, fee, ttl, nonce)-> str: :param nonce: the nonce of the account for the transaction """ body = dict( + tag=idf.OBJECT_TAG_NAME_SERVICE_PRECLAIM_TRANSACTION, + vsn=idf.VSN, commitment_id=commitment_id, fee=fee, account_id=account_id, ttl=ttl, nonce=nonce ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_NAME_SERVICE_PRECLAIM_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # sreturn self.api.post_name_preclaim(body=body).tx def tx_name_claim(self, account_id, name, name_salt, fee, ttl, nonce)-> str: @@ -538,6 +753,8 @@ def tx_name_claim(self, account_id, name, name_salt, fee, ttl, nonce)-> str: :param nonce: the nonce of the account for the transaction """ body = dict( + tag=idf.OBJECT_TAG_NAME_SERVICE_CLAIM_TRANSACTION, + vsn=idf.VSN, account_id=account_id, name=name, name_salt=name_salt, @@ -545,9 +762,7 @@ def tx_name_claim(self, account_id, name, name_salt, fee, ttl, nonce)-> str: ttl=ttl, nonce=nonce ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_NAME_SERVICE_CLAIM_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # return self.api.post_name_claim(body=body).tx def tx_name_update(self, account_id, name_id, pointers, name_ttl, client_ttl, fee, ttl, nonce)-> str: @@ -563,6 +778,8 @@ def tx_name_update(self, account_id, name_id, pointers, name_ttl, client_ttl, fe :param nonce: the nonce of the account for the transaction """ body = dict( + tag=idf.OBJECT_TAG_NAME_SERVICE_UPDATE_TRANSACTION, + vsn=idf.VSN, account_id=account_id, name_id=name_id, client_ttl=client_ttl, @@ -572,9 +789,7 @@ def tx_name_update(self, account_id, name_id, pointers, name_ttl, client_ttl, fe fee=fee, nonce=nonce ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_NAME_SERVICE_UPDATE_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # return self.api.post_name_update(body=body).tx def tx_name_transfer(self, account_id, name_id, recipient_id, fee, ttl, nonce)-> str: @@ -588,6 +803,8 @@ def tx_name_transfer(self, account_id, name_id, recipient_id, fee, ttl, nonce)-> :param nonce: the nonce of the account for the transaction """ body = dict( + tag=idf.OBJECT_TAG_NAME_SERVICE_TRANSFER_TRANSACTION, + vsn=idf.VSN, account_id=account_id, name_id=name_id, recipient_id=recipient_id, @@ -595,9 +812,7 @@ def tx_name_transfer(self, account_id, name_id, recipient_id, fee, ttl, nonce)-> fee=fee, nonce=nonce ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_NAME_SERVICE_TRANSFER_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # return self.api.post_name_transfer(body=body).tx def tx_name_revoke(self, account_id, name_id, fee, ttl, nonce)-> str: @@ -611,15 +826,15 @@ def tx_name_revoke(self, account_id, name_id, fee, ttl, nonce)-> str: """ body = dict( + tag=idf.OBJECT_TAG_NAME_SERVICE_REVOKE_TRANSACTION, + vsn=idf.VSN, account_id=account_id, name_id=name_id, ttl=ttl, fee=fee, nonce=nonce ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_NAME_SERVICE_REVOKE_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # return self.api.post_name_revoke(body=body).tx # CONTRACTS @@ -641,6 +856,8 @@ def tx_contract_create(self, owner_id, code, call_data, amount, deposit, gas, ga :param nonce: the nonce of the account for the transaction """ body = dict( + tag=idf.OBJECT_TAG_CONTRACT_CREATE_TRANSACTION, + vsn=idf.VSN, owner_id=owner_id, amount=amount, deposit=deposit, @@ -654,10 +871,7 @@ def tx_contract_create(self, owner_id, code, call_data, amount, deposit, gas, ga ttl=ttl, nonce=nonce ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_CONTRACT_CREATE_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native, contract_id(owner_id, nonce) - # tx = self.api.post_contract_create(body=body) + return _tx_native(op=PACK_TX, **body) # return tx.tx, tx.contract_id def tx_contract_call(self, caller_id, contract_id, call_data, function, arg, amount, gas, gas_price, abi_version, fee, ttl, nonce)-> str: @@ -679,6 +893,8 @@ def tx_contract_call(self, caller_id, contract_id, call_data, function, arg, amo """ body = dict( + tag=idf.OBJECT_TAG_CONTRACT_CALL_TRANSACTION, + vsn=idf.VSN, call_data=call_data, caller_id=caller_id, contract_id=contract_id, @@ -690,9 +906,7 @@ def tx_contract_call(self, caller_id, contract_id, call_data, function, arg, amo ttl=ttl, nonce=nonce ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_CONTRACT_CALL_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # return self.api.post_contract_call(body=body).tx # ORACLES @@ -705,6 +919,8 @@ def tx_oracle_register(self, account_id, Create a register oracle transaction """ body = dict( + tag=idf.OBJECT_TAG_ORACLE_REGISTER_TRANSACTION, + vsn=idf.VSN, account_id=account_id, query_format=query_format, response_format=response_format, @@ -717,10 +933,7 @@ def tx_oracle_register(self, account_id, ttl=ttl, nonce=nonce ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_ORACLE_REGISTER_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native - # tx = self.api.post_oracle_register(body=body) + return _tx_native(op=PACK_TX, **body) # return tx.tx def tx_oracle_query(self, oracle_id, sender_id, query, @@ -732,6 +945,8 @@ def tx_oracle_query(self, oracle_id, sender_id, query, """ body = dict( + tag=idf.OBJECT_TAG_ORACLE_QUERY_TRANSACTION, + vsn=idf.VSN, sender_id=sender_id, oracle_id=oracle_id, response_ttl=dict( @@ -748,9 +963,7 @@ def tx_oracle_query(self, oracle_id, sender_id, query, ttl=ttl, nonce=nonce, ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_ORACLE_QUERY_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # tx = self.api.post_oracle_query(body=body) # return tx.tx @@ -761,6 +974,8 @@ def tx_oracle_respond(self, oracle_id, query_id, response, Create a oracle response transaction """ body = dict( + tag=idf.OBJECT_TAG_ORACLE_RESPONSE_TRANSACTION, + vsn=idf.VSN, response_ttl=dict( type=response_ttl_type, value=response_ttl_value @@ -772,9 +987,7 @@ def tx_oracle_respond(self, oracle_id, query_id, response, ttl=ttl, nonce=nonce, ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_ORACLE_RESPONSE_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # tx = self.api.post_oracle_respond(body=body) # return tx.tx @@ -785,6 +998,8 @@ def tx_oracle_extend(self, oracle_id, Create a oracle extends transaction """ body = dict( + tag=idf.OBJECT_TAG_ORACLE_EXTEND_TRANSACTION, + vsn=idf.VSN, oracle_id=oracle_id, oracle_ttl=dict( type=ttl_type, @@ -794,9 +1009,7 @@ def tx_oracle_extend(self, oracle_id, ttl=ttl, nonce=nonce, ) - tx_native, min_fee = _tx_native(idf.OBJECT_TAG_ORACLE_EXTEND_TRANSACTION, idf.VSN, **body) - # compute the absolute ttl and the nonce - return tx_native + return _tx_native(op=PACK_TX, **body) # tx = self.api.post_oracle_extend(body=body) # return tx.tx From 535733241e678b830416507fc5e5f8c927fc673b Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 19:17:02 +0100 Subject: [PATCH 03/33] Update aens with new transaction management --- aeternity/aens.py | 50 +++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/aeternity/aens.py b/aeternity/aens.py index cc5da3a4..50b1984c 100644 --- a/aeternity/aens.py +++ b/aeternity/aens.py @@ -119,26 +119,26 @@ def full_claim_blocking(self, account, :param target: the public key to associate the name to (pointers) """ # set the blocking to true - blocking_orig = self.client.blocking_mode - self.client.blocking_mode = True + blocking_orig = self.client.config.blocking_mode + self.client.config.blocking_mode = True # if not self.is_available(): raise NameNotAvailable(self.domain) hashes = {} # run preclaim - t, s, g, h = self.preclaim(account, fee=preclaim_fee, tx_ttl=tx_ttl) - hashes['preclaim_tx'] = [t, s, g, h] + tx = self.preclaim(account, fee=preclaim_fee, tx_ttl=tx_ttl) + hashes['preclaim_tx'] = tx # run claim - t, s, g, h = self.claim(account, fee=claim_fee, tx_ttl=tx_ttl) - hashes['claim_tx'] = [t, s, g, h] + tx = self.claim(account, fee=claim_fee, tx_ttl=tx_ttl) + hashes['claim_tx'] = tx # target is the same of account is not specified if target is None: target = account.get_address() # run update - t, s, g, h = self.update(account, target, fee=update_fee, name_ttl=name_ttl, client_ttl=client_ttl) - hashes['update_tx'] = [t, s, g, h] + tx = self.update(account, target, fee=update_fee, name_ttl=name_ttl, client_ttl=client_ttl) + hashes['update_tx'] = tx # restore blocking value - self.client.blocking_mode = blocking_orig + self.client.config.blocking_mode = blocking_orig return hashes def preclaim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): @@ -156,13 +156,13 @@ def preclaim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): # create spend_tx tx = txb.tx_name_preclaim(account.get_address(), commitment_id, fee, ttl, nonce) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # update local status self.status = AEName.Status.PRECLAIMED - self.preclaim_tx_hash = tx_hash - return tx, tx_signed, sg, tx_hash + self.preclaim_tx_hash = tx_signed.hash + return tx_signed def claim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): if self.preclaimed_block_height is None: @@ -176,12 +176,12 @@ def claim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): # create transaction tx = txb.tx_name_claim(account.get_address(), name, self.preclaim_salt, fee, ttl, nonce) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # update status self.status = AEName.Status.CLAIMED - return tx, tx_signed, sg, tx_hash + return tx_signed def update(self, account, target, name_ttl=DEFAULT_NAME_TTL, @@ -206,10 +206,10 @@ def update(self, account, target, # create transaction tx = txb.tx_name_update(account.get_address(), name_id, pointers, name_ttl, client_ttl, fee, ttl, nonce) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) - return tx, tx_signed, sg, tx_hash + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) + return tx_signed def transfer_ownership(self, account, recipient_pubkey, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): """ @@ -225,12 +225,12 @@ def transfer_ownership(self, account, recipient_pubkey, fee=DEFAULT_FEE, tx_ttl= # create transaction tx = txb.tx_name_transfer(account.get_address(), name_id, recipient_pubkey, fee, ttl, nonce) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # update the status self.status = NameStatus.TRANSFERRED - return tx, tx_signed, sg, tx_hash + return tx_signed def revoke(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): """ @@ -246,9 +246,9 @@ def revoke(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): # create transaction tx = txb.tx_name_revoke(account.get_address(), name_id, fee, ttl, nonce) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # update the status self.status = NameStatus.REVOKED - return tx_hash + return tx_signed From 31794478c526c294c0206eea9df2f7980e8a81a6 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 19:17:30 +0100 Subject: [PATCH 04/33] Update contracts with new transaction management --- aeternity/contract.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/aeternity/contract.py b/aeternity/contract.py index cbe9ac36..10c07299 100644 --- a/aeternity/contract.py +++ b/aeternity/contract.py @@ -1,5 +1,5 @@ -from aeternity.openapi import OpenAPIClientException, UnsupportedEpochVersion -from aeternity import utils, config +from aeternity.openapi import OpenAPIClientException, UnsupportedNodeVersion +from aeternity import utils, config, hashing from aeternity.identifiers import CONTRACT_ID, CONTRACT_ROMA_VM, CONTRACT_ROMA_ABI, CONTRACT_MINERVA_VM, CONTRACT_MINERVA_ABI import semver @@ -67,12 +67,12 @@ def tx_call(self, account, function, arg, amount, gas, gas_price, abi_version, fee, ttl, nonce) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # unsigned transaction of the call - call_obj = self.client.get_transaction_info_by_hash(hash=tx_hash) - return tx, tx_signed, sg, tx_hash, call_obj + call_obj = self.client.get_transaction_info_by_hash(hash=tx_signed.hash) + return tx_signed, call_obj except OpenAPIClientException as e: raise ContractError(e) @@ -103,16 +103,17 @@ def tx_create(self, # get the account nonce and ttl nonce, ttl = self.client._get_nonce_ttl(account.get_address(), tx_ttl) # build the transaction - tx, contract_id = txb.tx_contract_create(account.get_address(), self.bytecode, call_data, - amount, deposit, gas, gas_price, vm_version, abi_version, - fee, ttl, nonce) + tx = txb.tx_contract_create(account.get_address(), self.bytecode, call_data, + amount, deposit, gas, gas_price, vm_version, abi_version, + fee, ttl, nonce) + # store the contract address in the instance variabl + self.address = hashing.contract_id(account.get_address(), nonce ) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) - # store the contract address in the instance variabl - self.address = contract_id - return tx, tx_signed, sg, tx_hash + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) + + return tx except OpenAPIClientException as e: raise ContractError(e) @@ -190,4 +191,4 @@ def _get_vm_abi_versions(self): return CONTRACT_ROMA_VM, CONTRACT_ROMA_ABI if semver.match(self.client.api_version, "<3.0.0"): return CONTRACT_MINERVA_VM, CONTRACT_MINERVA_ABI - raise UnsupportedEpochVersion(f"Version {self.client.api_version} is not supported") + raise UnsupportedNodeVersion(f"Version {self.client.api_version} is not supported") From cd28f3b13ab09acb96e2d3705bf4ea1039d45d30 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 19:18:22 +0100 Subject: [PATCH 05/33] Refactor support submodules --- aeternity/exceptions.py | 9 +++++++-- aeternity/hashing.py | 11 +++++++++++ aeternity/identifiers.py | 4 ++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/aeternity/exceptions.py b/aeternity/exceptions.py index f43fea7e..53d2a3b7 100644 --- a/aeternity/exceptions.py +++ b/aeternity/exceptions.py @@ -55,10 +55,15 @@ def __init__(self, tx_hash, reason): self.reason = reason -class UnsupportedEpochVersion(AException): +class UnsupportedNodeVersion(AException): """Raised when the node target runs an unsupported version""" -class ConfigException(AException): +class ConfigException(Exception): """Raised in case of configuration errors""" pass + + +class UnsupportedTransactionType(Exception): + """Raised for unknow transaction tag""" + pass diff --git a/aeternity/hashing.py b/aeternity/hashing.py index caa9a2b2..3158bf24 100644 --- a/aeternity/hashing.py +++ b/aeternity/hashing.py @@ -170,6 +170,17 @@ def _binary(val): raise ValueError("Byte serialization not supported") +def _binary_decode(data, data_type=None): + """ + Decodes a bite arrya to the selected datatype or to hex if no data_type is provided + """ + if data_type == int: + return _int_decode(data) + if data_type == str: + return data.decode("utf-8") + return data.hex() + + def _id(id_tag, hash_id): """Utility function to create and _id type""" return _int(id_tag) + decode(hash_id) diff --git a/aeternity/identifiers.py b/aeternity/identifiers.py index 64ad738f..7152a1a4 100644 --- a/aeternity/identifiers.py +++ b/aeternity/identifiers.py @@ -19,7 +19,7 @@ TRANSACTION_HASH = "th" # base58 Transaction hash # Base 64 -CONTRACT_BYTE_ARRAY = "cb" # base64 Contract byte array +BYTECODE = "cb" # base64 Contract byte array ORACLE_RESPONSE = "or" # base64 Oracle response ORACLE_QUERY = "ov" # base64 Oracle query PROOF_OF_INCLUSION = "pi" # base64 Proof of Inclusion @@ -47,7 +47,7 @@ # Indentifiers with base64 IDENTIFIERS_B64 = set([ - CONTRACT_BYTE_ARRAY, + BYTECODE, ORACLE_RESPONSE, ORACLE_QUERY, PROOF_OF_INCLUSION, From b7da1e7959fcd091e74e0ac20a3d67abf168b56d Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 20:19:27 +0100 Subject: [PATCH 06/33] Update oracles with new transaction management --- aeternity/oracles.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/aeternity/oracles.py b/aeternity/oracles.py index b044a014..dd486951 100644 --- a/aeternity/oracles.py +++ b/aeternity/oracles.py @@ -39,13 +39,13 @@ def execute(self, sender, query, fee, ttl, nonce ) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(sender, tx) + tx_signed = self.client.sign_transaction(sender, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # save the query id self.id = hashing.oracle_query_id(sender.get_address(), nonce, self.oracle_id) # return the transaction - return tx, tx_signed, sg, tx_hash + return tx_signed def get_response_object(self): # TODO: workaround for dashes in the parameter names @@ -84,15 +84,15 @@ def register(self, account, query_format, response_format, vm_version, fee, ttl, nonce ) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # register the oracle id # the oracle id is the account that register the oracle # with the prefix substituted by with ok_ self.id = f"{ORACLE_ID}_{account.get_address()[3:]}" # return the transaction - return tx, tx_signed, sg, tx_hash + return tx_signed def respond(self, account, query_id, response, response_ttl_type=config.ORACLE_DEFAULT_TTL_TYPE_DELTA, @@ -114,11 +114,11 @@ def respond(self, account, query_id, response, fee, ttl, nonce ) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # return the transaction - return tx, tx_signed, sg, tx_hash + return tx_signed def extend(self, account, query_id, response, ttl_type=config.ORACLE_DEFAULT_TTL_TYPE_DELTA, @@ -137,8 +137,8 @@ def extend(self, account, query_id, response, # create spend_tx tx = txb.tx_oracle_extend(self.id, ttl_type, ttl_value, fee, ttl, nonce) # sign the transaction - tx_signed, sg, tx_hash = self.client.sign_transaction(account, tx) + tx_signed = self.client.sign_transaction(account, tx.tx) # post the transaction to the chain - self.client.broadcast_transaction(tx_signed, tx_hash) + self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # return the transaction - return tx, tx_signed, sg, tx_hash + return tx_signed From 02f9a002546139cfd82ea3f10f0b6e39a620ce5c Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 21:38:06 +0100 Subject: [PATCH 07/33] Move default values into separate file --- aeternity/defaults.py | 33 +++++++++++++++++++++++++++++++++ aeternity/identifiers.py | 8 ++++++-- aeternity/transactions.py | 17 ++++++----------- 3 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 aeternity/defaults.py diff --git a/aeternity/defaults.py b/aeternity/defaults.py new file mode 100644 index 00000000..f2dad518 --- /dev/null +++ b/aeternity/defaults.py @@ -0,0 +1,33 @@ +from aeternity import identifiers + +# fee calculation +BASE_GAS = 15000 +GAS_PER_BYTE = 20 +GAS_PRICE = 1000000000 +# max number of block into the future that the name is going to be available +# https://github.com/aeternity/protocol/blob/epoch-v0.22.0/AENS.md#update +# https://github.com/aeternity/protocol/blob/44a93d3aab957ca820183c3520b9daf6b0fedff4/AENS.md#aens-entry +MAX_NAME_TLL = 36000 +MAX_NAME_CLIENT_TTL = 60000 +NAME_TTL = 500 +# default relative ttl in number of blocks for executing transaction on the chain +MAX_TX_TTL = 256 +TX_TTL = 0 +# contracts +CONTRACT_GAS = 1000000000 +CONTRACT_GAS_PRICE = 1000000000 +CONTRACT_DEPOSIT = 0 +CONTRACT_AMOUNT = 0 +# oracles +# https://github.com/aeternity/protocol/blob/master/oracles/oracles.md#technical-aspects-of-oracle-operations +ORACLE_VM_VERSION = identifiers.NO_VM +ORACLE_QUERY_FEE = 0 +ORACLE_TTL_VALUE = 500 +ORACLE_QUERY_TTL_VALUE = 10 +ORACLE_RESPONSE_TTL_VALUE = 10 +KEY_BLOCK_INTERVAL = 3 +# network id +NETWORK_ID = identifiers.NETWORK_ID_MAINNET +# TUNING +MAX_RETRIES = 8 # used in exponential backoff when checking a transaction +POLLING_INTERVAL = 2 # in seconds diff --git a/aeternity/identifiers.py b/aeternity/identifiers.py index 7152a1a4..ca3b0756 100644 --- a/aeternity/identifiers.py +++ b/aeternity/identifiers.py @@ -135,6 +135,10 @@ # For Minerva: 196609 # that is int.from_bytes(int(3).to_bytes(2, "big") + int(1).to_bytes(2, "big"), "big") CONTRACT_MINERVA_VM = 3 CONTRACT_MINERVA_ABI = 1 - # Oracles -ORACLE_DEFAULT_VM_VERSION = NO_VM +ORACLE_TTL_TYPE_DELTA = 'delta' +ORACLE_TTL_TYPE_BLOCK = 'block' + +# Network IDS +NETWORK_ID_MAINNET = "ae_mainnet" +NETWORK_ID_TESTNET = "ae_uat" diff --git a/aeternity/transactions.py b/aeternity/transactions.py index 017b6a45..e9af4eca 100644 --- a/aeternity/transactions.py +++ b/aeternity/transactions.py @@ -2,16 +2,12 @@ from aeternity.openapi import OpenAPICli from aeternity.config import ORACLE_DEFAULT_TTL_TYPE_DELTA, ORACLE_DEFAULT_TTL_TYPE_BLOCK from aeternity import identifiers as idf +from aeternity import defaults from aeternity.exceptions import UnsupportedTransactionType import rlp import math import namedtupled -BASE_GAS = 15000 -GAS_PER_BYTE = 20 -GAS_PRICE = 1000000000 -KEY_BLOCK_INTERVAL = 3 - PACK_TX = 1 UNPACK_TX = 0 @@ -65,20 +61,20 @@ def std_fee(tx_raw, fee_idx, base_gas_multiplier=1): # calculates the standard minimum transaction fee tx_copy = tx_raw # create a copy of the input tx_copy[fee_idx] = _int(0, 8) - return (BASE_GAS * base_gas_multiplier + len(rlp.encode(tx_copy)) * GAS_PER_BYTE) * GAS_PRICE + return (defaults.BASE_GAS * base_gas_multiplier + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) * defaults.GAS_PRICE def contract_fee(tx_raw, fee_idx, gas, base_gas_multiplier=1): # estimate the contract creation fee tx_copy = tx_raw # create a copy of the input tx_copy[fee_idx] = _int(0, 8) - return (BASE_GAS * base_gas_multiplier + gas + len(rlp.encode(tx_copy)) * GAS_PER_BYTE) * GAS_PRICE + return (defaults.BASE_GAS * base_gas_multiplier + gas + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) * defaults.GAS_PRICE def oracle_fee(tx_raw, fee_idx, relative_ttl): tx_copy = tx_raw # create a copy of the input tx_copy[fee_idx] = _int(0, 8) - fee = (BASE_GAS + len(rlp.encode(tx_copy)) * GAS_PER_BYTE) - fee += math.ceil(32000 * relative_ttl / math.floor(60 * 24 * 365 / KEY_BLOCK_INTERVAL)) - return fee * GAS_PRICE + fee = (defaults.BASE_GAS + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) + fee += math.ceil(32000 * relative_ttl / math.floor(60 * 24 * 365 / defaults.KEY_BLOCK_INTERVAL)) + return fee * defaults.GAS_PRICE def build_tx_object(tx_data, tx_raw, fee_idx, min_fee): if tx_data.get("fee") < min_fee: @@ -92,7 +88,6 @@ def build_tx_object(tx_data, tx_raw, fee_idx, min_fee): ) return namedtupled.map(tx, _nt_name="TxObject") - tx = {} # prepare tag and version if op == PACK_TX: tag = kwargs.get("tag", 0) From 1aa77fc32e83a1d10c2bc7aad03f65c02f407528 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 23:25:10 +0100 Subject: [PATCH 08/33] Refactor config to be part of the node submodule --- aeternity/contract.py | 26 +++++----- aeternity/node.py | 116 +++++++++++++++++++++--------------------- aeternity/oracles.py | 46 ++++++++--------- 3 files changed, 95 insertions(+), 93 deletions(-) diff --git a/aeternity/contract.py b/aeternity/contract.py index 10c07299..63c959f0 100644 --- a/aeternity/contract.py +++ b/aeternity/contract.py @@ -1,5 +1,5 @@ from aeternity.openapi import OpenAPIClientException, UnsupportedNodeVersion -from aeternity import utils, config, hashing +from aeternity import utils, defaults, hashing from aeternity.identifiers import CONTRACT_ID, CONTRACT_ROMA_VM, CONTRACT_ROMA_ABI, CONTRACT_MINERVA_VM, CONTRACT_MINERVA_ABI import semver @@ -39,13 +39,13 @@ def __init__(self, source_code, client, bytecode=None, address=None, abi=SOPHIA) self.bytecode = self.compile(self.source_code) def tx_call(self, account, function, arg, - amount=config.CONTRACT_DEFAULT_AMOUNT, - gas=config.CONTRACT_DEFAULT_GAS, - gas_price=config.CONTRACT_DEFAULT_GAS_PRICE, - fee=config.DEFAULT_FEE, + amount=defaults.CONTRACT_AMOUNT, + gas=defaults.CONTRACT_GAS, + gas_price=defaults.CONTRACT_GAS_PRICE, + fee=defaults.FEE, vm_version=None, abi_version=None, - tx_ttl=config.DEFAULT_TX_TTL): + tx_ttl=defaults.TX_TTL): """Call a sophia contract""" if not utils.is_valid_hash(self.address, prefix=CONTRACT_ID): @@ -78,15 +78,15 @@ def tx_call(self, account, function, arg, def tx_create(self, account, - amount=config.CONTRACT_DEFAULT_AMOUNT, - deposit=config.CONTRACT_DEFAULT_DEPOSIT, + amount=defaults.CONTRACT_AMOUNT, + deposit=defaults.CONTRACT_DEPOSIT, init_state="()", - gas=config.CONTRACT_DEFAULT_GAS, - gas_price=config.CONTRACT_DEFAULT_GAS_PRICE, - fee=config.DEFAULT_FEE, + gas=defaults.CONTRACT_GAS, + gas_price=defaults.CONTRACT_GAS_PRICE, + fee=defaults.FEE, vm_version=None, abi_version=None, - tx_ttl=config.DEFAULT_TX_TTL): + tx_ttl=defaults.TX_TTL): """ Create a contract and deploy it to the chain :return: address @@ -107,7 +107,7 @@ def tx_create(self, amount, deposit, gas, gas_price, vm_version, abi_version, fee, ttl, nonce) # store the contract address in the instance variabl - self.address = hashing.contract_id(account.get_address(), nonce ) + self.address = hashing.contract_id(account.get_address(), nonce) # sign the transaction tx_signed = self.client.sign_transaction(account, tx) # post the transaction to the chain diff --git a/aeternity/node.py b/aeternity/node.py index 4b608baa..63cd71af 100644 --- a/aeternity/node.py +++ b/aeternity/node.py @@ -5,77 +5,80 @@ from aeternity.transactions import TxSigner from aeternity.signing import Account from aeternity.openapi import OpenAPIClientException -from aeternity.exceptions import NameNotAvailable, InsufficientFundsException -from aeternity.exceptions import TransactionHashMismatch, TransactionWaitTimeoutExpired, TransactionNotFoundException -from aeternity import config, aens, openapi, transactions, contract, oracles +from aeternity import config, aens, openapi, transactions, contract, oracles, defaults +from aeternity.exceptions import TransactionWaitTimeoutExpired, TransactionHashMismatch logger = logging.getLogger(__name__) logging.root.setLevel(logging.DEBUG) -class NodeRequestError(Exception): - pass +class Config: + def __init__(self, + external_url='http://localhost:3013', + internal_url='http://localhost:3113', + websocket_url='http://localhost:3014', + force_compatibility=False, + **kwargs): + """ + :param external_url: the node external url + :param internal_url: the node internal url + :param websocket_url: the node websocket url + :param blocking_mode: block the client waiting for transactions (default False) + :param native: build transaction natively (do not use the node internal endpoints) (default True) + :param force_compatibility: ingnore node version compatibility check (default False) + :param debug: enable debug logging (default False) + """ + # endpoint urls + self.api_url = external_url + self.api_url_internal = internal_url + self.websocket_url = websocket_url + self.force_compatibility = force_compatibility + self.blocking_mode = kwargs.get("blocking_mode", False) + # defaults # TODO: pass defaults to the tx builder + self.tx_gas_per_byte = kwargs.get("tx_gas_per_byte", defaults.GAS_PER_BYTE) + self.tx_base_gas = kwargs.get("tx_base_gas", defaults.BASE_GAS) + self.tx_gas_price = kwargs.get("tx_gas_price", defaults.GAS_PRICE) + # get the version + self.network_id = kwargs.get("network_id", None) + # contracts defautls + self.contract_gas = kwargs.get("contract_gas", defaults.CONTRACT_GAS) + self.contract_gas_price = kwargs.get("contract_gas_price", defaults.CONTRACT_GAS_PRICE) + # oracles default + self.orcale_ttl_type = kwargs.get("oracle_ttl_type", defaults.ORACLE_TTL_TYPE) + # debug + self.debug = kwargs.get("debug", False) + + def __str__(self): + return f'ws:{self.websocket_url} ext:{self.api_url} int:{self.api_url_internal}' class NodeClient: - exception_by_reason = { - 'Name not found': NameNotAvailable, - 'No funds in account': InsufficientFundsException, - 'Transaction not found': TransactionNotFoundException, - } - - def __init__(self, configs=None, blocking_mode=False, native=True, offline=False, force_compatibility=False, debug=False): + def __init__(self, config=Config()): """ Initialize a new EpochClient - :param configs: the list of configurations to use or empty for default (default None) - :param blocking_mode: block the client waiting for transactions (default False) - :param native: build transaction natively (do not use the node internal endpoints) (default True) - :param offline: do not attempt to connect to a node (sign only) (default False) - :param force_compatibility: ingnore node version compatibility check (default False) - :param debug: enable debug logging (default False) + :param config: the configuration to use or empty for default (default None) """ - if configs is None: - configs = config.Config.get_defaults() - if isinstance(configs, config.Config): - configs = [configs] - self._configs = configs - self._active_config_idx = 0 - self._top_block = None + self.config = config # determine how the transaction are going to be created # if running offline they are forced to be native - self.native = native if not offline else True # shall the client work in blocking mode - self.blocking_mode = blocking_mode if not offline else False - self.offline = offline # instantiate the api client - self.api = None if offline else openapi.OpenAPICli(configs[0].api_url, - configs[0].api_url_internal, - debug=debug, - force_compatibility=force_compatibility) + self.api = openapi.OpenAPICli(url=config.api_url, + url_internal=config.api_url_internal, + debug=config.debug, + force_compatibility=config.force_compatibility) # instantiate the transaction builder object - self.tx_builder = transactions.TxBuilder(native=self.native, api=self.api) - - def set_native(self, build_native_transactions: bool): - prev_status = self.native - self.native = build_native_transactions - self.tx_builder.native_transactions = build_native_transactions - return prev_status + self.tx_builder = transactions.TxBuilder() + # network id + if self.config.network_id is None: + self.config.network_id = self.api.get_status().network_id # enable composition def __getattr__(self, attr): return getattr(self.api, attr) - def _get_active_config(self): - return self._configs[self._active_config_idx] - - def _use_next_config(self): - self._active_config_idx = (self._active_config_idx) + 1 % len(self._configs) - - def update_top_block(self): - self._top_block = self.get_top_block() - def compute_absolute_ttl(self, relative_ttl): """ Compute the absolute ttl by adding the ttl to the current height of the chain @@ -143,19 +146,18 @@ def broadcast_transaction(self, tx, tx_hash=None): if tx_hash is not None and reply.tx_hash != tx_hash: raise TransactionHashMismatch(f"Transaction hash doesn't match, expected {tx_hash} got {reply.tx_hash}") - if self.blocking_mode: + if self.config.blocking_mode: self.wait_for_transaction(reply.tx_hash) return reply.tx_hash def sign_transaction(self, account: Account, tx: str) -> (str, str, str): """ Sign a transaction - :return (tx_signed, signature, tx_hash): the signed transaction, the signature and the hash of the transaction + :return: the transaction for the transaction """ - s = TxSigner(account, self._get_active_config().network_id) - tx_signed, signature, tx_hash = s.sign_encode_transaction(tx) - - return tx_signed, signature, tx_hash + s = TxSigner(account, self.config.network_id) + tx = s.sign_encode_transaction(tx) + return tx def spend(self, account: Account, recipient_id, amount, payload="", fee=config.DEFAULT_FEE, tx_ttl=config.DEFAULT_TX_TTL): """ @@ -166,10 +168,10 @@ def spend(self, account: Account, recipient_id, amount, payload="", fee=config.D # build the transaction tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, fee, tx_ttl, nonce) # execute the transaction - tx_signed, signature, tx_hash = self.sign_transaction(account, tx) + tx = self.sign_transaction(account, tx.tx) # post the transaction - self.broadcast_transaction(tx_signed, tx_hash=tx_hash) - return tx, tx_signed, signature, tx_hash + self.broadcast_transaction(tx.tx, tx_hash=tx.hash) + return tx def wait_for_transaction(self, tx_hash, max_retries=config.MAX_RETRIES, polling_interval=config.POLLING_INTERVAL): """ diff --git a/aeternity/oracles.py b/aeternity/oracles.py index dd486951..401135f3 100644 --- a/aeternity/oracles.py +++ b/aeternity/oracles.py @@ -1,6 +1,6 @@ import logging -from aeternity import config, hashing -from aeternity.identifiers import ORACLE_ID, ORACLE_DEFAULT_VM_VERSION +from aeternity import hashing, defaults +from aeternity.identifiers import ORACLE_ID logger = logging.getLogger(__name__) @@ -16,13 +16,13 @@ def __init__(self, client, oracle_id, id=None): self.client = client def execute(self, sender, query, - query_fee=config.ORACLE_DEFAULT_QUERY_FEE, - query_ttl_type=config.ORACLE_DEFAULT_TTL_TYPE_DELTA, - query_ttl_value=config.ORACLE_DEFAULT_QUERY_TTL_VALUE, - response_ttl_type=config.ORACLE_DEFAULT_TTL_TYPE_DELTA, - response_ttl_value=config.ORACLE_DEFAULT_RESPONSE_TTL_VALUE, - fee=config.DEFAULT_FEE, - tx_ttl=config.DEFAULT_TX_TTL): + query_fee=defaults.ORACLE_QUERY_FEE, + query_ttl_type=defaults.ORACLE_TTL_TYPE, + query_ttl_value=defaults.ORACLE_QUERY_TTL_VALUE, + response_ttl_type=defaults.ORACLE_TTL_TYPE, + response_ttl_value=defaults.ORACLE_RESPONSE_TTL_VALUE, + fee=defaults.FEE, + tx_ttl=defaults.TX_TTL): """ Execute a query to the oracle """ @@ -63,12 +63,12 @@ def __init__(self, client, oracle_id=None): self.query_id = None def register(self, account, query_format, response_format, - query_fee=config.ORACLE_DEFAULT_QUERY_FEE, - ttl_type=config.ORACLE_DEFAULT_TTL_TYPE_DELTA, - ttl_value=config.ORACLE_DEFAULT_TTL_VALUE, - vm_version=ORACLE_DEFAULT_VM_VERSION, - fee=config.DEFAULT_FEE, - tx_ttl=config.DEFAULT_TX_TTL): + query_fee=defaults.ORACLE_QUERY_FEE, + ttl_type=defaults.ORACLE_TTL_TYPE, + ttl_value=defaults.ORACLE_TTL_VALUE, + vm_version=defaults.ORACLE_VM_VERSION, + fee=defaults.FEE, + tx_ttl=defaults.TX_TTL): """ Execute a registration of an oracle """ @@ -95,10 +95,10 @@ def register(self, account, query_format, response_format, return tx_signed def respond(self, account, query_id, response, - response_ttl_type=config.ORACLE_DEFAULT_TTL_TYPE_DELTA, - response_ttl_value=config.ORACLE_DEFAULT_RESPONSE_TTL_VALUE, - fee=config.DEFAULT_FEE, - tx_ttl=config.DEFAULT_TX_TTL): + response_ttl_type=defaults.ORACLE_TTL_TYPE, + response_ttl_value=defaults.ORACLE_RESPONSE_TTL_VALUE, + fee=defaults.FEE, + tx_ttl=defaults.TX_TTL): """ Post a response to an oracle query """ @@ -121,10 +121,10 @@ def respond(self, account, query_id, response, return tx_signed def extend(self, account, query_id, response, - ttl_type=config.ORACLE_DEFAULT_TTL_TYPE_DELTA, - ttl_value=config.ORACLE_DEFAULT_TTL_VALUE, - fee=config.DEFAULT_FEE, - tx_ttl=config.DEFAULT_TX_TTL): + ttl_type=defaults.ORACLE_TTL_TYPE, + ttl_value=defaults.ORACLE_TTL_VALUE, + fee=defaults.FEE, + tx_ttl=defaults.TX_TTL): """ Extend the ttl of an oracle """ From fd01a2c5898d1393c8fd20bacb5ca9b8ab309925 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 23:33:42 +0100 Subject: [PATCH 09/33] Api version check moved to the openapi client --- aeternity/openapi.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/aeternity/openapi.py b/aeternity/openapi.py index 446fef66..68eb3bbf 100644 --- a/aeternity/openapi.py +++ b/aeternity/openapi.py @@ -5,7 +5,7 @@ import namedtupled import logging -from aeternity.exceptions import UnsupportedEpochVersion, ConfigException +from aeternity.exceptions import UnsupportedNodeVersion, ConfigException import semver from . import __compatibility__ @@ -50,6 +50,7 @@ class OpenAPICli(object): def __init__(self, url, url_internal=None, debug=False, force_compatibility=False): try: + self.url, self.url_internal = url, url_internal # load the openapi json file from the node self.api_def = requests.get(f"{url}/api").json() self.api_version = self.api_def.get("info", {}).get("version", "unknown") @@ -58,12 +59,12 @@ def __init__(self, url, url_internal=None, debug=False, force_compatibility=Fals match_max = semver.match(self.api_version, __compatibility__.get("to_version")) if (not match_min or not match_max) and not force_compatibility: f, t = __compatibility__.get('from_version'), __compatibility__.get('to_version') - raise UnsupportedEpochVersion( + raise UnsupportedNodeVersion( f"unsupported node version {self.api_version}, supported version are {f} and {t}") except requests.exceptions.ConnectionError as e: - raise ConfigException(f"Error connecting to the node at {self.api_url}, connection unavailable") + raise ConfigException(f"Error connecting to the node at {self.url}, connection unavailable") except Exception as e: - raise UnsupportedEpochVersion(f"Unable to connect to the node: {e}") + raise UnsupportedNodeVersion(f"Unable to connect to the node: {e}") # enable printing debug messages self.debug = debug From 8a7866937938a32c962c1c26818718fbe0e50a4b Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 23:34:25 +0100 Subject: [PATCH 10/33] Update cli interface with new transaction management --- aeternity/__main__.py | 154 ++++++++++++++++-------------------------- aeternity/defaults.py | 2 + 2 files changed, 61 insertions(+), 95 deletions(-) diff --git a/aeternity/__main__.py b/aeternity/__main__.py index f8951946..a2146eaa 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -8,10 +8,10 @@ from aeternity import __version__ -from aeternity.node import NodeClient +from aeternity.node import NodeClient, Config from aeternity.transactions import TxSigner # from aeternity.oracle import Oracle, OracleQuery, NoOracleResponse -from . import utils, signing, aens, config +from . import utils, signing, aens, defaults, config from aeternity.contract import Contract from datetime import datetime, timezone @@ -20,8 +20,9 @@ logging.basicConfig(format='%(message)s', level=logging.INFO) -CTX_NODE_URL = 'EPOCH_URL' -CTX_NODE_URL_DEBUG = 'EPOCH_URL_DEBUG' +CTX_NODE_URL = 'NODE_URL' +CTX_NODE_URL_DEBUG = 'NODE_URL_DEBUG' +CTX_NODE_WS = 'NODE_URL_WS' CTX_KEY_PATH = 'KEY_PATH' CTX_QUIET = 'QUIET' CTX_AET_DOMAIN = 'AET_NAME' @@ -30,19 +31,21 @@ CTX_OUTPUT_JSON = 'CTX_OUTPUT_JSON' -def _node_cli(offline=False, native=True, network_id=None): +def _node_cli(network_id=None): try: ctx = click.get_current_context() # set the default configuration - url = ctx.obj.get(CTX_NODE_URL) - url_i = ctx.obj.get(CTX_NODE_URL_DEBUG) - url_i = url_i if url_i is not None else url - config.Config.set_defaults(config.Config( - external_url=url, - internal_url=url_i, + cfg = Config( + external_url=ctx.obj.get(CTX_NODE_URL), + internal_url=ctx.obj.get(CTX_NODE_URL_DEBUG), + websocket_url=ctx.obj.get(CTX_NODE_WS), force_compatibility=ctx.obj.get(CTX_FORCE_COMPATIBILITY), + blocking_mode=ctx.obj.get(CTX_BLOCKING_MODE), network_id=network_id - )) + ) + # load the aeternity node client + return NodeClient(cfg) + except config.ConfigException as e: print("Configuration error: ", e) exit(1) @@ -50,11 +53,6 @@ def _node_cli(offline=False, native=True, network_id=None): print(e) exit(1) - # load the aeternity node client - return NodeClient(blocking_mode=ctx.obj.get(CTX_BLOCKING_MODE), - offline=offline, - native=native) - def _account(keystore_name, password=None): """ @@ -126,7 +124,7 @@ def _po(label, value, offset=0, label_prefix=None): _pl(label, offset, value=value) -def _print_object(data, title=None): +def _print_object(data, title): ctx = click.get_current_context() if ctx.obj.get(CTX_OUTPUT_JSON, False): @@ -157,15 +155,15 @@ def _print_object(data, title=None): ] _sign_options = [ - click.option('--network-id', default=config.DEFAULT_NETWORK_ID, help="The network id to use when signing a transaction", show_default=True) + click.option('--network-id', default=defaults.NETWORK_ID, help="The network id to use when signing a transaction", show_default=True) ] _transaction_options = [ click.option('--debug-tx', 'debug_tx', is_flag=True, default=False, help='Use debug transaction generation endpoints from the node instead of the native python implementation'), - click.option('--ttl', 'ttl', type=int, default=config.DEFAULT_TX_TTL, + click.option('--ttl', 'ttl', type=int, default=defaults.TX_TTL, help=f'Set the transaction ttl (relative number, ex 100)', show_default=True), - click.option('--fee', 'fee', type=int, default=config.DEFAULT_FEE, + click.option('--fee', 'fee', type=int, default=defaults.FEE, help=f'Set the transaction fee', show_default=True), click.option('--nonce', 'nonce', type=int, default=0, help='Set the transaction nonce, if not set it will automatically generated'), ] @@ -326,7 +324,7 @@ def account_balance(keystore_name, password, force, wait, json_): @click.argument('keystore_name', required=True) @click.argument('recipient_id', required=True) @click.argument('amount', required=True, type=int) -@click.option('--ttl', default=config.DEFAULT_TX_TTL, help="Validity of the spend transaction in number of blocks (default forever)") +@click.option('--ttl', default=defaults.TX_TTL, help="Validity of the spend transaction in number of blocks (default forever)") @sign_options def account_spend(keystore_name, recipient_id, amount, ttl, password, network_id, force, wait, json_): try: @@ -334,16 +332,12 @@ def account_spend(keystore_name, recipient_id, amount, ttl, password, network_id account, keystore_path = _account(keystore_name, password=password) if not utils.is_valid_hash(recipient_id, prefix="ak"): raise ValueError("Invalid recipient address") - tx, tx_signed, signature, tx_hash = _node_cli(network_id=network_id).spend(account, recipient_id, amount, tx_ttl=ttl) - _print_object({ - "Sender account": account.get_address(), - "Recipient account": recipient_id, - 'Unsigned': tx, - 'Signed': tx_signed, - 'Hash': tx_hash - }, title='spend transaction') - except Exception as e: - print(e) + tx = _node_cli(network_id=network_id).spend(account, recipient_id, amount, tx_ttl=ttl) + _print_object(tx, title='spend transaction') + # except Exception as e: + # print(e) + finally: + pass @account.command('sign', help="Sign a transaction") @@ -357,16 +351,12 @@ def account_sign(keystore_name, password, network_id, unsigned_transaction, forc if not utils.is_valid_hash(unsigned_transaction, prefix="tx"): raise ValueError("Invalid transaction format") # force offline mode for the node_client - tx_signed, signature, tx_hash = TxSigner(account, network_id).sign_encode_transaction(unsigned_transaction) - _print_object({ - 'Signing account address': account.get_address(), - 'Signature': signature, - 'Unsigned': unsigned_transaction, - 'Signed': tx_signed, - 'Hash': tx_hash - }, title='signed transaction') - except Exception as e: - print(e) + tx = TxSigner(account, network_id).sign_encode_transaction(unsigned_transaction) + _print_object(tx, title='signed transaction') + # except Exception as e: + # print(e) + finally: + pass # _________ ____ ____ # | _ _ ||_ _||_ _| @@ -412,21 +402,12 @@ def tx_broadcast(signed_transaction, force, wait, json_): def tx_spend(sender_id, recipient_id, amount, debug_tx, ttl, fee, nonce, payload, force, wait, json_): try: set_global_options(force, wait, json_) - cli = _node_cli(native=not debug_tx) - if debug_tx: - nonce, ttl = cli._get_nonce_ttl(sender_id, ttl) + cli = _node_cli() tx = cli.tx_builder.tx_spend(sender_id, recipient_id, amount, payload, fee, ttl, nonce) # print the results - _print_object({ - "Sender account": sender_id, - "Recipient account": recipient_id, - "Amount": amount, - "TTL": ttl, - "fee": fee, - "Nonce": nonce, - "Payload": payload, - "Encoded": tx, - }, title='spend tx') + _print_object(tx, title='spend tx') + # finally: + # pass except Exception as e: print(e) @@ -448,9 +429,9 @@ def name(): @name.command('claim', help="Claim a domain name") @click.argument('keystore_name', required=True) @click.argument('domain', required=True) -@click.option("--name-ttl", default=config.DEFAULT_NAME_TTL, help=f'Lifetime of the name in blocks', show_default=True) -@click.option("--ttl", default=config.DEFAULT_TX_TTL, help=f'Lifetime of the claim request in blocks', show_default=True) -@click.option("--fee", default=config.DEFAULT_FEE, help=f'Transaction fee', show_default=True) +@click.option("--name-ttl", default=defaults.NAME_TTL, help=f'Lifetime of the name in blocks', show_default=True) +@click.option("--ttl", default=defaults.TX_TTL, help=f'Lifetime of the claim request in blocks', show_default=True) +@click.option("--fee", default=defaults.FEE, help=f'Transaction fee', show_default=True) @sign_options def name_register(keystore_name, domain, name_ttl, ttl, fee, password, network_id, force, wait, json_): try: @@ -488,8 +469,8 @@ def name_register(keystore_name, domain, name_ttl, ttl, fee, password, network_i @click.argument('keystore_name', required=True) @click.argument('domain', required=True) @click.argument('address', required=True) -@click.option("--name-ttl", default=config.DEFAULT_NAME_TTL, help=f'Lifetime of the claim in blocks', show_default=True) -@click.option("--ttl", default=config.DEFAULT_TX_TTL, help=f'Lifetime of the claim request in blocks', show_default=True) +@click.option("--name-ttl", default=defaults.NAME_TTL, help=f'Lifetime of the claim in blocks', show_default=True) +@click.option("--ttl", default=defaults.TX_TTL, help=f'Lifetime of the claim request in blocks', show_default=True) @sign_options def name_update(keystore_name, domain, address, name_ttl, ttl, password, network_id, force, wait, json_): """ @@ -503,13 +484,8 @@ def name_update(keystore_name, domain, address, name_ttl, ttl, password, network if name.status != name.Status.CLAIMED: print(f"Domain is {name.status} and cannot be transferred") exit(0) - _, signature, tx_hash = name.update(account, target=address, name_ttl=name_ttl, tx_ttl=ttl) - _print_object({ - "Transaction hash": tx_hash, - "Signature": signature, - "Sender account": account.get_address(), - "Target ID": address - }, title=f"Name {domain} status update") + tx = name.update(account, target=address, name_ttl=name_ttl, tx_ttl=ttl) + _print_object(tx, title=f"Name {domain} status update") except Exception as e: print(e) @@ -527,12 +503,8 @@ def name_revoke(keystore_name, domain, password, network_id, force, wait, json_) if name.status == name.Status.AVAILABLE: print("Domain is available, nothing to revoke") exit(0) - _, signature, tx_hash = name.revoke(account) - _print_object({ - "Transaction hash": tx_hash, - "Signature": signature, - "Sender account": account.get_address(), - }, title=f"Name {domain} status revoke") + tx = name.revoke(account) + _print_object(tx, title=f"Name {domain} status revoke") except Exception as e: print(e) @@ -554,13 +526,8 @@ def name_transfer(keystore_name, domain, address, password, network_id, force, w if name.status != name.Status.CLAIMED: print(f"Domain is {name.status} and cannot be transferred") exit(0) - _, signature, tx_hash = name.transfer_ownership(account, address) - _print_object({ - "Transaction hash": tx_hash, - "Signature": signature, - "Sender account": account.get_address(), - "Target ID": address - }, title=f"Name {domain} status transfer to {address}") + tx = name.transfer_ownership(account, address) + _print_object(tx, title=f"Name {domain} status transfer to {address}") except Exception as e: print(e) @@ -612,7 +579,7 @@ def contract_compile(contract_file): code = fp.read() c = _node_cli().Contract(Contract.SOPHIA) result = c.compile(code) - _print_object({"bytecode", result}) + _print_object({"bytecode", result}, title="contract") except Exception as e: print(e) @@ -620,7 +587,7 @@ def contract_compile(contract_file): @contract.command('deploy', help='Deploy a contract on the chain') @click.argument('keystore_name', required=True) @click.argument("contract_file", required=True) -@click.option("--gas", default=config.CONTRACT_DEFAULT_GAS, help='Amount of gas to deploy the contract', show_default=True) +@click.option("--gas", default=defaults.CONTRACT_GAS, help='Amount of gas to deploy the contract', show_default=True) @sign_options def contract_deploy(keystore_name, contract_file, gas, password, network_id, force, wait, json_): """ @@ -656,7 +623,7 @@ def contract_deploy(keystore_name, contract_file, gas, password, network_id, for "Contract id": contract.id, "Transaction hash": tx.tx_hash, "Deploy descriptor": deploy_descriptor, - }) + }, title="contract") except Exception as e: print(e) @@ -667,7 +634,7 @@ def contract_deploy(keystore_name, contract_file, gas, password, network_id, for @click.argument("function", required=True) @click.argument("params", required=True) @click.argument("return_type", required=True) -@click.option("--gas", default=config.CONTRACT_DEFAULT_GAS, help='Amount of gas to deploy the contract', show_default=True) +@click.option("--gas", default=defaults.CONTRACT_GAS, help='Amount of gas to deploy the contract', show_default=True) @sign_options def contract_call(keystore_name, deploy_descriptor, function, params, return_type, gas, password, network_id, force, wait, json_): try: @@ -681,13 +648,8 @@ def contract_call(keystore_name, deploy_descriptor, function, params, return_typ account, _ = _account(keystore_name, password=password) contract = _node_cli(network_id=network_id).Contract(source, bytecode=bytecode, address=address) - result = contract.tx_call(account, function, params, gas=gas) - _print_object({ - 'Contract id': contract.id, - 'Gas price': result.gas_price, - 'Gas used': result.gas_used, - 'Return value (encoded)': result.return_value, - }) + tx, result = contract.tx_call(account, function, params, gas=gas) + _print_object(tx, "contract call") if result.return_type == 'ok': value, remote_type = contract.decode_data(result.return_value, return_type) _print_object({ @@ -804,8 +766,9 @@ def chain_top(force, wait, json_): Print the information of the top block of the chain. """ set_global_options(force, wait, json_) - data = _node_cli().get_top_block() - _print_object(data) + cli = _node_cli() + data = cli.get_top_block() + _print_object(data, f"top for node at {cli.config.api_url} ") @chain.command('status') @@ -815,8 +778,9 @@ def chain_status(force, wait, json_): Print the node node status. """ set_global_options(force, wait, json_) - data = _node_cli().get_status() - _print_object(data) + cli = _node_cli() + data = cli.get_status() + _print_object(data, f"status for node at {cli.config.api_url} ") @chain.command('play') diff --git a/aeternity/defaults.py b/aeternity/defaults.py index f2dad518..637ad6b3 100644 --- a/aeternity/defaults.py +++ b/aeternity/defaults.py @@ -13,6 +13,7 @@ # default relative ttl in number of blocks for executing transaction on the chain MAX_TX_TTL = 256 TX_TTL = 0 +FEE = 0 # contracts CONTRACT_GAS = 1000000000 CONTRACT_GAS_PRICE = 1000000000 @@ -21,6 +22,7 @@ # oracles # https://github.com/aeternity/protocol/blob/master/oracles/oracles.md#technical-aspects-of-oracle-operations ORACLE_VM_VERSION = identifiers.NO_VM +ORACLE_TTL_TYPE = identifiers.ORACLE_TTL_TYPE_DELTA ORACLE_QUERY_FEE = 0 ORACLE_TTL_VALUE = 500 ORACLE_QUERY_TTL_VALUE = 10 From 18a6a5bf3ad29bc55bcf1ab6b13a5ce5a9977c4e Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sat, 2 Mar 2019 23:34:37 +0100 Subject: [PATCH 11/33] Update tests --- tests/conftest.py | 13 ++++++------- tests/test_cli.py | 12 ++++++------ tests/test_contract.py | 12 +++--------- tests/test_node.py | 3 +-- tests/test_oracle.py | 7 +++---- tests/test_signing.py | 8 ++++---- tests/test_transactions.py | 9 ++++++--- 7 files changed, 29 insertions(+), 35 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4e082dec..3988a1f6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,8 +6,7 @@ import random import string from aeternity.signing import Account -from aeternity.config import Config -from aeternity.node import NodeClient +from aeternity.node import NodeClient, Config PUBLIC_KEY = os.environ.get('WALLET_PUB') @@ -42,15 +41,15 @@ def chain_fixture(scope="module"): # set the key folder as environment variables genesis = Account.from_public_private_key_strings(PUBLIC_KEY, PRIVATE_KEY) - Config.set_defaults(Config( + # Instantiate the node client for the tests + NODE_CLI = NodeClient(Config( external_url=NODE_URL, internal_url=NODE_URL_DEBUG, - network_id=NETWORK_ID + network_id=NETWORK_ID, + blocking_mode=True, + debug=True )) - # Instantiate the node client for the tests - NODE_CLI = NodeClient(blocking_mode=True, debug=True, native=False) - NODE_CLI.spend(genesis, ACCOUNT.get_address(), 2000000000000000000) a = NODE_CLI.get_account_by_pubkey(pubkey=ACCOUNT.get_address()) print(f"Test account is {ACCOUNT.get_address()} with balance {a.balance}") diff --git a/tests/test_cli.py b/tests/test_cli.py index f7350619..f423151f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -136,10 +136,10 @@ def test_cli_inspect_transaction_by_hash(chain_fixture): # fill the account from genesys na = Account.generate() amount = random.randint(50, 150) - _, _, _, tx_hash = chain_fixture.NODE_CLI.spend(chain_fixture.ACCOUNT, na.get_address(), amount) + tx = chain_fixture.NODE_CLI.spend(chain_fixture.ACCOUNT, na.get_address(), amount) # now inspect the transaction - j = call_aecli('inspect', tx_hash) - assert j.get("hash") == tx_hash + j = call_aecli('inspect', tx.hash) + assert j.get("hash") == tx.hash assert j.get("block_height") > 0 assert j.get("tx", {}).get("recipient_id") == na.get_address() assert j.get("tx", {}).get("sender_id") == chain_fixture.ACCOUNT.get_address() @@ -164,11 +164,11 @@ def test_cli_phases_spend(chain_fixture, tempdir): nonce = chain_fixture.NODE_CLI.get_account_by_pubkey(pubkey=chain_fixture.ACCOUNT.get_address()).nonce + 1 j = call_aecli('tx', 'spend', chain_fixture.ACCOUNT.get_address(), recipient_id, '100', '--nonce', f'{nonce}') # assert chain_fixture.ACCOUNT.get_address == j.get("Sender account") - assert recipient_id == j.get("Recipient account") + assert recipient_id == j.get("data", {}).get("recipient_id") # step 2, sign the transaction - tx_unsigned = j.get("Encoded") + tx_unsigned = j.get("tx") s = call_aecli('account', 'sign', account_path, tx_unsigned, '--password', 'aeternity_bc', '--network-id', NETWORK_ID) - tx_signed = s.get("Signed") + tx_signed = s.get("tx") # recipient_account = chain_fixture.NODE_CLI.get_account_by_pubkey(pubkey=recipient_id) # assert recipient_account.balance == 0 # step 3 broadcast diff --git a/tests/test_contract.py b/tests/test_contract.py index d0f5fd4a..fa794ba2 100644 --- a/tests/test_contract.py +++ b/tests/test_contract.py @@ -37,7 +37,7 @@ def _sophia_contract_tx_call_online(node_cli, account): print("contract: ", contract.address) print("tx contract: ", tx) - _, _, _, _, result = contract.tx_call(account, 'main', '42', gas=500000) + _, result = contract.tx_call(account, 'main', '42', gas=500000) assert result is not None assert result.return_type == 'ok' print("return", result.return_value) @@ -51,36 +51,30 @@ def _sophia_contract_tx_call_online(node_cli, account): def test_sophia_contract_tx_create_native(chain_fixture): # save settings and go online - original = chain_fixture.NODE_CLI.set_native(True) _sophia_contract_tx_create_online(chain_fixture.NODE_CLI, chain_fixture.ACCOUNT) # restore settings - chain_fixture.NODE_CLI.set_native(original) def test_sophia_contract_tx_call_native(chain_fixture): # save settings and go online - original = chain_fixture.NODE_CLI.set_native(True) _sophia_contract_tx_call_online(chain_fixture.NODE_CLI, chain_fixture.ACCOUNT) # restore settings - chain_fixture.NODE_CLI.set_native(original) @pytest.mark.skip('Debug transaction disabled') def test_sophia_contract_tx_create_debug(chain_fixture): + # TODO: create a debug impl and test # save settings and go online - original = chain_fixture.NODE_CLI.set_native(False) _sophia_contract_tx_create_online(chain_fixture.NODE_CLI, chain_fixture.ACCOUNT) # restore settings - chain_fixture.NODE_CLI.set_native(original) @pytest.mark.skip('Debug transaction disabled') def test_sophia_contract_tx_call_debug(chain_fixture): + # TODO: create a debug impl and test # save settings and go online - original = chain_fixture.NODE_CLI.set_native(False) _sophia_contract_tx_call_online(chain_fixture.NODE_CLI, chain_fixture.ACCOUNT) # restore settings - chain_fixture.NODE_CLI.set_native(original) # test contracts diff --git a/tests/test_node.py b/tests/test_node.py index dc477f8f..11cd9348 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -13,10 +13,9 @@ def _test_node_spend(node_cli, sender_account): @pytest.mark.skip('Debug transaction disabled') def test_node_spend_debug(chain_fixture): - chain_fixture.NODE_CLI.set_native(False) + # TODO: create a debug impl and test _test_node_spend(chain_fixture.NODE_CLI, chain_fixture.ACCOUNT) def test_node_spend_native(chain_fixture): - chain_fixture.NODE_CLI.set_native(True) _test_node_spend(chain_fixture.NODE_CLI, chain_fixture.ACCOUNT) diff --git a/tests/test_oracle.py b/tests/test_oracle.py index 50585055..dc47893b 100644 --- a/tests/test_oracle.py +++ b/tests/test_oracle.py @@ -37,7 +37,7 @@ def _test_oracle_registration(node_cli, account): query_format="{'city': str}", response_format="{'temp_c': int}", ) - tx, tx_signed, signature, tx_hash = oracle.register(**weather_oracle) + oracle.register(**weather_oracle) assert oracle.id == account.get_address().replace("ak_", "ok_") oracle_api_response = node_cli.get_oracle_by_pubkey(pubkey=oracle.id) assert oracle_api_response.id == oracle.id @@ -51,7 +51,7 @@ def _test_oracle_query(node_cli, oracle, sender, query): def _test_oracle_respond(oracle, query, account, response): - tx, tx_signed, signature, tx_hash = oracle.respond(account, query.id, response) + oracle.respond(account, query.id, response) def _test_oracle_response(query, expected): @@ -64,7 +64,7 @@ def _test_oracle_response(query, expected): @pytest.mark.skip('Debug transaction disabled') def test_oracle_lifecycle_debug(chain_fixture): # registration - chain_fixture.NODE_CLI.set_native(False) + # TODO: create a debug impl and test oracle = _test_oracle_registration(chain_fixture.NODE_CLI, chain_fixture.ACCOUNT) # query query = _test_oracle_query(chain_fixture.NODE_CLI, oracle, chain_fixture.ACCOUNT_1, "{'city': 'Berlin'}") @@ -75,7 +75,6 @@ def test_oracle_lifecycle_debug(chain_fixture): def test_oracle_lifecycle_native(chain_fixture): # registration - chain_fixture.NODE_CLI.set_native(True) oracle = _test_oracle_registration(chain_fixture.NODE_CLI, chain_fixture.ACCOUNT_1) # query query = _test_oracle_query(chain_fixture.NODE_CLI, oracle, chain_fixture.ACCOUNT, "{'city': 'Sofia'}") diff --git a/tests/test_signing.py b/tests/test_signing.py index deaec33a..81e59c4b 100644 --- a/tests/test_signing.py +++ b/tests/test_signing.py @@ -13,12 +13,12 @@ def test_signing_create_transaction_signature(chain_fixture): # create a spend transaction nonce, ttl = chain_fixture.NODE_CLI._get_nonce_ttl(chain_fixture.ACCOUNT.get_address(), TEST_TTL) tx = chain_fixture.NODE_CLI.tx_builder.tx_spend(chain_fixture.ACCOUNT.get_address(), receiver_address, 321, "test test ", 0, ttl, nonce) - tx_signed, signature, tx_hash = chain_fixture.NODE_CLI.sign_transaction(chain_fixture.ACCOUNT, tx) + tx_signed = chain_fixture.NODE_CLI.sign_transaction(chain_fixture.ACCOUNT, tx.tx) # this call will fail if the hashes of the transaction do not match - chain_fixture.NODE_CLI.broadcast_transaction(tx_signed) + chain_fixture.NODE_CLI.broadcast_transaction(tx_signed.tx) # make sure this works for very short block times - spend_tx = chain_fixture.NODE_CLI.get_transaction_by_hash(hash=tx_hash) - assert spend_tx.signatures[0] == signature + spend_tx = chain_fixture.NODE_CLI.get_transaction_by_hash(hash=tx_signed.hash) + assert spend_tx.signatures[0] == tx_signed.signature def test_signing_is_valid_hash(): diff --git a/tests/test_transactions.py b/tests/test_transactions.py index 1fa29644..8543f47d 100644 --- a/tests/test_transactions.py +++ b/tests/test_transactions.py @@ -1,12 +1,13 @@ from aeternity.signing import Account from aeternity import transactions +import pytest def _execute_test(test_cases, NODE_CLI): for tt in test_cases: # get a native transaction - txbn = transactions.TxBuilder(api=NODE_CLI, native=True) + txbn = transactions.TxBuilder() txn = getattr(txbn, tt.get("tx"))(**tt["native"]) # get a debug transaction txbd = transactions.TxBuilder(api=NODE_CLI, native=False) @@ -62,6 +63,7 @@ def test_transaction_fee_calculation(): # get a debug transaction +@pytest.mark.skip("Debug trasnsaction disabled") def test_transaction_spend(chain_fixture): recipient_id = Account.generate().get_address() @@ -88,10 +90,10 @@ def test_transaction_spend(chain_fixture): for tt in tts: # get a native transaction - txbn = transactions.TxBuilder(api=chain_fixture.NODE_CLI, native=True) + txbn = transactions.TxBuilder() txn = txbn.tx_spend(tt["native"][0], tt["native"][1], tt["native"][2], tt["native"][3], tt["native"][4], tt["native"][5], tt["native"][6]) # get a debug transaction - txbd = transactions.TxBuilder(api=chain_fixture.NODE_CLI, native=False) + txbd = transactions.TxBuilder() txd = txbd.tx_spend(tt["debug"][0], tt["debug"][1], tt["debug"][2], tt["debug"][3], tt["debug"][4], tt["debug"][5], tt["debug"][6]) # theys should be the same if tt["match"]: @@ -100,6 +102,7 @@ def test_transaction_spend(chain_fixture): assert txn != txd +@pytest.mark.skip("Debug trasnsaction disabled") def test_transaction_oracle_register(chain_fixture): # account_id, recipient_id, amount, payload, fee, ttl, nonce tts = [ From 61dd64b16abc69d2add1ddf461542622e4185395 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 00:03:57 +0100 Subject: [PATCH 12/33] Fix defaults parameters --- aeternity/node.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/aeternity/node.py b/aeternity/node.py index 63cd71af..9f52d6be 100644 --- a/aeternity/node.py +++ b/aeternity/node.py @@ -5,7 +5,7 @@ from aeternity.transactions import TxSigner from aeternity.signing import Account from aeternity.openapi import OpenAPIClientException -from aeternity import config, aens, openapi, transactions, contract, oracles, defaults +from aeternity import aens, openapi, transactions, contract, oracles, defaults, identifiers from aeternity.exceptions import TransactionWaitTimeoutExpired, TransactionHashMismatch @@ -104,10 +104,10 @@ def get_next_nonce(self, account_address): def _get_nonce_ttl(self, account_address: str, relative_ttl: int): """ - Helper method to compute both ttl and nonce for an account + Helper method to compute both absoulute ttl and nonce for an account :return: (nonce, ttl) """ - ttl = self.compute_absolute_ttl(relative_ttl) + ttl = self.compute_absolute_ttl(relative_ttl) if relative_ttl > 0 else 0 nonce = self.get_next_nonce(account_address) return nonce, ttl @@ -129,10 +129,10 @@ def get_block_by_hash(self, hash=None): block = None if hash is None: return block - if hash.startswith("kh_"): + if hash.startswith(f"{identifiers.KEY_BLOCK_HASH}_"): # key block block = self.api.get_key_block_by_hash(hash=hash) - elif hash.startswith("mh_"): + elif hash.startswith(f"{identifiers.MICRO_BLOCK_HASH}_"): # micro block block = self.api.get_micro_block_header_by_hash(hash=hash) return block @@ -159,21 +159,28 @@ def sign_transaction(self, account: Account, tx: str) -> (str, str, str): tx = s.sign_encode_transaction(tx) return tx - def spend(self, account: Account, recipient_id, amount, payload="", fee=config.DEFAULT_FEE, tx_ttl=config.DEFAULT_TX_TTL): + def spend(self, account: Account, + recipient_id: str, + amount: int, + payload: str="", + fee: int=defaults.FEE, + tx_ttl: int=defaults.TX_TTL): """ Create and execute a spend transaction """ - # retrieve the nonce and ttl - nonce, tx_ttl = self._get_nonce_ttl(account.get_address(), tx_ttl) + # retrieve the nonce + account.nonce = self.get_next_nonce(account.get_address()) if account.nonce == 0 else account.nonce + 1 + # retrieve ttl + tx_ttl = self.compute_absolute_ttl(tx_ttl) # build the transaction - tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, fee, tx_ttl, nonce) + tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, fee, tx_ttl, account.nonce) # execute the transaction tx = self.sign_transaction(account, tx.tx) # post the transaction self.broadcast_transaction(tx.tx, tx_hash=tx.hash) return tx - def wait_for_transaction(self, tx_hash, max_retries=config.MAX_RETRIES, polling_interval=config.POLLING_INTERVAL): + def wait_for_transaction(self, tx_hash, max_retries=defaults.MAX_RETRIES, polling_interval=defaults.POLLING_INTERVAL): """ Wait for a transaction to be mined for an account The method will wait for a specific transaction to be included in the chain, From 4f47eaac119fb60878c25790b66111b22bb0ecfb Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 00:04:16 +0100 Subject: [PATCH 13/33] Add field nonce to account --- aeternity/signing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aeternity/signing.py b/aeternity/signing.py index 5c7f844f..fce07b2e 100644 --- a/aeternity/signing.py +++ b/aeternity/signing.py @@ -23,6 +23,7 @@ def __init__(self, signing_key, verifying_key): self.verifying_key = verifying_key pub_key = self.verifying_key.encode(encoder=RawEncoder) self.address = hashing.encode(ACCOUNT_ID, pub_key) + self.nonce = 0 def get_address(self): """get the keypair public_key base58 encoded and prefixed (ak_...)""" From 64ab6f000ea522fc1a9c75799a1fe558572ff1d5 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 00:04:39 +0100 Subject: [PATCH 14/33] Fix return type for transactions methods --- aeternity/transactions.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/aeternity/transactions.py b/aeternity/transactions.py index e9af4eca..9c45d7b6 100644 --- a/aeternity/transactions.py +++ b/aeternity/transactions.py @@ -688,7 +688,7 @@ def compute_tx_hash(encoded_tx: str) -> str: tx_raw = decode(encoded_tx) return hash_encode(idf.TRANSACTION_HASH, tx_raw) - def tx_spend(self, account_id, recipient_id, amount, payload, fee, ttl, nonce)-> str: + def tx_spend(self, account_id, recipient_id, amount, payload, fee, ttl, nonce)-> tuple: """ create a spend transaction :param account_id: the public key of the sender @@ -716,7 +716,7 @@ def tx_spend(self, account_id, recipient_id, amount, payload, fee, ttl, nonce)-> # NAMING # - def tx_name_preclaim(self, account_id, commitment_id, fee, ttl, nonce)-> str: + def tx_name_preclaim(self, account_id, commitment_id, fee, ttl, nonce)-> tuple: """ create a preclaim transaction :param account_id: the account registering the name @@ -737,7 +737,7 @@ def tx_name_preclaim(self, account_id, commitment_id, fee, ttl, nonce)-> str: return _tx_native(op=PACK_TX, **body) # sreturn self.api.post_name_preclaim(body=body).tx - def tx_name_claim(self, account_id, name, name_salt, fee, ttl, nonce)-> str: + def tx_name_claim(self, account_id, name, name_salt, fee, ttl, nonce)-> tuple: """ create a preclaim transaction :param account_id: the account registering the name @@ -760,7 +760,7 @@ def tx_name_claim(self, account_id, name, name_salt, fee, ttl, nonce)-> str: return _tx_native(op=PACK_TX, **body) # return self.api.post_name_claim(body=body).tx - def tx_name_update(self, account_id, name_id, pointers, name_ttl, client_ttl, fee, ttl, nonce)-> str: + def tx_name_update(self, account_id, name_id, pointers, name_ttl, client_ttl, fee, ttl, nonce)-> tuple: """ create an update transaction :param account_id: the account updating the name @@ -787,7 +787,7 @@ def tx_name_update(self, account_id, name_id, pointers, name_ttl, client_ttl, fe return _tx_native(op=PACK_TX, **body) # return self.api.post_name_update(body=body).tx - def tx_name_transfer(self, account_id, name_id, recipient_id, fee, ttl, nonce)-> str: + def tx_name_transfer(self, account_id, name_id, recipient_id, fee, ttl, nonce)-> tuple: """ create a transfer transaction :param account_id: the account transferring the name @@ -810,7 +810,7 @@ def tx_name_transfer(self, account_id, name_id, recipient_id, fee, ttl, nonce)-> return _tx_native(op=PACK_TX, **body) # return self.api.post_name_transfer(body=body).tx - def tx_name_revoke(self, account_id, name_id, fee, ttl, nonce)-> str: + def tx_name_revoke(self, account_id, name_id, fee, ttl, nonce)-> tuple: """ create a revoke transaction :param account_id: the account revoking the name @@ -834,7 +834,7 @@ def tx_name_revoke(self, account_id, name_id, fee, ttl, nonce)-> str: # CONTRACTS - def tx_contract_create(self, owner_id, code, call_data, amount, deposit, gas, gas_price, vm_version, abi_version, fee, ttl, nonce)-> str: + def tx_contract_create(self, owner_id, code, call_data, amount, deposit, gas, gas_price, vm_version, abi_version, fee, ttl, nonce)-> tuple: """ Create a contract transaction :param owner_id: the account creating the contract @@ -869,7 +869,7 @@ def tx_contract_create(self, owner_id, code, call_data, amount, deposit, gas, ga return _tx_native(op=PACK_TX, **body) # return tx.tx, tx.contract_id - def tx_contract_call(self, caller_id, contract_id, call_data, function, arg, amount, gas, gas_price, abi_version, fee, ttl, nonce)-> str: + def tx_contract_call(self, caller_id, contract_id, call_data, function, arg, amount, gas, gas_price, abi_version, fee, ttl, nonce)-> tuple: """ Create a contract call :param caller_id: the account creating the contract @@ -909,7 +909,7 @@ def tx_contract_call(self, caller_id, contract_id, call_data, function, arg, amo def tx_oracle_register(self, account_id, query_format, response_format, query_fee, ttl_type, ttl_value, vm_version, - fee, ttl, nonce)-> str: + fee, ttl, nonce)-> tuple: """ Create a register oracle transaction """ @@ -934,7 +934,7 @@ def tx_oracle_register(self, account_id, def tx_oracle_query(self, oracle_id, sender_id, query, query_fee, query_ttl_type, query_ttl_value, response_ttl_type, response_ttl_value, - fee, ttl, nonce)-> str: + fee, ttl, nonce)-> tuple: """ Create a oracle query transaction """ @@ -964,7 +964,7 @@ def tx_oracle_query(self, oracle_id, sender_id, query, def tx_oracle_respond(self, oracle_id, query_id, response, response_ttl_type, response_ttl_value, - fee, ttl, nonce)-> str: + fee, ttl, nonce)-> tuple: """ Create a oracle response transaction """ @@ -988,7 +988,7 @@ def tx_oracle_respond(self, oracle_id, query_id, response, def tx_oracle_extend(self, oracle_id, ttl_type, ttl_value, - fee, ttl, nonce)-> str: + fee, ttl, nonce)-> tuple: """ Create a oracle extends transaction """ From bcc212b5f630cb2320e6f178c765ebeba98fb0ed Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 00:06:23 +0100 Subject: [PATCH 15/33] Fix lint error --- aeternity/contract.py | 1 - 1 file changed, 1 deletion(-) diff --git a/aeternity/contract.py b/aeternity/contract.py index 63c959f0..01de6e69 100644 --- a/aeternity/contract.py +++ b/aeternity/contract.py @@ -112,7 +112,6 @@ def tx_create(self, tx_signed = self.client.sign_transaction(account, tx) # post the transaction to the chain self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) - return tx except OpenAPIClientException as e: raise ContractError(e) From a1165c0ac68fc35ba2dddd5b5fedab55bcd7ea3a Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 09:56:04 +0100 Subject: [PATCH 16/33] Estimate expiration date for ttl Add cli command to calculate the ttl --- aeternity/__main__.py | 37 ++++++++++++++++++++++++++----------- aeternity/node.py | 21 ++++++++++++++------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/aeternity/__main__.py b/aeternity/__main__.py index a2146eaa..1fa0df43 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -118,6 +118,9 @@ def _po(label, value, offset=0, label_prefix=None): o = offset + 2 for i, x in enumerate(value): _po(f"{label[:-1]} #{i+1}", x, o) + elif isinstance(value, datetime): + val = value.strftime("%Y-%m-%d %H:%M") + _pl(label, offset, value=val) else: if label.lower() == "time": value = datetime.fromtimestamp(value / 1000, timezone.utc).isoformat('T') @@ -155,7 +158,7 @@ def _print_object(data, title): ] _sign_options = [ - click.option('--network-id', default=defaults.NETWORK_ID, help="The network id to use when signing a transaction", show_default=True) + click.option('--network-id', default=None, help="The network id to use when signing a transaction") ] _transaction_options = [ @@ -334,10 +337,8 @@ def account_spend(keystore_name, recipient_id, amount, ttl, password, network_id raise ValueError("Invalid recipient address") tx = _node_cli(network_id=network_id).spend(account, recipient_id, amount, tx_ttl=ttl) _print_object(tx, title='spend transaction') - # except Exception as e: - # print(e) - finally: - pass + except Exception as e: + print(e) @account.command('sign', help="Sign a transaction") @@ -353,10 +354,8 @@ def account_sign(keystore_name, password, network_id, unsigned_transaction, forc # force offline mode for the node_client tx = TxSigner(account, network_id).sign_encode_transaction(unsigned_transaction) _print_object(tx, title='signed transaction') - # except Exception as e: - # print(e) - finally: - pass + except Exception as e: + print(e) # _________ ____ ____ # | _ _ ||_ _||_ _| @@ -406,8 +405,6 @@ def tx_spend(sender_id, recipient_id, amount, debug_tx, ttl, fee, nonce, payload tx = cli.tx_builder.tx_spend(sender_id, recipient_id, amount, payload, fee, ttl, nonce) # print the results _print_object(tx, title='spend tx') - # finally: - # pass except Exception as e: print(e) @@ -759,6 +756,24 @@ def chain(force, wait, json_): pass +@chain.command('ttl') +@click.argument('relative_ttl', type=int) +@global_options +def chain_ttl(relative_ttl, force, wait, json_): + """ + Print the information of the top block of the chain. + """ + try: + if relative_ttl < 0: + print("Error: the relative ttl must be a positive number") + set_global_options(force, wait, json_) + cli = _node_cli() + data = cli.compute_absolute_ttl(relative_ttl) + _print_object(data, f"ttl for node at {cli.config.api_url} ") + except Exception as e: + print("Error:", e) + + @chain.command('top') @global_options def chain_top(force, wait, json_): diff --git a/aeternity/node.py b/aeternity/node.py index 9f52d6be..970de91d 100644 --- a/aeternity/node.py +++ b/aeternity/node.py @@ -1,12 +1,13 @@ import logging import time import random +from datetime import datetime, timedelta +import namedtupled from aeternity.transactions import TxSigner from aeternity.signing import Account from aeternity.openapi import OpenAPIClientException from aeternity import aens, openapi, transactions, contract, oracles, defaults, identifiers - from aeternity.exceptions import TransactionWaitTimeoutExpired, TransactionHashMismatch logger = logging.getLogger(__name__) @@ -46,6 +47,8 @@ def __init__(self, self.contract_gas_price = kwargs.get("contract_gas_price", defaults.CONTRACT_GAS_PRICE) # oracles default self.orcale_ttl_type = kwargs.get("oracle_ttl_type", defaults.ORACLE_TTL_TYPE) + # chain defaults + self.key_block_interval = kwargs.get("key_block_interval", defaults.KEY_BLOCK_INTERVAL) # debug self.debug = kwargs.get("debug", False) @@ -84,11 +87,15 @@ def compute_absolute_ttl(self, relative_ttl): Compute the absolute ttl by adding the ttl to the current height of the chain :param relative_ttl: the relative ttl, if 0 will set the ttl to 0 """ - absolute_ttl = 0 + ttl = dict( + absolute_ttl=0, + height=self.get_current_key_block_height(), + estimated_expiration=datetime.now() + ) if relative_ttl > 0: - height = self.get_current_key_block_height() - absolute_ttl = height + relative_ttl - return absolute_ttl + ttl["absolute_ttl"] = ttl["height"] + relative_ttl + ttl["estimated_expiration"] = datetime.now() + timedelta(minutes=self.config.key_block_interval * relative_ttl) + return namedtupled.map(ttl, _nt_name="TTL") def get_next_nonce(self, account_address): """ @@ -109,7 +116,7 @@ def _get_nonce_ttl(self, account_address: str, relative_ttl: int): """ ttl = self.compute_absolute_ttl(relative_ttl) if relative_ttl > 0 else 0 nonce = self.get_next_nonce(account_address) - return nonce, ttl + return nonce, ttl.absolute_ttl def get_top_block(self): """ @@ -173,7 +180,7 @@ def spend(self, account: Account, # retrieve ttl tx_ttl = self.compute_absolute_ttl(tx_ttl) # build the transaction - tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, fee, tx_ttl, account.nonce) + tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, fee, tx_ttl.absolute_ttl, account.nonce) # execute the transaction tx = self.sign_transaction(account, tx.tx) # post the transaction From bcef821c7f20f620be576551df3f7b690573c08b Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 10:01:45 +0100 Subject: [PATCH 17/33] Add network-id command to print the network id --- aeternity/__main__.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/aeternity/__main__.py b/aeternity/__main__.py index 1fa0df43..2993f4d2 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -780,10 +780,13 @@ def chain_top(force, wait, json_): """ Print the information of the top block of the chain. """ - set_global_options(force, wait, json_) - cli = _node_cli() - data = cli.get_top_block() - _print_object(data, f"top for node at {cli.config.api_url} ") + try: + set_global_options(force, wait, json_) + cli = _node_cli() + data = cli.get_top_block() + _print_object(data, f"top for node at {cli.config.api_url} ") + except Exception as e: + print("Error:", e) @chain.command('status') @@ -792,10 +795,28 @@ def chain_status(force, wait, json_): """ Print the node node status. """ - set_global_options(force, wait, json_) - cli = _node_cli() - data = cli.get_status() - _print_object(data, f"status for node at {cli.config.api_url} ") + try: + set_global_options(force, wait, json_) + cli = _node_cli() + data = cli.get_status() + _print_object(data, f"status for node at {cli.config.api_url} ") + except Exception as e: + print("Error:", e) + + +@chain.command('network-id', help="Retrieve the network id of the target node") +@global_options +def chain_network_id(force, wait, json_): + """ + Print the node node status. + """ + try: + set_global_options(force, wait, json_) + cli = _node_cli() + data = cli.get_status().network_id + _print_object({"Network ID": data}, f"network id for node at {cli.config.api_url} ") + except Exception as e: + print("Error:", e) @chain.command('play') From b44019512bfa3c92602a4355071cd84cc23ae8ae Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 14:32:25 +0100 Subject: [PATCH 18/33] Refactor naming to avoid too early claims --- aeternity/__main__.py | 134 ++++++++++++-------------------------- aeternity/aens.py | 55 ++++++++-------- aeternity/defaults.py | 4 +- aeternity/exceptions.py | 7 +- aeternity/hashing.py | 18 +++++ aeternity/node.py | 9 +-- aeternity/openapi.py | 2 +- aeternity/transactions.py | 5 +- tests/test_aens.py | 8 +-- 9 files changed, 111 insertions(+), 131 deletions(-) diff --git a/aeternity/__main__.py b/aeternity/__main__.py index 2993f4d2..88f7f464 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -162,8 +162,6 @@ def _print_object(data, title): ] _transaction_options = [ - click.option('--debug-tx', 'debug_tx', is_flag=True, default=False, - help='Use debug transaction generation endpoints from the node instead of the native python implementation'), click.option('--ttl', 'ttl', type=int, default=defaults.TX_TTL, help=f'Set the transaction ttl (relative number, ex 100)', show_default=True), click.option('--fee', 'fee', type=int, default=defaults.FEE, @@ -211,8 +209,8 @@ def set_global_options(force, wait, json_): @click.group() @click.pass_context @click.version_option() -@click.option('--url', '-u', default='https://sdk-mainnet.aepps.com', envvar='EPOCH_URL', help='Aeternity node url', metavar='URL') -@click.option('--debug-url', '-d', default=None, envvar='EPOCH_URL_DEBUG', metavar='URL') +@click.option('--url', '-u', default='https://sdk-mainnet.aepps.com', envvar='NODE_URL', help='Aeternity node url', metavar='URL') +@click.option('--debug-url', '-d', default=None, envvar='NODE_URL_DEBUG', metavar='URL') @global_options @click.version_option(version=__version__) def cli(ctx, url, debug_url, force, wait, json_): @@ -398,7 +396,7 @@ def tx_broadcast(signed_transaction, force, wait, json_): @click.argument('amount', required=True, type=int) @transaction_options @click.option('--payload', default="", help="Spend transaction payload") -def tx_spend(sender_id, recipient_id, amount, debug_tx, ttl, fee, nonce, payload, force, wait, json_): +def tx_spend(sender_id, recipient_id, amount, ttl, fee, nonce, payload, force, wait, json_): try: set_global_options(force, wait, json_) cli = _node_cli() @@ -423,14 +421,12 @@ def name(): pass -@name.command('claim', help="Claim a domain name") +@name.command('pre-claim', help="Pre-claim a domain name") @click.argument('keystore_name', required=True) @click.argument('domain', required=True) -@click.option("--name-ttl", default=defaults.NAME_TTL, help=f'Lifetime of the name in blocks', show_default=True) -@click.option("--ttl", default=defaults.TX_TTL, help=f'Lifetime of the claim request in blocks', show_default=True) -@click.option("--fee", default=defaults.FEE, help=f'Transaction fee', show_default=True) +@transaction_options @sign_options -def name_register(keystore_name, domain, name_ttl, ttl, fee, password, network_id, force, wait, json_): +def name_pre_claim(keystore_name, domain, ttl, fee, nonce, password, network_id, force, wait, json_): try: set_global_options(force, wait, json_) account, _ = _account(keystore_name, password=password) @@ -440,24 +436,34 @@ def name_register(keystore_name, domain, name_ttl, ttl, fee, password, network_i print("Domain not available") exit(0) # preclaim - tx, tx_signed, sig, tx_hash = name.preclaim(account, fee, ttl) - _print_object({ - 'Signing account address': account.get_address(), - 'Signature': sig, - 'Unsigned': tx, - 'Signed': tx_signed, - 'Hash': tx_hash - }, title='preclaim transaction') + tx = name.preclaim(account, fee, ttl) + _print_object(tx, title='preclaim transaction') + except ValueError as e: + print(e) + except Exception as e: + print("Error", e) + + +@name.command('claim', help="Claim a domain name") +@click.argument('keystore_name', required=True) +@click.argument('domain', required=True) +@click.option("--name-ttl", default=defaults.NAME_TTL, help=f'Lifetime of the name in blocks', show_default=True, type=int) +@click.option("--name-salt", help=f'Salt used for the pre-claim transaction', required=True, type=int) +@click.option("--preclaim-tx-hash", help=f'The transaction hash of the pre-claim', required=True) +@transaction_options +@sign_options +def name_claim(keystore_name, domain, name_ttl, name_salt, preclaim_tx_hash, ttl, fee, nonce, password, network_id, force, wait, json_): + try: + set_global_options(force, wait, json_) + account, _ = _account(keystore_name, password=password) + name = _node_cli(network_id=network_id).AEName(domain) + name.update_status() + if name.status != aens.AEName.Status.AVAILABLE: + print("Domain not available") + exit(0) # claim - tx, tx_signed, sig, tx_hash = name.claim(account, fee, ttl) - _print_object({ - 'Signing account address': account.get_address(), - 'Signature': sig, - 'Unsigned': tx, - 'Signed': tx_signed, - 'Hash': tx_hash - }, title='claim transaction') - _print_object({}, title=f"Name {domain} claimed") + tx = name.claim(account, domain, name_salt, preclaim_tx_hash, fee=fee, tx_ttl=ttl) + _print_object(tx, title=f'Name {domain} claim transaction') except ValueError as e: print(e) @@ -467,9 +473,9 @@ def name_register(keystore_name, domain, name_ttl, ttl, fee, password, network_i @click.argument('domain', required=True) @click.argument('address', required=True) @click.option("--name-ttl", default=defaults.NAME_TTL, help=f'Lifetime of the claim in blocks', show_default=True) -@click.option("--ttl", default=defaults.TX_TTL, help=f'Lifetime of the claim request in blocks', show_default=True) +@transaction_options @sign_options -def name_update(keystore_name, domain, address, name_ttl, ttl, password, network_id, force, wait, json_): +def name_update(keystore_name, domain, address, name_ttl, ttl, fee, nonce, password, network_id, force, wait, json_): """ Update a name pointer """ @@ -490,8 +496,9 @@ def name_update(keystore_name, domain, address, name_ttl, ttl, password, network @name.command('revoke', help="Revoke a claimed name") @click.argument('keystore_name', required=True) @click.argument('domain', required=True) +@transaction_options @sign_options -def name_revoke(keystore_name, domain, password, network_id, force, wait, json_): +def name_revoke(keystore_name, domain, ttl, fee, nonce, password, network_id, force, wait, json_): try: set_global_options(force, wait, json_) account, _ = _account(keystore_name, password=password) @@ -510,8 +517,9 @@ def name_revoke(keystore_name, domain, password, network_id, force, wait, json_) @click.argument('keystore_name', required=True) @click.argument('domain', required=True) @click.argument('address') +@transaction_options @sign_options -def name_transfer(keystore_name, domain, address, password, network_id, force, wait, json_): +def name_transfer(keystore_name, domain, address, ttl, fee, nonce, password, network_id, force, wait, json_): """ Transfer a name to another account """ @@ -529,31 +537,6 @@ def name_transfer(keystore_name, domain, address, password, network_id, force, w print(e) -# ____ _ -# / __ \ | | -# | | | |_ __ __ _ ___| | ___ ___ -# | | | | '__/ _` |/ __| |/ _ \/ __| -# | |__| | | | (_| | (__| | __/\__ \ -# \____/|_| \__,_|\___|_|\___||___/ -# -# -@cli.group(help="Interact with oracles") -def oracle(): - pass - - -@oracle.command('register') -def oracle_register(): - print("register oracle") - pass - - -@oracle.command('query') -def oracle_query(): - print("query oracle") - pass - - # _____ _ _ # / ____| | | | | # | | ___ _ __ | |_ _ __ __ _ ___| |_ ___ @@ -658,16 +641,6 @@ def contract_call(keystore_name, deploy_descriptor, function, params, return_typ except Exception as e: print(e) -# ___ _______ _ ______ _____ ________ ______ -# .' `.|_ __ \ / \ .' ___ ||_ _| |_ __ |.' ____ \ -# / .-. \ | |__) | / _ \ / .' \_| | | | |_ \_|| (___ \_| -# | | | | | __ / / ___ \ | | | | _ | _| _ _.____`. -# \ `-' /_| | \ \_ _/ / \ \_\ `.___.'\ _| |__/ | _| |__/ || \____) | -# `.___.'|____| |___||____| |____|`.____ .'|________||________| \______.' -# - - -# TODO: implement cli for oracles # _____ _ # |_ _| | | @@ -686,14 +659,8 @@ def inspect(obj, force, wait, json_): try: set_global_options(force, wait, json_) if obj.endswith(".test"): - name = _node_cli().AEName(obj) - name.update_status() - _print_object({ - 'Status': name.status, - 'Name hash': name.name_hash, - 'Pointers': name.pointers, - 'TTL': name.name_ttl, - }, title="aens") + data = _node_cli().get_name_entry_by_name(name=obj) + _print_object(data, title="name") elif obj.startswith("kh_") or obj.startswith("mh_"): v = _node_cli().get_block_by_hash(obj) _print_object(v, title="block") @@ -719,24 +686,7 @@ def inspect(obj, force, wait, json_): else: raise ValueError(f"input not recognized: {obj}") except Exception as e: - print("Error:", e) - - -# @inspect.command('deploy', help='The contract deploy descriptor to inspect') -# @click.argument('contract_deploy_descriptor') -# def inspect_deploy(contract_deploy_descriptor): -# """ -# Inspect a contract deploy file that has been generated with the command -# aecli account X contract CONTRACT_SOURCE deploy -# """ -# try: -# with open(contract_deploy_descriptor) as fp: -# contract = json.load(fp) -# _print_object(contract) -# data = _node_cli().get_transaction_by_hash(hash=contract.get('transaction', 'N/A')) -# _print_object(data, "Transaction") -# except Exception as e: -# print(e) + print(e) # _____ _ _ diff --git a/aeternity/aens.py b/aeternity/aens.py index 50b1984c..1a0a2cec 100644 --- a/aeternity/aens.py +++ b/aeternity/aens.py @@ -1,8 +1,8 @@ -from aeternity.exceptions import NameNotAvailable, MissingPreclaim, NameUpdateError +from aeternity.exceptions import NameNotAvailable, MissingPreclaim, NameUpdateError, NameTooEarlyClaim, NameCommitmentIdMismatch from aeternity.openapi import OpenAPIClientException from aeternity.config import DEFAULT_TX_TTL, DEFAULT_FEE, DEFAULT_NAME_TTL, NAME_CLIENT_TTL from aeternity import hashing, utils, oracles -from aeternity.identifiers import ACCOUNT_ID, COMMITMENT, NAME +from aeternity.identifiers import ACCOUNT_ID, NAME class NameStatus: @@ -43,23 +43,6 @@ def validate_pointer(cls, pointer): not cls.validate_name(pointer, raise_exception=False) ) - @classmethod - def _get_name_id(cls, name): - """ - Compute the name id - """ - if isinstance(name, str): - name = name.encode('ascii') - return hashing.encode(NAME, name) - - def _get_commitment_id(self): - """ - Compute the commitment id - """ - self.preclaim_salt = hashing.randint() - commitment_id = hashing.hash_encode(COMMITMENT, hashing.namehash(self.domain) + self.preclaim_salt.to_bytes(32, 'big')) - return commitment_id - def _get_pointers(self, target): if target.startswith(ACCOUNT_ID): pointers = [{'id': target, 'key': 'account_pubkey'}] @@ -148,7 +131,7 @@ def preclaim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): # check which block we used to create the preclaim self.preclaimed_block_height = self.client.get_current_key_block_height() # calculate the commitment hash - commitment_id = self._get_commitment_id() + self.preclaim_salt, commitment_id = hashing.commitment_id(self.domain) # get the transaction builder txb = self.client.tx_builder # get the account nonce and ttl @@ -156,7 +139,7 @@ def preclaim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): # create spend_tx tx = txb.tx_name_preclaim(account.get_address(), commitment_id, fee, ttl, nonce) # sign the transaction - tx_signed = self.client.sign_transaction(account, tx.tx) + tx_signed = self.client.sign_transaction(account, tx.tx, metadata={"salt": self.preclaim_salt}) # post the transaction to the chain self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) # update local status @@ -164,11 +147,29 @@ def preclaim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): self.preclaim_tx_hash = tx_signed.hash return tx_signed - def claim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): - if self.preclaimed_block_height is None: - raise MissingPreclaim('You must call preclaim before claiming a name') - # name encoded - name = AEName._get_name_id(self.domain) + def claim(self, account, domain, name_salt, preclaim_tx_hash, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): + self.preclaim_salt = name_salt + # get the preclaim height + try: + pre_claim_tx = self.client.get_transaction_by_hash(hash=preclaim_tx_hash) + self.preclaimed_block_height = pre_claim_tx.block_height + except OpenAPIClientException: + raise MissingPreclaim(f"Preclaim transaction {preclaim_tx_hash} not found") + # if the commitment_id mismatch + pre_claim_commitment_id = pre_claim_tx.tx.commitment_id + _, commitment_id = hashing.commitment_id(domain, salt=name_salt) + if pre_claim_commitment_id != commitment_id: + raise NameCommitmentIdMismatch(f"Committment id mismatch, wanted {pre_claim_commitment_id} got {commitment_id}") + # if the transaction has not been mined + if self.preclaimed_block_height <= 0: + raise NameTooEarlyClaim(f"The pre-claim transaction has not been mined yet") + # get the current height + current_height = self.client.get_top_block().height + safe_height = self.preclaimed_block_height + self.client.config.key_block_confirmation_num + if current_height < safe_height: + raise NameTooEarlyClaim(f"It is not safe to execute the name claim before height {safe_height}, current height: {current_height}") + # name encode name + name = hashing.name_id(self.domain) # get the transaction builder txb = self.client.tx_builder # get the account nonce and ttl @@ -189,7 +190,7 @@ def update(self, account, target, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): - if self.status != NameStatus.CLAIMED: + if not self.check_claimed(): raise NameUpdateError('Must be claimed to update pointer') if isinstance(target, oracles.Oracle): diff --git a/aeternity/defaults.py b/aeternity/defaults.py index 637ad6b3..313ff29b 100644 --- a/aeternity/defaults.py +++ b/aeternity/defaults.py @@ -27,7 +27,9 @@ ORACLE_TTL_VALUE = 500 ORACLE_QUERY_TTL_VALUE = 10 ORACLE_RESPONSE_TTL_VALUE = 10 -KEY_BLOCK_INTERVAL = 3 +# Chain +KEY_BLOCK_INTERVAL = 3 # average time between key-blocks in minutes +KEY_BLOCK_CONFIRMATION_NUM = 3 # number of key blocks to wait for to consider a key-block confirmed # network id NETWORK_ID = identifiers.NETWORK_ID_MAINNET # TUNING diff --git a/aeternity/exceptions.py b/aeternity/exceptions.py index 53d2a3b7..4ad20ea1 100644 --- a/aeternity/exceptions.py +++ b/aeternity/exceptions.py @@ -19,7 +19,12 @@ class PreclaimFailed(AENSException): pass -class TooEarlyClaim(AENSException): +class NameTooEarlyClaim(Exception): + pass + + +class NameCommitmentIdMismatch(Exception): + """ Raised when a commitment id cannot be verified """ pass diff --git a/aeternity/hashing.py b/aeternity/hashing.py index 3158bf24..8d93ac03 100644 --- a/aeternity/hashing.py +++ b/aeternity/hashing.py @@ -132,6 +132,16 @@ def namehash_encode(prefix, name): return encode(prefix, namehash(name)) +def commitment_id(domain: str, salt: int=None)-> tuple: + """ + Compute the commitment id + :return: the generated salt and the commitment_id + """ + name_salt = randint() if salt is None else salt + commitment_id = hash_encode(identifiers.COMMITMENT, namehash(domain) + _int(name_salt, 32)) + return name_salt, commitment_id + + def _int(val: int, byte_length: int = None) -> bytes: """ Encode and int to a big endian byte array @@ -186,6 +196,14 @@ def _id(id_tag, hash_id): return _int(id_tag) + decode(hash_id) +def name_id(name): + """ + Encode a domain name + :param name: the domain name to encode + """ + return encode(identifiers.NAME, name) + + def contract_id(owner_id, nonce): """ Compute the contract id of a contract diff --git a/aeternity/node.py b/aeternity/node.py index 970de91d..84a7adc7 100644 --- a/aeternity/node.py +++ b/aeternity/node.py @@ -49,6 +49,7 @@ def __init__(self, self.orcale_ttl_type = kwargs.get("oracle_ttl_type", defaults.ORACLE_TTL_TYPE) # chain defaults self.key_block_interval = kwargs.get("key_block_interval", defaults.KEY_BLOCK_INTERVAL) + self.key_block_confirmation_num = kwargs.get("key_block_confirmation_num", defaults.KEY_BLOCK_CONFIRMATION_NUM) # debug self.debug = kwargs.get("debug", False) @@ -114,9 +115,9 @@ def _get_nonce_ttl(self, account_address: str, relative_ttl: int): Helper method to compute both absoulute ttl and nonce for an account :return: (nonce, ttl) """ - ttl = self.compute_absolute_ttl(relative_ttl) if relative_ttl > 0 else 0 + ttl = self.compute_absolute_ttl(relative_ttl).absolute_ttl if relative_ttl > 0 else 0 nonce = self.get_next_nonce(account_address) - return nonce, ttl.absolute_ttl + return nonce, ttl def get_top_block(self): """ @@ -157,13 +158,13 @@ def broadcast_transaction(self, tx, tx_hash=None): self.wait_for_transaction(reply.tx_hash) return reply.tx_hash - def sign_transaction(self, account: Account, tx: str) -> (str, str, str): + def sign_transaction(self, account: Account, tx: str, metadata: dict = None) -> tuple: """ Sign a transaction :return: the transaction for the transaction """ s = TxSigner(account, self.config.network_id) - tx = s.sign_encode_transaction(tx) + tx = s.sign_encode_transaction(tx, metadata) return tx def spend(self, account: Account, diff --git a/aeternity/openapi.py b/aeternity/openapi.py index 68eb3bbf..23af06c1 100644 --- a/aeternity/openapi.py +++ b/aeternity/openapi.py @@ -87,7 +87,7 @@ def p2s(name): self.base_url = f"{url}{base_path}" if url_internal is None: # do not build internal endpoints - self.skip_tags.append("internal") + self.skip_tags.add("internal") else: self.base_url_internal = f"{url_internal}{base_path}" diff --git a/aeternity/transactions.py b/aeternity/transactions.py index 9c45d7b6..6bfb4e17 100644 --- a/aeternity/transactions.py +++ b/aeternity/transactions.py @@ -29,9 +29,11 @@ def encode_signed_transaction(self, transaction, signature): encoded_signature = encode(idf.SIGNATURE, signature) return encoded_signed_tx, encoded_signature - def sign_encode_transaction(self, tx): + def sign_encode_transaction(self, tx, metadata: dict=None): """ Sign, encode and compute the hash of a transaction + :param tx: the TxObject to be signed + :param metadata: additional data to include in the output of the signed transaction object :return: encoded_signed_tx, encoded_signature, tx_hash """ # decode the transaction if not in native mode @@ -47,6 +49,7 @@ def sign_encode_transaction(self, tx): # return the object tx = dict( data=transaction.data, + metadata=metadata, tx=encoded_signed_tx, hash=tx_hash, signature=encoded_signature, diff --git a/tests/test_aens.py b/tests/test_aens.py index e6fe2f67..594bfdc7 100644 --- a/tests/test_aens.py +++ b/tests/test_aens.py @@ -1,14 +1,14 @@ from aeternity.aens import AEName +from aeternity import hashing from pytest import raises def test_name_committment(chain_fixture, random_domain): domain = random_domain - name = chain_fixture.NODE_CLI.AEName(domain) - cl = name._get_commitment_id() - cr = name.client.get_commitment_id(name=name.domain, salt=name.preclaim_salt) - assert cl == cr.commitment_id + salt, cid = hashing.commitment_id(domain) + cr = chain_fixture.NODE_CLI.client.get_commitment_id(name=domain, salt=salt) + assert cid == cr.commitment_id def test_name_validation_fails(chain_fixture): From 225a85c585689e2ceccb582d9e47a2ff421987a1 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 17:57:53 +0100 Subject: [PATCH 19/33] Remove legacy config file --- aeternity/config.py | 107 -------------------------------------------- 1 file changed, 107 deletions(-) delete mode 100644 aeternity/config.py diff --git a/aeternity/config.py b/aeternity/config.py deleted file mode 100644 index 8766a4cb..00000000 --- a/aeternity/config.py +++ /dev/null @@ -1,107 +0,0 @@ -import requests -import semver -from collections import MutableSequence -from . import __compatibility__ - -# fee calculation -GAS_PER_BYTE = 20 -BASE_GAS = 15000 -# max number of block into the future that the name is going to be available -# https://github.com/aeternity/protocol/blob/epoch-v0.22.0/AENS.md#update -# https://github.com/aeternity/protocol/blob/44a93d3aab957ca820183c3520b9daf6b0fedff4/AENS.md#aens-entry -NAME_MAX_TLL = 36000 -NAME_CLIENT_TTL = 60000 -DEFAULT_NAME_TTL = 500 -# default relative ttl in number of blocks for executing transaction on the chain -MAX_TX_TTL = 256 -DEFAULT_TX_TTL = 0 -# default fee for posting transaction -DEFAULT_FEE = 0 -# contracts -CONTRACT_DEFAULT_GAS = 100000 -CONTRACT_DEFAULT_GAS_PRICE = 1000000000 -CONTRACT_DEFAULT_DEPOSIT = 0 -CONTRACT_DEFAULT_VM_VERSION = 1 -CONTRACT_DEFAULT_AMOUNT = 0 -# oracles -# https://github.com/aeternity/protocol/blob/master/oracles/oracles.md#technical-aspects-of-oracle-operations -ORACLE_DEFAULT_QUERY_FEE = 0 -ORACLE_DEFAULT_TTL_TYPE_DELTA = 'delta' -ORACLE_DEFAULT_TTL_TYPE_BLOCK = 'block' -ORACLE_DEFAULT_TTL_VALUE = 500 -ORACLE_DEFAULT_QUERY_TTL_VALUE = 10 -ORACLE_DEFAULT_RESPONSE_TTL_VALUE = 10 - -# network id -DEFAULT_NETWORK_ID = "ae_mainnet" -# TUNING -MAX_RETRIES = 8 # used in exponential backoff when checking a transaction -POLLING_INTERVAL = 2 # in seconds - - -class ConfigException(Exception): - pass - - -class UnsupportedNodeVersion(Exception): - pass - - -class Config: - default_configs = None - - def __init__(self, - external_url='http://localhost:3013', - internal_url='http://localhost:3113', - websocket_url=None, - force_compatibility=False, - network_id=DEFAULT_NETWORK_ID): - - # endpoint urls - self.websocket_url = websocket_url - self.api_url_internal = internal_url - self.api_url = external_url - # get the version - self.name_url = f'{self.api_url}/name' - self.network_id = network_id - # retrieve the version of the node we are connecting to - try: - r = requests.get(f"{self.api_url}/v2/status").json() - self.node_version = r.get('node_version', 'unknown') - match_min = semver.match(self.node_version, __compatibility__.get("from_version")) - match_max = semver.match(self.node_version, __compatibility__.get("to_version")) - if (not match_min or not match_max) and not force_compatibility: - f, t = __compatibility__.get('from_version'), __compatibility__.get('to_version') - raise UnsupportedNodeVersion( - f"Unsupported node version {self.node_version}, supported version are {f} and {t}") - except requests.exceptions.ConnectionError as e: - raise ConfigException(f"Error connecting to the node node at {self.api_url}, connection unavailable") - except Exception as e: - raise UnsupportedNodeVersion(f"Unable to connect to the node: {e}") - - def __str__(self): - return f'ws:{self.websocket_url} ext:{self.api_url} int:{self.api_url_internal}' - - @classmethod - def set_defaults(cls, config): - """ - sets the default configuration that will be used when the node client - did not get a config passed into its constructor - - :return: None - """ - if not isinstance(config, MutableSequence): - config = [config] - cls.default_configs = config - - @classmethod - def get_defaults(cls): - """ - returns the previously set default config or constructs a configuration - automatically from environment variables - - :return: Config - """ - if cls.default_configs is None: - cls.default_configs = [Config()] - return cls.default_configs From d2610142d60de0f84723e92577a8695bb5fb1ca3 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 18:10:03 +0100 Subject: [PATCH 20/33] Remove legacy config file --- aeternity/__main__.py | 2 +- aeternity/aens.py | 41 +++++++++++++++++++-------------------- aeternity/defaults.py | 12 ++++++++---- aeternity/transactions.py | 21 ++++++++++---------- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/aeternity/__main__.py b/aeternity/__main__.py index 88f7f464..aabc397a 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -11,7 +11,7 @@ from aeternity.node import NodeClient, Config from aeternity.transactions import TxSigner # from aeternity.oracle import Oracle, OracleQuery, NoOracleResponse -from . import utils, signing, aens, defaults, config +from . import utils, signing, aens, defaults, exceptions from aeternity.contract import Contract from datetime import datetime, timezone diff --git a/aeternity/aens.py b/aeternity/aens.py index 1a0a2cec..a94f2f1c 100644 --- a/aeternity/aens.py +++ b/aeternity/aens.py @@ -1,6 +1,6 @@ from aeternity.exceptions import NameNotAvailable, MissingPreclaim, NameUpdateError, NameTooEarlyClaim, NameCommitmentIdMismatch from aeternity.openapi import OpenAPIClientException -from aeternity.config import DEFAULT_TX_TTL, DEFAULT_FEE, DEFAULT_NAME_TTL, NAME_CLIENT_TTL +from aeternity import defaults from aeternity import hashing, utils, oracles from aeternity.identifiers import ACCOUNT_ID, NAME @@ -78,13 +78,10 @@ def check_claimed(self): return self.status == NameStatus.CLAIMED def full_claim_blocking(self, account, - preclaim_fee=DEFAULT_FEE, - claim_fee=DEFAULT_FEE, - update_fee=DEFAULT_FEE, - name_ttl=DEFAULT_NAME_TTL, - client_ttl=NAME_CLIENT_TTL, + name_ttl=defaults.NAME_TTL, + client_ttl=defaults.NAME_CLIENT_TTL, target=None, - tx_ttl=DEFAULT_TX_TTL): + tx_ttl=defaults.TX_TTL): """ Execute a name claim and updates the pointer to it. @@ -109,29 +106,31 @@ def full_claim_blocking(self, account, raise NameNotAvailable(self.domain) hashes = {} # run preclaim - tx = self.preclaim(account, fee=preclaim_fee, tx_ttl=tx_ttl) + tx = self.preclaim(account) hashes['preclaim_tx'] = tx + # wait for the block confirmation + self.client.wait_for_confirmation(tx.hash) # run claim - tx = self.claim(account, fee=claim_fee, tx_ttl=tx_ttl) + tx = self.claim(account, tx.metadata.salt, tx.hash) hashes['claim_tx'] = tx # target is the same of account is not specified if target is None: target = account.get_address() # run update - tx = self.update(account, target, fee=update_fee, name_ttl=name_ttl, client_ttl=client_ttl) + tx = self.update(account, target, name_ttl=name_ttl, client_ttl=client_ttl) hashes['update_tx'] = tx # restore blocking value self.client.config.blocking_mode = blocking_orig return hashes - def preclaim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): + def preclaim(self, account, fee=defaults.FEE, tx_ttl=defaults.TX_TTL): """ Execute a name preclaim """ # check which block we used to create the preclaim self.preclaimed_block_height = self.client.get_current_key_block_height() # calculate the commitment hash - self.preclaim_salt, commitment_id = hashing.commitment_id(self.domain) + commitment_id, self.preclaim_salt = hashing.commitment_id(self.domain) # get the transaction builder txb = self.client.tx_builder # get the account nonce and ttl @@ -147,7 +146,7 @@ def preclaim(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): self.preclaim_tx_hash = tx_signed.hash return tx_signed - def claim(self, account, domain, name_salt, preclaim_tx_hash, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): + def claim(self, account, name_salt, preclaim_tx_hash, fee=defaults.FEE, tx_ttl=defaults.TX_TTL): self.preclaim_salt = name_salt # get the preclaim height try: @@ -157,14 +156,14 @@ def claim(self, account, domain, name_salt, preclaim_tx_hash, fee=DEFAULT_FEE, t raise MissingPreclaim(f"Preclaim transaction {preclaim_tx_hash} not found") # if the commitment_id mismatch pre_claim_commitment_id = pre_claim_tx.tx.commitment_id - _, commitment_id = hashing.commitment_id(domain, salt=name_salt) + commitment_id, _ = hashing.commitment_id(self.domain, salt=name_salt) if pre_claim_commitment_id != commitment_id: raise NameCommitmentIdMismatch(f"Committment id mismatch, wanted {pre_claim_commitment_id} got {commitment_id}") # if the transaction has not been mined if self.preclaimed_block_height <= 0: raise NameTooEarlyClaim(f"The pre-claim transaction has not been mined yet") # get the current height - current_height = self.client.get_top_block().height + current_height = self.client.get_current_key_block_height() safe_height = self.preclaimed_block_height + self.client.config.key_block_confirmation_num if current_height < safe_height: raise NameTooEarlyClaim(f"It is not safe to execute the name claim before height {safe_height}, current height: {current_height}") @@ -185,10 +184,10 @@ def claim(self, account, domain, name_salt, preclaim_tx_hash, fee=DEFAULT_FEE, t return tx_signed def update(self, account, target, - name_ttl=DEFAULT_NAME_TTL, - client_ttl=NAME_CLIENT_TTL, - fee=DEFAULT_FEE, - tx_ttl=DEFAULT_TX_TTL): + name_ttl=defaults.NAME_TTL, + client_ttl=defaults.NAME_CLIENT_TTL, + fee=defaults.FEE, + tx_ttl=defaults.TX_TTL): if not self.check_claimed(): raise NameUpdateError('Must be claimed to update pointer') @@ -212,7 +211,7 @@ def update(self, account, target, self.client.broadcast_transaction(tx_signed.tx, tx_signed.hash) return tx_signed - def transfer_ownership(self, account, recipient_pubkey, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): + def transfer_ownership(self, account, recipient_pubkey, fee=defaults.FEE, tx_ttl=defaults.TX_TTL): """ transfer ownership of a name :return: the transaction @@ -233,7 +232,7 @@ def transfer_ownership(self, account, recipient_pubkey, fee=DEFAULT_FEE, tx_ttl= self.status = NameStatus.TRANSFERRED return tx_signed - def revoke(self, account, fee=DEFAULT_FEE, tx_ttl=DEFAULT_TX_TTL): + def revoke(self, account, fee=defaults.FEE, tx_ttl=defaults.TX_TTL): """ revoke a name :return: the transaction diff --git a/aeternity/defaults.py b/aeternity/defaults.py index 313ff29b..3d44dd36 100644 --- a/aeternity/defaults.py +++ b/aeternity/defaults.py @@ -7,8 +7,9 @@ # max number of block into the future that the name is going to be available # https://github.com/aeternity/protocol/blob/epoch-v0.22.0/AENS.md#update # https://github.com/aeternity/protocol/blob/44a93d3aab957ca820183c3520b9daf6b0fedff4/AENS.md#aens-entry -MAX_NAME_TLL = 36000 -MAX_NAME_CLIENT_TTL = 60000 +NAME_MAX_TLL = 36000 +NAME_MAX_CLIENT_TTL = 84600 +NAME_CLIENT_TTL = NAME_MAX_CLIENT_TTL NAME_TTL = 500 # default relative ttl in number of blocks for executing transaction on the chain MAX_TX_TTL = 256 @@ -33,5 +34,8 @@ # network id NETWORK_ID = identifiers.NETWORK_ID_MAINNET # TUNING -MAX_RETRIES = 8 # used in exponential backoff when checking a transaction -POLLING_INTERVAL = 2 # in seconds +POLL_TX_MAX_RETRIES = 8 # used in exponential backoff when checking a transaction +POLL_TX_RETRIES_INTERVAL = 2 # in seconds +POLL_BLOCK_MAX_RETRIES = 20 # number of retries +POLL_BLOCK_RETRIES_INTERVAL = 30 # in seconds +# diff --git a/aeternity/transactions.py b/aeternity/transactions.py index 6bfb4e17..79afa4ef 100644 --- a/aeternity/transactions.py +++ b/aeternity/transactions.py @@ -1,6 +1,5 @@ from aeternity.hashing import _int, _int_decode, _binary, _binary_decode, _id, encode, decode, encode_rlp, decode_rlp, hash_encode from aeternity.openapi import OpenAPICli -from aeternity.config import ORACLE_DEFAULT_TTL_TYPE_DELTA, ORACLE_DEFAULT_TTL_TYPE_BLOCK from aeternity import identifiers as idf from aeternity import defaults from aeternity.exceptions import UnsupportedTransactionType @@ -522,7 +521,7 @@ def pointer_tag(pointer): _binary(kwargs.get("query_format")), _binary(kwargs.get("response_format")), _int(kwargs.get("query_fee")), - _int(0 if oracle_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(0 if oracle_ttl.get("type") == idf.ORACLE_TTL_TYPE_DELTA else 1), _int(oracle_ttl.get("value")), _int(kwargs.get("fee")), _int(kwargs.get("ttl")), @@ -540,7 +539,7 @@ def pointer_tag(pointer): response_format=_binary_decode(tx_native[5]), query_fee=_int_decode(tx_native[6]), oracle_ttl=dict( - type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[7]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[7]) else idf.ORACLE_TTL_TYPE_BLOCK, value=_int_decode(tx_native[8]), ), fee=_int_decode(tx_native[9]), @@ -565,9 +564,9 @@ def pointer_tag(pointer): _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")), _binary(kwargs.get("query")), _int(kwargs.get("query_fee")), - _int(0 if query_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(0 if query_ttl.get("type") == idf.ORACLE_TTL_TYPE_DELTA else 1), _int(query_ttl.get("value")), - _int(0 if response_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(0 if response_ttl.get("type") == idf.ORACLE_TTL_TYPE_DELTA else 1), _int(response_ttl.get("value")), _int(kwargs.get("fee")), _int(kwargs.get("ttl")), @@ -584,11 +583,11 @@ def pointer_tag(pointer): query=_binary_decode(tx_native[5]), query_fee=_int_decode(tx_native[6]), query_ttl=dict( - type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[7]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[7]) else idf.ORACLE_TTL_TYPE_BLOCK, value=_int_decode(tx_native[8]), ), response_ttl=dict( - type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[9]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[9]) else idf.ORACLE_TTL_TYPE_BLOCK, value=_int_decode(tx_native[10]), ), fee=_int_decode(tx_native[11]), @@ -610,7 +609,7 @@ def pointer_tag(pointer): _int(kwargs.get("nonce")), decode(kwargs.get("query_id")), _binary(kwargs.get("response")), - _int(0 if response_ttl.get("type") == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(0 if response_ttl.get("type") == idf.ORACLE_TTL_TYPE_DELTA else 1), _int(response_ttl.get("value")), _int(kwargs.get("fee")), _int(kwargs.get("ttl")), @@ -626,7 +625,7 @@ def pointer_tag(pointer): query_id=encode(idf.ORACLE_QUERY_ID, tx_native[4]), response=_binary(tx_native[5]), response_ttl=dict( - type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[6]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[6]) else idf.ORACLE_TTL_TYPE_BLOCK, value=_int_decode(tx_native[7]), ), fee=_int_decode(tx_native[8]), @@ -646,7 +645,7 @@ def pointer_tag(pointer): _int(vsn), _id(idf.ID_TAG_ORACLE, kwargs.get("oracle_id")), _int(kwargs.get("nonce")), - _int(0 if oracle_ttl.get("type", {}) == ORACLE_DEFAULT_TTL_TYPE_DELTA else 1), + _int(0 if oracle_ttl.get("type", {}) == idf.ORACLE_TTL_TYPE_DELTA else 1), _int(oracle_ttl.get("value")), _int(kwargs.get("fee")), _int(kwargs.get("ttl")), @@ -660,7 +659,7 @@ def pointer_tag(pointer): oracle_id=encode(idf.ORACLE_ID, tx_native[2]), nonce=_int_decode(tx_native[3]), oracle_ttl=dict( - type=ORACLE_DEFAULT_TTL_TYPE_DELTA if _int_decode(tx_native[4]) else ORACLE_DEFAULT_TTL_TYPE_BLOCK, + type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[4]) else idf.ORACLE_TTL_TYPE_BLOCK, value=_int_decode(tx_native[5]), ), fee=_int_decode(tx_native[6]), From 533a8ec775cc48d93fa94295785ee3c4feba1335 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 18:11:48 +0100 Subject: [PATCH 21/33] Add wait_for_confirmation method to the node client wait_for_confirmation waits for a N number of blocks from the transaction block before returning --- aeternity/node.py | 57 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/aeternity/node.py b/aeternity/node.py index 84a7adc7..4e0cba55 100644 --- a/aeternity/node.py +++ b/aeternity/node.py @@ -50,6 +50,11 @@ def __init__(self, # chain defaults self.key_block_interval = kwargs.get("key_block_interval", defaults.KEY_BLOCK_INTERVAL) self.key_block_confirmation_num = kwargs.get("key_block_confirmation_num", defaults.KEY_BLOCK_CONFIRMATION_NUM) + # tuning + self.poll_tx_max_retries = kwargs.get("poll_tx_max_retries", defaults.POLL_TX_MAX_RETRIES) + self.poll_tx_retries_interval = kwargs.get("poll_tx_retries_interval", defaults.POLL_TX_RETRIES_INTERVAL) + self.poll_block_max_retries = kwargs.get("poll_block_max_retries", defaults.POLL_BLOCK_MAX_RETRIES) + self.poll_block_retries_interval = kwargs.get("poll_block_retries_interval", defaults.POLL_BLOCK_RETRIES_INTERVAL) # debug self.debug = kwargs.get("debug", False) @@ -188,7 +193,7 @@ def spend(self, account: Account, self.broadcast_transaction(tx.tx, tx_hash=tx.hash) return tx - def wait_for_transaction(self, tx_hash, max_retries=defaults.MAX_RETRIES, polling_interval=defaults.POLLING_INTERVAL): + def wait_for_transaction(self, tx_hash, max_retries=None, polling_interval=None, confirm_transaction=False): """ Wait for a transaction to be mined for an account The method will wait for a specific transaction to be included in the chain, @@ -196,13 +201,20 @@ def wait_for_transaction(self, tx_hash, max_retries=defaults.MAX_RETRIES, pollin - the chain reply with a 404 not found (the transaction was expunged) - the account nonce is >= of the transaction nonce (transaction is in an illegal state) - the ttl of the transaction or the one passed as parameter has been reached - :return: True if the transaction id found False otherwise + :return: the block height of the transaction if it has been found + + Raises TransactionWaitTimeoutExpired if the transaction hasnt been found """ - if max_retries <= 0: + + retries = max_retries if max_retries is not None else self.config.poll_tx_max_retries + interval = polling_interval if polling_interval is not None else self.config.poll_tx_retries_interval + if retries <= 0: raise ValueError("Retries must be greater than 0") + # star tpolling n = 1 total_sleep = 0 + tx_height = -1 while True: # query the transaction try: @@ -212,17 +224,48 @@ def wait_for_transaction(self, tx_hash, max_retries=defaults.MAX_RETRIES, pollin # it may fail because it is not found that means that # or it was invalid or the ttl has expired raise TransactionWaitTimeoutExpired(tx_hash=tx_hash, reason=e.reason) - # if the tx.block_height > 0 we win - if tx.block_height > 0: + # if the tx.block_height >= min_block_height we are ok + if tx.block_height >= 0: + tx_height = tx.block_height break - if n >= max_retries: + if n >= retries: raise TransactionWaitTimeoutExpired(tx_hash=tx_hash, reason=f"The transaction was not included in {total_sleep} seconds, wait aborted") # calculate sleep time - sleep_time = (polling_interval ** n) + (random.randint(0, 1000) / 1000.0) + sleep_time = (interval ** n) + (random.randint(0, 1000) / 1000.0) time.sleep(sleep_time) total_sleep += sleep_time # increment n n += 1 + return tx_height + + def wait_for_confirmation(self, tx_hash, max_retries=None, polling_interval=None): + """ + Wait for a transaction to be confirmed by at least "key_block_confirmation_num" blocks + """ + # first wait for the transaction to be found + tx_height = self.wait_for_transaction(tx_hash) + # now caculate the min block height + min_block_height = tx_height + self.config.key_block_confirmation_num + # get teh + retries = max_retries if max_retries is not None else self.config.poll_block_max_retries + interval = polling_interval if polling_interval is not None else self.config.poll_block_retries_interval + if retries <= 0 or interval <= 0: + raise ValueError("max_retries and polling_interval must be greater than 0") + # star tpolling + n = 1 + total_sleep = 0 + while True: + current_height = self.get_current_key_block_height() + # if the tx.block_height >= min_block_height we are ok + if current_height >= min_block_height: + break + if n >= retries: + raise TransactionWaitTimeoutExpired(tx_hash=tx_hash, reason=f"The transaction was not included in {total_sleep} seconds, wait aborted") + # calculate sleep time + time.sleep(interval) + total_sleep += interval + # increment n + n += 1 # support naming def AEName(self, domain): From d2699779461f1ec32eda915ae6bc712acd0365ba Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 18:12:07 +0100 Subject: [PATCH 22/33] Fix namehash algorithm --- aeternity/hashing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aeternity/hashing.py b/aeternity/hashing.py index 8d93ac03..29cdf7aa 100644 --- a/aeternity/hashing.py +++ b/aeternity/hashing.py @@ -115,7 +115,7 @@ def hash_encode(prefix, data): def namehash(name): if isinstance(name, str): - name = name.encode('ascii') + name = name.lower().encode('ascii') # see: # https://github.com/aeternity/protocol/blob/master/AENS.md#hashing # and also: @@ -139,7 +139,7 @@ def commitment_id(domain: str, salt: int=None)-> tuple: """ name_salt = randint() if salt is None else salt commitment_id = hash_encode(identifiers.COMMITMENT, namehash(domain) + _int(name_salt, 32)) - return name_salt, commitment_id + return commitment_id, name_salt def _int(val: int, byte_length: int = None) -> bytes: From 3566c05900c4ace7448d31316d17890a8eb02b3a Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 18:14:12 +0100 Subject: [PATCH 23/33] More consistency in cli output Add comments Add missing exception --- aeternity/__main__.py | 68 +++++++++++++++++++++------------------ aeternity/exceptions.py | 6 ++++ aeternity/transactions.py | 7 ++-- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/aeternity/__main__.py b/aeternity/__main__.py index aabc397a..b8f8be1f 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -46,12 +46,10 @@ def _node_cli(network_id=None): # load the aeternity node client return NodeClient(cfg) - except config.ConfigException as e: - print("Configuration error: ", e) - exit(1) - except config.UnsupportedNodeVersion as e: - print(e) - exit(1) + except exceptions.ConfigException as e: + _print_error(e, title="configuration error", exit_code=1) + except exceptions.UnsupportedNodeVersion as e: + _print_error(e, exit_code=1) def _account(keystore_name, password=None): @@ -69,8 +67,7 @@ def _account(keystore_name, password=None): password = getpass.getpass("Enter the account password: ") return signing.Account.from_keystore(kf, password), os.path.abspath(kf) except Exception as e: - print(f"Keystore decryption failed: {e}") - exit(1) + _print_error(e, title="keystore decryption failed", exit_code=1) def _pl(label, offset, value=None): @@ -143,6 +140,11 @@ def _print_object(data, title): _po(title, data) +def _print_error(err, title="error", exit_code=0): + _print_object({"message": str(err)}, title) + sys.exit(exit_code) + + # Commands CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) @@ -266,7 +268,7 @@ def account_create(keystore_name, password, overwrite, force, wait, json_): 'Path': os.path.abspath(keystore_name) }, title='account') except Exception as e: - print(e) + _print_error(e, exit_code=1) @account.command('save', help='Save a private keys string to a password protected file account') @@ -288,7 +290,7 @@ def account_save(keystore_name, private_key, password, overwrite, force, wait, j 'Path': os.path.abspath(keystore_name) }, title='account') except Exception as e: - print(e) + _print_error(e, exit_code=1) @account.command('address', help="Print the account address (public key)") @@ -305,7 +307,7 @@ def account_address(password, keystore_name, private_key, force, wait, json_): o["Signing key"] = account.get_private_key() _print_object(o, title='account') except Exception as e: - print(e) + _print_error(e, exit_code=1) @account.command('balance', help="Get the balance of a account") @@ -318,7 +320,7 @@ def account_balance(keystore_name, password, force, wait, json_): account = _node_cli().get_account_by_pubkey(pubkey=account.get_address()) _print_object(account, title='account') except Exception as e: - print(e) + _print_error(e, exit_code=1) @account.command('spend', help="Create a transaction to another account") @@ -336,7 +338,7 @@ def account_spend(keystore_name, recipient_id, amount, ttl, password, network_id tx = _node_cli(network_id=network_id).spend(account, recipient_id, amount, tx_ttl=ttl) _print_object(tx, title='spend transaction') except Exception as e: - print(e) + _print_error(e, exit_code=1) @account.command('sign', help="Sign a transaction") @@ -353,7 +355,7 @@ def account_sign(keystore_name, password, network_id, unsigned_transaction, forc tx = TxSigner(account, network_id).sign_encode_transaction(unsigned_transaction) _print_object(tx, title='signed transaction') except Exception as e: - print(e) + _print_error(e, exit_code=1) # _________ ____ ____ # | _ _ ||_ _||_ _| @@ -387,7 +389,7 @@ def tx_broadcast(signed_transaction, force, wait, json_): "Transaction hash": tx_hash, }, title='transaction broadcast') except Exception as e: - print(e) + _print_error(e, exit_code=1) @tx.command('spend', help="Create a transaction to another account") @@ -404,7 +406,7 @@ def tx_spend(sender_id, recipient_id, amount, ttl, fee, nonce, payload, force, # print the results _print_object(tx, title='spend tx') except Exception as e: - print(e) + _print_error(e, exit_code=1) # _ _ # | \ | | @@ -439,9 +441,9 @@ def name_pre_claim(keystore_name, domain, ttl, fee, nonce, password, network_id, tx = name.preclaim(account, fee, ttl) _print_object(tx, title='preclaim transaction') except ValueError as e: - print(e) + _print_error(e, exit_code=1) except Exception as e: - print("Error", e) + _print_error(e, exit_code=1) @name.command('claim', help="Claim a domain name") @@ -462,10 +464,12 @@ def name_claim(keystore_name, domain, name_ttl, name_salt, preclaim_tx_hash, ttl print("Domain not available") exit(0) # claim - tx = name.claim(account, domain, name_salt, preclaim_tx_hash, fee=fee, tx_ttl=ttl) + tx = name.claim(account, name_salt, preclaim_tx_hash, fee=fee, tx_ttl=ttl) _print_object(tx, title=f'Name {domain} claim transaction') except ValueError as e: - print(e) + _print_error(e, exit_code=1) + except Exception as e: + _print_error(e, exit_code=1) @name.command('update') @@ -490,7 +494,7 @@ def name_update(keystore_name, domain, address, name_ttl, ttl, fee, nonce, passw tx = name.update(account, target=address, name_ttl=name_ttl, tx_ttl=ttl) _print_object(tx, title=f"Name {domain} status update") except Exception as e: - print(e) + _print_error(e, exit_code=1) @name.command('revoke', help="Revoke a claimed name") @@ -510,7 +514,7 @@ def name_revoke(keystore_name, domain, ttl, fee, nonce, password, network_id, fo tx = name.revoke(account) _print_object(tx, title=f"Name {domain} status revoke") except Exception as e: - print(e) + _print_error(e, exit_code=1) @name.command('transfer', help="Transfer a claimed domain to another account") @@ -534,7 +538,7 @@ def name_transfer(keystore_name, domain, address, ttl, fee, nonce, password, net tx = name.transfer_ownership(account, address) _print_object(tx, title=f"Name {domain} status transfer to {address}") except Exception as e: - print(e) + _print_error(e, exit_code=1) # _____ _ _ @@ -561,7 +565,7 @@ def contract_compile(contract_file): result = c.compile(code) _print_object({"bytecode", result}, title="contract") except Exception as e: - print(e) + _print_error(e, exit_code=1) @contract.command('deploy', help='Deploy a contract on the chain') @@ -605,7 +609,7 @@ def contract_deploy(keystore_name, contract_file, gas, password, network_id, for "Deploy descriptor": deploy_descriptor, }, title="contract") except Exception as e: - print(e) + _print_error(e, exit_code=1) @contract.command('call', help='Execute a function of the contract') @@ -639,7 +643,7 @@ def contract_call(keystore_name, deploy_descriptor, function, params, return_typ pass except Exception as e: - print(e) + _print_error(e, exit_code=1) # _____ _ @@ -686,7 +690,7 @@ def inspect(obj, force, wait, json_): else: raise ValueError(f"input not recognized: {obj}") except Exception as e: - print(e) + _print_error(e) # _____ _ _ @@ -721,7 +725,7 @@ def chain_ttl(relative_ttl, force, wait, json_): data = cli.compute_absolute_ttl(relative_ttl) _print_object(data, f"ttl for node at {cli.config.api_url} ") except Exception as e: - print("Error:", e) + _print_error(e) @chain.command('top') @@ -736,7 +740,7 @@ def chain_top(force, wait, json_): data = cli.get_top_block() _print_object(data, f"top for node at {cli.config.api_url} ") except Exception as e: - print("Error:", e) + _print_error(e) @chain.command('status') @@ -751,7 +755,7 @@ def chain_status(force, wait, json_): data = cli.get_status() _print_object(data, f"status for node at {cli.config.api_url} ") except Exception as e: - print("Error:", e) + _print_error(e)s @chain.command('network-id', help="Retrieve the network id of the target node") @@ -766,7 +770,7 @@ def chain_network_id(force, wait, json_): data = cli.get_status().network_id _print_object({"Network ID": data}, f"network id for node at {cli.config.api_url} ") except Exception as e: - print("Error:", e) + _print_error(e) @chain.command('play') @@ -798,7 +802,7 @@ def chain_play(height, limit, force, wait, json_): break g = cli.get_generation_by_hash(hash=g.key_block.prev_key_hash) except Exception as e: - print(e) + _print_error(e) g = None pass diff --git a/aeternity/exceptions.py b/aeternity/exceptions.py index 4ad20ea1..6c8a2bd6 100644 --- a/aeternity/exceptions.py +++ b/aeternity/exceptions.py @@ -55,11 +55,17 @@ class TransactionHashMismatch(AException): class TransactionWaitTimeoutExpired(AException): """Raised when a transaction hasn't been found after waiting for an amount of time""" + def __init__(self, tx_hash, reason): self.tx_hash = tx_hash self.reason = reason +class BlockWaitTimeoutExpired(Exception): + """Raised when a block height hasn't been reached after waiting for an amount of time""" + pass + + class UnsupportedNodeVersion(AException): """Raised when the node target runs an unsupported version""" diff --git a/aeternity/transactions.py b/aeternity/transactions.py index 79afa4ef..5e489f91 100644 --- a/aeternity/transactions.py +++ b/aeternity/transactions.py @@ -62,18 +62,19 @@ def _tx_native(op, **kwargs): def std_fee(tx_raw, fee_idx, base_gas_multiplier=1): # calculates the standard minimum transaction fee tx_copy = tx_raw # create a copy of the input - tx_copy[fee_idx] = _int(0, 8) + tx_copy[fee_idx] = _int(0, 8) # replace fee with a byte array of lenght 8 return (defaults.BASE_GAS * base_gas_multiplier + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) * defaults.GAS_PRICE def contract_fee(tx_raw, fee_idx, gas, base_gas_multiplier=1): # estimate the contract creation fee tx_copy = tx_raw # create a copy of the input - tx_copy[fee_idx] = _int(0, 8) + tx_copy[fee_idx] = _int(0, 8) # replace fee with a byte array of lenght 8 return (defaults.BASE_GAS * base_gas_multiplier + gas + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) * defaults.GAS_PRICE def oracle_fee(tx_raw, fee_idx, relative_ttl): + # estimate oracle fees tx_copy = tx_raw # create a copy of the input - tx_copy[fee_idx] = _int(0, 8) + tx_copy[fee_idx] = _int(0, 8) # replace fee with a byte array of lenght 8 fee = (defaults.BASE_GAS + len(rlp.encode(tx_copy)) * defaults.GAS_PER_BYTE) fee += math.ceil(32000 * relative_ttl / math.floor(60 * 24 * 365 / defaults.KEY_BLOCK_INTERVAL)) return fee * defaults.GAS_PRICE From 3123bbec4311ae1db7e9ffaa8684c452e6704f2e Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 18:14:30 +0100 Subject: [PATCH 24/33] Update test --- tests/conftest.py | 15 ++++++++++++++- tests/test_aens.py | 25 +++++-------------------- tests/test_api.py | 1 - tests/test_cli.py | 2 +- tests/test_hashing.py | 14 +++++++++++++- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3988a1f6..56b48f52 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,6 +32,19 @@ def random_domain(length=10): return f"{rand_str}.test" +@pytest.fixture +def client_fixture(scope="module"): + # Instantiate the node client for the tests + NODE_CLI = NodeClient(Config( + external_url=NODE_URL, + internal_url=NODE_URL_DEBUG, + network_id=NETWORK_ID, + blocking_mode=True, + debug=True, + )) + return namedtupled.map({"NODE_CLI": NODE_CLI}, _nt_name="TestData") + + @pytest.fixture def chain_fixture(scope="module"): @@ -47,7 +60,7 @@ def chain_fixture(scope="module"): internal_url=NODE_URL_DEBUG, network_id=NETWORK_ID, blocking_mode=True, - debug=True + debug=True, )) NODE_CLI.spend(genesis, ACCOUNT.get_address(), 2000000000000000000) diff --git a/tests/test_aens.py b/tests/test_aens.py index 594bfdc7..5106e23d 100644 --- a/tests/test_aens.py +++ b/tests/test_aens.py @@ -1,16 +1,8 @@ from aeternity.aens import AEName -from aeternity import hashing from pytest import raises -def test_name_committment(chain_fixture, random_domain): - domain = random_domain - salt, cid = hashing.commitment_id(domain) - cr = chain_fixture.NODE_CLI.client.get_commitment_id(name=domain, salt=salt) - assert cid == cr.commitment_id - - def test_name_validation_fails(chain_fixture): with raises(ValueError): chain_fixture.NODE_CLI.AEName('test.lol') @@ -35,13 +27,15 @@ def test_name_status_available(chain_fixture, random_domain): def test_name_claim_lifecycle(chain_fixture, random_domain): try: domain = random_domain - name = chain_fixture.NODE_CLI.AEName(domain) + node_cli = chain_fixture.NODE_CLI + name = node_cli.AEName(domain) assert name.status == AEName.Status.UNKNOWN name.update_status() assert name.status == AEName.Status.AVAILABLE - name.preclaim(chain_fixture.ACCOUNT) + preclaim = name.preclaim(chain_fixture.ACCOUNT) assert name.status == AEName.Status.PRECLAIMED - name.claim(chain_fixture.ACCOUNT) + node_cli.wait_for_confirmation(preclaim.hash) + name.claim(chain_fixture.ACCOUNT, preclaim.metadata.salt, preclaim.hash) assert name.status == AEName.Status.CLAIMED except Exception as e: print(e) @@ -99,15 +93,6 @@ def test_name_transfer_ownership(chain_fixture, random_domain): assert name.pointers[0].key == "account_pubkey" -# def test_transfer_failure_wrong_pubkey(): -# client = NodeClient() -# name = chain_fixture.NODE_CLI.AEName(random_domain) -# name.full_claim_blocking() -# client.wait_for_next_block() -# with raises(AENSException): -# name.transfer_ownership('ak_deadbeef') - - def test_name_revocation(chain_fixture, random_domain): domain = random_domain name = chain_fixture.NODE_CLI.AEName(domain) diff --git a/tests/test_api.py b/tests/test_api.py index c8a18931..9720cdf0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -53,7 +53,6 @@ def test_api_get_block_by_hash(chain_fixture): account = Account.generate().get_address() chain_fixture.NODE_CLI.spend(chain_fixture.ACCOUNT, account, 100) # wait for the next block - # client.wait_for_next_block() block = chain_fixture.NODE_CLI.get_block_by_hash(hash=latest_block.hash) # assert block.hash == latest_block.hash assert block.height == latest_block.height diff --git a/tests/test_cli.py b/tests/test_cli.py index f423151f..a124995b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -129,7 +129,7 @@ def test_cli_inspect_key_block_by_hash(chain_fixture): def test_cli_inspect_name(): j = call_aecli('inspect', 'whatever.test') - assert j.get("Status") == "AVAILABLE" + assert "name not found" in j.get("message", "").lower() def test_cli_inspect_transaction_by_hash(chain_fixture): diff --git a/tests/test_hashing.py b/tests/test_hashing.py index 9d387365..d3fe57d0 100644 --- a/tests/test_hashing.py +++ b/tests/test_hashing.py @@ -1,4 +1,4 @@ -from aeternity import hashing, transactions +from aeternity import hashing, transactions, node from pytest import raises @@ -106,3 +106,15 @@ def test_hashing_oracle_id(): for t in tt: assert (hashing.oracle_id(t[0]) == t[1]) == t[2] + + +def test_hashing_committment_id(client_fixture, random_domain): + name, salt = "aeternity.test", 10692426485854419779 + expected_committment = "cm_2by2qwnum96Z78WSFRJwhsC5qFzDgatrKk7PfH3yZ2wMBmsZF2" + cid, salt = hashing.commitment_id(name, salt) + assert expected_committment == cid + + domain = random_domain + cid, salt = hashing.commitment_id(domain) + cr = client_fixture.NODE_CLI.get_commitment_id(name=domain, salt=salt) + assert cid == cr.commitment_id From f13ea901132d93ecf97fb20415dca35422a8ee58 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 22:26:26 +0100 Subject: [PATCH 25/33] Fix typo --- aeternity/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aeternity/__main__.py b/aeternity/__main__.py index b8f8be1f..e4ce44fa 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -755,7 +755,7 @@ def chain_status(force, wait, json_): data = cli.get_status() _print_object(data, f"status for node at {cli.config.api_url} ") except Exception as e: - _print_error(e)s + _print_error(e) @chain.command('network-id', help="Retrieve the network id of the target node") From 31724ba2f00fd6303d11183f86aeac4a177e77a3 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Sun, 3 Mar 2019 22:31:15 +0100 Subject: [PATCH 26/33] Fix lint error --- tests/test_hashing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hashing.py b/tests/test_hashing.py index d3fe57d0..ad932090 100644 --- a/tests/test_hashing.py +++ b/tests/test_hashing.py @@ -1,4 +1,4 @@ -from aeternity import hashing, transactions, node +from aeternity import hashing, transactions from pytest import raises From 3b993f49225e37dd9d52312f621f130ad2dd6eca Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Mon, 4 Mar 2019 00:21:08 +0100 Subject: [PATCH 27/33] Add new example for spend transaction --- examples/aens_example.py | 10 ---- examples/index.md | 112 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 13 deletions(-) delete mode 100755 examples/aens_example.py diff --git a/examples/aens_example.py b/examples/aens_example.py deleted file mode 100755 index a9b7326c..00000000 --- a/examples/aens_example.py +++ /dev/null @@ -1,10 +0,0 @@ -from aeternity.aens import AEName -from aeternity.config import Config - -Config.set_defaults(Config(external_host=3013, internal_host=3113, websocket_host=3114)) - -name = AEName('foobar.aet') -if name.is_available(): - name.preclaim() - name.claim_blocking() - name.update(target='ak_deadbeef') diff --git a/examples/index.md b/examples/index.md index 5ad9f0ed..666782de 100644 --- a/examples/index.md +++ b/examples/index.md @@ -1,4 +1,110 @@ -[Naming](aens_example.py)
-Oracle [operator](oracle_operator_example.py) [client](oracle_request_example.py)
-[Simple spend transaction](spend.py) +# Examples +## Spend transaction (programmatically) + +The sample will use `testnet` + +### Before you start + +Will be using a newly created account for the following example. The first account will be the sender account. + +```python + +from aeternity import signing +# generate a new account +sender_account = signing.Account.generate() +# save the account in an encrypted format +sender_account.save_to_keystore_file("sender_account.json", "mypassword") +# print the account address +print("Sender account address: ", sender_account.get_address()) + +``` + +Go to [testnet.faucet.aepps.com](testnet.faucet.aepps.com) and paste there the sender account address + +### First example: easy spend transaction + +The account created at this step will be the recipient account + +```python + +from aeternity import signing, node + +# open the sender account +sender_account = signing.Account.from_keystore("sender_account.json", "mypassword") +# generate a new account +recipient_account = signing.Account.generate() + +# initialize a node client +aeternity_cli = node.NodeClient(node.Config( + external_url="https://sdk-testnet.aepps.com", + network_id="ae_uat" # optional +)) +# now build the transaction +tx = aeternity_cli.spend(sender_account, + recipient_account.get_address(), + 1000000000000000000 + ) +print(f"https://testnet.explorer.aepps.com/#/tx/{tx.hash}") + + +``` + +## Secound example: 3 steps spend transaction + +```python + +from aeternity import transactions, signing, node, identifiers + +# open the sender account +sender_account = signing.Account.from_keystore("sender_account.json", "mypassword") +# generate a new account +recipient_account = signing.Account.generate() + + +# Step 1: build the transaction + +# in this case we have to know the nonce before +# since building the transaction is performed offline +nonce = 2 + +tx_builder = transactions.TxBuilder() +tx = tx_builder.tx_spend(sender_account.get_address(), + recipient_account.get_address(), + 1000000000000000000, + "test tx", + 0, # fee, when 0 it is automatically computed + 0, # ttl for the transaction in number of blocks (default 0) + nonce) + +# Step 2: sign the transaction +tx_signer = transactions.TxSigner( + sender_account, + identifiers.NETWORK_ID_TESTNET # ae_uat +) +tx_signed = tx_signer.sign_encode_transaction(tx) + + +# Step 3: broadcast the transaction + +# initialize a node client +aeternity_cli = node.NodeClient(node.Config( + external_url="https://sdk-testnet.aepps.com", +)) + +# broadcast the transaction +aeternity_cli.broadcast_transaction(tx_signed.tx, tx_signed.hash) + +print(f"https://testnet.explorer.aepps.com/#/tx/{tx_signed.hash}") +print(f"now waiting for confirmation (it will take ~9 minutes)...") + +# Step 4: [optional] wait for transaction verification +# the default will wait 3 blocks after the transaction generation blocks +# the confirmation blocks number can be changed passing the +# key_block_confirmation_num +# parameter to teh node.Config +aeternity_cli.wait_for_confirmation(tx_signed.hash) + +print(f"transaction confirmed!") + +``` From 17143141fcb64ed93ac3c87532c5a28041baff5f Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Mon, 4 Mar 2019 00:48:13 +0100 Subject: [PATCH 28/33] Reorganize options for cli Removed legacy examples --- aeternity/__main__.py | 123 +++++++++++++++++++--------- aeternity/node.py | 3 +- examples/oracle_operator_example.py | 116 -------------------------- examples/oracle_request_example.py | 35 -------- examples/spend.py | 14 ---- 5 files changed, 87 insertions(+), 204 deletions(-) delete mode 100755 examples/oracle_operator_example.py delete mode 100755 examples/oracle_request_example.py delete mode 100755 examples/spend.py diff --git a/aeternity/__main__.py b/aeternity/__main__.py index e4ce44fa..344867f0 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -150,9 +150,12 @@ def _print_error(err, title="error", exit_code=0): # set global options TODO: this is a bit silly, we should probably switch back to stdlib _global_options = [ + click.option('--json', 'json_', is_flag=True, default=False, help='Print output in JSON format'), +] + +_online_options = [ click.option('--force', is_flag=True, default=False, help='Ignore aeternity node version compatibility check'), click.option('--wait', is_flag=True, default=False, help='Wait for transactions to be included'), - click.option('--json', 'json_', is_flag=True, default=False, help='Print output in JSON format'), ] _account_options = [ @@ -178,28 +181,31 @@ def global_options(func): return func +def online_options(func): + for option in reversed(_online_options): + func = option(func) + return func + + def account_options(func): - func = global_options(func) for option in reversed(_account_options): func = option(func) return func def sign_options(func): - func = account_options(func) for option in reversed(_sign_options): func = option(func) return func def transaction_options(func): - func = global_options(func) for option in reversed(_transaction_options): func = option(func) return func -def set_global_options(force, wait, json_): +def set_global_options(json_, force=False, wait=False): ctx = click.get_current_context() ctx.obj[CTX_FORCE_COMPATIBILITY] = force ctx.obj[CTX_BLOCKING_MODE] = wait @@ -215,7 +221,7 @@ def set_global_options(force, wait, json_): @click.option('--debug-url', '-d', default=None, envvar='NODE_URL_DEBUG', metavar='URL') @global_options @click.version_option(version=__version__) -def cli(ctx, url, debug_url, force, wait, json_): +def cli(ctx, url, debug_url, json_): """ Welcome to the aecli client. @@ -224,7 +230,6 @@ def cli(ctx, url, debug_url, force, wait, json_): """ ctx.obj[CTX_NODE_URL] = url ctx.obj[CTX_NODE_URL_DEBUG] = debug_url - set_global_options(force, wait, json_) @cli.command('config', help="Print the client configuration") @@ -252,10 +257,11 @@ def account(): @account.command('create', help="Create a new account") @click.argument('keystore_name') @click.option('--overwrite', default=False, is_flag=True, help="Overwrite existing keys without asking", show_default=True) +@global_options @account_options -def account_create(keystore_name, password, overwrite, force, wait, json_): +def account_create(keystore_name, password, overwrite, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_) new_account = signing.Account.generate() # TODO: introduce default configuration path if not overwrite and os.path.exists(keystore_name): @@ -275,10 +281,11 @@ def account_create(keystore_name, password, overwrite, force, wait, json_): @click.argument('keystore_name', required=True) @click.argument('private_key', required=True) @click.option('--overwrite', default=False, is_flag=True, help="Overwrite existing keys without asking", show_default=True) +@global_options @account_options -def account_save(keystore_name, private_key, password, overwrite, force, wait, json_): +def account_save(keystore_name, private_key, password, overwrite, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_) account = signing.Account.from_private_key_string(private_key) if not overwrite and os.path.exists(keystore_name): click.confirm(f'Keystore file {keystore_name} already exists, overwrite?', abort=True) @@ -296,10 +303,11 @@ def account_save(keystore_name, private_key, password, overwrite, force, wait, j @account.command('address', help="Print the account address (public key)") @click.argument('keystore_name') @click.option('--private-key', is_flag=True, help="Print the private key instead of the account address") +@global_options @account_options def account_address(password, keystore_name, private_key, force, wait, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, keystore_path = _account(keystore_name, password=password) o = {'Address': account.get_address()} if private_key: @@ -312,10 +320,12 @@ def account_address(password, keystore_name, private_key, force, wait, json_): @account.command('balance', help="Get the balance of a account") @click.argument('keystore_name') +@global_options @account_options +@online_options def account_balance(keystore_name, password, force, wait, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, _ = _account(keystore_name, password=password) account = _node_cli().get_account_by_pubkey(pubkey=account.get_address()) _print_object(account, title='account') @@ -327,11 +337,14 @@ def account_balance(keystore_name, password, force, wait, json_): @click.argument('keystore_name', required=True) @click.argument('recipient_id', required=True) @click.argument('amount', required=True, type=int) -@click.option('--ttl', default=defaults.TX_TTL, help="Validity of the spend transaction in number of blocks (default forever)") +@global_options +@account_options +@online_options +@transaction_options @sign_options def account_spend(keystore_name, recipient_id, amount, ttl, password, network_id, force, wait, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, keystore_path = _account(keystore_name, password=password) if not utils.is_valid_hash(recipient_id, prefix="ak"): raise ValueError("Invalid recipient address") @@ -344,10 +357,12 @@ def account_spend(keystore_name, recipient_id, amount, ttl, password, network_id @account.command('sign', help="Sign a transaction") @click.argument('keystore_name', required=True) @click.argument('unsigned_transaction', required=True) +@global_options +@account_options @sign_options -def account_sign(keystore_name, password, network_id, unsigned_transaction, force, wait, json_): +def account_sign(keystore_name, password, network_id, unsigned_transaction, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_) account, keystore_path = _account(keystore_name, password=password) if not utils.is_valid_hash(unsigned_transaction, prefix="tx"): raise ValueError("Invalid transaction format") @@ -378,9 +393,10 @@ def tx(): @tx.command('broadcast', help='Broadcast a transaction to the network') @click.argument('signed_transaction', required=True) @global_options +@online_options def tx_broadcast(signed_transaction, force, wait, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) if not utils.is_valid_hash(signed_transaction, prefix="tx"): raise ValueError("Invalid transaction format") cli = _node_cli() @@ -396,11 +412,12 @@ def tx_broadcast(signed_transaction, force, wait, json_): @click.argument('sender_id', required=True) @click.argument('recipient_id', required=True) @click.argument('amount', required=True, type=int) -@transaction_options @click.option('--payload', default="", help="Spend transaction payload") +@global_options +@transaction_options def tx_spend(sender_id, recipient_id, amount, ttl, fee, nonce, payload, force, wait, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) cli = _node_cli() tx = cli.tx_builder.tx_spend(sender_id, recipient_id, amount, payload, fee, ttl, nonce) # print the results @@ -426,11 +443,14 @@ def name(): @name.command('pre-claim', help="Pre-claim a domain name") @click.argument('keystore_name', required=True) @click.argument('domain', required=True) +@global_options +@account_options +@online_options @transaction_options @sign_options def name_pre_claim(keystore_name, domain, ttl, fee, nonce, password, network_id, force, wait, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, _ = _account(keystore_name, password=password) name = _node_cli(network_id=network_id).AEName(domain) name.update_status() @@ -452,11 +472,14 @@ def name_pre_claim(keystore_name, domain, ttl, fee, nonce, password, network_id, @click.option("--name-ttl", default=defaults.NAME_TTL, help=f'Lifetime of the name in blocks', show_default=True, type=int) @click.option("--name-salt", help=f'Salt used for the pre-claim transaction', required=True, type=int) @click.option("--preclaim-tx-hash", help=f'The transaction hash of the pre-claim', required=True) +@global_options +@account_options +@online_options @transaction_options @sign_options def name_claim(keystore_name, domain, name_ttl, name_salt, preclaim_tx_hash, ttl, fee, nonce, password, network_id, force, wait, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, _ = _account(keystore_name, password=password) name = _node_cli(network_id=network_id).AEName(domain) name.update_status() @@ -477,6 +500,9 @@ def name_claim(keystore_name, domain, name_ttl, name_salt, preclaim_tx_hash, ttl @click.argument('domain', required=True) @click.argument('address', required=True) @click.option("--name-ttl", default=defaults.NAME_TTL, help=f'Lifetime of the claim in blocks', show_default=True) +@global_options +@account_options +@online_options @transaction_options @sign_options def name_update(keystore_name, domain, address, name_ttl, ttl, fee, nonce, password, network_id, force, wait, json_): @@ -484,7 +510,7 @@ def name_update(keystore_name, domain, address, name_ttl, ttl, fee, nonce, passw Update a name pointer """ try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, _ = _account(keystore_name, password=password) name = _node_cli(network_id=network_id).AEName(domain) name.update_status() @@ -500,11 +526,14 @@ def name_update(keystore_name, domain, address, name_ttl, ttl, fee, nonce, passw @name.command('revoke', help="Revoke a claimed name") @click.argument('keystore_name', required=True) @click.argument('domain', required=True) +@global_options +@account_options +@online_options @transaction_options @sign_options def name_revoke(keystore_name, domain, ttl, fee, nonce, password, network_id, force, wait, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, _ = _account(keystore_name, password=password) name = _node_cli(network_id=network_id).AEName(domain) name.update_status() @@ -521,6 +550,9 @@ def name_revoke(keystore_name, domain, ttl, fee, nonce, password, network_id, fo @click.argument('keystore_name', required=True) @click.argument('domain', required=True) @click.argument('address') +@global_options +@account_options +@online_options @transaction_options @sign_options def name_transfer(keystore_name, domain, address, ttl, fee, nonce, password, network_id, force, wait, json_): @@ -528,7 +560,7 @@ def name_transfer(keystore_name, domain, address, ttl, fee, nonce, password, net Transfer a name to another account """ try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, _ = _account(keystore_name, password=password) name = _node_cli(network_id=network_id).AEName(domain) name.update_status() @@ -572,6 +604,10 @@ def contract_compile(contract_file): @click.argument('keystore_name', required=True) @click.argument("contract_file", required=True) @click.option("--gas", default=defaults.CONTRACT_GAS, help='Amount of gas to deploy the contract', show_default=True) +@global_options +@account_options +@online_options +@transaction_options @sign_options def contract_deploy(keystore_name, contract_file, gas, password, network_id, force, wait, json_): """ @@ -585,7 +621,7 @@ def contract_deploy(keystore_name, contract_file, gas, password, network_id, for """ try: with open(contract_file) as fp: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, _ = _account(keystore_name, password=password) code = fp.read() contract = _node_cli(network_id=network_id).Contract(code) @@ -619,6 +655,10 @@ def contract_deploy(keystore_name, contract_file, gas, password, network_id, for @click.argument("params", required=True) @click.argument("return_type", required=True) @click.option("--gas", default=defaults.CONTRACT_GAS, help='Amount of gas to deploy the contract', show_default=True) +@global_options +@account_options +@online_options +@transaction_options @sign_options def contract_call(keystore_name, deploy_descriptor, function, params, return_type, gas, password, network_id, force, wait, json_): try: @@ -628,7 +668,7 @@ def contract_call(keystore_name, deploy_descriptor, function, params, return_typ bytecode = contract.get('bytecode') address = contract.get('address') - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) account, _ = _account(keystore_name, password=password) contract = _node_cli(network_id=network_id).Contract(source, bytecode=bytecode, address=address) @@ -659,9 +699,10 @@ def contract_call(keystore_name, deploy_descriptor, function, params, return_typ @cli.command("inspect", help="Get information on transactions, blocks, etc...") @click.argument('obj') @global_options +@online_options def inspect(obj, force, wait, json_): try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) if obj.endswith(".test"): data = _node_cli().get_name_entry_by_name(name=obj) _print_object(data, title="name") @@ -705,14 +746,16 @@ def inspect(obj, force, wait, json_): @cli.group(help="Interact with the blockchain") @global_options -def chain(force, wait, json_): - set_global_options(force, wait, json_) +@online_options +def chain(json_, force, wait): + set_global_options(json_, force, wait) pass @chain.command('ttl') @click.argument('relative_ttl', type=int) @global_options +@online_options def chain_ttl(relative_ttl, force, wait, json_): """ Print the information of the top block of the chain. @@ -720,7 +763,7 @@ def chain_ttl(relative_ttl, force, wait, json_): try: if relative_ttl < 0: print("Error: the relative ttl must be a positive number") - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) cli = _node_cli() data = cli.compute_absolute_ttl(relative_ttl) _print_object(data, f"ttl for node at {cli.config.api_url} ") @@ -730,12 +773,13 @@ def chain_ttl(relative_ttl, force, wait, json_): @chain.command('top') @global_options -def chain_top(force, wait, json_): +@online_options +def chain_top(json_, force, wait): """ Print the information of the top block of the chain. """ try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) cli = _node_cli() data = cli.get_top_block() _print_object(data, f"top for node at {cli.config.api_url} ") @@ -745,12 +789,13 @@ def chain_top(force, wait, json_): @chain.command('status') @global_options -def chain_status(force, wait, json_): +@online_options +def chain_status(json_, force, wait): """ Print the node node status. """ try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) cli = _node_cli() data = cli.get_status() _print_object(data, f"status for node at {cli.config.api_url} ") @@ -760,12 +805,13 @@ def chain_status(force, wait, json_): @chain.command('network-id', help="Retrieve the network id of the target node") @global_options -def chain_network_id(force, wait, json_): +@online_options +def chain_network_id(json_, force, wait): """ Print the node node status. """ try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) cli = _node_cli() data = cli.get_status().network_id _print_object({"Network ID": data}, f"network id for node at {cli.config.api_url} ") @@ -777,12 +823,13 @@ def chain_network_id(force, wait, json_): @click.option('--height', type=int, help="From which height should play the chain (default top)") @click.option('--limit', '-l', type=int, default=sys.maxsize, help="Limit the number of block to print") @global_options +@online_options def chain_play(height, limit, force, wait, json_): """ play the blockchain backwards """ try: - set_global_options(force, wait, json_) + set_global_options(json_, force, wait) cli = _node_cli() g = cli.get_generation_by_height(height=height) if height is not None else cli.get_current_generation() # check the limit diff --git a/aeternity/node.py b/aeternity/node.py index 4e0cba55..580f1099 100644 --- a/aeternity/node.py +++ b/aeternity/node.py @@ -240,7 +240,8 @@ def wait_for_transaction(self, tx_hash, max_retries=None, polling_interval=None, def wait_for_confirmation(self, tx_hash, max_retries=None, polling_interval=None): """ - Wait for a transaction to be confirmed by at least "key_block_confirmation_num" blocks + Wait for a transaction to be confirmed by at least "key_block_confirmation_num" blocks (default 3) + The amount of blocks can be configured in the Config object using key_block_confirmation_num parameter """ # first wait for the transaction to be found tx_height = self.wait_for_transaction(tx_hash) diff --git a/examples/oracle_operator_example.py b/examples/oracle_operator_example.py deleted file mode 100755 index a8eec4a9..00000000 --- a/examples/oracle_operator_example.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python -import json -import logging -import re - -import requests - -from aeternity import Config -from aeternity import NodeClient -from aeternity import Oracle - - -logging.basicConfig(level=logging.DEBUG) - - -class OraclefJean(Oracle): - """ - An oracle that can provide data from JSON APIs from the web. - - Just provide an URL that returns JSON for a GET request - - And provide the a jq-style query: - (but it's reduced to alphanumeric non-quoted key traversal for plain - objects and lists, e.g.) - - {'this': {'is': ['some', 'awesome'], 'api': 'result'}}} - - with the parameter - jq='.this.is[1]' - would return - "awesome" - - """ - - def _error(self, message, data=None): - if data is None: - data = {} - return {'status': 'error', 'message': message, 'data': data} - - def _success(self, data): - return {'status': 'ok', 'message': '', 'data': data} - - def _jq_traverse(self, jq, data): - assert jq.startswith('.') # remove identity - if jq == '.': - return data - ret_data = data - for subpath in jq[1:].split('.'): - obj_traverse = subpath - list_index = None - list_indexed_match = re.match('(\w*)\[(\d+:?\d*)\]', subpath) - if list_indexed_match: - obj_traverse, list_index = list_indexed_match.groups() - if obj_traverse: - ret_data = ret_data[obj_traverse] - if list_index is not None: - # slices - if ':' in list_index: - start, end = list_index.split(':') - start, end = int(start), int(end) - ret_data = ret_data[start:end] - else: - # indices - ret_data = ret_data[int(list_index)] - return ret_data - - def get_response(self, message): - payload_query = message['payload']['query'] - payload_query = json.loads(payload_query) - try: - url, jq = payload_query['url'], payload_query['jq'] - except (KeyError, AssertionError) as exc: - print(exc) - return self._error('malformed query') - try: - json_data = requests.get(url).json() - except Exception: - return self._error('request/json error') - try: - ret_data = self._jq_traverse(jq, json_data) - except (KeyError, AssertionError): - return self._error('error traversing json/invalid jq') - # make sure the result is not huge - ret_data = json.dumps(ret_data) - if len(ret_data) > 1024: - return self._error('return data is too big (>1024 bytes)') - return self._success(ret_data) - - -dev1_config = Config(external_host=3013, internal_host=3113, websocket_host=3114) -oraclef_jean = OraclefJean( - # example spec (this spec is fictional and will be defined later) - query_format='''{'url': 'str', 'jq': 'str'}''', - # example spec (this spec is fictional and will be defined later) - response_format='''{'status': 'error'|'ok', 'message': 'str', 'data': {}}''', - default_ttl=50, - default_query_fee=4, - default_fee=6, - default_query_ttl=10, - default_response_ttl=10, -) -client = NodeClient(configs=dev1_config) -client.register_oracle(oraclef_jean) -client.listen_until(oraclef_jean.is_ready) - -print(f'''You can now query this oracle using the following parameters: - oracle_pubkey: {oraclef_jean.oracle_id} - query_fee: {oraclef_jean.get_query_fee()} - query_ttl: {oraclef_jean.get_query_ttl()} - response_ttl: {oraclef_jean.get_response_ttl()} - fee: {oraclef_jean.get_fee()} - query_format: {oraclef_jean.query_format} -''') -print('Oracle ready') -client.listen() -print('Oracle Stopped') diff --git a/examples/oracle_request_example.py b/examples/oracle_request_example.py deleted file mode 100755 index e545a5b1..00000000 --- a/examples/oracle_request_example.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python -import json -import logging - - -from aeternity import Config -from aeternity import NodeClient -from aeternity import OracleQuery - -logging.basicConfig(level=logging.DEBUG) - - -class AeternityInUSDOracleQuery(OracleQuery): - def on_response(self, message, query): - print(query) - print(message) - - -dev1_config = Config(external_host=3013, internal_host=3113, websocket_host=3114) -client = NodeClient(configs=dev1_config) -oracle_pubkey = 'ok_3WRqCYwdr9B5aeAMT7Bfv2gGZpLUdD4RQM4hzFRpRzRRZx7d8pohQ6xviXxDTLHVwWKDbGzxH1xRp19LtwBypFpCVBDjEQ' - -oracle_query = AeternityInUSDOracleQuery( - oracle_pubkey=oracle_pubkey, - query_fee=4, - query_ttl=10, - response_ttl=10, - fee=6, -) -client.mount(oracle_query) -oracle_query.query(json.dumps({ - 'url': 'https://api.coinmarketcap.com/v1/ticker/aeternity/?convert=USD', - 'jq': '.[0].price_usd', -})) -client.listen() diff --git a/examples/spend.py b/examples/spend.py deleted file mode 100755 index c4ea5993..00000000 --- a/examples/spend.py +++ /dev/null @@ -1,14 +0,0 @@ -from aeternity.node import NodeClient -from aeternity.config import Config - -import sys - -Config.set_defaults(Config(external_host=3013, internal_host=3113, websocket_host=3114)) - -recipient, amount = sys.argv[1:3] - -amount = int(amount) - -node = NodeClient() - -node.spend(recipient, amount) From c1ffe4280f193d838b9faf43d9db56248ea70fea Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Mon, 4 Mar 2019 01:28:55 +0100 Subject: [PATCH 29/33] Update readme --- README.md | 94 +++++++++++++++++++--------------- docs/assets/images/faucet.png | Bin 150281 -> 163922 bytes 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 53264463..c49d9e43 100644 --- a/README.md +++ b/README.md @@ -66,48 +66,49 @@ Commands: Use the environment variables -- `EPOCH_URL` -- `EPOCH_URL_DEBUG` +- `NODE_URL` +- `NODE_URL_DEBUG` ### Example usage -The following is a walkthrough to execute an offline spend transaction on the *sdk-edgenet* network +The following is a walkthrough to execute an offline spend transaction on the *testnet* network 1. Set the environment variables ``` -export EPOCH_URL=https://sdk-testnet.aepps.com -export EPOCH_URL_DEBUG=https://sdk-testnet.aepps.com +export NODE_URL=https://sdk-testnet.aepps.com ``` ❗ When not set the command line client will connect to mainnet 2. Retrieve the top block ``` -./aecli chain top - +$ aecli chain top + Beneficiary _______________________________________ ak_2iBPH7HUz3cSDVEUWiHg76MZJ6tZooVNBmmxcgVK6VV8KAE688 - Hash ______________________________________________ kh_2CkQuFZbxMh7KEjWQYphWxaTsTaL1tXrMDLyDwikKbwG1jNuRL - Height ____________________________________________ 12465 - Miner _____________________________________________ ak_q6LAEutWqrmMLvSWE1dG4hN3dmLb8ixsJnkZUhsybGEti7Biv - Nonce _____________________________________________ 13307153327850546964 - Prev hash _________________________________________ kh_oNMbgX3jW55tg3cr1DMpniCNfrJgQuqQUFEsXaozfhTca4rJt - Prev key hash _____________________________________ kh_oNMbgX3jW55tg3cr1DMpniCNfrJgQuqQUFEsXaozfhTca4rJt - State hash ________________________________________ bs_LaWiuHdYQ7Z1KBxXMeaPtYCG7JeCWU6ZqxfsGLTxYJU9cbsRq - Target ____________________________________________ 538660660 - Time ______________________________________________ 2018-12-23T22:15:34.290000+00:00 - Version ___________________________________________ 1 - + Hash ______________________________________________ kh_WTqMQQRsvmbtP5yrKPxd4p2PPKiA51AuPyCkVJk7d7HVtkhS6 + Height ____________________________________________ 46049 + Info ______________________________________________ cb_Xfbg4g== + Miner _____________________________________________ ak_24yQXT3g2jNryZbY2veHYcgQn3PspfTnkTHbXMwNYDQd9NZAs5 + Nonce _____________________________________________ 17848795956567990671 + Prev hash _________________________________________ kh_B5Q3F7Gxbmg2z3prkay2uYohvhv4xXUKQzKKkbTdm7Z3GuQxU + Prev key hash _____________________________________ kh_B5Q3F7Gxbmg2z3prkay2uYohvhv4xXUKQzKKkbTdm7Z3GuQxU + State hash ________________________________________ bs_bkP3QdFKCWNetDHfwL3rJG2hEgRzZRAQN6jh33SKwUd17tBjp + Target ____________________________________________ 538409724 + Time ______________________________________________ 2019-03-03T23:38:49.720000+00:00 + Version ___________________________________________ 2 + ``` 3. Create a new account ``` -aecli account create TEST.json +aecli account create Bob.json Enter the account password []: - Address ___________________________________________ ak_tMZTyGmbpZYdKde9pfjh8bnxD2PPEEXM4KQjarLNNaoPpwrxn - Path ______________________________________________ /..../TEST.json + Address ___________________________________________ ak_BobY97QUVR4iDLg4k3RKmy6shZYx9FR75nLaN33GsVmSnhWxn + Path ______________________________________________ /.../Bob.json + ``` ❗ Make sure that you use a long and difficult-to-guess password for an account that you plan to use on mainnet @@ -120,19 +121,19 @@ Enter the account password []: ``` aecli inspect th_2CV4a7xxDYj5ysaDjXNoCSLxnkowGM5bbyAvtdoPvHZwTSYykX - Block hash ________________________________________ mh_2vjFffExUZPVGo3q6CHRSzxVUhzLcUnQQUWpijFtSvKfoHwQWe - Block height ______________________________________ 12472 - Hash ______________________________________________ th_2CV4a7xxDYj5ysaDjXNoCSLxnkowGM5bbyAvtdoPvHZwTSYykX + Block hash ________________________________________ mh_2Kw9ktNiU36uFC2Fs2FZtpFfok6GQZTt5rY6Vs5bS6jNBw6bzd + Block height ______________________________________ 46052 + Hash ______________________________________________ th_2DWDxx2egaF887S4M7GGBWsEGqkQvPYcXsvJzqapd6CRLGKobJ - Signature #1 ____________________________________ sg_WtPeyKWN4zmcnZZXpAxCT8EvjF3qSjiUidc9cdxQooxe1JCLADTVbKDFm9S5bNwv3yq57PQKTG4XuUP4eTzD5jymPHpNu + Signature #1 ____________________________________ sg_CotVHb914KqZpSETmY4mWhpFpCCUdCJssYbJHx1iR6S94SrDPfTyqUfqQkoBW4dVSgU1kfFUJC9yc6sUaHSte2xBvCKzR Amount __________________________________________ 5000000000000000000 - Fee _____________________________________________ 20000 - Nonce ___________________________________________ 146 + Fee _____________________________________________ 20000000000000 + Nonce ___________________________________________ 1059 Payload _________________________________________ Faucet Tx - Recipient id ____________________________________ ak_2ioQbdSViNKjknaLUWphdRjpbTNVpMHpXf9X5ZkoVrhrCZGuyW + Recipient id ____________________________________ ak_BobY97QUVR4iDLg4k3RKmy6shZYx9FR75nLaN33GsVmSnhWxn Sender id _______________________________________ ak_2iBPH7HUz3cSDVEUWiHg76MZJ6tZooVNBmmxcgVK6VV8KAE688 - Ttl _____________________________________________ 12522 + Ttl _____________________________________________ 46102 Type ____________________________________________ SpendTx Version _________________________________________ 1 @@ -142,11 +143,11 @@ aecli inspect th_2CV4a7xxDYj5ysaDjXNoCSLxnkowGM5bbyAvtdoPvHZwTSYykX 6. Create another account ``` -aecli account create TEST1.json +aecli account create Alice.json Enter the account password []: - Address ___________________________________________ ak_2rT82n7BYbH9JCsgxWqJc4BbPb9UybxCeAGS2RtXc8pPxhWJDu - Path ______________________________________________ /..../TEST1.json + Address ___________________________________________ ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M + Path ______________________________________________ /.../Alice.json ``` @@ -154,25 +155,34 @@ Enter the account password []: 7. Transfer some tokens to an account to the other ``` -aecli account spend TEST.json ak_2rT82n7BYbH9JCsgxWqJc4BbPb9UybxCeAGS2RtXc8pPxhWJDu 1000000000000000000 --network-id ae_uat +aecli account spend Bob.json ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M 1000000000000000000 Enter the account password []: - Sender account ____________________________________ ak_2ioQbdSViNKjknaLUWphdRjpbTNVpMHpXf9X5ZkoVrhrCZGuyW - Recipient account _________________________________ ak_2rT82n7BYbH9JCsgxWqJc4BbPb9UybxCeAGS2RtXc8pPxhWJDu - Unsigned __________________________________________ tx_+FcMAaEB4pu+lXqNvHL+V7e1uq4GhGOTR6GA6xF1LNzT1sp/De6hAfP6rN+zuBu1JJBSjeuEBufFkXQ1fRK0iYsik1KyXgWWiA3gtrOnZAAAgk4ggjKtAYANBwQ3 - Signed ____________________________________________ tx_+KELAfhCuEBlJaNys8hURnXUAqVYsmgjSApQh7PbGm+r4KoJQwUH+zU+0z2+Y+Q/ecU/bdBvwqO7UcOyVnWlvVEoDFLUXfAFuFn4VwwBoQHim76Veo28cv5Xt7W6rgaEY5NHoYDrEXUs3NPWyn8N7qEB8/qs37O4G7UkkFKN64QG58WRdDV9ErSJiyKTUrJeBZaIDeC2s6dkAACCTiCCMq0BgBH1MuI= - Hash ______________________________________________ th_2PbPcfBMJGXe4GeAYWVT1CiY3DocgAe1RPP47VP1xaU2X5quJd + + Tag _____________________________________________ 12 + Vsn _____________________________________________ 1 + Sender id _______________________________________ ak_BobY97QUVR4iDLg4k3RKmy6shZYx9FR75nLaN33GsVmSnhWxn + Recipient id ____________________________________ ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M + Amount __________________________________________ 1000000000000000000 + Fee _____________________________________________ 16860000000000 + Ttl _____________________________________________ 0 + Nonce ___________________________________________ 4 + Payload _________________________________________ + + Metadata + Tx ________________________________________________ tx_+KMLAfhCuEAKN05UwTV0fSgO5woziVNnAMBcDrh46XlNFTZTJQlI05fz/8pVSyrb1guCLcw8n7++O887k/JEu6/XHcCSHOMMuFv4WQwBoQEYh8aMDs7saMDBvys+lbKds3Omnzm4crYNbs9xGolBm6EBE9B4l/BeyxMO//3ANxwyT+ZHL52j9nAZosRe/YFuK4eIDeC2s6dkAACGD1WGT5gAAASAN24JGA== + Hash ______________________________________________ th_2gAL72dtnaeDcZoZA9MbfSL1JrWzNErMJuikmTRvBY8zhkGh91 + Signature _________________________________________ sg_2LX9hnJRiYGSspzpS34QeN3PLT9bGSkFRbad9LXvLj5QUFoV5eHRf9SueDgLiiquCGbeFEBPBe7xMJidf8NMSuF16dngr + Network id ________________________________________ ae_uat ``` -❗ You dont need to set the `network-id` parameter for the mainnet! - 8. Verify the balance of the new account ``` -aecli inspect ak_2rT82n7BYbH9JCsgxWqJc4BbPb9UybxCeAGS2RtXc8pPxhWJDu +aecli inspect ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M Balance ___________________________________________ 1000000000000000000 - Id ________________________________________________ ak_2rT82n7BYbH9JCsgxWqJc4BbPb9UybxCeAGS2RtXc8pPxhWJDu + Id ________________________________________________ ak_9j8akv2PE2Mnt5khFeDvS9BGc3TBBrJkfcgaJHgBXcLLagX8M Nonce _____________________________________________ 0 ``` diff --git a/docs/assets/images/faucet.png b/docs/assets/images/faucet.png index 4d7de0c3aeea78c0d42df30844e42889abbb0e98..d85b609bc33a3a267f8bab3a3d4e3e2544b9432d 100644 GIT binary patch literal 163922 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

>6W>e*Uw`-Ll0}NU{ZhO6ZDgWdBRGmm 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 From 0a844c580b70393dd8143d702a9a2df3649faa8e Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Mon, 4 Mar 2019 01:29:15 +0100 Subject: [PATCH 30/33] Fix transaction _id decoding --- aeternity/transactions.py | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/aeternity/transactions.py b/aeternity/transactions.py index 5e489f91..f96c74da 100644 --- a/aeternity/transactions.py +++ b/aeternity/transactions.py @@ -122,8 +122,8 @@ def build_tx_object(tx_data, tx_raw, fee_idx, min_fee): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - sender_id=encode(idf.ACCOUNT_ID, tx_native[2]), - recipient_id=encode(idf.ACCOUNT_ID, tx_native[3]), + sender_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), + recipient_id=encode(idf.ACCOUNT_ID, tx_native[3][1:]), amount=_int_decode(tx_native[4]), fee=_int_decode(tx_native[5]), ttl=_int_decode(tx_native[6]), @@ -151,9 +151,9 @@ def build_tx_object(tx_data, tx_raw, fee_idx, min_fee): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), - commitment_id=encode(idf.COMMITMENT, tx_native[4]), + commitment_id=encode(idf.COMMITMENT, tx_native[4][1:]), fee=_int_decode(tx_native[5]), ttl=_int_decode(tx_native[6]), ) @@ -180,7 +180,7 @@ def build_tx_object(tx_data, tx_raw, fee_idx, min_fee): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), name=_binary_decode(tx_native[4], str), name_salt=_binary_decode(tx_native[5], int), @@ -222,9 +222,9 @@ def pointer_tag(pointer): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), - name=encode(idf.NAME, tx_native[4]), + name=encode(idf.NAME, tx_native[4][1:]), name_ttl=_int_decode(tx_native[5]), pointers=[], # TODO: decode pointers client_ttl=_int_decode(tx_native[7]), @@ -254,10 +254,10 @@ def pointer_tag(pointer): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), - name=encode(idf.NAME, tx_native[4]), - recipient_id=encode(idf.ACCOUNT_ID, tx_native[5]), + name=encode(idf.NAME, tx_native[4][1:]), + recipient_id=encode(idf.ACCOUNT_ID, tx_native[5][1:]), fee=_int_decode(tx_native[6]), ttl=_int_decode(tx_native[7]), ) @@ -283,9 +283,9 @@ def pointer_tag(pointer): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), - name=encode(idf.NAME, tx_native[4]), + name=encode(idf.NAME, tx_native[4][1:]), fee=_int_decode(tx_native[5]), ttl=_int_decode(tx_native[6]), ) @@ -319,9 +319,9 @@ def pointer_tag(pointer): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - owner_id=encode(idf.ACCOUNT_ID, tx_native[2]), + owner_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), - code=encode(idf.BYTECODE, tx_native[4]), + code=encode(idf.BYTECODE, tx_native[4][1:]), vm_version=_int_decode(tx_native[5][0:vml - 2]), abi_version=_int_decode(tx_native[5][vml - 2:]), fee=_int_decode(tx_native[6]), @@ -330,7 +330,7 @@ def pointer_tag(pointer): amount=_int_decode(tx_native[9]), gas=_int_decode(tx_native[10]), gas_price=_int_decode(tx_native[11]), - call_data=encode(idf.BYTECODE, tx_native[12]), + call_data=encode(idf.BYTECODE, tx_native[12][1:]), ) min_fee = tx_data.get("fee") else: @@ -360,16 +360,16 @@ def pointer_tag(pointer): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - caller_id=encode(idf.ACCOUNT_ID, tx_native[2]), + caller_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), - contract_id=encode(idf.CONTRACT_ID, tx_native[4]), + contract_id=encode(idf.CONTRACT_ID, tx_native[4][1:]), abi_version=_int_decode(tx_native[5]), fee=_int_decode(tx_native[6]), ttl=_int_decode(tx_native[7]), amount=_int_decode(tx_native[8]), gas=_int_decode(tx_native[9]), gas_price=_int_decode(tx_native[10]), - call_data=encode(idf.BYTECODE, tx_native[11]), + call_data=encode(idf.BYTECODE, tx_native[11][1:]), ) min_fee = tx_data.get("fee") else: @@ -534,7 +534,7 @@ def pointer_tag(pointer): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - account_id=encode(idf.ACCOUNT_ID, tx_native[2]), + account_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), query_format=_binary_decode(tx_native[4]), response_format=_binary_decode(tx_native[5]), @@ -578,9 +578,9 @@ def pointer_tag(pointer): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - sender_id=encode(idf.ACCOUNT_ID, tx_native[2]), + sender_id=encode(idf.ACCOUNT_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), - oracle_id=encode(idf.ORACLE_ID, tx_native[4]), + oracle_id=encode(idf.ORACLE_ID, tx_native[4][1:]), query=_binary_decode(tx_native[5]), query_fee=_int_decode(tx_native[6]), query_ttl=dict( @@ -621,9 +621,9 @@ def pointer_tag(pointer): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - oracle_id=encode(idf.ORACLE_ID, tx_native[2]), + oracle_id=encode(idf.ORACLE_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), - query_id=encode(idf.ORACLE_QUERY_ID, tx_native[4]), + query_id=encode(idf.ORACLE_QUERY_ID, tx_native[4][1:]), response=_binary(tx_native[5]), response_ttl=dict( type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[6]) else idf.ORACLE_TTL_TYPE_BLOCK, @@ -657,7 +657,7 @@ def pointer_tag(pointer): tx_data = dict( tag=tag, vsn=_int_decode(tx_native[1]), - oracle_id=encode(idf.ORACLE_ID, tx_native[2]), + oracle_id=encode(idf.ORACLE_ID, tx_native[2][1:]), nonce=_int_decode(tx_native[3]), oracle_ttl=dict( type=idf.ORACLE_TTL_TYPE_DELTA if _int_decode(tx_native[4]) else idf.ORACLE_TTL_TYPE_BLOCK, @@ -691,10 +691,10 @@ def compute_tx_hash(encoded_tx: str) -> str: tx_raw = decode(encoded_tx) return hash_encode(idf.TRANSACTION_HASH, tx_raw) - def tx_spend(self, account_id, recipient_id, amount, payload, fee, ttl, nonce)-> tuple: + def tx_spend(self, sender_id, recipient_id, amount, payload, fee, ttl, nonce)-> tuple: """ create a spend transaction - :param account_id: the public key of the sender + :param sender_id: the public key of the sender :param recipient_id: the public key of the recipient :param amount: the amount to send :param payload: the payload associated with the data @@ -709,7 +709,7 @@ def tx_spend(self, account_id, recipient_id, amount, payload, fee, ttl, nonce)-> recipient_id=recipient_id, amount=amount, fee=fee, - sender_id=account_id, + sender_id=sender_id, payload=payload, ttl=ttl, nonce=nonce, From 693c55ed0ee7046c00c17dfcaaf130d88cd8eaa8 Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Mon, 4 Mar 2019 01:30:19 +0100 Subject: [PATCH 31/33] Fix close tag for signatures --- README.md | 17 +++++++++-------- aeternity/__main__.py | 10 ++++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c49d9e43..2b117dd5 100644 --- a/README.md +++ b/README.md @@ -121,19 +121,20 @@ Enter the account password []: ``` aecli inspect th_2CV4a7xxDYj5ysaDjXNoCSLxnkowGM5bbyAvtdoPvHZwTSYykX - Block hash ________________________________________ mh_2Kw9ktNiU36uFC2Fs2FZtpFfok6GQZTt5rY6Vs5bS6jNBw6bzd - Block height ______________________________________ 46052 - Hash ______________________________________________ th_2DWDxx2egaF887S4M7GGBWsEGqkQvPYcXsvJzqapd6CRLGKobJ + Block hash ________________________________________ mh_2vjFffExUZPVGo3q6CHRSzxVUhzLcUnQQUWpijFtSvKfoHwQWe + Block height ______________________________________ 12472 + Hash ______________________________________________ th_2CV4a7xxDYj5ysaDjXNoCSLxnkowGM5bbyAvtdoPvHZwTSYykX - Signature #1 ____________________________________ sg_CotVHb914KqZpSETmY4mWhpFpCCUdCJssYbJHx1iR6S94SrDPfTyqUfqQkoBW4dVSgU1kfFUJC9yc6sUaHSte2xBvCKzR + Signature #1 ____________________________________ sg_WtPeyKWN4zmcnZZXpAxCT8EvjF3qSjiUidc9cdxQooxe1JCLADTVbKDFm9S5bNwv3yq57PQKTG4XuUP4eTzD5jymPHpNu + Amount __________________________________________ 5000000000000000000 - Fee _____________________________________________ 20000000000000 - Nonce ___________________________________________ 1059 + Fee _____________________________________________ 20000 + Nonce ___________________________________________ 146 Payload _________________________________________ Faucet Tx - Recipient id ____________________________________ ak_BobY97QUVR4iDLg4k3RKmy6shZYx9FR75nLaN33GsVmSnhWxn + Recipient id ____________________________________ ak_2ioQbdSViNKjknaLUWphdRjpbTNVpMHpXf9X5ZkoVrhrCZGuyW Sender id _______________________________________ ak_2iBPH7HUz3cSDVEUWiHg76MZJ6tZooVNBmmxcgVK6VV8KAE688 - Ttl _____________________________________________ 46102 + Ttl _____________________________________________ 12522 Type ____________________________________________ SpendTx Version _________________________________________ 1 diff --git a/aeternity/__main__.py b/aeternity/__main__.py index 344867f0..ad440215 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -115,6 +115,7 @@ def _po(label, value, offset=0, label_prefix=None): o = offset + 2 for i, x in enumerate(value): _po(f"{label[:-1]} #{i+1}", x, o) + _pl(f"", offset) elif isinstance(value, datetime): val = value.strftime("%Y-%m-%d %H:%M") _pl(label, offset, value=val) @@ -305,9 +306,9 @@ def account_save(keystore_name, private_key, password, overwrite, json_): @click.option('--private-key', is_flag=True, help="Print the private key instead of the account address") @global_options @account_options -def account_address(password, keystore_name, private_key, force, wait, json_): +def account_address(password, keystore_name, private_key, json_): try: - set_global_options(json_, force, wait) + set_global_options(json_) account, keystore_path = _account(keystore_name, password=password) o = {'Address': account.get_address()} if private_key: @@ -342,13 +343,14 @@ def account_balance(keystore_name, password, force, wait, json_): @online_options @transaction_options @sign_options -def account_spend(keystore_name, recipient_id, amount, ttl, password, network_id, force, wait, json_): +def account_spend(keystore_name, recipient_id, amount, fee, ttl, nonce, password, network_id, force, wait, json_): try: set_global_options(json_, force, wait) account, keystore_path = _account(keystore_name, password=password) + account.nonce = nonce if not utils.is_valid_hash(recipient_id, prefix="ak"): raise ValueError("Invalid recipient address") - tx = _node_cli(network_id=network_id).spend(account, recipient_id, amount, tx_ttl=ttl) + tx = _node_cli(network_id=network_id).spend(account, recipient_id, amount, tx_ttl=ttl, fee=fee) _print_object(tx, title='spend transaction') except Exception as e: _print_error(e, exit_code=1) From 6726feb248460b0c7eb6c2027678a163242f881e Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Mon, 4 Mar 2019 01:55:59 +0100 Subject: [PATCH 32/33] Fix params for tx spend --- aeternity/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aeternity/__main__.py b/aeternity/__main__.py index ad440215..05206691 100644 --- a/aeternity/__main__.py +++ b/aeternity/__main__.py @@ -417,9 +417,9 @@ def tx_broadcast(signed_transaction, force, wait, json_): @click.option('--payload', default="", help="Spend transaction payload") @global_options @transaction_options -def tx_spend(sender_id, recipient_id, amount, ttl, fee, nonce, payload, force, wait, json_): +def tx_spend(sender_id, recipient_id, amount, ttl, fee, nonce, payload, json_): try: - set_global_options(json_, force, wait) + set_global_options(json_) cli = _node_cli() tx = cli.tx_builder.tx_spend(sender_id, recipient_id, amount, payload, fee, ttl, nonce) # print the results From bc567c85679a975ea3a6aa3f7f3dd51c4032a67e Mon Sep 17 00:00:00 2001 From: Andrea Giacobino Date: Mon, 4 Mar 2019 01:56:24 +0100 Subject: [PATCH 33/33] Update tests for cli Add test for decoding of _id --- tests/test_cli.py | 4 ++-- tests/test_node.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index a124995b..334a166c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -23,7 +23,7 @@ def _account_path(tempdir, account): def call_aecli(*params): - args = [aecli_exe, '-u', NODE_URL, '-d', NODE_URL_DEBUG] + list(params) + ['--wait', '--json'] + args = [aecli_exe, '-u', NODE_URL, '-d', NODE_URL_DEBUG] + list(params) + ['--json'] cmd = " ".join(args) print(cmd) status, output = subprocess.getstatusoutput(cmd) @@ -92,7 +92,7 @@ def test_cli_spend(chain_fixture, tempdir): # generate a new address recipient_address = Account.generate().get_address() # call the cli - call_aecli('account', 'spend', account_path, recipient_address, "90", '--password', 'aeternity_bc', '--network-id', NETWORK_ID) + call_aecli('account', 'spend', account_path, recipient_address, "90", '--password', 'aeternity_bc', '--network-id', NETWORK_ID, '--wait') # test that the recipient account has the requested amount print(f"recipient address is {recipient_address}") recipient_account = chain_fixture.NODE_CLI.get_account_by_pubkey(pubkey=recipient_address) diff --git a/tests/test_node.py b/tests/test_node.py index 11cd9348..e3ccf8dc 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -5,7 +5,9 @@ def _test_node_spend(node_cli, sender_account): account = Account.generate().get_address() - node_cli.spend(sender_account, account, 100) + tx = node_cli.spend(sender_account, account, 100) + assert account == tx.data.recipient_id + assert sender_account.get_address() == tx.data.sender_id account = node_cli.get_account_by_pubkey(pubkey=account) balance = account.balance assert balance > 0