Merge branch 'lash/chainlib-cli' into 'master'
Add docstrings See merge request chaintool/chainlib-eth!1
This commit is contained in:
commit
ffcde95e5f
@ -1,2 +1,3 @@
|
|||||||
- 0.0.5-pending
|
- 0.0.5-pending
|
||||||
* Receive all ethereum components from chainlib package
|
* Receive all ethereum components from chainlib package
|
||||||
|
* Make settings configurable
|
||||||
|
@ -1 +1 @@
|
|||||||
include *requirements.txt LICENSE
|
include *requirements.txt LICENSE chainlib/eth/data/config/*
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# third-party imports
|
# external imports
|
||||||
import sha3
|
import sha3
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
@ -11,3 +11,34 @@ from crypto_dev_signer.encoding import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
to_checksum = to_checksum_address
|
to_checksum = to_checksum_address
|
||||||
|
|
||||||
|
|
||||||
|
class AddressChecksum:
|
||||||
|
"""Address checksummer implementation.
|
||||||
|
|
||||||
|
Primarily for use with chainlib.cli.wallet.Wallet
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def valid(cls, v):
|
||||||
|
"""Check if address is a valid checksum address
|
||||||
|
|
||||||
|
:param v: Address value, in hex
|
||||||
|
:type v: str
|
||||||
|
:rtype: bool
|
||||||
|
:returns: True if valid checksum
|
||||||
|
"""
|
||||||
|
return is_checksum_address(v)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sum(cls, v):
|
||||||
|
"""Create checksum from address
|
||||||
|
|
||||||
|
:param v: Address value, in hex
|
||||||
|
:type v: str
|
||||||
|
:raises ValueError: Invalid address
|
||||||
|
:rtype: str
|
||||||
|
:returns: Checksum address
|
||||||
|
"""
|
||||||
|
return to_checksum_address(v)
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
# third-party imports
|
# external imports
|
||||||
from chainlib.jsonrpc import JSONRPCRequest
|
from chainlib.jsonrpc import JSONRPCRequest
|
||||||
from chainlib.eth.tx import Tx
|
from chainlib.block import Block as BaseBlock
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
strip_0x,
|
strip_0x,
|
||||||
even,
|
even,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.eth.tx import Tx
|
||||||
|
|
||||||
|
|
||||||
def block_latest(id_generator=None):
|
def block_latest(id_generator=None):
|
||||||
|
"""Implements chainlib.interface.ChainInterface method
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_blockNumber'
|
o['method'] = 'eth_blockNumber'
|
||||||
@ -16,6 +21,8 @@ def block_latest(id_generator=None):
|
|||||||
|
|
||||||
|
|
||||||
def block_by_hash(hsh, include_tx=True, id_generator=None):
|
def block_by_hash(hsh, include_tx=True, id_generator=None):
|
||||||
|
"""Implements chainlib.interface.ChainInterface method
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_getBlockByHash'
|
o['method'] = 'eth_getBlockByHash'
|
||||||
@ -25,6 +32,8 @@ def block_by_hash(hsh, include_tx=True, id_generator=None):
|
|||||||
|
|
||||||
|
|
||||||
def block_by_number(n, include_tx=True, id_generator=None):
|
def block_by_number(n, include_tx=True, id_generator=None):
|
||||||
|
"""Implements chainlib.interface.ChainInterface method
|
||||||
|
"""
|
||||||
nhx = add_0x(even(hex(n)[2:]))
|
nhx = add_0x(even(hex(n)[2:]))
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
@ -35,6 +44,15 @@ def block_by_number(n, include_tx=True, id_generator=None):
|
|||||||
|
|
||||||
|
|
||||||
def transaction_count(block_hash, id_generator=None):
|
def transaction_count(block_hash, id_generator=None):
|
||||||
|
"""Generate json-rpc query to get transaction count of block
|
||||||
|
|
||||||
|
:param block_hash: Block hash, in hex
|
||||||
|
:type block_hash: str
|
||||||
|
:param id_generator: JSONRPC id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_getBlockTransactionCountByHash'
|
o['method'] = 'eth_getBlockTransactionCountByHash'
|
||||||
@ -42,7 +60,13 @@ def transaction_count(block_hash, id_generator=None):
|
|||||||
return j.finalize(o)
|
return j.finalize(o)
|
||||||
|
|
||||||
|
|
||||||
class Block:
|
class Block(BaseBlock):
|
||||||
|
"""Encapsulates an Ethereum block
|
||||||
|
|
||||||
|
:param src: Block representation data
|
||||||
|
:type src: dict
|
||||||
|
:todo: Add hex to number parse to normalize
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, src):
|
def __init__(self, src):
|
||||||
self.hash = src['hash']
|
self.hash = src['hash']
|
||||||
@ -58,22 +82,21 @@ class Block:
|
|||||||
self.timestamp = int(src['timestamp'])
|
self.timestamp = int(src['timestamp'])
|
||||||
|
|
||||||
|
|
||||||
def src(self):
|
def get_tx(self, tx_hash):
|
||||||
return self.block_src
|
i = 0
|
||||||
|
idx = -1
|
||||||
|
tx_hash = add_0x(tx_hash)
|
||||||
|
for tx in self.txs:
|
||||||
|
tx_hash_block = None
|
||||||
|
try:
|
||||||
|
tx_hash_block = add_0x(tx['hash'])
|
||||||
|
except TypeError:
|
||||||
|
tx_hash_block = add_0x(tx)
|
||||||
|
if tx_hash_block == tx_hash:
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
if idx == -1:
|
||||||
|
raise AttributeError('tx {} not found in block {}'.format(tx_hash, self.hash))
|
||||||
|
return idx
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
@ -2,6 +2,13 @@ from chainlib.jsonrpc import JSONRPCRequest
|
|||||||
|
|
||||||
|
|
||||||
def network_id(id_generator=None):
|
def network_id(id_generator=None):
|
||||||
|
"""Generate json-rpc query to retrieve network id from node
|
||||||
|
|
||||||
|
:param id_generator: JSON-RPC id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator=id_generator)
|
j = JSONRPCRequest(id_generator=id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'net_version'
|
o['method'] = 'net_version'
|
||||||
|
44
chainlib/eth/cli.py
Normal file
44
chainlib/eth/cli.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
from chainlib.cli import (
|
||||||
|
ArgumentParser,
|
||||||
|
argflag_std_read,
|
||||||
|
argflag_std_write,
|
||||||
|
argflag_std_base,
|
||||||
|
Config as BaseConfig,
|
||||||
|
Wallet as BaseWallet,
|
||||||
|
Rpc as BaseRpc,
|
||||||
|
Flag,
|
||||||
|
)
|
||||||
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.eth.address import AddressChecksum
|
||||||
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
|
|
||||||
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
class Wallet(BaseWallet):
|
||||||
|
"""Convenience constructor to set Ethereum defaults for chainlib cli Wallet object
|
||||||
|
|
||||||
|
:param checksummer: Address checksummer object
|
||||||
|
:type checksummer: Implementation of chainlib.eth.address.AddressChecksum
|
||||||
|
"""
|
||||||
|
def __init__(self, checksummer=AddressChecksum):
|
||||||
|
super(Wallet, self).__init__(EIP155Signer, checksummer=checksummer)
|
||||||
|
|
||||||
|
|
||||||
|
class Rpc(BaseRpc):
|
||||||
|
"""Convenience constructor to set Ethereum defaults for chainlib cli Rpc object
|
||||||
|
"""
|
||||||
|
def __init__(self, wallet=None):
|
||||||
|
super(Rpc, self).__init__(EthHTTPConnection, wallet=wallet)
|
||||||
|
|
||||||
|
|
||||||
|
class Config(BaseConfig):
|
||||||
|
"""Convenience constructor to set Ethereum defaults for the chainlib cli config object
|
||||||
|
"""
|
||||||
|
default_base_config_dir = os.path.join(script_dir, 'data', 'config')
|
||||||
|
default_fee_limit = 21000
|
@ -43,8 +43,33 @@ logg = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class EthHTTPConnection(JSONRPCHTTPConnection):
|
class EthHTTPConnection(JSONRPCHTTPConnection):
|
||||||
|
"""HTTP Interface for Ethereum node JSON-RPC
|
||||||
|
|
||||||
|
:todo: support https
|
||||||
|
"""
|
||||||
|
|
||||||
def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser, id_generator=None):
|
def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser, id_generator=None):
|
||||||
|
"""Poll for confirmation of a transaction on network.
|
||||||
|
|
||||||
|
Returns the result of the transaction if it was successfully executed on the network, and raises RevertEthException if execution fails.
|
||||||
|
|
||||||
|
This is a blocking call.
|
||||||
|
|
||||||
|
:param tx_hash_hex: Transaction hash to wait for, hex
|
||||||
|
:type tx_hash_hex: str
|
||||||
|
:param delay: Polling interval
|
||||||
|
:type delay: float
|
||||||
|
:param timeout: Max time to wait for confirmation (0 = no timeout)
|
||||||
|
:type timeout: float
|
||||||
|
:param error_parser: json-rpc response error parser
|
||||||
|
:type error_parser: chainlib.jsonrpc.ErrorParser
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.jsonrpc.JSONRPCIdGenerator
|
||||||
|
:raises TimeoutError: Timeout reached
|
||||||
|
:raises chainlib.eth.error.RevertEthException: Transaction confirmed but failed
|
||||||
|
:rtype: dict
|
||||||
|
:returns: Transaction receipt
|
||||||
|
"""
|
||||||
t = datetime.datetime.utcnow()
|
t = datetime.datetime.utcnow()
|
||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
@ -59,13 +84,13 @@ class EthHTTPConnection(JSONRPCHTTPConnection):
|
|||||||
)
|
)
|
||||||
req.add_header('Content-Type', 'application/json')
|
req.add_header('Content-Type', 'application/json')
|
||||||
data = json.dumps(o)
|
data = json.dumps(o)
|
||||||
logg.debug('(HTTP) poll receipt attempt {} {}'.format(i, data))
|
logg.debug('({}) poll receipt attempt {} {}'.format(str(self), i, data))
|
||||||
res = urlopen(req, data=data.encode('utf-8'))
|
res = urlopen(req, data=data.encode('utf-8'))
|
||||||
r = json.load(res)
|
r = json.load(res)
|
||||||
|
|
||||||
e = jsonrpc_result(r, error_parser)
|
e = jsonrpc_result(r, error_parser)
|
||||||
if e != None:
|
if e != None:
|
||||||
logg.debug('(HTTP) poll receipt completed {}'.format(r))
|
logg.debug('({}) poll receipt completed {}'.format(str(self), r))
|
||||||
logg.debug('e {}'.format(strip_0x(e['status'])))
|
logg.debug('e {}'.format(strip_0x(e['status'])))
|
||||||
if strip_0x(e['status']) == '00':
|
if strip_0x(e['status']) == '00':
|
||||||
raise RevertEthException(tx_hash_hex)
|
raise RevertEthException(tx_hash_hex)
|
||||||
@ -80,7 +105,17 @@ class EthHTTPConnection(JSONRPCHTTPConnection):
|
|||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'ETH HTTP JSONRPC'
|
||||||
|
|
||||||
|
|
||||||
def check_rpc(self, id_generator=None):
|
def check_rpc(self, id_generator=None):
|
||||||
|
"""Execute Ethereum specific json-rpc query to (superficially) check whether node is sane.
|
||||||
|
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.jsonrpc.JSONRPCIdGenerator
|
||||||
|
:raises Exception: Any exception indicates an invalid node
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
req = j.template()
|
req = j.template()
|
||||||
req['method'] = 'net_version'
|
req['method'] = 'net_version'
|
||||||
@ -89,12 +124,29 @@ class EthHTTPConnection(JSONRPCHTTPConnection):
|
|||||||
|
|
||||||
|
|
||||||
class EthUnixConnection(JSONRPCUnixConnection):
|
class EthUnixConnection(JSONRPCUnixConnection):
|
||||||
|
"""Unix socket implementation of Ethereum JSON-RPC
|
||||||
|
"""
|
||||||
|
|
||||||
def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser):
|
def wait(self, tx_hash_hex, delay=0.5, timeout=0.0, error_parser=error_parser):
|
||||||
|
"""See EthHTTPConnection. Not yet implemented for unix socket.
|
||||||
|
"""
|
||||||
raise NotImplementedError('Not yet implemented for unix socket')
|
raise NotImplementedError('Not yet implemented for unix socket')
|
||||||
|
|
||||||
|
|
||||||
def sign_transaction_to_rlp(chain_spec, doer, tx):
|
def sign_transaction_to_rlp(chain_spec, doer, tx):
|
||||||
|
"""Generate a signature query and execute it against a json-rpc signer backend.
|
||||||
|
|
||||||
|
Uses the `eth_signTransaction` json-rpc method, generated by chainlib.eth.sign.sign_transaction.
|
||||||
|
|
||||||
|
:param chain_spec: Chain spec to use for EIP155 signature.
|
||||||
|
:type chain_spec: chainlib.chain.ChainSpec
|
||||||
|
:param doer: Signer rpc backend
|
||||||
|
:type doer: chainlib.connection.RPCConnection implementing json-rpc
|
||||||
|
:param tx: Transaction object
|
||||||
|
:type tx: dict
|
||||||
|
:rtype: bytes
|
||||||
|
:returns: Ethereum signature
|
||||||
|
"""
|
||||||
txs = tx.serialize()
|
txs = tx.serialize()
|
||||||
logg.debug('serializing {}'.format(txs))
|
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
|
# TODO: because some rpc servers may fail when chainId is included, we are forced to spend cpu here on this
|
||||||
@ -110,27 +162,66 @@ def sign_transaction_to_rlp(chain_spec, doer, tx):
|
|||||||
|
|
||||||
|
|
||||||
def sign_message(doer, msg):
|
def sign_message(doer, msg):
|
||||||
|
"""Sign arbitrary data using the Ethereum message signer protocol.
|
||||||
|
|
||||||
|
:param doer: Signer rpc backend
|
||||||
|
:type doer: chainlib.connection.RPCConnection with json-rpc
|
||||||
|
:param msg: Message to sign, in hex
|
||||||
|
:type msg: str
|
||||||
|
:rtype: str
|
||||||
|
:returns: Signature, hex
|
||||||
|
"""
|
||||||
o = sign_message(msg)
|
o = sign_message(msg)
|
||||||
return doer(o)
|
return doer(o)
|
||||||
|
|
||||||
|
|
||||||
class EthUnixSignerConnection(EthUnixConnection):
|
class EthUnixSignerConnection(EthUnixConnection):
|
||||||
|
"""Connects rpc signer methods to Unix socket connection interface
|
||||||
|
"""
|
||||||
|
|
||||||
def sign_transaction_to_rlp(self, tx):
|
def sign_transaction_to_rlp(self, tx):
|
||||||
|
"""Sign transaction using unix socket rpc.
|
||||||
|
|
||||||
|
:param tx: Transaction object
|
||||||
|
:type tx: dict
|
||||||
|
:rtype: See chainlin.eth.connection.sign_transaction_to_rlp
|
||||||
|
:returns: See chainlin.eth.connection.sign_transaction_to_rlp
|
||||||
|
"""
|
||||||
return sign_transaction_to_rlp(self.chain_spec, self.do, tx)
|
return sign_transaction_to_rlp(self.chain_spec, self.do, tx)
|
||||||
|
|
||||||
|
|
||||||
def sign_message(self, tx):
|
def sign_message(self, msg):
|
||||||
return sign_message(self.do, tx)
|
"""Sign message using unix socket json-rpc.
|
||||||
|
|
||||||
|
:param msg: Message to sign, in hex
|
||||||
|
:type msg: str
|
||||||
|
:rtype: See chainlin.eth.connection.sign_message
|
||||||
|
:returns: See chainlin.eth.connection.sign_message
|
||||||
|
"""
|
||||||
|
return sign_message(self.do, msg)
|
||||||
|
|
||||||
|
|
||||||
class EthHTTPSignerConnection(EthHTTPConnection):
|
class EthHTTPSignerConnection(EthHTTPConnection):
|
||||||
|
|
||||||
def sign_transaction_to_rlp(self, tx):
|
def sign_transaction_to_rlp(self, tx):
|
||||||
|
"""Sign transaction using http json-rpc.
|
||||||
|
|
||||||
|
:param tx: Transaction object
|
||||||
|
:type tx: dict
|
||||||
|
:rtype: See chainlin.eth.connection.sign_transaction_to_rlp
|
||||||
|
:returns: See chainlin.eth.connection.sign_transaction_to_rlp
|
||||||
|
"""
|
||||||
return sign_transaction_to_rlp(self.chain_spec, self.do, tx)
|
return sign_transaction_to_rlp(self.chain_spec, self.do, tx)
|
||||||
|
|
||||||
|
|
||||||
def sign_message(self, tx):
|
def sign_message(self, tx):
|
||||||
|
"""Sign message using http json-rpc.
|
||||||
|
|
||||||
|
:param msg: Message to sign, in hex
|
||||||
|
:type msg: str
|
||||||
|
:rtype: See chainlin.eth.connection.sign_message
|
||||||
|
:returns: See chainlin.eth.connection.sign_message
|
||||||
|
"""
|
||||||
return sign_message(self.do, tx)
|
return sign_message(self.do, tx)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,4 +2,5 @@ ZERO_ADDRESS = '0x{:040x}'.format(0)
|
|||||||
ZERO_CONTENT = '0x{:064x}'.format(0)
|
ZERO_CONTENT = '0x{:064x}'.format(0)
|
||||||
MINIMUM_FEE_UNITS = 21000
|
MINIMUM_FEE_UNITS = 21000
|
||||||
MINIMUM_FEE_PRICE = 1000000000
|
MINIMUM_FEE_PRICE = 1000000000
|
||||||
|
DEFAULT_FEE_LIMIT = 8000000
|
||||||
MAX_UINT = int('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)
|
MAX_UINT = int('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)
|
||||||
|
@ -15,14 +15,14 @@ from chainlib.block import BlockSpec
|
|||||||
from chainlib.jsonrpc import JSONRPCRequest
|
from chainlib.jsonrpc import JSONRPCRequest
|
||||||
from .address import to_checksum_address
|
from .address import to_checksum_address
|
||||||
|
|
||||||
#logg = logging.getLogger(__name__)
|
logg = logging.getLogger(__name__)
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
re_method = r'^[a-zA-Z0-9_]+$'
|
re_method = r'^[a-zA-Z0-9_]+$'
|
||||||
|
|
||||||
class ABIContractType(enum.Enum):
|
class ABIContractType(enum.Enum):
|
||||||
|
"""Data types used by ABI encoders
|
||||||
|
"""
|
||||||
BYTES32 = 'bytes32'
|
BYTES32 = 'bytes32'
|
||||||
BYTES4 = 'bytes4'
|
BYTES4 = 'bytes4'
|
||||||
UINT256 = 'uint256'
|
UINT256 = 'uint256'
|
||||||
@ -36,14 +36,16 @@ dynamic_contract_types = [
|
|||||||
|
|
||||||
|
|
||||||
class ABIContract:
|
class ABIContract:
|
||||||
|
"""Base class for Ethereum smart contract encoder
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.types = []
|
self.types = []
|
||||||
self.contents = []
|
self.contents = []
|
||||||
|
|
||||||
|
|
||||||
class ABIMethodEncoder(ABIContract):
|
class ABIMethodEncoder(ABIContract):
|
||||||
|
"""Generate ABI method signatures from method signature string.
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ABIMethodEncoder, self).__init__()
|
super(ABIMethodEncoder, self).__init__()
|
||||||
self.method_name = None
|
self.method_name = None
|
||||||
@ -51,6 +53,12 @@ class ABIMethodEncoder(ABIContract):
|
|||||||
|
|
||||||
|
|
||||||
def method(self, m):
|
def method(self, m):
|
||||||
|
"""Set method name.
|
||||||
|
|
||||||
|
:param m: Method name
|
||||||
|
:type m: str
|
||||||
|
:raises ValueError: Invalid method name
|
||||||
|
"""
|
||||||
if re.match(re_method, m) == None:
|
if re.match(re_method, m) == None:
|
||||||
raise ValueError('Invalid method {}, must match regular expression {}'.format(re_method))
|
raise ValueError('Invalid method {}, must match regular expression {}'.format(re_method))
|
||||||
self.method_name = m
|
self.method_name = m
|
||||||
@ -58,12 +66,26 @@ class ABIMethodEncoder(ABIContract):
|
|||||||
|
|
||||||
|
|
||||||
def get_method(self):
|
def get_method(self):
|
||||||
|
"""Return currently set method signature string.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
:returns: Method signature
|
||||||
|
"""
|
||||||
if self.method_name == None:
|
if self.method_name == None:
|
||||||
return ''
|
return ''
|
||||||
return '{}({})'.format(self.method_name, ','.join(self.method_contents))
|
return '{}({})'.format(self.method_name, ','.join(self.method_contents))
|
||||||
|
|
||||||
|
|
||||||
def typ(self, v):
|
def typ(self, v):
|
||||||
|
"""Add argument type to argument vector.
|
||||||
|
|
||||||
|
Method name must be set before this is called.
|
||||||
|
|
||||||
|
:param v: Type to add
|
||||||
|
:type v: chainlib.eth.contract.ABIContractType
|
||||||
|
:raises AttributeError: Type set before method name
|
||||||
|
:raises TypeError: Invalid type
|
||||||
|
"""
|
||||||
if self.method_name == None:
|
if self.method_name == None:
|
||||||
raise AttributeError('method name must be set before adding types')
|
raise AttributeError('method name must be set before adding types')
|
||||||
if not isinstance(v, ABIContractType):
|
if not isinstance(v, ABIContractType):
|
||||||
@ -78,9 +100,16 @@ class ABIMethodEncoder(ABIContract):
|
|||||||
|
|
||||||
|
|
||||||
class ABIContractDecoder(ABIContract):
|
class ABIContractDecoder(ABIContract):
|
||||||
|
"""Decode serialized ABI contract input data to corresponding python primitives.
|
||||||
|
"""
|
||||||
|
|
||||||
def typ(self, v):
|
def typ(self, v):
|
||||||
|
"""Add type to argument array to parse input against.
|
||||||
|
|
||||||
|
:param v: Type
|
||||||
|
:type v: chainlib.eth.contract.ABIContractType
|
||||||
|
:raises TypeError: Invalid type
|
||||||
|
"""
|
||||||
if not isinstance(v, ABIContractType):
|
if not isinstance(v, ABIContractType):
|
||||||
raise TypeError('method type not valid; expected {}, got {}'.format(type(ABIContractType).__name__, type(v).__name__))
|
raise TypeError('method type not valid; expected {}, got {}'.format(type(ABIContractType).__name__, type(v).__name__))
|
||||||
self.types.append(v.value)
|
self.types.append(v.value)
|
||||||
@ -88,32 +117,74 @@ class ABIContractDecoder(ABIContract):
|
|||||||
|
|
||||||
|
|
||||||
def val(self, v):
|
def val(self, v):
|
||||||
|
"""Add value to value array.
|
||||||
|
|
||||||
|
:param v: Value, in hex
|
||||||
|
:type v: str
|
||||||
|
"""
|
||||||
self.contents.append(v)
|
self.contents.append(v)
|
||||||
logg.debug('content is now {}'.format(self.contents))
|
logg.debug('content is now {}'.format(self.contents))
|
||||||
|
|
||||||
|
|
||||||
def uint256(self, v):
|
def uint256(self, v):
|
||||||
|
"""Parse value as uint256.
|
||||||
|
|
||||||
|
:param v: Value, in hex
|
||||||
|
:type v: str
|
||||||
|
:rtype: int
|
||||||
|
:returns: Int value
|
||||||
|
"""
|
||||||
return int(v, 16)
|
return int(v, 16)
|
||||||
|
|
||||||
|
|
||||||
def bytes32(self, v):
|
def bytes32(self, v):
|
||||||
|
"""Parse value as bytes32.
|
||||||
|
|
||||||
|
:param v: Value, in hex
|
||||||
|
:type v: str
|
||||||
|
:rtype: str
|
||||||
|
:returns: Value, in hex
|
||||||
|
"""
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
def bool(self, v):
|
def bool(self, v):
|
||||||
|
"""Parse value as bool.
|
||||||
|
|
||||||
|
:param v: Value, in hex
|
||||||
|
:type v: str
|
||||||
|
:rtype: bool
|
||||||
|
:returns: Value
|
||||||
|
"""
|
||||||
return bool(self.uint256(v))
|
return bool(self.uint256(v))
|
||||||
|
|
||||||
|
|
||||||
def boolean(self, v):
|
def boolean(self, v):
|
||||||
|
"""Alias of chainlib.eth.contract.ABIContractDecoder.bool
|
||||||
|
"""
|
||||||
return bool(self.uint256(v))
|
return bool(self.uint256(v))
|
||||||
|
|
||||||
|
|
||||||
def address(self, v):
|
def address(self, v):
|
||||||
|
"""Parse value as address.
|
||||||
|
|
||||||
|
:param v: Value, in hex
|
||||||
|
:type v: str
|
||||||
|
:rtype: str
|
||||||
|
:returns: Value. in hex
|
||||||
|
"""
|
||||||
a = strip_0x(v)[64-40:]
|
a = strip_0x(v)[64-40:]
|
||||||
return to_checksum_address(a)
|
return to_checksum_address(a)
|
||||||
|
|
||||||
|
|
||||||
def string(self, v):
|
def string(self, v):
|
||||||
|
"""Parse value as string.
|
||||||
|
|
||||||
|
:param v: Value, in hex
|
||||||
|
:type v: str
|
||||||
|
:rtype: str
|
||||||
|
:returns: Value
|
||||||
|
"""
|
||||||
s = strip_0x(v)
|
s = strip_0x(v)
|
||||||
b = bytes.fromhex(s)
|
b = bytes.fromhex(s)
|
||||||
cursor = 0
|
cursor = 0
|
||||||
@ -131,18 +202,23 @@ class ABIContractDecoder(ABIContract):
|
|||||||
|
|
||||||
|
|
||||||
def decode(self):
|
def decode(self):
|
||||||
|
"""Apply decoder on value array using argument type array.
|
||||||
|
|
||||||
|
:rtype: list
|
||||||
|
:returns: List of decoded values
|
||||||
|
"""
|
||||||
r = []
|
r = []
|
||||||
logg.debug('contents {}'.format(self.contents))
|
logg.debug('contents {}'.format(self.contents))
|
||||||
for i in range(len(self.types)):
|
for i in range(len(self.types)):
|
||||||
m = getattr(self, self.types[i])
|
m = getattr(self, self.types[i])
|
||||||
s = self.contents[i]
|
s = self.contents[i]
|
||||||
logg.debug('{} {} {} {} {}'.format(i, m, self.types[i], self.contents[i], s))
|
|
||||||
#r.append(m(s.hex()))
|
|
||||||
r.append(m(s))
|
r.append(m(s))
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
|
"""Alias of chainlib.eth.contract.ABIContractDecoder.decode
|
||||||
|
"""
|
||||||
return self.decode()
|
return self.decode()
|
||||||
|
|
||||||
|
|
||||||
@ -151,7 +227,10 @@ class ABIContractDecoder(ABIContract):
|
|||||||
|
|
||||||
|
|
||||||
class ABIContractLogDecoder(ABIMethodEncoder, ABIContractDecoder):
|
class ABIContractLogDecoder(ABIMethodEncoder, ABIContractDecoder):
|
||||||
|
"""Decoder utils for log entries of an Ethereum network transaction receipt.
|
||||||
|
|
||||||
|
Uses chainlib.eth.contract.ABIContractDecoder.decode to render output from template.
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ABIContractLogDecoder, self).__init__()
|
super(ABIContractLogDecoder, self).__init__()
|
||||||
self.method_name = None
|
self.method_name = None
|
||||||
@ -159,20 +238,45 @@ class ABIContractLogDecoder(ABIMethodEncoder, ABIContractDecoder):
|
|||||||
|
|
||||||
|
|
||||||
def topic(self, event):
|
def topic(self, event):
|
||||||
|
"""Set topic to match.
|
||||||
|
|
||||||
|
:param event: Topic name
|
||||||
|
:type event: str
|
||||||
|
"""
|
||||||
self.method(event)
|
self.method(event)
|
||||||
|
|
||||||
|
|
||||||
def get_method_signature(self):
|
def get_method_signature(self):
|
||||||
|
"""Generate topic signature from set topic.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
:returns: Topic signature, in hex
|
||||||
|
"""
|
||||||
s = self.get_method()
|
s = self.get_method()
|
||||||
return keccak256_string_to_hex(s)
|
return keccak256_string_to_hex(s)
|
||||||
|
|
||||||
|
|
||||||
def typ(self, v):
|
def typ(self, v):
|
||||||
|
"""Add type to event argument array.
|
||||||
|
|
||||||
|
:param v: Type
|
||||||
|
:type v: chainlib.eth.contract.ABIContractType
|
||||||
|
"""
|
||||||
super(ABIContractLogDecoder, self).typ(v)
|
super(ABIContractLogDecoder, self).typ(v)
|
||||||
self.types.append(v.value)
|
self.types.append(v.value)
|
||||||
|
|
||||||
|
|
||||||
def apply(self, topics, data):
|
def apply(self, topics, data):
|
||||||
|
"""Set log entry data to parse.
|
||||||
|
|
||||||
|
After set, self.decode can be used to render the output.
|
||||||
|
|
||||||
|
:param topics: The topics array of the receipt, list of hex
|
||||||
|
:type topics: list
|
||||||
|
:param data: Non-indexed data, in hex
|
||||||
|
:type data: str
|
||||||
|
:raises ValueError: Topic of input does not match topic set in parser
|
||||||
|
"""
|
||||||
t = self.get_method_signature()
|
t = self.get_method_signature()
|
||||||
if topics[0] != t:
|
if topics[0] != t:
|
||||||
raise ValueError('topic mismatch')
|
raise ValueError('topic mismatch')
|
||||||
@ -189,6 +293,11 @@ class ABIContractEncoder(ABIMethodEncoder):
|
|||||||
|
|
||||||
|
|
||||||
def uint256(self, v):
|
def uint256(self, v):
|
||||||
|
"""Encode value to uint256 and add to input value vector.
|
||||||
|
|
||||||
|
:param v: Integer value
|
||||||
|
:type v: int
|
||||||
|
"""
|
||||||
v = int(v)
|
v = int(v)
|
||||||
b = v.to_bytes(32, 'big')
|
b = v.to_bytes(32, 'big')
|
||||||
self.contents.append(b.hex())
|
self.contents.append(b.hex())
|
||||||
@ -197,28 +306,52 @@ class ABIContractEncoder(ABIMethodEncoder):
|
|||||||
|
|
||||||
|
|
||||||
def bool(self, v):
|
def bool(self, v):
|
||||||
|
"""Alias of chainlib.eth.contract.ABIContractEncoder.boolean.
|
||||||
|
"""
|
||||||
return self.boolean(v)
|
return self.boolean(v)
|
||||||
|
|
||||||
|
|
||||||
def boolean(self, v):
|
def boolean(self, v):
|
||||||
|
"""Encode value to boolean and add to input value vector.
|
||||||
|
|
||||||
|
:param v: Trueish or falsish value
|
||||||
|
:type v: any
|
||||||
|
:rtype: See chainlib.eth.contract.ABIContractEncoder.uint256
|
||||||
|
:returns: See chainlib.eth.contract.ABIContractEncoder.uint256
|
||||||
|
"""
|
||||||
if bool(v):
|
if bool(v):
|
||||||
return self.uint256(1)
|
return self.uint256(1)
|
||||||
return self.uint256(0)
|
return self.uint256(0)
|
||||||
|
|
||||||
|
|
||||||
def address(self, v):
|
def address(self, v):
|
||||||
|
"""Encode value to address and add to input value vector.
|
||||||
|
|
||||||
|
:param v: Ethereum address, in hex
|
||||||
|
:type v: str
|
||||||
|
"""
|
||||||
self.bytes_fixed(32, v, 20)
|
self.bytes_fixed(32, v, 20)
|
||||||
self.types.append(ABIContractType.ADDRESS)
|
self.types.append(ABIContractType.ADDRESS)
|
||||||
self.__log_latest(v)
|
self.__log_latest(v)
|
||||||
|
|
||||||
|
|
||||||
def bytes32(self, v):
|
def bytes32(self, v):
|
||||||
|
"""Encode value to bytes32 and add to input value vector.
|
||||||
|
|
||||||
|
:param v: Bytes, in hex
|
||||||
|
:type v: str
|
||||||
|
"""
|
||||||
self.bytes_fixed(32, v)
|
self.bytes_fixed(32, v)
|
||||||
self.types.append(ABIContractType.BYTES32)
|
self.types.append(ABIContractType.BYTES32)
|
||||||
self.__log_latest(v)
|
self.__log_latest(v)
|
||||||
|
|
||||||
|
|
||||||
def bytes4(self, v):
|
def bytes4(self, v):
|
||||||
|
"""Encode value to bytes4 and add to input value vector.
|
||||||
|
|
||||||
|
:param v: Bytes, in hex
|
||||||
|
:type v: str
|
||||||
|
"""
|
||||||
self.bytes_fixed(4, v)
|
self.bytes_fixed(4, v)
|
||||||
self.types.append(ABIContractType.BYTES4)
|
self.types.append(ABIContractType.BYTES4)
|
||||||
self.__log_latest(v)
|
self.__log_latest(v)
|
||||||
@ -226,6 +359,11 @@ class ABIContractEncoder(ABIMethodEncoder):
|
|||||||
|
|
||||||
|
|
||||||
def string(self, v):
|
def string(self, v):
|
||||||
|
"""Encode value to string and add to input value vector.
|
||||||
|
|
||||||
|
:param v: String input
|
||||||
|
:type v: str
|
||||||
|
"""
|
||||||
b = v.encode('utf-8')
|
b = v.encode('utf-8')
|
||||||
l = len(b)
|
l = len(b)
|
||||||
contents = l.to_bytes(32, 'big')
|
contents = l.to_bytes(32, 'big')
|
||||||
@ -239,6 +377,16 @@ class ABIContractEncoder(ABIMethodEncoder):
|
|||||||
|
|
||||||
|
|
||||||
def bytes_fixed(self, mx, v, exact=0):
|
def bytes_fixed(self, mx, v, exact=0):
|
||||||
|
"""Add arbirary length byte data to value vector.
|
||||||
|
|
||||||
|
:param mx: Max length of input data.
|
||||||
|
:type mx: int
|
||||||
|
:param v: Byte input, hex or bytes
|
||||||
|
:type v: str | bytes
|
||||||
|
:param exact: Fail parsing if input does not translate to given byte length.
|
||||||
|
:type exact: int
|
||||||
|
:raises ValueError: Input length or input format mismatch.
|
||||||
|
"""
|
||||||
typ = type(v).__name__
|
typ = type(v).__name__
|
||||||
if typ == 'str':
|
if typ == 'str':
|
||||||
v = strip_0x(v)
|
v = strip_0x(v)
|
||||||
@ -260,8 +408,9 @@ class ABIContractEncoder(ABIMethodEncoder):
|
|||||||
self.contents.append(v.ljust(64, '0'))
|
self.contents.append(v.ljust(64, '0'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_method_signature(self):
|
def get_method_signature(self):
|
||||||
|
"""Return abi encoded signature of currently set method.
|
||||||
|
"""
|
||||||
s = self.get_method()
|
s = self.get_method()
|
||||||
if s == '':
|
if s == '':
|
||||||
return s
|
return s
|
||||||
@ -269,6 +418,11 @@ class ABIContractEncoder(ABIMethodEncoder):
|
|||||||
|
|
||||||
|
|
||||||
def get_contents(self):
|
def get_contents(self):
|
||||||
|
"""Encode value array.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
:returns: ABI encoded values, in hex
|
||||||
|
"""
|
||||||
direct_contents = ''
|
direct_contents = ''
|
||||||
pointer_contents = ''
|
pointer_contents = ''
|
||||||
l = len(self.types)
|
l = len(self.types)
|
||||||
@ -291,10 +445,19 @@ class ABIContractEncoder(ABIMethodEncoder):
|
|||||||
|
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
|
"""Alias of chainlib.eth.contract.ABIContractEncoder.encode
|
||||||
|
"""
|
||||||
return self.encode()
|
return self.encode()
|
||||||
|
|
||||||
|
|
||||||
def encode(self):
|
def encode(self):
|
||||||
|
"""Encode method and value array.
|
||||||
|
|
||||||
|
The data generated by this method is the literal data used as input to contract calls or transactions.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
:returns: ABI encoded contract input data, in hex
|
||||||
|
"""
|
||||||
m = self.get_method_signature()
|
m = self.get_method_signature()
|
||||||
c = self.get_contents()
|
c = self.get_contents()
|
||||||
return m + c
|
return m + c
|
||||||
@ -306,6 +469,13 @@ class ABIContractEncoder(ABIMethodEncoder):
|
|||||||
|
|
||||||
|
|
||||||
def abi_decode_single(typ, v):
|
def abi_decode_single(typ, v):
|
||||||
|
"""Convenience function to decode a single ABI encoded value against a given type.
|
||||||
|
|
||||||
|
:param typ: Type to parse value as
|
||||||
|
:type typ: chainlib.eth.contract.ABIContractEncoder
|
||||||
|
:param v: Value to parse, in hex
|
||||||
|
:type v: str
|
||||||
|
"""
|
||||||
d = ABIContractDecoder()
|
d = ABIContractDecoder()
|
||||||
d.typ(typ)
|
d.typ(typ)
|
||||||
d.val(v)
|
d.val(v)
|
||||||
@ -314,6 +484,17 @@ def abi_decode_single(typ, v):
|
|||||||
|
|
||||||
|
|
||||||
def code(address, block_spec=BlockSpec.LATEST, id_generator=None):
|
def code(address, block_spec=BlockSpec.LATEST, id_generator=None):
|
||||||
|
"""Generate json-rpc query to retrieve code stored at an Ethereum address.
|
||||||
|
|
||||||
|
:param address: Address to use for query, in hex
|
||||||
|
:type address: str
|
||||||
|
:param block_spec: Block height spec
|
||||||
|
:type block_spec: chainlib.block.BlockSpec
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.jsonrpc.JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
block_height = None
|
block_height = None
|
||||||
if block_spec == BlockSpec.LATEST:
|
if block_spec == BlockSpec.LATEST:
|
||||||
block_height = 'latest'
|
block_height = 'latest'
|
||||||
|
12
chainlib/eth/data/config/config.ini
Normal file
12
chainlib/eth/data/config/config.ini
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[rpc]
|
||||||
|
http_provider = http://localhost:8545
|
||||||
|
http_authentication =
|
||||||
|
http_username =
|
||||||
|
http_password =
|
||||||
|
|
||||||
|
[chain]
|
||||||
|
spec = evm:ethereum:1
|
||||||
|
|
||||||
|
[wallet]
|
||||||
|
key_file =
|
||||||
|
passphrase =
|
@ -1,23 +1,33 @@
|
|||||||
# local imports
|
# local imports
|
||||||
from chainlib.error import ExecutionError
|
from chainlib.error import ExecutionError
|
||||||
|
|
||||||
|
|
||||||
class EthException(Exception):
|
class EthException(Exception):
|
||||||
|
"""Base class for all Ethereum related errors.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RevertEthException(EthException, ExecutionError):
|
class RevertEthException(EthException, ExecutionError):
|
||||||
|
"""Raised when an rpc call or transaction reverts.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NotFoundEthException(EthException):
|
class NotFoundEthException(EthException):
|
||||||
|
"""Raised when rpc query is made against an identifier that is not known by the node.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RequestMismatchException(EthException):
|
class RequestMismatchException(EthException):
|
||||||
|
"""Raised when a request data parser is given unexpected input data.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DefaultErrorParser:
|
class DefaultErrorParser:
|
||||||
|
"""Generate eth specific exception for the default json-rpc query error parser.
|
||||||
|
"""
|
||||||
def translate(self, error):
|
def translate(self, error):
|
||||||
return EthException('default parser code {}'.format(error))
|
return EthException('default parser code {}'.format(error))
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# standard imports
|
# standard imports
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# third-party imports
|
# external imports
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
strip_0x,
|
strip_0x,
|
||||||
@ -16,6 +16,8 @@ from chainlib.eth.tx import (
|
|||||||
TxFormat,
|
TxFormat,
|
||||||
raw,
|
raw,
|
||||||
)
|
)
|
||||||
|
from chainlib.eth.jsonrpc import to_blockheight_param
|
||||||
|
from chainlib.block import BlockSpec
|
||||||
from chainlib.eth.constant import (
|
from chainlib.eth.constant import (
|
||||||
MINIMUM_FEE_UNITS,
|
MINIMUM_FEE_UNITS,
|
||||||
)
|
)
|
||||||
@ -24,22 +26,48 @@ logg = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def price(id_generator=None):
|
def price(id_generator=None):
|
||||||
|
"""Generate json-rpc query to retrieve current network gas price guess from node.
|
||||||
|
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.connection.JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_gasPrice'
|
o['method'] = 'eth_gasPrice'
|
||||||
return j.finalize(o)
|
return j.finalize(o)
|
||||||
|
|
||||||
|
|
||||||
def balance(address, id_generator=None):
|
def balance(address, id_generator=None, height=BlockSpec.LATEST):
|
||||||
|
"""Generate json-rpc query to retrieve gas balance of address.
|
||||||
|
|
||||||
|
:param address: Address to query balance for, in hex
|
||||||
|
:type address: str
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.connection.JSONRPCIdGenerator
|
||||||
|
:param height: Block height specifier
|
||||||
|
:type height: chainlib.block.BlockSpec
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_getBalance'
|
o['method'] = 'eth_getBalance'
|
||||||
o['params'].append(address)
|
o['params'].append(address)
|
||||||
o['params'].append('latest')
|
height = to_blockheight_param(height)
|
||||||
|
o['params'].append(height)
|
||||||
return j.finalize(o)
|
return j.finalize(o)
|
||||||
|
|
||||||
|
|
||||||
def parse_balance(balance):
|
def parse_balance(balance):
|
||||||
|
"""Parse result of chainlib.eth.gas.balance rpc query
|
||||||
|
|
||||||
|
:param balance: rpc result value, in hex or int
|
||||||
|
:type balance: any
|
||||||
|
:rtype: int
|
||||||
|
:returns: Balance integer value
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
r = int(balance, 10)
|
r = int(balance, 10)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -48,10 +76,29 @@ def parse_balance(balance):
|
|||||||
|
|
||||||
|
|
||||||
class Gas(TxFactory):
|
class Gas(TxFactory):
|
||||||
|
"""Gas transaction helper.
|
||||||
|
"""
|
||||||
|
|
||||||
def create(self, sender_address, recipient_address, value, tx_format=TxFormat.JSONRPC, id_generator=None):
|
def create(self, sender_address, recipient_address, value, data=None, tx_format=TxFormat.JSONRPC, id_generator=None):
|
||||||
|
"""Generate json-rpc query to execute gas transaction.
|
||||||
|
|
||||||
|
See parent class TxFactory for details on output format and general usage.
|
||||||
|
|
||||||
|
:param sender_address: Sender address, in hex
|
||||||
|
:type sender_address: str
|
||||||
|
:param recipient_address: Recipient address, in hex
|
||||||
|
:type recipient_address: str
|
||||||
|
:param value: Value of transaction, integer decimal value (wei)
|
||||||
|
:type value: int
|
||||||
|
:param data: Arbitrary input data, in hex. None means no data (vanilla gas transaction).
|
||||||
|
:type data: str
|
||||||
|
:param tx_format: Output format
|
||||||
|
:type tx_format: chainlib.eth.tx.TxFormat
|
||||||
|
"""
|
||||||
tx = self.template(sender_address, recipient_address, use_nonce=True)
|
tx = self.template(sender_address, recipient_address, use_nonce=True)
|
||||||
tx['value'] = value
|
tx['value'] = value
|
||||||
|
if data != None:
|
||||||
|
tx['data'] = data
|
||||||
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
||||||
tx_raw = self.signer.sign_transaction_to_rlp(txe)
|
tx_raw = self.signer.sign_transaction_to_rlp(txe)
|
||||||
tx_raw_hex = add_0x(tx_raw.hex())
|
tx_raw_hex = add_0x(tx_raw.hex())
|
||||||
@ -68,6 +115,17 @@ class Gas(TxFactory):
|
|||||||
|
|
||||||
|
|
||||||
class RPCGasOracle:
|
class RPCGasOracle:
|
||||||
|
"""JSON-RPC only gas parameter helper.
|
||||||
|
|
||||||
|
:param conn: RPC connection
|
||||||
|
:type conn: chainlib.connection.RPCConnection
|
||||||
|
:param code_callback: Callback method to evaluate gas usage for method and inputs.
|
||||||
|
:type code_callback: method taking abi encoded input data as single argument
|
||||||
|
:param min_price: Override gas price if less than given value
|
||||||
|
:type min_price: int
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.connection.JSONRPCIdGenerator
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, conn, code_callback=None, min_price=1, id_generator=None):
|
def __init__(self, conn, code_callback=None, min_price=1, id_generator=None):
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
@ -76,7 +134,20 @@ class RPCGasOracle:
|
|||||||
self.id_generator = id_generator
|
self.id_generator = id_generator
|
||||||
|
|
||||||
|
|
||||||
def get_gas(self, code=None):
|
def get_gas(self, code=None, input_data=None):
|
||||||
|
"""Retrieve gas parameters from node.
|
||||||
|
|
||||||
|
If code is given, the set code callback will be used to estimate gas usage.
|
||||||
|
|
||||||
|
If code is not given or code callback is not set, the chainlib.eth.constant.MINIMUM_FEE_UNITS constant will be used. This gas limit will only be enough gas for a gas transaction without input data.
|
||||||
|
|
||||||
|
:param code: EVM execution code to evaluate against, in hex
|
||||||
|
:type code: str
|
||||||
|
:param input_data: Contract input data, in hex
|
||||||
|
:type input_data: str
|
||||||
|
:rtype: tuple
|
||||||
|
:returns: Gas price in wei, and gas limit in gas units
|
||||||
|
"""
|
||||||
gas_price = 0
|
gas_price = 0
|
||||||
if self.conn != None:
|
if self.conn != None:
|
||||||
o = price(id_generator=self.id_generator)
|
o = price(id_generator=self.id_generator)
|
||||||
@ -93,13 +164,39 @@ class RPCGasOracle:
|
|||||||
|
|
||||||
|
|
||||||
class RPCPureGasOracle(RPCGasOracle):
|
class RPCPureGasOracle(RPCGasOracle):
|
||||||
|
"""Convenience constructor for rpc gas oracle without minimum price.
|
||||||
|
|
||||||
|
:param conn: RPC connection
|
||||||
|
:type conn: chainlib.connection.RPCConnection
|
||||||
|
:param code_callback: Callback method to evaluate gas usage for method and inputs.
|
||||||
|
:type code_callback: method taking abi encoded input data as single argument
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.connection.JSONRPCIdGenerator
|
||||||
|
"""
|
||||||
def __init__(self, conn, code_callback=None, id_generator=None):
|
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)
|
super(RPCPureGasOracle, self).__init__(conn, code_callback=code_callback, min_price=0, id_generator=id_generator)
|
||||||
|
|
||||||
|
|
||||||
class OverrideGasOracle(RPCGasOracle):
|
class OverrideGasOracle(RPCGasOracle):
|
||||||
|
"""Gas parameter helper that can be selectively overridden.
|
||||||
|
|
||||||
|
If both price and limit are set, the conn parameter will not be used.
|
||||||
|
|
||||||
|
If either price or limit is set to None, the rpc in the conn value will be used to query the missing value.
|
||||||
|
|
||||||
|
If both are None, behaves the same as chainlib.eth.gas.RPCGasOracle.
|
||||||
|
|
||||||
|
:param price: Set exact gas price
|
||||||
|
:type price: int
|
||||||
|
:param limit: Set exact gas limit
|
||||||
|
:type limit: int
|
||||||
|
:param conn: RPC connection for fallback query
|
||||||
|
:type conn: chainlib.connection.RPCConnection
|
||||||
|
:param code_callback: Callback method to evaluate gas usage for method and inputs.
|
||||||
|
:type code_callback: method taking abi encoded input data as single argument
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.connection.JSONRPCIdGenerator
|
||||||
|
"""
|
||||||
def __init__(self, price=None, limit=None, conn=None, code_callback=None, id_generator=None):
|
def __init__(self, price=None, limit=None, conn=None, code_callback=None, id_generator=None):
|
||||||
self.conn = None
|
self.conn = None
|
||||||
self.code_callback = None
|
self.code_callback = None
|
||||||
@ -117,6 +214,8 @@ class OverrideGasOracle(RPCGasOracle):
|
|||||||
|
|
||||||
|
|
||||||
def get_gas(self, code=None):
|
def get_gas(self, code=None):
|
||||||
|
"""See chainlib.eth.gas.RPCGasOracle.
|
||||||
|
"""
|
||||||
r = None
|
r = None
|
||||||
fee_units = None
|
fee_units = None
|
||||||
fee_price = None
|
fee_price = None
|
||||||
|
@ -14,3 +14,31 @@
|
|||||||
|
|
||||||
#106 Timeout Should be used when an action timedout.
|
#106 Timeout Should be used when an action timedout.
|
||||||
#107 Conflict Should be used when an action conflicts with another (ongoing?) action.
|
#107 Conflict Should be used when an action conflicts with another (ongoing?) action.
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
from hexathon import add_0x
|
||||||
|
|
||||||
|
|
||||||
|
def to_blockheight_param(height):
|
||||||
|
"""Translate blockheight specifier to Ethereum json-rpc blockheight argument.
|
||||||
|
|
||||||
|
:param height: Height argument
|
||||||
|
:type height: any
|
||||||
|
:rtype: str
|
||||||
|
:returns: Argument value
|
||||||
|
"""
|
||||||
|
if height == None:
|
||||||
|
height = 'latest'
|
||||||
|
elif isinstance(height, str):
|
||||||
|
try:
|
||||||
|
height = int(height)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if isinstance(height, int):
|
||||||
|
if height == 0:
|
||||||
|
height = 'latest'
|
||||||
|
elif height < 0:
|
||||||
|
height = 'pending'
|
||||||
|
else:
|
||||||
|
height = add_0x(int(height).to_bytes(8, 'big').hex())
|
||||||
|
return height
|
||||||
|
@ -3,12 +3,18 @@ import sha3
|
|||||||
|
|
||||||
|
|
||||||
class LogBloom:
|
class LogBloom:
|
||||||
|
"""Helper for Ethereum receipt log bloom filters.
|
||||||
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.content = bytearray(256)
|
self.content = bytearray(256)
|
||||||
|
|
||||||
|
|
||||||
def add(self, element):
|
def add(self, element):
|
||||||
|
"""Add topic element to filter.
|
||||||
|
|
||||||
|
:param element: Topic element
|
||||||
|
:type element: bytes
|
||||||
|
"""
|
||||||
if not isinstance(element, bytes):
|
if not isinstance(element, bytes):
|
||||||
raise ValueError('element must be bytes')
|
raise ValueError('element must be bytes')
|
||||||
h = sha3.keccak_256()
|
h = sha3.keccak_256()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# third-party imports
|
# external imports
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
strip_0x,
|
strip_0x,
|
||||||
@ -8,17 +8,40 @@ from hexathon import (
|
|||||||
from chainlib.jsonrpc import JSONRPCRequest
|
from chainlib.jsonrpc import JSONRPCRequest
|
||||||
|
|
||||||
|
|
||||||
def nonce(address, id_generator=None):
|
def nonce(address, confirmed=False, id_generator=None):
|
||||||
|
"""Generate json-rpc query to retrieve next nonce of address from node.
|
||||||
|
|
||||||
|
:param address: Address to retrieve nonce for, in hex
|
||||||
|
:type address: str
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.connection.JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_getTransactionCount'
|
o['method'] = 'eth_getTransactionCount'
|
||||||
o['params'].append(address)
|
o['params'].append(address)
|
||||||
o['params'].append('pending')
|
o['params'].append('pending')
|
||||||
|
if confirmed:
|
||||||
|
o['params'].append('latest')
|
||||||
|
else:
|
||||||
|
o['params'].append('pending')
|
||||||
return j.finalize(o)
|
return j.finalize(o)
|
||||||
|
|
||||||
|
|
||||||
class NonceOracle:
|
def nonce_confirmed(address, id_generator=None):
|
||||||
|
return nonce(address, confirmed=True, id_generator=id_generator)
|
||||||
|
|
||||||
|
|
||||||
|
class NonceOracle:
|
||||||
|
"""Base class for the nonce parameter helpers.
|
||||||
|
|
||||||
|
:param address: Address to retireve nonce for, in hex
|
||||||
|
:type address: str
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.connection.JSONRPCIdGenerator
|
||||||
|
"""
|
||||||
def __init__(self, address, id_generator=None):
|
def __init__(self, address, id_generator=None):
|
||||||
self.address = address
|
self.address = address
|
||||||
self.id_generator = id_generator
|
self.id_generator = id_generator
|
||||||
@ -26,23 +49,45 @@ class NonceOracle:
|
|||||||
|
|
||||||
|
|
||||||
def get_nonce(self):
|
def get_nonce(self):
|
||||||
|
"""Load initial nonce value.
|
||||||
|
"""
|
||||||
raise NotImplementedError('Class must be extended')
|
raise NotImplementedError('Class must be extended')
|
||||||
|
|
||||||
|
|
||||||
def next_nonce(self):
|
def next_nonce(self):
|
||||||
|
"""Return next nonce value and advance.
|
||||||
|
|
||||||
|
:rtype: int
|
||||||
|
:returns: Next nonce for address.
|
||||||
|
"""
|
||||||
n = self.nonce
|
n = self.nonce
|
||||||
self.nonce += 1
|
self.nonce += 1
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
class RPCNonceOracle(NonceOracle):
|
class RPCNonceOracle(NonceOracle):
|
||||||
|
"""JSON-RPC only nonce parameter helper.
|
||||||
|
|
||||||
|
:param address: Address to retireve nonce for, in hex
|
||||||
|
:type address: str
|
||||||
|
:param conn: RPC connection
|
||||||
|
:type conn: chainlib.connection.RPCConnection
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: chainlib.connection.JSONRPCIdGenerator
|
||||||
|
"""
|
||||||
def __init__(self, address, conn, id_generator=None):
|
def __init__(self, address, conn, id_generator=None):
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
super(RPCNonceOracle, self).__init__(address, id_generator=id_generator)
|
super(RPCNonceOracle, self).__init__(address, id_generator=id_generator)
|
||||||
|
|
||||||
|
|
||||||
def get_nonce(self):
|
def get_nonce(self):
|
||||||
|
"""Load and return nonce value from network.
|
||||||
|
|
||||||
|
Note! First call to next_nonce after calling get_nonce will return the same value!
|
||||||
|
|
||||||
|
:rtype: int
|
||||||
|
:returns: Initial nonce
|
||||||
|
"""
|
||||||
o = nonce(self.address, id_generator=self.id_generator)
|
o = nonce(self.address, id_generator=self.id_generator)
|
||||||
r = self.conn.do(o)
|
r = self.conn.do(o)
|
||||||
n = strip_0x(r)
|
n = strip_0x(r)
|
||||||
@ -50,14 +95,28 @@ class RPCNonceOracle(NonceOracle):
|
|||||||
|
|
||||||
|
|
||||||
class OverrideNonceOracle(NonceOracle):
|
class OverrideNonceOracle(NonceOracle):
|
||||||
|
"""Manually set initial nonce value.
|
||||||
|
|
||||||
def __init__(self, address, nonce):
|
:param address: Address to retireve nonce for, in hex
|
||||||
self.nonce = nonce
|
:type address: str
|
||||||
super(OverrideNonceOracle, self).__init__(address)
|
:param nonce: Nonce value
|
||||||
|
:type nonce: int
|
||||||
|
:param id_generator: json-rpc id generator (not used)
|
||||||
|
:type id_generator: chainlib.connection.JSONRPCIdGenerator
|
||||||
|
"""
|
||||||
|
def __init__(self, address, nonce, id_generator=None):
|
||||||
|
self.initial_nonce = nonce
|
||||||
|
self.nonce = self.initial_nonce
|
||||||
|
super(OverrideNonceOracle, self).__init__(address, id_generator=id_generator)
|
||||||
|
|
||||||
|
|
||||||
def get_nonce(self):
|
def get_nonce(self):
|
||||||
return self.nonce
|
"""Returns initial nonce value set at object construction.
|
||||||
|
|
||||||
|
:rtype: int
|
||||||
|
:returns: Initial nonce value.
|
||||||
|
"""
|
||||||
|
return self.initial_nonce
|
||||||
|
|
||||||
|
|
||||||
DefaultNonceOracle = RPCNonceOracle
|
DefaultNonceOracle = RPCNonceOracle
|
||||||
|
@ -17,7 +17,7 @@ from chainlib.connection import (
|
|||||||
from chainlib.eth.unittest.ethtester import create_tester_signer
|
from chainlib.eth.unittest.ethtester import create_tester_signer
|
||||||
from chainlib.eth.address import to_checksum_address
|
from chainlib.eth.address import to_checksum_address
|
||||||
|
|
||||||
logg = logging.getLogger() #__name__)
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
@ -37,13 +37,6 @@ def call_sender(
|
|||||||
eth_accounts,
|
eth_accounts,
|
||||||
):
|
):
|
||||||
return eth_accounts[0]
|
return eth_accounts[0]
|
||||||
#
|
|
||||||
#
|
|
||||||
#@pytest.fixture(scope='function')
|
|
||||||
#def eth_signer(
|
|
||||||
# init_eth_tester,
|
|
||||||
# ):
|
|
||||||
# return init_eth_tester
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
|
@ -1,30 +1,19 @@
|
|||||||
#!python3
|
|
||||||
|
|
||||||
"""Token balance query script
|
|
||||||
|
|
||||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
|
||||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
import os
|
import os
|
||||||
import json
|
|
||||||
import argparse
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# third-party imports
|
# external imports
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
strip_0x,
|
strip_0x,
|
||||||
even,
|
even,
|
||||||
)
|
)
|
||||||
import sha3
|
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.address import to_checksum
|
import chainlib.eth.cli
|
||||||
|
from chainlib.eth.address import AddressChecksum
|
||||||
from chainlib.jsonrpc import (
|
from chainlib.jsonrpc import (
|
||||||
jsonrpc_result,
|
jsonrpc_result,
|
||||||
IntSequenceGenerator,
|
IntSequenceGenerator,
|
||||||
@ -35,53 +24,37 @@ from chainlib.eth.gas import (
|
|||||||
balance,
|
balance,
|
||||||
)
|
)
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
if default_eth_provider == None:
|
#config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
arg_flags = chainlib.eth.cli.argflag_std_read
|
||||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
argparser.add_positional('address', type=str, help='Ethereum address of recipient')
|
||||||
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()
|
args = argparser.parse_args()
|
||||||
|
#config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir)
|
||||||
|
config = chainlib.eth.cli.Config.from_args(args, arg_flags)
|
||||||
|
|
||||||
|
wallet = chainlib.eth.cli.Wallet()
|
||||||
|
wallet.from_config(config)
|
||||||
|
holder_address = args.address
|
||||||
|
if wallet.get_signer_address() == None and holder_address != None:
|
||||||
|
holder_address = wallet.from_address(holder_address)
|
||||||
|
|
||||||
if args.vv:
|
rpc = chainlib.eth.cli.Rpc()
|
||||||
logg.setLevel(logging.DEBUG)
|
conn = rpc.connect_by_config(config)
|
||||||
elif args.v:
|
|
||||||
logg.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
rpc_id_generator = None
|
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||||
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():
|
def main():
|
||||||
r = None
|
r = None
|
||||||
decimals = 18
|
decimals = 18
|
||||||
|
|
||||||
o = balance(address, id_generator=rpc_id_generator)
|
o = balance(holder_address, id_generator=rpc.id_generator)
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
|
|
||||||
hx = strip_0x(r)
|
hx = strip_0x(r)
|
||||||
|
@ -4,12 +4,13 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import argparse
|
#import argparse
|
||||||
import logging
|
import logging
|
||||||
import select
|
import select
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.address import to_checksum
|
import chainlib.eth.cli
|
||||||
|
from chainlib.eth.address import AddressChecksum
|
||||||
from chainlib.eth.connection import EthHTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.eth.tx import count
|
from chainlib.eth.tx import count
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
@ -21,63 +22,28 @@ from hexathon import add_0x
|
|||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
if default_eth_provider == None:
|
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
|
||||||
|
|
||||||
def stdin_arg():
|
arg_flags = chainlib.eth.cli.argflag_std_read
|
||||||
h = select.select([sys.stdin], [], [], 0)
|
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||||
if len(h[0]) > 0:
|
argparser.add_positional('address', type=str, help='Ethereum address of recipient')
|
||||||
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()
|
args = argparser.parse_args()
|
||||||
|
config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir)
|
||||||
|
|
||||||
if args.address == None:
|
holder_address = args.address
|
||||||
argparser.error('need first positional argument or value from stdin')
|
wallet = chainlib.eth.cli.Wallet()
|
||||||
|
wallet.from_config(config)
|
||||||
|
if wallet.get_signer_address() == None and holder_address != None:
|
||||||
|
wallet.from_address(holder_address)
|
||||||
|
|
||||||
if args.vv:
|
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||||
logg.setLevel(logging.DEBUG)
|
conn = rpc.connect_by_config(config)
|
||||||
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():
|
def main():
|
||||||
recipient = to_checksum(args.address)
|
o = count(holder_address, id_generator=rpc.id_generator)
|
||||||
if not args.u and recipient != add_0x(args.address):
|
r = conn.do(o)
|
||||||
raise ValueError('invalid checksum address')
|
|
||||||
|
|
||||||
o = count(recipient, id_generator=rpc_id_generator)
|
|
||||||
r = rpc.do(o)
|
|
||||||
count_result = None
|
count_result = None
|
||||||
try:
|
try:
|
||||||
count_result = int(r, 16)
|
count_result = int(r, 16)
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
#!python3
|
|
||||||
|
|
||||||
"""Decode raw transaction
|
|
||||||
|
|
||||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
|
||||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
@ -18,48 +9,29 @@ import logging
|
|||||||
import select
|
import select
|
||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
|
import chainlib.eth.cli
|
||||||
from chainlib.eth.tx import unpack
|
from chainlib.eth.tx import unpack
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.runnable.util import decode_for_puny_humans
|
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
def stdin_arg(t=0):
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
h = select.select([sys.stdin], [], [], t)
|
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||||
if len(h[0]) > 0:
|
|
||||||
v = h[0][0].read()
|
|
||||||
return v.rstrip()
|
|
||||||
return None
|
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
arg_flags = chainlib.eth.cli.Flag.VERBOSE | chainlib.eth.cli.Flag.CHAIN_SPEC | chainlib.eth.cli.Flag.ENV_PREFIX | chainlib.eth.cli.Flag.RAW
|
||||||
argparser.add_argument('-i', '--chain-id', dest='i', default='evm:ethereum:1', type=str, help='Numeric network id')
|
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
argparser.add_positional('tx_data', type=str, help='Transaction data to decode')
|
||||||
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()
|
args = argparser.parse_args()
|
||||||
|
config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir)
|
||||||
|
|
||||||
if args.vv:
|
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||||
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():
|
def main():
|
||||||
tx_raw = argp
|
decode_for_puny_humans(args.tx_data, chain_spec, sys.stdout)
|
||||||
decode_for_puny_humans(tx_raw, chain_spec, sys.stdout)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
#!python3
|
|
||||||
|
|
||||||
"""Gas transfer script
|
|
||||||
|
|
||||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
|
||||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
@ -19,114 +10,53 @@ import logging
|
|||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
|
||||||
from crypto_dev_signer.keystore.dict import DictKeystore
|
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.address import to_checksum
|
from chainlib.eth.address import to_checksum_address
|
||||||
from chainlib.eth.connection import EthHTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.jsonrpc import (
|
from chainlib.jsonrpc import (
|
||||||
JSONRPCRequest,
|
JSONRPCRequest,
|
||||||
IntSequenceGenerator,
|
IntSequenceGenerator,
|
||||||
)
|
)
|
||||||
from chainlib.eth.nonce import (
|
from chainlib.eth.gas import Gas
|
||||||
RPCNonceOracle,
|
|
||||||
OverrideNonceOracle,
|
|
||||||
)
|
|
||||||
from chainlib.eth.gas import (
|
|
||||||
RPCGasOracle,
|
|
||||||
OverrideGasOracle,
|
|
||||||
Gas,
|
|
||||||
)
|
|
||||||
from chainlib.eth.gas import balance as gas_balance
|
from chainlib.eth.gas import balance as gas_balance
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.eth.runnable.util import decode_for_puny_humans
|
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||||
|
import chainlib.eth.cli
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.WALLET
|
||||||
if default_eth_provider == None:
|
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
argparser.add_argument('--data', type=str, help='Transaction data')
|
||||||
|
argparser.add_positional('amount', type=int, help='Token amount to send')
|
||||||
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()
|
args = argparser.parse_args()
|
||||||
|
extra_args = {
|
||||||
|
'data': None,
|
||||||
if args.vv:
|
'amount': None,
|
||||||
logg.setLevel(logging.DEBUG)
|
}
|
||||||
elif args.v:
|
#config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_config_dir=config_dir)
|
||||||
logg.setLevel(logging.INFO)
|
config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args)
|
||||||
|
|
||||||
block_all = args.ww
|
block_all = args.ww
|
||||||
block_last = args.w or block_all
|
block_last = args.w or block_all
|
||||||
|
|
||||||
passphrase_env = 'ETH_PASSPHRASE'
|
wallet = chainlib.eth.cli.Wallet()
|
||||||
if args.env_prefix != None:
|
wallet.from_config(config)
|
||||||
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
|
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||||
keystore = DictKeystore()
|
conn = rpc.connect_by_config(config)
|
||||||
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
|
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||||
if args.seq:
|
|
||||||
rpc_id_generator = IntSequenceGenerator()
|
|
||||||
|
|
||||||
auth = None
|
value = config.get('_AMOUNT')
|
||||||
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
|
send = config.true('_RPC_SEND')
|
||||||
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):
|
def balance(address, id_generator):
|
||||||
@ -137,29 +67,34 @@ def balance(address, id_generator):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
recipient = to_checksum(args.recipient)
|
signer = rpc.get_signer()
|
||||||
if not args.u and recipient != add_0x(args.recipient):
|
signer_address = rpc.get_sender_address()
|
||||||
|
|
||||||
|
g = Gas(chain_spec, signer=signer, gas_oracle=rpc.get_gas_oracle(), nonce_oracle=rpc.get_nonce_oracle())
|
||||||
|
|
||||||
|
recipient = to_checksum_address(config.get('_RECIPIENT'))
|
||||||
|
if not config.true('_UNSAFE') and recipient != add_0x(config.get('_RECIPIENT')):
|
||||||
raise ValueError('invalid checksum address')
|
raise ValueError('invalid checksum address')
|
||||||
|
|
||||||
logg.info('gas transfer from {} to {} value {}'.format(signer_address, recipient, value))
|
logg.info('gas transfer from {} to {} value {}'.format(signer_address, recipient, value))
|
||||||
if logg.isEnabledFor(logging.DEBUG):
|
if logg.isEnabledFor(logging.DEBUG):
|
||||||
try:
|
try:
|
||||||
sender_balance = balance(signer_address, rpc_id_generator)
|
sender_balance = balance(signer_address, rpc.id_generator)
|
||||||
recipient_balance = balance(recipient, rpc_id_generator)
|
recipient_balance = balance(recipient, rpc.id_generator)
|
||||||
logg.debug('sender {} balance before: {}'.format(signer_address, sender_balance))
|
logg.debug('sender {} balance before: {}'.format(signer_address, sender_balance))
|
||||||
logg.debug('recipient {} balance before: {}'.format(recipient, recipient_balance))
|
logg.debug('recipient {} balance before: {}'.format(recipient, recipient_balance))
|
||||||
except urllib.error.URLError:
|
except urllib.error.URLError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
(tx_hash_hex, o) = g.create(signer_address, recipient, value, id_generator=rpc_id_generator)
|
(tx_hash_hex, o) = g.create(signer_address, recipient, value, data=config.get('_DATA'), id_generator=rpc.id_generator)
|
||||||
|
|
||||||
if send:
|
if send:
|
||||||
conn.do(o)
|
conn.do(o)
|
||||||
if block_last:
|
if block_last:
|
||||||
r = conn.wait(tx_hash_hex)
|
r = conn.wait(tx_hash_hex)
|
||||||
if logg.isEnabledFor(logging.DEBUG):
|
if logg.isEnabledFor(logging.DEBUG):
|
||||||
sender_balance = balance(signer_address, rpc_id_generator)
|
sender_balance = balance(signer_address, rpc.id_generator)
|
||||||
recipient_balance = balance(recipient, rpc_id_generator)
|
recipient_balance = balance(recipient, rpc.id_generator)
|
||||||
logg.debug('sender {} balance after: {}'.format(signer_address, sender_balance))
|
logg.debug('sender {} balance after: {}'.format(signer_address, sender_balance))
|
||||||
logg.debug('recipient {} balance after: {}'.format(recipient, recipient_balance))
|
logg.debug('recipient {} balance after: {}'.format(recipient, recipient_balance))
|
||||||
if r['status'] == 0:
|
if r['status'] == 0:
|
||||||
@ -167,12 +102,13 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print(tx_hash_hex)
|
print(tx_hash_hex)
|
||||||
else:
|
else:
|
||||||
if logg.isEnabledFor(logging.INFO):
|
#if logg.isEnabledFor(logging.INFO):
|
||||||
|
if config.true('_RAW'):
|
||||||
|
print(o['params'][0])
|
||||||
|
else:
|
||||||
io_str = io.StringIO()
|
io_str = io.StringIO()
|
||||||
decode_for_puny_humans(o['params'][0], chain_spec, io_str)
|
decode_for_puny_humans(o['params'][0], chain_spec, io_str)
|
||||||
print(io_str.getvalue())
|
print(io_str.getvalue())
|
||||||
else:
|
|
||||||
print(o['params'][0])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
#!python3
|
|
||||||
|
|
||||||
"""Data retrieval script
|
|
||||||
|
|
||||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
|
||||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
@ -19,80 +10,56 @@ import enum
|
|||||||
import select
|
import select
|
||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
|
from potaahto.symbols import snake_and_camel
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
strip_0x,
|
strip_0x,
|
||||||
)
|
)
|
||||||
import sha3
|
import sha3
|
||||||
|
|
||||||
# local imports
|
|
||||||
from chainlib.eth.address import to_checksum
|
|
||||||
from chainlib.jsonrpc import (
|
from chainlib.jsonrpc import (
|
||||||
JSONRPCRequest,
|
JSONRPCRequest,
|
||||||
jsonrpc_result,
|
jsonrpc_result,
|
||||||
IntSequenceGenerator,
|
IntSequenceGenerator,
|
||||||
)
|
)
|
||||||
|
from chainlib.chain import ChainSpec
|
||||||
|
from chainlib.status import Status
|
||||||
|
|
||||||
|
# local imports
|
||||||
from chainlib.eth.connection import EthHTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.eth.tx import (
|
from chainlib.eth.tx import (
|
||||||
Tx,
|
Tx,
|
||||||
pack,
|
pack,
|
||||||
)
|
)
|
||||||
from chainlib.eth.address import to_checksum_address
|
from chainlib.eth.address import (
|
||||||
from chainlib.eth.block import Block
|
to_checksum_address,
|
||||||
from chainlib.chain import ChainSpec
|
is_checksum_address,
|
||||||
from chainlib.status import Status
|
)
|
||||||
|
from chainlib.eth.block import (
|
||||||
|
Block,
|
||||||
|
block_by_hash,
|
||||||
|
)
|
||||||
from chainlib.eth.runnable.util import decode_for_puny_humans
|
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||||
|
from chainlib.eth.jsonrpc import to_blockheight_param
|
||||||
|
import chainlib.eth.cli
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s')
|
logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s')
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
if default_eth_provider == None:
|
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
|
||||||
|
|
||||||
def stdin_arg(t=0):
|
arg_flags = chainlib.eth.cli.argflag_std_read
|
||||||
h = select.select([sys.stdin], [], [], t)
|
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||||
if len(h[0]) > 0:
|
argparser.add_positional('item', type=str, help='Address or transaction to retrieve data for')
|
||||||
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()
|
args = argparser.parse_args()
|
||||||
|
config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir)
|
||||||
|
|
||||||
if args.vv:
|
rpc = chainlib.eth.cli.Rpc()
|
||||||
logg.setLevel(logging.DEBUG)
|
conn = rpc.connect_by_config(config)
|
||||||
elif args.v:
|
|
||||||
logg.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
argp = args.item
|
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||||
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)
|
item = add_0x(args.item)
|
||||||
as_rlp = bool(args.rlp)
|
|
||||||
|
|
||||||
|
|
||||||
def get_transaction(conn, tx_hash, id_generator):
|
def get_transaction(conn, tx_hash, id_generator):
|
||||||
@ -106,7 +73,7 @@ def get_transaction(conn, tx_hash, id_generator):
|
|||||||
logg.error('Transaction {} not found'.format(tx_hash))
|
logg.error('Transaction {} not found'.format(tx_hash))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if as_rlp:
|
if config.true('_RAW'):
|
||||||
tx_src = Tx.src_normalize(tx_src)
|
tx_src = Tx.src_normalize(tx_src)
|
||||||
return pack(tx_src, chain_spec).hex()
|
return pack(tx_src, chain_spec).hex()
|
||||||
|
|
||||||
@ -125,16 +92,24 @@ def get_transaction(conn, tx_hash, id_generator):
|
|||||||
tx = Tx(tx_src)
|
tx = Tx(tx_src)
|
||||||
if rcpt != None:
|
if rcpt != None:
|
||||||
tx.apply_receipt(rcpt)
|
tx.apply_receipt(rcpt)
|
||||||
|
rcpt = snake_and_camel(rcpt)
|
||||||
|
o = block_by_hash(rcpt['block_hash'])
|
||||||
|
r = conn.do(o)
|
||||||
|
block = Block(r)
|
||||||
|
tx.apply_block(block)
|
||||||
|
logg.debug('foo {}'.format(tx_src))
|
||||||
tx.generate_wire(chain_spec)
|
tx.generate_wire(chain_spec)
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
|
|
||||||
def get_address(conn, address, id_generator):
|
|
||||||
|
def get_address(conn, address, id_generator, height):
|
||||||
j = JSONRPCRequest(id_generator=id_generator)
|
j = JSONRPCRequest(id_generator=id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_getCode'
|
o['method'] = 'eth_getCode'
|
||||||
o['params'].append(address)
|
o['params'].append(address)
|
||||||
o['params'].append('latest')
|
height = to_blockheight_param(height)
|
||||||
|
o['params'].append(height)
|
||||||
o = j.finalize(o)
|
o = j.finalize(o)
|
||||||
code = conn.do(o)
|
code = conn.do(o)
|
||||||
|
|
||||||
@ -146,11 +121,18 @@ def get_address(conn, address, id_generator):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
address = item
|
||||||
r = None
|
r = None
|
||||||
if len(item) > 42:
|
if len(address) > 42:
|
||||||
r = get_transaction(conn, item, rpc_id_generator).to_human()
|
r = get_transaction(conn, address, rpc.id_generator)
|
||||||
elif args.u or to_checksum_address(item):
|
if not config.true('_RAW'):
|
||||||
r = get_address(conn, item, rpc_id_generator)
|
r = r.to_human()
|
||||||
|
else:
|
||||||
|
if config.get('_UNSAFE'):
|
||||||
|
address = to_checksum_address(address)
|
||||||
|
elif not is_checksum_address(address):
|
||||||
|
raise ValueError('invalid checksum address: {}'.format(address))
|
||||||
|
r = get_address(conn, address, rpc.id_generator, config.get('_HEIGHT'))
|
||||||
print(r)
|
print(r)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
#!python3
|
|
||||||
|
|
||||||
"""Token balance query script
|
|
||||||
|
|
||||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
|
||||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
@ -17,19 +8,18 @@ import json
|
|||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# third-party imports
|
# external imports
|
||||||
|
from chainlib.chain import ChainSpec
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
add_0x,
|
add_0x,
|
||||||
strip_0x,
|
strip_0x,
|
||||||
even,
|
even,
|
||||||
)
|
)
|
||||||
import sha3
|
import sha3
|
||||||
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.eth.address import (
|
from chainlib.eth.address import AddressChecksum
|
||||||
to_checksum_address,
|
|
||||||
is_checksum_address,
|
|
||||||
)
|
|
||||||
from chainlib.eth.chain import network_id
|
from chainlib.eth.chain import network_id
|
||||||
from chainlib.eth.block import (
|
from chainlib.eth.block import (
|
||||||
block_latest,
|
block_latest,
|
||||||
@ -43,79 +33,49 @@ from chainlib.eth.gas import (
|
|||||||
balance,
|
balance,
|
||||||
price,
|
price,
|
||||||
)
|
)
|
||||||
from chainlib.jsonrpc import (
|
import chainlib.eth.cli
|
||||||
IntSequenceGenerator,
|
|
||||||
)
|
|
||||||
from chainlib.chain import ChainSpec
|
|
||||||
|
|
||||||
BLOCK_SAMPLES = 10
|
BLOCK_SAMPLES = 10
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
if default_eth_provider == None:
|
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
arg_flags = chainlib.eth.cli.argflag_std_read
|
||||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
argparser.add_positional('address', type=str, help='Address to retrieve info for', required=False)
|
||||||
argparser.add_argument('-H', '--human', dest='human', action='store_true', help='Use human-friendly formatting')
|
argparser.add_argument('--long', action='store_true', help='Calculate averages through sampling of blocks and txs')
|
||||||
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()
|
args = argparser.parse_args()
|
||||||
|
|
||||||
|
config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args={'long': None}, default_config_dir=config_dir)
|
||||||
|
|
||||||
if args.vv:
|
holder_address = args.address
|
||||||
logg.setLevel(logging.DEBUG)
|
wallet = chainlib.eth.cli.Wallet()
|
||||||
elif args.v:
|
wallet.from_config(config)
|
||||||
logg.setLevel(logging.INFO)
|
if wallet.get_signer_address() == None and holder_address != None:
|
||||||
|
wallet.from_address(holder_address)
|
||||||
|
|
||||||
signer = None
|
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||||
holder_address = None
|
conn = rpc.connect_by_config(config)
|
||||||
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'
|
token_symbol = 'eth'
|
||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||||
|
|
||||||
human = args.human
|
human = not config.true('_RAW')
|
||||||
|
|
||||||
longmode = args.l
|
longmode = config.true('_LONG')
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
o = network_id(id_generator=rpc_id_generator)
|
o = network_id(id_generator=rpc.id_generator)
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
#if human:
|
#if human:
|
||||||
# n = format(n, ',')
|
# n = format(n, ',')
|
||||||
sys.stdout.write('Network id: {}\n'.format(r))
|
sys.stdout.write('Network id: {}\n'.format(r))
|
||||||
|
|
||||||
o = block_latest(id_generator=rpc_id_generator)
|
o = block_latest(id_generator=rpc.id_generator)
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
n = int(r, 16)
|
n = int(r, 16)
|
||||||
first_block_number = n
|
first_block_number = n
|
||||||
@ -123,7 +83,7 @@ def main():
|
|||||||
n = format(n, ',')
|
n = format(n, ',')
|
||||||
sys.stdout.write('Block: {}\n'.format(n))
|
sys.stdout.write('Block: {}\n'.format(n))
|
||||||
|
|
||||||
o = block_by_number(first_block_number, False, id_generator=rpc_id_generator)
|
o = block_by_number(first_block_number, False, id_generator=rpc.id_generator)
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
last_block = Block(r)
|
last_block = Block(r)
|
||||||
last_timestamp = last_block.timestamp
|
last_timestamp = last_block.timestamp
|
||||||
@ -132,7 +92,7 @@ def main():
|
|||||||
aggr_time = 0.0
|
aggr_time = 0.0
|
||||||
aggr_gas = 0
|
aggr_gas = 0
|
||||||
for i in range(BLOCK_SAMPLES):
|
for i in range(BLOCK_SAMPLES):
|
||||||
o = block_by_number(first_block_number-i, False, id_generator=rpc_id_generator)
|
o = block_by_number(first_block_number-i, False, id_generator=rpc.id_generator)
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
block = Block(r)
|
block = Block(r)
|
||||||
aggr_time += last_block.timestamp - block.timestamp
|
aggr_time += last_block.timestamp - block.timestamp
|
||||||
@ -150,7 +110,7 @@ def main():
|
|||||||
sys.stdout.write('Gaslimit: {}\n'.format(n))
|
sys.stdout.write('Gaslimit: {}\n'.format(n))
|
||||||
sys.stdout.write('Blocktime: {}\n'.format(aggr_time / BLOCK_SAMPLES))
|
sys.stdout.write('Blocktime: {}\n'.format(aggr_time / BLOCK_SAMPLES))
|
||||||
|
|
||||||
o = price(id_generator=rpc_id_generator)
|
o = price(id_generator=rpc.id_generator)
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
n = int(r, 16)
|
n = int(r, 16)
|
||||||
if human:
|
if human:
|
||||||
|
@ -1,12 +1,3 @@
|
|||||||
#!python3
|
|
||||||
|
|
||||||
"""Gas transfer script
|
|
||||||
|
|
||||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
|
||||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# standard imports
|
# standard imports
|
||||||
@ -19,6 +10,7 @@ import logging
|
|||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
|
import chainlib.eth.cli
|
||||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||||
from crypto_dev_signer.keystore.dict import DictKeystore
|
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||||
from hexathon import (
|
from hexathon import (
|
||||||
@ -45,129 +37,88 @@ from chainlib.eth.tx import (
|
|||||||
TxFactory,
|
TxFactory,
|
||||||
raw,
|
raw,
|
||||||
)
|
)
|
||||||
|
from chainlib.error import SignerMissingException
|
||||||
from chainlib.chain import ChainSpec
|
from chainlib.chain import ChainSpec
|
||||||
from chainlib.eth.runnable.util import decode_for_puny_humans
|
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||||
|
from chainlib.eth.jsonrpc import to_blockheight_param
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
if default_eth_provider == None:
|
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC
|
||||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||||
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
|
argparser.add_positional('data', type=str, help='Transaction data')
|
||||||
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()
|
args = argparser.parse_args()
|
||||||
|
config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir)
|
||||||
|
|
||||||
if args.vv:
|
|
||||||
logg.setLevel(logging.DEBUG)
|
|
||||||
elif args.v:
|
|
||||||
logg.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
block_all = args.ww
|
block_all = args.ww
|
||||||
block_last = args.w or block_all
|
block_last = args.w or block_all
|
||||||
|
|
||||||
passphrase_env = 'ETH_PASSPHRASE'
|
wallet = chainlib.eth.cli.Wallet(EIP155Signer)
|
||||||
if args.env_prefix != None:
|
wallet.from_config(config)
|
||||||
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
|
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||||
keystore = DictKeystore()
|
conn = rpc.connect_by_config(config)
|
||||||
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
|
send = config.true('_RPC_SEND')
|
||||||
if args.seq:
|
|
||||||
rpc_id_generator = IntSequenceGenerator()
|
|
||||||
|
|
||||||
auth = None
|
if config.get('_EXEC_ADDRESS') != 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
|
send = False
|
||||||
|
|
||||||
nonce_oracle = None
|
chain_spec = None
|
||||||
gas_oracle = None
|
try:
|
||||||
if signer_address != None and not local:
|
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||||
if args.nonce != None:
|
except AttributeError:
|
||||||
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce)
|
pass
|
||||||
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():
|
def main():
|
||||||
recipient = None
|
|
||||||
if args.a != None:
|
signer_address = None
|
||||||
recipient = add_0x(to_checksum(args.a))
|
try:
|
||||||
if not args.u and recipient != add_0x(recipient):
|
signer = rpc.get_signer()
|
||||||
|
signer_address = rpc.get_signer_address()
|
||||||
|
except SignerMissingException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if config.get('_EXEC_ADDRESS') != None:
|
||||||
|
exec_address = add_0x(to_checksum(config.get('_EXEC_ADDRESS')))
|
||||||
|
if not args.u and exec_address != add_0x(exec_address):
|
||||||
raise ValueError('invalid checksum address')
|
raise ValueError('invalid checksum address')
|
||||||
|
|
||||||
if local:
|
j = JSONRPCRequest(id_generator=rpc.id_generator)
|
||||||
j = JSONRPCRequest(id_generator=rpc_id_generator)
|
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_call'
|
o['method'] = 'eth_call'
|
||||||
o['params'].append({
|
o['params'].append({
|
||||||
'to': recipient,
|
'to': exec_address,
|
||||||
'from': signer_address,
|
'from': signer_address,
|
||||||
'value': '0x00',
|
'value': '0x00',
|
||||||
'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit
|
'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit
|
||||||
'gasPrice': '0x01',
|
'gasPrice': '0x01',
|
||||||
'data': add_0x(args.data),
|
'data': add_0x(args.data),
|
||||||
})
|
})
|
||||||
o['params'].append('latest')
|
height = to_blockheight_param(config.get('_HEIGHT'))
|
||||||
|
o['params'].append(height)
|
||||||
o = j.finalize(o)
|
o = j.finalize(o)
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
print(strip_0x(r))
|
try:
|
||||||
|
print(strip_0x(r))
|
||||||
|
except ValueError:
|
||||||
|
sys.stderr.write('query returned an empty value\n')
|
||||||
|
sys.exit(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif signer_address != None:
|
elif signer_address != None:
|
||||||
|
if chain_spec == None:
|
||||||
|
raise ValueError('chain spec must be specified')
|
||||||
|
g = TxFactory(chain_spec, signer=rpc.get_signer(), gas_oracle=rpc.get_gas_oracle(), nonce_oracle=rpc.get_nonce_oracle())
|
||||||
tx = g.template(signer_address, recipient, use_nonce=True)
|
tx = g.template(signer_address, recipient, use_nonce=True)
|
||||||
if args.data != None:
|
if args.data != None:
|
||||||
tx = g.set_code(tx, add_0x(args.data))
|
tx = g.set_code(tx, add_0x(args.data))
|
||||||
|
|
||||||
(tx_hash_hex, o) = g.finalize(tx, id_generator=rpc_id_generator)
|
(tx_hash_hex, o) = g.finalize(tx, id_generator=rpc.id_generator)
|
||||||
|
|
||||||
if send:
|
if send:
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
@ -177,7 +128,7 @@ def main():
|
|||||||
print(tx_hash_hex)
|
print(tx_hash_hex)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
o = raw(args.data, id_generator=rpc_id_generator)
|
o = raw(args.data, id_generator=rpc.id_generator)
|
||||||
if send:
|
if send:
|
||||||
r = conn.do(o)
|
r = conn.do(o)
|
||||||
print(r)
|
print(r)
|
||||||
|
@ -3,6 +3,17 @@ from chainlib.jsonrpc import JSONRPCRequest
|
|||||||
|
|
||||||
|
|
||||||
def new_account(passphrase='', id_generator=None):
|
def new_account(passphrase='', id_generator=None):
|
||||||
|
"""Generate json-rpc query to create new account in keystore.
|
||||||
|
|
||||||
|
Uses the personal_newAccount rpc call.
|
||||||
|
|
||||||
|
:param passphrase: Passphrase string
|
||||||
|
:type passphrase: str
|
||||||
|
:param id_generator: JSONRPC id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'personal_newAccount'
|
o['method'] = 'personal_newAccount'
|
||||||
@ -11,6 +22,17 @@ def new_account(passphrase='', id_generator=None):
|
|||||||
|
|
||||||
|
|
||||||
def sign_transaction(payload, id_generator=None):
|
def sign_transaction(payload, id_generator=None):
|
||||||
|
"""Generate json-rpc query to sign transaction using the node keystore.
|
||||||
|
|
||||||
|
The node must have the private key corresponding to the from-field in the transaction object.
|
||||||
|
|
||||||
|
:param payload: Transaction
|
||||||
|
:type payload: dict
|
||||||
|
:param id_generator: JSONRPC id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_signTransaction'
|
o['method'] = 'eth_signTransaction'
|
||||||
@ -19,6 +41,19 @@ def sign_transaction(payload, id_generator=None):
|
|||||||
|
|
||||||
|
|
||||||
def sign_message(address, payload, id_generator=None):
|
def sign_message(address, payload, id_generator=None):
|
||||||
|
"""Generate json-rpc query to sign an arbirary message using the node keystore.
|
||||||
|
|
||||||
|
The node must have the private key corresponding to the address parameter.
|
||||||
|
|
||||||
|
:param address: Address of key to sign with, in hex
|
||||||
|
:type address: str
|
||||||
|
:param payload: Arbirary message, in hex
|
||||||
|
:type payload: str
|
||||||
|
:param id_generator: JSONRPC id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_sign'
|
o['method'] = 'eth_sign'
|
||||||
|
@ -9,6 +9,7 @@ import sha3
|
|||||||
from hexathon import (
|
from hexathon import (
|
||||||
strip_0x,
|
strip_0x,
|
||||||
add_0x,
|
add_0x,
|
||||||
|
compact,
|
||||||
)
|
)
|
||||||
from rlp import decode as rlp_decode
|
from rlp import decode as rlp_decode
|
||||||
from rlp import encode as rlp_encode
|
from rlp import encode as rlp_encode
|
||||||
@ -16,25 +17,34 @@ from crypto_dev_signer.eth.transaction import EIP155Transaction
|
|||||||
from crypto_dev_signer.encoding import public_key_to_address
|
from crypto_dev_signer.encoding import public_key_to_address
|
||||||
from crypto_dev_signer.eth.encoding import chain_id_to_v
|
from crypto_dev_signer.eth.encoding import chain_id_to_v
|
||||||
from potaahto.symbols import snake_and_camel
|
from potaahto.symbols import snake_and_camel
|
||||||
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
from chainlib.hash import keccak256_hex_to_hex
|
from chainlib.hash import keccak256_hex_to_hex
|
||||||
from chainlib.status import Status
|
from chainlib.status import Status
|
||||||
|
from chainlib.jsonrpc import JSONRPCRequest
|
||||||
|
from chainlib.tx import Tx as BaseTx
|
||||||
|
from chainlib.eth.nonce import (
|
||||||
|
nonce as nonce_query,
|
||||||
|
nonce_confirmed as nonce_query_confirmed,
|
||||||
|
)
|
||||||
|
from chainlib.block import BlockSpec
|
||||||
|
|
||||||
|
# local imports
|
||||||
from .address import to_checksum
|
from .address import to_checksum
|
||||||
from .constant import (
|
from .constant import (
|
||||||
MINIMUM_FEE_UNITS,
|
MINIMUM_FEE_UNITS,
|
||||||
MINIMUM_FEE_PRICE,
|
MINIMUM_FEE_PRICE,
|
||||||
ZERO_ADDRESS,
|
ZERO_ADDRESS,
|
||||||
|
DEFAULT_FEE_LIMIT,
|
||||||
)
|
)
|
||||||
from .contract import ABIContractEncoder
|
from .contract import ABIContractEncoder
|
||||||
from chainlib.jsonrpc import JSONRPCRequest
|
from .jsonrpc import to_blockheight_param
|
||||||
|
|
||||||
logg = logging.getLogger().getChild(__name__)
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TxFormat(enum.IntEnum):
|
class TxFormat(enum.IntEnum):
|
||||||
|
"""Tx generator output formats
|
||||||
|
"""
|
||||||
DICT = 0x00
|
DICT = 0x00
|
||||||
RAW = 0x01
|
RAW = 0x01
|
||||||
RAW_SIGNED = 0x02
|
RAW_SIGNED = 0x02
|
||||||
@ -56,24 +66,22 @@ field_debugs = [
|
|||||||
's',
|
'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
|
count = nonce_query
|
||||||
|
count_pending = nonce_query
|
||||||
def count_confirmed(address):
|
count_confirmed = nonce_query_confirmed
|
||||||
return count(address, True)
|
|
||||||
|
|
||||||
|
|
||||||
def pack(tx_src, chain_spec):
|
def pack(tx_src, chain_spec):
|
||||||
|
"""Serialize wire format transaction from transaction representation.
|
||||||
|
|
||||||
|
:param tx_src: Transaction source.
|
||||||
|
:type tx_src: dict
|
||||||
|
:param chain_spec: Chain spec to calculate EIP155 v value
|
||||||
|
:type chain_spec: chainlib.chain.ChainSpec
|
||||||
|
:rtype: bytes
|
||||||
|
:returns: Serialized transaction
|
||||||
|
"""
|
||||||
if isinstance(tx_src, Tx):
|
if isinstance(tx_src, Tx):
|
||||||
tx_src = tx_src.as_dict()
|
tx_src = tx_src.as_dict()
|
||||||
tx_src = Tx.src_normalize(tx_src)
|
tx_src = Tx.src_normalize(tx_src)
|
||||||
@ -96,6 +104,15 @@ def pack(tx_src, chain_spec):
|
|||||||
|
|
||||||
|
|
||||||
def unpack(tx_raw_bytes, chain_spec):
|
def unpack(tx_raw_bytes, chain_spec):
|
||||||
|
"""Deserialize wire format transaction to transaction representation.
|
||||||
|
|
||||||
|
:param tx_raw_bytes: Serialized transaction
|
||||||
|
:type tx_raw_bytes: bytes
|
||||||
|
:param chain_spec: Chain spec to calculate EIP155 v value
|
||||||
|
:type chain_spec: chainlib.chain.ChainSpec
|
||||||
|
:rtype: dict
|
||||||
|
:returns: Transaction representation
|
||||||
|
"""
|
||||||
chain_id = chain_spec.chain_id()
|
chain_id = chain_spec.chain_id()
|
||||||
tx = __unpack_raw(tx_raw_bytes, chain_id)
|
tx = __unpack_raw(tx_raw_bytes, chain_id)
|
||||||
tx['nonce'] = int.from_bytes(tx['nonce'], 'big')
|
tx['nonce'] = int.from_bytes(tx['nonce'], 'big')
|
||||||
@ -106,6 +123,15 @@ def unpack(tx_raw_bytes, chain_spec):
|
|||||||
|
|
||||||
|
|
||||||
def unpack_hex(tx_raw_bytes, chain_spec):
|
def unpack_hex(tx_raw_bytes, chain_spec):
|
||||||
|
"""Deserialize wire format transaction to transaction representation, using hex values for all numeric value fields.
|
||||||
|
|
||||||
|
:param tx_raw_bytes: Serialized transaction
|
||||||
|
:type tx_raw_bytes: bytes
|
||||||
|
:param chain_spec: Chain spec to calculate EIP155 v value
|
||||||
|
:type chain_spec: chainlib.chain.ChainSpec
|
||||||
|
:rtype: dict
|
||||||
|
:returns: Transaction representation
|
||||||
|
"""
|
||||||
chain_id = chain_spec.chain_id()
|
chain_id = chain_spec.chain_id()
|
||||||
tx = __unpack_raw(tx_raw_bytes, chain_id)
|
tx = __unpack_raw(tx_raw_bytes, chain_id)
|
||||||
tx['nonce'] = add_0x(hex(tx['nonce']))
|
tx['nonce'] = add_0x(hex(tx['nonce']))
|
||||||
@ -193,6 +219,15 @@ def __unpack_raw(tx_raw_bytes, chain_id=1):
|
|||||||
|
|
||||||
|
|
||||||
def transaction(hsh, id_generator=None):
|
def transaction(hsh, id_generator=None):
|
||||||
|
"""Generate json-rpc query to retrieve transaction by hash from node.
|
||||||
|
|
||||||
|
:param hsh: Transaction hash, in hex
|
||||||
|
:type hsh: str
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator=id_generator)
|
j = JSONRPCRequest(id_generator=id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_getTransactionByHash'
|
o['method'] = 'eth_getTransactionByHash'
|
||||||
@ -201,6 +236,17 @@ def transaction(hsh, id_generator=None):
|
|||||||
|
|
||||||
|
|
||||||
def transaction_by_block(hsh, idx, id_generator=None):
|
def transaction_by_block(hsh, idx, id_generator=None):
|
||||||
|
"""Generate json-rpc query to retrieve transaction by block hash and index.
|
||||||
|
|
||||||
|
:param hsh: Block hash, in hex
|
||||||
|
:type hsh: str
|
||||||
|
:param idx: Transaction index
|
||||||
|
:type idx: int
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator=id_generator)
|
j = JSONRPCRequest(id_generator=id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_getTransactionByBlockHashAndIndex'
|
o['method'] = 'eth_getTransactionByBlockHashAndIndex'
|
||||||
@ -210,6 +256,15 @@ def transaction_by_block(hsh, idx, id_generator=None):
|
|||||||
|
|
||||||
|
|
||||||
def receipt(hsh, id_generator=None):
|
def receipt(hsh, id_generator=None):
|
||||||
|
"""Generate json-rpc query to retrieve transaction receipt by transaction hash from node.
|
||||||
|
|
||||||
|
:param hsh: Transaction hash, in hex
|
||||||
|
:type hsh: str
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator=id_generator)
|
j = JSONRPCRequest(id_generator=id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_getTransactionReceipt'
|
o['method'] = 'eth_getTransactionReceipt'
|
||||||
@ -218,6 +273,15 @@ def receipt(hsh, id_generator=None):
|
|||||||
|
|
||||||
|
|
||||||
def raw(tx_raw_hex, id_generator=None):
|
def raw(tx_raw_hex, id_generator=None):
|
||||||
|
"""Generator json-rpc query to send raw transaction to node.
|
||||||
|
|
||||||
|
:param hsh: Serialized transaction, in hex
|
||||||
|
:type hsh: str
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: dict
|
||||||
|
:returns: rpc query object
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator=id_generator)
|
j = JSONRPCRequest(id_generator=id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_sendRawTransaction'
|
o['method'] = 'eth_sendRawTransaction'
|
||||||
@ -226,8 +290,23 @@ def raw(tx_raw_hex, id_generator=None):
|
|||||||
|
|
||||||
|
|
||||||
class TxFactory:
|
class TxFactory:
|
||||||
|
"""Base class for generating and signing transactions or contract calls.
|
||||||
|
|
||||||
fee = 8000000
|
For transactions (state changes), a signer, gas oracle and nonce oracle needs to be supplied.
|
||||||
|
|
||||||
|
Gas oracle and nonce oracle may in some cases be needed for contract calls, if the node insists on counting gas for read-only operations.
|
||||||
|
|
||||||
|
:param chain_spec: Chain spec to use for signer.
|
||||||
|
:type chain_spec: chainlib.chain.ChainSpec
|
||||||
|
:param signer: Signer middleware.
|
||||||
|
:type param: Object implementing interface ofchainlib.eth.connection.sign_transaction_to_rlp.
|
||||||
|
:param gas_oracle: Backend to generate gas parameters
|
||||||
|
:type gas_oracle: Object implementing chainlib.eth.gas.GasOracle interface
|
||||||
|
:param nonce_oracle: Backend to generate gas parameters
|
||||||
|
:type nonce_oracle: Object implementing chainlib.eth.nonce.NonceOracle interface
|
||||||
|
"""
|
||||||
|
|
||||||
|
fee = DEFAULT_FEE_LIMIT
|
||||||
|
|
||||||
def __init__(self, chain_spec, signer=None, gas_oracle=None, nonce_oracle=None):
|
def __init__(self, chain_spec, signer=None, gas_oracle=None, nonce_oracle=None):
|
||||||
self.gas_oracle = gas_oracle
|
self.gas_oracle = gas_oracle
|
||||||
@ -237,6 +316,15 @@ class TxFactory:
|
|||||||
|
|
||||||
|
|
||||||
def build_raw(self, tx):
|
def build_raw(self, tx):
|
||||||
|
"""Sign transaction data, returning the transaction hash and serialized transaction.
|
||||||
|
|
||||||
|
In most cases, chainlib.eth.tx.TxFactory.finalize should be used instead.
|
||||||
|
|
||||||
|
:param tx: Transaction representation
|
||||||
|
:type tx: dict
|
||||||
|
:rtype: tuple
|
||||||
|
:returns: Transaction hash (in hex), serialized transaction (in hex)
|
||||||
|
"""
|
||||||
if tx['to'] == None or tx['to'] == '':
|
if tx['to'] == None or tx['to'] == '':
|
||||||
tx['to'] = '0x'
|
tx['to'] = '0x'
|
||||||
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
||||||
@ -247,12 +335,34 @@ class TxFactory:
|
|||||||
|
|
||||||
|
|
||||||
def build(self, tx, id_generator=None):
|
def build(self, tx, id_generator=None):
|
||||||
|
"""Sign transaction and wrap in raw transaction json-rpc query.
|
||||||
|
|
||||||
|
In most cases, chainlib.eth.tx.TxFactory.finalize should be used instead.
|
||||||
|
|
||||||
|
:param tx: Transaction representation
|
||||||
|
type tx: dict
|
||||||
|
:param id_generator: JSONRPC id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: tuple
|
||||||
|
:returns: Transaction hash (in hex), raw transaction rpc query object
|
||||||
|
"""
|
||||||
(tx_hash_hex, tx_raw_hex) = self.build_raw(tx)
|
(tx_hash_hex, tx_raw_hex) = self.build_raw(tx)
|
||||||
o = raw(tx_raw_hex, id_generator=id_generator)
|
o = raw(tx_raw_hex, id_generator=id_generator)
|
||||||
return (tx_hash_hex, o)
|
return (tx_hash_hex, o)
|
||||||
|
|
||||||
|
|
||||||
def template(self, sender, recipient, use_nonce=False):
|
def template(self, sender, recipient, use_nonce=False):
|
||||||
|
"""Generate a base transaction template.
|
||||||
|
|
||||||
|
:param sender: Sender address, in hex
|
||||||
|
:type sender: str
|
||||||
|
:param receipient: Recipient address, in hex
|
||||||
|
:type recipient: str
|
||||||
|
:param use_nonce: Use and advance nonce in nonce generator.
|
||||||
|
:type use_nonce: bool
|
||||||
|
:rtype: dict
|
||||||
|
:returns: Transaction representation.
|
||||||
|
"""
|
||||||
gas_price = MINIMUM_FEE_PRICE
|
gas_price = MINIMUM_FEE_PRICE
|
||||||
gas_limit = MINIMUM_FEE_UNITS
|
gas_limit = MINIMUM_FEE_UNITS
|
||||||
if self.gas_oracle != None:
|
if self.gas_oracle != None:
|
||||||
@ -276,18 +386,35 @@ class TxFactory:
|
|||||||
|
|
||||||
|
|
||||||
def normalize(self, tx):
|
def normalize(self, tx):
|
||||||
|
"""Generate field name redundancies (camel-case, snake-case).
|
||||||
|
|
||||||
|
:param tx: Transaction representation
|
||||||
|
:type tx: dict
|
||||||
|
:rtype: dict:
|
||||||
|
:returns: Transaction representation with redudant field names
|
||||||
|
"""
|
||||||
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
||||||
txes = txe.serialize()
|
txes = txe.serialize()
|
||||||
return {
|
return {
|
||||||
'from': tx['from'],
|
'from': tx['from'],
|
||||||
'to': txes['to'],
|
'to': txes['to'],
|
||||||
'gasPrice': txes['gasPrice'],
|
'gasPrice': '0x' + compact(txes['gasPrice']),
|
||||||
'gas': txes['gas'],
|
'gas': '0x' + compact(txes['gas']),
|
||||||
'data': txes['data'],
|
'data': txes['data'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def finalize(self, tx, tx_format=TxFormat.JSONRPC, id_generator=None):
|
def finalize(self, tx, tx_format=TxFormat.JSONRPC, id_generator=None):
|
||||||
|
"""Sign transaction and for specified output format.
|
||||||
|
|
||||||
|
:param tx: Transaction representation
|
||||||
|
:type tx: dict
|
||||||
|
:param tx_format: Transaction output format
|
||||||
|
:type tx_format: chainlib.eth.tx.TxFormat
|
||||||
|
:raises NotImplementedError: Unknown tx_format value
|
||||||
|
:rtype: varies
|
||||||
|
:returns: Transaction output in specified format.
|
||||||
|
"""
|
||||||
if tx_format == TxFormat.JSONRPC:
|
if tx_format == TxFormat.JSONRPC:
|
||||||
return self.build(tx, id_generator=id_generator)
|
return self.build(tx, id_generator=id_generator)
|
||||||
elif tx_format == TxFormat.RLP_SIGNED:
|
elif tx_format == TxFormat.RLP_SIGNED:
|
||||||
@ -296,6 +423,17 @@ class TxFactory:
|
|||||||
|
|
||||||
|
|
||||||
def set_code(self, tx, data, update_fee=True):
|
def set_code(self, tx, data, update_fee=True):
|
||||||
|
"""Apply input data to transaction.
|
||||||
|
|
||||||
|
:param tx: Transaction representation
|
||||||
|
:type tx: dict
|
||||||
|
:param data: Input data to apply, in hex
|
||||||
|
:type data: str
|
||||||
|
:param update_fee: Recalculate gas limit based on added input
|
||||||
|
:type update_fee: bool
|
||||||
|
:rtype: dict
|
||||||
|
:returns: Transaction representation
|
||||||
|
"""
|
||||||
tx['data'] = data
|
tx['data'] = data
|
||||||
if update_fee:
|
if update_fee:
|
||||||
tx['gas'] = TxFactory.fee
|
tx['gas'] = TxFactory.fee
|
||||||
@ -307,6 +445,19 @@ class TxFactory:
|
|||||||
|
|
||||||
|
|
||||||
def transact_noarg(self, method, contract_address, sender_address, tx_format=TxFormat.JSONRPC):
|
def transact_noarg(self, method, contract_address, sender_address, tx_format=TxFormat.JSONRPC):
|
||||||
|
"""Convenience generator for contract transaction with no arguments.
|
||||||
|
|
||||||
|
:param method: Method name
|
||||||
|
:type method: str
|
||||||
|
:param contract_address: Contract address to transaction against, in hex
|
||||||
|
:type contract_address: str
|
||||||
|
:param sender_address: Transaction sender, in hex
|
||||||
|
:type sender_address: str
|
||||||
|
:param tx_format: Transaction output format
|
||||||
|
:type tx_format: chainlib.eth.tx.TxFormat
|
||||||
|
:rtype: varies
|
||||||
|
:returns: Transaction output in selected format
|
||||||
|
"""
|
||||||
enc = ABIContractEncoder()
|
enc = ABIContractEncoder()
|
||||||
enc.method(method)
|
enc.method(method)
|
||||||
data = enc.get()
|
data = enc.get()
|
||||||
@ -316,7 +467,22 @@ class TxFactory:
|
|||||||
return tx
|
return tx
|
||||||
|
|
||||||
|
|
||||||
def call_noarg(self, method, contract_address, sender_address=ZERO_ADDRESS, id_generator=None):
|
def call_noarg(self, method, contract_address, sender_address=ZERO_ADDRESS, height=BlockSpec.LATEST, id_generator=None):
|
||||||
|
"""Convenience generator for contract (read-only) call with no arguments.
|
||||||
|
|
||||||
|
:param method: Method name
|
||||||
|
:type method: str
|
||||||
|
:param contract_address: Contract address to transaction against, in hex
|
||||||
|
:type contract_address: str
|
||||||
|
:param sender_address: Transaction sender, in hex
|
||||||
|
:type sender_address: str
|
||||||
|
:param height: Transaction height specifier
|
||||||
|
:type height: chainlib.block.BlockSpec
|
||||||
|
:param id_generator: json-rpc id generator
|
||||||
|
:type id_generator: JSONRPCIdGenerator
|
||||||
|
:rtype: varies
|
||||||
|
:returns: Transaction output in selected format
|
||||||
|
"""
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
o['method'] = 'eth_call'
|
o['method'] = 'eth_call'
|
||||||
@ -326,34 +492,38 @@ class TxFactory:
|
|||||||
tx = self.template(sender_address, contract_address)
|
tx = self.template(sender_address, contract_address)
|
||||||
tx = self.set_code(tx, data)
|
tx = self.set_code(tx, data)
|
||||||
o['params'].append(self.normalize(tx))
|
o['params'].append(self.normalize(tx))
|
||||||
o['params'].append('latest')
|
height = to_blockheight_param(height)
|
||||||
|
o['params'].append(height)
|
||||||
o = j.finalize(o)
|
o = j.finalize(o)
|
||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
class Tx:
|
class Tx(BaseTx):
|
||||||
|
"""Wraps transaction data, transaction receipt data and block data, enforces local standardization of fields, and provides useful output formats for viewing transaction contents.
|
||||||
|
|
||||||
|
If block is applied, the transaction data or transaction hash must exist in its transactions array.
|
||||||
|
|
||||||
|
If receipt is applied, the transaction hash in the receipt must match the hash in the transaction data.
|
||||||
|
|
||||||
|
:param src: Transaction representation
|
||||||
|
:type src: dict
|
||||||
|
:param block: Apply block object in which transaction in mined.
|
||||||
|
:type block: chainlib.block.Block
|
||||||
|
:param rcpt: Apply receipt data
|
||||||
|
:type rcpt: dict
|
||||||
|
#:todo: force tx type schema parser (whether expect hex or int etc)
|
||||||
|
#:todo: divide up constructor method
|
||||||
|
"""
|
||||||
|
|
||||||
# TODO: force tx type schema parser (whether expect hex or int etc)
|
|
||||||
def __init__(self, src, block=None, rcpt=None):
|
def __init__(self, src, block=None, rcpt=None):
|
||||||
self.tx_src = self.src_normalize(src)
|
self.__rcpt_block_hash = None
|
||||||
|
|
||||||
|
src = self.src_normalize(src)
|
||||||
self.index = -1
|
self.index = -1
|
||||||
tx_hash = add_0x(src['hash'])
|
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)
|
self.hash = strip_0x(tx_hash)
|
||||||
|
if block != None:
|
||||||
|
self.apply_block(block)
|
||||||
try:
|
try:
|
||||||
self.value = int(strip_0x(src['value']), 16)
|
self.value = int(strip_0x(src['value']), 16)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
@ -378,6 +548,7 @@ class Tx:
|
|||||||
inpt = src['input']
|
inpt = src['input']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
inpt = src['data']
|
inpt = src['data']
|
||||||
|
src['input'] = src['data']
|
||||||
|
|
||||||
if inpt != '0x':
|
if inpt != '0x':
|
||||||
inpt = strip_0x(inpt)
|
inpt = strip_0x(inpt)
|
||||||
@ -408,13 +579,27 @@ class Tx:
|
|||||||
|
|
||||||
self.wire = None
|
self.wire = None
|
||||||
|
|
||||||
|
self.tx_src = src
|
||||||
|
|
||||||
|
|
||||||
def src(self):
|
def src(self):
|
||||||
|
"""Retrieve normalized representation source used to construct transaction object.
|
||||||
|
|
||||||
|
:rtype: dict
|
||||||
|
:returns: Transaction representation
|
||||||
|
"""
|
||||||
return self.tx_src
|
return self.tx_src
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def src_normalize(self, src):
|
def src_normalize(self, src):
|
||||||
|
"""Normalizes transaction representation source data.
|
||||||
|
|
||||||
|
:param src: Transaction representation
|
||||||
|
:type src: dict
|
||||||
|
:rtype: dict
|
||||||
|
:returns: Transaction representation, normalized
|
||||||
|
"""
|
||||||
src = snake_and_camel(src)
|
src = snake_and_camel(src)
|
||||||
|
|
||||||
if isinstance(src.get('v'), str):
|
if isinstance(src.get('v'), str):
|
||||||
@ -430,16 +615,40 @@ class Tx:
|
|||||||
|
|
||||||
|
|
||||||
def apply_receipt(self, rcpt):
|
def apply_receipt(self, rcpt):
|
||||||
|
"""Apply receipt data to transaction object.
|
||||||
|
|
||||||
|
Effect is the same as passing a receipt at construction.
|
||||||
|
|
||||||
|
:param rcpt: Receipt data
|
||||||
|
:type rcpt: dict
|
||||||
|
"""
|
||||||
rcpt = self.src_normalize(rcpt)
|
rcpt = self.src_normalize(rcpt)
|
||||||
logg.debug('rcpt {}'.format(rcpt))
|
logg.debug('rcpt {}'.format(rcpt))
|
||||||
|
|
||||||
|
tx_hash = add_0x(rcpt['transaction_hash'])
|
||||||
|
if rcpt['transaction_hash'] != add_0x(self.hash):
|
||||||
|
raise ValueError('rcpt hash {} does not match transaction hash {}'.format(rcpt['transaction_hash'], self.hash))
|
||||||
|
|
||||||
|
block_hash = add_0x(rcpt['block_hash'])
|
||||||
|
if self.block != None:
|
||||||
|
if block_hash != add_0x(self.block.hash):
|
||||||
|
raise ValueError('rcpt block hash {} does not match transaction block hash {}'.format(rcpt['block_hash'], self.block.hash))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
status_number = int(rcpt['status'], 16)
|
status_number = int(rcpt['status'], 16)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
status_number = int(rcpt['status'])
|
status_number = int(rcpt['status'])
|
||||||
if status_number == 1:
|
if rcpt['block_number'] == None:
|
||||||
self.status = Status.SUCCESS
|
self.status = Status.PENDING
|
||||||
elif status_number == 0:
|
else:
|
||||||
self.status = Status.ERROR
|
if status_number == 1:
|
||||||
|
self.status = Status.SUCCESS
|
||||||
|
elif status_number == 0:
|
||||||
|
self.status = Status.ERROR
|
||||||
|
try:
|
||||||
|
self.tx_index = int(rcpt['transaction_index'], 16)
|
||||||
|
except TypeError:
|
||||||
|
self.tx_index = int(rcpt['transaction_index'])
|
||||||
# TODO: replace with rpc receipt/transaction translator when available
|
# TODO: replace with rpc receipt/transaction translator when available
|
||||||
contract_address = rcpt.get('contractAddress')
|
contract_address = rcpt.get('contractAddress')
|
||||||
if contract_address == None:
|
if contract_address == None:
|
||||||
@ -452,20 +661,43 @@ class Tx:
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
self.gas_used = int(rcpt['gasUsed'])
|
self.gas_used = int(rcpt['gasUsed'])
|
||||||
|
|
||||||
|
self.__rcpt_block_hash = rcpt['block_hash']
|
||||||
|
|
||||||
|
|
||||||
def apply_block(self, block):
|
def apply_block(self, block):
|
||||||
#block_src = self.src_normalize(block_src)
|
"""Apply block to transaction object.
|
||||||
|
|
||||||
|
:param block: Block object
|
||||||
|
:type block: chainlib.block.Block
|
||||||
|
"""
|
||||||
|
if self.__rcpt_block_hash != None:
|
||||||
|
if block.hash != self.__rcpt_block_hash:
|
||||||
|
raise ValueError('block hash {} does not match already applied receipt block hash {}'.format(block.hash, self.__rcpt_block_hash))
|
||||||
|
self.index = block.get_tx(self.hash)
|
||||||
self.block = block
|
self.block = block
|
||||||
|
|
||||||
|
|
||||||
def generate_wire(self, chain_spec):
|
def generate_wire(self, chain_spec):
|
||||||
b = pack(self.src(), chain_spec)
|
"""Generate transaction wire format.
|
||||||
self.wire = add_0x(b.hex())
|
|
||||||
|
:param chain_spec: Chain spec to interpret EIP155 v value.
|
||||||
|
:type chain_spec: chainlib.chain.ChainSpec
|
||||||
|
:rtype: str
|
||||||
|
:returns: Wire format, in hex
|
||||||
|
"""
|
||||||
|
if self.wire == None:
|
||||||
|
b = pack(self.src(), chain_spec)
|
||||||
|
self.wire = add_0x(b.hex())
|
||||||
|
return self.wire
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_src(src, block=None):
|
def from_src(src, block=None, rcpt=None):
|
||||||
return Tx(src, block=block)
|
"""Creates a new Tx object.
|
||||||
|
|
||||||
|
Alias of constructor.
|
||||||
|
"""
|
||||||
|
return Tx(src, block=block, rcpt=rcpt)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -480,13 +712,18 @@ class Tx:
|
|||||||
|
|
||||||
|
|
||||||
def to_human(self):
|
def to_human(self):
|
||||||
|
"""Human-readable string dump of transaction contents.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
:returns: Contents
|
||||||
|
"""
|
||||||
s = """hash {}
|
s = """hash {}
|
||||||
from {}
|
from {}
|
||||||
to {}
|
to {}
|
||||||
value {}
|
value {}
|
||||||
nonce {}
|
nonce {}
|
||||||
gasPrice {}
|
gas_price {}
|
||||||
gasLimit {}
|
gas_limit {}
|
||||||
input {}
|
input {}
|
||||||
""".format(
|
""".format(
|
||||||
self.hash,
|
self.hash,
|
||||||
@ -500,13 +737,24 @@ input {}
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.status != Status.PENDING:
|
if self.status != Status.PENDING:
|
||||||
s += """gasUsed {}
|
s += """gas_used {}
|
||||||
""".format(
|
""".format(
|
||||||
self.gas_used,
|
self.gas_used,
|
||||||
)
|
)
|
||||||
|
|
||||||
s += 'status ' + self.status.name + '\n'
|
s += 'status ' + self.status.name + '\n'
|
||||||
|
|
||||||
|
if self.block != None:
|
||||||
|
s += """block_number {}
|
||||||
|
block_hash {}
|
||||||
|
tx_index {}
|
||||||
|
""".format(
|
||||||
|
self.block.number,
|
||||||
|
self.block.hash,
|
||||||
|
self.tx_index,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if self.contract != None:
|
if self.contract != None:
|
||||||
s += """contract {}
|
s += """contract {}
|
||||||
""".format(
|
""".format(
|
||||||
@ -520,4 +768,3 @@ input {}
|
|||||||
)
|
)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ address = add_0x(address_bytes.hex())
|
|||||||
rpc_provider = os.environ.get('RPC_PROVIDER', 'http://localhost:8545')
|
rpc_provider = os.environ.get('RPC_PROVIDER', 'http://localhost:8545')
|
||||||
rpc = EthHTTPConnection(rpc_provider)
|
rpc = EthHTTPConnection(rpc_provider)
|
||||||
o = balance(address)
|
o = balance(address)
|
||||||
|
print(o)
|
||||||
r = rpc.do(o)
|
r = rpc.do(o)
|
||||||
|
|
||||||
clean_address = strip_0x(address)
|
clean_address = strip_0x(address)
|
||||||
|
@ -3,22 +3,23 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from chainlib.jsonrpc import jsonrpc_template
|
from chainlib.jsonrpc import JSONRPCRequest
|
||||||
from chainlib.eth.connection import EthHTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
|
|
||||||
# set up node connection and execute rpc call
|
# set up node connection and execute rpc call
|
||||||
rpc_provider = os.environ.get('RPC_PROVIDER', 'http://localhost:8545')
|
rpc_provider = os.environ.get('RPC_PROVIDER', 'http://localhost:8545')
|
||||||
rpc = EthHTTPConnection(rpc_provider)
|
conn = EthHTTPConnection(rpc_provider)
|
||||||
|
|
||||||
# check the connection
|
# check the connection
|
||||||
if not rpc.check():
|
if not conn.check():
|
||||||
sys.stderr.write('node {} not usable\n'.format(rpc_provider))
|
sys.stderr.write('node {} not usable\n'.format(rpc_provider))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# build and send rpc call
|
# build and send rpc call
|
||||||
o = jsonrpc_template()
|
g = JSONRPCRequest()
|
||||||
|
o = g.template()
|
||||||
o['method'] = 'eth_blockNumber'
|
o['method'] = 'eth_blockNumber'
|
||||||
r = rpc.do(o)
|
r = conn.do(o)
|
||||||
|
|
||||||
# interpret result for humans
|
# interpret result for humans
|
||||||
try:
|
try:
|
||||||
|
41
example/jsonrpc_factory.py
Normal file
41
example/jsonrpc_factory.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# standard imports
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.jsonrpc import JSONRPCRequest
|
||||||
|
from chainlib.chain import ChainSpec
|
||||||
|
from chainlib.connection import (
|
||||||
|
JSONRPCHTTPConnection,
|
||||||
|
RPCConnection,
|
||||||
|
ConnType,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# set up node connection and execute rpc call
|
||||||
|
rpc_provider = os.environ.get('RPC_PROVIDER', 'http://localhost:8545')
|
||||||
|
RPCConnection.register_constructor(ConnType.HTTP, JSONRPCHTTPConnection)
|
||||||
|
|
||||||
|
tag = 'baz'
|
||||||
|
chain_spec = ChainSpec('foo', 'bar', 42, tag=tag)
|
||||||
|
RPCConnection.register_location(rpc_provider, chain_spec, tag='default')
|
||||||
|
conn = RPCConnection.connect(chain_spec, 'default')
|
||||||
|
|
||||||
|
# check the connection
|
||||||
|
if not conn.check():
|
||||||
|
sys.stderr.write('node {} not usable\n'.format(rpc_provider))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# build and send rpc call
|
||||||
|
g = JSONRPCRequest()
|
||||||
|
o = g.template()
|
||||||
|
o['method'] = 'eth_blockNumber'
|
||||||
|
r = conn.do(o)
|
||||||
|
|
||||||
|
# interpret result for humans
|
||||||
|
try:
|
||||||
|
block_number = int(r, 10)
|
||||||
|
except ValueError:
|
||||||
|
block_number = int(r, 16)
|
||||||
|
|
||||||
|
print('block number {}'.format(block_number))
|
@ -21,13 +21,21 @@ from chainlib.eth.tx import (
|
|||||||
)
|
)
|
||||||
from chainlib.error import JSONRPCException
|
from chainlib.error import JSONRPCException
|
||||||
|
|
||||||
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
|
||||||
# eth transactions need an explicit chain parameter as part of their signature
|
# eth transactions need an explicit chain parameter as part of their signature
|
||||||
chain_spec = ChainSpec.from_chain_str('evm:ethereum:1')
|
chain_spec = ChainSpec.from_chain_str('evm:ethereum:1')
|
||||||
|
|
||||||
# create keystore and signer
|
# create keystore and signer
|
||||||
keystore = DictKeystore()
|
keystore = DictKeystore()
|
||||||
signer = EIP155Signer(keystore)
|
signer = EIP155Signer(keystore)
|
||||||
sender_address = keystore.new()
|
|
||||||
|
# import private key for sender
|
||||||
|
sender_keystore_file = os.path.join(script_dir, '..', 'tests', 'testdata', 'keystore', 'UTC--2021-01-08T17-18-44.521011372Z--eb3907ecad74a0013c259d5874ae7f22dcbcc95c')
|
||||||
|
sender_address = keystore.import_keystore_file(sender_keystore_file)
|
||||||
|
|
||||||
|
# create a new address to use as recipient
|
||||||
recipient_address = keystore.new()
|
recipient_address = keystore.new()
|
||||||
|
|
||||||
# set up node connection
|
# set up node connection
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
crypto-dev-signer~=0.4.14b6
|
crypto-dev-signer>=0.4.14b7,<=0.4.14
|
||||||
pysha3==1.0.2
|
pysha3==1.0.2
|
||||||
hexathon~=0.0.1a7
|
hexathon~=0.0.1a8
|
||||||
websocket-client==0.57.0
|
websocket-client==0.57.0
|
||||||
potaahto~=0.0.1a1
|
potaahto~=0.0.1a1
|
||||||
chainlib==0.0.5a1
|
chainlib==0.0.8a2
|
||||||
|
confini>=0.4.1a1,<0.5.0
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = chainlib-eth
|
name = chainlib-eth
|
||||||
version = 0.0.5a1
|
version = 0.0.8a2
|
||||||
description = Ethereum implementation of the chainlib interface
|
description = Ethereum implementation of the chainlib interface
|
||||||
author = Louis Holbrook
|
author = Louis Holbrook
|
||||||
author_email = dev@holbrook.no
|
author_email = dev@holbrook.no
|
||||||
@ -24,6 +24,7 @@ licence_files =
|
|||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
|
include_package_data = True
|
||||||
python_requires = >= 3.6
|
python_requires = >= 3.6
|
||||||
packages =
|
packages =
|
||||||
chainlib.eth
|
chainlib.eth
|
||||||
@ -40,4 +41,5 @@ console_scripts =
|
|||||||
eth-get = chainlib.eth.runnable.get:main
|
eth-get = chainlib.eth.runnable.get:main
|
||||||
eth-decode = chainlib.eth.runnable.decode:main
|
eth-decode = chainlib.eth.runnable.decode:main
|
||||||
eth-info = chainlib.eth.runnable.info:main
|
eth-info = chainlib.eth.runnable.info:main
|
||||||
|
eth-nonce = chainlib.eth.runnable.count:main
|
||||||
eth = chainlib.eth.runnable.info:main
|
eth = chainlib.eth.runnable.info:main
|
||||||
|
19
tests/test_block.py
Normal file
19
tests/test_block.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# standard imports
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from chainlib.eth.jsonrpc import to_blockheight_param
|
||||||
|
|
||||||
|
|
||||||
|
class TestBlock(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_blockheight_param(self):
|
||||||
|
self.assertEqual(to_blockheight_param('latest'), 'latest')
|
||||||
|
self.assertEqual(to_blockheight_param(0), 'latest')
|
||||||
|
self.assertEqual(to_blockheight_param('pending'), 'pending')
|
||||||
|
self.assertEqual(to_blockheight_param(-1), 'pending')
|
||||||
|
self.assertEqual(to_blockheight_param(1), '0x0000000000000001')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -28,6 +28,7 @@ from hexathon import (
|
|||||||
strip_0x,
|
strip_0x,
|
||||||
add_0x,
|
add_0x,
|
||||||
)
|
)
|
||||||
|
from chainlib.eth.block import Block
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@ -35,6 +36,7 @@ logg = logging.getLogger()
|
|||||||
|
|
||||||
class TxTestCase(EthTesterCase):
|
class TxTestCase(EthTesterCase):
|
||||||
|
|
||||||
|
|
||||||
def test_tx_reciprocal(self):
|
def test_tx_reciprocal(self):
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
gas_oracle = RPCGasOracle(self.rpc)
|
gas_oracle = RPCGasOracle(self.rpc)
|
||||||
@ -75,5 +77,70 @@ class TxTestCase(EthTesterCase):
|
|||||||
logg.debug('r {}'.format(tx_signed_raw_bytes_recovered.hex()))
|
logg.debug('r {}'.format(tx_signed_raw_bytes_recovered.hex()))
|
||||||
self.assertEqual(tx_signed_raw_bytes, tx_signed_raw_bytes_recovered)
|
self.assertEqual(tx_signed_raw_bytes, tx_signed_raw_bytes_recovered)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_block(self):
|
||||||
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
|
gas_oracle = RPCGasOracle(self.rpc)
|
||||||
|
c = Gas(signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle, chain_spec=self.chain_spec)
|
||||||
|
(tx_hash_hex, o) = c.create(self.accounts[0], self.accounts[1], 1024, tx_format=TxFormat.RLP_SIGNED)
|
||||||
|
tx_data = unpack(bytes.fromhex(strip_0x(o)), self.chain_spec)
|
||||||
|
|
||||||
|
block_hash = os.urandom(32).hex()
|
||||||
|
block = Block({
|
||||||
|
'hash': block_hash,
|
||||||
|
'number': 42,
|
||||||
|
'timestamp': 13241324,
|
||||||
|
'transactions': [],
|
||||||
|
})
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
tx = Tx(tx_data, block=block)
|
||||||
|
|
||||||
|
tx_unknown_hash = os.urandom(32).hex()
|
||||||
|
block.txs = [add_0x(tx_unknown_hash)]
|
||||||
|
block.txs.append(add_0x(tx_data['hash']))
|
||||||
|
tx = Tx(tx_data, block=block)
|
||||||
|
|
||||||
|
block.txs = [add_0x(tx_unknown_hash)]
|
||||||
|
block.txs.append(tx_data)
|
||||||
|
tx = Tx(tx_data, block=block)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_receipt(self):
|
||||||
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
|
gas_oracle = RPCGasOracle(self.rpc)
|
||||||
|
c = Gas(signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle, chain_spec=self.chain_spec)
|
||||||
|
(tx_hash_hex, o) = c.create(self.accounts[0], self.accounts[1], 1024, tx_format=TxFormat.RLP_SIGNED)
|
||||||
|
tx_data = unpack(bytes.fromhex(strip_0x(o)), self.chain_spec)
|
||||||
|
|
||||||
|
rcpt = {
|
||||||
|
'transaction_hash': os.urandom(32).hex(),
|
||||||
|
'block_hash': os.urandom(32).hex(),
|
||||||
|
'status': 1,
|
||||||
|
'block_number': 42,
|
||||||
|
'transaction_index': 1,
|
||||||
|
'logs': [],
|
||||||
|
'gas_used': 21000,
|
||||||
|
}
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
tx = Tx(tx_data, rcpt=rcpt)
|
||||||
|
|
||||||
|
rcpt['transaction_hash'] = tx_data['hash']
|
||||||
|
tx = Tx(tx_data, rcpt=rcpt)
|
||||||
|
|
||||||
|
block_hash = os.urandom(32).hex()
|
||||||
|
block = Block({
|
||||||
|
'hash': block_hash,
|
||||||
|
'number': 42,
|
||||||
|
'timestamp': 13241324,
|
||||||
|
'transactions': [],
|
||||||
|
})
|
||||||
|
block.txs = [add_0x(tx_data['hash'])]
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
tx = Tx(tx_data, rcpt=rcpt, block=block)
|
||||||
|
|
||||||
|
rcpt['block_hash'] = block.hash
|
||||||
|
tx = Tx(tx_data, rcpt=rcpt, block=block)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
Reference in New Issue
Block a user