Compare commits

...

41 Commits

Author SHA1 Message Date
lash 772781c1b8
Correct tx status human output 2022-05-18 22:01:59 +00:00
lash bf166af544
Fix crash in human tx render on null receipt, order of rpc in rpc settings process 2022-05-14 20:36:09 +00:00
lash 43f3aff255
Compact gas price limit fields in tx json 2022-05-14 20:05:32 +00:00
lash 586eccfba0
Remove crashing logline in gas cli tool 2022-05-14 16:36:21 +00:00
lash dfe18a4238
Make wallet settings processing pass without recipient set 2022-05-14 12:23:49 +00:00
lash 3f24ab93fc
Add state config 2022-05-13 13:44:33 +00:00
lash 7ed616a13d
Add contract process settings 2022-05-13 07:49:15 +00:00
lash f22115ff8c
Fix bug in handling of data and recipeint inputs 2022-05-12 18:42:33 +00:00
lash 7ea2b43d60
Update deps 2022-05-12 18:30:20 +00:00
lash 7dc62676ba
Rehabilitate tests 2022-05-12 18:29:11 +00:00
lash e03c5b8035
Implement wait tool on settings module 2022-05-12 18:20:52 +00:00
lash 6ad0edd8a8
Remove commented code 2022-05-12 18:07:33 +00:00
lash e053098652
Implement get cli tool on settings modulewq 2022-05-12 18:05:13 +00:00
lash e621913f74
Implement 'raw' cli tool on settings module 2022-05-12 16:13:25 +00:00
lash 3c55bc9ac2
Implement 'raw' cli tool on settings module 2022-05-12 16:13:02 +00:00
lash 65b10ea306
Implement 'raw' cli tool on settings module 2022-05-12 16:12:00 +00:00
lash e723f26267
Implement info cli tool on settings module 2022-05-12 15:46:29 +00:00
lash 9470b81fad
Implement decode tool on settings module 2022-05-12 15:19:16 +00:00
lash 2d06d60eed
Implement block cli tool on settings module 2022-05-12 15:08:37 +00:00
lash 33d9877bac
implement gas tool on new settings module 2022-05-12 14:08:36 +00:00
lash 05d3357318
Add settings module, implement for balance tool 2022-05-12 13:53:33 +00:00
lash 85e5359e7c
Add settings module, implement for balance tool 2022-05-12 13:49:42 +00:00
lash 72f3b8d346
Rehabilitate encode tool 2022-05-12 13:22:23 +00:00
lash 21ebdeae77
Rehabilitate wait for tx tool 2022-05-12 13:05:02 +00:00
lash 628028e4ce
Rehabilitate raw cli tool 2022-05-12 12:58:24 +00:00
lash 7604b48e12
Rehabilitate chain info tool 2022-05-12 11:23:00 +00:00
lash d70dfc3f03
Rehabilitate decode tool 2022-05-12 11:10:47 +00:00
lash c060222638
Rehabilitate gas cli tool on new chainlib 2022-05-12 09:29:10 +00:00
lash e16cc956e0
Rehabilitate 'count' tool 2022-05-12 08:20:06 +00:00
lash 9012b9884a
Apply balance tool on new chainlib setup 2022-05-12 06:27:16 +00:00
lash a1896b40c1
Apply new arg and config handling from chainlib 2022-05-12 05:30:33 +00:00
lash 2f90eb632f
Upgrade chainlib 2022-05-10 19:05:54 +00:00
lash 7b7496a374
Fix get cli tool, src generation bug 2022-05-10 19:02:49 +00:00
lash 23f977482c
Correct method to property calls 2022-05-09 19:21:45 +00:00
lash 20dfb641ff
Remove commented code 2022-05-09 18:49:04 +00:00
lash 972535f1f9
Complete test and refactor for generic tx, result, block objects 2022-05-09 18:46:10 +00:00
lash a2168a50e3
WIP implement generic tx and block 2022-05-09 10:00:29 +00:00
lash 9548ed5d1b
Add author property in block 2022-05-07 11:31:06 +00:00
lash e499770d6d
Upgrade deps 2022-05-04 18:15:33 +00:00
lash e49fae9717
Upgrade deps, bump vesrion 2022-04-28 15:44:04 +00:00
lash 84c4a82abb
Bump dep 2022-04-28 15:43:18 +00:00
27 changed files with 1248 additions and 450 deletions

View File

@ -1,3 +1,14 @@
- 0.3.1
* Fix missing application of status on tx result
- 0.3.0
* Implement chainlib with new arg and config handling
* Implement cli tools on settings module
- 0.2.0
* Implement chainlib generic tx, block and tx result objects
- 0.1.3
* Add block author field
- 0.1.2
* Upgrade chainlib dep
- 0.1.1
* Add fee_limit, fee_price alias to Tx object
- 0.1.0:

View File

@ -1,4 +1,7 @@
import sys
# standard imports
import logging
import datetime
# external imports
from chainlib.jsonrpc import JSONRPCRequest
from chainlib.block import Block as BaseBlock
@ -6,10 +9,14 @@ from hexathon import (
add_0x,
strip_0x,
compact,
to_int as hex_to_int,
)
# local imports
from chainlib.eth.tx import Tx
from .src import Src
logg = logging.getLogger(__name__)
def block_latest(id_generator=None):
@ -76,7 +83,7 @@ def syncing(id_generator=None):
return j.finalize(o)
class Block(BaseBlock):
class Block(BaseBlock, Src):
"""Encapsulates an Ethereum block
:param src: Block representation data
@ -86,21 +93,32 @@ class Block(BaseBlock):
tx_generator = Tx
def __init__(self, src):
self.hash = src['hash']
def __init__(self, src=None):
super(Block, self).__init__(src=src)
self.set_hash(self.src['hash'])
try:
self.number = int(strip_0x(src['number']), 16)
self.number = int(strip_0x(self.src['number']), 16)
except TypeError:
self.number = int(src['number'])
self.txs = src['transactions']
self.block_src = src
self.number = int(self.src['number'])
self.txs = self.src['transactions']
self.block_src = self.src
try:
self.timestamp = int(strip_0x(src['timestamp']), 16)
self.timestamp = int(strip_0x(self.src['timestamp']), 16)
except TypeError:
self.timestamp = int(src['timestamp'])
self.timestamp = int(self.src['timestamp'])
try:
self.author = self.src['author']
except KeyError:
self.author = self.src['miner']
self.fee_limit = self.src['gas_limit']
self.fee_cost = self.src['gas_used']
self.parent_hash = self.src['parent_hash']
def get_tx(self, tx_hash):
def tx_index_by_hash(self, tx_hash):
i = 0
idx = -1
tx_hash = add_0x(tx_hash)
@ -118,3 +136,28 @@ class Block(BaseBlock):
raise AttributeError('tx {} not found in block {}'.format(tx_hash, self.hash))
return idx
def to_human(self):
s = """hash: {}
number: {}
parent: {}
timestamp: {}
time: {}
author: {}
gas_limit: {}
gas_used: {}
txs: {}
""".format(
self.hash,
self.number,
self.parent_hash,
self.timestamp,
datetime.datetime.fromtimestamp(self.timestamp),
self.author,
hex_to_int(self.fee_limit),
hex_to_int(self.fee_cost),
len(self.txs),
)
return s

View File

@ -1,10 +1,6 @@
# external imports
from chainlib.cli import (
ArgumentParser,
argflag_std_read,
argflag_std_write,
argflag_std_base,
argflag_std_base_read,
flag_reset as argflag_reset,
Flag,
from chainlib.cli.arg import (
ArgumentParser,
Arg,
ArgFlag,
process_args,
)

View File

@ -2,7 +2,11 @@
import os
# external imports
from chainlib.cli import Config as BaseConfig
from chainlib.cli.config import (
Config as BaseConfig,
process_config as base_process_config,
)
script_dir = os.path.dirname(os.path.realpath(__file__))
data_dir = os.path.join(script_dir, '..')
@ -14,14 +18,12 @@ class Config(BaseConfig):
default_base_config_dir = os.path.join(data_dir, 'data', 'config')
default_fee_limit = 21000
@classmethod
def from_args(cls, args, arg_flags=0x0f, env=os.environ, extra_args={}, base_config_dir=None, default_config_dir=None, user_config_dir=None, default_fee_limit=None, logger=None, load_callback=None):
super(Config, cls).override_defaults(base_dir=cls.default_base_config_dir)
if default_fee_limit == None:
default_fee_limit = cls.default_fee_limit
config = BaseConfig.from_args(args, arg_flags=arg_flags, env=env, extra_args=extra_args, base_config_dir=base_config_dir, default_config_dir=default_config_dir, user_config_dir=user_config_dir, default_fee_limit=default_fee_limit, logger=logger, load_callback=load_callback)
if not config.get('RPC_DIALECT'):
def process_config(config, arg, args, flags):
config = base_process_config(config, arg, args, flags)
if arg.match('provider', flags):
if not bool(config.get('RPC_DIALECT')):
config.add('default', 'RPC_DIALECT', exists_ok=True)
elif config.get('RPC_DIALECT') not in [
'openethereum',
@ -29,5 +31,4 @@ class Config(BaseConfig):
]:
raise ValueError('unknown rpc dialect {}'.format(config.get('RPC_DIALECT')))
return config
return config

1
chainlib/eth/cli/log.py Normal file
View File

@ -0,0 +1 @@
from chainlib.cli.log import process_log

View File

@ -15,6 +15,3 @@ class Wallet(BaseWallet):
"""
def __init__(self, checksummer=AddressChecksum):
super(Wallet, self).__init__(EIP155Signer, checksummer=checksummer, keystore=DictKeystore())

