Skip to content

Commit

Permalink
Add L1 Data Fee Deduction
Browse files Browse the repository at this point in the history
Add gas consumption for L1 data fees as well to fulfill the OP Stack
Spec and compensate the sequencer for posting data on L1. To do this, a
new decorator to the default AnteHandler to calculate the L1 data fee
is added.
  • Loading branch information
natebeauregard committed Oct 29, 2024
1 parent 7055b06 commit a6b24b1
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 8 deletions.
Binary file modified cmd/monogen/testapp.zip
Binary file not shown.
4 changes: 2 additions & 2 deletions cmd/monogen/testapp/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/types/module"
_ "github.com/cosmos/cosmos-sdk/x/auth" // import for side-effects
"github.com/cosmos/cosmos-sdk/x/auth/ante"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
_ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import for side-effects
_ "github.com/cosmos/cosmos-sdk/x/bank" // import for side-effects
Expand Down Expand Up @@ -163,9 +162,10 @@ func New(
app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, make(map[string]module.AppModuleSimulation))
app.sm.RegisterStoreDecoders()

anteHandler, err := helpers.NewAnteHandler(ante.HandlerOptions{
anteHandler, err := helpers.NewAnteHandler(helpers.HandlerOptions{
AccountKeeper: app.AccountKeeper,
BankKeeper: app.BankKeeper,
RollupKeeper: app.RollupKeeper,
SignModeHandler: app.txConfig.SignModeHandler(),
})
if err != nil {
Expand Down
15 changes: 14 additions & 1 deletion x/rollup/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"fmt"

"cosmossdk.io/core/store"
"github.com/cosmos/cosmos-sdk/codec"
Expand Down Expand Up @@ -31,7 +32,7 @@ func NewKeeper(
}
}

// Helper. Prepares a `message` event with the module name and emits it
// EmitEvents prepares a `message` event with the module name and emits it
// along with the provided events.
func (k *Keeper) EmitEvents(goCtx context.Context, events sdk.Events) {
ctx := sdk.UnwrapSDKContext(goCtx)
Expand All @@ -44,3 +45,15 @@ func (k *Keeper) EmitEvents(goCtx context.Context, events sdk.Events) {

ctx.EventManager().EmitEvents(events)
}

func (k *Keeper) GetL1BlockInfo(ctx sdk.Context) (*types.L1BlockInfo, error) { //nolint:gocritic // hugeParam
l1BlockInfoBz, err := k.storeService.OpenKVStore(ctx).Get([]byte(types.KeyL1BlockInfo))
if err != nil {
return nil, fmt.Errorf("get l1 block info: %w", err)
}
var l1BlockInfo types.L1BlockInfo
if err = l1BlockInfo.Unmarshal(l1BlockInfoBz); err != nil {
return nil, fmt.Errorf("unmarshal l1 block info: %w", err)
}
return &l1BlockInfo, nil
}
49 changes: 44 additions & 5 deletions x/rollup/tx/helpers/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,57 @@ package helpers
import (
"fmt"

storetypes "cosmossdk.io/store/types"
txsigning "cosmossdk.io/x/tx/signing"
sdktypes "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
rollupkeeper "github.com/polymerdao/monomer/x/rollup/keeper"
rolluptx "github.com/polymerdao/monomer/x/rollup/tx"
)

type AnteHandler struct {
authAnte sdktypes.AnteHandler
authAnteHandler sdktypes.AnteHandler
l1DataAnteHandler sdktypes.AnteHandler
}

func NewAnteHandler(options authante.HandlerOptions) (*AnteHandler, error) { //nolint:gocritic // hugeParam
authAnteHandler, err := authante.NewAnteHandler(options)
// HandlerOptions are the options required for constructing the AnteHandler used for non-deposit txs.
// The options are the same as those required for constructing the default x/auth AnteHandler with the
// addition of the RollupKeeper.
type HandlerOptions struct {
AccountKeeper authante.AccountKeeper
BankKeeper authtypes.BankKeeper
RollupKeeper *rollupkeeper.Keeper
ExtensionOptionChecker authante.ExtensionOptionChecker
FeegrantKeeper authante.FeegrantKeeper
SignModeHandler *txsigning.HandlerMap
SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error
TxFeeChecker authante.TxFeeChecker
}

func NewAnteHandler(options HandlerOptions) (*AnteHandler, error) { //nolint:gocritic // hugeParam
authAnteHandler, err := authante.NewAnteHandler(authante.HandlerOptions{
AccountKeeper: options.AccountKeeper,
BankKeeper: options.BankKeeper,
ExtensionOptionChecker: options.ExtensionOptionChecker,
FeegrantKeeper: options.FeegrantKeeper,
SignModeHandler: options.SignModeHandler,
SigGasConsumer: options.SigGasConsumer,
TxFeeChecker: options.TxFeeChecker,
})
if err != nil {
return nil, fmt.Errorf("new auth ante handler: %v", err)
}

l1DataAnteHandler, err := NewL1DataAnteHandler(options)
if err != nil {
return nil, fmt.Errorf("new l1 data ante handler: %v", err)
}

return &AnteHandler{
authAnte: authAnteHandler,
authAnteHandler: authAnteHandler,
l1DataAnteHandler: l1DataAnteHandler,
}, nil
}

Expand All @@ -35,10 +70,14 @@ func (a *AnteHandler) AnteHandle(
}
return newCtx, err
default: // Unfortunately, the Cosmos SDK does not export its default tx type.
newCtx, err := a.authAnte(ctx, tx, simulate)
newCtx, err := a.authAnteHandler(ctx, tx, simulate)
if err != nil {
return newCtx, fmt.Errorf("auth ante handle: %v", err)
}
newCtx, err = a.l1DataAnteHandler(newCtx, tx, simulate)
if err != nil {
return newCtx, fmt.Errorf("l1 data ante handle: %v", err)
}
return newCtx, nil
}
}
21 changes: 21 additions & 0 deletions x/rollup/tx/helpers/l1_data_ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package helpers

import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)

// NewL1DataAnteHandler has a single decorator (NewConsumeGasForL1DataDecorator)
// to consume gas to compensate the sequencer for posting the transaction to Ethereum.
func NewL1DataAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { //nolint:gocritic // hugeparam
if options.RollupKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "rollup keeper is required for ante builder")
}

