Skip to content

Commit

Permalink
#428 eth_getCode implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
otselnik committed Jan 27, 2022
1 parent 29cacd9 commit 86675bd
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 9 deletions.
66 changes: 66 additions & 0 deletions proxy/indexer/accounts_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from .utils import BaseDB, str_fmt_object
from .pg_common import decode


class NeonAccountInfo:
def __init__(self, neon_account: str, pda_account: str, code_account: str, slot: int, code: str = None):
self.neon_account = neon_account
self.pda_account = pda_account
self.code_account = code_account
self.slot = slot
self.code = code

def __str__(self):
return str_fmt_object(self)


class NeonAccountDB(BaseDB):
def __init__(self):
BaseDB.__init__(self)

def _create_table_sql(self) -> str:
self._table_name = 'neon_accounts'
return f"""
CREATE TABLE IF NOT EXISTS {self._table_name} (
neon_account CHAR(42),
pda_account CHAR(50) UNIQUE,
code_account CHAR(50),
slot BIGINT,
code TEXT
);"""

def set_acc(self, neon_account: str, pda_account: str, code_account: str, slot: int, code: str = None):
with self._conn.cursor() as cursor:
cursor.execute(f'''
INSERT INTO {self._table_name}(neon_account, pda_account, code_account, slot, code)
VALUES(%s, %s, %s, %s, %s)
ON CONFLICT (pda_account) DO UPDATE
SET
code_account=EXCLUDED.code_account,
slot=EXCLUDED.slot,
code=EXCLUDED.code
WHERE
({self._table_name}.slot<EXCLUDED.slot)
OR
({self._table_name}.code=NULL AND EXCLUDED.code<>NULL)
;
''',
(neon_account, pda_account, code_account, slot, code))

def _acc_from_value(self, value) -> NeonAccountInfo:
if not value:
return None

return NeonAccountInfo(
neon_account=value[0],
pda_account=value[1],
code_account=value[2],
slot=value[3],
code=decode(value[4])
)

def get_account_info(self, account) -> NeonAccountInfo:
return self._acc_from_value(
self._fetchone(['neon_account', 'pda_account', 'code_account', 'slot', 'code'],
[('neon_account', account)],
['slot desc']))
55 changes: 52 additions & 3 deletions proxy/indexer/indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
import os
import time
from logged_groups import logged_group
from solana.system_program import SYS_PROGRAM_ID

try:
from indexer_base import IndexerBase, PARALLEL_REQUESTS
from indexer_db import IndexerDB
from utils import SolanaIxSignInfo, NeonTxResultInfo, NeonTxInfo, Canceller, str_fmt_object, FINALIZED
from utils import get_accounts_from_storage
from utils import get_accounts_from_storage, check_error
except ImportError:
from .indexer_base import IndexerBase, PARALLEL_REQUESTS
from .indexer_db import IndexerDB, FINALIZED
from .utils import SolanaIxSignInfo, NeonTxResultInfo, NeonTxInfo, Canceller, str_fmt_object, FINALIZED
from .utils import get_accounts_from_storage
from .utils import get_accounts_from_storage, check_error

from ..environment import EVM_LOADER_ID

Expand Down Expand Up @@ -251,6 +252,9 @@ def iter_txs(self):
for tx in self._tx_table.values():
yield tx

def add_account_to_db(self, neon_account: str, pda_account: str, code_account: str, slot: int):
self._db.fill_account_info(neon_account, pda_account, code_account, slot)


@logged_group("neon.Indexer")
class DummyIxDecoder:
Expand Down Expand Up @@ -448,6 +452,50 @@ def _decode_datachunck(self, ix_info: SolanaIxInfo) -> WriteIxDecoder._DataChunk
)


class CreateAccountIxDecoder(DummyIxDecoder):
def __init__(self, state: ReceiptsParserState):
DummyIxDecoder.__init__(self, 'CreateAccount', state)

def execute(self) -> bool:
self._decoding_start()

