62 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
lash
a71b3d6388 Add changelog detail 2022-04-26 21:36:44 +00:00
lash
2dd92fe74b Add fee limit, fee price to Tx object 2022-04-19 19:46:11 +00:00
lash
129e25bbf5 Bump chainlib dep 2022-04-10 19:05:58 +00:00
lash
a76020b13b Add dict output 2022-04-10 19:04:50 +00:00
lash
3cfeb9ce84 Upgrade deps 2022-03-06 19:33:09 +00:00
lash
601db731d9 Handle int instead of hex signature values in pack from eth_tester 2022-03-01 14:43:43 +00:00
lash
c954f4d1b4 Allow encode without nonce when not tx, add rcpt src to tx 2022-03-01 07:50:30 +00:00
lash
b59eebe181 Add remaining man pages 2022-02-25 11:07:40 +00:00
lash
2f906037a9 Add more detail on examples 2022-02-24 20:05:38 +00:00
lash
1362c9aac6 WIP add eth-encode overrides 2022-02-24 19:00:54 +00:00
lash
e5e8680fc6 Improve eth-encode interface with --format and --mode options 2022-02-24 15:51:38 +00:00
lash
90d4c4eb95 Add man page for eth-encode 2022-02-24 12:07:56 +00:00
lash
edf8c4b604 Add chain spec example for eth-gas 2022-02-24 11:40:35 +00:00
lash
f61ac91205 Auto-apply overrides and examples if they exist, add examples for eth-gas 2022-02-24 11:32:33 +00:00
lash
7e6daa0be3 Add get man page, remove chain spec arg from get, info 2022-02-24 10:36:15 +00:00
lash
a3081367db Add makefile for man 2022-02-23 13:16:04 +00:00
lash
d49e3dd6ec Add environment description override 2022-02-23 09:47:03 +00:00
lash
d3f2b1f1fa WIP add enviornment support 2022-02-22 14:47:06 +00:00
lash
79a1123dc8 Upgrade chainlib, allow passphrase file for wallet key 2022-02-20 18:27:51 +00:00
lash
5c09630b6e Skip buggy hexathon 2022-02-14 13:57:37 +00:00
lash
e9f31ed7f1 Make block by number compatible with geth 2022-02-12 12:30:52 +00:00
51 changed files with 1742 additions and 472 deletions

View File

@@ -1,5 +1,33 @@
- 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:
* Allow nonce ommission in encode when not tx mode
* Add rcpt src to tx object
- 0.0.27:
* Add man pages with chainlib man page generator helper
* Remove redundant arg flags from runnables: get
- 0.0.26:
* Remove manual 0x handling bug in tx
- 0.0.25:
* Upgrade chainlib to get passphrase file for wallet keyfile
- 0.0.24:
* Upgrade from hexathon bug breaking compact hex function
- 0.0.23:
* Make get block args and responses compatible with picky golang marhaling in geth
- 0.0.22:
* Enable unpack of pre EIP-155 transactions
* Allow missing status property of receipts in non-strict modes
- 0.0.21:
* Remove warnings from cytoolz/rlp in funga-eth
- 0.0.15:

16
Makefile Normal file
View File

@@ -0,0 +1,16 @@
PREFIX ?= /usr/local
BUILD_DIR = build/$(PREFIX)/share/man
man:
mkdir -vp $(BUILD_DIR)
chainlib-man.py -b `PYTHONPATH=. python chainlib/eth/runnable/flags.py gas` -v -n eth-gas -d $(BUILD_DIR)/ man
chainlib-man.py -b `PYTHONPATH=. python chainlib/eth/runnable/flags.py info` -v -n eth-info -d $(BUILD_DIR)/ man
chainlib-man.py -b `PYTHONPATH=. python chainlib/eth/runnable/flags.py get` -v -n eth-get -d $(BUILD_DIR)/ man
chainlib-man.py -b `PYTHONPATH=. python chainlib/eth/runnable/flags.py decode` -v -n eth-decode -d $(BUILD_DIR)/ man
chainlib-man.py -b `PYTHONPATH=. python chainlib/eth/runnable/flags.py encode` -v -n eth-encode -d $(BUILD_DIR)/ man
chainlib-man.py -b `PYTHONPATH=. python chainlib/eth/runnable/flags.py count` -v -n eth-count -d $(BUILD_DIR)/ man
chainlib-man.py -b `PYTHONPATH=. python chainlib/eth/runnable/flags.py raw` -v -n eth-raw -d $(BUILD_DIR)/ man
chainlib-man.py -b `PYTHONPATH=. python chainlib/eth/runnable/flags.py wait` -v -n eth-wait -d $(BUILD_DIR)/ man
chainlib-man.py -b `PYTHONPATH=. python chainlib/eth/runnable/flags.py balance` -v -n eth-balance -d $(BUILD_DIR)/ man
.PHONY: man