anteDecorators := []sdk.AnteDecorator{
NewConsumeGasForL1DataDecorator(options.RollupKeeper),
}

return sdk.ChainAnteDecorators(anteDecorators...), nil
}
91 changes: 91 additions & 0 deletions x/rollup/tx/helpers/l1_data_decorator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package helpers

import (
"fmt"
"math/big"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/rlp"
rollupkeeper "github.com/polymerdao/monomer/x/rollup/keeper"
)

// ConsumeGasForL1DataDecorator will consume gas to compensate the sequencer
// for posting the transaction to Ethereum. The gas cost is calculated based
// on the Ecotone upgrade and the sequencer is expected to post the transaction
// using blobs.
type ConsumeGasForL1DataDecorator struct {
rollupKeeper *rollupkeeper.Keeper
}

func NewConsumeGasForL1DataDecorator(rollupKeeper *rollupkeeper.Keeper) ConsumeGasForL1DataDecorator {
return ConsumeGasForL1DataDecorator{
rollupKeeper: rollupKeeper,
}
}

func (d ConsumeGasForL1DataDecorator) AnteHandle(
ctx sdk.Context, //nolint:gocritic // hugeparam
tx sdk.Tx,
simulate bool,
next sdk.AnteHandler,
) (sdk.Context, error) {
l1BlockInfo, err := d.rollupKeeper.GetL1BlockInfo(ctx)
if err != nil {
return ctx, fmt.Errorf("get l1 block info: %w", err)
}

baseFee := new(big.Int).SetBytes(l1BlockInfo.BaseFee)
baseFeeScalar := big.NewInt(int64(l1BlockInfo.BaseFeeScalar))
blobBaseFee := new(big.Int).SetBytes(l1BlockInfo.BlobBaseFee)
blobBaseFeeScalar := big.NewInt(int64(l1BlockInfo.BlobBaseFeeScalar))

l1GasUsed, err := getL1GasUsed(tx)
if err != nil {
return ctx, fmt.Errorf("get l1 gas used: %w", err)
}

const baseFeeMultiplier = 16
const divisor = 16e6

// scaledBaseFee calculation: scaledBaseFee = l1BaseFee * lBaseFeeScalar * 16
scaledBaseFee := new(big.Int).Mul(new(big.Int).Mul(baseFee, baseFeeScalar), big.NewInt(baseFeeMultiplier))

// scaledBlobBaseFee calculation: scaledBlobBaseFee = l1BlobBaseFee * l1BlobBaseFeeScalar
scaledBlobBaseFee := new(big.Int).Mul(blobBaseFee, blobBaseFeeScalar)

// l1DataCost calculation: l1DataCost = [l1GasUsed * (scaledBaseFee + scaledBlobBaseFee)] / 16e6
l1DataCost := new(big.Int).Div(new(big.Int).Mul(l1GasUsed, new(big.Int).Add(scaledBaseFee, scaledBlobBaseFee)), big.NewInt(divisor))

if !l1DataCost.IsUint64() {
return ctx, fmt.Errorf("l1 data cost overflow: %s", l1DataCost)
}

ctx.GasMeter().ConsumeGas(l1DataCost.Uint64(), "l1 data")

return next(ctx, tx, simulate)
}

// getL1GasUsed calculates the compressed size of a transaction when encoded with RLP. This is used to estimate
// the gas cost of a transaction being posted to the L1 chain by the sequencer.
func getL1GasUsed(tx sdk.Tx) (*big.Int, error) {
txBz, err := rlp.EncodeToBytes(tx)
if err != nil {
return nil, fmt.Errorf("failed to rlp encode tx: %w", err)
}

const zeroCost = 4
const oneCost = 16
const signatureCost = 68 * 16

// l1GasUsed calculation: l1GasUsed = (zeroes * 4 + ones * 16) + signatureCost
l1GasUsed := big.NewInt(signatureCost)
for _, b := range txBz {
if b == 0 {
l1GasUsed.Add(l1GasUsed, big.NewInt(zeroCost))
} else {
l1GasUsed.Add(l1GasUsed, big.NewInt(oneCost))
}
}

return l1GasUsed, nil
}

0 comments on commit a6b24b1

Please sign in to comment.