0%

StudyRecord-以太坊源码分析-将交易递交给EVM

处理交易的入口:

整个evm调用的入口在go-ethereum/core/state_transaction.go中.
ApplyTransaction()就是执行交易的入口,而交易的执行就离不开EVM
每处理一笔交易,就要创建一个 EVM对象 来执行交易中的数据。

20220720154820

处理交易流程:

ApplyTransaction函数:

ApplyTransaction()applyTransaction()函数的主要功能是:将交易转化成Message,创建EVM对象,调用ApplyMessage()执行交易,生成日志对象;

  1. 将交易转换成Message

  2. 初始化一个EVM的执行环境

  3. 新建 EVM 对象

  4. 执行交易,改变stateDB世界状态,然后生成收据

core/state_processor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
// 转换一个Message对象
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number), header.BaseFee)
if err != nil {
return nil, err
}
// Create a new context to be used in the EVM environment
// 创建要在EVM环境中使用的 区块上下文
blockContext := NewEVMBlockContext(header, bc, author)
// 使用区块上下文创建EVM对象
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
// 调用applyTransaction函数
return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
}

接着看applyTransaction()函数

applyTransaction函数:

core/state_processor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
// Create a new context to be used in the EVM environment.
// 创建交易上下文
txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb)

// Apply the transaction to the current state (included in the env).
// 执行交易,获取返回结果
result, err := ApplyMessage(evm, msg, gp)
if err != nil {
return nil, err
}

// Update the state with pending changes.
// 更改stateDB世界状态
var root []byte
// 如果是拜占庭硬分叉,清理世界状态
if config.IsByzantium(blockNumber) {
statedb.Finalise(true)
} else {
// 否则计算状态树根,用于产生收据
root = statedb.IntermediateRoot(config.IsEIP158(blockNumber)).Bytes()
}
// 增加header中的usedGas
*usedGas += result.UsedGas

// Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx.
// 创建用于交易的新收据,在tx中存储root和使用的气体
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
if result.Failed() {
receipt.Status = types.ReceiptStatusFailed
} else {
receipt.Status = types.ReceiptStatusSuccessful
}
receipt.TxHash = tx.Hash()
receipt.GasUsed = result.UsedGas

// If the transaction created a contract, store the creation address in the receipt.
// 如果交易创建了一个合约,要把合约地址放在收据里
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
}

// Set the receipt logs and create the bloom filter.
// 记录收据的信息
receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash)
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
receipt.BlockHash = blockHash
receipt.BlockNumber = blockNumber
receipt.TransactionIndex = uint(statedb.TxIndex())
return receipt, err
}

接着看ApplyMessage()函数

ApplyMessage函数:

ApplyMessage()中,首先新建一个交易工作环境,然后紧接着调用TransitionDb()方法:

core/state_transition.go
1
2
3
4
// ApplyMessage returns the bytes returned by any EVM execution (if it took place)...
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) {
return NewStateTransition(evm, msg, gp).TransitionDb()
}

StateTransition.TransitionDb函数:

StateTransition结构体:

交易工作环境(StateTransition)的数据结构如下:

core/state_transition.go
1
2
3
4
5
6
7
8
9
10
11
12
13
type StateTransition struct {
gp *GasPool // 区块工作环境中的gas剩余额度,就是header中的gasLimit
msg Message // 交易转化的message
gas uint64 // 交易的gas余额,最开始等于initialGas,随着交易执行会递减
gasPrice *big.Int
gasFeeCap *big.Int
gasTipCap *big.Int
initialGas uint64 // 初始gas,等于交易的gasLimit
value *big.Int // 交易转账额度
data []byte // 交易的input,如果是合约创建,data就是合约代码
state vm.StateDB // 状态树
evm *vm.EVM // evm对象
}

这里gp是整个区块所有交易可用的gas,其实就是来自于headergaslimit
headergasLimit是通过父区块的gasUsed推算出来的。
initialGas是交易的gasLimit,gas是余额(等于initialGas减去交易usedGas)。

TransitionDb函数:

TransitionDb()的主要功能是 初始化交易工作环境,执行交易,然后处理交易执行前后的gas增减。

  1. 前置检查:预先检查noncegas值,初始化交易工作环境的gas初始值;

  2. 计算并扣除固定gas消耗;

  3. 通过比较0地址,判断是否是创建新合约,调用evm创建或执行交易;

  4. 奖励旷工:归还剩余的 gas,并将已消耗的 gas 计入矿工账户中

core/state_transition.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// TransitionDb will transition the state by applying the current message and
// returning the evm execution result with following fields.
//
// - used gas:
// total gas used (including gas being refunded)
// - returndata:
// the returned data from evm
// - concrete execution error:
// various **EVM** error which aborts the execution,
// e.g. ErrOutOfGas, ErrExecutionReverted
//
// However if any consensus issue encountered, return the error directly with
// nil evm execution result.
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
// 1. the nonce of the message caller is correct caller的nonce是否正确
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) caller是否有足够的余额来支付交易费(gaslimit + gasprice)
// 3. the amount of gas required is available in the block 区块里剩余可用gas数量
// 4. the purchased gas is enough to cover intrinsic usage 购买的gas是否足够支付固定gas消耗量
// 5. there is no overflow when calculating intrinsic gas 计算固定gas消耗量时有没溢出
// 6. caller has enough balance to cover asset transfer for **topmost** call caller是否有足够的余额来支付资产的转移