View File

@@ -1,14 +1,22 @@
# standard imports
import logging
import datetime
# external imports
from chainlib.jsonrpc import JSONRPCRequest
from chainlib.block import Block as BaseBlock
from hexathon import (
add_0x,
strip_0x,
even,
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):
@@ -34,7 +42,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:]))
hx = strip_0x(hex(n))
nhx = add_0x(compact(hx), compact_value=True)
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_getBlockByNumber'
@@ -74,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
@@ -84,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)
@@ -116,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,9 +1,6 @@
# external imports
from chainlib.cli import (
ArgumentParser,
argflag_std_read,
argflag_std_write,
argflag_std_base,
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 =

2
chainlib/eth/data/env/env.ini vendored Normal file
View File

@@ -0,0 +1,2 @@
[rpc]
provider = Fully-qualified URL to EVM node's RPC endpoint.

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_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
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

@@ -11,8 +11,7 @@ import urllib
import sha3
# external imports
import chainlib.eth.cli
from chainlib.eth.cli.encode import CLIEncoder
from chainlib.cli import flag_reset
from funga.eth.signer import EIP155Signer
from funga.eth.keystore.dict import DictKeystore
from hexathon import (
@@ -21,6 +20,18 @@ 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
from chainlib.eth.connection import EthHTTPConnection
@@ -28,14 +39,6 @@ from chainlib.jsonrpc import (
JSONRPCRequest,
IntSequenceGenerator,
)
from chainlib.eth.nonce import (
RPCNonceOracle,
OverrideNonceOracle,
)
from chainlib.eth.gas import (
RPCGasOracle,
OverrideGasOracle,
)
from chainlib.eth.tx import (
TxFactory,
TxFormat,
@@ -53,21 +56,29 @@ 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 | chainlib.eth.cli.Flag.FEE
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
argparser.add_argument('--notx', action='store_true', help='Network send is not a transaction')
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,
'notx': None,
}
config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_config_dir=config_dir)
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)
@@ -102,28 +113,64 @@ def main():
code += cli_encoder.get()
if not config.get('_SIGNATURE'):
print(strip_0x(code))
return
exec_address = config.get('_EXEC_ADDRESS')
if exec_address:
exec_address = add_0x(to_checksum_address(exec_address))
if signer == None or config.true('_NOTX'):
if config.true('_RAW'):
print(strip_0x(code))
return
mode = args.mode
if mode == None:
if signer == None:
mode = 'call'
else:
mode = 'tx'
if not exec_address:
argparser.error('exec address (-e) must be defined')
if not config.get('_SIGNATURE'):
if mode != 'arg':
logg.error('mode tx without contract method signature makes no sense. Use eth-get with --data instead.')
sys.exit(1)
if args.format == 'rpc':
logg.error('rpc format with arg put does not make sense')
sys.exit(1)
if mode == 'arg':
print(strip_0x(code))
return
elif not exec_address:
logg.error('exec address (-e) must be defined with mode "{}"'.format(args.mode))
sys.exit(1)
if config.get('RPC_PROVIDER'):
logg.debug('provider {}'.format(config.get('RPC_PROVIDER')))
if not config.get('_FEE_LIMIT') or not config.get('_FEE_PRICE'):
gas_oracle = rpc.get_gas_oracle()
(price, limit) = gas_oracle.get_gas()
if not config.get('_FEE_PRICE'):
config.add(price, '_FEE_PRICE')
if not config.get('_FEE_LIMIT'):
config.add(limit, '_FEE_LIMIT')
if mode == 'tx':
if not config.get('_NONCE'):
nonce_oracle = rpc.get_nonce_oracle()
config.add(nonce_oracle.get_nonce(), '_NONCE')
else:
for arg in [
'_FEE_PRICE',
'_FEE_LIMIT',
'_NONCE',
]:
if not config.get(arg):
logg.error('--{} must be specified when no rpc provider has been set.'.format(arg.replace('_', '-').lower()))
sys.exit(1)
if mode == 'call': #signer == None or config.true('_NOTX'):
c = TxFactory(chain_spec)
j = JSONRPCRequest(id_generator=rpc.id_generator)
o = j.template()
o['method'] = 'eth_call'
gas_limit = add_0x(int.to_bytes(config.get('_FEE_LIMIT'), 8, byteorder='big').hex(), compact_value=True)
gas_price = add_0x(int.to_bytes(config.get('_FEE_PRICE'), 8, byteorder='big').hex(), compact_value=True)
o['method'] = 'eth_call'
o['params'].append({
'to': exec_address,
'from': signer_address,
@@ -135,16 +182,22 @@ def main():
height = to_blockheight_param(config.get('_HEIGHT'))
o['params'].append(height)
o = j.finalize(o)
r = conn.do(o)
try:
print(strip_0x(r))
return
except ValueError:
sys.stderr.write('query returned an empty value ({})\n'.format(r))
sys.exit(1)
if not exec_address:
argparser.error('exec address (-e) must be defined')
if config.get('_RPC_SEND'):
r = conn.do(o)
try:
print(strip_0x(r))
return
except ValueError:
sys.stderr.write('query returned an empty value ({})\n'.format(r))
sys.exit(1)
else:
print(o)
return
if signer == None:
logg.error('mode "tx" without signer does not make sense. Please specify a key file with -y.')
sys.exit(1)
if chain_spec == None:
raise ValueError('chain spec must be specified')

View File

@@ -0,0 +1,23 @@
# standard imports
import sys
# external imports
import chainlib.eth.cli
from hexathon import add_0x
cmds = {
'gas': chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.WALLET,
'info': chainlib.eth.cli.argflag_reset(chainlib.cli.argflag_std_base_read, chainlib.eth.cli.Flag.CHAIN_SPEC),
'get': chainlib.eth.cli.argflag_reset(chainlib.cli.argflag_std_base_read, chainlib.eth.cli.Flag.CHAIN_SPEC),
'decode': chainlib.cli.argflag_std_base | chainlib.eth.cli.Flag.CHAIN_SPEC,
'encode': 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,
'count': chainlib.eth.cli.argflag_std_base_read | chainlib.eth.cli.Flag.WALLET,
'raw': chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC,
'balance': chainlib.eth.cli.argflag_std_base | chainlib.eth.cli.Flag.WALLET,
'wait': chainlib.eth.cli.argflag_reset(chainlib.eth.cli.argflag_std_base_read | chainlib.eth.cli.Flag.NO_TARGET | chainlib.eth.cli.Flag.RPC_AUTH, chainlib.eth.cli.Flag.CHAIN_SPEC | chainlib.eth.cli.Flag.RAW),
'checksum': 0,
}
if __name__ == '__main__':
b = cmds[sys.argv[1]]
print(add_0x(hex(b)))

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,34 +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_read
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))
@@ -82,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)
@@ -104,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
@@ -122,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,35 +108,37 @@ 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)
n = int(r, 16)
o = block_latest(id_generator=settings.get('RPC_ID_GENERATOR'))
r = settings.get('CONN').do(o)
try:
n = int(r, 16)
except ValueError:
n = int(r)
first_block_number = n
if human:
n = format(n, ',')
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
@@ -141,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, ',')
@@ -151,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,74 +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_positional('data', type=str, help='Transaction data')
argparser.add_argument('--mode', choices=['tx', 'call'], type=str, help='Mode of operation')
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:
@@ -112,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'):
@@ -130,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__)
@@ -95,7 +103,11 @@ def pack(tx_src, chain_spec):
tx_src['r'],
tx_src['s'],
]:
for b in bytes.fromhex(strip_0x(a)):
try:
a = strip_0x(a)
except TypeError:
a = strip_0x(hex(a)) # believe it or not, eth_tester returns signatures as ints not hex
for b in bytes.fromhex(a):
signature[cursor] = b
cursor += 1
@@ -397,11 +409,15 @@ 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': '0x' + compact(txes['gasPrice']),
'gas': '0x' + compact(txes['gas']),
'gasPrice': add_0x(gas_price, compact_value=True),
'gas': add_0x(gas, compact_value=True),
'data': txes['data'],
}
@@ -423,6 +439,8 @@ class TxFactory:
return self.build_raw(tx)
elif tx_format == TxFormat.RAW_ARGS:
return strip_0x(tx['data'])
elif tx_format == TxFormat.DICT:
return tx
raise NotImplementedError('tx formatting {} not implemented'.format(tx_format))
@@ -502,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.
@@ -519,106 +581,88 @@ class Tx(BaseTx):
#:todo: divide up constructor method
"""
def __init__(self, src, block=None, rcpt=None):
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
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.warning('no inline raw tx src, and no raw rendering implemented, field will be "None"')
self.status = Status.PENDING
self.logs = None
if rcpt != None:
self.apply_receipt(rcpt)
logg.debug('no inline raw tx src, and no raw rendering implemented, field will be "None"')
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()
return self.src
def apply_receipt(self, rcpt):
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.
@@ -626,46 +670,14 @@ class Tx(BaseTx):
:param rcpt: Receipt data
:type rcpt: dict
"""
rcpt = self.src_normalize(rcpt)
logg.debug('rcpt {}'.format(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'])
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):
@@ -674,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
@@ -690,18 +699,18 @@ 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
@staticmethod
def from_src(src, block=None, rcpt=None):
def from_src(src, block=None, rcpt=None, strict=False):
"""Creates a new Tx object.
Alias of constructor.
"""
return Tx(src, block=block, rcpt=rcpt)
return Tx(src, block=block, rcpt=rcpt, strict=strict)
def __str__(self):
@@ -740,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 {}
@@ -755,7 +767,7 @@ tx_index {}
""".format(
self.block.number,
self.block.hash,
self.tx_index,
self.result.tx_index,
)
@@ -768,7 +780,9 @@ tx_index {}
if self.wire != None:
s += """src {}
""".format(
self.wire,
str(self.wire),
)
return s

View File

@@ -0,0 +1,16 @@
.TH eth-balance 1
.SH NAME
eth-balance \- Get network gas token balance for an address
.SH SYNOPSIS
\fBeth-balance\fP [ -p \fIrpc_provider\fP ] \fIaddress\fP
.SH DESCRIPTION
.P
Query the network for the gas token balance of an account.
.P
The balance will be returned as an integer value denominated in the gas unit.
.SS OPTION

View File

@@ -0,0 +1 @@
a Address to get balance for. -a, --address

View File

@@ -0,0 +1,7 @@
.SH ABOUT THE CHECKSUM
.P
Checksum protection may be used as protection against address typos.
.P
It uses the value of the address itself as input to an algorithm, which results in altering the case of the letters in the hexadecimal address.
.P
A checksum address consisting of only numbers will always pass checksum.

View File

@@ -0,0 +1,11 @@
.TH eth-checksum 1
.SH NAME
eth-checksum \- Calculate checksum address
.SH SYNOPSIS
\fBeth-checksum\fP \fIaddress\fP
.SH DESCRIPTION
.P
Converts \fIaddress\fP into a checksum-protected address.

15
man/eth-count.head.groff Normal file
View File

@@ -0,0 +1,15 @@
.TH eth-count 1
.SH NAME
eth-count \- Get transaction count for an address
.SH SYNOPSIS
\fBeth-count\fP [ -p \fIrpc_provider\fP ] \fIaddress\fP
.SH DESCRIPTION
.P
Query the network for the number of transactions known for an account. The result can be used as the \fInonce\fP value for a consecutive transaction.
.SS OPTIONS

1
man/eth-count.overrides Normal file
View File

@@ -0,0 +1 @@
a Address to count transactions for. -a, --address

14
man/eth-decode.head.groff Normal file
View File

@@ -0,0 +1,14 @@
.TH eth-decode 1
.SH NAME
eth-decode \- Decode a transaction in wire-format to human readable form
.SH SYNOPSIS
\fBeth-decode\fI [ -i \fIchain_spec\fP ] encoded_tx
.SH DESCRIPTION
.P
This tool deserializes a wire-format transaction and outputs its fields in human-readable form. It does not require a node to operate, and does not apply transaction state.
.P
The transaction wire-format is an ordered concatenation of transaction values, which in turn is serialized using the Recurive Length Prefix (RLP) format. \fBeth-decode\fP accepts the RLP-encoded transaction as a hex string.
.P

View File

@@ -0,0 +1,31 @@
.SH MODES
The modes parameter specifies what kind of operaion the encoding should be generated for. There are three valid modes:
.SS tx
Generates a transaction that changes state. Used with \fB--format\fP \fIrpc\fP it will generate an \fIeth_sendRawTransaction\fP jsonrpc object. Used with \fB--format\fP \fIbin\fP it outputs signed RLP only in hexdecimal.
.SS call
Generates a query that reads state. Used with \fB--format\fP \fIrpc\fP it will generate a \fIeth_call\fP jsonrpc object with applicable fields filled out from environment, arguments and/or rpc retrieval. \fB--format\fP \fIbin\fP is not valid for this mode.
.SS arg
Encodes the argument part only, optionally with a method signature. \fB--format\fP \fIrpc\fP is not valid for this mode.
.SH SPECIFYING VALUES
Dynamic value types are not yet supported.
.SS Specifying an unsigned integer:
.IP u:1024
.SS Specifying an address:
.IP a:19062190B1925b5b6689D7073fDfC8c2976EF8Cb
.SS Specifying bytes values:
.IP b:deadbeef
.IP b4:deadbeef
.IP b32:deadbeef
.SS Specifying a string value:
.IP s:foobar

View File

@@ -0,0 +1,22 @@
.SS Build a signed ERC20 transfer in wire format, setting nonce and fee details manually.
.EX
$ eth-encode -f bin -y <\fIkey_file_path\fP> -e <\fItoken_address\fP> --fee-price 1000000000 --fee-limit 100000 --nonce 42 --signature transfer a:00000000000000000000000000000000DeaDBeef u:1024
.EE
.SS Build Smart contract call with method signature, retrieving fee and nonce settings from rpc
\fBeth-encode\fP --mode call -f rpc -e <\fItoken_address\fP> --signature balanceOf -p <\fIrpc_endpoint\fP> a:deadbeef
.SS Build smart contract ERC20 transfer argument with signature
.EX
\fBeth-encode\fP --mode arg --signature transfer a:00000000000000000000000000000000DeaDBeef u:1024
.TP Outupt
a9059cbb00000000000000000000000000000000000000000000000000000000deadbeef0000000000000000000000000000000000000000000000000000000000000400
.EE
.SS Build smart contract ERC20 transfer argument types without signature
.EX
\fBeth-encode\fP --mode arg a:00000000000000000000000000000000DeaDBeef u:1024
.TP Output
00000000000000000000000000000000000000000000000000000000deadbeef0000000000000000000000000000000000000000000000000000000000000400
.EE

17
man/eth-encode.head.groff Normal file
View File

@@ -0,0 +1,17 @@
.TH eth-encode 1
.SH NAME
eth-encode \- Encode arbitrary contract calls and transactions
.SH SYNOPSIS
\fBeth-encode\fP [ --mode \fImode\fP ] [ -f \fIformat\fP ] [ -p \fIrpc_provider\fP ] [ -i \fIchain_Spec\fP] [ -s ] [ -w ] [ -e \fIsmart_contract_address\fP ] --signature \fIcontract_method_name\fP [ \fIvalue_specifiers\fP ... ]
.SH DESCRIPTION
Generate wire-format or rpc query any EVM smart contract call or transaction by specifying method and arguments.
The resulting call or transaction can either be stored offline for later use, or directly sent to the network using the \fB-s\fP option.
A description of value formatting is given in the \fBSPECIFYING VALUES\fP section below. Usage is demonstrated in the \fBEXAMPLES\fP section.
.SS OPTIONS

1
man/eth-encode.overrides Normal file
View File

@@ -0,0 +1 @@
mode Mode of operation encoding is for. Must be one of "tx," "call," or "arg." See \fBMODES\fP below. --mode mode

View File

@@ -0,0 +1,75 @@
.P
In the follwing we willconsaider transactions signed by the private key for address Eb3907eCad74a0013c259D5874AE7f22DcBcC95C:
.SS Offline transaction
.EX
$ eth-gas -y <\fIkey_file_path\fP> -a 00000000000000000000000000000000DeaDBeef --fee-price 100000000000 --fee-limit 21000 --nonce 42 1024
from: Eb3907eCad74a0013c259D5874AE7f22DcBcC95C
to: 00000000000000000000000000000000DeaDBeef
nonce: 42
gasPrice: 100000000000 (100 gwei)
gas: 21000
value: 1024 (0.000000000000001024 eth)
data: 0x
v: 37
recovery_byte: 0
r: 0x0c97432d4db724e66a56f7ced04174cf6129e2555709f206dd6d3a156b4af23a
s: 0x287862548314a59c7ca6139eee8b51400eb40a67b08b8dc13d67302abecccae0
chainId: 1
hash: 0x003030af05460633e85b16fff7a17607818dc67e58f89396e5491ad6f5438971
hash_unsigned: 0xa59cf9e5438b186de381892b7879ce66476d5469478c7148880da5d553ade651
src: 0xf8662a85174876e8008252089400000000000000000000000000000000deadbeef8204008025a00c97432d4db724e66a56f7ced04174cf6129e2555709f206dd6d3a156b4af23aa0287862548314a59c7ca6139eee8b51400eb40a67b08b8dc13d67302abecccae0
.EE
.SS Offline transaction with arbitrary data
.EX
$ eth-gas -y <\fIkey_file_path\fP> -a 00000000000000000000000000000000DeaDBeef --fee-price 100000000000 --fee-limit 21000 --nonce 42 --data 0x2a 1024
from: Eb3907eCad74a0013c259D5874AE7f22DcBcC95C
to: 00000000000000000000000000000000DeaDBeef
nonce: 42
gasPrice: 100000000000 (100 gwei)
gas: 21000
value: 1024 (0.000000000000001024 eth)
data: 0x2a
v: 37
recovery_byte: 0
r: 0x800b6982d3f178201d7e7e7693b9c90b3fbcd54d04b6fff5284c81101fad54dd
s: 0x3b86d710d31ac74b58f0040b0f51fdb6bdbabea62a68cf99c05e765e7e81de87
chainId: 1
hash: 0xede30052befd80760c5ab543babdccc3d97fe90523e5710d77220155a82faa47
hash_unsigned: 0xad82d8cf1a412541c8a94ef71c50e9172c3a37853af036adee2f55c577da9770
src: 0xf8662a85174876e8008252089400000000000000000000000000000000deadbeef8204002a25a0800b6982d3f178201d7e7e7693b9c90b3fbcd54d04b6fff5284c81101fad54dda03b86d710d31ac74b58f0040b0f51fdb6bdbabea62a68cf99c05e765e7e81de87
.EE
.SS Offline transaction with wire-format output
.EX
$ eth-gas -y <\fIkey_file_path\fP> -a 00000000000000000000000000000000DeaDBeef --fee-price 100000000000 --fee-limit 21000 --nonce 42 --raw 1024
0xf8662a85174876e8008252089400000000000000000000000000000000deadbeef8204008025a00c97432d4db724e66a56f7ced04174cf6129e2555709f206dd6d3a156b4af23aa0287862548314a59c7ca6139eee8b51400eb40a67b08b8dc13d67302abecccae0
.EE
.SS Sign transaction for a different network
.EX
$ eth-gas -i evm:london:3:rinkeby -y /home/lash/src/contrib/grassrootseconomics/cic-dev/UTC--2021-01-08T17-18-44.521011372Z--eb3907ecad74a0013c259d5874ae7f22dcbcc95c -a 00000000000000000000000000000000DeaDBeef --fee-price 100000000000 --fee-limit 21000 --nonce 42 --data 0x2a 1024
from: Eb3907eCad74a0013c259D5874AE7f22DcBcC95C
to: 00000000000000000000000000000000DeaDBeef
nonce: 42
gasPrice: 100000000000 (100 gwei)
gas: 21000
value: 1024 (0.000000000000001024 eth)
data: 0x2a
v: 41
recovery_byte: 0
r: 0xe522c25784111a512cbf46f883e3bdacffc2cdbd465fa1042892c28fc10ee054
s: 0x5f84eb51a0c9871cfcedaba4e6274b300014899b3a2fec9292de6fe5919bcd07
chainId: 3
hash: 0x771200a20072294a6a843b89b98d5f868c9aa94da75dacf6a9f5834dfd241199
hash_unsigned: 0xa5f9e8a5cda6985d81f4129955b7529f48ecb54728badac16ec550384e3a2bcc
src: 0xf8662a85174876e8008252089400000000000000000000000000000000deadbeef8204002a29a0e522c25784111a512cbf46f883e3bdacffc2cdbd465fa1042892c28fc10ee054a05f84eb51a0c9871cfcedaba4e6274b300014899b3a2fec9292de6fe5919bcd07
.EE
.P
The wire-format can in turn be decoded using the \fBeth-decode\fP tool)

19
man/eth-gas.head.groff Normal file
View File

@@ -0,0 +1,19 @@
.TH eth-gas 1
.SH NAME
eth-gas - Create EVM gas token transaction
.SH SYNOPSIS
.P
\fBeth-gas\fP -a RECIPIENT -y KEYFILE [ -p \fIrpc_provider ] [ -s ] [ -u ] AMOUNT
.P
\fBeth-gas\fP -a RECIPIENT -y KEYFILE [ --fee-price PRICE ] [ --fee-limit LIMIT ] [ --nonce NONCE ] [ -s ] [ -u ] AMOUNT
.SH DESCRIPTION
\fBeth-gas\fP generates serialized gas token transactions in various output formats.
If an \fIrpc_provider\fP is available, the valuess \fIfee_price\fP and \fInonce\fP will be automatically retrieved if they are not explicitly specified. If missing values cannot be retrieved, an error will occur.
Providing all arguments enables fully offline creation of the transaction.
See \fBEXAMPLES\fP for more details on inputs and outputs.
.SS OPTIONS

1
man/eth-gas.overrides Normal file
View File

@@ -0,0 +1 @@
a Beneficiary of the gas token transaction.

19
man/eth-get.head.groff Normal file
View File

@@ -0,0 +1,19 @@
.TH eth-get 1
.SH NAME
eth-get \- Retrieve transaction and transaction state from network
.SH SYNOPSIS
.PP
\fBeth-get\fP [ -p \fIrpc_provider\fP] \fItx_hash\fP
.PP
\fBeth-get\fP [ -p \fIrpc_provider\fP] \fIaddress\fP
.SH DESCRIPTION
.P
Retrieve a transaction by its transaction hash, or contract code by its address.
.P
In the context of transaction, \fBeth-get\fP retrieves the state of the transaction (the "receipt") and applies it to the output. It also re-serializes the transaction wire format for reference.
.SS OPTIONS

27
man/eth-info.head.groff Normal file
View File

@@ -0,0 +1,27 @@
.TH eth-info 1
.SH NAME
eth-info - Return key metrics from the current state of the EVM network.
.SH SYNOPSIS
\fBeth-info\fP [ -p \fIrpc_provider\fP ] [ \fIkey\fP ]
.SH DESCRIPTION
\fBeth-info\fP reports statistics from the current state of the EVM network:
.EX
$ eth-info
Block: 1024478
Gas price: 1000000000
.EE
An individual data item may be specified using the \fIkey\fP positional argument:
.EX
$ eth-info block
Block: 1024478
$ eth-info --raw gas_price
1000000000
.EE
.SS OPTIONS

View File

@@ -0,0 +1,8 @@
.SH EXAMPLES
Outputs of \fBeth-gas\fP and \fBeth-encode\fP map directly to inputs for \fBeth-raw\fP.
.SS Encapsulate a gas transaction
.EX
$ eth-gas -y <\fIkey_file_path\fP> -a 00000000000000000000000000000000DeaDBeef --fee-price 100000000000 --fee-limit 21000 --nonce 42 --raw 1024 | eth-raw

26
man/eth-raw.head.groff Normal file
View File

@@ -0,0 +1,26 @@
.TH eth-raw 1
.SH NAME
eth-raw \- Create and send a jsonrpc transaction from signed rlp transaction
.SH SYNOPSIS
.P
\fBeth-raw\fP [ --deploy ] \fItransaction_data\fP
.P
\fBeth-raw\fP [ -p \fIrpc_provider\fP ] [ --deploy ] -s \fItransaction_data\fP
.SH DESCRIPTION
.P
Creates a jsonrpc object from serialized data.
.P
If \fB-y\fP is defined, and \fB--mode\fP is not set, or set to "tx", a \fIeth_sendRawTransaction\fP object will be created.
.P
Otherwise, an \fIeth_call\fP object will be created. The recipient of the call will be the contract specified with \fB-e\fP. If \fB-y\fP has been set, the sender of the query will be the address corresponding to the key file.
.P
If the \fB-s\fP option is added, the jsonrpc object will be sent to the rpc provider. \fBNote\fP If the jsonrpc object is a transaction, this may incur real costs.
.P
See the \fBEXAMPLES\fP section for details on how to use \fBeth-raw\fP with the outputs from \fBeth-gas\fP and \fBeth-encode\fP
.SS OPTIONS

View File

@@ -0,0 +1,4 @@
.SH SEE ALSO
.BP
confini-dump(1), eth-keyfile(1), eth-encode(1), eth-gas(1)

17
man/eth-wait.head.groff Normal file
View File

@@ -0,0 +1,17 @@
.TH eth-wait 1
.SH NAE
eth-wait \- Wait for a transaction to be confirmed on network
.SH SYNOPSIS
\fBeth-wait\fP [ -p \fIrpc_provider\fP ] \fItransaction_hash\fP ...
.SH DESCRIPTION
.P
Blocks until network confirmation for the transactions are returned from \fIrpc_provider\fP.
.P
By default, if execution of the transaction failed on the network, the command will cause an error. This behavior can be adjusted using the \fB--ignore\fP or \fB--ignore-all\fP option.
.P
A transaction hash unknown to the \fIrpc_provider\fP will also cause an error.
.SS OPTIONS

2
man/eth-wait.overrides Normal file
View File

@@ -0,0 +1,2 @@
ignore Ignore error from the specified transaction. May be defined more than once. --ignore tx_hash
ignoreall Ignore errors from all transactions. --ignore-all

View File

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

View File

@@ -1,10 +1,10 @@
[metadata]
name = chainlib-eth
version = 0.0.22
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,4 +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 = chainlib.eth.runnable.info: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)
@@ -50,6 +72,19 @@ class TxTestCase(EthTesterCase):
self.assertTrue(is_same_address(tx['to'], self.accounts[1]))
def test_tx_repack(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)
self.rpc.do(o)
o = transaction(tx_hash_hex)
tx_src = self.rpc.do(o)
tx = Tx(tx_src)
tx_bin = pack(tx.src, self.chain_spec)
def test_tx_pack(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
gas_oracle = RPCGasOracle(self.rpc)
@@ -94,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)
@@ -136,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)