JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

一筆交易裡面到底裝了什麼

打開一筆交易,把每個欄位攤在桌上——收款方、金額、為它排序的 nonce、為它買到位置的手續費,以及證明「是你授權的」那枚簽章——並看清為什麼發送方根本不需要被寫進去。

一道已簽名的指令,而不是一個請求

當你在銀行 App 裡按下「轉帳」,你其實是在「請求」銀行幫你搬錢。一名行員——或一台銀行完全掌控的伺服器——會核對你是誰、檢查你的餘額,然後更新一份它自己擁有的私有資料庫。但區塊鏈沒有行員,也沒有可以打電話過去的人。因此一筆交易不能是對某個受信任中間人提出的禮貌請求;它必須是一道自給自足、已經簽名的「指令」,本身就帶著一個素昧平生的陌生人核對它所需的一切——誰付款、付給誰、付多少、以什麼順序——外加一個證明「合法擁有者確實授權了這筆交易」的數學憑據。

在上一篇裡,你從外部看了區塊:一個裝著交易、並鏈向過去的密封盒子。這一篇,我們打開盒子裡的其中一筆交易,把每個欄位都攤在桌上。讀完之後,你會像節點一樣讀懂一筆原始交易,並清楚知道每個欄位為什麼非存在不可。

拆開信封:一次看清所有欄位

下面是一筆簡單的交易——0.05 ETH 從一個帳戶轉到另一個帳戶——用錢包或節點交給你時的樣子呈現。數值都是十六進位,這是鏈的原生格式;後面的註解把它們翻回一般人看得懂的數字。

{
  "type": "0x2",                          // EIP-1559 (type-2) transaction
  "chainId": "0x1",                       // 1 = Ethereum mainnet
  "nonce": "0x2a",                        // 42 -> this account's 43rd transaction
  "to": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
  "value": "0xb1a2bc2ec50000",           // 0.05 ETH = 50,000,000,000,000,000 wei
  "maxPriorityFeePerGas": "0x77359400",   // 2 gwei  -> tip to the block proposer
  "maxFeePerGas": "0x6fc23ac00",          // 30 gwei -> the most you'll pay per gas
  "gas": "0x5208",                        // gas limit 21000 (a plain transfer)
  "input": "0x",                          // empty calldata: this moves value, it doesn't call code
  "yParity": "0x1",                       // \
  "r": "0x4f6c4a...e2b7",                 //  > the ECDSA signature (secp256k1), abbreviated
  "s": "0x18a90c...b3d1"                  // /
}
一筆 type-2(EIP-1559)以太坊轉帳,逐欄位拆解。

注意有什麼——以及「少了」什麼。這裡沒有 `from` 欄位。發送方從來不會被寫進去;它是從簽章裡回推出來的,這個巧妙之處我們留到最後再講。其餘所有欄位只負責四件差事:誰付給誰、付多少(`to`、`value`、`input`)、排序(`nonce`)、付錢給網路(三個 gas 欄位),以及授權(`yParity`、`r`、`s`)。我們一件差事、一件差事地看。

誰、多少——以及那個空的 data 欄位

`to` 是收款方的 20 位元組位址——是「對方」公鑰的指紋,這在密碼學那一階講過。`value` 是金額,以 wei 為單位,也就是最小、不可再分的單位(1 ETH = 10^18 wei)。把錢寫成整數 wei 是刻意的:整數沒有小數要四捨五入,也沒有浮點誤差,所以 0.05 ETH「恰好」就是 50,000,000,000,000,000 wei——也就是整數 `0xb1a2bc2ec50000`,不多也不少。

`input`(常稱為 calldata)是 `0x`——空的——因為這只是一筆單純的付款。如果我們呼叫的是智慧合約,這個欄位就會裝著函式選擇器與它的參數,於是同樣形狀的一筆交易就會去「做」某件事,而不只是「搬動」某樣東西。事實上,同一種格式涵蓋三種用途,僅靠 `to` 與 `input` 來區分:有 `to`、data 為空,是轉帳;有 `to`、又有 data,是函式呼叫;`to` 為「空」、又帶 data,則是部署一份全新的合約。

nonce:在混亂中建立秩序(並杜絕重放)

nonce(這裡是 `0x2a` = 42)是「每個帳戶各自一份」的計數器:從 0 開始,帳戶每送出一筆交易就「恰好加一」。它看起來只是個不起眼的記帳數字,卻悄悄地一次解決了兩個棘手的問題。

  1. 排序。 成千上萬個節點在網路上「聽到」你的交易時,先後順序各不相同。nonce 消除了這種模糊:某帳戶 nonce 為 42 的交易,只能在 41 之後、43 之前被處理。先廣播 nonce 43,它就只能乾等——鏈拒絕跳號執行。
  2. 杜絕重放。 一旦 nonce 42 被收錄,這個號碼就永遠用掉了。有人抄走你那串一模一樣的已簽名位元組、再廣播一次,也撈不到任何好處:鏈已經為你的帳戶用掉了 nonce 42,會直接駁回這個重複品。要是沒有 nonce,任何人都能把你過去的某筆付款一次又一次地重放。

這也說明了交易為什麼會「卡住」。如果你廣播了 nonce 43,但 nonce 42 始終沒上鏈——比方說你把它的手續費設得太低——那麼 43 就會卡在這個缺口後面動彈不得:有效,卻無法被處理。錢包的解法是「加速」(用更高的手續費重送 nonce 42)或「取消」(在那個 nonce 上送一筆 0 ETH 的自我轉帳,把位置填掉)。要安全地做到這件事本身就是一門功夫:nonce 管理

