Skip to content

Commit 4045e90

Browse files
#295 iterative execution (#332)
* #291 extract transaction sender class * #291 move perm accs to transaction sender * #291 fix state * #291 fix errors * #291 merge fixes * #291 refactoring * #291 move EXTRA_GAS to environment * #291 capitalize CONFIRMATION_CHECK_DELAY * #291 sort imports * #291 relative paths * #291 Should be fixed in #326 * #291 testing chnages * fix storage account check * #291 rename `trx_with_create_and_airdrop` -> `make_trx_with_create_and_airdrop` * #295 fix state * #291 iterative combined * #295 do not get measurments * #291 pull request fixes * #295 turn combined instructions ON * #295 make neon_instructions return transasactions * #291 merge fix * #295 get rid of `USE_COMBINED_START_CONTINUE` * #295 requested fixes * #295 call_continue_bucked refactoring * #295 fix * #295 leave only combined iterative transactions * #295 move constants into class * #295 refactoring Co-authored-by: sinev-valentine <[email protected]>
1 parent 5d6dab2 commit 4045e90

File tree

6 files changed

+154
-60
lines changed

6 files changed

+154
-60
lines changed

proxy/common_neon/errors.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def getError(self):
1212
if self.data: error['data'] = self.data
1313
return error
1414

15+
1516
class SolanaErrors(Enum):
1617
AccountNotFound = "Invalid param: could not find account"
1718

proxy/common_neon/neon_instruction.py

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -283,14 +283,17 @@ def make_noniterative_call_transaction(self, length_before: int = 0) -> Transact
283283
return trx
284284

285285

286-
def make_partial_call_instruction(self) -> TransactionInstruction:
287-
return TransactionInstruction(
286+
def make_continue_transaction(self, steps, index=None) -> Transaction:
287+
data = bytearray.fromhex("14") + self.collateral_pool_index_buf + steps.to_bytes(8, byteorder="little")
288+
if index:
289+
data = data + index.to_bytes(8, byteorder="little")
290+
291+
return Transaction().add(TransactionInstruction(
288292
program_id = EVM_LOADER_ID,
289-
data = bytearray.fromhex("13") + self.collateral_pool_index_buf + int(0).to_bytes(8, byteorder="little") + self.msg,
293+
data = data,
290294
keys = [
291295
AccountMeta(pubkey=self.storage, is_signer=False, is_writable=True),
292296

293-
AccountMeta(pubkey=SYSVAR_INSTRUCTION_PUBKEY, is_signer=False, is_writable=False),
294297
AccountMeta(pubkey=self.operator_account, is_signer=True, is_writable=True),
295298
AccountMeta(pubkey=self.collateral_pool_address, is_signer=False, is_writable=True),
296299
AccountMeta(pubkey=self.operator_neon_address, is_signer=False, is_writable=True),
@@ -301,28 +304,19 @@ def make_partial_call_instruction(self) -> TransactionInstruction:
301304

302305
AccountMeta(pubkey=SYSVAR_INSTRUCTION_PUBKEY, is_signer=False, is_writable=False),
303306
] + obligatory_accounts
304-
)
305-
306-
307-
def make_iterative_call_transaction(self, length_before: int = 0) -> Transaction:
308-
trx = Transaction()
309-
trx.add(self.make_keccak_instruction(length_before + 1, len(self.eth_trx.unsigned_msg()), 13))
310-
trx.add(self.make_partial_call_instruction())
311-
return trx
307+
))
312308

313309

314-
def make_call_from_account_instruction(self) -> Transaction:
310+
def make_cancel_transaction(self) -> Transaction:
315311
return Transaction().add(TransactionInstruction(
316312
program_id = EVM_LOADER_ID,
317-
data = bytearray.fromhex("16") + self.collateral_pool_index_buf + int(0).to_bytes(8, byteorder="little"),
313+
data = bytearray.fromhex("15") + self.eth_trx.nonce.to_bytes(8, 'little'),
318314
keys = [
319-
AccountMeta(pubkey=self.holder, is_signer=False, is_writable=True),
320315
AccountMeta(pubkey=self.storage, is_signer=False, is_writable=True),
321-
322316
AccountMeta(pubkey=self.operator_account, is_signer=True, is_writable=True),
323-
AccountMeta(pubkey=self.collateral_pool_address, is_signer=False, is_writable=True),
324317
AccountMeta(pubkey=self.operator_neon_address, is_signer=False, is_writable=True),
325318
AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True),
319+
AccountMeta(pubkey=INCINERATOR_PUBKEY, is_signer=False, is_writable=True),
326320
AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False),
327321

328322
] + self.eth_accounts + [
@@ -332,17 +326,15 @@ def make_call_from_account_instruction(self) -> Transaction:
332326
))
333327

