1. 程式人生 > >比特幣核心資料結構

比特幣核心資料結構

我們學習計算機時曾經有這麼一個定義:程式=資料結構+演算法,對於一個區塊鏈,我認為從技術方面看與程式的定義類似,核心一個是共識演算法,一個是核心資料結構,這兩點直接決定了這條區塊鏈工作執行原理。比特幣的共識演算法,在這一篇《雜湊函式與比特幣共識演算法PoW》中已經講述了其原理,這一篇主要講述比特幣核心資料結構這一部分。主要包括如下內容:

  • Blockchain(區塊鏈)
  • Block(區塊)
  • Block Header(區塊頭部)
  • Merkle Tree(默克爾樹)
  • Transaction(交易)

Blockchain

區塊鏈很難用一個什麼定義來描述,如果一定要描述的話,借用以太坊黃皮書中的解釋:區塊鏈就是一個具有共享狀態的密碼性安全交易的單機(cryptographically secure transactional singleton machine with shared-state)。

這裡按資料結構的層面來描述區塊鏈,就是用雜湊指標將一些列區塊串聯起來形成的一條資料鏈。在比特幣區塊鏈中,是有區塊這個概念的,現在部分割槽塊鏈中已經沒有區塊這個概念了,感興趣的可以關注一下DAG。

Block

上面講到區塊鏈是由一個個區塊構成的,哪區塊是怎麼定義的?由區塊頭部和交易兩部分構成:

  • 區塊頭部: a data structure containing the block's metadata
  • 交易列表: an array (vector in rust) of transactions

C++版本實現定義:

class CBlock : public CBlockHeader
{
public:
    // network and disk
    std::vector<CTransactionRef> vtx;

    //...省略部分程式碼...
}

Rust版本實現定義,可以看到基本都是一樣的:

#[derive(Debug, PartialEq, Clone, Serializable, Deserializable)]
pub struct Block {
    pub block_header: BlockHeader,          // 區塊頭部
    pub transactions: Vec<Transaction>,     // 交易列表
}

BlockHeader

區塊頭部定義如下:

c++版本實現定義:

/** Nodes collect new transactions into a block, hash them into a hash tree,
 * and scan through nonce values to make the block's hash satisfy proof-of-work
 * requirements.  When they solve the proof-of-work, they broadcast the block
 * to everyone and the block is added to the block chain.  The first transaction
 * in the block is a special one that creates a new coin owned by the creator
 * of the block.
 */
class CBlockHeader
{
public:
    // header
    int32_t nVersion;       // 版本號,指定驗證規則(indicates which set of block validation rules to follow) 
    uint256 hashPrevBlock;  // 前一區塊雜湊(實際計算時取得是前一區塊頭雜湊)(a reference to the parent/previous block in the blockchain)
    uint256 hashMerkleRoot; // 默克爾根雜湊(a hash (root hash) of the merkle tree data structure containing a block's transactions)
    uint32_t nTime;         // 時戳(seconds from Unix Epoch)
    uint32_t nBits;         // 區塊難度(aka the difficulty target for this block)
    uint32_t nNonce;        // 工作量證明nonce(value used in proof-of-work)

    // ...部分程式碼省略...
}

Rust實現版本定義,可以看到基本都是一樣的:

#[derive(PartialEq, Clone, Serializable, Deserializable)]
pub struct BlockHeader {
    pub version: u32,                   // 版本號,指定驗證規則
    pub previous_header_hash: H256,     // 前一區塊雜湊
    pub merkle_root_hash: H256,         // 默克爾根雜湊
    pub time: u32,                      // 時戳
    pub bits: Compact,                  // 區塊難度
    pub nonce: u32,                     // 工作量證明nonce
}

Merkle Tree

上面區塊頭部的hashMerkleRoot欄位就是默克爾樹根雜湊值,默克爾樹可以說是區塊中最重要的一個數據結構,不僅僅提供了整個區塊所有交易的完整性驗證,。

Rust版本Merkle tree程式碼實現如下:

/// Calculates the root of the merkle tree
/// https://en.bitcoin.it/wiki/Protocol_documentation#Merkle_Trees
pub fn merkle_root<T: AsRef<H256> + Sync>(hashes: &[T]) -> H256{
    // 遞迴結束條件
    if hashes.len() == 1 {
        return hashes[0].as_ref().clone();
    }
    // 雜湊成對,為下一步計算做準備
    let mut row = Vec::with_capacity(hashes.len() / 2);
    let mut i = 0;
    while i + 1 < hashes.len() {
        row.push((&hashes[i], &hashes[i + 1]));
        i += 2
    }

    // duplicate the last element if len is not even
    if hashes.len() % 2 == 1 {
        let last = &hashes[hashes.len() - 1];
        row.push((last, last));     //如果元素是奇數個數,將最後一個元素複製一份,自己與自己配對
    }
    
    // 一層一層的遞迴計算,一直到只有最後一個根雜湊值
    let res: Vec<_>;
    // Only compute in parallel if there is enough work to benefit it
    if row.len() > 250 {
        res = row.par_iter().map(|x| merkle_node_hash(&x.0, &x.1)).collect();
    } else {
        res = row.iter().map(|x| merkle_node_hash(&x.0, &x.1)).collect();
    }
    merkle_root(&res)       
    // 這裡不用擔心遞迴的深度,其一,比特幣區塊大小有限,限制了交易數量;其二,是因為Merkle tree是類似二叉樹的結構,遞迴計算次數為log(n),即使n非常大,遞迴次數也很小。
}

/// Calculate merkle tree node hash
pub fn merkle_node_hash<T>(left: T, right: T) -> H256 where T: AsRef<H256> {
    dhash256(&*concat(left, right))     //雙SHA-256雜湊運算
}

#[inline]
fn concat<T>(a: T, b: T) -> H512 where T: AsRef<H256> { //將2個雜湊值並在一起
    let mut result = H512::default();
    result[0..32].copy_from_slice(&**a.as_ref());
    result[32..64].copy_from_slice(&**b.as_ref());
    result
}

Transaction

Rust實現版本定義如下:

pub struct Transaction {
    pub version: i32,                       //協議版本,明確這筆交易參照的規則協議
    pub inputs: Vec<TransactionInput>,      //輸入列表
    pub outputs: Vec<TransactionOutput>,    //輸出列表
    pub lock_time: u32,                     //鎖定時間
}

可以看到,交易中最重要的是輸入列表和輸出列表,說明這次比特幣轉賬時比特幣的來源和轉賬去向。

Rust版本實現輸入列表定義:

pub struct TransactionInput {
    pub previous_output: OutPoint,      //上一筆交易輸出
    pub script_sig: Bytes,              //解鎖指令碼
    pub sequence: u32,                  //序列號
    pub script_witness: Vec<Bytes>,     //見證指令碼
}

/** An outpoint - a combination of a transaction hash and an index n into its vout */
pub struct OutPoint {
    pub hash: H256,     //交易ID
    pub index: u32,     //輸出索引,表明是交易中的第幾個輸出
}

上面這個OutPoint定義如何沒有理解的話,可參考下圖進行理解:

Rust版本實現輸出列表定義:

pub struct TransactionOutput {
    pub value: u64,             //輸出金額
    pub script_pubkey: Bytes,   //鎖定指令碼,對於一個比特幣交易來說,交易本身是不用關心輸出的地址,交易只需要關心鎖定指令碼,當使用的時候能使用正確的解鎖指令碼即可動用比特幣。
}

更多請參考我的另一篇博文比特幣交易。

參考文件
Bitcoin Developer Reference
parity-bitcoin