// Check clauses 1-3, buy gas if everything is correct
// 先检查1-3项,然后调用buyGas;
// buyGas是要从区块的gasPool中取出一定的gas用于执行交易
if err := st.preCheck(); err != nil {
return nil, err
}

if st.evm.Config.Debug {
st.evm.Config.Tracer.CaptureTxStart(st.initialGas)
defer func() {
st.evm.Config.Tracer.CaptureTxEnd(st.gas)
}()
}

var (
msg = st.msg
sender = vm.AccountRef(msg.From())
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil)
// 目标地址为0地址,则为创建合约,否则是交易或合约调用
contractCreation = msg.To() == nil
)

// Check clauses 4-5, subtract intrinsic gas if everything is correct
// 计算并扣除固定的gas消耗
// 固定的gas消耗(21000) + 非0值gas + 0值gas
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul)
if err != nil {
return nil, err
}
if st.gas < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas)
}
// 更新剩余的gas值
st.gas -= gas

// Check clause 6
if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) {
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From().Hex())
}

// Set up the initial access list.
if rules.IsBerlin {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
}
var (
ret []byte
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
)
if contractCreation {
// 如果是创建合约,调用 evm.Create
ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value)
} else {
// Increment the nonce for the next transaction
// 如果是交易或合约调用,则先设置交易发送方的nonce值+1
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
// 调用 evm.Call 执行交易
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
}

// 归还剩余的 gas,并将已消耗的 gas 计入矿工账户中
if !rules.IsLondon {
// Before EIP-3529: refunds were capped to gasUsed / 2
st.refundGas(params.RefundQuotient)
} else {
// After EIP-3529: refunds are capped to gasUsed / 5
st.refundGas(params.RefundQuotientEIP3529)
}
effectiveTip := st.gasPrice
if rules.IsLondon {
effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee))
}

if st.evm.Config.NoBaseFee && st.gasFeeCap.Sign() == 0 && st.gasTipCap.Sign() == 0 {
// Skip fee payment when NoBaseFee is set and the fee fields
// are 0. This avoids a negative effectiveTip being applied to
// the coinbase when simulating calls.
} else {
fee := new(big.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTip)
// 奖励矿工
st.state.AddBalance(st.evm.Context.Coinbase, fee)
}
// 返回执行结果
return &ExecutionResult{
UsedGas: st.gasUsed(),
Err: vmerr,
ReturnData: ret,
}, nil
}

preCheck函数:

继续分解TransitionDb里的具体实现在交易执行之前,要buyGas()之前,要做一些前置检查preCheck(),主要检查项是:

  1. caller的nonce是否正确

  2. caller是否有足够的余额来支付交易费(gaslimit + gasprice)

  3. 区块里剩余可用gas数量

buyGas函数:

前置检查完后,要从区块的gasPool中取出一定的gas用于执行交易,即调用buyGas():

  1. 将initialGas和gas都设置为交易的gasLimit

  2. 并从总的gaspool里减去预支的gas(即gasLimit)

  3. 交易发起者的账户也要减去相应的价值。

core/state_transition.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func (st *StateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.Gas()) // 返回pool里剩余的gas
mgval = mgval.Mul(mgval, st.gasPrice) // Pool gas剩余量 * 交易设的gasPrice
balanceCheck := mgval
// gas费上限
if st.gasFeeCap != nil {
balanceCheck = new(big.Int).SetUint64(st.msg.Gas())
balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap)
balanceCheck.Add(balanceCheck, st.value)
}
if have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want)
}
// 总的gaspool里减去预支的gas,先扣gasFee,用不完会退款
if err := st.gp.SubGas(st.msg.Gas()); err != nil {
return err
}
// 设置交易工作环境的gas为 交易的gasLimit
st.gas += st.msg.Gas()
st.initialGas = st.msg.Gas()
// 交易发起者的账户也要减去相应的价值
st.state.SubBalance(st.msg.From(), mgval)
return nil
}

gas使用流程:

  1. 从区块的gasPool中取出一定的gas用于执行交易buyGas()

  2. 计算并扣除固定的gas消耗IntrinsicGas()(固定的gas消耗(21000) + 非0值gas + 0值gas)

  3. 执行交易,获取剩余的gas

  4. 归还剩余的 gas refundGas(),并将已消耗的 gas 计入矿工账户中

refundGas函数:

/core/state_transition.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (st *StateTransition) refundGas(refundQuotient uint64) {
// Apply refund counter, capped to a refund quotient
// refundQuotient退款系数
// Before EIP-3529: 退款封顶为 gasUsed / 2
// After EIP-3529: 退款封顶为 gasUsed / 5
refund := st.gasUsed() / refundQuotient
if refund > st.state.GetRefund() {
refund = st.state.GetRefund()
}
st.gas += refund

// Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
// 原账户eth余额增加
st.state.AddBalance(st.msg.From(), remaining)

// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
// 把额度退回gas pool,让别的交易有额度可以使用
st.gp.AddGas(st.gas)
}

Refs: