Skip to content

Commit e74e568

Browse files
#291 Proxy refactoring (#324)
* #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` * #291 pull request fixes * #291 merge fix * #291 rename operator and associated token accounts Co-authored-by: sinev-valentine <[email protected]>
1 parent c7ef793 commit e74e568

24 files changed

+1376
-1266
lines changed

.buildkite/steps/build-image.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ set -euo pipefail
33

44
REVISION=$(git rev-parse HEAD)
55

6-
set ${SOLANA_REVISION:=v1.7.9-resources}
6+
set ${SOLANA_REVISION:=v1.7.9-testnet}
77
set ${EVM_LOADER_REVISION:=latest}
88

99
# Refreshing neonlabsorg/solana:latest image is required to run .buildkite/steps/build-image.sh locally

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG SOLANA_REVISION=v1.7.9-resources
1+
ARG SOLANA_REVISION=v1.7.9-testnet
22
ARG EVM_LOADER_REVISION=latest
33

44
FROM neonlabsorg/solana:${SOLANA_REVISION} AS cli

proxy/common_neon/address.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import logging
2+
import random
3+
4+
from eth_keys import keys as eth_keys
5+
from hashlib import sha256
6+
from solana.publickey import PublicKey
7+
from spl.token.instructions import get_associated_token_address
8+
from typing import NamedTuple
9+
10+
from .layouts import ACCOUNT_INFO_LAYOUT
11+
from ..environment import neon_cli, ETH_TOKEN_MINT_ID, EVM_LOADER_ID
12+
13+
14+
logger = logging.getLogger(__name__)
15+
logger.setLevel(logging.DEBUG)
16+
17+
18+
class EthereumAddress:
19+
def __init__(self, data, private=None):
20+
if isinstance(data, str):
21+
data = bytes(bytearray.fromhex(data[2:]))
22+
self.data = data
23+
self.private = private
24+
25+
@staticmethod
26+
def random():
27+
letters = '0123456789abcdef'
28+
data = bytearray.fromhex(''.join([random.choice(letters) for k in range(64)]))
29+
pk = eth_keys.PrivateKey(data)
30+
return EthereumAddress(pk.public_key.to_canonical_address(), pk)
31+
32+
def __str__(self):
33+
return '0x'+self.data.hex()
34+
35+
def __repr__(self):
36+
return self.__str__()
37+
38+
def __bytes__(self): return self.data
39+
40+
41+
def accountWithSeed(base, seed):
42+
result = PublicKey(sha256(bytes(base) + bytes(seed) + bytes(PublicKey(EVM_LOADER_ID))).digest())
43+
return result
44+
45+
46+
def ether2program(ether):
47+
if isinstance(ether, str):
48+
pass
49+
elif isinstance(ether, EthereumAddress):
50+
ether = str(ether)
51+
else:
52+
ether = ether.hex()
53+
output = neon_cli().call("create-program-address", ether)
54+
items = output.rstrip().split(' ')
55+
return items[0], int(items[1])
56+
57+
58+
def getTokenAddr(account):
59+
return get_associated_token_address(PublicKey(account), ETH_TOKEN_MINT_ID)
60+
61+
62+
class AccountInfo(NamedTuple):
63+
ether: eth_keys.PublicKey
64+
trx_count: int
65+
code_account: PublicKey
66+
67+
@staticmethod
68+
def frombytes(data):
69+
cont = ACCOUNT_INFO_LAYOUT.parse(data)
70+
return AccountInfo(cont.ether, cont.trx_count, PublicKey(cont.code_account))

proxy/common_neon/constants.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
KECCAK_PROGRAM = "KeccakSecp256k11111111111111111111111111111"
2+
INCINERATOR_PUBKEY = "1nc1nerator11111111111111111111111111111111"
3+
SYSVAR_INSTRUCTION_PUBKEY = "Sysvar1nstructions1111111111111111111111111"
4+
5+
STORAGE_SIZE = 128*1024
6+
7+
ACCOUNT_SEED_VERSION=b'\1'
8+
9+
COLLATERALL_POOL_MAX=10
10+
11+
EMPTY_STORAGE_TAG=0
12+
FINALIZED_STORAGE_TAG=5

