chainlib-eth

Ethereum implementation of the chainlib interface
Info | Log | Files | Refs | README | LICENSE

tx.py (26450B)


      1 # standard imports
      2 import logging
      3 import enum
      4 import re
      5 import math
      6 
      7 # external imports
      8 import coincurve
      9 import sha3
     10 from hexathon import (
     11         strip_0x,
     12         add_0x,
     13         compact,
     14         to_int as hex_to_int,
     15         same as hex_same,
     16         unpad as hex_unpad,
     17         int_to_minbytes,
     18         )
     19 from rlp import decode as rlp_decode
     20 from rlp import encode as rlp_encode
     21 from funga.eth.transaction import EIP155Transaction
     22 from funga.eth.encoding import (
     23         public_key_to_address,
     24         chain_id_to_v,
     25         )
     26 from potaahto.symbols import snake_and_camel
     27 from chainlib.hash import keccak256_hex_to_hex
     28 from chainlib.status import Status
     29 from chainlib.jsonrpc import JSONRPCRequest
     30 from chainlib.tx import (
     31         Tx as BaseTx,
     32         TxResult as BaseTxResult,
     33         )
     34 from chainlib.eth.nonce import (
     35         nonce as nonce_query,
     36         nonce_confirmed as nonce_query_confirmed,
     37         )
     38 from chainlib.eth.address import is_same_address
     39 from chainlib.block import BlockSpec
     40 from chainlib.src import SrcItem
     41 
     42 # local imports
     43 from .address import to_checksum
     44 from .constant import (
     45         MINIMUM_FEE_UNITS,
     46         MINIMUM_FEE_PRICE,
     47         ZERO_ADDRESS,
     48         DEFAULT_FEE_LIMIT,
     49         )
     50 from .contract import ABIContractEncoder
     51 from .jsonrpc import to_blockheight_param
     52 from .src import Src
     53 from .dialect import DialectFilter
     54 
     55 logg = logging.getLogger(__name__)
     56 
     57 eth_dialect_filter = DialectFilter()
     58 
     59 
     60 class TxFormat(enum.IntEnum):
     61     """Tx generator output formats
     62     """
     63     DICT = 0x00
     64     RAW = 0x01
     65     RAW_SIGNED = 0x02
     66     RAW_ARGS = 0x03
     67     RLP = 0x10
     68     RLP_SIGNED = 0x11
     69     JSONRPC = 0x10
     70      
     71 
     72 field_debugs = [
     73         'nonce',
     74         'gasPrice',
     75         'gas',
     76         'to',
     77         'value',
     78         'data',
     79         'v',
     80         'r',
     81         's',
     82         ]
     83 
     84 
     85 count = nonce_query
     86 count_pending = nonce_query
     87 count_confirmed = nonce_query_confirmed
     88 
     89 
     90 def pack(tx_src, chain_spec):
     91     """Serialize wire format transaction from transaction representation.
     92 
     93     :param tx_src: Transaction source.
     94     :type tx_src: dict
     95     :param chain_spec: Chain spec to calculate EIP155 v value
     96     :type chain_spec: chainlib.chain.ChainSpec
     97     :rtype: bytes
     98     :returns: Serialized transaction
     99     """
    100     if isinstance(tx_src, Tx):
    101         tx_src = tx_src.as_dict()
    102     tx_src = Tx.src_normalize(tx_src)
    103     tx = EIP155Transaction(tx_src, tx_src['nonce'], chain_spec.chain_id())
    104 
    105     signature = bytearray(65)
    106     cursor = 0
    107     for a in [
    108             tx_src['r'],
    109             tx_src['s'],
    110             ]:
    111         try:
    112             a = strip_0x(a)
    113         except TypeError:
    114             a = strip_0x(hex(a)) # believe it or not, eth_tester returns signatures as ints not hex
    115         for b in bytes.fromhex(a):
    116             signature[cursor] = b
    117             cursor += 1
    118 
    119     #signature[cursor] = chainv_to_v(chain_spec.chain_id(), tx_src['v'])
    120     tx.apply_signature(chain_spec.chain_id(), signature, v=tx_src['v'])
    121     logg.debug('tx {}'.format(tx.serialize()))
    122     return tx.rlp_serialize()
    123 
    124 
    125 def unpack(tx_raw_bytes, chain_spec):
    126     """Deserialize wire format transaction to transaction representation.
    127 
    128     :param tx_raw_bytes: Serialized transaction
    129     :type tx_raw_bytes: bytes
    130     :param chain_spec: Chain spec to calculate EIP155 v value
    131     :type chain_spec: chainlib.chain.ChainSpec
    132     :rtype: dict
    133     :returns: Transaction representation
    134     """
    135     chain_id = chain_spec.chain_id()
    136     tx = __unpack_raw(tx_raw_bytes, chain_id)
    137     tx['nonce'] = int.from_bytes(tx['nonce'], 'big')
    138     tx['gasPrice'] = int.from_bytes(tx['gasPrice'], 'big')
    139     tx['gas'] = int.from_bytes(tx['gas'], 'big')
    140     tx['value'] = int.from_bytes(tx['value'], 'big')
    141     return tx
    142 
    143 
    144 def unpack_hex(tx_raw_bytes, chain_spec):
    145     """Deserialize wire format transaction to transaction representation, using hex values for all numeric value fields.
    146 
    147     :param tx_raw_bytes: Serialized transaction
    148     :type tx_raw_bytes: bytes
    149     :param chain_spec: Chain spec to calculate EIP155 v value
    150     :type chain_spec: chainlib.chain.ChainSpec
    151     :rtype: dict
    152     :returns: Transaction representation
    153     """
    154     chain_id = chain_spec.chain_id()
    155     tx = __unpack_raw(tx_raw_bytes, chain_id)
    156     tx['nonce'] = add_0x(hex(tx['nonce']))
    157     tx['gasPrice'] = add_0x(hex(tx['gasPrice']))
    158     tx['gas'] = add_0x(hex(tx['gas']))
    159     tx['value'] = add_0x(hex(tx['value']))
    160     tx['chainId'] = add_0x(hex(tx['chainId']))
    161     return tx
    162 
    163 
    164 def __unpack_raw(tx_raw_bytes, chain_id=1):
    165     try:
    166         d = rlp_decode(tx_raw_bytes)
    167     except Exception as e:
    168         raise ValueError('RLP deserialization failed: {}'.format(e))
    169 
    170     logg.debug('decoding using chain id {}'.format(str(chain_id)))
    171     
    172     j = 0
    173     for i in d:
    174         v = i.hex()
    175         if j != 3 and v == '':
    176             v = '00'
    177         logg.debug('decoded {}: {}'.format(field_debugs[j], v))
    178         j += 1
    179     vb = chain_id
    180     if chain_id != 0:
    181         v = int.from_bytes(d[6], 'big')
    182         if v > 29:
    183             vb = v - (chain_id * 2) - 35
    184     r = bytearray(32)
    185     r[32-len(d[7]):] = d[7]
    186     s = bytearray(32)
    187     s[32-len(d[8]):] = d[8]
    188     logg.debug('vb {}'.format(vb))
    189     sig = b''.join([r, s, bytes([vb])])
    190 
    191     h = sha3.keccak_256()
    192     h.update(rlp_encode(d))
    193     signed_hash = h.digest()
    194 
    195     d[6] = chain_id
    196     d[7] = b''
    197     d[8] = b''
    198 
    199     h = sha3.keccak_256()
    200     h.update(rlp_encode(d))
    201     unsigned_hash = h.digest()
    202     
    203     #p = so.recover_public_key_from_msg_hash(unsigned_hash)
    204     #a = p.to_checksum_address()
    205     pubk = coincurve.PublicKey.from_signature_and_message(sig, unsigned_hash, hasher=None)
    206     a = public_key_to_address(pubk)
    207     logg.debug('decoded recovery byte {}'.format(vb))
    208     logg.debug('decoded address {}'.format(a))
    209     logg.debug('decoded signed hash {}'.format(signed_hash.hex()))
    210     logg.debug('decoded unsigned hash {}'.format(unsigned_hash.hex()))
    211 
    212     to = d[3].hex() or None
    213     if to != None:
    214         to = to_checksum(to)
    215 
    216     data = d[5].hex()
    217     try:
    218         data = add_0x(data)
    219     except:
    220         data = '0x'
    221 
    222     v_bytes = int_to_minbytes(v)
    223     v_hex = v_bytes.hex()
    224     v_hex = hex_unpad(v_hex)
    225 
    226     return {
    227         'from': a,
    228         'to': to, 
    229         'nonce': d[0],
    230         'gasPrice': d[1],
    231         'gas': d[2],
    232         'value': d[4],
    233         'data': data,
    234         'v': add_0x(v_hex, pad=False),
    235         'recovery_byte': vb,
    236         'r': add_0x(sig[:32].hex()),
    237         's': add_0x(sig[32:64].hex()),
    238         'chainId': chain_id,
    239         'hash': add_0x(signed_hash.hex()),
    240         'hash_unsigned': add_0x(unsigned_hash.hex()),
    241             }
    242 
    243 
    244 def transaction(hsh, id_generator=None):
    245     """Generate json-rpc query to retrieve transaction by hash from node.
    246 
    247     :param hsh: Transaction hash, in hex
    248     :type hsh: str
    249     :param id_generator: json-rpc id generator
    250     :type id_generator: JSONRPCIdGenerator
    251     :rtype: dict
    252     :returns: rpc query object
    253     """
    254     j = JSONRPCRequest(id_generator=id_generator)
    255     o = j.template()
    256     o['method'] = 'eth_getTransactionByHash'
    257     o['params'].append(add_0x(hsh))
    258     return j.finalize(o)
    259 
    260 
    261 def transaction_by_block(hsh, idx, id_generator=None):
    262     """Generate json-rpc query to retrieve transaction by block hash and index.
    263 
    264     :param hsh: Block hash, in hex
    265     :type hsh: str
    266     :param idx: Transaction index
    267     :type idx: int
    268     :param id_generator: json-rpc id generator
    269     :type id_generator: JSONRPCIdGenerator
    270     :rtype: dict
    271     :returns: rpc query object
    272     """
    273     j = JSONRPCRequest(id_generator=id_generator)
    274     o = j.template()
    275     o['method'] = 'eth_getTransactionByBlockHashAndIndex'
    276     o['params'].append(add_0x(hsh))
    277     o['params'].append(hex(idx))
    278     return j.finalize(o)
    279 
    280 
    281 def receipt(hsh, id_generator=None):
    282     """Generate json-rpc query to retrieve transaction receipt by transaction hash from node.
    283 
    284     :param hsh: Transaction hash, in hex
    285     :type hsh: str
    286     :param id_generator: json-rpc id generator
    287     :type id_generator: JSONRPCIdGenerator
    288     :rtype: dict
    289     :returns: rpc query object
    290     """
    291     j = JSONRPCRequest(id_generator=id_generator)
    292     o = j.template()
    293     o['method'] = 'eth_getTransactionReceipt'
    294     o['params'].append(add_0x(hsh))
    295     return j.finalize(o)
    296 
    297 
    298 def raw(tx_raw_hex, id_generator=None):
    299     """Generator json-rpc query to send raw transaction to node.
    300 
    301     :param hsh: Serialized transaction, in hex
    302     :type hsh: str
    303     :param id_generator: json-rpc id generator
    304     :type id_generator: JSONRPCIdGenerator
    305     :rtype: dict
    306     :returns: rpc query object
    307     """
    308     j = JSONRPCRequest(id_generator=id_generator)
    309     o = j.template()
    310     o['method'] = 'eth_sendRawTransaction'
    311     o['params'].append(add_0x(tx_raw_hex))
    312     return j.finalize(o)
    313 
    314 
    315 class TxFactory:
    316     """Base class for generating and signing transactions or contract calls.
    317 
    318     For transactions (state changes), a signer, gas oracle and nonce oracle needs to be supplied.
    319 
    320     Gas oracle and nonce oracle may in some cases be needed for contract calls, if the node insists on counting gas for read-only operations.
    321 
    322     :param chain_spec: Chain spec to use for signer.
    323     :type chain_spec: chainlib.chain.ChainSpec
    324     :param signer: Signer middleware.
    325     :type param: Object implementing interface ofchainlib.eth.connection.sign_transaction_to_wire
    326     :param gas_oracle: Backend to generate gas parameters
    327     :type gas_oracle: Object implementing chainlib.eth.gas.GasOracle interface
    328     :param nonce_oracle: Backend to generate gas parameters
    329     :type nonce_oracle: Object implementing chainlib.eth.nonce.NonceOracle interface
    330     """
    331 
    332     fee = DEFAULT_FEE_LIMIT
    333 
    334     def __init__(self, chain_spec, signer=None, gas_oracle=None, nonce_oracle=None):
    335         self.gas_oracle = gas_oracle
    336         self.nonce_oracle = nonce_oracle
    337         self.chain_spec = chain_spec
    338         self.signer = signer
    339 
    340 
    341     def build_raw(self, tx):
    342         """Sign transaction data, returning the transaction hash and serialized transaction.
    343 
    344         In most cases, chainlib.eth.tx.TxFactory.finalize should be used instead.
    345 
    346         :param tx: Transaction representation
    347         :type tx: dict
    348         :rtype: tuple
    349         :returns: Transaction hash (in hex), serialized transaction (in hex)
    350         """
    351         if tx['to'] == None or tx['to'] == '':
    352             tx['to'] = '0x'
    353         txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
    354         tx_raw = self.signer.sign_transaction_to_wire(txe)
    355         tx_raw_hex = add_0x(tx_raw.hex())
    356         tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
    357         return (tx_hash_hex, tx_raw_hex)
    358 
    359 
    360     def build(self, tx, id_generator=None):
    361         """Sign transaction and wrap in raw transaction json-rpc query.
    362 
    363         In most cases, chainlib.eth.tx.TxFactory.finalize should be used instead.
    364 
    365         :param tx: Transaction representation
    366         type tx: dict
    367         :param id_generator: JSONRPC id generator
    368         :type id_generator: JSONRPCIdGenerator
    369         :rtype: tuple
    370         :returns: Transaction hash (in hex), raw transaction rpc query object
    371         """
    372         (tx_hash_hex, tx_raw_hex) = self.build_raw(tx) 
    373         o = raw(tx_raw_hex, id_generator=id_generator)
    374         return (tx_hash_hex, o)
    375 
    376 
    377     def template(self, sender, recipient, use_nonce=False):
    378         """Generate a base transaction template.
    379 
    380         :param sender: Sender address, in hex
    381         :type sender: str
    382         :param receipient: Recipient address, in hex
    383         :type recipient: str
    384         :param use_nonce: Use and advance nonce in nonce generator.
    385         :type use_nonce: bool
    386         :rtype: dict
    387         :returns: Transaction representation.
    388         """
    389         gas_price = MINIMUM_FEE_PRICE
    390         gas_limit = MINIMUM_FEE_UNITS
    391         if self.gas_oracle != None:
    392             (gas_price, gas_limit) = self.gas_oracle.get_gas()
    393         logg.debug('using gas price {} limit {}'.format(gas_price, gas_limit))
    394         nonce = 0
    395         o = {
    396                 'from': sender,
    397                 'to': recipient,
    398                 'value': 0,
    399                 'data': '0x',
    400                 'gasPrice': gas_price,
    401                 'gas': gas_limit,
    402                 'chainId': self.chain_spec.chain_id(),
    403                 }
    404         if self.nonce_oracle != None and use_nonce:
    405             nonce = self.nonce_oracle.next_nonce()
    406             logg.debug('using nonce {} for address {}'.format(nonce, sender))
    407         o['nonce'] = nonce
    408         return o
    409 
    410 
    411     def normalize(self, tx):
    412         """Generate field name redundancies (camel-case, snake-case).
    413 
    414         :param tx: Transaction representation
    415         :type tx: dict
    416         :rtype: dict:
    417         :returns: Transaction representation with redudant field names
    418         """
    419         txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
    420         txes = txe.serialize()
    421         gas_price = strip_0x(txes['gasPrice'])
    422         gas_price = compact(gas_price)
    423         gas = strip_0x(txes['gas'])
    424         gas = compact(gas)
    425         return {
    426             'from': tx['from'],
    427             'to': txes['to'],
    428             'gasPrice': add_0x(gas_price, compact_value=True),
    429             'gas': add_0x(gas, compact_value=True),
    430             'data': txes['data'],
    431                 }
    432 
    433 
    434     def finalize(self, tx, tx_format=TxFormat.JSONRPC, id_generator=None):
    435         """Sign transaction and for specified output format.
    436 
    437         :param tx: Transaction representation
    438         :type tx: dict
    439         :param tx_format: Transaction output format
    440         :type tx_format: chainlib.eth.tx.TxFormat
    441         :raises NotImplementedError: Unknown tx_format value
    442         :rtype: varies
    443         :returns: Transaction output in specified format.
    444         """
    445         if tx_format == TxFormat.JSONRPC:
    446             return self.build(tx, id_generator=id_generator)
    447         elif tx_format == TxFormat.RLP_SIGNED:
    448             return self.build_raw(tx)
    449         elif tx_format == TxFormat.RAW_ARGS:
    450             return strip_0x(tx['data'])
    451         elif tx_format == TxFormat.DICT:
    452             return tx
    453         raise NotImplementedError('tx formatting {} not implemented'.format(tx_format))
    454 
    455 
    456     def set_code(self, tx, data, update_fee=True):
    457         """Apply input data to transaction.
    458 
    459         :param tx: Transaction representation
    460         :type tx: dict
    461         :param data: Input data to apply, in hex
    462         :type data: str
    463         :param update_fee: Recalculate gas limit based on added input
    464         :type update_fee: bool
    465         :rtype: dict
    466         :returns: Transaction representation
    467         """
    468         tx['data'] = data
    469         if update_fee:
    470             tx['gas'] = TxFactory.fee
    471             if self.gas_oracle != None:
    472                 (price, tx['gas']) = self.gas_oracle.get_gas(code=data)
    473             else:
    474                 logg.debug('using hardcoded gas limit of 8000000 until we have reliable vm executor')
    475         return tx
    476 
    477     
    478     def transact_noarg(self, method, contract_address, sender_address, tx_format=TxFormat.JSONRPC):
    479         """Convenience generator for contract transaction with no arguments.
    480 
    481         :param method: Method name
    482         :type method: str
    483         :param contract_address: Contract address to transaction against, in hex
    484         :type contract_address: str
    485         :param sender_address: Transaction sender, in hex
    486         :type sender_address: str
    487         :param tx_format: Transaction output format
    488         :type tx_format: chainlib.eth.tx.TxFormat
    489         :rtype: varies
    490         :returns: Transaction output in selected format
    491         """
    492         enc = ABIContractEncoder()
    493         enc.method(method)
    494         data = enc.get()
    495         tx = self.template(sender_address, contract_address, use_nonce=True)
    496         tx = self.set_code(tx, data)
    497         tx = self.finalize(tx, tx_format)
    498         return tx
    499 
    500 
    501     def call_noarg(self, method, contract_address, sender_address=ZERO_ADDRESS, height=BlockSpec.LATEST, id_generator=None):
    502         """Convenience generator for contract (read-only) call with no arguments.
    503 
    504         :param method: Method name
    505         :type method: str
    506         :param contract_address: Contract address to transaction against, in hex
    507         :type contract_address: str
    508         :param sender_address: Transaction sender, in hex
    509         :type sender_address: str
    510         :param height: Transaction height specifier
    511         :type height: chainlib.block.BlockSpec
    512         :param id_generator: json-rpc id generator
    513         :type id_generator: JSONRPCIdGenerator
    514         :rtype: varies
    515         :returns: Transaction output in selected format
    516         """
    517         j = JSONRPCRequest(id_generator)
    518         o = j.template()
    519         o['method'] = 'eth_call'
    520         enc = ABIContractEncoder()
    521         enc.method(method)
    522         data = add_0x(enc.get())
    523         tx = self.template(sender_address, contract_address)
    524         tx = self.set_code(tx, data)
    525         o['params'].append(self.normalize(tx))
    526         height = to_blockheight_param(height)
    527         o['params'].append(height)
    528         o = j.finalize(o)
    529         return o
    530 
    531 
    532 class TxResult(BaseTxResult, Src):
    533 
    534     def apply_src(self, v, dialect_filter=None):
    535         self.contract = None
    536 
    537         super(TxResult, self).apply_src(v, dialect_filter=dialect_filter)
    538 
    539 
    540     def load_src(self, dialect_filter=None):
    541         self.set_hash(self.src['transaction_hash'])
    542         try:
    543             status_number = int(self.src['status'], 16)
    544         except TypeError:
    545             status_number = int(self.src['status'])
    546         except KeyError as e:
    547             if strict:
    548                 raise(e)
    549             logg.debug('setting "success" status on missing status property for {}'.format(self.hash))
    550             status_number = 1
    551 
    552         if self.src['block_number'] == None:
    553             self.status = Status.PENDING
    554         else:
    555             if status_number == 1:
    556                 self.status = Status.SUCCESS
    557             elif status_number == 0:
    558                 self.status = Status.ERROR
    559             try:
    560                 self.tx_index = hex_to_int(self.src['transaction_index'])
    561             except TypeError:
    562                 self.tx_index = int(self.src['transaction_index'])
    563             self.block_hash = self.src['block_hash']
    564 
    565         
    566         # TODO: replace with rpc receipt/transaction translator when available
    567         contract_address = self.src.get('contract_address')
    568         if contract_address != None:
    569             self.contract = contract_address
    570 
    571         self.logs = self.src['logs']
    572         try:
    573             self.fee_cost = hex_to_int(self.src['gas_used'])
    574         except TypeError:
    575             self.fee_cost = int(self.src['gas_used'])
    576 
    577 
    578 class Tx(BaseTx, Src):
    579     """Wraps transaction data, transaction receipt data and block data, enforces local standardization of fields, and provides useful output formats for viewing transaction contents.
    580 
    581     If block is applied, the transaction data or transaction hash must exist in its transactions array.
    582 
    583     If receipt is applied, the transaction hash in the receipt must match the hash in the transaction data.
    584 
    585     :param src: Transaction representation
    586     :type src: dict
    587     :param block: Apply block object in which transaction in mined.
    588     :type block: chainlib.block.Block
    589     :param rcpt: Apply receipt data 
    590     :type rcpt: dict
    591     #:todo: force tx type schema parser (whether expect hex or int etc)
    592     #:todo: divide up constructor method
    593     """
    594 
    595     def __init__(self, src, block=None, result=None, strict=False, rcpt=None, dialect_filter=eth_dialect_filter):
    596         # backwards compat
    597         self.gas_price = None
    598         self.gas_limit = None
    599         self.contract = None
    600         self.v = None
    601         self.r = None
    602         self.s = None
    603 
    604         super(Tx, self).__init__(src, block=block, result=result, strict=strict, dialect_filter=dialect_filter)
    605 
    606         if result == None and rcpt != None:
    607             self.apply_receipt(rcpt, dialect_filter=dialect_filter)
    608 
    609 
    610     #def apply_src(self, src, dialect_filter=None):
    611     #    src = super(Tx, self).apply_src(src, dialect_filter=dialect_filter)
    612 
    613 
    614     def load_src(self, dialect_filter=eth_dialect_filter):
    615         src = self.src
    616         if dialect_filter != None:
    617             src = dialect_filter.apply_tx(src)
    618         self.apply_src(src)
    619         hsh = self.normal(self.src['hash'], SrcItem.HASH)
    620         self.set_hash(hsh)
    621 
    622         try:
    623             self.value = hex_to_int(self.src['value'])
    624         except TypeError:
    625             self.value = int(self.src['value'])
    626 
    627         try:
    628             self.nonce = hex_to_int(self.src['nonce'])
    629         except TypeError:
    630             self.nonce = int(self.src['nonce'])
    631 
    632         try:
    633             self.fee_limit = hex_to_int(self.src['gas'])
    634         except TypeError:
    635             self.fee_limit = int(self.src['gas'])
    636 
    637         try:
    638             self.fee_price = hex_to_int(self.src['gas_price'])
    639         except TypeError:
    640             self.fee_price = int(self.src['gas_price'])
    641 
    642         self.gas_price = self.fee_price
    643         self.gas_limit = self.fee_limit
    644 
    645         address_from = self.normal(self.src['from'], SrcItem.ADDRESS)
    646         self.outputs = [to_checksum(address_from)]
    647 
    648         to = self.src['to']
    649         if to != None:
    650             to = to_checksum(strip_0x(to))
    651         self.inputs = [to]
    652     
    653         try:
    654             self.payload = self.normal(self.src['input'], SrcItem.PAYLOAD)
    655         except KeyError:
    656             self.payload = self.normal(self.src['data'], SrcItem.PAYLOAD)
    657 
    658 
    659         try:
    660             self.set_wire(self.src['raw'])
    661         except KeyError:
    662             logg.debug('no inline raw tx self.src, and no raw rendering implemented, field will be "None"')
    663 
    664         v = self.src.get('v')
    665         if type(v).__name__ == 'int':
    666             v = int_to_minbytes(v)
    667             v = v.hex()
    668             if len(v) == 0:
    669                 v = '0'
    670             else:
    671                 v = hex_unpad(v)
    672             v = add_0x(v, pad=False)
    673             self.src['v'] = v
    674 
    675         self.r = self.src.get('r')
    676         self.s = self.src.get('s')
    677         self.v = self.src.get('v')
    678 
    679 
    680     def as_dict(self):
    681         return self.src
    682 
    683 
    684     def apply_receipt(self, rcpt, strict=False, dialect_filter=None):
    685         result = TxResult(src=rcpt, dialect_filter=dialect_filter)
    686         self.apply_result(result, dialect_filter=dialect_filter)
    687 
    688 
    689     def apply_result(self, result, strict=False, dialect_filter=None):
    690         """Apply receipt data to transaction object.
    691 
    692         Effect is the same as passing a receipt at construction.
    693 
    694         :param rcpt: Receipt data
    695         :type rcpt: dict
    696         """
    697         if not hex_same(result.hash, self.hash):
    698             raise ValueError('result hash {} does not match transaction hash {}'.format(result.hash, self.hash))
    699 
    700         if self.block != None:
    701             if not hex_same(result.block_hash, self.block.hash):
    702                 raise ValueError('result block hash {} does not match transaction block hash {}'.format(result.block_hash, self.block.hash))
    703 
    704         super(Tx, self).apply_result(result)
    705 
    706 
    707     def apply_block(self, block, dialect_filter=None):
    708         """Apply block to transaction object.
    709 
    710         :param block: Block object
    711         :type block: chainlib.block.Block
    712         """
    713         self.index = block.get_tx(self.hash)
    714         self.block = block
    715 
    716 
    717     def generate_wire(self, chain_spec):
    718         """Generate transaction wire format.
    719 
    720         :param chain_spec: Chain spec to interpret EIP155 v value.
    721         :type chain_spec: chainlib.chain.ChainSpec
    722         :rtype: str
    723         :returns: Wire format, in hex
    724         """
    725         if self.wire == None:
    726             b = pack(self.src, chain_spec)
    727             self.set_wire(add_0x(b.hex()))
    728         return self.wire
    729 
    730 
    731     @staticmethod
    732     def from_src(src, block=None, rcpt=None, strict=False, chain_spec=None, dialect_filter=eth_dialect_filter):
    733         """Creates a new Tx object.
    734 
    735         Alias of constructor.
    736         """
    737         tx = Tx(src, block=block, rcpt=rcpt, strict=strict, dialect_filter=dialect_filter)
    738         if chain_spec != None:
    739             tx.generate_wire(chain_spec)
    740         return tx
    741 
    742 
    743     def __str__(self):
    744         if self.block != None:
    745             return 'tx {} status {} block {} index {}'.format(add_0x(self.hash), self.status.name, self.block.number, self.index)
    746         else:
    747             return 'tx {} status {}'.format(add_0x(self.hash), self.status.name)
    748 
    749 
    750     def __repr__(self):
    751         return self.__str__()
    752 
    753 
    754     def to_human(self, fields=None, skip_keys=False):
    755         """Human-readable string dump of transaction contents.
    756 
    757         :rtype: str
    758         :returns: Contents
    759         """
    760 
    761         outkeys = [
    762                 'hash',
    763                 'from',
    764                 'to',
    765                 'value',
    766                 'nonce',
    767                 'gas_price',
    768                 'gas_limit',
    769                 'input',
    770                 'status',
    771                 ]
    772 
    773         outvals = [
    774             self.hash,
    775             self.outputs[0],
    776             self.inputs[0],
    777             self.value,
    778             self.nonce,
    779             self.gas_price,
    780             self.gas_limit,
    781             self.payload,
    782                 ]
    783 
    784         status = Status.UNKNOWN.name
    785         logg.debug('selfstatus {}'.format(self.status))
    786         
    787         try:
    788             status = self.result.status.name
    789         except AttributeError:
    790             logg.debug('tx {} does not have a result yet', self.hash)
    791 
    792         #s += 'status ' + status + '\n'
    793         outvals.append(status)
    794 
    795         if self.result != None and self.result.status != Status.PENDING:
    796             outkeys.append('gas_used')
    797             outvals.append(self.result.fee_cost)
    798         if self.block != None:
    799             outkeys += [
    800                 'block_number',
    801                 'block_hash',
    802                 'tx_index',
    803                 ]
    804             outvals += [
    805                 self.block.number,
    806                 self.block.hash,
    807                 self.result.tx_index,
    808                 ]
    809 
    810         if self.wire != None:
    811             outkeys.append('src')
    812             outvals.append(self.wire)
    813 
    814         if self.result != None and self.result.contract != None:
    815             outkeys.append('contract')
    816             outvals.append(self.result.contract)
    817 
    818         s = ''
    819         for i, k in enumerate(outkeys):
    820             if fields != None:
    821                 if k not in fields:
    822                     continue
    823             if len(s) > 0:
    824                 s += '\n'
    825             if skip_keys:
    826                 s += outvals[i]
    827             else:
    828                 s += '{} {}'.format(k, outvals[i])
    829 
    830         return s