手續費:為自己在下一個區塊買下一個位置

區塊空間是稀缺的;要全世界每個節點都重新執行你的交易、更新共享狀態,得花掉實實在在的算力。所以一筆交易必須「付費」才能被收錄。以太坊用 gas 來計量這份工作,並透過三個欄位為它定價。自 EIP-1559 起,像我們這樣的單純轉帳會這樣使用它們:

  1. `gas`(gas 上限,21000)。 你授權的工作量上限。一筆單純的 ETH 轉帳永遠剛好花 21000 gas;合約呼叫則需要更多,而且每一步都由 gas 計量逐步計費,讓運算不會失控地把你的錢吃光。
  2. `maxPriorityFeePerGas`(小費,2 gwei)。 每單位 gas 額外付給區塊提議者的獎勵,讓你的交易值得被挑中——也就是你的優先費
  3. `maxFeePerGas`(上限,30 gwei)。 你願意為每單位 gas 支付的最高金額。網路本身的基礎費會隨需求上下浮動;你實付的是「基礎費 + 小費」,以這個上限為頂,沒用到的部分會退回。
gas used  (a plain ETH transfer)      = 21,000
base fee  (set by the network)        = 18 gwei   -> burned
priority fee / tip (you choose)       =  2 gwei   -> to the block proposer
effective gas price = base + tip      = 20 gwei   (<= maxFeePerGas of 30, so OK)

total fee = 21,000 x 20 gwei = 420,000 gwei = 0.00042 ETH
   of which  burned = 21,000 x 18 = 378,000 gwei = 0.000378 ETH
   and the   tip    = 21,000 x  2 =  42,000 gwei = 0.000042 ETH
當基礎費為 18 gwei 時,把這筆手續費算出來。

所以這筆轉帳大約花 0.00042 ETH。關鍵在於:基礎費那一部分會被銷毀(直接消滅,使 ETH 的總量減少),只有那一小撮小費進到提議者口袋。由於一個區塊能容納的 gas 有上限(它的gas 上限),手續費其實是一場拍賣:當區塊塞滿時,提議者會依小費高低替排隊的交易排序,出價低的就慢慢等。比特幣是按交易的位元組大小、而非 gas 來收費,但道理完全相同——你都是在競標稀缺的空間。

簽章:永不洩漏祕密的證明

最後幾個欄位——`yParity`、`r`、`s`——就是數位簽章,也是整件事的核心。回想密碼學那一階:在 secp256k1 曲線上的 ECDSA 簽章,能讓持有私鑰的人為一則訊息簽名,使得任何握有對應公鑰的人都能驗證它——而私鑰本身從頭到尾不會出現在任何地方。

但錢包並「不會」直接對人類看得懂的交易簽名。它簽的是其餘所有欄位的一個 Keccak-256 雜湊。這單一個雜湊一次承諾了每一個數值:只要把收款方、金額、nonce 或手續費改動一個位元,雜湊就會變、簽章就驗不過,於是每個誠實的節點都會把這筆交易丟掉。簽章正是讓一筆交易在通往區塊的漫漫長路上保持可察覺竄改的關鍵——沒有人能在途中改動它而不讓它失效。

# 1. Serialize every field EXCEPT the signature (RLP), prefixed by the type byte
payload = 0x02 || rlp([ chainId, nonce, maxPriorityFeePerGas, maxFeePerGas,
                        gas, to, value, input, accessList ])

# 2. Hash that payload down to 32 bytes
sigHash = keccak256(payload)

# 3. Sign the hash with the private key (ECDSA over secp256k1)
(r, s, yParity) = ecdsa_sign(sigHash, privateKey)

# 4. The transaction's id is the hash of the FULL signed transaction
txHash = keccak256(0x02 || rlp([ ...fields, yParity, r, s ]))

# The sender is NEVER written down -- anyone can recover it from the signature:
publicKey = ecrecover(sigHash, yParity, r, s)
from      = address(keccak256(publicKey)[12:])   # last 20 bytes of the key's hash
對欄位的雜湊簽名,再從簽章回推出發送方。

接下來這段優雅之處,終於解釋了那個缺席的 `from`。給定 `(r, s, yParity)` 與被簽名的雜湊,任何人都能執行 `ecrecover` 來回推出產生這枚簽章的公鑰——而位址不過就是那把公鑰的雜湊。所以發送方並不是「被宣稱、然後被信任」的;它是「被推導、並被證明」出來的。沒有可以偽造的身分欄位,也沒有用來查人的登記簿。光憑數學就回答了唯一要緊的問題:是誰授權了這筆交易?正是這唯一一道保證,讓一筆交易能穿過一整片陌生人的網路,抵達時依然值得信任。

按下送出之後:進入 mempool

一筆完整、已簽名的交易此刻還「不在」鏈上——它只是一則躺在你裝置裡的有效訊息。你的錢包(如果你自己保管私鑰,那就是非託管錢包)會把它廣播給少數幾個對等節點,這些節點再轉發給它們的鄰居,鄰居再轉給鄰居——這場散播洪流叫做交易傳播——直到它抵達 mempool:每個節點都在記憶體裡保有的「待處理」交易候車室。

交易就這樣「待處理」地待著,直到某個區塊生產者伸手把它挑走——如我們所見,會偏好小費較高的——並把它寫進一個區塊。到這一刻它才算被「收錄」;而要讓一筆已收錄的交易真正無法回頭(也就是「最終性」),那是留待後面階段的故事。完整的旅程——廣播、mempool 的經濟學,以及在一筆交易後方層層堆疊的確認數——正是本階最後一篇的主題。