334328

335-
def make_continue_instruction(self, steps, index=None) -> Transaction:
336-
data = bytearray.fromhex("14") + self.collateral_pool_index_buf + steps.to_bytes(8, byteorder="little")
337-
if index:
338-
data = data + index.to_bytes(8, byteorder="little")
339-
340-
return Transaction().add(TransactionInstruction(
329+
def make_partial_call_or_continue_instruction(self, steps: int = 0) -> TransactionInstruction:
330+
data = bytearray.fromhex("0D") + self.collateral_pool_index_buf + steps.to_bytes(8, byteorder="little") + self.msg
331+
return TransactionInstruction(
341332
program_id = EVM_LOADER_ID,
342333
data = data,
343334
keys = [
344335
AccountMeta(pubkey=self.storage, is_signer=False, is_writable=True),
345336

337+
AccountMeta(pubkey=SYSVAR_INSTRUCTION_PUBKEY, is_signer=False, is_writable=False),
346338
AccountMeta(pubkey=self.operator_account, is_signer=True, is_writable=True),
347339
AccountMeta(pubkey=self.collateral_pool_address, is_signer=False, is_writable=True),
348340
AccountMeta(pubkey=self.operator_neon_address, is_signer=False, is_writable=True),
@@ -353,19 +345,31 @@ def make_continue_instruction(self, steps, index=None) -> Transaction:
353345

354346
AccountMeta(pubkey=SYSVAR_INSTRUCTION_PUBKEY, is_signer=False, is_writable=False),
355347
] + obligatory_accounts
356-
))
348+
)
357349

358350

