Skip to content
This repository was archived by the owner on Aug 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions aeternity/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from aeternity import __version__

from aeternity.epoch import EpochClient
from aeternity.transactions import TxSigner
from aeternity import exceptions
# from aeternity.oracle import Oracle, OracleQuery, NoOracleResponse
from . import utils, signing, aens, config
from aeternity.contract import Contract
Expand Down Expand Up @@ -38,10 +38,9 @@ def _epoch_cli(offline=False, native=False, network_id=None):
config.Config.set_defaults(config.Config(
external_url=url,
internal_url=url_i,
force_compatibility=ctx.obj.get(CTX_FORCE_COMPATIBILITY),
network_id=network_id
))
except config.ConfigException as e:
except exceptions.ConfigException as e:
print("Configuration error: ", e)
exit(1)
except config.UnsupportedEpochVersion as e:
Expand All @@ -50,6 +49,7 @@ def _epoch_cli(offline=False, native=False, network_id=None):

# load the epoch client
return EpochClient(blocking_mode=ctx.obj.get(CTX_BLOCKING_MODE),
force_compatibility=ctx.obj.get(CTX_FORCE_COMPATIBILITY),
offline=offline,
native=native)

Expand Down Expand Up @@ -355,7 +355,8 @@ 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 epoch_client
tx_signed, signature, tx_hash = TxSigner(account, network_id).sign_encode_transaction(unsigned_transaction)
epoch_cli = EpochClient(configs=config.Config(network_id=network_id), offline=True)
tx_signed, signature, tx_hash = epoch_cli.sign_transaction(account, unsigned_transaction)
_print_object({
'Signing account address': account.get_address(),
'Signature': signature,
Expand Down
36 changes: 4 additions & 32 deletions aeternity/config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import requests
import sys
import semver
from collections import MutableSequence
from . import __compatibility__

# vm version specification
# https://github.com/aeternity/protocol/blob/master/contracts/contract_vms.md#virtual-machines-on-the-%C3%A6ternity-blockchain
Expand All @@ -18,7 +15,7 @@
DEFAULT_NAME_TTL = 500
# default relative ttl in number of blocks for executing transaction on the chain
MAX_TX_TTL = sys.maxsize
DEFAULT_TX_TTL = 500
DEFAULT_TX_TTL = 0
# default fee for posting transaction
DEFAULT_FEE = 20000
# contracts
Expand All @@ -45,48 +42,23 @@
POLLING_INTERVAL = 2 # in seconds


class ConfigException(Exception):
pass


class UnsupportedEpochVersion(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,
channels_url='http://localhost:3014',
network_id=DEFAULT_NETWORK_ID):

# endpoint urls
self.websocket_url = websocket_url
self.channels_url = channels_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 UnsupportedEpochVersion(
f"Unsupported epoch version {self.node_version}, supported version are {f} and {t}")
except requests.exceptions.ConnectionError as e:
raise ConfigException(f"Error connecting to the epoch node at {self.api_url}, connection unavailable")
except Exception as e:
raise UnsupportedEpochVersion(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}'
return f'ext:{self.api_url} int:{self.api_url_internal} ch:{self.channels_url} '

@classmethod
def set_defaults(cls, config):
Expand Down
34 changes: 22 additions & 12 deletions aeternity/epoch.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,34 @@ class EpochClient:
'Transaction not found': TransactionNotFoundException,
}

def __init__(self, configs=None, blocking_mode=False, retry=True, debug=False, native=True, offline=False):
def __init__(self, configs=None, blocking_mode=False, native=True, offline=False, force_compatibility=False, debug=False):
"""
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)
"""
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._retry = retry
# 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)
self.api = None if offline else openapi.OpenAPICli(configs[0].api_url,
configs[0].api_url_internal,
debug=debug,
force_compatibility=force_compatibility)
# instantiate the transaction builder object
self.tx_builder = transactions.TxBuilder(native=self.native, api=self.api)

Expand All @@ -69,12 +80,13 @@ def update_top_block(self):
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, must be > 0
:param relative_ttl: the relative ttl, if 0 will set the ttl to 0
"""
if relative_ttl <= 0:
raise ValueError("ttl must be greater than 0")
height = self.get_current_key_block_height()
return height + relative_ttl
absolute_ttl = 0
if relative_ttl > 0:
height = self.get_current_key_block_height()
absolute_ttl = height + relative_ttl
return absolute_ttl

def get_next_nonce(self, account_address):
"""
Expand Down Expand Up @@ -138,10 +150,8 @@ def broadcast_transaction(self, tx, tx_hash=None):

def sign_transaction(self, account: Account, tx: str) -> (str, str, str):
"""
Sign and broadcast a transaction if conditions are met
Conditions are
- account is not None
- operation mode is not offline
Sign a transaction
:return (tx_signed, signature, tx_hash): the signed transaction, the signature and the hash of the transaction
"""
s = TxSigner(account, self._get_active_config().network_id)
tx_signed, signature, tx_hash = s.sign_encode_transaction(tx)
Expand Down
11 changes: 10 additions & 1 deletion aeternity/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,17 @@ class TransactionHashMismatch(AException):
pass


class TransactionWaitTimeoutExpired(Exception):
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 UnsupportedEpochVersion(AException):
"""Raised when the node target runs an unsupported version"""


class ConfigException(AException):
"""Raised in case of configuration errors"""
pass
25 changes: 22 additions & 3 deletions aeternity/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
from collections import namedtuple
import logging

from aeternity.exceptions import UnsupportedEpochVersion, ConfigException
import semver

from . import __compatibility__


class OpenAPIArgsException(Exception):
"""Raised when there is an error in method arguments"""
Expand Down Expand Up @@ -42,9 +47,23 @@ class OpenAPICli(object):
"boolean": "bool",
}