proxy/common_neon/costs.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import base58
2+
import psycopg2
3+
4+
from ..environment import EVM_LOADER_ID
5+
from ..indexer.sql_dict import POSTGRES_USER, POSTGRES_HOST, POSTGRES_DB, POSTGRES_PASSWORD
6+
7+
class SQLCost():
8+
def __init__(self):
9+
10+
self.conn = psycopg2.connect(
11+
dbname=POSTGRES_DB,
12+
user=POSTGRES_USER,
13+
password=POSTGRES_PASSWORD,
14+
host=POSTGRES_HOST
15+
)
16+
17+
self.conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
18+
cur = self.conn.cursor()
19+
cur.execute('''
20+
CREATE TABLE IF NOT EXISTS OPERATOR_COST
21+
(
22+
hash char(64),
23+
cost bigint,
24+
used_gas bigint,
25+
sender char(40),
26+
to_address char(40) ,
27+
sig char(100),
28+
status varchar(100),
29+
reason varchar(100)
30+
)'''
31+
)
32+
33+
def close(self):
34+
self.conn.close()
35+
36+
def insert(self, hash, cost, used_gas, sender, to_address, sig, status, reason):
37+
cur = self.conn.cursor()
38+
cur.execute('''
39+
INSERT INTO OPERATOR_COST (hash, cost, used_gas, sender, to_address, sig, status, reason)
40+
VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
41+
''',
42+
(hash, cost, used_gas, sender, to_address, sig, status, reason)
43+
)
44+
45+
46+
class CostSingleton(object):
47+
def __new__(cls):
48+
if not hasattr(cls, 'instance'):
49+
cls.instance = super(CostSingleton, cls).__new__(cls)
50+
cls.instance.operator_cost = SQLCost()
51+
return cls.instance
52+
53+
54+
def update_transaction_cost(receipt, eth_trx, extra_sol_trx=False, reason=None):
55+
cost = receipt['result']['meta']['preBalances'][0] - receipt['result']['meta']['postBalances'][0]
56+
if eth_trx:
57+
hash = eth_trx.hash_signed().hex()
58+
sender = eth_trx.sender()
59+
to_address = eth_trx.toAddress.hex() if eth_trx.toAddress else "None"
60+
else:
61+
hash = None
62+
sender = None
63+
to_address = None
64+
65+
sig = receipt['result']['transaction']['signatures'][0]
66+
used_gas=None
67+
68+
tx_info = receipt['result']
69+
accounts = tx_info["transaction"]["message"]["accountKeys"]
70+
evm_loader_instructions = []
71+
72+
for idx, instruction in enumerate(tx_info["transaction"]["message"]["instructions"]):
73+
if accounts[instruction["programIdIndex"]] == EVM_LOADER_ID:
74+
evm_loader_instructions.append(idx)
75+
76+
for inner in (tx_info['meta']['innerInstructions']):
77+
if inner["index"] in evm_loader_instructions:
78+
for event in inner['instructions']:
79+
if accounts[event['programIdIndex']] == EVM_LOADER_ID:
80+
used_gas = base58.b58decode(event['data'])[2:10]
81+
used_gas = int().from_bytes(used_gas, "little")
82+
83+
table = CostSingleton().operator_cost
84+
table.insert(
85+
hash,
86+
cost,
87+
used_gas if used_gas else 0,
88+
sender,
89+
to_address,
90+
sig,
91+
'extra' if extra_sol_trx else 'ok',
92+
reason if reason else ''
93+
)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import json
2+
import logging
3+
4+
from .errors import EthereumError
5+
from ..environment import neon_cli
6+
7+
8+
logger = logging.getLogger(__name__)
9+
logger.setLevel(logging.DEBUG)
10+
11+
12+
def call_emulated(contract_id, caller_id, data=None, value=None):
13+
output = emulator(contract_id, caller_id, data, value)
14+
logger.debug("call_emulated %s %s %s %s return %s", contract_id, caller_id, data, value, output)
15+
result = json.loads(output)
16+
exit_status = result['exit_status']
17+
if exit_status == 'revert':
18+
result_value = result['result']
19+
if len(result_value) < 8 or result_value[:8] != '08c379a0':
20+
raise EthereumError(code=3, message='execution reverted')
21+
22+
offset = int(result_value[8:8+64], 16)
23+
length = int(result_value[8+64:8+64+64], 16)
24+
message = str(bytes.fromhex(result_value[8+offset*2+64:8+offset*2+64+length*2]), 'utf8')
25+
raise EthereumError(code=3, message='execution reverted: '+message, data='0x'+result_value)
26+
if result["exit_status"] != "succeed":
27+
raise Exception("evm emulator error ", result)
28+
return result
29+
30+
31+
def emulator(contract, sender, data, value):
32+
data = data or "none"
33+
value = value or ""
34+
return neon_cli().call("emulate", sender, contract, data, value)

proxy/common_neon/errors.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
from enum import Enum
22

33

4+
class EthereumError(Exception):
5+
def __init__(self, code, message, data=None):
6+
self.code = code
7+
self.message = message
8+
self.data = data
9+
10+
def getError(self):
11+
error = {'code': self.code, 'message': self.message}
12+
if self.data: error['data'] = self.data
13+
return error
14+
415
class SolanaErrors(Enum):
516
AccountNotFound = "Invalid param: could not find account"
617

proxy/common_neon/layouts.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
from construct import Bytes, Int8ul, Int64ul
3+
from construct import Struct
4+
5+
STORAGE_ACCOUNT_INFO_LAYOUT = Struct(
6+
# "tag" / Int8ul,
7+
"caller" / Bytes(20),
8+
"nonce" / Int64ul,
9+
"gas_limit" / Int64ul,
10+
"gas_price" / Int64ul,
11+
"slot" / Int64ul,
12+
"operator" / Bytes(32),
13+
"accounts_len" / Int64ul,
14+
"executor_data_size" / Int64ul,
15+
"evm_data_size" / Int64ul,
16+
"gas_used_and_paid" / Int64ul,
17+
"number_of_payments" / Int64ul,
18+
"sign" / Bytes(65),
19+
)
20+
21+
ACCOUNT_INFO_LAYOUT = Struct(
22+
"type" / Int8ul,
23+
"ether" / Bytes(20),
24+
"nonce" / Int8ul,
25+
"trx_count" / Bytes(8),
26+
"code_account" / Bytes(32),
27+
"is_rw_blocked" / Int8ul,
28+
"rw_blocked_acc" / Bytes(32),
29+
"eth_token_account" / Bytes(32),
30+
"ro_blocked_cnt" / Int8ul,
31+
)
32+
33+
34+
CREATE_ACCOUNT_LAYOUT = Struct(
35+
"lamports" / Int64ul,
36+
"space" / Int64ul,
37+
"ether" / Bytes(20),
38+
"nonce" / Int8ul
39+
)

0 commit comments

Comments
 (0)