Skip to content

Commit

Permalink
Merge pull request #371 from OffchainLabs/posting_gas_hook
Browse files Browse the repository at this point in the history
redesign PostingGasHook
  • Loading branch information
tsahee authored Nov 7, 2024
2 parents 51ff426 + a7d32f2 commit ed53c04
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 43 deletions.
4 changes: 2 additions & 2 deletions core/arbitrum_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ var InterceptRPCMessage = func(
// Gets ArbOS's maximum intended gas per second
var GetArbOSSpeedLimitPerSecond func(statedb *state.StateDB) (uint64, error)

// Allows ArbOS to update the gas cap so that it ignores the message's specific L1 poster costs.
var InterceptRPCGasCap = func(gascap *uint64, msg *Message, header *types.Header, statedb *state.StateDB) {}
// While processing RPC only - Ask ArbOS what are the poster costs for this message.
var RPCPostingGasHook = func(msg *Message, header *types.Header, statedb *state.StateDB) (uint64, error) { return 0, nil }

// Renders a solidity error in human-readable form
var RenderRPCError func(data []byte) error
Expand Down
5 changes: 2 additions & 3 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -945,12 +945,11 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
config.BlockOverrides.Apply(&vmctx)
}
// Execute the trace
gasLimitNotSetByUser := args.Gas == nil
if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID, gasLimitNotSetByUser); err != nil {
if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil {
return nil, err
}
var (
msg = args.ToMessage(vmctx.BaseFee, api.backend.RPCGasCap(), block.Header(), statedb, core.MessageEthcallMode, api.backend.ChainConfig().ChainID, gasLimitNotSetByUser)
msg = args.ToMessage(vmctx.BaseFee, api.backend.RPCGasCap(), block.Header(), statedb, core.MessageEthcallMode)
tx = args.ToTransaction()
traceConfig *TraceConfig
)
Expand Down
16 changes: 7 additions & 9 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1130,11 +1130,10 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S

// Get a new instance of the EVM.
var err error
gasLimitNotSetByUser := args.Gas == nil
if err = args.CallDefaults(globalGasCap, blockCtx.BaseFee, b.ChainConfig().ChainID, gasLimitNotSetByUser); err != nil {
if err = args.CallDefaults(globalGasCap, blockCtx.BaseFee, b.ChainConfig().ChainID); err != nil {
return nil, err
}
msg := args.ToMessage(blockCtx.BaseFee, globalGasCap, header, state, runMode, b.ChainConfig().ChainID, gasLimitNotSetByUser)
msg := args.ToMessage(blockCtx.BaseFee, globalGasCap, header, state, runMode)

// Arbitrum: support NodeInterface.sol by swapping out the message if needed
var res *core.ExecutionResult
Expand Down Expand Up @@ -1296,20 +1295,20 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
ErrorRatio: gasestimator.EstimateGasErrorRatio,
RunScheduledTxes: runScheduledTxes,
}
gasLimitNotSetByUser := args.Gas == nil
if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID, gasLimitNotSetByUser); err != nil {
if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
return 0, err
}
// Run the gas estimation andwrap any revertals into a custom return
// Arbitrum: this also appropriately recursively calls another args.ToMessage with increased gasCap by posterCostInL2Gas amount
call := args.ToMessage(header.BaseFee, gasCap, header, state, core.MessageGasEstimationMode, b.ChainConfig().ChainID, gasLimitNotSetByUser)
call := args.ToMessage(header.BaseFee, gasCap, header, state, core.MessageGasEstimationMode)

// Arbitrum: raise the gas cap to ignore L1 costs so that it's compute-only
{
gasCap, err = args.L2OnlyGasCap(header.BaseFee, gasCap, header, state, core.MessageGasEstimationMode, b.ChainConfig().ChainID, gasLimitNotSetByUser)
postingGas, err := core.RPCPostingGasHook(call, header, state)
if err != nil {
return 0, err
}
gasCap += postingGas
}

estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
Expand Down Expand Up @@ -1725,7 +1724,6 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
}

// Ensure any missing fields are filled, extract the recipient and input data
gasLimitNotSetByUser := args.Gas == nil
if err := args.setDefaults(ctx, b, true); err != nil {
return nil, 0, nil, err
}
Expand Down Expand Up @@ -1753,7 +1751,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
statedb := db.Copy()
// Set the accesslist to the last al
args.AccessList = &accessList
msg := args.ToMessage(header.BaseFee, b.RPCGasCap(), header, statedb, core.MessageEthcallMode, b.ChainConfig().ChainID, gasLimitNotSetByUser)
msg := args.ToMessage(header.BaseFee, b.RPCGasCap(), header, statedb, core.MessageEthcallMode)

// Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
Expand Down
59 changes: 30 additions & 29 deletions internal/ethapi/transaction_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ type TransactionArgs struct {

// This configures whether blobs are allowed to be passed.
blobSidecarAllowed bool

// was gas originally set by user
gasNotSetByUser bool
}

// from retrieves the transaction sender address.
Expand Down Expand Up @@ -139,6 +142,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGas
}

