Add docstrings
This commit is contained in:
		
							parent
							
								
									8374d83045
								
							
						
					
					
						commit
						d13708d3ce
					
				| @ -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) | ||||
| @ -259,9 +407,10 @@ class ABIContractEncoder(ABIMethodEncoder): | ||||
|             raise ValueError('invalid input {}'.format(typ)) | ||||
|         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) | ||||
|         print(strip_0x(r)) | ||||
|         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 status_number == 1: | ||||
|             self.status = Status.SUCCESS | ||||
|         elif status_number == 0: | ||||
|             self.status = Status.ERROR | ||||
|         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): | ||||
|         b = pack(self.src(), chain_spec) | ||||
|         self.wire = add_0x(b.hex()) | ||||
|         """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