2021-06-28 07:48:36 +02:00
# standard imports
import logging
import enum
import re
# external imports
import coincurve
import sha3
from hexathon import (
strip_0x ,
add_0x ,
2021-08-21 09:27:40 +02:00
compact ,
2021-06-28 07:48:36 +02:00
)
from rlp import decode as rlp_decode
from rlp import encode as rlp_encode
2021-10-18 14:23:54 +02:00
from funga . eth . transaction import EIP155Transaction
from funga . eth . encoding import (
public_key_to_address ,
chain_id_to_v ,
)
2021-06-28 07:48:36 +02:00
from potaahto . symbols import snake_and_camel
from chainlib . hash import keccak256_hex_to_hex
from chainlib . status import Status
2021-08-21 09:27:40 +02:00
from chainlib . jsonrpc import JSONRPCRequest
from chainlib . tx import Tx as BaseTx
from chainlib . eth . nonce import (
nonce as nonce_query ,
nonce_confirmed as nonce_query_confirmed ,
)
from chainlib . block import BlockSpec
# local imports
2021-06-28 07:48:36 +02:00
from . address import to_checksum
from . constant import (
MINIMUM_FEE_UNITS ,
MINIMUM_FEE_PRICE ,
ZERO_ADDRESS ,
2021-08-21 09:27:40 +02:00
DEFAULT_FEE_LIMIT ,
2021-06-28 07:48:36 +02:00
)
from . contract import ABIContractEncoder
2021-08-21 09:27:40 +02:00
from . jsonrpc import to_blockheight_param
2021-06-28 07:48:36 +02:00
2021-08-21 09:27:40 +02:00
logg = logging . getLogger ( __name__ )
2021-06-28 07:48:36 +02:00
class TxFormat ( enum . IntEnum ) :
2021-08-21 09:27:40 +02:00
""" Tx generator output formats
"""
2021-06-28 07:48:36 +02:00
DICT = 0x00
RAW = 0x01
RAW_SIGNED = 0x02
RAW_ARGS = 0x03
RLP = 0x10
RLP_SIGNED = 0x11
JSONRPC = 0x10
field_debugs = [
' nonce ' ,
' gasPrice ' ,
' gas ' ,
' to ' ,
' value ' ,
' data ' ,
' v ' ,
' r ' ,
' s ' ,
]
2021-08-21 09:27:40 +02:00
count = nonce_query
count_pending = nonce_query
count_confirmed = nonce_query_confirmed
2021-06-28 07:48:36 +02:00
def pack ( tx_src , chain_spec ) :
2021-08-21 09:27:40 +02:00
""" Serialize wire format transaction from transaction representation.
: param tx_src : Transaction source .
: type tx_src : dict
: param chain_spec : Chain spec to calculate EIP155 v value
: type chain_spec : chainlib . chain . ChainSpec
: rtype : bytes
: returns : Serialized transaction
"""
2021-06-28 07:48:36 +02:00
if isinstance ( tx_src , Tx ) :
tx_src = tx_src . as_dict ( )
tx_src = Tx . src_normalize ( tx_src )
tx = EIP155Transaction ( tx_src , tx_src [ ' nonce ' ] , chain_spec . chain_id ( ) )
signature = bytearray ( 65 )
cursor = 0
for a in [
tx_src [ ' r ' ] ,
tx_src [ ' s ' ] ,
] :
2022-03-01 15:43:43 +01:00
try :
a = strip_0x ( a )
except TypeError :
a = strip_0x ( hex ( a ) ) # believe it or not, eth_tester returns signatures as ints not hex
for b in bytes . fromhex ( a ) :
2021-06-28 07:48:36 +02:00
signature [ cursor ] = b
cursor + = 1
#signature[cursor] = chainv_to_v(chain_spec.chain_id(), tx_src['v'])
tx . apply_signature ( chain_spec . chain_id ( ) , signature , v = tx_src [ ' v ' ] )
logg . debug ( ' tx {} ' . format ( tx . serialize ( ) ) )
return tx . rlp_serialize ( )
def unpack ( tx_raw_bytes , chain_spec ) :
2021-08-21 09:27:40 +02:00
""" Deserialize wire format transaction to transaction representation.
: param tx_raw_bytes : Serialized transaction
: type tx_raw_bytes : bytes
: param chain_spec : Chain spec to calculate EIP155 v value
: type chain_spec : chainlib . chain . ChainSpec
: rtype : dict
: returns : Transaction representation
"""
2021-06-28 07:48:36 +02:00
chain_id = chain_spec . chain_id ( )
tx = __unpack_raw ( tx_raw_bytes , chain_id )
tx [ ' nonce ' ] = int . from_bytes ( tx [ ' nonce ' ] , ' big ' )
tx [ ' gasPrice ' ] = int . from_bytes ( tx [ ' gasPrice ' ] , ' big ' )
tx [ ' gas ' ] = int . from_bytes ( tx [ ' gas ' ] , ' big ' )
tx [ ' value ' ] = int . from_bytes ( tx [ ' value ' ] , ' big ' )
return tx
def unpack_hex ( tx_raw_bytes , chain_spec ) :
2021-08-21 09:27:40 +02:00
""" Deserialize wire format transaction to transaction representation, using hex values for all numeric value fields.
: param tx_raw_bytes : Serialized transaction
: type tx_raw_bytes : bytes
: param chain_spec : Chain spec to calculate EIP155 v value
: type chain_spec : chainlib . chain . ChainSpec
: rtype : dict
: returns : Transaction representation
"""
2021-06-28 07:48:36 +02:00
chain_id = chain_spec . chain_id ( )
tx = __unpack_raw ( tx_raw_bytes , chain_id )
tx [ ' nonce ' ] = add_0x ( hex ( tx [ ' nonce ' ] ) )
tx [ ' gasPrice ' ] = add_0x ( hex ( tx [ ' gasPrice ' ] ) )
tx [ ' gas ' ] = add_0x ( hex ( tx [ ' gas ' ] ) )
tx [ ' value ' ] = add_0x ( hex ( tx [ ' value ' ] ) )
tx [ ' chainId ' ] = add_0x ( hex ( tx [ ' chainId ' ] ) )
return tx
def __unpack_raw ( tx_raw_bytes , chain_id = 1 ) :
2021-06-28 09:10:53 +02:00
try :
d = rlp_decode ( tx_raw_bytes )
except Exception as e :
raise ValueError ( ' RLP deserialization failed: {} ' . format ( e ) )
2021-06-28 07:48:36 +02:00
logg . debug ( ' decoding using chain id {} ' . format ( str ( chain_id ) ) )
j = 0
for i in d :
v = i . hex ( )
if j != 3 and v == ' ' :
v = ' 00 '
logg . debug ( ' decoded {} : {} ' . format ( field_debugs [ j ] , v ) )
j + = 1
vb = chain_id
if chain_id != 0 :
v = int . from_bytes ( d [ 6 ] , ' big ' )
2022-01-23 21:36:29 +01:00
if v > 29 :
vb = v - ( chain_id * 2 ) - 35
2021-06-28 07:48:36 +02:00
r = bytearray ( 32 )
r [ 32 - len ( d [ 7 ] ) : ] = d [ 7 ]
s = bytearray ( 32 )
s [ 32 - len ( d [ 8 ] ) : ] = d [ 8 ]
logg . debug ( ' vb {} ' . format ( vb ) )
sig = b ' ' . join ( [ r , s , bytes ( [ vb ] ) ] )
h = sha3 . keccak_256 ( )
h . update ( rlp_encode ( d ) )
signed_hash = h . digest ( )
d [ 6 ] = chain_id
d [ 7 ] = b ' '
d [ 8 ] = b ' '
h = sha3 . keccak_256 ( )
h . update ( rlp_encode ( d ) )
unsigned_hash = h . digest ( )
#p = so.recover_public_key_from_msg_hash(unsigned_hash)
#a = p.to_checksum_address()
pubk = coincurve . PublicKey . from_signature_and_message ( sig , unsigned_hash , hasher = None )
a = public_key_to_address ( pubk )
logg . debug ( ' decoded recovery byte {} ' . format ( vb ) )
logg . debug ( ' decoded address {} ' . format ( a ) )
logg . debug ( ' decoded signed hash {} ' . format ( signed_hash . hex ( ) ) )
logg . debug ( ' decoded unsigned hash {} ' . format ( unsigned_hash . hex ( ) ) )
to = d [ 3 ] . hex ( ) or None
if to != None :
to = to_checksum ( to )
data = d [ 5 ] . hex ( )
try :
data = add_0x ( data )
except :
data = ' 0x '
return {
' from ' : a ,
' to ' : to ,
' nonce ' : d [ 0 ] ,
' gasPrice ' : d [ 1 ] ,
' gas ' : d [ 2 ] ,
' value ' : d [ 4 ] ,
' data ' : data ,
' v ' : v ,
' recovery_byte ' : vb ,
' r ' : add_0x ( sig [ : 32 ] . hex ( ) ) ,
' s ' : add_0x ( sig [ 32 : 64 ] . hex ( ) ) ,
' chainId ' : chain_id ,
' hash ' : add_0x ( signed_hash . hex ( ) ) ,
' hash_unsigned ' : add_0x ( unsigned_hash . hex ( ) ) ,
}
def transaction ( hsh , id_generator = None ) :
2021-08-21 09:27:40 +02:00
""" Generate json-rpc query to retrieve transaction by hash from node.
: param hsh : Transaction hash , in hex
: type hsh : str
: param id_generator : json - rpc id generator
: type id_generator : JSONRPCIdGenerator
: rtype : dict
: returns : rpc query object
"""
2021-06-28 07:48:36 +02:00
j = JSONRPCRequest ( id_generator = id_generator )
o = j . template ( )
o [ ' method ' ] = ' eth_getTransactionByHash '
o [ ' params ' ] . append ( add_0x ( hsh ) )
return j . finalize ( o )
def transaction_by_block ( hsh , idx , id_generator = None ) :
2021-08-21 09:27:40 +02:00
""" Generate json-rpc query to retrieve transaction by block hash and index.
: param hsh : Block hash , in hex
: type hsh : str
: param idx : Transaction index
: type idx : int
: param id_generator : json - rpc id generator
: type id_generator : JSONRPCIdGenerator
: rtype : dict
: returns : rpc query object
"""
2021-06-28 07:48:36 +02:00
j = JSONRPCRequest ( id_generator = id_generator )
o = j . template ( )
o [ ' method ' ] = ' eth_getTransactionByBlockHashAndIndex '
o [ ' params ' ] . append ( add_0x ( hsh ) )
o [ ' params ' ] . append ( hex ( idx ) )
return j . finalize ( o )
def receipt ( hsh , id_generator = None ) :
2021-08-21 09:27:40 +02:00
""" Generate json-rpc query to retrieve transaction receipt by transaction hash from node.
: param hsh : Transaction hash , in hex
: type hsh : str
: param id_generator : json - rpc id generator
: type id_generator : JSONRPCIdGenerator
: rtype : dict
: returns : rpc query object
"""
2021-06-28 07:48:36 +02:00
j = JSONRPCRequest ( id_generator = id_generator )
o = j . template ( )
o [ ' method ' ] = ' eth_getTransactionReceipt '
o [ ' params ' ] . append ( add_0x ( hsh ) )
return j . finalize ( o )
def raw ( tx_raw_hex , id_generator = None ) :
2021-08-21 09:27:40 +02:00
""" Generator json-rpc query to send raw transaction to node.
: param hsh : Serialized transaction , in hex
: type hsh : str
: param id_generator : json - rpc id generator
: type id_generator : JSONRPCIdGenerator
: rtype : dict
: returns : rpc query object
"""
2021-06-28 07:48:36 +02:00
j = JSONRPCRequest ( id_generator = id_generator )
o = j . template ( )
o [ ' method ' ] = ' eth_sendRawTransaction '
o [ ' params ' ] . append ( add_0x ( tx_raw_hex ) )
return j . finalize ( o )
class TxFactory :
2021-08-21 09:27:40 +02:00
""" Base class for generating and signing transactions or contract calls.
2021-06-28 07:48:36 +02:00
2021-08-21 09:27:40 +02:00
For transactions ( state changes ) , a signer , gas oracle and nonce oracle needs to be supplied .
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 .
: param chain_spec : Chain spec to use for signer .
: type chain_spec : chainlib . chain . ChainSpec
: param signer : Signer middleware .
2021-08-24 17:55:01 +02:00
: type param : Object implementing interface ofchainlib . eth . connection . sign_transaction_to_wire
2021-08-21 09:27:40 +02:00
: param gas_oracle : Backend to generate gas parameters
: type gas_oracle : Object implementing chainlib . eth . gas . GasOracle interface
: param nonce_oracle : Backend to generate gas parameters
: type nonce_oracle : Object implementing chainlib . eth . nonce . NonceOracle interface
"""
fee = DEFAULT_FEE_LIMIT
2021-06-28 07:48:36 +02:00
def __init__ ( self , chain_spec , signer = None , gas_oracle = None , nonce_oracle = None ) :
self . gas_oracle = gas_oracle
self . nonce_oracle = nonce_oracle
self . chain_spec = chain_spec
self . signer = signer
def build_raw ( self , tx ) :
2021-08-21 09:27:40 +02:00
""" Sign transaction data, returning the transaction hash and serialized transaction.
In most cases , chainlib . eth . tx . TxFactory . finalize should be used instead .
: param tx : Transaction representation
: type tx : dict
: rtype : tuple
: returns : Transaction hash ( in hex ) , serialized transaction ( in hex )
"""
2021-06-28 07:48:36 +02:00
if tx [ ' to ' ] == None or tx [ ' to ' ] == ' ' :
tx [ ' to ' ] = ' 0x '
txe = EIP155Transaction ( tx , tx [ ' nonce ' ] , tx [ ' chainId ' ] )
2021-08-24 17:55:01 +02:00
tx_raw = self . signer . sign_transaction_to_wire ( txe )
2021-06-28 07:48:36 +02:00
tx_raw_hex = add_0x ( tx_raw . hex ( ) )
tx_hash_hex = add_0x ( keccak256_hex_to_hex ( tx_raw_hex ) )
return ( tx_hash_hex , tx_raw_hex )
def build ( self , tx , id_generator = None ) :
2021-08-21 09:27:40 +02:00
""" Sign transaction and wrap in raw transaction json-rpc query.
In most cases , chainlib . eth . tx . TxFactory . finalize should be used instead .
: param tx : Transaction representation
type tx : dict
: param id_generator : JSONRPC id generator
: type id_generator : JSONRPCIdGenerator
: rtype : tuple
: returns : Transaction hash ( in hex ) , raw transaction rpc query object
"""
2021-06-28 07:48:36 +02:00
( tx_hash_hex , tx_raw_hex ) = self . build_raw ( tx )
o = raw ( tx_raw_hex , id_generator = id_generator )
return ( tx_hash_hex , o )
def template ( self , sender , recipient , use_nonce = False ) :
2021-08-21 09:27:40 +02:00
""" Generate a base transaction template.
: param sender : Sender address , in hex
: type sender : str
: param receipient : Recipient address , in hex
: type recipient : str
: param use_nonce : Use and advance nonce in nonce generator .
: type use_nonce : bool
: rtype : dict
: returns : Transaction representation .
"""
2021-06-28 07:48:36 +02:00
gas_price = MINIMUM_FEE_PRICE
gas_limit = MINIMUM_FEE_UNITS
if self . gas_oracle != None :
( gas_price , gas_limit ) = self . gas_oracle . get_gas ( )
logg . debug ( ' using gas price {} limit {} ' . format ( gas_price , gas_limit ) )
nonce = 0
o = {
' from ' : sender ,
' to ' : recipient ,
' value ' : 0 ,
' data ' : ' 0x ' ,
' gasPrice ' : gas_price ,
' gas ' : gas_limit ,
' chainId ' : self . chain_spec . chain_id ( ) ,
}
if self . nonce_oracle != None and use_nonce :
nonce = self . nonce_oracle . next_nonce ( )
logg . debug ( ' using nonce {} for address {} ' . format ( nonce , sender ) )
o [ ' nonce ' ] = nonce
return o
def normalize ( self , tx ) :
2021-08-21 09:27:40 +02:00
""" Generate field name redundancies (camel-case, snake-case).
: param tx : Transaction representation
: type tx : dict
: rtype : dict :
: returns : Transaction representation with redudant field names
"""
2021-06-28 07:48:36 +02:00
txe = EIP155Transaction ( tx , tx [ ' nonce ' ] , tx [ ' chainId ' ] )
txes = txe . serialize ( )
2022-02-22 15:47:06 +01:00
gas_price = strip_0x ( txes [ ' gasPrice ' ] )
gas = strip_0x ( txes [ ' gas ' ] )
2021-06-28 07:48:36 +02:00
return {
' from ' : tx [ ' from ' ] ,
' to ' : txes [ ' to ' ] ,
2022-02-22 15:47:06 +01:00
' gasPrice ' : add_0x ( compact ( gas_price ) ) ,
' gas ' : add_0x ( compact ( gas ) ) ,
2021-06-28 07:48:36 +02:00
' data ' : txes [ ' data ' ] ,
}
def finalize ( self , tx , tx_format = TxFormat . JSONRPC , id_generator = None ) :
2021-08-21 09:27:40 +02:00
""" Sign transaction and for specified output format.
: param tx : Transaction representation
: type tx : dict
: param tx_format : Transaction output format
: type tx_format : chainlib . eth . tx . TxFormat
: raises NotImplementedError : Unknown tx_format value
: rtype : varies
: returns : Transaction output in specified format .
"""
2021-06-28 07:48:36 +02:00
if tx_format == TxFormat . JSONRPC :
return self . build ( tx , id_generator = id_generator )
elif tx_format == TxFormat . RLP_SIGNED :
return self . build_raw ( tx )
2021-12-06 19:00:55 +01:00
elif tx_format == TxFormat . RAW_ARGS :
return strip_0x ( tx [ ' data ' ] )
2021-06-28 07:48:36 +02:00
raise NotImplementedError ( ' tx formatting {} not implemented ' . format ( tx_format ) )
def set_code ( self , tx , data , update_fee = True ) :
2021-08-21 09:27:40 +02:00
""" Apply input data to transaction.
: param tx : Transaction representation
: type tx : dict
: param data : Input data to apply , in hex
: type data : str
: param update_fee : Recalculate gas limit based on added input
: type update_fee : bool
: rtype : dict
: returns : Transaction representation
"""
2021-06-28 07:48:36 +02:00
tx [ ' data ' ] = data
if update_fee :
tx [ ' gas ' ] = TxFactory . fee
if self . gas_oracle != None :
( price , tx [ ' gas ' ] ) = self . gas_oracle . get_gas ( code = data )
else :
logg . debug ( ' using hardcoded gas limit of 8000000 until we have reliable vm executor ' )
return tx
def transact_noarg ( self , method , contract_address , sender_address , tx_format = TxFormat . JSONRPC ) :
2021-08-21 09:27:40 +02:00
""" Convenience generator for contract transaction with no arguments.
: param method : Method name
: type method : str
: param contract_address : Contract address to transaction against , in hex
: type contract_address : str
: param sender_address : Transaction sender , in hex
: type sender_address : str
: param tx_format : Transaction output format
: type tx_format : chainlib . eth . tx . TxFormat
: rtype : varies
: returns : Transaction output in selected format
"""
2021-06-28 07:48:36 +02:00
enc = ABIContractEncoder ( )
enc . method ( method )
data = enc . get ( )
tx = self . template ( sender_address , contract_address , use_nonce = True )
tx = self . set_code ( tx , data )
tx = self . finalize ( tx , tx_format )
return tx
2021-08-21 09:27:40 +02:00
def call_noarg ( self , method , contract_address , sender_address = ZERO_ADDRESS , height = BlockSpec . LATEST , id_generator = None ) :
""" Convenience generator for contract (read-only) call with no arguments.
: param method : Method name
: type method : str
: param contract_address : Contract address to transaction against , in hex
: type contract_address : str
: param sender_address : Transaction sender , in hex
: type sender_address : str
: param height : Transaction height specifier
: type height : chainlib . block . BlockSpec
: param id_generator : json - rpc id generator
: type id_generator : JSONRPCIdGenerator
: rtype : varies
: returns : Transaction output in selected format
"""
2021-06-28 07:48:36 +02:00
j = JSONRPCRequest ( id_generator )
o = j . template ( )
o [ ' method ' ] = ' eth_call '
enc = ABIContractEncoder ( )
enc . method ( method )
data = add_0x ( enc . get ( ) )
tx = self . template ( sender_address , contract_address )
tx = self . set_code ( tx , data )
o [ ' params ' ] . append ( self . normalize ( tx ) )
2021-08-21 09:27:40 +02:00
height = to_blockheight_param ( height )
o [ ' params ' ] . append ( height )
2021-06-28 07:48:36 +02:00
o = j . finalize ( o )
return o
2021-08-21 09:27:40 +02:00
class Tx ( BaseTx ) :
""" 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 .
If receipt is applied , the transaction hash in the receipt must match the hash in the transaction data .
: param src : Transaction representation
: type src : dict
: param block : Apply block object in which transaction in mined .
: type block : chainlib . block . Block
: param rcpt : Apply receipt data
: type rcpt : dict
#:todo: force tx type schema parser (whether expect hex or int etc)
#:todo: divide up constructor method
"""
2021-06-28 07:48:36 +02:00
2022-02-12 13:30:13 +01:00
def __init__ ( self , src , block = None , rcpt = None , strict = False ) :
2021-08-21 09:27:40 +02:00
self . __rcpt_block_hash = None
src = self . src_normalize ( src )
2021-06-28 07:48:36 +02:00
self . index = - 1
tx_hash = add_0x ( src [ ' hash ' ] )
self . hash = strip_0x ( tx_hash )
2021-08-21 09:27:40 +02:00
if block != None :
self . apply_block ( block )
2021-06-28 07:48:36 +02:00
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 . contract = None
try :
inpt = src [ ' input ' ]
except KeyError :
inpt = src [ ' data ' ]
2021-08-21 09:27:40 +02:00
src [ ' input ' ] = src [ ' data ' ]
2021-06-28 07:48:36 +02:00
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 :
2022-03-01 08:50:30 +01:00
logg . debug ( ' no inline raw tx src, and no raw rendering implemented, field will be " None " ' )
2021-06-28 07:48:36 +02:00
self . status = Status . PENDING
self . logs = None
2022-03-01 08:50:30 +01:00
self . tx_rcpt_src = None
2021-06-28 07:48:36 +02:00
if rcpt != None :
2022-02-12 13:30:13 +01:00
self . apply_receipt ( rcpt , strict = strict )
2021-06-28 07:48:36 +02:00
self . v = src . get ( ' v ' )
self . r = src . get ( ' r ' )
self . s = src . get ( ' s ' )
self . wire = None
2021-08-21 09:27:40 +02:00
self . tx_src = src
2021-06-28 07:48:36 +02:00
def src ( self ) :
2021-08-21 09:27:40 +02:00
""" Retrieve normalized representation source used to construct transaction object.
: rtype : dict
: returns : Transaction representation
"""
2021-06-28 07:48:36 +02:00
return self . tx_src
@classmethod
def src_normalize ( self , src ) :
2021-08-21 09:27:40 +02:00
""" Normalizes transaction representation source data.
: param src : Transaction representation
: type src : dict
: rtype : dict
: returns : Transaction representation , normalized
"""
2021-06-28 07:48:36 +02:00
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 ( )
2022-03-01 08:50:30 +01:00
def rcpt_src ( self ) :
return self . tx_rcpt_src
2022-02-12 13:30:13 +01:00
def apply_receipt ( self , rcpt , strict = False ) :
2021-08-21 09:27:40 +02:00
""" Apply receipt data to transaction object.
Effect is the same as passing a receipt at construction .
: param rcpt : Receipt data
: type rcpt : dict
"""
2021-06-28 07:48:36 +02:00
rcpt = self . src_normalize ( rcpt )
logg . debug ( ' rcpt {} ' . format ( rcpt ) )
2022-03-01 08:50:30 +01:00
self . tx_rcpt_src = rcpt
2021-08-21 09:27:40 +02:00
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 ) )
2021-06-28 07:48:36 +02:00
try :
status_number = int ( rcpt [ ' status ' ] , 16 )
except TypeError :
status_number = int ( rcpt [ ' status ' ] )
2022-02-12 13:30:13 +01:00
except KeyError as e :
if strict :
raise ( e )
2022-03-01 08:50:30 +01:00
logg . debug ( ' setting " success " status on missing status property for {} ' . format ( self . hash ) )
2022-02-12 13:30:13 +01:00
status_number = 1
2021-08-21 09:27:40 +02:00
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 ' ] )
2021-06-28 07:48:36 +02:00
# 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 ' ] )
2021-08-21 09:27:40 +02:00
self . __rcpt_block_hash = rcpt [ ' block_hash ' ]
2021-06-28 07:48:36 +02:00
def apply_block ( self , block ) :
2021-08-21 09:27:40 +02:00
""" Apply block to transaction object.
: 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 ) )
self . index = block . get_tx ( self . hash )
2021-06-28 07:48:36 +02:00
self . block = block
def generate_wire ( self , chain_spec ) :
2021-08-21 09:27:40 +02:00
""" Generate transaction wire format.
: param chain_spec : Chain spec to interpret EIP155 v value .
: type chain_spec : chainlib . chain . ChainSpec
: rtype : str
: returns : Wire format , in hex
"""
if self . wire == None :
b = pack ( self . src ( ) , chain_spec )
self . wire = add_0x ( b . hex ( ) )
return self . wire
2021-06-28 07:48:36 +02:00
@staticmethod
2022-02-12 13:30:13 +01:00
def from_src ( src , block = None , rcpt = None , strict = False ) :
2021-08-21 09:27:40 +02:00
""" Creates a new Tx object.
Alias of constructor .
"""
2022-02-12 13:30:13 +01:00
return Tx ( src , block = block , rcpt = rcpt , strict = strict )
2021-06-28 07:48:36 +02:00
def __str__ ( self ) :
if self . block != None :
return ' tx {} status {} block {} index {} ' . format ( add_0x ( self . hash ) , self . status . name , self . block . number , self . index )
else :
return ' tx {} status {} ' . format ( add_0x ( self . hash ) , self . status . name )
def __repr__ ( self ) :
return self . __str__ ( )
def to_human ( self ) :
2021-08-21 09:27:40 +02:00
""" Human-readable string dump of transaction contents.
: rtype : str
: returns : Contents
"""
2021-06-28 07:48:36 +02:00
s = """ hash {}
from { }
to { }
value { }
nonce { }
2021-08-21 09:27:40 +02:00
gas_price { }
gas_limit { }
2021-06-28 07:48:36 +02:00
input { }
""" .format(
self . hash ,
self . outputs [ 0 ] ,
self . inputs [ 0 ] ,
self . value ,
self . nonce ,
self . gas_price ,
self . gas_limit ,
self . payload ,
)
if self . status != Status . PENDING :
2021-08-21 09:27:40 +02:00
s + = """ gas_used {}
2021-06-28 07:48:36 +02:00
""" .format(
self . gas_used ,
)
s + = ' status ' + self . status . name + ' \n '
2021-08-21 09:27:40 +02:00
if self . block != None :
s + = """ block_number {}
block_hash { }
tx_index { }
""" .format(
self . block . number ,
self . block . hash ,
self . tx_index ,
)
2021-06-28 07:48:36 +02:00
if self . contract != None :
s + = """ contract {}
""" .format(
self . contract ,
)
if self . wire != None :
s + = """ src {}
""" .format(
self . wire ,
)
return s