在区块链网络当中,所有的数据都以区块的形式记录在各个节点上。而每个区块又以单独的文件保存在节点本地磁盘上,在比特币(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: f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6
Index: 0
scriptSig: 304502206e21798a42fae0e854281abd38bacd1aeed3ee3738d9e1446618c4571d10
90db022100e2ac980643b0b82c0e88ffdfec6b64e3e6ba35e7ba5fdd7d5d6cc8d25c6b241501
首先是前一笔交易的哈希值(Previous tx),然后是花费的是第几个输出(Index),最后是解锁脚本(scriptSig),其中解锁脚本=签名+公钥,只有提供正确的解锁脚本,才能花费对应的交易。
一个简单的输出如下:
Output:
Value: 5000000000
scriptPubKey: OP_DUP OP_HASH160 404371705fa9bd789a2fcd52d2c580b65d35549d
OP_EQUALVERIFY OP_CHECKSIG
首先是输出金额(Value),然后是锁定脚本,锁定脚本包括脚本系统中的一系列操作符。
比特币目前提供了两种不同的交易类型,如下所示。通过这两者类型的交易可以组合出更加复杂的交易,称之为合约。
(1)Pay-to-PubkeyHash
scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
scriptSig: <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具有以下特点: