Unlike bitcoin, Simplechain serves as an intelligent contract platform. When each transaction is executed as a message in the Simplechain virtual machine, a transaction Receipt is obtained. This transaction receipt records the processing result information about this transaction:
The receipt information is divided into three parts: consensus information, transaction information, and block information. The following describes all kinds of information.
Introduction to transaction receipt
Transaction receipt consensus information
Consensus means that this part of information is also involved in the verification when verifying the validity of the block. The reason for this information to participate in verification is to ensure that transactions must be executed in a fixed order in the block and record the status information after the transaction is executed. This can strengthen the transaction order.
//core/state_processor.go:104
var root []byte
if config.IsByzantium(header.Number) {
statedb.Finalise(true)
} else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
}
//...
receipt := types.NewReceipt(root, failed, *usedGas)
- CumulativeGasUsed:the accumulated Gas consumed by executed transactions in the block, including the current transaction.
- Logs: the list of smart contract events generated by the execution of the current transaction.
- Bloom:an event Bloom filter extracted from Logs to quickly detect whether an event on a topic exists in Logs.
How does this information participate in consensus verification? In fact, only the receipt hash is involved in the verification, while the receipt hash calculation only contains this information. First, the root hash value of Merkel tree that obtains the receipt information of the entire block during verification. Then judge whether the hash value is the same as the content defined by the block header.
//core/block_validator.go:92
receiptSha := types.DeriveSha(receipts)
if receiptSha != header.ReceiptHash {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)",
header.ReceiptHash, receiptSha)
}
The root hash value generated by the function types. Metastesha is to form the Merck tree with the RLP encoding information of the list element (here is the transaction receipt), and finally obtain the hash value of the list.
//core/types/derive_sha.go:32
func DeriveSha(list DerivableList) common.Hash {
keybuf := new(bytes.Buffer)
trie := new(trie.Trie)
for i := 0; i < list.Len(); i++ {
keybuf.Reset()
rlp.Encode(keybuf, uint(i))
trie.Update(keybuf.Bytes(), list.GetRlp(i))
}
return trie.Hash()
}
// core/types/receipt.go:237
func (r Receipts) GetRlp(i int) []byte {
bytes, err := rlp.EncodeToBytes(r[i])
if err != nil {
panic(err)
}
return bytes
}
The transaction receipt implements the RLP encoding interface. In the method EncodeRLP, a private tunntrlp is built.
//core/types/receipt.go:119
func (r *Receipt) EncodeRLP(w io.Writer) error {
return rlp.Encode(w,
&receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs})
}
Transaction receipt transaction information
This part of information records the transaction information corresponding to the receipt, including:
- TxHash : the transaction hash corresponding to the transaction receipt.
- ContractAddress: records the address of the new contract when the transaction is deployed.
//core/state_processor.go:118
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
}
- GasUsed: the Gas handling fee consumed in the execution of this transaction
Transaction receipt block information
This part of information is to facilitate external reading of transaction receipts, not only to know the transaction execution, but also to specify the number of transactions in which block the transaction belongs.
- BlockHash: the hash of the block where the transaction is located.
- BlockNumber: the height of the block where the transaction is located.
- TransactionIndex:the sequence number of the transaction in the block.
These three information are specified in real time when the transaction receipt is read from the Leveldb database.
//core/rawdb/accessors_chain.go:315
receipts := make(types.Receipts, len(storageReceipts))
logIndex := uint(0)
for i, receipt := range storageReceipts {
//...
receipts[i] = (*types.Receipt)(receipt)
receipts[i].BlockHash = hash
receipts[i].BlockNumber = big.NewInt(0).SetUint64(number)
receipts[i].TransactionIndex = uint(i)
}
Transaction receipt structure
Transaction receipt is the transaction execution result information sorted out according to the results after the Simplechain virtual machine processes the transaction. It reflects the Simplechain changes and transaction execution status before and after the transaction is executed.
The construction details have already been mentioned before and will not be described in detail. The complete transaction receipt construction code is given here.
// core/state_processor.go:94
context := NewEVMContext(msg, header, bc, author)
vmenv := vm.NewEVM(context, statedb, config, cfg)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
if err != nil {
return nil, 0, err
}
var root []byte
if config.IsByzantium(header.Number) {
statedb.Finalise(true)
} else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
}
*usedGas += gas
receipt := types.NewReceipt(root, failed, *usedGas)
receipt.TxHash = tx.Hash()
receipt.GasUsed = gas
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
}
receipt.Logs = statedb.GetLogs(tx.Hash())
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
receipt.BlockHash = statedb.BlockHash()
receipt.BlockNumber = header.Number
receipt.TransactionIndex = uint(statedb.TxIndex())
return receipt, gas, err
Transaction receipt storage
As an intermediate product of transaction execution, the transaction receipt is used to quickly obtain the execution details of a transaction. Simplechain stores transaction receipts in real time when following block storage. However, in order to reduce the storage capacity, only necessary content is stored.
First, convert the transaction receipt object to simplified content during storage.
//core/rawdb/accessors_chain.go:338
storageReceipts := make([]*types.ReceiptForStorage, len(receipts))
for i, receipt := range receipts {
storageReceipts[i] = (*types.ReceiptForStorage)(receipt)
}
Streamlined content is a structure that is specifically defined for storage. RLP encoding is used to store the transaction receipt set.
//core/rawdb/accessors_chain.go:342
bytes, err := rlp.EncodeToBytes(storageReceipts)
if err != nil {
log.Crit("Failed to encode block receipts", "err", err)
}
if err := db.Put(blockReceiptsKey(number, hash), bytes); err != nil {
log.Crit("Failed to store block receipts", "err", err)
}
Look at the EncodeRLP method of tunntforstorage to know what content is stored
//core/types/receipt.go:179
func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
enc := &receiptStorageRLP{
PostStateOrStatus: (*Receipt)(r).statusEncoding(),
CumulativeGasUsed: r.CumulativeGasUsed,
TxHash: r.TxHash,
ContractAddress: r.ContractAddress,
Logs: make([]*LogForStorage, len(r.Logs)),
GasUsed: r.GasUsed,
}
for i, log := range r.Logs {
enc.Logs[i] = (*LogForStorage)(log)
}
return rlp.Encode(w, enc)
}
According to the EncodeRLP method, it can be concluded that only part of the content is stored during storage, and the Logs are also specially processed. LogForStorage