commit 972535f1f9cc32ddafefafa68f046763d6f01cb1
parent a2168a50e3b9ea56e9921fd4a485465d3d967d1d
Author: lash <dev@holbrook.no>
Date: Mon, 9 May 2022 18:46:10 +0000
Complete test and refactor for generic tx, result, block objects
Diffstat:
5 files changed, 80 insertions(+), 140 deletions(-)
diff --git a/chainlib/eth/block.py b/chainlib/eth/block.py
@@ -10,6 +10,7 @@ from hexathon import (
# local imports
from chainlib.eth.tx import Tx
+from .src import Src
def block_latest(id_generator=None):
@@ -76,7 +77,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
@@ -88,7 +89,8 @@ class Block(BaseBlock):
def __init__(self, src):
super(Block, self).__init__(src)
- self.hash = src['hash']
+ import sys
+ self.set_hash(src['hash'])
try:
self.number = int(strip_0x(src['number']), 16)
except TypeError:
diff --git a/chainlib/eth/src.py b/chainlib/eth/src.py
@@ -1,3 +1,6 @@
+# standard imports
+import logging
+
# external imports
from potaahto.symbols import snake_and_camel
from hexathon import (
@@ -11,12 +14,15 @@ from chainlib.src import (
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'])
diff --git a/chainlib/eth/tx.py b/chainlib/eth/tx.py
@@ -11,6 +11,7 @@ from hexathon import (
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
@@ -31,6 +32,7 @@ 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
@@ -516,6 +518,50 @@ class TxFactory:
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):
"""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):
- if result == None:
- result = rcpt
-
# backwards compat
self.gas_price = None
self.gas_limit = None
@@ -546,75 +589,9 @@ class Tx(BaseTx, Src):
self.s = None
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):
@@ -626,7 +603,8 @@ class Tx(BaseTx, 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:
self.value = hex_to_int(src['value'])
@@ -673,34 +651,16 @@ class Tx(BaseTx, Src):
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):
return self.src()
- def rcpt_src(self):
- return self.tx_rcpt_src
+ def apply_receipt(self, rcpt, strict=False):
+ result = TxResult(rcpt)
+ self.apply_result(result)
- def apply_receipt(self, rcpt, strict=False):
+ def apply_result(self, result, strict=False):
"""Apply receipt data to transaction object.
Effect is the same as passing a receipt at construction.
@@ -708,53 +668,14 @@ class Tx(BaseTx, Src):
:param rcpt: Receipt data
:type rcpt: dict
"""
- rcpt = self.src_normalize(rcpt)
- logg.debug('rcpt {}'.format(rcpt))
- self.tx_rcpt_src = rcpt
+ if not hex_same(result.hash, self.hash):
+ raise ValueError('result hash {} does not match transaction hash {}'.format(result.hash, self.hash))
- tx_hash = add_0x(rcpt['transaction_hash'])
- if rcpt['transaction_hash'] != add_0x(self.hash):
- raise ValueError('rcpt hash {} does not match transaction hash {}'.format(rcpt['transaction_hash'], self.hash))
-
- block_hash = add_0x(rcpt['block_hash'])
if self.block != None:
- if block_hash != add_0x(self.block.hash):
- raise ValueError('rcpt block hash {} does not match transaction block hash {}'.format(rcpt['block_hash'], self.block.hash))
+ if not hex_same(result.block_hash, self.block.hash):
+ raise ValueError('result block hash {} does not match transaction block hash {}'.format(result.block_hash, self.block.hash))
- try:
- status_number = int(rcpt['status'], 16)
- except TypeError:
- status_number = int(rcpt['status'])
- except KeyError as e:
- if strict:
- raise(e)
- logg.debug('setting "success" status on missing status property for {}'.format(self.hash))
- status_number = 1
-
- if rcpt['block_number'] == None:
- self.status = Status.PENDING
- else:
- if status_number == 1:
- self.status = Status.SUCCESS
- elif status_number == 0:
- self.status = Status.ERROR
- try:
- self.tx_index = int(rcpt['transaction_index'], 16)
- except TypeError:
- self.tx_index = int(rcpt['transaction_index'])
- # TODO: replace with rpc receipt/transaction translator when available
- contract_address = rcpt.get('contractAddress')
- if contract_address == None:
- contract_address = rcpt.get('contract_address')
- if contract_address != None:
- self.contract = contract_address
- self.logs = rcpt['logs']
- try:
- self.gas_used = int(rcpt['gasUsed'], 16)
- except TypeError:
- self.gas_used = int(rcpt['gasUsed'])
-
- #self.__rcpt_block_hash = rcpt['block_hash']
+ super(Tx, self).apply_result(result)
def apply_block(self, block):
@@ -861,3 +782,5 @@ tx_index {}
)
return s
+
+
diff --git a/tests/test_stat.py b/tests/test_stat.py
@@ -1,6 +1,7 @@
# standard imports
import unittest
import datetime
+import os
# external imports
from chainlib.stat import ChainStat
@@ -19,6 +20,7 @@ class TestStat(unittest.TestCase):
'hash': None,
'transactions': [],
'number': 41,
+ 'author': os.urandom(20).hex(),
})
d = datetime.datetime.utcnow()
@@ -27,6 +29,7 @@ class TestStat(unittest.TestCase):
'hash': None,
'transactions': [],
'number': 42,
+ 'author': os.urandom(20).hex(),
})
s.block_apply(block_a)
@@ -39,6 +42,7 @@ class TestStat(unittest.TestCase):
'hash': None,
'transactions': [],
'number': 43,
+ 'author': os.urandom(20).hex(),
})
s.block_apply(block_c)
diff --git a/tests/test_tx.py b/tests/test_tx.py
@@ -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
@@ -58,6 +59,7 @@ class TxTestCase(EthTesterCase):
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):
@@ -127,6 +129,7 @@ class TxTestCase(EthTesterCase):
'number': 42,
'timestamp': 13241324,
'transactions': [],
+ 'author': os.urandom(20).hex(),
})
with self.assertRaises(AttributeError):
tx = Tx(tx_data, block=block)
@@ -169,7 +172,9 @@ class TxTestCase(EthTesterCase):
'number': 42,
'timestamp': 13241324,
'transactions': [],
+ 'author': os.urandom(20).hex(),
})
+
block.txs = [add_0x(tx_data['hash'])]
with self.assertRaises(ValueError):
tx = Tx(tx_data, rcpt=rcpt, block=block)