2021-02-08 11:23:07 +01:00
|
|
|
|
# standard imports
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
# third-party imports
|
|
|
|
|
import sha3
|
2021-02-12 00:24:10 +01:00
|
|
|
|
from hexathon import (
|
|
|
|
|
strip_0x,
|
|
|
|
|
add_0x,
|
|
|
|
|
)
|
2021-02-08 11:23:07 +01:00
|
|
|
|
from eth_keys import KeyAPI
|
|
|
|
|
from eth_keys.backends import NativeECCBackend
|
|
|
|
|
from rlp import decode as rlp_decode
|
|
|
|
|
from rlp import encode as rlp_encode
|
2021-02-09 12:12:37 +01:00
|
|
|
|
from crypto_dev_signer.eth.transaction import EIP155Transaction
|
2021-02-08 11:23:07 +01:00
|
|
|
|
|
2021-02-12 00:24:10 +01:00
|
|
|
|
|
2021-02-08 11:23:07 +01:00
|
|
|
|
# local imports
|
2021-02-12 00:24:10 +01:00
|
|
|
|
from chainlib.hash import keccak256_hex_to_hex
|
2021-02-08 11:23:07 +01:00
|
|
|
|
from .address import to_checksum
|
2021-02-09 12:12:37 +01:00
|
|
|
|
from .constant import (
|
|
|
|
|
MINIMUM_FEE_UNITS,
|
|
|
|
|
MINIMUM_FEE_PRICE,
|
2021-02-11 12:43:54 +01:00
|
|
|
|
ZERO_ADDRESS,
|
2021-02-09 12:12:37 +01:00
|
|
|
|
)
|
2021-02-12 00:24:10 +01:00
|
|
|
|
from .rpc import jsonrpc_template
|
2021-02-08 11:23:07 +01:00
|
|
|
|
|
|
|
|
|
logg = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
field_debugs = [
|
|
|
|
|
'nonce',
|
|
|
|
|
'gasPrice',
|
|
|
|
|
'gas',
|
|
|
|
|
'to',
|
|
|
|
|
'value',
|
|
|
|
|
'data',
|
|
|
|
|
'v',
|
|
|
|
|
'r',
|
|
|
|
|
's',
|
|
|
|
|
]
|
|
|
|
|
|
2021-02-17 10:13:22 +01:00
|
|
|
|
def unpack(tx_raw_bytes, chain_id=1):
|
2021-02-08 11:23:07 +01:00
|
|
|
|
d = rlp_decode(tx_raw_bytes)
|
|
|
|
|
|
|
|
|
|
logg.debug('decoding using chain id {}'.format(chain_id))
|
|
|
|
|
j = 0
|
|
|
|
|
for i in d:
|
|
|
|
|
logg.debug('decoded {}: {}'.format(field_debugs[j], i.hex()))
|
|
|
|
|
j += 1
|
|
|
|
|
vb = chain_id
|
|
|
|
|
if chain_id != 0:
|
|
|
|
|
v = int.from_bytes(d[6], 'big')
|
|
|
|
|
vb = v - (chain_id * 2) - 35
|
|
|
|
|
s = b''.join([d[7], d[8], bytes([vb])])
|
|
|
|
|
so = KeyAPI.Signature(signature_bytes=s)
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'from': a,
|
|
|
|
|
'nonce': int.from_bytes(d[0], 'big'),
|
|
|
|
|
'gasPrice': int.from_bytes(d[1], 'big'),
|
|
|
|
|
'gas': int.from_bytes(d[2], 'big'),
|
|
|
|
|
'to': to,
|
|
|
|
|
'value': int.from_bytes(d[4], 'big'),
|
|
|
|
|
'data': '0x' + d[5].hex(),
|
|
|
|
|
'v': chain_id,
|
|
|
|
|
'r': '0x' + s[:32].hex(),
|
|
|
|
|
's': '0x' + s[32:64].hex(),
|
|
|
|
|
'chainId': chain_id,
|
|
|
|
|
'hash': '0x' + signed_hash.hex(),
|
|
|
|
|
'hash_unsigned': '0x' + unsigned_hash.hex(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-02-17 10:13:22 +01:00
|
|
|
|
def receipt(hsh):
|
|
|
|
|
o = jsonrpc_template()
|
|
|
|
|
o['method'] = 'eth_getTransactionReceipt'
|
|
|
|
|
o['params'].append(hsh)
|
|
|
|
|
return o
|
|
|
|
|
|
|
|
|
|
|
2021-02-09 09:42:46 +01:00
|
|
|
|
class TxFactory:
|
|
|
|
|
|
2021-02-09 12:12:37 +01:00
|
|
|
|
def __init__(self, signer=None, gas_oracle=None, nonce_oracle=None, chain_id=1):
|
2021-02-09 09:42:46 +01:00
|
|
|
|
self.gas_oracle = gas_oracle
|
|
|
|
|
self.nonce_oracle = nonce_oracle
|
|
|
|
|
self.chain_id = chain_id
|
|
|
|
|
self.signer = signer
|
|
|
|
|
|
|
|
|
|
|
2021-02-17 10:13:22 +01:00
|
|
|
|
def build_raw(self, tx):
|
2021-02-12 00:24:10 +01:00
|
|
|
|
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
|
|
|
|
self.signer.signTransaction(txe)
|
|
|
|
|
tx_raw = txe.rlp_serialize()
|
|
|
|
|
tx_raw_hex = add_0x(tx_raw.hex())
|
|
|
|
|
tx_hash_hex = add_0x(keccak256_hex_to_hex(tx_raw_hex))
|
2021-02-17 10:13:22 +01:00
|
|
|
|
return (tx_hash_hex, tx_raw_hex)
|
|
|
|
|
|
|
|
|
|
def build(self, tx):
|
|
|
|
|
(tx_hash_hex, tx_raw_hex) = self.build_raw(tx)
|
2021-02-12 00:24:10 +01:00
|
|
|
|
|
|
|
|
|
o = jsonrpc_template()
|
|
|
|
|
o['method'] = 'eth_sendRawTransaction'
|
|
|
|
|
o['params'].append(tx_raw_hex)
|
|
|
|
|
|
|
|
|
|
return (tx_hash_hex, o)
|
|
|
|
|
|
|
|
|
|
|
2021-02-15 19:23:26 +01:00
|
|
|
|
def template(self, sender, recipient, use_nonce=False):
|
2021-02-09 12:12:37 +01:00
|
|
|
|
gas_price = MINIMUM_FEE_PRICE
|
2021-02-17 21:20:21 +01:00
|
|
|
|
gas_limit = MINIMUM_FEE_UNITS
|
2021-02-09 12:12:37 +01:00
|
|
|
|
if self.gas_oracle != None:
|
2021-02-17 21:20:21 +01:00
|
|
|
|
(gas_price, gas_limit) = self.gas_oracle.get()
|
|
|
|
|
logg.debug('using gas price {} limit {}'.format(gas_price, gas_limit))
|
2021-02-09 12:12:37 +01:00
|
|
|
|
nonce = 0
|
2021-02-15 19:23:26 +01:00
|
|
|
|
o = {
|
2021-02-09 09:42:46 +01:00
|
|
|
|
'from': sender,
|
|
|
|
|
'to': recipient,
|
|
|
|
|
'value': 0,
|
|
|
|
|
'data': '0x',
|
|
|
|
|
'gasPrice': gas_price,
|
2021-02-17 21:20:21 +01:00
|
|
|
|
'gas': gas_limit,
|
2021-02-09 09:42:46 +01:00
|
|
|
|
'chainId': self.chain_id,
|
|
|
|
|
}
|
2021-02-15 19:23:26 +01:00
|
|
|
|
if self.nonce_oracle != None and use_nonce:
|
|
|
|
|
nonce = self.nonce_oracle.next()
|
|
|
|
|
logg.debug('using nonce {} for address {}'.format(nonce, sender))
|
2021-02-15 19:27:06 +01:00
|
|
|
|
o['nonce'] = nonce
|
2021-02-15 19:23:26 +01:00
|
|
|
|
return o
|
2021-02-09 12:12:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def normalize(self, tx):
|
|
|
|
|
txe = EIP155Transaction(tx, tx['nonce'], tx['chainId'])
|
|
|
|
|
txes = txe.serialize()
|
|
|
|
|
return {
|
|
|
|
|
'from': tx['from'],
|
|
|
|
|
'to': txes['to'],
|
|
|
|
|
'gasPrice': txes['gasPrice'],
|
|
|
|
|
'gas': txes['gas'],
|
|
|
|
|
'data': txes['data'],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_code(self, tx, data, update_fee=True):
|
|
|
|
|
tx['data'] = data
|
|
|
|
|
if update_fee:
|
|
|
|
|
logg.debug('using hardcoded gas limit of 8000000 until we have reliable vm executor')
|
|
|
|
|
tx['gas'] = 8000000
|
|
|
|
|
return tx
|
2021-02-11 10:16:05 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Tx:
|
|
|
|
|
|
2021-02-12 00:24:10 +01:00
|
|
|
|
def __init__(self, src, block=None):
|
|
|
|
|
self.index = -1
|
|
|
|
|
self.status = -1
|
|
|
|
|
if block != None:
|
|
|
|
|
self.index = int(strip_0x(src['transactionIndex']), 16)
|
2021-02-11 12:43:54 +01:00
|
|
|
|
self.value = int(strip_0x(src['value']), 16)
|
|
|
|
|
self.nonce = int(strip_0x(src['nonce']), 16)
|
|
|
|
|
self.hash = strip_0x(src['hash'])
|
2021-02-12 00:24:10 +01:00
|
|
|
|
address_from = strip_0x(src['from'])
|
|
|
|
|
self.gasPrice = int(strip_0x(src['gasPrice']), 16)
|
|
|
|
|
self.gasLimit = int(strip_0x(src['gas']), 16)
|
|
|
|
|
self.outputs = [to_checksum(address_from)]
|
2021-02-11 12:43:54 +01:00
|
|
|
|
|
|
|
|
|
inpt = src['input']
|
|
|
|
|
if inpt != '0x':
|
|
|
|
|
inpt = strip_0x(inpt)
|
|
|
|
|
else:
|
|
|
|
|
inpt = None
|
|
|
|
|
self.payload = inpt
|
|
|
|
|
|
|
|
|
|
to = src['to']
|
|
|
|
|
if to == None:
|
|
|
|
|
to = ZERO_ADDRESS
|
2021-02-12 00:24:10 +01:00
|
|
|
|
self.inputs = [to_checksum(strip_0x(to))]
|
2021-02-11 12:43:54 +01:00
|
|
|
|
|
2021-02-11 10:16:05 +01:00
|
|
|
|
self.block = block
|
2021-02-11 12:43:54 +01:00
|
|
|
|
self.wire = src['raw']
|
|
|
|
|
self.src = src
|
2021-02-11 10:16:05 +01:00
|
|
|
|
|
|
|
|
|
|
2021-02-11 12:43:54 +01:00
|
|
|
|
def __repr__(self):
|
2021-02-11 10:16:05 +01:00
|
|
|
|
return 'block {} tx {} {}'.format(self.block.number, self.index, self.hash)
|
2021-02-11 12:43:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
2021-02-12 00:24:10 +01:00
|
|
|
|
return """from {}
|
|
|
|
|
to {}
|
|
|
|
|
value {}
|
|
|
|
|
nonce {}
|
|
|
|
|
gasPrice {}
|
|
|
|
|
gasLimit {}
|
|
|
|
|
input {}""".format(
|
|
|
|
|
self.outputs[0],
|
|
|
|
|
self.inputs[0],
|
|
|
|
|
self.value,
|
|
|
|
|
self.nonce,
|
|
|
|
|
self.gasPrice,
|
|
|
|
|
self.gasLimit,
|
|
|
|
|
self.payload,
|
|
|
|
|
)
|