区块由交易组成。区块体中包含若干项交易数据。
交易主要包含两类数据:交易输入和交易输出。
- 交易输入用来指明钱的来源
- 交易输出用来指明钱的去向
除了交易输入和交易输出外,交易中还包含版本号和锁定时间。交易数据的存储结构如下:
交易对象中的各个字段含义如下:
字段 | 大小 | 描述 |
---|---|---|
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); //锁定脚本的内容
}
上一篇:(二) 区块链数据结构-区块
下一篇:(四) 区块链数据结构 – 脚本