目录
1. 总览
以太坊是基于交易的状态机,基于区块,交易在区块中顺序执行,所有节点的大量冗余计算保证了可信性
2. 数据结构
账户状态 accountState: nonce balance storageRoot codeHash
nonce: 当前账户发出的交易数量,后面的交易中的nonce跟账户保持一致,合约账户创建后,nonce为1
storageRoot: EOA为空值,没有存储
COA有值,合约所有存储变量及值的键值对,作为叶子节点,最后生成MPT的根root
codeHash: EOA为空字串的哈希值
COA合约账户关联代码的哈希值
storage Trie的叶子节点:
F(address,storage position, blockNumber) ---> strorage Data
世界状态 World State:就是所有账户状态
状态树 State Trie,根root写入到区块头中
keccak256(address) --> RLP(accountState)
state Trie的数据存储在各个节点的数据库LevelDB中,即 state DB
state root ---> RLP(state trie)
交易 Transaction: nonce gasPrice gasLimit to value data v r s
交易树 Transaction Trie,根root写入到区块头中
RLP(transactionIndex) ---> RLP(transaction)
收据 Receipt: madState gasUsed logBloom logs
收据树 Receipt Trie, 根root写入到区块头中
RLP(transactionIndex) ---> RLP(transaction Receipt)
区块 Block:blockHeader transactionList ommerHeaderList
区块头 BlockHeader: parentHash ommersHash benefitiary stateRoot transactionRoot receiptRoot logBloom difficulty number gasLimit gasUsed timeStamp extraData mixHash nonce
3. 区块形成过程
矿工节点:
ommer验证 ---> 从交易中选取交易并顺序执行 ---> 奖励发放 ---> 生成区块 ---> pow验证状态
非矿工节点:
ommer验证 ---> 顺序执行区块中包含的交易 ---> 奖励发放 ---> 验证区块 ---> pow验证
如果在计算pow的过程中收到另一个区块,并验证ok,前面打包到自己区块,正在计算pow的交易要回退状态,会退到父区块状态(state DB),甚至有可能要切换分支,回到与接收的区块所在分支的分叉点的世界状态,并再计算到新分支的末尾节点
4. 交易执行
交易字段有 tx: nonce gasprice gaslimit to value data
交易分类: tx.to 有效 tx.data 为空 ===》 EOA之间的普通转账
tx.to 为空 tx.data 不为空 ==》合约创建
tx.to 有效 tx.data 不为空 ==》 消息调用
交易执行顺序:
1. 有效性检查
ECRecover(hash(tx), v, r, s) == sender.address
tx.nonce == sender.nonce
tx.gas0 <= tx.gasLimit
sender.balance >= tx.gas0 * tx.gasPrice
block.gasUsed + tx.gas0 <= block.gasLimit
2. 不可更改状态修改
sender.nonce += 1
sender.balance -= tx.gas0 * tx.gasPrice
3. 根据交易类型分类执行
转 账: sender.balance -= tx.value
tx.to.balance += tx.value
合约创建: 将tx.data作为EVM字节码在EVM中执行
消息调用: 将tx.data作为ABI 编码在EVM中执行
4. 处理结束
返还gas 删除自毁账户和空账户
5. 执行模型
EOA账户发起交易,以太坊内部转换成message,触发 EVM Computation执行
首先1个交易的执行是在一个区块内部的,所以外部执行环境也就是区块的状态:
Execution Context: coin_base timeStamp blockNumber difficulty gas_limit
其次1个交易:
Transaction Context: gasPrice origin
最后1个交易转成message 传入 EVM Computation
Message: gas value to sender data code depth create_address code_address storage_address should_transfer_value
EVM Computaion 基于账户代码,存储变量开始执行tx.data中指定的函数,如果再EVM 字节码中有 create/call/callcode/delegateCall/staticCall中的1个,会生成child message,
按照交易分类:
合约创建:
1. 计算合约地址
newAddress = B(96...255)(keccak256(RLP(sender, sender.nonce)))
2. 转成message
Message: gas value to sender data code create_address code_address storage_address
tx.gas tx.data tx.to tx.from NULL tx.data newAddress newAddress newAddress
3. 交由EVM Computation执行
4. 保存输出(runtime bytecode) hash值即codeHash 保存到账户状态中
合约代码是世界状态的一部分,用全局独立的MPT来维护所有账户的关联代码,账户地址作为key,但是访问受限
只能通过EVM的create写入
只能通过EVM的codesize codecopy extcodesize exitcodecopy call callcode delegatecall staticcall 间接操作访问
消息调用:
1. 从tx.to的地址上取出account_code account_code = state.account_db.get_code(tx.to)
2. 转成message
Message: gas value to sender data code create_address code_address storage_address
tx.gas tx.value tx.to tx.from tx.data account_code none tx.to tx.to
6. Gas 返还
SSTORE将非0值存储槽修改为0值,返还15000,操作本身消耗5000,计入计数器Ar
Selfdestruct 指令删除返还24000,操作本身消耗5000,计入计数器Ar
实际消耗的gas为Ga,交易执行完剩余gas为Gr
返还gas G = Gr + min(Ga/2, Ar)