if check_error(self.ix.tx):
return self._decoding_skip("Ignore failed create account")

if len(self.ix.ix_data) < 61:
return self._decoding_skip('not enough data to get the Neon account')

neon_account = "0x" + self.ix.ix_data[20+8+8+4:60].hex()
pda_account = self.ix.get_account(1)
code_account = self.ix.get_account(3)
if code_account == str(SYS_PROGRAM_ID) or code_account == '':
code_account = None

self.debug(f"neon_account({neon_account}), pda_account({pda_account}), code_account({code_account}), slot({self.ix.sign.slot})")

self.state.add_account_to_db(neon_account, pda_account, code_account, self.ix.sign.slot)
return True


class ResizeStorageAccountIxDecoder(DummyIxDecoder):
def __init__(self, state: ReceiptsParserState):
DummyIxDecoder.__init__(self, 'ResizeStorageAccount', state)

def execute(self) -> bool:
self._decoding_start()

if check_error(self.ix.tx):
return self._decoding_skip("Ignore failed resize account")

pda_account = self.ix.get_account(0)
code_account = self.ix.get_account(2)

self.debug(f"pda_account({pda_account}), code_account({code_account}), slot({self.ix.sign.slot})")

self.state.add_account_to_db(None, pda_account, code_account, self.ix.sign.slot)
return True


