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
|
||||
* 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
|
||||
from hexathon import (
|
||||
strip_0x,
|
||||
@ -11,3 +11,34 @@ from crypto_dev_signer.encoding import (
|
||||
)
|
||||
|
||||
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.eth.tx import Tx
|
||||
from chainlib.block import Block as BaseBlock
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
even,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from chainlib.eth.tx import Tx
|
||||
|
||||
|
||||
def block_latest(id_generator=None):
|
||||
"""Implements chainlib.interface.ChainInterface method
|
||||
"""
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
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):
|
||||
"""Implements chainlib.interface.ChainInterface method
|
||||
"""
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
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):
|
||||
"""Implements chainlib.interface.ChainInterface method
|
||||
"""
|
||||
nhx = add_0x(even(hex(n)[2:]))
|
||||
j = JSONRPCRequest(id_generator)
|
||||
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):
|
||||
"""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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_getBlockTransactionCountByHash'
|
||||
@ -42,7 +60,13 @@ def transaction_count(block_hash, id_generator=None):
|
||||
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):
|
||||
self.hash = src['hash']
|
||||
@ -58,22 +82,21 @@ class Block:
|
||||
self.timestamp = int(src['timestamp'])
|
||||
|
||||
|
||||
def src(self):
|
||||
return self.block_src
|
||||
def get_tx(self, tx_hash):
|
||||
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):
|
||||
"""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)
|
||||
o = j.template()
|
||||
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):
|
||||
"""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):
|
||||
"""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()
|
||||
i = 0
|
||||
while True:
|
||||
@ -59,13 +84,13 @@ class EthHTTPConnection(JSONRPCHTTPConnection):
|
||||
)
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
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'))
|
||||
r = json.load(res)
|
||||
|
||||
e = jsonrpc_result(r, error_parser)
|
||||
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'])))
|
||||
if strip_0x(e['status']) == '00':
|
||||
raise RevertEthException(tx_hash_hex)
|
||||
@ -80,7 +105,17 @@ class EthHTTPConnection(JSONRPCHTTPConnection):
|
||||
i += 1
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return 'ETH HTTP JSONRPC'
|
||||
|
||||
|
||||
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)
|
||||
req = j.template()
|
||||
req['method'] = 'net_version'
|
||||
@ -89,12 +124,29 @@ class EthHTTPConnection(JSONRPCHTTPConnection):
|
||||
|
||||
|
||||
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):
|
||||
"""See EthHTTPConnection. Not yet implemented for unix socket.
|
||||
"""
|
||||
raise NotImplementedError('Not yet implemented for unix socket')
|
||||
|
||||
|
||||
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()
|
||||
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
|
||||
@ -110,27 +162,66 @@ def sign_transaction_to_rlp(chain_spec, doer, tx):
|
||||
|
||||
|
||||
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)
|
||||
return doer(o)
|
||||
|
||||
|
||||
class EthUnixSignerConnection(EthUnixConnection):
|
||||
"""Connects rpc signer methods to Unix socket connection interface
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def sign_message(self, tx):
|
||||
return sign_message(self.do, tx)
|
||||
def sign_message(self, msg):
|
||||
"""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):
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -2,4 +2,5 @@ ZERO_ADDRESS = '0x{:040x}'.format(0)
|
||||
ZERO_CONTENT = '0x{:064x}'.format(0)
|
||||
MINIMUM_FEE_UNITS = 21000
|
||||
MINIMUM_FEE_PRICE = 1000000000
|
||||
DEFAULT_FEE_LIMIT = 8000000
|
||||
MAX_UINT = int('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)
|
||||
|
@ -15,14 +15,14 @@ from chainlib.block import BlockSpec
|
||||
from chainlib.jsonrpc import JSONRPCRequest
|
||||
from .address import to_checksum_address
|
||||
|
||||
#logg = logging.getLogger(__name__)
|
||||
logg = logging.getLogger()
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
re_method = r'^[a-zA-Z0-9_]+$'
|
||||
|
||||
class ABIContractType(enum.Enum):
|
||||
|
||||
"""Data types used by ABI encoders
|
||||
"""
|
||||
BYTES32 = 'bytes32'
|
||||
BYTES4 = 'bytes4'
|
||||
UINT256 = 'uint256'
|
||||
@ -36,14 +36,16 @@ dynamic_contract_types = [
|
||||
|
||||
|
||||
class ABIContract:
|
||||
|
||||
"""Base class for Ethereum smart contract encoder
|
||||
"""
|
||||
def __init__(self):
|
||||
self.types = []
|
||||
self.contents = []
|
||||
|
||||
|
||||
class ABIMethodEncoder(ABIContract):
|
||||
|
||||
"""Generate ABI method signatures from method signature string.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(ABIMethodEncoder, self).__init__()
|
||||
self.method_name = None
|
||||
@ -51,6 +53,12 @@ class ABIMethodEncoder(ABIContract):
|
||||
|
||||
|
||||
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:
|
||||
raise ValueError('Invalid method {}, must match regular expression {}'.format(re_method))
|
||||
self.method_name = m
|
||||
@ -58,12 +66,26 @@ class ABIMethodEncoder(ABIContract):
|
||||
|
||||
|
||||
def get_method(self):
|
||||
"""Return currently set method signature string.
|
||||
|
||||
:rtype: str
|
||||
:returns: Method signature
|
||||
"""
|
||||
if self.method_name == None:
|
||||
return ''
|
||||
return '{}({})'.format(self.method_name, ','.join(self.method_contents))
|
||||
|
||||
|
||||
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:
|
||||
raise AttributeError('method name must be set before adding types')
|
||||
if not isinstance(v, ABIContractType):
|
||||
@ -78,9 +100,16 @@ class ABIMethodEncoder(ABIContract):
|
||||
|
||||
|
||||
class ABIContractDecoder(ABIContract):
|
||||
|
||||
"""Decode serialized ABI contract input data to corresponding python primitives.
|
||||
"""
|
||||
|
||||
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):
|
||||
raise TypeError('method type not valid; expected {}, got {}'.format(type(ABIContractType).__name__, type(v).__name__))
|
||||
self.types.append(v.value)
|
||||
@ -88,32 +117,74 @@ class ABIContractDecoder(ABIContract):
|
||||
|
||||
|
||||
def val(self, v):
|
||||
"""Add value to value array.
|
||||
|
||||
:param v: Value, in hex
|
||||
:type v: str
|
||||
"""
|
||||
self.contents.append(v)
|
||||
logg.debug('content is now {}'.format(self.contents))
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def bytes32(self, v):
|
||||
"""Parse value as bytes32.
|
||||
|
||||
:param v: Value, in hex
|
||||
:type v: str
|
||||
:rtype: str
|
||||
:returns: Value, in hex
|
||||
"""
|
||||
return 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))
|
||||
|
||||
|
||||
def boolean(self, v):
|
||||
"""Alias of chainlib.eth.contract.ABIContractDecoder.bool
|
||||
"""
|
||||
return bool(self.uint256(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:]
|
||||
return to_checksum_address(a)
|
||||
|
||||
|
||||
def string(self, v):
|
||||
"""Parse value as string.
|
||||
|
||||
:param v: Value, in hex
|
||||
:type v: str
|
||||
:rtype: str
|
||||
:returns: Value
|
||||
"""
|
||||
s = strip_0x(v)
|
||||
b = bytes.fromhex(s)
|
||||
cursor = 0
|
||||
@ -131,18 +202,23 @@ class ABIContractDecoder(ABIContract):
|
||||
|
||||
|
||||
def decode(self):
|
||||
"""Apply decoder on value array using argument type array.
|
||||
|
||||
:rtype: list
|
||||
:returns: List of decoded values
|
||||
"""
|
||||
r = []
|
||||
logg.debug('contents {}'.format(self.contents))
|
||||
for i in range(len(self.types)):
|
||||
m = getattr(self, self.types[i])
|
||||
s = self.contents[i]
|
||||
logg.debug('{} {} {} {} {}'.format(i, m, self.types[i], self.contents[i], s))
|
||||
#r.append(m(s.hex()))
|
||||
r.append(m(s))
|
||||
return r
|
||||
|
||||
|
||||
def get(self):
|
||||
"""Alias of chainlib.eth.contract.ABIContractDecoder.decode
|
||||
"""
|
||||
return self.decode()
|
||||
|
||||
|
||||
@ -151,7 +227,10 @@ class ABIContractDecoder(ABIContract):
|
||||
|
||||
|
||||
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):
|
||||
super(ABIContractLogDecoder, self).__init__()
|
||||
self.method_name = None
|
||||
@ -159,20 +238,45 @@ class ABIContractLogDecoder(ABIMethodEncoder, ABIContractDecoder):
|
||||
|
||||
|
||||
def topic(self, event):
|
||||
"""Set topic to match.
|
||||
|
||||
:param event: Topic name
|
||||
:type event: str
|
||||
"""
|
||||
self.method(event)
|
||||
|
||||
|
||||
def get_method_signature(self):
|
||||
"""Generate topic signature from set topic.
|
||||
|
||||
:rtype: str
|
||||
:returns: Topic signature, in hex
|
||||
"""
|
||||
s = self.get_method()
|
||||
return keccak256_string_to_hex(s)
|
||||
|
||||
|
||||
def typ(self, v):
|
||||
"""Add type to event argument array.
|
||||
|
||||
:param v: Type
|
||||
:type v: chainlib.eth.contract.ABIContractType
|
||||
"""
|
||||
super(ABIContractLogDecoder, self).typ(v)
|
||||
self.types.append(v.value)
|
||||
|
||||
|
||||
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()
|
||||
if topics[0] != t:
|
||||
raise ValueError('topic mismatch')
|
||||
@ -189,6 +293,11 @@ class ABIContractEncoder(ABIMethodEncoder):
|
||||
|
||||
|
||||
def uint256(self, v):
|
||||
"""Encode value to uint256 and add to input value vector.
|
||||
|
||||
:param v: Integer value
|
||||
:type v: int
|
||||
"""
|
||||
v = int(v)
|
||||
b = v.to_bytes(32, 'big')
|
||||
self.contents.append(b.hex())
|
||||
@ -197,28 +306,52 @@ class ABIContractEncoder(ABIMethodEncoder):
|
||||
|
||||
|
||||
def bool(self, v):
|
||||
"""Alias of chainlib.eth.contract.ABIContractEncoder.boolean.
|
||||
"""
|
||||
return self.boolean(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):
|
||||
return self.uint256(1)
|
||||
return self.uint256(0)
|
||||
|
||||
|
||||
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.types.append(ABIContractType.ADDRESS)
|
||||
self.__log_latest(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.types.append(ABIContractType.BYTES32)
|
||||
self.__log_latest(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.types.append(ABIContractType.BYTES4)
|
||||
self.__log_latest(v)
|
||||
@ -226,6 +359,11 @@ class ABIContractEncoder(ABIMethodEncoder):
|
||||
|
||||
|
||||
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')
|
||||
l = len(b)
|
||||
contents = l.to_bytes(32, 'big')
|
||||
@ -239,6 +377,16 @@ class ABIContractEncoder(ABIMethodEncoder):
|
||||
|
||||
|
||||
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__
|
||||
if typ == 'str':
|
||||
v = strip_0x(v)
|
||||
@ -260,8 +408,9 @@ class ABIContractEncoder(ABIMethodEncoder):
|
||||
self.contents.append(v.ljust(64, '0'))
|
||||
|
||||
|
||||
|
||||
def get_method_signature(self):
|
||||
"""Return abi encoded signature of currently set method.
|
||||
"""
|
||||
s = self.get_method()
|
||||
if s == '':
|
||||
return s
|
||||
@ -269,6 +418,11 @@ class ABIContractEncoder(ABIMethodEncoder):
|
||||
|
||||
|
||||
def get_contents(self):
|
||||
"""Encode value array.
|
||||
|
||||
:rtype: str
|
||||
:returns: ABI encoded values, in hex
|
||||
"""
|
||||
direct_contents = ''
|
||||
pointer_contents = ''
|
||||
l = len(self.types)
|
||||
@ -291,10 +445,19 @@ class ABIContractEncoder(ABIMethodEncoder):
|
||||
|
||||
|
||||
def get(self):
|
||||
"""Alias of chainlib.eth.contract.ABIContractEncoder.encode
|
||||
"""
|
||||
return self.encode()
|
||||
|
||||
|
||||
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()
|
||||
c = self.get_contents()
|
||||
return m + c
|
||||
@ -306,6 +469,13 @@ class ABIContractEncoder(ABIMethodEncoder):
|
||||
|
||||
|
||||
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.typ(typ)
|
||||
d.val(v)
|
||||
@ -314,6 +484,17 @@ def abi_decode_single(typ, v):
|
||||
|
||||
|
||||
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
|
||||
if block_spec == BlockSpec.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
|
||||
from chainlib.error import ExecutionError
|
||||
|
||||
|
||||
class EthException(Exception):
|
||||
"""Base class for all Ethereum related errors.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RevertEthException(EthException, ExecutionError):
|
||||
"""Raised when an rpc call or transaction reverts.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class NotFoundEthException(EthException):
|
||||
"""Raised when rpc query is made against an identifier that is not known by the node.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RequestMismatchException(EthException):
|
||||
"""Raised when a request data parser is given unexpected input data.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DefaultErrorParser:
|
||||
|
||||
"""Generate eth specific exception for the default json-rpc query error parser.
|
||||
"""
|
||||
def translate(self, error):
|
||||
return EthException('default parser code {}'.format(error))
|
||||
|
@ -1,7 +1,7 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
# external imports
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
@ -16,6 +16,8 @@ from chainlib.eth.tx import (
|
||||
TxFormat,
|
||||
raw,
|
||||
)
|
||||
from chainlib.eth.jsonrpc import to_blockheight_param
|
||||
from chainlib.block import BlockSpec
|
||||
from chainlib.eth.constant import (
|
||||
MINIMUM_FEE_UNITS,
|
||||
)
|
||||
@ -24,22 +26,48 @@ logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_gasPrice'
|
||||
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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_getBalance'
|
||||
o['params'].append(address)
|
||||
o['params'].append('latest')
|
||||
height = to_blockheight_param(height)
|
||||
o['params'].append(height)
|
||||
return j.finalize(o)
|
||||
|
||||
|
||||
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:
|
||||
r = int(balance, 10)
|
||||
except ValueError:
|
||||
@ -48,10 +76,29 @@ def parse_balance(balance):
|
||||
|
||||
|
||||
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['value'] = value
|
||||
if data != None:
|
||||
tx['data'] = data
|
||||
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
||||
tx_raw = self.signer.sign_transaction_to_rlp(txe)
|
||||
tx_raw_hex = add_0x(tx_raw.hex())
|
||||
@ -68,6 +115,17 @@ class Gas(TxFactory):
|
||||
|
||||
|
||||
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):
|
||||
self.conn = conn
|
||||
@ -76,7 +134,20 @@ class RPCGasOracle:
|
||||
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
|
||||
if self.conn != None:
|
||||
o = price(id_generator=self.id_generator)
|
||||
@ -93,13 +164,39 @@ class 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):
|
||||
super(RPCPureGasOracle, self).__init__(conn, code_callback=code_callback, min_price=0, id_generator=id_generator)
|
||||
|
||||
|
||||
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):
|
||||
self.conn = None
|
||||
self.code_callback = None
|
||||
@ -117,6 +214,8 @@ class OverrideGasOracle(RPCGasOracle):
|
||||
|
||||
|
||||
def get_gas(self, code=None):
|
||||
"""See chainlib.eth.gas.RPCGasOracle.
|
||||
"""
|
||||
r = None
|
||||
fee_units = None
|
||||
fee_price = None
|
||||
|
@ -14,3 +14,31 @@
|
||||
|
||||
#106 Timeout Should be used when an action timedout.
|
||||
#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:
|
||||
|
||||
"""Helper for Ethereum receipt log bloom filters.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.content = bytearray(256)
|
||||
|
||||
|
||||
def add(self, element):
|
||||
"""Add topic element to filter.
|
||||
|
||||
:param element: Topic element
|
||||
:type element: bytes
|
||||
"""
|
||||
if not isinstance(element, bytes):
|
||||
raise ValueError('element must be bytes')
|
||||
h = sha3.keccak_256()
|
||||
|
@ -1,4 +1,4 @@
|
||||
# third-party imports
|
||||
# external imports
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
@ -8,17 +8,40 @@ from hexathon import (
|
||||
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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_getTransactionCount'
|
||||
o['params'].append(address)
|
||||
o['params'].append('pending')
|
||||
if confirmed:
|
||||
o['params'].append('latest')
|
||||
else:
|
||||
o['params'].append('pending')
|
||||
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):
|
||||
self.address = address
|
||||
self.id_generator = id_generator
|
||||
@ -26,23 +49,45 @@ class NonceOracle:
|
||||
|
||||
|
||||
def get_nonce(self):
|
||||
"""Load initial nonce value.
|
||||
"""
|
||||
raise NotImplementedError('Class must be extended')
|
||||
|
||||
|
||||
def next_nonce(self):
|
||||
"""Return next nonce value and advance.
|
||||
|
||||
:rtype: int
|
||||
:returns: Next nonce for address.
|
||||
"""
|
||||
n = self.nonce
|
||||
self.nonce += 1
|
||||
return n
|
||||
|
||||
|
||||
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):
|
||||
self.conn = conn
|
||||
super(RPCNonceOracle, self).__init__(address, id_generator=id_generator)
|
||||
|
||||
|
||||
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)
|
||||
r = self.conn.do(o)
|
||||
n = strip_0x(r)
|
||||
@ -50,14 +95,28 @@ class RPCNonceOracle(NonceOracle):
|
||||
|
||||
|
||||
class OverrideNonceOracle(NonceOracle):
|
||||
"""Manually set initial nonce value.
|
||||
|
||||
def __init__(self, address, nonce):
|
||||
self.nonce = nonce
|
||||
super(OverrideNonceOracle, self).__init__(address)
|
||||
:param address: Address to retireve nonce for, in hex
|
||||
:type address: str
|
||||
: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):
|
||||
return self.nonce
|
||||
"""Returns initial nonce value set at object construction.
|
||||
|
||||
:rtype: int
|
||||
:returns: Initial nonce value.
|
||||
"""
|
||||
return self.initial_nonce
|
||||
|
||||
|
||||
DefaultNonceOracle = RPCNonceOracle
|
||||
|
@ -17,7 +17,7 @@ from chainlib.connection import (
|
||||
from chainlib.eth.unittest.ethtester import create_tester_signer
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
|
||||
logg = logging.getLogger() #__name__)
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
@ -37,13 +37,6 @@ def call_sender(
|
||||
eth_accounts,
|
||||
):
|
||||
return eth_accounts[0]
|
||||
#
|
||||
#
|
||||
#@pytest.fixture(scope='function')
|
||||
#def eth_signer(
|
||||
# init_eth_tester,
|
||||
# ):
|
||||
# return init_eth_tester
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
|
@ -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
|
||||
|
||||
# standard imports
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
# external imports
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
even,
|
||||
)
|
||||
import sha3
|
||||
|
||||
# local imports
|
||||
from chainlib.eth.address import to_checksum
|
||||
import chainlib.eth.cli
|
||||
from chainlib.eth.address import AddressChecksum
|
||||
from chainlib.jsonrpc import (
|
||||
jsonrpc_result,
|
||||
IntSequenceGenerator,
|
||||
@ -35,53 +24,37 @@ from chainlib.eth.gas import (
|
||||
balance,
|
||||
)
|
||||
from chainlib.chain import ChainSpec
|
||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
||||
if default_eth_provider == None:
|
||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
#config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('address', type=str, help='Account address')
|
||||
arg_flags = chainlib.eth.cli.argflag_std_read
|
||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||
argparser.add_positional('address', type=str, help='Ethereum address of recipient')
|
||||
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:
|
||||
logg.setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logg.setLevel(logging.INFO)
|
||||
rpc = chainlib.eth.cli.Rpc()
|
||||
conn = rpc.connect_by_config(config)
|
||||
|
||||
rpc_id_generator = None
|
||||
if args.seq:
|
||||
rpc_id_generator = IntSequenceGenerator()
|
||||
|
||||
auth = None
|
||||
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
|
||||
from chainlib.auth import BasicAuth
|
||||
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
|
||||
conn = EthHTTPConnection(args.p, auth=auth)
|
||||
|
||||
gas_oracle = OverrideGasOracle(conn)
|
||||
|
||||
address = to_checksum(args.address)
|
||||
if not args.u and address != add_0x(args.address):
|
||||
raise ValueError('invalid checksum address')
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||
|
||||
def main():
|
||||
r = None
|
||||
decimals = 18
|
||||
|
||||
o = balance(address, id_generator=rpc_id_generator)
|
||||
o = balance(holder_address, id_generator=rpc.id_generator)
|
||||
r = conn.do(o)
|
||||
|
||||
hx = strip_0x(r)
|
||||
|
@ -4,12 +4,13 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
#import argparse
|
||||
import logging
|
||||
import select
|
||||
|
||||
# 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.tx import count
|
||||
from chainlib.chain import ChainSpec
|
||||
@ -21,63 +22,28 @@ from hexathon import add_0x
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
||||
if default_eth_provider == None:
|
||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||
|
||||
def stdin_arg():
|
||||
h = select.select([sys.stdin], [], [], 0)
|
||||
if len(h[0]) > 0:
|
||||
v = h[0][0].read()
|
||||
return v.rstrip()
|
||||
return None
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('address', nargs='?', type=str, default=stdin_arg(), help='Ethereum address of recipient')
|
||||
arg_flags = chainlib.eth.cli.argflag_std_read
|
||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||
argparser.add_positional('address', type=str, help='Ethereum address of recipient')
|
||||
args = argparser.parse_args()
|
||||
config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir)
|
||||
|
||||
if args.address == None:
|
||||
argparser.error('need first positional argument or value from stdin')
|
||||
holder_address = args.address
|
||||
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:
|
||||
logg.setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logg.setLevel(logging.INFO)
|
||||
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||
conn = rpc.connect_by_config(config)
|
||||
|
||||
|
||||
signer_address = None
|
||||
keystore = DictKeystore()
|
||||
if args.y != None:
|
||||
logg.debug('loading keystore file {}'.format(args.y))
|
||||
signer_address = keystore.import_keystore_file(args.y, passphrase)
|
||||
logg.debug('now have key for signer address {}'.format(signer_address))
|
||||
signer = EIP155Signer(keystore)
|
||||
|
||||
rpc_id_generator = None
|
||||
if args.seq:
|
||||
rpc_id_generator = IntSequenceGenerator()
|
||||
|
||||
auth = None
|
||||
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
|
||||
from chainlib.auth import BasicAuth
|
||||
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
|
||||
rpc = EthHTTPConnection(args.p, auth=auth)
|
||||
|
||||
def main():
|
||||
recipient = to_checksum(args.address)
|
||||
if not args.u and recipient != add_0x(args.address):
|
||||
raise ValueError('invalid checksum address')
|
||||
|
||||
o = count(recipient, id_generator=rpc_id_generator)
|
||||
r = rpc.do(o)
|
||||
o = count(holder_address, id_generator=rpc.id_generator)
|
||||
r = conn.do(o)
|
||||
count_result = None
|
||||
try:
|
||||
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
|
||||
|
||||
# standard imports
|
||||
@ -18,48 +9,29 @@ import logging
|
||||
import select
|
||||
|
||||
# external imports
|
||||
import chainlib.eth.cli
|
||||
from chainlib.eth.tx import unpack
|
||||
from chainlib.chain import ChainSpec
|
||||
|
||||
# local imports
|
||||
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
def stdin_arg(t=0):
|
||||
h = select.select([sys.stdin], [], [], t)
|
||||
if len(h[0]) > 0:
|
||||
v = h[0][0].read()
|
||||
return v.rstrip()
|
||||
return None
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-i', '--chain-id', dest='i', default='evm:ethereum:1', type=str, help='Numeric network id')
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('tx', type=str, nargs='?', default=stdin_arg(), help='hex-encoded signed raw transaction')
|
||||
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 = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||
argparser.add_positional('tx_data', type=str, help='Transaction data to decode')
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||
|
||||
def main():
|
||||
tx_raw = argp
|
||||
decode_for_puny_humans(tx_raw, chain_spec, sys.stdout)
|
||||
decode_for_puny_humans(args.tx_data, chain_spec, sys.stdout)
|
||||
|
||||
if __name__ == '__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
|
||||
|
||||
# standard imports
|
||||
@ -19,114 +10,53 @@ import logging
|
||||
import urllib
|
||||
|
||||
# external imports
|
||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from chainlib.eth.address import to_checksum
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.eth.connection import EthHTTPConnection
|
||||
from chainlib.jsonrpc import (
|
||||
JSONRPCRequest,
|
||||
IntSequenceGenerator,
|
||||
)
|
||||
from chainlib.eth.nonce import (
|
||||
RPCNonceOracle,
|
||||
OverrideNonceOracle,
|
||||
)
|
||||
from chainlib.eth.gas import (
|
||||
RPCGasOracle,
|
||||
OverrideGasOracle,
|
||||
Gas,
|
||||
)
|
||||
from chainlib.eth.gas import Gas
|
||||
from chainlib.eth.gas import balance as gas_balance
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||
import chainlib.eth.cli
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
||||
if default_eth_provider == None:
|
||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
|
||||
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||
argparser.add_argument('--nonce', type=int, help='override nonce')
|
||||
argparser.add_argument('--gas-price', dest='gas_price', type=int, help='override gas price')
|
||||
argparser.add_argument('--gas-limit', dest='gas_limit', type=int, help='override gas limit')
|
||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('-s', '--send', dest='s', action='store_true', help='Send to network')
|
||||
argparser.add_argument('recipient', type=str, help='ethereum address of recipient')
|
||||
argparser.add_argument('amount', type=int, help='gas value in wei')
|
||||
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.WALLET
|
||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||
argparser.add_argument('--data', type=str, help='Transaction data')
|
||||
argparser.add_positional('amount', type=int, help='Token amount to send')
|
||||
args = argparser.parse_args()
|
||||
|
||||
|
||||
if args.vv:
|
||||
logg.setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logg.setLevel(logging.INFO)
|
||||
extra_args = {
|
||||
'data': None,
|
||||
'amount': None,
|
||||
}
|
||||
#config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_config_dir=config_dir)
|
||||
config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args)
|
||||
|
||||
block_all = args.ww
|
||||
block_last = args.w or block_all
|
||||
|
||||
passphrase_env = 'ETH_PASSPHRASE'
|
||||
if args.env_prefix != None:
|
||||
passphrase_env = args.env_prefix + '_' + passphrase_env
|
||||
passphrase = os.environ.get(passphrase_env)
|
||||
if passphrase == None:
|
||||
logg.warning('no passphrase given')
|
||||
passphrase=''
|
||||
wallet = chainlib.eth.cli.Wallet()
|
||||
wallet.from_config(config)
|
||||
|
||||
signer_address = None
|
||||
keystore = DictKeystore()
|
||||
if args.y != None:
|
||||
logg.debug('loading keystore file {}'.format(args.y))
|
||||
signer_address = keystore.import_keystore_file(args.y, password=passphrase)
|
||||
logg.debug('now have key for signer address {}'.format(signer_address))
|
||||
signer = EIP155Signer(keystore)
|
||||
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||
conn = rpc.connect_by_config(config)
|
||||
|
||||
rpc_id_generator = None
|
||||
if args.seq:
|
||||
rpc_id_generator = IntSequenceGenerator()
|
||||
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||
|
||||
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)
|
||||
value = config.get('_AMOUNT')
|
||||
|
||||
nonce_oracle = None
|
||||
if args.nonce != None:
|
||||
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce, id_generator=rpc_id_generator)
|
||||
else:
|
||||
nonce_oracle = RPCNonceOracle(signer_address, conn, id_generator=rpc_id_generator)
|
||||
|
||||
gas_oracle = None
|
||||
if args.gas_price or args.gas_limit != None:
|
||||
gas_oracle = OverrideGasOracle(price=args.gas_price, limit=args.gas_limit, conn=conn, id_generator=rpc_id_generator)
|
||||
else:
|
||||
gas_oracle = RPCGasOracle(conn, id_generator=rpc_id_generator)
|
||||
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||
|
||||
value = args.amount
|
||||
|
||||
send = args.s
|
||||
|
||||
g = Gas(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||
send = config.true('_RPC_SEND')
|
||||
|
||||
|
||||
def balance(address, id_generator):
|
||||
@ -137,29 +67,34 @@ def balance(address, id_generator):
|
||||
|
||||
|
||||
def main():
|
||||
recipient = to_checksum(args.recipient)
|
||||
if not args.u and recipient != add_0x(args.recipient):
|
||||
signer = rpc.get_signer()
|
||||
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')
|
||||
|
||||
logg.info('gas transfer from {} to {} value {}'.format(signer_address, recipient, value))
|
||||
if logg.isEnabledFor(logging.DEBUG):
|
||||
try:
|
||||
sender_balance = balance(signer_address, rpc_id_generator)
|
||||
recipient_balance = balance(recipient, rpc_id_generator)
|
||||
sender_balance = balance(signer_address, rpc.id_generator)
|
||||
recipient_balance = balance(recipient, rpc.id_generator)
|
||||
logg.debug('sender {} balance before: {}'.format(signer_address, sender_balance))
|
||||
logg.debug('recipient {} balance before: {}'.format(recipient, recipient_balance))
|
||||
except urllib.error.URLError:
|
||||
pass
|
||||
|
||||
(tx_hash_hex, o) = g.create(signer_address, recipient, value, id_generator=rpc_id_generator)
|
||||
(tx_hash_hex, o) = g.create(signer_address, recipient, value, data=config.get('_DATA'), id_generator=rpc.id_generator)
|
||||
|
||||
if send:
|
||||
conn.do(o)
|
||||
if block_last:
|
||||
r = conn.wait(tx_hash_hex)
|
||||
if logg.isEnabledFor(logging.DEBUG):
|
||||
sender_balance = balance(signer_address, rpc_id_generator)
|
||||
recipient_balance = balance(recipient, rpc_id_generator)
|
||||
sender_balance = balance(signer_address, rpc.id_generator)
|
||||
recipient_balance = balance(recipient, rpc.id_generator)
|
||||
logg.debug('sender {} balance after: {}'.format(signer_address, sender_balance))
|
||||
logg.debug('recipient {} balance after: {}'.format(recipient, recipient_balance))
|
||||
if r['status'] == 0:
|
||||
@ -167,12 +102,13 @@ def main():
|
||||
sys.exit(1)
|
||||
print(tx_hash_hex)
|
||||
else:
|
||||
if logg.isEnabledFor(logging.INFO):
|
||||
#if logg.isEnabledFor(logging.INFO):
|
||||
if config.true('_RAW'):
|
||||
print(o['params'][0])
|
||||
else:
|
||||
io_str = io.StringIO()
|
||||
decode_for_puny_humans(o['params'][0], chain_spec, io_str)
|
||||
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
|
||||
|
||||
# standard imports
|
||||
@ -19,80 +10,56 @@ import enum
|
||||
import select
|
||||
|
||||
# external imports
|
||||
from potaahto.symbols import snake_and_camel
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
)
|
||||
import sha3
|
||||
|
||||
# local imports
|
||||
from chainlib.eth.address import to_checksum
|
||||
from chainlib.jsonrpc import (
|
||||
JSONRPCRequest,
|
||||
jsonrpc_result,
|
||||
IntSequenceGenerator,
|
||||
)
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.status import Status
|
||||
|
||||
# local imports
|
||||
from chainlib.eth.connection import EthHTTPConnection
|
||||
from chainlib.eth.tx import (
|
||||
Tx,
|
||||
pack,
|
||||
)
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.eth.block import Block
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.status import Status
|
||||
from chainlib.eth.address import (
|
||||
to_checksum_address,
|
||||
is_checksum_address,
|
||||
)
|
||||
from chainlib.eth.block import (
|
||||
Block,
|
||||
block_by_hash,
|
||||
)
|
||||
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')
|
||||
logg = logging.getLogger()
|
||||
|
||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
||||
if default_eth_provider == None:
|
||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||
|
||||
def stdin_arg(t=0):
|
||||
h = select.select([sys.stdin], [], [], t)
|
||||
if len(h[0]) > 0:
|
||||
v = h[0][0].read()
|
||||
return v.rstrip()
|
||||
return None
|
||||
|
||||
argparser = argparse.ArgumentParser('eth-get', description='display information about an Ethereum address or transaction', epilog='address/transaction can be provided as an argument or from standard input')
|
||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('--rlp', action='store_true', help='Display transaction as raw rlp')
|
||||
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
|
||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('item', nargs='?', default=stdin_arg(), type=str, help='Item to get information for (address og transaction)')
|
||||
arg_flags = chainlib.eth.cli.argflag_std_read
|
||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||
argparser.add_positional('item', type=str, help='Address or transaction to retrieve data for')
|
||||
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)
|
||||
rpc = chainlib.eth.cli.Rpc()
|
||||
conn = rpc.connect_by_config(config)
|
||||
|
||||
argp = args.item
|
||||
if argp == None:
|
||||
argp = stdin_arg(None)
|
||||
if argsp == None:
|
||||
argparser.error('need first positional argument or value from stdin')
|
||||
|
||||
rpc_id_generator = None
|
||||
if args.seq:
|
||||
rpc_id_generator = IntSequenceGenerator()
|
||||
|
||||
auth = None
|
||||
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
|
||||
from chainlib.auth import BasicAuth
|
||||
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
|
||||
conn = EthHTTPConnection(args.p, auth=auth)
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||
|
||||
item = add_0x(args.item)
|
||||
as_rlp = bool(args.rlp)
|
||||
|
||||
|
||||
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))
|
||||
sys.exit(1)
|
||||
|
||||
if as_rlp:
|
||||
if config.true('_RAW'):
|
||||
tx_src = Tx.src_normalize(tx_src)
|
||||
return pack(tx_src, chain_spec).hex()
|
||||
|
||||
@ -125,16 +92,24 @@ def get_transaction(conn, tx_hash, id_generator):
|
||||
tx = Tx(tx_src)
|
||||
if rcpt != None:
|
||||
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)
|
||||
return tx
|
||||
|
||||
|
||||
def get_address(conn, address, id_generator):
|
||||
|
||||
def get_address(conn, address, id_generator, height):
|
||||
j = JSONRPCRequest(id_generator=id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_getCode'
|
||||
o['params'].append(address)
|
||||
o['params'].append('latest')
|
||||
height = to_blockheight_param(height)
|
||||
o['params'].append(height)
|
||||
o = j.finalize(o)
|
||||
code = conn.do(o)
|
||||
|
||||
@ -146,11 +121,18 @@ def get_address(conn, address, id_generator):
|
||||
|
||||
|
||||
def main():
|
||||
address = item
|
||||
r = None
|
||||
if len(item) > 42:
|
||||
r = get_transaction(conn, item, rpc_id_generator).to_human()
|
||||
elif args.u or to_checksum_address(item):
|
||||
r = get_address(conn, item, rpc_id_generator)
|
||||
if len(address) > 42:
|
||||
r = get_transaction(conn, address, rpc.id_generator)
|
||||
if not config.true('_RAW'):
|
||||
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)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
# standard imports
|
||||
@ -17,19 +8,18 @@ import json
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
# external imports
|
||||
from chainlib.chain import ChainSpec
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
even,
|
||||
)
|
||||
import sha3
|
||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||
|
||||
# local imports
|
||||
from chainlib.eth.address import (
|
||||
to_checksum_address,
|
||||
is_checksum_address,
|
||||
)
|
||||
from chainlib.eth.address import AddressChecksum
|
||||
from chainlib.eth.chain import network_id
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
@ -43,79 +33,49 @@ from chainlib.eth.gas import (
|
||||
balance,
|
||||
price,
|
||||
)
|
||||
from chainlib.jsonrpc import (
|
||||
IntSequenceGenerator,
|
||||
)
|
||||
from chainlib.chain import ChainSpec
|
||||
import chainlib.eth.cli
|
||||
|
||||
BLOCK_SAMPLES = 10
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
||||
if default_eth_provider == None:
|
||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-H', '--human', dest='human', action='store_true', help='Use human-friendly formatting')
|
||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||
argparser.add_argument('-l', '--long', dest='l', action='store_true', help='Calculate averages through sampling of blocks and txs')
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Include summary for keyfile')
|
||||
argparser.add_argument('address', nargs='?', type=str, help='Include summary for address (conflicts with -y)')
|
||||
arg_flags = chainlib.eth.cli.argflag_std_read
|
||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||
argparser.add_positional('address', type=str, help='Address to retrieve info for', required=False)
|
||||
argparser.add_argument('--long', action='store_true', help='Calculate averages through sampling of blocks and txs')
|
||||
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:
|
||||
logg.setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logg.setLevel(logging.INFO)
|
||||
holder_address = args.address
|
||||
wallet = chainlib.eth.cli.Wallet()
|
||||
wallet.from_config(config)
|
||||
if wallet.get_signer_address() == None and holder_address != None:
|
||||
wallet.from_address(holder_address)
|
||||
|
||||
signer = None
|
||||
holder_address = None
|
||||
if args.address != None:
|
||||
if not args.u and not is_checksum_address(args.address):
|
||||
raise ValueError('invalid checksum address {}'.format(args.address))
|
||||
holder_address = add_0x(args.address)
|
||||
elif args.y != None:
|
||||
f = open(args.y, 'r')
|
||||
o = json.load(f)
|
||||
f.close()
|
||||
holder_address = add_0x(to_checksum_address(o['address']))
|
||||
|
||||
rpc_id_generator = None
|
||||
if args.seq:
|
||||
rpc_id_generator = IntSequenceGenerator()
|
||||
|
||||
auth = None
|
||||
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
|
||||
from chainlib.auth import BasicAuth
|
||||
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
|
||||
conn = EthHTTPConnection(args.p, auth=auth)
|
||||
|
||||
gas_oracle = OverrideGasOracle(conn)
|
||||
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||
conn = rpc.connect_by_config(config)
|
||||
|
||||
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():
|
||||
o = network_id(id_generator=rpc_id_generator)
|
||||
o = network_id(id_generator=rpc.id_generator)
|
||||
r = conn.do(o)
|
||||
#if human:
|
||||
# n = format(n, ',')
|
||||
sys.stdout.write('Network id: {}\n'.format(r))
|
||||
|
||||
o = block_latest(id_generator=rpc_id_generator)
|
||||
o = block_latest(id_generator=rpc.id_generator)
|
||||
r = conn.do(o)
|
||||
n = int(r, 16)
|
||||
first_block_number = n
|
||||
@ -123,7 +83,7 @@ def main():
|
||||
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)
|
||||
last_block = Block(r)
|
||||
last_timestamp = last_block.timestamp
|
||||
@ -132,7 +92,7 @@ def main():
|
||||
aggr_time = 0.0
|
||||
aggr_gas = 0
|
||||
for i in range(BLOCK_SAMPLES):
|
||||
o = block_by_number(first_block_number-i, False, id_generator=rpc_id_generator)
|
||||
o = block_by_number(first_block_number-i, False, id_generator=rpc.id_generator)
|
||||
r = conn.do(o)
|
||||
block = Block(r)
|
||||
aggr_time += last_block.timestamp - block.timestamp
|
||||
@ -150,7 +110,7 @@ def main():
|
||||
sys.stdout.write('Gaslimit: {}\n'.format(n))
|
||||
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)
|
||||
n = int(r, 16)
|
||||
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
|
||||
|
||||
# standard imports
|
||||
@ -19,6 +10,7 @@ import logging
|
||||
import urllib
|
||||
|
||||
# external imports
|
||||
import chainlib.eth.cli
|
||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||
from hexathon import (
|
||||
@ -45,129 +37,88 @@ from chainlib.eth.tx import (
|
||||
TxFactory,
|
||||
raw,
|
||||
)
|
||||
from chainlib.error import SignerMissingException
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.runnable.util import decode_for_puny_humans
|
||||
from chainlib.eth.jsonrpc import to_blockheight_param
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
default_eth_provider = os.environ.get('RPC_PROVIDER')
|
||||
if default_eth_provider == None:
|
||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
config_dir = os.path.join(script_dir, '..', 'data', 'config')
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
|
||||
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
||||
argparser.add_argument('--nonce', type=int, help='override nonce')
|
||||
argparser.add_argument('--gas-price', dest='gas_price', type=int, help='override gas price')
|
||||
argparser.add_argument('--gas-limit', dest='gas_limit', type=int, help='override gas limit')
|
||||
argparser.add_argument('-a', '--recipient', dest='a', type=str, help='recipient address (None for contract creation)')
|
||||
argparser.add_argument('-value', type=int, help='gas value of transaction in wei')
|
||||
argparser.add_argument('--seq', action='store_true', help='Use sequential rpc ids')
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('-s', '--send', dest='s', action='store_true', help='Send to network')
|
||||
argparser.add_argument('-l', '--local', dest='l', action='store_true', help='Local contract call')
|
||||
argparser.add_argument('data', nargs='?', type=str, help='Transaction data')
|
||||
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC
|
||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||
argparser.add_positional('data', type=str, help='Transaction data')
|
||||
args = argparser.parse_args()
|
||||
|
||||
|
||||
if args.vv:
|
||||
logg.setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logg.setLevel(logging.INFO)
|
||||
config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_config_dir=config_dir)
|
||||
|
||||
block_all = args.ww
|
||||
block_last = args.w or block_all
|
||||
|
||||
passphrase_env = 'ETH_PASSPHRASE'
|
||||
if args.env_prefix != None:
|
||||
passphrase_env = args.env_prefix + '_' + passphrase_env
|
||||
passphrase = os.environ.get(passphrase_env)
|
||||
if passphrase == None:
|
||||
logg.warning('no passphrase given')
|
||||
passphrase=''
|
||||
wallet = chainlib.eth.cli.Wallet(EIP155Signer)
|
||||
wallet.from_config(config)
|
||||
|
||||
signer_address = None
|
||||
keystore = DictKeystore()
|
||||
if args.y != None:
|
||||
logg.debug('loading keystore file {}'.format(args.y))
|
||||
signer_address = keystore.import_keystore_file(args.y, password=passphrase)
|
||||
logg.debug('now have key for signer address {}'.format(signer_address))
|
||||
signer = EIP155Signer(keystore)
|
||||
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||
conn = rpc.connect_by_config(config)
|
||||
|
||||
rpc_id_generator = None
|
||||
if args.seq:
|
||||
rpc_id_generator = IntSequenceGenerator()
|
||||
send = config.true('_RPC_SEND')
|
||||
|
||||
auth = None
|
||||
if os.environ.get('RPC_AUTHENTICATION') == 'basic':
|
||||
from chainlib.auth import BasicAuth
|
||||
auth = BasicAuth(os.environ['RPC_USERNAME'], os.environ['RPC_PASSWORD'])
|
||||
conn = EthHTTPConnection(args.p, auth=auth)
|
||||
|
||||
send = args.s
|
||||
|
||||
local = args.l
|
||||
if local:
|
||||
if config.get('_EXEC_ADDRESS') != None:
|
||||
send = False
|
||||
|
||||
nonce_oracle = None
|
||||
gas_oracle = None
|
||||
if signer_address != None and not local:
|
||||
if args.nonce != None:
|
||||
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce)
|
||||
else:
|
||||
nonce_oracle = RPCNonceOracle(signer_address, conn)
|
||||
|
||||
if args.gas_price or args.gas_limit != None:
|
||||
gas_oracle = OverrideGasOracle(price=args.gas_price, limit=args.gas_limit, conn=conn, id_generator=rpc_id_generator)
|
||||
else:
|
||||
gas_oracle = RPCGasOracle(conn, id_generator=rpc_id_generator)
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||
|
||||
value = args.value
|
||||
|
||||
|
||||
g = TxFactory(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||
chain_spec = None
|
||||
try:
|
||||
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def main():
|
||||
recipient = None
|
||||
if args.a != None:
|
||||
recipient = add_0x(to_checksum(args.a))
|
||||
if not args.u and recipient != add_0x(recipient):
|
||||
|
||||
signer_address = None
|
||||
try:
|
||||
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')
|
||||
|
||||
if local:
|
||||
j = JSONRPCRequest(id_generator=rpc_id_generator)
|
||||
j = JSONRPCRequest(id_generator=rpc.id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
o['params'].append({
|
||||
'to': recipient,
|
||||
'to': exec_address,
|
||||
'from': signer_address,
|
||||
'value': '0x00',
|
||||
'gas': add_0x(int.to_bytes(8000000, 8, byteorder='big').hex()), # TODO: better get of network gas limit
|
||||
'gasPrice': '0x01',
|
||||
'data': add_0x(args.data),
|
||||
})
|
||||
o['params'].append('latest')
|
||||
height = to_blockheight_param(config.get('_HEIGHT'))
|
||||
o['params'].append(height)
|
||||
o = j.finalize(o)
|
||||
r = conn.do(o)
|
||||
try:
|
||||
print(strip_0x(r))
|
||||
except ValueError:
|
||||
sys.stderr.write('query returned an empty value\n')
|
||||
sys.exit(1)
|
||||
return
|
||||
|
||||
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)
|
||||
if args.data != None:
|
||||
tx = g.set_code(tx, add_0x(args.data))
|
||||
|
||||
(tx_hash_hex, o) = g.finalize(tx, id_generator=rpc_id_generator)
|
||||
(tx_hash_hex, o) = g.finalize(tx, id_generator=rpc.id_generator)
|
||||
|
||||
if send:
|
||||
r = conn.do(o)
|
||||
@ -177,7 +128,7 @@ def main():
|
||||
print(tx_hash_hex)
|
||||
|
||||
else:
|
||||
o = raw(args.data, id_generator=rpc_id_generator)
|
||||
o = raw(args.data, id_generator=rpc.id_generator)
|
||||
if send:
|
||||
r = conn.do(o)
|
||||
print(r)
|
||||
|
@ -3,6 +3,17 @@ from chainlib.jsonrpc import JSONRPCRequest
|
||||
|
||||
|
||||
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)
|
||||
o = j.template()
|
||||
o['method'] = 'personal_newAccount'
|
||||
@ -11,6 +22,17 @@ def new_account(passphrase='', 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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_signTransaction'
|
||||
@ -19,6 +41,19 @@ def sign_transaction(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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_sign'
|
||||
|
@ -9,6 +9,7 @@ import sha3
|
||||
from hexathon import (
|
||||
strip_0x,
|
||||
add_0x,
|
||||
compact,
|
||||
)
|
||||
from rlp import decode as rlp_decode
|
||||
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.eth.encoding import chain_id_to_v
|
||||
from potaahto.symbols import snake_and_camel
|
||||
|
||||
|
||||
# local imports
|
||||
from chainlib.hash import keccak256_hex_to_hex
|
||||
from chainlib.status import Status
|
||||
from 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 .constant import (
|
||||
MINIMUM_FEE_UNITS,
|
||||
MINIMUM_FEE_PRICE,
|
||||
ZERO_ADDRESS,
|
||||
DEFAULT_FEE_LIMIT,
|
||||
)
|
||||
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):
|
||||
"""Tx generator output formats
|
||||
"""
|
||||
DICT = 0x00
|
||||
RAW = 0x01
|
||||
RAW_SIGNED = 0x02
|
||||
@ -56,24 +66,22 @@ field_debugs = [
|
||||
's',
|
||||
]
|
||||
|
||||
def count(address, confirmed=False, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator=id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_getTransactionCount'
|
||||
o['params'].append(address)
|
||||
if confirmed:
|
||||
o['params'].append('latest')
|
||||
else:
|
||||
o['params'].append('pending')
|
||||
return j.finalize(o)
|
||||
|
||||
count_pending = count
|
||||
|
||||
def count_confirmed(address):
|
||||
return count(address, True)
|
||||
count = nonce_query
|
||||
count_pending = nonce_query
|
||||
count_confirmed = nonce_query_confirmed
|
||||
|
||||
|
||||
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):
|
||||
tx_src = tx_src.as_dict()
|
||||
tx_src = Tx.src_normalize(tx_src)
|
||||
@ -96,6 +104,15 @@ def pack(tx_src, 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()
|
||||
tx = __unpack_raw(tx_raw_bytes, chain_id)
|
||||
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):
|
||||
"""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()
|
||||
tx = __unpack_raw(tx_raw_bytes, chain_id)
|
||||
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):
|
||||
"""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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_getTransactionByHash'
|
||||
@ -201,6 +236,17 @@ def transaction(hsh, 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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_getTransactionByBlockHashAndIndex'
|
||||
@ -210,6 +256,15 @@ def transaction_by_block(hsh, idx, 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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_getTransactionReceipt'
|
||||
@ -218,6 +273,15 @@ def receipt(hsh, 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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_sendRawTransaction'
|
||||
@ -226,8 +290,23 @@ def raw(tx_raw_hex, id_generator=None):
|
||||
|
||||
|
||||
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):
|
||||
self.gas_oracle = gas_oracle
|
||||
@ -237,6 +316,15 @@ class TxFactory:
|
||||
|
||||
|
||||
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'] == '':
|
||||
tx['to'] = '0x'
|
||||
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
||||
@ -247,12 +335,34 @@ class TxFactory:
|
||||
|
||||
|
||||
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)
|
||||
o = raw(tx_raw_hex, id_generator=id_generator)
|
||||
return (tx_hash_hex, o)
|
||||
|
||||
|
||||
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_limit = MINIMUM_FEE_UNITS
|
||||
if self.gas_oracle != None:
|
||||
@ -276,18 +386,35 @@ class TxFactory:
|
||||
|
||||
|
||||
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'])
|
||||
txes = txe.serialize()
|
||||
return {
|
||||
'from': tx['from'],
|
||||
'to': txes['to'],
|
||||
'gasPrice': txes['gasPrice'],
|
||||
'gas': txes['gas'],
|
||||
'gasPrice': '0x' + compact(txes['gasPrice']),
|
||||
'gas': '0x' + compact(txes['gas']),
|
||||
'data': txes['data'],
|
||||
}
|
||||
|
||||
|
||||
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:
|
||||
return self.build(tx, id_generator=id_generator)
|
||||
elif tx_format == TxFormat.RLP_SIGNED:
|
||||
@ -296,6 +423,17 @@ class TxFactory:
|
||||
|
||||
|
||||
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
|
||||
if update_fee:
|
||||
tx['gas'] = TxFactory.fee
|
||||
@ -307,6 +445,19 @@ class TxFactory:
|
||||
|
||||
|
||||
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.method(method)
|
||||
data = enc.get()
|
||||
@ -316,7 +467,22 @@ class TxFactory:
|
||||
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)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
@ -326,34 +492,38 @@ class TxFactory:
|
||||
tx = self.template(sender_address, contract_address)
|
||||
tx = self.set_code(tx, data)
|
||||
o['params'].append(self.normalize(tx))
|
||||
o['params'].append('latest')
|
||||
height = to_blockheight_param(height)
|
||||
o['params'].append(height)
|
||||
o = j.finalize(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):
|
||||
self.tx_src = self.src_normalize(src)
|
||||
self.__rcpt_block_hash = None
|
||||
|
||||
src = self.src_normalize(src)
|
||||
self.index = -1
|
||||
tx_hash = add_0x(src['hash'])
|
||||
if block != None:
|
||||
i = 0
|
||||
for tx in block.txs:
|
||||
tx_hash_block = None
|
||||
try:
|
||||
tx_hash_block = tx['hash']
|
||||
except TypeError:
|
||||
tx_hash_block = add_0x(tx)
|
||||
if tx_hash_block == tx_hash:
|
||||
self.index = i
|
||||
break
|
||||
i += 1
|
||||
if self.index == -1:
|
||||
raise AttributeError('tx {} not found in block {}'.format(tx_hash, block.hash))
|
||||
self.block = block
|
||||
self.hash = strip_0x(tx_hash)
|
||||
if block != None:
|
||||
self.apply_block(block)
|
||||
try:
|
||||
self.value = int(strip_0x(src['value']), 16)
|
||||
except TypeError:
|
||||
@ -378,6 +548,7 @@ class Tx:
|
||||
inpt = src['input']
|
||||
except KeyError:
|
||||
inpt = src['data']
|
||||
src['input'] = src['data']
|
||||
|
||||
if inpt != '0x':
|
||||
inpt = strip_0x(inpt)
|
||||
@ -408,13 +579,27 @@ class Tx:
|
||||
|
||||
self.wire = None
|
||||
|
||||
self.tx_src = src
|
||||
|
||||
|
||||
def src(self):
|
||||
"""Retrieve normalized representation source used to construct transaction object.
|
||||
|
||||
:rtype: dict
|
||||
:returns: Transaction representation
|
||||
"""
|
||||
return self.tx_src
|
||||
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
|
||||
if isinstance(src.get('v'), str):
|
||||
@ -430,16 +615,40 @@ class Tx:
|
||||
|
||||
|
||||
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)
|
||||
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:
|
||||
status_number = int(rcpt['status'], 16)
|
||||
except TypeError:
|
||||
status_number = int(rcpt['status'])
|
||||
if rcpt['block_number'] == None:
|
||||
self.status = Status.PENDING
|
||||
else:
|
||||
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
|
||||
contract_address = rcpt.get('contractAddress')
|
||||
if contract_address == None:
|
||||
@ -452,20 +661,43 @@ class Tx:
|
||||
except TypeError:
|
||||
self.gas_used = int(rcpt['gasUsed'])
|
||||
|
||||
self.__rcpt_block_hash = rcpt['block_hash']
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def generate_wire(self, chain_spec):
|
||||
"""Generate transaction wire format.
|
||||
|
||||
: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
|
||||
def from_src(src, block=None):
|
||||
return Tx(src, block=block)
|
||||
def from_src(src, block=None, rcpt=None):
|
||||
"""Creates a new Tx object.
|
||||
|
||||
Alias of constructor.
|
||||
"""
|
||||
return Tx(src, block=block, rcpt=rcpt)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
@ -480,13 +712,18 @@ class Tx:
|
||||
|
||||
|
||||
def to_human(self):
|
||||
"""Human-readable string dump of transaction contents.
|
||||
|
||||
:rtype: str
|
||||
:returns: Contents
|
||||
"""
|
||||
s = """hash {}
|
||||
from {}
|
||||
to {}
|
||||
value {}
|
||||
nonce {}
|
||||
gasPrice {}
|
||||
gasLimit {}
|
||||
gas_price {}
|
||||
gas_limit {}
|
||||
input {}
|
||||
""".format(
|
||||
self.hash,
|
||||
@ -500,13 +737,24 @@ input {}
|
||||
)
|
||||
|
||||
if self.status != Status.PENDING:
|
||||
s += """gasUsed {}
|
||||
s += """gas_used {}
|
||||
""".format(
|
||||
self.gas_used,
|
||||
)
|
||||
|
||||
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:
|
||||
s += """contract {}
|
||||
""".format(
|
||||
@ -520,4 +768,3 @@ input {}
|
||||
)
|
||||
|
||||
return s
|
||||
|
||||
|
@ -17,6 +17,7 @@ address = add_0x(address_bytes.hex())
|
||||
rpc_provider = os.environ.get('RPC_PROVIDER', 'http://localhost:8545')
|
||||
rpc = EthHTTPConnection(rpc_provider)
|
||||
o = balance(address)
|
||||
print(o)
|
||||
r = rpc.do(o)
|
||||
|
||||
clean_address = strip_0x(address)
|
||||
|
@ -3,22 +3,23 @@ import os
|
||||
import sys
|
||||
|
||||
# local imports
|
||||
from chainlib.jsonrpc import jsonrpc_template
|
||||
from chainlib.jsonrpc import JSONRPCRequest
|
||||
from chainlib.eth.connection import EthHTTPConnection
|
||||
|
||||
# set up node connection and execute rpc call
|
||||
rpc_provider = os.environ.get('RPC_PROVIDER', 'http://localhost:8545')
|
||||
rpc = EthHTTPConnection(rpc_provider)
|
||||
conn = EthHTTPConnection(rpc_provider)
|
||||
|
||||
# check the connection
|
||||
if not rpc.check():
|
||||
if not conn.check():
|
||||
sys.stderr.write('node {} not usable\n'.format(rpc_provider))
|
||||
sys.exit(1)
|
||||
|
||||
# build and send rpc call
|
||||
o = jsonrpc_template()
|
||||
g = JSONRPCRequest()
|
||||
o = g.template()
|
||||
o['method'] = 'eth_blockNumber'
|
||||
r = rpc.do(o)
|
||||
r = conn.do(o)
|
||||
|
||||
# interpret result for humans
|
||||
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
|
||||
|
||||
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
# eth transactions need an explicit chain parameter as part of their signature
|
||||
chain_spec = ChainSpec.from_chain_str('evm:ethereum:1')
|
||||
|
||||
# create keystore and signer
|
||||
keystore = DictKeystore()
|
||||
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()
|
||||
|
||||
# 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
|
||||
hexathon~=0.0.1a7
|
||||
hexathon~=0.0.1a8
|
||||
websocket-client==0.57.0
|
||||
potaahto~=0.0.1a1
|
||||
chainlib==0.0.5a1
|
||||
chainlib==0.0.8a2
|
||||
confini>=0.4.1a1,<0.5.0
|
||||
|
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = chainlib-eth
|
||||
version = 0.0.5a1
|
||||
version = 0.0.8a2
|
||||
description = Ethereum implementation of the chainlib interface
|
||||
author = Louis Holbrook
|
||||
author_email = dev@holbrook.no
|
||||
@ -24,6 +24,7 @@ licence_files =
|
||||
LICENSE.txt
|
||||
|
||||
[options]
|
||||
include_package_data = True
|
||||
python_requires = >= 3.6
|
||||
packages =
|
||||
chainlib.eth
|
||||
@ -40,4 +41,5 @@ console_scripts =
|
||||
eth-get = chainlib.eth.runnable.get:main
|
||||
eth-decode = chainlib.eth.runnable.decode:main
|
||||
eth-info = chainlib.eth.runnable.info:main
|
||||
eth-nonce = chainlib.eth.runnable.count: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,
|
||||
add_0x,
|
||||
)
|
||||
from chainlib.eth.block import Block
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
@ -35,6 +36,7 @@ logg = logging.getLogger()
|
||||
|
||||
class TxTestCase(EthTesterCase):
|
||||
|
||||
|
||||
def test_tx_reciprocal(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
gas_oracle = RPCGasOracle(self.rpc)
|
||||
@ -75,5 +77,70 @@ class TxTestCase(EthTesterCase):
|
||||
logg.debug('r {}'.format(tx_signed_raw_bytes_recovered.hex()))
|
||||
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__':
|
||||
unittest.main()
|
||||
|
Loading…
Reference in New Issue
Block a user