View File

@ -6,6 +6,7 @@ import logging
# external imports
from hexathon import (
strip_0x,
add_0x,
pad,
)
@ -502,6 +503,8 @@ def code(address, block_spec=BlockSpec.LATEST, id_generator=None):
block_height = 'pending'
else:
block_height = int(block_spec)
block_height = block_height.to_bytes(8, byteorder='big')
block_height = add_0x(block_height.hex())
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_getCode'

View File

@ -5,10 +5,19 @@ credentials =
dialect = default
scheme = http
verify = 1
timeout = 10.0
proxy =
[chain]
spec = evm:berlin:1:ethereum
min_fee = 21000
max_fee = 8000000
[wallet]
key_file =
passphrase =
[state]
path =
runtime_path =
backend =

View File

@ -10,51 +10,73 @@ from hexathon import (
strip_0x,
even,
)
# local imports
import chainlib.eth.cli
from chainlib.eth.address import AddressChecksum
from chainlib.settings import ChainSettings
from chainlib.chain import ChainSpec
from funga.eth.signer import EIP155Signer
from chainlib.jsonrpc import (
jsonrpc_result,
IntSequenceGenerator,
)
# local imports
import chainlib.eth.cli
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from chainlib.eth.address import AddressChecksum
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.gas import (
OverrideGasOracle,
balance,
)
from chainlib.chain import ChainSpec
from funga.eth.signer import EIP155Signer
from chainlib.eth.settings import process_settings
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__))
#config_dir = os.path.join(script_dir, '..', 'data', 'config')
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')
def process_config_local(config, arg, args, flags):
config.add(args.address, '_RECIPIENT', False)
return config
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_READ
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
argparser.add_argument('address', type=str, help='Ethereum address of recipient')
args = argparser.parse_args()
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)
logg = process_log(args, logg)
logg.debug('flags {} {} {}'.format(flags, arg_flags.SEQ, flags & arg_flags.SEQ))
rpc = chainlib.eth.cli.Rpc()
conn = rpc.connect_by_config(config)
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
settings = ChainSettings()
settings = process_settings(settings, config)
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
def main():
r = None
decimals = 18
o = balance(holder_address, id_generator=rpc.id_generator)
r = conn.do(o)
o = balance(settings.get('RECIPIENT'), id_generator=settings.get('RPC_ID_GENERATOR'))
r = settings.get('CONN').do(o)
hx = strip_0x(r)
balance_value = int(hx, 16)

View File