if args.Gas == nil {
args.gasNotSetByUser = true
if skipGasEstimation { // Skip gas usage estimation if a precise gas limit is not critical, e.g., in non-transaction calls.
gas := hexutil.Uint64(b.RPCGasCap())
if gas == 0 {
Expand Down Expand Up @@ -373,9 +377,25 @@ func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context) error {
return nil
}

func (args *TransactionArgs) setGasUsingCap(globalGasCap uint64) {
args.gasNotSetByUser = args.gasNotSetByUser || (args.Gas == nil)
if args.gasNotSetByUser {
gas := globalGasCap
if gas == 0 {
gas = uint64(math.MaxUint64 / 2)
}
args.Gas = (*hexutil.Uint64)(&gas)
} else {
if globalGasCap > 0 && globalGasCap < uint64(*args.Gas) {
log.Warn("Caller gas above allowance, capping", "requested", args.Gas, "cap", globalGasCap)
args.Gas = (*hexutil.Uint64)(&globalGasCap)
}
}
}

// CallDefaults sanitizes the transaction arguments, often filling in zero values,
// for the purpose of eth_call class of RPC methods.
func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, chainID *big.Int, gasLimitNotSetByUser bool) error {
func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int, chainID *big.Int) error {
// Reject invalid combinations of pre- and post-1559 fee styles
if args.GasPrice != nil && ((args.MaxFeePerGas != nil && args.MaxFeePerGas.ToInt().Cmp(common.Big0) != 0) || (args.MaxPriorityFeePerGas != nil && args.MaxPriorityFeePerGas.ToInt().Cmp(common.Big0) != 0)) {
return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
Expand All @@ -387,18 +407,7 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int,
return fmt.Errorf("chainId does not match node's (have=%v, want=%v)", have, chainID)
}
}
if gasLimitNotSetByUser {
gas := globalGasCap
if gas == 0 {
gas = uint64(math.MaxUint64 / 2)
}
args.Gas = (*hexutil.Uint64)(&gas)
} else {
if globalGasCap > 0 && globalGasCap < uint64(*args.Gas) {
log.Warn("Caller gas above allowance, capping", "requested", args.Gas, "cap", globalGasCap)
args.Gas = (*hexutil.Uint64)(&globalGasCap)
}
}
args.setGasUsingCap(globalGasCap)
if args.Nonce == nil {
args.Nonce = new(hexutil.Uint64)
}
Expand Down Expand Up @@ -427,7 +436,7 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int,
}

// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called.
func (args *TransactionArgs) ToMessage(baseFee *big.Int, globalGasCap uint64, header *types.Header, state *state.StateDB, runMode core.MessageRunMode, chainID *big.Int, gasLimitNotSetByUser bool) *core.Message {
func (args *TransactionArgs) ToMessage(baseFee *big.Int, globalGasCap uint64, header *types.Header, state *state.StateDB, runMode core.MessageRunMode) *core.Message {
var (
gasPrice *big.Int
gasFeeCap *big.Int
Expand Down Expand Up @@ -480,28 +489,20 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, globalGasCap uint64, he
SkipL1Charging: skipL1Charging,
}
// Arbitrum: raise the gas cap to ignore L1 costs so that it's compute-only
if state != nil {
if state != nil && !skipL1Charging {
// ToMessage recurses once to allow ArbOS to intercept the result for all callers
// ArbOS uses this to modify globalGasCap so that the cap will ignore this tx's specific L1 data costs
core.InterceptRPCGasCap(&globalGasCap, msg, header, state)
err := args.CallDefaults(globalGasCap, baseFee, chainID, gasLimitNotSetByUser)
// If we fail to call defaults, we should panic because it's a programming error since we've already called it once
if err != nil {
panic(fmt.Sprintf("CallDefaults failed: %v", err))
postingGas, err := core.RPCPostingGasHook(msg, header, state)
if err == nil {
args.setGasUsingCap(globalGasCap + postingGas)
msg.GasLimit = uint64(*args.Gas)
} else {
log.Error("error reading posting gas", "err", err)
}
return args.ToMessage(baseFee, globalGasCap, header, nil, runMode, chainID, gasLimitNotSetByUser) // we pass a nil to avoid another recursion
}
return msg
}

// Raises the vanilla gas cap by the tx's l1 data costs in l2 terms. This creates a new gas cap that after
// data payments are made, equals the original vanilla cap for the remaining, L2-specific work the tx does.
func (args *TransactionArgs) L2OnlyGasCap(baseFee *big.Int, gasCap uint64, header *types.Header, state *state.StateDB, runMode core.MessageRunMode, chainID *big.Int, gasLimitNotSetByUser bool) (uint64, error) {
msg := args.ToMessage(baseFee, gasCap, header, nil, runMode, chainID, gasLimitNotSetByUser)
core.InterceptRPCGasCap(&gasCap, msg, header, state)
return gasCap, nil
}

// ToTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called.
func (args *TransactionArgs) ToTransaction() *types.Transaction {
Expand Down

0 comments on commit ed53c04

Please sign in to comment.