Skip to content

Commit

Permalink
#291 Proxy refactoring (#324)
Browse files Browse the repository at this point in the history
* #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]>
  • Loading branch information
otselnik and sinev-valentine authored Dec 2, 2021
1 parent c7ef793 commit e74e568
Show file tree
Hide file tree
Showing 24 changed files with 1,376 additions and 1,266 deletions.
2 changes: 1 addition & 1 deletion .buildkite/steps/build-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set -euo pipefail

REVISION=$(git rev-parse HEAD)

set ${SOLANA_REVISION:=v1.7.9-resources}
set ${SOLANA_REVISION:=v1.7.9-testnet}
set ${EVM_LOADER_REVISION:=latest}

# Refreshing neonlabsorg/solana:latest image is required to run .buildkite/steps/build-image.sh locally
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG SOLANA_REVISION=v1.7.9-resources
ARG SOLANA_REVISION=v1.7.9-testnet
ARG EVM_LOADER_REVISION=latest

FROM neonlabsorg/solana:${SOLANA_REVISION} AS cli
Expand Down
70 changes: 70 additions & 0 deletions proxy/common_neon/address.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import logging
import random

from eth_keys import keys as eth_keys
from hashlib import sha256
from solana.publickey import PublicKey
from spl.token.instructions import get_associated_token_address
from typing import NamedTuple

from .layouts import ACCOUNT_INFO_LAYOUT
from ..environment import neon_cli, ETH_TOKEN_MINT_ID, EVM_LOADER_ID


logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)


class EthereumAddress:
def __init__(self, data, private=None):
if isinstance(data, str):
data = bytes(bytearray.fromhex(data[2:]))
self.data = data
self.private = private

@staticmethod
def random():
letters = '0123456789abcdef'
data = bytearray.fromhex(''.join([random.choice(letters) for k in range(64)]))
pk = eth_keys.PrivateKey(data)
return EthereumAddress(pk.public_key.to_canonical_address(), pk)

def __str__(self):
return '0x'+self.data.hex()

def __repr__(self):
return self.__str__()

def __bytes__(self): return self.data


def accountWithSeed(base, seed):
result = PublicKey(sha256(bytes(base) + bytes(seed) + bytes(PublicKey(EVM_LOADER_ID))).digest())
return result


def ether2program(ether):
if isinstance(ether, str):
pass
elif isinstance(ether, EthereumAddress):
ether = str(ether)
else:
ether = ether.hex()
output = neon_cli().call("create-program-address", ether)
items = output.rstrip().split(' ')
return items[0], int(items[1])


def getTokenAddr(account):
return get_associated_token_address(PublicKey(account), ETH_TOKEN_MINT_ID)


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

@staticmethod
def frombytes(data):
cont = ACCOUNT_INFO_LAYOUT.parse(data)
return AccountInfo(cont.ether, cont.trx_count, PublicKey(cont.code_account))
12 changes: 12 additions & 0 deletions proxy/common_neon/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
KECCAK_PROGRAM = "KeccakSecp256k11111111111111111111111111111"
INCINERATOR_PUBKEY = "1nc1nerator11111111111111111111111111111111"
SYSVAR_INSTRUCTION_PUBKEY = "Sysvar1nstructions1111111111111111111111111"

STORAGE_SIZE = 128*1024

ACCOUNT_SEED_VERSION=b'\1'

COLLATERALL_POOL_MAX=10

EMPTY_STORAGE_TAG=0
FINALIZED_STORAGE_TAG=5
93 changes: 93 additions & 0 deletions proxy/common_neon/costs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import base58
import psycopg2

from ..environment import EVM_LOADER_ID
from ..indexer.sql_dict import POSTGRES_USER, POSTGRES_HOST, POSTGRES_DB, POSTGRES_PASSWORD

class SQLCost():
def __init__(self):

self.conn = psycopg2.connect(
dbname=POSTGRES_DB,
user=POSTGRES_USER,
password=POSTGRES_PASSWORD,
host=POSTGRES_HOST
)

self.conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cur = self.conn.cursor()
cur.execute('''
CREATE TABLE IF NOT EXISTS OPERATOR_COST
(
hash char(64),
cost bigint,
used_gas bigint,
sender char(40),
to_address char(40) ,
sig char(100),
status varchar(100),
reason varchar(100)
)'''
)

def close(self):
self.conn.close()

def insert(self, hash, cost, used_gas, sender, to_address, sig, status, reason):
cur = self.conn.cursor()
cur.execute('''
INSERT INTO OPERATOR_COST (hash, cost, used_gas, sender, to_address, sig, status, reason)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
''',
(hash, cost, used_gas, sender, to_address, sig, status, reason)
)


