以太坊的本质就是一个基于交易的状态机,从创世状态开始,每次执行block的交易后,状态被修改成最新的的最终状态,在任何时刻这个最终状态都代表着以太坊当前的最新状态。
状态转换示意如下图:
- 状态数据实际上也是保存在block上的状态树中的。
- 创世状态里面记录了创世文件初始化的账户数据及其他状态数据,每次生成一个block,执行其中的交易,修改状态数据,并以增量修改的方式记录在最新的block的状态树中。
- 被最终确认后的block(包括状态)保证永久无法被篡改(符合拜占庭容错的条件下)。
- 上图中最新的以太坊状态是执行blockN+1中的所有交易后的世界状态。
- 此时如果本地矿工从交易池中挖到最新的候选block, 或者其他矿工挖到最新的block并在本矿工挖到之前通过该候选block过来,在验证block中的所有打包交易有效性之后,执行该后选block中所有交易。
- 执行交易的过程就是虚拟机EVM执行交易中的合约代码的过程,全部执行完成后,将被修改的状态记录到候选block的状态树上,然后将该候选block进行上链,包括解决分叉,持久化block数据,更新才能的数据等。
和状态转换相关的最重要的2个接口是:
- apply_block(state,block): block级的状态转换
- apply_transaction(state,tx): 一个交易执行的状态转换
apply_block(state,block)
- 首先做好state的备份,方便后面转换过程中出现失败时进行回滚;
- 为了更加通用化处理,抽象出共识策略的接口,用户可以自定义共识策略,目前支持PoW,PoA共识策略;
- 共识策略的初始化的工作是。。。
- 验证block头数据的有效性;
- 验证共识相关的数据的有效性, 例如PoA共识中extra字段有相应的格式要求;
- 对叔块的一些验证,因为入股哦该block最终被确认的话, 叔块也是可以获得相应的奖励;
- 交易树是一个merkle树,交易最终生成一个nerkle根hash,如果和block中记录的hash‘值不一致,说明交易数据被篡改,该block是非法的;
- 执行block中的所有交易,也就是状态转换, 详见下面小节;
- 状态转换完成后,对旷工和叔块的旷工进行奖励;计算方法是:
- 验证交易的执行结果,bloom位图保存合约执行中产生的事件,收据树保存交易执行结果hash;
- block本身的数据也保存到state中;
- 至此完成整个block的状态转换工作;
apply_transaction(state,tx)
- 执行每个block中的交易时修改的状态是一个增量修改,因此state中的logs(交易产生的事件)和suicides(执行“自杀”代码的事件记录),refunds(eth退款)字段只记录本block执行后的结果;
- 验证交易的有效性,交易的sender(从前民中恢复出账户地址和from参数比较),交易的编号(每一笔sender发出的交易有一个唯一的数字,是连续自增的整数),交易的gas(sender是否有足够的资金支付本次交易的gas费用);
- 如果是一个创建合约的交易,还要扣除而外的gas费用;
- 后面是交易中合约的执行以及gas费用的计算和扣除的工作;
apply_msg(ext,msg)
其中apply_msg是执行该交易的过程:
这里特殊合约的执行和一般合约代码的执行都是在虚拟机中完成的,详见<虚拟机的实现分析>一文。