在區塊鏈網路當中,所有的資料都以區塊的形式記錄在各個節點上。而每個區塊又以單獨的檔案儲存在節點本地磁碟上,在比特幣(Linux系統)中所有的區塊資訊都儲存在~/.bitcoin/blocks/目錄下面,並以blk***.dat檔案名稱標示,如下圖所示:
區塊結構
根據https://en.bitcoin.it/wiki/Block所描述的,區塊的結構如下:
Field |
描述 |
大小 |
Magic no |
”魔法數“,常數0xD9B4BEF9 |
4 bytes |
Blocksize |
區塊大小 |
4 bytes |
Blockheader |
區塊頭 |
80 bytes |
Transaction counter |
交易數量,正整數 |
1 - 9 bytes |
transactions |
交易列表 |
-many transactions |
首先是一個“魔法數”,根據描述這是個常數佔4個位元組,然後是4個位元組的區塊大小,然後是區塊頭80位元組,然後是1-9位元組的交易數量,最後是所有的交易。但是在實際的比特幣代碼當中卻並不是這麼定義的,
class CBlock : public CBlockHeader{public: // network and disk std::vector<CTransactionRef> vtx; // memory only mutable bool fChecked; // ... CBlockHeader GetBlockHeader() const { CBlockHeader block; block.nVersion = nVersion; block.hashPrevBlock = hashPrevBlock; block.hashMerkleRoot = hashMerkleRoot; block.nTime = nTime; block.nBits = nBits; block.nNonce = nNonce; return block; } std::string ToString() const;};
可以發現,實際上只定義了區塊頭和所有的交易,或許是不同版本的差異,但是看github上過去版本的bitcoin代碼中也都沒有定義上述表格中的變數,所以個人認為實際的結構應該是代碼中定義的。
再來看看區塊頭部的結構,https://en.bitcoin.it/wiki/Block_hashing_algorithm
Field |
目的 |
更新時間 |
大小 (Bytes) |
Version |
區塊版本號碼 |
升級軟體並指定新版本號碼時 |
4 |
hashPrevBlock |
前一個區塊的256-bit 雜湊值 |
產生新的區塊 |
32 |
hashMerkleRoot |
Merkle樹根的256-bit 雜湊值 |
收到了新的交易 |
32 |
Time |
從 1970-01-01 00:00 UTC到現在為止的時間間隔,單位為秒 |
每幾秒 |
4 |
Bits |
當前POW的目標雜湊值的壓縮形式 |
難度調整時 |
4 |
Nonce |
32-bit 隨機數 |
嘗試新的hash時 |
4 |
每個區塊頭都包含前一個區塊的雜湊值,所以所有的區塊就像鏈表一樣連成了一條鏈,鏈的頭部就是創世塊(Genesis Block)。所有的交易都以Merkle tree的形式進行索引,並在block header中儲存Merkle tree的樹根,如果當前交易數量是奇數的話,那麼最後一個交易將會被計算兩次雜湊值。當前POW的難度以壓縮的形式儲存在4個位元組的Bits中,最後通過mining找到的隨機數記錄在最後的Nonce中。 交易結構
關於比特幣交易,更好的參考是比特幣官方文檔https://en.bitcoin.it/wiki/Transaction,解釋的很詳細。
交易是在區塊鏈網路中傳輸的最基本的資料結構,所有有效交易最終都會被打包進區塊中並儲存在區塊鏈上,比特幣中交易的資料結構如下,
Field |
描述 |
大小 |
Version no |
版本號碼,當前為1 |
4 bytes |
In-counter |
輸入交易數量,正整數 |
1 - 9 bytes |
list of inputs |
輸入列表,每個區塊中第一個交易被稱為“Coinbase” |
-many inputs |
Out-counter |
輸出交易數量,正整數 |
1 - 9 bytes |
list of outputs |
輸出資料行表,每個區塊中第一個輸出交易是給礦工的獎勵 |
-many outputs |
lock_time |
鎖定時間,如果非0並且小於0xFFFFFFFF,那麼就是指塊序號;如果交易已經終結,那麼就是指時間戳記 |
4 bytes |
一個簡單的交易輸入如下:
Input:Previous tx: f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6Index: 0scriptSig: 304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446618c4571d1090db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d5d6cc8d25c6b241501
首先是前一筆交易的雜湊值(Previous tx),然後是花費的是第幾個輸出(Index),最後是解鎖指令碼(scriptSig),其中解鎖指令碼=簽名+公開金鑰,只有提供正確的解鎖指令碼,才能花費對應的交易。
一個簡單的輸出如下:
Output:Value: 5000000000scriptPubKey: OP_DUP OP_HASH160 404371705fa9bd789a2fcd52d2c580b65d35549dOP_EQUALVERIFY OP_CHECKSIG
首先是輸出金額(Value),然後是鎖定指令碼,鎖定指令碼包括指令碼系統中的一系列操作符。 交易類型
比特幣目前提供了兩種不同的交易類型,如下所示。通過這兩者類型的交易可以組合出更加複雜的交易,稱之為合約。
(1)Pay-to-PubkeyHash
scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIGscriptSig: <sig> <pubKey>
這個也是最常見的交易,目標地址就是比特幣地址,花費時需要提供簽名和公開金鑰。
(2)Pay-to-Script-Hash(P2SH)
scriptPubKey: OP_HASH160 <scriptHash> OP_EQUAL scriptSig: ..signatures... <serialized script>
在P2SH中,目標地址由指令碼雜湊取代,解鎖指令碼中才包含簽名和指令碼內容。下面有兩種類型的交易的對比,首先是普通的多簽名指令碼,解鎖指令碼中只需要5個公開金鑰中任意兩個私密金鑰的簽名即可。
Locking Script |
2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 OP_CHECKMULTISIG |
Unlocking Script |
Sig1 Sig2 |
然後是P2SH指令碼,
Redeem Script |
2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 OP_CHECKMULTISIG |
Locking Script |
OP_HASH160 <20-byte hash of redeem script> OP_EQUAL |
Unlocking Script |
Sig1 Sig2 redeem script |
可以看出鎖定指令碼中只需要包括Redeem Script的雜湊即可,而不用包含長長的一串公開金鑰資訊,具體的指令碼內容由解鎖指令碼提供,相當於是將交易的大部分內容交給了交易花費者去處理,從而減少了網路傳輸的資料。根據《Master Bitcoin》所述,與直接使⽤複雜指令碼以鎖定輸出的⽅式相⽐,P2SH具有以下特點: 在交易輸出中,複雜指令碼由簡短電⼦指紋取代,使得交易代碼變短。 指令碼能被編譯為地址,⽀付指令的發出者和⽀付者的⽐特幣錢包不需要複雜⼯序就可以執⾏P2SH。 P2SH將構建指令碼的重擔轉移⾄接收⽅,⽽⾮發送⽅。 P2SH將⻓指令碼資料存放區的負擔從輸出⽅(儲存於UTXO集,影響記憶體)轉移⾄輸⼊⽅(僅儲存於區塊鏈)。 P2SH將⻓指令碼資料存放區的重擔從當前(⽀付時)轉移⾄未來(花費時)。 P2SH將⻓指令碼的交易費成本從發送⽅轉移⾄接收⽅,接收⽅在使⽤該筆資⾦時必須含有贖回指令碼。