Merge branch 'lash/chain-stat' into 'master'
Add chain stat, info cli See merge request nolash/chainlib!2
This commit is contained in:
commit
72f49841f8
@ -42,10 +42,16 @@ class Block:
|
||||
|
||||
def __init__(self, src):
|
||||
self.hash = src['hash']
|
||||
try:
|
||||
self.number = int(strip_0x(src['number']), 16)
|
||||
except TypeError:
|
||||
self.number = int(src['number'])
|
||||
self.txs = src['transactions']
|
||||
self.block_src = src
|
||||
try:
|
||||
self.timestamp = int(strip_0x(src['timestamp']), 16)
|
||||
except TypeError:
|
||||
self.timestamp = int(src['timestamp'])
|
||||
|
||||
|
||||
def src(self):
|
||||
|
@ -36,6 +36,7 @@ from chainlib.eth.gas import (
|
||||
OverrideGasOracle,
|
||||
balance,
|
||||
)
|
||||
from chainlib.chain import ChainSpec
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
@ -48,7 +49,6 @@ argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provide
|
||||
argparser.add_argument('-a', '--token-address', dest='a', type=str, help='Token address. If not set, will return gas balance')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=default_abi_dir, help='Directory containing bytecode and abi (default {})'.format(default_abi_dir))
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('address', type=str, help='Account address')
|
||||
|
@ -2,7 +2,7 @@
|
||||
import sys
|
||||
|
||||
# local imports
|
||||
from chainlib.eth.address import to_checksum
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
|
||||
|
||||
print(to_checksum(sys.argv[1]))
|
||||
print(to_checksum_address(sys.argv[1]))
|
||||
|
@ -11,6 +11,7 @@ import logging
|
||||
from chainlib.eth.address import to_checksum
|
||||
from chainlib.eth.connection import EthHTTPConnection
|
||||
from chainlib.eth.tx import count
|
||||
from chainlib.chain import ChainSpec
|
||||
from crypto_dev_signer.keystore.dict import DictKeystore
|
||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||
|
||||
|
@ -34,6 +34,7 @@ from chainlib.jsonrpc import (
|
||||
from chainlib.eth.connection import EthHTTPConnection
|
||||
from chainlib.eth.tx import Tx
|
||||
from chainlib.eth.block import Block
|
||||
from chainlib.chain import ChainSpec
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
@ -45,7 +46,6 @@ argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-t', '--token-address', dest='t', type=str, help='Token address. If not set, will return gas balance')
|
||||
argparser.add_argument('-t', '--token-address', dest='t', type=str, help='Token address. If not set, will return gas balance')
|
||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=default_abi_dir, help='Directory containing bytecode and abi (default {})'.format(default_abi_dir))
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
|
136
chainlib/eth/runnable/info.py
Normal file
136
chainlib/eth/runnable/info.py
Normal file
@ -0,0 +1,136 @@
|
||||
#!python3
|
||||
|
||||
"""Token balance query script
|
||||
|
||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
||||
|
||||
"""
|
||||
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# standard imports
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
even,
|
||||
)
|
||||
import sha3
|
||||
from eth_abi import encode_single
|
||||
|
||||
# local imports
|
||||
from chainlib.eth.address import (
|
||||
to_checksum_address,
|
||||
is_checksum_address,
|
||||
)
|
||||
from chainlib.jsonrpc import (
|
||||
jsonrpc_template,
|
||||
jsonrpc_result,
|
||||
)
|
||||
from chainlib.eth.block import block_latest
|
||||
from chainlib.eth.tx import count
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from chainlib.eth.connection import EthHTTPConnection
|
||||
from chainlib.eth.gas import (
|
||||
OverrideGasOracle,
|
||||
balance,
|
||||
price,
|
||||
)
|
||||
from chainlib.chain import ChainSpec
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
default_abi_dir = os.environ.get('ETH_ABI_DIR', '/usr/share/local/cic/solidity/abi')
|
||||
default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-H', '--human', dest='human', action='store_true', help='Use human-friendly formatting')
|
||||
argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Include summary for keyfile')
|
||||
argparser.add_argument('address', nargs='?', type=str, help='Include summary for address (conflicts with -y)')
|
||||
args = argparser.parse_args()
|
||||
|
||||
|
||||
if args.vv:
|
||||
logg.setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logg.setLevel(logging.INFO)
|
||||
|
||||
signer = None
|
||||
holder_address = None
|
||||
if args.address != None:
|
||||
if not args.u and is_checksum_address(args.address):
|
||||
raise ValueError('invalid checksum address {}'.format(args.address))
|
||||
holder_address = add_0x(args.address)
|
||||
elif args.y != None:
|
||||
f = open(args.y, 'r')
|
||||
o = json.load(f)
|
||||
f.close()
|
||||
holder_address = add_0x(to_checksum_address(o['address']))
|
||||
|
||||
|
||||
#if holder_address != None:
|
||||
# passphrase_env = 'ETH_PASSPHRASE'
|
||||
# if args.env_prefix != None:
|
||||
# passphrase_env = args.env_prefix + '_' + passphrase_env
|
||||
# passphrase = os.environ.get(passphrase_env)
|
||||
# logg.error('pass {}'.format(passphrase_env))
|
||||
# if passphrase == None:
|
||||
# logg.warning('no passphrase given')
|
||||
# passphrase=''
|
||||
#
|
||||
# holder_address = None
|
||||
# keystore = DictKeystore()
|
||||
# if args.y != None:
|
||||
# logg.debug('loading keystore file {}'.format(args.y))
|
||||
# signer_address = keystore.import_keystore_file(args.y, password=passphrase)
|
||||
# logg.debug('now have key for signer address {}'.format(signer_address))
|
||||
# signer = EIP155Signer(keystore)
|
||||
|
||||
conn = EthHTTPConnection(args.p)
|
||||
gas_oracle = OverrideGasOracle(conn)
|
||||
|
||||
token_symbol = 'eth'
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||
|
||||
human = args.human
|
||||
|
||||
|
||||
def main():
|
||||
o = block_latest()
|
||||
r = conn.do(o)
|
||||
n = int(r, 16)
|
||||
if human:
|
||||
n = format(n, ',')
|
||||
sys.stdout.write('Block: {}\n'.format(n))
|
||||
|
||||
o = price()
|
||||
r = conn.do(o)
|
||||
n = int(r, 16)
|
||||
if human:
|
||||
n = format(n, ',')
|
||||
sys.stdout.write('Gasprice: {}\n'.format(n))
|
||||
|
||||
if holder_address != None:
|
||||
o = count(holder_address)
|
||||
r = conn.do(o)
|
||||
n = int(r, 16)
|
||||
sys.stdout.write('Address: {}\n'.format(holder_address))
|
||||
sys.stdout.write('Nonce: {}\n'.format(n))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,6 +1,7 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import enum
|
||||
import re
|
||||
|
||||
# external imports
|
||||
import coincurve
|
||||
@ -270,20 +271,56 @@ class TxFactory:
|
||||
|
||||
class Tx:
|
||||
|
||||
re_camel_snake = re.compile(r'([a-z0-9]+)([A-Z])')
|
||||
|
||||
# TODO: force tx type schema parser (whether expect hex or int etc)
|
||||
def __init__(self, src, block=None, rcpt=None):
|
||||
logg.debug('src {}'.format(src))
|
||||
self.src = self.src_normalize(src)
|
||||
self.index = -1
|
||||
tx_hash = add_0x(src['hash'])
|
||||
if block != None:
|
||||
self.index = int(strip_0x(src['transactionIndex']), 16)
|
||||
i = 0
|
||||
for tx in block.txs:
|
||||
tx_hash_block = None
|
||||
try:
|
||||
tx_hash_block = tx['hash']
|
||||
except TypeError:
|
||||
tx_hash_block = add_0x(tx)
|
||||
logg.debug('tx {} cmp {}'.format(tx, tx_hash))
|
||||
if tx_hash_block == tx_hash:
|
||||
self.index = i
|
||||
break
|
||||
i += 1
|
||||
if self.index == -1:
|
||||
raise AttributeError('tx {} not found in block {}'.format(tx_hash, block.hash))
|
||||
self.block = block
|
||||
self.hash = strip_0x(tx_hash)
|
||||
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)
|
||||
self.hash = strip_0x(src['hash'])
|
||||
except TypeError:
|
||||
self.nonce = int(src['nonce'])
|
||||
address_from = strip_0x(src['from'])
|
||||
self.gasPrice = int(strip_0x(src['gasPrice']), 16)
|
||||
self.gasLimit = int(strip_0x(src['gas']), 16)
|
||||
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)]
|
||||
self.contract = None
|
||||
|
||||
try:
|
||||
inpt = src['input']
|
||||
except KeyError:
|
||||
inpt = src['data']
|
||||
|
||||
if inpt != '0x':
|
||||
inpt = strip_0x(inpt)
|
||||
else:
|
||||
@ -310,8 +347,31 @@ class Tx:
|
||||
self.apply_receipt(rcpt)
|
||||
|
||||
|
||||
@classmethod
|
||||
def src_normalize(self, src):
|
||||
src_normal = {}
|
||||
for k in src.keys():
|
||||
s = ''
|
||||
right_pos = 0
|
||||
for m in self.re_camel_snake.finditer(k):
|
||||
g = m.group(0)
|
||||
s += g[:len(g)-1]
|
||||
s += '_' + g[len(g)-1].lower()
|
||||
right_pos = m.span()[1]
|
||||
|
||||
|
||||
s += k[right_pos:]
|
||||
src_normal[k] = src[k]
|
||||
if s != k:
|
||||
logg.debug('adding snake {} for camel {}'.format(s, k))
|
||||
src_normal[s] = src[k]
|
||||
|
||||
return src_normal
|
||||
|
||||
|
||||
def apply_receipt(self, rcpt):
|
||||
status_number = int(strip_0x(rcpt['status']))
|
||||
logg.debug('rcpt {}'.format(rcpt))
|
||||
status_number = int(rcpt['status'], 16)
|
||||
if status_number == 1:
|
||||
self.status = Status.SUCCESS
|
||||
elif status_number == 0:
|
||||
@ -323,6 +383,7 @@ class Tx:
|
||||
if contract_address != None:
|
||||
self.contract = contract_address
|
||||
self.logs = rcpt['logs']
|
||||
self.gas_used = int(rcpt['gasUsed'], 16)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
@ -338,19 +399,25 @@ nonce {}
|
||||
gasPrice {}
|
||||
gasLimit {}
|
||||
input {}
|
||||
status {}
|
||||
""".format(
|
||||
self.hash,
|
||||
self.outputs[0],
|
||||
self.inputs[0],
|
||||
self.value,
|
||||
self.nonce,
|
||||
self.gasPrice,
|
||||
self.gasLimit,
|
||||
self.gas_price,
|
||||
self.gas_limit,
|
||||
self.payload,
|
||||
self.status.name,
|
||||
)
|
||||
|
||||
if self.status != Status.PENDING:
|
||||
s += """gasUsed {}
|
||||
""".format(
|
||||
self.gas_used,
|
||||
)
|
||||
|
||||
s += 'status ' + self.status.name
|
||||
|
||||
if self.contract != None:
|
||||
s += """contract {}
|
||||
""".format(
|
||||
|
@ -80,6 +80,11 @@ class TestRPCConnection(RPCConnection):
|
||||
return jsonrpc_result(r, error_parser)
|
||||
|
||||
|
||||
def eth_blockNumber(self, p):
|
||||
block = self.backend.get_block_by_number('latest')
|
||||
return block['number']
|
||||
|
||||
|
||||
def eth_getBlockByNumber(self, p):
|
||||
b = bytes.fromhex(strip_0x(p[0]))
|
||||
n = int.from_bytes(b, 'big')
|
||||
|
30
chainlib/stat.py
Normal file
30
chainlib/stat.py
Normal file
@ -0,0 +1,30 @@
|
||||
import datetime
|
||||
|
||||
class ChainStat:
|
||||
|
||||
def __init__(self):
|
||||
self.block_timestamp_last = None
|
||||
self.block_avg_aggregate = None
|
||||
self.block_avg_count = -1
|
||||
|
||||
|
||||
def block_apply(self, block):
|
||||
if self.block_timestamp_last == None:
|
||||
self.block_timestamp_last = block.timestamp
|
||||
|
||||
aggregate = block.timestamp - self.block_timestamp_last
|
||||
|
||||
if self.block_avg_aggregate == None:
|
||||
self.block_avg_aggregate = float(aggregate)
|
||||
else:
|
||||
self.block_avg_aggregate *= self.block_avg_count
|
||||
self.block_avg_aggregate += block.timestamp - self.block_timestamp_last
|
||||
self.block_avg_aggregate /= (self.block_avg_count + 1)
|
||||
|
||||
print('aggr {}'.format(type(self.block_avg_aggregate)))
|
||||
self.block_avg_count += 1
|
||||
|
||||
self.block_timestamp_last = block.timestamp
|
||||
|
||||
def block_average(self):
|
||||
return self.block_avg_aggregate
|
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = chainlib
|
||||
version = 0.0.2a1
|
||||
version = 0.0.2a6
|
||||
description = Generic blockchain access library and tooling
|
||||
author = Louis Holbrook
|
||||
author_email = dev@holbrook.no
|
||||
@ -41,3 +41,5 @@ console_scripts =
|
||||
eth-transfer = chainlib.eth.runnable.transfer:main
|
||||
eth-get = chainlib.eth.runnable.get:main
|
||||
eth-decode = chainlib.eth.runnable.decode:main
|
||||
eth-info = chainlib.eth.runnable.info:main
|
||||
eth = chainlib.eth.runnable.info:main
|
||||
|
49
tests/test_stat.py
Normal file
49
tests/test_stat.py
Normal file
@ -0,0 +1,49 @@
|
||||
# standard imports
|
||||
import unittest
|
||||
import datetime
|
||||
|
||||
# external imports
|
||||
from chainlib.stat import Stat
|
||||
from chainlib.eth.block import Block
|
||||
|
||||
|
||||
class TestStat(unittest.TestCase):
|
||||
|
||||
def test_block(self):
|
||||
|
||||
s = ChainStat()
|
||||
|
||||
d = datetime.datetime.utcnow() - datetime.timedelta(seconds=30)
|
||||
block_a = Block({
|
||||
'timestamp': d.timestamp(),
|
||||
'hash': None,
|
||||
'transactions': [],
|
||||
'number': 41,
|
||||
})
|
||||
|
||||
d = datetime.datetime.utcnow()
|
||||
block_b = Block({
|
||||
'timestamp': d.timestamp(),
|
||||
'hash': None,
|
||||
'transactions': [],
|
||||
'number': 42,
|
||||
})
|
||||
|
||||
s.block_apply(block_a)
|
||||
s.block_apply(block_b)
|
||||
self.assertEqual(s.block_average(), 30.0)
|
||||
|
||||
d = datetime.datetime.utcnow() + datetime.timedelta(seconds=10)
|
||||
block_c = Block({
|
||||
'timestamp': d.timestamp(),
|
||||
'hash': None,
|
||||
'transactions': [],
|
||||
'number': 43,
|
||||
})
|
||||
|
||||
s.block_apply(block_c)
|
||||
self.assertEqual(s.block_average(), 20.0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user