diff --git a/proxy/common_neon/solana_interactor.py b/proxy/common_neon/solana_interactor.py index 56015e127..10fc20633 100644 --- a/proxy/common_neon/solana_interactor.py +++ b/proxy/common_neon/solana_interactor.py @@ -5,13 +5,16 @@ import re import time +from solana.blockhash import Blockhash from solana.rpc.api import Client as SolanaClient +from solana.rpc.api import SendTransactionError from solana.rpc.commitment import Confirmed from solana.rpc.types import TxOpts +from solana.transaction import Transaction from .costs import update_transaction_cost from .utils import get_from_dict -from ..environment import EVM_LOADER_ID, CONFIRMATION_CHECK_DELAY, LOG_SENDING_SOLANA_TRANSACTION +from ..environment import EVM_LOADER_ID, CONFIRMATION_CHECK_DELAY, LOG_SENDING_SOLANA_TRANSACTION, RETRY_ON_FAIL logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -74,14 +77,38 @@ def _getAccountData(self, account, expected_length, owner=None): def send_transaction(self, trx, eth_trx, reason=None): - reciept = self.send_transaction_unconfirmed(trx) - result = self.collect_result(reciept, eth_trx, reason) - return result - + for _i in range(RETRY_ON_FAIL): + reciept = self.send_transaction_unconfirmed(trx) + try: + return self.collect_result(reciept, eth_trx, reason) + except RuntimeError as err: + if str(err).find("could not confirm transaction") > 0: + time.sleep(0.1) + continue + raise + RuntimeError("Failed {} times to send transaction or get confirmnation {}".format(RETRY_ON_FAIL, trx.__dict__)) + + + def send_transaction_unconfirmed(self, txn: Transaction): + for _i in range(RETRY_ON_FAIL): + # TODO: Cache recent blockhash + blockhash_resp = self.client.get_recent_blockhash() # commitment=Confirmed + if not blockhash_resp["result"]: + raise RuntimeError("failed to get recent blockhash") + blockhash = blockhash_resp["result"]["value"]["blockhash"] + txn.recent_blockhash = Blockhash(blockhash) + txn.sign(self.signer) + try: + return self.client.send_raw_transaction(txn.serialize(), opts=TxOpts(preflight_commitment=Confirmed))["result"] + except SendTransactionError as err: + err_type = get_from_dict(err.result, "data", "err") + if err_type is not None and isinstance(err_type, str) and err_type == "BlockhashNotFound": + logger.debug("BlockhashNotFound {}".format(blockhash)) + time.sleep(0.1) + continue + raise + raise RuntimeError("Failed trying {} times to get Blockhash for transaction {}".format(RETRY_ON_FAIL, txn.__dict__)) - def send_transaction_unconfirmed(self, trx): - result = self.client.send_transaction(trx, self.signer, opts=TxOpts(preflight_commitment=Confirmed))["result"] - return result def collect_result(self, reciept, eth_trx, reason=None): self.confirm_transaction(reciept) @@ -89,6 +116,7 @@ def collect_result(self, reciept, eth_trx, reason=None): update_transaction_cost(result, eth_trx, reason) return result + def send_measured_transaction(self, trx, eth_trx, reason): if LOG_SENDING_SOLANA_TRANSACTION: logger.debug("send_measured_transaction for reason %s: %s ", reason, trx.__dict__) diff --git a/proxy/common_neon/transaction_sender.py b/proxy/common_neon/transaction_sender.py index 1043c9ff4..8831aedb6 100644 --- a/proxy/common_neon/transaction_sender.py +++ b/proxy/common_neon/transaction_sender.py @@ -372,16 +372,27 @@ def write_trx_to_holder_account(self): msg = self.eth_trx.signature() + len(self.eth_trx.unsigned_msg()).to_bytes(8, byteorder="little") + self.eth_trx.unsigned_msg() offset = 0 - receipts = [] rest = msg + trxs = [] while len(rest): (part, rest) = (rest[:1000], rest[1000:]) trx = self.instruction.make_write_transaction(offset, part) - receipts.append(self.sender.send_transaction_unconfirmed(trx)) + trxs.append(trx) offset += len(part) - logger.debug("receipts %s", receipts) - self.sender.collect_results(receipts, eth_trx=self.eth_trx, reason='WriteHolder') + while len(trxs) > 0: + receipts = {} + for trx in trxs: + receipts[self.sender.send_transaction_unconfirmed(trx)] = trx + + logger.debug("receipts %s", receipts) + for rcpt, trx in receipts.items(): + try: + self.sender.collect_result(rcpt, eth_trx=self.eth_trx, reason='WriteHolder') + except Exception as err: + logger.debug("collect_result exception: {}".format(str(err))) + else: + trxs.remove(trx) def call_continue(self): diff --git a/proxy/environment.py b/proxy/environment.py index 3ac27f98a..316be1659 100644 --- a/proxy/environment.py +++ b/proxy/environment.py @@ -19,6 +19,7 @@ LOG_SENDING_SOLANA_TRANSACTION = os.environ.get("LOG_SENDING_SOLANA_TRANSACTION", "NO") == "YES" LOG_NEON_CLI_DEBUG = os.environ.get("LOG_NEON_CLI_DEBUG", "NO") == "YES" WRITE_TRANSACTION_COST_IN_DB = os.environ.get("WRITE_TRANSACTION_COST_IN_DB", "NO") == "YES" +RETRY_ON_FAIL = int(os.environ.get("RETRY_ON_FAIL", "2")) class solana_cli: def call(self, *args): diff --git a/proxy/plugin/solana_rest_api.py b/proxy/plugin/solana_rest_api.py index 61f522b7a..a09e3585b 100644 --- a/proxy/plugin/solana_rest_api.py +++ b/proxy/plugin/solana_rest_api.py @@ -46,7 +46,7 @@ modelInstanceLock = threading.Lock() modelInstance = None -NEON_PROXY_PKG_VERSION = '0.4.1-rc0' +NEON_PROXY_PKG_VERSION = '0.5.1-dev' NEON_PROXY_REVISION = 'NEON_PROXY_REVISION_TO_BE_REPLACED' class EthereumModel: diff --git a/proxy/run-proxy.sh b/proxy/run-proxy.sh index 199489563..3b018d131 100755 --- a/proxy/run-proxy.sh +++ b/proxy/run-proxy.sh @@ -12,6 +12,7 @@ if [ "$CONFIG" == "ci" ]; then [[ -z "$MINIMAL_GAS_PRICE" ]] && export MINIMAL_GAS_PRICE=0 [[ -z "$POSTGRES_HOST" ]] && export POSTGRES_HOST="postgres" [[ -z "$CANCEL_TIMEOUT" ]] && export CANCEL_TIMEOUT=10 + [[ -z "$RETRY_ON_FAIL" ]] && export RETRY_ON_FAIL=10 elif [ "$CONFIG" == "local" ]; then [[ -z "$SOLANA_URL" ]] && export SOLANA_URL="http://localhost:8899" [[ -z "$EXTRA_GAS" ]] && export EXTRA_GAS=0 @@ -19,6 +20,7 @@ elif [ "$CONFIG" == "local" ]; then [[ -z "$MINIMAL_GAS_PRICE" ]] && export MINIMAL_GAS_PRICE=0 [[ -z "$POSTGRES_HOST" ]] && export POSTGRES_HOST="localhost" [[ -z "$CANCEL_TIMEOUT" ]] && export CANCEL_TIMEOUT=10 + [[ -z "$RETRY_ON_FAIL" ]] && export RETRY_ON_FAIL=10 elif [ "$CONFIG" == "devnet" ]; then [[ -z "$SOLANA_URL" ]] && export SOLANA_URL="https://api.devnet.solana.com" [[ -z "$EVM_LOADER" ]] && export EVM_LOADER=eeLSJgWzzxrqKv1UxtRVVH8FX3qCQWUs9QuAjJpETGU @@ -27,6 +29,7 @@ elif [ "$CONFIG" == "devnet" ]; then [[ -z "$MINIMAL_GAS_PRICE" ]] && export MINIMAL_GAS_PRICE=1 [[ -z "$POSTGRES_HOST" ]] && export POSTGRES_HOST="localhost" [[ -z "$CANCEL_TIMEOUT" ]] && export CANCEL_TIMEOUT=60 + [[ -z "$RETRY_ON_FAIL" ]] && export RETRY_ON_FAIL=10 elif [ "$CONFIG" == "testnet" ]; then [[ -z "$SOLANA_URL" ]] && export SOLANA_URL="https://api.testnet.solana.com" [[ -z "$EVM_LOADER" ]] && export EVM_LOADER=eeLSJgWzzxrqKv1UxtRVVH8FX3qCQWUs9QuAjJpETGU @@ -35,6 +38,7 @@ elif [ "$CONFIG" == "testnet" ]; then [[ -z "$MINIMAL_GAS_PRICE" ]] && export MINIMAL_GAS_PRICE="1" [[ -z "$POSTGRES_HOST" ]] && export POSTGRES_HOST="localhost" [[ -z "$CANCEL_TIMEOUT" ]] && export CANCEL_TIMEOUT=60 + [[ -z "$RETRY_ON_FAIL" ]] && export RETRY_ON_FAIL=10 elif [ "$CONFIG" != "custom" ]; then exit 1 fi diff --git a/setup.py b/setup.py index 0d2c67516..7d8e87305 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ """ from setuptools import setup, find_packages -VERSION = (2, 2, 0) +VERSION = (0, 5, 1) __version__ = '.'.join(map(str, VERSION[0:3])) __description__ = '''⚡⚡⚡Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on Network monitoring, controls & Application development, testing, debugging.'''