359-
def make_cancel_instruction(self) -> Transaction:
351+
def make_partial_call_or_continue_transaction(self, steps: int = 0, length_before: int = 0) -> Transaction:
352+
trx = Transaction()
353+
trx.add(self.make_keccak_instruction(length_before + 1, len(self.eth_trx.unsigned_msg()), 13))
354+
trx.add(self.make_partial_call_or_continue_instruction(steps))
355+
return trx
356+
357+
358+
def make_partial_call_or_continue_from_account_data(self, steps, index=None) -> Transaction:
359+
data = bytearray.fromhex("0E") + self.collateral_pool_index_buf + steps.to_bytes(8, byteorder='little')
360+
if index:
361+
data = data + index.to_bytes(8, byteorder="little")
360362
return Transaction().add(TransactionInstruction(
361363
program_id = EVM_LOADER_ID,
362-
data = bytearray.fromhex("15") + self.eth_trx.nonce.to_bytes(8, 'little'),
364+
data = data,
363365
keys = [
366+
AccountMeta(pubkey=self.holder, is_signer=False, is_writable=True),
364367
AccountMeta(pubkey=self.storage, is_signer=False, is_writable=True),
368+
365369
AccountMeta(pubkey=self.operator_account, is_signer=True, is_writable=True),
370+
AccountMeta(pubkey=self.collateral_pool_address, is_signer=False, is_writable=True),
366371
AccountMeta(pubkey=self.operator_neon_address, is_signer=False, is_writable=True),
367372
AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True),
368-
AccountMeta(pubkey=INCINERATOR_PUBKEY, is_signer=False, is_writable=True),
369373
AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False),
370374

371375
] + self.eth_accounts + [

proxy/common_neon/solana_interactor.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@
55
import re
66
import time
77

8+
from solana.rpc.api import Client as SolanaClient
89
from solana.rpc.commitment import Confirmed
910
from solana.rpc.types import TxOpts
1011

1112
from .costs import update_transaction_cost
13+
from .utils import get_from_dict
1214
from ..environment import EVM_LOADER_ID, CONFIRMATION_CHECK_DELAY, LOG_SENDING_SOLANA_TRANSACTION
1315

1416
logger = logging.getLogger(__name__)
1517
logger.setLevel(logging.DEBUG)
1618

1719

1820
class SolanaInteractor:
19-
def __init__(self, signer, client) -> None:
21+
def __init__(self, signer, client: SolanaClient) -> None:
2022
self.signer = signer
2123
self.client = client
2224

@@ -194,6 +196,16 @@ def check_if_program_exceeded_instructions(err_result):
194196
return False
195197

196198

199+
def check_if_storage_is_empty_error(err_result):
200+
error_arr = get_from_dict(err_result, "data", "err", "InstructionError")
201+
if error_arr is not None and isinstance(error_arr, list):
202+
error_dict = error_arr[1]
203+
if isinstance(error_dict, dict) and 'Custom' in error_dict:
204+
if error_dict['Custom'] == 1 or error_dict['Custom'] == 4:
205+
return True
206+
return False
207+
208+
197209
def check_if_continue_returned(result):
198210
tx_info = result['result']
199211
accounts = tx_info["transaction"]["message"]["accountKeys"]

proxy/common_neon/transaction_sender.py

Lines changed: 109 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
import logging
3+
import math
34
import os
45
import rlp
56
import time
@@ -18,7 +19,8 @@
1819
from .emulator_interactor import call_emulated
1920
from .layouts import ACCOUNT_INFO_LAYOUT
2021
from .neon_instruction import NeonInstruction
21-
from .solana_interactor import SolanaInteractor, check_if_continue_returned, check_if_program_exceeded_instructions
22+
from .solana_interactor import SolanaInteractor, check_if_continue_returned, \
23+
check_if_program_exceeded_instructions, check_if_storage_is_empty_error
2224
from ..environment import EVM_LOADER_ID
2325
from ..plugin.eth_proto import Trx as EthTrx
2426

@@ -67,7 +69,7 @@ def execute(self):
6769
try:
6870
if call_iterative:
6971
try:
70-
return iterative_executor.call_signed_iterative()
72+
return iterative_executor.call_signed_iterative_combined()
7173
except Exception as err:
7274
logger.debug(str(err))
7375
if str(err).startswith("transaction too large:"):
@@ -77,7 +79,7 @@ def execute(self):
7779
raise
7880

7981
if call_from_holder:
80-
return iterative_executor.call_signed_with_holder_acc()
82+
return iterative_executor.call_signed_with_holder_combined()
8183
finally:
8284
self.free_perm_accs()
8385

@@ -93,7 +95,7 @@ def create_noniterative_executor(self):
9395

9496
def create_iterative_executor(self):
9597
self.instruction.init_iterative(self.storage, self.holder, self.perm_accs_id)
96-
return IterativeTransactionSender(self.sender, self.instruction, self.create_acc_trx, self.eth_trx, self.steps)
98+
return IterativeTransactionSender(self.sender, self.instruction, self.create_acc_trx, self.eth_trx, self.steps, self.steps_emulated)
9799

98100

99101
def init_perm_accs(self):
@@ -302,6 +304,8 @@ def create_account_list_by_emulate(self):
302304
AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True),
303305
] + add_keys_05
304306

307+
self.steps_emulated = output_json["steps_executed"]
308+
305309

306310
class NoniterativeTransactionSender:
307311
def __init__(self, solana_interactor: SolanaInteractor, neon_instruction: NeonInstruction, create_acc_trx: Transaction, eth_trx: EthTrx):
@@ -321,53 +325,52 @@ def call_signed_noniterative(self):
321325

322326