@ -0,0 +1,168 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# standard imports
import sys
import os
import json
import argparse
import logging
import enum
import select
# external imports
from potaahto.symbols import snake_and_camel
from hexathon import (
add_0x,
strip_0x,
)
import sha3
from chainlib.jsonrpc import (
JSONRPCRequest,
jsonrpc_result,
IntSequenceGenerator,
)
from chainlib.chain import ChainSpec
from chainlib.status import Status
from chainlib.settings import ChainSettings
# local imports
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.tx import (
Tx,
pack,
)
from chainlib.eth.address import (
to_checksum_address,
is_checksum_address,
)
from chainlib.eth.block import (
Block,
block_by_hash,
block_by_number,
)
from chainlib.eth.runnable.util import decode_for_puny_humans
from chainlib.eth.jsonrpc import to_blockheight_param
import chainlib.eth.cli
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from chainlib.eth.settings import process_settings
logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__))
config_dir = os.path.join(script_dir, '..', 'data', 'config')
def process_config_local(config, arg, args, flags):
config.add(args.block, '_BLOCK', False)
return config
def process_settings_local(settings, config):
block_identifier = config.get('_BLOCK')
maybe_hex = None
is_number = False
try:
maybe_hex = strip_0x(block_identifier)
except ValueError:
is_number = True
if maybe_hex != None:
if len(maybe_hex) != 64:
is_number = True
else:
is_number = True
r = None
if not is_number:
config.add(block_identifier, '_HASH', False)
else:
settings.set('_BLOCK', int(block_identifier))
return process_settings(settings, config)
argparser = chainlib.eth.cli.ArgumentParser()
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_BASE_READ
flags = arg_flags.less(flags, arg_flags.CHAIN_SPEC)
argparser = process_args(argparser, arg, flags)
argparser.add_argument('block', nargs='?', type=str, help='Block hash or number to retrieve')
args = argparser.parse_args()
logg = process_log(args, logg)
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
settings = ChainSettings()
settings = process_settings_local(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
def get_block(settings):
hsh = settings.get('HASH')[0]
r = None
if hsh == None:
r = get_block_number(
settings.get('CONN'),
settings.get('_BLOCK'),
settings.get('RPC_ID_GENERATOR'),
)
else:
r = get_block_hash(
settings.get('CONN'),
hsh,
settings.get('RPC_ID_GENERATOR'),
)
return block_process(r)
def get_block_number(conn, block_number, id_generator):
o = block_by_number(block_number, include_tx=False)
block_src = conn.do(o)
if block_src == None:
logg.error('Block number {} not found'.format(block_number))
sys.exit(1)
return block_src
def get_block_hash(conn, block_hash, id_generator):
block_hash = add_0x(block_hash)
o = block_by_hash(block_hash, include_tx=False)
block_src = conn.do(o)
if block_src == None:
logg.error('Block hash {} not found'.format(block_hash))
sys.exit(1)
return block_src
def block_process(block_src):
return Block(block_src)
def main():
r = get_block(settings)
if not config.true('_RAW'):
r = r.to_human()
else:
r = repr(r)
if r != None:
print(r)
if __name__ == '__main__':
main()

View File

@ -8,43 +8,70 @@ import json
import logging
import select
# local imports
import chainlib.eth.cli
from chainlib.eth.address import AddressChecksum
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.tx import count
# external imports
from chainlib.settings import ChainSettings
from chainlib.chain import ChainSpec
from chainlib.jsonrpc import IntSequenceGenerator
from funga.eth.keystore.dict import DictKeystore
from funga.eth.signer import EIP155Signer
from hexathon import add_0x
logging.basicConfig(level=logging.WARNING)
# local imports
import chainlib.eth.cli
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from chainlib.eth.address import AddressChecksum
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.tx import count
from chainlib.eth.settings import process_settings
logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__))
config_dir = os.path.join(script_dir, '..', 'data', 'config')
arg_flags = chainlib.eth.cli.argflag_std_base_read | chainlib.eth.cli.Flag.WALLET
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
argparser.add_positional('address', type=str, help='Ethereum address of recipient')
def process_config_local(config, arg, args, flags):
config.add(args.address, '_RECIPIENT', False)
return config
argparser = chainlib.eth.cli.ArgumentParser()
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_BASE_READ | arg_flags.WALLET | arg_flags.UNSAFE
argparser = process_args(argparser, arg, flags)
argparser.add_argument('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)
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)
logg = process_log(args, logg)
logg.debug('flags {} {} {}'.format(flags, arg_flags.SEQ, flags & arg_flags.SEQ))
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
conn = rpc.connect_by_config(config)
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
settings = ChainSettings()
settings = process_settings(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
def main():
# TODO: should tolerate if address not prefixed with 0x
o = count(add_0x(holder_address), id_generator=rpc.id_generator)
r = conn.do(o)
o = count(
settings.get('RECIPIENT'),
id_generator=settings.get('RPC_ID_GENERATOR'),
)
r = settings.get('CONN').do(o)
count_result = None
try:
count_result = int(r, 16)

View File

@ -11,10 +11,24 @@ import select
# external imports
import chainlib.eth.cli
from chainlib.eth.tx import unpack
from chainlib.settings import ChainSettings
from chainlib.chain import ChainSpec
# local imports
import chainlib.eth.cli
from chainlib.eth.runnable.util import decode_for_puny_humans
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from chainlib.eth.settings import process_settings
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@ -22,16 +36,39 @@ logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__))
config_dir = os.path.join(script_dir, '..', 'data', 'config')
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)
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
def process_config_local(config, arg, args, flags):
config.add(args.tx_data, '_TX_DATA', False)
return config
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.VERBOSE | arg_flags.CHAIN_SPEC | arg_flags.RAW | arg_flags.ENV | arg_flags.SEQ
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
argparser.add_argument('tx_data', type=str, help='Transaction data to decode')
args = argparser.parse_args()
logg = process_log(args, logg)
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
settings = ChainSettings()
settings = process_settings(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
def main():
decode_for_puny_humans(args.tx_data, chain_spec, sys.stdout)
decode_for_puny_humans(
config.get('_TX_DATA'),
settings.get('CHAIN_SPEC'),
sys.stdout,
)
if __name__ == '__main__':
main()

View File

@ -21,6 +21,16 @@ from hexathon import (
# local imports
import chainlib.eth.cli
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from chainlib.eth.cli.encode import CLIEncoder
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.address import to_checksum
@ -47,22 +57,28 @@ script_dir = os.path.dirname(os.path.realpath(__file__))
config_dir = os.path.join(script_dir, '..', 'data', 'config')
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC | chainlib.eth.cli.Flag.FEE | chainlib.eth.cli.Flag.FMT_HUMAN | chainlib.eth.cli.Flag.FMT_WIRE | chainlib.eth.cli.Flag.FMT_RPC
arg_flags = flag_reset(arg_flags, chainlib.cli.Flag.NO_TARGET)
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
def process_config_local(config, arg, args, flags):
config.add(args.signature, '_SIGNATURE', False)
config.add(args.contract_args, '_CONTRACT_ARGS', False)
return config
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_WRITE | arg_flags.EXEC | arg_flags.FEE | arg_flags.FMT_HUMAN | arg_flags.FMT_WIRE | arg_flags.FMT_RPC
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
argparser.add_argument('--mode', type=str, choices=['tx', 'call', 'arg'], help='Mode of operation')
argparser.add_argument('--signature', type=str, help='Method signature to encode')
argparser.add_argument('contract_args', type=str, nargs='*', help='arguments to encode')
args = argparser.parse_args()
extra_args = {
'signature': None,
'contract_args': None,
}
config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_config_dir=config_dir)
logg.debug('config loaded:\n{}'.format(config))
block_all = args.ww
block_last = args.w or block_all
logg = process_log(args, logg)
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
wallet = chainlib.eth.cli.Wallet(EIP155Signer)
wallet.from_config(config)

View File

@ -14,56 +14,68 @@ from hexathon import (
add_0x,
strip_0x,
)
# local imports
from chainlib.eth.address import to_checksum_address
from chainlib.eth.connection import EthHTTPConnection
from chainlib.settings import ChainSettings
from chainlib.jsonrpc import (
JSONRPCRequest,
IntSequenceGenerator,
)
from chainlib.chain import ChainSpec
# local imports
from chainlib.eth.address import to_checksum_address
from chainlib.eth.connection import EthHTTPConnection
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
from chainlib.eth.address import (
is_same_address,
is_checksum_address,
)
import chainlib.eth.cli
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from chainlib.eth.settings import process_settings
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.WALLET
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
def process_config_local(config, arg, args, flags):
config.add(args.data, '_DATA', False)
config.add(args.amount, '_VALUE', False)
return config
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_WRITE | arg_flags.WALLET
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
argparser.add_argument('--data', type=str, help='Transaction data')
argparser.add_positional('amount', type=int, help='Token amount to send')
argparser.add_argument('amount', type=str, help='Token amount to send')
args = argparser.parse_args()
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
logg = process_log(args, logg)
wallet = chainlib.eth.cli.Wallet()
wallet.from_config(config)
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
conn = rpc.connect_by_config(config)
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
value = config.get('_AMOUNT')
send = config.true('_RPC_SEND')
settings = ChainSettings()
settings = process_settings(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
def balance(address, id_generator):
def balance(conn, address, id_generator):
o = gas_balance(address, id_generator=id_generator)
r = conn.do(o)
try:
@ -75,47 +87,71 @@ def balance(address, id_generator):
def main():
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())
g = Gas(
settings.get('CHAIN_SPEC'),
signer=settings.get('SIGNER'),
gas_oracle=settings.get('GAS_ORACLE'),
nonce_oracle=settings.get('NONCE_ORACLE'),
)
recipient = to_checksum_address(config.get('_RECIPIENT'))
if not config.true('_UNSAFE') and not is_checksum_address(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(add_0x(signer_address), rpc.id_generator)
recipient_balance = balance(add_0x(recipient), rpc.id_generator)
logg.debug('sender {} balance before: {}'.format(signer_address, sender_balance))
logg.debug('recipient {} balance before: {}'.format(recipient, recipient_balance))
sender_balance = balance(
settings.get('CONN'),
settings.get('SENDER_ADDRESS'),
settings.get('RPC_ID_GENERATOR'),
)
recipient_balance = balance(
settings.get('CONN'),
settings.get('RECIPIENT'),
settings.get('RPC_ID_GENERATOR'),
)
logg.debug('sender {} balance before: {}'.format(settings.get('SENDER_ADDRESS'), sender_balance))
logg.debug('recipient {} balance before: {}'.format(settings.get('RECIPIENT'), recipient_balance))
except urllib.error.URLError:
pass
(tx_hash_hex, o) = g.create(signer_address, add_0x(recipient), value, data=config.get('_DATA'), id_generator=rpc.id_generator)
(tx_hash_hex, o) = g.create(
settings.get('SENDER_ADDRESS'),
settings.get('RECIPIENT'),
settings.get('VALUE'),
data=config.get('_DATA'),
id_generator=settings.get('RPC_ID_GENERATOR'),
)
logg.info('gas transfer from {} to {} value {} hash {}'.format(settings.get('SENDER_ADDRESS'), settings.get('RECIPIENT'), settings.get('VALUE'), tx_hash_hex))
if send:
conn.do(o)
if block_last:
r = conn.wait(tx_hash_hex)
if settings.get('RPC_SEND'):
settings.get('CONN').do(o)
if config.true('_WAIT'):
r = settings.get('CONN').wait(tx_hash_hex)
if logg.isEnabledFor(logging.DEBUG):
sender_balance = balance(add_0x(signer_address), rpc.id_generator)
recipient_balance = balance(add_0x(recipient), rpc.id_generator)
logg.debug('sender {} balance after: {}'.format(signer_address, sender_balance))
logg.debug('recipient {} balance after: {}'.format(recipient, recipient_balance))
sender_balance = balance(
settings.get('CONN'),
settings.get('SENDER_ADDRESS'),
settings.get('RPC_ID_GENERATOR'),
)
recipient_balance = balance(
settings.get('CONN'),
settings.get('RECIPIENT'),
settings.get('RPC_ID_GENERATOR'),
)
logg.debug('sender {} balance before: {}'.format(settings.get('SENDER_ADDRESS'), sender_balance))
logg.debug('recipient {} balance before: {}'.format(settings.get('RECIPIENT'), recipient_balance))
if r['status'] == 0:
logg.critical('VM revert for {}. Wish I could tell you more'.format(tx_hash_hex))
sys.exit(1)
print(tx_hash_hex)
else:
#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)
decode_for_puny_humans(o['params'][0], settings.get('CHAIN_SPEC'), io_str)
print(io_str.getvalue())

View File

@ -23,12 +23,15 @@ from chainlib.jsonrpc import (
)
from chainlib.chain import ChainSpec
from chainlib.status import Status
from chainlib.settings import ChainSettings
# local imports
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.tx import (
Tx,
pack,
transaction,
receipt,
)
from chainlib.eth.address import (
to_checksum_address,
@ -41,35 +44,67 @@ from chainlib.eth.block import (
from chainlib.eth.runnable.util import decode_for_puny_humans
from chainlib.eth.jsonrpc import to_blockheight_param
import chainlib.eth.cli
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.contract import code
from chainlib.eth.cli.log import process_log
from chainlib.eth.settings import process_settings
logging.basicConfig(level=logging.WARNING, format='%(asctime)s %(levelname)s %(filename)s:%(lineno)d %(message)s')
logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__))
config_dir = os.path.join(script_dir, '..', 'data', 'config')
arg_flags = chainlib.eth.cli.argflag_std_base_read
arg_flags = chainlib.eth.cli.argflag_reset(arg_flags, chainlib.eth.cli.Flag.CHAIN_SPEC)
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
argparser.add_positional('item', type=str, help='Address or transaction to retrieve data for')
def process_config_local(config, arg, args, flags):
config.add(args.item, '_ITEM', False)
return config
def process_settings_local(settings, config):
item = config.get('_ITEM')
item = strip_0x(item)
if len(item) == 40:
config.add(item, '_RECIPIENT', False)
elif len(item) == 64:
config.add(item, '_HASH', False)
return process_settings(settings, config)
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_BASE_READ | arg_flags.TARGET
flags = arg_flags.less(flags, arg_flags.CHAIN_SPEC)
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
argparser.add_argument('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)
rpc = chainlib.eth.cli.Rpc()
conn = rpc.connect_by_config(config)
logg = process_log(args, logg)
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
item = add_0x(args.item)
settings = ChainSettings()
settings = process_settings_local(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
def get_transaction(conn, tx_hash, id_generator):
tx_hash = add_0x(tx_hash)
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'eth_getTransactionByHash'
o['params'].append(tx_hash)
o = j.finalize(o)
def get_transaction(conn, chain_spec, tx_hash, id_generator):
o = transaction(tx_hash, id_generator=id_generator)
tx_src = conn.do(o)
if tx_src == None:
logg.error('Transaction {} not found'.format(tx_hash))
@ -83,12 +118,8 @@ def get_transaction(conn, tx_hash, id_generator):
status = -1
rcpt = None
o = j.template()
o['method'] = 'eth_getTransactionReceipt'
o['params'].append(tx_hash)
o = j.finalize(o)
o = receipt(tx_hash, id_generator=id_generator)
rcpt = conn.do(o)
#status = int(strip_0x(rcpt['status']), 16)
if tx == None:
tx = Tx(tx_src)
@ -105,17 +136,10 @@ def get_transaction(conn, tx_hash, id_generator):
def get_address(conn, address, id_generator, height):
address = add_0x(address)
j = JSONRPCRequest(id_generator=id_generator)
o = j.template()
o['method'] = 'eth_getCode'
o['params'].append(address)
height = to_blockheight_param(height)
o['params'].append(height)
o = j.finalize(o)
code = conn.do(o)
o = code(address, height, id_generator=id_generator)
r = conn.do(o)
content = strip_0x(code, allow_empty=True)
content = strip_0x(r, allow_empty=True)
if len(content) == 0:
return None
@ -123,18 +147,24 @@ def get_address(conn, address, id_generator, height):
def main():
address = item
r = None
if len(address) > 42:
r = get_transaction(conn, address, rpc.id_generator)
if settings.get('HASH') != None:
hsh = settings.get('HASH')[0]
r = get_transaction(
settings.get('CONN'),
settings.get('CHAIN_SPEC'),
hsh,
settings.get('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'))
r = get_address(
settings.get('CONN'),
settings.get('RECIPIENT'),
settings.get('RPC_ID_GENERATOR'),
settings.get('HEIGHT'),
)
if r != None:
print(r)

View File

@ -17,6 +17,7 @@ from hexathon import (
)
import sha3
from funga.eth.signer import EIP155Signer
from chainlib.settings import ChainSettings
# local imports
from chainlib.eth.address import AddressChecksum
@ -35,10 +36,20 @@ from chainlib.eth.gas import (
price,
)
import chainlib.eth.cli
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from chainlib.eth.settings import process_settings
BLOCK_SAMPLES = 10
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__))
@ -54,34 +65,37 @@ results_translation = {
}
arg_flags = chainlib.eth.cli.argflag_std_read
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
def process_config_local(config, arg, args, flags):
config.add(args.local, '_LOCAL', False)
config.add(args.long, '_LONG', False)
config.add(args.entry, '_ENTRY', False)
if config.get('_ENTRY') != None:
if config.get('_ENTRY') not in results_translation.keys():
raise ValueError('Unknown entry {}'.format(config.get('_ENTRY')))
return config
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_READ | arg_flags.ENV
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
argparser.add_argument('--long', action='store_true', help='Calculate averages through sampling of blocks and txs')
argparser.add_argument('--local', action='store_true', help='Include local info')
argparser.add_positional('entry', required=False, help='Output single item')
argparser.add_argument('entry', nargs='?', help='Output single item')
args = argparser.parse_args()
extra_args = {
'local': None,
'long': None,
'entry': None,
}
config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_config_dir=config_dir)
logg = process_log(args, logg)
if config.get('_ENTRY') != None:
if config.get('_ENTRY') not in results_translation.keys():
raise ValueError('Unknown entry {}'.format(config.get('_ENTRY')))
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
rpc = chainlib.eth.cli.Rpc()
conn = rpc.connect_by_config(config)
settings = ChainSettings()
settings = process_settings(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
token_symbol = 'eth'
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
human = not config.true('_RAW')
longmode = config.true('_LONG')
def set_result(results, k, v, w=sys.stdout):
kt = results_translation[k]
@ -94,17 +108,16 @@ def set_result(results, k, v, w=sys.stdout):
def main():
human = not config.true('_RAW')
results = {}
o = network_id(id_generator=rpc.id_generator)
r = conn.do(o)
#if human:
# n = format(n, ',')
o = network_id(id_generator=settings.get('RPC_ID_GENERATOR'))
r = settings.get('CONN').do(o)
if set_result(results, 'network_id', r):
return
o = block_latest(id_generator=rpc.id_generator)
r = conn.do(o)
o = block_latest(id_generator=settings.get('RPC_ID_GENERATOR'))
r = settings.get('CONN').do(o)
try:
n = int(r, 16)
except ValueError:
@ -115,17 +128,17 @@ def main():
if set_result(results, 'block', n):
return
o = block_by_number(first_block_number, False, id_generator=rpc.id_generator)
r = conn.do(o)
o = block_by_number(first_block_number, False, id_generator=settings.get('RPC_ID_GENERATOR'))
r = settings.get('CONN').do(o)
last_block = Block(r)
last_timestamp = last_block.timestamp
if longmode:
if config.true('_LONG'):
aggr_time = 0.0
aggr_gas = 0
for i in range(BLOCK_SAMPLES):
o = block_by_number(first_block_number-i, False, id_generator=rpc.id_generator)
r = conn.do(o)
o = block_by_number(first_block_number-i, False, id_generator=settings.get('RPC_ID_GENERATOR'))
r = settings.get('CONN').do(o)
block = Block(r)
aggr_time += last_block.timestamp - block.timestamp
@ -144,8 +157,8 @@ def main():
if set_result(results, 'block_time', aggr_time / BLOCK_SAMPLES):
return
o = price(id_generator=rpc.id_generator)
r = conn.do(o)
o = price(id_generator=settings.get('RPC_ID_GENERATOR'))
r = settings.get('CONN').do(o)
n = int(r, 16)
if human:
n = format(n, ',')
@ -154,7 +167,7 @@ def main():
if config.get('_LOCAL'):
o = syncing()
r = conn.do(o)
r = settings.get('CONN').do(o)
if set_result(results, 'syncing', r):
return

View File

@ -10,13 +10,15 @@ import logging
import urllib
# external imports
import chainlib.eth.cli
from chainlib.settings import ChainSettings
from funga.eth.signer import EIP155Signer
from funga.eth.keystore.dict import DictKeystore
from hexathon import (
add_0x,
strip_0x,
)
from chainlib.error import SignerMissingException
from chainlib.chain import ChainSpec
# local imports
from chainlib.eth.address import to_checksum
@ -37,75 +39,79 @@ 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
import chainlib.eth.cli
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from chainlib.eth.settings import process_settings
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__))
config_dir = os.path.join(script_dir, '..', 'data', 'config')
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
def process_config_local(config, arg, args, flags):
config.add(args.deploy, '_DEPLOY', False)
config.add(args.mode, '_MODE', False)
config.add(args.data, '_DATA', False)
return config
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_WRITE | arg_flags.EXEC
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
argparser.add_argument('--deploy', action='store_true', help='Deploy data as contract')
argparser.add_argument('--mode', choices=['tx', 'call'], type=str, help='Mode of operation')
argparser.add_positional('data', type=str, help='Transaction data')
argparser.add_argument('data', type=str, help='Transaction data')
args = argparser.parse_args()
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
logg = process_log(args, logg)
wallet = chainlib.eth.cli.Wallet(EIP155Signer)
wallet.from_config(config)
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
conn = rpc.connect_by_config(config)
settings = ChainSettings()
settings = process_settings(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
send = config.true('_RPC_SEND')
chain_spec = None
try:
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
except AttributeError:
pass
def main():
if config.get('_EXEC_ADDRESS') != None or config.true('_DEPLOY'):
if not args.u and exec_address != exec_address:
raise ValueError('invalid checksum address')
signer_address = None
try:
signer = rpc.get_signer()
signer_address = rpc.get_signer_address()
except SignerMissingException:
pass
if config.get('_EXEC_ADDRESS') != None or args.deploy:
exec_address = None
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):
if not args.u and exec_address != exec_address:
raise ValueError('invalid checksum address')
if signer_address == None:
j = JSONRPCRequest(id_generator=rpc.id_generator)
if settings.get('SENDER_ADDRESS'):
j = JSONRPCRequest(id_generator=settings.get('RPC_ID_GENERATOR'))
o = j.template()
o['method'] = 'eth_call'
o['params'].append({
'to': exec_address,
'from': signer_address,
'to': settings.get('EXEC'),
'from': settings.get('SENDER_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),
'data': add_0x(config.get('_DATA')),
})
height = to_blockheight_param(config.get('_HEIGHT'))
o['params'].append(height)
o = j.finalize(o)
r = conn.do(o)
r = settings.get('CONN').do(o)
try:
print(strip_0x(r))
except ValueError:
@ -113,17 +119,26 @@ def main():
sys.exit(1)
else:
if chain_spec == None:
if settings.get('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, exec_address, use_nonce=True)
if args.data != None:
tx = g.set_code(tx, add_0x(args.data))
g = TxFactory(
settings.get('CHAIN_SPEC'),
signer=settings.get('SIGNER'),
gas_oracle=settings.get('GAS_ORACLE'),
nonce_oracle=settings.get('NONCE_ORACLE'),
)
tx = g.template(
settings.get('SENDER_ADDRESS'),
settings.get('EXEC'),
use_nonce=True,
)
if config.get('_DATA') != None:
tx = g.set_code(tx, add_0x(config.get('_DATA')))
(tx_hash_hex, o) = g.finalize(tx, id_generator=rpc.id_generator)
if send:
r = conn.do(o)
r = settings.get('CONN').do(o)
print(r)
else:
if config.get('_RAW'):
@ -131,11 +146,11 @@ def main():
print(o)
else:
o = raw(args.data, id_generator=rpc.id_generator)
if send:
r = conn.do(o)
if block_last:
r = conn.wait(tx_hash_hex)
o = raw(config.get('_DATA'), id_generator=settings.get('RPC_ID_GENERATOR'))
if settings.get('RPC_SEND'):
r = settings.get('CONN').do(o)
if config.true('_WAIT'):
r = settings.get('CONN').wait(tx_hash_hex)
if r['status'] == 0:
logg.critical('VM revert for {}. Wish I could tell you more'.format(tx_hash_hex))
sys.exit(1)

View File

@ -10,9 +10,14 @@ import logging
import urllib
# external imports
import chainlib.eth.cli
from chainlib.settings import ChainSettings
from funga.eth.signer import EIP155Signer
from funga.eth.keystore.dict import DictKeystore
from chainlib.chain import ChainSpec
from chainlib.jsonrpc import (
JSONRPCRequest,
IntSequenceGenerator,
)
from hexathon import (
add_0x,
strip_0x,
@ -22,10 +27,6 @@ from hexathon import (
# local imports
from chainlib.eth.address import to_checksum
from chainlib.eth.connection import EthHTTPConnection
from chainlib.jsonrpc import (
JSONRPCRequest,
IntSequenceGenerator,
)
from chainlib.eth.nonce import (
RPCNonceOracle,
OverrideNonceOracle,
@ -39,37 +40,60 @@ from chainlib.eth.tx import (
raw,
)
from chainlib.eth.error import RevertEthException
from chainlib.chain import ChainSpec
from chainlib.eth.runnable.util import decode_for_puny_humans
from chainlib.eth.jsonrpc import to_blockheight_param
import chainlib.eth.cli
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from chainlib.eth.settings import process_settings
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
script_dir = os.path.dirname(os.path.realpath(__file__))
config_dir = os.path.join(script_dir, '..', 'data', 'config')
arg_flags = chainlib.eth.cli.argflag_std_read
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
def process_config_local(config, arg, args, flags):
config.add(args.ignore, '_IGNORE', False)
config.add(args.ignore_all, '_IGNORE_ALL', False)
config.add(args.hashes, '_HASH', False)
return config
def process_settings_local(settings, config):
settings.set('HASH', config.get('_HASH'))
return settings
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_READ
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
argparser.add_argument('--ignore', type=str, action='append', default=[], help='Ignore error from the given transaction')
argparser.add_argument('--ignore-all', action='store_true', dest='ignore_all', help='Ignore errors from all transactions')
argparser.add_positional('hashes', append=True, type=str, help='Transaction hashes to wait for')
argparser.add_argument('hashes', nargs='*', type=str, help='Transaction hashes to wait for')
args = argparser.parse_args()
extra_args = {
'ignore': None,
'ignore_all': None,
'hashes': None,
}
config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_config_dir=config_dir)
rpc = chainlib.eth.cli.Rpc()
conn = rpc.connect_by_config(config)
logg = process_log(args, logg)
chain_spec = None
try:
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
except AttributeError:
pass
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
logg.debug('config loaded:\n{}'.format(config))
settings = ChainSettings()
settings = process_settings(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
def main():
@ -80,29 +104,30 @@ def main():
for hsh in config.get('_IGNORE'):
hashes_ignore.append(add_0x(hex_uniform(strip_0x(hsh))))
if len(config.get('_HASHES')) == 1:
if len(settings.get('HASH')) == 1:
hsh = settings.get('HASH')[0]
try:
hsh = add_0x(hex_uniform(strip_0x(config.get('_HASHES')[0])))
hashes_ready = [hsh]
except ValueError:
logg.debug('hash argument not a hash, will try it as a file name')
f = open(config.get('_HASHES')[0])
f = open(hsh)
for hsh in f:
logg.debug('hshs {}'.format(hsh))
hashes_ready.append(add_0x(hex_uniform(strip_0x(hsh.rstrip()))))
hashes_ready.append(hsh)
f.close()
else:
for hsh in config.get('_HASHES'):
logg.debug('hsh {}'.format(hsh))
hashes_ready.append(add_0x(hex_uniform(strip_0x(hsh))))
for hsh in settings.get('HASH'):
if hsh in hashes_ready:
logg.debug('skipping duplicate hash {}'.format(hsh))
continue
hashes_ready.append(hsh)
for hsh in hashes_ready:
logg.debug('processing transaction hash {}'.format(hsh))
logg.info('processing transaction hash {}'.format(hsh))
try:
r = conn.wait(hsh)
r = settings.get('CONN').wait(hsh)
except RevertEthException:
if config.get('_IGNORE_ALL') or hsh in hashes_ignore:
logg.info('ignoring revert in transaction hash {}'.format(hsh))
logg.debug('ignoring revert in transaction hash {}'.format(hsh))
continue
sys.stderr.write('revert in transaction hash {}\n'.format(hsh))
sys.exit(1)
@ -110,5 +135,3 @@ def main():
if __name__ == '__main__':
main()

156
chainlib/eth/settings.py Normal file
View File

@ -0,0 +1,156 @@
# external imports
from chainlib.settings import process_settings as base_process_settings
from chainlib.error import SignerMissingException
from hexathon import (
add_0x,
strip_0x,
)
from chainlib.block import BlockSpec
# local imports
import chainlib.eth.cli
from chainlib.eth.address import to_checksum_address
def process_settings_rpc(settings, config):
rpc = chainlib.eth.cli.Rpc(settings.get('WALLET'))
conn = rpc.connect_by_config(config)
settings.set('CONN', conn)
settings.set('RPC_ID_GENERATOR', rpc.id_generator)
settings.set('RPC_SEND', config.true('_RPC_SEND'))
gas_oracle = rpc.get_gas_oracle()
settings.set('GAS_ORACLE', gas_oracle)
try:
settings.set('SIGNER', rpc.get_signer())
sender_address = rpc.get_sender_address()
settings.set('SENDER_ADDRESS', add_0x(sender_address))
except AttributeError:
pass
except SignerMissingException:
pass
nonce_oracle = rpc.get_nonce_oracle()
settings.set('NONCE_ORACLE', nonce_oracle)
return settings
def process_settings_blockspec(settings, config):
blockspec_in = None
try:
blockspec_in = config.get('_HEIGHT')
except KeyError:
return settings
blockspec = None
if blockspec_in == 'latest':
blockspec = BlockSpec.LATEST
elif blockspec_in == 'pending':
blockspec = BlockSpec.PENDING
else:
blockspec = int(blockspec_in)
settings.set('HEIGHT', blockspec)
return settings
def process_settings_wallet(settings, config):
wallet = chainlib.eth.cli.Wallet()
wallet.from_config(config)
settings.set('WALLET', wallet)
recipient_in = None
try:
recipient_in = config.get('_RECIPIENT')
except KeyError:
return settings
if recipient_in == None:
return settings
if wallet.get_signer_address() == None and recipient_in != None:
recipient_in = wallet.from_address(recipient_in)
recipient_in = strip_0x(recipient_in)
recipient = to_checksum_address(recipient_in)
if not config.true('_UNSAFE') and recipient != recipient_in:
raise ValueError('invalid checksum address: {}'.format(recipient_in))
recipient = add_0x(recipient)
settings.set('RECIPIENT', recipient)
return settings
def process_settings_contract(settings, config):
exec_address_in = None
try:
exec_address_in = config.get('_EXEC_ADDRESS')
except KeyError:
return settings
if exec_address_in == None:
return settings
exec_address = to_checksum_address(exec_address_in)
if not config.true('_UNSAFE') and exec_address != exec_address_in:
raise ValueError('invalid checksum address: {}'.format(exec_address_in))
exec_address = add_0x(exec_address)
settings.set('EXEC', exec_address)
return settings
def process_settings_data(settings, config):
data = None
try:
data = config.get('_DATA')
except KeyError:
return settings
if data == None:
return settings
data = add_0x(data)
settings.set('DATA', data)
return settings
def process_settings_hash(settings, config):
hshs = None
try:
hshs = config.get('_HASH')
except KeyError:
return settings
if isinstance(hshs, str):
hshs = [hshs]
r = []
for hsh in hshs:
hsh = strip_0x(hsh)
l = len(hsh)
if l != 64:
raise ValueError('invalid hash length {} for {}'.format(l, hsh))
hsh = add_0x(hsh)
r.append(hsh)
settings.set('HASH', r)
return settings
def process_settings(settings, config):
settings = base_process_settings(settings, config)
settings = process_settings_wallet(settings, config)
settings = process_settings_rpc(settings, config)
settings = process_settings_blockspec(settings, config)
settings = process_settings_data(settings, config)
settings = process_settings_hash(settings, config)
settings = process_settings_contract(settings, config)
return settings

53
chainlib/eth/src.py Normal file
View File

@ -0,0 +1,53 @@
# standard imports
import logging
import json
# external imports
from potaahto.symbols import snake_and_camel
from hexathon import (
uniform,
strip_0x,
)
# local imports
from chainlib.src import (
Src as BaseSrc,
SrcItem,
)
logg = logging.getLogger(__name__)
class Src(BaseSrc):
@classmethod
def src_normalize(self, v):
src = snake_and_camel(v)
logg.debug('normalize has {}'.format(src))
if isinstance(src.get('v'), str):
try:
src['v'] = int(src['v'])
except ValueError:
src['v'] = int(src['v'], 16)
return src
def normal(self, v, typ=SrcItem.AUTO):
if typ == SrcItem.SRC:
return self.src_normalize(v)
if typ == SrcItem.HASH:
v = strip_0x(v, pad=False)
v = uniform(v, compact_value=True)
elif typ == SrcItem.ADDRESS:
v = strip_0x(v, pad=False)
v = uniform(v, compact_value=True)
elif typ == SrcItem.PAYLOAD:
v = strip_0x(v, pad=False, allow_empty=True)
v = uniform(v, compact_value=False, allow_empty=True)
return v
def __repr__(self):
return json.dumps(self.src)

View File

@ -10,6 +10,8 @@ from hexathon import (
strip_0x,
add_0x,
compact,
to_int as hex_to_int,
same as hex_same,
)
from rlp import decode as rlp_decode
from rlp import encode as rlp_encode
@ -22,12 +24,17 @@ from potaahto.symbols import snake_and_camel
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.tx import (
Tx as BaseTx,
TxResult as BaseTxResult,
)
from chainlib.eth.nonce import (
nonce as nonce_query,
nonce_confirmed as nonce_query_confirmed,
)
from chainlib.eth.address import is_same_address
from chainlib.block import BlockSpec
from chainlib.src import SrcItem
# local imports
from .address import to_checksum
@ -39,6 +46,7 @@ from .constant import (
)
from .contract import ABIContractEncoder
from .jsonrpc import to_blockheight_param
from .src import Src
logg = logging.getLogger(__name__)
@ -402,12 +410,14 @@ class TxFactory:
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
txes = txe.serialize()
gas_price = strip_0x(txes['gasPrice'])
gas_price = compact(gas_price)
gas = strip_0x(txes['gas'])
gas = compact(gas)
return {
'from': tx['from'],
'to': txes['to'],
'gasPrice': add_0x(compact(gas_price)),
'gas': add_0x(compact(gas)),
'gasPrice': add_0x(gas_price, compact_value=True),
'gas': add_0x(gas, compact_value=True),
'data': txes['data'],
}
@ -510,7 +520,51 @@ class TxFactory:
return o
class Tx(BaseTx):
class TxResult(BaseTxResult, Src):
def apply_src(self, v):
self.contract = None
v = super(TxResult, self).apply_src(v)
self.set_hash(v['transaction_hash'])
try:
status_number = int(v['status'], 16)
except TypeError:
status_number = int(v['status'])
except KeyError as e:
if strict:
raise(e)
logg.debug('setting "success" status on missing status property for {}'.format(self.hash))
status_number = 1
if v['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 = hex_to_int(v['transaction_index'])
except TypeError:
self.tx_index = int(v['transaction_index'])
self.block_hash = v['block_hash']
# TODO: replace with rpc receipt/transaction translator when available
contract_address = v.get('contract_address')
if contract_address != None:
self.contract = contract_address
self.logs = v['logs']
try:
self.fee_cost = hex_to_int(v['gas_used'])
except TypeError:
self.fee_cost = int(v['gas_used'])
class Tx(BaseTx, Src):
"""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.
@ -527,114 +581,88 @@ class Tx(BaseTx):
#:todo: divide up constructor method
"""
def __init__(self, src, block=None, rcpt=None, strict=False):
self.__rcpt_block_hash = None
src = self.src_normalize(src)
self.index = -1
tx_hash = add_0x(src['hash'])
self.hash = strip_0x(tx_hash)
if block != None:
self.apply_block(block)
try:
self.value = int(strip_0x(src['value']), 16)
except TypeError:
self.value = int(src['value'])
try:
self.nonce = int(strip_0x(src['nonce']), 16)
except TypeError:
self.nonce = int(src['nonce'])
address_from = strip_0x(src['from'])
try:
self.gas_price = int(strip_0x(src['gasPrice']), 16)
except TypeError:
self.gas_price = int(src['gasPrice'])
try:
self.gas_limit = int(strip_0x(src['gas']), 16)
except TypeError:
self.gas_limit = int(src['gas'])
self.outputs = [to_checksum(address_from)]
def __init__(self, src, block=None, result=None, strict=False, rcpt=None):
# backwards compat
self.gas_price = None
self.gas_limit = None
self.contract = None
self.v = None
self.r = None
self.s = None
self.fee_limit = self.gas_limit
self.fee_price = self.gas_price
super(Tx, self).__init__(src, block=block, result=result, strict=strict)
if result == None and rcpt != None:
self.apply_receipt(rcpt)
def apply_src(self, src):
try:
inpt = src['input']
except KeyError:
inpt = src['data']
src['input'] = src['data']
if inpt != '0x':
inpt = strip_0x(inpt)
else:
inpt = ''
self.payload = inpt
src = super(Tx, self).apply_src(src)
hsh = self.normal(src['hash'], SrcItem.HASH)
self.set_hash(hsh)
try:
self.value = hex_to_int(src['value'])
except TypeError:
self.value = int(src['value'])
try:
self.nonce = hex_to_int(src['nonce'])
except TypeError:
self.nonce = int(src['nonce'])
try:
self.fee_limit = hex_to_int(src['gas'])
except TypeError:
self.fee_limit = int(src['gas'])
try:
self.fee_price = hex_to_int(src['gas_price'])
except TypeError:
self.fee_price = int(src['gas_price'])
self.gas_price = self.fee_price
self.gas_limit = self.fee_limit
address_from = self.normal(src['from'], SrcItem.ADDRESS)
self.outputs = [to_checksum(address_from)]
to = src['to']
if to == None:
to = ZERO_ADDRESS
self.inputs = [to_checksum(strip_0x(to))]
self.block = block
self.payload = self.normal(src['input'], SrcItem.PAYLOAD)
try:
self.wire = src['raw']
self.set_wire(src['raw'])
except KeyError:
logg.debug('no inline raw tx src, and no raw rendering implemented, field will be "None"')
self.status = Status.PENDING
self.logs = None
self.tx_rcpt_src = None
if rcpt != None:
self.apply_receipt(rcpt, strict=strict)
self.v = src.get('v')
self.r = src.get('r')
self.s = src.get('s')
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):
try:
src['v'] = int(src['v'])
except ValueError:
src['v'] = int(src['v'], 16)
return src
#self.status = Status.PENDING
def as_dict(self):
return self.src()
def rcpt_src(self):
return self.tx_rcpt_src
return self.src
def apply_receipt(self, rcpt, strict=False):
result = TxResult(src=rcpt)
self.apply_result(result)
def apply_result(self, result, strict=False):
"""Apply receipt data to transaction object.
Effect is the same as passing a receipt at construction.
@ -642,53 +670,14 @@ class Tx(BaseTx):
:param rcpt: Receipt data
:type rcpt: dict
"""
rcpt = self.src_normalize(rcpt)
logg.debug('rcpt {}'.format(rcpt))
self.tx_rcpt_src = rcpt
if not hex_same(result.hash, self.hash):
raise ValueError('result hash {} does not match transaction hash {}'.format(result.hash, self.hash))
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))
if not hex_same(result.block_hash, self.block.hash):
raise ValueError('result block hash {} does not match transaction block hash {}'.format(result.block_hash, self.block.hash))
try:
status_number = int(rcpt['status'], 16)
except TypeError:
status_number = int(rcpt['status'])
except KeyError as e:
if strict:
raise(e)
logg.debug('setting "success" status on missing status property for {}'.format(self.hash))
status_number = 1
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:
contract_address = rcpt.get('contract_address')
if contract_address != None:
self.contract = contract_address
self.logs = rcpt['logs']
try:
self.gas_used = int(rcpt['gasUsed'], 16)
except TypeError:
self.gas_used = int(rcpt['gasUsed'])
self.__rcpt_block_hash = rcpt['block_hash']
super(Tx, self).apply_result(result)
def apply_block(self, block):
@ -697,9 +686,6 @@ class Tx(BaseTx):
: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
@ -713,8 +699,8 @@ class Tx(BaseTx):
:returns: Wire format, in hex
"""
if self.wire == None:
b = pack(self.src(), chain_spec)
self.wire = add_0x(b.hex())
b = pack(self.src, chain_spec)
self.set_wire(add_0x(b.hex()))
return self.wire
@ -763,13 +749,16 @@ input {}
self.payload,
)
if self.status != Status.PENDING:
status = Status.UNKNOWN.name
logg.debug('selfstatus {}'.format(self.status))
if self.result != None and self.result.status != Status.PENDING:
s += """gas_used {}
""".format(
self.gas_used,
self.result.fee_cost,
)
status = self.result.status.name
s += 'status ' + self.status.name + '\n'
s += 'status ' + status + '\n'
if self.block != None:
s += """block_number {}
@ -778,7 +767,7 @@ tx_index {}
""".format(
self.block.number,
self.block.hash,
self.tx_index,
self.result.tx_index,
)
@ -791,7 +780,9 @@ tx_index {}
if self.wire != None:
s += """src {}
""".format(
self.wire,
str(self.wire),
)
return s

View File

@ -1,7 +1,6 @@
funga-eth~=0.6.0
funga-eth~=0.6.1
pysha3==1.0.2
hexathon~=0.1.5
hexathon~=0.1.7
websocket-client==0.57.0
potaahto~=0.1.1
chainlib~=0.1.0
confini~=0.6.0
chainlib~=0.3.0

View File

@ -1,10 +1,10 @@
[metadata]
name = chainlib-eth
version = 0.1.1
version = 0.3.1
description = Ethereum implementation of the chainlib interface
author = Louis Holbrook
author_email = dev@holbrook.no
url = https://gitlab.com/chaintools/chainlib
url = https://gitlab.com/chaintool/chainlib-eth
keywords =
dlt
blockchain
@ -26,7 +26,7 @@ licence_files =
[options]
include_package_data = True
python_requires = >= 3.6
python_requires = >= 3.7
packages =
chainlib.eth
chainlib.eth.dialect
@ -48,3 +48,4 @@ console_scripts =
eth-info = chainlib.eth.runnable.info:main
eth-nonce = chainlib.eth.runnable.count:main
eth-wait = chainlib.eth.runnable.wait:main
eth-block = chainlib.eth.runnable.block:main

View File

@ -1,12 +1,67 @@
# standard imports
import unittest
import os
import datetime
import logging
# local imports
from chainlib.eth.jsonrpc import to_blockheight_param
from chainlib.eth.block import Block
logging.basicConfig(level=logging.DEBUG)
class TestBlock(unittest.TestCase):
def test_block(self):
tx_one_src = {
'hash': os.urandom(32).hex(),
'from': os.urandom(20).hex(),
'to': os.urandom(20).hex(),
'value': 13,
'data': '0xdeadbeef',
'nonce': 666,
'gasPrice': 100,
'gas': 21000,
}
tx_two_src_hash = os.urandom(32).hex()
block_hash = os.urandom(32).hex()
parent_hash = os.urandom(32).hex()
block_author = os.urandom(20).hex()
block_time = datetime.datetime.utcnow().timestamp()
block_src = {
'number': 42,
'hash': block_hash,
'author': block_author,
'transactions': [
tx_one_src,
tx_two_src_hash,
],
'timestamp': block_time,
'gas_used': '0x1234',
'gas_limit': '0x2345',
'parent_hash': parent_hash
}
block = Block(block_src)
self.assertEqual(block.number, 42)
self.assertEqual(block.hash, block_hash)
self.assertEqual(block.author, block_author)
self.assertEqual(block.timestamp, int(block_time))
tx_index = block.tx_index_by_hash(tx_one_src['hash'])
self.assertEqual(tx_index, 0)
tx_retrieved = block.tx_by_index(tx_index)
self.assertEqual(tx_retrieved.hash, tx_one_src['hash'])
tx_index = block.tx_index_by_hash(tx_two_src_hash)
self.assertEqual(tx_index, 1)
def test_blockheight_param(self):
self.assertEqual(to_blockheight_param('latest'), 'latest')
self.assertEqual(to_blockheight_param(0), 'latest')

51
tests/test_cli.py Normal file
View File

@ -0,0 +1,51 @@
# standard imports
import unittest
import os
import logging
# external imports
from aiee.arg import process_args
# local imports
#from chainlib.cli.base import argflag_std_base
from chainlib.eth.cli.arg import (
ArgFlag,
Arg,
ArgumentParser,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
script_dir = os.path.dirname(os.path.realpath(__file__))
data_dir = os.path.join(script_dir, 'testdata')
config_dir = os.path.join(data_dir, 'config')
logging.basicConfig(level=logging.DEBUG)
class TestCli(unittest.TestCase):
def setUp(self):
self.flags = ArgFlag()
self.arg = Arg(self.flags)
def test_args_process_single(self):
ap = ArgumentParser()
flags = self.flags.VERBOSE | self.flags.CONFIG
process_args(ap, self.arg, flags)
argv = [
'-vv',
'-n',
'foo',
]
args = ap.parse_args(argv)
config = Config(config_dir)
config = process_config(config, self.arg, args, flags)
self.assertEqual(config.get('CONFIG_USER_NAMESPACE'), 'foo')
if __name__ == '__main__':
unittest.main()

View File

@ -1,6 +1,7 @@
# standard imports
import unittest
import datetime
import os
# external imports
from chainlib.stat import ChainStat
@ -19,6 +20,10 @@ class TestStat(unittest.TestCase):
'hash': None,
'transactions': [],
'number': 41,
'author': os.urandom(20).hex(),
'gas_used': '0x1234',
'gas_limit': '0x2345',
'parent_hash': None,
})
d = datetime.datetime.utcnow()
@ -27,6 +32,10 @@ class TestStat(unittest.TestCase):
'hash': None,
'transactions': [],
'number': 42,
'author': os.urandom(20).hex(),
'gas_used': '0x1234',
'gas_limit': '0x2345',
'parent_hash': None,
})
s.block_apply(block_a)
@ -39,6 +48,10 @@ class TestStat(unittest.TestCase):
'hash': None,
'transactions': [],
'number': 43,
'author': os.urandom(20).hex(),
'gas_used': '0x1234',
'gas_limit': '0x2345',
'parent_hash': None,
})
s.block_apply(block_c)

View File

@ -30,6 +30,7 @@ from chainlib.eth.address import (
from hexathon import (
strip_0x,
add_0x,
same as hex_same,
)
from chainlib.eth.block import Block
@ -39,6 +40,27 @@ logg = logging.getLogger()
class TxTestCase(EthTesterCase):
def test_tx_basic(self):
tx_src = {
'hash': os.urandom(32).hex(),
'from': os.urandom(20).hex(),
'to': os.urandom(20).hex(),
'value': 13,
'data': '0xdeadbeef',
'nonce': 666,
'gasPrice': 100,
'gas': 21000,
}
tx = Tx(tx_src)
self.assertEqual(tx.hash, tx_src['hash'])
self.assertTrue(is_same_address(tx.outputs[0], tx_src['from']))
self.assertTrue(is_same_address(tx.inputs[0], tx_src['to']))
self.assertEqual(tx.value, tx_src['value'])
self.assertEqual(tx.nonce, tx_src['nonce'])
self.assertTrue(hex_same(tx.payload, tx_src['data']))
def test_tx_reciprocal(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
@ -60,7 +82,7 @@ class TxTestCase(EthTesterCase):
o = transaction(tx_hash_hex)
tx_src = self.rpc.do(o)
tx = Tx(tx_src)
tx_bin = pack(tx.src(), self.chain_spec)
tx_bin = pack(tx.src, self.chain_spec)
def test_tx_pack(self):
@ -107,6 +129,10 @@ class TxTestCase(EthTesterCase):
'number': 42,
'timestamp': 13241324,
'transactions': [],
'author': os.urandom(20).hex(),
'gas_used': 21000,
'gas_limit': '0x2345',
'parent_hash': None,
})
with self.assertRaises(AttributeError):
tx = Tx(tx_data, block=block)
@ -149,7 +175,12 @@ class TxTestCase(EthTesterCase):
'number': 42,
'timestamp': 13241324,
'transactions': [],
'author': os.urandom(20).hex(),
'gas_used': 21000,
'gas_limit': '0x2345',
'parent_hash': None,
})
block.txs = [add_0x(tx_data['hash'])]
with self.assertRaises(ValueError):
tx = Tx(tx_data, rcpt=rcpt, block=block)