生活類比
首先看這個名詞UTXO (Unspent Transaction Output, “未花費事務輸出”),老實說,第一次看到這個術語的時候,一時之間真是有些懵,如果說是未花費的余額還能理解,我錢包里有1000,花了200,還有800未花費,這是很符合通常的理解邏輯的,可這個未花費的“事務輸出”是個什么意思,實際上,這與比特幣中的交易事務結構是很有關系的。為了讓大家更容易理解,我們暫且先不來解析這個交易數據結構,讓我們進入到一個倉庫,我們知道倉庫的主要業務就是進和出,倉庫也會把日常的進出流水賬記錄下來, 為了查詢統計方便, 除了流水賬通常還會匯總一 份庫存表出來,舉例如下:
以上圖為例,這是從2017-8- 1倒2017-8- 5之間,倉庫記錄的出入流水賬, 為了統計方便,倉庫還匯總保存了一份每天的庫存日報表,如下:
每天倉庫在需要出庫的時候,只要查看一下庫存日報表就知道數量是否足夠了,比如2017-8-3需要出庫15支毛筆,此時查看庫存表發現毛筆的庫存量有30支,足夠發出,于是就將庫存表中的毛筆數量減掉15,并且將出庫明細記錄在流水賬中。然而,這里有一個問題,庫存日報表是另外編制保存的,那就有可能發生數據不一致的情況,比如2017-8-2時毛筆的庫存本來是30卻誤寫為20,這樣導致后續的賬務就都是錯的了。因此在有些系統中,為了防止出現這樣的不一致,索性不再另外保存庫存表,而只是出一張視圖統計(邏輯統計,并非實際去保存這樣一個統計比特幣中的交易事務過程與.上述的庫存進出是很相像的,某個錢包地址中轉入了一筆比特幣,然后這個地址又向其他錢包地址轉出了一筆比特幣,這些不斷發生的入和出跟倉庫的進出是異曲同工的。然而,在比特幣中并沒有去保存份“庫存表 ”每當“出庫”的時候也并不是去“庫存表”中進行扣除,而是直接消耗“入庫記錄”,也就是說在出庫的時候就去找有沒有之前的入庫記錄拿來扣除,比如2017-8-3時需要出庫15支毛筆,此時系統就會去搜索之前的入庫記錄,發現有2017-8-1和2017-8-2分別有一筆數量為10和20的入庫記錄,為了滿足15的發出數量,首先可以消耗掉10的這一筆,然后從20的這筆再消耗掉5支, 判斷成功后, 系統會直接產生一條數量為10的出庫記錄和數量為5的出庫記錄,按這樣的方法,將每一筆入和出都對應了起來。在比特幣的交易事務結構中,“入”就是指金額轉入,“出”就是指金額轉出,為了讓大家對這種金額轉入與轉出有一個更加通俗的理解,我們來看一幅 示意圖:
上圖展示 了比特幣中的交 易事務結構,在比特幣的交易事務數據中, 存儲的就是這樣的輸入和輸出,相當于倉庫中的進出流水賬,并且“輸入”和“輸出’彼此對應,或者更準確地說,“輸入”就是指向之前的“ 輸出”,我們解釋一下圖中發生的交易事務。
001號交易為Coinbase交易,也就是挖礦交易,在這個交易中,“輸入”部分沒有對應的“輸出”,而是由系統直接獎勵發行比特幣,礦工Alice得到了12.5個比特幣的獎勵,放在001號交易的“輸出” 部分。此時,對于Alice來說, 擁有了這12.5個比特幣的支配權,這12.5個比特幣的輸出可以作為下一筆交易的“輸入”,顧名思義,這筆“輸出”就稱之為是Alice的未花費輸出,也就是Alice的UTXO的意思。
002號交易中,Alice轉 賬6比特幣到Bob的地址,Alice找到 了自己的UTXO (如果Alice不止一筆UTXO,可以根據一定的規則去選用,比如將小金額的先花費掉)。由于只需要轉賬6比特幣,可是UTXO中卻有12.5個,因此需要找零6.5個到自己的地址中,由此產生了002號中的交易輸出,注意,在002號交易輸出中的Alice地址是可以和001號中的Alice地址不一樣的,只要都是屬于Alice自己的錢包地址就可以。
003號交易中,Bob轉賬了2比特幣到Lily的地址,過程與002號交易相同,就不再贅述了。相信大家看到這里,已經基本理解了所謂的UTXO是什么意思,我們再來總結一下。
1)比特幣的交易中不是通過賬戶的增減來實現的,而是一筆筆關聯的輸入/輸出交易事務。
2)每一筆的交易都要花費“輸入”,然后產生“輸出”,這個產生的“輸出”就是所謂的“未花費過的交易輸出”,也就是UTXO。每一筆交易事務都有一個唯一的編號,稱為交易事務ID,這是通過哈希算法計算而來的,當需要引用某一筆交易事務中的“輸出”時,主要提供交易事務ID和所處“輸出”列表中的序號就可以了。
3)由于沒有賬戶的概念,因此當“輸入”部分的金額大于所需的“輸出”時,必須給自己找零,這個找零也是作為交易的一部分包含在‘輸出”中。有朋友會問:這個UTXO的意思是明白了,可是就這么一條條的“輸入”和“輸出怎么證明哪一條UTXO是屬于誰的呢?
在比特幣中,是使用輸入腳本和輸出腳本程序實現的,有時候也稱為“鎖定腳本”和“解鎖腳本”。簡單地說,就是通過“鎖定腳本”,利用私鑰簽名解鎖自己的某一條UTXO (也就是之前的“輸出”),然后使用對方的公鑰鎖定新的“輸出”,成功后,這筆新的“輸出”就成為了對方的UTXO。同樣,對方也可以使用“鎖定腳本”和“解鎖腳本”來實現轉賬。這個腳本程序其實本質上就可以看成是比特幣中的數字合約,這也是為什么比特幣被稱為可編程數字貨幣的原因,它的轉入/轉出或者說輸入/輸出是通過腳本程序的組合來自動實現的,實現過程中還使用到了私鑰和公鑰,也就是公開密鑰算法,所以比特幣還稱為可編程加密數字貨幣。
具體模型
比特幣的區塊鏈由一個個區塊串聯構成,而每個區塊又包含一個或多個交易。
任何一個交易,它總是由若干個輸入(Input)和若干個輸出(Output)構成,一個Input指向的是前面區塊的某個Output,只有Coinbase交易(礦工獎勵的鑄幣交易)沒有輸入,只有憑空輸出。所以,任何交易,總是可以由Input溯源到Coinbase交易。
這些交易的Input和Output總是可以串聯起來:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│Block #1 │ │Block #2 │ │Block #3 │ │Block #4 │
│┌──┬────┬───┐│ │┌──┬────┬───┐│ │┌──┬────┬───┐│ │┌──┬────┬───┐│
││CB│50.0│OUT├┼──┐ ││CB│50.0│OUT├┼──┐ ││CB│50.0│OUT├┼──┐ ││CB│50.0│OUT││
│└──┴────┴───┘│ │ │└──┴────┴───┘│ │ │└──┴────┴───┘│ │ │└──┴────┴───┘│
│ │ │ │┌──┬────┬───┐│ │ │┌──┬────┬───┐│ │ │┌──┬────┬───┐│
│ │ │ ││ │8.70│OUT├┼──┼──>│IN│ │ ││ └──>│IN│25.0│OUT││
│ │ └──>│IN├────┼───┤│ │ │├──┤58.7│OUT││ │├──┼────┼───┤│
│ │ ││ │41.3│OUT├┼─┐└──>│IN│ │ ││ ┌──>│IN│66.3│OUT││
│ │ │└──┴────┴───┘│ │ │└──┴────┴───┘│ │ │└──┴────┴───┘│
└─────────────┘ └─────────────┘ │ └─────────────┘ │ └─────────────┘
└────────────────────┘
還沒有被下一個交易花費的Output被稱為UTXO:Unspent TX Output,即未花費交易輸出。給定任何一個區塊,計算當前所有的UXTO金額之和,等同于自創世區塊到給定區塊的挖礦獎勵之和。
因此,比特幣的交易模型和我們平時使用的銀行賬號有所不同,它并沒有賬戶這個說法,只有UTXO。想要確定某個人擁有的比特幣,并無法通過某個賬戶查到,必須知道此人控制的所有UTXO金額之和。
在錢包程序中,錢包管理的是一組私鑰,對應的是一組公鑰和地址。錢包程序必須從創世區塊開始掃描每一筆交易,如果:
- 遇到某筆交易的某個Output是錢包管理的地址之一,則錢包余額增加;
- 遇到某筆交易的某個Input是錢包管理的地址之一,則錢包余額減少。
錢包的當前余額總是錢包地址關聯的所有UTXO金額之和。
如果剛裝了一個新錢包,導入了一組私鑰,在錢包掃描完整個比特幣區塊之前,是無法得知當前管理的地址余額的。那么,給定一個地址,要查詢該地址的余額,難道要從頭掃描幾百GB的區塊鏈數據?
當然不是。
要做到瞬時查詢,我們知道,使用關系數據庫的主鍵進行查詢,由于用了索引,速度極快。
因此,對區塊鏈進行查詢之前,首先要掃描整個區塊鏈,重建一個類似關系數據庫的地址-余額映射表。這個表的結構如下:
address | balance | lastUpdatedAtBlock |
---|---|---|
address-1 | 50.0 | 0 |
一開始,這是一個空表。每當掃描一個區塊的所有交易后,某些地址的余額增加,另一些地址的余額減少,兩者之差恰好為區塊獎勵:
address | balance | lastUpdatedAtBlock |
---|---|---|
address-1 | 50.0 | 0 |
address-2 | 40.0 | 3 |
address-3 | 50.0 | 3 |
address-4 | 10.0 | 3 |
這樣,掃描完所有區塊后,我們就得到了整個區塊鏈所有地址的完整余額記錄,查詢的時候,并不是從區塊鏈查詢,而是從本地數據庫查詢。大多數錢包程序使用LevelDB來存儲這些信息,手機錢包程序則是請求服務器,由服務器查詢數據庫后返回結果。
如果我們把MySQL這樣的數據庫看作可修改的,那么區塊鏈就是不可修改,只能追加的只讀數據庫。但是,MySQL這樣的數據庫雖然其狀態是可修改的,但它的狀態改變卻是由修改語句(INSERT/UPDATE/DELETE)引起的。把MySQL的binlog日志完整地記錄下來,再進行重放,即可在另一臺機器上完整地重建整個數據庫。把區塊鏈看作不可修改的binlog日志,我們只要把每個區塊的所有交易重放一遍,即可重建一個地址-余額的數據庫。
可見,比特幣的區塊鏈記錄的是修改日志,而不是當前狀態。
小結
比特幣區塊鏈使用UTXO模型,它沒有賬戶這個概念;
重建整個地址-余額數據庫需要掃描整個區塊鏈,并按每個交易依次更新記錄,即可得到當前狀態。