323327
class IterativeTransactionSender:
324-
def __init__(self, solana_interactor: SolanaInteractor, neon_instruction: NeonInstruction, create_acc_trx: Transaction, eth_trx: EthTrx, steps: int):
328+
CONTINUE_REGULAR = 'ContinueV02'
329+
CONTINUE_COMBINED = 'PartialCallOrContinueFromRawEthereumTX'
330+
CONTINUE_HOLDER_COMB = 'ExecuteTrxFromAccountDataIterativeOrContinue'
331+
332+
def __init__(self, solana_interactor: SolanaInteractor, neon_instruction: NeonInstruction, create_acc_trx: Transaction, eth_trx: EthTrx, steps: int, steps_emulated: int):
325333
self.sender = solana_interactor
326334
self.instruction = neon_instruction
327335
self.create_acc_trx = create_acc_trx
328336
self.eth_trx = eth_trx
329337
self.steps = steps
338+
self.steps_emulated = steps_emulated
339+
self.instruction_type = self.CONTINUE_REGULAR
330340

331341

332-
def call_signed_iterative(self):
333-
if len(self.create_acc_trx.instructions):
334-
precall_txs = Transaction()
335-
precall_txs.add(self.create_acc_trx)
336-
self.sender.send_measured_transaction(precall_txs, self.eth_trx, 'CreateAccountsForTrx')
337-
338-
call_txs = self.instruction.make_iterative_call_transaction()
339-
340-
logger.debug("Partial call")
341-
self.sender.send_measured_transaction(call_txs, self.eth_trx, 'PartialCallFromRawEthereumTXv02')
342-
342+
def call_signed_iterative_combined(self):
343+
self.create_accounts_for_trx()
344+
self.instruction_type = self.CONTINUE_COMBINED
343345
return self.call_continue()
344346

345347

346-
def call_signed_with_holder_acc(self):
348+
def call_signed_with_holder_combined(self):
347349
self.write_trx_to_holder_account()
348-
if len(self.create_acc_trx.instructions):
349-
precall_txs = Transaction()
350-
precall_txs.add(self.create_acc_trx)
351-
self.sender.send_measured_transaction(precall_txs, self.eth_trx, 'create_accounts_for_deploy')
350+
self.create_accounts_for_trx()
351+
self.instruction_type = self.CONTINUE_HOLDER_COMB
352+
return self.call_continue()
352353

353-
# ExecuteTrxFromAccountDataIterative
354-
logger.debug("ExecuteTrxFromAccountDataIterative:")
355-
call_txs = self.instruction.make_call_from_account_instruction()
356-
self.sender.send_measured_transaction(call_txs, self.eth_trx, 'ExecuteTrxFromAccountDataIterativeV02')
357354

358-
return self.call_continue()
355+
def create_accounts_for_trx(self):
356+
length = len(self.create_acc_trx.instructions)
357+
if length == 0:
358+
return
359+
logger.debug(f"Create account for trx: {length}")
360+
precall_txs = Transaction()
361+
precall_txs.add(self.create_acc_trx)
362+
self.sender.send_measured_transaction(precall_txs, self.eth_trx, 'CreateAccountsForTrx')
359363

360364

361365
def write_trx_to_holder_account(self):
366+
logger.debug('write_trx_to_holder_account')
362367
msg = self.eth_trx.signature() + len(self.eth_trx.unsigned_msg()).to_bytes(8, byteorder="little") + self.eth_trx.unsigned_msg()
363368

364-
# Write transaction to transaction holder account
365369
offset = 0
366370
receipts = []
367371
rest = msg
368372
while len(rest):
369373
(part, rest) = (rest[:1000], rest[1000:])
370-
# logger.debug("sender_sol %s %s %s", sender_sol, holder, acc.public_key())
371374
trx = self.instruction.make_write_transaction(offset, part)
372375
receipts.append(self.sender.send_transaction_unconfirmed(trx))
373376
offset += len(part)
@@ -377,6 +380,19 @@ def write_trx_to_holder_account(self):
377380

378381

379382
def call_continue(self):
383+
return_result = None
384+
try:
385+
return_result = self.call_continue_bucked()
386+
except Exception as err:
387+
logger.debug("call_continue_bucked_combined exception: {}".format(str(err)))
388+
389+
if return_result is not None:
390+
return return_result
391+
392+
return self.call_continue_iterative()
393+
394+
395+
def call_continue_iterative(self):
380396
try:
381397
return self.call_continue_step_by_step()
382398
except Exception as err:
@@ -398,7 +414,7 @@ def call_continue_step_by_step(self):
398414
def call_continue_step(self):
399415
step_count = self.steps
400416
while step_count > 0:
401-
trx = self.instruction.make_continue_instruction(step_count)
417+
trx = self.instruction.make_continue_transaction(step_count)
402418

