Skip to content

Commit

Permalink
Merge pull request #4 from SecretSaturn/relayer-improvements
Browse files Browse the repository at this point in the history
Relayer improvements
  • Loading branch information
SecretSaturn authored Apr 26, 2024
2 parents 70ac2a4 + 90c48e7 commit ec296f0
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 37 deletions.
81 changes: 69 additions & 12 deletions TNLS-Relayers/eth_interface.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import json
import os
from copy import deepcopy
from logging import getLogger, basicConfig, INFO, StreamHandler
from typing import List
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock
from threading import Lock, Timer
from time import sleep

from web3 import Web3, middleware
from web3.datastructures import AttributeDict
Expand All @@ -18,12 +18,9 @@ class EthInterface(BaseChainInterface):
Implementaion of BaseChainInterface for eth.
"""

def __init__(self, private_key="", address="", provider=None, contract_address = "", chain_id="", api_endpoint="", timeout = 1, **_kwargs):
def __init__(self, private_key="", address="", provider=None, contract_address="", chain_id="", api_endpoint="", timeout=1, sync_interval=30, **_kwargs):
if provider is None:
"""
If we don't have a set provider, read it from config.
"""

# If no provider, set a default with middleware for various blockchain scenarios
provider = Web3(Web3.HTTPProvider(api_endpoint, request_kwargs={'timeout': timeout}))
provider.middleware_onion.inject(geth_poa_middleware, layer=0)
provider.middleware_onion.add(middleware.time_based_cache_middleware)
Expand All @@ -37,13 +34,53 @@ def __init__(self, private_key="", address="", provider=None, contract_address =
self.chain_id = chain_id
self.nonce = self.provider.eth.get_transaction_count(self.address, 'pending')

# Set up logging
basicConfig(
level=INFO,
format="%(asctime)s [Eth Interface: %(levelname)8.8s] %(message)s",
handlers=[StreamHandler()],
)
self.logger = getLogger()
pass

# Initialize lock, executor, and sync interval
self.nonce_lock = Lock()
self.timer = None
self.sync_interval = sync_interval
self.executor = ThreadPoolExecutor(max_workers=1)

# Schedule nonce synchronization
self.schedule_sync()

def schedule_sync(self):
"""
Schedule nonce sync task with the executor and restart the timer
"""
try:
self.executor.submit(self.sync_nonce)
except Exception as e:
self.logger.error(f"Error during Ethereum nonce sync: {e}")
finally:
# Re-run the sync at specified intervals
self.timer = Timer(self.sync_interval, self.schedule_sync)
self.timer.start()

def sync_nonce(self):
"""
Sync the nonce with the latest data from the provider
"""
try:
with self.nonce_lock:
self.logger.info(f"Starting Chain-id {self.chain_id} nonce sync")
sleep(1) # Introduce a delay if needed to reduce frequency of sync errors
new_nonce = self.provider.eth.get_transaction_count(self.address, 'pending')
if self.nonce is None or new_nonce >= self.nonce:
self.nonce = new_nonce
self.logger.info(f"Chain-id {self.chain_id} nonce synced")
else:
self.logger.warning(
f"New nonce {new_nonce} is not greater than or equal to the old nonce {self.nonce}.")
except Exception as e:
self.logger.error(f"Error syncing nonce: {e}")

def create_transaction(self, contract_function, *args, **kwargs):
"""
Expand Down Expand Up @@ -122,12 +159,32 @@ def get_last_txs(self, block_number=None, contract_interface=None):
block_number = self.get_last_block()

valid_transactions = contract_interface.contract.events.logNewTask().get_logs(
fromBlock=block_number,
fromBlock=block_number
)

if len(valid_transactions) == 0:
return []

transaction_hashes = [event['transactionHash'].hex() for event in valid_transactions]
try:
block_transactions = self.provider.eth.get_block(block_number, full_transactions=True)['transactions']
filtered_transactions = [tx for tx in block_transactions if tx['hash'].hex() in transaction_hashes]
# Fetch block with transaction hashes only
block_data = self.provider.eth.get_block(block_number, full_transactions=False)

