Skip to content

Commit

Permalink
alt: add retries for ALT creation
Browse files Browse the repository at this point in the history
  • Loading branch information
afalaleev committed Jan 14, 2025
1 parent 6cbbe89 commit d72fceb
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 39 deletions.
2 changes: 1 addition & 1 deletion common/solana/transaction_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
]
SolRpcTxErrorInfo = _tx.TransactionErrorType
SolRpcTxFieldErrorCode = _tx.TransactionErrorFieldless
SolRpcTxIxErrorInfo = _tx.InstructionErrorType
SolRpcTxIxErrorInfo = _tx.TransactionErrorInstructionError
SolRpcTxIxFieldErrorCode = _tx.InstructionErrorFieldless
SolRpcSendTxErrorInfo = _resp.RpcSimulateTransactionResult
SolRpcNodeUnhealthyErrorInfo = _err.NodeUnhealthy
Expand Down
88 changes: 57 additions & 31 deletions common/solana_rpc/transaction_error_parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import re
from typing import Sequence
from typing import Sequence, Final

from ..neon.neon_program import NeonProg
from ..solana.log_tree_decoder import SolTxLogTreeDecoder
Expand All @@ -15,24 +15,48 @@
SolRpcSendTxErrorInfo,
SolRpcNodeUnhealthyErrorInfo,
SolRpcTxReceiptInfo,
SolRpcInvalidParamErrorInfo,
)
from ..utils.cached import cached_method, cached_property


class SolTxErrorParser:
_already_finalized_msg = "Program log: Transaction already finalized"
_log_truncated_msg = "Log truncated"
_require_resize_iter_msg = "Deployment of contract which needs more than 10kb of account space needs several"
_cb_exceeded_msg = "exceeded CUs meter at BPF instruction"
_cb_exceeded_msg_v2 = "Computational budget exceeded"
_out_of_memory_msg = "Program log: EVM Allocator out of memory"
_memory_alloc_fail_msg = "Program log: Error: memory allocation failed, out of memory"

_create_acct_re = re.compile(r"Create Account: account Address { address: \w+, base: Some\(\w+\) } already in use")
_create_neon_acct_re = re.compile(r"Program log: [a-zA-Z_/.]+:\d+ : Account \w+ - expected system owned")
_nonce_re = re.compile(r"Program log: Invalid Nonce, origin \w+ nonce (\d+) != Transaction nonce (\d+)")
_out_of_gas_re = re.compile(r"Program log: Out of Gas, limit = (\d+), required = (\d+)")
_already_finalized_msg: Final[str] = "Program log: Transaction already finalized"
_log_truncated_msg: Final[str] = "Log truncated"
_require_resize_iter_msg: Final[str] = (
"Deployment of contract which needs more than 10kb of account space needs several"
)
_cb_exceeded_msg: Final[str] = "exceeded CUs meter at BPF instruction"
_cb_exceeded_msg_v2: Final[str] = "Computational budget exceeded"
_out_of_memory_msg: Final[str] = "Program log: EVM Allocator out of memory"
_memory_alloc_fail_msg: Final[str] = "Program log: Error: memory allocation failed, out of memory"

# fmt: off
_alt_tx_error_list: Final[Sequence[SolRpcTxFieldErrorCode]] = tuple([
SolRpcTxFieldErrorCode.AddressLookupTableNotFound,
SolRpcTxFieldErrorCode.InvalidAddressLookupTableOwner,
SolRpcTxFieldErrorCode.InvalidAddressLookupTableData,
SolRpcTxFieldErrorCode.InvalidAddressLookupTableIndex,
])
_alt_ix_error_list = tuple([
SolRpcTxIxFieldErrorCode.InvalidInstructionData,
SolRpcTxIxFieldErrorCode.InvalidAccountOwner,
SolRpcTxIxFieldErrorCode.InvalidArgument,
])
# fmt: on
_alt_fail_msg: Final[str] = "Program AddressLookupTab1e1111111111111111111111111 failed: "

_create_acct_re: Final[re.Pattern] = re.compile(
r"Create Account: account Address { address: \w+, base: Some\(\w+\) } already in use"
)
_create_neon_acct_re: Final[re.Pattern] = re.compile(
r"Program log: [a-zA-Z_/.]+:\d+ : Account \w+ - expected system owned"
)
_nonce_re: Final[re.Pattern] = re.compile(
r"Program log: Invalid Nonce, origin \w+ nonce (\d+) != Transaction nonce (\d+)"
)
_out_of_gas_re: Final[re.Pattern] = re.compile(
r"Program log: Out of Gas, limit = (\d+), required = (\d+)"
)

def __init__(self, tx: SolTx, receipt: SolRpcTxReceiptInfo) -> None:
self._tx = tx
Expand All @@ -48,16 +72,23 @@ def check_if_error(self) -> bool:
return False

@cached_method
def check_if_invalid_ix_data(self) -> bool:
if isinstance(self._receipt, SolRpcInvalidParamErrorInfo):
def check_if_alt_error(self) -> bool:
if not (tx_error := self._get_tx_error()):
return False
elif tx_error in self._alt_tx_error_list:
return True
elif self._get_tx_error() == SolRpcTxFieldErrorCode.InvalidAddressLookupTableIndex:
return True
return self._get_tx_ix_error() == SolRpcTxIxFieldErrorCode.InvalidInstructionData
elif tx_error not in self._alt_ix_error_list:
return False

log_list = self._get_log_list()
for log in log_list:
if log.startswith(self._alt_fail_msg):
return True
return False

