區塊鏈筆記(3)比特幣交易的數據和流程

區塊鏈技術只能用來做關於金融交易的應用麼?或許先去了解它有關交易的細節,才能看到是否有其它應用的可能。

1 交易的數據模型

1.1 起因

在此之前,我們關於Bitcoin Core介紹了許多,以及把它當作工具如何使用,現在我們將進一步來研究下區塊鏈中的數據模型。

爲什麼說將區塊和交易當作數據模型來理解非常重要?

我的答案是:爲了知道如何使用數據。

我們使用區塊鏈應用與網絡中的其它節點進行通信、交互以及協作時,可能更關注的是協議。但如果直接去看協議,可能會不容易看得通透,例如在面對一些問題:通過協議傳輸的數據長什麼樣?開發自己的區塊鏈應用時,數據是主角,那如何組織和使用它呢?要搞清楚,數據模型這座大山勢必要推倒。

另外談到數據這個話題,開發者可以通過操作碼(Op-code)的方式向區塊中嵌入額外的數據,對此目前社區反應出兩種不同的聲音,以比特幣平臺爲例,一些人認爲比特幣區塊鏈如此便包含了許多非金融數據,當區塊鏈不斷延展的同時,會對那些不在意這些數據的人的存儲空間帶來了沉重的負擔;另一些人則認爲這些非金融數據的存在,可能使區塊鏈在金融領域之外,產生更多的應用可能。

Op-code:來自比特幣腳本語言的一些操作碼,用於在公鑰腳本簽名腳本中推送數據或執行函數。

其實在社區中看到類似的爭論還是蠻有意思的,早期時候,人們爲了給比特幣交易添加備註信息,或其他和交易本身無關的非金融數據,是通過刻錄比特幣的方式來進行的,就是在不同的交易中,將output裏的驗證腳本換成其他數據,這會使得UTXO數據集不斷變大,因爲這麼做會導致這筆交易裏的比特幣不能再被花費,又因爲整個比特幣系統出於速度的考慮,會把所有未被花費的交易(UTXO)都存儲在內存中,這必然使得網絡各節點中包含大量的冗餘信息,造成跨節點分類賬的維護成本變高。而現在,隨着新的改進方案已納入區塊鏈和操作碼中,如Op-return。如此協議已經漸趨成熟,UTXO數據集就不會誇張的膨脹.

UTXO:即未花費的交易輸出(Unspent Transaction Outputs),它是比特幣交易生成及驗證的一個核心概念。交易構成了一組鏈式結構,所有合法的比特幣交易都可以追溯到前向一個或多個交易的輸出,這些鏈條的源頭都是挖礦獎勵,末尾則是當前未花費的交易輸出。另外值得提的一點是,在比特幣錢包當中,我們都可以看到賬戶餘額,但在這個賬戶餘額的概念與我們所熟知的銀行賬戶餘額有着巨大的不同,其實站在UTXO交易模型上看,並沒有什麼所謂一個一個的比特幣,有的只是UTXO。當我們說張三擁有10個比特幣的時候,我們實際上是在說,當前區塊鏈賬本中,有若干筆交易的UTXO項的收款人寫的是張三的地址,而這些UTXO項的數額總和是10。比特幣錢包中所看到的賬戶餘額,實際上則是錢包通過掃描區塊鏈並聚合所有屬於該用戶的UTXO計算得來的。

Op-return:本質上講,OP_RETURN是一個腳本操作碼,是專門被設計出來承載額外的交易信息的。它的作用就像我們在日常轉賬過程中的備註信息。通過它發送的數據會和我們進行的比特幣交易一樣,永久保存在比特幣區塊鏈的區塊中。

1.2 交易的輸入和輸出