# Convert transaction hashes to hexadecimal strings
transaction_hashes_hex = [tx.hex() for tx in block_data['transactions']]

# Set of required transaction hashes in hexadecimal
required_hashes = set(transaction_hashes) # Your predefined list of hashes

# Find matching transaction hashes
matching_hashes = set(transaction_hashes_hex).intersection(required_hashes)

# Fetch full details for only the required transactions
filtered_transactions = []
if matching_hashes:
# Fetch each transaction individually based on matching hashes
for hash in matching_hashes:
filtered_transactions.append(self.provider.eth.get_transaction(hash))
except Exception as e:
self.logger.warning(e)
return []
Expand Down Expand Up @@ -199,7 +256,7 @@ def call_function(self, function_name, *args):
if isinstance(value, list):
args[i] = tuple(value)
kwargs = None
with self.lock:
with self.lock and self.interface.nonce_lock:
if kwargs is None:
txn = self.interface.create_transaction(function, *args)
elif args is None:
Expand Down
62 changes: 51 additions & 11 deletions TNLS-Relayers/scrt_interface.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import json
from copy import deepcopy
from logging import getLogger, basicConfig, DEBUG, StreamHandler
from threading import Lock
from threading import Lock, Timer
from concurrent.futures import ThreadPoolExecutor
from typing import List
import asyncio
from time import sleep

from secret_sdk.client.lcd import LCDClient
from secret_sdk.client.lcd.api.tx import CreateTxOptions, BroadcastMode
Expand All @@ -20,9 +21,7 @@ class SCRTInterface(BaseChainInterface):
NOTE: the below default private key is for testing only, and does not correspond to any real account/wallet
"""

def __init__(self, private_key="",
address=None, api_url="", chain_id="", provider=None, feegrant_address = None,
**kwargs):
def __init__(self, private_key="", address=None, api_url="", chain_id="", provider=None, feegrant_address=None, sync_interval=30, **kwargs):
if isinstance(private_key, str):
self.private_key = RawKey.from_hex(private_key)
else:
Expand All @@ -33,14 +32,55 @@ def __init__(self, private_key="",
self.provider = provider
self.address = address
self.feegrant_address = feegrant_address
assert self.address == str(self.private_key.acc_address), f"Address {self.address} and private key " \
f"{self.private_key.acc_address} mismatch"
assert self.address == str(self.private_key.acc_address), f"Address {self.address} and private key {self.private_key.acc_address} mismatch"
self.wallet = self.provider.wallet(self.private_key)
account_number_and_sequence = self.wallet.account_number_and_sequence()
self.account_number = account_number_and_sequence['account_number']
self.sequence = int(account_number_and_sequence['sequence'])
self.logger = getLogger()

# Initialize account number and sequence
self.account_number = None
self.sequence = None

self.timer = None;

self.sequence_lock = Lock()

self.sync_interval = sync_interval
self.executor = ThreadPoolExecutor(max_workers=1)
self.schedule_sync()

def schedule_sync(self):
"""
Schedule the sync task with the executor and restart the timer
"""
try:
self.executor.submit(self.sync_account_number_and_sequence)
except Exception as e:
self.logger.error(f"Error during Secret sequence sync: {e}")
finally:
self.timer = Timer(self.sync_interval, self.schedule_sync)
self.timer.start()


def sync_account_number_and_sequence(self):
"""
Syncs the account number and sequence with the latest data from the provider
"""
try:
with self.sequence_lock:
self.logger.info("Starting Secret sequence sync")
sleep(3)
account_info = self.wallet.account_number_and_sequence()
self.account_number = account_info['account_number']
new_sequence = int(account_info['sequence'])
if self.sequence is None or new_sequence >= self.sequence:
self.sequence = new_sequence
self.logger.info("Secret sequence synced")
else:
self.logger.warning(
f"New sequence {new_sequence} is not greater than the old sequence {self.sequence}.")
except Exception as e:
self.logger.error(f"Error syncing account number and sequence: {e}")

def sign_and_send_transaction(self, tx):
"""
Signs and broadcasts a transaction to the network, returns the broadcast receipt
Expand Down Expand Up @@ -229,7 +269,7 @@ def call_function(self, function_name, *args):
args = args[0]
if isinstance(args, str):
args = json.loads(args)
with self.lock:
with self.lock and self.interface.sequence_lock:
txn = self.construct_txn(function_schema, function_name, args)
transaction_result = self.interface.sign_and_send_transaction(txn)
try:
Expand Down
28 changes: 14 additions & 14 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
#Testnet Chains

