Skip to content

Commit

Permalink
#256 create and airdrop eth account (#259)
Browse files Browse the repository at this point in the history
* constants and utils

* solana rest api tools imports

* comments and tabs

* rename calling getTokens function

* Meet SolanaErrors

* Implement creating account on getting balance

* Move tests and add get_from_dict test

* Add test "Metamask creates an account"

* Just not ot loose changes

* implemented

* Fix tests on CI

* set NEW_USER_AIRDROP_AMOUNT on CI

* improve logging

* improve logging of SendTransactionError

* spit and polish

* extend airdrop tests

* spit and polish

* spit and polish

* move tests

* move tests

* Get rid off extra data.py

* Improve logging

* spit and polish

* spit and polish

* spit and polish

* spit and polish

* Pass MINIMAL_GAS_PRICE int airdrop tests

* spit and polish

* move test_operator_spending.py

* move test_operator_spending.py

* spit and polish

* spit and polish

* Fix message printing

* spit and polish

* spit and polish

* spit and polish

* spit and polish

* use error instead of debug

* Revert "constants and utils"

This reverts commit 5056536.

# Conflicts:
#	proxy/testing/test_eth_sendRawTransaction.py

* Emphasize meaning of trx extending functions
This reverts commit 5056536.

# Conflicts:
#	proxy/testing/test_eth_sendRawTransaction.py

* Resolve @otselik remarks
# Conflicts:
#	proxy/testing/test_eth_sendRawTransaction.py

* rollback common/utils.py

* Rollback some changes

* Resolve remarks

* Use exception to check result of get_token_balance_gwei

* just not to loose changes

* spit and polish

* Update tests

* Simplify airdrop processing

* Spit and polish

* Spit and polish

* Spit and polish

* Freeze changes up

* Isolate errors

* spit and polish

* spit and polish

Co-authored-by: rozhkovdmitrii <[email protected]>
  • Loading branch information
rozhkovdmitrii and rozhkovdmitrii authored Nov 18, 2021
1 parent 3c6f4bc commit 6e1864e
Show file tree
Hide file tree
Showing 22 changed files with 360 additions and 158 deletions.
1 change: 1 addition & 0 deletions .buildkite/steps/deploy-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ docker run --rm -ti --network=container:proxy \
-e EVM_LOADER \
-e SOLANA_URL \
-e EXTRA_GAS=100000 \
-e NEW_USER_AIRDROP_AMOUNT=100 \
--entrypoint ./proxy/deploy-test.sh \
${EXTRA_ARGS:-} \
$PROXY_IMAGE \
Expand Down
Empty file added proxy/common_neon/__init__.py
Empty file.
11 changes: 11 additions & 0 deletions proxy/common_neon/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from enum import Enum


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


class SolanaAccountNotFoundError(Exception):
"""Provides special error processing"""
def __init__(self):
super().__init__(SolanaErrors.AccountNotFound.value)
13 changes: 13 additions & 0 deletions proxy/common_neon/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import Dict, Optional, Any


def get_from_dict(src: Dict, *path) -> Optional[Any]:
"""Provides smart getting values from python dictionary"""
val = src
for key in path:
if not isinstance(val, dict):
return None
val = val.get(key)
if val is None:
return None
return val
4 changes: 2 additions & 2 deletions proxy/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def call(self, *args):
cmd = ["solana",
"--url", solana_url,
] + list(args)
print(cmd)
logger.debug("Calling: " + " ".join(cmd))
return subprocess.check_output(cmd, universal_newlines=True)
except subprocess.CalledProcessError as err:
import sys
Expand All @@ -32,7 +32,7 @@ def call(self, *args):
"--url", solana_url,
"--evm_loader={}".format(evm_loader_id),
] + list(args)
print(cmd)
logger.debug("Calling: " + " ".join(cmd))
return subprocess.check_output(cmd, timeout=neon_cli_timeout, universal_newlines=True)
except subprocess.CalledProcessError as err:
import sys
Expand Down
79 changes: 45 additions & 34 deletions proxy/plugin/solana_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
:copyright: (c) 2013-present by Abhinav Singh and contributors.
:license: BSD, see LICENSE for more details.
"""
from typing import List, Tuple
from typing import List, Tuple, Optional
import copy
import json
import unittest
import eth_utils
import rlp
import solana
from solana.account import Account as sol_Account
Expand All @@ -20,21 +22,22 @@
from ..http.websocket import WebsocketFrame
from ..http.server import HttpWebServerBasePlugin, httpProtocolTypes
from .eth_proto import Trx as EthTrx
from solana.rpc.api import Client as SolanaClient
from solana.rpc.api import Client as SolanaClient, SendTransactionError as SolanaTrxError
from sha3 import keccak_256
import base58
import traceback
import threading
from .solana_rest_api_tools import EthereumAddress, getTokens, getAccountInfo, \
call_signed, call_emulated, EthereumError, neon_config_load, MINIMAL_GAS_PRICE

from .solana_rest_api_tools import EthereumAddress, get_token_balance_or_airdrop, getAccountInfo, call_signed, \
call_emulated, EthereumError, neon_config_load, MINIMAL_GAS_PRICE
from solana.rpc.commitment import Commitment, Confirmed
from web3 import Web3
import logging
from ..core.acceptor.pool import proxy_id_glob
import os
from ..indexer.utils import get_trx_results, LogDB
from ..indexer.sql_dict import SQLDict
from proxy.environment import evm_loader_id, solana_cli, solana_url, neon_cli
from ..environment import evm_loader_id, solana_cli, solana_url, neon_cli

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
Expand All @@ -46,23 +49,7 @@

class EthereumModel:
def __init__(self):
# Initialize user account
res = solana_cli().call('config', 'get')
substr = "Keypair Path: "
path = ""
for line in res.splitlines():
if line.startswith(substr):
path = line[len(substr):].strip()
if path == "":
raise Exception("cannot get keypair path")

with open(path.strip(), mode='r') as file:
pk = (file.read())
nums = list(map(int, pk.strip("[] \n").split(',')))
nums = nums[0:32]
values = bytes(nums)
self.signer = sol_Account(values)

self.signer = self.get_solana_account()
self.client = SolanaClient(solana_url)

self.logs_db = LogDB()
Expand All @@ -77,7 +64,27 @@ def __init__(self):
logger.debug("worker id {}".format(self.proxy_id))

neon_config_load(self)
pass


@staticmethod
def get_solana_account() -> Optional[sol_Account]:
solana_account: Optional[sol_Account] = None
res = solana_cli().call('config', 'get')
substr = "Keypair Path: "
path = ""
for line in res.splitlines():
if line.startswith(substr):
path = line[len(substr):].strip()
if path == "":
raise Exception("cannot get keypair path")

with open(path.strip(), mode='r') as file:
pk = (file.read())
nums = list(map(int, pk.strip("[] \n").split(',')))
nums = nums[0:32]
values = bytes(nums)
solana_account = sol_Account(values)
return solana_account

def web3_clientVersion(self):
neon_config_load(self)
Expand Down Expand Up @@ -120,7 +127,6 @@ def process_block_tag(self, tag):
slot = int(tag, 16)
return slot


def eth_blockNumber(self):
slot = self.client.get_slot(commitment=Confirmed)['result']
logger.debug("eth_blockNumber %s", hex(slot))
Expand All @@ -132,9 +138,9 @@ def eth_getBalance(self, account, tag):
"""
eth_acc = EthereumAddress(account)
logger.debug('eth_getBalance: %s %s', account, eth_acc)
balance = getTokens(self.client, self.signer, evm_loader_id, eth_acc, self.signer.public_key())
balance = get_token_balance_or_airdrop(self.client, self.signer, evm_loader_id, eth_acc)