不論你面對的是哪種區塊鏈應用,交易都是區塊鏈系統中最重要的部分。你可以把交易理解爲組成區塊鏈宇宙的原子,正如原子是組成所有生命的基礎,交易則是組成數據塊的單位。你可能已經注意到了,比特幣區塊鏈上所做的任何事情都是,爲了確保一筆交易能否被創建,並在網絡中傳播和驗證,以及最終添加到區塊鏈上。當然搞清楚這些具體細節,還是爲了以後能夠創建自己的區塊鏈應用。所以現在還是一步一步來,先回顧下交易是如何運作的,以及它的輸入和輸出,這對後面討論交易的數據模型來說很重要。

交易描述的是一筆資金從它的原始所有者(input)向即將所有者(output)價值轉化的數據結構

以下交易詳情是使用之前我們介紹過的站點,查看比特幣測試鏈上的一筆交易:
clipboard.png
從圖中顯而易見的是,有兩筆爲0.01BTC的輸入,參與了一次0.001BTC的轉賬後,又退回給原所有者0.019BTC,基於此我想問的是:這些輸入從何而來,產生的新輸出又去向何處?

一個交易的輸入,都來自與另一個交易的未花費輸出(UTXO)。

clipboard.png

在交易發生時有獲取賬戶餘額的需求,都是通過統計整個區塊鏈上,該錢包地址關聯的所有UTXO(未花費交易輸出)上的比特幣數量來完成的。所以並不存在存儲一個賬戶餘額的字段,或者一個比特幣的地址。

1.3 數據模型

這一小節我們來看交易的信息在數據模型中是如何存儲的。如果要求網絡返回一個原始交易信息給我們,所得到的可能是像下面這樣的信息:

0100000001f3f6a909f8521adb57d898d2985834e632374e770fd9e2b98656f1bf1fdfd427010000006b48304502203a776322ebf8eb8b58cc6ced4f2574f4c73aa664edce0b0022690f2f6f47c521022100b82353305988cb0ebd443089a173ceec93fe4dbfe98d74419ecc84a6a698e31d012103c5c1bc61f60ce3d6223a63cedbece03b12ef9f0068f2f3c4a7e7f06c523c3664ffffffff0260e31600000000001976a914977ae6e32349b99b72196cb62b5ef37329ed81b488ac063d1000000000001976a914f76bc4190f3d8e2315e5c11c59cfc8be9df747e388ac00000000

這是一條還未解碼成JSON對象的十六進制數據。雖然確實不是很容易看的懂,但其實組織的還是很有條理的。以上面這條信息爲例,從起始位開始,一條交易一般包含如下內容:

  1. 比特幣的版本(Version):01000000
  2. 交易的輸入數量(Input Count):01
  3. 交易的輸入信息(Input Info):f3f6a909f8521adb57d898d2985834e632374e770fd9e2b98656f1bf1fdfd427010000006b48304502203a776322ebf8eb8b58cc6ced4f2574f4c73aa664edce0b0022690f2f6f47c521022100b82353305988cb0ebd443089a173ceec93fe4dbfe98d74419ecc84a6a698e31d012103c5c1bc61f60ce3d6223a63cedbece03b12ef9f0068f2f3c4a7e7f06c523c3664ffffffff
  4. 交易的輸出數量(Output Count):02
  5. 交易的輸出信息(Output Info):60e31600000000001976a914977ae6e32349b99b72196cb62b5ef37329ed81b488ac063d1000000000001976a914f76bc4190f3d8e2315e5c11c59cfc8be9df747e388ac
  6. 鎖定時間(loctime):00000000。它表示該條交易最早被確認後,寫入的最早區塊或最早被確認寫入的時間:

    • 若該字段非零,且<5億,則表示該條交易最早被寫入的區塊的區塊號。
    • 若>5億,則表示該條交易最早被寫入區塊的時間。
    • 若爲零,則表示該條交易立即被寫入區塊。

其中在交易的輸入信息和輸出信息中,還分別包含了一小段用以驗證該次交易是否有效地指令腳本:即輸入信息中的解鎖腳本(UnLocking script)和輸出信息中的鎖定腳本(Locking script)。

