Initial commit; receive all eth code from chainlib package

This commit is contained in:
nolash
2021-06-28 07:48:36 +02:00
commit f8afc172c6
59 changed files with 4535 additions and 0 deletions

13
chainlib/eth/address.py Normal file
View File

@@ -0,0 +1,13 @@
# third-party imports
import sha3
from hexathon import (
strip_0x,
uniform,
)
from crypto_dev_signer.encoding import (
is_address,
is_checksum_address,
to_checksum_address,
)
to_checksum = to_checksum_address

79
chainlib/eth/block.py Normal file
View File

@@ -0,0 +1,79 @@
# third-party imports
from chainlib.jsonrpc import JSONRPCRequest
from chainlib.eth.tx import Tx
from hexathon import (
add_0x,
strip_0x,
even,
)
def block_latest(id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_blockNumber'
return j.finalize(o)
def block_by_hash(hsh, include_tx=True, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_getBlockByHash'
o['params'].append(hsh)
o['params'].append(include_tx)
return j.finalize(o)
def block_by_number(n, include_tx=True, id_generator=None):
nhx = add_0x(even(hex(n)[2:]))
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_getBlockByNumber'
o['params'].append(nhx)
o['params'].append(include_tx)
return j.finalize(o)
def transaction_count(block_hash, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_getBlockTransactionCountByHash'
o['params'].append(block_hash)
return j.finalize(o)
class Block:
def __init__(self, src):
self.hash = src['hash']
try:
self.number = int(strip_0x(src['number']), 16)
except TypeError:
self.number = int(src['number'])
self.txs = src['transactions']
self.block_src = src
try:
self.timestamp = int(strip_0x(src['timestamp']), 16)
except TypeError:
self.timestamp = int(src['timestamp'])
def src(self):
return self.block_src
def tx(self, i):
return Tx(self.txs[i], self)
def tx_src(self, i):
return self.txs[i]
def __str__(self):
return 'block {} {} ({} txs)'.format(self.number, self.hash, len(self.txs))
@staticmethod
def from_src(src):
return Block(src)

8
chainlib/eth/chain.py Normal file
View File

@@ -0,0 +1,8 @@
from chainlib.jsonrpc import JSONRPCRequest
def network_id(id_generator=None):
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'net_version'
return j.finalize(o)

140
chainlib/eth/connection.py Normal file
View File

@@ -0,0 +1,140 @@
# standard imports
import copy
import logging
import json
import datetime
import time
import socket
from urllib.request import (
Request,
urlopen,
)
# third-party imports
from hexathon import (
add_0x,
strip_0x,
)
# local imports
from .error import (
DefaultErrorParser,
RevertEthException,
)
from .sign import (
sign_transaction,
)
from chainlib.connection import (
ConnType,
RPCConnection,
JSONRPCHTTPConnection,
JSONRPCUnixConnection,
error_parser,
)
from chainlib.jsonrpc import (
JSONRPCRequest,
jsonrpc_result,
)
from chainlib.eth.tx import (
unpack,
)
logg = logging.getLogger(__name__)
class EthHTTPConnection(JSONRPCHTTPConnection):
def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser, id_generator=None):
t = datetime.datetime.utcnow()
i = 0
while True:
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] ='eth_getTransactionReceipt'
o['params'].append(add_0x(tx_hash_hex))
o = j.finalize(o)
req = Request(
self.location,
method='POST',
)
req.add_header('Content-Type', 'application/json')
data = json.dumps(o)
logg.debug('(HTTP) poll receipt attempt {} {}'.format(i, data))
res = urlopen(req, data=data.encode('utf-8'))
r = json.load(res)
e = jsonrpc_result(r, error_parser)
if e != None:
logg.debug('(HTTP) poll receipt completed {}'.format(r))
logg.debug('e {}'.format(strip_0x(e['status'])))
if strip_0x(e['status']) == '00':
raise RevertEthException(tx_hash_hex)
return e
if timeout > 0.0:
delta = (datetime.datetime.utcnow() - t) + datetime.timedelta(seconds=delay)
if delta.total_seconds() >= timeout:
raise TimeoutError(tx_hash)
time.sleep(delay)
i += 1
def check_rpc(self, id_generator=None):
j = JSONRPCRequest(id_generator)
req = j.template()
req['method'] = 'net_version'
req = j.finalize(req)
r = self.do(req)
class EthUnixConnection(JSONRPCUnixConnection):
def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser):
raise NotImplementedError('Not yet implemented for unix socket')
def sign_transaction_to_rlp(chain_spec, doer, tx):
txs = tx.serialize()
logg.debug('serializing {}'.format(txs))
# TODO: because some rpc servers may fail when chainId is included, we are forced to spend cpu here on this
chain_id = txs.get('chainId') or 1
if chain_spec != None:
chain_id = chain_spec.chain_id()
txs['chainId'] = add_0x(chain_id.to_bytes(2, 'big').hex())
txs['from'] = add_0x(tx.sender)
o = sign_transaction(txs)
r = doer(o)
logg.debug('sig got {}'.format(r))
return bytes.fromhex(strip_0x(r))
def sign_message(doer, msg):
o = sign_message(msg)
return doer(o)
class EthUnixSignerConnection(EthUnixConnection):
def sign_transaction_to_rlp(self, tx):
return sign_transaction_to_rlp(self.chain_spec, self.do, tx)
def sign_message(self, tx):
return sign_message(self.do, tx)
class EthHTTPSignerConnection(EthHTTPConnection):
def sign_transaction_to_rlp(self, tx):
return sign_transaction_to_rlp(self.chain_spec, self.do, tx)
def sign_message(self, tx):
return sign_message(self.do, tx)
RPCConnection.register_constructor(ConnType.HTTP, EthHTTPConnection, tag='eth_default')
RPCConnection.register_constructor(ConnType.HTTP_SSL, EthHTTPConnection, tag='eth_default')
RPCConnection.register_constructor(ConnType.UNIX, EthUnixConnection, tag='eth_default')

5
chainlib/eth/constant.py Normal file
View File

