Transactions
如何建立一個簡單的交易?
- tx 包建構協議的資訊在哪裡?以比特幣為例,Bitcoin Wiki - 協議文件:tx
- 有沒有像如何兌換基本 Tx 的答案這樣的 python 序列化範例?
在 Google 中找到的所有內容都在使用不同的庫。
2)從乙太坊黃皮書可知,一筆交易的邏輯結構如下。
----------------------------- | Nonce | Up to 32 bytes | ----------------------------- | GasPrice | Up to 32 bytes | ----------------------------- | GasLimit | Up to 32 bytes | ----------------------------- | To | 20 bytes addr | ----------------------------- | Value | Up to 32 bytes | ----------------------------- | Data | 0 - unlimited | ----------------------------- | V | 1 (usually) | ----------------------------- | R | 32 bytes | ----------------------------- | S | 32 bytes | -----------------------------
筆記:
- 這只是邏輯結構。實際數據以 RLP 格式編碼,因此由於添加了長度前綴而更長。
- V 欄位在 EIP-155 之前始終為 1 個字節。可以肯定地說,每個主要客戶都實施了 EIP-155。對於主網、測試網,即使使用 EIP-155,該欄位也保持為 1 個字節。對於具有較大值的“鏈 ID”的專用網路,該欄位可以更長。看到這個問題。
以這個雜湊的交易為例:
0x14a298c1eea89f42285948b7d51eeac2876ca7406c9784b9b90dd3591d156d64
出去:
“0xf86b80850ba43b7400825208947917bc33eea648809c285607579c9919fb864f8f8703baf82d03a0008025a0067940651530790861714b2e8fd8b080361d1ada048189000c07a6684896fdebdec45acc679c04ac4ac697afb3
這是109個字節。如果我們解析數據
f86b length 80 nonce (0: this is the minimum an account can have) 85 0ba43b7400 gas price 82 5208 gas limit (this is fixed for simple payments) 94 7917bc33eea648809c285607579c9919fb864f8f (address, always 20 bytes) 87 03baf82d03a000 (value, in theory this can be shrunken to zero) 80 (data, already zero length) 25 (V, one byte) a0 067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46 (R) a0 69b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69 (S)
現在我將嘗試使用 RLP 庫來選擇結構。
import rlp tx_message = list() # tx_len - f8 tx_nonce = '' tx_gasPrice = 0x0ba43b7400 tx_gasLimit = 0x5208 tx_to = 0xcce5fd90eabab3d5d35119eed7f2ac5796e3d06c tx_value = 0x03baf82d03a000 tx_data = 0x00 tx_w = 0x25 tx_r = 0x067940651530790861714b2e8fd8b080361d1ada048189000c07a66848afde46 tx_s = 0x69b041db7c29dbcc6becf42017ca7ac086b12bd53ec8ee494596f790fb6a0a69 tx_message.extend( ( rlp.encode(tx_nonce), rlp.encode(tx_gasPrice), rlp.encode(tx_gasLimit), rlp.encode(tx_to), rlp.encode(tx_value), rlp.encode(tx_data), rlp.encode(tx_w), rlp.encode(tx_r), rlp.encode(tx_s), ) ) result_b = b''.join(tx_message) result = result_b.hex()
我想在上面的程式碼中顯示可以選擇結構,但這還不是真的,因為我們沒有考慮交易的大小,也沒有簽名。現在我們將重寫工作程式碼。
class Transaction(rlp.Serializable): fields = [ ('nonce', big_endian_int), ('gasprice', big_endian_int), ('startgas', big_endian_int), ('to', Binary.fixed_length(20, allow_empty=True)), ('value', big_endian_int), ('data', binary), ('v', big_endian_int), ('r', big_endian_int), ('s', big_endian_int), ] _sender = None def __init__(self, nonce, gasprice, startgas, to, value, data, v=0, r=0, s=0): # self.data = None to = normalize_address(to, allow_blank=True) super(Transaction, self).__init__(nonce, gasprice, startgas, to, value, data, v, r, s) if gasprice >= TT256 or startgas >= TT256 or value >= TT256 or nonce >= TT256: logging.error("Values way too high!") def sign(self, key, network_id=None): """ Sign this transaction with a private key. A potentially already existing signature would be overridden. """ if network_id is None: rawhash = sha3( rlp.encode( unsigned_tx_from_tx(self), UnsignedTransaction ) ) else: assert 1 <= network_id < 2 ** 63 - 18 rawhash = sha3( rlp.encode( rlp.infer_sedes(self).serialize(self)[:-3] + [network_id, b'', b''] ) ) key = normalize_key(key) v, r, s = ecsign(rawhash, key) if network_id is not None: self.v += 8 + network_id * 2 ret = self.copy(v=v, r=r, s=s) ret._sender = privtoaddr(key) return ret class UnsignedTransaction(rlp.Serializable): fields = [] for field, sedes in Transaction._meta.fields: if field not in "vrs": fields.append((field, sedes)) def unsigned_tx_from_tx(tx): return UnsignedTransaction( nonce=tx.nonce, gasprice=tx.gasprice, startgas=tx.startgas, to=tx.to, value=tx.value, data=tx.data, ) def fetch_url_json_path_int(url, path): def func(): request = req.Request(url, headers={'User-Agent': 'wallet'}) try: payload = req.urlopen(request).read() except Exception as e: logging.error(f'[fetch_url_json_path_int] {e}') try: data = loads(payload) for component in path.split('/'): if isinstance(data, dict): data = data[component] elif isinstance(data, (list, tuple)): data = data[int(component)] return data except Exception as e: return data return func def get_tx_count(address): return fetch_url_json_path_int(f'{url_tx_count}{address}', 'result')() url_tx_count = 'https://api.etherscan.io/api?module=proxy&action=eth_getTransactionCount&address=' private_key = hashlib.sha256('This keyword!!!'.encode()).hexdigest() nonce = int(get_tx_count('0xcce5fd90eabab3d5d35119eed7f2ac5796e3d06c'), 16) gasPrice = 28500000000 gasLimit = 21000 to = 0x77f5055E19247E091e0C5bb3483190F9E6E43d3f value = 0 data = codecs.decode('', 'hex') transaction = Transaction( nonce=nonce, gasprice=gasPrice, startgas=gasLimit, to=to, value=value, data=data, ).sign(private_key) print(rlp.encode(transaction))
功能發送至:
url_broadcast_transaction = 'https://api.blockcypher.com/v1/eth/main/txs/push' def broadcast_transaction(message): timeout_in_second = 10 data = {'tx': message.hex()} params = {'token': None} r = requests.post( url=url_broadcast_transaction, json=data, params=params, verify=True, timeout=timeout_in_second ) logging.debug(r.text) return r.text
在這裡,您可以看到程式碼中缺少的模組。
最重要的!對於交易, gasLimit將等於 21000,但對於合約則不是。gasPrice需要到這裡這個連結。收到gasPrice後,需要從GWei轉換為Wei