403419
logger.debug("Step count {}".format(step_count))
404420
try:
@@ -413,8 +429,71 @@ def call_continue_step(self):
413429

414430

415431
def call_cancel(self):
416-
trx = self.instruction.make_cancel_instruction()
432+
trx = self.instruction.make_cancel_transaction()
417433

418434
logger.debug("Cancel")
419435
result = self.sender.send_measured_transaction(trx, self.eth_trx, 'CancelWithNonce')
420436
return result['result']['transaction']['signatures'][0]
437+
438+
439+
def call_continue_bucked(self):
440+
logger.debug("Send bucked combined: %s", self.instruction_type)
441+
steps = self.steps
442+
443+
receipts = []
444+
for index in range(math.ceil(self.steps_emulated/self.steps) + self.addition_count()):
445+
try:
446+
trx = self.make_bucked_trx(steps, index)
447+
receipts.append(self.sender.send_transaction_unconfirmed(trx))
448+
except SendTransactionError as err:
449+
logger.error(f"Failed to call continue bucked, error: {err.result}")
450+
if check_if_storage_is_empty_error(err.result):
451+
pass
452+
elif check_if_program_exceeded_instructions(err.result):
453+
steps = int(steps * 90 / 100)
454+
else:
455+
raise
456+
except Exception as err:
457+
logger.debug(str(err))
458+
if str(err).startswith('failed to get recent blockhash'):
459+
pass
460+
else:
461+
raise
462+
463+
return self.collect_bucked_results(receipts, self.instruction_type)
464+
465+
466+
def addition_count(self):
467+
'''
468+
How many transactions are needed depending on trx type:
469+
CONTINUE_COMBINED: 2 (1 for begin and 1 for decreased steps)
470+
CONTINUE_HOLDER_COMB: 1 for begin
471+
0 otherwise
472+
'''
473+
addition_count = 0
474+
if self.instruction_type == self.CONTINUE_COMBINED:
475+
addition_count = 2
476+
elif self.instruction_type == self.CONTINUE_HOLDER_COMB:
477+
addition_count = 1
478+
return addition_count
479+
480+
481+
def make_bucked_trx(self, steps, index):
482+
if self.instruction_type == self.CONTINUE_REGULAR:
483+
return self.instruction.make_continue_transaction(steps, index)
484+
elif self.instruction_type == self.CONTINUE_COMBINED:
485+
return self.instruction.make_partial_call_or_continue_transaction(steps - index)
486+
elif self.instruction_type == self.CONTINUE_HOLDER_COMB:
487+
return self.instruction.make_partial_call_or_continue_from_account_data(steps, index)
488+
else:
489+
raise Exception("Unknown continue type: {}".format(self.instruction_type))
490+
491+
492+
def collect_bucked_results(self, receipts, reason):
493+
logger.debug(f"Collected bucked results: {receipts}")
494+
result_list = self.sender.collect_results(receipts, eth_trx=self.eth_trx, reason=reason)
495+
for result in result_list:
496+
# self.sender.get_measurements(result)
497+
signature = check_if_continue_returned(result)
498+
if signature:
499+
return signature

proxy/environment.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
NEW_USER_AIRDROP_AMOUNT = int(os.environ.get("NEW_USER_AIRDROP_AMOUNT", "0"))
1414
CONFIRMATION_CHECK_DELAY = float(os.environ.get("NEON_CONFIRMATION_CHECK_DELAY", "0.1"))
15-
USE_COMBINED_START_CONTINUE = os.environ.get("USE_COMBINED_START_CONTINUE", "NO") == "YES"
1615
CONTINUE_COUNT_FACTOR = int(os.environ.get("CONTINUE_COUNT_FACTOR", "3"))
1716
TIMEOUT_TO_RELOAD_NEON_CONFIG = int(os.environ.get("TIMEOUT_TO_RELOAD_NEON_CONFIG", "3600"))
1817
MINIMAL_GAS_PRICE=int(os.environ.get("MINIMAL_GAS_PRICE", 1))*10**9

0 commit comments

Comments
 (0)