天天看点

(二) 区块链数据结构-区块

区块是区块链的核心单元。区块链由区块互相连接而成。

区块

区块由区块头和区块体两部分组成。其中区块的大小被限制在1M以内(为了防止资源浪费和DOS攻击),区块头的大小被固定为80个字节。

(二) 区块链数据结构-区块

但目前随机比特币的发展,交易数量持续增加,1M的大小能存储的交易数量有限,导致大量的交易积压。因此目前正在考虑扩容方案。

区块头

区块头中记录了版本号、上一个区块的Hash地址、merkle根、区块创建时间戳、区块的工作量难度目标以及用于计算目标的参数值。

字段 大小 描述
version 4字节 版本号,⽤于跟踪软件/协议的更新
prevBlockHash 32字节 上一个区块的Hash地址
merkleRoot 32字节 该区块中交易的merkl e树根的哈希值(稍后详细说明)
time 4字节 该区块的创建时间戳
difficultyTarget 4字节 该区块链工作量证明难度目标(稍后讲解工作量证明)
nonce 4字节 用于证明工作量的计算参数

区块体

区块体中记录了该区块存储的交易数量以及交易数据。

字段 大小 描述
numTransactionsBytes 1字节 交易数量占用的字节数
numTransactions 0-8个字节 区块内存储的交易数量
transactions 不确定 区块内存的多个交易数据

为了节约区块的存储空间,区块内的交易数量字段采用了压缩存储。在读取交易数量之前,会先读取numTransactionsBytes字段值。

- 如果该值小于253,则用直接将该值作为交易数量

- 如果该值等于253,则读取之后的两个字节作为交易数量

- 如果该值等于254,则读取之后的4个字节作为交易数量

- 否则,读取之后的8个字节作为交易数量

区块的核心代码

1. 区块中的核心常量定义

/** How many bytes are required to represent a block header WITHOUT the trailing 00 length byte. */
    //区块头的大小,当前为80个字节
    public static final int HEADER_SIZE = 80;   

    static final long ALLOWED_TIME_DRIFT = 2 * 60 * 60; // Same value as Bitcoin Core.

    /**
     * A constant shared by the entire network: how large in bytes a block is allowed to be. One day we may have to
     * upgrade everyone to change this, so Bitcoin can continue to grow. For now it exists as an anti-DoS measure to
     * avoid somebody creating a titanically huge but valid block and forcing everyone to download/store it forever.
     */
    //全网共享的常量,用于表示区块的最大字节数。随着比特币会持续的发展,日后升级网络时可能会变更该数字。
    //目前该值作为解决拒接攻击的一种措施,避免有人创建巨量的区块,造成整个网络的资源浪费。
    public static final int MAX_BLOCK_SIZE = 1 * 1000 * 1000;
    /**
     * A "sigop" is a signature verification operation. Because they're expensive we also impose a separate limit on
     * the number in a block to prevent somebody mining a huge block that has way more sigops than normal, so is very
     * expensive/slow to verify.
     */
    //sigop是签名校验操作,因此这个操作需要大量的资源,因此需要限制大小,防止资源浪费或降低网络性能
    public static final int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE / 50;

    /** A value for difficultyTarget (nBits) that allows half of all possible hash solutions. Used in unit testing. */
    //工作量的难度目标
    public static final long EASIEST_DIFFICULTY_TARGET = 0x207fFFFFL;
           

2. 区块中的核心变量定义

private long version;               //区块链的版本号
    private Sha256Hash prevBlockHash;   //前一个区块的hash地址
    private Sha256Hash merkleRoot;      //交易标识的merkle根
    private long time;                  //区块创建时间戳
    private long difficultyTarget; // "nBits"   //区块工作难度目标
    private long nonce;                 //用于证明区块工作量的参数

    // TODO: Get rid of all the direct accesses to this field. It's a long-since unnecessary holdover from the Dalvik days.
    /** If null, it means this object holds only the headers. */
    //区块中存储的交易数据
    @Nullable List<Transaction> transactions;

    /** Stores the hash of the block. If null, getHash() will recalculate it. */
    //当前区块的hash地址
    private Sha256Hash hash;
           

3. 解析区块二进制数据

//从原始字节数据中构造区块对象
    @Override
    protected void parse() throws ProtocolException {
        // header
        cursor = offset;            
        version = readUint32();     //读取4个字节的版本号
        prevBlockHash = readHash(); //读取前一个区块的hash地址
        merkleRoot = readHash();    //读取merkle交易树的根值
        time = readUint32();        //读取区块的创建时间戳
        difficultyTarget = readUint32();    //读取区块的难度目标
        nonce = readUint32();               //读取区块用于计算难度的随机数
        //通过区块头计算当前区块的hash地址
        hash = Sha256Hash.wrapReversed(Sha256Hash.hashTwice(payload, offset, cursor - offset));
        headerBytesValid = serializer.isParseRetainMode();  //是否缓存区块的hash地址

        // transactions
        //解析区块内的交易数据
        parseTransactions(offset + HEADER_SIZE);

        //计算区块的字节数
        length = cursor - offset;
    }
           

4. 解析区块内的交易数据

//解析区块内的交易数据
    protected void parseTransactions(final int transactionsOffset) throws ProtocolException {
        cursor = transactionsOffset;            //设置读取数据的起始偏移地址

        optimalEncodingMessageSize = HEADER_SIZE;   //初始化编码后的区块大小
        if (payload.length == cursor) {
            // This message is just a header, it has no transactions.
            transactionBytesValid = false;
            return;
        }

        int numTransactions = (int) readVarInt();   //获取区块内的交易数据量

        //累加编码后的区块大小,不同的整数经过编码后,占用的存储空间不一样,因此需要通过VarInt进行计算
        optimalEncodingMessageSize += VarInt.sizeOf(numTransactions);

        transactions = new ArrayList<>(numTransactions);

        //逐一构造区块内的交易数据
        for (int i = 0; i < numTransactions; i++) {
            //构造区块内的交易数据
            Transaction tx = new Transaction(params, payload, cursor, this, serializer, UNKNOWN_LENGTH);
            // Label the transaction as coming from the P2P network, so code that cares where we first saw it knows.
            tx.getConfidence().setSource(TransactionConfidence.Source.NETWORK);
            transactions.add(tx);
            cursor += tx.getMessageSize();
            optimalEncodingMessageSize += tx.getOptimalEncodingMessageSize();
        }
        transactionBytesValid = serializer.isParseRetainMode();
    }
           

上一篇:(一) 区块链数据结构-区块链

下一篇:(三) 区块链数据结构 – 交易