diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py
index a91cc2f1..9532e3be 100644
--- a/skale/contracts/base_contract.py
+++ b/skale/contracts/base_contract.py
@@ -19,7 +19,7 @@
""" SKALE base contract class """
import logging
-from functools import wraps
+from functools import partial, wraps
from web3 import Web3
@@ -31,38 +31,40 @@
logger = logging.getLogger(__name__)
-def transaction_method(gas_limit):
- def real_decorator(transaction):
- @wraps(transaction)
- def wrapper(self, *args, wait_for=True, timeout=4, blocks_to_wait=50,
- gas_price=None, nonce=None,
- dry_run_only=False, skip_dry_run=False,
- raise_for_status=True, **kwargs):
- method = transaction(self, *args, **kwargs)
- tx_res = TxRes()
- if not skip_dry_run:
- tx_res.dry_run_result = make_dry_run_call(self.skale.wallet,
- method, gas_limit)
+def transaction_method(transaction=None, *, gas_limit=10):
+ if transaction is None:
+ return partial(transaction_method, gas_limit=gas_limit)
- if not dry_run_only and (skip_dry_run or tx_res.dry_run_passed()):
- gas_price = gas_price or self.skale.gas_price
- tx_res.hash = post_transaction(self.skale.wallet, method,
- gas_limit, gas_price, nonce)
- if wait_for:
- tx_res.receipt = wait_for_receipt_by_blocks(
- self.skale.web3,
- tx_res.hash,
- timeout=timeout,
- blocks_to_wait=blocks_to_wait
- )
+ @wraps(transaction)
+ def wrapper(self, *args, wait_for=True,
+ wait_timeout=4, blocks_to_wait=50,
+ gas_price=None, nonce=None,
+ dry_run_only=False, skip_dry_run=False, raise_for_status=True,
+ **kwargs):
+ method = transaction(self, *args, **kwargs)
+ tx_res = TxRes()
+ if not skip_dry_run:
+ tx_res.dry_run_result = make_dry_run_call(self.skale.wallet,
+ method, gas_limit)
- if raise_for_status:
- tx_res.raise_for_status()
+ if not dry_run_only and (skip_dry_run or tx_res.dry_run_passed()):
+ gas_price = gas_price or self.skale.gas_price
+ tx_res.hash = post_transaction(self.skale.wallet, method,
+ gas_limit, gas_price, nonce)
+ if wait_for:
+ tx_res.receipt = wait_for_receipt_by_blocks(
+ self.skale.web3,
+ tx_res.hash,
+ timeout=wait_timeout,
+ blocks_to_wait=blocks_to_wait
+ )
- return tx_res
+ if raise_for_status:
+ tx_res.raise_for_status()
- return wrapper
- return real_decorator
+ return tx_res
+
+ return wrapper
class BaseContract:
diff --git a/skale/contracts/constants_holder.py b/skale/contracts/constants_holder.py
index eae8b930..0a16d4d5 100644
--- a/skale/contracts/constants_holder.py
+++ b/skale/contracts/constants_holder.py
@@ -22,7 +22,7 @@
class ConstantsHolder(BaseContract):
- @transaction_method(GAS['set_periods'])
+ @transaction_method(gas_limit=GAS['set_periods'])
def set_periods(self, new_reward_period, new_delta_period):
return self.contract.functions.setPeriods(new_reward_period, new_delta_period)
@@ -32,14 +32,14 @@ def get_reward_period(self):
def get_delta_period(self):
return self.contract.functions.deltaPeriod().call()
- @transaction_method(GAS['set_check_time'])
+ @transaction_method(gas_limit=GAS['set_check_time'])
def set_check_time(self, new_check_time):
return self.contract.functions.setCheckTime(new_check_time)
def get_check_time(self):
return self.contract.functions.checkTime().call()
- @transaction_method(GAS['set_latency'])
+ @transaction_method(gas_limit=GAS['set_latency'])
def set_latency(self, new_allowable_latency):
return self.contract.functions.setLatency(new_allowable_latency)
@@ -54,7 +54,7 @@ def msr(self) -> int:
"""
return self.contract.functions.msr().call()
- @transaction_method(GAS['set_msr'])
+ @transaction_method(gas_limit=GAS['set_msr'])
def _set_msr(self, new_msr: int) -> None:
"""For internal usage only"""
return self.contract.functions.setMSR(new_msr)
@@ -62,6 +62,6 @@ def _set_msr(self, new_msr: int) -> None:
def get_launch_timestamp(self) -> int:
return self.contract.functions.launchTimestamp().call()
- @transaction_method(GAS['set_launch_timestamp'])
+ @transaction_method(gas_limit=GAS['set_launch_timestamp'])
def set_launch_timestamp(self, launch_timestamp: int):
return self.contract.functions.setLaunchTimestamp(launch_timestamp)
diff --git a/skale/contracts/delegation/delegation_controller.py b/skale/contracts/delegation/delegation_controller.py
index 777d7147..d1701ddc 100644
--- a/skale/contracts/delegation/delegation_controller.py
+++ b/skale/contracts/delegation/delegation_controller.py
@@ -126,7 +126,7 @@ def get_all_delegations_by_validator(self, validator_id: int) -> list:
delegation_ids = self._get_delegation_ids_by_validator(validator_id)
return self.get_all_delegations(delegation_ids)
- @transaction_method(GAS['delegate'])
+ @transaction_method(gas_limit=GAS['delegate'])
def delegate(self, validator_id: int, amount: int, delegation_period: int, info: str) -> TxRes:
"""Creates request to delegate amount of tokens to validator_id.
@@ -143,7 +143,7 @@ def delegate(self, validator_id: int, amount: int, delegation_period: int, info:
"""
return self.contract.functions.delegate(validator_id, amount, delegation_period, info)
- @transaction_method(GAS['accept_pending_delegation'])
+ @transaction_method(gas_limit=GAS['accept_pending_delegation'])
def accept_pending_delegation(self, delegation_id: int) -> TxRes:
"""Accepts a pending delegation by delegation ID.
@@ -154,7 +154,7 @@ def accept_pending_delegation(self, delegation_id: int) -> TxRes:
"""
return self.contract.functions.acceptPendingDelegation(delegation_id)
- @transaction_method(GAS['cancel_pending_delegation'])
+ @transaction_method(gas_limit=GAS['cancel_pending_delegation'])
def cancel_pending_delegation(self, delegation_id: int) -> TxRes:
"""Cancel pending delegation request.
@@ -165,7 +165,7 @@ def cancel_pending_delegation(self, delegation_id: int) -> TxRes:
"""
return self.contract.functions.cancelPendingDelegation(delegation_id)
- @transaction_method(GAS['request_undelegation'])
+ @transaction_method(gas_limit=GAS['request_undelegation'])
def request_undelegation(self, delegation_id: int) -> TxRes:
""" This method is for undelegating request in the end of
delegation period (3/6/12 months)
diff --git a/skale/contracts/delegation/distributor.py b/skale/contracts/delegation/distributor.py
index edf31885..2cbcaa4a 100644
--- a/skale/contracts/delegation/distributor.py
+++ b/skale/contracts/delegation/distributor.py
@@ -62,7 +62,7 @@ def get_earned_fee_amount(self, address: str) -> dict:
'from': address
})
- @transaction_method(GAS['withdraw_bounty'])
+ @transaction_method(gas_limit=GAS['withdraw_bounty'])
def withdraw_bounty(self, validator_id: int, to: str) -> TxRes:
"""Withdraw earned bounty to specified address
@@ -75,7 +75,7 @@ def withdraw_bounty(self, validator_id: int, to: str) -> TxRes:
"""
return self.contract.functions.withdrawBounty(validator_id, to)
- @transaction_method(GAS['withdraw_fee'])
+ @transaction_method(gas_limit=GAS['withdraw_fee'])
def withdraw_fee(self, to: str) -> TxRes:
"""Withdraw earned fee to specified address
diff --git a/skale/contracts/delegation/validator_service.py b/skale/contracts/delegation/validator_service.py
index 1c23caa0..b637bd83 100644
--- a/skale/contracts/delegation/validator_service.py
+++ b/skale/contracts/delegation/validator_service.py
@@ -157,12 +157,12 @@ def get_validator_node_indices(self, validator_id: int) -> list:
"""
return self.contract.functions.getValidatorNodeIndexes(validator_id).call()
- @transaction_method(GAS['enable_validator'])
+ @transaction_method(gas_limit=GAS['enable_validator'])
def _enable_validator(self, validator_id: int) -> TxRes:
"""For internal usage only"""
return self.contract.functions.enableValidator(validator_id)
- @transaction_method(GAS['disable_validator'])
+ @transaction_method(gas_limit=GAS['disable_validator'])
def _disable_validator(self, validator_id: int) -> TxRes:
"""For internal usage only"""
return self.contract.functions.disableValidator(validator_id)
@@ -175,7 +175,7 @@ def is_accepting_new_requests(self, validator_id: int) -> bool:
"""For internal usage only"""
return self.contract.functions.isAcceptingNewRequests(validator_id).call()
- @transaction_method(GAS['register_validator'])
+ @transaction_method(gas_limit=GAS['register_validator'])
def register_validator(self, name: str, description: str, fee_rate: int,
min_delegation_amount: int) -> TxRes:
"""Registers a new validator in the SKALE Manager contracts.
@@ -199,7 +199,7 @@ def get_link_node_signature(self, validator_id: int) -> str:
signed_hash = self.skale.wallet.sign_hash(unsigned_hash.hex())
return signed_hash.signature.hex()
- @transaction_method(GAS['link_node_address'])
+ @transaction_method(gas_limit=GAS['link_node_address'])
def link_node_address(self, node_address: str, signature: str) -> TxRes:
"""Link node address to your validator account.
@@ -212,7 +212,7 @@ def link_node_address(self, node_address: str, signature: str) -> TxRes:
"""
return self.contract.functions.linkNodeAddress(node_address, signature)
- @transaction_method(GAS['unlink_node_address'])
+ @transaction_method(gas_limit=GAS['unlink_node_address'])
def unlink_node_address(self, node_address: str) -> TxRes:
"""Unlink node address from your validator account.
diff --git a/skale/contracts/dkg.py b/skale/contracts/dkg.py
index a18427a1..df324a29 100644
--- a/skale/contracts/dkg.py
+++ b/skale/contracts/dkg.py
@@ -18,6 +18,7 @@
# along with SKALE.py. If not, see .
from skale.contracts import BaseContract, transaction_method
+from skale.transactions.tools import retry_tx
from skale.utils.constants import GAS
@@ -25,25 +26,29 @@ class DKG(BaseContract):
def gas_price(self):
return self.skale.gas_price * 5 // 4
- @transaction_method(GAS['dkg_broadcast'])
+ @retry_tx
+ @transaction_method(gas_limit=GAS['dkg_broadcast'])
def broadcast(self, group_index, node_index,
verification_vector, secret_key_contribution):
return self.contract.functions.broadcast(group_index, node_index,
verification_vector,
secret_key_contribution)
- @transaction_method(GAS['dkg_response'])
+ @retry_tx
+ @transaction_method(gas_limit=GAS['dkg_response'])
def response(self, group_index, from_node_index,
secret_number, multiplied_share):
return self.contract.functions.response(group_index, from_node_index,
secret_number,
multiplied_share)
- @transaction_method(GAS['dkg_alright'])
+ @retry_tx
+ @transaction_method(gas_limit=GAS['dkg_alright'])
def alright(self, group_index, from_node_index):
return self.contract.functions.alright(group_index, from_node_index)
- @transaction_method(GAS['dkg_complaint'])
+ @retry_tx
+ @transaction_method(gas_limit=GAS['dkg_complaint'])
def complaint(self, group_index, from_node_index, to_node_index):
return self.contract.functions.complaint(group_index, from_node_index,
to_node_index)
diff --git a/skale/contracts/manager.py b/skale/contracts/manager.py
index 7060aab7..f8696b7d 100644
--- a/skale/contracts/manager.py
+++ b/skale/contracts/manager.py
@@ -31,7 +31,8 @@
class Manager(BaseContract):
- @transaction_method(GAS['create_node'])
+
+ @transaction_method(gas_limit=GAS['create_node'])
def create_node(self, ip, port, name, public_ip=None):
logger.info(
f'create_node: {ip}:{port}, public ip: {public_ip} name: {name}')
@@ -74,11 +75,12 @@ def create_node_data_to_bytes(self, ip, public_ip, port, name, pk, nonce):
def create_default_schain(self, name):
lifetime = 3600
nodes_type = 4
- price_in_wei = self.skale.schains.get_schain_price(nodes_type, lifetime)
+ price_in_wei = self.skale.schains.get_schain_price(
+ nodes_type, lifetime)
return self.create_schain(lifetime, nodes_type, price_in_wei, name,
wait_for=True)
- @transaction_method(GAS['create_schain'])
+ @transaction_method(gas_limit=GAS['create_schain'])
def create_schain(self, lifetime, type_of_nodes, deposit, name):
logger.info(
f'create_schain: type_of_nodes: {type_of_nodes}, name: {name}')
@@ -108,28 +110,28 @@ def create_schain_data_to_bytes(self, lifetime, type_of_nodes, name,
)
return data_bytes
- @transaction_method(GAS['get_bounty'])
+ @transaction_method(gas_limit=GAS['get_bounty'])
def get_bounty(self, node_id):
return self.contract.functions.getBounty(node_id)
- @transaction_method(GAS['send_verdict'])
+ @transaction_method(gas_limit=GAS['send_verdict'])
def send_verdict(self, validator, node_id, downtime, latency):
return self.contract.functions.sendVerdict(validator, node_id, downtime,
latency)
- @transaction_method(GAS['send_verdicts'])
+ @transaction_method(gas_limit=GAS['send_verdicts'])
def send_verdicts(self, validator, nodes_ids, downtimes, latencies):
return self.contract.functions.sendVerdicts(validator, nodes_ids,
downtimes, latencies)
- @transaction_method(GAS['delete_node'])
+ @transaction_method(gas_limit=GAS['delete_node'])
def deregister(self, node_id):
return self.contract.functions.deleteNode(node_id)
- @transaction_method(GAS['delete_schain'])
+ @transaction_method(gas_limit=GAS['delete_schain'])
def delete_schain(self, schain_name):
return self.contract.functions.deleteSchain(schain_name)
- @transaction_method(GAS['delete_node_by_root'])
+ @transaction_method(gas_limit=GAS['delete_node_by_root'])
def delete_node_by_root(self, node_id):
return self.contract.functions.deleteNodeByRoot(node_id)
diff --git a/skale/contracts/test/time_helpers_with_debug.py b/skale/contracts/test/time_helpers_with_debug.py
index cb56712c..24e93767 100644
--- a/skale/contracts/test/time_helpers_with_debug.py
+++ b/skale/contracts/test/time_helpers_with_debug.py
@@ -25,7 +25,7 @@
class TimeHelpersWithDebug(BaseContract):
"""Wrapper for TimeHelpersWithDebug.sol functions (internal usage only)"""
- @transaction_method(GAS['skip_time'])
+ @transaction_method(gas_limit=GAS['skip_time'])
def skip_time(self, sec: int) -> TxRes:
"""Skip time on contracts
diff --git a/skale/contracts/token.py b/skale/contracts/token.py
index 25efcb2d..5aaabb18 100644
--- a/skale/contracts/token.py
+++ b/skale/contracts/token.py
@@ -23,13 +23,13 @@
class Token(BaseContract):
- @transaction_method(GAS['token_transfer'])
+ @transaction_method(gas_limit=GAS['token_transfer'])
def transfer(self, address, value):
return self.contract.functions.send(address, value, b'')
def get_balance(self, address):
return self.contract.functions.balanceOf(address).call()
- @transaction_method(GAS['token_transfer'])
+ @transaction_method(gas_limit=GAS['token_transfer'])
def add_authorized(self, address, wallet): # pragma: no cover
return self.contract.functions.addAuthorized(address)
diff --git a/skale/dataclasses/current_node_info.py b/skale/dataclasses/current_node_info.py
index dc00717f..617ad207 100644
--- a/skale/dataclasses/current_node_info.py
+++ b/skale/dataclasses/current_node_info.py
@@ -23,7 +23,7 @@
class CurrentNodeInfo(NodeInfo):
def __init__(self, node_id, node_name, base_port, bind_ip, ima_mainnet=None,
ima_mp_schain=None, ima_mp_mainnet=None, wallets=None, rotate_after_block=64,
- schain_log_level='trace', schain_log_level_config='trace'):
+ schain_log_level='info', schain_log_level_config='info'):
self.bind_ip = bind_ip
self.schain_log_level = schain_log_level
self.schain_log_level_config = schain_log_level_config
diff --git a/skale/schain_config/generator.py b/skale/schain_config/generator.py
index bacd7345..7a8c7a7e 100644
--- a/skale/schain_config/generator.py
+++ b/skale/schain_config/generator.py
@@ -79,8 +79,8 @@ def generate_schain_config(base_config, node_info, schain_info):
def generate_skale_schain_config(skale, schain_name, node_id, base_config=None, ima_mainnet=None,
ima_mp_schain=None, ima_mp_mainnet=None, wallets=None,
- rotate_after_block=64, schain_log_level='trace',
- schain_log_level_config='trace'):
+ rotate_after_block=64, schain_log_level='info',
+ schain_log_level_config='info'):
node = skale.nodes_data.get(node_id)
schain = skale.schains_data.get_by_name(schain_name)
diff --git a/skale/transactions/tools.py b/skale/transactions/tools.py
index 3dfcca0c..d51617d6 100644
--- a/skale/transactions/tools.py
+++ b/skale/transactions/tools.py
@@ -18,7 +18,10 @@
# along with SKALE.py. If not, see .
import logging
+import time
+from functools import partial, wraps
+from skale.dataclasses.tx_res import TransactionFailedError, TxRes
from skale.utils.web3_utils import get_eth_nonce
logger = logging.getLogger(__name__)
@@ -94,3 +97,47 @@ def send_eth(web3, account, amount, wallet):
f'tx: {web3.toHex(tx)}'
)
return tx
+
+
+def retry_tx(tx=None, *, max_retries=3, timeout=-1):
+ if tx is None:
+ return partial(retry_tx, max_retries=3, timeout=timeout)
+
+ @wraps(tx)
+ def wrapper(*args, **kwargs):
+ return run_tx_with_retry(
+ tx, *args,
+ max_retries=max_retries,
+ retry_timeout=timeout, **kwargs
+ )
+ return wrapper
+
+
+def run_tx_with_retry(transaction, *args, max_retries=3,
+ retry_timeout=-1,
+ **kwargs) -> TxRes:
+ success = False
+ attempt = 0
+ tx_res = None
+ exp_timeout = 1
+ while not success and attempt < max_retries:
+ try:
+ tx_res = transaction(*args, **kwargs)
+ tx_res.raise_for_status()
+ except TransactionFailedError as err:
+ logger.error(f'Tx attempt {attempt}/{max_retries} failed',
+ exc_info=err)
+ timeout = exp_timeout if retry_timeout < 0 else exp_timeout
+ time.sleep(timeout)
+ exp_timeout *= 2
+ else:
+ success = True
+ attempt += 1
+ if success:
+ logger.info(f'Tx {transaction.__name__} completed '
+ f'after {attempt}/{max_retries} retries')
+ else:
+ logger.error(
+ f'Tx {transaction.__name__} failed after '
+ f'{max_retries} retries')
+ return tx_res
diff --git a/skale/utils/helper.py b/skale/utils/helper.py
index a9eae7ac..45321e07 100644
--- a/skale/utils/helper.py
+++ b/skale/utils/helper.py
@@ -28,7 +28,6 @@
from logging import Formatter, StreamHandler
from random import randint
-
logger = logging.getLogger(__name__)
diff --git a/tests/schain_config/generator_test.py b/tests/schain_config/generator_test.py
index 7decc570..6e467b45 100644
--- a/tests/schain_config/generator_test.py
+++ b/tests/schain_config/generator_test.py
@@ -50,7 +50,7 @@ def test_generate_node_info():
assert node_info['imaMessageProxyMainNet'] == ZERO_ADDRESS
assert node_info['rotateAfterBlock'] == 128
- assert node_info['logLevel'] == 'trace'
+ assert node_info['logLevel'] == 'info'
assert node_info['logLevelConfig'] == 'info'
assert len(node_info) == NODE_INFO_LEN