Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sort transactions by execution time #693

Merged
merged 3 commits into from
Apr 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion proxy/airdropper/airdropper.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def process_functions(self):

def process_receipts(self):
max_slot = 0
for slot, _, trx in self.transaction_receipts.get_trxs(self.latest_processed_slot):
for slot, _, trx in self.transaction_receipts.get_txs(self.latest_processed_slot):
max_slot = max(max_slot, slot)
if trx['transaction']['message']['instructions'] is not None:
self.process_trx_airdropper_mode(trx)
Expand Down
25 changes: 1 addition & 24 deletions proxy/common_neon/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from eth_keys import keys as eth_keys
from hashlib import sha256
from solana.publickey import PublicKey
from typing import NamedTuple


class EthereumAddress:
Expand Down Expand Up @@ -36,7 +35,7 @@ def __repr__(self):
def __bytes__(self): return self.data


def accountWithSeed(base, seed):
def accountWithSeed(base: bytes, seed: bytes) -> PublicKey:
from ..environment import EVM_LOADER_ID

result = PublicKey(sha256(bytes(base) + bytes(seed) + bytes(PublicKey(EVM_LOADER_ID))).digest())
Expand All @@ -59,25 +58,3 @@ def ether2program(ether):
seed = [ACCOUNT_SEED_VERSION, bytes.fromhex(ether)]
(pda, nonce) = PublicKey.find_program_address(seed, PublicKey(EVM_LOADER_ID))
return str(pda), nonce


class AccountInfoLayout(NamedTuple):
ether: eth_keys.PublicKey
balance: int
trx_count: int
code_account: PublicKey

def is_payed(self) -> bool:
return self.state != 0

@staticmethod
def frombytes(data) -> AccountInfoLayout:
from .layouts import ACCOUNT_INFO_LAYOUT

cont = ACCOUNT_INFO_LAYOUT.parse(data)
return AccountInfoLayout(
ether=cont.ether,
balance=int.from_bytes(cont.balance, "little"),
trx_count=int.from_bytes(cont.trx_count, "little"),
code_account=PublicKey(cont.code_account)
)
5 changes: 3 additions & 2 deletions proxy/common_neon/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@

COLLATERALL_POOL_MAX=10

EMPTY_STORAGE_TAG=0
FINALIZED_STORAGE_TAG=5
EMPTY_STORAGE_TAG = 0
FINALIZED_STORAGE_TAG = 5
ACTIVE_STORAGE_TAG = 30
4 changes: 2 additions & 2 deletions proxy/common_neon/layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
from construct import Struct