@cached_method
def check_if_cb_exceeded(self) -> bool:
if self._get_tx_ix_error() == SolRpcTxIxFieldErrorCode.ComputationalBudgetExceeded:
if self._get_tx_error() == SolRpcTxIxFieldErrorCode.ComputationalBudgetExceeded:
return True

log_list = self._get_log_list()
Expand Down Expand Up @@ -87,7 +118,7 @@ def check_if_out_of_memory(self) -> bool:
@cached_method
def check_if_require_resize_iter(self) -> bool:
if self.check_if_preprocessed_error():
if self._get_tx_ix_error() == SolRpcTxIxFieldErrorCode.ProgramFailedToComplete:
if self._get_tx_error() == SolRpcTxIxFieldErrorCode.ProgramFailedToComplete:
return True

log_list = self._get_evm_log_list()
Expand Down Expand Up @@ -115,7 +146,7 @@ def check_if_blockhash_notfound(self) -> bool:

@cached_method
def check_if_sol_account_already_exists(self) -> bool:
return self._get_tx_ix_error() == SolRpcTxIxFieldErrorCode.AccountAlreadyInitialized
return self._get_tx_error() == SolRpcTxIxFieldErrorCode.AccountAlreadyInitialized

@cached_method
def check_if_preprocessed_error(self) -> bool:
Expand Down Expand Up @@ -145,21 +176,16 @@ def get_out_of_gas_error(self) -> tuple[int, int] | None:
return int(has_gas_limit), int(req_gas_limit)
return None

def _get_tx_error(self) -> SolRpcTxErrorInfo | None:
def _get_tx_error(self) -> SolRpcTxErrorInfo | SolRpcTxIxFieldErrorCode | None:
if isinstance(self._receipt, SolRpcSendTxErrorInfo):
if isinstance(self._receipt.err, SolRpcTxFieldErrorCode):
return self._receipt.err
elif isinstance(self._receipt.err, SolRpcTxIxErrorInfo):
return self._receipt.err.err
elif isinstance(self._receipt, SolRpcTxSlotInfo):
if isinstance(self._receipt.transaction.meta.err, SolRpcTxErrorInfo):
return self._receipt.transaction.meta.err
return None

def _get_tx_ix_error(self) -> SolRpcTxIxErrorInfo | None:
if isinstance(self._receipt, SolRpcSendTxErrorInfo):
if isinstance(self._receipt.err, SolRpcTxIxErrorInfo):
return self._receipt.err.err
elif isinstance(self._receipt, SolRpcTxSlotInfo):
if isinstance(self._receipt.transaction.meta.err, SolRpcTxIxErrorInfo):
elif isinstance(self._receipt.transaction.meta.err, SolRpcTxIxErrorInfo):
return self._receipt.transaction.meta.err.err
return None

Expand Down
9 changes: 4 additions & 5 deletions common/solana_rpc/transaction_list_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from ..config.constants import ONE_BLOCK_SEC
from ..ethereum.errors import EthNonceTooLowError, EthNonceTooHighError, EthOutOfGasError
from ..solana.commit_level import SolCommit
from ..solana.errors import SolAltError
from ..solana.hash import SolBlockHash
from ..solana.signature import SolTxSig
from ..solana.transaction import SolTx
Expand Down Expand Up @@ -52,7 +53,7 @@ class Status(enum.Enum):

# Fail errors
CbExceededError = enum.auto()
InvalidIxDataError = enum.auto()
AltError = enum.auto()
RequireResizeIterError = enum.auto()
OutOfMemoryError = enum.auto()
BadNonceError = enum.auto()
Expand Down Expand Up @@ -90,7 +91,6 @@ class SolTxListSender:
SolTxSendState.Status.NoReceiptError,
SolTxSendState.Status.BlockHashNotFoundError,
SolTxSendState.Status.NodeBehindError,
SolTxSendState.Status.InvalidIxDataError,
)

def __init__(
Expand Down Expand Up @@ -454,9 +454,8 @@ def _decode_tx_status(self, tx: SolTx, now: int, tx_receipt: SolRpcTxReceiptInfo
elif tx_error_parser.check_if_neon_account_already_exists():
# no exception: neon account exists - the goal is reached
return self._DecodeResult(status.NeonAccountAlreadyExistsError, None)
elif tx_error_parser.check_if_invalid_ix_data():
_LOG.debug("invalid ix receipt %s: %s", tx, tx_receipt)
return self._DecodeResult(status.InvalidIxDataError, None)
elif tx_error_parser.check_if_alt_error():
return self._DecodeResult(status.AltError, SolAltError("Bad ALT on send tx"))
elif tx_error_parser.check_if_cb_exceeded():
if cu_consumed := tx_error_parser.cu_consumed:
_LOG.debug("CUs consumed: %s", cu_consumed)
Expand Down
4 changes: 2 additions & 2 deletions proxy/executor/strategy_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,8 @@ def _store_sol_tx_list(self):
[
(tx_state.tx, tx_state.status == tx_state.status.GoodReceipt)
for tx_state in tx_list_sender.tx_state_list
# we shouldn't retry txs with the exceed Compute Budget error
if tx_state.status != tx_state.status.CbExceededError
# we shouldn't retry txs with the exceed Compute Budget and ALT errors
if tx_state.status not in (tx_state.status.CbExceededError, tx_state.status.AltError)
]
)

Expand Down

0 comments on commit d72fceb

Please sign in to comment.