這裏的腳本(script),指的是記錄在每條交易中的一系列指令字符,執行用於驗證交易是否有效及比特幣能否發出。而名稱與之類似的比特幣腳本語句
(Bitcoin Script)是一種基於棧的簡單輕量級的語句,被設計用來能通用於一系列硬件平臺上做相關運算的指令。我們可以在棧中存儲數字或數據常量,並使用一系列前綴爲OP_的指令(Opcode)對數據進行操作。例如通過OP_ADD將棧中的兩個數據進行相加,通過OP_EQUAL來檢查棧頂的兩個元素是否相等,OP_DUP複製棧頂的數據等等,總共大概有80多個指令,詳見Opcodes的維基百科。

接下來我們通過一條簡單的算數運算指令來具體觀察上面提到的三個概念:解鎖腳本、鎖定腳本和包含Opcodes指令的比特幣腳本語句,算數指令如下:

2 6 OP_ADD 8 OP_EQUAL

比特幣腳本語句的執行順序是從左向右的,並且是基於棧結構的,那麼這條語句的執行步驟就應當是:

  1. 數字2入棧;
  2. 數字6入棧;
  3. 執行OP_ADD:數字6和2依次出棧後,相加所得的結果(8)再入棧;
  4. 數字8入棧
  5. 執行OP_EQUAL:數字8和8依次出棧後,進行相等比較,所得的結果(True)再入棧

其中我們可以將6 OP_ADD 8 OP_EQUAL這部分視爲鎖定腳本,它需要滿足使其最終結果爲True的解鎖腳本(2),才能完成算數驗證。也就是說如果用這條語句來驗證交易的有效性,那麼所有知道數字2能滿足條件的解鎖語句,都可使其生效。

對於比特幣腳本語言有兩個特性:

  • 無流程控制:語句簡單,不存在循環和條件控制,好處是不用擔心死循環之類的阻塞性錯誤;缺點是不夠靈活。
  • 無狀態:在執行過程前後,不保存任何關於狀態的值,好處是安全,不論在哪個平臺上執行相同的語句都會得到相同的答案;不足是比較簡單。

任何實現方式的特點,都有其長短優劣,在做整體方案架構的考量時,應謹慎根據業務場景進行選取。

而在實際情況中,我們驗證交易有效性所使用得解鎖腳本(UnLocking script)和鎖定腳本(Locking script)構成的比特幣腳本語句是如下的結構:

<sig> <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

其中對應於解鎖腳本(UnLocking script)和鎖定腳本(Locking script)的部分分別是:

  • UnLocking script:<sig> <pubKey>
  • Locking script:OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG

忘了說清楚一點,驗證交易發生的有效性,並不是用同一個交易的解鎖腳本(UnLocking script)和鎖定腳本(Locking script)進行驗證。而是用當前進行交易的解鎖腳本,與該輸入回溯的UTXO中的鎖定腳本進行驗證,而當前交易的鎖定腳本則是用來,和未來將要發生的交易中的解鎖腳本進行驗證。具體驗證關係如下圖:
clipboard.png

交易的有效性驗證的工作原理其實很簡單,就是利用了非對稱加密,在解鎖腳本中,包含了錢包所有者用私鑰生成的簽名。因爲只有錢包所有者纔有交易權,才能生成判斷交易有效地解鎖腳本。

具體拆分上面的原始交易數據如下圖:
clipboard.png

其中我們將輸入信息細化爲如下部分:

  • Previous output hash:所有的輸入都可以回溯到一個輸出,即上一筆交易所產生的UTXO。
  • Previous output index:可能一筆交易會包含多項UTXO,這項便是指定多個UTXO的索引,其中第一個UTXO從0開始算。
  • Script Size(bytes):表示解鎖腳本的字節數大小。
  • scriptSig:上文談到的解鎖腳本
  • Sequence:這目前是比特幣廢棄的一個屬性位,默認設爲ffffffff

而輸出信息也可細化出如下部分:

  • Amount:比特幣輸出的數量,按比特幣最小單位(Satoshis)計算,10^8 Satoshis = 1 Bitcoin.
  • Script Size(bytes): 表示鎖定腳本的字節數大小。
  • scriptPubKey:上文談到的解鎖腳本。