"11155111": #Ethereum Sepolia
active: false
active: true
type: "evm"
chain_id: "11155111"
api_endpoint: https://eth-sepolia-public.unifra.io
Expand All @@ -116,17 +116,17 @@


"80002": #Polygon Amoy
active: false
active: true
type: "evm"
chain_id: "80002"
api_endpoint: https://polygon-amoy.gateway.tenderly.co
api_endpoint: https://rpc-amoy.polygon.technology
contract_address: "0x8EaAB5e8551781F3E8eb745E7fcc7DAeEFd27b1f"
timeout: 2
contract_schema: '[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"increaseTaskId","inputs":[{"name":"_newTaskId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"initialize","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"payoutBalance","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"postExecution","inputs":[{"name":"_taskId","type":"uint256","internalType":"uint256"},{"name":"_sourceNetwork","type":"string","internalType":"string"},{"name":"_info","type":"tuple","internalType":"struct Gateway.PostExecutionInfo","components":[{"name":"payload_hash","type":"bytes32","internalType":"bytes32"},{"name":"packet_hash","type":"bytes32","internalType":"bytes32"},{"name":"callback_address","type":"bytes20","internalType":"bytes20"},{"name":"callback_selector","type":"bytes4","internalType":"bytes4"},{"name":"callback_gas_limit","type":"bytes4","internalType":"bytes4"},{"name":"packet_signature","type":"bytes","internalType":"bytes"},{"name":"result","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"requestRandomness","inputs":[{"name":"_numWords","type":"uint32","internalType":"uint32"},{"name":"_callbackGasLimit","type":"uint32","internalType":"uint32"}],"outputs":[{"name":"requestId","type":"uint256","internalType":"uint256"}],"stateMutability":"payable"},{"type":"function","name":"send","inputs":[{"name":"_payloadHash","type":"bytes32","internalType":"bytes32"},{"name":"_userAddress","type":"address","internalType":"address"},{"name":"_routingInfo","type":"string","internalType":"string"},{"name":"_info","type":"tuple","internalType":"struct Gateway.ExecutionInfo","components":[{"name":"user_key","type":"bytes","internalType":"bytes"},{"name":"user_pubkey","type":"bytes","internalType":"bytes"},{"name":"routing_code_hash","type":"string","internalType":"string"},{"name":"task_destination_network","type":"string","internalType":"string"},{"name":"handle","type":"string","internalType":"string"},{"name":"nonce","type":"bytes12","internalType":"bytes12"},{"name":"callback_gas_limit","type":"uint32","internalType":"uint32"},{"name":"payload","type":"bytes","internalType":"bytes"},{"name":"payload_signature","type":"bytes","internalType":"bytes"}]}],"outputs":[],"stateMutability":"payable"},{"type":"function","name":"taskId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"tasks","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"payload_hash_reduced","type":"bytes31","internalType":"bytes31"},{"name":"completed","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeHandler","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint64","indexed":false,"internalType":"uint64"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"TaskCompleted","inputs":[{"name":"taskId","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"callbackSuccessful","type":"bool","indexed":false,"internalType":"bool"}],"anonymous":false},{"type":"event","name":"logNewTask","inputs":[{"name":"task_id","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"source_network","type":"string","indexed":false,"internalType":"string"},{"name":"user_address","type":"address","indexed":false,"internalType":"address"},{"name":"routing_info","type":"string","indexed":false,"internalType":"string"},{"name":"payload_hash","type":"bytes32","indexed":false,"internalType":"bytes32"},{"name":"info","type":"tuple","indexed":false,"internalType":"struct Gateway.ExecutionInfo","components":[{"name":"user_key","type":"bytes","internalType":"bytes"},{"name":"user_pubkey","type":"bytes","internalType":"bytes"},{"name":"routing_code_hash","type":"string","internalType":"string"},{"name":"task_destination_network","type":"string","internalType":"string"},{"name":"handle","type":"string","internalType":"string"},{"name":"nonce","type":"bytes12","internalType":"bytes12"},{"name":"callback_gas_limit","type":"uint32","internalType":"uint32"},{"name":"payload","type":"bytes","internalType":"bytes"},{"name":"payload_signature","type":"bytes","internalType":"bytes"}]}],"anonymous":false},{"type":"error","name":"InvalidBytesLength","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"InvalidPacketSignature","inputs":[]},{"type":"error","name":"InvalidPayloadHash","inputs":[]},{"type":"error","name":"InvalidSignature","inputs":[]},{"type":"error","name":"InvalidSignatureLength","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]},{"type":"error","name":"PaidRequestFeeTooLow","inputs":[]},{"type":"error","name":"TaskAlreadyCompleted","inputs":[]},{"type":"error","name":"TooManyVRFRandomWordsRequested","inputs":[]}]'
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"84532": #Base Sepolia
active: false
active: true
type: "evm"
chain_id: "84532"
api_endpoint: https://sepolia.base.org
Expand All @@ -136,7 +136,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"11155420": #Optmism Sepolia
active: false
active: true
type: "evm"
chain_id: "11155420"
api_endpoint: https://sepolia.optimism.io
Expand All @@ -146,7 +146,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"421614": #Arbitrum Sepolia
active: False
active: true
type: "evm"
chain_id: "421614"
api_endpoint: https://sepolia-rollup.arbitrum.io/rpc
Expand All @@ -156,7 +156,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"80085": #Berachain Artio
active: false
active: true
type: "evm"
chain_id: "80085"
api_endpoint: https://artio.rpc.berachain.com/
Expand All @@ -176,7 +176,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"128123": #Tezos Etherlink Testnet
active: false
active: true
type: "evm"
chain_id: "128123"
api_endpoint: https://node.ghostnet.etherlink.com
Expand All @@ -186,7 +186,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"107107114116": #Kakarot Sepolia
active: false
active: true
type: "evm"
chain_id: "107107114116"
api_endpoint: https://sepolia-rpc.kakarot.org
Expand All @@ -196,7 +196,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"59902": #Metis Sepolia
active: false
active: true
type: "evm"
chain_id: "59902"
api_endpoint: https://sepolia.metisdevops.link
Expand All @@ -206,7 +206,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"1313161555": #Near Aurora Testnet
active: false
active: true
type: "evm"
chain_id: "1313161555"
api_endpoint: https://testnet.aurora.dev
Expand All @@ -216,7 +216,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"59141": #Linea Sepolia
active: false
active: true
type: "evm"
chain_id: "59141"
api_endpoint: https://rpc.sepolia.linea.build
Expand All @@ -226,7 +226,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"51": #XDC Apothem
active: false
active: true
type: "evm"
chain_id: "51"
api_endpoint: https://erpc.apothem.network
Expand All @@ -236,7 +236,7 @@
wallet_address: "0xbb6B8abe049466f637b3Ac648E7Dd9850E193346"

"4202": #Lisk Sepolia
active: false
active: true
type: "evm"
chain_id: "4202"
api_endpoint: https://rpc.sepolia-api.lisk.com
Expand Down

0 comments on commit ec296f0

Please sign in to comment.