WIP implement generic tx and block
This commit is contained in:
parent
9548ed5d1b
commit
a2168a50e3
@ -1,3 +1,5 @@
|
||||
- 0.2.0
|
||||
* Implement chainlib generic tx, block and tx result objects
|
||||
- 0.1.3
|
||||
* Add block author field
|
||||
- 0.1.2
|
||||
|
@ -87,6 +87,7 @@ class Block(BaseBlock):
|
||||
tx_generator = Tx
|
||||
|
||||
def __init__(self, src):
|
||||
super(Block, self).__init__(src)
|
||||
self.hash = src['hash']
|
||||
try:
|
||||
self.number = int(strip_0x(src['number']), 16)
|
||||
@ -101,7 +102,7 @@ class Block(BaseBlock):
|
||||
self.author = src['author']
|
||||
|
||||
|
||||
def get_tx(self, tx_hash):
|
||||
def tx_index_by_hash(self, tx_hash):
|
||||
i = 0
|
||||
idx = -1
|
||||
tx_hash = add_0x(tx_hash)
|
||||
@ -118,4 +119,3 @@ class Block(BaseBlock):
|
||||
if idx == -1:
|
||||
raise AttributeError('tx {} not found in block {}'.format(tx_hash, self.hash))
|
||||
return idx
|
||||
|
||||
|
42
chainlib/eth/src.py
Normal file
42
chainlib/eth/src.py
Normal file
@ -0,0 +1,42 @@
|
||||
# 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,
|
||||
)
|
||||
|
||||
|
||||
class Src(BaseSrc):
|
||||
|
||||
@classmethod
|
||||
def src_normalize(self, v):
|
||||
src = snake_and_camel(v)
|
||||
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
|
@ -10,6 +10,7 @@ from hexathon import (
|
||||
strip_0x,
|
||||
add_0x,
|
||||
compact,
|
||||
to_int as hex_to_int,
|
||||
)
|
||||
from rlp import decode as rlp_decode
|
||||
from rlp import encode as rlp_encode
|
||||
@ -22,12 +23,16 @@ 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.block import BlockSpec
|
||||
from chainlib.src import SrcItem
|
||||
|
||||
# local imports
|
||||
from .address import to_checksum
|
||||
@ -39,6 +44,7 @@ from .constant import (
|
||||
)
|
||||
from .contract import ABIContractEncoder
|
||||
from .jsonrpc import to_blockheight_param
|
||||
from .src import Src
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
@ -510,7 +516,7 @@ class TxFactory:
|
||||
return o
|
||||
|
||||
|
||||
class Tx(BaseTx):
|
||||
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.
|
||||
@ -527,103 +533,163 @@ class Tx(BaseTx):
|
||||
#:todo: divide up constructor method
|
||||
"""
|
||||
|
||||
def __init__(self, src, block=None, rcpt=None, strict=False):
|
||||
self.__rcpt_block_hash = None
|
||||
def __init__(self, src, block=None, result=None, strict=False, rcpt=None):
|
||||
if result == None:
|
||||
result = rcpt
|
||||
|
||||
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)]
|
||||
# backwards compat
|
||||
self.gas_price = None
|
||||
self.gas_limit = None
|
||||
self.contract = None
|
||||
self.v = None
|
||||
self.r = None
|
||||
self.s = None
|
||||
|
||||
self.fee_limit = self.gas_limit
|
||||
self.fee_price = self.gas_price
|
||||
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
|
||||
|
||||
|
||||
|
||||
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)
|
||||
|
||||
self.hash = self.normal(src['hash'], SrcItem.HASH)
|
||||
|
||||
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.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.v = src.get('v')
|
||||
self.r = src.get('r')
|
||||
self.s = src.get('s')
|
||||
|
||||
self.wire = None
|
||||
|
||||
self.tx_src = src
|
||||
self.status = Status.PENDING
|
||||
|
||||
|
||||
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
|
||||
# @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):
|
||||
@ -688,7 +754,7 @@ class Tx(BaseTx):
|
||||
except TypeError:
|
||||
self.gas_used = int(rcpt['gasUsed'])
|
||||
|
||||
self.__rcpt_block_hash = rcpt['block_hash']
|
||||
#self.__rcpt_block_hash = rcpt['block_hash']
|
||||
|
||||
|
||||
def apply_block(self, block):
|
||||
@ -697,9 +763,9 @@ 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))
|
||||
#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
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = chainlib-eth
|
||||
version = 0.1.3
|
||||
version = 0.2.0
|
||||
description = Ethereum implementation of the chainlib interface
|
||||
author = Louis Holbrook
|
||||
author_email = dev@holbrook.no
|
||||
|
@ -1,12 +1,63 @@
|
||||
# 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()
|
||||
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,
|
||||
}
|
||||
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')
|
||||
|
@ -39,6 +39,26 @@ 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'])
|
||||
|
||||
|
||||
def test_tx_reciprocal(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
|
Loading…
Reference in New Issue
Block a user