class CallFromRawIxDecoder(DummyIxDecoder):
def __init__(self, state: ReceiptsParserState):
DummyIxDecoder.__init__(self, 'CallFromRaw', state)
Expand Down Expand Up @@ -631,7 +679,7 @@ def __init__(self, solana_url, evm_loader_id):
self.ix_decoder_map = {
0x00: WriteIxDecoder(self.state),
0x01: DummyIxDecoder('Finalize', self.state),
0x02: DummyIxDecoder('CreateAccount', self.state),
0x02: CreateAccountIxDecoder(self.state),
0x03: DummyIxDecoder('Call', self.state),
0x04: DummyIxDecoder('CreateAccountWithSeed', self.state),
0x05: CallFromRawIxDecoder(self.state),
Expand All @@ -643,6 +691,7 @@ def __init__(self, solana_url, evm_loader_id):
0x0c: CancelIxDecoder(self.state),
0x0d: PartialCallOrContinueIxDecoder(self.state),
0x0e: ExecuteOrContinueIxParser(self.state),
0x11: ResizeStorageAccountIxDecoder(self.state),
0x12: WriteWithHolderIxDecoder(self.state),
0x13: PartialCallV02IxDecoder(self.state),
0x14: ContinueV02IxDecoder(self.state),
Expand Down
32 changes: 31 additions & 1 deletion proxy/indexer/indexer_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@

try:
from utils import LogDB, NeonTxInfo, NeonTxResultInfo, SolanaIxSignInfo, FINALIZED
from accounts_db import NeonAccountDB, NeonAccountInfo
from blocks_db import SolanaBlocksDB, SolanaBlockDBInfo
from transactions_db import NeonTxsDB, NeonTxDBInfo
from sql_dict import SQLDict
from utils import get_code_from_account
except ImportError:
from .utils import LogDB, NeonTxInfo, NeonTxResultInfo, SolanaIxSignInfo, FINALIZED
from .accounts_db import NeonAccountDB, NeonAccountInfo
from .blocks_db import SolanaBlocksDB, SolanaBlockDBInfo
from .transactions_db import NeonTxsDB, NeonTxDBInfo
from .sql_dict import SQLDict
from .utils import get_code_from_account


@logged_group("neon.Indexer")
Expand All @@ -20,6 +24,7 @@ def __init__(self, client):
self._logs_db = LogDB()
self._blocks_db = SolanaBlocksDB()
self._txs_db = NeonTxsDB()
self._account_db = NeonAccountDB()
self._client = client

self._constants = SQLDict(tablename="constants")
Expand All @@ -41,7 +46,6 @@ def submit_transaction(self, neon_tx: NeonTxInfo, neon_res: NeonTxResultInfo, us
rec['blockNumber'] = hex(block.height)
self._logs_db.push_logs(neon_res.logs, block)
tx = NeonTxDBInfo(neon_tx=neon_tx, neon_res=neon_res, block=block, used_ixs=used_ixs)
self.debug(f'submit_transaction NeonTxDBInfo {tx}')
self._txs_db.set_tx(tx)
except Exception as err:
err_tb = "".join(traceback.format_tb(err.__traceback__))
Expand All @@ -65,6 +69,19 @@ def _fill_block_from_net(self, block: SolanaBlockDBInfo):
self._blocks_db.set_block(block)
return block

def _fill_account_data_from_net(self, account: NeonAccountInfo):
if account.code_account:
code = get_code_from_account(self._client, account.code_account)
if code:
account.code = code
self._account_db.set_acc(
account.neon_account,
account.pda_account,
account.code_account,
account.slot,
account.code)
return account

def get_block_by_slot(self, slot) -> SolanaBlockDBInfo:
block = self._blocks_db.get_block_by_slot(slot)
if not block.hash:
Expand Down Expand Up @@ -120,6 +137,19 @@ def get_tx_by_neon_sign(self, neon_sign) -> NeonTxDBInfo:
tx.block = self.get_block_by_slot(tx.neon_res.slot)
return tx

def get_contract_code(self, address) -> str:
account = self._account_db.get_account_info(address)
if not account:
return '0x'
if account.code_account and not account.code:
self._fill_account_data_from_net(account)
if account.code:
return account.code
return '0x'

def fill_account_info(self, neon_account: str, pda_account: str, code_account: str, slot: int):
self._account_db.set_acc(neon_account, pda_account, code_account, slot)

def del_not_finalized(self, from_slot: int, to_slot: int):
for d in [self._logs_db, self._blocks_db, self._txs_db]:
d.del_not_finalized(from_slot=from_slot, to_slot=to_slot)
18 changes: 16 additions & 2 deletions proxy/indexer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@

def check_error(trx):
if 'meta' in trx and 'err' in trx['meta'] and trx['meta']['err'] is not None:
# logger.debug("Got err trx")
# logger.debug("\n{}".format(json.dumps(trx['meta']['err'])))
return True
return False

Expand Down Expand Up @@ -260,6 +258,22 @@ def get_accounts_from_storage(client, storage_account, *, logger):
return None


def get_code_from_account(client, address):
code_account_info = client.get_account_info(address)['result']['value']
if code_account_info is None:
return None
data = base64.b64decode(code_account_info['data'][0])
if len(data) < 26:
return None
_tag = data[0]
_public_key = data[1:21]
size_data = data[21:25]
code_size = int.from_bytes(size_data, "little")
if len(data) < 25 + code_size:
return None
return '0x' + data[25:25+code_size].hex()


@logged_group("neon.Indexer")
class BaseDB:
def __init__(self):
Expand Down
5 changes: 3 additions & 2 deletions proxy/plugin/solana_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,9 @@ def eth_getTransactionByHash(self, trxId):
return None
return self._getTransaction(tx)

def eth_getCode(self, param, param1):
return "0x01"
def eth_getCode(self, account, _tag):
account = account.lower()
return self.db.get_contract_code(account)

def eth_sendTransaction(self, trx):
self.debug("eth_sendTransaction")
Expand Down
4 changes: 3 additions & 1 deletion proxy/testing/test_indexer_cancel_hanged.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ def test_canceled(self):
trx_receipt = proxy.eth.wait_for_transaction_receipt(self.tx_hash)
print('trx_receipt:', trx_receipt)
self.assertEqual(trx_receipt['status'], 0)

code = proxy.eth.get_code(self.storage_contract.address)
print('code:', code)
print('code:', self.storage_contract.bytecode)


if __name__ == '__main__':
Expand Down

0 comments on commit 86675bd

Please sign in to comment.