2 創建交易

通過比特幣錢包的GUI工具,雖然能夠完成比特幣區塊鏈生命週期中的基本操作,但存在一些侷限性,所以接下來爲了更深入的瞭解比特幣區塊鏈交易的細節,我們將使用調試控制檯來創建一個交易,具體步驟如下:

  1. 在比特幣錢包中查看所有的UTXO
  2. 查看一個特定UTXO的細節
  3. 創建一個原始交易
  4. 解碼該原始交易
  5. 對該原始交易進行簽名
  6. 將這個交易提交到網絡
  7. 通過TxID查詢所創建的交易

2.1 查看UTXO

我們可以通過在上一節介紹的比特幣錢包的調試窗口(Help-Debug Window)中,查看本錢包所有的UTXO,查詢命令爲:listunspent。發現查詢結果是由一個個UTXO對象構成的數組組成的,截取其中一個UTXO如下所示:

[
  ...
  {
    "txid": "811ffa0a5c8020a21f115df020b35a00503e4a87523b025390577ee727fbb73f", // 交易Id
    "vout": 1, // 輸出序號
    "address": "2N1KFMyBJZksopo7gpr7L5QwbtuphLREkGN", // 地址
    "redeemScript": "001462fab42642cbfe84c69a9e17fcb6c1ae27f63748", // 贖回腳本
    "scriptPubKey": "a9145883d125a1bb6db07e886bb167d966013f407c4487", // 公鑰腳本
    "amount": 0.01898328, // 可用金額
    "confirmations": 26738, // 確認次數
    "spendable": true, // 當前錢包是否擁有私鑰,以便能夠消費該UTXO
    "solvable": true, // 是否可用,缺少祕鑰時忽略
    "safe": true // 未經確認的交易將被認爲是不安全的
  },
  ...
]

2.2 查看一個UTXO詳情

這步我們使用命令:gettxout來查詢一個未花費交易的詳情,該命令接收三個參數:交易ID、未花費輸出的序號(從0開始)、一個可選的布爾值用來控制是否顯示內存池中還未驗證的輸出。
複製上一步中的交易ID的查詢命令如下:

gettxout 811ffa0a5c8020a21f115df020b35a00503e4a87523b025390577ee727fbb73f 0

運行後得到的結果如下:

{
  "bestblock": "00000000000000a88e2e39c56235eb61eaf40fca8273e31d5ce49a4d8577d51f",
  "confirmations": 26842, // 驗證次數
  "value": 0.00100000, // 交易金額(單位是BTC)
  "scriptPubKey": { // 解鎖腳本
    "asm": "OP_HASH160 c6176d6f78b0205a83bf4bbc516a23dc00a4ca64 OP_EQUAL", // 彙編格式(assembly)
    "hex": "a914c6176d6f78b0205a83bf4bbc516a23dc00a4ca6487", // 十六進制格式
    "reqSigs": 1, // 所需的簽名數
    "type": "scripthash", // 加密類型
    "addresses": [ // 收款地址列表
      "2NBJdr34cWkdr31rQRRMvcFYAg7kM8wTiNB"
    ]
  },
  "coinbase": false
}

2.3 創建一個原始交易

使用命令:createrawtransaction,創建一個未簽名的序列化交易,該交易並不會存儲在錢包或傳輸到網絡。需要兩個傳參:第一個是前一個輸出的引用,第二個是P2PKH或P2SH標準的收款地址及收款數量。創建命令示意如下:

createrawtransaction '[{"txid":"811ffa0a5c8020a21f115df020b35a00503e4a87523b025390577ee727fbb73f","vout": 1}]' '{"2NBn87R8AAwtXUNmmFULvDhmPyeka1X7rRD":0.001, "2NBJdr34cWkdr31rQRRMvcFYAg7kM8wTiNB": 0.001}'

我執行後得到的輸出:

02000000013fb7fb27e77e579053023b52874a3e50005ab320f05d111fa220805c0afa1f810100000000ffffffff02a08601000000000017a914cb4a40c6ccaf652cc9a6459047494359c3ff25d787a08601000000000017a914c6176d6f78b0205a83bf4bbc516a23dc00a4ca648700000000

2.4 解碼

上一步所創建原始交易的輸出,是一串十六進制字符串,顯然沒有什麼可讀性。爲了確認我們所創建的正確性,我們需要將其解碼爲可讀的JSON格式,使用到的命令是decoderawtransaction,執行如下:

decoderawtransaction 02000000013fb7fb27e77e579053023b52874a3e50005ab320f05d111fa220805c0afa1f810100000000ffffffff02a08601000000000017a914cb4a40c6ccaf652cc9a6459047494359c3ff25d787a08601000000000017a914c6176d6f78b0205a83bf4bbc516a23dc00a4ca648700000000

輸出結果如下:

{
  "txid": "8af75c03ca2e7e84135b2809f73e75d758cfc5b72c1e51ae18b770baef844b54",
  "hash": "8af75c03ca2e7e84135b2809f73e75d758cfc5b72c1e51ae18b770baef844b54",
  "version": 2,
  "size": 115,
  "vsize": 115,
  "weight": 460,
  "locktime": 0,
  "vin": [
    {
      "txid": "811ffa0a5c8020a21f115df020b35a00503e4a87523b025390577ee727fbb73f",
      "vout": 1,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.00100000,
      "n": 0,
      "scriptPubKey": {
        "asm": "OP_HASH160 cb4a40c6ccaf652cc9a6459047494359c3ff25d7 OP_EQUAL",
        "hex": "a914cb4a40c6ccaf652cc9a6459047494359c3ff25d787",
        "reqSigs": 1,
        "type": "scripthash",
        "addresses": [
          "2NBn87R8AAwtXUNmmFULvDhmPyeka1X7rRD"
        ]
      }
    },
    {
      "value": 0.00100000,
      "n": 1,
      "scriptPubKey": {
        "asm": "OP_HASH160 c6176d6f78b0205a83bf4bbc516a23dc00a4ca64 OP_EQUAL",
        "hex": "a914c6176d6f78b0205a83bf4bbc516a23dc00a4ca6487",
        "reqSigs": 1,
        "type": "scripthash",
        "addresses": [
          "2NBJdr34cWkdr31rQRRMvcFYAg7kM8wTiNB"
        ]
      }
    }
  ]
}

2.5 簽名

從上面可讀性更好的原始交易信息中,看到交易輸入的scriptSig字段爲空,這是因爲我們還沒有爲這個簽名,證明我們擁有對UTXO的使用權。接下來使用命令signrawtransactionwithwallet進行簽名:

signrawtransactionwithwallet 02000000013fb7fb27e77e579053023b52874a3e50005ab320f05d111fa220805c0afa1f810100000000ffffffff02a08601000000000017a914cb4a40c6ccaf652cc9a6459047494359c3ff25d787a08601000000000017a914c6176d6f78b0205a83bf4bbc516a23dc00a4ca648700000000

簽名成功的輸出結果如下:

{
  "hex": "020000000001013fb7fb27e77e579053023b52874a3e50005ab320f05d111fa220805c0afa1f81010000001716001462fab42642cbfe84c69a9e17fcb6c1ae27f63748ffffffff02a08601000000000017a914cb4a40c6ccaf652cc9a6459047494359c3ff25d787a08601000000000017a914c6176d6f78b0205a83bf4bbc516a23dc00a4ca64870247304402207fbd59f6e806dc1aab5f602b796dc2ecfa96f0e018c7fe4ecc7dcf190e0619f10220168dffa1d5bd5876518530c72fd3bff59337050949c9732dabcfaeef7533de44012103959e3af1e6ddb01d6ac54966cda59464ab27fcaf34b0dca6df02f75d3df7668800000000",
  "complete": true
}

然後對簽名後的輸出進行JSON解碼,會發現輸入部分多了些內容:

{
...
"vin": [{
  "txid": "811ffa0a5c8020a21f115df020b35a00503e4a87523b025390577ee727fbb73f",
  "vout": 1,
  "scriptSig": {
    "asm": "001462fab42642cbfe84c69a9e17fcb6c1ae27f63748",
    "hex": "16001462fab42642cbfe84c69a9e17fcb6c1ae27f63748"
  },
  "txinwitness": ["304402207fbd59f6e806dc1aab5f602b796dc2ecfa96f0e018c7fe4ecc7dcf190e0619f10220168dffa1d5bd5876518530c72fd3bff59337050949c9732dabcfaeef7533de4401", "03959e3af1e6ddb01d6ac54966cda59464ab27fcaf34b0dca6df02f75d3df76688"],
  "sequence": 4294967295
}],
...
}

2.6 將簽名後的交易推送至網絡

使用命令sendrawtransaction將已簽名的交易推送至網絡。

sendrawtransaction 020000000001013fb7fb27e77e579053023b52874a3e50005ab320f05d111fa220805c0afa1f81010000001716001462fab42642cbfe84c69a9e17fcb6c1ae27f63748ffffffff02a08601000000000017a914cb4a40c6ccaf652cc9a6459047494359c3ff25d787a08601000000000017a914c6176d6f78b0205a83bf4bbc516a23dc00a4ca64870247304402207fbd59f6e806dc1aab5f602b796dc2ecfa96f0e018c7fe4ecc7dcf190e0619f10220168dffa1d5bd5876518530c72fd3bff59337050949c9732dabcfaeef7533de44012103959e3af1e6ddb01d6ac54966cda59464ab27fcaf34b0dca6df02f75d3df7668800000000

執行後返回的結果是交易ID的十六進制值:

24cd5619a366ad6a3a34a29766fd5f82c39657bc15dcfdcd4d7363a65f401c8b

2.7 查看交易詳情

至此整個交易的聲明週期就完成了,我們可以通過gettransaction來查看,上一步完成交易的詳情:

gettransaction 24cd5619a366ad6a3a34a29766fd5f82c39657bc15dcfdcd4d7363a65f401c8b

得到詳情結果如下:

{
  "amount": -0.00200000,
  "fee": -0.01698328,
  "confirmations": 1,
  "blockhash": "000000000000006715d295c34b2896d0c28f67a092869610200684e45fdd3ad9",
  "blockindex": 1,
  "blocktime": 1558762656,
  "txid": "24cd5619a366ad6a3a34a29766fd5f82c39657bc15dcfdcd4d7363a65f401c8b",
  "walletconflicts": [
  ],
  "time": 1558762586,
  "timereceived": 1558762586,
  "bip125-replaceable": "no",
  "details": [
    {
      "address": "2NBn87R8AAwtXUNmmFULvDhmPyeka1X7rRD",
      "category": "send",
      "amount": -0.00100000,
      "vout": 0,
      "fee": -0.01698328,
      "abandoned": false
    },
    {
      "address": "2NBJdr34cWkdr31rQRRMvcFYAg7kM8wTiNB",
      "category": "send",
      "amount": -0.00100000,
      "label": "like you",
      "vout": 1,
      "fee": -0.01698328,
      "abandoned": false
    }
  ],
  "hex": "020000000001013fb7fb27e77e579053023b52874a3e50005ab320f05d111fa220805c0afa1f81010000001716001462fab42642cbfe84c69a9e17fcb6c1ae27f63748ffffffff02a08601000000000017a914cb4a40c6ccaf652cc9a6459047494359c3ff25d787a08601000000000017a914c6176d6f78b0205a83bf4bbc516a23dc00a4ca64870247304402207fbd59f6e806dc1aab5f602b796dc2ecfa96f0e018c7fe4ecc7dcf190e0619f10220168dffa1d5bd5876518530c72fd3bff59337050949c9732dabcfaeef7533de44012103959e3af1e6ddb01d6ac54966cda59464ab27fcaf34b0dca6df02f75d3df7668800000000"
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章