Skip to content

Commit

Permalink
Merge pull request #1253 from OffchainLabs/ordering-fee
Browse files Browse the repository at this point in the history
Tx Tips in L2
  • Loading branch information
rachel-bousfield authored Oct 27, 2022
2 parents 0b0b1a9 + b10b9de commit d25fc97
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 54 deletions.
2 changes: 2 additions & 0 deletions arbos/arbosState/arbosstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ func (state *ArbosState) UpgradeArbosVersion(upgradeTo uint64, firstTime bool) e
// no state changes needed
case 7:
// no state changes needed
case 8:
// no state changes needed
default:
return fmt.Errorf("unrecognized ArbOS version %v, %w", state.arbosVersion, ErrFatalNodeOutOfDate)
}
Expand Down
8 changes: 4 additions & 4 deletions arbos/block_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func ProduceBlockAdvanced(

complete := types.Transactions{}
receipts := types.Receipts{}
gasPrice := header.BaseFee
basefee := header.BaseFee
time := header.Time
expectedBalanceDelta := new(big.Int)
redeems := types.Transactions{}
Expand Down Expand Up @@ -237,15 +237,15 @@ func ProduceBlockAdvanced(
return nil, nil, err
}

if gasPrice.Sign() > 0 {
if basefee.Sign() > 0 {
dataGas = math.MaxUint64
posterCost, _ := state.L1PricingState().GetPosterInfo(tx, poster)
posterCostInL2Gas := arbmath.BigDiv(posterCost, gasPrice)
posterCostInL2Gas := arbmath.BigDiv(posterCost, basefee)

if posterCostInL2Gas.IsUint64() {
dataGas = posterCostInL2Gas.Uint64()
} else {
log.Error("Could not get poster cost in L2 terms", "posterCost", posterCost, "gasPrice", gasPrice)
log.Error("Could not get poster cost in L2 terms", "posterCost", posterCost, "basefee", basefee)
}
}

Expand Down
68 changes: 37 additions & 31 deletions arbos/tx_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package arbos
import (
"errors"
"fmt"
"math"
"math/big"
"time"

Expand Down Expand Up @@ -42,6 +41,7 @@ type TxProcessor struct {
PosterFee *big.Int // set once in GasChargingHook to track L1 calldata costs
posterGas uint64
computeHoldGas uint64 // amount of gas temporarily held to prevent compute from exceeding the gas limit
delayedInbox bool // whether this tx was submitted through the delayed inbox
Callers []common.Address
TopTxType *byte // set once in StartTxHook
evm *vm.EVM
Expand All @@ -62,6 +62,7 @@ func NewTxProcessor(evm *vm.EVM, msg core.Message) *TxProcessor {
state: arbosState,
PosterFee: new(big.Int),
posterGas: 0,
delayedInbox: evm.Context.Coinbase != l1pricing.BatchPosterAddress,
Callers: []common.Address{},
TopTxType: nil,
evm: evm,
Expand Down Expand Up @@ -350,13 +351,14 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r
return false, 0, nil, nil
}

func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error {
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
gasPrice := p.evm.Context.BaseFee
tipReceipient, _ := p.state.NetworkFeeAccount()
basefee := p.evm.Context.BaseFee

var poster common.Address
if p.msg.RunMode() != types.MessageCommitMode {
Expand All @@ -366,9 +368,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error {
}
posterCost, calldataUnits := p.state.L1PricingState().PosterDataCost(p.msg, poster)
if calldataUnits > 0 {
if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil {
return err
}
p.state.Restrict(p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits))
}

if p.msg.RunMode() == types.MessageGasEstimationMode {
Expand All @@ -378,29 +378,28 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error {

minGasPrice, _ := p.state.L2PricingState().MinBaseFeeWei()

adjustedPrice := arbmath.BigMulByFrac(gasPrice, 7, 8) // assume congestion
adjustedPrice := arbmath.BigMulByFrac(basefee, 7, 8) // assume congestion
if arbmath.BigLessThan(adjustedPrice, minGasPrice) {
adjustedPrice = minGasPrice
}
gasPrice = adjustedPrice
basefee = adjustedPrice

// Pad the L1 cost in case the L1 gas price rises
posterCost = arbmath.BigMulByBips(posterCost, GasEstimationL1PricePadding)
}

if gasPrice.Sign() > 0 {
posterCostInL2Gas := arbmath.BigDiv(posterCost, gasPrice) // the cost as if it were an amount of gas
if !posterCostInL2Gas.IsUint64() {
posterCostInL2Gas = arbmath.UintToBig(math.MaxUint64)
}
p.posterGas = posterCostInL2Gas.Uint64()
p.PosterFee = arbmath.BigMul(posterCostInL2Gas, gasPrice) // round down
if basefee.Sign() > 0 {
// 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.

p.posterGas = arbmath.BigToUintSaturating(arbmath.BigDiv(posterCost, basefee))
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 core.ErrIntrinsicGas
return tipReceipient, core.ErrIntrinsicGas
}
*gasRemaining -= gasNeededToStartEVM

Expand All @@ -413,7 +412,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error {
*gasRemaining = gasAvailable
}
}
return nil
return tipReceipient, nil
}

func (p *TxProcessor) NonrefundableGas() uint64 {
Expand All @@ -431,7 +430,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) {

underlyingTx := p.msg.UnderlyingTransaction()
networkFeeAccount, _ := p.state.NetworkFeeAccount()
gasPrice := p.evm.Context.BaseFee
basefee := p.evm.Context.BaseFee
scenario := util.TracingAfterEVM

if gasLeft > p.msg.Gas() {
Expand All @@ -443,7 +442,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) {
inner, _ := underlyingTx.GetInner().(*types.ArbitrumRetryTx)

// undo Geth's refund to the From address
gasRefund := arbmath.BigMulByUint(gasPrice, gasLeft)
gasRefund := arbmath.BigMulByUint(basefee, gasLeft)
err := util.BurnBalance(&inner.From, gasRefund, p.evm, scenario, "undoRefund")
if err != nil {
log.Error("Uh oh, Geth didn't refund the user", inner.From, gasRefund)
Expand Down Expand Up @@ -478,7 +477,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) {
takeFunds(maxRefund, inner.SubmissionFeeRefund)
}
// Conceptually, the gas charge is taken from the L1 deposit pool if possible.
takeFunds(maxRefund, arbmath.BigMulByUint(gasPrice, gasUsed))
takeFunds(maxRefund, arbmath.BigMulByUint(basefee, gasUsed))
// Refund any unused gas, without overdrafting the L1 deposit.
refundNetworkFee(gasRefund)

Expand All @@ -502,13 +501,13 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) {
return
}

totalCost := arbmath.BigMul(gasPrice, 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
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, "gasPrice", gasPrice, "posterFee", p.PosterFee)
log.Error("total cost < poster cost", "gasUsed", gasUsed, "basefee", basefee, "posterFee", p.PosterFee)
p.PosterFee = big.NewInt(0)
computeCost = totalCost
}
Expand All @@ -520,8 +519,8 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) {
if infraFeeAccount != (common.Address{}) {
infraFee, err := p.state.L2PricingState().MinBaseFeeWei()
p.state.Restrict(err)
if arbmath.BigLessThan(gasPrice, infraFee) {
infraFee = gasPrice
if arbmath.BigLessThan(basefee, infraFee) {
infraFee = basefee
}
computeGas := arbmath.SaturatingUSub(gasUsed, p.posterGas)
infraComputeCost := arbmath.BigMulByUint(infraFee, computeGas)
Expand All @@ -532,9 +531,9 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) {
if arbmath.BigGreaterThan(computeCost, common.Big0) {
util.MintBalance(&networkFeeAccount, computeCost, p.evm, scenario, purpose)
}
posterFeeDestination := p.evm.Context.Coinbase
if p.state.ArbOSVersion() >= 2 {
posterFeeDestination = l1pricing.L1PricerFundsPoolAddress
posterFeeDestination := l1pricing.L1PricerFundsPoolAddress
if p.state.ArbOSVersion() < 2 {
posterFeeDestination = p.evm.Context.Coinbase
}
util.MintBalance(&posterFeeDestination, p.PosterFee, p.evm, scenario, purpose)

Expand Down Expand Up @@ -628,10 +627,17 @@ func (p *TxProcessor) L1BlockHash(blockCtx vm.BlockContext, l1BlockNumber uint64
return hash, nil
}

func (p *TxProcessor) DropTip() bool {
return p.state.ArbOSVersion() < 9 || p.delayedInbox
}

func (p *TxProcessor) GetPaidGasPrice() *big.Int {
gasPrice := p.evm.Context.BaseFee
if p.msg.RunMode() != types.MessageCommitMode && p.msg.GasFeeCap().Sign() == 0 {
gasPrice.SetInt64(0) // gasprice zero behavior
gasPrice := p.evm.GasPrice
if p.state.ArbOSVersion() < 9 {
gasPrice = p.evm.Context.BaseFee
if p.msg.RunMode() != types.MessageCommitMode && p.msg.GasFeeCap().Sign() == 0 {
gasPrice.SetInt64(0) // gasprice zero behavior
}
}
return gasPrice
}
Expand Down
2 changes: 1 addition & 1 deletion go-ethereum
68 changes: 50 additions & 18 deletions system_tests/fees_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,68 @@ func TestSequencerFeePaid(t *testing.T) {

// get the network fee account
arbOwnerPublic, err := precompilesgen.NewArbOwnerPublic(common.HexToAddress("0x6b"), l2client)
Require(t, err, "could not deploy ArbOwner contract")
Require(t, err, "failed to deploy contract")
arbGasInfo, err := precompilesgen.NewArbGasInfo(common.HexToAddress("0x6c"), l2client)
Require(t, err, "could not deploy ArbOwner contract")
Require(t, err, "failed to deploy contract")
arbDebug, err := precompilesgen.NewArbDebug(common.HexToAddress("0xff"), l2client)
Require(t, err, "failed to deploy contract")
networkFeeAccount, err := arbOwnerPublic.GetNetworkFeeAccount(callOpts)
Require(t, err, "could not get the network fee account")

l1Estimate, err := arbGasInfo.GetL1BaseFeeEstimate(callOpts)
Require(t, err)
networkBefore := GetBalance(t, ctx, l2client, networkFeeAccount)

l2info.GasPrice = GetBaseFee(t, l2client, ctx)
tx, receipt := TransferBalance(t, "Faucet", "Faucet", big.NewInt(0), l2info, l2client, ctx)
txSize := compressedTxSize(t, tx)
baseFee := GetBaseFee(t, l2client, ctx)
l2info.GasPrice = baseFee

networkAfter := GetBalance(t, ctx, l2client, networkFeeAccount)
l1Charge := arbmath.BigMulByUint(l2info.GasPrice, receipt.GasUsedForL1)
testFees := func(tip uint64) (*big.Int, *big.Int) {
tipCap := arbmath.BigMulByUint(baseFee, tip)
txOpts := l2info.GetDefaultTransactOpts("Faucet", ctx)
txOpts.GasTipCap = tipCap
gasPrice := arbmath.BigAdd(baseFee, tipCap)

networkRevenue := arbmath.BigSub(networkAfter, networkBefore)
gasUsedForL2 := receipt.GasUsed - receipt.GasUsedForL1
if !arbmath.BigEquals(networkRevenue, arbmath.BigMulByUint(tx.GasPrice(), gasUsedForL2)) {
Fail(t, "network didn't receive expected payment")
}
networkBefore := GetBalance(t, ctx, l2client, networkFeeAccount)

tx, err := arbDebug.Events(&txOpts, true, [32]byte{})
Require(t, err)
receipt, err := EnsureTxSucceeded(ctx, l2client, tx)
Require(t, err)

networkAfter := GetBalance(t, ctx, l2client, networkFeeAccount)
l1Charge := arbmath.BigMulByUint(l2info.GasPrice, receipt.GasUsedForL1)

// the network should receive
// 1. compute costs
// 2. tip on the compute costs
// 3. tip on the data costs
networkRevenue := arbmath.BigSub(networkAfter, networkBefore)
gasUsedForL2 := receipt.GasUsed - receipt.GasUsedForL1
feePaidForL2 := arbmath.BigMulByUint(gasPrice, gasUsedForL2)
tipPaidToNet := arbmath.BigMulByUint(tipCap, receipt.GasUsedForL1)
if !arbmath.BigEquals(networkRevenue, arbmath.BigAdd(feePaidForL2, tipPaidToNet)) {
Fail(t, "network didn't receive expected payment", networkRevenue, feePaidForL2, tipPaidToNet)
}

l1GasBought := arbmath.BigDiv(l1Charge, l1Estimate).Uint64()
l1GasActual := txSize * params.TxDataNonZeroGasEIP2028
txSize := compressedTxSize(t, tx)
l1GasBought := arbmath.BigDiv(l1Charge, l1Estimate).Uint64()
l1GasActual := txSize * params.TxDataNonZeroGasEIP2028

colors.PrintBlue("bytes ", l1GasBought/params.TxDataNonZeroGasEIP2028, txSize)
colors.PrintBlue("bytes ", l1GasBought/params.TxDataNonZeroGasEIP2028, txSize)

if l1GasBought != l1GasActual {
Fail(t, "the sequencer's future revenue does not match its costs", l1GasBought, l1GasActual)
if l1GasBought != l1GasActual {
Fail(t, "the sequencer's future revenue does not match its costs", l1GasBought, l1GasActual)
}
return networkRevenue, tipPaidToNet
}

net0, tip0 := testFees(0)
net2, tip2 := testFees(2)

if tip0.Sign() != 0 {
Fail(t, "nonzero tip")
}
if arbmath.BigEquals(arbmath.BigSub(net2, tip2), net0) {
Fail(t, "a tip of 2 should yield a total of 3")
}
}

Expand Down

0 comments on commit d25fc97

Please sign in to comment.