Complete test and refactor for generic tx, result, block objects
This commit is contained in:
parent
a2168a50e3
commit
972535f1f9
@ -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:
|
||||||
|
@ -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'])
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user