Complete test and refactor for generic tx, result, block objects

This commit is contained in:
lash 2022-05-09 18:46:10 +00:00
parent a2168a50e3
commit 972535f1f9
Signed by: lash
GPG Key ID: 21D2E7BB88C2A746
5 changed files with 81 additions and 141 deletions

View File

@ -10,6 +10,7 @@ from hexathon import (
# local imports # local imports
from chainlib.eth.tx import Tx from chainlib.eth.tx import Tx
from .src import Src
def block_latest(id_generator=None): def block_latest(id_generator=None):
@ -76,7 +77,7 @@ def syncing(id_generator=None):
return j.finalize(o) return j.finalize(o)
class Block(BaseBlock): class Block(BaseBlock, Src):
"""Encapsulates an Ethereum block """Encapsulates an Ethereum block
:param src: Block representation data :param src: Block representation data
@ -88,7 +89,8 @@ class Block(BaseBlock):
def __init__(self, src): def __init__(self, src):
super(Block, self).__init__(src) super(Block, self).__init__(src)
self.hash = src['hash'] import sys
self.set_hash(src['hash'])
try: try:
self.number = int(strip_0x(src['number']), 16) self.number = int(strip_0x(src['number']), 16)
except TypeError: except TypeError:

View File

@ -1,3 +1,6 @@
# standard imports
import logging
# external imports # external imports
from potaahto.symbols import snake_and_camel from potaahto.symbols import snake_and_camel
from hexathon import ( from hexathon import (
@ -11,12 +14,15 @@ from chainlib.src import (
SrcItem, SrcItem,
) )
logg = logging.getLogger(__name__)
class Src(BaseSrc): class Src(BaseSrc):
@classmethod @classmethod
def src_normalize(self, v): def src_normalize(self, v):
src = snake_and_camel(v) src = snake_and_camel(v)
logg.debug('normalize has {}'.format(src))
if isinstance(src.get('v'), str): if isinstance(src.get('v'), str):
try: try:
src['v'] = int(src['v']) src['v'] = int(src['v'])

View File

@ -11,6 +11,7 @@ from hexathon import (
add_0x, add_0x,
compact, compact,
to_int as hex_to_int, to_int as hex_to_int,
same as hex_same,
) )
from rlp import decode as rlp_decode from rlp import decode as rlp_decode
from rlp import encode as rlp_encode from rlp import encode as rlp_encode
@ -31,6 +32,7 @@ from chainlib.eth.nonce import (
nonce as nonce_query, nonce as nonce_query,
nonce_confirmed as nonce_query_confirmed, nonce_confirmed as nonce_query_confirmed,
) )
from chainlib.eth.address import is_same_address
from chainlib.block import BlockSpec from chainlib.block import BlockSpec
from chainlib.src import SrcItem from chainlib.src import SrcItem
@ -516,6 +518,50 @@ class TxFactory:
return o return o
class TxResult(BaseTxResult, Src):
def apply_src(self, v):
self.contract = None
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): 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. """Wraps transaction data, transaction receipt data and block data, enforces local standardization of fields, and provides useful output formats for viewing transaction contents.
@ -534,9 +580,6 @@ class Tx(BaseTx, Src):
""" """
def __init__(self, src, block=None, result=None, strict=False, rcpt=None): def __init__(self, src, block=None, result=None, strict=False, rcpt=None):
if result == None:
result = rcpt
# backwards compat # backwards compat
self.gas_price = None self.gas_price = None
self.gas_limit = None self.gas_limit = None
@ -546,75 +589,9 @@ class Tx(BaseTx, Src):
self.s = None self.s = None
super(Tx, self).__init__(src, block=block, result=result, strict=strict) super(Tx, self).__init__(src, block=block, result=result, strict=strict)
#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)]
# self.fee_limit = self.gas_limit
# self.fee_price = self.gas_price
# 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
# to = src['to']
# if to == None:
# to = ZERO_ADDRESS
# self.inputs = [to_checksum(strip_0x(to))]
# self.block = block
# try:
# self.wire = src['raw']
# except KeyError:
# logg.debug('no inline raw tx src, and no raw rendering implemented, field will be "None"')
# self.status = Status.PENDING
# self.logs = None
#self.tx_rcpt_src = None
#if rcpt != None:
# self.apply_receipt(rcpt, strict=strict)
#self.outputs = [to_checksum(address_from)]
# self.v = src.get('v')
# self.r = src.get('r')
# self.s = src.get('s')
# self.wire = None
# self.tx_src = src
if result == None and rcpt != None:
self.apply_receipt(rcpt)
def apply_src(self, src): def apply_src(self, src):
@ -626,7 +603,8 @@ class Tx(BaseTx, Src):
src = super(Tx, self).apply_src(src) src = super(Tx, self).apply_src(src)
self.hash = self.normal(src['hash'], SrcItem.HASH) hsh = self.normal(src['hash'], SrcItem.HASH)
self.set_hash(hsh)
try: try:
self.value = hex_to_int(src['value']) self.value = hex_to_int(src['value'])
@ -673,34 +651,16 @@ class Tx(BaseTx, Src):
self.status = Status.PENDING self.status = Status.PENDING
# @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
def as_dict(self): def as_dict(self):
return self.src() return self.src()
def rcpt_src(self):
return self.tx_rcpt_src
def apply_receipt(self, rcpt, strict=False): def apply_receipt(self, rcpt, strict=False):
result = TxResult(rcpt)
self.apply_result(result)
def apply_result(self, result, strict=False):
"""Apply receipt data to transaction object. """Apply receipt data to transaction object.
Effect is the same as passing a receipt at construction. Effect is the same as passing a receipt at construction.
@ -708,53 +668,14 @@ class Tx(BaseTx, Src):
:param rcpt: Receipt data :param rcpt: Receipt data
:type rcpt: dict :type rcpt: dict
""" """
rcpt = self.src_normalize(rcpt) if not hex_same(result.hash, self.hash):
logg.debug('rcpt {}'.format(rcpt)) raise ValueError('result hash {} does not match transaction hash {}'.format(result.hash, self.hash))
self.tx_rcpt_src = rcpt
tx_hash = add_0x(rcpt['transaction_hash'])
if rcpt['transaction_hash'] != add_0x(self.hash):
raise ValueError('rcpt hash {} does not match transaction hash {}'.format(rcpt['transaction_hash'], self.hash))
block_hash = add_0x(rcpt['block_hash'])
if self.block != None: if self.block != None:
if block_hash != add_0x(self.block.hash): if not hex_same(result.block_hash, self.block.hash):
raise ValueError('rcpt block hash {} does not match transaction block hash {}'.format(rcpt['block_hash'], self.block.hash)) raise ValueError('result block hash {} does not match transaction block hash {}'.format(result.block_hash, self.block.hash))
try: super(Tx, self).apply_result(result)
status_number = int(rcpt['status'], 16)
except TypeError:
status_number = int(rcpt['status'])
except KeyError as e:
if strict:
raise(e)
logg.debug('setting "success" status on missing status property for {}'.format(self.hash))
status_number = 1
if rcpt['block_number'] == None:
self.status = Status.PENDING
else:
if status_number == 1:
self.status = Status.SUCCESS
elif status_number == 0:
self.status = Status.ERROR
try:
self.tx_index = int(rcpt['transaction_index'], 16)
except TypeError:
self.tx_index = int(rcpt['transaction_index'])
# TODO: replace with rpc receipt/transaction translator when available
contract_address = rcpt.get('contractAddress')
if contract_address == None:
contract_address = rcpt.get('contract_address')
if contract_address != None:
self.contract = contract_address
self.logs = rcpt['logs']
try:
self.gas_used = int(rcpt['gasUsed'], 16)
except TypeError:
self.gas_used = int(rcpt['gasUsed'])
#self.__rcpt_block_hash = rcpt['block_hash']
def apply_block(self, block): def apply_block(self, block):
@ -861,3 +782,5 @@ tx_index {}
) )
return s return s

View File

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

View File

@ -30,6 +30,7 @@ from chainlib.eth.address import (
from hexathon import ( from hexathon import (
strip_0x, strip_0x,
add_0x, add_0x,
same as hex_same,
) )
from chainlib.eth.block import Block from chainlib.eth.block import Block
@ -58,6 +59,7 @@ class TxTestCase(EthTesterCase):
self.assertTrue(is_same_address(tx.inputs[0], tx_src['to'])) self.assertTrue(is_same_address(tx.inputs[0], tx_src['to']))
self.assertEqual(tx.value, tx_src['value']) self.assertEqual(tx.value, tx_src['value'])
self.assertEqual(tx.nonce, tx_src['nonce']) self.assertEqual(tx.nonce, tx_src['nonce'])
self.assertTrue(hex_same(tx.payload, tx_src['data']))
def test_tx_reciprocal(self): def test_tx_reciprocal(self):
@ -127,6 +129,7 @@ class TxTestCase(EthTesterCase):
'number': 42, 'number': 42,
'timestamp': 13241324, 'timestamp': 13241324,
'transactions': [], 'transactions': [],
'author': os.urandom(20).hex(),
}) })
with self.assertRaises(AttributeError): with self.assertRaises(AttributeError):
tx = Tx(tx_data, block=block) tx = Tx(tx_data, block=block)
@ -169,7 +172,9 @@ class TxTestCase(EthTesterCase):
'number': 42, 'number': 42,
'timestamp': 13241324, 'timestamp': 13241324,
'transactions': [], 'transactions': [],
'author': os.urandom(20).hex(),
}) })
block.txs = [add_0x(tx_data['hash'])] block.txs = [add_0x(tx_data['hash'])]
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
tx = Tx(tx_data, rcpt=rcpt, block=block) tx = Tx(tx_data, rcpt=rcpt, block=block)