@@ -0,0 +1,5 @@
ZERO_ADDRESS = '0x{:040x}'.format(0)
ZERO_CONTENT = '0x{:064x}'.format(0)
MINIMUM_FEE_UNITS = 21000
MINIMUM_FEE_PRICE = 1000000000
MAX_UINT = int('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)

328
chainlib/eth/contract.py Normal file
View File

@@ -0,0 +1,328 @@
# standard imports
import enum
import re
import logging
# external imports
from hexathon import (
strip_0x,
pad,
)
# local imports
from chainlib.hash import keccak256_string_to_hex
from chainlib.block import BlockSpec
from chainlib.jsonrpc import JSONRPCRequest
from .address import to_checksum_address
#logg = logging.getLogger(__name__)
logg = logging.getLogger()
re_method = r'^[a-zA-Z0-9_]+$'
class ABIContractType(enum.Enum):
BYTES32 = 'bytes32'
BYTES4 = 'bytes4'
UINT256 = 'uint256'
ADDRESS = 'address'
STRING = 'string'
BOOLEAN = 'bool'
dynamic_contract_types = [
ABIContractType.STRING,
]
class ABIContract:
def __init__(self):
self.types = []
self.contents = []
class ABIMethodEncoder(ABIContract):
def __init__(self):
super(ABIMethodEncoder, self).__init__()
self.method_name = None
self.method_contents = []
def method(self, m):
if re.match(re_method, m) == None:
raise ValueError('Invalid method {}, must match regular expression {}'.format(re_method))
self.method_name = m
self.__log_method()
def get_method(self):
if self.method_name == None:
return ''
return '{}({})'.format(self.method_name, ','.join(self.method_contents))
def typ(self, v):
if self.method_name == None:
raise AttributeError('method name must be set before adding types')
if not isinstance(v, ABIContractType):
raise TypeError('method type not valid; expected {}, got {}'.format(type(ABIContractType).__name__, type(v).__name__))
self.method_contents.append(v.value)
self.__log_method()
def __log_method(self):
logg.debug('method set to {}'.format(self.get_method()))
class ABIContractDecoder:
def typ(self, v):
if not isinstance(v, ABIContractType):
raise TypeError('method type not valid; expected {}, got {}'.format(type(ABIContractType).__name__, type(v).__name__))
self.types.append(v.value)
self.__log_typ()
def val(self, v):
self.contents.append(v)
logg.debug('content is now {}'.format(self.contents))
def uint256(self, v):
return int(v, 16)
def bytes32(self, v):
return v
def bool(self, v):
return bool(self.uint256(v))
def boolean(self, v):
return bool(self.uint256(v))
def address(self, v):
a = strip_0x(v)[64-40:]
return to_checksum_address(a)
def string(self, v):
s = strip_0x(v)
b = bytes.fromhex(s)
cursor = 0
offset = int.from_bytes(b[cursor:cursor+32], 'big')
cursor += 32
length = int.from_bytes(b[cursor:cursor+32], 'big')
cursor += 32
content = b[cursor:cursor+length]
logg.debug('parsing string offset {} length {} content {}'.format(offset, length, content))
return content.decode('utf-8')
def __log_typ(self):
logg.debug('types set to ({})'.format(','.join(self.types)))
def decode(self):
r = []
logg.debug('contents {}'.format(self.contents))
for i in range(len(self.types)):
m = getattr(self, self.types[i])
s = self.contents[i]
logg.debug('{} {} {} {} {}'.format(i, m, self.types[i], self.contents[i], s))
r.append(m(s.hex()))
return r
def get(self):
return self.decode()
def __str__(self):
return self.decode()
class ABIContractLogDecoder(ABIMethodEncoder, ABIContractDecoder):
def __init__(self):
super(ABIContractLogDecoder, self).__init__()
self.method_name = None
self.indexed_content = []
def topic(self, event):
self.method(event)
def get_method_signature(self):
s = self.get_method()
return keccak256_string_to_hex(s)
def typ(self, v):
super(ABIContractLogDecoder, self).typ(v)
self.types.append(v.value)
def apply(self, topics, data):
t = self.get_method_signature()
if topics[0] != t:
raise ValueError('topic mismatch')
for i in range(len(topics) - 1):
self.contents.append(topics[i+1])
self.contents += data
class ABIContractEncoder(ABIMethodEncoder):
def __log_latest(self, v):
l = len(self.types) - 1
logg.debug('Encoder added {} -> {} ({})'.format(v, self.contents[l], self.types[l].value))
def uint256(self, v):
v = int(v)
b = v.to_bytes(32, 'big')
self.contents.append(b.hex())
self.types.append(ABIContractType.UINT256)
self.__log_latest(v)
def bool(self, v):
return self.boolean(v)
def boolean(self, v):
if bool(v):
return self.uint256(1)
return self.uint256(0)
def address(self, v):
self.bytes_fixed(32, v, 20)
self.types.append(ABIContractType.ADDRESS)
self.__log_latest(v)
def bytes32(self, v):
self.bytes_fixed(32, v)
self.types.append(ABIContractType.BYTES32)
self.__log_latest(v)
def bytes4(self, v):
self.bytes_fixed(4, v)
self.types.append(ABIContractType.BYTES4)
self.__log_latest(v)
def string(self, v):
b = v.encode('utf-8')
l = len(b)
contents = l.to_bytes(32, 'big')
contents += b
padlen = 32 - (l % 32)
contents += padlen * b'\x00'
self.bytes_fixed(len(contents), contents)
self.types.append(ABIContractType.STRING)
self.__log_latest(v)
return contents
def bytes_fixed(self, mx, v, exact=0):
typ = type(v).__name__
if typ == 'str':
v = strip_0x(v)
l = len(v)
if exact > 0 and l != exact * 2:
raise ValueError('value wrong size; expected {}, got {})'.format(mx, l))
if l > mx * 2:
raise ValueError('value too long ({})'.format(l))
v = pad(v, mx)
elif typ == 'bytes':
l = len(v)
if exact > 0 and l != exact:
raise ValueError('value wrong size; expected {}, got {})'.format(mx, l))
b = bytearray(mx)
b[mx-l:] = v
v = pad(b.hex(), mx)
else:
raise ValueError('invalid input {}'.format(typ))
self.contents.append(v.ljust(64, '0'))
def get_method_signature(self):
s = self.get_method()
if s == '':
return s
return keccak256_string_to_hex(s)[:8]
def get_contents(self):
direct_contents = ''
pointer_contents = ''
l = len(self.types)
pointer_cursor = 32 * l
for i in range(l):
if self.types[i] in dynamic_contract_types:
content_length = len(self.contents[i])
pointer_contents += self.contents[i]
direct_contents += pointer_cursor.to_bytes(32, 'big').hex()
pointer_cursor += int(content_length / 2)
else:
direct_contents += self.contents[i]
s = ''.join(direct_contents + pointer_contents)
for i in range(0, len(s), 64):
l = len(s) - i
if l > 64:
l = 64
logg.debug('code word {} {}'.format(int(i / 64), s[i:i+64]))
return s
def get(self):
return self.encode()
def encode(self):
m = self.get_method_signature()
c = self.get_contents()
return m + c
def __str__(self):
return self.encode()
def abi_decode_single(typ, v):
d = ABIContractDecoder()
d.typ(typ)
d.val(v)
r = d.decode()
return r[0]
def code(address, block_spec=BlockSpec.LATEST, id_generator=None):
block_height = None
if block_spec == BlockSpec.LATEST:
block_height = 'latest'
elif block_spec == BlockSpec.PENDING:
block_height = 'pending'
else:
block_height = int(block_spec)
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_getCode'
o['params'].append(address)
o['params'].append(block_height)
return j.finalize(o)

23
chainlib/eth/error.py Normal file
View File

@@ -0,0 +1,23 @@
# local imports
from chainlib.error import ExecutionError
class EthException(Exception):
pass
class RevertEthException(EthException, ExecutionError):
pass
class NotFoundEthException(EthException):
pass
class RequestMismatchException(EthException):
pass
class DefaultErrorParser:
def translate(self, error):
return EthException('default parser code {}'.format(error))

149
chainlib/eth/gas.py Normal file
View File

@@ -0,0 +1,149 @@
# standard imports
import logging
# third-party imports
from hexathon import (
add_0x,
strip_0x,
)
from crypto_dev_signer.eth.transaction import EIP155Transaction
# local imports
from chainlib.hash import keccak256_hex_to_hex
from chainlib.jsonrpc import JSONRPCRequest
from chainlib.eth.tx import (
TxFactory,
TxFormat,
raw,
)
from chainlib.eth.constant import (
MINIMUM_FEE_UNITS,
)
logg = logging.getLogger(__name__)
def price(id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_gasPrice'
return j.finalize(o)
def balance(address, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_getBalance'
o['params'].append(address)
o['params'].append('latest')
return j.finalize(o)
def parse_balance(balance):
try:
r = int(balance, 10)
except ValueError:
r = int(balance, 16)
return r
class Gas(TxFactory):
def create(self, sender_address, recipient_address, value, tx_format=TxFormat.JSONRPC, id_generator=None):
tx = self.template(sender_address, recipient_address, use_nonce=True)
tx['value'] = value
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
tx_raw = self.signer.sign_transaction_to_rlp(txe)
tx_raw_hex = add_0x(tx_raw.hex())
tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
o = None
if tx_format == TxFormat.JSONRPC:
o = raw(tx_raw_hex, id_generator=id_generator)
elif tx_format == TxFormat.RLP_SIGNED:
o = tx_raw_hex
return (tx_hash_hex, o)
class RPCGasOracle:
def __init__(self, conn, code_callback=None, min_price=1, id_generator=None):
self.conn = conn
self.code_callback = code_callback
self.min_price = min_price
self.id_generator = id_generator
def get_gas(self, code=None):
gas_price = 0
if self.conn != None:
o = price(id_generator=self.id_generator)
r = self.conn.do(o)
n = strip_0x(r)
gas_price = int(n, 16)
fee_units = MINIMUM_FEE_UNITS
if self.code_callback != None:
fee_units = self.code_callback(code)
if gas_price < self.min_price:
logg.debug('adjusting price {} to set minimum {}'.format(gas_price, self.min_price))
gas_price = self.min_price
return (gas_price, fee_units)
class RPCPureGasOracle(RPCGasOracle):
def __init__(self, conn, code_callback=None, id_generator=None):
super(RPCPureGasOracle, self).__init__(conn, code_callback=code_callback, min_price=0, id_generator=id_generator)
class OverrideGasOracle(RPCGasOracle):
def __init__(self, price=None, limit=None, conn=None, code_callback=None, id_generator=None):
self.conn = None
self.code_callback = None
self.limit = limit
self.price = price
price_conn = None
if self.limit == None or self.price == None:
if self.price == None:
price_conn = conn
logg.debug('override gas oracle with rpc fallback; price {} limit {}'.format(self.price, self.limit))
super(OverrideGasOracle, self).__init__(price_conn, code_callback, id_generator=id_generator)
def get_gas(self, code=None):
r = None
fee_units = None
fee_price = None
rpc_results = super(OverrideGasOracle, self).get_gas(code)
if self.limit != None:
fee_units = self.limit
if self.price != None:
fee_price = self.price
if fee_price == None:
if rpc_results != None:
fee_price = rpc_results[0]
logg.debug('override gas oracle without explicit price, setting from rpc {}'.format(fee_price))
else:
fee_price = MINIMUM_FEE_PRICE
logg.debug('override gas oracle without explicit price, setting default {}'.format(fee_price))
if fee_units == None:
if rpc_results != None:
fee_units = rpc_results[1]
logg.debug('override gas oracle without explicit limit, setting from rpc {}'.format(fee_units))
else:
fee_units = MINIMUM_FEE_UNITS
logg.debug('override gas oracle without explicit limit, setting default {}'.format(fee_units))
return (fee_price, fee_units)
DefaultGasOracle = RPCGasOracle

16
chainlib/eth/jsonrpc.py Normal file
View File

@@ -0,0 +1,16 @@
# proposed custom errors
# source: https://eth.wiki/json-rpc/json-rpc-error-codes-improvement-proposal
#1 Unauthorized Should be used when some action is not authorized, e.g. sending from a locked account.
#2 Action not allowed Should be used when some action is not allowed, e.g. preventing an action, while another depending action is processing on, like sending again when a confirmation popup is shown to the user (?).
#3 Execution error Will contain a subset of custom errors in the data field. See below.
#100 X doesnt exist Should be used when something which should be there is not found. (Doesnt apply to eth_getTransactionBy_ and eth_getBlock_. They return a success with value null)
#101 Requires ether Should be used for actions which require somethin else, e.g. gas or a value.
#102 Gas too low Should be used when a to low value of gas was given.
#103 Gas limit exceeded Should be used when a limit is exceeded, e.g. for the gas limit in a block.
#104 Rejected Should be used when an action was rejected, e.g. because of its content (too long contract code, containing wrong characters ?, should differ from -32602 - Invalid params).
#105 Ether too low Should be used when a to low value of Ether was given.
#106 Timeout Should be used when an action timedout.
#107 Conflict Should be used when an action conflicts with another (ongoing?) action.

24
chainlib/eth/log.py Normal file
View File

@@ -0,0 +1,24 @@
# external imports
import sha3
class LogBloom:
def __init__(self):
self.content = bytearray(256)
def add(self, element):
if not isinstance(element, bytes):
raise ValueError('element must be bytes')
h = sha3.keccak_256()
h.update(element)
z = h.digest()
for j in range(3):
c = j * 2
v = int.from_bytes(z[c:c+2], byteorder='big')
v &= 0x07ff
m = 255 - int(v / 8)
n = v % 8
self.content[m] |= (1 << n)

63
chainlib/eth/nonce.py Normal file
View File

@@ -0,0 +1,63 @@
# third-party imports
from hexathon import (
add_0x,
strip_0x,
)
# local imports
from chainlib.jsonrpc import JSONRPCRequest
def nonce(address, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_getTransactionCount'
o['params'].append(address)
o['params'].append('pending')
return j.finalize(o)
class NonceOracle:
def __init__(self, address, id_generator=None):
self.address = address
self.id_generator = id_generator
self.nonce = self.get_nonce()
def get_nonce(self):
raise NotImplementedError('Class must be extended')
def next_nonce(self):
n = self.nonce
self.nonce += 1
return n
class RPCNonceOracle(NonceOracle):
def __init__(self, address, conn, id_generator=None):
self.conn = conn
super(RPCNonceOracle, self).__init__(address, id_generator=id_generator)
def get_nonce(self):
o = nonce(self.address, id_generator=self.id_generator)
r = self.conn.do(o)
n = strip_0x(r)
return int(n, 16)
class OverrideNonceOracle(NonceOracle):
def __init__(self, address, nonce):
self.nonce = nonce
super(OverrideNonceOracle, self).__init__(address)
def get_nonce(self):
return self.nonce
DefaultNonceOracle = RPCNonceOracle

View File

@@ -0,0 +1,3 @@
from .fixtures_ethtester import *
from .fixtures_chain import *
from .fixtures_signer import *

View File

@@ -0,0 +1,17 @@
# external imports
import pytest
# local imports
from chainlib.chain import ChainSpec
@pytest.fixture(scope='session')
def default_chain_spec():
return ChainSpec('evm', 'foo', 42)
@pytest.fixture(scope='session')
def default_chain_config():
return {
'foo': 42,
}

View File

@@ -0,0 +1,105 @@
# standard imports
import os
import logging
# external imports
import eth_tester
import pytest
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from crypto_dev_signer.keystore.dict import DictKeystore
# local imports
from chainlib.eth.unittest.base import *
from chainlib.connection import (
RPCConnection,
ConnType,
)
from chainlib.eth.unittest.ethtester import create_tester_signer
from chainlib.eth.address import to_checksum_address
logg = logging.getLogger() #__name__)
@pytest.fixture(scope='function')
def eth_keystore():
return DictKeystore()
@pytest.fixture(scope='function')
def init_eth_tester(
eth_keystore,
):
return create_tester_signer(eth_keystore)
@pytest.fixture(scope='function')
def call_sender(
eth_accounts,
):
return eth_accounts[0]
#
#
#@pytest.fixture(scope='function')
#def eth_signer(
# init_eth_tester,
# ):
# return init_eth_tester
@pytest.fixture(scope='function')
def eth_rpc(
default_chain_spec,
init_eth_rpc,
):
return RPCConnection.connect(default_chain_spec, 'default')
@pytest.fixture(scope='function')
def eth_accounts(
init_eth_tester,
):
addresses = list(init_eth_tester.get_accounts())
for address in addresses:
balance = init_eth_tester.get_balance(address)
logg.debug('prefilled account {} balance {}'.format(address, balance))
return addresses
@pytest.fixture(scope='function')
def eth_empty_accounts(
eth_keystore,
init_eth_tester,
):
a = []
for i in range(10):
#address = init_eth_tester.new_account()
address = eth_keystore.new()
checksum_address = add_0x(to_checksum_address(address))
a.append(checksum_address)
logg.info('added address {}'.format(checksum_address))
return a
@pytest.fixture(scope='function')
def eth_signer(
eth_keystore,
):
return EIP155Signer(eth_keystore)
@pytest.fixture(scope='function')
def init_eth_rpc(
default_chain_spec,
init_eth_tester,
eth_signer,
):
rpc_conn = TestRPCConnection(None, init_eth_tester, eth_signer)
def rpc_with_tester(url=None, chain_spec=default_chain_spec):
return rpc_conn
RPCConnection.register_constructor(ConnType.CUSTOM, rpc_with_tester, tag='default')
RPCConnection.register_constructor(ConnType.CUSTOM, rpc_with_tester, tag='signer')
RPCConnection.register_location('custom', default_chain_spec, tag='default', exist_ok=True)
RPCConnection.register_location('custom', default_chain_spec, tag='signer', exist_ok=True)
return None

View File

@@ -0,0 +1,18 @@
# standard imports
#import os
# external imports
import pytest
#from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
@pytest.fixture(scope='function')
def agent_roles(
eth_accounts,
):
return {
'ALICE': eth_accounts[20],
'BOB': eth_accounts[21],
'CAROL': eth_accounts[23],
'DAVE': eth_accounts[24],
}

View File

View File

@@ -0,0 +1,102 @@
#!python3
"""Token balance query script
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
"""
# SPDX-License-Identifier: GPL-3.0-or-later
# standard imports
import os
import json
import argparse
import logging
# third-party imports
from hexathon import (
add_0x,
strip_0x,
even,
)
import sha3
from eth_abi import encode_single
# local imports
from chainlib.eth.address import to_checksum
from chainlib.jsonrpc import (
jsonrpc_result,
IntSequenceGenerator,
)
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.gas import (
OverrideGasOracle,
balance,
)
from chainlib.chain import ChainSpec
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
default_eth_provider = os.environ.get('RPC_PROVIDER')
if default_eth_provider == None:
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
argparser = argparse.ArgumentParser()
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
argparser.add_argument('-v', action='store_true', help='Be verbose')
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
argparser.add_argument('address', type=str, help='Account address')
args = argparser.parse_args()
if args.vv:
logg.setLevel(logging.DEBUG)
elif args.v:
logg.setLevel(logging.INFO)
rpc_id_generator = None
if args.seq:
rpc_id_generator = IntSequenceGenerator()
auth = None
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
from chainlib.auth import BasicAuth
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
conn = EthHTTPConnection(args.p, auth=auth)
gas_oracle = OverrideGasOracle(conn)
address = to_checksum(args.address)
if not args.u and address != add_0x(args.address):
raise ValueError('invalid checksum address')
chain_spec = ChainSpec.from_chain_str(args.i)
def main():
r = None
decimals = 18
o = balance(address, id_generator=rpc_id_generator)
r = conn.do(o)
hx = strip_0x(r)
balance_value = int(hx, 16)
logg.debug('balance {} = {} decimals {}'.format(even(hx), balance_value, decimals))
balance_str = str(balance_value)
balance_len = len(balance_str)
if balance_len < decimals + 1:
print('0.{}'.format(balance_str.zfill(decimals)))
else:
offset = balance_len-decimals
print('{}.{}'.format(balance_str[:offset],balance_str[offset:]))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,33 @@
# standard imports
import sys
import select
# external imports
from hexathon import strip_0x
# local imports
from chainlib.eth.address import to_checksum_address
v = None
if len(sys.argv) > 1:
v = sys.argv[1]
else:
h = select.select([sys.stdin], [], [], 0)
if len(h[0]) > 0:
v = h[0][0].read()
v = v.rstrip()
if v == None:
sys.stderr.write('input missing\n')
sys.exit(1)
def main():
try:
print(to_checksum_address(strip_0x(v)))
except ValueError as e:
sys.stderr.write('invalid input: {}\n'.format(e))
sys.exit(1)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,90 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# standard imports
import sys
import os
import json
import argparse
import logging
import select
# local imports
from chainlib.eth.address import to_checksum
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.tx import count
from chainlib.chain import ChainSpec
from chainlib.jsonrpc import IntSequenceGenerator
from crypto_dev_signer.keystore.dict import DictKeystore
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from hexathon import add_0x
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
default_eth_provider = os.environ.get('RPC_PROVIDER')
if default_eth_provider == None:
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
def stdin_arg():
h = select.select([sys.stdin], [], [], 0)
if len(h[0]) > 0:
v = h[0][0].read()
return v.rstrip()
return None
argparser = argparse.ArgumentParser()
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
argparser.add_argument('-v', action='store_true', help='Be verbose')
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
argparser.add_argument('address', nargs='?', type=str, default=stdin_arg(), help='Ethereum address of recipient')
args = argparser.parse_args()
if args.address == None:
argparser.error('need first positional argument or value from stdin')
if args.vv:
logg.setLevel(logging.DEBUG)
elif args.v:
logg.setLevel(logging.INFO)
signer_address = None
keystore = DictKeystore()
if args.y != None:
logg.debug('loading keystore file {}'.format(args.y))
signer_address = keystore.import_keystore_file(args.y, passphrase)
logg.debug('now have key for signer address {}'.format(signer_address))
signer = EIP155Signer(keystore)
rpc_id_generator = None
if args.seq:
rpc_id_generator = IntSequenceGenerator()
auth = None
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
from chainlib.auth import BasicAuth
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
rpc = EthHTTPConnection(args.p, auth=auth)
def main():
recipient = to_checksum(args.address)
if not args.u and recipient != add_0x(args.address):
raise ValueError('invalid checksum address')
o = count(recipient, id_generator=rpc_id_generator)
r = rpc.do(o)
count_result = None
try:
count_result = int(r, 16)
except ValueError:
count_result = int(r, 10)
print(count_result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,65 @@
#!python3
"""Decode raw transaction
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
"""
# SPDX-License-Identifier: GPL-3.0-or-later
# standard imports
import sys
import os
import json
import argparse
import logging
import select
# external imports
from chainlib.eth.tx import unpack
from chainlib.chain import ChainSpec
# local imports
from chainlib.eth.runnable.util import decode_for_puny_humans
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
def stdin_arg(t=0):
h = select.select([sys.stdin], [], [], t)
if len(h[0]) > 0:
v = h[0][0].read()
return v.rstrip()
return None
argparser = argparse.ArgumentParser()
argparser.add_argument('-i', '--chain-id', dest='i', default='evm:ethereum:1', type=str, help='Numeric network id')
argparser.add_argument('-v', action='store_true', help='Be verbose')
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
argparser.add_argument('tx', type=str, nargs='?', default=stdin_arg(), help='hex-encoded signed raw transaction')
args = argparser.parse_args()
if args.vv:
logg.setLevel(logging.DEBUG)
elif args.v:
logg.setLevel(logging.INFO)
argp = args.tx
logg.debug('txxxx {}'.format(args.tx))
if argp == None:
argp = stdin_arg(t=3)
if argp == None:
argparser.error('need first positional argument or value from stdin')
chain_spec = ChainSpec.from_chain_str(args.i)
def main():
tx_raw = argp
decode_for_puny_humans(tx_raw, chain_spec, sys.stdout)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,180 @@
#!python3
"""Gas transfer script
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
"""
# SPDX-License-Identifier: GPL-3.0-or-later
# standard imports
import io
import sys
import os
import json
import argparse
import logging
import urllib
# external imports
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from crypto_dev_signer.keystore.dict import DictKeystore
from hexathon import (
add_0x,
strip_0x,
)
# local imports
from chainlib.eth.address import to_checksum
from chainlib.eth.connection import EthHTTPConnection
from chainlib.jsonrpc import (
JSONRPCRequest,
IntSequenceGenerator,
)
from chainlib.eth.nonce import (
RPCNonceOracle,
OverrideNonceOracle,
)
from chainlib.eth.gas import (
RPCGasOracle,
OverrideGasOracle,
Gas,
)
from chainlib.eth.gas import balance as gas_balance
from chainlib.chain import ChainSpec
from chainlib.eth.runnable.util import decode_for_puny_humans
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
default_eth_provider = os.environ.get('RPC_PROVIDER')
if default_eth_provider == None:
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
argparser = argparse.ArgumentParser()
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
argparser.add_argument('--nonce', type=int, help='override nonce')
argparser.add_argument('--gas-price', dest='gas_price', type=int, help='override gas price')
argparser.add_argument('--gas-limit', dest='gas_limit', type=int, help='override gas limit')
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
argparser.add_argument('-v', action='store_true', help='Be verbose')
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
argparser.add_argument('-s', '--send', dest='s', action='store_true', help='Send to network')
argparser.add_argument('recipient', type=str, help='ethereum address of recipient')
argparser.add_argument('amount', type=int, help='gas value in wei')
args = argparser.parse_args()
if args.vv:
logg.setLevel(logging.DEBUG)
elif args.v:
logg.setLevel(logging.INFO)
block_all = args.ww
block_last = args.w or block_all
passphrase_env = 'ETH_PASSPHRASE'
if args.env_prefix != None:
passphrase_env = args.env_prefix + '_' + passphrase_env
passphrase = os.environ.get(passphrase_env)
if passphrase == None:
logg.warning('no passphrase given')
passphrase=''
signer_address = None
keystore = DictKeystore()
if args.y != None:
logg.debug('loading keystore file {}'.format(args.y))
signer_address = keystore.import_keystore_file(args.y, password=passphrase)
logg.debug('now have key for signer address {}'.format(signer_address))
signer = EIP155Signer(keystore)
rpc_id_generator = None
if args.seq:
rpc_id_generator = IntSequenceGenerator()
auth = None
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
from chainlib.auth import BasicAuth
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
conn = EthHTTPConnection(args.p, auth=auth)
nonce_oracle = None
if args.nonce != None:
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce, id_generator=rpc_id_generator)
else:
nonce_oracle = RPCNonceOracle(signer_address, conn, id_generator=rpc_id_generator)
gas_oracle = None
if args.gas_price or args.gas_limit != None:
gas_oracle = OverrideGasOracle(price=args.gas_price, limit=args.gas_limit, conn=conn, id_generator=rpc_id_generator)
else:
gas_oracle = RPCGasOracle(conn, id_generator=rpc_id_generator)
chain_spec = ChainSpec.from_chain_str(args.i)
value = args.amount
send = args.s
g = Gas(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
def balance(address, id_generator):
o = gas_balance(address, id_generator=id_generator)
r = conn.do(o)
hx = strip_0x(r)
return int(hx, 16)
def main():
recipient = to_checksum(args.recipient)
if not args.u and recipient != add_0x(args.recipient):
raise ValueError('invalid checksum address')
logg.info('gas transfer from {} to {} value {}'.format(signer_address, recipient, value))
if logg.isEnabledFor(logging.DEBUG):
try:
sender_balance = balance(signer_address, rpc_id_generator)
recipient_balance = balance(recipient, rpc_id_generator)
logg.debug('sender {} balance before: {}'.format(signer_address, sender_balance))
logg.debug('recipient {} balance before: {}'.format(recipient, recipient_balance))
except urllib.error.URLError:
pass
(tx_hash_hex, o) = g.create(signer_address, recipient, value, id_generator=rpc_id_generator)
if send:
conn.do(o)
if block_last:
r = conn.wait(tx_hash_hex)
if logg.isEnabledFor(logging.DEBUG):
sender_balance = balance(signer_address, rpc_id_generator)
recipient_balance = balance(recipient, rpc_id_generator)
logg.debug('sender {} balance after: {}'.format(signer_address, sender_balance))
logg.debug('recipient {} balance after: {}'.format(recipient, recipient_balance))
if r['status'] == 0:
logg.critical('VM revert. Wish I could tell you more')
sys.exit(1)
print(tx_hash_hex)
else:
if logg.isEnabledFor(logging.INFO):
io_str = io.StringIO()
decode_for_puny_humans(o['params'][0], chain_spec, io_str)
print(io_str.getvalue())
else:
print(o['params'][0])
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,158 @@
#!python3
"""Data retrieval script
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
"""
# SPDX-License-Identifier: GPL-3.0-or-later
# standard imports
import sys
import os
import json
import argparse
import logging
import enum
import select
# external imports
from hexathon import (
add_0x,
strip_0x,
)
import sha3
# local imports
from chainlib.eth.address import to_checksum
from chainlib.jsonrpc import (
JSONRPCRequest,
jsonrpc_result,
IntSequenceGenerator,
)
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.tx import (
Tx,
pack,
)
from chainlib.eth.address import to_checksum_address
from chainlib.eth.block import Block
from chainlib.chain import ChainSpec
from chainlib.status import Status
from chainlib.eth.runnable.util import decode_for_puny_humans
logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s')
logg = logging.getLogger()
default_eth_provider = os.environ.get('RPC_PROVIDER')
if default_eth_provider == None:
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
def stdin_arg(t=0):
h = select.select([sys.stdin], [], [], t)
if len(h[0]) > 0:
v = h[0][0].read()
return v.rstrip()
return None
argparser = argparse.ArgumentParser('eth-get', description='display information about an Ethereum address or transaction', epilog='address/transaction can be provided as an argument or from standard input')
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
argparser.add_argument('--rlp', action='store_true', help='Display transaction as raw rlp')
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
argparser.add_argument('-v', action='store_true', help='Be verbose')
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
argparser.add_argument('item', nargs='?', default=stdin_arg(), type=str, help='Item to get information for (address og transaction)')
args = argparser.parse_args()
if args.vv:
logg.setLevel(logging.DEBUG)
elif args.v:
logg.setLevel(logging.INFO)
argp = args.item
if argp == None:
argp = stdin_arg(None)
if argsp == None:
argparser.error('need first positional argument or value from stdin')
rpc_id_generator = None
if args.seq:
rpc_id_generator = IntSequenceGenerator()
auth = None
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
from chainlib.auth import BasicAuth
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
conn = EthHTTPConnection(args.p, auth=auth)
chain_spec = ChainSpec.from_chain_str(args.i)
item = add_0x(args.item)
as_rlp = bool(args.rlp)
def get_transaction(conn, tx_hash, id_generator):
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'eth_getTransactionByHash'
o['params'].append(tx_hash)
o = j.finalize(o)
tx_src = conn.do(o)
if tx_src == None:
logg.error('Transaction {} not found'.format(tx_hash))
sys.exit(1)
if as_rlp:
tx_src = Tx.src_normalize(tx_src)
return pack(tx_src, chain_spec).hex()
tx = None
status = -1
rcpt = None
o = j.template()
o['method'] = 'eth_getTransactionReceipt'
o['params'].append(tx_hash)
o = j.finalize(o)
rcpt = conn.do(o)
#status = int(strip_0x(rcpt['status']), 16)
if tx == None:
tx = Tx(tx_src)
if rcpt != None:
tx.apply_receipt(rcpt)
tx.generate_wire(chain_spec)
return tx
def get_address(conn, address, id_generator):
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'eth_getCode'
o['params'].append(address)
o['params'].append('latest')
o = j.finalize(o)
code = conn.do(o)
content = strip_0x(code, allow_empty=True)
if len(content) == 0:
return None
return content
def main():
r = None
if len(item) > 42:
r = get_transaction(conn, item, rpc_id_generator).to_human()
elif args.u or to_checksum_address(item):
r = get_address(conn, item, rpc_id_generator)
print(r)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,170 @@
#!python3
"""Token balance query script
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
"""
# SPDX-License-Identifier: GPL-3.0-or-later
# standard imports
import datetime
import sys
import os
import json
import argparse
import logging
# third-party imports
from hexathon import (
add_0x,
strip_0x,
even,
)
import sha3
from eth_abi import encode_single
# local imports
from chainlib.eth.address import (
to_checksum_address,
is_checksum_address,
)
from chainlib.eth.chain import network_id
from chainlib.eth.block import (
block_latest,
block_by_number,
Block,
)
from chainlib.eth.tx import count
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.gas import (
OverrideGasOracle,
balance,
price,
)
from chainlib.jsonrpc import (
IntSequenceGenerator,
)
from chainlib.chain import ChainSpec
BLOCK_SAMPLES = 10
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
default_eth_provider = os.environ.get('RPC_PROVIDER')
if default_eth_provider == None:
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
argparser = argparse.ArgumentParser()
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
argparser.add_argument('-H', '--human', dest='human', action='store_true', help='Use human-friendly formatting')
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
argparser.add_argument('-l', '--long', dest='l', action='store_true', help='Calculate averages through sampling of blocks and txs')
argparser.add_argument('-v', action='store_true', help='Be verbose')
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Include summary for keyfile')
argparser.add_argument('address', nargs='?', type=str, help='Include summary for address (conflicts with -y)')
args = argparser.parse_args()
if args.vv:
logg.setLevel(logging.DEBUG)
elif args.v:
logg.setLevel(logging.INFO)
signer = None
holder_address = None
if args.address != None:
if not args.u and not is_checksum_address(args.address):
raise ValueError('invalid checksum address {}'.format(args.address))
holder_address = add_0x(args.address)
elif args.y != None:
f = open(args.y, 'r')
o = json.load(f)
f.close()
holder_address = add_0x(to_checksum_address(o['address']))
rpc_id_generator = None
if args.seq:
rpc_id_generator = IntSequenceGenerator()
auth = None
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
from chainlib.auth import BasicAuth
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
conn = EthHTTPConnection(args.p, auth=auth)
gas_oracle = OverrideGasOracle(conn)
token_symbol = 'eth'
chain_spec = ChainSpec.from_chain_str(args.i)
human = args.human
longmode = args.l
def main():
o = network_id(id_generator=rpc_id_generator)
r = conn.do(o)
#if human:
# n = format(n, ',')
sys.stdout.write('Network id: {}\n'.format(r))
o = block_latest(id_generator=rpc_id_generator)
r = conn.do(o)
n = int(r, 16)
first_block_number = n
if human:
n = format(n, ',')
sys.stdout.write('Block: {}\n'.format(n))
o = block_by_number(first_block_number, False, id_generator=rpc_id_generator)
r = conn.do(o)
last_block = Block(r)
last_timestamp = last_block.timestamp
if longmode:
aggr_time = 0.0
aggr_gas = 0
for i in range(BLOCK_SAMPLES):
o = block_by_number(first_block_number-i, False, id_generator=rpc_id_generator)
r = conn.do(o)
block = Block(r)
aggr_time += last_block.timestamp - block.timestamp
gas_limit = int(r['gasLimit'], 16)
aggr_gas += gas_limit
last_block = block
last_timestamp = block.timestamp
n = int(aggr_gas / BLOCK_SAMPLES)
if human:
n = format(n, ',')
sys.stdout.write('Gaslimit: {}\n'.format(n))
sys.stdout.write('Blocktime: {}\n'.format(aggr_time / BLOCK_SAMPLES))
o = price(id_generator=rpc_id_generator)
r = conn.do(o)
n = int(r, 16)
if human:
n = format(n, ',')
sys.stdout.write('Gasprice: {}\n'.format(n))
if holder_address != None:
o = count(holder_address)
r = conn.do(o)
n = int(r, 16)
sys.stdout.write('Address: {}\n'.format(holder_address))
sys.stdout.write('Nonce: {}\n'.format(n))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,189 @@
#!python3
"""Gas transfer script
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
"""
# SPDX-License-Identifier: GPL-3.0-or-later
# standard imports
import io
import sys
import os
import json
import argparse
import logging
import urllib
# external imports
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from crypto_dev_signer.keystore.dict import DictKeystore
from hexathon import (
add_0x,
strip_0x,
)
# local imports
from chainlib.eth.address import to_checksum
from chainlib.eth.connection import EthHTTPConnection
from chainlib.jsonrpc import (
JSONRPCRequest,
IntSequenceGenerator,
)
from chainlib.eth.nonce import (
RPCNonceOracle,
OverrideNonceOracle,
)
from chainlib.eth.gas import (
RPCGasOracle,
OverrideGasOracle,
)
from chainlib.eth.tx import (
TxFactory,
raw,
)
from chainlib.chain import ChainSpec
from chainlib.eth.runnable.util import decode_for_puny_humans
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
default_eth_provider = os.environ.get('RPC_PROVIDER')
if default_eth_provider == None:
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
argparser = argparse.ArgumentParser()
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
argparser.add_argument('--nonce', type=int, help='override nonce')
argparser.add_argument('--gas-price', dest='gas_price', type=int, help='override gas price')
argparser.add_argument('--gas-limit', dest='gas_limit', type=int, help='override gas limit')
argparser.add_argument('-a', '--recipient', dest='a', type=str, help='recipient address (None for contract creation)')
argparser.add_argument('-value', type=int, help='gas value of transaction in wei')
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
argparser.add_argument('-v', action='store_true', help='Be verbose')
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
argparser.add_argument('-s', '--send', dest='s', action='store_true', help='Send to network')
argparser.add_argument('-l', '--local', dest='l', action='store_true', help='Local contract call')
argparser.add_argument('data', nargs='?', type=str, help='Transaction data')
args = argparser.parse_args()
if args.vv:
logg.setLevel(logging.DEBUG)
elif args.v:
logg.setLevel(logging.INFO)
block_all = args.ww
block_last = args.w or block_all
passphrase_env = 'ETH_PASSPHRASE'
if args.env_prefix != None:
passphrase_env = args.env_prefix + '_' + passphrase_env
passphrase = os.environ.get(passphrase_env)
if passphrase == None:
logg.warning('no passphrase given')
passphrase=''
signer_address = None
keystore = DictKeystore()
if args.y != None:
logg.debug('loading keystore file {}'.format(args.y))
signer_address = keystore.import_keystore_file(args.y, password=passphrase)
logg.debug('now have key for signer address {}'.format(signer_address))
signer = EIP155Signer(keystore)
rpc_id_generator = None
if args.seq:
rpc_id_generator = IntSequenceGenerator()
auth = None
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
from chainlib.auth import BasicAuth
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
conn = EthHTTPConnection(args.p, auth=auth)
send = args.s
local = args.l
if local:
send = False
nonce_oracle = None
gas_oracle = None
if signer_address != None and not local:
if args.nonce != None:
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce)
else:
nonce_oracle = RPCNonceOracle(signer_address, conn)
if args.gas_price or args.gas_limit != None:
gas_oracle = OverrideGasOracle(price=args.gas_price, limit=args.gas_limit, conn=conn, id_generator=rpc_id_generator)
else:
gas_oracle = RPCGasOracle(conn, id_generator=rpc_id_generator)
chain_spec = ChainSpec.from_chain_str(args.i)
value = args.value
g = TxFactory(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
def main():
recipient = None
if args.a != None:
recipient = add_0x(to_checksum(args.a))
if not args.u and recipient != add_0x(recipient):
raise ValueError('invalid checksum address')
if local:
j = JSONRPCRequest(id_generator=rpc_id_generator)
o = j.template()
o['method'] = 'eth_call'
o['params'].append({
'to': recipient,
'from': signer_address,
'value': '0x00',
'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit
'gasPrice': '0x01',
'data': add_0x(args.data),
})
o['params'].append('latest')
o = j.finalize(o)
r = conn.do(o)
print(strip_0x(r))
return
elif signer_address != None:
tx = g.template(signer_address, recipient, use_nonce=True)
if args.data != None:
tx = g.set_code(tx, add_0x(args.data))
(tx_hash_hex, o) = g.finalize(tx, id_generator=rpc_id_generator)
if send:
r = conn.do(o)
print(r)
else:
print(o)
print(tx_hash_hex)
else:
o = raw(args.data, id_generator=rpc_id_generator)
if send:
r = conn.do(o)
print(r)
else:
print(o)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,21 @@
import json
import websocket
ws = websocket.create_connection('ws://localhost:8545')
o = {
"jsonrpc": "2.0",
"method": "eth_subscribe",
"params": [
"newHeads",
],
"id": 0,
}
ws.send(json.dumps(o).encode('utf-8'))
while True:
print(ws.recv())
ws.close()

View File

@@ -0,0 +1,31 @@
# local imports
from chainlib.eth.tx import unpack
from hexathon import (
strip_0x,
add_0x,
)
def decode_out(tx, writer, skip_keys=[]):
for k in tx.keys():
if k in skip_keys:
continue
x = None
if k == 'value':
x = '{:.18f} eth'.format(tx[k] / (10**18))
elif k == 'gasPrice':
x = '{} gwei'.format(int(tx[k] / (10**9)))
elif k == 'value':
k = 'gas-value'
if x != None:
writer.write('{}: {} ({})\n'.format(k, tx[k], x))
else:
writer.write('{}: {}\n'.format(k, tx[k]))
def decode_for_puny_humans(tx_raw, chain_spec, writer, skip_keys=[]):
tx_raw = strip_0x(tx_raw)
tx_raw_bytes = bytes.fromhex(tx_raw)
tx = unpack(tx_raw_bytes, chain_spec)
decode_out(tx, writer, skip_keys=skip_keys)
writer.write('src: {}\n'.format(add_0x(tx_raw)))

26
chainlib/eth/sign.py Normal file
View File

@@ -0,0 +1,26 @@
# local imports
from chainlib.jsonrpc import JSONRPCRequest
def new_account(passphrase='', id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'personal_newAccount'
o['params'] = [passphrase]
return j.finalize(o)
def sign_transaction(payload, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_signTransaction'
o['params'] = [payload]
return j.finalize(o)
def sign_message(address, payload, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_sign'
o['params'] = [address, payload]
return j.finalize(o)

520
chainlib/eth/tx.py Normal file
View File

@@ -0,0 +1,520 @@
# standard imports
import logging
import enum
import re
# external imports
import coincurve
import sha3
from hexathon import (
strip_0x,
add_0x,
)
from rlp import decode as rlp_decode
from rlp import encode as rlp_encode
from crypto_dev_signer.eth.transaction import EIP155Transaction
from crypto_dev_signer.encoding import public_key_to_address
from crypto_dev_signer.eth.encoding import chain_id_to_v
from potaahto.symbols import snake_and_camel
# local imports
from chainlib.hash import keccak256_hex_to_hex
from chainlib.status import Status
from .address import to_checksum
from .constant import (
MINIMUM_FEE_UNITS,
MINIMUM_FEE_PRICE,
ZERO_ADDRESS,
)
from .contract import ABIContractEncoder
from chainlib.jsonrpc import JSONRPCRequest
logg = logging.getLogger().getChild(__name__)
class TxFormat(enum.IntEnum):
DICT = 0x00
RAW = 0x01
RAW_SIGNED = 0x02
RAW_ARGS = 0x03
RLP = 0x10
RLP_SIGNED = 0x11
JSONRPC = 0x10
field_debugs = [
'nonce',
'gasPrice',
'gas',
'to',
'value',
'data',
'v',
'r',
's',
]
def count(address, confirmed=False, id_generator=None):
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'eth_getTransactionCount'
o['params'].append(address)
if confirmed:
o['params'].append('latest')
else:
o['params'].append('pending')
return j.finalize(o)
count_pending = count
def count_confirmed(address):
return count(address, True)
def pack(tx_src, chain_spec):
if isinstance(tx_src, Tx):
tx_src = tx_src.as_dict()
tx_src = Tx.src_normalize(tx_src)
tx = EIP155Transaction(tx_src, tx_src['nonce'], chain_spec.chain_id())
signature = bytearray(65)
cursor = 0
for a in [
tx_src['r'],
tx_src['s'],
]:
for b in bytes.fromhex(strip_0x(a)):
signature[cursor] = b
cursor += 1
#signature[cursor] = chainv_to_v(chain_spec.chain_id(), tx_src['v'])
tx.apply_signature(chain_spec.chain_id(), signature, v=tx_src['v'])
logg.debug('tx {}'.format(tx.serialize()))
return tx.rlp_serialize()
def unpack(tx_raw_bytes, chain_spec):
chain_id = chain_spec.chain_id()
tx = __unpack_raw(tx_raw_bytes, chain_id)
tx['nonce'] = int.from_bytes(tx['nonce'], 'big')
tx['gasPrice'] = int.from_bytes(tx['gasPrice'], 'big')
tx['gas'] = int.from_bytes(tx['gas'], 'big')
tx['value'] = int.from_bytes(tx['value'], 'big')
return tx
def unpack_hex(tx_raw_bytes, chain_spec):
chain_id = chain_spec.chain_id()
tx = __unpack_raw(tx_raw_bytes, chain_id)
tx['nonce'] = add_0x(hex(tx['nonce']))
tx['gasPrice'] = add_0x(hex(tx['gasPrice']))
tx['gas'] = add_0x(hex(tx['gas']))
tx['value'] = add_0x(hex(tx['value']))
tx['chainId'] = add_0x(hex(tx['chainId']))
return tx
def __unpack_raw(tx_raw_bytes, chain_id=1):
d = rlp_decode(tx_raw_bytes)
logg.debug('decoding using chain id {}'.format(str(chain_id)))
j = 0
for i in d:
v = i.hex()
if j != 3 and v == '':
v = '00'
logg.debug('decoded {}: {}'.format(field_debugs[j], v))
j += 1
vb = chain_id
if chain_id != 0:
v = int.from_bytes(d[6], 'big')
vb = v - (chain_id * 2) - 35
r = bytearray(32)
r[32-len(d[7]):] = d[7]
s = bytearray(32)
s[32-len(d[8]):] = d[8]
logg.debug('vb {}'.format(vb))
sig = b''.join([r, s, bytes([vb])])
#so = KeyAPI.Signature(signature_bytes=sig)
h = sha3.keccak_256()
h.update(rlp_encode(d))
signed_hash = h.digest()
d[6] = chain_id
d[7] = b''
d[8] = b''
h = sha3.keccak_256()
h.update(rlp_encode(d))
unsigned_hash = h.digest()
#p = so.recover_public_key_from_msg_hash(unsigned_hash)
#a = p.to_checksum_address()
pubk = coincurve.PublicKey.from_signature_and_message(sig, unsigned_hash, hasher=None)
a = public_key_to_address(pubk)
logg.debug('decoded recovery byte {}'.format(vb))
logg.debug('decoded address {}'.format(a))
logg.debug('decoded signed hash {}'.format(signed_hash.hex()))
logg.debug('decoded unsigned hash {}'.format(unsigned_hash.hex()))
to = d[3].hex() or None
if to != None:
to = to_checksum(to)
data = d[5].hex()
try:
data = add_0x(data)
except:
data = '0x'
return {
'from': a,
'to': to,
'nonce': d[0],
'gasPrice': d[1],
'gas': d[2],
'value': d[4],
'data': data,
'v': v,
'recovery_byte': vb,
'r': add_0x(sig[:32].hex()),
's': add_0x(sig[32:64].hex()),
'chainId': chain_id,
'hash': add_0x(signed_hash.hex()),
'hash_unsigned': add_0x(unsigned_hash.hex()),
}
def transaction(hsh, id_generator=None):
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'eth_getTransactionByHash'
o['params'].append(add_0x(hsh))
return j.finalize(o)
def transaction_by_block(hsh, idx, id_generator=None):
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'eth_getTransactionByBlockHashAndIndex'
o['params'].append(add_0x(hsh))
o['params'].append(hex(idx))
return j.finalize(o)
def receipt(hsh, id_generator=None):
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'eth_getTransactionReceipt'
o['params'].append(add_0x(hsh))
return j.finalize(o)
def raw(tx_raw_hex, id_generator=None):
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'eth_sendRawTransaction'
o['params'].append(add_0x(tx_raw_hex))
return j.finalize(o)
class TxFactory:
fee = 8000000
def __init__(self, chain_spec, signer=None, gas_oracle=None, nonce_oracle=None):
self.gas_oracle = gas_oracle
self.nonce_oracle = nonce_oracle
self.chain_spec = chain_spec
self.signer = signer
def build_raw(self, tx):
if tx['to'] == None or tx['to'] == '':
tx['to'] = '0x'
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
tx_raw = self.signer.sign_transaction_to_rlp(txe)
tx_raw_hex = add_0x(tx_raw.hex())
tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
return (tx_hash_hex, tx_raw_hex)
def build(self, tx, id_generator=None):
(tx_hash_hex, tx_raw_hex) = self.build_raw(tx)
o = raw(tx_raw_hex, id_generator=id_generator)
return (tx_hash_hex, o)
def template(self, sender, recipient, use_nonce=False):
gas_price = MINIMUM_FEE_PRICE
gas_limit = MINIMUM_FEE_UNITS
if self.gas_oracle != None:
(gas_price, gas_limit) = self.gas_oracle.get_gas()
logg.debug('using gas price {} limit {}'.format(gas_price, gas_limit))
nonce = 0
o = {
'from': sender,
'to': recipient,
'value': 0,
'data': '0x',
'gasPrice': gas_price,
'gas': gas_limit,
'chainId': self.chain_spec.chain_id(),
}
if self.nonce_oracle != None and use_nonce:
nonce = self.nonce_oracle.next_nonce()
logg.debug('using nonce {} for address {}'.format(nonce, sender))
o['nonce'] = nonce
return o
def normalize(self, tx):
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
txes = txe.serialize()
return {
'from': tx['from'],
'to': txes['to'],
'gasPrice': txes['gasPrice'],
'gas': txes['gas'],
'data': txes['data'],
}
def finalize(self, tx, tx_format=TxFormat.JSONRPC, id_generator=None):
if tx_format == TxFormat.JSONRPC:
return self.build(tx, id_generator=id_generator)
elif tx_format == TxFormat.RLP_SIGNED:
return self.build_raw(tx)
raise NotImplementedError('tx formatting {} not implemented'.format(tx_format))
def set_code(self, tx, data, update_fee=True):
tx['data'] = data
if update_fee:
tx['gas'] = TxFactory.fee
if self.gas_oracle != None:
(price, tx['gas']) = self.gas_oracle.get_gas(code=data)
else:
logg.debug('using hardcoded gas limit of 8000000 until we have reliable vm executor')
return tx
def transact_noarg(self, method, contract_address, sender_address, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method(method)
data = enc.get()
tx = self.template(sender_address, contract_address, use_nonce=True)
tx = self.set_code(tx, data)
tx = self.finalize(tx, tx_format)
return tx
def call_noarg(self, method, contract_address, sender_address=ZERO_ADDRESS, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method(method)
data = add_0x(enc.get())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)
o['params'].append(self.normalize(tx))
o['params'].append('latest')
o = j.finalize(o)
return o
class Tx:
# TODO: force tx type schema parser (whether expect hex or int etc)
def __init__(self, src, block=None, rcpt=None):
self.tx_src = self.src_normalize(src)
self.index = -1
tx_hash = add_0x(src['hash'])
if block != None:
i = 0
for tx in block.txs:
tx_hash_block = None
try:
tx_hash_block = tx['hash']
except TypeError:
tx_hash_block = add_0x(tx)
if tx_hash_block == tx_hash:
self.index = i
break
i += 1
if self.index == -1:
raise AttributeError('tx {} not found in block {}'.format(tx_hash, block.hash))
self.block = block
self.hash = strip_0x(tx_hash)
try:
self.value = int(strip_0x(src['value']), 16)
except TypeError:
self.value = int(src['value'])
try:
self.nonce = int(strip_0x(src['nonce']), 16)
except TypeError:
self.nonce = int(src['nonce'])
address_from = strip_0x(src['from'])
try:
self.gas_price = int(strip_0x(src['gasPrice']), 16)
except TypeError:
self.gas_price = int(src['gasPrice'])
try:
self.gas_limit = int(strip_0x(src['gas']), 16)
except TypeError:
self.gas_limit = int(src['gas'])
self.outputs = [to_checksum(address_from)]
self.contract = None
try:
inpt = src['input']
except KeyError:
inpt = src['data']
if inpt != '0x':
inpt = strip_0x(inpt)
else:
inpt = ''
self.payload = inpt
to = src['to']
if to == None:
to = ZERO_ADDRESS
self.inputs = [to_checksum(strip_0x(to))]
self.block = block
try:
self.wire = src['raw']
except KeyError:
logg.warning('no inline raw tx src, and no raw rendering implemented, field will be "None"')
self.status = Status.PENDING
self.logs = None
if rcpt != None:
self.apply_receipt(rcpt)
self.v = src.get('v')
self.r = src.get('r')
self.s = src.get('s')
self.wire = None
def src(self):
return self.tx_src
@classmethod
def src_normalize(self, src):
src = snake_and_camel(src)
if isinstance(src.get('v'), str):
try:
src['v'] = int(src['v'])
except ValueError:
src['v'] = int(src['v'], 16)
return src
def as_dict(self):
return self.src()
def apply_receipt(self, rcpt):
rcpt = self.src_normalize(rcpt)
logg.debug('rcpt {}'.format(rcpt))
try:
status_number = int(rcpt['status'], 16)
except TypeError:
status_number = int(rcpt['status'])
if status_number == 1:
self.status = Status.SUCCESS
elif status_number == 0:
self.status = Status.ERROR
# TODO: replace with rpc receipt/transaction translator when available
contract_address = rcpt.get('contractAddress')
if contract_address == None:
contract_address = rcpt.get('contract_address')
if contract_address != None:
self.contract = contract_address
self.logs = rcpt['logs']
try:
self.gas_used = int(rcpt['gasUsed'], 16)
except TypeError:
self.gas_used = int(rcpt['gasUsed'])
def apply_block(self, block):
#block_src = self.src_normalize(block_src)
self.block = block
def generate_wire(self, chain_spec):
b = pack(self.src(), chain_spec)
self.wire = add_0x(b.hex())
@staticmethod
def from_src(src, block=None):
return Tx(src, block=block)
def __str__(self):
if self.block != None:
return 'tx {} status {} block {} index {}'.format(add_0x(self.hash), self.status.name, self.block.number, self.index)
else:
return 'tx {} status {}'.format(add_0x(self.hash), self.status.name)
def __repr__(self):
return self.__str__()
def to_human(self):
s = """hash {}
from {}
to {}
value {}
nonce {}
gasPrice {}
gasLimit {}
input {}
""".format(
self.hash,
self.outputs[0],
self.inputs[0],
self.value,
self.nonce,
self.gas_price,
self.gas_limit,
self.payload,
)
if self.status != Status.PENDING:
s += """gasUsed {}
""".format(
self.gas_used,
)
s += 'status ' + self.status.name + '\n'
if self.contract != None:
s += """contract {}
""".format(
self.contract,
)
if self.wire != None:
s += """src {}
""".format(
self.wire,
)
return s

View File

@@ -0,0 +1,218 @@
# standard imports
import os
import logging
# external imports
import eth_tester
import coincurve
from chainlib.connection import (
RPCConnection,
error_parser,
)
from chainlib.eth.address import (
to_checksum_address,
)
from chainlib.jsonrpc import (
jsonrpc_response,
jsonrpc_error,
jsonrpc_result,
)
from hexathon import (
unpad,
add_0x,
strip_0x,
)
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from crypto_dev_signer.encoding import private_key_to_address
logg = logging.getLogger().getChild(__name__)
test_pk = bytes.fromhex('5087503f0a9cc35b38665955eb830c63f778453dd11b8fa5bd04bc41fd2cc6d6')
class EthTesterSigner(eth_tester.EthereumTester):
def __init__(self, backend, keystore):
super(EthTesterSigner, self).__init__(backend)
logg.debug('accounts {}'.format(self.get_accounts()))
self.keystore = keystore
self.backend = backend
self.backend.add_account(test_pk)
for pk in self.backend.account_keys:
pubk = pk.public_key
address = pubk.to_checksum_address()
logg.debug('test keystore have pk {} pubk {} addr {}'.format(pk, pk.public_key, address))
self.keystore.import_raw_key(pk._raw_key)
def new_account(self):
pk = os.urandom(32)
address = self.keystore.import_raw_key(pk)
checksum_address = add_0x(to_checksum_address(address))
self.backend.add_account(pk)
return checksum_address
class TestRPCConnection(RPCConnection):
def __init__(self, location, backend, signer):
super(TestRPCConnection, self).__init__(location)
self.backend = backend
self.signer = signer
def do(self, o, error_parser=error_parser):
logg.debug('testrpc do {}'.format(o))
m = getattr(self, o['method'])
if m == None:
raise ValueError('unhandled method {}'.format(o['method']))
r = None
try:
result = m(o['params'])
logg.debug('result {}'.format(result))
r = jsonrpc_response(o['id'], result)
except Exception as e:
logg.exception(e)
r = jsonrpc_error(o['id'], message=str(e))
return jsonrpc_result(r, error_parser)
def eth_blockNumber(self, p):
block = self.backend.get_block_by_number('latest')
return block['number']
def eth_getBlockByNumber(self, p):
b = bytes.fromhex(strip_0x(p[0]))
n = int.from_bytes(b, 'big')
block = self.backend.get_block_by_number(n)
return block
def eth_getBlockByHash(self, p):
block = self.backend.get_block_by_hash(p[0])
return block
def eth_getTransactionByBlock(self, p):
block = self.eth_getBlockByHash(p)
try:
tx_index = int(p[1], 16)
except TypeError:
tx_index = int(p[1])
tx_hash = block['transactions'][tx_index]
tx = self.eth_getTransactionByHash([tx_hash])
return tx
def eth_getBalance(self, p):
balance = self.backend.get_balance(p[0])
hx = balance.to_bytes(32, 'big').hex()
return add_0x(unpad(hx))
def eth_getTransactionCount(self, p):
nonce = self.backend.get_nonce(p[0])
hx = nonce.to_bytes(4, 'big').hex()
return add_0x(unpad(hx))
def eth_getTransactionByHash(self, p):
tx = self.backend.get_transaction_by_hash(p[0])
return tx
def eth_getTransactionByBlockHashAndIndex(self, p):
#logg.debug('p {}'.format(p))
#block = self.eth_getBlockByHash(p[0])
#tx = block.transactions[p[1]]
#return eth_getTransactionByHash(tx[0])
return self.eth_getTransactionByBlock(p)
def eth_getTransactionReceipt(self, p):
rcpt = self.backend.get_transaction_receipt(p[0])
if rcpt.get('block_number') == None:
rcpt['block_number'] = rcpt['blockNumber']
else:
rcpt['blockNumber'] = rcpt['block_number']
return rcpt
def eth_getCode(self, p):
r = self.backend.get_code(p[0])
return r
def eth_call(self, p):
tx_ethtester = to_ethtester_call(p[0])
r = self.backend.call(tx_ethtester)
return r
def eth_gasPrice(self, p):
return hex(1000000000)
def personal_newAccount(self, passphrase):
a = self.backend.new_account()
return a
def eth_sign(self, p):
r = self.signer.sign_ethereum_message(strip_0x(p[0]), strip_0x(p[1]))
return r
def eth_sendRawTransaction(self, p):
r = self.backend.send_raw_transaction(p[0])
return r
def eth_signTransaction(self, p):
raise NotImplementedError('needs transaction deserializer for EIP155Transaction')
tx_dict = p[0]
tx = EIP155Transaction(tx_dict, tx_dict['nonce'], tx_dict['chainId'])
passphrase = p[1]
r = self.signer.sign_transaction_to_rlp(tx, passphrase)
return r
def __verify_signer(self, tx, passphrase=''):
pk_bytes = self.backend.keystore.get(tx.sender)
pk = coincurve.PrivateKey(secret=pk_bytes)
result_address = private_key_to_address(pk)
assert strip_0x(result_address) == strip_0x(tx.sender)
def sign_transaction(self, tx, passphrase=''):
self.__verify_signer(tx, passphrase)
return self.signer.sign_transaction(tx, passphrase)
def sign_transaction_to_rlp(self, tx, passphrase=''):
self.__verify_signer(tx, passphrase)
return self.signer.sign_transaction_to_rlp(tx, passphrase)
def disconnect(self):
pass
def to_ethtester_call(tx):
if tx['gas'] == '':
tx['gas'] = '0x00'
if tx['gasPrice'] == '':
tx['gasPrice'] = '0x00'
tx = {
'to': tx['to'],
'from': tx['from'],
'gas': int(tx['gas'], 16),
'gas_price': int(tx['gasPrice'], 16),
'data': tx['data'],
}
return tx

View File

@@ -0,0 +1,80 @@
# standard imports
import os
import unittest
import logging
# external imports
import eth_tester
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from crypto_dev_signer.keystore.dict import DictKeystore
from hexathon import (
strip_0x,
add_0x,
)
from eth import constants
from eth.vm.forks.byzantium import ByzantiumVM
# local imports
from .base import (
EthTesterSigner,
TestRPCConnection,
)
from chainlib.connection import (
RPCConnection,
ConnType,
)
from chainlib.eth.address import to_checksum_address
from chainlib.chain import ChainSpec
logg = logging.getLogger(__name__)
test_address = bytes.fromhex('Eb3907eCad74a0013c259D5874AE7f22DcBcC95C')
def create_tester_signer(keystore):
genesis_params = eth_tester.backends.pyevm.main.get_default_genesis_params({
'gas_limit': 8000000,
'coinbase': test_address, # doesn't seem to work
})
vm_configuration = (
(constants.GENESIS_BLOCK_NUMBER, ByzantiumVM),
)
genesis_state = eth_tester.PyEVMBackend._generate_genesis_state(num_accounts=30)
eth_backend = eth_tester.PyEVMBackend(
genesis_state=genesis_state,
genesis_parameters=genesis_params,
vm_configuration=vm_configuration,
)
return EthTesterSigner(eth_backend, keystore)
class EthTesterCase(unittest.TestCase):
def __init__(self, foo):
super(EthTesterCase, self).__init__(foo)
self.accounts = []
def setUp(self):
self.chain_spec = ChainSpec('evm', 'foochain', 42)
self.keystore = DictKeystore()
eth_tester_instance = create_tester_signer(self.keystore)
self.signer = EIP155Signer(self.keystore)
self.helper = eth_tester_instance
self.backend = self.helper.backend
self.rpc = TestRPCConnection(None, eth_tester_instance, self.signer)
for a in self.keystore.list():
self.accounts.append(add_0x(to_checksum_address(a)))
def rpc_with_tester(chain_spec=self.chain_spec, url=None):
return self.rpc
RPCConnection.register_constructor(ConnType.CUSTOM, rpc_with_tester, tag='default')
RPCConnection.register_constructor(ConnType.CUSTOM, rpc_with_tester, tag='signer')
RPCConnection.register_location('custom', self.chain_spec, tag='default', exist_ok=True)
RPCConnection.register_location('custom', self.chain_spec, tag='signer', exist_ok=True)
def tearDown(self):
pass