原标题:《以太坊智能合约ABI、事件和日志》
作者:北京大学胡悦阳,本文仅代表作者观点
原文链接:https://mp.weixin.qq.com/s/QKz8r1MpntGuw_xM9gh9_w
智能合约是什么
智能合约是在区块链上运行的应用或程序。通常情况下,它们为一组具有特定规则的数字化协议,且该协议能够被强制执行。这些规则由计算机源代码预先定义,网络节点会复制和执行这些计算机源码。
简单的说,智能合约就是区块链上一个包含合约代码和存储空间的虚拟账户。
智能合约的行为由合约代码控制,而智能合约的账户存储则保存了合约的状态。
在以太坊平台上,智能合约的代码运行在以太坊虚拟机中,EVM是一个图灵完备的虚拟机,是以太坊的核心。
在以太坊的点对点网络中,每个全节点上都包含一个以太坊虚拟机,当节点需要打包或验证区块时,便将交易相关的可执行代码送入EVM中执行,执行的结果更新了以太坊账户的状态并被记录在区块链上。
智能合约的操作
创建智能合约的流程:
编写智能合约的代码编译智能合约的代码变成可以在EVM上执行的bytecode,同时可以通过编译获得智能合约的ABI部署到区块链,通过一个交易将bytecode存储在链上,并获得合约地址调用智能合约的流程:
发起一笔指向智能合约地址的交易,智能合约代码分布式地运行在网络中每个节点的以太坊虚拟机中,然后会获得交易的回执。回执保存交易的输入参数、输出、执行状态等。
举一个例子,首先在Remix平台编写一个智能合约Hello.sol
pragma?solidity?>=0.4.21;contract?Hello{????string?message;????event?SetMessage(string?_message);????function?set(string?memory?_message)?public?{????????message?=?_message;??????????emit?SetMessage(_message);????}????function?get()?public?view?returns(string?memory){????????return?message;????}}
编译成字节码,发起一笔交易部署到区块链中,得到交易回执。
这样就成功的将合约部署到区块链网络中了。
ABI是什么
上文提到调用智能合约,需要发起一笔指向合约地址的交易,以太坊节点会根据输入的信息,选择要执行合约中的哪一个函数和函数的参数。如何知道智能合约提供哪些函数以及参数要求呢,就需要用到ABI了。
合约ABI是在以太坊生态系统中与合约进行交互的标准方法,既可以从区块链外部进行,也可以用于合约间的交互。
ABI类似程序中的接口文档,描述了字段名称、字段类型、方法名称、参数名称、参数类型、方法返回值类型等。
通俗的解释:
ABI是合约接口的说明ABI定义与合约进行交互的数据编码解码规则以之前的Hello.sol为例,在编译合约的时候可以生成合约的ABI
,????????"name":?"SetMessage",????????"type":?"event"????},????{????????"inputs":?,????????"name":?"get",????????"outputs":?,????????"stateMutability":?"view",????????"type":?"function"????},????{????????"inputs":?,????????"name":?"set",????????"outputs":?,????????"stateMutability":?"nonpayable",????????"type":?"function"????}]
沉睡八年某以太坊早期投资者将200枚ETH转入Coinbase:7月20日消息,据Twitter用户????function?baz(uint32?x,?bool?y)?public?pure?returns?(bool?r)?{?r?=?x?>?32?||?y;?}????function?sam(bytes?memory,?bool,?uint?memory)?public?pure?{}}
案例1
函数:baz(uint32,bool)?
调用:baz(69,true)
?0xcdcd77c0:函数选择器,在python中通过sha3("set(string)").hex()得到0xcdcd77c0
?0x0000000000000000000000000000000000000000000000000000000000000045,十进制69,转成16进制为45,因为是正数,高位补0至32bytes
?0x0000000000000000000000000000000000000000000000000000000000000001,bool类型,true=1,false=0,高位补0
最终的字节码为
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
会返回bool类型.在这个调用中,返回值是false,它的输出将是单字节数组
0x0000000000000000000000000000000000000000000000000000000000000000
案例2
函数:bar(bytes3memory)?
调用:bar()
?0xfce353f6:函数选择器,在python中通过sha3("bar(bytes3)").hex()得到0xfce353f6
?固定长度不需要计算偏移量
?0x6162630000000000000000000000000000000000000000000000000000000000,字符串abc转成16进制后为616263,低位补0
以太坊未确认交易为130,497笔:金色财经消息,据OKLink数据显示,以太坊未确认交易130,497笔,当前全网算力为381.86TH/s,全网难度为5.08P,当前持币地址为55,186,574个,同比增加206,551个,24h链上交易量为3,402,743.55ETH,当前平均出块时间为14s。[2021/2/21 17:35:19]
?0x6465660000000000000000000000000000000000000000000000000000000000,字符串def转成16进制后为646566,低位补0
字符串转16进制的python参考代码
import?binasciis?=?'abc'str_16?=?binascii.b2a_hex(s.encode('utf-8'))??#?字符串转16进制print(str_16)
案例3
函数:sam(bytes,bool,uint)
调用:sam("dave",true,")
?a5643bf2:函数选择器,在python中通过sha3("sam(bytes,bool,uint256)").hex()得到a5643bf2.请注意,将uint替换为其规范表示形式uint256。
?0x0000000000000000000000000000000000000000000000000000000000000060:动态类型,计算偏移量。这个的偏移量是指实际存储值的位置,由于这个函数有3个变量,那么实际存储值的位置就是第四个32bytes位置,也就是说偏移量等于3*32bytes=96,转成16进制后就是对应的值
?0x0000000000000000000000000000000000000000000000000000000000000001:第二个参数,布尔值true
?0x00000000000000000000000000000000000000000000000000000000000000a0:动态类型,计算偏移量,这个偏移量就等于参数长度3*32bytes+前面的动态参数参数占有的长度,那么具体的值就是3*32bytes+(1*32bytes+1*32bytes)=5*32bytes=160,转成16进制就是a0,高位补全就是对应的值
?0x0000000000000000000000000000000000000000000000000000000000000004:第一个参数的数据部分,代表元素中字节数组的长度,在这种情况下为4。
?0x6461766500000000000000000000000000000000000000000000000000000000:“dave”的utf-8编码,填充为32字节。
?0x0000000000000000000000000000000000000000000000000000000000000003:第三个参数的数据部分,代表数组中元素的个数,在这种情况下为3.
?0x0000000000000000000000000000000000000000000000000000000000000001:第三个参数的第一项。
?0x0000000000000000000000000000000000000000000000000000000000000002:第三个参数的第二项。
?0x0000000000000000000000000000000000000000000000000000000000000003:第三个参数的第三项。
最终的字节码为
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
以太坊未确认交易为115,418笔:金色财经消息,据OKLink数据显示,以太坊未确认交易115,418笔,当前全网算力为276.35TH/s,全网难度为3.66P,当前持币地址为52,061,185个,同比增加118,271个,24h链上交易量为2,537,781.89ETH,当前平均出块时间为13s。[2020/12/27 15:49:25]
综上所述,ABI是合约接口的说明,并定义了与合约进行交互的数据编码解码规则。
事件和日志
区块链是一个块列表,从根本上讲就是交易列表。每一个交易都有一个收据,其中包含0个或多个日志记录。日志记录表示从智能合约触发的事件的结果。在以太坊中,事件是一个基本功能,可以将数据记录成日志,保存在区块链上。事件也可以与外部交互,比如与前端进行交互。事件强调功能,是指触发操作的行为,日志强调存储,是指触发事件后,将数据保存在区块链上,形成日志。
事件如何定义和触发
在solidity中,使用关键字event来定义事件,使用关键字emit来触发事件,其参数列表就是需要保存在区块链上的数据,最多可有三个具有indexed属性的参数,表示其可以被索引,便于查找。
contract?MyContract{????event?Transfer(address?indexed?from,?address?indexed?to,?uint256?value);????function?transfer(address?_to,?uint256?_value)?public?returns?(bool)?{????emit?Transfer(msg.sender,?_to,?_value);????return?true;??}}
事件的作用
事件可以在不同的场景下使用,主要有如下三种作用。
获取合约执行结果过滤日志存储合约数据1、获取合约执行结果
在开发Dapp时,我们会通过发送一笔交易来调用智能合约的某个函数,但是我们不能立即得到返回值。因为交易不是立刻打包进区块链的,在这种场景下,可以使用事件来解决这个问题。
以和前端交互为例,我们可以通过编写代码来监听某一特定事件来做到更新前端。例如通过如下代码来监听上文提到的合约中的Transfer事件。
var?event?=?myContract.Transfer();event.watch(function(error,?result){????if?(err)?{????console.log(err)????return;??}??console.log(result.args._value。);
当调用transfer函数的交易被打包进区块链中时,将会触发回调中的watch函数,前端可以得到有效的transfer函数的返回值。
事件通常可以被看作带有数据的异步触发器。当一个合约想要触发前端时,合约会发出一个事件。因为前端正在监听这个事件,一旦监听到相关事件,前端可以采取相应的操作,比如显示消息,更新前端展示内容等。
2、过滤日志
日志不能被合约访问,Solidity没有提供查询日志的接口,在监听日志的时候,Solidity提供了filter功能,借此我们可以实现对日志的查找过滤。在Transfer事件中,from和to参数被设置成indexed,说明其是可以被索引的。所以我们可以监听特定的事件,例如转账地址为0xab213的事件,也可以监听从0xab213地址转账到0x417ac的事件,但由于value参数没有indexed属性,所以我们不能监听例如value为100的事件。
在此场景下,我们如果想过滤指定地址发出的交易,我们可以通过web3.js编写如下代码。
动态 | GE 航空与微软 Azure 建立基于以太坊的供应链追溯区块链 TRUEngine:据链闻消息,通用电气(GE)航空公司表示已与微软 Azure 合作建立供应链追溯区块链 TRUEngine。GE 航空为全球航空业提供约 60% 的喷气发动机,GE 航空旗下数字集团部门表示,其目标是在整个行业的合作伙伴联盟中共享区块链,与 MTU 维护公司等合作伙伴历时两年多开发基于以太坊的 TRUEngine,监控和整理飞机发动机关键零件的制造和生命周期的相关数据。[2019/5/13]
Mycontract.deployed().then(function?(instance)?{var?event?=?instance.Transfer({}function?(error,?result)?{????var?obj1?=?{????????'_to':?'0xab213',????}????var?obj2?=?{????????'fromBlock':?0,????????'toBlock':?'latest'????}????var?event?=?instance.Transfer(obj1,?obj2)????event.watch(function?(error,?result)?{????????console.log(JSON.stringify(result))????}。).then(function?(value)?{????console.log(value。).catch(function?(e)?{????console.log(e。)
参数说明:
?obj1:添加indexed属性的参数,在这里我们可以过滤特定地址0xab213发起的交易
?obj2:Solidity提供的额外的过滤参数,可选的主要参数有:
–fromBlock:指定过滤的起始位置,值为块的编号,默认为latest
–toBlock:指定过滤的结束位置,值为块的编号,默认为latest
·callfunction:回调函数function(error,result
3、存储合约数据
与上面讲述的不同,事件可以作为便宜的多的一种存储形式。通过触发事件,存储在日志上的数据,基本上每字节花费8gas,但是智能合约每存储32字节花费20000gas。尽管日志可以节省大量gas,但是无法从任何智能合约中读取日志信息。需要根据使用场景来选择适合的存储办法,日志作为一种廉价存储的方式,适合存储可由前端展示的历史数据。
日志记录的组成
EVM具有5个用于发出日志的操作码:LOG0,LOG1,LOG2,LOG3和LOG4。通过这些操作码来创建日志记录。
每个日志记录都包含topics和data。Topics是bytes32类型的参数,不同的操作码描述包含在日志记录中的Topics数量。LOG1包含1个topic,LOG2包含2个topics,最多支持4个topics。
Topics用于描述事件,日志中存储的不同的具有indexed属性的事件就叫不同的主题。比如对于事件Transfer来说,其定义为eventTransfer(addressindexedfrom,addressindexedto,uint256value);,有三个主题。第一个主题为事件签名的哈希值,即通过keccak256("Transfer(address,address,uint256)")来得到,如果该事件是匿名事件,那么就没有这个主题。后面两个具有indexed属性的参数,可以用来进行过滤进行精确查找。
由于只能容纳32个字节的数据,所以无法将数据或字符串之类的参数用作Topic,应该将其作为data包含在日志记录中。如果想要包含超过32字节的topic,应该将其哈希。Topic可视作事件的索引,其使用场景在于可以有效缩小搜索查询范围内的数据。
日志记录的另一个部分是数据。Topic是可以搜索的,但是数据却不可以,数据可以摆脱Topic的32字节大小的限制,包含例如数组或字符串的复杂数据。
下面举一个例子来说明。还是以上文中的Transfer事件为例,由于Transfer不是一个匿名事件,所以第一个Topic包含事件签名。
contract?MyContract{????event?Transfer(address?indexed?from,?address?indexed?to,?uint256?value);????function?transfer(address?_to,?uint256?_value)?public?returns?(bool)?{????emit?Transfer(msg.sender,?_to,?_value);????return?true;??}}
在事件的参数部分,Transfer事件有三个参数from、to、value,其中from和to被声明为indexed,标识其被视为topic,value参数不会被索引,将会作为日志的数据部分。此事件包含3个topics,将使用LOG3操作码来创建日志记录。
举个例子,将上述示例合约部署到区块链网络中,我们使用:0x246B0ED379bdDbe1aDaC56277Ce5cB3018c24E04地址调用transfer函数,to参数指定为0x3F0b9B0D373C26328A879430383e87F4780AD410,value指定为1,发起一笔交易获得交易的回执信息。其中在logs属性中,记录如下信息
,????"type":"mined",????"id":"log_cf046b97"??}]
其中,topics数组中有三个元素,第一个为keccak256("Transfer(address,address,uint256)")的结果,第二个为from参数的值,这里为0x246B0ED379bdDbe1aDaC56277Ce5cB3018c24E04,第三个为to参数的值,这里为0x3F0b9B0D373C26328A879430383e87F4780AD410。data字段是通过对value进行编码得到的,这里将1转成32字节长度的16进制的值,得到0x0000000000000000000000000000000000000000000000000000000000000001。
布隆过滤器LogBloomandFilter
布隆过滤器在以太坊中用于检索交易日志log,方便交易结果的查询以及交易事件通知。在以太坊的区块头中,有一个区域叫做logsBloom。这个区域存储了当前区块中所有收据的日志的布隆过滤器,有2048个bit,相当于256个字节。在一个交易的收据中,可能存在0个或多个日志记录,每个日志记录中包含了相应的Topics和data。在一个交易的收据中同样也存在布隆过滤器,记录了所有的日志记录数据。
下面介绍布隆过滤器的原理。我们知道,查找一个元素是否存在于一个集合中,可以使用数组这种数据结构。但是当数据量非常庞大的情况下,数组的空间开销和查询开销也会变得非常大。
布隆过滤器的原理是当一个元素被加入到一个集合中时,使用K个哈希函数,对该元素求哈希值,得到K个不同的哈希值,分别记作X1,X2,X3,…,XK。将这K个数字作为位图的下标,将对应的
BitMap,BitMap,BitMap,…,BitMap都设置为1。即用K个bit来表示一个元素是否存在。
当想要查询某个元素是否存在于这个集合中时,用相同的K个哈希函数,得到Y,Y,Y...Y,如果这K个哈希值对应的位图下标均为1,则表示这个元素可能存在,如果有任意一个下标不为1,则说明这个元素肯定不存在。
举例说明,假设集合中现在有3个元素{X,Y,Z},使用3个哈希函数。首先将数组进行初始化,每一位置为0。然后对于集合中的每一个元素,都通过3个哈希函数进行哈希计算,每次计算都会产生一个下标,将数组中该下标对应的值设置为1。这时候想要查询一个元素W是否在该集合中,依次用这3个哈希函数将W映射到数组的3个位点上,如果3个位点对应的值都为1,则可能存在于集合中,若有一个位点的值不为1,则该元素一定不在这个集合中。
布隆过滤器存在一定的误判性,因为存在一定的哈希碰撞可能,所以当位点上的值都为1时,只能反映该元素可能存在于该集合中。但这不影响其广泛的应用性,布隆过滤器是一种存储效率很高的数据结构,其空间利用率和时间利用率非常高,插入数据和查询数据的时间复杂度为O。
布隆过滤器可以用于事件查询,在以太坊中,系统会先创建各个主题的布隆过滤器,然后通过合并获得事件的布隆过滤器,再次合并得到交易的布隆过滤器,最后合并得到区块的布隆过滤器。在查询过程中,查询满足指定特征的事件的过程是,先根据查询条件得到布隆过滤器,如果其位向量是区块布隆过滤器的子向量,则可能存在此区块中,如果不是其子向量,那么就不存在此区块中。如果可能存在,继续比对区块中各个交易的布隆过滤器,比对交易中每个事件的布隆过滤器。如果与事件的布隆过滤器匹配,再进行严格的数据验证,相同的话即说明存在。
参考资料
https://academy.binance.com/zh/articles/what-are-smart-contracts
https://segmentfault.com/a/1190000016634359
https://cloud.tencent.com/developer/article/1328286
https://fisco-bcos-documentation.readthedocs.io/zhCN/latest/docs/articles/3features/35contract/abiof_contract.html
http://www.jouypub.com/2018/437e42a5629ea0ccd567909c94abb4a4/
https://media.consensys.net/technical-introduction-to-events-and-logs-in-ethereum-a074d65dd61e
标签:TRAESSRANANSSmart Trade NetworksFESSChainAlgorandMOONLYFANS
巴比特讯,9月11日消息,区块链媒体Decrypt将建立一个媒体DAO,希望实现内容制作和分发过程的去中心化,为内容创作开辟一条更好的道路,从而加强作者与其所涵盖的项目之间的联系,并对社区成员付出的时间和见解做出补偿.
1900/1/1 0:00:00软分叉激活指的是一个比特币全节点开始增设一个或多个共识规则的瞬间。这种转换会在节点之间产生协调风险。所以开发者多年来花了相当多的力气来创建和提升软分叉激活机制,以尽可能降低出问题的概率.
1900/1/1 0:00:00据青岛市人民政府官网9月17日报道,青岛市人民政府近日印发《青岛市“十四五”战略性新兴产业发展规划》,其中列出10大重点产业,在第九项“区块链”产业中,《规划》指出推动建设区块链底层基础服务平台,支撑区块链示范性应用落地.
1900/1/1 0:00:00原标题:《元宇宙漫游手册:新挑战,新思路》Challenge?元宇宙的发展面临什么挑战?IP?元宇宙的知识产权如何定义?NFT?NFT如何助力元宇宙? 元宇宙的发展挑战 知识产权 众所周知,互联网世界打破了现有的知识产权利用模式.
1900/1/1 0:00:00本文将探讨: a)我们如何辨别MicroStrategy(MSTR)的价值,这是一家在加密货币领域处于独特情况的公司,以及价值投资者如何运作的基础知识.
1900/1/1 0:00:00来源:Twitter 作者:A16z合伙人JonLai当游戏中的真钱交易不顶用时,为什么边玩边赚模式却可行?AxieInfinity、暗黑破坏神3、星战前夜、反恐精英这些游戏如何能够相互借鉴?本贴通过一个框架描述了为什么边玩边赚模式可.
1900/1/1 0:00:00