def __init__(self, url, url_internal=None, debug=False):
# load the openapi json file from the node
self.api_def = requests.get(f"{url}/api").json()
def __init__(self, url, url_internal=None, debug=False, force_compatibility=False):
try:
# load the openapi json file from the node
self.api_def = requests.get(f"{url}/api").json()
api_version = self.api_def.get("info", {}).get("version", "unknown")
# retrieve the version of the node we are connecting to
match_min = semver.match(api_version, __compatibility__.get("from_version"))
match_max = semver.match(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(
f"unsupported epoch version {api_version}, supported version are {f} and {t}")
except requests.exceptions.ConnectionError as e:
raise ConfigException(f"Error connecting to the epoch node at {self.api_url}, connection unavailable")
except Exception as e:
raise UnsupportedEpochVersion(f"Unable to connect to the node: {e}")

# enable printing debug messages
self.debug = debug

Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ services:
volumes:
- ${PWD}/docker/epoch.yaml:/home/epoch/epoch.yaml
- ${PWD}/docker/keys/node:/home/epoch/node/keys
- ${PWD}/docker/accounts_test.json:/home/epoch/node/data/aecore/.genesis/accounts_test.json
- node_db:/home/epoch/node/data/mnesia
- node_keys:/home/epoch/node/keys

Expand Down
3 changes: 3 additions & 0 deletions docker/accounts_test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"ak_2a1j2Mk9YSmC1gioUq4PWRm3bsv887MbuRVwyv4KaUGoR1eiKi": 100000000000001000000000000000000
}
9 changes: 9 additions & 0 deletions tests/test_epoch.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from tests import ACCOUNT, EPOCH_CLI
from aeternity.signing import Account
from aeternity.epoch import EpochClient
from aeternity.config import Config

# from aeternity.exceptions import TransactionNotFoundException

Expand All @@ -20,3 +22,10 @@ def test_epoch_spend_internal():
def test_epoch_spend_native():
EPOCH_CLI.set_native(True)
_test_epoch_spend()


def test_epoch_offline():
new_cli = EpochClient(offline=True, configs=Config(external_url="no", internal_url="no"))
tx = new_cli.tx_builder.tx_spend(ACCOUNT.get_address(), Account.generate().get_address(), 10, "", 20000, 0, 10)
tx_signed, sg, tx_hash = new_cli.sign_transaction(ACCOUNT, tx)
assert len(tx_signed) > 0