(三) 区块链数据结构 – 交易

区块由交易组成。区块体中包含若干项交易数据。


交易

交易主要包含两类数据:交易输入和交易输出。
- 交易输入用来指明钱的来源
- 交易输出用来指明钱的去向

除了交易输入和交易输出外,交易中还包含版本号和锁定时间。交易数据的存储结构如下:

交易数据存储格式

交易对象中的各个字段含义如下:

字段 大小 描述
version 4个字节 明确该笔交易参考的版本规则
numInputs 1-9个字节 交易中包含的输入数量
inputs 不定长 交易中包含的输入数据
numOutputs 1-9个字节 交易中包含的输出数量
outputs 不定长 交易中包含的输出数据
lockTime 4个字节 交易锁定时间

注:如果锁定时间为0,代表立即执行,无需锁定。如果该值小于5亿,则代表区块链到达该高度前,交易不会被执行,如果该值大于5亿,则该值代表Unix纪元时间戳,交易在该时间之后执行。

核心代码 - 变量定义

    // These are bitcoin serialized.
    private long version;                           //交易遵循的版本号
    private ArrayList<TransactionInput> inputs;     //交易的金钱来源
    private ArrayList<TransactionOutput> outputs;   //交易的金钱去向

    private long lockTime;                          //交易的锁定时间

核心代码 - 数据解析

    //解析区块原始字节数据,构造交易对象
    @Override
    protected void parse() throws ProtocolException {
        cursor = offset;

        version = readUint32();         //读取版本号
        optimalEncodingMessageSize = 4;

        // First come the inputs.
        long numInputs = readVarInt();  //读取输入交易量
        optimalEncodingMessageSize += VarInt.sizeOf(numInputs);

        inputs = new ArrayList<>((int) numInputs);

        //逐一解析输入交易对象
        for (long i = 0; i < numInputs; i++) {
            //构造输入交易对象
            TransactionInput input = new TransactionInput(params, this, payload, cursor, serializer);
            inputs.add(input);
            long scriptLen = readVarInt(TransactionOutPoint.MESSAGE_LENGTH);
            optimalEncodingMessageSize += TransactionOutPoint.MESSAGE_LENGTH + VarInt.sizeOf(scriptLen) + scriptLen + 4;
            cursor += scriptLen + 4;
        }

        // Now the outputs
        long numOutputs = readVarInt(); //读取输出交易量
        optimalEncodingMessageSize += VarInt.sizeOf(numOutputs);
        outputs = new ArrayList<>((int) numOutputs);

        //逐一解析输出交易对象
        for (long i = 0; i < numOutputs; i++) {
            //构造输出交易对象
            TransactionOutput output = new TransactionOutput(params, this, payload, cursor, serializer);
            outputs.add(output);
            long scriptLen = readVarInt(8);
            optimalEncodingMessageSize += 8 + VarInt.sizeOf(scriptLen) + scriptLen;
            cursor += scriptLen;
        }
        lockTime = readUint32();
        optimalEncodingMessageSize += 4;
        length = cursor - offset;
    }

交易输入

交易输入指明了交易的金钱来源。在比特币系统中,交易的金钱来源,并不来自于某一特定账户,而来自于其他人的输出。
比如A和B分别转给C一笔金钱,则当C向D支付时,需要用A和B的输出作为输入。

交易输入中,存储了其关联的交易输出指针,同时还存储了该输出的解锁脚本。

交易输入结构如下:

交易输入存储格式

交易输入中,各个字段的含义如下:

字段 大小 描述
hash 32个字节 关联输出所在的交易Hash地址
index 4个字节 关联输出在其交易输出集合中的索引
scriptLen 1-9个字节 解锁脚本长度
scriptBytes 不定长 解锁脚本数据
sequence 4个字节 序列号,暂未使用

注:只有解锁脚本正确,才能对输出进行消费。解锁脚本需要使用当前用户的秘钥进行签名,因此只有当前用户,能使用别人来的输出。

核心代码 - 变量定义

    // Allows for altering transactions after they were broadcast. Values below NO_SEQUENCE-1 mean it can be altered.
    private long sequence;
    // Data needed to connect to the output of the transaction we're gathering coins from.
    //关联的交易输出
    private TransactionOutPoint outpoint;
    // The "script bytes" might not actually be a script. In coinbase transactions where new coins are minted there
    // is no input transaction, so instead the scriptBytes contains some extra stuff (like a rollover nonce) that we
    // don't care about much. The bytes are turned into a Script object (cached below) on demand via a getter.
    // 脚本字节数组可能并不是实际的脚本,在创世区块中,没有输入交易,该字段可能存储其他数据。正常情况下,该数据会转换成脚本对象
    private byte[] scriptBytes;

核心代码 - 数据解析

    //通过区块的原始字节数据,构造输入交易对象
    @Override
    protected void parse() throws ProtocolException {
        //构造该输入交易对应的输出(接收地址)
        //解析数据时,会解析出对应输出所在交易的Hash地址以及交易输出对应的索引值
        outpoint = new TransactionOutPoint(params, payload, cursor, this, serializer);
        cursor += outpoint.getMessageSize();
        int scriptLen = (int) readVarInt(); //读取脚本长度
        length = cursor - offset + scriptLen + 4;

        scriptBytes = readBytes(scriptLen); //读取解锁脚本数据
        sequence = readUint32();            //序列号,目前未使用
    }

交易输出

交易输出指明了钱的去向,拥有该输出,并用私钥解密后,方可作为其他交易的输入,进行消费。

交易输出中,存储了支付的金额以及锁定脚本。交易输出的存储结构如下:

交易输出存储格式

交易输出中,各个字段含义如下:

字段 大小 描述
value 8个字节 支付金额(单位为聪)
scriptLen 1-9个字节 锁定脚本长度
scriptBytes 1-9个字节 锁定脚本数据

注:只有接收者,通过秘钥加密生成解锁脚本后,方可继续使用该输出作为输入,继续消费。

核心代码 - 变量定义

    // The output's value is kept as a native type in order to save class instances.
    //支付金额
    private long value;

    // A transaction output has a script used for authenticating that the redeemer is allowed to spend
    // this output.
    //锁定脚本
    private byte[] scriptBytes;

核心代码 - 数据解析

    //解析交易输出数据,包含输出金额和锁定脚本(接收者可以解锁该脚本)
    @Override
    protected void parse() throws ProtocolException {
        value = readInt64();                    //获取交易输出的金额
        scriptLen = (int) readVarInt();         //锁定脚本的长度
        length = cursor - offset + scriptLen;
        scriptBytes = readBytes(scriptLen);     //锁定脚本的内容
    }

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

下一篇:(四) 区块链数据结构 – 脚本

猜你喜欢

转载自blog.csdn.net/maerdym/article/details/79753617