这篇文章上次修改于 293 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
每个函数中只贴出了关键代码。
启动节点
启动 Arb node
func main() {
os.Exit(mainImpl())
}
创建执行层
func mainImpl() int {
execNode, err := gethexec.CreateExecutionNode(
ctx,
stack,
chainDb,
l2BlockChain,
l1Client,
func() *gethexec.Config { return &liveNodeConfig.Get().Execution },
)
}
创建 sequencer
sequencer 拥有 PublishTransaction()
func CreateExecutionNode(
ctx context.Context,
stack *node.Node,
chainDB ethdb.Database,
l2BlockChain *core.BlockChain,
l1client arbutil.L1Interface,
configFetcher ConfigFetcher,
) (*ExecutionNode, error) {
execEngine, err := NewExecutionEngine(l2BlockChain)
if config.Sequencer.Enable {
seqConfigFetcher := func() *SequencerConfig { return &configFetcher().Sequencer }
sequencer, err = NewSequencer(execEngine, parentChainReader, seqConfigFetcher)
if err != nil {
return nil, err
}
txPublisher = sequencer
}
txPublisher = NewTxPreChecker(txPublisher, l2BlockChain, txprecheckConfigFetcher)
arbInterface, err := NewArbInterface(execEngine, txPublisher)
backend, filterSystem, err := arbitrum.NewBackend(stack, &config.RPC, chainDB, arbInterface, filterConfig)
}
交易处理过程
提交交易
// Transaction pool API
func (a *APIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
return a.b.EnqueueL2Message(ctx, signedTx, nil)
}
func (b *Backend) EnqueueL2Message(ctx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error {
return b.arb.PublishTransaction(ctx, tx, options)
}
交易进入 sequencer 队列
func (s *Sequencer) PublishTransaction(parentCtx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error {
sequencerBacklogGauge.Inc(1)
defer sequencerBacklogGauge.Dec(1)
...
queueItem := txQueueItem{
tx,
options,
resultChan,
false,
queueCtx,
time.Now(),
}
select {
case s.txQueue <- queueItem:
}
取出交易
func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) {
var queueItems []txQueueItem
var totalBatchSize int
...
case queueItem = <-s.txQueue:
...
queueItems = s.precheckNonces(queueItems)
txes := make([]*types.Transaction, len(queueItems))
block, err := s.execEngine.SequenceTransactions(header, txes, hooks)
}
func (s *ExecutionEngine) SequenceTransactions(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, hooks *arbos.SequencingHooks) (*types.Block, error) {
return s.sequencerWrapper(func() (*types.Block, error) {
hooks.TxErrors = nil
return s.sequenceTransactionsWithBlockMutex(header, txes, hooks)
})
}
处理交易
func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, hooks *arbos.SequencingHooks) (*types.Block, error) {
hooks := arbos.NoopSequencingHooks()
block, receipts, err := arbos.ProduceBlockAdvanced(
header,
txes,
delayedMessagesRead,
lastBlockHeader,
statedb,
s.bc,
s.bc.Config(),
hooks,
)
}
func ProduceBlockAdvanced(
l1Header *arbostypes.L1IncomingMessageHeader,
txes types.Transactions,
delayedMessagesRead uint64,
lastBlockHeader *types.Header,
statedb *state.StateDB,
chainContext core.ChainContext,
chainConfig *params.ChainConfig,
sequencingHooks *SequencingHooks,
) (*types.Block, types.Receipts, error) {
// Prepend a tx before all others to touch up the state (update the L1 block num, pricing pools, etc)
// 会调用合约
startTx := InternalTxStartBlock(chainConfig.ChainID, l1Header.L1BaseFee, l1BlockNum, header, lastBlockHeader)
txes = append(types.Transactions{types.NewTx(startTx)}, txes...)
for len(txes) > 0 || len(redeems) > 0 {
// repeatedly process the next tx, doing redeems created along the way in FIFO order
var tx *types.Transaction
receipt, result, err := (func() (*types.Receipt, *core.ExecutionResult, error) {
// hooks.PreTxFilter = nil
if err = hooks.PreTxFilter(chainConfig, header, statedb, state, tx, options, sender, l1Info); err != nil {
return nil, nil, err
}
// extraPreTxFilter = nill
if err = extraPreTxFilter(chainConfig, header, statedb, state, tx, options, sender, l1Info); err != nil {
return nil, nil, err
}
// 处理交易
receipt, result, err := core.ApplyTransactionWithResultFilter(
chainConfig,
chainContext,
&header.Coinbase,
&gasPool,
statedb,
header,
tx,
&header.GasUsed,
vm.Config{},
func(result *core.ExecutionResult) error {
return hooks.PostTxFilter(header, state, tx, sender, dataGas, result)
},
)
// extraPostTxFilter = nil
if err = extraPostTxFilter(chainConfig, header, statedb, state, tx, options, sender, l1Info, result); err != nil {
statedb.RevertToSnapshot(snap)
return nil, nil, err
}
return receipt, result, nil
})()
}
func ApplyTransactionWithResultFilter(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, resultFilter func(*ExecutionResult) error) (*types.Receipt, *ExecutionResult, error) {
msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee)
if err != nil {
return nil, nil, err
}
// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{BlobHashes: tx.BlobHashes()}, statedb, config, cfg)
return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv, resultFilter)
}
运行交易
func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM, resultFilter func(*ExecutionResult) error) (*types.Receipt, *ExecutionResult, error) {
// Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, msg, gp)
}
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
return NewStateTransition(evm, msg, gp).TransitionDb()
}
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
endTxNow, startHookUsedGas, err, returnData := st.evm.ProcessingHook.StartTxHook()
// 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
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
// 3. the amount of gas required is available in the block
// 4. the purchased gas is enough to cover intrinsic usage
// 5. there is no overflow when calculating intrinsic gas
// 6. caller has enough balance to cover asset transfer for **topmost** call
// Check clauses 1-3, buy gas if everything is correct
if err := st.preCheck(); err != nil {
return nil, err
}
// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
if err != nil {
return nil, err
}
if st.gasRemaining < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
}
st.gasRemaining -= gas
tipReceipient, err := st.evm.ProcessingHook.GasChargingHook(&st.gasRemaining)
if contractCreation {
deployedContract = &common.Address{}
ret, *deployedContract, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, msg.Value)
} else {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value)
}
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 := msg.GasPrice
if rules.IsLondon {
effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee))
}
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.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(tipReceipient, fee)
tipAmount = fee
}
st.evm.ProcessingHook.EndTxHook(st.gasRemaining, vmerr == nil)
return &ExecutionResult{
UsedGas: st.gasUsed(),
Err: vmerr,
ReturnData: ret,
ScheduledTxes: st.evm.ProcessingHook.ScheduledTxes(),
TopLevelDeployed: deployedContract,
}, nil
}
交易 fee
TransactionToMessage() 中创建了 Message 后,修改 msg.GasPrice 为两者中的最小值:最大小费单价 + 基础费;最大手续费单价 msg.GasPrice = cmath.BigMin(msg.GasPrice.Add(msg.GasTipCap, baseFee), msg.GasFeeCap)
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
msg := &Message{
Tx: tx,
Nonce: tx.Nonce(),
GasLimit: tx.Gas(),
GasPrice: new(big.Int).Set(tx.GasPrice()),
GasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
GasTipCap: new(big.Int).Set(tx.GasTipCap()),
To: tx.To(),
Value: tx.Value(),
Data: tx.Data(),
AccessList: tx.AccessList(),
SkipAccountChecks: tx.SkipAccountChecks(), // TODO Arbitrum upstream this was init'd to false
BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
msg.GasPrice = cmath.BigMin(msg.GasPrice.Add(msg.GasTipCap, baseFee), msg.GasFeeCap)
}
var err error
msg.From, err = types.Sender(s, tx)
return msg, err
}
TransitionDb() 中有如下逻辑,一般不会走到
// Arbitrum: drop tip for delayed (and old) messages
// 假如 ArbOSVersion 是 11,DropTip() 返回 true
// func (p *TxProcessor) DropTip() bool {
// version := p.state.ArbOSVersion()
// return version != 9 || p.delayedInbox
// }
if st.evm.ProcessingHook.DropTip() && st.msg.GasPrice.Cmp(st.evm.Context.BaseFee) > 0 {
st.msg.GasPrice = st.evm.Context.BaseFee
st.msg.GasTipCap = common.Big0
}
TransitionDb() -> preCheck() -> buyGas() 中,大多数情况下都从用户的账户中扣除 st.msg.GasLimit * st.msg.GasPrice
func (st *StateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval = mgval.Mul(mgval, st.msg.GasPrice)
balanceCheck := new(big.Int).Set(mgval)
if st.msg.GasFeeCap != nil {
balanceCheck.SetUint64(st.msg.GasLimit)
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
balanceCheck.Add(balanceCheck, st.msg.Value)
}
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if blobGas := st.blobGasUsed(); blobGas > 0 {
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
blobBalanceCheck := new(big.Int).SetUint64(blobGas)
blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap)
balanceCheck.Add(balanceCheck, blobBalanceCheck)
// Pay for blobGasUsed * actual blob fee
blobFee := new(big.Int).SetUint64(blobGas)
blobFee.Mul(blobFee, eip4844.CalcBlobFee(*st.evm.Context.ExcessBlobGas))
mgval.Add(mgval, blobFee)
}
}
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)
}
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err
}
st.gasRemaining += st.msg.GasLimit
st.initialGas = st.msg.GasLimit
st.state.SubBalance(st.msg.From, mgval)
// Arbitrum: record fee payment
if tracer := st.evm.Config.Tracer; tracer != nil {
tracer.CaptureArbitrumTransfer(st.evm, &st.msg.From, nil, mgval, true, "feePayment")
}
return nil
}
TransitionDb() 中扣除 intrinsic gas
// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
if err != nil {
return nil, err
}
if st.gasRemaining < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas)
}
st.gasRemaining -= gas
TransitionDb() -> GasChargingHook() 中扣掉 L1 gas,并将 L1 fee 换算为等价的 L2 fee
tipReceipient, err := st.evm.ProcessingHook.GasChargingHook(&st.gasRemaining)
func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, error) {
// Because a user pays a 1-dimensional gas price, we must re-express poster L1 calldata costs
// as if the user was buying an equivalent amount of L2 compute gas. This hook determines what
// that cost looks like, ensuring the user can pay and saving the result for later reference.
var gasNeededToStartEVM uint64
tipReceipient, _ := p.state.NetworkFeeAccount()
basefee := p.evm.Context.BaseFee
var poster common.Address
if p.msg.TxRunMode != core.MessageCommitMode {
poster = l1pricing.BatchPosterAddress
} else {
poster = p.evm.Context.Coinbase
}
if p.msg.TxRunMode == core.MessageCommitMode {
p.msg.SkipL1Charging = false
}
if basefee.Sign() > 0 && !p.msg.SkipL1Charging {
// Since tips go to the network, and not to the poster, we use the basefee.
// Note, this only determines the amount of gas bought, not the price per gas.
brotliCompressionLevel, err := p.state.BrotliCompressionLevel()
if err != nil {
return common.Address{}, fmt.Errorf("failed to get brotli compression level: %w", err)
}
posterCost, calldataUnits := p.state.L1PricingState().PosterDataCost(p.msg, poster, brotliCompressionLevel)
if calldataUnits > 0 {
p.state.Restrict(p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits))
}
p.posterGas = GetPosterGas(p.state, basefee, p.msg.TxRunMode, posterCost)
p.PosterFee = arbmath.BigMulByUint(basefee, p.posterGas) // round down
gasNeededToStartEVM = p.posterGas
}
if *gasRemaining < gasNeededToStartEVM {
// the user couldn't pay for call data, so give up
return tipReceipient, core.ErrIntrinsicGas
}
*gasRemaining -= gasNeededToStartEVM
if p.msg.TxRunMode != core.MessageEthcallMode {
// If this is a real tx, limit the amount of computed based on the gas pool.
// We do this by charging extra gas, and then refunding it later.
gasAvailable, _ := p.state.L2PricingState().PerBlockGasLimit()
if *gasRemaining > gasAvailable {
p.computeHoldGas = *gasRemaining - gasAvailable
*gasRemaining = gasAvailable
}
}
return tipReceipient, nil
}
TransitionDb() 中执行 tx
if contractCreation {
deployedContract = &common.Address{}
ret, *deployedContract, st.gasRemaining, vmerr = st.evm.Create(sender, msg.Data, st.gasRemaining, msg.Value)
} else {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1)
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value)
}
TransitionDb() 中将多扣的 fee 还给用户
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)
}
func (st *StateTransition) refundGas(refundQuotient uint64) {
// Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice)
st.state.AddBalance(st.msg.From, remaining)
}
TransitionDb() 中将小费 effectiveTip * st.gasUsed()
给 networkFeeAccount
effectiveTip := msg.GasPrice
if rules.IsLondon {
effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee))
}
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.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(tipReceipient, fee) // tipReceipient = networkFeeAccount
tipAmount = fee
}
TransitionDb() -> EndTxHook() 中,将 base fee 给 infraFeeAccount 和 l1pricing.L1PricerFundsPoolAddress,如果还有小费,给 networkFeeAccount
func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) {
basefee := p.evm.Context.BaseFee
totalCost := arbmath.BigMul(basefee, arbmath.UintToBig(gasUsed)) // total cost = price of gas * gas burnt
computeCost := arbmath.BigSub(totalCost, p.PosterFee) // total cost = network's compute + poster's L1 costs
if computeCost.Sign() < 0 {
// Uh oh, there's a bug in our charging code.
// Give all funds to the network account and continue.
log.Error("total cost < poster cost", "gasUsed", gasUsed, "basefee", basefee, "posterFee", p.PosterFee)
p.PosterFee = big.NewInt(0)
computeCost = totalCost
}
purpose := "feeCollection"
if p.state.ArbOSVersion() > 4 {
infraFeeAccount, err := p.state.InfraFeeAccount()
p.state.Restrict(err)
if infraFeeAccount != (common.Address{}) {
minBaseFee, err := p.state.L2PricingState().MinBaseFeeWei()
p.state.Restrict(err)
infraFee := arbmath.BigMin(minBaseFee, basefee)
computeGas := arbmath.SaturatingUSub(gasUsed, p.posterGas)
infraComputeCost := arbmath.BigMulByUint(infraFee, computeGas)
util.MintBalance(&infraFeeAccount, infraComputeCost, p.evm, scenario, purpose)
computeCost = arbmath.BigSub(computeCost, infraComputeCost)
}
}
if arbmath.BigGreaterThan(computeCost, common.Big0) {
util.MintBalance(&networkFeeAccount, computeCost, p.evm, scenario, purpose)
}
posterFeeDestination := l1pricing.L1PricerFundsPoolAddress
if p.state.ArbOSVersion() < 2 {
posterFeeDestination = p.evm.Context.Coinbase
}
util.MintBalance(&posterFeeDestination, p.PosterFee, p.evm, scenario, purpose)
if p.state.ArbOSVersion() >= 10 {
if _, err := p.state.L1PricingState().AddToL1FeesAvailable(p.PosterFee); err != nil {
log.Error("failed to update L1FeesAvailable: ", "err", err)
}
}
if p.msg.GasPrice.Sign() > 0 { // in tests, gas price could be 0
// ArbOS's gas pool is meant to enforce the computational speed-limit.
// We don't want to remove from the pool the poster's L1 costs (as expressed in L2 gas in this func)
// Hence, we deduct the previously saved poster L2-gas-equivalent to reveal the compute-only gas
var computeGas uint64
if gasUsed > p.posterGas {
// Don't include posterGas in computeGas as it doesn't represent processing time.
computeGas = gasUsed - p.posterGas
} else {
// Somehow, the core message transition succeeded, but we didn't burn the posterGas.
// An invariant was violated. To be safe, subtract the entire gas used from the gas pool.
log.Error("total gas used < poster gas component", "gasUsed", gasUsed, "posterGas", p.posterGas)
computeGas = gasUsed
}
p.state.Restrict(p.state.L2PricingState().AddToGasPool(-arbmath.SaturatingCast(computeGas)))
}
}
没有评论