交易收据包含交易的产出(状态和日志)。收据数据存储在状态数据库,根哈希值存储在block的header中。
区块中智能合约的信息存储有2种方式,账户存储和日志存储。
账户存储(Account Storage)定义了智能合约状态以及可访问的合约。在下图所示的state Trie里
日志存储(Log Storage)是用来存储中间状态,这些状态其实并不是给合约使用的,一般是给其他第三方dAPP来访问(比如前后端程序,以及一些分析网站)。日志存储比起账户存储便宜的多。如下图所示的receipt trie
以一个简单的erc721(NFT)合约为例,看看合约是如何存储的:
// 部分代码
contract ERC721 is IERC721 {
// 存储在 receipt trie 里
event Transfer(address indexed from, address indexed to, uint indexed id);
event Approval(address indexed owner, address indexed spender, uint indexed id);
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
// Mapping from token ID to owner address
// 存储在 state trie 里
mapping(uint => address) internal _ownerOf;
// Mapping owner address to token count
// 存储在 state trie 里
mapping(address => uint) internal _balanceOf;
。。。。
function _mint(address to, uint id) internal {
require(to != address(0), "mint to zero address");
require(_ownerOf[id] == address(0), "already minted");
_balanceOf[to]++;
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
}
Token 所有权(owner)->账户存储:智能合约必须显式的展示谁拥有token。合约能证明谁是拥有者,并提供transfer函数,token id和token owner必须存储在账户存储中
Token 所有权变更历史->日志存储:合约里只需要知道Token当前归谁所拥有。投资分析或其他需求,要想追踪token的所有权变更历史,需要到日志里查找(谁minted或者交易)。
UI 通知->日志存储:当一个NFT被mint的时候,dAPP希望能收到通知,了解详情。因为是异步交易,智能合约没法直接返回结果给前端(或者后端)。所以,需要mint结束的时候,会写到log里,这样dApps可以监听日志,以便获得通知来展示他们。
链下触发器(off chain triggers)->日志存储:如果你需要将你的NFT转给(transfer)其他链(比如你在其他链部署了游戏),你可以新建一个转账(transfer)给网关,交易的时候会记录日志。网关将会获取数据,在其他链mint
智能合约如果想记录日志,代码里可以使用emit,这样就可以将收据记录到日志记录中
contract MyNFTCollection{
// 隐射token ID到拥有者地址
// 存储在Account Storage
mapping(uint256 => address) private _owners;
// Declaration of Event
event Transfer(address indexed _from, address indexed _to, uint256 tokenId);
//
function _transfer(address from, address to, uint256 tokenId) internal virtual{
// written to the log records
emit Transfer(from, to, tokenId);
}
}
每个交易只有一个交易收据。在日志里,收据包括 状态,gas 使用量, logs bloom
Status
值是0或1,标记交易是否成功。因为是异步交易,调用者需要等待交易结果,可以通过这个值来判断。
如果gas费不够(也有可能是因为其他原因),整个交易可能会回退。调用者只能读取status,回退的信息无法直接获取。要想获取完整信息需要检查client 节点 traces,它包含了信息的详情和执行。
Gas Used
整个交易的gas使用量
Logs
日志collection。一条日志包括主题(topics)和数据(data)。主题List包括事件签名,indexed 内容。一条日志可以有4个主题。主题总容量受限,最好不要存储大量数据。通常来说,想作为搜索关键字的,可以存储为topic。其他数据存储到data里
以一段简单的代码为例
emit Transfer(from, to, tokenID)
因为 event name, from, to 都是indexed, 下面的查询效率就比较高了
- 所有Token,今天的交易记录
- 过去一个小时,这个用户(钱包地址)的销售token记录
- 上周,这个用户(钱包地址)购买记录
因为tokenID并没有indexed,存储在data域里,类似“这个tokenID今天是否有交易”这种查询,会比较慢
Logs Bloom
查询指定用户(钱包)在某个块(block)里,所有销售的token
我们可以分析日志来完成上面的查询,因为“from”地址是indexed的。但是要完成查询,需要遍历整个块里的交易。
假设块里有500个交易,我们就需要遍历所有交易的logs(包括topics和data)。那我们是否可以创建一个对象,以便我们可以快速查询交易日志里是否有我们需要信息呢?Bloom Filter 就是干这事的。
从一个简单的例子开始,我们来了解Bloom Filter。比如我们要在一个list里查找是否包含某个用户。最简单的方法就是遍历。显而易见,如果数据量大的话,遍历的成本还是比较高的,速度也慢。一个比较简单的方法就是,把所有用户的名字做hash,然后映射到一个地方,如下图
如图所示,最终的映射结构是每个结果的并集。来看看如何查询
bloom filter 能得到2种结果:“可能存在”和“一定不存在”
让我们回到之前的区块例子,假设块中的500个交易,有2个交易符合条件。我们就可以查询logs bloom(topics创建的)是否存在用户账户(地址),如果命中,再到交易中查询详情。这样会大幅减少查询时间。
EVM(eth虚拟机)会汇聚所有 交易级别的 logs bloom ,创建一个块级别的logs bloom 。
查询指定用户(钱包)在过去1天里,所有销售的token
这个查询就需要跨多个块,我们可以在块的bloom filter 里先查找,如果有的话,再到块里找。
Trie(树)结构
现在我们知道了什么是收据,接着来了解一下数据结构
这种组织结构对轻节点友好。因为轻节点只存储了块的header。类似“过去N天,从X钱包转出的交易”,轻节点必须去全节点查询。轻节点blocker header里的merkle tree的root hash,能验证全节点里的tree是否是安全的。文章来源:https://www.toymoban.com/news/detail-429582.html
参考:
https://medium.com/coinmonks/ethereum-data-transaction-receipt-trie-and-logs-simplified-30e3ae8dc3cf文章来源地址https://www.toymoban.com/news/detail-429582.html
到了这里,关于eth以太坊数据块-交易收据树和日志的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!