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