class CostSingleton(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(CostSingleton, cls).__new__(cls)
cls.instance.operator_cost = SQLCost()
return cls.instance


def update_transaction_cost(receipt, eth_trx, extra_sol_trx=False, reason=None):
cost = receipt['result']['meta']['preBalances'][0] - receipt['result']['meta']['postBalances'][0]
if eth_trx:
hash = eth_trx.hash_signed().hex()
sender = eth_trx.sender()
to_address = eth_trx.toAddress.hex() if eth_trx.toAddress else "None"
else:
hash = None
sender = None
to_address = None

sig = receipt['result']['transaction']['signatures'][0]
used_gas=None

tx_info = receipt['result']
accounts = tx_info["transaction"]["message"]["accountKeys"]
evm_loader_instructions = []

for idx, instruction in enumerate(tx_info["transaction"]["message"]["instructions"]):
if accounts[instruction["programIdIndex"]] == EVM_LOADER_ID:
evm_loader_instructions.append(idx)

for inner in (tx_info['meta']['innerInstructions']):
if inner["index"] in evm_loader_instructions:
for event in inner['instructions']:
if accounts[event['programIdIndex']] == EVM_LOADER_ID:
used_gas = base58.b58decode(event['data'])[2:10]
used_gas = int().from_bytes(used_gas, "little")

table = CostSingleton().operator_cost
table.insert(
hash,
cost,
used_gas if used_gas else 0,
sender,
to_address,
sig,
'extra' if extra_sol_trx else 'ok',
reason if reason else ''
)
34 changes: 34 additions & 0 deletions proxy/common_neon/emulator_interactor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import json
import logging

from .errors import EthereumError
from ..environment import neon_cli


logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)


def call_emulated(contract_id, caller_id, data=None, value=None):
output = emulator(contract_id, caller_id, data, value)
logger.debug("call_emulated %s %s %s %s return %s", contract_id, caller_id, data, value, output)
result = json.loads(output)
exit_status = result['exit_status']
if exit_status == 'revert':
result_value = result['result']
if len(result_value) < 8 or result_value[:8] != '08c379a0':
raise EthereumError(code=3, message='execution reverted')

offset = int(result_value[8:8+64], 16)
length = int(result_value[8+64:8+64+64], 16)
message = str(bytes.fromhex(result_value[8+offset*2+64:8+offset*2+64+length*2]), 'utf8')
raise EthereumError(code=3, message='execution reverted: '+message, data='0x'+result_value)
if result["exit_status"] != "succeed":
raise Exception("evm emulator error ", result)
return result


def emulator(contract, sender, data, value):
data = data or "none"
value = value or ""
return neon_cli().call("emulate", sender, contract, data, value)
11 changes: 11 additions & 0 deletions proxy/common_neon/errors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
from enum import Enum


class EthereumError(Exception):
def __init__(self, code, message, data=None):
self.code = code
self.message = message
self.data = data

def getError(self):
error = {'code': self.code, 'message': self.message}
if self.data: error['data'] = self.data
return error

class SolanaErrors(Enum):
AccountNotFound = "Invalid param: could not find account"

Expand Down
39 changes: 39 additions & 0 deletions proxy/common_neon/layouts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

from construct import Bytes, Int8ul, Int64ul
from construct import Struct

STORAGE_ACCOUNT_INFO_LAYOUT = Struct(
# "tag" / Int8ul,
"caller" / Bytes(20),
"nonce" / Int64ul,
"gas_limit" / Int64ul,
"gas_price" / Int64ul,
"slot" / Int64ul,
"operator" / Bytes(32),
"accounts_len" / Int64ul,
"executor_data_size" / Int64ul,
"evm_data_size" / Int64ul,
"gas_used_and_paid" / Int64ul,
"number_of_payments" / Int64ul,
"sign" / Bytes(65),
)

ACCOUNT_INFO_LAYOUT = Struct(
"type" / Int8ul,
"ether" / Bytes(20),
"nonce" / Int8ul,
"trx_count" / Bytes(8),
"code_account" / Bytes(32),
"is_rw_blocked" / Int8ul,
"rw_blocked_acc" / Bytes(32),
"eth_token_account" / Bytes(32),
"ro_blocked_cnt" / Int8ul,
)


CREATE_ACCOUNT_LAYOUT = Struct(
"lamports" / Int64ul,
"space" / Int64ul,
"ether" / Bytes(20),
"nonce" / Int8ul
)
Loading

0 comments on commit e74e568

Please sign in to comment.