diff --git a/src/contract.py b/src/contract.py index 6f61221..821c675 100644 --- a/src/contract.py +++ b/src/contract.py @@ -15,13 +15,13 @@ MODULE_ROOT_PATH = pathlib.Path(__file__).parent.parent.resolve() -class LoggableReceipt(): +class LoggableReceipt: def __init__(self, fields, fail_reason=None): - self.blockNumber = fields['blockNumber'] - self.gasUsed = fields['gasUsed'] - self.status = fields['status'] - self.txHash = fields['transactionHash'].hex() - self.txIndex = fields['transactionIndex'] + self.blockNumber = fields["blockNumber"] + self.gasUsed = fields["gasUsed"] + self.status = fields["status"] + self.txHash = fields["transactionHash"].hex() + self.txIndex = fields["transactionIndex"] def succeeded(self): return self.status == 1 @@ -34,24 +34,22 @@ def __str__(self): ) -class LoggableBounce(): +class LoggableBounce: def __init__(self, tx_hash, err, details=None): self.txHash = tx_hash.hex() self.err = err self.details = details def __str__(self): - detail_parts = ''.join([f" {k}={v}" for k, v in self.details.items()]) + detail_parts = "".join([f" {k}={v}" for k, v in self.details.items()]) - return ( - f"txHash=0x{self.txHash}" - f" err={repr(self.err)}" - f"{detail_parts}" - ) + return f"txHash=0x{self.txHash}" f" err={repr(self.err)}" f"{detail_parts}" class ProofChainContract: - def __init__(self, rpc_endpoint, finalizer_address, finalizer_prvkey, proofchain_address): + def __init__( + self, rpc_endpoint, finalizer_address, finalizer_prvkey, proofchain_address + ): self.nonce = None self.counter = 0 self.finalizer_address = finalizer_address @@ -59,13 +57,12 @@ def __init__(self, rpc_endpoint, finalizer_address, finalizer_prvkey, proofchain self.provider: Web3.HTTPProvider = Web3.HTTPProvider(rpc_endpoint) self.w3: Web3 = Web3(self.provider) self.gas = int(os.getenv("GAS_LIMIT")) - self.gasPrice = web3.auto.w3.toWei(os.getenv('GAS_PRICE'), 'gwei') + self.gasPrice = web3.auto.w3.toWei(os.getenv("GAS_PRICE"), "gwei") self.w3.middleware_onion.inject(geth_poa_middleware, layer=0) self.contractAddress: str = proofchain_address - with (MODULE_ROOT_PATH / 'abi' / 'ProofChainContractABI').open('r') as f: + with (MODULE_ROOT_PATH / "abi" / "ProofChainContractABI").open("r") as f: self.contract = self.w3.eth.contract( - address=self.contractAddress, - abi=f.read() + address=self.contractAddress, abi=f.read() ) self.logger = logformat.get_logger("Contract") @@ -103,7 +100,9 @@ def _retry_with_backoff(self, fn, retries=2, backoff_in_seconds=1, **kwargs): ex_desc = "\n".join(traceback.format_exception_only(ex)) self.logger.warning(f"exception occurred (will retry): {ex_desc}") - sleep_interval = (backoff_in_seconds * (2 ** exp)) + random.uniform(0, 1) + sleep_interval = (backoff_in_seconds * (2**exp)) + random.uniform( + 0, 1 + ) time.sleep(sleep_interval) retries_left -= 1 exp += 1 @@ -117,17 +116,23 @@ def _attempt_send_finalize(self, chainId, blockHeight, timeout=None): self.gasPrice = self.w3.eth.gasPrice self.logger.info(f"TX dynamic gas price is {self.gasPrice}") transaction = self.contract.functions.finalizeAndRewardSpecimenSession( - chainId, - blockHeight).buildTransaction({ - 'gas': self.gas, - 'gasPrice': self.gasPrice, - 'from': self.finalizer_address, - 'nonce': self.nonce - }) - signed_txn = self.w3.eth.account.signTransaction(transaction, private_key=self.finalizer_prvkey) + chainId, blockHeight + ).buildTransaction( + { + "gas": self.gas, + "gasPrice": self.gasPrice, + "from": self.finalizer_address, + "nonce": self.nonce, + } + ) + signed_txn = self.w3.eth.account.signTransaction( + transaction, private_key=self.finalizer_prvkey + ) balance_before_send_wei = self.w3.eth.get_balance(self.finalizer_address) - balance_before_send_glmr = web3.auto.w3.fromWei(balance_before_send_wei, 'ether') + balance_before_send_glmr = web3.auto.w3.fromWei( + balance_before_send_wei, "ether" + ) predicted_tx_hash = eth_hash.auto.keccak(signed_txn.rawTransaction) @@ -147,13 +152,19 @@ def _attempt_send_finalize(self, chainId, blockHeight, timeout=None): raise jsonrpc_err = ex.args[0] - if 'code' not in jsonrpc_err or 'message' not in jsonrpc_err: + if "code" not in jsonrpc_err or "message" not in jsonrpc_err: raise - match (jsonrpc_err['code'], jsonrpc_err['message']): - case (-32603, 'nonce too low'): - self.report_transaction_bounce(predicted_tx_hash, err="nonce too low", details={"txNonce": self.nonce}) - self.logger.info("Pausing to allow pending txs to clear, then refreshing nonce...") + match (jsonrpc_err["code"], jsonrpc_err["message"]): + case (-32603, "nonce too low"): + self.report_transaction_bounce( + predicted_tx_hash, + err="nonce too low", + details={"txNonce": self.nonce}, + ) + self.logger.info( + "Pausing to allow pending txs to clear, then refreshing nonce..." + ) time.sleep(60) self._refresh_nonce() @@ -171,8 +182,12 @@ def report_transaction_receipt(self, tx_hash, timeout=None, **kwargs): return (True, None) try: - self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout, poll_latency=1.0) - receipt = LoggableReceipt(self.w3.eth.get_transaction_receipt(tx_hash), **kwargs) + self.w3.eth.wait_for_transaction_receipt( + tx_hash, timeout=timeout, poll_latency=1.0 + ) + receipt = LoggableReceipt( + self.w3.eth.get_transaction_receipt(tx_hash), **kwargs + ) if receipt.succeeded(): self.nonce += 1 @@ -195,7 +210,7 @@ def block_number(self): return self._retry_with_backoff(self._attempt_block_number) def _attempt_block_number(self): - return (True, self.w3.eth.get_block('latest').number) + return (True, self.w3.eth.get_block("latest").number) # def subscribe_on_event(self, cb, from_block=1): # event_filter = self.contract.events.SessionStarted.createFilter(fromBlock=from_block) @@ -210,7 +225,9 @@ def _attempt_block_number(self): # # close loop to free up system resources # loop.close() def estimate_gas_price(self): - pending_transactions = self.provider.make_request("parity_futureTransactions", []) + pending_transactions = self.provider.make_request( + "parity_futureTransactions", [] + ) gas_prices = [] gases = [] print(pending_transactions) diff --git a/src/finalizationrequest.py b/src/finalizationrequest.py index c404844..536f610 100644 --- a/src/finalizationrequest.py +++ b/src/finalizationrequest.py @@ -36,12 +36,16 @@ def update_block_id(self, bid): def confirm_request(self): if self.chainId not in FinalizationRequest.requests_to_be_confirmed: return None - FinalizationRequest.requests_to_be_confirmed[self.chainId].pop(self.blockHeight, None) + FinalizationRequest.requests_to_be_confirmed[self.chainId].pop( + self.blockHeight, None + ) def finalize_request(self): if self.chainId not in FinalizationRequest.requests_to_be_finalized: return None - FinalizationRequest.requests_to_be_finalized[self.chainId].pop(self.blockHeight, None) + FinalizationRequest.requests_to_be_finalized[self.chainId].pop( + self.blockHeight, None + ) self.finalized_time = time.time() @@ -66,9 +70,15 @@ def confirm_later(self): def waiting_for_confirm(self): if self.chainId not in FinalizationRequest.requests_to_be_confirmed: return False - return self.blockHeight in FinalizationRequest.requests_to_be_confirmed[self.chainId] + return ( + self.blockHeight + in FinalizationRequest.requests_to_be_confirmed[self.chainId] + ) def waiting_for_finalize(self): if self.chainId not in FinalizationRequest.requests_to_be_finalized: return False - return self.blockHeight in FinalizationRequest.requests_to_be_finalized[self.chainId] + return ( + self.blockHeight + in FinalizationRequest.requests_to_be_finalized[self.chainId] + ) diff --git a/src/finalizer.py b/src/finalizer.py index 29853cb..edcff01 100644 --- a/src/finalizer.py +++ b/src/finalizer.py @@ -23,7 +23,7 @@ def wait_for_next_observer_chain_block(self): return time.sleep(4.0) except Exception as ex: - self.logger.critical(''.join(traceback.format_exception(ex))) + self.logger.critical("".join(traceback.format_exception(ex))) time.sleep(4.0) def __main_loop(self): @@ -40,7 +40,9 @@ def __main_loop(self): open_session_count += 1 if len(ready_to_finalize) == 0: - self.logger.debug(f"Nothing ready to finalize height={self.observer_chain_block_height} openSessions={open_session_count}") + self.logger.debug( + f"Nothing ready to finalize height={self.observer_chain_block_height} openSessions={open_session_count}" + ) return self.logger.info(f"Finalizing {len(ready_to_finalize)} proof-sessions...") @@ -76,8 +78,10 @@ def refinalize_rejected_requests(self): def _attempt_to_finalize(self, fr): try: - self.contract.send_finalize(chainId=int(fr.chainId), blockHeight=int(fr.blockHeight), timeout=300) + self.contract.send_finalize( + chainId=int(fr.chainId), blockHeight=int(fr.blockHeight), timeout=300 + ) fr.finalize_request() fr.confirm_later() except Exception as ex: - self.logger.critical(''.join(traceback.format_exception(ex))) + self.logger.critical("".join(traceback.format_exception(ex))) diff --git a/src/logformat.py b/src/logformat.py index b5934d8..05ba322 100644 --- a/src/logformat.py +++ b/src/logformat.py @@ -16,7 +16,9 @@ class LogFormat(logging.Formatter): logging.INFO: logging.Formatter(FMT_TEMPLATE + ANSI_RESET), logging.WARNING: logging.Formatter(ANSI_YELLOW + FMT_TEMPLATE + ANSI_RESET), logging.ERROR: logging.Formatter(ANSI_RED + FMT_TEMPLATE + ANSI_RESET), - logging.CRITICAL: logging.Formatter(ANSI_BOLD + ANSI_RED + FMT_TEMPLATE + ANSI_RESET) + logging.CRITICAL: logging.Formatter( + ANSI_BOLD + ANSI_RED + FMT_TEMPLATE + ANSI_RESET + ), } def format(self, record): diff --git a/src/main.py b/src/main.py index 879d9af..8583052 100644 --- a/src/main.py +++ b/src/main.py @@ -29,20 +29,20 @@ def is_any_thread_alive(threads): logging.basicConfig( stream=sys.stdout, format="%(levelname)s %(name)s (%(filename)s:%(lineno)d) - %(message)s", - level=logging.INFO + level=logging.INFO, ) contract = ProofChainContract( rpc_endpoint=RPC_ENDPOINT, proofchain_address=PROOFCHAIN_ADDRESS, finalizer_prvkey=FINALIZER_PRIVATE_KEY, - finalizer_address=FINALIZER_ADDRESS + finalizer_address=FINALIZER_ADDRESS, ) dbm = DBManager( starting_point=int(BLOCK_ID_START), user=DB_USER, password=DB_PASSWORD, database=DB_DATABASE, - host=DB_HOST + host=DB_HOST, ) dbm.daemon = True