return hex(balance*10**9)
return hex(balance * eth_utils.denoms.gwei)

def eth_getLogs(self, obj):
fromBlock = None
Expand Down Expand Up @@ -366,7 +372,7 @@ def eth_getTransactionByHash(self, trxId, block_info = None):
"s": eth_trx[8],
}

logger.debug ("eth_getTransactionByHash: %s", json.dumps(ret, indent=3))
logger.debug("eth_getTransactionByHash: %s", json.dumps(ret, indent=3))
return ret

def eth_getCode(self, param, param1):
Expand Down Expand Up @@ -455,8 +461,8 @@ def eth_sendRawTransaction(self, rawTrx):

return eth_signature

except solana.rpc.api.SendTransactionError as err:
logger.debug("eth_sendRawTransaction solana.rpc.api.SendTransactionError:%s", err.result)
except SolanaTrxError as err:
self._log_transaction_error(err, logger)
raise
except EthereumError as err:
logger.debug("eth_sendRawTransaction EthereumError:%s", err)
Expand All @@ -465,6 +471,14 @@ def eth_sendRawTransaction(self, rawTrx):
logger.debug("eth_sendRawTransaction type(err):%s, Exception:%s", type(err), err)
raise

def _log_transaction_error(self, error: SolanaTrxError, logger):
result = copy.deepcopy(error.result)
logs = result.get("data", {}).get("logs", [])
result.get("data", {}).update({"logs": ["\n\t" + log for log in logs]})
log_msg = str(result).replace("\\n\\t", "\n\t")
logger.error(f"Got SendTransactionError: {log_msg}")


class JsonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, bytearray):
Expand All @@ -475,12 +489,11 @@ def default(self, obj):


class SolanaContractTests(unittest.TestCase):

def setUp(self):
self.model = EthereumModel()
self.owner = '0xc1566af4699928fdf9be097ca3dc47ece39f8f8e'
self.token1 = '0x49a449cd7fd8fbcf34d103d98f2c05245020e35b'
# self.assertEqual(self.getBalance(self.owner), 1000*10**18)
# self.assertEqual(self.getBalance(self.token1), 0)

def getBalance(self, account):
return int(self.model.eth_getBalance(account, 'latest'), 16)
Expand Down Expand Up @@ -534,7 +547,6 @@ def test_transferTokens(self):
self.assertTrue(receiptId in block['transactions'])



class SolanaProxyPlugin(HttpWebServerBasePlugin):
"""Extend in-built Web Server to add Reverse Proxy capabilities.
"""
Expand Down Expand Up @@ -571,7 +583,7 @@ def process_request(self, request):
try:
method = getattr(self.model, request['method'])
response['result'] = method(*request['params'])
except solana.rpc.api.SendTransactionError as err:
except SolanaTrxError as err:
traceback.print_exc()
response['error'] = err.result
except EthereumError as err:
Expand All @@ -595,7 +607,6 @@ def handle_request(self, request: HttpParser) -> None:
})))
return

# print('headers', request.headers)
logger.debug('<<< %s 0x%x %s', threading.get_ident(), id(self.model), request.body.decode('utf8'))
response = None

Expand Down
Loading

0 comments on commit 6e1864e

Please sign in to comment.