Transaction and Receipt Data Structure and Assembly Process¶
Tags: “java-sdk” “assembly transaction” “data structure” “transaction” “transaction receipt” “
1. Transaction data structure interpretation¶
The transaction of 3.0 is defined in ‘bcos-tars-protocol / bcos-tars-protocol / tars / Transaction.tars’ in the FISCO-BCOS repository. You can see the link: Transaction.tars。The data structure is as follows:
module bcostars {
struct TransactionData {
1 optional int version; / / Transaction version number. Currently, there are three types of transactions: v0, v1, and v2
2 optional string chainID; / / Chain name
3 optional string groupID; / / group name
4 optional long blockLimit; / / Block height of transaction limit execution
5 optional string nonce; / / Transaction uniqueness identification
6 optional string to; / / The contract address of the transaction call
7 optional vector<byte> input; / / Parameters of the transaction call contract, encoded by ABI / Scale
8 optional string abi; / / The JSON string of the ABI. We recommend that you add the ABI when deploying a contract
9 optional string value; / / v1 New transaction field, original transfer amount
10 optional string gasPrice; / / The new field in the v1 transaction. The unit price of gas during execution(gas/wei)
11 optional long gasLimit; / / The upper limit of the gas used when the transaction is executed
12 optional string maxFeePerGas; / / v1 new transaction field, EIP1559 reserved field
13 optional string maxPriorityFeePerGas; / / v1 new transaction field, EIP1559 reserved field
14 optional vector<byte> extension; / / v2 new fields for additional storage
};
struct Transaction {
1 optional TransactionData data; / / Transaction Base Field
2 optional vector<byte> dataHash; / / Hash value of the transaction base field data
3 optional vector<byte> signature; / / Signature of the hash value byte of the transaction base field data
4 optional long importTime; / / Time when the transaction arrives in the trading pool
5 optional int attribute; / / Transaction attribute. EVM transaction is 1. Default value: 0;WASM deal for 2;WASM deployment deal is 2|| 8;
// 6 optional string source;
7 optional vector<byte> sender; / / The EOA address from which the transaction originated
8 optional string extraData; / / Transaction extra field, which does not calculate the hash
};
};
2. Transaction receipt data structure interpretation¶
The transaction receipt of 3.0 is defined in ‘bcos-tars-protocol / bcos-tars-protocol / tars / TransactionReceipt.tars’ in the FISCO-BCOS warehouse. You can see the link: TransactionReceipt.tars。The data structure is as follows:
module bcostars {
struct LogEntry { / / Event structure
1 optional string address; / / Event Contract Address
2 optional vector<vector<byte>> topic; / / The topic of the event's indexed field, up to 4
3 optional vector<byte> data; / / Value other than the indexed field of the event, ABI encoding
};
struct TransactionReceiptData { / / Transaction receipt basis type
1 require int version; / / The version of the transaction receipt. Currently, there are v0, v1, and v2
2 optional string gasUsed; / / Gas used by the transaction
3 optional string contractAddress; / / The contract address of the transaction call. If the transaction is a deployment contract, the new contract address
4 optional int status; / / Transaction execution status, 0 is successful;Non-0 is unsuccessful, and an error message will be written in output (Error(string) value after ABI encoding)
5 optional vector<byte> output; / / Transaction execution return value
6 optional vector<LogEntry> logEntries; / / Event list
7 optional long blockNumber;/ / Block height where the transaction is executed
8 optional string effectiveGasPrice; / / The gas unit price (gas / wei) that takes effect when the transaction is executed
};
struct TransactionReceipt { / / Transaction receipt type
1 optional TransactionReceiptData data; / / Transaction receipt basis type
2 optional vector<byte> dataHash; / / Hash value of the transaction receipt base type data
3 optional string message; / / Transaction receipt return information
};
};
3. The assembly process of the transaction¶
As shown above, the SDK needs to assemble the ‘TransactionData’ first, then assemble the transaction data structure as’ Transaction ‘, and finally send it to the blockchain node。Specific steps are as follows:
The actual parameters of the transaction call contract, using ABI / Scale encoding as the ‘input’ field;
Enter the ‘blockLimit’ field, which is usually the height of the current block+600;
Incoming ‘nonce’ field, usually a random hexadecimal string;
Pass in other parameters to construct the ‘TransactionData’ structure object;
Hash the object of ‘TransactionData’, the hash calculation algorithm can be found in Section 4; -Use the key to perform signature calculation on the hash value (byte array) calculated in the previous step to obtain the signature;
Pass in other parameters to construct the ‘Transaction’ structure object;
Encode the ‘Transaction’ structure object using the ‘Tars’ encoding;
Get the final transaction raw data, send to the chain。
4. TransactionData hash calculation algorithm and example¶
TransactionData performs a hash calculation by assembling the bytes of all the fields in the object and finally performing a hash calculation on the byte array。C++An example of an implementation is as follows:
int32_t version = boost::endian::native_to_big((int32_t)hashFields.version);
hasher.update(version);
hasher.update(hashFields.chainID);
hasher.update(hashFields.groupID);
int64_t blockLimit = boost::endian::native_to_big((int64_t)hashFields.blockLimit);
hasher.update(blockLimit);
hasher.update(hashFields.nonce);
hasher.update(hashFields.to);
hasher.update(hashFields.input);
hasher.update(hashFields.abi);
// if version == 1, update value, gasPrice, gasLimit, maxFeePerGas, maxPriorityFeePerGas to
// hashBuffer calculate hash
if ((uint32_t)hashFields.version >= (uint32_t)bcos::protocol::TransactionVersion::V1_VERSION)
{
hasher.update(hashFields.value);
hasher.update(hashFields.gasPrice);
int64_t bigEndGasLimit = boost::endian::native_to_big((int64_t)hashFields.gasLimit);
hasher.update(bigEndGasLimit);
hasher.update(hashFields.maxFeePerGas);
hasher.update(hashFields.maxPriorityFeePerGas);
}
if ((uint32_t)hashFields.version >= (uint32_t)bcos::protocol::TransactionVersion::V2_VERSION)
{
hasher.update(hashFields.extension);
}
hasher.final(out);
An example of a Java implementation is as follows:
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// version
byteArrayOutputStream.write(toBytesPadded(BigInteger.valueOf(getVersion()), 4));
// chainId
byteArrayOutputStream.write(getChainID().getBytes());
// groupId
byteArrayOutputStream.write(getGroupID().getBytes());
// blockLimit
byteArrayOutputStream.write(toBytesPadded(BigInteger.valueOf(getBlockLimit()), 8));
// nonce
byteArrayOutputStream.write(Hex.decode(getNonce()));
// to
byteArrayOutputStream.write(getTo().getBytes());
// input
byteArrayOutputStream.write(Hex.decode(getInput()));
// abi
byteArrayOutputStream.write(getAbi().getBytes());
if (getVersion() == TransactionVersion.V1.getValue()) {
byteArrayOutputStream.write(getValue().getBytes());
byteArrayOutputStream.write(getGasPrice().getBytes());
byteArrayOutputStream.write(toBytesPadded(BigInteger.valueOf(getGasLimit()), 8));
byteArrayOutputStream.write(getMaxFeePerGas().getBytes());
byteArrayOutputStream.write(getMaxPriorityFeePerGas().getBytes());
}
if (getVersion() == TransactionVersion.V2.getValue()) {
byteArrayOutputStream.write(getExtension());
}
return byteArrayOutputStream.toByteArray();
5. TransactionReceiptData hash calculation algorithm and example¶
As described in Section 4, TransactionReceiptData’s hash is also calculated by assembling the bytes of all the fields within the object and finally hashing the byte array。C++An example of an implementation is as follows:
int32_t version = boost::endian::native_to_big((int32_t)hashFields.version);
hasher.update(version);
hasher.update(hashFields.gasUsed);
hasher.update(hashFields.contractAddress);
int32_t status = boost::endian::native_to_big((int32_t)hashFields.status);
hasher.update(status);
hasher.update(hashFields.output);
if(hashFields.version >= int32_t(bcos::protocol::TransactionVersion::V1_VERSION))
{
hasher.update(hashFields.effectiveGasPrice);
}
for (auto const& log : hashFields.logEntries)
{
hasher.update(log.address);
for (auto const& topicItem : log.topic)
{
hasher.update(topicItem);
}
hasher.update(log.data);
}
int64_t blockNumber = boost::endian::native_to_big((int64_t)hashFields.blockNumber);
hasher.update(blockNumber);
hasher.final(out);
An example of a Java implementation is as follows:
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(toBytesPadded(BigInteger.valueOf(getVersion()), 4));
byteArrayOutputStream.write(getGasUsed().getBytes());
byteArrayOutputStream.write(getContractAddress().getBytes());
byteArrayOutputStream.write(toBytesPadded(BigInteger.valueOf(getStatus()), 4));
byteArrayOutputStream.write(getOutput().getBytes());
if (getVersion() == TransactionVersion.V1.getValue()) {
byteArrayOutputStream.write(getEffectiveGasPrice().getBytes());
}
for (Logs logEntry : getLogEntries()) {
byteArrayOutputStream.write(logEntry.getAddress().getBytes());
for (String topic : logEntry.getTopics()) {
byteArrayOutputStream.write(topic.getBytes());
}
byteArrayOutputStream.write(logEntry.getData().getBytes());
}
byteArrayOutputStream.write(toBytesPadded(getBlockNumber(), 8));
return byteArrayOutputStream.toByteArray();