diff --git a/chainlib/eth/block.py b/chainlib/eth/block.py index 769ff82..f9a3d2e 100644 --- a/chainlib/eth/block.py +++ b/chainlib/eth/block.py @@ -7,7 +7,7 @@ from hexathon import ( even, ) -def block(): +def block_latest(): o = jsonrpc_template() o['method'] = 'eth_blockNumber' return o diff --git a/chainlib/eth/erc20.py b/chainlib/eth/erc20.py index f15c82e..1471faf 100644 --- a/chainlib/eth/erc20.py +++ b/chainlib/eth/erc20.py @@ -22,19 +22,7 @@ erc20_transfer_signature = keccak256_string_to_hex('transfer(address,uint256)')[ class ERC20TxFactory(TxFactory): - def build(self, tx): - txe = EIP155Transaction(tx, tx['nonce'], tx['chainId']) - self.signer.signTransaction(txe) - tx_raw = txe.rlp_serialize() - tx_raw_hex = add_0x(tx_raw.hex()) - tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex)) - - o = jsonrpc_template() - o['method'] = 'eth_sendRawTransaction' - o['params'].append(tx_raw_hex) - - return (tx_hash_hex, o) - + def erc20_balance(self, contract_address, address, sender_address=ZERO_ADDRESS): o = jsonrpc_template() diff --git a/chainlib/eth/nonce.py b/chainlib/eth/nonce.py index 089906a..dc5592e 100644 --- a/chainlib/eth/nonce.py +++ b/chainlib/eth/nonce.py @@ -21,10 +21,17 @@ class DefaultNonceOracle: def __init__(self, address, conn): self.address = address self.conn = conn + self.nonce = self.get() - def next(self): + def get(self): o = nonce(self.address) r = self.conn.do(o) n = strip_0x(r) return int(n, 16) + + + def next(self): + n = self.nonce + self.nonce += 1 + return n diff --git a/chainlib/eth/runnable/get.py b/chainlib/eth/runnable/get.py new file mode 100644 index 0000000..79e41b4 --- /dev/null +++ b/chainlib/eth/runnable/get.py @@ -0,0 +1,101 @@ +#!python3 + +"""Token balance query script + +.. moduleauthor:: Louis Holbrook +.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 + +""" + +# SPDX-License-Identifier: GPL-3.0-or-later + +# standard imports +import os +import json +import argparse +import logging +import enum + +# third-party imports +from hexathon import ( + add_0x, + strip_0x, + even, + ) +import sha3 +from eth_abi import encode_single + +# local imports +from chainlib.eth.address import to_checksum +from chainlib.eth.rpc import ( + jsonrpc_template, + jsonrpc_result, + ) +from chainlib.eth.connection import HTTPConnection +from chainlib.eth.tx import Tx +from chainlib.eth.block import Block + +logging.basicConfig(level=logging.WARNING) +logg = logging.getLogger() + +default_abi_dir = os.environ.get('ETH_ABI_DIR', '/usr/share/local/cic/solidity/abi') +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('-t', '--token-address', dest='t', type=str, help='Token address. If not set, will return gas balance') +argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress') +argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=default_abi_dir, help='Directory containing bytecode and abi (default {})'.format(default_abi_dir)) +argparser.add_argument('-v', action='store_true', help='Be verbose') +argparser.add_argument('tx_hash', type=str, help='Transaction hash') +args = argparser.parse_args() + + +if args.v: + logg.setLevel(logging.DEBUG) + +conn = HTTPConnection(args.p) + +tx_hash = args.tx_hash + + +class Status(enum.Enum): + UNCONFIRMED = -1 + REVERTED = 0 + SUCCESS = 1 + + +def main(): + o = jsonrpc_template() + o['method'] = 'eth_getTransactionByHash' + o['params'].append(tx_hash) + tx_src = conn.do(o) + + tx = None + status = -1 + if tx_src['blockHash'] != None: + o = jsonrpc_template() + o['method'] = 'eth_getBlockByHash' + o['params'].append(tx_src['blockHash']) + o['params'].append(True) + block_src = conn.do(o) + block = Block(block_src) + for t in block.txs: + if t['hash'] == tx_hash: + tx = Tx(t, block) + break + o = jsonrpc_template() + o['method'] = 'eth_getTransactionReceipt' + o['params'].append(tx_hash) + rcpt = conn.do(o) + status = int(strip_0x(rcpt['status']), 16) + + if tx == None: + tx = Tx(tx_src) + print(tx) + status_name = Status(status).name + print('status {}'.format(status_name)) + + +if __name__ == '__main__': + main() diff --git a/chainlib/eth/runnable/transfer.py b/chainlib/eth/runnable/transfer.py index 63de7f9..5741c81 100644 --- a/chainlib/eth/runnable/transfer.py +++ b/chainlib/eth/runnable/transfer.py @@ -18,7 +18,6 @@ import logging # third-party imports from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer from crypto_dev_signer.keystore import DictKeystore -from crypto_dev_signer.eth.helper import EthTxExecutor from hexathon import ( add_0x, strip_0x, diff --git a/chainlib/eth/tx.py b/chainlib/eth/tx.py index 8a7ade1..7696881 100644 --- a/chainlib/eth/tx.py +++ b/chainlib/eth/tx.py @@ -3,20 +3,26 @@ import logging # third-party imports import sha3 -from hexathon import strip_0x +from hexathon import ( + strip_0x, + add_0x, + ) from eth_keys import KeyAPI from eth_keys.backends import NativeECCBackend from rlp import decode as rlp_decode from rlp import encode as rlp_encode from crypto_dev_signer.eth.transaction import EIP155Transaction + # local imports +from chainlib.hash import keccak256_hex_to_hex from .address import to_checksum from .constant import ( MINIMUM_FEE_UNITS, MINIMUM_FEE_PRICE, ZERO_ADDRESS, ) +from .rpc import jsonrpc_template logg = logging.getLogger(__name__) @@ -97,6 +103,20 @@ class TxFactory: self.signer = signer + def build(self, tx): + txe = EIP155Transaction(tx, tx['nonce'], tx['chainId']) + self.signer.signTransaction(txe) + tx_raw = txe.rlp_serialize() + tx_raw_hex = add_0x(tx_raw.hex()) + tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex)) + + o = jsonrpc_template() + o['method'] = 'eth_sendRawTransaction' + o['params'].append(tx_raw_hex) + + return (tx_hash_hex, o) + + def template(self, sender, recipient): gas_price = MINIMUM_FEE_PRICE if self.gas_oracle != None: @@ -121,7 +141,6 @@ class TxFactory: def normalize(self, tx): txe = EIP155Transaction(tx, tx['nonce'], tx['chainId']) txes = txe.serialize() - print(txes) return { 'from': tx['from'], 'to': txes['to'], @@ -141,12 +160,18 @@ class TxFactory: class Tx: - def __init__(self, src, block): - self.index = int(strip_0x(src['transactionIndex']), 16) + def __init__(self, src, block=None): + self.index = -1 + self.status = -1 + if block != None: + self.index = int(strip_0x(src['transactionIndex']), 16) self.value = int(strip_0x(src['value']), 16) self.nonce = int(strip_0x(src['nonce']), 16) self.hash = strip_0x(src['hash']) - self.outputs = [strip_0x(src['from'])] + address_from = strip_0x(src['from']) + self.gasPrice = int(strip_0x(src['gasPrice']), 16) + self.gasLimit = int(strip_0x(src['gas']), 16) + self.outputs = [to_checksum(address_from)] inpt = src['input'] if inpt != '0x': @@ -158,7 +183,7 @@ class Tx: to = src['to'] if to == None: to = ZERO_ADDRESS - self.inputs = [strip_0x(to)] + self.inputs = [to_checksum(strip_0x(to))] self.block = block self.wire = src['raw'] @@ -170,4 +195,18 @@ class Tx: def __str__(self): - return 'from {} to {} value {} input {}'.format(self.outputs[0], self.inputs[0], self.value, self.payload) + return """from {} +to {} +value {} +nonce {} +gasPrice {} +gasLimit {} +input {}""".format( + self.outputs[0], + self.inputs[0], + self.value, + self.nonce, + self.gasPrice, + self.gasLimit, + self.payload, + ) diff --git a/setup.cfg b/setup.cfg index 119bdee..1b82515 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = chainlib -version = 0.0.1a6 +version = 0.0.1a7 description = Generic blockchain access library and tooling author = Louis Holbrook author_email = dev@holbrook.no @@ -37,3 +37,4 @@ console_scripts = eth-checksum = chainlib.eth.runnable.checksum:main eth-gas = chainlib.eth.runnable.gas:main eth-transfer = chainlib.eth.runnable.transfer:main + eth-get = chainlib.eth.runnable.get:main