Skip to content

Commit

Permalink
Adjust strategies to accomodate latest paradigm for execute i.e. retu…
Browse files Browse the repository at this point in the history
…rn None if the machine should do nothing and wait based on this strategy.

Remove Wait exception and its uses.
  • Loading branch information
derekpierre committed Mar 4, 2024
1 parent 73e3e0e commit 013279c
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 28 deletions.
7 changes: 0 additions & 7 deletions atxm/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,6 @@ class InsufficientFunds(RPCError):
"""raised when a transaction exceeds the spending cap"""


class Wait(Exception):
"""
Raised when a strategy exceeds a limitation.
Used to mark a pending transaction as "wait, don't retry".
"""


class TransactionFaulted(Exception):
"""Raised when a transaction has been faulted."""

Expand Down
15 changes: 10 additions & 5 deletions atxm/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from web3 import Web3
from web3.types import TxParams

from atxm.exceptions import TransactionFaulted, TransactionReverted, Wait
from atxm.exceptions import TransactionFaulted, TransactionReverted
from atxm.strategies import (
AsyncTxStrategy,
FixedRateSpeedUp,
Expand Down Expand Up @@ -249,7 +249,7 @@ def __handle_active_transaction(self) -> bool:
1. paused
2. reverted (fault)
3. finalized
4. strategize: retry, wait, or fault
4. strategize: retry, do nothing and wait, or fault
Returns True if the next queued transaction can be broadcasted right now.
"""
Expand Down Expand Up @@ -323,19 +323,24 @@ def __strategize(self) -> Optional[PendingTx]:
raise RuntimeError("No active transaction to strategize")

_active_copy = deepcopy(self._tx_tracker.pending)
params_updated = False
for strategy in self._strategies:
try:
params = strategy.execute(pending=_active_copy)
except Wait as e:
log.info(f"[wait] strategy {strategy.__class__} signalled wait: {e}")
return
except TransactionFaulted as e:
self._tx_tracker.fault(fault_error=e)
return
if params:
# in case the strategy accidentally returns None
# keep the parameters as they are.
_active_copy.params.update(params)
params_updated = True

if not params_updated:
log.info(
f"[wait] strategies made no suggested updates to pending tx #{_active_copy.id} - skipping retry round"
)
return

# (!) retry the transaction with the new parameters
retry_params = TxParams(_active_copy.params)
Expand Down
45 changes: 29 additions & 16 deletions atxm/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from atxm.exceptions import (
Fault,
Wait,
TransactionFaulted,
)
from atxm.logging import log
Expand Down Expand Up @@ -69,7 +68,7 @@ class InsufficientFundsPause(AsyncTxStrategy):

_NAME = "insufficient-funds"

def execute(self, pending: PendingTx) -> TxParams:
def execute(self, pending: PendingTx) -> Optional[TxParams]:
balance = self.w3.eth.get_balance(pending._from)
if balance == 0:
self.log.warn(
Expand All @@ -81,8 +80,7 @@ def execute(self, pending: PendingTx) -> TxParams:
message="Insufficient funds",
)
# log.warn(f"Insufficient funds for transaction #{pending.params['nonce']}")
# raise Wait("Insufficient funds")
return pending.params
return None


class TimeoutStrategy(AsyncTxStrategy):
Expand Down Expand Up @@ -125,7 +123,7 @@ def __active_timed_out(self, pending: PendingTx) -> bool:
)
return False

def execute(self, pending: PendingTx) -> TxParams:
def execute(self, pending: PendingTx) -> Optional[TxParams]:
if not pending:
# should never get here
raise RuntimeError("pending tx should not be None")
Expand All @@ -137,43 +135,58 @@ def execute(self, pending: PendingTx) -> TxParams:
fault=Fault.TIMEOUT,
message="Transaction has timed out",
)
return pending.params
return None


class FixedRateSpeedUp(AsyncTxStrategy):
"""Speedup strategy for pending transactions."""

SPEEDUP_FACTOR = 1.125 # 12.5% increase
MAX_TIP = Gwei(1) # gwei maxPriorityFeePerGas per transaction
_SPEEDUP_INCREASE = 0.125 # 12.5%
_MAX_TIP = Gwei(1) # gwei maxPriorityFeePerGas per transaction

_NAME = f"speedup-{SPEEDUP_FACTOR}%"
_NAME = "speedup"

def __init__(
self,
w3: Web3,
speedup_percentage: Optional[float] = None,
max_tip: Optional[Gwei] = None,
):
super().__init__(w3)

if speedup_percentage and speedup_percentage >= 1:
raise ValueError(
f"Invalid speedup increase percentage - {speedup_percentage}"
)

self.speedup_factor = 1 + (speedup_percentage or self._SPEEDUP_INCREASE)
self.max_tip = max_tip or self._MAX_TIP

def _calculate_speedup_fee(self, pending: TxParams) -> Tuple[Wei, Wei]:
base_fee = self.w3.eth.get_block("latest")["baseFeePerGas"]
suggested_tip = self.w3.eth.max_priority_fee
_log_gas_weather(base_fee, suggested_tip)
max_priority_fee = round(
max(pending["maxPriorityFeePerGas"], suggested_tip) * self.SPEEDUP_FACTOR
max(pending["maxPriorityFeePerGas"], suggested_tip) * self.speedup_factor
)
max_fee_per_gas = round(
max(
pending["maxFeePerGas"] * self.SPEEDUP_FACTOR,
pending["maxFeePerGas"] * self.speedup_factor,
(base_fee * 2) + max_priority_fee,
)
)
return max_priority_fee, max_fee_per_gas

def execute(self, pending: PendingTx) -> TxParams:
def execute(self, pending: PendingTx) -> Optional[TxParams]:
params = pending.params
old_tip, old_max_fee = params["maxPriorityFeePerGas"], params["maxFeePerGas"]
new_tip, new_max_fee = self._calculate_speedup_fee(params)
tip_increase = round(Web3.from_wei(new_tip - old_tip, "gwei"), 4)
fee_increase = round(Web3.from_wei(new_max_fee - old_max_fee, "gwei"), 4)

if new_tip > self.MAX_TIP:
raise Wait(
f"Pending transaction maxPriorityFeePerGas exceeds spending cap {self.MAX_TIP}"
)
if new_tip > self.max_tip:
# nothing the strategy can do here - don't change the params
return None

latest_nonce = self.w3.eth.get_transaction_count(params["from"], "latest")
pending_nonce = self.w3.eth.get_transaction_count(params["from"], "pending")
Expand Down

0 comments on commit 013279c

Please sign in to comment.