Transactions

如何建立一個簡單的交易?

  • June 27, 2019
  1. tx 包建構協議的資訊在哪裡?以比特幣為例,Bitcoin Wiki - 協議文件:tx
  2. 有沒有像如何兌換基本 Tx 的答案這樣的 python 序列化範例?

在 Google 中找到的所有內容都在使用不同的庫。

1)伊斯梅爾在評論中 回复說資訊在黃皮書裡有

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       |
-----------------------------

筆記:

  1. 這只是邏輯結構。實際數據以 RLP 格式編碼,因此由於添加了長度前綴而更長。
  2. 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

引用自:https://ethereum.stackexchange.com/questions/70584