我們在第2講“區塊鏈到底是怎么運行”一文中,提到了村長給張三轉賬的例子,那里村長的例子就是UTXO模型的一個簡化版本。
評論區里有不少留言在問:“為什么不直接記余額呢?”看來很多人都對這個問題很感興趣,今天我們就來聊一聊這個話題。
區塊鏈網絡中有兩種記賬模式,除了UTXO模型還有 Account Based 結構,也就是普通賬戶模型,也叫賬戶余額模型,前者在比特幣系的數字貨幣中被廣泛使用,后者更多是用在智能合約型的區塊鏈上。
普通賬戶模型
我們先從傳統的賬戶模型出發來聊聊是如何記賬的,假設我們現在有一個支付系統,在這個支付系統中有村長和張三兩個賬戶,村長賬戶里有100萬,現在要轉賬給張三10萬,這其中涉及的操作是這樣的:
- 檢查村長的賬戶余額是否大於10萬;
- 把村長的賬戶扣除10萬變成90萬,然后發送一筆轉賬消息給張三的賬戶;
- 張三的賬戶接受到轉賬消息,將張三的賬戶余額加10萬。
我們可以發現,無論是村長還是張三,都具有一個余額作為狀態,即當前余額是記錄在某個地方的,只需要讀出來即可,這種設計我們叫做賬戶余額模型。
如果以上三個步驟是在一個中心化系統中,甚至在同一個數據庫中,那將非常簡單,會直接退化成一個事務,我們見到的銀行賬戶、信用卡系統、證券交易系統、各種電商類應用,理財類應用基本都是一個中心化系統中的,最多也就是跨表跨數據庫。
想必這類場景下的設計,各位工程師對此應該是了如指掌的。
如果以上的步驟中,村長和張三的賬戶分屬兩個不同的系統,例如從A銀行到B銀行,就需要經過人民銀行支付系統,即可信任的中心化第三方來做中介。
你可能發現了,在跨行轉賬的這種情況下,是沒有辦法做事務的,所以1和3是不同步的,如果3操作失敗,還需要從2倒退到1的狀態,這個情況叫做沖正交易。
普通賬戶模型具有自定義數據類型的優點,但是卻需要自己設計事務機制,就是上述所說的沖正交易。而接下來所講的UTXO模型則恰恰相反。
UTXO模型
UTXO全稱是:“Unspent Transaction Output”,這指的是:未花費的交易輸出。這里面三個單詞分別表示 “未花費的”“交易”“輸出”,接下來我來詳細講解一下UTXO的含義。
UTXO的核心設計思路是無狀態,它記錄的是交易事件,而不記錄最終狀態,也就是說只記錄變更事件,用戶需要根據歷史記錄自行計算余額。
有點像MySQL中的Binlog,主從模式的情況下,按照Binlog來更新數據,Redis的AOF模式備份模式也是如此,UTXO也是類似的思路。
下面我們按照按照普通賬戶中的例子來重新講解一遍。
如果要記錄交易本身,那么我們可以構造一筆交易,這筆交易中村長轉賬10萬給張三的同時,90萬轉給自己。
如下所示:
村長 100萬 --> 張三 10萬
--> 村長 90萬
這里其實有三條子記錄,左邊一條,右邊兩條,左邊叫做輸入,右邊叫做輸出。
輸入和輸出組成了交易,輸入和輸入需要滿足一些約束條件:
- 任意一個交易必須至少一個輸入、一個輸出;
- 輸入必須全部移動,不能只使用部分,所以才產生了第二個輸出指向村長自己;
- 輸入金額 = 輸出金額之和 + 交易手續費,這里必須是等式。
對於村長來說,首先構造交易的輸入輸出,滿足上述條件,然后廣播到全網,接收方自行判斷交易是否屬於自己。這里滿足約束條件構成的交易模型,也就是村長記錄的三條轉賬事件就是UTXO模型。
賬戶余額模型與UTXO的比較
我們可以歸納出UTXO與普通賬戶模型的一些區別。
- 存儲空間,UTXO占用空間比賬戶模型高,因為賬戶模型只記錄最終狀態。
- 易用性,UTXO比較難處理,賬戶模型簡單容易理解。例如UTXO在使用上,還需要配合高效的UTXO組裝算法,這個算法要求盡可能降低輸入輸出的個數,還要讓“零錢“歸整,算法的復雜度相比賬戶余額無疑要高。
- 安全性,UTXO比賬戶模型要高,UTXO本身具備ACID的記賬機制,而賬戶模型需要自行處理,例如重放攻擊。
普通賬戶模型具有較高的自由度,可以讓智能合約有更好的發揮空間,並且它避免了UTXO的復雜組裝邏輯,精度控制上也更為得心應手。
UTXO似乎天然是為數字貨幣設計的,具有較高頻次跨賬戶轉移場景都使用UTXO會比較好,考慮到智能合約的普適性,UTXO與智能合約並不能很好地兼容,但是這也對開發者的自身水平提出了更高的要求。
區塊鏈中的UTXO模型
我們借用比特幣開發者文檔中UTXO模型的圖示,來看看UTXO實際的構造形式。
上圖中,所有的交易都可以找到前向交易,例如TX5的前向交易是TX2,TX2中的Output1作為TX5中的Input0。
意思就是TX2中的付款人使用了Output1中指向的比特幣轉移給 TX5 中的收款人,接着TX5中的人又把收到的比特幣轉移給了TX6中的收款人,成為了TX6中 Output0。
我們也可以發現,TX6中的收款人還沒有產生TX7交易,也就是說Output0還沒有被花費,
這時候我們終於得到了UTXO的真正語義:Unspent Transaction Output,未花費的交易輸出。
我們這時候可以發現UTXO也同樣能表示余額,不過是重演計算的方式,它用不同的方式表達了余額,我們把一個地址上所有的UTXO全部找出來,就是這個地址總的余額了。
我們還可以發現,無論是TX5還是TX2,都已經成為歷史交易,它們都忠實客觀地記錄了兩筆交易,這兩筆交易代表的是事件,而不是余額狀態轉移,這是我們看到的最直觀的區別。
我們再來看看一個真實的交易例子。
這是區塊鏈上一筆真實交易的例子,它記錄了一筆450ETP的轉賬記錄。
左邊是輸入,右邊是兩筆輸出,其中第二個輸出是給自己的賬戶,這和我們村長轉賬給張三的例子是一樣的。
下圖是交易解碼為JSON格式的樣子,可以看到Previous_output是放到Inputs數組里的,意思就是前向輸出作為本次的輸入。
{ "hash" : "89e80e14db07c4904a57e2c1efb689bccbbf43942103c1a92166d5c0f27ea3d2", "height" : 1093399, "inputs" : [ { "address" : "MLWtmjwCtmK44FMwJMSfAkHaEvnnb2N6HX", "previous_output" : { "hash" : "770a72f35d3e3a78bd468949bad649f03b241cf7e2a84cc2d6fdabacdcc47f06", "index" : 0 }, "script" : "[ 304402202b21d7a79276985dc99777b70fd5095796dad58f35e29a019d2cb6cca5df481802205ffab088a6047f5b6382ba02a0eed4e78ab7950fe264d3774e8b0b357a7593d101 ] [ 03ea3462dc01e7b5569e89737211887035f8f1e99e1fe4332181d83daccaa6d917 ]", "sequence" : 4294967295 } ], "lock_time" : "0", "outputs" : [ { "address" : "MGz9yjLLn4AqyraRjSpiP2GmTWKnT3yfiL", "attachment" : { "type" : "etp" }, "index" : 0, "locked_height_range" : 0, "script" : "dup hash160 [ 63ab0013d183f2592e4b46a358df01e88a09c0b8 ] equalverify checksig", "value" : 45000000000 }, { "address" : "MLWtmjwCtmK44FMwJMSfAkHaEvnnb2N6HX", "attachment" : { "type" : "etp" }, "index" : 1, "locked_height_range" : 0, "script" : "dup hash160 [ 8a63941b392771c40f1c15e4374808f6bb464cba ] equalverify checksig", "value" : 118082150283 } ], "version" : "2" }
我們再看看比特幣上的例子:
這一筆比特幣交易包含6個輸入,幾十個輸出,交易一共3.5kb,交易的輸入輸出會影響交易大小,比特幣的交易費是根據字節收費的,交易尺寸越大越貴,而交易尺寸主要和輸入輸出的個數有關,也就是說,算法上並不規定輸入輸出的個數,而只有區塊尺寸限制。
在比特幣中將小於100kb的交易稱為標准交易,超過100kb的稱為非標准交易。它的前向input以及生成一個out約占用161~250 bytes 。所以在比特幣中,大約的inputs/ouputs的最大數目限制為 100KB/161B ~= 600個。
UTXO的特性及缺點
從計算的角度來說,UTXO具有非常好的並行支付能力,也就是我們上文中所說的如果沒有尺寸限制,一筆交易可以包含任意筆輸入輸出,同時也沒有次序要求,在一筆交易中哪一個UTXO在前,哪個在后面不影響最終結果。
從存儲的角度來說,UTXO具有較好的可裁剪特性,可裁剪性指的是UTXO類型的交易,如果從最老的那一筆UTXO開始截斷數據庫,那么之前的數據可以刪除掉了。
如果想進一步壓縮數據尺寸,可以在任意位置截斷,記錄UTXO對應的交易哈希即可,然后從其他節點獲取並校驗UTXO,這也是SPV輕錢包工作的基礎之一。
以太坊中並沒有使用比特幣的這種UTXO設計,這與以太坊的宗旨有關,以太坊的目標是構建通用計算,而比特幣是數字貨幣,需求不同導致設計的不同。
V神指出了UTXO的缺陷,一共有三類。
1.可表達的狀態少 。
UTXO只能是已花費或者未花費狀態,這就沒有給需要任何其它內部狀態的多階段合約或者腳本留出生存空間,這也意味着UTXO只能用於建立簡單的、一次性的合約,UTXO更像是一種二進制控制位。
2.區塊鏈盲點(Blockchain-blindness)。
UTXO的腳本只能看到自己這條歷史軌跡,無法看到區塊鏈的數據的全貌,這導致了功能性擴展受到了限制,我們在花費比特幣的過程中需要小心翼翼的組合UTXO,這也導致了系統狀態邏輯復雜,不適合設計成智能合約的基礎結構。
3.價值盲點(Value-blindness)。
UTXO腳本不能提供非常精細的金額控制,基於賬戶模型的余額在花費過程中,可以任意的按值存取,它僅取決於程序能表示的最小精度。
而UTXO要求必須全部移動,如果要滿足一個目標值金額,對組合UTXO算法的要求會比較高,采用許多有不同面值的UTXO,一方面要求盡可能地精確,另一方面又要求輸入輸出的數量盡可能的小。
UTXO是比特幣上的原生設計,在區塊鏈以前是沒有這種邏輯數據結構,UTXO的出現給了人們看待數據轉移的不同視角,但UTXO不是所有區塊鏈所必需的,公鏈開發過程中的是否選用UTXO模型可以根據業務場景進行判斷。
總結
好了,今天我們分別介紹了普通賬戶模型和UTXO模型,並從不同角度比較了二者的優劣。
從技術選擇上來看,比特幣選擇UTXO是為了滿足支付的安全性,以太坊選擇普通賬戶模型是為了智能合約的自由度。
最后留給你一個問題,歷史上UTXO或賬戶模型是否引發過比較嚴重的使用缺陷呢?你可以給我留言,我們一起討論,感謝你的收聽,我們下期再見。