bugfixes; startall works 2025-12-09

This commit is contained in:
tim
2025-12-09 15:11:58 -04:00
parent 88057607d5
commit 4936150c3b
3 changed files with 31 additions and 9 deletions

View File

@@ -42,10 +42,12 @@ class Account (LocalAccount):
# log.debug(f'available accounts: {Account._pool.qsize()}') # log.debug(f'available accounts: {Account._pool.qsize()}')
try: try:
async with asyncio.timeout(1): async with asyncio.timeout(1):
result = await Account._pool.get() result: "Account" = await Account._pool.get()
except asyncio.TimeoutError: except asyncio.TimeoutError:
log.error('waiting for an available account') log.error('waiting for an available account')
result = await Account._pool.get() result = await Account._pool.get()
# mark as out of pool
result._in_pool = False
metric.account_available.set(Account._pool.qsize()) metric.account_available.set(Account._pool.qsize())
return result return result
@@ -59,17 +61,20 @@ class Account (LocalAccount):
if Account._main_account is None: if Account._main_account is None:
Account._main_account = account Account._main_account = account
Account._pool.put_nowait(account) Account._pool.put_nowait(account)
account._in_pool = True # this account is now in the pool
Account._all.append(account) Account._all.append(account)
metric.account_available.set(Account._pool.qsize()) metric.account_available.set(Account._pool.qsize())
metric.account_total.set(len(Account._all)) metric.account_total.set(len(Account._all))
log.info(f'Account pool {[a.address for a in Account._all]}') log.info(f'Account pool {[a.address for a in Account._all]}')
def __init__(self, local_account: LocalAccount): # todo chain_id? def __init__(self, local_account: LocalAccount): # todo chain_id?
super().__init__(local_account._key_obj, local_account._publicapi) # from digging into the source code super().__init__(local_account._key_obj, local_account._publicapi) # from digging into the source code
self.chain_id = current_chain.get().id self.chain_id = current_chain.get().id
self.signing_middleware = construct_sign_and_send_raw_middleware(self) self.signing_middleware = construct_sign_and_send_raw_middleware(self)
self._nonce: Optional[int] = None self._nonce: Optional[int] = None
self.tx_id: Optional[str] = None # current transaction id self.tx_id: Optional[str] = None # current transaction id
# release() idempotency tracking
self._in_pool: bool = False
async def next_nonce(self): async def next_nonce(self):
if self._nonce is None: if self._nonce is None:
@@ -86,8 +91,21 @@ class Account (LocalAccount):
return current_w3.get().eth.get_balance(self.address) return current_w3.get().eth.get_balance(self.address)
def release(self): def release(self):
metric.account_available.set(Account._pool.qsize() + 1) """
Return this Account to the pool.
Idempotent: calling release() multiple times without a new acquire()
will only enqueue the account once.
"""
# If we're already in the pool, do nothing.
if self._in_pool:
# Optional debug log; comment out if too noisy.
# log.debug(f'Account {self.address} already in pool; ignoring extra release()')
return
Account._pool.put_nowait(self) Account._pool.put_nowait(self)
self._in_pool = True
metric.account_available.set(Account._pool.qsize())
def __str__(self): def __str__(self):
return self.address return self.address

View File

@@ -33,7 +33,8 @@ class ContractTransaction:
async def wait(self) -> TxReceipt: async def wait(self) -> TxReceipt:
if self.receipt is None: if self.receipt is None:
self.receipt = await current_w3.get().eth.wait_for_transaction_receipt(self.id) self.receipt = await current_w3.get().eth.wait_for_transaction_receipt(self.id)
self.account.release() if self.account is not None:
self.account.release()
return self.receipt return self.receipt
async def sign(self, account: Account): async def sign(self, account: Account):
@@ -153,10 +154,14 @@ class ContractProxy:
def __getattr__(self, item): def __getattr__(self, item):
if item == 'constructor': if item == 'constructor':
found = self.contract.constructor found = self.contract.constructor
elif item in self.contract.functions:
found = self.contract.functions[item]
else: else:
raise AttributeError(item) funcs = self.contract.functions
# In web3.py v6+, contract functions are exposed as attributes, not via __getitem__.
# Using getattr ensures we obtain the callable factory for the function; indexing may return None.
# Additionally, guard against unexpected None to fail fast with a clear error.
found = getattr(funcs, item, None)
if not callable(found):
raise AttributeError(f"Function '{item}' not found on contract {self._interface_name} at {self.address}")
return self._wrapper(self.address, item, found) return self._wrapper(self.address, item, found)
def __repr__(self): def __repr__(self):

View File

@@ -3,7 +3,6 @@ from dataclasses import dataclass
from typing import Optional, Union, Any from typing import Optional, Union, Any
from uuid import UUID from uuid import UUID
from triton.profiler import deactivate
from web3.exceptions import ContractPanicError, ContractLogicError from web3.exceptions import ContractPanicError, ContractLogicError
from web3.types import EventData from web3.types import EventData