STORAGE_ACCOUNT_INFO_LAYOUT = Struct(
# "tag" / Int8ul,
"tag" / Int8ul,
"caller" / Bytes(20),
"nonce" / Int64ul,
"gas_limit" / Bytes(32),
"gas_price" / Bytes(32),
"slot" / Int64ul,
"operator" / Bytes(32),
"accounts_len" / Int64ul,
"account_list_len" / Int64ul,
"executor_data_size" / Int64ul,
"evm_data_size" / Int64ul,
"gas_used_and_paid" / Bytes(32),
Expand Down
10 changes: 7 additions & 3 deletions proxy/common_neon/neon_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,17 +236,21 @@ def make_noniterative_call_transaction(self, length_before: int = 0) -> Transact
trx.add(self.make_05_call_instruction())
return trx

def make_cancel_transaction(self, cancel_keys=None) -> Transaction:
def make_cancel_transaction(self, storage=None, nonce=None, cancel_keys=None) -> Transaction:
if cancel_keys:
append_keys = cancel_keys
else:
append_keys = self.eth_accounts
append_keys += obligatory_accounts
if not nonce:
nonce = self.eth_trx.nonce
if not storage:
storage = self.storage
return TransactionWithComputeBudget().add(TransactionInstruction(
program_id = EVM_LOADER_ID,
data = bytearray.fromhex("15") + self.eth_trx.nonce.to_bytes(8, 'little'),
data = bytearray.fromhex("15") + nonce.to_bytes(8, 'little'),
keys=[
AccountMeta(pubkey=self.storage, is_signer=False, is_writable=True),
AccountMeta(pubkey=storage, is_signer=False, is_writable=True),
AccountMeta(pubkey=self.operator_account, is_signer=True, is_writable=True),
AccountMeta(pubkey=INCINERATOR_PUBKEY, is_signer=False, is_writable=True),
] + append_keys
Expand Down
107 changes: 98 additions & 9 deletions proxy/common_neon/solana_interactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import time
import traceback
import requests
import json

from typing import Optional

Expand All @@ -24,9 +25,8 @@
from ..environment import FUZZING_BLOCKHASH, CONFIRM_TIMEOUT, FINALIZED
from ..environment import RETRY_ON_FAIL

from ..common_neon.layouts import ACCOUNT_INFO_LAYOUT
from ..common_neon.layouts import ACCOUNT_INFO_LAYOUT, STORAGE_ACCOUNT_INFO_LAYOUT
from ..common_neon.address import EthereumAddress, ether2program
from ..common_neon.address import AccountInfoLayout
from ..common_neon.utils import get_from_dict


Expand All @@ -37,6 +37,78 @@ class AccountInfo(NamedTuple):
data: bytes


class NeonAccountInfo(NamedTuple):
ether: str
nonce: int
trx_count: int
balance: int
code_account: PublicKey
is_rw_blocked: bool
ro_blocked_cnt: int

@staticmethod
def frombytes(data) -> NeonAccountInfo:
cont = ACCOUNT_INFO_LAYOUT.parse(data)
return NeonAccountInfo(
ether=cont.ether.hex(),
nonce=cont.nonce,
trx_count=int.from_bytes(cont.trx_count, "little"),
balance=int.from_bytes(cont.balance, "little"),
code_account=PublicKey(cont.code_account),
is_rw_blocked=(cont.is_rw_blocked != 0),
ro_blocked_cnt=cont.ro_blocked_cnt
)


class StorageAccountInfo(NamedTuple):
tag: int
caller: str
nonce: int
gas_limit: int
gas_price: int
slot: int
operator: PublicKey
account_list_len: int
executor_data_size: int
evm_data_size: int
gas_used_and_paid: int
number_of_payments: int
sign: bytes
account_list: [str]

@staticmethod
def frombytes(data) -> StorageAccountInfo:
storage = STORAGE_ACCOUNT_INFO_LAYOUT.parse(data)

account_list = []
offset = STORAGE_ACCOUNT_INFO_LAYOUT.sizeof()
for _ in range(storage.account_list_len):
writable = (data[offset] > 0)
offset += 1

some_pubkey = PublicKey(data[offset:offset + 32])
offset += 32

account_list.append((writable, str(some_pubkey)))

return StorageAccountInfo(
tag=storage.tag,
caller=storage.caller.hex(),
nonce=storage.nonce,
gas_limit=int.from_bytes(storage.gas_limit, "little"),
gas_price=int.from_bytes(storage.gas_price, "little"),
slot=storage.slot,
operator=PublicKey(storage.operator),
account_list_len=storage.account_list_len,
executor_data_size=storage.executor_data_size,
evm_data_size=storage.evm_data_size,
gas_used_and_paid=int.from_bytes(storage.gas_used_and_paid, "little"),
number_of_payments=storage.number_of_payments,
sign=storage.sign,
account_list=account_list
)


class SendResult(NamedTuple):
error: dict
result: dict
Expand Down Expand Up @@ -124,9 +196,15 @@ def _send_rpc_batch_request(self, method: str, params_list: List[Any]) -> List[R
def get_cluster_nodes(self) -> [dict]:
return self._send_rpc_request("getClusterNodes").get('result', [])

def is_health(self) -> bool:
status = self._send_rpc_request('getHealth').get('result', 'bad')
return status == 'ok'
def get_slots_behind(self) -> Optional[int]:
response = self._send_rpc_request('getHealth')
status = response.get('result')
if status == 'ok':
return 0
slots_behind = get_from_dict(response, 'error', 'data', 'numSlotsBehind')
if slots_behind:
return int(slots_behind)
return None

def get_signatures_for_address(self, before: Optional[str], limit: int, commitment='confirmed') -> []:
opts: Dict[str, Union[int, str]] = {}
Expand Down Expand Up @@ -237,15 +315,26 @@ def get_token_account_balance_list(self, pubkey_list: [Union[str, PublicKey]], c

return balance_list

def get_account_info_layout(self, eth_account: EthereumAddress) -> Optional[AccountInfoLayout]:
def get_neon_account_info(self, eth_account: EthereumAddress) -> Optional[NeonAccountInfo]:
account_sol, nonce = ether2program(eth_account)
info = self.get_account_info(account_sol)
if info is None:
return None
elif len(info.data) < ACCOUNT_INFO_LAYOUT.sizeof():
raise RuntimeError(f"Wrong data length for account data {account_sol}: " +
f"{len(info.data)} < {ACCOUNT_INFO_LAYOUT.sizeof()}")
return AccountInfoLayout.frombytes(info.data)
return NeonAccountInfo.frombytes(info.data)

def get_storage_account_info(self, storage_account: PublicKey) -> Optional[StorageAccountInfo]:
info = self.get_account_info(storage_account, length=0)
if info is None:
return None
elif info.tag != 30:
return None
elif len(info.data) < STORAGE_ACCOUNT_INFO_LAYOUT.sizeof():
raise RuntimeError(f"Wrong data length for storage data {storage_account}: " +
f"{len(info.data)} < {STORAGE_ACCOUNT_INFO_LAYOUT.sizeof()}")
return StorageAccountInfo.frombytes(info.data)

def get_multiple_rent_exempt_balances_for_size(self, size_list: [int], commitment='confirmed') -> [int]:
opts = {
Expand Down Expand Up @@ -402,7 +491,7 @@ def _send_multiple_transactions(self, signer: SolanaAccount, tx_list: [Transacti
for response, tx in zip(response_list, tx_list):
result = response.get('result')
error = response.get('error')
if error and get_from_dict('data', 'err') == 'AlreadyProcessed':
if error and get_from_dict(error, 'data', 'err') == 'AlreadyProcessed':
error = None
result = tx.signature()
result_list.append(SendResult(result=result, error=error))
Expand All @@ -420,7 +509,7 @@ def send_multiple_transactions(self, signer: SolanaAccount, tx_list: [], waiter,
receipt_list = []
for s in send_result_list:
if s.error:
self.debug(f'Got error on preflight check of transaction: {s.error}')
self.debug(f'Got error on preflight check of transaction: {json.dumps(s.error, sort_keys=True)}')
receipt_list.append(s.error)
else:
receipt_list.append(confirmed_list.pop(0))
Expand Down
11 changes: 6 additions & 5 deletions proxy/common_neon/solana_tx_list_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from logged_groups import logged_group
from typing import Optional
from solana.transaction import Transaction
from base58 import b58encode

from .costs import update_transaction_cost
from .solana_receipt_parser import SolReceiptParser, SolTxError
Expand All @@ -24,9 +25,9 @@ def __init__(self, sender, tx_list: [Transaction], name: str,

self._blockhash = None
self._retry_idx = 0
self._total_success_cnt = 0
self._slots_behind = 0
self._tx_list = tx_list
self.success_sign_list = []
self._node_behind_list = []
self._bad_block_list = []
self._blocked_account_list = []
Expand Down Expand Up @@ -66,7 +67,7 @@ def send(self) -> SolTxListSender:
receipt_list = solana.send_multiple_transactions(signer, self._tx_list, waiter, skip, commitment)
self.update_transaction_cost(receipt_list)

success_cnt = 0
success_sign_list = []
for receipt, tx in zip(receipt_list, self._tx_list):
receipt_parser = SolReceiptParser(receipt)
slots_behind = receipt_parser.get_slots_behind()
Expand All @@ -83,20 +84,20 @@ def send(self) -> SolTxListSender:
elif receipt_parser.check_if_error():
self._unknown_error_list.append(receipt)
else:
success_cnt += 1
success_sign_list.append(b58encode(tx.signature()).decode("utf-8"))
self._retry_idx = 0
self._on_success_send(tx, receipt)

self.debug(f'retry {self._retry_idx}, ' +
f'total receipts {len(receipt_list)}, ' +
f'success receipts {self._total_success_cnt}(+{success_cnt}), ' +
f'success receipts {len(self.success_sign_list)}(+{len(success_sign_list)}), ' +
f'node behind {len(self._node_behind_list)}, '
f'bad blocks {len(self._bad_block_list)}, ' +
f'blocked accounts {len(self._blocked_account_list)}, ' +
f'budget exceeded {len(self._budget_exceeded_list)}, ' +
f'unknown error: {len(self._unknown_error_list)}')

self._total_success_cnt += success_cnt
self.success_sign_list += success_sign_list
self._on_post_send()

if len(self._tx_list):
Expand Down
Loading