Skip to content

Commit 4c87c95

Browse files
Merge pull request #362 from neonlabsorg/develop->master_0.5.0
v0.5.0
2 parents 4e03e73 + 981c178 commit 4c87c95

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3071
-1463
lines changed

.buildkite/steps/build-image.sh

Lines changed: 2 additions & 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:=stable}
88

99
# Refreshing neonlabsorg/solana:latest image is required to run .buildkite/steps/build-image.sh locally
@@ -15,4 +15,5 @@ docker pull neonlabsorg/evm_loader:${EVM_LOADER_REVISION}
1515
docker build -t neonlabsorg/proxy:${REVISION} \
1616
--build-arg SOLANA_REVISION=${SOLANA_REVISION} \
1717
--build-arg EVM_LOADER_REVISION=${EVM_LOADER_REVISION} \
18+
--build-arg PROXY_REVISION=${REVISION} \
1819
.

.buildkite/steps/deploy-test.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ docker run --rm -ti --network=container:proxy \
7878
-e EVM_LOADER \
7979
-e SOLANA_URL \
8080
-e EXTRA_GAS=100000 \
81+
-e NEW_USER_AIRDROP_AMOUNT=100 \
82+
-e POSTGRES_DB=neon-db \
83+
-e POSTGRES_USER=neon-proxy \
84+
-e POSTGRES_PASSWORD=neon-proxy-pass \
85+
-e POSTGRES_HOST=postgres \
8186
--entrypoint ./proxy/deploy-test.sh \
8287
${EXTRA_ARGS:-} \
8388
$PROXY_IMAGE \

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
ARG SOLANA_REVISION=v1.7.9-resources
1+
ARG SOLANA_REVISION=v1.7.9-testnet
22
ARG EVM_LOADER_REVISION=stable
33

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

66
FROM neonlabsorg/evm_loader:${EVM_LOADER_REVISION} AS spl
77

88
FROM ubuntu:20.04
9+
ARG PROXY_REVISION
910

1011
RUN apt update && \
1112
DEBIAN_FRONTEND=noninteractive apt -y install \
@@ -41,6 +42,7 @@ COPY --from=spl /opt/solana_utils.py \
4142
COPY --from=spl /opt/neon-cli /spl/bin/emulator
4243

4344
COPY . /opt
45+
RUN sed -i 's/NEON_PROXY_REVISION_TO_BE_REPLACED/'"$PROXY_REVISION"'/g' /opt/proxy/plugin/solana_rest_api.py
4446
COPY proxy/operator-keypair.json /root/.config/solana/id.json
4547
RUN cd /usr/local/lib/python3.8/dist-packages/ && patch -p0 </opt/solana-py.patch
4648

proxy/__main__.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,26 @@
88
:copyright: (c) 2013-present by Abhinav Singh and contributors.
99
:license: BSD, see LICENSE for more details.
1010
"""
11+
1112
from .proxy import entry_point
13+
import os
14+
from .indexer.airdropper import run_airdropper
1215

1316
if __name__ == '__main__':
14-
entry_point()
17+
airdropper_mode = os.environ.get('AIRDROPPER_MODE', 'False').lower() in [1, 'true', 'True']
18+
if airdropper_mode:
19+
print("Will run in airdropper mode")
20+
solana_url = os.environ['SOLANA_URL']
21+
evm_loader_id = os.environ['EVM_LOADER']
22+
faucet_url = os.environ['FAUCET_URL']
23+
wrapper_whitelist = os.environ['INDEXER_ERC20_WRAPPER_WHITELIST'].split(',')
24+
airdrop_amount = int(os.environ['AIRDROP_AMOUNT'])
25+
log_level = os.environ['LOG_LEVEL']
26+
run_airdropper(solana_url,
27+
evm_loader_id,
28+
faucet_url,
29+
wrapper_whitelist,
30+
airdrop_amount,
31+
log_level)
32+
else:
33+
entry_point()

proxy/common_neon/__init__.py

Whitespace-only changes.

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: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from enum import Enum
2+
3+
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+
15+
16+
class SolanaErrors(Enum):
17+
AccountNotFound = "Invalid param: could not find account"
18+
19+
20+
class SolanaAccountNotFoundError(Exception):
21+
"""Provides special error processing"""
22+
def __init__(self):
23+
super().__init__(SolanaErrors.AccountNotFound.value)

0 commit comments

Comments
 (0)