From 3e35d61d973a2f4fe14715285ffab14270fa93ef Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 24 Dec 2024 15:50:21 +0700 Subject: [PATCH 01/53] add block sdk with default lane --- app/app.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 11 +++++++---- go.sum | 17 ++++++++++------- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/app/app.go b/app/app.go index 242e6e40c..e78fbd110 100644 --- a/app/app.go +++ b/app/app.go @@ -6,6 +6,11 @@ import ( "os" "path/filepath" + blocksdkabci "github.com/skip-mev/block-sdk/v2/abci" + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + "github.com/skip-mev/block-sdk/v2/block" + "github.com/skip-mev/block-sdk/v2/block/base" + defaultlane "github.com/skip-mev/block-sdk/v2/lanes/base" "github.com/spf13/cast" abci "github.com/cometbft/cometbft/abci/types" @@ -21,6 +26,7 @@ import ( "cosmossdk.io/client/v2/autocli" "cosmossdk.io/core/appmodule" "cosmossdk.io/log" + "cosmossdk.io/math" "cosmossdk.io/x/tx/signing" upgradetypes "cosmossdk.io/x/upgrade/types" @@ -253,6 +259,31 @@ func NewBandApp( app.sm.RegisterStoreDecoders() + // Create the signer extractor. This is used to extract the expected signers from + // a transaction. Each lane can have a different signer extractor if needed. + signerAdapter := signerextraction.NewDefaultAdapter() + + defaultConfig := base.LaneConfig{ + Logger: app.Logger(), + TxEncoder: app.txConfig.TxEncoder(), + TxDecoder: app.txConfig.TxDecoder(), + MaxBlockSpace: math.LegacyZeroDec(), + SignerExtractor: signerAdapter, + MaxTxs: 0, + } + defaultMatchHandler := base.DefaultMatchHandler() + defaultLane := defaultlane.NewDefaultLane( + defaultConfig, + defaultMatchHandler, + ) + + lanedMempool, err := block.NewLanedMempool(app.Logger(), []block.Lane{defaultLane}) + if err != nil { + panic(err) + } + // set the mempool + app.SetMempool(lanedMempool) + // Initialize stores. app.MountKVStores(app.GetKVStoreKey()) app.MountTransientStores(app.GetTransientStoreKey()) @@ -282,6 +313,25 @@ func NewBandApp( panic(fmt.Errorf("failed to create ante handler: %s", err)) } + // update ante-handlers on lanes + opt := []base.LaneOption{ + base.WithAnteHandler(anteHandler), + } + defaultLane.WithOptions(opt...) + + // ABCI handlers + // prepare proposal + proposalHandler := blocksdkabci.NewDefaultProposalHandler( + app.Logger(), + txConfig.TxDecoder(), + txConfig.TxEncoder(), + lanedMempool, + ) + + // set the Prepare / ProcessProposal Handlers on the app to be the `LanedMempool`'s + app.SetPrepareProposal(proposalHandler.PrepareProposalHandler()) + app.SetProcessProposal(proposalHandler.ProcessProposalHandler()) + postHandler, err := NewPostHandler( PostHandlerOptions{}, ) diff --git a/go.mod b/go.mod index a53aeb336..9ec58ab74 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/bandprotocol/chain/v3 -go 1.22.3 +go 1.22.4 + +toolchain go1.22.10 require ( cosmossdk.io/api v0.7.6 @@ -41,6 +43,7 @@ require ( github.com/oasisprotocol/oasis-core/go v0.2202.7 github.com/peterbourgon/diskv v2.0.1+incompatible github.com/prometheus/client_golang v1.20.5 + github.com/skip-mev/block-sdk/v2 v2.1.5 github.com/spf13/cast v1.7.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -101,7 +104,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/emicklei/dot v1.6.2 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect @@ -138,7 +141,7 @@ require ( github.com/hashicorp/go-metrics v0.5.3 // indirect github.com/hashicorp/go-plugin v1.5.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -154,7 +157,7 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/lib/pq v1.10.7 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/linxGnu/grocksdb v1.8.14 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect diff --git a/go.sum b/go.sum index 16aa94b59..9d7475658 100644 --- a/go.sum +++ b/go.sum @@ -455,8 +455,8 @@ github.com/ethereum/go-ethereum v1.14.12 h1:8hl57x77HSUo+cXExrURjU/w1VhL+ShCTJrT github.com/ethereum/go-ethereum v1.14.12/go.mod h1:RAC2gVMWJ6FkxSPESfbshrcKpIokgQKsVKmAuqdekDY= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -708,8 +708,9 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -799,8 +800,8 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/levigross/grequests v0.0.0-20231203190023-9c307ef1f48d h1:8fVmm2qScPn4JAF/YdTtqrPP3n58FgZ4GbKTNfaPuRs= github.com/levigross/grequests v0.0.0-20231203190023-9c307ef1f48d/go.mod h1:dFu6nuJHC3u9kCDcyGrEL7LwhK2m6Mt+alyiiIjDrRY= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ= @@ -999,8 +1000,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skip-mev/block-sdk/v2 v2.1.5 h1:3uoYG2ayP253wiohBPKdD3LrkJGd1Kgw914mmI1ZyOI= +github.com/skip-mev/block-sdk/v2 v2.1.5/go.mod h1:E8SvITZUdxkes3gI3+kgESZL+NLffkcLKnowUgYTOf4= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= From 738dabdef83821e5df79c668c9e3bcfdb6d4319e Mon Sep 17 00:00:00 2001 From: colmazia Date: Wed, 25 Dec 2024 17:03:09 +0700 Subject: [PATCH 02/53] add feeds lanes for MsgSubmitSignalPrices --- app/app.go | 25 +++----------- app/lanes.go | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 app/lanes.go diff --git a/app/app.go b/app/app.go index e78fbd110..dca8e9e4e 100644 --- a/app/app.go +++ b/app/app.go @@ -7,10 +7,8 @@ import ( "path/filepath" blocksdkabci "github.com/skip-mev/block-sdk/v2/abci" - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" "github.com/skip-mev/block-sdk/v2/block" "github.com/skip-mev/block-sdk/v2/block/base" - defaultlane "github.com/skip-mev/block-sdk/v2/lanes/base" "github.com/spf13/cast" abci "github.com/cometbft/cometbft/abci/types" @@ -26,7 +24,6 @@ import ( "cosmossdk.io/client/v2/autocli" "cosmossdk.io/core/appmodule" "cosmossdk.io/log" - "cosmossdk.io/math" "cosmossdk.io/x/tx/signing" upgradetypes "cosmossdk.io/x/upgrade/types" @@ -259,25 +256,10 @@ func NewBandApp( app.sm.RegisterStoreDecoders() - // Create the signer extractor. This is used to extract the expected signers from - // a transaction. Each lane can have a different signer extractor if needed. - signerAdapter := signerextraction.NewDefaultAdapter() - - defaultConfig := base.LaneConfig{ - Logger: app.Logger(), - TxEncoder: app.txConfig.TxEncoder(), - TxDecoder: app.txConfig.TxDecoder(), - MaxBlockSpace: math.LegacyZeroDec(), - SignerExtractor: signerAdapter, - MaxTxs: 0, - } - defaultMatchHandler := base.DefaultMatchHandler() - defaultLane := defaultlane.NewDefaultLane( - defaultConfig, - defaultMatchHandler, - ) + // initialize lanes + mempool + feedsLane, defaultLane := CreateLanes(app, txConfig) - lanedMempool, err := block.NewLanedMempool(app.Logger(), []block.Lane{defaultLane}) + lanedMempool, err := block.NewLanedMempool(app.Logger(), []block.Lane{feedsLane, defaultLane}) if err != nil { panic(err) } @@ -317,6 +299,7 @@ func NewBandApp( opt := []base.LaneOption{ base.WithAnteHandler(anteHandler), } + feedsLane.WithOptions(opt...) defaultLane.WithOptions(opt...) // ABCI handlers diff --git a/app/lanes.go b/app/lanes.go new file mode 100644 index 000000000..36c06646b --- /dev/null +++ b/app/lanes.go @@ -0,0 +1,98 @@ +package band + +import ( + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + "github.com/skip-mev/block-sdk/v2/block/base" + defaultlane "github.com/skip-mev/block-sdk/v2/lanes/base" + + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + + feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" +) + +const ( + maxTxPerFeeds = 500 // this is the maximum # of bids that will be held in the app-side in-memory mempool + maxTxPerDefaultLane = 3000 // all other txs +) + +var ( + defaultLaneBlockspacePercentage = math.LegacyMustNewDecFromStr("0.90") + FeedsBlockspacePercentage = math.LegacyMustNewDecFromStr("0.10") +) + +// CreateLanes walks through the process of creating the lanes for the block sdk. In this function +// we create three separate lanes - MEV, Free, and Default - and then return them. +func CreateLanes(app *BandApp, txConfig client.TxConfig) (*base.BaseLane, *base.BaseLane) { + // Create the signer extractor. This is used to extract the expected signers from + // a transaction. Each lane can have a different signer extractor if needed. + signerAdapter := signerextraction.NewDefaultAdapter() + + // Create the configurations for each lane. These configurations determine how many + // transactions the lane can store, the maximum block space the lane can consume, and + // the signer extractor used to extract the expected signers from a transaction. + + // Create a mev configuration that accepts maxTxPerFeeds transactions and consumes FeedsBlockspacePercentage of the + // block space. + feedsLaneConfig := base.LaneConfig{ + Logger: app.Logger(), + TxEncoder: txConfig.TxEncoder(), + TxDecoder: txConfig.TxDecoder(), + MaxBlockSpace: FeedsBlockspacePercentage, + SignerExtractor: signerAdapter, + MaxTxs: maxTxPerFeeds, + } + + // Create a default configuration that accepts maxTxPerDefaultLane transactions and consumes defaultLaneBlockspacePercentage of the + // block space. + defaultConfig := base.LaneConfig{ + Logger: app.Logger(), + TxEncoder: txConfig.TxEncoder(), + TxDecoder: txConfig.TxDecoder(), + MaxBlockSpace: defaultLaneBlockspacePercentage, + SignerExtractor: signerAdapter, + MaxTxs: maxTxPerDefaultLane, + } + + // Create the match handlers for each lane. These match handlers determine whether or not + // a transaction belongs in the lane. + + // Create the final match handler for the default lane. I.e this will direct all txs that are + // not free nor mev to this lane + defaultMatchHandler := base.DefaultMatchHandler() + + options := []base.LaneOption{ + base.WithMatchHandler(FeedsMatchHandler()), + } + + // Create the lanes. + feedsLane, err := base.NewBaseLane(feedsLaneConfig, "feeds", options...) + if err != nil { + panic(err) + } + + defaultLane := defaultlane.NewDefaultLane( + defaultConfig, + defaultMatchHandler, + ) + + return feedsLane, defaultLane +} + +func FeedsMatchHandler() base.MatchHandler { + return func(_ sdk.Context, tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + + for _, msg := range msgs { + if _, ok := msg.(*feedstypes.MsgSubmitSignalPrices); !ok { + return false + } + } + return true + } +} From 5805491f17242b1eebfbf3c94a57d56a635e927c Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 4 Feb 2025 14:43:13 +0700 Subject: [PATCH 03/53] create band custom mempool --- app/ante.go | 38 ++- app/app.go | 53 ++-- app/mempool.go | 254 +++++++++++++++++ app/proposal_handler.go | 254 +++++++++++++++++ app/proposal_handler_test.go | 516 +++++++++++++++++++++++++++++++++++ 5 files changed, 1085 insertions(+), 30 deletions(-) create mode 100644 app/mempool.go create mode 100644 app/proposal_handler.go create mode 100644 app/proposal_handler_test.go diff --git a/app/ante.go b/app/ante.go index 24b1b5911..214e01c2b 100644 --- a/app/ante.go +++ b/app/ante.go @@ -98,11 +98,13 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { ante.NewTxTimeoutHeightDecorator(), ante.NewValidateMemoDecorator(options.AccountKeeper), ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), - ante.NewDeductFeeDecorator( - options.AccountKeeper, - options.BankKeeper, - options.FeegrantKeeper, - options.TxFeeChecker, + NewIgnoreDecorator( + ante.NewDeductFeeDecorator( + options.AccountKeeper, + options.BankKeeper, + options.FeegrantKeeper, + options.TxFeeChecker, + ), ), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewSetPubKeyDecorator(options.AccountKeeper), @@ -115,3 +117,29 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { return sdk.ChainAnteDecorators(anteDecorators...), nil } + +// IgnoreDecorator is an AnteDecorator that wraps an existing AnteDecorator. It allows +// for the AnteDecorator to be ignored for specified lanes. +type IgnoreDecorator struct { + decorator sdk.AnteDecorator +} + +// NewIgnoreDecorator returns a new IgnoreDecorator instance. +func NewIgnoreDecorator(decorator sdk.AnteDecorator) *IgnoreDecorator { + return &IgnoreDecorator{ + decorator: decorator, + } +} + +// AnteHandle implements the sdk.AnteDecorator interface. If the transaction belongs to +// one of the lanes, the next AnteHandler is called. Otherwise, the decorator's AnteHandler +// is called. +func (sd IgnoreDecorator) AnteHandle( + ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler, +) (sdk.Context, error) { + if isBankSendTx(tx) || isDelegateTx(tx) { + return next(ctx, tx, simulate) + } + + return sd.decorator.AnteHandle(ctx, tx, simulate, next) +} diff --git a/app/app.go b/app/app.go index dca8e9e4e..b8f7506b8 100644 --- a/app/app.go +++ b/app/app.go @@ -6,9 +6,6 @@ import ( "os" "path/filepath" - blocksdkabci "github.com/skip-mev/block-sdk/v2/abci" - "github.com/skip-mev/block-sdk/v2/block" - "github.com/skip-mev/block-sdk/v2/block/base" "github.com/spf13/cast" abci "github.com/cometbft/cometbft/abci/types" @@ -256,15 +253,18 @@ func NewBandApp( app.sm.RegisterStoreDecoders() - // initialize lanes + mempool - feedsLane, defaultLane := CreateLanes(app, txConfig) + // // initialize lanes + mempool + // feedsLane, defaultLane := CreateLanes(app, txConfig) - lanedMempool, err := block.NewLanedMempool(app.Logger(), []block.Lane{feedsLane, defaultLane}) - if err != nil { - panic(err) - } + // lanedMempool, err := block.NewLanedMempool(app.Logger(), []block.Lane{feedsLane, defaultLane}) + // if err != nil { + // panic(err) + // } + + // create Band mempool + bandMempool := NewBandMempool(txConfig.TxEncoder()) // set the mempool - app.SetMempool(lanedMempool) + app.SetMempool(bandMempool) // Initialize stores. app.MountKVStores(app.GetKVStoreKey()) @@ -295,21 +295,24 @@ func NewBandApp( panic(fmt.Errorf("failed to create ante handler: %s", err)) } - // update ante-handlers on lanes - opt := []base.LaneOption{ - base.WithAnteHandler(anteHandler), - } - feedsLane.WithOptions(opt...) - defaultLane.WithOptions(opt...) - - // ABCI handlers - // prepare proposal - proposalHandler := blocksdkabci.NewDefaultProposalHandler( - app.Logger(), - txConfig.TxDecoder(), - txConfig.TxEncoder(), - lanedMempool, - ) + // // update ante-handlers on lanes + // opt := []base.LaneOption{ + // base.WithAnteHandler(anteHandler), + // } + // feedsLane.WithOptions(opt...) + // defaultLane.WithOptions(opt...) + + // // ABCI handlers + // // prepare proposal + // proposalHandler := blocksdkabci.NewDefaultProposalHandler( + // app.Logger(), + // txConfig.TxDecoder(), + // txConfig.TxEncoder(), + // lanedMempool, + // ) + + // proposal handler + proposalHandler := NewDefaultProposalHandler(app.Logger(), txConfig.TxDecoder(), bandMempool) // set the Prepare / ProcessProposal Handlers on the app to be the `LanedMempool`'s app.SetPrepareProposal(proposalHandler.PrepareProposalHandler()) diff --git a/app/mempool.go b/app/mempool.go new file mode 100644 index 000000000..251761430 --- /dev/null +++ b/app/mempool.go @@ -0,0 +1,254 @@ +package band + +import ( + "context" + "encoding/hex" + "fmt" + "strings" + + comettypes "github.com/cometbft/cometbft/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" + + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +var _ sdkmempool.Mempool = (*BandMempool)(nil) + +// BandMempool defines the Band mempool implementation. +type BandMempool struct { + txEncoder sdk.TxEncoder + // bankSendTxs contains the submit price transactions. + bankSendTxs []TxWithInfo + + // delegateTxs contains the delegate transactions. + delegateTxs []TxWithInfo + + // otherTxs contains the other transactions. + otherTxs []TxWithInfo +} + +// NewBandMempool returns a new BandMempool. +func NewBandMempool(txEncoder sdk.TxEncoder) *BandMempool { + return &BandMempool{ + txEncoder: txEncoder, + bankSendTxs: []TxWithInfo{}, + delegateTxs: []TxWithInfo{}, + otherTxs: []TxWithInfo{}, + } +} + +// Insert inserts a transaction into the mempool. +func (m *BandMempool) Insert(ctx context.Context, tx sdk.Tx) error { + txInfo, err := m.GetTxInfo(tx) + if err != nil { + fmt.Println("failed to get hash of tx", "err", err) + + return err + } + + // if tx is a bank send transaction + if isBankSendTx(tx) { + m.bankSendTxs = append(m.bankSendTxs, txInfo) + return nil + } + + // if tx is a delegate transaction + if isDelegateTx(tx) { + m.delegateTxs = append(m.delegateTxs, txInfo) + return nil + } + + // if tx is any other transaction + m.otherTxs = append(m.otherTxs, txInfo) + return nil +} + +// Select returns an Iterator over the app-side mempool. +func (m *BandMempool) Select(ctx context.Context, txs [][]byte) sdkmempool.Iterator { + return nil +} + +// CountTx returns the number of transactions currently in the mempool. +func (m *BandMempool) CountTx() int { + return len(m.bankSendTxs) + len(m.delegateTxs) + len(m.otherTxs) +} + +// Remove attempts to remove a transaction from the mempool. +func (m *BandMempool) Remove(tx sdk.Tx) error { + fmt.Println("Removing transaction", tx, "from mempool") + txInfo, err := m.GetTxInfo(tx) + if err != nil { + fmt.Println("failed to get hash of tx", "err", err) + + return err + } + // Remove from bankSendTxs + m.bankSendTxs = removeTxFromSlice(m.bankSendTxs, txInfo) + // Remove from delegateTxs + m.delegateTxs = removeTxFromSlice(m.delegateTxs, txInfo) + // Remove from otherTxs + m.otherTxs = removeTxFromSlice(m.otherTxs, txInfo) + return nil +} + +// PrepareBandProposal fills the proposal with transactions from each lane. +func (m *BandMempool) PrepareBandProposal(ctx sdk.Context, proposal Proposal) (Proposal, error) { + fmt.Println("number of txs in bankSendTxs", len(m.bankSendTxs)) + fmt.Println("number of txs in delegateTxs", len(m.delegateTxs)) + fmt.Println("number of txs in otherTxs", len(m.otherTxs)) + // Calculate the gas limits for each category. + totalGasLimit := proposal.Info.MaxGasLimit + bankSendGasLimit := (30 * totalGasLimit) / 100 + delegateGasLimit := (30 * totalGasLimit) / 100 + otherGasLimit := (40 * totalGasLimit) / 100 + + // Fill the proposal with bankSendTxs (up to 30% of the gas limit). + proposal, bankSendRemaining := m.fillProposalWithTxs(proposal, m.bankSendTxs, bankSendGasLimit) + if bankSendRemaining > 0 { + // If there is remaining gas after filling bankSendTxs, use it for delegateTxs. + delegateGasLimit += bankSendRemaining + } + + // Fill the proposal with delegateTxs (up to 30% of the gas limit). + proposal, delegateRemaining := m.fillProposalWithTxs(proposal, m.delegateTxs, delegateGasLimit) + if delegateRemaining > 0 { + // If there is remaining gas after filling delegateTxs, use it for otherTxs. + otherGasLimit += delegateRemaining + } + + // Fill the proposal with otherTxs (up to 40% of the gas limit). + proposal, otherRemaining := m.fillProposalWithTxs(proposal, m.otherTxs, otherGasLimit) + if otherRemaining > 0 { + // If there is still remaining gas, fill it in the order: bankSendTxs -> delegateTxs -> otherTxs. + proposal, otherRemaining = m.fillProposalWithTxs(proposal, m.bankSendTxs, otherRemaining) + proposal, otherRemaining = m.fillProposalWithTxs(proposal, m.delegateTxs, otherRemaining) + proposal, _ = m.fillProposalWithTxs(proposal, m.otherTxs, otherRemaining) + } + + return proposal, nil +} + +// fillProposalWithTxs fills the proposal with transactions from a given slice until the gas limit is reached. +// It returns the updated proposal and the remaining gas limit. +func (m *BandMempool) fillProposalWithTxs( + proposal Proposal, + txInfos []TxWithInfo, + gasLimit uint64, +) (Proposal, uint64) { + for _, txInfo := range txInfos { + if txInfo.GasLimit > gasLimit { + // TODO: consider continuing to the next transaction if the gas limit is exceeded. + break + } + + if err := proposal.Add(txInfo); err != nil { + // TODO: consider break in case of full proposal. + continue + } + + gasLimit -= txInfo.GasLimit + } + + return proposal, gasLimit +} + +func (m *BandMempool) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { + txBytes, err := m.txEncoder(tx) + if err != nil { + return TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) + } + + // TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc. + gasTx, ok := tx.(sdk.FeeTx) + if !ok { + return TxWithInfo{}, fmt.Errorf("failed to cast transaction to gas tx") + } + + return TxWithInfo{ + Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), + Size: int64(len(txBytes)), + GasLimit: gasTx.GetGas(), + TxBytes: txBytes, + }, nil +} + +// TxWithInfo contains the information required for a transaction to be +// included in a proposal. +type TxWithInfo struct { + // Hash is the hex-encoded hash of the transaction. + Hash string + // Size is the size of the transaction in bytes. + Size int64 + // GasLimit is the gas limit of the transaction. + GasLimit uint64 + // TxBytes is the bytes of the transaction. + TxBytes []byte +} + +// removeTxFromSlice removes a transaction from a slice of transactions. +func removeTxFromSlice(txInfos []TxWithInfo, txInfo TxWithInfo) []TxWithInfo { + for i, t := range txInfos { + if t.Hash == txInfo.Hash { + fmt.Println("****** Removing transaction", txInfo.Hash, "from slice") + return append(txInfos[:i], txInfos[i+1:]...) + } + } + return txInfos +} + +// BandMempoolIterator is an iterator over the BandMempool. +type BandMempoolIterator struct { + txs []sdk.Tx + index int +} + +// Next returns the next iterator. If there are no more transactions, it returns nil. +func (it *BandMempoolIterator) Next() sdkmempool.Iterator { + it.index++ + if it.index >= len(it.txs) { + return nil + } + return it +} + +// Tx returns the transaction at the current position of the iterator. +func (it *BandMempoolIterator) Tx() sdk.Tx { + if it.index < len(it.txs) { + return it.txs[it.index] + } + return nil +} + +// isBankSendTx returns true if the transaction is a bank send transaction. +func isBankSendTx(tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + + for _, msg := range msgs { + if _, ok := msg.(*banktypes.MsgSend); !ok { + return false + } + } + + return true +} + +// isDelegateTx returns true if the transaction is a delegate transaction. +func isDelegateTx(tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + + for _, msg := range msgs { + if _, ok := msg.(*stakingtypes.MsgDelegate); !ok { + return false + } + } + + return true +} diff --git a/app/proposal_handler.go b/app/proposal_handler.go new file mode 100644 index 000000000..746da11b7 --- /dev/null +++ b/app/proposal_handler.go @@ -0,0 +1,254 @@ +package band + +import ( + "fmt" + + "github.com/skip-mev/block-sdk/v2/block/utils" + + abci "github.com/cometbft/cometbft/abci/types" + + "cosmossdk.io/log" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type ( + // ProposalHandler is a wrapper around the ABCI++ PrepareProposal and ProcessProposal + // handlers for the BandMempool. + ProposalHandler struct { + logger log.Logger + txDecoder sdk.TxDecoder + bandMempool *BandMempool + useCustomProcessProposal bool + } +) + +// NewDefaultProposalHandler returns a new ABCI++ proposal handler for the BandMempool. +// This proposal handler will not use custom process proposal logic. +func NewDefaultProposalHandler( + logger log.Logger, + txDecoder sdk.TxDecoder, + bandMempool *BandMempool, +) *ProposalHandler { + return &ProposalHandler{ + logger: logger, + txDecoder: txDecoder, + bandMempool: bandMempool, + useCustomProcessProposal: false, + } +} + +// PrepareProposalHandler prepares the proposal by selecting transactions from the BandMempool. +func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { + return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (resp *abci.ResponsePrepareProposal, err error) { + if req.Height <= 1 { + return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil + } + + // Recover from any panics during proposal preparation. + defer func() { + if rec := recover(); rec != nil { + h.logger.Error("failed to prepare proposal", "err", err) + resp = &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)} + err = fmt.Errorf("failed to prepare proposal: %v", rec) + } + }() + + h.logger.Info( + "preparing proposal from BandMempool", + "height", req.Height, + ) + + // Get the max gas limit and max block size for the proposal. + _, maxGasLimit := GetBlockLimits(ctx) + proposal := NewProposal(h.logger, req.MaxTxBytes, maxGasLimit) + + // Fill the proposal with transactions from the BandMempool. + finalProposal, err := h.bandMempool.PrepareBandProposal(ctx, proposal) + + h.logger.Info( + "prepared proposal", + "num_txs", len(proposal.Txs), + "total_tx_bytes", proposal.Info.BlockSize, + "max_tx_bytes", proposal.Info.MaxBlockSize, + "total_gas_limit", proposal.Info.GasLimit, + "max_gas_limit", proposal.Info.MaxGasLimit, + "height", req.Height, + ) + + return &abci.ResponsePrepareProposal{ + Txs: finalProposal.Txs, + }, nil + } +} + +// ProcessProposalHandler processes the proposal by verifying all transactions in the proposal. +func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { + if !h.useCustomProcessProposal { + return baseapp.NoOpProcessProposal() + } + + return func(ctx sdk.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) { + if req.Height <= 1 { + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil + } + + // Recover from any panics during proposal processing. + defer func() { + if rec := recover(); rec != nil { + h.logger.Error("failed to process proposal", "recover_err", rec) + resp = &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + err = fmt.Errorf("failed to process proposal: %v", rec) + } + }() + + // Decode the transactions in the proposal. + decodedTxs, err := utils.GetDecodedTxs(h.txDecoder, req.Txs) + if err != nil { + h.logger.Error("failed to decode txs", "err", err) + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err + } + + // Verify each transaction in the proposal. + for _, tx := range decodedTxs { + // Perform custom verification logic here if needed. + // For now, we assume all transactions are valid. + h.logger.Info("verified transaction", "tx", tx) + } + + h.logger.Info( + "processed proposal", + "num_txs", len(decodedTxs), + "height", req.Height, + ) + + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil + } +} + +// ==================================== +// Utils +// ==================================== + +const ( + // MaxUint64 is the maximum value of a uint64. + MaxUint64 = 1<<64 - 1 +) + +// GetBlockLimits retrieves the maximum number of bytes and gas limit allowed in a block. +func GetBlockLimits(ctx sdk.Context) (int64, uint64) { + blockParams := ctx.ConsensusParams().Block + + // If the max gas is set to 0, then the max gas limit for the block can be infinite. + // Otherwise, we use the max gas limit casted as a uint64 which is how gas limits are + // extracted from sdk.Tx's. + var maxGasLimit uint64 + if maxGas := blockParams.MaxGas; maxGas > 0 { + maxGasLimit = uint64(maxGas) + } else { + maxGasLimit = MaxUint64 + } + + return blockParams.MaxBytes, maxGasLimit +} + +// ==================================== +// Proposal +// ==================================== + +// ProposalInfo contains the metadata about a given proposal that was built by +// the block-sdk. This is used to verify and consolidate proposal data across +// the network. +type ProposalInfo struct { + // TxsByLane contains information about how each partial proposal + // was constructed by the block-sdk lanes. + TxsByLane map[string]uint64 `protobuf:"bytes,1,rep,name=txs_by_lane,json=txsByLane,proto3" json:"txs_by_lane,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + // MaxBlockSize corresponds to the upper bound on the size of the + // block that was used to construct this block proposal. + MaxBlockSize int64 `protobuf:"varint,2,opt,name=max_block_size,json=maxBlockSize,proto3" json:"max_block_size,omitempty"` + // MaxGasLimit corresponds to the upper bound on the gas limit of the + // block that was used to construct this block proposal. + MaxGasLimit uint64 `protobuf:"varint,3,opt,name=max_gas_limit,json=maxGasLimit,proto3" json:"max_gas_limit,omitempty"` + // BlockSize corresponds to the size of this block proposal. + BlockSize int64 `protobuf:"varint,4,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"` + // GasLimit corresponds to the gas limit of this block proposal. + GasLimit uint64 `protobuf:"varint,5,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` +} + +type ( + // Proposal defines a block proposal type. + Proposal struct { + Logger log.Logger + + // Txs is the list of transactions in the proposal. + Txs [][]byte + // Cache is a cache of the selected transactions in the proposal. + Cache map[string]struct{} + // Info contains information about the state of the proposal. + Info ProposalInfo + + currentSize int64 + currentGas uint64 + } +) + +// NewProposal returns a new empty proposal. Any transactions added to the proposal +// will be subject to the given max block size and max gas limit. +func NewProposal(logger log.Logger, maxBlockSize int64, maxGasLimit uint64) Proposal { + return Proposal{ + Logger: logger, + Txs: make([][]byte, 0), + Cache: make(map[string]struct{}), + Info: ProposalInfo{ + TxsByLane: make(map[string]uint64), + MaxBlockSize: maxBlockSize, + MaxGasLimit: maxGasLimit, + }, + } +} + +// Contains returns true if the proposal contains the given transaction. +func (p *Proposal) Contains(txHash string) bool { + _, ok := p.Cache[txHash] + return ok +} + +// Add adds a transaction to the proposal. +func (p *Proposal) Add(txInfo TxWithInfo) error { + fmt.Println("try add tx to proposal: ", txInfo.Hash) + // if the transaction is already in the block proposal, return an error. + if p.Contains(txInfo.Hash) { + return fmt.Errorf("transaction already in proposal: %s", txInfo.Hash) + } + + // if the transaction is too large, return an error. + if p.currentSize+txInfo.Size > p.Info.MaxBlockSize { + return fmt.Errorf( + "transaction size exceeds max block size: %d > %d", + p.currentSize+txInfo.Size, + p.Info.MaxBlockSize, + ) + } + + // if the transaction gas limit is too large, return an error. + if p.currentGas+txInfo.GasLimit > p.Info.MaxGasLimit { + return fmt.Errorf( + "transaction gas limit exceeds max gas limit: %d > %d", + p.currentGas+txInfo.GasLimit, + p.Info.MaxGasLimit, + ) + } + + // add the transaction to the proposal. + p.Txs = append(p.Txs, txInfo.TxBytes) + p.Cache[txInfo.Hash] = struct{}{} + + p.Info.BlockSize += txInfo.Size + p.Info.GasLimit += txInfo.GasLimit + + p.currentSize += txInfo.Size + p.currentGas += txInfo.GasLimit + + return nil +} diff --git a/app/proposal_handler_test.go b/app/proposal_handler_test.go new file mode 100644 index 000000000..3f14df6b7 --- /dev/null +++ b/app/proposal_handler_test.go @@ -0,0 +1,516 @@ +package band + +import ( + "math/rand" + "testing" + + "cosmossdk.io/log" + "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/suite" + + cometabci "github.com/cometbft/cometbft/abci/types" + tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types" +) + +type Account struct { + PrivKey cryptotypes.PrivKey + PubKey cryptotypes.PubKey + Address sdk.AccAddress + ConsKey cryptotypes.PrivKey +} + +type ProposalHandlerTestSuite struct { + suite.Suite + ctx sdk.Context + key *storetypes.KVStoreKey + + encodingConfig EncodingConfig + random *rand.Rand + accounts []Account + gasTokenDenom string +} + +func TestProposalHandlerTestSuite(t *testing.T) { + suite.Run(t, new(ProposalHandlerTestSuite)) +} + +func (s *ProposalHandlerTestSuite) SetupTest() { + // Set up basic TX encoding config. + s.encodingConfig = CreateTestEncodingConfig() + + // Create a few random accounts + s.random = rand.New(rand.NewSource(1)) + s.accounts = RandomAccounts(s.random, 5) + s.gasTokenDenom = "uband" + + s.key = storetypes.NewKVStoreKey("test") + testCtx := testutil.DefaultContextWithDB(s.T(), s.key, storetypes.NewTransientStoreKey("transient_test")) + s.ctx = testCtx.Ctx.WithIsCheckTx(true) + s.ctx = s.ctx.WithBlockHeight(1) +} + +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} + +func CreateTestEncodingConfig() EncodingConfig { + legacyAmino := codec.NewLegacyAmino() + interfaceRegistry, err := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), + }, + }, + }) + if err != nil { + panic(err) + } + + appCodec := codec.NewProtoCodec(interfaceRegistry) + txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) + + std.RegisterLegacyAminoCodec(legacyAmino) + std.RegisterInterfaces(interfaceRegistry) + + return EncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Codec: appCodec, + TxConfig: txConfig, + Amino: legacyAmino, + } +} + +func RandomAccounts(r *rand.Rand, n int) []Account { + accs := make([]Account, n) + + for i := 0; i < n; i++ { + pkSeed := make([]byte, 15) + r.Read(pkSeed) + + accs[i].PrivKey = secp256k1.GenPrivKeyFromSecret(pkSeed) + accs[i].PubKey = accs[i].PrivKey.PubKey() + accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) + + accs[i].ConsKey = ed25519.GenPrivKeyFromSecret(pkSeed) + } + + return accs +} + +func (s *ProposalHandlerTestSuite) SetupSubTest() { + s.setBlockParams(100, 1000000000000) +} + +func (s *ProposalHandlerTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { + s.ctx = s.ctx.WithConsensusParams( + tmprototypes.ConsensusParams{ + Block: &tmprototypes.BlockParams{ + MaxBytes: maxBlockSize, + MaxGas: maxGasLimit, + }, + }, + ) +} + +func (s *ProposalHandlerTestSuite) setUpProposalHandlers(bandMempool *BandMempool) *ProposalHandler { + return NewDefaultProposalHandler( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxDecoder(), + bandMempool, + ) +} + +func (s *ProposalHandlerTestSuite) TestPrepareProposal() { + s.Run("can prepare a proposal with no transactions", func() { + // Create a new BandMempool + bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) + + // Set up the band proposal handler with no transactions + proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) + s.Require().NoError(err) + s.Require().NotNil(resp) + s.Require().Equal(0, len(resp.Txs)) + }) + + s.Run("can build a proposal with a single bank send tx", func() { + // Create a bank transaction that will be inserted into the bank lane + tx, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a new BandMempool + bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) + + // Insert the transaction into the mempool + err = bandMempool.Insert(s.ctx, tx) + s.Require().NoError(err) + + // Set up the band proposal handler with no transactions + proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) + s.Require().NotNil(resp) + s.Require().NoError(err) + + proposal := s.getTxBytes(tx) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) + }) + + s.Run("can build a proposal with single tx in every types", func() { + // Create a bank transaction that will be inserted into the bank lane + tx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a delegate transaction that will be inserted into the delegate lane + tx2, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a mixed transaction that will be inserted into the other lane + tx3, err := CreateMixedTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a new BandMempool + bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) + + // Insert the transaction into the mempool + err = bandMempool.Insert(s.ctx, tx1) + s.Require().NoError(err) + + err = bandMempool.Insert(s.ctx, tx2) + s.Require().NoError(err) + + err = bandMempool.Insert(s.ctx, tx3) + s.Require().NoError(err) + + // Set up the band proposal handler with no transactions + proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) + s.Require().NotNil(resp) + s.Require().NoError(err) + + proposal := s.getTxBytes(tx1, tx2, tx3) + s.Require().Equal(3, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) + }) + + s.Run("can build a proposal with one bank tx over gas limit", func() { + // Create a bank transaction that will be inserted into the bank lane + bankTx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a bank transaction that will be inserted into the bank lane but over gas limit + bankTx2, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a delegate transaction that will be inserted into the delegate lane + delegateTx1, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + 30, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a mixed transaction that will be inserted into the other lane + otherTx1, err := CreateMixedTx( + s.encodingConfig.TxConfig, + s.accounts[3], + 0, + 0, + 40, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a new BandMempool + bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) + + // Insert the transaction into the mempool + err = bandMempool.Insert(s.ctx, bankTx1) + s.Require().NoError(err) + + err = bandMempool.Insert(s.ctx, bankTx2) + s.Require().NoError(err) + + err = bandMempool.Insert(s.ctx, delegateTx1) + s.Require().NoError(err) + + err = bandMempool.Insert(s.ctx, otherTx1) + s.Require().NoError(err) + + // Set up the band proposal handler with no transactions + proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) + s.Require().NotNil(resp) + s.Require().NoError(err) + + proposal := s.getTxBytes(bankTx1, delegateTx1, otherTx1) + s.Require().Equal(3, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) + }) + + s.Run("can build a proposal with one bank tx over gas limit but has space left", func() { + // Create a bank transaction that will be inserted into the bank lane + bankTx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a bank transaction that will be inserted into the bank lane but over gas limit + bankTx2, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a delegate transaction that will be inserted into the delegate lane + delegateTx1, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + 30, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a mixed transaction that will be inserted into the other lane + otherTx1, err := CreateMixedTx( + s.encodingConfig.TxConfig, + s.accounts[3], + 0, + 0, + 30, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Create a new BandMempool + bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) + + // Insert the transaction into the mempool + err = bandMempool.Insert(s.ctx, bankTx1) + s.Require().NoError(err) + + err = bandMempool.Insert(s.ctx, bankTx2) + s.Require().NoError(err) + + err = bandMempool.Insert(s.ctx, delegateTx1) + s.Require().NoError(err) + + err = bandMempool.Insert(s.ctx, otherTx1) + s.Require().NoError(err) + + // Set up the band proposal handler with no transactions + proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) + s.Require().NotNil(resp) + s.Require().NoError(err) + + proposal := s.getTxBytes(bankTx1, delegateTx1, otherTx1, bankTx2) + s.Require().Equal(4, len(resp.Txs)) + s.Require().Equal(proposal, resp.Txs) + }) +} + +func CreateBankSendTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, gasLimit uint64, fees ...sdk.Coin) (authsigning.Tx, error) { + msgs := make([]sdk.Msg, 1) + msgs[0] = &banktypes.MsgSend{ + FromAddress: account.Address.String(), + ToAddress: account.Address.String(), + } + + txBuilder := txCfg.NewTxBuilder() + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + sigV2 := txsigning.SignatureV2{ + PubKey: account.PrivKey.PubKey(), + Data: &txsigning.SingleSignatureData{ + SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: nonce, + } + if err := txBuilder.SetSignatures(sigV2); err != nil { + return nil, err + } + + txBuilder.SetTimeoutHeight(timeout) + + txBuilder.SetFeeAmount(fees) + + txBuilder.SetGasLimit(gasLimit) + + return txBuilder.GetTx(), nil +} + +func CreateDelegateTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, gasLimit uint64, fees ...sdk.Coin) (authsigning.Tx, error) { + msgs := make([]sdk.Msg, 1) + msgs[0] = &stakingtypes.MsgDelegate{ + DelegatorAddress: account.Address.String(), + ValidatorAddress: account.Address.String(), + } + + txBuilder := txCfg.NewTxBuilder() + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + sigV2 := txsigning.SignatureV2{ + PubKey: account.PrivKey.PubKey(), + Data: &txsigning.SingleSignatureData{ + SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: nonce, + } + if err := txBuilder.SetSignatures(sigV2); err != nil { + return nil, err + } + + txBuilder.SetTimeoutHeight(timeout) + + txBuilder.SetFeeAmount(fees) + + txBuilder.SetGasLimit(gasLimit) + + return txBuilder.GetTx(), nil +} + +func CreateMixedTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, gasLimit uint64, fees ...sdk.Coin) (authsigning.Tx, error) { + msgs := make([]sdk.Msg, 2) + msgs[0] = &banktypes.MsgSend{ + FromAddress: account.Address.String(), + ToAddress: account.Address.String(), + } + msgs[1] = &stakingtypes.MsgDelegate{ + DelegatorAddress: account.Address.String(), + ValidatorAddress: account.Address.String(), + } + + txBuilder := txCfg.NewTxBuilder() + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + sigV2 := txsigning.SignatureV2{ + PubKey: account.PrivKey.PubKey(), + Data: &txsigning.SingleSignatureData{ + SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: nonce, + } + if err := txBuilder.SetSignatures(sigV2); err != nil { + return nil, err + } + + txBuilder.SetTimeoutHeight(timeout) + + txBuilder.SetFeeAmount(fees) + + txBuilder.SetGasLimit(gasLimit) + + return txBuilder.GetTx(), nil +} + +func (s *ProposalHandlerTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte { + txBytes := make([][]byte, len(txs)) + for i, tx := range txs { + bz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + + txBytes[i] = bz + } + return txBytes +} From cecc2c7ee1f064f65ca6b71fefa227ccf32df629 Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 7 Feb 2025 17:12:18 +0700 Subject: [PATCH 04/53] refactor and add enforce one tx per signer option --- app/app.go | 8 +- app/lanes.go | 143 ++++---- app/mempool.go | 254 -------------- app/mempool/lane.go | 119 +++++++ app/mempool/mempool.go | 161 +++++++++ app/mempool/mempool_test.go | 572 ++++++++++++++++++++++++++++++++ app/mempool/proposal.go | 114 +++++++ app/mempool/proposal_handler.go | 127 +++++++ app/mempool/types.go | 19 ++ app/proposal_handler.go | 254 -------------- app/proposal_handler_test.go | 516 ---------------------------- 11 files changed, 1183 insertions(+), 1104 deletions(-) delete mode 100644 app/mempool.go create mode 100644 app/mempool/lane.go create mode 100644 app/mempool/mempool.go create mode 100644 app/mempool/mempool_test.go create mode 100644 app/mempool/proposal.go create mode 100644 app/mempool/proposal_handler.go create mode 100644 app/mempool/types.go delete mode 100644 app/proposal_handler.go delete mode 100644 app/proposal_handler_test.go diff --git a/app/app.go b/app/app.go index b8f7506b8..1fbd0e0aa 100644 --- a/app/app.go +++ b/app/app.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" "github.com/spf13/cast" abci "github.com/cometbft/cometbft/abci/types" @@ -53,6 +54,7 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/bandprotocol/chain/v3/app/keepers" + "github.com/bandprotocol/chain/v3/app/mempool" "github.com/bandprotocol/chain/v3/app/upgrades" v3 "github.com/bandprotocol/chain/v3/app/upgrades/v3" nodeservice "github.com/bandprotocol/chain/v3/client/grpc/node" @@ -261,8 +263,10 @@ func NewBandApp( // panic(err) // } + bandLanes := DefaultLanes() + // create Band mempool - bandMempool := NewBandMempool(txConfig.TxEncoder()) + bandMempool := mempool.NewMempool(txConfig.TxEncoder(), signerextraction.NewDefaultAdapter(), bandLanes) // set the mempool app.SetMempool(bandMempool) @@ -312,7 +316,7 @@ func NewBandApp( // ) // proposal handler - proposalHandler := NewDefaultProposalHandler(app.Logger(), txConfig.TxDecoder(), bandMempool) + proposalHandler := mempool.NewDefaultProposalHandler(app.Logger(), txConfig.TxDecoder(), bandMempool) // set the Prepare / ProcessProposal Handlers on the app to be the `LanedMempool`'s app.SetPrepareProposal(proposalHandler.PrepareProposalHandler()) diff --git a/app/lanes.go b/app/lanes.go index 36c06646b..c7a409d72 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -1,98 +1,85 @@ package band import ( - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" - "github.com/skip-mev/block-sdk/v2/block/base" - defaultlane "github.com/skip-mev/block-sdk/v2/lanes/base" - - "cosmossdk.io/math" - - "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" -) - -const ( - maxTxPerFeeds = 500 // this is the maximum # of bids that will be held in the app-side in-memory mempool - maxTxPerDefaultLane = 3000 // all other txs + "github.com/bandprotocol/chain/v3/app/mempool" ) -var ( - defaultLaneBlockspacePercentage = math.LegacyMustNewDecFromStr("0.90") - FeedsBlockspacePercentage = math.LegacyMustNewDecFromStr("0.10") -) - -// CreateLanes walks through the process of creating the lanes for the block sdk. In this function -// we create three separate lanes - MEV, Free, and Default - and then return them. -func CreateLanes(app *BandApp, txConfig client.TxConfig) (*base.BaseLane, *base.BaseLane) { - // Create the signer extractor. This is used to extract the expected signers from - // a transaction. Each lane can have a different signer extractor if needed. - signerAdapter := signerextraction.NewDefaultAdapter() - - // Create the configurations for each lane. These configurations determine how many - // transactions the lane can store, the maximum block space the lane can consume, and - // the signer extractor used to extract the expected signers from a transaction. - - // Create a mev configuration that accepts maxTxPerFeeds transactions and consumes FeedsBlockspacePercentage of the - // block space. - feedsLaneConfig := base.LaneConfig{ - Logger: app.Logger(), - TxEncoder: txConfig.TxEncoder(), - TxDecoder: txConfig.TxDecoder(), - MaxBlockSpace: FeedsBlockspacePercentage, - SignerExtractor: signerAdapter, - MaxTxs: maxTxPerFeeds, +// isBankSendTx returns true if this transaction is strictly a bank send transaction (MsgSend). +func isBankSendTx(tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false } - - // Create a default configuration that accepts maxTxPerDefaultLane transactions and consumes defaultLaneBlockspacePercentage of the - // block space. - defaultConfig := base.LaneConfig{ - Logger: app.Logger(), - TxEncoder: txConfig.TxEncoder(), - TxDecoder: txConfig.TxDecoder(), - MaxBlockSpace: defaultLaneBlockspacePercentage, - SignerExtractor: signerAdapter, - MaxTxs: maxTxPerDefaultLane, + for _, msg := range msgs { + if _, ok := msg.(*banktypes.MsgSend); !ok { + return false + } } + return true +} - // Create the match handlers for each lane. These match handlers determine whether or not - // a transaction belongs in the lane. - - // Create the final match handler for the default lane. I.e this will direct all txs that are - // not free nor mev to this lane - defaultMatchHandler := base.DefaultMatchHandler() - - options := []base.LaneOption{ - base.WithMatchHandler(FeedsMatchHandler()), +// isDelegateTx returns true if this transaction is strictly a staking delegate transaction (MsgDelegate). +func isDelegateTx(tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false } - - // Create the lanes. - feedsLane, err := base.NewBaseLane(feedsLaneConfig, "feeds", options...) - if err != nil { - panic(err) + for _, msg := range msgs { + if _, ok := msg.(*stakingtypes.MsgDelegate); !ok { + return false + } } + return true +} - defaultLane := defaultlane.NewDefaultLane( - defaultConfig, - defaultMatchHandler, +// isOtherTx returns true if the transaction is neither a pure bank send nor a pure delegate transaction. +func isOtherTx(tx sdk.Tx) bool { + return !isBankSendTx(tx) && !isDelegateTx(tx) +} + +// BankSendLane returns a lane named "bankSend" that matches only MsgSend transactions, +// assigning 30% of the block's gas limit to this lane. +func BankSendLane() *mempool.Lane { + return mempool.NewLane( + "bankSend", + isBankSendTx, + 30, // percentage + false, // EnforceOneTxPerSigner? set to true if you want one tx per signer ) +} - return feedsLane, defaultLane +// DelegateLane returns a lane named "delegate" that matches only MsgDelegate transactions, +// assigning 30% of the block's gas limit to this lane. +func DelegateLane() *mempool.Lane { + return mempool.NewLane( + "delegate", + isDelegateTx, + 30, // percentage + false, // EnforceOneTxPerSigner? set to true if you want one tx per signer + ) } -func FeedsMatchHandler() base.MatchHandler { - return func(_ sdk.Context, tx sdk.Tx) bool { - msgs := tx.GetMsgs() - if len(msgs) == 0 { - return false - } +// OtherLane returns a lane named "other" for any transaction that does not strictly +// match isBankSendTx or isDelegateTx. It allocates 40% of the block's gas limit. +func OtherLane() *mempool.Lane { + return mempool.NewLane( + "other", + isOtherTx, + 40, // percentage + false, // EnforceOneTxPerSigner? set to true if you want one tx per signer + ) +} - for _, msg := range msgs { - if _, ok := msg.(*feedstypes.MsgSubmitSignalPrices); !ok { - return false - } - } - return true +// DefaultLanes is a convenience helper returning the typical three lanes: +// bankSend, delegate, and other (30%, 30%, and 40%). +func DefaultLanes() []*mempool.Lane { + return []*mempool.Lane{ + BankSendLane(), + DelegateLane(), + OtherLane(), } } diff --git a/app/mempool.go b/app/mempool.go deleted file mode 100644 index 251761430..000000000 --- a/app/mempool.go +++ /dev/null @@ -1,254 +0,0 @@ -package band - -import ( - "context" - "encoding/hex" - "fmt" - "strings" - - comettypes "github.com/cometbft/cometbft/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" - - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" -) - -var _ sdkmempool.Mempool = (*BandMempool)(nil) - -// BandMempool defines the Band mempool implementation. -type BandMempool struct { - txEncoder sdk.TxEncoder - // bankSendTxs contains the submit price transactions. - bankSendTxs []TxWithInfo - - // delegateTxs contains the delegate transactions. - delegateTxs []TxWithInfo - - // otherTxs contains the other transactions. - otherTxs []TxWithInfo -} - -// NewBandMempool returns a new BandMempool. -func NewBandMempool(txEncoder sdk.TxEncoder) *BandMempool { - return &BandMempool{ - txEncoder: txEncoder, - bankSendTxs: []TxWithInfo{}, - delegateTxs: []TxWithInfo{}, - otherTxs: []TxWithInfo{}, - } -} - -// Insert inserts a transaction into the mempool. -func (m *BandMempool) Insert(ctx context.Context, tx sdk.Tx) error { - txInfo, err := m.GetTxInfo(tx) - if err != nil { - fmt.Println("failed to get hash of tx", "err", err) - - return err - } - - // if tx is a bank send transaction - if isBankSendTx(tx) { - m.bankSendTxs = append(m.bankSendTxs, txInfo) - return nil - } - - // if tx is a delegate transaction - if isDelegateTx(tx) { - m.delegateTxs = append(m.delegateTxs, txInfo) - return nil - } - - // if tx is any other transaction - m.otherTxs = append(m.otherTxs, txInfo) - return nil -} - -// Select returns an Iterator over the app-side mempool. -func (m *BandMempool) Select(ctx context.Context, txs [][]byte) sdkmempool.Iterator { - return nil -} - -// CountTx returns the number of transactions currently in the mempool. -func (m *BandMempool) CountTx() int { - return len(m.bankSendTxs) + len(m.delegateTxs) + len(m.otherTxs) -} - -// Remove attempts to remove a transaction from the mempool. -func (m *BandMempool) Remove(tx sdk.Tx) error { - fmt.Println("Removing transaction", tx, "from mempool") - txInfo, err := m.GetTxInfo(tx) - if err != nil { - fmt.Println("failed to get hash of tx", "err", err) - - return err - } - // Remove from bankSendTxs - m.bankSendTxs = removeTxFromSlice(m.bankSendTxs, txInfo) - // Remove from delegateTxs - m.delegateTxs = removeTxFromSlice(m.delegateTxs, txInfo) - // Remove from otherTxs - m.otherTxs = removeTxFromSlice(m.otherTxs, txInfo) - return nil -} - -// PrepareBandProposal fills the proposal with transactions from each lane. -func (m *BandMempool) PrepareBandProposal(ctx sdk.Context, proposal Proposal) (Proposal, error) { - fmt.Println("number of txs in bankSendTxs", len(m.bankSendTxs)) - fmt.Println("number of txs in delegateTxs", len(m.delegateTxs)) - fmt.Println("number of txs in otherTxs", len(m.otherTxs)) - // Calculate the gas limits for each category. - totalGasLimit := proposal.Info.MaxGasLimit - bankSendGasLimit := (30 * totalGasLimit) / 100 - delegateGasLimit := (30 * totalGasLimit) / 100 - otherGasLimit := (40 * totalGasLimit) / 100 - - // Fill the proposal with bankSendTxs (up to 30% of the gas limit). - proposal, bankSendRemaining := m.fillProposalWithTxs(proposal, m.bankSendTxs, bankSendGasLimit) - if bankSendRemaining > 0 { - // If there is remaining gas after filling bankSendTxs, use it for delegateTxs. - delegateGasLimit += bankSendRemaining - } - - // Fill the proposal with delegateTxs (up to 30% of the gas limit). - proposal, delegateRemaining := m.fillProposalWithTxs(proposal, m.delegateTxs, delegateGasLimit) - if delegateRemaining > 0 { - // If there is remaining gas after filling delegateTxs, use it for otherTxs. - otherGasLimit += delegateRemaining - } - - // Fill the proposal with otherTxs (up to 40% of the gas limit). - proposal, otherRemaining := m.fillProposalWithTxs(proposal, m.otherTxs, otherGasLimit) - if otherRemaining > 0 { - // If there is still remaining gas, fill it in the order: bankSendTxs -> delegateTxs -> otherTxs. - proposal, otherRemaining = m.fillProposalWithTxs(proposal, m.bankSendTxs, otherRemaining) - proposal, otherRemaining = m.fillProposalWithTxs(proposal, m.delegateTxs, otherRemaining) - proposal, _ = m.fillProposalWithTxs(proposal, m.otherTxs, otherRemaining) - } - - return proposal, nil -} - -// fillProposalWithTxs fills the proposal with transactions from a given slice until the gas limit is reached. -// It returns the updated proposal and the remaining gas limit. -func (m *BandMempool) fillProposalWithTxs( - proposal Proposal, - txInfos []TxWithInfo, - gasLimit uint64, -) (Proposal, uint64) { - for _, txInfo := range txInfos { - if txInfo.GasLimit > gasLimit { - // TODO: consider continuing to the next transaction if the gas limit is exceeded. - break - } - - if err := proposal.Add(txInfo); err != nil { - // TODO: consider break in case of full proposal. - continue - } - - gasLimit -= txInfo.GasLimit - } - - return proposal, gasLimit -} - -func (m *BandMempool) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { - txBytes, err := m.txEncoder(tx) - if err != nil { - return TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) - } - - // TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc. - gasTx, ok := tx.(sdk.FeeTx) - if !ok { - return TxWithInfo{}, fmt.Errorf("failed to cast transaction to gas tx") - } - - return TxWithInfo{ - Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), - Size: int64(len(txBytes)), - GasLimit: gasTx.GetGas(), - TxBytes: txBytes, - }, nil -} - -// TxWithInfo contains the information required for a transaction to be -// included in a proposal. -type TxWithInfo struct { - // Hash is the hex-encoded hash of the transaction. - Hash string - // Size is the size of the transaction in bytes. - Size int64 - // GasLimit is the gas limit of the transaction. - GasLimit uint64 - // TxBytes is the bytes of the transaction. - TxBytes []byte -} - -// removeTxFromSlice removes a transaction from a slice of transactions. -func removeTxFromSlice(txInfos []TxWithInfo, txInfo TxWithInfo) []TxWithInfo { - for i, t := range txInfos { - if t.Hash == txInfo.Hash { - fmt.Println("****** Removing transaction", txInfo.Hash, "from slice") - return append(txInfos[:i], txInfos[i+1:]...) - } - } - return txInfos -} - -// BandMempoolIterator is an iterator over the BandMempool. -type BandMempoolIterator struct { - txs []sdk.Tx - index int -} - -// Next returns the next iterator. If there are no more transactions, it returns nil. -func (it *BandMempoolIterator) Next() sdkmempool.Iterator { - it.index++ - if it.index >= len(it.txs) { - return nil - } - return it -} - -// Tx returns the transaction at the current position of the iterator. -func (it *BandMempoolIterator) Tx() sdk.Tx { - if it.index < len(it.txs) { - return it.txs[it.index] - } - return nil -} - -// isBankSendTx returns true if the transaction is a bank send transaction. -func isBankSendTx(tx sdk.Tx) bool { - msgs := tx.GetMsgs() - if len(msgs) == 0 { - return false - } - - for _, msg := range msgs { - if _, ok := msg.(*banktypes.MsgSend); !ok { - return false - } - } - - return true -} - -// isDelegateTx returns true if the transaction is a delegate transaction. -func isDelegateTx(tx sdk.Tx) bool { - msgs := tx.GetMsgs() - if len(msgs) == 0 { - return false - } - - for _, msg := range msgs { - if _, ok := msg.(*stakingtypes.MsgDelegate); !ok { - return false - } - } - - return true -} diff --git a/app/mempool/lane.go b/app/mempool/lane.go new file mode 100644 index 000000000..6be2294e1 --- /dev/null +++ b/app/mempool/lane.go @@ -0,0 +1,119 @@ +package mempool + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Lane defines a logical grouping of transactions within the mempool. +type Lane struct { + // Name is a friendly identifier for this lane (e.g. "bankSend", "delegate"). + Name string + + // Filter determines if a given transaction belongs in this lane. + Filter func(sdk.Tx) bool + + // Percentage is the fraction (0-100) of the block's gas budget allocated to this lane. + Percentage uint64 + + // EnforceOneTxPerSigner indicates that each signer may only have one tx + // included in this lane per proposal. + EnforceOneTxPerSigner bool + + // signersUsed tracks which signers have already added a transaction in this lane + // for the current proposal. (Only used if EnforceOneTxPerSigner == true.) + signersUsed map[string]bool + + // txs holds the set of transactions that matched Filter. + txs []TxWithInfo +} + +// NewLane is a simple constructor for a lane. +func NewLane(name string, filter func(sdk.Tx) bool, percentage uint64, enforceOneTxPerSigner bool) *Lane { + return &Lane{ + Name: name, + Filter: filter, + Percentage: percentage, + EnforceOneTxPerSigner: enforceOneTxPerSigner, + signersUsed: make(map[string]bool), + txs: []TxWithInfo{}, + } +} + +// AddTx appends a transaction to this lane's slice. +func (l *Lane) AddTx(tx TxWithInfo) { + l.txs = append(l.txs, tx) +} + +// RemoveTx removes a transaction from this lane by matching TxWithInfo.Hash. +func (l *Lane) RemoveTx(txInfo TxWithInfo) { + newTxs := make([]TxWithInfo, 0, len(l.txs)) + for _, t := range l.txs { + if t.Hash != txInfo.Hash { + newTxs = append(newTxs, t) + } + } + l.txs = newTxs +} + +// GetTxs returns the slice of lane transactions. +func (l *Lane) GetTxs() []TxWithInfo { + return l.txs +} + +// SetTxs overwrites the lane's transactions with the new slice. +func (l *Lane) SetTxs(newTxs []TxWithInfo) { + l.txs = newTxs +} + +// ResetState resets the lane’s state for a new proposal, +// clearing the signersUsed map. +func (l *Lane) ResetState() { + l.signersUsed = make(map[string]bool) +} + +// FillProposal attempts to add lane transactions to the proposal, +// respecting the laneGasLimit and the “one tx per signer” rule (if enforced). +// It returns the leftover (unconsumed) gas for the lane. +func (l *Lane) FillProposal(proposal *Proposal, laneGasLimit uint64) uint64 { + for _, txInfo := range l.txs { + // If the next tx doesn't fit the lane's gas budget, skip it (or break). + if txInfo.GasLimit > laneGasLimit { + continue + } + + // If we enforce "one tx per signer," check if any signer has already used this lane. + if l.EnforceOneTxPerSigner { + skip := false + for _, signer := range txInfo.Signers { + signerAddr := signer.Signer.String() + if l.signersUsed[signerAddr] { + // This signer has already used the lane for a prior tx. + skip = true + break + } + } + if skip { + continue + } + } + + // Attempt to add the transaction to the proposal. + if err := proposal.Add(txInfo); err != nil { + // If we fail (e.g. block size or gas limit exceeded), skip or break based on your policy. + continue + } + + // The transaction fits the lane's gas limit and the proposal’s constraints. + laneGasLimit -= txInfo.GasLimit + + // Mark signers as used if one-tx-per-signer is enforced. + if l.EnforceOneTxPerSigner { + for _, signer := range txInfo.Signers { + signerAddr := signer.Signer.String() + l.signersUsed[signerAddr] = true + } + } + } + + return laneGasLimit +} diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go new file mode 100644 index 000000000..8468c0a7d --- /dev/null +++ b/app/mempool/mempool.go @@ -0,0 +1,161 @@ +package mempool + +import ( + "context" + "encoding/hex" + "fmt" + "strings" + + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + + comettypes "github.com/cometbft/cometbft/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" +) + +// Mempool implements the sdkmempool.Mempool interface and uses Lanes internally. +type Mempool struct { + txEncoder sdk.TxEncoder + signerExtractor signerextraction.Adapter + lanes []*Lane +} + +// NewMempool returns a new mempool with the given lanes. +func NewMempool( + txEncoder sdk.TxEncoder, + signerExtractor signerextraction.Adapter, + lanes []*Lane, +) *Mempool { + return &Mempool{ + txEncoder: txEncoder, + signerExtractor: signerExtractor, + lanes: lanes, + } +} + +var _ sdkmempool.Mempool = (*Mempool)(nil) + +// Insert inserts a transaction into the first matching lane. +func (m *Mempool) Insert(ctx context.Context, tx sdk.Tx) error { + txInfo, err := m.GetTxInfo(tx) + if err != nil { + return fmt.Errorf("Insert: failed to get tx info: %w", err) + } + + placed := false + for _, lane := range m.lanes { + if lane.Filter(tx) { + lane.AddTx(txInfo) + placed = true + break + } + } + if !placed { + fmt.Printf("Insert: no lane matched for tx %s\n", txInfo.Hash) + } + return nil +} + +// Select returns a Mempool iterator (currently nil). +func (m *Mempool) Select(ctx context.Context, txs [][]byte) sdkmempool.Iterator { + return nil +} + +// CountTx returns the total number of transactions across all lanes. +func (m *Mempool) CountTx() int { + count := 0 + for _, lane := range m.lanes { + count += len(lane.GetTxs()) + } + return count +} + +// Remove attempts to remove a transaction from whichever lane it is in. +func (m *Mempool) Remove(tx sdk.Tx) error { + txInfo, err := m.GetTxInfo(tx) + if err != nil { + return fmt.Errorf("Remove: failed to get tx info: %w", err) + } + for _, lane := range m.lanes { + lane.RemoveTx(txInfo) + } + return nil +} + +// PrepareProposal divides the block gas limit among lanes (based on lane percentage), +// then calls each lane’s FillProposal method. If leftover gas is important to you, +// you can implement a second pass or distribute leftover to subsequent lanes, etc. +func (m *Mempool) PrepareProposal(ctx sdk.Context, proposal Proposal) (Proposal, error) { + // Reset each lane’s state for the new proposal, clearing signersUsed, etc. + for _, lane := range m.lanes { + lane.ResetState() + } + + totalGasLimit := proposal.Info.MaxGasLimit + + // 1) Compute each lane's gas budget from its percentage + laneGasLimits := make([]uint64, len(m.lanes)) + for i, lane := range m.lanes { + laneGasLimits[i] = (lane.Percentage * totalGasLimit) / 100 + } + + // 2) Fill the proposal lane by lane + remainder := uint64(0) + for i, lane := range m.lanes { + remainder += lane.FillProposal(&proposal, laneGasLimits[i]) + } + + // 3) use remainder gas from first round to fill proposal further + for _, lane := range m.lanes { + remainder = lane.FillProposal(&proposal, remainder) + } + return proposal, nil +} + +// GetTxInfo returns metadata (hash, size, gas, signers) for the given tx by encoding it. +func (m *Mempool) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { + txBytes, err := m.txEncoder(tx) + if err != nil { + return TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) + } + + gasTx, ok := tx.(sdk.FeeTx) + if !ok { + return TxWithInfo{}, fmt.Errorf("failed to cast transaction to FeeTx") + } + + signers, err := m.signerExtractor.GetSigners(tx) + if err != nil { + return TxWithInfo{}, err + } + + return TxWithInfo{ + Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), + Size: int64(len(txBytes)), + GasLimit: gasTx.GetGas(), + TxBytes: txBytes, + Signers: signers, + }, nil +} + +// MempoolIterator is an example iterator (optional). +type MempoolIterator struct { + txs []sdk.Tx + index int +} + +func (it *MempoolIterator) Next() sdkmempool.Iterator { + it.index++ + if it.index >= len(it.txs) { + return nil + } + return it +} + +func (it *MempoolIterator) Tx() sdk.Tx { + if it.index < len(it.txs) { + return it.txs[it.index] + } + return nil +} diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go new file mode 100644 index 000000000..7c761a882 --- /dev/null +++ b/app/mempool/mempool_test.go @@ -0,0 +1,572 @@ +package mempool + +import ( + "context" + "math/rand" + "testing" + + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + "github.com/stretchr/testify/suite" + + cometabci "github.com/cometbft/cometbft/abci/types" + tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/gogoproto/proto" + + "cosmossdk.io/log" + "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/tx/signing" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// ----------------------------------------------------------------------------- +// Test Suite Setup +// ----------------------------------------------------------------------------- + +type Account struct { + PrivKey cryptotypes.PrivKey + PubKey cryptotypes.PubKey + Address sdk.AccAddress + ConsKey cryptotypes.PrivKey +} + +type MempoolTestSuite struct { + suite.Suite + + ctx sdk.Context + key *storetypes.KVStoreKey + encodingConfig EncodingConfig + random *rand.Rand + accounts []Account + gasTokenDenom string +} + +func TestMempoolTestSuite(t *testing.T) { + suite.Run(t, new(MempoolTestSuite)) +} + +func (s *MempoolTestSuite) SetupTest() { + s.encodingConfig = CreateTestEncodingConfig() + + s.random = rand.New(rand.NewSource(1)) + s.accounts = RandomAccounts(s.random, 5) + s.gasTokenDenom = "uband" + + s.key = storetypes.NewKVStoreKey("test") + testCtx := testutil.DefaultContextWithDB(s.T(), s.key, storetypes.NewTransientStoreKey("transient_test")) + s.ctx = testCtx.Ctx.WithIsCheckTx(true) + s.ctx = s.ctx.WithBlockHeight(1) +} + +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} + +func CreateTestEncodingConfig() EncodingConfig { + legacyAmino := codec.NewLegacyAmino() + interfaceRegistry, err := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), + }, + }, + }) + if err != nil { + panic(err) + } + + appCodec := codec.NewProtoCodec(interfaceRegistry) + txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) + + std.RegisterLegacyAminoCodec(legacyAmino) + std.RegisterInterfaces(interfaceRegistry) + + return EncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Codec: appCodec, + TxConfig: txConfig, + Amino: legacyAmino, + } +} + +func RandomAccounts(r *rand.Rand, n int) []Account { + accs := make([]Account, n) + for i := 0; i < n; i++ { + pkSeed := make([]byte, 15) + r.Read(pkSeed) + + accs[i].PrivKey = secp256k1.GenPrivKeyFromSecret(pkSeed) + accs[i].PubKey = accs[i].PrivKey.PubKey() + accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) + + accs[i].ConsKey = ed25519.GenPrivKeyFromSecret(pkSeed) + } + return accs +} + +func (s *MempoolTestSuite) SetupSubTest() { + s.setBlockParams(100, 1000000000000) +} + +func (s *MempoolTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { + s.ctx = s.ctx.WithConsensusParams( + tmprototypes.ConsensusParams{ + Block: &tmprototypes.BlockParams{ + MaxBytes: maxBlockSize, + MaxGas: maxGasLimit, + }, + }, + ) +} + +// ----------------------------------------------------------------------------- +// Create Mempool + Lanes +// ----------------------------------------------------------------------------- + +// In your actual code, you'd likely have a helper that returns a mempool with +// bank, delegate, and "other" lanes at 30%, 30%, 40%. +func (s *MempoolTestSuite) newMempool() *Mempool { + lanes := []*Lane{ + NewLane("bankSend", isBankSendTx, 30, false), + NewLane("delegate", isDelegateTx, 30, true), + NewLane("other", isOtherTx, 40, false), + } + + // Provide your actual signer extraction adapter if needed. For test, a default or mock might suffice. + return NewMempool(s.encodingConfig.TxConfig.TxEncoder(), signerextraction.NewDefaultAdapter(), lanes) +} + +func isBankSendTx(tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if _, ok := msg.(*banktypes.MsgSend); !ok { + return false + } + } + return true +} + +func isDelegateTx(tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if _, ok := msg.(*stakingtypes.MsgDelegate); !ok { + return false + } + } + return true +} + +func isOtherTx(tx sdk.Tx) bool { + // fallback if not pure bank send nor pure delegate + return !isBankSendTx(tx) && !isDelegateTx(tx) +} + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +func (s *MempoolTestSuite) TestPrepareProposal() { + s.SetupSubTest() + + s.Run("can prepare a proposal with no transactions", func() { + mem := s.newMempool() + + // Build a "proposal handler" that uses the mempool + ph := s.newProposalHandler(mem) + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ + Height: 2, + MaxTxBytes: maxTxBytes, + }) + s.Require().NoError(err) + s.Require().NotNil(resp) + s.Require().Equal(0, len(resp.Txs)) + }) + + s.Run("a single bank send tx", func() { + tx, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + mem := s.newMempool() + s.Require().NoError(mem.Insert(context.Background(), tx)) + + ph := s.newProposalHandler(mem) + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ + Height: 2, + MaxTxBytes: maxTxBytes, + }) + s.Require().NoError(err) + s.Require().NotNil(resp) + + expected := s.getTxBytes(tx) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(expected, resp.Txs) + }) + + s.Run("single tx in every type", func() { + tx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + tx2, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + tx3, err := CreateMixedTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + mem := s.newMempool() + s.Require().NoError(mem.Insert(context.Background(), tx1)) + s.Require().NoError(mem.Insert(context.Background(), tx2)) + s.Require().NoError(mem.Insert(context.Background(), tx3)) + + ph := s.newProposalHandler(mem) + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ + Height: 2, + MaxTxBytes: maxTxBytes, + }) + s.Require().NoError(err) + s.Require().NotNil(resp) + + expected := s.getTxBytes(tx1, tx2, tx3) + s.Require().Equal(3, len(resp.Txs)) + s.Require().Equal(expected, resp.Txs) + }) + + s.Run("one bank tx over gas limit", func() { + bankTx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + bankTx2, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + delegateTx1, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + 30, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + otherTx1, err := CreateMixedTx( + s.encodingConfig.TxConfig, + s.accounts[3], + 0, + 0, + 40, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + mem := s.newMempool() + s.Require().NoError(mem.Insert(context.Background(), bankTx1)) + s.Require().NoError(mem.Insert(context.Background(), bankTx2)) + s.Require().NoError(mem.Insert(context.Background(), delegateTx1)) + s.Require().NoError(mem.Insert(context.Background(), otherTx1)) + + ph := s.newProposalHandler(mem) + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ + Height: 2, + MaxTxBytes: maxTxBytes, + }) + s.Require().NoError(err) + s.Require().NotNil(resp) + + // Expected that bankTx2 may not fit in lane's gas budget on the first pass. + expected := s.getTxBytes(bankTx1, delegateTx1, otherTx1) + s.Require().Equal(3, len(resp.Txs)) + s.Require().Equal(expected, resp.Txs) + }) + + s.Run("one bank tx over gas limit but has space left", func() { + bankTx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + bankTx2, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + delegateTx1, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + 30, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + otherTx1, err := CreateMixedTx( + s.encodingConfig.TxConfig, + s.accounts[3], + 0, + 0, + 30, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + mem := s.newMempool() + s.Require().NoError(mem.Insert(context.Background(), bankTx1)) + s.Require().NoError(mem.Insert(context.Background(), bankTx2)) + s.Require().NoError(mem.Insert(context.Background(), delegateTx1)) + s.Require().NoError(mem.Insert(context.Background(), otherTx1)) + + ph := s.newProposalHandler(mem) + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ + Height: 2, + MaxTxBytes: maxTxBytes, + }) + s.Require().NoError(err) + s.Require().NotNil(resp) + + expected := s.getTxBytes(bankTx1, delegateTx1, otherTx1, bankTx2) + s.Require().Equal(4, len(resp.Txs)) + s.Require().Equal(expected, resp.Txs) + }) + + s.Run("enforce one tx per signer", func() { + delegateTx1, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + delegateTx2, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 1, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + mem := s.newMempool() + s.Require().NoError(mem.Insert(context.Background(), delegateTx1)) + s.Require().NoError(mem.Insert(context.Background(), delegateTx2)) + + ph := s.newProposalHandler(mem) + + maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes + resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ + Height: 2, + MaxTxBytes: maxTxBytes, + }) + s.Require().NoError(err) + s.Require().NotNil(resp) + + expected := s.getTxBytes(delegateTx1) + s.Require().Equal(1, len(resp.Txs)) + s.Require().Equal(expected, resp.Txs) + }) +} + +// newProposalHandler is analogous to your band proposal handler that uses +// the mempool in PrepareProposal. +func (s *MempoolTestSuite) newProposalHandler(mem *Mempool) *ProposalHandler { + // You may need to adjust: e.g. if your real code calls + // `NewDefaultProposalHandler(logger, txDecoder, mempool)`. + return NewDefaultProposalHandler( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxDecoder(), + mem, + ) +} + +// ----------------------------------------------------------------------------- +// Tx creation helpers (same logic you had) +// ----------------------------------------------------------------------------- + +func CreateBankSendTx( + txCfg client.TxConfig, + account Account, + nonce, timeout uint64, + gasLimit uint64, + fees ...sdk.Coin, +) (authsigning.Tx, error) { + msgs := []sdk.Msg{ + &banktypes.MsgSend{ + FromAddress: account.Address.String(), + ToAddress: account.Address.String(), + }, + } + + return buildTx(txCfg, account, msgs, nonce, timeout, gasLimit, fees...) +} + +func CreateDelegateTx( + txCfg client.TxConfig, + account Account, + nonce, timeout uint64, + gasLimit uint64, + fees ...sdk.Coin, +) (authsigning.Tx, error) { + msgs := []sdk.Msg{ + &stakingtypes.MsgDelegate{ + DelegatorAddress: account.Address.String(), + ValidatorAddress: account.Address.String(), + }, + } + return buildTx(txCfg, account, msgs, nonce, timeout, gasLimit, fees...) +} + +// MixedTx includes both a bank send and delegate to ensure it goes to "other". +func CreateMixedTx( + txCfg client.TxConfig, + account Account, + nonce, timeout uint64, + gasLimit uint64, + fees ...sdk.Coin, +) (authsigning.Tx, error) { + msgs := []sdk.Msg{ + &banktypes.MsgSend{ + FromAddress: account.Address.String(), + ToAddress: account.Address.String(), + }, + &stakingtypes.MsgDelegate{ + DelegatorAddress: account.Address.String(), + ValidatorAddress: account.Address.String(), + }, + } + return buildTx(txCfg, account, msgs, nonce, timeout, gasLimit, fees...) +} + +func buildTx( + txCfg client.TxConfig, + account Account, + msgs []sdk.Msg, + nonce, timeout, gasLimit uint64, + fees ...sdk.Coin, +) (authsigning.Tx, error) { + txBuilder := txCfg.NewTxBuilder() + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + sigV2 := txsigning.SignatureV2{ + PubKey: account.PrivKey.PubKey(), + Data: &txsigning.SingleSignatureData{ + SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: nonce, + } + if err := txBuilder.SetSignatures(sigV2); err != nil { + return nil, err + } + + txBuilder.SetTimeoutHeight(timeout) + txBuilder.SetFeeAmount(fees) + txBuilder.SetGasLimit(gasLimit) + + return txBuilder.GetTx(), nil +} + +// getTxBytes encodes the given transactions. +func (s *MempoolTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte { + txBytes := make([][]byte, len(txs)) + for i, tx := range txs { + bz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + txBytes[i] = bz + } + return txBytes +} diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go new file mode 100644 index 000000000..6e8ab0012 --- /dev/null +++ b/app/mempool/proposal.go @@ -0,0 +1,114 @@ +package mempool + +import ( + "fmt" + + "cosmossdk.io/log" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// MaxUint64 is the maximum value of a uint64. +const MaxUint64 = 1<<64 - 1 + +// ProposalInfo contains metadata about how this proposal was constructed. +type ProposalInfo struct { + // TxsByLane is a map counting how many transactions came from each "lane" (optional usage). + TxsByLane map[string]uint64 `json:"txs_by_lane,omitempty"` + + // MaxBlockSize is the upper bound on the block size used to construct this proposal. + MaxBlockSize int64 `json:"max_block_size,omitempty"` + // MaxGasLimit is the upper bound on the total gas used to construct this proposal. + MaxGasLimit uint64 `json:"max_gas_limit,omitempty"` + + // BlockSize is the current total block size of this proposal. + BlockSize int64 `json:"block_size,omitempty"` + // GasLimit is the current total gas of this proposal. + GasLimit uint64 `json:"gas_limit,omitempty"` +} + +// Proposal represents a block proposal under construction. +type Proposal struct { + Logger log.Logger + + // Txs is the list of transactions in the proposal. + Txs [][]byte + // Cache helps quickly check for duplicates by tx hash. + Cache map[string]struct{} + // Info contains metadata about the proposal's block usage. + Info ProposalInfo + + currentSize int64 + currentGas uint64 +} + +// NewProposal returns a new empty proposal constrained by max block size and max gas limit. +func NewProposal(logger log.Logger, maxBlockSize int64, maxGasLimit uint64) Proposal { + return Proposal{ + Logger: logger, + Txs: make([][]byte, 0), + Cache: make(map[string]struct{}), + Info: ProposalInfo{ + TxsByLane: make(map[string]uint64), + MaxBlockSize: maxBlockSize, + MaxGasLimit: maxGasLimit, + }, + } +} + +// Contains returns true if the proposal already has a transaction with the given txHash. +func (p *Proposal) Contains(txHash string) bool { + _, ok := p.Cache[txHash] + return ok +} + +// Add attempts to add a transaction to the proposal, respecting size/gas limits. +func (p *Proposal) Add(txInfo TxWithInfo) error { + if p.Contains(txInfo.Hash) { + return fmt.Errorf("transaction already in proposal: %s", txInfo.Hash) + } + + // Check block size limit + if p.currentSize+txInfo.Size > p.Info.MaxBlockSize { + return fmt.Errorf( + "transaction size exceeds max block size: %d > %d", + p.currentSize+txInfo.Size, + p.Info.MaxBlockSize, + ) + } + + // Check block gas limit + if p.currentGas+txInfo.GasLimit > p.Info.MaxGasLimit { + return fmt.Errorf( + "transaction gas limit exceeds max gas limit: %d > %d", + p.currentGas+txInfo.GasLimit, + p.Info.MaxGasLimit, + ) + } + + // Add transaction + p.Txs = append(p.Txs, txInfo.TxBytes) + p.Cache[txInfo.Hash] = struct{}{} + + p.Info.BlockSize += txInfo.Size + p.Info.GasLimit += txInfo.GasLimit + + p.currentSize += txInfo.Size + p.currentGas += txInfo.GasLimit + + return nil +} + +// GetBlockLimits retrieves the maximum block size and gas limit from context. +func GetBlockLimits(ctx sdk.Context) (int64, uint64) { + blockParams := ctx.ConsensusParams().Block + + var maxGasLimit uint64 + if maxGas := blockParams.MaxGas; maxGas > 0 { + maxGasLimit = uint64(maxGas) + } else { + maxGasLimit = MaxUint64 + } + + return blockParams.MaxBytes, maxGasLimit +} diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go new file mode 100644 index 000000000..f42af2320 --- /dev/null +++ b/app/mempool/proposal_handler.go @@ -0,0 +1,127 @@ +package mempool + +import ( + "fmt" + + "github.com/skip-mev/block-sdk/v2/block/utils" + + abci "github.com/cometbft/cometbft/abci/types" + + "cosmossdk.io/log" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type ( + // ProposalHandler wraps ABCI++ PrepareProposal/ProcessProposal for the Mempool. + ProposalHandler struct { + logger log.Logger + txDecoder sdk.TxDecoder + Mempool *Mempool + useCustomProcessProposal bool + } +) + +// NewDefaultProposalHandler returns a new ABCI++ proposal handler for the Mempool. +func NewDefaultProposalHandler( + logger log.Logger, + txDecoder sdk.TxDecoder, + mempool *Mempool, +) *ProposalHandler { + return &ProposalHandler{ + logger: logger, + txDecoder: txDecoder, + Mempool: mempool, + useCustomProcessProposal: false, // set to true if you want custom logic + } +} + +// PrepareProposalHandler builds the next block proposal from the Mempool. +func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { + return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (resp *abci.ResponsePrepareProposal, err error) { + // For height <= 1, just return the default TXs (e.g., chain start). + if req.Height <= 1 { + return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil + } + + defer func() { + if rec := recover(); rec != nil { + h.logger.Error("failed to prepare proposal", "err", err) + resp = &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)} + err = fmt.Errorf("failed to prepare proposal: %v", rec) + } + }() + + h.logger.Info("preparing proposal from Mempool", "height", req.Height) + + // Gather block limits + _, maxGasLimit := GetBlockLimits(ctx) + proposal := NewProposal(h.logger, req.MaxTxBytes, maxGasLimit) + + // Populate proposal from Mempool + finalProposal, err := h.Mempool.PrepareProposal(ctx, proposal) + if err != nil { + // If an error occurs, we can still return what we have or choose to return nothing + h.logger.Error("failed to prepare proposal", "err", err) + return &abci.ResponsePrepareProposal{Txs: [][]byte{}}, err + } + + h.logger.Info( + "prepared proposal", + "num_txs", len(finalProposal.Txs), + "total_tx_bytes", finalProposal.Info.BlockSize, + "max_tx_bytes", finalProposal.Info.MaxBlockSize, + "total_gas_limit", finalProposal.Info.GasLimit, + "max_gas_limit", finalProposal.Info.MaxGasLimit, + "height", req.Height, + ) + + return &abci.ResponsePrepareProposal{ + Txs: finalProposal.Txs, + }, nil + } +} + +// ProcessProposalHandler optionally validates the proposal's transactions prior to consensus acceptance. +func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { + if !h.useCustomProcessProposal { + // By default, do nothing special on ProcessProposal. + return baseapp.NoOpProcessProposal() + } + + return func(ctx sdk.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) { + if req.Height <= 1 { + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil + } + + defer func() { + if rec := recover(); rec != nil { + h.logger.Error("failed to process proposal", "recover_err", rec) + resp = &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + err = fmt.Errorf("failed to process proposal: %v", rec) + } + }() + + // Decode the transactions in the proposal. + decodedTxs, err := utils.GetDecodedTxs(h.txDecoder, req.Txs) + if err != nil { + h.logger.Error("failed to decode txs", "err", err) + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err + } + + // (Optional) verify each transaction in the proposal. + for _, tx := range decodedTxs { + // Custom verification logic can go here. + h.logger.Info("verified transaction", "tx", tx) + } + + h.logger.Info( + "processed proposal", + "num_txs", len(decodedTxs), + "height", req.Height, + ) + + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil + } +} diff --git a/app/mempool/types.go b/app/mempool/types.go new file mode 100644 index 000000000..de8d01604 --- /dev/null +++ b/app/mempool/types.go @@ -0,0 +1,19 @@ +package mempool + +import ( + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" +) + +// TxWithInfo holds metadata required for a transaction to be included in a proposal. +type TxWithInfo struct { + // Hash is the hex-encoded hash of the transaction. + Hash string + // Size is the size of the transaction in bytes. + Size int64 + // GasLimit is the gas limit of the transaction. + GasLimit uint64 + // TxBytes is the raw transaction bytes. + TxBytes []byte + // Signers defines the signers of a transaction. + Signers []signerextraction.SignerData +} diff --git a/app/proposal_handler.go b/app/proposal_handler.go deleted file mode 100644 index 746da11b7..000000000 --- a/app/proposal_handler.go +++ /dev/null @@ -1,254 +0,0 @@ -package band - -import ( - "fmt" - - "github.com/skip-mev/block-sdk/v2/block/utils" - - abci "github.com/cometbft/cometbft/abci/types" - - "cosmossdk.io/log" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type ( - // ProposalHandler is a wrapper around the ABCI++ PrepareProposal and ProcessProposal - // handlers for the BandMempool. - ProposalHandler struct { - logger log.Logger - txDecoder sdk.TxDecoder - bandMempool *BandMempool - useCustomProcessProposal bool - } -) - -// NewDefaultProposalHandler returns a new ABCI++ proposal handler for the BandMempool. -// This proposal handler will not use custom process proposal logic. -func NewDefaultProposalHandler( - logger log.Logger, - txDecoder sdk.TxDecoder, - bandMempool *BandMempool, -) *ProposalHandler { - return &ProposalHandler{ - logger: logger, - txDecoder: txDecoder, - bandMempool: bandMempool, - useCustomProcessProposal: false, - } -} - -// PrepareProposalHandler prepares the proposal by selecting transactions from the BandMempool. -func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { - return func(ctx sdk.Context, req *abci.RequestPrepareProposal) (resp *abci.ResponsePrepareProposal, err error) { - if req.Height <= 1 { - return &abci.ResponsePrepareProposal{Txs: req.Txs}, nil - } - - // Recover from any panics during proposal preparation. - defer func() { - if rec := recover(); rec != nil { - h.logger.Error("failed to prepare proposal", "err", err) - resp = &abci.ResponsePrepareProposal{Txs: make([][]byte, 0)} - err = fmt.Errorf("failed to prepare proposal: %v", rec) - } - }() - - h.logger.Info( - "preparing proposal from BandMempool", - "height", req.Height, - ) - - // Get the max gas limit and max block size for the proposal. - _, maxGasLimit := GetBlockLimits(ctx) - proposal := NewProposal(h.logger, req.MaxTxBytes, maxGasLimit) - - // Fill the proposal with transactions from the BandMempool. - finalProposal, err := h.bandMempool.PrepareBandProposal(ctx, proposal) - - h.logger.Info( - "prepared proposal", - "num_txs", len(proposal.Txs), - "total_tx_bytes", proposal.Info.BlockSize, - "max_tx_bytes", proposal.Info.MaxBlockSize, - "total_gas_limit", proposal.Info.GasLimit, - "max_gas_limit", proposal.Info.MaxGasLimit, - "height", req.Height, - ) - - return &abci.ResponsePrepareProposal{ - Txs: finalProposal.Txs, - }, nil - } -} - -// ProcessProposalHandler processes the proposal by verifying all transactions in the proposal. -func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { - if !h.useCustomProcessProposal { - return baseapp.NoOpProcessProposal() - } - - return func(ctx sdk.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) { - if req.Height <= 1 { - return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil - } - - // Recover from any panics during proposal processing. - defer func() { - if rec := recover(); rec != nil { - h.logger.Error("failed to process proposal", "recover_err", rec) - resp = &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} - err = fmt.Errorf("failed to process proposal: %v", rec) - } - }() - - // Decode the transactions in the proposal. - decodedTxs, err := utils.GetDecodedTxs(h.txDecoder, req.Txs) - if err != nil { - h.logger.Error("failed to decode txs", "err", err) - return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err - } - - // Verify each transaction in the proposal. - for _, tx := range decodedTxs { - // Perform custom verification logic here if needed. - // For now, we assume all transactions are valid. - h.logger.Info("verified transaction", "tx", tx) - } - - h.logger.Info( - "processed proposal", - "num_txs", len(decodedTxs), - "height", req.Height, - ) - - return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil - } -} - -// ==================================== -// Utils -// ==================================== - -const ( - // MaxUint64 is the maximum value of a uint64. - MaxUint64 = 1<<64 - 1 -) - -// GetBlockLimits retrieves the maximum number of bytes and gas limit allowed in a block. -func GetBlockLimits(ctx sdk.Context) (int64, uint64) { - blockParams := ctx.ConsensusParams().Block - - // If the max gas is set to 0, then the max gas limit for the block can be infinite. - // Otherwise, we use the max gas limit casted as a uint64 which is how gas limits are - // extracted from sdk.Tx's. - var maxGasLimit uint64 - if maxGas := blockParams.MaxGas; maxGas > 0 { - maxGasLimit = uint64(maxGas) - } else { - maxGasLimit = MaxUint64 - } - - return blockParams.MaxBytes, maxGasLimit -} - -// ==================================== -// Proposal -// ==================================== - -// ProposalInfo contains the metadata about a given proposal that was built by -// the block-sdk. This is used to verify and consolidate proposal data across -// the network. -type ProposalInfo struct { - // TxsByLane contains information about how each partial proposal - // was constructed by the block-sdk lanes. - TxsByLane map[string]uint64 `protobuf:"bytes,1,rep,name=txs_by_lane,json=txsByLane,proto3" json:"txs_by_lane,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` - // MaxBlockSize corresponds to the upper bound on the size of the - // block that was used to construct this block proposal. - MaxBlockSize int64 `protobuf:"varint,2,opt,name=max_block_size,json=maxBlockSize,proto3" json:"max_block_size,omitempty"` - // MaxGasLimit corresponds to the upper bound on the gas limit of the - // block that was used to construct this block proposal. - MaxGasLimit uint64 `protobuf:"varint,3,opt,name=max_gas_limit,json=maxGasLimit,proto3" json:"max_gas_limit,omitempty"` - // BlockSize corresponds to the size of this block proposal. - BlockSize int64 `protobuf:"varint,4,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"` - // GasLimit corresponds to the gas limit of this block proposal. - GasLimit uint64 `protobuf:"varint,5,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` -} - -type ( - // Proposal defines a block proposal type. - Proposal struct { - Logger log.Logger - - // Txs is the list of transactions in the proposal. - Txs [][]byte - // Cache is a cache of the selected transactions in the proposal. - Cache map[string]struct{} - // Info contains information about the state of the proposal. - Info ProposalInfo - - currentSize int64 - currentGas uint64 - } -) - -// NewProposal returns a new empty proposal. Any transactions added to the proposal -// will be subject to the given max block size and max gas limit. -func NewProposal(logger log.Logger, maxBlockSize int64, maxGasLimit uint64) Proposal { - return Proposal{ - Logger: logger, - Txs: make([][]byte, 0), - Cache: make(map[string]struct{}), - Info: ProposalInfo{ - TxsByLane: make(map[string]uint64), - MaxBlockSize: maxBlockSize, - MaxGasLimit: maxGasLimit, - }, - } -} - -// Contains returns true if the proposal contains the given transaction. -func (p *Proposal) Contains(txHash string) bool { - _, ok := p.Cache[txHash] - return ok -} - -// Add adds a transaction to the proposal. -func (p *Proposal) Add(txInfo TxWithInfo) error { - fmt.Println("try add tx to proposal: ", txInfo.Hash) - // if the transaction is already in the block proposal, return an error. - if p.Contains(txInfo.Hash) { - return fmt.Errorf("transaction already in proposal: %s", txInfo.Hash) - } - - // if the transaction is too large, return an error. - if p.currentSize+txInfo.Size > p.Info.MaxBlockSize { - return fmt.Errorf( - "transaction size exceeds max block size: %d > %d", - p.currentSize+txInfo.Size, - p.Info.MaxBlockSize, - ) - } - - // if the transaction gas limit is too large, return an error. - if p.currentGas+txInfo.GasLimit > p.Info.MaxGasLimit { - return fmt.Errorf( - "transaction gas limit exceeds max gas limit: %d > %d", - p.currentGas+txInfo.GasLimit, - p.Info.MaxGasLimit, - ) - } - - // add the transaction to the proposal. - p.Txs = append(p.Txs, txInfo.TxBytes) - p.Cache[txInfo.Hash] = struct{}{} - - p.Info.BlockSize += txInfo.Size - p.Info.GasLimit += txInfo.GasLimit - - p.currentSize += txInfo.Size - p.currentGas += txInfo.GasLimit - - return nil -} diff --git a/app/proposal_handler_test.go b/app/proposal_handler_test.go deleted file mode 100644 index 3f14df6b7..000000000 --- a/app/proposal_handler_test.go +++ /dev/null @@ -1,516 +0,0 @@ -package band - -import ( - "math/rand" - "testing" - - "cosmossdk.io/log" - "cosmossdk.io/math" - storetypes "cosmossdk.io/store/types" - "cosmossdk.io/x/tx/signing" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/address" - "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/std" - "github.com/cosmos/cosmos-sdk/testutil" - sdk "github.com/cosmos/cosmos-sdk/types" - txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/suite" - - cometabci "github.com/cometbft/cometbft/abci/types" - tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types" -) - -type Account struct { - PrivKey cryptotypes.PrivKey - PubKey cryptotypes.PubKey - Address sdk.AccAddress - ConsKey cryptotypes.PrivKey -} - -type ProposalHandlerTestSuite struct { - suite.Suite - ctx sdk.Context - key *storetypes.KVStoreKey - - encodingConfig EncodingConfig - random *rand.Rand - accounts []Account - gasTokenDenom string -} - -func TestProposalHandlerTestSuite(t *testing.T) { - suite.Run(t, new(ProposalHandlerTestSuite)) -} - -func (s *ProposalHandlerTestSuite) SetupTest() { - // Set up basic TX encoding config. - s.encodingConfig = CreateTestEncodingConfig() - - // Create a few random accounts - s.random = rand.New(rand.NewSource(1)) - s.accounts = RandomAccounts(s.random, 5) - s.gasTokenDenom = "uband" - - s.key = storetypes.NewKVStoreKey("test") - testCtx := testutil.DefaultContextWithDB(s.T(), s.key, storetypes.NewTransientStoreKey("transient_test")) - s.ctx = testCtx.Ctx.WithIsCheckTx(true) - s.ctx = s.ctx.WithBlockHeight(1) -} - -type EncodingConfig struct { - InterfaceRegistry types.InterfaceRegistry - Codec codec.Codec - TxConfig client.TxConfig - Amino *codec.LegacyAmino -} - -func CreateTestEncodingConfig() EncodingConfig { - legacyAmino := codec.NewLegacyAmino() - interfaceRegistry, err := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ - ProtoFiles: proto.HybridResolver, - SigningOptions: signing.Options{ - AddressCodec: address.Bech32Codec{ - Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), - }, - ValidatorAddressCodec: address.Bech32Codec{ - Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), - }, - }, - }) - if err != nil { - panic(err) - } - - appCodec := codec.NewProtoCodec(interfaceRegistry) - txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) - - std.RegisterLegacyAminoCodec(legacyAmino) - std.RegisterInterfaces(interfaceRegistry) - - return EncodingConfig{ - InterfaceRegistry: interfaceRegistry, - Codec: appCodec, - TxConfig: txConfig, - Amino: legacyAmino, - } -} - -func RandomAccounts(r *rand.Rand, n int) []Account { - accs := make([]Account, n) - - for i := 0; i < n; i++ { - pkSeed := make([]byte, 15) - r.Read(pkSeed) - - accs[i].PrivKey = secp256k1.GenPrivKeyFromSecret(pkSeed) - accs[i].PubKey = accs[i].PrivKey.PubKey() - accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) - - accs[i].ConsKey = ed25519.GenPrivKeyFromSecret(pkSeed) - } - - return accs -} - -func (s *ProposalHandlerTestSuite) SetupSubTest() { - s.setBlockParams(100, 1000000000000) -} - -func (s *ProposalHandlerTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { - s.ctx = s.ctx.WithConsensusParams( - tmprototypes.ConsensusParams{ - Block: &tmprototypes.BlockParams{ - MaxBytes: maxBlockSize, - MaxGas: maxGasLimit, - }, - }, - ) -} - -func (s *ProposalHandlerTestSuite) setUpProposalHandlers(bandMempool *BandMempool) *ProposalHandler { - return NewDefaultProposalHandler( - log.NewNopLogger(), - s.encodingConfig.TxConfig.TxDecoder(), - bandMempool, - ) -} - -func (s *ProposalHandlerTestSuite) TestPrepareProposal() { - s.Run("can prepare a proposal with no transactions", func() { - // Create a new BandMempool - bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) - - // Set up the band proposal handler with no transactions - proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() - - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) - s.Require().NoError(err) - s.Require().NotNil(resp) - s.Require().Equal(0, len(resp.Txs)) - }) - - s.Run("can build a proposal with a single bank send tx", func() { - // Create a bank transaction that will be inserted into the bank lane - tx, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a new BandMempool - bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) - - // Insert the transaction into the mempool - err = bandMempool.Insert(s.ctx, tx) - s.Require().NoError(err) - - // Set up the band proposal handler with no transactions - proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() - - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) - s.Require().NotNil(resp) - s.Require().NoError(err) - - proposal := s.getTxBytes(tx) - s.Require().Equal(1, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs) - }) - - s.Run("can build a proposal with single tx in every types", func() { - // Create a bank transaction that will be inserted into the bank lane - tx1, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a delegate transaction that will be inserted into the delegate lane - tx2, err := CreateDelegateTx( - s.encodingConfig.TxConfig, - s.accounts[1], - 0, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a mixed transaction that will be inserted into the other lane - tx3, err := CreateMixedTx( - s.encodingConfig.TxConfig, - s.accounts[2], - 0, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a new BandMempool - bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) - - // Insert the transaction into the mempool - err = bandMempool.Insert(s.ctx, tx1) - s.Require().NoError(err) - - err = bandMempool.Insert(s.ctx, tx2) - s.Require().NoError(err) - - err = bandMempool.Insert(s.ctx, tx3) - s.Require().NoError(err) - - // Set up the band proposal handler with no transactions - proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() - - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) - s.Require().NotNil(resp) - s.Require().NoError(err) - - proposal := s.getTxBytes(tx1, tx2, tx3) - s.Require().Equal(3, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs) - }) - - s.Run("can build a proposal with one bank tx over gas limit", func() { - // Create a bank transaction that will be inserted into the bank lane - bankTx1, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 20, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a bank transaction that will be inserted into the bank lane but over gas limit - bankTx2, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[1], - 0, - 0, - 20, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a delegate transaction that will be inserted into the delegate lane - delegateTx1, err := CreateDelegateTx( - s.encodingConfig.TxConfig, - s.accounts[2], - 0, - 0, - 30, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a mixed transaction that will be inserted into the other lane - otherTx1, err := CreateMixedTx( - s.encodingConfig.TxConfig, - s.accounts[3], - 0, - 0, - 40, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a new BandMempool - bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) - - // Insert the transaction into the mempool - err = bandMempool.Insert(s.ctx, bankTx1) - s.Require().NoError(err) - - err = bandMempool.Insert(s.ctx, bankTx2) - s.Require().NoError(err) - - err = bandMempool.Insert(s.ctx, delegateTx1) - s.Require().NoError(err) - - err = bandMempool.Insert(s.ctx, otherTx1) - s.Require().NoError(err) - - // Set up the band proposal handler with no transactions - proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() - - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) - s.Require().NotNil(resp) - s.Require().NoError(err) - - proposal := s.getTxBytes(bankTx1, delegateTx1, otherTx1) - s.Require().Equal(3, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs) - }) - - s.Run("can build a proposal with one bank tx over gas limit but has space left", func() { - // Create a bank transaction that will be inserted into the bank lane - bankTx1, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 20, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a bank transaction that will be inserted into the bank lane but over gas limit - bankTx2, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[1], - 0, - 0, - 20, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a delegate transaction that will be inserted into the delegate lane - delegateTx1, err := CreateDelegateTx( - s.encodingConfig.TxConfig, - s.accounts[2], - 0, - 0, - 30, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a mixed transaction that will be inserted into the other lane - otherTx1, err := CreateMixedTx( - s.encodingConfig.TxConfig, - s.accounts[3], - 0, - 0, - 30, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - // Create a new BandMempool - bandMempool := NewBandMempool(s.encodingConfig.TxConfig.TxEncoder()) - - // Insert the transaction into the mempool - err = bandMempool.Insert(s.ctx, bankTx1) - s.Require().NoError(err) - - err = bandMempool.Insert(s.ctx, bankTx2) - s.Require().NoError(err) - - err = bandMempool.Insert(s.ctx, delegateTx1) - s.Require().NoError(err) - - err = bandMempool.Insert(s.ctx, otherTx1) - s.Require().NoError(err) - - // Set up the band proposal handler with no transactions - proposalHandler := s.setUpProposalHandlers(bandMempool).PrepareProposalHandler() - - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := proposalHandler(s.ctx, &cometabci.RequestPrepareProposal{Height: 2, MaxTxBytes: maxTxBytes}) - s.Require().NotNil(resp) - s.Require().NoError(err) - - proposal := s.getTxBytes(bankTx1, delegateTx1, otherTx1, bankTx2) - s.Require().Equal(4, len(resp.Txs)) - s.Require().Equal(proposal, resp.Txs) - }) -} - -func CreateBankSendTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, gasLimit uint64, fees ...sdk.Coin) (authsigning.Tx, error) { - msgs := make([]sdk.Msg, 1) - msgs[0] = &banktypes.MsgSend{ - FromAddress: account.Address.String(), - ToAddress: account.Address.String(), - } - - txBuilder := txCfg.NewTxBuilder() - if err := txBuilder.SetMsgs(msgs...); err != nil { - return nil, err - } - - sigV2 := txsigning.SignatureV2{ - PubKey: account.PrivKey.PubKey(), - Data: &txsigning.SingleSignatureData{ - SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, - Signature: nil, - }, - Sequence: nonce, - } - if err := txBuilder.SetSignatures(sigV2); err != nil { - return nil, err - } - - txBuilder.SetTimeoutHeight(timeout) - - txBuilder.SetFeeAmount(fees) - - txBuilder.SetGasLimit(gasLimit) - - return txBuilder.GetTx(), nil -} - -func CreateDelegateTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, gasLimit uint64, fees ...sdk.Coin) (authsigning.Tx, error) { - msgs := make([]sdk.Msg, 1) - msgs[0] = &stakingtypes.MsgDelegate{ - DelegatorAddress: account.Address.String(), - ValidatorAddress: account.Address.String(), - } - - txBuilder := txCfg.NewTxBuilder() - if err := txBuilder.SetMsgs(msgs...); err != nil { - return nil, err - } - - sigV2 := txsigning.SignatureV2{ - PubKey: account.PrivKey.PubKey(), - Data: &txsigning.SingleSignatureData{ - SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, - Signature: nil, - }, - Sequence: nonce, - } - if err := txBuilder.SetSignatures(sigV2); err != nil { - return nil, err - } - - txBuilder.SetTimeoutHeight(timeout) - - txBuilder.SetFeeAmount(fees) - - txBuilder.SetGasLimit(gasLimit) - - return txBuilder.GetTx(), nil -} - -func CreateMixedTx(txCfg client.TxConfig, account Account, nonce, timeout uint64, gasLimit uint64, fees ...sdk.Coin) (authsigning.Tx, error) { - msgs := make([]sdk.Msg, 2) - msgs[0] = &banktypes.MsgSend{ - FromAddress: account.Address.String(), - ToAddress: account.Address.String(), - } - msgs[1] = &stakingtypes.MsgDelegate{ - DelegatorAddress: account.Address.String(), - ValidatorAddress: account.Address.String(), - } - - txBuilder := txCfg.NewTxBuilder() - if err := txBuilder.SetMsgs(msgs...); err != nil { - return nil, err - } - - sigV2 := txsigning.SignatureV2{ - PubKey: account.PrivKey.PubKey(), - Data: &txsigning.SingleSignatureData{ - SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, - Signature: nil, - }, - Sequence: nonce, - } - if err := txBuilder.SetSignatures(sigV2); err != nil { - return nil, err - } - - txBuilder.SetTimeoutHeight(timeout) - - txBuilder.SetFeeAmount(fees) - - txBuilder.SetGasLimit(gasLimit) - - return txBuilder.GetTx(), nil -} - -func (s *ProposalHandlerTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte { - txBytes := make([][]byte, len(txs)) - for i, tx := range txs { - bz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) - s.Require().NoError(err) - - txBytes[i] = bz - } - return txBytes -} From 057b778ae668d53edc9759b9bbdeba2d1fc41e1b Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 14 Feb 2025 16:56:58 +0700 Subject: [PATCH 05/53] integrate cosmos mempool to store tx in lane, add unit tests --- app/app.go | 5 +- app/lanes.go | 60 ++-- app/mempool/block_space.go | 34 ++ app/mempool/lane.go | 290 +++++++++++---- app/mempool/lane_test.go | 215 +++++++++++ app/mempool/mempool.go | 196 +++++----- app/mempool/mempool_test.go | 614 +++++++++++++++++--------------- app/mempool/proposal.go | 45 ++- app/mempool/proposal_handler.go | 4 +- app/mempool/utils.go | 22 ++ 10 files changed, 1000 insertions(+), 485 deletions(-) create mode 100644 app/mempool/block_space.go create mode 100644 app/mempool/lane_test.go create mode 100644 app/mempool/utils.go diff --git a/app/app.go b/app/app.go index 1fbd0e0aa..53203b24a 100644 --- a/app/app.go +++ b/app/app.go @@ -6,7 +6,6 @@ import ( "os" "path/filepath" - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" "github.com/spf13/cast" abci "github.com/cometbft/cometbft/abci/types" @@ -263,10 +262,10 @@ func NewBandApp( // panic(err) // } - bandLanes := DefaultLanes() + bandLanes := BandLanes(app) // create Band mempool - bandMempool := mempool.NewMempool(txConfig.TxEncoder(), signerextraction.NewDefaultAdapter(), bandLanes) + bandMempool := mempool.NewMempool(app.Logger(), bandLanes) // set the mempool app.SetMempool(bandMempool) diff --git a/app/lanes.go b/app/lanes.go index c7a409d72..97897d5a1 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -1,9 +1,12 @@ package band import ( + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" "github.com/bandprotocol/chain/v3/app/mempool" ) @@ -41,45 +44,44 @@ func isOtherTx(tx sdk.Tx) bool { return !isBankSendTx(tx) && !isDelegateTx(tx) } -// BankSendLane returns a lane named "bankSend" that matches only MsgSend transactions, -// assigning 30% of the block's gas limit to this lane. -func BankSendLane() *mempool.Lane { - return mempool.NewLane( +// BandLanes returns the default lanes for the Band Protocol blockchain. +func BandLanes(app *BandApp) []*mempool.Lane { + // 1. Create the signer extractor. This is used to extract the expected signers from + // a transaction. Each lane can have a different signer extractor if needed. + signerAdapter := signerextraction.NewDefaultAdapter() + + BankSendLane := mempool.NewLane( + app.Logger(), + app.txConfig.TxEncoder(), + signerAdapter, "bankSend", isBankSendTx, - 30, // percentage - false, // EnforceOneTxPerSigner? set to true if you want one tx per signer + math.LegacyMustNewDecFromStr("0.05"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), ) -} -// DelegateLane returns a lane named "delegate" that matches only MsgDelegate transactions, -// assigning 30% of the block's gas limit to this lane. -func DelegateLane() *mempool.Lane { - return mempool.NewLane( + DelegateLane := mempool.NewLane( + app.Logger(), + app.txConfig.TxEncoder(), + signerAdapter, "delegate", isDelegateTx, - 30, // percentage - false, // EnforceOneTxPerSigner? set to true if you want one tx per signer + math.LegacyMustNewDecFromStr("0.05"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), ) -} -// OtherLane returns a lane named "other" for any transaction that does not strictly -// match isBankSendTx or isDelegateTx. It allocates 40% of the block's gas limit. -func OtherLane() *mempool.Lane { - return mempool.NewLane( + OtherLane := mempool.NewLane( + app.Logger(), + app.txConfig.TxEncoder(), + signerAdapter, "other", isOtherTx, - 40, // percentage - false, // EnforceOneTxPerSigner? set to true if you want one tx per signer + math.LegacyMustNewDecFromStr("0.1"), + math.LegacyMustNewDecFromStr("0.4"), + sdkmempool.DefaultPriorityMempool(), ) -} -// DefaultLanes is a convenience helper returning the typical three lanes: -// bankSend, delegate, and other (30%, 30%, and 40%). -func DefaultLanes() []*mempool.Lane { - return []*mempool.Lane{ - BankSendLane(), - DelegateLane(), - OtherLane(), - } + return []*mempool.Lane{BankSendLane, DelegateLane, OtherLane} } diff --git a/app/mempool/block_space.go b/app/mempool/block_space.go new file mode 100644 index 000000000..14d4d4e05 --- /dev/null +++ b/app/mempool/block_space.go @@ -0,0 +1,34 @@ +package mempool + +// BlockSpace defines the block space. +type BlockSpace struct { + txBytes int64 + gas uint64 +} + +// NewBlockSpace returns a new block space. +func NewBlockSpace(txBytes int64, gas uint64) BlockSpace { + return BlockSpace{ + txBytes: txBytes, + gas: gas, + } +} + +// IsReached returns true if the block space is reached. +func (bs BlockSpace) IsReached(size int64, gas uint64) bool { + return size >= bs.txBytes || gas >= bs.gas +} + +// IsExceeded returns true if the block space is exceeded. +func (bs BlockSpace) IsExceeded(size int64, gas uint64) bool { + return size > bs.txBytes || gas > bs.gas +} + +// Decrease decreases the block space. +func (bs *BlockSpace) DecreaseBy(size int64, gas uint64) { + bs.txBytes -= size + if bs.txBytes < 0 { + bs.txBytes = 0 + } + bs.gas -= gas +} diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 6be2294e1..5b5c99768 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -1,119 +1,255 @@ package mempool import ( + "context" + "encoding/hex" + "fmt" + "strings" + + "cosmossdk.io/log" + "cosmossdk.io/math" + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + + comettypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" + sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" ) // Lane defines a logical grouping of transactions within the mempool. type Lane struct { - // Name is a friendly identifier for this lane (e.g. "bankSend", "delegate"). + Logger log.Logger + TxEncoder sdk.TxEncoder + SignerExtractor signerextraction.Adapter + // Name is a identifier for this lane (e.g. "bankSend", "delegate"). Name string // Filter determines if a given transaction belongs in this lane. - Filter func(sdk.Tx) bool - - // Percentage is the fraction (0-100) of the block's gas budget allocated to this lane. - Percentage uint64 + Match func(sdk.Tx) bool - // EnforceOneTxPerSigner indicates that each signer may only have one tx - // included in this lane per proposal. - EnforceOneTxPerSigner bool + MaxTransactionSpace math.LegacyDec + MaxLaneSpace math.LegacyDec - // signersUsed tracks which signers have already added a transaction in this lane - // for the current proposal. (Only used if EnforceOneTxPerSigner == true.) - signersUsed map[string]bool - - // txs holds the set of transactions that matched Filter. - txs []TxWithInfo + // laneMempool is the mempool that is responsible for storing transactions + // that are waiting to be processed. + laneMempool sdkmempool.Mempool } // NewLane is a simple constructor for a lane. -func NewLane(name string, filter func(sdk.Tx) bool, percentage uint64, enforceOneTxPerSigner bool) *Lane { +func NewLane( + logger log.Logger, + txEncoder sdk.TxEncoder, + signerExtractor signerextraction.Adapter, + name string, + matchFn func(sdk.Tx) bool, + maxTransactionSpace math.LegacyDec, + maxLaneSpace math.LegacyDec, + laneMempool sdkmempool.Mempool, +) *Lane { return &Lane{ - Name: name, - Filter: filter, - Percentage: percentage, - EnforceOneTxPerSigner: enforceOneTxPerSigner, - signersUsed: make(map[string]bool), - txs: []TxWithInfo{}, + Logger: logger, + TxEncoder: txEncoder, + SignerExtractor: signerExtractor, + Name: name, + Match: matchFn, + MaxTransactionSpace: maxTransactionSpace, + MaxLaneSpace: maxLaneSpace, + laneMempool: laneMempool, } } -// AddTx appends a transaction to this lane's slice. -func (l *Lane) AddTx(tx TxWithInfo) { - l.txs = append(l.txs, tx) +func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { + return l.laneMempool.Insert(ctx, tx) } -// RemoveTx removes a transaction from this lane by matching TxWithInfo.Hash. -func (l *Lane) RemoveTx(txInfo TxWithInfo) { - newTxs := make([]TxWithInfo, 0, len(l.txs)) - for _, t := range l.txs { - if t.Hash != txInfo.Hash { - newTxs = append(newTxs, t) - } - } - l.txs = newTxs +func (l *Lane) CountTx() int { + return l.laneMempool.CountTx() } -// GetTxs returns the slice of lane transactions. -func (l *Lane) GetTxs() []TxWithInfo { - return l.txs +func (l *Lane) Remove(tx sdk.Tx) error { + return l.laneMempool.Remove(tx) } -// SetTxs overwrites the lane's transactions with the new slice. -func (l *Lane) SetTxs(newTxs []TxWithInfo) { - l.txs = newTxs -} +// FillProposal fills the proposal with transactions from the lane mempool. +// It returns the total size and gas of the transactions added to the proposal. +// If customLaneLimit is provided, it will be used instead of the lane's limit. +func (l *Lane) FillProposal( + ctx sdk.Context, + proposal *Proposal, +) (sizeUsed int64, gasUsed uint64, iterator sdkmempool.Iterator, txsToRemove []sdk.Tx) { + var ( + transactionLimit BlockSpace + laneLimit BlockSpace + ) + // Get the transaction and lane limit for the lane. + transactionLimit = proposal.GetLimit(l.MaxTransactionSpace) + laneLimit = proposal.GetLimit(l.MaxLaneSpace) + + // Select all transactions in the mempool that are valid and not already in the + // partial proposal. + for iterator = l.laneMempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() { + // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. + // We can tolerate a few bytes/gas over the limit, since we limit the size of each transaction. + if laneLimit.IsReached(sizeUsed, gasUsed) { + + break + } -// ResetState resets the lane’s state for a new proposal, -// clearing the signersUsed map. -func (l *Lane) ResetState() { - l.signersUsed = make(map[string]bool) -} + tx := iterator.Tx() + txInfo, err := l.GetTxInfo(ctx, tx) + if err != nil { + l.Logger.Info("failed to get hash of tx", "err", err) + + txsToRemove = append(txsToRemove, tx) + continue + } + + // if the transaction is exceed the limit, we remove it from the lane mempool. + if transactionLimit.IsExceeded(txInfo.Size, txInfo.GasLimit) { + l.Logger.Info( + "failed to select tx for lane; tx exceeds limit", + "tx_hash", txInfo.Hash, + "lane", l.Name, + ) -// FillProposal attempts to add lane transactions to the proposal, -// respecting the laneGasLimit and the “one tx per signer” rule (if enforced). -// It returns the leftover (unconsumed) gas for the lane. -func (l *Lane) FillProposal(proposal *Proposal, laneGasLimit uint64) uint64 { - for _, txInfo := range l.txs { - // If the next tx doesn't fit the lane's gas budget, skip it (or break). - if txInfo.GasLimit > laneGasLimit { + txsToRemove = append(txsToRemove, tx) continue } - // If we enforce "one tx per signer," check if any signer has already used this lane. - if l.EnforceOneTxPerSigner { - skip := false - for _, signer := range txInfo.Signers { - signerAddr := signer.Signer.String() - if l.signersUsed[signerAddr] { - // This signer has already used the lane for a prior tx. - skip = true - break - } - } - if skip { - continue - } + // Verify the transaction. + if err = l.VerifyTx(ctx, tx, false); err != nil { + l.Logger.Info( + "failed to verify tx", + "tx_hash", txInfo.Hash, + "err", err, + ) + + txsToRemove = append(txsToRemove, tx) + continue } - // Attempt to add the transaction to the proposal. + // Add the transaction to the proposal. + // TODO: check if the transaction cannot be added here, it should also cannot be added afterward. if err := proposal.Add(txInfo); err != nil { - // If we fail (e.g. block size or gas limit exceeded), skip or break based on your policy. + l.Logger.Info( + "failed to add tx to proposal", + "lane", l.Name, + "tx_hash", txInfo.Hash, + "err", err, + ) + + break + } + + // Update the total size and gas. + sizeUsed += txInfo.Size + gasUsed += txInfo.GasLimit + } + + return +} + +func (l *Lane) FillProposalBy( + ctx sdk.Context, + proposal *Proposal, + iterator sdkmempool.Iterator, + laneLimit BlockSpace, +) (sizeUsed int64, gasUsed uint64, txsToRemove []sdk.Tx) { + // get the transaction limit for the lane. + transactionLimit := proposal.GetLimit(l.MaxTransactionSpace) + + // Select all transactions in the mempool that are valid and not already in the + // partial proposal. + for ; iterator != nil; iterator = iterator.Next() { + // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. + // We can tolerate a few bytes/gas over the limit, since we limit the size of each transaction. + if laneLimit.IsReached(sizeUsed, gasUsed) { + break + } + + tx := iterator.Tx() + txInfo, err := l.GetTxInfo(ctx, tx) + if err != nil { + l.Logger.Info("failed to get hash of tx", "err", err) + + txsToRemove = append(txsToRemove, tx) continue } - // The transaction fits the lane's gas limit and the proposal’s constraints. - laneGasLimit -= txInfo.GasLimit + // if the transaction is exceed the limit, we remove it from the lane mempool. + if transactionLimit.IsExceeded(txInfo.Size, txInfo.GasLimit) { + l.Logger.Info( + "failed to select tx for lane; tx exceeds limit", + "tx_hash", txInfo.Hash, + "lane", l.Name, + ) - // Mark signers as used if one-tx-per-signer is enforced. - if l.EnforceOneTxPerSigner { - for _, signer := range txInfo.Signers { - signerAddr := signer.Signer.String() - l.signersUsed[signerAddr] = true - } + txsToRemove = append(txsToRemove, tx) + continue } + + // Verify the transaction. + if err = l.VerifyTx(ctx, tx, false); err != nil { + l.Logger.Info( + "failed to verify tx", + "tx_hash", txInfo.Hash, + "err", err, + ) + + txsToRemove = append(txsToRemove, tx) + continue + } + + // Add the transaction to the proposal. + if err := proposal.Add(txInfo); err != nil { + l.Logger.Info( + "failed to add tx to proposal", + "lane", l.Name, + "tx_hash", txInfo.Hash, + "err", err, + ) + + break + } + + // Update the total size and gas. + sizeUsed += txInfo.Size + gasUsed += txInfo.GasLimit } - return laneGasLimit + return +} + +// GetTxInfo returns various information about the transaction that +// belongs to the lane including its priority, signer's, sequence number, +// size and more. +func (l *Lane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (TxWithInfo, error) { + txBytes, err := l.TxEncoder(tx) + if err != nil { + return TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) + } + + // TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc. + gasTx, ok := tx.(sdk.FeeTx) + if !ok { + return TxWithInfo{}, fmt.Errorf("failed to cast transaction to gas tx") + } + + signers, err := l.SignerExtractor.GetSigners(tx) + if err != nil { + return TxWithInfo{}, err + } + + return TxWithInfo{ + Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), + Size: int64(len(txBytes)), + GasLimit: gasTx.GetGas(), + TxBytes: txBytes, + Signers: signers, + }, nil +} + +// TODO: Add a method to verify the transaction. +// VerifyTx verifies that the transaction is valid respecting to the msg server +func (l *Lane) VerifyTx(ctx sdk.Context, tx sdk.Tx, simulate bool) error { + return nil } diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go new file mode 100644 index 000000000..1db6a544c --- /dev/null +++ b/app/mempool/lane_test.go @@ -0,0 +1,215 @@ +package mempool + +import ( + "math/rand" + "testing" + + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" + txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +// LaneTestSuite is a testify.Suite for unit-testing the Lane functionality. +type LaneTestSuite struct { + suite.Suite + + encodingConfig EncodingConfig + random *rand.Rand + accounts []Account + gasTokenDenom string + ctx sdk.Context +} + +func TestLaneTestSuite(t *testing.T) { + suite.Run(t, new(LaneTestSuite)) +} + +func (s *LaneTestSuite) SetupTest() { + s.encodingConfig = CreateTestEncodingConfig() + s.random = rand.New(rand.NewSource(1)) + s.accounts = RandomAccounts(s.random, 3) + s.gasTokenDenom = "uband" + + testCtx := testutil.DefaultContextWithDB( + s.T(), + storetypes.NewKVStoreKey("test"), + storetypes.NewTransientStoreKey("transient_test"), + ) + s.ctx = testCtx.Ctx.WithIsCheckTx(true) + s.ctx = s.ctx.WithBlockHeight(1) +} + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +func (s *LaneTestSuite) TestLaneInsertAndCount() { + // Create a Lane that matches all txs (Match always returns true), + // just to test Insert/Count. + lane := NewLane( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + signerextraction.NewDefaultAdapter(), + "testLane", + func(sdk.Tx) bool { return true }, // accept all + math.LegacyMustNewDecFromStr("0.3"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), + ) + + // Create and insert two transactions + tx1 := s.createSimpleTx(s.accounts[0], 0, 10) + tx2 := s.createSimpleTx(s.accounts[1], 0, 10) + + s.Require().NoError(lane.Insert(s.ctx, tx1)) + s.Require().NoError(lane.Insert(s.ctx, tx2)) + + // Ensure lane sees 2 transactions + s.Require().Equal(2, lane.CountTx()) +} + +func (s *LaneTestSuite) TestLaneRemove() { + // Lane that matches all txs + lane := NewLane( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + signerextraction.NewDefaultAdapter(), + "testLane", + func(sdk.Tx) bool { return true }, // accept all + math.LegacyMustNewDecFromStr("0.3"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), + ) + + tx := s.createSimpleTx(s.accounts[0], 0, 10) + s.Require().NoError(lane.Insert(s.ctx, tx)) + s.Require().Equal(1, lane.CountTx()) + + // Remove it + err := lane.Remove(tx) + s.Require().NoError(err) + s.Require().Equal(0, lane.CountTx()) +} + +func (s *LaneTestSuite) TestLaneFillProposal() { + // Lane that matches all txs + lane := NewLane( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + signerextraction.NewDefaultAdapter(), + "testLane", + func(sdk.Tx) bool { return true }, // accept all + math.LegacyMustNewDecFromStr("0.2"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), + ) + + // Insert 3 transactions + tx1 := s.createSimpleTx(s.accounts[0], 0, 20) + tx2 := s.createSimpleTx(s.accounts[1], 1, 20) + tx3 := s.createSimpleTx(s.accounts[2], 2, 50) // This might be large + tx4 := s.createSimpleTx(s.accounts[2], 3, 30) // This might be large + tx5 := s.createSimpleTx(s.accounts[2], 4, 20) + tx6 := s.createSimpleTx(s.accounts[2], 5, 20) + tx7 := s.createSimpleTx(s.accounts[2], 6, 10) + tx8 := s.createSimpleTx(s.accounts[2], 7, 10) + s.Require().NoError(lane.Insert(s.ctx, tx1)) + s.Require().NoError(lane.Insert(s.ctx, tx2)) + s.Require().NoError(lane.Insert(s.ctx, tx3)) + s.Require().NoError(lane.Insert(s.ctx, tx4)) + s.Require().NoError(lane.Insert(s.ctx, tx5)) + s.Require().NoError(lane.Insert(s.ctx, tx6)) + s.Require().NoError(lane.Insert(s.ctx, tx7)) + s.Require().NoError(lane.Insert(s.ctx, tx8)) + + // Create a proposal with block-limits + proposal := NewProposal( + log.NewTestLogger(s.T()), + 1000000000000, + 100, + ) + + // FillProposal + sizeUsed, gasUsed, iterator, txsToRemove := lane.FillProposal(s.ctx, &proposal) + + // We expect tx1 and tx2 to be included in the proposal. + // Then the gas should be over the limit, so tx3 is yet to be considered. + s.Require().Equal(int64(440), sizeUsed) + s.Require().Equal(uint64(40), gasUsed, "20 gas from tx1 and 20 gas from tx2") + s.Require().NotNil(iterator) + s.Require(). + Len(txsToRemove, 0, "tx3 is yet to be considered") + + // The proposal should contain 2 transactions in Txs(). + expectedIncludedTxs := s.getTxBytes(tx1, tx2) + s.Require().Equal(2, len(proposal.Txs), "two txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.Txs) + + // Calculate the remaining block space + remainderLimit := NewBlockSpace(proposal.Info.MaxBlockSize-sizeUsed, proposal.Info.MaxGasLimit-gasUsed) + + // Call FillProposalBy with the remainder limit and iterator from the previous call. + sizeUsed, gasUsed, txsToRemove = lane.FillProposalBy(s.ctx, &proposal, iterator, remainderLimit) + + // We expect tx1, tx2, tx5, tx6, tx7, tx8 to be included in the proposal. + s.Require().Equal(int64(884), sizeUsed) + s.Require().Equal(uint64(60), gasUsed) + s.Require().Equal([]sdk.Tx{tx3, tx4}, txsToRemove) + s.Require(). + Len(txsToRemove, 2, "tx3 and tx4 are removed") + + // The proposal should contain 2 transactions in Txs(). + expectedIncludedTxs = s.getTxBytes(tx1, tx2, tx5, tx6, tx7, tx8) + s.Require().Equal(6, len(proposal.Txs), "two txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.Txs) +} + +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- + +// createSimpleTx creates a basic single-bank-send Tx with the specified gasLimit. +func (s *LaneTestSuite) createSimpleTx(account Account, sequence uint64, gasLimit uint64) sdk.Tx { + msg := &banktypes.MsgSend{ + FromAddress: account.Address.String(), + ToAddress: account.Address.String(), + } + txBuilder := s.encodingConfig.TxConfig.NewTxBuilder() + if err := txBuilder.SetMsgs(msg); err != nil { + s.Require().NoError(err) + } + + sigV2 := txsigning.SignatureV2{ + PubKey: account.PrivKey.PubKey(), + Data: &txsigning.SingleSignatureData{ + SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: sequence, + } + err := txBuilder.SetSignatures(sigV2) + s.Require().NoError(err) + + txBuilder.SetGasLimit(gasLimit) + return txBuilder.GetTx() +} + +// getTxBytes encodes the given transactions to raw bytes for comparison. +func (s *LaneTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte { + txBytes := make([][]byte, len(txs)) + for i, tx := range txs { + bz, err := s.encodingConfig.TxConfig.TxEncoder()(tx) + s.Require().NoError(err) + txBytes[i] = bz + } + return txBytes +} diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index 8468c0a7d..ab9ce67d1 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -2,58 +2,50 @@ package mempool import ( "context" - "encoding/hex" "fmt" - "strings" - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" - - comettypes "github.com/cometbft/cometbft/types" + "cosmossdk.io/log" sdk "github.com/cosmos/cosmos-sdk/types" sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" ) +var _ sdkmempool.Mempool = (*Mempool)(nil) + // Mempool implements the sdkmempool.Mempool interface and uses Lanes internally. type Mempool struct { - txEncoder sdk.TxEncoder - signerExtractor signerextraction.Adapter - lanes []*Lane + logger log.Logger + + lanes []*Lane } // NewMempool returns a new mempool with the given lanes. func NewMempool( - txEncoder sdk.TxEncoder, - signerExtractor signerextraction.Adapter, + logger log.Logger, lanes []*Lane, ) *Mempool { return &Mempool{ - txEncoder: txEncoder, - signerExtractor: signerExtractor, - lanes: lanes, + logger: logger, + lanes: lanes, } } -var _ sdkmempool.Mempool = (*Mempool)(nil) - -// Insert inserts a transaction into the first matching lane. -func (m *Mempool) Insert(ctx context.Context, tx sdk.Tx) error { - txInfo, err := m.GetTxInfo(tx) - if err != nil { - return fmt.Errorf("Insert: failed to get tx info: %w", err) - } +// Insert will insert a transaction into the mempool. It inserts the transaction +// into the first lane that it matches. +func (m *Mempool) Insert(ctx context.Context, tx sdk.Tx) (err error) { + defer func() { + if r := recover(); r != nil { + m.logger.Error("panic in Insert", "err", r) + err = fmt.Errorf("panic in Insert: %v", r) + } + }() - placed := false for _, lane := range m.lanes { - if lane.Filter(tx) { - lane.AddTx(txInfo) - placed = true - break + if lane.Match(tx) { + return lane.Insert(ctx, tx) } } - if !placed { - fmt.Printf("Insert: no lane matched for tx %s\n", txInfo.Hash) - } + return nil } @@ -66,20 +58,27 @@ func (m *Mempool) Select(ctx context.Context, txs [][]byte) sdkmempool.Iterator func (m *Mempool) CountTx() int { count := 0 for _, lane := range m.lanes { - count += len(lane.GetTxs()) + count += lane.CountTx() } return count } -// Remove attempts to remove a transaction from whichever lane it is in. -func (m *Mempool) Remove(tx sdk.Tx) error { - txInfo, err := m.GetTxInfo(tx) - if err != nil { - return fmt.Errorf("Remove: failed to get tx info: %w", err) - } +// Remove removes a transaction from the mempool. This assumes that the transaction +// is contained in only one of the lanes. +func (m *Mempool) Remove(tx sdk.Tx) (err error) { + defer func() { + if r := recover(); r != nil { + m.logger.Error("panic in Remove", "err", r) + err = fmt.Errorf("panic in Remove: %v", r) + } + }() + for _, lane := range m.lanes { - lane.RemoveTx(txInfo) + if lane.Match(tx) { + return lane.Remove(tx) + } } + return nil } @@ -87,75 +86,92 @@ func (m *Mempool) Remove(tx sdk.Tx) error { // then calls each lane’s FillProposal method. If leftover gas is important to you, // you can implement a second pass or distribute leftover to subsequent lanes, etc. func (m *Mempool) PrepareProposal(ctx sdk.Context, proposal Proposal) (Proposal, error) { - // Reset each lane’s state for the new proposal, clearing signersUsed, etc. - for _, lane := range m.lanes { - lane.ResetState() - } + // 1) Perform the initial fill of proposals + laneIterators, txsToRemove, totalSize, totalGas := m.fillInitialProposals(ctx, &proposal) - totalGasLimit := proposal.Info.MaxGasLimit + // 2) Calculate the remaining block space + remainderLimit := NewBlockSpace(proposal.Info.MaxBlockSize-totalSize, proposal.Info.MaxGasLimit-totalGas) - // 1) Compute each lane's gas budget from its percentage - laneGasLimits := make([]uint64, len(m.lanes)) - for i, lane := range m.lanes { - laneGasLimits[i] = (lane.Percentage * totalGasLimit) / 100 - } + // 3) Fill proposals with leftover space + m.fillRemainderProposals(ctx, &proposal, laneIterators, txsToRemove, remainderLimit) - // 2) Fill the proposal lane by lane - remainder := uint64(0) - for i, lane := range m.lanes { - remainder += lane.FillProposal(&proposal, laneGasLimits[i]) - } + // 4) Remove the transactions that were invalidated from each lane + m.removeTxsFromLanes(txsToRemove) - // 3) use remainder gas from first round to fill proposal further - for _, lane := range m.lanes { - remainder = lane.FillProposal(&proposal, remainder) - } return proposal, nil } -// GetTxInfo returns metadata (hash, size, gas, signers) for the given tx by encoding it. -func (m *Mempool) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { - txBytes, err := m.txEncoder(tx) - if err != nil { - return TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) - } +// fillInitialProposals iterates over lanes, calling FillProposal. It returns: +// - laneIterators: the Iterator for each lane +// - txsToRemove: slice-of-slice of transactions to be removed later +// - totalSize: total block size used +// - totalGas: total gas used +func (m *Mempool) fillInitialProposals( + ctx sdk.Context, + proposal *Proposal, +) ( + []sdkmempool.Iterator, + [][]sdk.Tx, + int64, + uint64, +) { + var ( + totalSize int64 + totalGas uint64 + ) + + laneIterators := make([]sdkmempool.Iterator, len(m.lanes)) + txsToRemove := make([][]sdk.Tx, len(m.lanes)) - gasTx, ok := tx.(sdk.FeeTx) - if !ok { - return TxWithInfo{}, fmt.Errorf("failed to cast transaction to FeeTx") - } + for i, lane := range m.lanes { + sizeUsed, gasUsed, iterator, txs := lane.FillProposal(ctx, proposal) + totalSize += sizeUsed + totalGas += gasUsed - signers, err := m.signerExtractor.GetSigners(tx) - if err != nil { - return TxWithInfo{}, err + laneIterators[i] = iterator + txsToRemove[i] = txs } - return TxWithInfo{ - Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), - Size: int64(len(txBytes)), - GasLimit: gasTx.GetGas(), - TxBytes: txBytes, - Signers: signers, - }, nil + return laneIterators, txsToRemove, totalSize, totalGas } -// MempoolIterator is an example iterator (optional). -type MempoolIterator struct { - txs []sdk.Tx - index int -} +// fillRemainderProposals performs an additional fill on each lane using the leftover +// BlockSpace. It updates txsToRemove to include any newly removed transactions. +func (m *Mempool) fillRemainderProposals( + ctx sdk.Context, + proposal *Proposal, + laneIterators []sdkmempool.Iterator, + txsToRemove [][]sdk.Tx, + remainderLimit BlockSpace, +) { + for i, lane := range m.lanes { + sizeUsed, gasUsed, removedTxs := lane.FillProposalBy( + ctx, + proposal, + laneIterators[i], + remainderLimit, + ) -func (it *MempoolIterator) Next() sdkmempool.Iterator { - it.index++ - if it.index >= len(it.txs) { - return nil + // Decrement the remainder for subsequent lanes + remainderLimit.DecreaseBy(sizeUsed, gasUsed) + + // Append any newly removed transactions to be removed + txsToRemove[i] = append(txsToRemove[i], removedTxs...) } - return it } -func (it *MempoolIterator) Tx() sdk.Tx { - if it.index < len(it.txs) { - return it.txs[it.index] +// removeTxsFromLanes loops through each lane and removes all transactions +// accumulated in txsToRemove. +func (m *Mempool) removeTxsFromLanes(txsToRemove [][]sdk.Tx) { + for i, lane := range m.lanes { + for _, tx := range txsToRemove[i] { + if err := lane.Remove(tx); err != nil { + m.logger.Error( + "failed to remove transactions from lane", + "lane", lane.Name, + "err", err, + ) + } + } } - return nil } diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index 7c761a882..de49b6525 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -1,23 +1,21 @@ package mempool import ( - "context" "math/rand" "testing" signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" "github.com/stretchr/testify/suite" - cometabci "github.com/cometbft/cometbft/abci/types" - tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types" - - "github.com/cosmos/gogoproto/proto" - "cosmossdk.io/log" "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/tx/signing" + tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/address" @@ -28,6 +26,7 @@ import ( "github.com/cosmos/cosmos-sdk/std" "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" + sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" @@ -50,7 +49,6 @@ type MempoolTestSuite struct { suite.Suite ctx sdk.Context - key *storetypes.KVStoreKey encodingConfig EncodingConfig random *rand.Rand accounts []Account @@ -68,10 +66,16 @@ func (s *MempoolTestSuite) SetupTest() { s.accounts = RandomAccounts(s.random, 5) s.gasTokenDenom = "uband" - s.key = storetypes.NewKVStoreKey("test") - testCtx := testutil.DefaultContextWithDB(s.T(), s.key, storetypes.NewTransientStoreKey("transient_test")) + testCtx := testutil.DefaultContextWithDB( + s.T(), + storetypes.NewKVStoreKey("test"), + storetypes.NewTransientStoreKey("transient_test"), + ) s.ctx = testCtx.Ctx.WithIsCheckTx(true) s.ctx = s.ctx.WithBlockHeight(1) + + // Default consensus params + s.setBlockParams(100, 1000000000000) } type EncodingConfig struct { @@ -127,10 +131,6 @@ func RandomAccounts(r *rand.Rand, n int) []Account { return accs } -func (s *MempoolTestSuite) SetupSubTest() { - s.setBlockParams(100, 1000000000000) -} - func (s *MempoolTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { s.ctx = s.ctx.WithConsensusParams( tmprototypes.ConsensusParams{ @@ -146,17 +146,48 @@ func (s *MempoolTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { // Create Mempool + Lanes // ----------------------------------------------------------------------------- -// In your actual code, you'd likely have a helper that returns a mempool with -// bank, delegate, and "other" lanes at 30%, 30%, 40%. func (s *MempoolTestSuite) newMempool() *Mempool { - lanes := []*Lane{ - NewLane("bankSend", isBankSendTx, 30, false), - NewLane("delegate", isDelegateTx, 30, true), - NewLane("other", isOtherTx, 40, false), - } + signerAdapter := signerextraction.NewDefaultAdapter() + + BankSendLane := NewLane( + log.NewTestLogger(s.T()), + s.encodingConfig.TxConfig.TxEncoder(), + signerAdapter, + "bankSend", + isBankSendTx, + math.LegacyMustNewDecFromStr("0.2"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), + ) + + DelegateLane := NewLane( + log.NewTestLogger(s.T()), + s.encodingConfig.TxConfig.TxEncoder(), + signerAdapter, + "delegate", + isDelegateTx, + math.LegacyMustNewDecFromStr("0.2"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), + ) + + OtherLane := NewLane( + log.NewTestLogger(s.T()), + s.encodingConfig.TxConfig.TxEncoder(), + signerAdapter, + "other", + isOtherTx, + math.LegacyMustNewDecFromStr("0.4"), + math.LegacyMustNewDecFromStr("0.4"), + sdkmempool.DefaultPriorityMempool(), + ) - // Provide your actual signer extraction adapter if needed. For test, a default or mock might suffice. - return NewMempool(s.encodingConfig.TxConfig.TxEncoder(), signerextraction.NewDefaultAdapter(), lanes) + lanes := []*Lane{BankSendLane, DelegateLane, OtherLane} + + return NewMempool( + log.NewTestLogger(s.T()), + lanes, + ) } func isBankSendTx(tx sdk.Tx) bool { @@ -191,288 +222,294 @@ func isOtherTx(tx sdk.Tx) bool { } // ----------------------------------------------------------------------------- -// Tests +// Individual Test Methods // ----------------------------------------------------------------------------- -func (s *MempoolTestSuite) TestPrepareProposal() { - s.SetupSubTest() - - s.Run("can prepare a proposal with no transactions", func() { - mem := s.newMempool() - - // Build a "proposal handler" that uses the mempool - ph := s.newProposalHandler(mem) - - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ - Height: 2, - MaxTxBytes: maxTxBytes, - }) - s.Require().NoError(err) - s.Require().NotNil(resp) - s.Require().Equal(0, len(resp.Txs)) - }) - - s.Run("a single bank send tx", func() { - tx, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - mem := s.newMempool() - s.Require().NoError(mem.Insert(context.Background(), tx)) - - ph := s.newProposalHandler(mem) - - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ - Height: 2, - MaxTxBytes: maxTxBytes, - }) - s.Require().NoError(err) - s.Require().NotNil(resp) - - expected := s.getTxBytes(tx) - s.Require().Equal(1, len(resp.Txs)) - s.Require().Equal(expected, resp.Txs) - }) - - s.Run("single tx in every type", func() { - tx1, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - tx2, err := CreateDelegateTx( - s.encodingConfig.TxConfig, - s.accounts[1], - 0, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - tx3, err := CreateMixedTx( - s.encodingConfig.TxConfig, - s.accounts[2], - 0, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - mem := s.newMempool() - s.Require().NoError(mem.Insert(context.Background(), tx1)) - s.Require().NoError(mem.Insert(context.Background(), tx2)) - s.Require().NoError(mem.Insert(context.Background(), tx3)) - - ph := s.newProposalHandler(mem) - - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ - Height: 2, - MaxTxBytes: maxTxBytes, - }) - s.Require().NoError(err) - s.Require().NotNil(resp) - - expected := s.getTxBytes(tx1, tx2, tx3) - s.Require().Equal(3, len(resp.Txs)) - s.Require().Equal(expected, resp.Txs) - }) - - s.Run("one bank tx over gas limit", func() { - bankTx1, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 20, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - bankTx2, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[1], - 0, - 0, - 20, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) - - delegateTx1, err := CreateDelegateTx( - s.encodingConfig.TxConfig, - s.accounts[2], - 0, - 0, - 30, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) +// TestNoTransactions ensures no transactions exist => empty proposal +func (s *MempoolTestSuite) TestNoTransactions() { + mem := s.newMempool() - otherTx1, err := CreateMixedTx( - s.encodingConfig.TxConfig, - s.accounts[3], - 0, - 0, - 40, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) + proposal := NewProposal( + log.NewTestLogger(s.T()), + s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxGas), + ) - mem := s.newMempool() - s.Require().NoError(mem.Insert(context.Background(), bankTx1)) - s.Require().NoError(mem.Insert(context.Background(), bankTx2)) - s.Require().NoError(mem.Insert(context.Background(), delegateTx1)) - s.Require().NoError(mem.Insert(context.Background(), otherTx1)) + result, err := mem.PrepareProposal(s.ctx, proposal) + s.Require().NoError(err) + s.Require().NotNil(result) + s.Require().Equal(0, len(result.Txs)) +} - ph := s.newProposalHandler(mem) +// TestSingleBankTx ensures a single bank tx is included +func (s *MempoolTestSuite) TestSingleBankTx() { + tx, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ - Height: 2, - MaxTxBytes: maxTxBytes, - }) - s.Require().NoError(err) - s.Require().NotNil(resp) + mem := s.newMempool() + s.Require().NoError(mem.Insert(s.ctx, tx)) - // Expected that bankTx2 may not fit in lane's gas budget on the first pass. - expected := s.getTxBytes(bankTx1, delegateTx1, otherTx1) - s.Require().Equal(3, len(resp.Txs)) - s.Require().Equal(expected, resp.Txs) - }) + proposal := NewProposal( + log.NewTestLogger(s.T()), + s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxGas), + ) - s.Run("one bank tx over gas limit but has space left", func() { - bankTx1, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 20, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) + result, err := mem.PrepareProposal(s.ctx, proposal) + s.Require().NoError(err) + s.Require().NotNil(result) - bankTx2, err := CreateBankSendTx( - s.encodingConfig.TxConfig, - s.accounts[1], - 0, - 0, - 20, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) + expectedIncludedTxs := s.getTxBytes(tx) + s.Require().Equal(1, len(result.Txs)) + s.Require().Equal(expectedIncludedTxs, result.Txs) +} - delegateTx1, err := CreateDelegateTx( - s.encodingConfig.TxConfig, - s.accounts[2], - 0, - 0, - 30, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) +// TestOneTxPerLane checks a single transaction in each lane type +func (s *MempoolTestSuite) TestOneTxPerLane() { + tx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + tx2, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + tx3, err := CreateMixedTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + 1, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + mem := s.newMempool() + // Insert in reverse order to ensure ordering is correct + s.Require().NoError(mem.Insert(s.ctx, tx3)) + s.Require().NoError(mem.Insert(s.ctx, tx2)) + s.Require().NoError(mem.Insert(s.ctx, tx1)) + + proposal := NewProposal( + log.NewTestLogger(s.T()), + s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxGas), + ) - otherTx1, err := CreateMixedTx( - s.encodingConfig.TxConfig, - s.accounts[3], - 0, - 0, - 30, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) + result, err := mem.PrepareProposal(s.ctx, proposal) + s.Require().NoError(err) + s.Require().NotNil(result) - mem := s.newMempool() - s.Require().NoError(mem.Insert(context.Background(), bankTx1)) - s.Require().NoError(mem.Insert(context.Background(), bankTx2)) - s.Require().NoError(mem.Insert(context.Background(), delegateTx1)) - s.Require().NoError(mem.Insert(context.Background(), otherTx1)) + expectedIncludedTxs := s.getTxBytes(tx1, tx2, tx3) + s.Require().Equal(3, len(result.Txs)) + s.Require().Equal(expectedIncludedTxs, result.Txs) +} - ph := s.newProposalHandler(mem) +// TestTxOverLimit checks if a tx over the block limit is rejected +func (s *MempoolTestSuite) TestTxOverLimit() { + tx, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 101, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ - Height: 2, - MaxTxBytes: maxTxBytes, - }) - s.Require().NoError(err) - s.Require().NotNil(resp) + mem := s.newMempool() + s.Require().NoError(mem.Insert(s.ctx, tx)) - expected := s.getTxBytes(bankTx1, delegateTx1, otherTx1, bankTx2) - s.Require().Equal(4, len(resp.Txs)) - s.Require().Equal(expected, resp.Txs) - }) + proposal := NewProposal( + log.NewTestLogger(s.T()), + s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxGas), + ) - s.Run("enforce one tx per signer", func() { - delegateTx1, err := CreateDelegateTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 0, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) + result, err := mem.PrepareProposal(s.ctx, proposal) + s.Require().NoError(err) - delegateTx2, err := CreateDelegateTx( - s.encodingConfig.TxConfig, - s.accounts[0], - 1, - 0, - 1, - sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), - ) - s.Require().NoError(err) + s.Require().Equal(0, len(result.Txs)) - mem := s.newMempool() - s.Require().NoError(mem.Insert(context.Background(), delegateTx1)) - s.Require().NoError(mem.Insert(context.Background(), delegateTx2)) + // Ensure the tx is removed + for _, lane := range mem.lanes { + s.Require().Equal(0, lane.CountTx()) + } +} - ph := s.newProposalHandler(mem) +// TestTxsOverGasLimit checks if txs over the gas limit are rejected +func (s *MempoolTestSuite) TestTxsOverGasLimit() { + bankTx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + bankTx2, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 1, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + delegateTx1, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + delegateTx2, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 1, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + otherTx1, err := CreateMixedTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + 40, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + mem := s.newMempool() + // Insert in reverse order to ensure ordering is correct + s.Require().NoError(mem.Insert(s.ctx, otherTx1)) + s.Require().NoError(mem.Insert(s.ctx, delegateTx2)) + s.Require().NoError(mem.Insert(s.ctx, delegateTx1)) + s.Require().NoError(mem.Insert(s.ctx, bankTx2)) + s.Require().NoError(mem.Insert(s.ctx, bankTx1)) + + proposal := NewProposal( + log.NewTestLogger(s.T()), + s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxGas), + ) - maxTxBytes := s.ctx.ConsensusParams().Block.MaxBytes - resp, err := ph.PrepareProposalHandler()(s.ctx, &cometabci.RequestPrepareProposal{ - Height: 2, - MaxTxBytes: maxTxBytes, - }) - s.Require().NoError(err) - s.Require().NotNil(resp) + result, err := mem.PrepareProposal(s.ctx, proposal) + s.Require().NoError(err) + s.Require().NotNil(result) - expected := s.getTxBytes(delegateTx1) - s.Require().Equal(1, len(resp.Txs)) - s.Require().Equal(expected, resp.Txs) - }) + // should not contain the otherTx1 + expectedIncludedTxs := s.getTxBytes(bankTx1, bankTx2, delegateTx1, delegateTx2) + s.Require().Equal(4, len(result.Txs)) + s.Require().Equal(expectedIncludedTxs, result.Txs) } -// newProposalHandler is analogous to your band proposal handler that uses -// the mempool in PrepareProposal. -func (s *MempoolTestSuite) newProposalHandler(mem *Mempool) *ProposalHandler { - // You may need to adjust: e.g. if your real code calls - // `NewDefaultProposalHandler(logger, txDecoder, mempool)`. - return NewDefaultProposalHandler( - log.NewNopLogger(), - s.encodingConfig.TxConfig.TxDecoder(), - mem, +// TestFillUpLeftOverSpace checks if the proposal fills up the remaining space +func (s *MempoolTestSuite) TestFillUpLeftOverSpace() { + bankTx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + bankTx2, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 1, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + bankTx3, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 2, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + delegateTx1, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + delegateTx2, err := CreateDelegateTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 1, + 0, + 20, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), ) + s.Require().NoError(err) + + mem := s.newMempool() + // Insert in reverse order to ensure ordering is correct + s.Require().NoError(mem.Insert(s.ctx, delegateTx2)) + s.Require().NoError(mem.Insert(s.ctx, delegateTx1)) + s.Require().NoError(mem.Insert(s.ctx, bankTx3)) + s.Require().NoError(mem.Insert(s.ctx, bankTx2)) + s.Require().NoError(mem.Insert(s.ctx, bankTx1)) + + proposal := NewProposal( + log.NewTestLogger(s.T()), + s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxGas), + ) + + result, err := mem.PrepareProposal(s.ctx, proposal) + s.Require().NoError(err) + s.Require().NotNil(result) + + // should contain bankTx3 as the last tx + expectedIncludedTxs := s.getTxBytes(bankTx1, bankTx2, delegateTx1, delegateTx2, bankTx3) + s.Require().Equal(5, len(result.Txs)) + s.Require().Equal(expectedIncludedTxs, result.Txs) } // ----------------------------------------------------------------------------- -// Tx creation helpers (same logic you had) +// Tx creation helpers // ----------------------------------------------------------------------------- func CreateBankSendTx( @@ -488,7 +525,6 @@ func CreateBankSendTx( ToAddress: account.Address.String(), }, } - return buildTx(txCfg, account, msgs, nonce, timeout, gasLimit, fees...) } @@ -560,7 +596,7 @@ func buildTx( return txBuilder.GetTx(), nil } -// getTxBytes encodes the given transactions. +// getTxBytes encodes the given transactions to raw bytes for comparison. func (s *MempoolTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte { txBytes := make([][]byte, len(txs)) for i, tx := range txs { @@ -570,3 +606,23 @@ func (s *MempoolTestSuite) getTxBytes(txs ...sdk.Tx) [][]byte { } return txBytes } + +// decodeTxs decodes the given TxWithInfo slice back into sdk.Tx for easy comparison. +func (s *MempoolTestSuite) decodeTxs(infos []TxWithInfo) []sdk.Tx { + res := make([]sdk.Tx, len(infos)) + for i, info := range infos { + tx, err := s.encodingConfig.TxConfig.TxDecoder()(info.TxBytes) + s.Require().NoError(err) + res[i] = tx + } + return res +} + +// extractTxBytes is a convenience to get a [][]byte from []TxWithInfo. +func extractTxBytes(txs []TxWithInfo) [][]byte { + bz := make([][]byte, len(txs)) + for i, tx := range txs { + bz[i] = tx.TxBytes + } + return bz +} diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index 6e8ab0012..32ef54a77 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -4,7 +4,9 @@ import ( "fmt" "cosmossdk.io/log" + "cosmossdk.io/math" + comettypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -99,16 +101,51 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { return nil } +// GetLimit returns the maximum block space available for a given ratio. +func (p *Proposal) GetLimit(ratio math.LegacyDec) BlockSpace { + var ( + txBytesLimit int64 + gasLimit uint64 + ) + + // In the case where the ratio is zero, we return the max tx bytes remaining. + if ratio.IsZero() { + txBytesLimit = p.Info.MaxBlockSize - p.Info.BlockSize + if txBytesLimit < 0 { + txBytesLimit = 0 + } + + // Unsigned subtraction needs an additional check + if p.Info.GasLimit >= p.Info.MaxGasLimit { + gasLimit = 0 + } else { + gasLimit = p.Info.MaxGasLimit - p.Info.GasLimit + } + } else { + // Otherwise, we calculate the max tx bytes / gas limit for the lane based on the ratio. + txBytesLimit = ratio.MulInt64(p.Info.MaxBlockSize).TruncateInt().Int64() + gasLimit = ratio.MulInt(math.NewIntFromUint64(p.Info.MaxGasLimit)).TruncateInt().Uint64() + } + + return NewBlockSpace(txBytesLimit, gasLimit) +} + // GetBlockLimits retrieves the maximum block size and gas limit from context. func GetBlockLimits(ctx sdk.Context) (int64, uint64) { blockParams := ctx.ConsensusParams().Block var maxGasLimit uint64 - if maxGas := blockParams.MaxGas; maxGas > 0 { - maxGasLimit = uint64(maxGas) - } else { + + if blockParams.MaxGas == -1 { maxGasLimit = MaxUint64 + } else { + maxGasLimit = uint64(blockParams.MaxGas) + } + + maxBytesLimit := blockParams.MaxBytes + if blockParams.MaxBytes == -1 { + maxBytesLimit = comettypes.MaxBlockSizeBytes } - return blockParams.MaxBytes, maxGasLimit + return maxBytesLimit, maxGasLimit } diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go index f42af2320..19ecffb84 100644 --- a/app/mempool/proposal_handler.go +++ b/app/mempool/proposal_handler.go @@ -3,8 +3,6 @@ package mempool import ( "fmt" - "github.com/skip-mev/block-sdk/v2/block/utils" - abci "github.com/cometbft/cometbft/abci/types" "cosmossdk.io/log" @@ -104,7 +102,7 @@ func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { }() // Decode the transactions in the proposal. - decodedTxs, err := utils.GetDecodedTxs(h.txDecoder, req.Txs) + decodedTxs, err := GetDecodedTxs(h.txDecoder, req.Txs) if err != nil { h.logger.Error("failed to decode txs", "err", err) return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err diff --git a/app/mempool/utils.go b/app/mempool/utils.go new file mode 100644 index 000000000..1f97e4b14 --- /dev/null +++ b/app/mempool/utils.go @@ -0,0 +1,22 @@ +package mempool + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GetDecodedTxs returns the decoded transactions from the given bytes. +func GetDecodedTxs(txDecoder sdk.TxDecoder, txs [][]byte) ([]sdk.Tx, error) { + var decodedTxs []sdk.Tx + for _, txBz := range txs { + tx, err := txDecoder(txBz) + if err != nil { + return nil, fmt.Errorf("failed to decode transaction: %w", err) + } + + decodedTxs = append(decodedTxs, tx) + } + + return decodedTxs, nil +} From ebb0f6e933ccba78436331b770d10239b675fa2b Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 18 Feb 2025 14:46:09 +0700 Subject: [PATCH 06/53] implement tx verification by msgServer in lane insertion, use ignore decorator for ignoring fee --- app/ante.go | 14 +- app/app.go | 28 +- app/lanes.go | 323 +++++++++-- app/mempool/lane.go | 100 ++-- app/mempool/lane_test.go | 8 +- app/mempool/mempool.go | 12 +- app/mempool/mempool_test.go | 16 +- app/mempool/proposal.go | 4 +- x/globalfee/feechecker/feechecker.go | 109 ---- x/globalfee/feechecker/feechecker_test.go | 640 ---------------------- 10 files changed, 376 insertions(+), 878 deletions(-) diff --git a/app/ante.go b/app/ante.go index 214e01c2b..b7dd46208 100644 --- a/app/ante.go +++ b/app/ante.go @@ -11,6 +11,7 @@ import ( authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/bandprotocol/chain/v3/app/mempool" bandtsskeeper "github.com/bandprotocol/chain/v3/x/bandtss/keeper" feedskeeper "github.com/bandprotocol/chain/v3/x/feeds/keeper" "github.com/bandprotocol/chain/v3/x/globalfee/feechecker" @@ -32,6 +33,7 @@ type HandlerOptions struct { TSSKeeper *tsskeeper.Keeper BandtssKeeper *bandtsskeeper.Keeper FeedsKeeper *feedskeeper.Keeper + Lanes []*mempool.Lane } func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { @@ -105,6 +107,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { options.FeegrantKeeper, options.TxFeeChecker, ), + options.Lanes..., ), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewSetPubKeyDecorator(options.AccountKeeper), @@ -122,12 +125,14 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { // for the AnteDecorator to be ignored for specified lanes. type IgnoreDecorator struct { decorator sdk.AnteDecorator + lanes []*mempool.Lane } // NewIgnoreDecorator returns a new IgnoreDecorator instance. -func NewIgnoreDecorator(decorator sdk.AnteDecorator) *IgnoreDecorator { +func NewIgnoreDecorator(decorator sdk.AnteDecorator, lanes ...*mempool.Lane) *IgnoreDecorator { return &IgnoreDecorator{ decorator: decorator, + lanes: lanes, } } @@ -137,8 +142,11 @@ func NewIgnoreDecorator(decorator sdk.AnteDecorator) *IgnoreDecorator { func (sd IgnoreDecorator) AnteHandle( ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler, ) (sdk.Context, error) { - if isBankSendTx(tx) || isDelegateTx(tx) { - return next(ctx, tx, simulate) + cacheCtx, _ := ctx.CacheContext() + for _, lane := range sd.lanes { + if lane.Match(cacheCtx, tx) { + return next(ctx, tx, simulate) + } } return sd.decorator.AnteHandle(ctx, tx, simulate, next) diff --git a/app/app.go b/app/app.go index 53203b24a..835cd1ba7 100644 --- a/app/app.go +++ b/app/app.go @@ -254,15 +254,8 @@ func NewBandApp( app.sm.RegisterStoreDecoders() - // // initialize lanes + mempool - // feedsLane, defaultLane := CreateLanes(app, txConfig) - - // lanedMempool, err := block.NewLanedMempool(app.Logger(), []block.Lane{feedsLane, defaultLane}) - // if err != nil { - // panic(err) - // } - - bandLanes := BandLanes(app) + feedsLane, tssLane, oracleLane, defaultLane := CreateLanes(app) + bandLanes := []*mempool.Lane{feedsLane, tssLane, oracleLane, defaultLane} // create Band mempool bandMempool := mempool.NewMempool(app.Logger(), bandLanes) @@ -292,28 +285,13 @@ func NewBandApp( IBCKeeper: app.IBCKeeper, StakingKeeper: app.StakingKeeper, GlobalfeeKeeper: &app.GlobalFeeKeeper, + Lanes: []*mempool.Lane{feedsLane, tssLane, oracleLane}, // every lane except default lane }, ) if err != nil { panic(fmt.Errorf("failed to create ante handler: %s", err)) } - // // update ante-handlers on lanes - // opt := []base.LaneOption{ - // base.WithAnteHandler(anteHandler), - // } - // feedsLane.WithOptions(opt...) - // defaultLane.WithOptions(opt...) - - // // ABCI handlers - // // prepare proposal - // proposalHandler := blocksdkabci.NewDefaultProposalHandler( - // app.Logger(), - // txConfig.TxDecoder(), - // txConfig.TxEncoder(), - // lanedMempool, - // ) - // proposal handler proposalHandler := mempool.NewDefaultProposalHandler(app.Logger(), txConfig.TxDecoder(), bandMempool) diff --git a/app/lanes.go b/app/lanes.go index 97897d5a1..d2da2586c 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -1,87 +1,342 @@ package band import ( + "fmt" + + signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + "github.com/cosmos/cosmos-sdk/x/authz" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" "github.com/bandprotocol/chain/v3/app/mempool" + bandtsskeeper "github.com/bandprotocol/chain/v3/x/bandtss/keeper" + feedskeeper "github.com/bandprotocol/chain/v3/x/feeds/keeper" + feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" + oraclekeeper "github.com/bandprotocol/chain/v3/x/oracle/keeper" + oracletypes "github.com/bandprotocol/chain/v3/x/oracle/types" + tsskeeper "github.com/bandprotocol/chain/v3/x/tss/keeper" + tsstypes "github.com/bandprotocol/chain/v3/x/tss/types" ) -// isBankSendTx returns true if this transaction is strictly a bank send transaction (MsgSend). -func isBankSendTx(tx sdk.Tx) bool { - msgs := tx.GetMsgs() - if len(msgs) == 0 { - return false +// FeedsLaneMatchHandler is a function that returns the match function for the Feeds lane. +func FeedsLaneMatchHandler( + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + feedsMsgServer feedstypes.MsgServer, +) func(sdk.Context, sdk.Tx) bool { + return func(ctx sdk.Context, tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if !isValidMsgSubmitSignalPrices(ctx, msg, cdc, authzKeeper, feedsMsgServer) { + return false + } + } + fmt.Println("valid feeds message") + return true } - for _, msg := range msgs { - if _, ok := msg.(*banktypes.MsgSend); !ok { +} + +// isValidMsgSubmitSignalPrices return true if the message is a valid feeds' MsgSubmitSignalPrices. +func isValidMsgSubmitSignalPrices( + ctx sdk.Context, + msg sdk.Msg, + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + feedsMsgServer feedstypes.MsgServer, +) bool { + switch msg := msg.(type) { + case *feedstypes.MsgSubmitSignalPrices: + if _, err := feedsMsgServer.SubmitSignalPrices(ctx, msg); err != nil { return false } + case *authz.MsgExec: + msgs, err := msg.GetMessages() + if err != nil { + return false + } + + grantee, err := sdk.AccAddressFromBech32(msg.Grantee) + if err != nil { + return false + } + + for _, m := range msgs { + signers, _, err := cdc.GetMsgV1Signers(m) + if err != nil { + return false + } + // Check if this grantee have authorization for the message. + cap, _ := authzKeeper.GetAuthorization( + ctx, + grantee, + sdk.AccAddress(signers[0]), + sdk.MsgTypeURL(m), + ) + if cap == nil { + return false + } + + // Check if this message should be free or not. + if !isValidMsgSubmitSignalPrices(ctx, m, cdc, authzKeeper, feedsMsgServer) { + return false + } + } + default: + return false } + return true } -// isDelegateTx returns true if this transaction is strictly a staking delegate transaction (MsgDelegate). -func isDelegateTx(tx sdk.Tx) bool { - msgs := tx.GetMsgs() - if len(msgs) == 0 { +// TssLaneMatchHandler is a function that returns the match function for the TSS lane. +func TssLaneMatchHandler( + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + bandtssKeeper *bandtsskeeper.Keeper, + tssMsgServer tsstypes.MsgServer, +) func(sdk.Context, sdk.Tx) bool { + return func(ctx sdk.Context, tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if !isValidTssLaneMsg(ctx, msg, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { + return false + } + } + fmt.Println("valid tss message") + return true + } +} + +// isValidTssLaneMsg return true if the message is a valid for TSS lane. +func isValidTssLaneMsg( + ctx sdk.Context, + msg sdk.Msg, + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + bandtssKeeper *bandtsskeeper.Keeper, + tssMsgServer tsstypes.MsgServer, +) bool { + switch msg := msg.(type) { + case *tsstypes.MsgSubmitDKGRound1: + if _, err := tssMsgServer.SubmitDKGRound1(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgSubmitDKGRound2: + if _, err := tssMsgServer.SubmitDKGRound2(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgConfirm: + if _, err := tssMsgServer.Confirm(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgComplain: + if _, err := tssMsgServer.Complain(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgSubmitDEs: + acc, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return false + } + + currentGroupID := bandtssKeeper.GetCurrentGroup(ctx).GroupID + incomingGroupID := bandtssKeeper.GetIncomingGroupID(ctx) + if !bandtssKeeper.HasMember(ctx, acc, currentGroupID) && + !bandtssKeeper.HasMember(ctx, acc, incomingGroupID) { + return false + } + + if _, err := tssMsgServer.SubmitDEs(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgSubmitSignature: + if _, err := tssMsgServer.SubmitSignature(ctx, msg); err != nil { + return false + } + case *authz.MsgExec: + msgs, err := msg.GetMessages() + if err != nil { + return false + } + + grantee, err := sdk.AccAddressFromBech32(msg.Grantee) + if err != nil { + return false + } + + for _, m := range msgs { + signers, _, err := cdc.GetMsgV1Signers(m) + if err != nil { + return false + } + // Check if this grantee have authorization for the message. + cap, _ := authzKeeper.GetAuthorization( + ctx, + grantee, + sdk.AccAddress(signers[0]), + sdk.MsgTypeURL(m), + ) + if cap == nil { + return false + } + + // Check if this message should be free or not. + if !isValidTssLaneMsg(ctx, m, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { + return false + } + } + default: return false } - for _, msg := range msgs { - if _, ok := msg.(*stakingtypes.MsgDelegate); !ok { + + return true +} + +// oracleLaneMatchHandler is a function that returns the match function for the oracle lane. +func oracleLaneMatchHandler( + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + oracleMsgServer oracletypes.MsgServer, +) func(sdk.Context, sdk.Tx) bool { + return func(ctx sdk.Context, tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if !isValidMsgReportData(ctx, msg, cdc, authzKeeper, oracleMsgServer) { + return false + } + } + fmt.Println("valid bank send message") + return true + } +} + +// isValidMsgReportData return true if the message is a valid oracle's MsgReportData. +func isValidMsgReportData( + ctx sdk.Context, + msg sdk.Msg, + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + oracleMsgServer oracletypes.MsgServer, +) bool { + switch msg := msg.(type) { + case *oracletypes.MsgReportData: + if _, err := oracleMsgServer.ReportData(ctx, msg); err != nil { + return false + } + case *authz.MsgExec: + msgs, err := msg.GetMessages() + if err != nil { + return false + } + + grantee, err := sdk.AccAddressFromBech32(msg.Grantee) + if err != nil { return false } + + for _, m := range msgs { + signers, _, err := cdc.GetMsgV1Signers(m) + if err != nil { + return false + } + // Check if this grantee have authorization for the message. + cap, _ := authzKeeper.GetAuthorization( + ctx, + grantee, + sdk.AccAddress(signers[0]), + sdk.MsgTypeURL(m), + ) + if cap == nil { + return false + } + + // Check if this message should be free or not. + if !isValidMsgReportData(ctx, m, cdc, authzKeeper, oracleMsgServer) { + return false + } + } + default: + return false } + return true } -// isOtherTx returns true if the transaction is neither a pure bank send nor a pure delegate transaction. -func isOtherTx(tx sdk.Tx) bool { - return !isBankSendTx(tx) && !isDelegateTx(tx) +// DefaultLaneMatchHandler is a function that returns the match function for the default lane. +func DefaultLaneMatchHandler() func(sdk.Context, sdk.Tx) bool { + return func(_ sdk.Context, _ sdk.Tx) bool { + fmt.Println("valid default message") + return true + } } -// BandLanes returns the default lanes for the Band Protocol blockchain. -func BandLanes(app *BandApp) []*mempool.Lane { +// CreateLanes creates the lanes for the Band mempool. +func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mempool.Lane) { // 1. Create the signer extractor. This is used to extract the expected signers from // a transaction. Each lane can have a different signer extractor if needed. signerAdapter := signerextraction.NewDefaultAdapter() - BankSendLane := mempool.NewLane( + feedsMsgServer := feedskeeper.NewMsgServerImpl(app.FeedsKeeper) + tssMsgServer := tsskeeper.NewMsgServerImpl(app.TSSKeeper) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(app.OracleKeeper) + + feedsLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), signerAdapter, - "bankSend", - isBankSendTx, + "feedsLane", + FeedsLaneMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), ) - DelegateLane := mempool.NewLane( + tssLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), signerAdapter, - "delegate", - isDelegateTx, + "tssLane", + TssLaneMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), math.LegacyMustNewDecFromStr("0.05"), - math.LegacyMustNewDecFromStr("0.3"), + math.LegacyMustNewDecFromStr("0.2"), + sdkmempool.DefaultPriorityMempool(), + ) + + oracleLane = mempool.NewLane( + app.Logger(), + app.txConfig.TxEncoder(), + signerAdapter, + "oracleLane", + oracleLaneMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), + math.LegacyMustNewDecFromStr("0.05"), + math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), ) - OtherLane := mempool.NewLane( + defaultLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), signerAdapter, - "other", - isOtherTx, - math.LegacyMustNewDecFromStr("0.1"), - math.LegacyMustNewDecFromStr("0.4"), + "defaultLane", + DefaultLaneMatchHandler(), + math.LegacyMustNewDecFromStr("0.3"), + math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), ) - return []*mempool.Lane{BankSendLane, DelegateLane, OtherLane} + return } diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 5b5c99768..b3b35e980 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -6,11 +6,13 @@ import ( "fmt" "strings" - "cosmossdk.io/log" - "cosmossdk.io/math" signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" comettypes "github.com/cometbft/cometbft/types" + + "cosmossdk.io/log" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" ) @@ -20,27 +22,26 @@ type Lane struct { Logger log.Logger TxEncoder sdk.TxEncoder SignerExtractor signerextraction.Adapter - // Name is a identifier for this lane (e.g. "bankSend", "delegate"). - Name string - - // Filter determines if a given transaction belongs in this lane. - Match func(sdk.Tx) bool + Name string + Match func(ctx sdk.Context, tx sdk.Tx) bool MaxTransactionSpace math.LegacyDec MaxLaneSpace math.LegacyDec - // laneMempool is the mempool that is responsible for storing transactions - // that are waiting to be processed. laneMempool sdkmempool.Mempool + + // txIndex holds the uppercase hex-encoded hash for every transaction + // currently in this lane's mempool. + txIndex map[string]struct{} } -// NewLane is a simple constructor for a lane. +// NewLane is a constructor for a lane. func NewLane( logger log.Logger, txEncoder sdk.TxEncoder, signerExtractor signerextraction.Adapter, name string, - matchFn func(sdk.Tx) bool, + matchFn func(sdk.Context, sdk.Tx) bool, maxTransactionSpace math.LegacyDec, maxLaneSpace math.LegacyDec, laneMempool sdkmempool.Mempool, @@ -54,11 +55,25 @@ func NewLane( MaxTransactionSpace: maxTransactionSpace, MaxLaneSpace: maxLaneSpace, laneMempool: laneMempool, + + // Initialize the txIndex. + txIndex: make(map[string]struct{}), } } func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { - return l.laneMempool.Insert(ctx, tx) + txInfo, err := l.GetTxInfo(tx) + if err != nil { + return err + } + + if err = l.laneMempool.Insert(ctx, tx); err != nil { + return err + } + + l.txIndex[txInfo.Hash] = struct{}{} + fmt.Println("insert tx to lane", l.Name, txInfo.Hash) + return nil } func (l *Lane) CountTx() int { @@ -66,7 +81,28 @@ func (l *Lane) CountTx() int { } func (l *Lane) Remove(tx sdk.Tx) error { - return l.laneMempool.Remove(tx) + txInfo, err := l.GetTxInfo(tx) + if err != nil { + return err + } + + if err = l.laneMempool.Remove(tx); err != nil { + return err + } + + // Remove it from the local index + delete(l.txIndex, txInfo.Hash) + return nil +} + +func (l *Lane) Contains(tx sdk.Tx) bool { + txInfo, err := l.GetTxInfo(tx) + if err != nil { + return false + } + + _, exists := l.txIndex[txInfo.Hash] + return exists } // FillProposal fills the proposal with transactions from the lane mempool. @@ -90,12 +126,11 @@ func (l *Lane) FillProposal( // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. // We can tolerate a few bytes/gas over the limit, since we limit the size of each transaction. if laneLimit.IsReached(sizeUsed, gasUsed) { - break } tx := iterator.Tx() - txInfo, err := l.GetTxInfo(ctx, tx) + txInfo, err := l.GetTxInfo(tx) if err != nil { l.Logger.Info("failed to get hash of tx", "err", err) @@ -115,18 +150,6 @@ func (l *Lane) FillProposal( continue } - // Verify the transaction. - if err = l.VerifyTx(ctx, tx, false); err != nil { - l.Logger.Info( - "failed to verify tx", - "tx_hash", txInfo.Hash, - "err", err, - ) - - txsToRemove = append(txsToRemove, tx) - continue - } - // Add the transaction to the proposal. // TODO: check if the transaction cannot be added here, it should also cannot be added afterward. if err := proposal.Add(txInfo); err != nil { @@ -149,7 +172,6 @@ func (l *Lane) FillProposal( } func (l *Lane) FillProposalBy( - ctx sdk.Context, proposal *Proposal, iterator sdkmempool.Iterator, laneLimit BlockSpace, @@ -167,7 +189,7 @@ func (l *Lane) FillProposalBy( } tx := iterator.Tx() - txInfo, err := l.GetTxInfo(ctx, tx) + txInfo, err := l.GetTxInfo(tx) if err != nil { l.Logger.Info("failed to get hash of tx", "err", err) @@ -187,18 +209,6 @@ func (l *Lane) FillProposalBy( continue } - // Verify the transaction. - if err = l.VerifyTx(ctx, tx, false); err != nil { - l.Logger.Info( - "failed to verify tx", - "tx_hash", txInfo.Hash, - "err", err, - ) - - txsToRemove = append(txsToRemove, tx) - continue - } - // Add the transaction to the proposal. if err := proposal.Add(txInfo); err != nil { l.Logger.Info( @@ -222,7 +232,7 @@ func (l *Lane) FillProposalBy( // GetTxInfo returns various information about the transaction that // belongs to the lane including its priority, signer's, sequence number, // size and more. -func (l *Lane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (TxWithInfo, error) { +func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { txBytes, err := l.TxEncoder(tx) if err != nil { return TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) @@ -247,9 +257,3 @@ func (l *Lane) GetTxInfo(ctx sdk.Context, tx sdk.Tx) (TxWithInfo, error) { Signers: signers, }, nil } - -// TODO: Add a method to verify the transaction. -// VerifyTx verifies that the transaction is valid respecting to the msg server -func (l *Lane) VerifyTx(ctx sdk.Context, tx sdk.Tx, simulate bool) error { - return nil -} diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 1db6a544c..4834e9d23 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -60,7 +60,7 @@ func (s *LaneTestSuite) TestLaneInsertAndCount() { s.encodingConfig.TxConfig.TxEncoder(), signerextraction.NewDefaultAdapter(), "testLane", - func(sdk.Tx) bool { return true }, // accept all + func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.3"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), @@ -84,7 +84,7 @@ func (s *LaneTestSuite) TestLaneRemove() { s.encodingConfig.TxConfig.TxEncoder(), signerextraction.NewDefaultAdapter(), "testLane", - func(sdk.Tx) bool { return true }, // accept all + func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.3"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), @@ -107,7 +107,7 @@ func (s *LaneTestSuite) TestLaneFillProposal() { s.encodingConfig.TxConfig.TxEncoder(), signerextraction.NewDefaultAdapter(), "testLane", - func(sdk.Tx) bool { return true }, // accept all + func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.2"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), @@ -158,7 +158,7 @@ func (s *LaneTestSuite) TestLaneFillProposal() { remainderLimit := NewBlockSpace(proposal.Info.MaxBlockSize-sizeUsed, proposal.Info.MaxGasLimit-gasUsed) // Call FillProposalBy with the remainder limit and iterator from the previous call. - sizeUsed, gasUsed, txsToRemove = lane.FillProposalBy(s.ctx, &proposal, iterator, remainderLimit) + sizeUsed, gasUsed, txsToRemove = lane.FillProposalBy(&proposal, iterator, remainderLimit) // We expect tx1, tx2, tx5, tx6, tx7, tx8 to be included in the proposal. s.Require().Equal(int64(884), sizeUsed) diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index ab9ce67d1..ff91c3ee6 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -40,8 +40,9 @@ func (m *Mempool) Insert(ctx context.Context, tx sdk.Tx) (err error) { } }() + cacheSdkCtx, _ := sdk.UnwrapSDKContext(ctx).CacheContext() for _, lane := range m.lanes { - if lane.Match(tx) { + if lane.Match(cacheSdkCtx, tx) { return lane.Insert(ctx, tx) } } @@ -74,7 +75,7 @@ func (m *Mempool) Remove(tx sdk.Tx) (err error) { }() for _, lane := range m.lanes { - if lane.Match(tx) { + if lane.Contains(tx) { return lane.Remove(tx) } } @@ -86,14 +87,15 @@ func (m *Mempool) Remove(tx sdk.Tx) (err error) { // then calls each lane’s FillProposal method. If leftover gas is important to you, // you can implement a second pass or distribute leftover to subsequent lanes, etc. func (m *Mempool) PrepareProposal(ctx sdk.Context, proposal Proposal) (Proposal, error) { + cacheCtx, _ := ctx.CacheContext() // 1) Perform the initial fill of proposals - laneIterators, txsToRemove, totalSize, totalGas := m.fillInitialProposals(ctx, &proposal) + laneIterators, txsToRemove, totalSize, totalGas := m.fillInitialProposals(cacheCtx, &proposal) // 2) Calculate the remaining block space remainderLimit := NewBlockSpace(proposal.Info.MaxBlockSize-totalSize, proposal.Info.MaxGasLimit-totalGas) // 3) Fill proposals with leftover space - m.fillRemainderProposals(ctx, &proposal, laneIterators, txsToRemove, remainderLimit) + m.fillRemainderProposals(&proposal, laneIterators, txsToRemove, remainderLimit) // 4) Remove the transactions that were invalidated from each lane m.removeTxsFromLanes(txsToRemove) @@ -138,7 +140,6 @@ func (m *Mempool) fillInitialProposals( // fillRemainderProposals performs an additional fill on each lane using the leftover // BlockSpace. It updates txsToRemove to include any newly removed transactions. func (m *Mempool) fillRemainderProposals( - ctx sdk.Context, proposal *Proposal, laneIterators []sdkmempool.Iterator, txsToRemove [][]sdk.Tx, @@ -146,7 +147,6 @@ func (m *Mempool) fillRemainderProposals( ) { for i, lane := range m.lanes { sizeUsed, gasUsed, removedTxs := lane.FillProposalBy( - ctx, proposal, laneIterators[i], remainderLimit, diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index de49b6525..7e6b10d38 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -7,15 +7,15 @@ import ( signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" "github.com/stretchr/testify/suite" + tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/gogoproto/proto" + "cosmossdk.io/log" "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/tx/signing" - tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types" - - "github.com/cosmos/gogoproto/proto" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/address" @@ -190,7 +190,7 @@ func (s *MempoolTestSuite) newMempool() *Mempool { ) } -func isBankSendTx(tx sdk.Tx) bool { +func isBankSendTx(_ sdk.Context, tx sdk.Tx) bool { msgs := tx.GetMsgs() if len(msgs) == 0 { return false @@ -203,7 +203,7 @@ func isBankSendTx(tx sdk.Tx) bool { return true } -func isDelegateTx(tx sdk.Tx) bool { +func isDelegateTx(_ sdk.Context, tx sdk.Tx) bool { msgs := tx.GetMsgs() if len(msgs) == 0 { return false @@ -216,9 +216,9 @@ func isDelegateTx(tx sdk.Tx) bool { return true } -func isOtherTx(tx sdk.Tx) bool { +func isOtherTx(_ sdk.Context, tx sdk.Tx) bool { // fallback if not pure bank send nor pure delegate - return !isBankSendTx(tx) && !isDelegateTx(tx) + return true } // ----------------------------------------------------------------------------- diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index 32ef54a77..b7103ddd4 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -3,10 +3,11 @@ package mempool import ( "fmt" + comettypes "github.com/cometbft/cometbft/types" + "cosmossdk.io/log" "cosmossdk.io/math" - comettypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -66,6 +67,7 @@ func (p *Proposal) Contains(txHash string) bool { // Add attempts to add a transaction to the proposal, respecting size/gas limits. func (p *Proposal) Add(txInfo TxWithInfo) error { + fmt.Println("try add tx to proposal", txInfo.Hash) if p.Contains(txInfo.Hash) { return fmt.Errorf("transaction already in proposal: %s", txInfo.Hash) } diff --git a/x/globalfee/feechecker/feechecker.go b/x/globalfee/feechecker/feechecker.go index c4766fd62..32a303437 100644 --- a/x/globalfee/feechecker/feechecker.go +++ b/x/globalfee/feechecker/feechecker.go @@ -1,14 +1,11 @@ package feechecker import ( - "math" - sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/x/authz" authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" @@ -17,7 +14,6 @@ import ( feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" "github.com/bandprotocol/chain/v3/x/globalfee/keeper" oraclekeeper "github.com/bandprotocol/chain/v3/x/oracle/keeper" - oracletypes "github.com/bandprotocol/chain/v3/x/oracle/types" tsskeeper "github.com/bandprotocol/chain/v3/x/tss/keeper" tsstypes "github.com/bandprotocol/chain/v3/x/tss/types" ) @@ -89,11 +85,6 @@ func (fc FeeChecker) CheckTxFee( return feeCoins, priority, nil } - // Check if this tx should be free or not - if fc.IsBypassMinFeeTx(ctx, tx) { - return sdk.Coins{}, int64(math.MaxInt64), nil - } - minGasPrices := getMinGasPrices(ctx) globalMinGasPrices, err := fc.GetGlobalMinGasPrices(ctx) if err != nil { @@ -125,106 +116,6 @@ func (fc FeeChecker) CheckTxFee( return feeCoins, priority, nil } -// IsBypassMinFeeTx checks whether tx is min fee bypassable. -func (fc FeeChecker) IsBypassMinFeeTx(ctx sdk.Context, tx sdk.Tx) bool { - newCtx, _ := ctx.CacheContext() - - // Check if all messages are free - for _, msg := range tx.GetMsgs() { - if !fc.IsBypassMinFeeMsg(newCtx, msg) { - return false - } - } - - return true -} - -// IsBypassMinFeeMsg checks whether msg is min fee bypassable. -func (fc FeeChecker) IsBypassMinFeeMsg(ctx sdk.Context, msg sdk.Msg) bool { - switch msg := msg.(type) { - case *oracletypes.MsgReportData: - if err := checkValidMsgReport(ctx, fc.OracleKeeper, msg); err != nil { - return false - } - case *feedstypes.MsgSubmitSignalPrices: - if _, err := fc.FeedsMsgServer.SubmitSignalPrices(ctx, msg); err != nil { - return false - } - case *tsstypes.MsgSubmitDKGRound1: - if _, err := fc.TSSMsgServer.SubmitDKGRound1(ctx, msg); err != nil { - return false - } - case *tsstypes.MsgSubmitDKGRound2: - if _, err := fc.TSSMsgServer.SubmitDKGRound2(ctx, msg); err != nil { - return false - } - case *tsstypes.MsgConfirm: - if _, err := fc.TSSMsgServer.Confirm(ctx, msg); err != nil { - return false - } - case *tsstypes.MsgComplain: - if _, err := fc.TSSMsgServer.Complain(ctx, msg); err != nil { - return false - } - case *tsstypes.MsgSubmitDEs: - acc, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return false - } - - currentGroupID := fc.BandtssKeeper.GetCurrentGroup(ctx).GroupID - incomingGroupID := fc.BandtssKeeper.GetIncomingGroupID(ctx) - if !fc.BandtssKeeper.HasMember(ctx, acc, currentGroupID) && - !fc.BandtssKeeper.HasMember(ctx, acc, incomingGroupID) { - return false - } - - if _, err := fc.TSSMsgServer.SubmitDEs(ctx, msg); err != nil { - return false - } - case *tsstypes.MsgSubmitSignature: - if _, err := fc.TSSMsgServer.SubmitSignature(ctx, msg); err != nil { - return false - } - case *authz.MsgExec: - msgs, err := msg.GetMessages() - if err != nil { - return false - } - - grantee, err := sdk.AccAddressFromBech32(msg.Grantee) - if err != nil { - return false - } - - for _, m := range msgs { - signers, _, err := fc.cdc.GetMsgV1Signers(m) - if err != nil { - return false - } - // Check if this grantee have authorization for the message. - cap, _ := fc.AuthzKeeper.GetAuthorization( - ctx, - grantee, - sdk.AccAddress(signers[0]), - sdk.MsgTypeURL(m), - ) - if cap == nil { - return false - } - - // Check if this message should be free or not. - if !fc.IsBypassMinFeeMsg(ctx, m) { - return false - } - } - default: - return false - } - - return true -} - // GetGlobalMinGasPrices returns global min gas prices func (fc FeeChecker) GetGlobalMinGasPrices(ctx sdk.Context) (sdk.DecCoins, error) { globalMinGasPrices := fc.GlobalfeeKeeper.GetParams(ctx).MinimumGasPrices diff --git a/x/globalfee/feechecker/feechecker_test.go b/x/globalfee/feechecker/feechecker_test.go index 455c7be2c..cf2f61256 100644 --- a/x/globalfee/feechecker/feechecker_test.go +++ b/x/globalfee/feechecker/feechecker_test.go @@ -1,7 +1,6 @@ package feechecker_test import ( - "math" "testing" "time" @@ -16,10 +15,8 @@ import ( "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/authz" - "github.com/bandprotocol/chain/v3/pkg/tss" bandtesting "github.com/bandprotocol/chain/v3/testing" bandtsstypes "github.com/bandprotocol/chain/v3/x/bandtss/types" feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" @@ -150,643 +147,6 @@ func (suite *FeeCheckerTestSuite) SetupTest() { ) } -func (suite *FeeCheckerTestSuite) TestValidRawReport() { - msgs := []sdk.Msg{ - oracletypes.NewMsgReportData(suite.requestID, []oracletypes.RawReport{}, bandtesting.Validators[0].ValAddress), - } - stubTx := &StubTx{Msgs: msgs} - - // test - check report tx - isReportTx := suite.FeeChecker.IsBypassMinFeeMsg(suite.ctx, msgs[0]) - suite.Require().True(isReportTx) - - // test - check tx fee - fee, priority, err := suite.FeeChecker.CheckTxFee(suite.ctx, stubTx) - suite.Require().NoError(err) - suite.Require().Equal(sdk.Coins{}, fee) - suite.Require().Equal(int64(math.MaxInt64), priority) -} - -func (suite *FeeCheckerTestSuite) TestNotValidRawReport() { - msgs := []sdk.Msg{oracletypes.NewMsgReportData(1, []oracletypes.RawReport{}, bandtesting.Alice.ValAddress)} - stubTx := &StubTx{Msgs: msgs} - - // test - check report tx - isReportTx := suite.FeeChecker.IsBypassMinFeeMsg(suite.ctx, msgs[0]) - suite.Require().False(isReportTx) - - // test - check tx fee - _, _, err := suite.FeeChecker.CheckTxFee(suite.ctx, stubTx) - suite.Require().Error(err) -} - -func (suite *FeeCheckerTestSuite) TestValidReport() { - reportMsgs := []sdk.Msg{ - oracletypes.NewMsgReportData(suite.requestID, []oracletypes.RawReport{}, bandtesting.Validators[0].ValAddress), - } - authzMsg := authz.NewMsgExec(bandtesting.Alice.Address, reportMsgs) - stubTx := &StubTx{Msgs: []sdk.Msg{&authzMsg}} - - // test - check bypass min fee - isBypassMinFeeMsg := suite.FeeChecker.IsBypassMinFeeMsg(suite.ctx, reportMsgs[0]) - suite.Require().True(isBypassMinFeeMsg) - - // test - check tx fee - fee, priority, err := suite.FeeChecker.CheckTxFee(suite.ctx, stubTx) - suite.Require().NoError(err) - suite.Require().Equal(sdk.Coins{}, fee) - suite.Require().Equal(int64(math.MaxInt64), priority) -} - -func (suite *FeeCheckerTestSuite) TestNoAuthzReport() { - reportMsgs := []sdk.Msg{ - oracletypes.NewMsgReportData(suite.requestID, []oracletypes.RawReport{}, bandtesting.Validators[0].ValAddress), - } - authzMsg := authz.NewMsgExec(bandtesting.Bob.Address, reportMsgs) - stubTx := &StubTx{ - Msgs: []sdk.Msg{&authzMsg}, - GasPrices: sdk.NewDecCoins(sdk.NewDecCoin("uband", sdkmath.NewInt(1))), - } - - // test - check bypass min fee - isBypassMinFeeMsg := suite.FeeChecker.IsBypassMinFeeMsg(suite.ctx, &authzMsg) - suite.Require().False(isBypassMinFeeMsg) - - // test - check tx fee - _, _, err := suite.FeeChecker.CheckTxFee(suite.ctx, stubTx) - suite.Require().NoError(err) -} - -func (suite *FeeCheckerTestSuite) TestNotValidReport() { - reportMsgs := []sdk.Msg{ - oracletypes.NewMsgReportData( - suite.requestID+1, - []oracletypes.RawReport{}, - bandtesting.Validators[0].ValAddress, - ), - } - authzMsg := authz.NewMsgExec(bandtesting.Alice.Address, reportMsgs) - stubTx := &StubTx{Msgs: []sdk.Msg{&authzMsg}} - - // test - check bypass min fee - isBypassMinFeeMsg := suite.FeeChecker.IsBypassMinFeeMsg(suite.ctx, &authzMsg) - suite.Require().False(isBypassMinFeeMsg) - - // test - check tx fee - _, _, err := suite.FeeChecker.CheckTxFee(suite.ctx, stubTx) - suite.Require().Error(err) -} - -func (suite *FeeCheckerTestSuite) TestNotReportMsg() { - requestMsg := oracletypes.NewMsgRequestData( - 1, - BasicCalldata, - 1, - 1, - BasicClientID, - bandtesting.Coins100000000uband, - bandtesting.TestDefaultPrepareGas, - bandtesting.TestDefaultExecuteGas, - bandtesting.FeePayer.Address, - 0, - ) - stubTx := &StubTx{ - Msgs: []sdk.Msg{requestMsg}, - GasPrices: sdk.NewDecCoins( - sdk.NewDecCoinFromDec("uaaaa", sdkmath.LegacyNewDecWithPrec(100, 3)), - sdk.NewDecCoinFromDec("uaaab", sdkmath.LegacyNewDecWithPrec(1, 3)), - sdk.NewDecCoinFromDec("uaaac", sdkmath.LegacyNewDecWithPrec(0, 3)), - sdk.NewDecCoinFromDec("uband", sdkmath.LegacyNewDecWithPrec(3, 3)), - sdk.NewDecCoinFromDec("uccca", sdkmath.LegacyNewDecWithPrec(0, 3)), - sdk.NewDecCoinFromDec("ucccb", sdkmath.LegacyNewDecWithPrec(1, 3)), - sdk.NewDecCoinFromDec("ucccc", sdkmath.LegacyNewDecWithPrec(100, 3)), - ), - } - - // test - check bypass min fee - isBypassMinFeeMsg := suite.FeeChecker.IsBypassMinFeeMsg(suite.ctx, requestMsg) - suite.Require().False(isBypassMinFeeMsg) - - // test - check tx fee - fee, priority, err := suite.FeeChecker.CheckTxFee(suite.ctx, stubTx) - suite.Require().NoError(err) - suite.Require().Equal(stubTx.GetFee(), fee) - suite.Require().Equal(int64(30), priority) -} - -func (suite *FeeCheckerTestSuite) TestReportMsgAndOthersTypeMsgInTheSameAuthzMsgs() { - reportMsg := oracletypes.NewMsgReportData( - suite.requestID, - []oracletypes.RawReport{}, - bandtesting.Validators[0].ValAddress, - ) - requestMsg := oracletypes.NewMsgRequestData( - 1, - BasicCalldata, - 1, - 1, - BasicClientID, - bandtesting.Coins100000000uband, - bandtesting.TestDefaultPrepareGas, - bandtesting.TestDefaultExecuteGas, - bandtesting.FeePayer.Address, - 0, - ) - msgs := []sdk.Msg{reportMsg, requestMsg} - authzMsg := authz.NewMsgExec(bandtesting.Alice.Address, msgs) - stubTx := &StubTx{ - Msgs: []sdk.Msg{&authzMsg}, - GasPrices: sdk.NewDecCoins(sdk.NewDecCoin("uband", sdkmath.NewInt(1))), - } - - // test - check bypass min fee - isBypassMinFeeMsg := suite.FeeChecker.IsBypassMinFeeMsg(suite.ctx, &authzMsg) - suite.Require().False(isBypassMinFeeMsg) - - // test - check tx fee - fee, priority, err := suite.FeeChecker.CheckTxFee(suite.ctx, stubTx) - suite.Require().NoError(err) - suite.Require().Equal(stubTx.GetFee(), fee) - suite.Require().Equal(int64(10000), priority) -} - -func (suite *FeeCheckerTestSuite) TestReportMsgAndOthersTypeMsgInTheSameTx() { - reportMsg := oracletypes.NewMsgReportData( - suite.requestID, - []oracletypes.RawReport{}, - bandtesting.Validators[0].ValAddress, - ) - requestMsg := oracletypes.NewMsgRequestData( - 1, - BasicCalldata, - 1, - 1, - BasicClientID, - bandtesting.Coins100000000uband, - bandtesting.TestDefaultPrepareGas, - bandtesting.TestDefaultExecuteGas, - bandtesting.FeePayer.Address, - 0, - ) - stubTx := &StubTx{ - Msgs: []sdk.Msg{reportMsg, requestMsg}, - GasPrices: sdk.NewDecCoins(sdk.NewDecCoin("uband", sdkmath.NewInt(1))), - } - - // test - check bypass min fee - isBypassMinFeeMsg := suite.FeeChecker.IsBypassMinFeeMsg(suite.ctx, requestMsg) - suite.Require().False(isBypassMinFeeMsg) - - // test - check tx fee - fee, priority, err := suite.FeeChecker.CheckTxFee(suite.ctx, stubTx) - suite.Require().NoError(err) - suite.Require().Equal(stubTx.GetFee(), fee) - suite.Require().Equal(int64(10000), priority) -} - -func (suite *FeeCheckerTestSuite) TestIsBypassMinFeeTxAndCheckTxFee() { - testCases := []struct { - name string - stubTx func() *StubTx - expIsBypassMinFeeTx bool - expErr error - expFee sdk.Coins - expPriority int64 - }{ - { - name: "valid MsgReportData", - stubTx: func() *StubTx { - return &StubTx{ - Msgs: []sdk.Msg{ - oracletypes.NewMsgReportData( - suite.requestID, - []oracletypes.RawReport{}, - bandtesting.Validators[0].ValAddress, - ), - }, - } - }, - expIsBypassMinFeeTx: true, - expErr: nil, - expFee: sdk.Coins{}, - expPriority: math.MaxInt64, - }, - { - name: "valid MsgSubmitDEs", - stubTx: func() *StubTx { - privD, _ := tss.GenerateSigningNonce([]byte{}) - privE, _ := tss.GenerateSigningNonce([]byte{}) - - return &StubTx{ - Msgs: []sdk.Msg{ - &tsstypes.MsgSubmitDEs{ - DEs: []tsstypes.DE{ - { - PubD: privD.Point(), - PubE: privE.Point(), - }, - }, - Sender: bandtesting.Validators[0].Address.String(), - }, - }, - } - }, - expIsBypassMinFeeTx: true, - expErr: nil, - expFee: sdk.Coins{}, - expPriority: math.MaxInt64, - }, - { - name: "invalid MsgSubmitDEs", - stubTx: func() *StubTx { - return &StubTx{ - Msgs: []sdk.Msg{ - &tsstypes.MsgSubmitDEs{ - DEs: []tsstypes.DE{ - { - PubD: nil, - PubE: nil, - }, - }, - Sender: "wrong address", - }, - }, - } - }, - expIsBypassMinFeeTx: false, - expErr: sdkerrors.ErrInsufficientFee, - expFee: nil, - expPriority: 0, - }, - { - name: "valid MsgSubmitDEs in valid MsgExec", - stubTx: func() *StubTx { - privD, _ := tss.GenerateSigningNonce([]byte{}) - privE, _ := tss.GenerateSigningNonce([]byte{}) - - msgExec := authz.NewMsgExec(bandtesting.Alice.Address, []sdk.Msg{ - &tsstypes.MsgSubmitDEs{ - DEs: []tsstypes.DE{ - { - PubD: privD.Point(), - PubE: privE.Point(), - }, - }, - Sender: bandtesting.Validators[0].Address.String(), - }, - }) - - return &StubTx{ - Msgs: []sdk.Msg{ - &msgExec, - }, - } - }, - expIsBypassMinFeeTx: true, - expErr: nil, - expFee: sdk.Coins{}, - expPriority: math.MaxInt64, - }, - { - name: "valid MsgSubmitDEs in invalid MsgExec", - stubTx: func() *StubTx { - privD, _ := tss.GenerateSigningNonce([]byte{}) - privE, _ := tss.GenerateSigningNonce([]byte{}) - - msgExec := authz.NewMsgExec(bandtesting.Bob.Address, []sdk.Msg{ - &tsstypes.MsgSubmitDEs{ - DEs: []tsstypes.DE{ - { - PubD: privD.Point(), - PubE: privE.Point(), - }, - }, - Sender: bandtesting.Validators[0].Address.String(), - }, - }) - - return &StubTx{ - Msgs: []sdk.Msg{ - &msgExec, - }, - } - }, - expIsBypassMinFeeTx: false, - expErr: sdkerrors.ErrInsufficientFee, - expFee: nil, - expPriority: 0, - }, - { - name: "valid MsgReportData in valid MsgExec", - stubTx: func() *StubTx { - msgExec := authz.NewMsgExec(bandtesting.Alice.Address, []sdk.Msg{ - oracletypes.NewMsgReportData( - suite.requestID, - []oracletypes.RawReport{}, - bandtesting.Validators[0].ValAddress, - ), - }) - - return &StubTx{ - Msgs: []sdk.Msg{ - &msgExec, - }, - } - }, - expIsBypassMinFeeTx: true, - expErr: nil, - expFee: sdk.Coins{}, - expPriority: math.MaxInt64, - }, - { - name: "invalid MsgReportData with not enough fee", - stubTx: func() *StubTx { - return &StubTx{ - Msgs: []sdk.Msg{ - oracletypes.NewMsgReportData(1, []oracletypes.RawReport{}, bandtesting.Alice.ValAddress), - }, - } - }, - expIsBypassMinFeeTx: false, - expErr: sdkerrors.ErrInsufficientFee, - expFee: nil, - expPriority: 0, - }, - { - name: "invalid MsgReportData in valid MsgExec with not enough fee", - stubTx: func() *StubTx { - msgExec := authz.NewMsgExec(bandtesting.Alice.Address, []sdk.Msg{ - oracletypes.NewMsgReportData( - suite.requestID+1, - []oracletypes.RawReport{}, - bandtesting.Validators[0].ValAddress, - ), - }) - - return &StubTx{ - Msgs: []sdk.Msg{ - &msgExec, - }, - } - }, - expIsBypassMinFeeTx: false, - expErr: sdkerrors.ErrInsufficientFee, - expFee: nil, - expPriority: 0, - }, - { - name: "valid MsgReportData in invalid MsgExec with enough fee", - stubTx: func() *StubTx { - msgExec := authz.NewMsgExec(bandtesting.Bob.Address, []sdk.Msg{ - oracletypes.NewMsgReportData( - suite.requestID, - []oracletypes.RawReport{}, - bandtesting.Validators[0].ValAddress, - ), - }) - - return &StubTx{ - Msgs: []sdk.Msg{ - &msgExec, - }, - GasPrices: sdk.NewDecCoins(sdk.NewDecCoin("uband", sdkmath.NewInt(1))), - } - }, - expIsBypassMinFeeTx: false, - expErr: nil, - expFee: sdk.NewCoins(sdk.NewCoin("uband", sdkmath.NewInt(1000000))), - expPriority: 10000, - }, - { - name: "valid MsgRequestData", - stubTx: func() *StubTx { - msgRequestData := oracletypes.NewMsgRequestData( - 1, - BasicCalldata, - 1, - 1, - BasicClientID, - bandtesting.Coins100000000uband, - bandtesting.TestDefaultPrepareGas, - bandtesting.TestDefaultExecuteGas, - bandtesting.FeePayer.Address, - 0, - ) - - return &StubTx{ - Msgs: []sdk.Msg{msgRequestData}, - GasPrices: sdk.NewDecCoins( - sdk.NewDecCoinFromDec("uaaaa", sdkmath.LegacyNewDecWithPrec(100, 3)), - sdk.NewDecCoinFromDec("uaaab", sdkmath.LegacyNewDecWithPrec(1, 3)), - sdk.NewDecCoinFromDec("uaaac", sdkmath.LegacyNewDecWithPrec(0, 3)), - sdk.NewDecCoinFromDec("uband", sdkmath.LegacyNewDecWithPrec(3, 3)), - sdk.NewDecCoinFromDec("uccca", sdkmath.LegacyNewDecWithPrec(0, 3)), - sdk.NewDecCoinFromDec("ucccb", sdkmath.LegacyNewDecWithPrec(1, 3)), - sdk.NewDecCoinFromDec("ucccc", sdkmath.LegacyNewDecWithPrec(100, 3)), - ), - } - }, - expIsBypassMinFeeTx: false, - expErr: nil, - expFee: sdk.NewCoins( - sdk.NewCoin("uaaaa", sdkmath.NewInt(100000)), - sdk.NewCoin("uaaab", sdkmath.NewInt(1000)), - sdk.NewCoin("uband", sdkmath.NewInt(3000)), - sdk.NewCoin("ucccb", sdkmath.NewInt(1000)), - sdk.NewCoin("ucccc", sdkmath.NewInt(100000)), - ), - expPriority: 30, - }, - { - name: "valid MsgRequestData and valid MsgReport in valid MsgExec with enough fee", - stubTx: func() *StubTx { - msgReportData := oracletypes.NewMsgReportData( - suite.requestID, - []oracletypes.RawReport{}, - bandtesting.Validators[0].ValAddress, - ) - msgRequestData := oracletypes.NewMsgRequestData( - 1, - BasicCalldata, - 1, - 1, - BasicClientID, - bandtesting.Coins100000000uband, - bandtesting.TestDefaultPrepareGas, - bandtesting.TestDefaultExecuteGas, - bandtesting.FeePayer.Address, - 0, - ) - msgs := []sdk.Msg{msgReportData, msgRequestData} - authzMsg := authz.NewMsgExec(bandtesting.Alice.Address, msgs) - - return &StubTx{ - Msgs: []sdk.Msg{&authzMsg}, - GasPrices: sdk.NewDecCoins(sdk.NewDecCoin("uband", sdkmath.NewInt(1))), - } - }, - expIsBypassMinFeeTx: false, - expErr: nil, - expFee: sdk.NewCoins( - sdk.NewCoin("uband", sdkmath.NewInt(1000000)), - ), - expPriority: 10000, - }, - { - name: "valid MsgRequestData and valid MsgReport with enough fee", - stubTx: func() *StubTx { - msgReportData := oracletypes.NewMsgReportData( - suite.requestID, - []oracletypes.RawReport{}, - bandtesting.Validators[0].ValAddress, - ) - msgRequestData := oracletypes.NewMsgRequestData( - 1, - BasicCalldata, - 1, - 1, - BasicClientID, - bandtesting.Coins100000000uband, - bandtesting.TestDefaultPrepareGas, - bandtesting.TestDefaultExecuteGas, - bandtesting.FeePayer.Address, - 0, - ) - - return &StubTx{ - Msgs: []sdk.Msg{msgReportData, msgRequestData}, - GasPrices: sdk.NewDecCoins(sdk.NewDecCoin("uband", sdkmath.NewInt(1))), - } - }, - expIsBypassMinFeeTx: false, - expErr: nil, - expFee: sdk.NewCoins( - sdk.NewCoin("uband", sdkmath.NewInt(1000000)), - ), - expPriority: 10000, - }, - { - name: "valid MsgSubmitSignalPrices", - stubTx: func() *StubTx { - return &StubTx{ - Msgs: []sdk.Msg{ - feedstypes.NewMsgSubmitSignalPrices( - bandtesting.Validators[0].ValAddress.String(), - suite.ctx.BlockTime().Unix(), - []feedstypes.SignalPrice{}, - ), - }, - } - }, - expIsBypassMinFeeTx: true, - expErr: nil, - expFee: sdk.Coins{}, - expPriority: math.MaxInt64, - }, - { - name: "valid MsgSubmitSignalPrices in valid MsgExec", - stubTx: func() *StubTx { - msgExec := authz.NewMsgExec(bandtesting.Alice.Address, []sdk.Msg{ - feedstypes.NewMsgSubmitSignalPrices( - bandtesting.Validators[0].ValAddress.String(), - suite.ctx.BlockTime().Unix(), - []feedstypes.SignalPrice{}, - ), - }) - - return &StubTx{ - Msgs: []sdk.Msg{ - &msgExec, - }, - } - }, - expIsBypassMinFeeTx: true, - expErr: nil, - expFee: sdk.Coins{}, - expPriority: math.MaxInt64, - }, - { - name: "invalid MsgSubmitSignalPrices with not enough fee", - stubTx: func() *StubTx { - return &StubTx{ - Msgs: []sdk.Msg{ - feedstypes.NewMsgSubmitSignalPrices( - bandtesting.Alice.ValAddress.String(), - suite.ctx.BlockTime().Unix(), - []feedstypes.SignalPrice{}, - ), - }, - } - }, - expIsBypassMinFeeTx: false, - expErr: sdkerrors.ErrInsufficientFee, - expFee: nil, - expPriority: 0, - }, - { - name: "invalid MsgSubmitSignalPrices in valid MsgExec with not enough fee", - stubTx: func() *StubTx { - msgExec := authz.NewMsgExec(bandtesting.Alice.Address, []sdk.Msg{ - feedstypes.NewMsgSubmitSignalPrices( - bandtesting.Alice.ValAddress.String(), - suite.ctx.BlockTime().Unix(), - []feedstypes.SignalPrice{}, - ), - }) - - return &StubTx{ - Msgs: []sdk.Msg{ - &msgExec, - }, - } - }, - expIsBypassMinFeeTx: false, - expErr: sdkerrors.ErrInsufficientFee, - expFee: nil, - expPriority: 0, - }, - { - name: "valid MsgSubmitSignalPrices in invalid MsgExec with enough fee", - stubTx: func() *StubTx { - msgExec := authz.NewMsgExec(bandtesting.Bob.Address, []sdk.Msg{ - feedstypes.NewMsgSubmitSignalPrices( - bandtesting.Validators[0].ValAddress.String(), - suite.ctx.BlockTime().Unix(), - []feedstypes.SignalPrice{}, - ), - }) - - return &StubTx{ - Msgs: []sdk.Msg{ - &msgExec, - }, - GasPrices: sdk.NewDecCoins(sdk.NewDecCoin("uband", sdkmath.NewInt(1))), - } - }, - expIsBypassMinFeeTx: false, - expErr: nil, - expFee: sdk.NewCoins(sdk.NewCoin("uband", sdkmath.NewInt(1000000))), - expPriority: 10000, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - stubTx := tc.stubTx() - - // test - IsByPassMinFeeTx - isByPassMinFeeTx := suite.FeeChecker.IsBypassMinFeeTx(suite.ctx, stubTx) - suite.Require().Equal(tc.expIsBypassMinFeeTx, isByPassMinFeeTx) - - // test - CheckTxFee - fee, priority, err := suite.FeeChecker.CheckTxFee(suite.ctx, stubTx) - suite.Require().ErrorIs(err, tc.expErr) - suite.Require().Equal(fee, tc.expFee) - suite.Require().Equal(tc.expPriority, priority) - }) - } -} - func (suite *FeeCheckerTestSuite) TestDefaultZeroGlobalFee() { coins, err := suite.FeeChecker.DefaultZeroGlobalFee(suite.ctx) From 70676de2808068c793aeff6c518a5701d67fcfa0 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 20 Feb 2025 16:19:39 +0700 Subject: [PATCH 07/53] use blockspace in all mempool package --- app/mempool/block_space.go | 101 ++++++++++++++++++++++++++++---- app/mempool/lane.go | 31 +++++----- app/mempool/lane_test.go | 14 ++--- app/mempool/mempool.go | 24 ++++---- app/mempool/proposal.go | 85 ++++++--------------------- app/mempool/proposal_handler.go | 6 +- app/mempool/types.go | 6 +- 7 files changed, 142 insertions(+), 125 deletions(-) diff --git a/app/mempool/block_space.go b/app/mempool/block_space.go index 14d4d4e05..8fe029452 100644 --- a/app/mempool/block_space.go +++ b/app/mempool/block_space.go @@ -1,5 +1,11 @@ package mempool +import ( + "fmt" + + "cosmossdk.io/math" +) + // BlockSpace defines the block space. type BlockSpace struct { txBytes int64 @@ -14,21 +20,94 @@ func NewBlockSpace(txBytes int64, gas uint64) BlockSpace { } } -// IsReached returns true if the block space is reached. -func (bs BlockSpace) IsReached(size int64, gas uint64) bool { - return size >= bs.txBytes || gas >= bs.gas +// --- Getters --- +func (bs BlockSpace) TxBytes() int64 { + return bs.txBytes +} + +func (bs BlockSpace) Gas() uint64 { + return bs.gas +} + +// --- Comparison Methods --- + +// IsReachedBy returns true if 'other' usage has reached this BlockSpace's limits. +func (bs BlockSpace) IsReachedBy(other BlockSpace) bool { + return other.txBytes >= bs.txBytes || other.gas >= bs.gas +} + +// IsExceededBy returns true if 'other' usage has exceeded this BlockSpace's limits. +func (bs BlockSpace) IsExceededBy(other BlockSpace) bool { + return other.txBytes > bs.txBytes || other.gas > bs.gas } -// IsExceeded returns true if the block space is exceeded. -func (bs BlockSpace) IsExceeded(size int64, gas uint64) bool { - return size > bs.txBytes || gas > bs.gas +// --- Math Methods --- + +// IncreaseBy increases this BlockSpace by another BlockSpace's size/gas. +func (bs *BlockSpace) IncreaseBy(other BlockSpace) { + bs.txBytes += other.txBytes + bs.gas += other.gas } -// Decrease decreases the block space. -func (bs *BlockSpace) DecreaseBy(size int64, gas uint64) { - bs.txBytes -= size - if bs.txBytes < 0 { +// DecreaseBy decreases this BlockSpace by another BlockSpace's size/gas. +// Ensures txBytes and gas never go below zero. +func (bs *BlockSpace) DecreaseBy(other BlockSpace) { + // Decrease txBytes + if other.txBytes > bs.txBytes { bs.txBytes = 0 + } else { + bs.txBytes -= other.txBytes + } + + // Decrease gas + if other.gas > bs.gas { + bs.gas = 0 + } else { + bs.gas -= other.gas + } +} + +// Sub returns the difference between this BlockSpace and another BlockSpace. +// Ensures txBytes and gas never go below zero. +func (bs BlockSpace) Sub(other BlockSpace) BlockSpace { + // Calculate txBytes + txBytes := bs.txBytes - other.txBytes + if txBytes < 0 { + txBytes = 0 + } + + // Calculate gas + if other.gas > bs.gas { + return BlockSpace{ + txBytes: txBytes, + gas: 0, + } + } + gas := bs.gas - other.gas + + return BlockSpace{ + txBytes: txBytes, + gas: gas, + } +} + +// Add returns the sum of this BlockSpace and another BlockSpace. +func (bs BlockSpace) Add(other BlockSpace) BlockSpace { + return BlockSpace{ + txBytes: bs.txBytes + other.txBytes, + gas: bs.gas + other.gas, + } +} + +// MulDec returns a new BlockSpace with txBytes and gas multiplied by a decimal. +func (bs BlockSpace) MulDec(dec math.LegacyDec) BlockSpace { + return BlockSpace{ + txBytes: dec.MulInt64(bs.txBytes).TruncateInt().Int64(), + gas: dec.MulInt(math.NewIntFromUint64(bs.gas)).TruncateInt().Uint64(), } - bs.gas -= gas +} + +// --- Stringer --- +func (bs BlockSpace) String() string { + return fmt.Sprintf("BlockSpace{txBytes: %d, gas: %d}", bs.txBytes, bs.gas) } diff --git a/app/mempool/lane.go b/app/mempool/lane.go index b3b35e980..79061e310 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -72,7 +72,6 @@ func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { } l.txIndex[txInfo.Hash] = struct{}{} - fmt.Println("insert tx to lane", l.Name, txInfo.Hash) return nil } @@ -111,7 +110,7 @@ func (l *Lane) Contains(tx sdk.Tx) bool { func (l *Lane) FillProposal( ctx sdk.Context, proposal *Proposal, -) (sizeUsed int64, gasUsed uint64, iterator sdkmempool.Iterator, txsToRemove []sdk.Tx) { +) (blockUsed BlockSpace, iterator sdkmempool.Iterator, txsToRemove []sdk.Tx) { var ( transactionLimit BlockSpace laneLimit BlockSpace @@ -125,7 +124,7 @@ func (l *Lane) FillProposal( for iterator = l.laneMempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() { // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. // We can tolerate a few bytes/gas over the limit, since we limit the size of each transaction. - if laneLimit.IsReached(sizeUsed, gasUsed) { + if laneLimit.IsReachedBy(blockUsed) { break } @@ -139,7 +138,7 @@ func (l *Lane) FillProposal( } // if the transaction is exceed the limit, we remove it from the lane mempool. - if transactionLimit.IsExceeded(txInfo.Size, txInfo.GasLimit) { + if transactionLimit.IsExceededBy(txInfo.BlockSpace) { l.Logger.Info( "failed to select tx for lane; tx exceeds limit", "tx_hash", txInfo.Hash, @@ -163,9 +162,7 @@ func (l *Lane) FillProposal( break } - // Update the total size and gas. - sizeUsed += txInfo.Size - gasUsed += txInfo.GasLimit + blockUsed.IncreaseBy(txInfo.BlockSpace) } return @@ -175,7 +172,7 @@ func (l *Lane) FillProposalBy( proposal *Proposal, iterator sdkmempool.Iterator, laneLimit BlockSpace, -) (sizeUsed int64, gasUsed uint64, txsToRemove []sdk.Tx) { +) (blockUsed BlockSpace, txsToRemove []sdk.Tx) { // get the transaction limit for the lane. transactionLimit := proposal.GetLimit(l.MaxTransactionSpace) @@ -184,7 +181,7 @@ func (l *Lane) FillProposalBy( for ; iterator != nil; iterator = iterator.Next() { // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. // We can tolerate a few bytes/gas over the limit, since we limit the size of each transaction. - if laneLimit.IsReached(sizeUsed, gasUsed) { + if laneLimit.IsReachedBy(blockUsed) { break } @@ -198,7 +195,7 @@ func (l *Lane) FillProposalBy( } // if the transaction is exceed the limit, we remove it from the lane mempool. - if transactionLimit.IsExceeded(txInfo.Size, txInfo.GasLimit) { + if transactionLimit.IsExceededBy(txInfo.BlockSpace) { l.Logger.Info( "failed to select tx for lane; tx exceeds limit", "tx_hash", txInfo.Hash, @@ -222,8 +219,7 @@ func (l *Lane) FillProposalBy( } // Update the total size and gas. - sizeUsed += txInfo.Size - gasUsed += txInfo.GasLimit + blockUsed.IncreaseBy(txInfo.BlockSpace) } return @@ -249,11 +245,12 @@ func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { return TxWithInfo{}, err } + BlockSpace := NewBlockSpace(int64(len(txBytes)), gasTx.GetGas()) + return TxWithInfo{ - Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), - Size: int64(len(txBytes)), - GasLimit: gasTx.GetGas(), - TxBytes: txBytes, - Signers: signers, + Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), + BlockSpace: BlockSpace, + TxBytes: txBytes, + Signers: signers, }, nil } diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 4834e9d23..fbec34ec5 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -139,12 +139,12 @@ func (s *LaneTestSuite) TestLaneFillProposal() { ) // FillProposal - sizeUsed, gasUsed, iterator, txsToRemove := lane.FillProposal(s.ctx, &proposal) + blockUsed, iterator, txsToRemove := lane.FillProposal(s.ctx, &proposal) // We expect tx1 and tx2 to be included in the proposal. // Then the gas should be over the limit, so tx3 is yet to be considered. - s.Require().Equal(int64(440), sizeUsed) - s.Require().Equal(uint64(40), gasUsed, "20 gas from tx1 and 20 gas from tx2") + s.Require().Equal(int64(440), blockUsed.TxBytes()) + s.Require().Equal(uint64(40), blockUsed.Gas(), "20 gas from tx1 and 20 gas from tx2") s.Require().NotNil(iterator) s.Require(). Len(txsToRemove, 0, "tx3 is yet to be considered") @@ -155,14 +155,14 @@ func (s *LaneTestSuite) TestLaneFillProposal() { s.Require().Equal(expectedIncludedTxs, proposal.Txs) // Calculate the remaining block space - remainderLimit := NewBlockSpace(proposal.Info.MaxBlockSize-sizeUsed, proposal.Info.MaxGasLimit-gasUsed) + remainderLimit := proposal.MaxBlockSpace.Sub(proposal.TotalBlockSpace) // Call FillProposalBy with the remainder limit and iterator from the previous call. - sizeUsed, gasUsed, txsToRemove = lane.FillProposalBy(&proposal, iterator, remainderLimit) + blockUsed, txsToRemove = lane.FillProposalBy(&proposal, iterator, remainderLimit) // We expect tx1, tx2, tx5, tx6, tx7, tx8 to be included in the proposal. - s.Require().Equal(int64(884), sizeUsed) - s.Require().Equal(uint64(60), gasUsed) + s.Require().Equal(int64(884), blockUsed.TxBytes()) + s.Require().Equal(uint64(60), blockUsed.Gas()) s.Require().Equal([]sdk.Tx{tx3, tx4}, txsToRemove) s.Require(). Len(txsToRemove, 2, "tx3 and tx4 are removed") diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index ff91c3ee6..20be11964 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -88,11 +88,12 @@ func (m *Mempool) Remove(tx sdk.Tx) (err error) { // you can implement a second pass or distribute leftover to subsequent lanes, etc. func (m *Mempool) PrepareProposal(ctx sdk.Context, proposal Proposal) (Proposal, error) { cacheCtx, _ := ctx.CacheContext() + // 1) Perform the initial fill of proposals - laneIterators, txsToRemove, totalSize, totalGas := m.fillInitialProposals(cacheCtx, &proposal) + laneIterators, txsToRemove, blockUsed := m.fillInitialProposals(cacheCtx, &proposal) // 2) Calculate the remaining block space - remainderLimit := NewBlockSpace(proposal.Info.MaxBlockSize-totalSize, proposal.Info.MaxGasLimit-totalGas) + remainderLimit := proposal.MaxBlockSpace.Sub(blockUsed) // 3) Fill proposals with leftover space m.fillRemainderProposals(&proposal, laneIterators, txsToRemove, remainderLimit) @@ -114,27 +115,22 @@ func (m *Mempool) fillInitialProposals( ) ( []sdkmempool.Iterator, [][]sdk.Tx, - int64, - uint64, + BlockSpace, ) { - var ( - totalSize int64 - totalGas uint64 - ) + totalBlockUsed := NewBlockSpace(0, 0) laneIterators := make([]sdkmempool.Iterator, len(m.lanes)) txsToRemove := make([][]sdk.Tx, len(m.lanes)) for i, lane := range m.lanes { - sizeUsed, gasUsed, iterator, txs := lane.FillProposal(ctx, proposal) - totalSize += sizeUsed - totalGas += gasUsed + blockUsed, iterator, txs := lane.FillProposal(ctx, proposal) + totalBlockUsed.IncreaseBy(blockUsed) laneIterators[i] = iterator txsToRemove[i] = txs } - return laneIterators, txsToRemove, totalSize, totalGas + return laneIterators, txsToRemove, totalBlockUsed } // fillRemainderProposals performs an additional fill on each lane using the leftover @@ -146,14 +142,14 @@ func (m *Mempool) fillRemainderProposals( remainderLimit BlockSpace, ) { for i, lane := range m.lanes { - sizeUsed, gasUsed, removedTxs := lane.FillProposalBy( + blockUsed, removedTxs := lane.FillProposalBy( proposal, laneIterators[i], remainderLimit, ) // Decrement the remainder for subsequent lanes - remainderLimit.DecreaseBy(sizeUsed, gasUsed) + remainderLimit.DecreaseBy(blockUsed) // Append any newly removed transactions to be removed txsToRemove[i] = append(txsToRemove[i], removedTxs...) diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index b7103ddd4..46814903f 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -14,22 +14,6 @@ import ( // MaxUint64 is the maximum value of a uint64. const MaxUint64 = 1<<64 - 1 -// ProposalInfo contains metadata about how this proposal was constructed. -type ProposalInfo struct { - // TxsByLane is a map counting how many transactions came from each "lane" (optional usage). - TxsByLane map[string]uint64 `json:"txs_by_lane,omitempty"` - - // MaxBlockSize is the upper bound on the block size used to construct this proposal. - MaxBlockSize int64 `json:"max_block_size,omitempty"` - // MaxGasLimit is the upper bound on the total gas used to construct this proposal. - MaxGasLimit uint64 `json:"max_gas_limit,omitempty"` - - // BlockSize is the current total block size of this proposal. - BlockSize int64 `json:"block_size,omitempty"` - // GasLimit is the current total gas of this proposal. - GasLimit uint64 `json:"gas_limit,omitempty"` -} - // Proposal represents a block proposal under construction. type Proposal struct { Logger log.Logger @@ -38,24 +22,20 @@ type Proposal struct { Txs [][]byte // Cache helps quickly check for duplicates by tx hash. Cache map[string]struct{} - // Info contains metadata about the proposal's block usage. - Info ProposalInfo - - currentSize int64 - currentGas uint64 + // MaxBlockSpace is the maximum block space available for this proposal. + MaxBlockSpace BlockSpace + // TotalBlockSpace is the total block space used by the proposal. + TotalBlockSpace BlockSpace } // NewProposal returns a new empty proposal constrained by max block size and max gas limit. func NewProposal(logger log.Logger, maxBlockSize int64, maxGasLimit uint64) Proposal { return Proposal{ - Logger: logger, - Txs: make([][]byte, 0), - Cache: make(map[string]struct{}), - Info: ProposalInfo{ - TxsByLane: make(map[string]uint64), - MaxBlockSize: maxBlockSize, - MaxGasLimit: maxGasLimit, - }, + Logger: logger, + Txs: make([][]byte, 0), + Cache: make(map[string]struct{}), + MaxBlockSpace: NewBlockSpace(maxBlockSize, maxGasLimit), + TotalBlockSpace: NewBlockSpace(0, 0), } } @@ -72,21 +52,13 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { return fmt.Errorf("transaction already in proposal: %s", txInfo.Hash) } - // Check block size limit - if p.currentSize+txInfo.Size > p.Info.MaxBlockSize { - return fmt.Errorf( - "transaction size exceeds max block size: %d > %d", - p.currentSize+txInfo.Size, - p.Info.MaxBlockSize, - ) - } + currentBlockSpace := p.TotalBlockSpace.Add(txInfo.BlockSpace) - // Check block gas limit - if p.currentGas+txInfo.GasLimit > p.Info.MaxGasLimit { + // Check block size limit + if p.MaxBlockSpace.IsExceededBy(currentBlockSpace) { return fmt.Errorf( - "transaction gas limit exceeds max gas limit: %d > %d", - p.currentGas+txInfo.GasLimit, - p.Info.MaxGasLimit, + "transaction space exceeds max block space: %s > %s", + currentBlockSpace.String(), p.MaxBlockSpace.String(), ) } @@ -94,42 +66,19 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { p.Txs = append(p.Txs, txInfo.TxBytes) p.Cache[txInfo.Hash] = struct{}{} - p.Info.BlockSize += txInfo.Size - p.Info.GasLimit += txInfo.GasLimit - - p.currentSize += txInfo.Size - p.currentGas += txInfo.GasLimit + p.TotalBlockSpace = currentBlockSpace return nil } // GetLimit returns the maximum block space available for a given ratio. func (p *Proposal) GetLimit(ratio math.LegacyDec) BlockSpace { - var ( - txBytesLimit int64 - gasLimit uint64 - ) - // In the case where the ratio is zero, we return the max tx bytes remaining. if ratio.IsZero() { - txBytesLimit = p.Info.MaxBlockSize - p.Info.BlockSize - if txBytesLimit < 0 { - txBytesLimit = 0 - } - - // Unsigned subtraction needs an additional check - if p.Info.GasLimit >= p.Info.MaxGasLimit { - gasLimit = 0 - } else { - gasLimit = p.Info.MaxGasLimit - p.Info.GasLimit - } - } else { - // Otherwise, we calculate the max tx bytes / gas limit for the lane based on the ratio. - txBytesLimit = ratio.MulInt64(p.Info.MaxBlockSize).TruncateInt().Int64() - gasLimit = ratio.MulInt(math.NewIntFromUint64(p.Info.MaxGasLimit)).TruncateInt().Uint64() + return p.MaxBlockSpace.Sub(p.TotalBlockSpace) } - return NewBlockSpace(txBytesLimit, gasLimit) + return p.MaxBlockSpace.MulDec(ratio) } // GetBlockLimits retrieves the maximum block size and gas limit from context. diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go index 19ecffb84..0a7294b4a 100644 --- a/app/mempool/proposal_handler.go +++ b/app/mempool/proposal_handler.go @@ -68,10 +68,8 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { h.logger.Info( "prepared proposal", "num_txs", len(finalProposal.Txs), - "total_tx_bytes", finalProposal.Info.BlockSize, - "max_tx_bytes", finalProposal.Info.MaxBlockSize, - "total_gas_limit", finalProposal.Info.GasLimit, - "max_gas_limit", finalProposal.Info.MaxGasLimit, + "total_block_space", finalProposal.TotalBlockSpace.String(), + "max_block_space", finalProposal.MaxBlockSpace.String(), "height", req.Height, ) diff --git a/app/mempool/types.go b/app/mempool/types.go index de8d01604..63b938dfe 100644 --- a/app/mempool/types.go +++ b/app/mempool/types.go @@ -8,10 +8,8 @@ import ( type TxWithInfo struct { // Hash is the hex-encoded hash of the transaction. Hash string - // Size is the size of the transaction in bytes. - Size int64 - // GasLimit is the gas limit of the transaction. - GasLimit uint64 + // BlockSpace is the block space used by the transaction. + BlockSpace BlockSpace // TxBytes is the raw transaction bytes. TxBytes []byte // Signers defines the signers of a transaction. From 6c873b03219638fef02f26be2ad80989d50722da Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 20 Feb 2025 16:20:37 +0700 Subject: [PATCH 08/53] cleanup print --- app/lanes.go | 6 ------ app/mempool/proposal.go | 1 - 2 files changed, 7 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index d2da2586c..ba8dbe192 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -1,8 +1,6 @@ package band import ( - "fmt" - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" "cosmossdk.io/math" @@ -39,7 +37,6 @@ func FeedsLaneMatchHandler( return false } } - fmt.Println("valid feeds message") return true } } @@ -113,7 +110,6 @@ func TssLaneMatchHandler( return false } } - fmt.Println("valid tss message") return true } } @@ -219,7 +215,6 @@ func oracleLaneMatchHandler( return false } } - fmt.Println("valid bank send message") return true } } @@ -279,7 +274,6 @@ func isValidMsgReportData( // DefaultLaneMatchHandler is a function that returns the match function for the default lane. func DefaultLaneMatchHandler() func(sdk.Context, sdk.Tx) bool { return func(_ sdk.Context, _ sdk.Tx) bool { - fmt.Println("valid default message") return true } } diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index 46814903f..a4a9e6422 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -47,7 +47,6 @@ func (p *Proposal) Contains(txHash string) bool { // Add attempts to add a transaction to the proposal, respecting size/gas limits. func (p *Proposal) Add(txInfo TxWithInfo) error { - fmt.Println("try add tx to proposal", txInfo.Hash) if p.Contains(txInfo.Hash) { return fmt.Errorf("transaction already in proposal: %s", txInfo.Hash) } From f81df6c906d5b8b60b6992a50adc42439c410faf Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 20 Feb 2025 16:33:10 +0700 Subject: [PATCH 09/53] use signer adapter from cosmos instead of block-sdk, add comments --- app/lanes.go | 4 +--- app/mempool/lane.go | 18 ++++++++++++------ app/mempool/lane_test.go | 7 +++---- app/mempool/mempool_test.go | 3 +-- app/mempool/types.go | 4 ++-- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index ba8dbe192..7d3abc537 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -1,8 +1,6 @@ package band import ( - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" - "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/codec" @@ -282,7 +280,7 @@ func DefaultLaneMatchHandler() func(sdk.Context, sdk.Tx) bool { func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mempool.Lane) { // 1. Create the signer extractor. This is used to extract the expected signers from // a transaction. Each lane can have a different signer extractor if needed. - signerAdapter := signerextraction.NewDefaultAdapter() + signerAdapter := sdkmempool.NewDefaultSignerExtractionAdapter() feedsMsgServer := feedskeeper.NewMsgServerImpl(app.FeedsKeeper) tssMsgServer := tsskeeper.NewMsgServerImpl(app.TSSKeeper) diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 79061e310..fa18a91f3 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -6,8 +6,6 @@ import ( "fmt" "strings" - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" - comettypes "github.com/cometbft/cometbft/types" "cosmossdk.io/log" @@ -21,7 +19,7 @@ import ( type Lane struct { Logger log.Logger TxEncoder sdk.TxEncoder - SignerExtractor signerextraction.Adapter + SignerExtractor sdkmempool.SignerExtractionAdapter Name string Match func(ctx sdk.Context, tx sdk.Tx) bool @@ -39,7 +37,7 @@ type Lane struct { func NewLane( logger log.Logger, txEncoder sdk.TxEncoder, - signerExtractor signerextraction.Adapter, + signerExtractor sdkmempool.SignerExtractionAdapter, name string, matchFn func(sdk.Context, sdk.Tx) bool, maxTransactionSpace math.LegacyDec, @@ -61,6 +59,7 @@ func NewLane( } } +// Insert inserts a transaction into the lane's mempool. func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { txInfo, err := l.GetTxInfo(tx) if err != nil { @@ -75,10 +74,12 @@ func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { return nil } +// CountTx returns the total number of transactions in the lane's mempool. func (l *Lane) CountTx() int { return l.laneMempool.CountTx() } +// Remove removes a transaction from the lane's mempool. func (l *Lane) Remove(tx sdk.Tx) error { txInfo, err := l.GetTxInfo(tx) if err != nil { @@ -94,6 +95,7 @@ func (l *Lane) Remove(tx sdk.Tx) error { return nil } +// Contains returns true if the lane's mempool contains the transaction. func (l *Lane) Contains(tx sdk.Tx) bool { txInfo, err := l.GetTxInfo(tx) if err != nil { @@ -104,9 +106,10 @@ func (l *Lane) Contains(tx sdk.Tx) bool { return exists } -// FillProposal fills the proposal with transactions from the lane mempool. +// FillProposal fills the proposal with transactions from the lane mempool with the its own limit. // It returns the total size and gas of the transactions added to the proposal. -// If customLaneLimit is provided, it will be used instead of the lane's limit. +// It also returns an iterator to the next transaction in the mempool and a list +// of transactions that were removed from the lane mempool. func (l *Lane) FillProposal( ctx sdk.Context, proposal *Proposal, @@ -168,6 +171,9 @@ func (l *Lane) FillProposal( return } +// FillProposalBy fills the proposal with transactions from the lane mempool with the given iterator and limit. +// It returns the total size and gas of the transactions added to the proposal. +// It also returns a list of transactions that were removed from the lane mempool. func (l *Lane) FillProposalBy( proposal *Proposal, iterator sdkmempool.Iterator, diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index fbec34ec5..bab25e26d 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -4,7 +4,6 @@ import ( "math/rand" "testing" - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" "github.com/stretchr/testify/suite" "cosmossdk.io/log" @@ -58,7 +57,7 @@ func (s *LaneTestSuite) TestLaneInsertAndCount() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - signerextraction.NewDefaultAdapter(), + sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.3"), @@ -82,7 +81,7 @@ func (s *LaneTestSuite) TestLaneRemove() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - signerextraction.NewDefaultAdapter(), + sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.3"), @@ -105,7 +104,7 @@ func (s *LaneTestSuite) TestLaneFillProposal() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - signerextraction.NewDefaultAdapter(), + sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.2"), diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index 7e6b10d38..3e7cc511f 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -4,7 +4,6 @@ import ( "math/rand" "testing" - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" "github.com/stretchr/testify/suite" tmprototypes "github.com/cometbft/cometbft/proto/tendermint/types" @@ -147,7 +146,7 @@ func (s *MempoolTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { // ----------------------------------------------------------------------------- func (s *MempoolTestSuite) newMempool() *Mempool { - signerAdapter := signerextraction.NewDefaultAdapter() + signerAdapter := sdkmempool.NewDefaultSignerExtractionAdapter() BankSendLane := NewLane( log.NewTestLogger(s.T()), diff --git a/app/mempool/types.go b/app/mempool/types.go index 63b938dfe..9bff486ea 100644 --- a/app/mempool/types.go +++ b/app/mempool/types.go @@ -1,7 +1,7 @@ package mempool import ( - signerextraction "github.com/skip-mev/block-sdk/v2/adapters/signer_extraction_adapter" + sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" ) // TxWithInfo holds metadata required for a transaction to be included in a proposal. @@ -13,5 +13,5 @@ type TxWithInfo struct { // TxBytes is the raw transaction bytes. TxBytes []byte // Signers defines the signers of a transaction. - Signers []signerextraction.SignerData + Signers []sdkmempool.SignerData } From e6e1995e4b8dfb576a9257077cc38878b0035c93 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 20 Feb 2025 17:33:05 +0700 Subject: [PATCH 10/53] add comment --- app/mempool/block_space.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/mempool/block_space.go b/app/mempool/block_space.go index 8fe029452..05b6cbbc7 100644 --- a/app/mempool/block_space.go +++ b/app/mempool/block_space.go @@ -108,6 +108,8 @@ func (bs BlockSpace) MulDec(dec math.LegacyDec) BlockSpace { } // --- Stringer --- + +// String returns a string representation of the BlockSpace. func (bs BlockSpace) String() string { return fmt.Sprintf("BlockSpace{txBytes: %d, gas: %d}", bs.txBytes, bs.gas) } From ff957c338408304052a6b79cb276666c38cc8e2b Mon Sep 17 00:00:00 2001 From: colmazia Date: Wed, 26 Feb 2025 14:01:39 +0700 Subject: [PATCH 11/53] use noop process proposal --- app/app.go | 2 +- app/mempool/proposal_handler.go | 46 +++------------------------------ 2 files changed, 5 insertions(+), 43 deletions(-) diff --git a/app/app.go b/app/app.go index 835cd1ba7..e50bf9ca9 100644 --- a/app/app.go +++ b/app/app.go @@ -293,7 +293,7 @@ func NewBandApp( } // proposal handler - proposalHandler := mempool.NewDefaultProposalHandler(app.Logger(), txConfig.TxDecoder(), bandMempool) + proposalHandler := mempool.NewProposalHandler(app.Logger(), txConfig.TxDecoder(), bandMempool) // set the Prepare / ProcessProposal Handlers on the app to be the `LanedMempool`'s app.SetPrepareProposal(proposalHandler.PrepareProposalHandler()) diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go index 0a7294b4a..0e8f8fc7e 100644 --- a/app/mempool/proposal_handler.go +++ b/app/mempool/proposal_handler.go @@ -21,8 +21,8 @@ type ( } ) -// NewDefaultProposalHandler returns a new ABCI++ proposal handler for the Mempool. -func NewDefaultProposalHandler( +// NewProposalHandler returns a new ABCI++ proposal handler for the Mempool. +func NewProposalHandler( logger log.Logger, txDecoder sdk.TxDecoder, mempool *Mempool, @@ -79,45 +79,7 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { } } -// ProcessProposalHandler optionally validates the proposal's transactions prior to consensus acceptance. +// ProcessProposalHandler returns a no-op process proposal handler. func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { - if !h.useCustomProcessProposal { - // By default, do nothing special on ProcessProposal. - return baseapp.NoOpProcessProposal() - } - - return func(ctx sdk.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) { - if req.Height <= 1 { - return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil - } - - defer func() { - if rec := recover(); rec != nil { - h.logger.Error("failed to process proposal", "recover_err", rec) - resp = &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} - err = fmt.Errorf("failed to process proposal: %v", rec) - } - }() - - // Decode the transactions in the proposal. - decodedTxs, err := GetDecodedTxs(h.txDecoder, req.Txs) - if err != nil { - h.logger.Error("failed to decode txs", "err", err) - return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, err - } - - // (Optional) verify each transaction in the proposal. - for _, tx := range decodedTxs { - // Custom verification logic can go here. - h.logger.Info("verified transaction", "tx", tx) - } - - h.logger.Info( - "processed proposal", - "num_txs", len(decodedTxs), - "height", req.Height, - ) - - return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil - } + return baseapp.NoOpProcessProposal() } From 0b1c9f7e787250fc485fd1320e4052e4678d1a5d Mon Sep 17 00:00:00 2001 From: Nattapat Iammelap Date: Tue, 18 Mar 2025 15:10:40 +0700 Subject: [PATCH 12/53] Add separate lane for Oracle requests based on report limit dependency --- app/app.go | 6 +- app/lanes.go | 112 +++++++++++++++++++++++--- app/mempool/lane.go | 33 ++++++++ app/mempool/lane_test.go | 153 ++++++++++++++++++++++++++++++++++++ app/mempool/mempool_test.go | 104 ++++++++++++++++++++++++ 5 files changed, 394 insertions(+), 14 deletions(-) diff --git a/app/app.go b/app/app.go index e50bf9ca9..507f5f1fd 100644 --- a/app/app.go +++ b/app/app.go @@ -254,8 +254,8 @@ func NewBandApp( app.sm.RegisterStoreDecoders() - feedsLane, tssLane, oracleLane, defaultLane := CreateLanes(app) - bandLanes := []*mempool.Lane{feedsLane, tssLane, oracleLane, defaultLane} + feedsLane, tssLane, oracleReportLane, oracleRequestLane, defaultLane := CreateLanes(app) + bandLanes := []*mempool.Lane{feedsLane, tssLane, oracleReportLane, oracleRequestLane, defaultLane} // create Band mempool bandMempool := mempool.NewMempool(app.Logger(), bandLanes) @@ -285,7 +285,7 @@ func NewBandApp( IBCKeeper: app.IBCKeeper, StakingKeeper: app.StakingKeeper, GlobalfeeKeeper: &app.GlobalFeeKeeper, - Lanes: []*mempool.Lane{feedsLane, tssLane, oracleLane}, // every lane except default lane + Lanes: []*mempool.Lane{feedsLane, tssLane, oracleReportLane}, // every lane except default lane }, ) if err != nil { diff --git a/app/lanes.go b/app/lanes.go index 7d3abc537..4fc30defb 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -197,8 +197,8 @@ func isValidTssLaneMsg( return true } -// oracleLaneMatchHandler is a function that returns the match function for the oracle lane. -func oracleLaneMatchHandler( +// oracleReportLaneMatchHandler is a function that returns the match function for the oracle lane. +func oracleReportLaneMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, oracleMsgServer oracletypes.MsgServer, @@ -269,6 +269,78 @@ func isValidMsgReportData( return true } +// oracleRequestLaneMatchHandler is a function that returns the match function for the oracle request lane. +func oracleRequestLaneMatchHandler( + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + oracleMsgServer oracletypes.MsgServer, +) func(sdk.Context, sdk.Tx) bool { + return func(ctx sdk.Context, tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if !isValidMsgRequestData(ctx, msg, cdc, authzKeeper, oracleMsgServer) { + return false + } + } + return true + } +} + +// isValidMsgRequestData return true if the message is a valid oracle's MsgRequestData. +func isValidMsgRequestData( + ctx sdk.Context, + msg sdk.Msg, + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + oracleMsgServer oracletypes.MsgServer, +) bool { + switch msg := msg.(type) { + case *oracletypes.MsgRequestData: + if _, err := oracleMsgServer.RequestData(ctx, msg); err != nil { + return false + } + case *authz.MsgExec: + msgs, err := msg.GetMessages() + if err != nil { + return false + } + + grantee, err := sdk.AccAddressFromBech32(msg.Grantee) + if err != nil { + return false + } + + for _, m := range msgs { + signers, _, err := cdc.GetMsgV1Signers(m) + if err != nil { + return false + } + // Check if this grantee have authorization for the message. + cap, _ := authzKeeper.GetAuthorization( + ctx, + grantee, + sdk.AccAddress(signers[0]), + sdk.MsgTypeURL(m), + ) + if cap == nil { + return false + } + + // Check if this message should be free or not. + if !isValidMsgRequestData(ctx, m, cdc, authzKeeper, oracleMsgServer) { + return false + } + } + default: + return false + } + + return true +} + // DefaultLaneMatchHandler is a function that returns the match function for the default lane. func DefaultLaneMatchHandler() func(sdk.Context, sdk.Tx) bool { return func(_ sdk.Context, _ sdk.Tx) bool { @@ -277,7 +349,7 @@ func DefaultLaneMatchHandler() func(sdk.Context, sdk.Tx) bool { } // CreateLanes creates the lanes for the Band mempool. -func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mempool.Lane) { +func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequestLane, defaultLane *mempool.Lane) { // 1. Create the signer extractor. This is used to extract the expected signers from // a transaction. Each lane can have a different signer extractor if needed. signerAdapter := sdkmempool.NewDefaultSignerExtractionAdapter() @@ -292,9 +364,10 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem signerAdapter, "feedsLane", FeedsLaneMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), - math.LegacyMustNewDecFromStr("0.05"), - math.LegacyMustNewDecFromStr("0.3"), + math.LegacyMustNewDecFromStr("0.02"), + math.LegacyMustNewDecFromStr("0.5"), sdkmempool.DefaultPriorityMempool(), + nil, ) tssLane = mempool.NewLane( @@ -303,20 +376,36 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem signerAdapter, "tssLane", TssLaneMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), - math.LegacyMustNewDecFromStr("0.05"), + math.LegacyMustNewDecFromStr("0.02"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), + nil, + ) + + oracleRequestLane = mempool.NewLane( + app.Logger(), + app.txConfig.TxEncoder(), + signerAdapter, + "oracleRequestLane", + oracleRequestLaneMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), + math.LegacyMustNewDecFromStr("0.1"), + math.LegacyMustNewDecFromStr("0.1"), + sdkmempool.DefaultPriorityMempool(), + nil, ) - oracleLane = mempool.NewLane( + oracleReportLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), signerAdapter, - "oracleLane", - oracleLaneMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), + "oracleReportLane", + oracleReportLaneMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), + func(isExceeded bool) { + oracleRequestLane.SetIsBlocked(isExceeded) + }, ) defaultLane = mempool.NewLane( @@ -325,9 +414,10 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem signerAdapter, "defaultLane", DefaultLaneMatchHandler(), - math.LegacyMustNewDecFromStr("0.3"), - math.LegacyMustNewDecFromStr("0.3"), + math.LegacyMustNewDecFromStr("0.1"), + math.LegacyMustNewDecFromStr("0.1"), sdkmempool.DefaultPriorityMempool(), + nil, ) return diff --git a/app/mempool/lane.go b/app/mempool/lane.go index fa18a91f3..8f2fb3e98 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -31,6 +31,11 @@ type Lane struct { // txIndex holds the uppercase hex-encoded hash for every transaction // currently in this lane's mempool. txIndex map[string]struct{} + + // OnExceed is a callback function that is called when the lane exceeds its limit. + OnExceed func(exceeded bool) + + isBlocked bool } // NewLane is a constructor for a lane. @@ -43,6 +48,7 @@ func NewLane( maxTransactionSpace math.LegacyDec, maxLaneSpace math.LegacyDec, laneMempool sdkmempool.Mempool, + onExceed func(exceeded bool), ) *Lane { return &Lane{ Logger: logger, @@ -53,9 +59,12 @@ func NewLane( MaxTransactionSpace: maxTransactionSpace, MaxLaneSpace: maxLaneSpace, laneMempool: laneMempool, + OnExceed: onExceed, // Initialize the txIndex. txIndex: make(map[string]struct{}), + + isBlocked: false, } } @@ -122,6 +131,13 @@ func (l *Lane) FillProposal( transactionLimit = proposal.GetLimit(l.MaxTransactionSpace) laneLimit = proposal.GetLimit(l.MaxLaneSpace) + isExceeded := false + + if l.isBlocked { + iterator = l.laneMempool.Select(ctx, nil) + return + } + // Select all transactions in the mempool that are valid and not already in the // partial proposal. for iterator = l.laneMempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() { @@ -168,6 +184,14 @@ func (l *Lane) FillProposal( blockUsed.IncreaseBy(txInfo.BlockSpace) } + if laneLimit.IsReachedBy(blockUsed) { + isExceeded = true + } + + if l.OnExceed != nil { + l.OnExceed(isExceeded) + } + return } @@ -182,6 +206,10 @@ func (l *Lane) FillProposalBy( // get the transaction limit for the lane. transactionLimit := proposal.GetLimit(l.MaxTransactionSpace) + if l.isBlocked { + return + } + // Select all transactions in the mempool that are valid and not already in the // partial proposal. for ; iterator != nil; iterator = iterator.Next() { @@ -260,3 +288,8 @@ func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { Signers: signers, }, nil } + +// SetIsBlocked sets the isBlocked flag to the given value. +func (l *Lane) SetIsBlocked(isBlocked bool) { + l.isBlocked = isBlocked +} diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index bab25e26d..0bf872f4e 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -63,6 +63,7 @@ func (s *LaneTestSuite) TestLaneInsertAndCount() { math.LegacyMustNewDecFromStr("0.3"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), + nil, ) // Create and insert two transactions @@ -87,6 +88,7 @@ func (s *LaneTestSuite) TestLaneRemove() { math.LegacyMustNewDecFromStr("0.3"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), + nil, ) tx := s.createSimpleTx(s.accounts[0], 0, 10) @@ -110,6 +112,7 @@ func (s *LaneTestSuite) TestLaneFillProposal() { math.LegacyMustNewDecFromStr("0.2"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), + nil, ) // Insert 3 transactions @@ -172,6 +175,156 @@ func (s *LaneTestSuite) TestLaneFillProposal() { s.Require().Equal(expectedIncludedTxs, proposal.Txs) } +type onExceedMock struct { + exceeded bool +} + +func (f *onExceedMock) OnExceed(exceeded bool) { + f.exceeded = exceeded +} + +func (s *LaneTestSuite) TestLaneOnExceed() { + onExceedMock := &onExceedMock{} + lane := NewLane( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + sdkmempool.NewDefaultSignerExtractionAdapter(), + "testLane", + func(sdk.Context, sdk.Tx) bool { return true }, // accept all + math.LegacyMustNewDecFromStr("0.3"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), + onExceedMock.OnExceed, + ) + + // Insert 3 transactions + tx1 := s.createSimpleTx(s.accounts[0], 0, 20) + + s.Require().NoError(lane.Insert(s.ctx, tx1)) + + // Create a proposal with block-limits + proposal := NewProposal( + log.NewTestLogger(s.T()), + 1000000000000, + 100, + ) + + // FillProposal + blockUsed, iterator, _ := lane.FillProposal(s.ctx, &proposal) + + // We expect tx1 to be included in the proposal. + s.Require().Equal(uint64(20), blockUsed.Gas(), "20 gas from tx1") + s.Require().Nil(iterator) + + // The proposal should contain 2 transactions in Txs(). + expectedIncludedTxs := s.getTxBytes(tx1) + s.Require().Equal(1, len(proposal.Txs), "one txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.Txs) + + s.Require().False(onExceedMock.exceeded, "onExceed should be called with false") + + // Insert 3 transactions + tx2 := s.createSimpleTx(s.accounts[1], 1, 20) + tx3 := s.createSimpleTx(s.accounts[2], 2, 30) + + s.Require().NoError(lane.Insert(s.ctx, tx2)) + s.Require().NoError(lane.Insert(s.ctx, tx3)) + + // Create a proposal with block-limits + proposal = NewProposal( + log.NewTestLogger(s.T()), + 1000000000000, + 100, + ) + + // FillProposal + blockUsed, iterator, _ = lane.FillProposal(s.ctx, &proposal) + + // We expect tx1 and tx2 to be included in the proposal. + // Then the gas should be over the limit, so tx3 is yet to be considered. + s.Require().Equal(uint64(40), blockUsed.Gas(), "20 gas from tx1 and 20 gas from tx2") + s.Require().NotNil(iterator) + + // The proposal should contain 2 transactions in Txs(). + expectedIncludedTxs = s.getTxBytes(tx1, tx2) + s.Require().Equal(2, len(proposal.Txs), "two txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.Txs) + + s.Require().True(onExceedMock.exceeded, "onExceed should be called with true") +} + +func (s *LaneTestSuite) TestLaneBlocked() { + // Lane that matches all txs + lane := NewLane( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + sdkmempool.NewDefaultSignerExtractionAdapter(), + "testLane", + func(sdk.Context, sdk.Tx) bool { return true }, // accept all + math.LegacyMustNewDecFromStr("0.2"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), + nil, + ) + + lane.SetIsBlocked(true) + + // Insert 3 transactions + tx1 := s.createSimpleTx(s.accounts[0], 0, 20) + tx2 := s.createSimpleTx(s.accounts[1], 1, 20) + tx3 := s.createSimpleTx(s.accounts[2], 2, 30) + + s.Require().NoError(lane.Insert(s.ctx, tx1)) + s.Require().NoError(lane.Insert(s.ctx, tx2)) + s.Require().NoError(lane.Insert(s.ctx, tx3)) + + // Create a proposal with block-limits + proposal := NewProposal( + log.NewTestLogger(s.T()), + 1000000000000, + 100, + ) + + // FillProposal + blockUsed, iterator, txsToRemove := lane.FillProposal(s.ctx, &proposal) + + s.Require().True(lane.isBlocked) + + // We expect no txs to be included in the proposal. + s.Require().Equal(int64(0), blockUsed.TxBytes()) + s.Require().Equal(uint64(0), blockUsed.Gas(), "0 gas") + s.Require().NotNil(iterator) + s.Require(). + Len(txsToRemove, 0, "no txs are removed") + + // The proposal should contain 0 transactions in Txs(). + expectedIncludedTxs := [][]byte{} + s.Require().Equal(0, len(proposal.Txs), "no txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.Txs) + + s.Require().Equal(lane.laneMempool.Select(s.ctx, nil).Tx(), tx1) + s.Require().Equal(iterator.Tx(), tx1) + + // Calculate the remaining block space + remainderLimit := proposal.MaxBlockSpace.Sub(proposal.TotalBlockSpace) + + // Call FillProposalBy with the remainder limit and iterator from the previous call. + blockUsed, txsToRemove = lane.FillProposalBy(&proposal, iterator, remainderLimit) + + // We expect no txs to be included in the proposal. + s.Require().Equal(int64(0), blockUsed.TxBytes()) + s.Require().Equal(uint64(0), blockUsed.Gas()) + s.Require(). + Len(txsToRemove, 0, "no txs are removed") + + // The proposal should contain 0 transactions in Txs(). + expectedIncludedTxs = [][]byte{} + s.Require().Equal(0, len(proposal.Txs), "no txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.Txs) + + s.Require().Equal(lane.laneMempool.Select(s.ctx, nil).Tx(), tx1) +} + // ----------------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------------- diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index 3e7cc511f..eab761bc9 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -157,6 +157,7 @@ func (s *MempoolTestSuite) newMempool() *Mempool { math.LegacyMustNewDecFromStr("0.2"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), + nil, ) DelegateLane := NewLane( @@ -168,6 +169,7 @@ func (s *MempoolTestSuite) newMempool() *Mempool { math.LegacyMustNewDecFromStr("0.2"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), + nil, ) OtherLane := NewLane( @@ -179,6 +181,7 @@ func (s *MempoolTestSuite) newMempool() *Mempool { math.LegacyMustNewDecFromStr("0.4"), math.LegacyMustNewDecFromStr("0.4"), sdkmempool.DefaultPriorityMempool(), + nil, ) lanes := []*Lane{BankSendLane, DelegateLane, OtherLane} @@ -507,6 +510,107 @@ func (s *MempoolTestSuite) TestFillUpLeftOverSpace() { s.Require().Equal(expectedIncludedTxs, result.Txs) } +func (s *MempoolTestSuite) TestDependencyBlockLane() { + signerAdapter := sdkmempool.NewDefaultSignerExtractionAdapter() + + DependentLane := NewLane( + log.NewTestLogger(s.T()), + s.encodingConfig.TxConfig.TxEncoder(), + signerAdapter, + "dependent", + isOtherTx, + math.LegacyMustNewDecFromStr("0.5"), + math.LegacyMustNewDecFromStr("0.5"), + sdkmempool.DefaultPriorityMempool(), + nil, + ) + + DependencyLane := NewLane( + log.NewTestLogger(s.T()), + s.encodingConfig.TxConfig.TxEncoder(), + signerAdapter, + "dependency", + isBankSendTx, + math.LegacyMustNewDecFromStr("0.5"), + math.LegacyMustNewDecFromStr("0.5"), + sdkmempool.DefaultPriorityMempool(), + func(exceeded bool) { + DependentLane.SetIsBlocked(exceeded) + }, + ) + + lanes := []*Lane{DependencyLane, DependentLane} + + mem := NewMempool( + log.NewTestLogger(s.T()), + lanes, + ) + + tx1, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[0], + 0, + 0, + 30, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + tx3, err := CreateMixedTx( + s.encodingConfig.TxConfig, + s.accounts[2], + 0, + 0, + 30, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + // Insert in reverse order to ensure ordering is correct + s.Require().NoError(mem.Insert(s.ctx, tx3)) + s.Require().NoError(mem.Insert(s.ctx, tx1)) + + proposal := NewProposal( + log.NewTestLogger(s.T()), + s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxGas), + ) + + result, err := mem.PrepareProposal(s.ctx, proposal) + s.Require().NoError(err) + s.Require().NotNil(result) + + expectedIncludedTxs := s.getTxBytes(tx1, tx3) + s.Require().Equal(2, len(result.Txs)) + s.Require().Equal(expectedIncludedTxs, result.Txs) + + tx2, err := CreateBankSendTx( + s.encodingConfig.TxConfig, + s.accounts[1], + 0, + 0, + 30, + sdk.NewCoin(s.gasTokenDenom, math.NewInt(1000000)), + ) + s.Require().NoError(err) + + s.Require().NoError(mem.Insert(s.ctx, tx2)) + + proposal = NewProposal( + log.NewTestLogger(s.T()), + s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxGas), + ) + + result, err = mem.PrepareProposal(s.ctx, proposal) + s.Require().NoError(err) + s.Require().NotNil(result) + + expectedIncludedTxs = s.getTxBytes(tx1, tx2) + s.Require().Equal(2, len(result.Txs)) + s.Require().Equal(expectedIncludedTxs, result.Txs) +} + // ----------------------------------------------------------------------------- // Tx creation helpers // ----------------------------------------------------------------------------- From 3d10cf98a76272d10bb663ca1d1fa16cddbc7b5d Mon Sep 17 00:00:00 2001 From: Nattapat Iammelap Date: Mon, 24 Mar 2025 17:43:14 +0700 Subject: [PATCH 13/53] correct variables name and comment --- app/app.go | 2 +- app/lanes.go | 4 +- app/mempool/lane.go | 17 +++---- app/mempool/lane_test.go | 97 +++++++++++++++++++++++++++++++------ app/mempool/mempool_test.go | 20 ++++---- 5 files changed, 104 insertions(+), 36 deletions(-) diff --git a/app/app.go b/app/app.go index 507f5f1fd..da5f08b0a 100644 --- a/app/app.go +++ b/app/app.go @@ -285,7 +285,7 @@ func NewBandApp( IBCKeeper: app.IBCKeeper, StakingKeeper: app.StakingKeeper, GlobalfeeKeeper: &app.GlobalFeeKeeper, - Lanes: []*mempool.Lane{feedsLane, tssLane, oracleReportLane}, // every lane except default lane + Lanes: []*mempool.Lane{feedsLane, tssLane, oracleReportLane}, // only lanes that are gas free }, ) if err != nil { diff --git a/app/lanes.go b/app/lanes.go index 4fc30defb..263877420 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -403,8 +403,8 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), - func(isExceeded bool) { - oracleRequestLane.SetIsBlocked(isExceeded) + func(isFilled bool) { + oracleRequestLane.SetIsBlocked(isFilled) }, ) diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 8f2fb3e98..56ea24df3 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -32,8 +32,8 @@ type Lane struct { // currently in this lane's mempool. txIndex map[string]struct{} - // OnExceed is a callback function that is called when the lane exceeds its limit. - OnExceed func(exceeded bool) + // OnFilled is a callback function that is called when the lane exceeds its limit. + OnFilled func(isFilled bool) isBlocked bool } @@ -48,7 +48,7 @@ func NewLane( maxTransactionSpace math.LegacyDec, maxLaneSpace math.LegacyDec, laneMempool sdkmempool.Mempool, - onExceed func(exceeded bool), + onFilled func(isFilled bool), ) *Lane { return &Lane{ Logger: logger, @@ -59,7 +59,7 @@ func NewLane( MaxTransactionSpace: maxTransactionSpace, MaxLaneSpace: maxLaneSpace, laneMempool: laneMempool, - OnExceed: onExceed, + OnFilled: onFilled, // Initialize the txIndex. txIndex: make(map[string]struct{}), @@ -131,10 +131,9 @@ func (l *Lane) FillProposal( transactionLimit = proposal.GetLimit(l.MaxTransactionSpace) laneLimit = proposal.GetLimit(l.MaxLaneSpace) - isExceeded := false + isFilled := false if l.isBlocked { - iterator = l.laneMempool.Select(ctx, nil) return } @@ -185,11 +184,11 @@ func (l *Lane) FillProposal( } if laneLimit.IsReachedBy(blockUsed) { - isExceeded = true + isFilled = true } - if l.OnExceed != nil { - l.OnExceed(isExceeded) + if l.OnFilled != nil { + l.OnFilled(isFilled) } return diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 0bf872f4e..de30d75e7 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -175,16 +175,16 @@ func (s *LaneTestSuite) TestLaneFillProposal() { s.Require().Equal(expectedIncludedTxs, proposal.Txs) } -type onExceedMock struct { - exceeded bool +type onFilledMock struct { + isFilled bool } -func (f *onExceedMock) OnExceed(exceeded bool) { - f.exceeded = exceeded +func (f *onFilledMock) OnFilled(isFilled bool) { + f.isFilled = isFilled } -func (s *LaneTestSuite) TestLaneOnExceed() { - onExceedMock := &onExceedMock{} +func (s *LaneTestSuite) TestLaneOnFilled() { + onFilledMock := &onFilledMock{} lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -194,10 +194,10 @@ func (s *LaneTestSuite) TestLaneOnExceed() { math.LegacyMustNewDecFromStr("0.3"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), - onExceedMock.OnExceed, + onFilledMock.OnFilled, ) - // Insert 3 transactions + // Insert a transaction tx1 := s.createSimpleTx(s.accounts[0], 0, 20) s.Require().NoError(lane.Insert(s.ctx, tx1)) @@ -216,14 +216,14 @@ func (s *LaneTestSuite) TestLaneOnExceed() { s.Require().Equal(uint64(20), blockUsed.Gas(), "20 gas from tx1") s.Require().Nil(iterator) - // The proposal should contain 2 transactions in Txs(). + // The proposal should contain 1 transaction in Txs(). expectedIncludedTxs := s.getTxBytes(tx1) s.Require().Equal(1, len(proposal.Txs), "one txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.Txs) - s.Require().False(onExceedMock.exceeded, "onExceed should be called with false") + s.Require().False(onFilledMock.isFilled, "onFilled should be called with false") - // Insert 3 transactions + // Insert 2 more transactions tx2 := s.createSimpleTx(s.accounts[1], 1, 20) tx3 := s.createSimpleTx(s.accounts[2], 2, 30) @@ -250,7 +250,77 @@ func (s *LaneTestSuite) TestLaneOnExceed() { s.Require().Equal(2, len(proposal.Txs), "two txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.Txs) - s.Require().True(onExceedMock.exceeded, "onExceed should be called with true") + s.Require().True(onFilledMock.isFilled, "onFilled should be called with true") +} + +func (s *LaneTestSuite) TestLaneExactlyFilled() { + onFilledMock := &onFilledMock{} + lane := NewLane( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + sdkmempool.NewDefaultSignerExtractionAdapter(), + "testLane", + func(sdk.Context, sdk.Tx) bool { return true }, // accept all + math.LegacyMustNewDecFromStr("0.3"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), + onFilledMock.OnFilled, + ) + + // Insert a transaction + tx1 := s.createSimpleTx(s.accounts[0], 0, 20) + + s.Require().NoError(lane.Insert(s.ctx, tx1)) + + // Create a proposal with block-limits + proposal := NewProposal( + log.NewTestLogger(s.T()), + 1000000000000, + 100, + ) + + // FillProposal + blockUsed, iterator, _ := lane.FillProposal(s.ctx, &proposal) + + // We expect tx1 to be included in the proposal. + s.Require().Equal(uint64(20), blockUsed.Gas(), "20 gas from tx1") + s.Require().Nil(iterator) + + // The proposal should contain 1 transaction in Txs(). + expectedIncludedTxs := s.getTxBytes(tx1) + s.Require().Equal(1, len(proposal.Txs), "one txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.Txs) + + s.Require().False(onFilledMock.isFilled, "onFilled should be called with false") + + // Insert 2 more transactions + tx2 := s.createSimpleTx(s.accounts[1], 1, 10) + tx3 := s.createSimpleTx(s.accounts[2], 2, 30) + + s.Require().NoError(lane.Insert(s.ctx, tx2)) + s.Require().NoError(lane.Insert(s.ctx, tx3)) + + // Create a proposal with block-limits + proposal = NewProposal( + log.NewTestLogger(s.T()), + 1000000000000, + 100, + ) + + // FillProposal + blockUsed, iterator, _ = lane.FillProposal(s.ctx, &proposal) + + // We expect tx1 and tx2 to be included in the proposal. + // Then the gas should be over the limit, so tx3 is yet to be considered. + s.Require().Equal(uint64(30), blockUsed.Gas(), "20 gas from tx1 and 10 gas from tx2") + s.Require().NotNil(iterator) + + // The proposal should contain 2 transactions in Txs(). + expectedIncludedTxs = s.getTxBytes(tx1, tx2) + s.Require().Equal(2, len(proposal.Txs), "two txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.Txs) + + s.Require().True(onFilledMock.isFilled, "onFilled should be called with true") } func (s *LaneTestSuite) TestLaneBlocked() { @@ -293,7 +363,7 @@ func (s *LaneTestSuite) TestLaneBlocked() { // We expect no txs to be included in the proposal. s.Require().Equal(int64(0), blockUsed.TxBytes()) s.Require().Equal(uint64(0), blockUsed.Gas(), "0 gas") - s.Require().NotNil(iterator) + s.Require().Nil(iterator) s.Require(). Len(txsToRemove, 0, "no txs are removed") @@ -303,7 +373,6 @@ func (s *LaneTestSuite) TestLaneBlocked() { s.Require().Equal(expectedIncludedTxs, proposal.Txs) s.Require().Equal(lane.laneMempool.Select(s.ctx, nil).Tx(), tx1) - s.Require().Equal(iterator.Tx(), tx1) // Calculate the remaining block space remainderLimit := proposal.MaxBlockSpace.Sub(proposal.TotalBlockSpace) diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index eab761bc9..08b6c9b9b 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -534,8 +534,8 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { math.LegacyMustNewDecFromStr("0.5"), math.LegacyMustNewDecFromStr("0.5"), sdkmempool.DefaultPriorityMempool(), - func(exceeded bool) { - DependentLane.SetIsBlocked(exceeded) + func(isFilled bool) { + DependentLane.SetIsBlocked(isFilled) }, ) @@ -546,7 +546,7 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { lanes, ) - tx1, err := CreateBankSendTx( + bankTx1, err := CreateBankSendTx( s.encodingConfig.TxConfig, s.accounts[0], 0, @@ -556,7 +556,7 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { ) s.Require().NoError(err) - tx3, err := CreateMixedTx( + MixedTx1, err := CreateMixedTx( s.encodingConfig.TxConfig, s.accounts[2], 0, @@ -567,8 +567,8 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { s.Require().NoError(err) // Insert in reverse order to ensure ordering is correct - s.Require().NoError(mem.Insert(s.ctx, tx3)) - s.Require().NoError(mem.Insert(s.ctx, tx1)) + s.Require().NoError(mem.Insert(s.ctx, MixedTx1)) + s.Require().NoError(mem.Insert(s.ctx, bankTx1)) proposal := NewProposal( log.NewTestLogger(s.T()), @@ -580,11 +580,11 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { s.Require().NoError(err) s.Require().NotNil(result) - expectedIncludedTxs := s.getTxBytes(tx1, tx3) + expectedIncludedTxs := s.getTxBytes(bankTx1, MixedTx1) s.Require().Equal(2, len(result.Txs)) s.Require().Equal(expectedIncludedTxs, result.Txs) - tx2, err := CreateBankSendTx( + bankTx2, err := CreateBankSendTx( s.encodingConfig.TxConfig, s.accounts[1], 0, @@ -594,7 +594,7 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { ) s.Require().NoError(err) - s.Require().NoError(mem.Insert(s.ctx, tx2)) + s.Require().NoError(mem.Insert(s.ctx, bankTx2)) proposal = NewProposal( log.NewTestLogger(s.T()), @@ -606,7 +606,7 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { s.Require().NoError(err) s.Require().NotNil(result) - expectedIncludedTxs = s.getTxBytes(tx1, tx2) + expectedIncludedTxs = s.getTxBytes(bankTx1, bankTx2) s.Require().Equal(2, len(result.Txs)) s.Require().Equal(expectedIncludedTxs, result.Txs) } From 9ae52869537b282165e9a4244826e6cdd76ad220 Mon Sep 17 00:00:00 2001 From: colmazia Date: Wed, 2 Apr 2025 15:45:33 +0700 Subject: [PATCH 14/53] fix double gas issue --- app/ante.go | 20 ++-- app/app.go | 12 +- app/lanes.go | 103 ++++++++-------- app/match.go | 263 +++++++++++++++++++++++++++++++++++++++++ app/mempool/mempool.go | 2 +- 5 files changed, 337 insertions(+), 63 deletions(-) create mode 100644 app/match.go diff --git a/app/ante.go b/app/ante.go index b7dd46208..3a88c0f98 100644 --- a/app/ante.go +++ b/app/ante.go @@ -11,7 +11,6 @@ import ( authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - "github.com/bandprotocol/chain/v3/app/mempool" bandtsskeeper "github.com/bandprotocol/chain/v3/x/bandtss/keeper" feedskeeper "github.com/bandprotocol/chain/v3/x/feeds/keeper" "github.com/bandprotocol/chain/v3/x/globalfee/feechecker" @@ -33,7 +32,7 @@ type HandlerOptions struct { TSSKeeper *tsskeeper.Keeper BandtssKeeper *bandtsskeeper.Keeper FeedsKeeper *feedskeeper.Keeper - Lanes []*mempool.Lane + MatchFns []func(sdk.Context, sdk.Tx) bool } func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { @@ -107,7 +106,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { options.FeegrantKeeper, options.TxFeeChecker, ), - options.Lanes..., + options.MatchFns..., ), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewSetPubKeyDecorator(options.AccountKeeper), @@ -125,14 +124,14 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { // for the AnteDecorator to be ignored for specified lanes. type IgnoreDecorator struct { decorator sdk.AnteDecorator - lanes []*mempool.Lane + matchFns []func(sdk.Context, sdk.Tx) bool } // NewIgnoreDecorator returns a new IgnoreDecorator instance. -func NewIgnoreDecorator(decorator sdk.AnteDecorator, lanes ...*mempool.Lane) *IgnoreDecorator { +func NewIgnoreDecorator(decorator sdk.AnteDecorator, matchFns ...func(sdk.Context, sdk.Tx) bool) *IgnoreDecorator { return &IgnoreDecorator{ decorator: decorator, - lanes: lanes, + matchFns: matchFns, } } @@ -142,9 +141,14 @@ func NewIgnoreDecorator(decorator sdk.AnteDecorator, lanes ...*mempool.Lane) *Ig func (sd IgnoreDecorator) AnteHandle( ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler, ) (sdk.Context, error) { + // IgnoreDecorator is only used for check tx. + if !ctx.IsCheckTx() { + return sd.decorator.AnteHandle(ctx, tx, simulate, next) + } + cacheCtx, _ := ctx.CacheContext() - for _, lane := range sd.lanes { - if lane.Match(cacheCtx, tx) { + for _, matchFn := range sd.matchFns { + if matchFn(cacheCtx, tx) { return next(ctx, tx, simulate) } } diff --git a/app/app.go b/app/app.go index e50bf9ca9..c46718fc5 100644 --- a/app/app.go +++ b/app/app.go @@ -58,7 +58,9 @@ import ( v3 "github.com/bandprotocol/chain/v3/app/upgrades/v3" nodeservice "github.com/bandprotocol/chain/v3/client/grpc/node" proofservice "github.com/bandprotocol/chain/v3/client/grpc/oracle/proof" + feedskeeper "github.com/bandprotocol/chain/v3/x/feeds/keeper" oraclekeeper "github.com/bandprotocol/chain/v3/x/oracle/keeper" + tsskeeper "github.com/bandprotocol/chain/v3/x/tss/keeper" ) var ( @@ -267,6 +269,10 @@ func NewBandApp( app.MountTransientStores(app.GetTransientStoreKey()) app.MountMemoryStores(app.GetMemoryStoreKey()) + feedsMsgServer := feedskeeper.NewMsgServerImpl(app.FeedsKeeper) + tssMsgServer := tsskeeper.NewMsgServerImpl(app.TSSKeeper) + oracleMsgServer := oraclekeeper.NewMsgServerImpl(app.OracleKeeper) + anteHandler, err := NewAnteHandler( HandlerOptions{ HandlerOptions: ante.HandlerOptions{ @@ -285,7 +291,11 @@ func NewBandApp( IBCKeeper: app.IBCKeeper, StakingKeeper: app.StakingKeeper, GlobalfeeKeeper: &app.GlobalFeeKeeper, - Lanes: []*mempool.Lane{feedsLane, tssLane, oracleLane}, // every lane except default lane + MatchFns: []func(sdk.Context, sdk.Tx) bool{ + FeedsSubmitSignalPriceTxMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), + TssTxMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), + oracleReportTxMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), + }, // every lane except default lane }, ) if err != nil { diff --git a/app/lanes.go b/app/lanes.go index 7d3abc537..182880a55 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -11,11 +11,8 @@ import ( "github.com/bandprotocol/chain/v3/app/mempool" bandtsskeeper "github.com/bandprotocol/chain/v3/x/bandtss/keeper" - feedskeeper "github.com/bandprotocol/chain/v3/x/feeds/keeper" feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" - oraclekeeper "github.com/bandprotocol/chain/v3/x/oracle/keeper" oracletypes "github.com/bandprotocol/chain/v3/x/oracle/types" - tsskeeper "github.com/bandprotocol/chain/v3/x/tss/keeper" tsstypes "github.com/bandprotocol/chain/v3/x/tss/types" ) @@ -23,15 +20,23 @@ import ( func FeedsLaneMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, - feedsMsgServer feedstypes.MsgServer, ) func(sdk.Context, sdk.Tx) bool { return func(ctx sdk.Context, tx sdk.Tx) bool { + gasTx, ok := tx.(sdk.FeeTx) + if !ok { + return false + } + + if !gasTx.GetFee().IsZero() { + return false + } + msgs := tx.GetMsgs() if len(msgs) == 0 { return false } for _, msg := range msgs { - if !isValidMsgSubmitSignalPrices(ctx, msg, cdc, authzKeeper, feedsMsgServer) { + if !isMsgSubmitSignalPrices(ctx, msg, cdc, authzKeeper) { return false } } @@ -39,19 +44,16 @@ func FeedsLaneMatchHandler( } } -// isValidMsgSubmitSignalPrices return true if the message is a valid feeds' MsgSubmitSignalPrices. -func isValidMsgSubmitSignalPrices( +// isMsgSubmitSignalPrices return true if the message is a valid feeds' MsgSubmitSignalPrices. +func isMsgSubmitSignalPrices( ctx sdk.Context, msg sdk.Msg, cdc codec.Codec, authzKeeper *authzkeeper.Keeper, - feedsMsgServer feedstypes.MsgServer, ) bool { switch msg := msg.(type) { case *feedstypes.MsgSubmitSignalPrices: - if _, err := feedsMsgServer.SubmitSignalPrices(ctx, msg); err != nil { - return false - } + return true case *authz.MsgExec: msgs, err := msg.GetMessages() if err != nil { @@ -80,7 +82,7 @@ func isValidMsgSubmitSignalPrices( } // Check if this message should be free or not. - if !isValidMsgSubmitSignalPrices(ctx, m, cdc, authzKeeper, feedsMsgServer) { + if !isMsgSubmitSignalPrices(ctx, m, cdc, authzKeeper) { return false } } @@ -96,15 +98,23 @@ func TssLaneMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, bandtssKeeper *bandtsskeeper.Keeper, - tssMsgServer tsstypes.MsgServer, ) func(sdk.Context, sdk.Tx) bool { return func(ctx sdk.Context, tx sdk.Tx) bool { + gasTx, ok := tx.(sdk.FeeTx) + if !ok { + return false + } + + if !gasTx.GetFee().IsZero() { + return false + } + msgs := tx.GetMsgs() if len(msgs) == 0 { return false } for _, msg := range msgs { - if !isValidTssLaneMsg(ctx, msg, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { + if !isTssLaneMsg(ctx, msg, cdc, authzKeeper, bandtssKeeper) { return false } } @@ -112,32 +122,23 @@ func TssLaneMatchHandler( } } -// isValidTssLaneMsg return true if the message is a valid for TSS lane. -func isValidTssLaneMsg( +// isTssLaneMsg return true if the message is a valid for TSS lane. +func isTssLaneMsg( ctx sdk.Context, msg sdk.Msg, cdc codec.Codec, authzKeeper *authzkeeper.Keeper, bandtssKeeper *bandtsskeeper.Keeper, - tssMsgServer tsstypes.MsgServer, ) bool { switch msg := msg.(type) { case *tsstypes.MsgSubmitDKGRound1: - if _, err := tssMsgServer.SubmitDKGRound1(ctx, msg); err != nil { - return false - } + return true case *tsstypes.MsgSubmitDKGRound2: - if _, err := tssMsgServer.SubmitDKGRound2(ctx, msg); err != nil { - return false - } + return true case *tsstypes.MsgConfirm: - if _, err := tssMsgServer.Confirm(ctx, msg); err != nil { - return false - } + return true case *tsstypes.MsgComplain: - if _, err := tssMsgServer.Complain(ctx, msg); err != nil { - return false - } + return true case *tsstypes.MsgSubmitDEs: acc, err := sdk.AccAddressFromBech32(msg.Sender) if err != nil { @@ -150,14 +151,9 @@ func isValidTssLaneMsg( !bandtssKeeper.HasMember(ctx, acc, incomingGroupID) { return false } - - if _, err := tssMsgServer.SubmitDEs(ctx, msg); err != nil { - return false - } + return true case *tsstypes.MsgSubmitSignature: - if _, err := tssMsgServer.SubmitSignature(ctx, msg); err != nil { - return false - } + return true case *authz.MsgExec: msgs, err := msg.GetMessages() if err != nil { @@ -186,7 +182,7 @@ func isValidTssLaneMsg( } // Check if this message should be free or not. - if !isValidTssLaneMsg(ctx, m, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { + if !isTssLaneMsg(ctx, m, cdc, authzKeeper, bandtssKeeper) { return false } } @@ -201,15 +197,23 @@ func isValidTssLaneMsg( func oracleLaneMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, - oracleMsgServer oracletypes.MsgServer, ) func(sdk.Context, sdk.Tx) bool { return func(ctx sdk.Context, tx sdk.Tx) bool { + gasTx, ok := tx.(sdk.FeeTx) + if !ok { + return false + } + + if !gasTx.GetFee().IsZero() { + return false + } + msgs := tx.GetMsgs() if len(msgs) == 0 { return false } for _, msg := range msgs { - if !isValidMsgReportData(ctx, msg, cdc, authzKeeper, oracleMsgServer) { + if !isMsgReportData(ctx, msg, cdc, authzKeeper) { return false } } @@ -217,19 +221,16 @@ func oracleLaneMatchHandler( } } -// isValidMsgReportData return true if the message is a valid oracle's MsgReportData. -func isValidMsgReportData( +// isMsgReportData return true if the message is a valid oracle's MsgReportData. +func isMsgReportData( ctx sdk.Context, msg sdk.Msg, cdc codec.Codec, authzKeeper *authzkeeper.Keeper, - oracleMsgServer oracletypes.MsgServer, ) bool { switch msg := msg.(type) { case *oracletypes.MsgReportData: - if _, err := oracleMsgServer.ReportData(ctx, msg); err != nil { - return false - } + return true case *authz.MsgExec: msgs, err := msg.GetMessages() if err != nil { @@ -258,7 +259,7 @@ func isValidMsgReportData( } // Check if this message should be free or not. - if !isValidMsgReportData(ctx, m, cdc, authzKeeper, oracleMsgServer) { + if !isMsgReportData(ctx, m, cdc, authzKeeper) { return false } } @@ -282,16 +283,12 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem // a transaction. Each lane can have a different signer extractor if needed. signerAdapter := sdkmempool.NewDefaultSignerExtractionAdapter() - feedsMsgServer := feedskeeper.NewMsgServerImpl(app.FeedsKeeper) - tssMsgServer := tsskeeper.NewMsgServerImpl(app.TSSKeeper) - oracleMsgServer := oraclekeeper.NewMsgServerImpl(app.OracleKeeper) - feedsLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), signerAdapter, "feedsLane", - FeedsLaneMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), + FeedsLaneMatchHandler(app.appCodec, &app.AuthzKeeper), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), @@ -302,7 +299,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem app.txConfig.TxEncoder(), signerAdapter, "tssLane", - TssLaneMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), + TssLaneMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), @@ -313,7 +310,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem app.txConfig.TxEncoder(), signerAdapter, "oracleLane", - oracleLaneMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), + oracleLaneMatchHandler(app.appCodec, &app.AuthzKeeper), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), diff --git a/app/match.go b/app/match.go new file mode 100644 index 000000000..a1bdbad8c --- /dev/null +++ b/app/match.go @@ -0,0 +1,263 @@ +package band + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + + bandtsskeeper "github.com/bandprotocol/chain/v3/x/bandtss/keeper" + feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" + oracletypes "github.com/bandprotocol/chain/v3/x/oracle/types" + tsstypes "github.com/bandprotocol/chain/v3/x/tss/types" +) + +// FeedsSubmitSignalPriceTxMatchHandler is a function that returns the match function for the Feeds SubmitSignalPriceTx. +func FeedsSubmitSignalPriceTxMatchHandler( + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + feedsMsgServer feedstypes.MsgServer, +) func(sdk.Context, sdk.Tx) bool { + return func(ctx sdk.Context, tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if !isValidMsgSubmitSignalPrices(ctx, msg, cdc, authzKeeper, feedsMsgServer) { + return false + } + } + return true + } +} + +// isValidMsgSubmitSignalPrices return true if the message is a valid feeds' MsgSubmitSignalPrices. +func isValidMsgSubmitSignalPrices( + ctx sdk.Context, + msg sdk.Msg, + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + feedsMsgServer feedstypes.MsgServer, +) bool { + switch msg := msg.(type) { + case *feedstypes.MsgSubmitSignalPrices: + if _, err := feedsMsgServer.SubmitSignalPrices(ctx, msg); err != nil { + return false + } + case *authz.MsgExec: + msgs, err := msg.GetMessages() + if err != nil { + return false + } + + grantee, err := sdk.AccAddressFromBech32(msg.Grantee) + if err != nil { + return false + } + + for _, m := range msgs { + signers, _, err := cdc.GetMsgV1Signers(m) + if err != nil { + return false + } + // Check if this grantee have authorization for the message. + cap, _ := authzKeeper.GetAuthorization( + ctx, + grantee, + sdk.AccAddress(signers[0]), + sdk.MsgTypeURL(m), + ) + if cap == nil { + return false + } + + // Check if this message should be free or not. + if !isValidMsgSubmitSignalPrices(ctx, m, cdc, authzKeeper, feedsMsgServer) { + return false + } + } + default: + return false + } + + return true +} + +// TssTxMatchHandler is a function that returns the match function for the TSS Tx. +func TssTxMatchHandler( + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + bandtssKeeper *bandtsskeeper.Keeper, + tssMsgServer tsstypes.MsgServer, +) func(sdk.Context, sdk.Tx) bool { + return func(ctx sdk.Context, tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if !isValidTssTxMsg(ctx, msg, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { + return false + } + } + return true + } +} + +// isValidTssTxMsg return true if the message is a valid for TSS Tx. +func isValidTssTxMsg( + ctx sdk.Context, + msg sdk.Msg, + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + bandtssKeeper *bandtsskeeper.Keeper, + tssMsgServer tsstypes.MsgServer, +) bool { + switch msg := msg.(type) { + case *tsstypes.MsgSubmitDKGRound1: + if _, err := tssMsgServer.SubmitDKGRound1(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgSubmitDKGRound2: + if _, err := tssMsgServer.SubmitDKGRound2(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgConfirm: + if _, err := tssMsgServer.Confirm(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgComplain: + if _, err := tssMsgServer.Complain(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgSubmitDEs: + acc, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return false + } + + currentGroupID := bandtssKeeper.GetCurrentGroup(ctx).GroupID + incomingGroupID := bandtssKeeper.GetIncomingGroupID(ctx) + if !bandtssKeeper.HasMember(ctx, acc, currentGroupID) && + !bandtssKeeper.HasMember(ctx, acc, incomingGroupID) { + return false + } + + if _, err := tssMsgServer.SubmitDEs(ctx, msg); err != nil { + return false + } + case *tsstypes.MsgSubmitSignature: + if _, err := tssMsgServer.SubmitSignature(ctx, msg); err != nil { + return false + } + case *authz.MsgExec: + msgs, err := msg.GetMessages() + if err != nil { + return false + } + + grantee, err := sdk.AccAddressFromBech32(msg.Grantee) + if err != nil { + return false + } + + for _, m := range msgs { + signers, _, err := cdc.GetMsgV1Signers(m) + if err != nil { + return false + } + // Check if this grantee have authorization for the message. + cap, _ := authzKeeper.GetAuthorization( + ctx, + grantee, + sdk.AccAddress(signers[0]), + sdk.MsgTypeURL(m), + ) + if cap == nil { + return false + } + + // Check if this message should be free or not. + if !isValidTssTxMsg(ctx, m, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { + return false + } + } + default: + return false + } + + return true +} + +// oracleReportTxMatchHandler is a function that returns the match function for the oracle report tx. +func oracleReportTxMatchHandler( + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + oracleMsgServer oracletypes.MsgServer, +) func(sdk.Context, sdk.Tx) bool { + return func(ctx sdk.Context, tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if !isValidMsgReportData(ctx, msg, cdc, authzKeeper, oracleMsgServer) { + return false + } + } + return true + } +} + +// isValidMsgReportData return true if the message is a valid oracle's MsgReportData. +func isValidMsgReportData( + ctx sdk.Context, + msg sdk.Msg, + cdc codec.Codec, + authzKeeper *authzkeeper.Keeper, + oracleMsgServer oracletypes.MsgServer, +) bool { + switch msg := msg.(type) { + case *oracletypes.MsgReportData: + if _, err := oracleMsgServer.ReportData(ctx, msg); err != nil { + return false + } + case *authz.MsgExec: + msgs, err := msg.GetMessages() + if err != nil { + return false + } + + grantee, err := sdk.AccAddressFromBech32(msg.Grantee) + if err != nil { + return false + } + + for _, m := range msgs { + signers, _, err := cdc.GetMsgV1Signers(m) + if err != nil { + return false + } + // Check if this grantee have authorization for the message. + cap, _ := authzKeeper.GetAuthorization( + ctx, + grantee, + sdk.AccAddress(signers[0]), + sdk.MsgTypeURL(m), + ) + if cap == nil { + return false + } + + // Check if this message should be free or not. + if !isValidMsgReportData(ctx, m, cdc, authzKeeper, oracleMsgServer) { + return false + } + } + default: + return false + } + + return true +} diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index 20be11964..c1361a022 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -47,7 +47,7 @@ func (m *Mempool) Insert(ctx context.Context, tx sdk.Tx) (err error) { } } - return nil + return } // Select returns a Mempool iterator (currently nil). From 3ab008276f5daa7bce29b368e042490139fac084 Mon Sep 17 00:00:00 2001 From: colmazia Date: Wed, 2 Apr 2025 15:53:48 +0700 Subject: [PATCH 15/53] add and delete comments --- app/app.go | 2 +- app/lanes.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/app.go b/app/app.go index c46718fc5..09bc50dd1 100644 --- a/app/app.go +++ b/app/app.go @@ -295,7 +295,7 @@ func NewBandApp( FeedsSubmitSignalPriceTxMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), TssTxMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), oracleReportTxMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), - }, // every lane except default lane + }, }, ) if err != nil { diff --git a/app/lanes.go b/app/lanes.go index 182880a55..93bc7329e 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -22,6 +22,7 @@ func FeedsLaneMatchHandler( authzKeeper *authzkeeper.Keeper, ) func(sdk.Context, sdk.Tx) bool { return func(ctx sdk.Context, tx sdk.Tx) bool { + // Feeds lane only matches fee-less transactions gasTx, ok := tx.(sdk.FeeTx) if !ok { return false @@ -100,6 +101,7 @@ func TssLaneMatchHandler( bandtssKeeper *bandtsskeeper.Keeper, ) func(sdk.Context, sdk.Tx) bool { return func(ctx sdk.Context, tx sdk.Tx) bool { + // TSS lane only matches fee-less transactions gasTx, ok := tx.(sdk.FeeTx) if !ok { return false @@ -199,6 +201,7 @@ func oracleLaneMatchHandler( authzKeeper *authzkeeper.Keeper, ) func(sdk.Context, sdk.Tx) bool { return func(ctx sdk.Context, tx sdk.Tx) bool { + // Oracle lane only matches fee-less transactions gasTx, ok := tx.(sdk.FeeTx) if !ok { return false From 2b58d0d9b5ad2c327b41ce0d7782bd55e80cc4a7 Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 4 Apr 2025 15:17:35 +0700 Subject: [PATCH 16/53] fix from comments --- app/mempool/block_space.go | 24 ------------------------ app/mempool/lane.go | 18 +++++++++++++++--- app/mempool/mempool.go | 4 ++-- app/mempool/utils.go | 22 ---------------------- 4 files changed, 17 insertions(+), 51 deletions(-) delete mode 100644 app/mempool/utils.go diff --git a/app/mempool/block_space.go b/app/mempool/block_space.go index 05b6cbbc7..48b5c7a50 100644 --- a/app/mempool/block_space.go +++ b/app/mempool/block_space.go @@ -43,30 +43,6 @@ func (bs BlockSpace) IsExceededBy(other BlockSpace) bool { // --- Math Methods --- -// IncreaseBy increases this BlockSpace by another BlockSpace's size/gas. -func (bs *BlockSpace) IncreaseBy(other BlockSpace) { - bs.txBytes += other.txBytes - bs.gas += other.gas -} - -// DecreaseBy decreases this BlockSpace by another BlockSpace's size/gas. -// Ensures txBytes and gas never go below zero. -func (bs *BlockSpace) DecreaseBy(other BlockSpace) { - // Decrease txBytes - if other.txBytes > bs.txBytes { - bs.txBytes = 0 - } else { - bs.txBytes -= other.txBytes - } - - // Decrease gas - if other.gas > bs.gas { - bs.gas = 0 - } else { - bs.gas -= other.gas - } -} - // Sub returns the difference between this BlockSpace and another BlockSpace. // Ensures txBytes and gas never go below zero. func (bs BlockSpace) Sub(other BlockSpace) BlockSpace { diff --git a/app/mempool/lane.go b/app/mempool/lane.go index fa18a91f3..2400e24e6 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" "strings" + "sync" comettypes "github.com/cometbft/cometbft/types" @@ -31,6 +32,9 @@ type Lane struct { // txIndex holds the uppercase hex-encoded hash for every transaction // currently in this lane's mempool. txIndex map[string]struct{} + + // Add mutex for thread safety + mtx sync.RWMutex } // NewLane is a constructor for a lane. @@ -66,6 +70,9 @@ func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { return err } + l.mtx.Lock() + defer l.mtx.Unlock() + if err = l.laneMempool.Insert(ctx, tx); err != nil { return err } @@ -86,11 +93,13 @@ func (l *Lane) Remove(tx sdk.Tx) error { return err } + l.mtx.Lock() + defer l.mtx.Unlock() + if err = l.laneMempool.Remove(tx); err != nil { return err } - // Remove it from the local index delete(l.txIndex, txInfo.Hash) return nil } @@ -102,6 +111,9 @@ func (l *Lane) Contains(tx sdk.Tx) bool { return false } + l.mtx.RLock() + defer l.mtx.RUnlock() + _, exists := l.txIndex[txInfo.Hash] return exists } @@ -165,7 +177,7 @@ func (l *Lane) FillProposal( break } - blockUsed.IncreaseBy(txInfo.BlockSpace) + blockUsed = blockUsed.Add(txInfo.BlockSpace) } return @@ -225,7 +237,7 @@ func (l *Lane) FillProposalBy( } // Update the total size and gas. - blockUsed.IncreaseBy(txInfo.BlockSpace) + blockUsed = blockUsed.Add(txInfo.BlockSpace) } return diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index 20be11964..2ca1afc51 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -124,7 +124,7 @@ func (m *Mempool) fillInitialProposals( for i, lane := range m.lanes { blockUsed, iterator, txs := lane.FillProposal(ctx, proposal) - totalBlockUsed.IncreaseBy(blockUsed) + totalBlockUsed = totalBlockUsed.Add(blockUsed) laneIterators[i] = iterator txsToRemove[i] = txs @@ -149,7 +149,7 @@ func (m *Mempool) fillRemainderProposals( ) // Decrement the remainder for subsequent lanes - remainderLimit.DecreaseBy(blockUsed) + remainderLimit = remainderLimit.Sub(blockUsed) // Append any newly removed transactions to be removed txsToRemove[i] = append(txsToRemove[i], removedTxs...) diff --git a/app/mempool/utils.go b/app/mempool/utils.go deleted file mode 100644 index 1f97e4b14..000000000 --- a/app/mempool/utils.go +++ /dev/null @@ -1,22 +0,0 @@ -package mempool - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// GetDecodedTxs returns the decoded transactions from the given bytes. -func GetDecodedTxs(txDecoder sdk.TxDecoder, txs [][]byte) ([]sdk.Tx, error) { - var decodedTxs []sdk.Tx - for _, txBz := range txs { - tx, err := txDecoder(txBz) - if err != nil { - return nil, fmt.Errorf("failed to decode transaction: %w", err) - } - - decodedTxs = append(decodedTxs, tx) - } - - return decodedTxs, nil -} From 9fc1a7b6c429f15d4ec29f3eee76dd2616162f2c Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 4 Apr 2025 16:21:53 +0700 Subject: [PATCH 17/53] make field in lane, proposal private --- app/mempool/lane.go | 63 ++++++++++++++++++--------------- app/mempool/lane_test.go | 10 +++--- app/mempool/mempool.go | 4 +-- app/mempool/mempool_test.go | 20 +++++------ app/mempool/proposal.go | 42 +++++++++++----------- app/mempool/proposal_handler.go | 8 ++--- 6 files changed, 76 insertions(+), 71 deletions(-) diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 2400e24e6..b99ab6db6 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -18,14 +18,14 @@ import ( // Lane defines a logical grouping of transactions within the mempool. type Lane struct { - Logger log.Logger - TxEncoder sdk.TxEncoder - SignerExtractor sdkmempool.SignerExtractionAdapter - Name string - Match func(ctx sdk.Context, tx sdk.Tx) bool + logger log.Logger + txEncoder sdk.TxEncoder + signerExtractor sdkmempool.SignerExtractionAdapter + name string + matchFn func(ctx sdk.Context, tx sdk.Tx) bool - MaxTransactionSpace math.LegacyDec - MaxLaneSpace math.LegacyDec + maxTransactionSpace math.LegacyDec + maxLaneSpace math.LegacyDec laneMempool sdkmempool.Mempool @@ -49,13 +49,13 @@ func NewLane( laneMempool sdkmempool.Mempool, ) *Lane { return &Lane{ - Logger: logger, - TxEncoder: txEncoder, - SignerExtractor: signerExtractor, - Name: name, - Match: matchFn, - MaxTransactionSpace: maxTransactionSpace, - MaxLaneSpace: maxLaneSpace, + logger: logger, + txEncoder: txEncoder, + signerExtractor: signerExtractor, + name: name, + matchFn: matchFn, + maxTransactionSpace: maxTransactionSpace, + maxLaneSpace: maxLaneSpace, laneMempool: laneMempool, // Initialize the txIndex. @@ -118,6 +118,11 @@ func (l *Lane) Contains(tx sdk.Tx) bool { return exists } +// Match returns true if the transaction belongs to the lane. +func (l *Lane) Match(ctx sdk.Context, tx sdk.Tx) bool { + return l.matchFn(ctx, tx) +} + // FillProposal fills the proposal with transactions from the lane mempool with the its own limit. // It returns the total size and gas of the transactions added to the proposal. // It also returns an iterator to the next transaction in the mempool and a list @@ -131,8 +136,8 @@ func (l *Lane) FillProposal( laneLimit BlockSpace ) // Get the transaction and lane limit for the lane. - transactionLimit = proposal.GetLimit(l.MaxTransactionSpace) - laneLimit = proposal.GetLimit(l.MaxLaneSpace) + transactionLimit = proposal.GetLimit(l.maxTransactionSpace) + laneLimit = proposal.GetLimit(l.maxLaneSpace) // Select all transactions in the mempool that are valid and not already in the // partial proposal. @@ -146,7 +151,7 @@ func (l *Lane) FillProposal( tx := iterator.Tx() txInfo, err := l.GetTxInfo(tx) if err != nil { - l.Logger.Info("failed to get hash of tx", "err", err) + l.logger.Info("failed to get hash of tx", "err", err) txsToRemove = append(txsToRemove, tx) continue @@ -154,10 +159,10 @@ func (l *Lane) FillProposal( // if the transaction is exceed the limit, we remove it from the lane mempool. if transactionLimit.IsExceededBy(txInfo.BlockSpace) { - l.Logger.Info( + l.logger.Info( "failed to select tx for lane; tx exceeds limit", "tx_hash", txInfo.Hash, - "lane", l.Name, + "lane", l.name, ) txsToRemove = append(txsToRemove, tx) @@ -167,9 +172,9 @@ func (l *Lane) FillProposal( // Add the transaction to the proposal. // TODO: check if the transaction cannot be added here, it should also cannot be added afterward. if err := proposal.Add(txInfo); err != nil { - l.Logger.Info( + l.logger.Info( "failed to add tx to proposal", - "lane", l.Name, + "lane", l.name, "tx_hash", txInfo.Hash, "err", err, ) @@ -192,7 +197,7 @@ func (l *Lane) FillProposalBy( laneLimit BlockSpace, ) (blockUsed BlockSpace, txsToRemove []sdk.Tx) { // get the transaction limit for the lane. - transactionLimit := proposal.GetLimit(l.MaxTransactionSpace) + transactionLimit := proposal.GetLimit(l.maxTransactionSpace) // Select all transactions in the mempool that are valid and not already in the // partial proposal. @@ -206,7 +211,7 @@ func (l *Lane) FillProposalBy( tx := iterator.Tx() txInfo, err := l.GetTxInfo(tx) if err != nil { - l.Logger.Info("failed to get hash of tx", "err", err) + l.logger.Info("failed to get hash of tx", "err", err) txsToRemove = append(txsToRemove, tx) continue @@ -214,10 +219,10 @@ func (l *Lane) FillProposalBy( // if the transaction is exceed the limit, we remove it from the lane mempool. if transactionLimit.IsExceededBy(txInfo.BlockSpace) { - l.Logger.Info( + l.logger.Info( "failed to select tx for lane; tx exceeds limit", "tx_hash", txInfo.Hash, - "lane", l.Name, + "lane", l.name, ) txsToRemove = append(txsToRemove, tx) @@ -226,9 +231,9 @@ func (l *Lane) FillProposalBy( // Add the transaction to the proposal. if err := proposal.Add(txInfo); err != nil { - l.Logger.Info( + l.logger.Info( "failed to add tx to proposal", - "lane", l.Name, + "lane", l.name, "tx_hash", txInfo.Hash, "err", err, ) @@ -247,7 +252,7 @@ func (l *Lane) FillProposalBy( // belongs to the lane including its priority, signer's, sequence number, // size and more. func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { - txBytes, err := l.TxEncoder(tx) + txBytes, err := l.txEncoder(tx) if err != nil { return TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) } @@ -258,7 +263,7 @@ func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { return TxWithInfo{}, fmt.Errorf("failed to cast transaction to gas tx") } - signers, err := l.SignerExtractor.GetSigners(tx) + signers, err := l.signerExtractor.GetSigners(tx) if err != nil { return TxWithInfo{}, err } diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index bab25e26d..8a1ae6a21 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -150,11 +150,11 @@ func (s *LaneTestSuite) TestLaneFillProposal() { // The proposal should contain 2 transactions in Txs(). expectedIncludedTxs := s.getTxBytes(tx1, tx2) - s.Require().Equal(2, len(proposal.Txs), "two txs in the proposal") - s.Require().Equal(expectedIncludedTxs, proposal.Txs) + s.Require().Equal(2, len(proposal.txs), "two txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) // Calculate the remaining block space - remainderLimit := proposal.MaxBlockSpace.Sub(proposal.TotalBlockSpace) + remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) // Call FillProposalBy with the remainder limit and iterator from the previous call. blockUsed, txsToRemove = lane.FillProposalBy(&proposal, iterator, remainderLimit) @@ -168,8 +168,8 @@ func (s *LaneTestSuite) TestLaneFillProposal() { // The proposal should contain 2 transactions in Txs(). expectedIncludedTxs = s.getTxBytes(tx1, tx2, tx5, tx6, tx7, tx8) - s.Require().Equal(6, len(proposal.Txs), "two txs in the proposal") - s.Require().Equal(expectedIncludedTxs, proposal.Txs) + s.Require().Equal(6, len(proposal.txs), "two txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) } // ----------------------------------------------------------------------------- diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index 2ca1afc51..85eccc5f8 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -93,7 +93,7 @@ func (m *Mempool) PrepareProposal(ctx sdk.Context, proposal Proposal) (Proposal, laneIterators, txsToRemove, blockUsed := m.fillInitialProposals(cacheCtx, &proposal) // 2) Calculate the remaining block space - remainderLimit := proposal.MaxBlockSpace.Sub(blockUsed) + remainderLimit := proposal.maxBlockSpace.Sub(blockUsed) // 3) Fill proposals with leftover space m.fillRemainderProposals(&proposal, laneIterators, txsToRemove, remainderLimit) @@ -164,7 +164,7 @@ func (m *Mempool) removeTxsFromLanes(txsToRemove [][]sdk.Tx) { if err := lane.Remove(tx); err != nil { m.logger.Error( "failed to remove transactions from lane", - "lane", lane.Name, + "lane", lane.name, "err", err, ) } diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index 3e7cc511f..7a68e9482 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -237,7 +237,7 @@ func (s *MempoolTestSuite) TestNoTransactions() { result, err := mem.PrepareProposal(s.ctx, proposal) s.Require().NoError(err) s.Require().NotNil(result) - s.Require().Equal(0, len(result.Txs)) + s.Require().Equal(0, len(result.txs)) } // TestSingleBankTx ensures a single bank tx is included @@ -266,8 +266,8 @@ func (s *MempoolTestSuite) TestSingleBankTx() { s.Require().NotNil(result) expectedIncludedTxs := s.getTxBytes(tx) - s.Require().Equal(1, len(result.Txs)) - s.Require().Equal(expectedIncludedTxs, result.Txs) + s.Require().Equal(1, len(result.txs)) + s.Require().Equal(expectedIncludedTxs, result.txs) } // TestOneTxPerLane checks a single transaction in each lane type @@ -319,8 +319,8 @@ func (s *MempoolTestSuite) TestOneTxPerLane() { s.Require().NotNil(result) expectedIncludedTxs := s.getTxBytes(tx1, tx2, tx3) - s.Require().Equal(3, len(result.Txs)) - s.Require().Equal(expectedIncludedTxs, result.Txs) + s.Require().Equal(3, len(result.txs)) + s.Require().Equal(expectedIncludedTxs, result.txs) } // TestTxOverLimit checks if a tx over the block limit is rejected @@ -347,7 +347,7 @@ func (s *MempoolTestSuite) TestTxOverLimit() { result, err := mem.PrepareProposal(s.ctx, proposal) s.Require().NoError(err) - s.Require().Equal(0, len(result.Txs)) + s.Require().Equal(0, len(result.txs)) // Ensure the tx is removed for _, lane := range mem.lanes { @@ -427,8 +427,8 @@ func (s *MempoolTestSuite) TestTxsOverGasLimit() { // should not contain the otherTx1 expectedIncludedTxs := s.getTxBytes(bankTx1, bankTx2, delegateTx1, delegateTx2) - s.Require().Equal(4, len(result.Txs)) - s.Require().Equal(expectedIncludedTxs, result.Txs) + s.Require().Equal(4, len(result.txs)) + s.Require().Equal(expectedIncludedTxs, result.txs) } // TestFillUpLeftOverSpace checks if the proposal fills up the remaining space @@ -503,8 +503,8 @@ func (s *MempoolTestSuite) TestFillUpLeftOverSpace() { // should contain bankTx3 as the last tx expectedIncludedTxs := s.getTxBytes(bankTx1, bankTx2, delegateTx1, delegateTx2, bankTx3) - s.Require().Equal(5, len(result.Txs)) - s.Require().Equal(expectedIncludedTxs, result.Txs) + s.Require().Equal(5, len(result.txs)) + s.Require().Equal(expectedIncludedTxs, result.txs) } // ----------------------------------------------------------------------------- diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index a4a9e6422..cea7b89ce 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -16,32 +16,32 @@ const MaxUint64 = 1<<64 - 1 // Proposal represents a block proposal under construction. type Proposal struct { - Logger log.Logger + logger log.Logger // Txs is the list of transactions in the proposal. - Txs [][]byte + txs [][]byte // Cache helps quickly check for duplicates by tx hash. - Cache map[string]struct{} - // MaxBlockSpace is the maximum block space available for this proposal. - MaxBlockSpace BlockSpace - // TotalBlockSpace is the total block space used by the proposal. - TotalBlockSpace BlockSpace + cache map[string]struct{} + // maxBlockSpace is the maximum block space available for this proposal. + maxBlockSpace BlockSpace + // totalBlockSpace is the total block space used by the proposal. + totalBlockSpace BlockSpace } // NewProposal returns a new empty proposal constrained by max block size and max gas limit. func NewProposal(logger log.Logger, maxBlockSize int64, maxGasLimit uint64) Proposal { return Proposal{ - Logger: logger, - Txs: make([][]byte, 0), - Cache: make(map[string]struct{}), - MaxBlockSpace: NewBlockSpace(maxBlockSize, maxGasLimit), - TotalBlockSpace: NewBlockSpace(0, 0), + logger: logger, + txs: make([][]byte, 0), + cache: make(map[string]struct{}), + maxBlockSpace: NewBlockSpace(maxBlockSize, maxGasLimit), + totalBlockSpace: NewBlockSpace(0, 0), } } // Contains returns true if the proposal already has a transaction with the given txHash. func (p *Proposal) Contains(txHash string) bool { - _, ok := p.Cache[txHash] + _, ok := p.cache[txHash] return ok } @@ -51,21 +51,21 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { return fmt.Errorf("transaction already in proposal: %s", txInfo.Hash) } - currentBlockSpace := p.TotalBlockSpace.Add(txInfo.BlockSpace) + currentBlockSpace := p.totalBlockSpace.Add(txInfo.BlockSpace) // Check block size limit - if p.MaxBlockSpace.IsExceededBy(currentBlockSpace) { + if p.maxBlockSpace.IsExceededBy(currentBlockSpace) { return fmt.Errorf( "transaction space exceeds max block space: %s > %s", - currentBlockSpace.String(), p.MaxBlockSpace.String(), + currentBlockSpace.String(), p.maxBlockSpace.String(), ) } // Add transaction - p.Txs = append(p.Txs, txInfo.TxBytes) - p.Cache[txInfo.Hash] = struct{}{} + p.txs = append(p.txs, txInfo.TxBytes) + p.cache[txInfo.Hash] = struct{}{} - p.TotalBlockSpace = currentBlockSpace + p.totalBlockSpace = currentBlockSpace return nil } @@ -74,10 +74,10 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { func (p *Proposal) GetLimit(ratio math.LegacyDec) BlockSpace { // In the case where the ratio is zero, we return the max tx bytes remaining. if ratio.IsZero() { - return p.MaxBlockSpace.Sub(p.TotalBlockSpace) + return p.maxBlockSpace.Sub(p.totalBlockSpace) } - return p.MaxBlockSpace.MulDec(ratio) + return p.maxBlockSpace.MulDec(ratio) } // GetBlockLimits retrieves the maximum block size and gas limit from context. diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go index 0e8f8fc7e..bdc434525 100644 --- a/app/mempool/proposal_handler.go +++ b/app/mempool/proposal_handler.go @@ -67,14 +67,14 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { h.logger.Info( "prepared proposal", - "num_txs", len(finalProposal.Txs), - "total_block_space", finalProposal.TotalBlockSpace.String(), - "max_block_space", finalProposal.MaxBlockSpace.String(), + "num_txs", len(finalProposal.txs), + "total_block_space", finalProposal.totalBlockSpace.String(), + "max_block_space", finalProposal.maxBlockSpace.String(), "height", req.Height, ) return &abci.ResponsePrepareProposal{ - Txs: finalProposal.Txs, + Txs: finalProposal.txs, }, nil } } From 0c8395853f5c9ba728edfc4be8b2d8dec1537ac0 Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 4 Apr 2025 18:05:47 +0700 Subject: [PATCH 18/53] fix from comments --- app/ante.go | 6 +++--- app/lanes.go | 12 ++++++------ app/mempool/lane.go | 6 ++---- app/mempool/mempool_test.go | 8 ++++---- app/mempool/proposal.go | 7 ++++--- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/ante.go b/app/ante.go index b7dd46208..140c65c5c 100644 --- a/app/ante.go +++ b/app/ante.go @@ -139,15 +139,15 @@ func NewIgnoreDecorator(decorator sdk.AnteDecorator, lanes ...*mempool.Lane) *Ig // AnteHandle implements the sdk.AnteDecorator interface. If the transaction belongs to // one of the lanes, the next AnteHandler is called. Otherwise, the decorator's AnteHandler // is called. -func (sd IgnoreDecorator) AnteHandle( +func (ig IgnoreDecorator) AnteHandle( ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler, ) (sdk.Context, error) { cacheCtx, _ := ctx.CacheContext() - for _, lane := range sd.lanes { + for _, lane := range ig.lanes { if lane.Match(cacheCtx, tx) { return next(ctx, tx, simulate) } } - return sd.decorator.AnteHandle(ctx, tx, simulate, next) + return ig.decorator.AnteHandle(ctx, tx, simulate, next) } diff --git a/app/lanes.go b/app/lanes.go index 7d3abc537..51be35970 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -278,9 +278,9 @@ func DefaultLaneMatchHandler() func(sdk.Context, sdk.Tx) bool { // CreateLanes creates the lanes for the Band mempool. func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mempool.Lane) { - // 1. Create the signer extractor. This is used to extract the expected signers from + // Create the signer extractor. This is used to extract the expected signers from // a transaction. Each lane can have a different signer extractor if needed. - signerAdapter := sdkmempool.NewDefaultSignerExtractionAdapter() + signerExtractor := sdkmempool.NewDefaultSignerExtractionAdapter() feedsMsgServer := feedskeeper.NewMsgServerImpl(app.FeedsKeeper) tssMsgServer := tsskeeper.NewMsgServerImpl(app.TSSKeeper) @@ -289,7 +289,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem feedsLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), - signerAdapter, + signerExtractor, "feedsLane", FeedsLaneMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), math.LegacyMustNewDecFromStr("0.05"), @@ -300,7 +300,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem tssLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), - signerAdapter, + signerExtractor, "tssLane", TssLaneMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), math.LegacyMustNewDecFromStr("0.05"), @@ -311,7 +311,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem oracleLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), - signerAdapter, + signerExtractor, "oracleLane", oracleLaneMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), math.LegacyMustNewDecFromStr("0.05"), @@ -322,7 +322,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem defaultLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), - signerAdapter, + signerExtractor, "defaultLane", DefaultLaneMatchHandler(), math.LegacyMustNewDecFromStr("0.3"), diff --git a/app/mempool/lane.go b/app/mempool/lane.go index b99ab6db6..dfe3f0199 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -170,7 +170,6 @@ func (l *Lane) FillProposal( } // Add the transaction to the proposal. - // TODO: check if the transaction cannot be added here, it should also cannot be added afterward. if err := proposal.Add(txInfo); err != nil { l.logger.Info( "failed to add tx to proposal", @@ -257,7 +256,6 @@ func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { return TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) } - // TODO: Add an adapter to lanes so that this can be flexible to support EVM, etc. gasTx, ok := tx.(sdk.FeeTx) if !ok { return TxWithInfo{}, fmt.Errorf("failed to cast transaction to gas tx") @@ -268,11 +266,11 @@ func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { return TxWithInfo{}, err } - BlockSpace := NewBlockSpace(int64(len(txBytes)), gasTx.GetGas()) + blockSpace := NewBlockSpace(int64(len(txBytes)), gasTx.GetGas()) return TxWithInfo{ Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), - BlockSpace: BlockSpace, + BlockSpace: blockSpace, TxBytes: txBytes, Signers: signers, }, nil diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index 7a68e9482..6814dc4bc 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -146,12 +146,12 @@ func (s *MempoolTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { // ----------------------------------------------------------------------------- func (s *MempoolTestSuite) newMempool() *Mempool { - signerAdapter := sdkmempool.NewDefaultSignerExtractionAdapter() + signerExtractor := sdkmempool.NewDefaultSignerExtractionAdapter() BankSendLane := NewLane( log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), - signerAdapter, + signerExtractor, "bankSend", isBankSendTx, math.LegacyMustNewDecFromStr("0.2"), @@ -162,7 +162,7 @@ func (s *MempoolTestSuite) newMempool() *Mempool { DelegateLane := NewLane( log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), - signerAdapter, + signerExtractor, "delegate", isDelegateTx, math.LegacyMustNewDecFromStr("0.2"), @@ -173,7 +173,7 @@ func (s *MempoolTestSuite) newMempool() *Mempool { OtherLane := NewLane( log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), - signerAdapter, + signerExtractor, "other", isOtherTx, math.LegacyMustNewDecFromStr("0.4"), diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index cea7b89ce..4ea5bd6db 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -2,17 +2,18 @@ package mempool import ( "fmt" + "math" comettypes "github.com/cometbft/cometbft/types" "cosmossdk.io/log" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) // MaxUint64 is the maximum value of a uint64. -const MaxUint64 = 1<<64 - 1 +const MaxUint64 = math.MaxUint64 // Proposal represents a block proposal under construction. type Proposal struct { @@ -71,7 +72,7 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { } // GetLimit returns the maximum block space available for a given ratio. -func (p *Proposal) GetLimit(ratio math.LegacyDec) BlockSpace { +func (p *Proposal) GetLimit(ratio sdkmath.LegacyDec) BlockSpace { // In the case where the ratio is zero, we return the max tx bytes remaining. if ratio.IsZero() { return p.maxBlockSpace.Sub(p.totalBlockSpace) From 40a72352e9683ee6279aea3d9906dab51fd91463 Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 4 Apr 2025 18:09:17 +0700 Subject: [PATCH 19/53] fix from comments --- go.mod | 5 +---- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 13c0a7fcb..629699941 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/bandprotocol/chain/v3 -go 1.22.4 - -toolchain go1.22.10 +go 1.22.3 require ( cosmossdk.io/api v0.7.6 @@ -43,7 +41,6 @@ require ( github.com/oasisprotocol/oasis-core/go v0.2202.7 github.com/peterbourgon/diskv v2.0.1+incompatible github.com/prometheus/client_golang v1.20.5 - github.com/skip-mev/block-sdk/v2 v2.1.5 github.com/spf13/cast v1.7.1 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.19.0 diff --git a/go.sum b/go.sum index 0455936a3..12faf2cb1 100644 --- a/go.sum +++ b/go.sum @@ -1000,10 +1000,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skip-mev/block-sdk/v2 v2.1.5 h1:3uoYG2ayP253wiohBPKdD3LrkJGd1Kgw914mmI1ZyOI= -github.com/skip-mev/block-sdk/v2 v2.1.5/go.mod h1:E8SvITZUdxkes3gI3+kgESZL+NLffkcLKnowUgYTOf4= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= From d07815f443dee62b3eb7654d16abf2c66f6c2b89 Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 8 Apr 2025 15:45:14 +0700 Subject: [PATCH 20/53] fix from comments --- app/ante.go | 8 +++-- app/lanes.go | 12 ++++---- app/mempool/block_space.go | 54 ++++++++++++++++++++++----------- app/mempool/lane.go | 33 +++++++++----------- app/mempool/lane_test.go | 2 +- app/mempool/mempool.go | 17 ++++++++--- app/mempool/mempool_test.go | 12 ++++---- app/mempool/proposal.go | 4 +-- app/mempool/proposal_handler.go | 20 ++++++------ 9 files changed, 92 insertions(+), 70 deletions(-) diff --git a/app/ante.go b/app/ante.go index 140c65c5c..12163e091 100644 --- a/app/ante.go +++ b/app/ante.go @@ -136,9 +136,11 @@ func NewIgnoreDecorator(decorator sdk.AnteDecorator, lanes ...*mempool.Lane) *Ig } } -// AnteHandle implements the sdk.AnteDecorator interface. If the transaction belongs to -// one of the lanes, the next AnteHandler is called. Otherwise, the decorator's AnteHandler -// is called. +// NewIgnoreDecorator is a wrapper that implements the sdk.AnteDecorator interface, +// providing two execution paths for processing transactions: +// - If a transaction matches one of the designated bypass lanes, it is forwarded +// directly to the next AnteHandler. +// - Otherwise, the transaction is processed using the embedded decorator’s AnteHandler. func (ig IgnoreDecorator) AnteHandle( ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler, ) (sdk.Context, error) { diff --git a/app/lanes.go b/app/lanes.go index 51be35970..a7b9ddea0 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -19,8 +19,8 @@ import ( tsstypes "github.com/bandprotocol/chain/v3/x/tss/types" ) -// FeedsLaneMatchHandler is a function that returns the match function for the Feeds lane. -func FeedsLaneMatchHandler( +// feedsLaneMatchHandler is a function that returns the match function for the Feeds lane. +func feedsLaneMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, feedsMsgServer feedstypes.MsgServer, @@ -91,8 +91,8 @@ func isValidMsgSubmitSignalPrices( return true } -// TssLaneMatchHandler is a function that returns the match function for the TSS lane. -func TssLaneMatchHandler( +// tssLaneMatchHandler is a function that returns the match function for the TSS lane. +func tssLaneMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, bandtssKeeper *bandtsskeeper.Keeper, @@ -291,7 +291,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem app.txConfig.TxEncoder(), signerExtractor, "feedsLane", - FeedsLaneMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), + feedsLaneMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), @@ -302,7 +302,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem app.txConfig.TxEncoder(), signerExtractor, "tssLane", - TssLaneMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), + tssLaneMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), diff --git a/app/mempool/block_space.go b/app/mempool/block_space.go index 48b5c7a50..69792d6d7 100644 --- a/app/mempool/block_space.go +++ b/app/mempool/block_space.go @@ -2,18 +2,19 @@ package mempool import ( "fmt" + "math" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" ) // BlockSpace defines the block space. type BlockSpace struct { - txBytes int64 + txBytes uint64 gas uint64 } // NewBlockSpace returns a new block space. -func NewBlockSpace(txBytes int64, gas uint64) BlockSpace { +func NewBlockSpace(txBytes uint64, gas uint64) BlockSpace { return BlockSpace{ txBytes: txBytes, gas: gas, @@ -21,7 +22,7 @@ func NewBlockSpace(txBytes int64, gas uint64) BlockSpace { } // --- Getters --- -func (bs BlockSpace) TxBytes() int64 { +func (bs BlockSpace) TxBytes() uint64 { return bs.txBytes } @@ -46,20 +47,22 @@ func (bs BlockSpace) IsExceededBy(other BlockSpace) bool { // Sub returns the difference between this BlockSpace and another BlockSpace. // Ensures txBytes and gas never go below zero. func (bs BlockSpace) Sub(other BlockSpace) BlockSpace { + var txBytes uint64 + var gas uint64 + // Calculate txBytes - txBytes := bs.txBytes - other.txBytes - if txBytes < 0 { + if other.txBytes > bs.txBytes { txBytes = 0 + } else { + txBytes = bs.txBytes - other.txBytes } // Calculate gas if other.gas > bs.gas { - return BlockSpace{ - txBytes: txBytes, - gas: 0, - } + gas = 0 + } else { + gas = bs.gas - other.gas } - gas := bs.gas - other.gas return BlockSpace{ txBytes: txBytes, @@ -69,17 +72,34 @@ func (bs BlockSpace) Sub(other BlockSpace) BlockSpace { // Add returns the sum of this BlockSpace and another BlockSpace. func (bs BlockSpace) Add(other BlockSpace) BlockSpace { + var txBytes uint64 + var gas uint64 + + // Calculate txBytes + if bs.txBytes > math.MaxUint64-other.txBytes { + txBytes = math.MaxUint64 + } else { + txBytes = bs.txBytes + other.txBytes + } + + // Calculate gas + if bs.gas > math.MaxUint64-other.gas { + gas = math.MaxUint64 + } else { + gas = bs.gas + other.gas + } + return BlockSpace{ - txBytes: bs.txBytes + other.txBytes, - gas: bs.gas + other.gas, + txBytes: txBytes, + gas: gas, } } -// MulDec returns a new BlockSpace with txBytes and gas multiplied by a decimal. -func (bs BlockSpace) MulDec(dec math.LegacyDec) BlockSpace { +// Scale returns a new BlockSpace with txBytes and gas multiplied by a decimal. +func (bs BlockSpace) Scale(dec sdkmath.LegacyDec) BlockSpace { return BlockSpace{ - txBytes: dec.MulInt64(bs.txBytes).TruncateInt().Int64(), - gas: dec.MulInt(math.NewIntFromUint64(bs.gas)).TruncateInt().Uint64(), + txBytes: dec.MulInt(sdkmath.NewIntFromUint64(bs.txBytes)).TruncateInt().Uint64(), + gas: dec.MulInt(sdkmath.NewIntFromUint64(bs.gas)).TruncateInt().Uint64(), } } diff --git a/app/mempool/lane.go b/app/mempool/lane.go index dfe3f0199..3ba27e339 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -34,7 +34,7 @@ type Lane struct { txIndex map[string]struct{} // Add mutex for thread safety - mtx sync.RWMutex + mu sync.RWMutex } // NewLane is a constructor for a lane. @@ -70,8 +70,8 @@ func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { return err } - l.mtx.Lock() - defer l.mtx.Unlock() + l.mu.Lock() + defer l.mu.Unlock() if err = l.laneMempool.Insert(ctx, tx); err != nil { return err @@ -93,8 +93,8 @@ func (l *Lane) Remove(tx sdk.Tx) error { return err } - l.mtx.Lock() - defer l.mtx.Unlock() + l.mu.Lock() + defer l.mu.Unlock() if err = l.laneMempool.Remove(tx); err != nil { return err @@ -111,8 +111,8 @@ func (l *Lane) Contains(tx sdk.Tx) bool { return false } - l.mtx.RLock() - defer l.mtx.RUnlock() + l.mu.RLock() + defer l.mu.RUnlock() _, exists := l.txIndex[txInfo.Hash] return exists @@ -123,7 +123,7 @@ func (l *Lane) Match(ctx sdk.Context, tx sdk.Tx) bool { return l.matchFn(ctx, tx) } -// FillProposal fills the proposal with transactions from the lane mempool with the its own limit. +// FillProposal fills the proposal with transactions from the lane mempool with its own limit. // It returns the total size and gas of the transactions added to the proposal. // It also returns an iterator to the next transaction in the mempool and a list // of transactions that were removed from the lane mempool. @@ -131,13 +131,9 @@ func (l *Lane) FillProposal( ctx sdk.Context, proposal *Proposal, ) (blockUsed BlockSpace, iterator sdkmempool.Iterator, txsToRemove []sdk.Tx) { - var ( - transactionLimit BlockSpace - laneLimit BlockSpace - ) // Get the transaction and lane limit for the lane. - transactionLimit = proposal.GetLimit(l.maxTransactionSpace) - laneLimit = proposal.GetLimit(l.maxLaneSpace) + transactionLimit := proposal.GetLimit(l.maxTransactionSpace) + laneLimit := proposal.GetLimit(l.maxLaneSpace) // Select all transactions in the mempool that are valid and not already in the // partial proposal. @@ -187,10 +183,10 @@ func (l *Lane) FillProposal( return } -// FillProposalBy fills the proposal with transactions from the lane mempool with the given iterator and limit. +// FillProposalByIterator fills the proposal with transactions from the lane mempool with the given iterator and limit. // It returns the total size and gas of the transactions added to the proposal. // It also returns a list of transactions that were removed from the lane mempool. -func (l *Lane) FillProposalBy( +func (l *Lane) FillProposalByIterator( proposal *Proposal, iterator sdkmempool.Iterator, laneLimit BlockSpace, @@ -198,8 +194,7 @@ func (l *Lane) FillProposalBy( // get the transaction limit for the lane. transactionLimit := proposal.GetLimit(l.maxTransactionSpace) - // Select all transactions in the mempool that are valid and not already in the - // partial proposal. + // Select all transactions in the mempool that are valid and not already in the partial proposal. for ; iterator != nil; iterator = iterator.Next() { // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. // We can tolerate a few bytes/gas over the limit, since we limit the size of each transaction. @@ -266,7 +261,7 @@ func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { return TxWithInfo{}, err } - blockSpace := NewBlockSpace(int64(len(txBytes)), gasTx.GetGas()) + blockSpace := NewBlockSpace(uint64(len(txBytes)), gasTx.GetGas()) return TxWithInfo{ Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 8a1ae6a21..0440994b1 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -157,7 +157,7 @@ func (s *LaneTestSuite) TestLaneFillProposal() { remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) // Call FillProposalBy with the remainder limit and iterator from the previous call. - blockUsed, txsToRemove = lane.FillProposalBy(&proposal, iterator, remainderLimit) + blockUsed, txsToRemove = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) // We expect tx1, tx2, tx5, tx6, tx7, tx8 to be included in the proposal. s.Require().Equal(int64(884), blockUsed.TxBytes()) diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index 85eccc5f8..84f84a084 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -3,6 +3,7 @@ package mempool import ( "context" "fmt" + "math" "cosmossdk.io/log" @@ -40,9 +41,9 @@ func (m *Mempool) Insert(ctx context.Context, tx sdk.Tx) (err error) { } }() - cacheSdkCtx, _ := sdk.UnwrapSDKContext(ctx).CacheContext() + cacheSDKCtx, _ := sdk.UnwrapSDKContext(ctx).CacheContext() for _, lane := range m.lanes { - if lane.Match(cacheSdkCtx, tx) { + if lane.Match(cacheSDKCtx, tx) { return lane.Insert(ctx, tx) } } @@ -56,10 +57,16 @@ func (m *Mempool) Select(ctx context.Context, txs [][]byte) sdkmempool.Iterator } // CountTx returns the total number of transactions across all lanes. +// Returns math.MaxInt if the total count would overflow. func (m *Mempool) CountTx() int { count := 0 for _, lane := range m.lanes { - count += lane.CountTx() + laneCount := lane.CountTx() + if laneCount > 0 && count > math.MaxInt-laneCount { + // If adding laneCount would cause overflow, return MaxInt + return math.MaxInt + } + count += laneCount } return count } @@ -84,7 +91,7 @@ func (m *Mempool) Remove(tx sdk.Tx) (err error) { } // PrepareProposal divides the block gas limit among lanes (based on lane percentage), -// then calls each lane’s FillProposal method. If leftover gas is important to you, +// then calls each lane's FillProposal method. If leftover gas is important to you, // you can implement a second pass or distribute leftover to subsequent lanes, etc. func (m *Mempool) PrepareProposal(ctx sdk.Context, proposal Proposal) (Proposal, error) { cacheCtx, _ := ctx.CacheContext() @@ -142,7 +149,7 @@ func (m *Mempool) fillRemainderProposals( remainderLimit BlockSpace, ) { for i, lane := range m.lanes { - blockUsed, removedTxs := lane.FillProposalBy( + blockUsed, removedTxs := lane.FillProposalByIterator( proposal, laneIterators[i], remainderLimit, diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index 6814dc4bc..2a85482a9 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -230,7 +230,7 @@ func (s *MempoolTestSuite) TestNoTransactions() { proposal := NewProposal( log.NewTestLogger(s.T()), - s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxBytes), uint64(s.ctx.ConsensusParams().Block.MaxGas), ) @@ -257,7 +257,7 @@ func (s *MempoolTestSuite) TestSingleBankTx() { proposal := NewProposal( log.NewTestLogger(s.T()), - s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxBytes), uint64(s.ctx.ConsensusParams().Block.MaxGas), ) @@ -310,7 +310,7 @@ func (s *MempoolTestSuite) TestOneTxPerLane() { proposal := NewProposal( log.NewTestLogger(s.T()), - s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxBytes), uint64(s.ctx.ConsensusParams().Block.MaxGas), ) @@ -340,7 +340,7 @@ func (s *MempoolTestSuite) TestTxOverLimit() { proposal := NewProposal( log.NewTestLogger(s.T()), - s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxBytes), uint64(s.ctx.ConsensusParams().Block.MaxGas), ) @@ -417,7 +417,7 @@ func (s *MempoolTestSuite) TestTxsOverGasLimit() { proposal := NewProposal( log.NewTestLogger(s.T()), - s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxBytes), uint64(s.ctx.ConsensusParams().Block.MaxGas), ) @@ -493,7 +493,7 @@ func (s *MempoolTestSuite) TestFillUpLeftOverSpace() { proposal := NewProposal( log.NewTestLogger(s.T()), - s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxBytes), uint64(s.ctx.ConsensusParams().Block.MaxGas), ) diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index 4ea5bd6db..a39f400f8 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -30,7 +30,7 @@ type Proposal struct { } // NewProposal returns a new empty proposal constrained by max block size and max gas limit. -func NewProposal(logger log.Logger, maxBlockSize int64, maxGasLimit uint64) Proposal { +func NewProposal(logger log.Logger, maxBlockSize uint64, maxGasLimit uint64) Proposal { return Proposal{ logger: logger, txs: make([][]byte, 0), @@ -78,7 +78,7 @@ func (p *Proposal) GetLimit(ratio sdkmath.LegacyDec) BlockSpace { return p.maxBlockSpace.Sub(p.totalBlockSpace) } - return p.maxBlockSpace.MulDec(ratio) + return p.maxBlockSpace.Scale(ratio) } // GetBlockLimits retrieves the maximum block size and gas limit from context. diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go index bdc434525..fd90dd932 100644 --- a/app/mempool/proposal_handler.go +++ b/app/mempool/proposal_handler.go @@ -11,15 +11,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -type ( - // ProposalHandler wraps ABCI++ PrepareProposal/ProcessProposal for the Mempool. - ProposalHandler struct { - logger log.Logger - txDecoder sdk.TxDecoder - Mempool *Mempool - useCustomProcessProposal bool - } -) +// ProposalHandler wraps ABCI++ PrepareProposal/ProcessProposal for the Mempool. +type ProposalHandler struct { + logger log.Logger + txDecoder sdk.TxDecoder + Mempool *Mempool + useCustomProcessProposal bool +} // NewProposalHandler returns a new ABCI++ proposal handler for the Mempool. func NewProposalHandler( @@ -55,13 +53,13 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { // Gather block limits _, maxGasLimit := GetBlockLimits(ctx) - proposal := NewProposal(h.logger, req.MaxTxBytes, maxGasLimit) + proposal := NewProposal(h.logger, uint64(req.MaxTxBytes), maxGasLimit) // Populate proposal from Mempool finalProposal, err := h.Mempool.PrepareProposal(ctx, proposal) if err != nil { // If an error occurs, we can still return what we have or choose to return nothing - h.logger.Error("failed to prepare proposal", "err", err) + h.logger.Error("failed to prepare proposal", "err", err) return &abci.ResponsePrepareProposal{Txs: [][]byte{}}, err } From c5f05afb3498b6f7ca076809ecace4ce3a10e282 Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 8 Apr 2025 15:59:01 +0700 Subject: [PATCH 21/53] fix test --- app/mempool/lane_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 0440994b1..d6adbe9b9 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -142,7 +142,7 @@ func (s *LaneTestSuite) TestLaneFillProposal() { // We expect tx1 and tx2 to be included in the proposal. // Then the gas should be over the limit, so tx3 is yet to be considered. - s.Require().Equal(int64(440), blockUsed.TxBytes()) + s.Require().Equal(uint64(440), blockUsed.TxBytes()) s.Require().Equal(uint64(40), blockUsed.Gas(), "20 gas from tx1 and 20 gas from tx2") s.Require().NotNil(iterator) s.Require(). @@ -160,7 +160,7 @@ func (s *LaneTestSuite) TestLaneFillProposal() { blockUsed, txsToRemove = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) // We expect tx1, tx2, tx5, tx6, tx7, tx8 to be included in the proposal. - s.Require().Equal(int64(884), blockUsed.TxBytes()) + s.Require().Equal(uint64(884), blockUsed.TxBytes()) s.Require().Equal(uint64(60), blockUsed.Gas()) s.Require().Equal([]sdk.Tx{tx3, tx4}, txsToRemove) s.Require(). From 4162d4280b390b275e57db5c6d6426f21710b53e Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 8 Apr 2025 17:38:41 +0700 Subject: [PATCH 22/53] remove logic in lane match function to reduce gas used --- app/ante.go | 4 +-- app/lanes.go | 91 ++++------------------------------------------------ 2 files changed, 8 insertions(+), 87 deletions(-) diff --git a/app/ante.go b/app/ante.go index 98b60fc45..37409bd21 100644 --- a/app/ante.go +++ b/app/ante.go @@ -145,11 +145,11 @@ func (ig IgnoreDecorator) AnteHandle( ) (sdk.Context, error) { // IgnoreDecorator is only used for check tx. if !ctx.IsCheckTx() { - return sd.decorator.AnteHandle(ctx, tx, simulate, next) + return ig.decorator.AnteHandle(ctx, tx, simulate, next) } cacheCtx, _ := ctx.CacheContext() - for _, matchFn := range sd.matchFns { + for _, matchFn := range ig.matchFns { if matchFn(cacheCtx, tx) { return next(ctx, tx, simulate) } diff --git a/app/lanes.go b/app/lanes.go index f570bc179..46554a3fe 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -61,28 +61,7 @@ func isMsgSubmitSignalPrices( return false } - grantee, err := sdk.AccAddressFromBech32(msg.Grantee) - if err != nil { - return false - } - for _, m := range msgs { - signers, _, err := cdc.GetMsgV1Signers(m) - if err != nil { - return false - } - // Check if this grantee have authorization for the message. - cap, _ := authzKeeper.GetAuthorization( - ctx, - grantee, - sdk.AccAddress(signers[0]), - sdk.MsgTypeURL(m), - ) - if cap == nil { - return false - } - - // Check if this message should be free or not. if !isMsgSubmitSignalPrices(ctx, m, cdc, authzKeeper) { return false } @@ -133,28 +112,12 @@ func isTssLaneMsg( bandtssKeeper *bandtsskeeper.Keeper, ) bool { switch msg := msg.(type) { - case *tsstypes.MsgSubmitDKGRound1: - return true - case *tsstypes.MsgSubmitDKGRound2: - return true - case *tsstypes.MsgConfirm: - return true - case *tsstypes.MsgComplain: - return true - case *tsstypes.MsgSubmitDEs: - acc, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return false - } - - currentGroupID := bandtssKeeper.GetCurrentGroup(ctx).GroupID - incomingGroupID := bandtssKeeper.GetIncomingGroupID(ctx) - if !bandtssKeeper.HasMember(ctx, acc, currentGroupID) && - !bandtssKeeper.HasMember(ctx, acc, incomingGroupID) { - return false - } - return true - case *tsstypes.MsgSubmitSignature: + case *tsstypes.MsgSubmitDKGRound1, + *tsstypes.MsgSubmitDKGRound2, + *tsstypes.MsgConfirm, + *tsstypes.MsgComplain, + *tsstypes.MsgSubmitDEs, + *tsstypes.MsgSubmitSignature: return true case *authz.MsgExec: msgs, err := msg.GetMessages() @@ -162,28 +125,7 @@ func isTssLaneMsg( return false } - grantee, err := sdk.AccAddressFromBech32(msg.Grantee) - if err != nil { - return false - } - for _, m := range msgs { - signers, _, err := cdc.GetMsgV1Signers(m) - if err != nil { - return false - } - // Check if this grantee have authorization for the message. - cap, _ := authzKeeper.GetAuthorization( - ctx, - grantee, - sdk.AccAddress(signers[0]), - sdk.MsgTypeURL(m), - ) - if cap == nil { - return false - } - - // Check if this message should be free or not. if !isTssLaneMsg(ctx, m, cdc, authzKeeper, bandtssKeeper) { return false } @@ -240,28 +182,7 @@ func isMsgReportData( return false } - grantee, err := sdk.AccAddressFromBech32(msg.Grantee) - if err != nil { - return false - } - for _, m := range msgs { - signers, _, err := cdc.GetMsgV1Signers(m) - if err != nil { - return false - } - // Check if this grantee have authorization for the message. - cap, _ := authzKeeper.GetAuthorization( - ctx, - grantee, - sdk.AccAddress(signers[0]), - sdk.MsgTypeURL(m), - ) - if cap == nil { - return false - } - - // Check if this message should be free or not. if !isMsgReportData(ctx, m, cdc, authzKeeper) { return false } From 82b228a5cf36e6f1b1b3dd9330bef86b08b1f3bf Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 8 Apr 2025 17:45:16 +0700 Subject: [PATCH 23/53] add context for matchFns option --- app/ante.go | 22 +++++++++++----------- app/app.go | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/ante.go b/app/ante.go index 37409bd21..6f139c40f 100644 --- a/app/ante.go +++ b/app/ante.go @@ -23,16 +23,16 @@ import ( // channel keeper. type HandlerOptions struct { ante.HandlerOptions - Cdc codec.Codec - AuthzKeeper *authzkeeper.Keeper - OracleKeeper *oraclekeeper.Keeper - IBCKeeper *ibckeeper.Keeper - StakingKeeper *stakingkeeper.Keeper - GlobalfeeKeeper *globalfeekeeper.Keeper - TSSKeeper *tsskeeper.Keeper - BandtssKeeper *bandtsskeeper.Keeper - FeedsKeeper *feedskeeper.Keeper - MatchFns []func(sdk.Context, sdk.Tx) bool + Cdc codec.Codec + AuthzKeeper *authzkeeper.Keeper + OracleKeeper *oraclekeeper.Keeper + IBCKeeper *ibckeeper.Keeper + StakingKeeper *stakingkeeper.Keeper + GlobalfeeKeeper *globalfeekeeper.Keeper + TSSKeeper *tsskeeper.Keeper + BandtssKeeper *bandtsskeeper.Keeper + FeedsKeeper *feedskeeper.Keeper + IgnoreDecoratorMatchFns []func(sdk.Context, sdk.Tx) bool } func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { @@ -106,7 +106,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { options.FeegrantKeeper, options.TxFeeChecker, ), - options.MatchFns..., + options.IgnoreDecoratorMatchFns..., ), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewSetPubKeyDecorator(options.AccountKeeper), diff --git a/app/app.go b/app/app.go index 09bc50dd1..b96fd64e0 100644 --- a/app/app.go +++ b/app/app.go @@ -291,7 +291,7 @@ func NewBandApp( IBCKeeper: app.IBCKeeper, StakingKeeper: app.StakingKeeper, GlobalfeeKeeper: &app.GlobalFeeKeeper, - MatchFns: []func(sdk.Context, sdk.Tx) bool{ + IgnoreDecoratorMatchFns: []func(sdk.Context, sdk.Tx) bool{ FeedsSubmitSignalPriceTxMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), TssTxMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), oracleReportTxMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), From aa598c7a9f5361bbc25233676972d7f0212337a4 Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 8 Apr 2025 18:02:35 +0700 Subject: [PATCH 24/53] fix matchFn names --- app/app.go | 4 ++-- app/match.go | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/app.go b/app/app.go index b96fd64e0..dadb4dce7 100644 --- a/app/app.go +++ b/app/app.go @@ -292,8 +292,8 @@ func NewBandApp( StakingKeeper: app.StakingKeeper, GlobalfeeKeeper: &app.GlobalFeeKeeper, IgnoreDecoratorMatchFns: []func(sdk.Context, sdk.Tx) bool{ - FeedsSubmitSignalPriceTxMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), - TssTxMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), + feedsSubmitSignalPriceTxMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), + tssTxMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), oracleReportTxMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), }, }, diff --git a/app/match.go b/app/match.go index a1bdbad8c..3be550808 100644 --- a/app/match.go +++ b/app/match.go @@ -12,8 +12,8 @@ import ( tsstypes "github.com/bandprotocol/chain/v3/x/tss/types" ) -// FeedsSubmitSignalPriceTxMatchHandler is a function that returns the match function for the Feeds SubmitSignalPriceTx. -func FeedsSubmitSignalPriceTxMatchHandler( +// feedsSubmitSignalPriceTxMatchHandler is a function that returns the match function for the Feeds SubmitSignalPriceTx. +func feedsSubmitSignalPriceTxMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, feedsMsgServer feedstypes.MsgServer, @@ -84,8 +84,8 @@ func isValidMsgSubmitSignalPrices( return true } -// TssTxMatchHandler is a function that returns the match function for the TSS Tx. -func TssTxMatchHandler( +// tssTxMatchHandler is a function that returns the match function for the TSS Tx. +func tssTxMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, bandtssKeeper *bandtsskeeper.Keeper, @@ -97,7 +97,7 @@ func TssTxMatchHandler( return false } for _, msg := range msgs { - if !isValidTssTxMsg(ctx, msg, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { + if !isValidTSSTxMsg(ctx, msg, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { return false } } @@ -105,8 +105,8 @@ func TssTxMatchHandler( } } -// isValidTssTxMsg return true if the message is a valid for TSS Tx. -func isValidTssTxMsg( +// isValidTSSTxMsg return true if the message is a valid for TSS Tx. +func isValidTSSTxMsg( ctx sdk.Context, msg sdk.Msg, cdc codec.Codec, @@ -179,7 +179,7 @@ func isValidTssTxMsg( } // Check if this message should be free or not. - if !isValidTssTxMsg(ctx, m, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { + if !isValidTSSTxMsg(ctx, m, cdc, authzKeeper, bandtssKeeper, tssMsgServer) { return false } } From 5e66f6a35ea3c703e08fbbb56b1b8c20d78448ba Mon Sep 17 00:00:00 2001 From: Nattapat Iammelap Date: Thu, 10 Apr 2025 15:25:09 +0700 Subject: [PATCH 25/53] fix typo --- app/lanes.go | 4 +-- app/mempool/lane.go | 46 +++++++++++++-------------- app/mempool/lane_test.go | 62 ++++++++++++++++++------------------- app/mempool/mempool_test.go | 16 +++++----- 4 files changed, 64 insertions(+), 64 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index a0af4c9c9..fa59d1ae3 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -299,8 +299,8 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), - func(isFilled bool) { - oracleRequestLane.SetIsBlocked(isFilled) + func(isLaneLimitExceeded bool) { + oracleRequestLane.SetBlocked(isLaneLimitExceeded) }, ) diff --git a/app/mempool/lane.go b/app/mempool/lane.go index a3c134d5d..0c96c4222 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -33,10 +33,10 @@ type Lane struct { // currently in this lane's mempool. txIndex map[string]struct{} - // OnFilled is a callback function that is called when the lane exceeds its limit. - OnFilled func(isFilled bool) + // handleLaneLimitCheck is a callback function that is called when the lane exceeds its limit. + handleLaneLimitCheck func(isLaneLimitExceeded bool) - isBlocked bool + blocked bool // Add mutex for thread safety mu sync.RWMutex @@ -52,23 +52,23 @@ func NewLane( maxTransactionSpace math.LegacyDec, maxLaneSpace math.LegacyDec, laneMempool sdkmempool.Mempool, - onFilled func(isFilled bool), + handleLaneLimitCheck func(isLaneLimitExceeded bool), ) *Lane { return &Lane{ - logger: logger, - txEncoder: txEncoder, - signerExtractor: signerExtractor, - name: name, - matchFn: matchFn, - maxTransactionSpace: maxTransactionSpace, - maxLaneSpace: maxLaneSpace, - laneMempool: laneMempool, - OnFilled: onFilled, + logger: logger, + txEncoder: txEncoder, + signerExtractor: signerExtractor, + name: name, + matchFn: matchFn, + maxTransactionSpace: maxTransactionSpace, + maxLaneSpace: maxLaneSpace, + laneMempool: laneMempool, + handleLaneLimitCheck: handleLaneLimitCheck, // Initialize the txIndex. txIndex: make(map[string]struct{}), - isBlocked: false, + blocked: false, } } @@ -144,9 +144,9 @@ func (l *Lane) FillProposal( transactionLimit := proposal.GetLimit(l.maxTransactionSpace) laneLimit := proposal.GetLimit(l.maxLaneSpace) - isFilled := false + isLaneLimitExceeded := false - if l.isBlocked { + if l.blocked { return } @@ -196,11 +196,11 @@ func (l *Lane) FillProposal( } if laneLimit.IsReachedBy(blockUsed) { - isFilled = true + isLaneLimitExceeded = true } - if l.OnFilled != nil { - l.OnFilled(isFilled) + if l.handleLaneLimitCheck != nil { + l.handleLaneLimitCheck(isLaneLimitExceeded) } return @@ -217,7 +217,7 @@ func (l *Lane) FillProposalByIterator( // get the transaction limit for the lane. transactionLimit := proposal.GetLimit(l.maxTransactionSpace) - if l.isBlocked { + if l.blocked { return } @@ -298,7 +298,7 @@ func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { }, nil } -// SetIsBlocked sets the isBlocked flag to the given value. -func (l *Lane) SetIsBlocked(isBlocked bool) { - l.isBlocked = isBlocked +// SetBlocked sets the blocked flag to the given value. +func (l *Lane) SetBlocked(blocked bool) { + l.blocked = blocked } diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 46920bba6..eda15394f 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -175,16 +175,16 @@ func (s *LaneTestSuite) TestLaneFillProposal() { s.Require().Equal(expectedIncludedTxs, proposal.txs) } -type onFilledMock struct { - isFilled bool +type handleLaneLimitCheckMock struct { + isLaneLimitExceeded bool } -func (f *onFilledMock) OnFilled(isFilled bool) { - f.isFilled = isFilled +func (f *handleLaneLimitCheckMock) handleLaneLimitCheck(isLaneLimitExceeded bool) { + f.isLaneLimitExceeded = isLaneLimitExceeded } -func (s *LaneTestSuite) TestLaneOnFilled() { - onFilledMock := &onFilledMock{} +func (s *LaneTestSuite) TestLaneHandleLaneLimitCheck() { + handleLaneLimitCheckMock := &handleLaneLimitCheckMock{} lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -194,7 +194,7 @@ func (s *LaneTestSuite) TestLaneOnFilled() { math.LegacyMustNewDecFromStr("0.3"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), - onFilledMock.OnFilled, + handleLaneLimitCheckMock.handleLaneLimitCheck, ) // Insert a transaction @@ -218,10 +218,10 @@ func (s *LaneTestSuite) TestLaneOnFilled() { // The proposal should contain 1 transaction in Txs(). expectedIncludedTxs := s.getTxBytes(tx1) - s.Require().Equal(1, len(proposal.Txs), "one txs in the proposal") - s.Require().Equal(expectedIncludedTxs, proposal.Txs) + s.Require().Equal(1, len(proposal.txs), "one txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().False(onFilledMock.isFilled, "onFilled should be called with false") + s.Require().False(handleLaneLimitCheckMock.isLaneLimitExceeded, "handleLaneLimitCheck should be called with false") // Insert 2 more transactions tx2 := s.createSimpleTx(s.accounts[1], 1, 20) @@ -247,14 +247,14 @@ func (s *LaneTestSuite) TestLaneOnFilled() { // The proposal should contain 2 transactions in Txs(). expectedIncludedTxs = s.getTxBytes(tx1, tx2) - s.Require().Equal(2, len(proposal.Txs), "two txs in the proposal") - s.Require().Equal(expectedIncludedTxs, proposal.Txs) + s.Require().Equal(2, len(proposal.txs), "two txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().True(onFilledMock.isFilled, "onFilled should be called with true") + s.Require().True(handleLaneLimitCheckMock.isLaneLimitExceeded, "OoLaneLimitExceeded should be called with true") } func (s *LaneTestSuite) TestLaneExactlyFilled() { - onFilledMock := &onFilledMock{} + handleLaneLimitCheckMock := &handleLaneLimitCheckMock{} lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -264,7 +264,7 @@ func (s *LaneTestSuite) TestLaneExactlyFilled() { math.LegacyMustNewDecFromStr("0.3"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), - onFilledMock.OnFilled, + handleLaneLimitCheckMock.handleLaneLimitCheck, ) // Insert a transaction @@ -288,10 +288,10 @@ func (s *LaneTestSuite) TestLaneExactlyFilled() { // The proposal should contain 1 transaction in Txs(). expectedIncludedTxs := s.getTxBytes(tx1) - s.Require().Equal(1, len(proposal.Txs), "one txs in the proposal") - s.Require().Equal(expectedIncludedTxs, proposal.Txs) + s.Require().Equal(1, len(proposal.txs), "one txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().False(onFilledMock.isFilled, "onFilled should be called with false") + s.Require().False(handleLaneLimitCheckMock.isLaneLimitExceeded, "handleLaneLimitCheck should be called with false") // Insert 2 more transactions tx2 := s.createSimpleTx(s.accounts[1], 1, 10) @@ -317,10 +317,10 @@ func (s *LaneTestSuite) TestLaneExactlyFilled() { // The proposal should contain 2 transactions in Txs(). expectedIncludedTxs = s.getTxBytes(tx1, tx2) - s.Require().Equal(2, len(proposal.Txs), "two txs in the proposal") - s.Require().Equal(expectedIncludedTxs, proposal.Txs) + s.Require().Equal(2, len(proposal.txs), "two txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().True(onFilledMock.isFilled, "onFilled should be called with true") + s.Require().True(handleLaneLimitCheckMock.isLaneLimitExceeded, "handleLaneLimitCheck should be called with true") } func (s *LaneTestSuite) TestLaneBlocked() { @@ -337,7 +337,7 @@ func (s *LaneTestSuite) TestLaneBlocked() { nil, ) - lane.SetIsBlocked(true) + lane.SetBlocked(true) // Insert 3 transactions tx1 := s.createSimpleTx(s.accounts[0], 0, 20) @@ -358,10 +358,10 @@ func (s *LaneTestSuite) TestLaneBlocked() { // FillProposal blockUsed, iterator, txsToRemove := lane.FillProposal(s.ctx, &proposal) - s.Require().True(lane.isBlocked) + s.Require().True(lane.blocked) // We expect no txs to be included in the proposal. - s.Require().Equal(int64(0), blockUsed.TxBytes()) + s.Require().Equal(uint64(0), blockUsed.TxBytes()) s.Require().Equal(uint64(0), blockUsed.Gas(), "0 gas") s.Require().Nil(iterator) s.Require(). @@ -369,27 +369,27 @@ func (s *LaneTestSuite) TestLaneBlocked() { // The proposal should contain 0 transactions in Txs(). expectedIncludedTxs := [][]byte{} - s.Require().Equal(0, len(proposal.Txs), "no txs in the proposal") - s.Require().Equal(expectedIncludedTxs, proposal.Txs) + s.Require().Equal(0, len(proposal.txs), "no txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) s.Require().Equal(lane.laneMempool.Select(s.ctx, nil).Tx(), tx1) // Calculate the remaining block space - remainderLimit := proposal.MaxBlockSpace.Sub(proposal.TotalBlockSpace) + remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) // Call FillProposalBy with the remainder limit and iterator from the previous call. - blockUsed, txsToRemove = lane.FillProposalBy(&proposal, iterator, remainderLimit) + blockUsed, txsToRemove = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) // We expect no txs to be included in the proposal. - s.Require().Equal(int64(0), blockUsed.TxBytes()) + s.Require().Equal(uint64(0), blockUsed.TxBytes()) s.Require().Equal(uint64(0), blockUsed.Gas()) s.Require(). Len(txsToRemove, 0, "no txs are removed") // The proposal should contain 0 transactions in Txs(). expectedIncludedTxs = [][]byte{} - s.Require().Equal(0, len(proposal.Txs), "no txs in the proposal") - s.Require().Equal(expectedIncludedTxs, proposal.Txs) + s.Require().Equal(0, len(proposal.txs), "no txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) s.Require().Equal(lane.laneMempool.Select(s.ctx, nil).Tx(), tx1) } diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index 9108d36ff..bffea1f6e 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -534,8 +534,8 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { math.LegacyMustNewDecFromStr("0.5"), math.LegacyMustNewDecFromStr("0.5"), sdkmempool.DefaultPriorityMempool(), - func(isFilled bool) { - DependentLane.SetIsBlocked(isFilled) + func(isLaneLimitExceeded bool) { + DependentLane.SetBlocked(isLaneLimitExceeded) }, ) @@ -572,7 +572,7 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { proposal := NewProposal( log.NewTestLogger(s.T()), - s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxBytes), uint64(s.ctx.ConsensusParams().Block.MaxGas), ) @@ -581,8 +581,8 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { s.Require().NotNil(result) expectedIncludedTxs := s.getTxBytes(bankTx1, MixedTx1) - s.Require().Equal(2, len(result.Txs)) - s.Require().Equal(expectedIncludedTxs, result.Txs) + s.Require().Equal(2, len(result.txs)) + s.Require().Equal(expectedIncludedTxs, result.txs) bankTx2, err := CreateBankSendTx( s.encodingConfig.TxConfig, @@ -598,7 +598,7 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { proposal = NewProposal( log.NewTestLogger(s.T()), - s.ctx.ConsensusParams().Block.MaxBytes, + uint64(s.ctx.ConsensusParams().Block.MaxBytes), uint64(s.ctx.ConsensusParams().Block.MaxGas), ) @@ -607,8 +607,8 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { s.Require().NotNil(result) expectedIncludedTxs = s.getTxBytes(bankTx1, bankTx2) - s.Require().Equal(2, len(result.Txs)) - s.Require().Equal(expectedIncludedTxs, result.Txs) + s.Require().Equal(2, len(result.txs)) + s.Require().Equal(expectedIncludedTxs, result.txs) } // ----------------------------------------------------------------------------- From 7f340d4808d9357adda578a71be8134009e388f0 Mon Sep 17 00:00:00 2001 From: Marca23 <141217300+Marca23@users.noreply.github.com> Date: Wed, 16 Apr 2025 15:29:06 +0700 Subject: [PATCH 26/53] [Fix][Mempool] Fix invalid block sequence (#614) * Add Contain method to Mempool * Implement custom CheckTx method for BandApp * change feeds lane mempool type to sender nonce * set SenderNonceMempool maxTx * set max tx for feeds lane mempool to unlimited --------- Co-authored-by: Nattapat Iammelap --- app/app.go | 67 ++++++++++++++++++++++++++++++++++++++++++ app/lanes.go | 2 +- app/mempool/mempool.go | 18 ++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/app/app.go b/app/app.go index dadb4dce7..4fe15acd3 100644 --- a/app/app.go +++ b/app/app.go @@ -40,6 +40,7 @@ import ( "github.com/cosmos/cosmos-sdk/std" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/msgservice" sigtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" @@ -537,3 +538,69 @@ func (app *BandApp) AutoCliOpts() autocli.AppOptions { ConsensusAddressCodec: authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()), } } + +// CheckTx returns a CheckTx handler that wraps a given CheckTx handler and evicts txs that are not +// in the app-side mempool on ReCheckTx. +func (app *BandApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { + // decode tx + tx, err := app.BaseApp.TxDecode(req.Tx) + if err != nil { + return sdkerrors.ResponseCheckTxWithEvents( + fmt.Errorf("failed to decode tx: %w", err), + 0, + 0, + nil, + false, + ), nil + } + + mempool := app.Mempool().(*mempool.Mempool) + + isRecheck := req.Type == abci.CheckTxType_Recheck + txInMempool := mempool.Contains(tx) + + // if the mode is ReCheck and the app's mempool does not contain the given tx, we fail + // immediately, to purge the tx from the comet mempool. + if isRecheck && !txInMempool { + app.Logger().Debug( + "tx from comet mempool not found in app-side mempool", + "tx", tx, + ) + + return sdkerrors.ResponseCheckTxWithEvents( + fmt.Errorf("tx from comet mempool not found in app-side mempool"), + 0, + 0, + nil, + false, + ), nil + } + + // prepare cleanup closure to remove tx if marked + removeTx := false + defer func() { + if removeTx { + // remove the tx + if err := mempool.Remove(tx); err != nil { + app.Logger().Debug( + "failed to remove tx from app-side mempool when purging for re-check failure", + "removal-err", err, + ) + } + } + }() + + // run the checkTxHandler + res, checkTxError := app.BaseApp.CheckTx(req) + // if re-check fails for a transaction, we'll need to explicitly purge the tx from + // the app-side mempool + if isInvalidCheckTxExecution(res, checkTxError) && isRecheck && txInMempool { + removeTx = true + } + + return res, checkTxError +} + +func isInvalidCheckTxExecution(resp *abci.ResponseCheckTx, checkTxErr error) bool { + return resp == nil || resp.Code != 0 || checkTxErr != nil +} diff --git a/app/lanes.go b/app/lanes.go index 46554a3fe..6f5d5d91e 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -215,7 +215,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleLane, defaultLane *mem feedsLaneMatchHandler(app.appCodec, &app.AuthzKeeper), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.3"), - sdkmempool.DefaultPriorityMempool(), + sdkmempool.NewSenderNonceMempool(sdkmempool.SenderNonceMaxTxOpt(0)), ) tssLane = mempool.NewLane( diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index f1ff53b81..e42d0545b 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -178,3 +178,21 @@ func (m *Mempool) removeTxsFromLanes(txsToRemove [][]sdk.Tx) { } } } + +// Contains returns true if the transaction is contained in any of the lanes. +func (m *Mempool) Contains(tx sdk.Tx) (contains bool) { + defer func() { + if r := recover(); r != nil { + m.logger.Error("panic in Contains", "err", r) + contains = false + } + }() + + for _, lane := range m.lanes { + if lane.Contains(tx) { + return true + } + } + + return false +} From 2ed34e2139c0b4b0e300a360f75c8bfd7d5ccd54 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 17 Apr 2025 17:43:37 +0700 Subject: [PATCH 27/53] fix comments --- app/app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/app.go b/app/app.go index 4fe15acd3..e99768972 100644 --- a/app/app.go +++ b/app/app.go @@ -559,7 +559,7 @@ func (app *BandApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er isRecheck := req.Type == abci.CheckTxType_Recheck txInMempool := mempool.Contains(tx) - // if the mode is ReCheck and the app's mempool does not contain the given tx, we fail + // if the mode is Recheck and the app's mempool does not contain the given tx, we fail // immediately, to purge the tx from the comet mempool. if isRecheck && !txInMempool { app.Logger().Debug( @@ -592,7 +592,7 @@ func (app *BandApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, er // run the checkTxHandler res, checkTxError := app.BaseApp.CheckTx(req) - // if re-check fails for a transaction, we'll need to explicitly purge the tx from + // if Recheck fails for a transaction, we'll need to explicitly purge the tx from // the app-side mempool if isInvalidCheckTxExecution(res, checkTxError) && isRecheck && txInMempool { removeTx = true From 136286c9b8c471d9a295492fa64d9b7b01ef5e5e Mon Sep 17 00:00:00 2001 From: Nattapat Iammelap Date: Fri, 18 Apr 2025 15:37:32 +0700 Subject: [PATCH 28/53] add comments for mempool lanes --- app/lanes.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/lanes.go b/app/lanes.go index 94e8b57fb..9c6a3029e 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -254,6 +254,9 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ // a transaction. Each lane can have a different signer extractor if needed. signerExtractor := sdkmempool.NewDefaultSignerExtractionAdapter() + // feedsLane handles feeds submit signal price transactions. + // Each transaction has a gas limit of 0.02, and the total gas limit for the lane is 0.5. + // It uses SenderNonceMempool to ensure transactions are ordered by sender and nonce, with no per-sender tx limit. feedsLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), @@ -266,6 +269,8 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ nil, ) + // tssLane handles TSS transactions. + // Each transaction has a gas limit of 0.02, and the total gas limit for the lane is 0.2. tssLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), @@ -278,6 +283,9 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ nil, ) + // oracleRequestLane handles oracle request data transactions. + // Each transaction has a gas limit of 0.1, and the total gas limit for the lane is 0.1. + // It is blocked if the oracle report lane exceeds its limit. oracleRequestLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), @@ -290,6 +298,9 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ nil, ) + // oracleReportLane handles oracle report data transactions. + // Each transaction has a gas limit of 0.05, and the total gas limit for the lane is 0.2. + // It block the oracle request lane if it exceeds its limit. oracleReportLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), @@ -304,6 +315,8 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ }, ) + // defaultLane handles all other transactions. + // Each transaction has a gas limit of 0.1, and the total gas limit for the lane is 0.1. defaultLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), From b84bf7e8ab2fd97325b12241bf2443f4c66bcc34 Mon Sep 17 00:00:00 2001 From: Nattapat Iammelap Date: Mon, 21 Apr 2025 10:38:25 +0700 Subject: [PATCH 29/53] fix comment --- app/lanes.go | 12 ++++++------ app/mempool/lane.go | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index 9c6a3029e..137d52663 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -213,7 +213,7 @@ func oracleRequestLaneMatchHandler( } } -// isValidMsgRequestData return true if the message is a valid oracle's MsgRequestData. +// isMsgRequestData return true if the message is a valid oracle's MsgRequestData. func isMsgRequestData( ctx sdk.Context, msg sdk.Msg, @@ -255,7 +255,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ signerExtractor := sdkmempool.NewDefaultSignerExtractionAdapter() // feedsLane handles feeds submit signal price transactions. - // Each transaction has a gas limit of 0.02, and the total gas limit for the lane is 0.5. + // Each transaction has a gas limit of 2%, and the total gas limit for the lane is 50%. // It uses SenderNonceMempool to ensure transactions are ordered by sender and nonce, with no per-sender tx limit. feedsLane = mempool.NewLane( app.Logger(), @@ -270,7 +270,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ ) // tssLane handles TSS transactions. - // Each transaction has a gas limit of 0.02, and the total gas limit for the lane is 0.2. + // Each transaction has a gas limit of 2%, and the total gas limit for the lane is 20%. tssLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), @@ -284,7 +284,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ ) // oracleRequestLane handles oracle request data transactions. - // Each transaction has a gas limit of 0.1, and the total gas limit for the lane is 0.1. + // Each transaction has a gas limit of 10%, and the total gas limit for the lane is 10%. // It is blocked if the oracle report lane exceeds its limit. oracleRequestLane = mempool.NewLane( app.Logger(), @@ -299,7 +299,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ ) // oracleReportLane handles oracle report data transactions. - // Each transaction has a gas limit of 0.05, and the total gas limit for the lane is 0.2. + // Each transaction has a gas limit of 5%, and the total gas limit for the lane is 20%. // It block the oracle request lane if it exceeds its limit. oracleReportLane = mempool.NewLane( app.Logger(), @@ -316,7 +316,7 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ ) // defaultLane handles all other transactions. - // Each transaction has a gas limit of 0.1, and the total gas limit for the lane is 0.1. + // Each transaction has a gas limit of 10%, and the total gas limit for the lane is 10%. defaultLane = mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 0c96c4222..4989cb95a 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -36,6 +36,7 @@ type Lane struct { // handleLaneLimitCheck is a callback function that is called when the lane exceeds its limit. handleLaneLimitCheck func(isLaneLimitExceeded bool) + // blocked indicates whether the transaction in this lane should be excluded from the proposal for the current block. blocked bool // Add mutex for thread safety From baf082d0579a7d092a04f0a613752d816e81193c Mon Sep 17 00:00:00 2001 From: Nattapat Iammelap Date: Tue, 22 Apr 2025 16:30:07 +0700 Subject: [PATCH 30/53] add comment and refactor --- app/lanes.go | 2 +- app/mempool/lane.go | 51 +++++++++++++++++++--------------------- app/mempool/lane_test.go | 22 ++++++++--------- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index 137d52663..658474eb1 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -137,7 +137,7 @@ func isTssLaneMsg( return true } -// oracleReportLaneMatchHandler is a function that returns the match function for the oracle lane. +// oracleReportLaneMatchHandler is a function that returns the match function for the oracle report lane. func oracleReportLaneMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 4989cb95a..f5b0435fc 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -33,8 +33,8 @@ type Lane struct { // currently in this lane's mempool. txIndex map[string]struct{} - // handleLaneLimitCheck is a callback function that is called when the lane exceeds its limit. - handleLaneLimitCheck func(isLaneLimitExceeded bool) + // callbackAfterFillProposal is a callback function that is called when the lane exceeds its limit. + callbackAfterFillProposal func(isLaneLimitExceeded bool) // blocked indicates whether the transaction in this lane should be excluded from the proposal for the current block. blocked bool @@ -53,18 +53,18 @@ func NewLane( maxTransactionSpace math.LegacyDec, maxLaneSpace math.LegacyDec, laneMempool sdkmempool.Mempool, - handleLaneLimitCheck func(isLaneLimitExceeded bool), + callbackAfterFillProposal func(isLaneLimitExceeded bool), ) *Lane { return &Lane{ - logger: logger, - txEncoder: txEncoder, - signerExtractor: signerExtractor, - name: name, - matchFn: matchFn, - maxTransactionSpace: maxTransactionSpace, - maxLaneSpace: maxLaneSpace, - laneMempool: laneMempool, - handleLaneLimitCheck: handleLaneLimitCheck, + logger: logger, + txEncoder: txEncoder, + signerExtractor: signerExtractor, + name: name, + matchFn: matchFn, + maxTransactionSpace: maxTransactionSpace, + maxLaneSpace: maxLaneSpace, + laneMempool: laneMempool, + callbackAfterFillProposal: callbackAfterFillProposal, // Initialize the txIndex. txIndex: make(map[string]struct{}), @@ -141,16 +141,15 @@ func (l *Lane) FillProposal( ctx sdk.Context, proposal *Proposal, ) (blockUsed BlockSpace, iterator sdkmempool.Iterator, txsToRemove []sdk.Tx) { - // Get the transaction and lane limit for the lane. - transactionLimit := proposal.GetLimit(l.maxTransactionSpace) - laneLimit := proposal.GetLimit(l.maxLaneSpace) - - isLaneLimitExceeded := false - + // if the lane is blocked, we do not add any transactions to the proposal. if l.blocked { return } + // Get the transaction and lane limit for the lane. + transactionLimit := proposal.GetLimit(l.maxTransactionSpace) + laneLimit := proposal.GetLimit(l.maxLaneSpace) + // Select all transactions in the mempool that are valid and not already in the // partial proposal. for iterator = l.laneMempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() { @@ -196,12 +195,9 @@ func (l *Lane) FillProposal( blockUsed = blockUsed.Add(txInfo.BlockSpace) } - if laneLimit.IsReachedBy(blockUsed) { - isLaneLimitExceeded = true - } - - if l.handleLaneLimitCheck != nil { - l.handleLaneLimitCheck(isLaneLimitExceeded) + // call the callback function of the lane after fill proposal. + if l.callbackAfterFillProposal != nil { + l.callbackAfterFillProposal(laneLimit.IsReachedBy(blockUsed)) } return @@ -215,13 +211,14 @@ func (l *Lane) FillProposalByIterator( iterator sdkmempool.Iterator, laneLimit BlockSpace, ) (blockUsed BlockSpace, txsToRemove []sdk.Tx) { - // get the transaction limit for the lane. - transactionLimit := proposal.GetLimit(l.maxTransactionSpace) - + // if the lane is blocked, we do not add any transactions to the proposal. if l.blocked { return } + // get the transaction limit for the lane. + transactionLimit := proposal.GetLimit(l.maxTransactionSpace) + // Select all transactions in the mempool that are valid and not already in the partial proposal. for ; iterator != nil; iterator = iterator.Next() { // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index eda15394f..73e8e3637 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -175,16 +175,16 @@ func (s *LaneTestSuite) TestLaneFillProposal() { s.Require().Equal(expectedIncludedTxs, proposal.txs) } -type handleLaneLimitCheckMock struct { +type callbackAfterFillProposalMock struct { isLaneLimitExceeded bool } -func (f *handleLaneLimitCheckMock) handleLaneLimitCheck(isLaneLimitExceeded bool) { +func (f *callbackAfterFillProposalMock) callbackAfterFillProposal(isLaneLimitExceeded bool) { f.isLaneLimitExceeded = isLaneLimitExceeded } -func (s *LaneTestSuite) TestLaneHandleLaneLimitCheck() { - handleLaneLimitCheckMock := &handleLaneLimitCheckMock{} +func (s *LaneTestSuite) TestLaneCallbackAfterFillProposal() { + callbackAfterFillProposalMock := &callbackAfterFillProposalMock{} lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -194,7 +194,7 @@ func (s *LaneTestSuite) TestLaneHandleLaneLimitCheck() { math.LegacyMustNewDecFromStr("0.3"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), - handleLaneLimitCheckMock.handleLaneLimitCheck, + callbackAfterFillProposalMock.callbackAfterFillProposal, ) // Insert a transaction @@ -221,7 +221,7 @@ func (s *LaneTestSuite) TestLaneHandleLaneLimitCheck() { s.Require().Equal(1, len(proposal.txs), "one txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().False(handleLaneLimitCheckMock.isLaneLimitExceeded, "handleLaneLimitCheck should be called with false") + s.Require().False(callbackAfterFillProposalMock.isLaneLimitExceeded, "callbackAfterFillProposal should be called with false") // Insert 2 more transactions tx2 := s.createSimpleTx(s.accounts[1], 1, 20) @@ -250,11 +250,11 @@ func (s *LaneTestSuite) TestLaneHandleLaneLimitCheck() { s.Require().Equal(2, len(proposal.txs), "two txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().True(handleLaneLimitCheckMock.isLaneLimitExceeded, "OoLaneLimitExceeded should be called with true") + s.Require().True(callbackAfterFillProposalMock.isLaneLimitExceeded, "OoLaneLimitExceeded should be called with true") } func (s *LaneTestSuite) TestLaneExactlyFilled() { - handleLaneLimitCheckMock := &handleLaneLimitCheckMock{} + callbackAfterFillProposalMock := &callbackAfterFillProposalMock{} lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), @@ -264,7 +264,7 @@ func (s *LaneTestSuite) TestLaneExactlyFilled() { math.LegacyMustNewDecFromStr("0.3"), math.LegacyMustNewDecFromStr("0.3"), sdkmempool.DefaultPriorityMempool(), - handleLaneLimitCheckMock.handleLaneLimitCheck, + callbackAfterFillProposalMock.callbackAfterFillProposal, ) // Insert a transaction @@ -291,7 +291,7 @@ func (s *LaneTestSuite) TestLaneExactlyFilled() { s.Require().Equal(1, len(proposal.txs), "one txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().False(handleLaneLimitCheckMock.isLaneLimitExceeded, "handleLaneLimitCheck should be called with false") + s.Require().False(callbackAfterFillProposalMock.isLaneLimitExceeded, "callbackAfterFillProposal should be called with false") // Insert 2 more transactions tx2 := s.createSimpleTx(s.accounts[1], 1, 10) @@ -320,7 +320,7 @@ func (s *LaneTestSuite) TestLaneExactlyFilled() { s.Require().Equal(2, len(proposal.txs), "two txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().True(handleLaneLimitCheckMock.isLaneLimitExceeded, "handleLaneLimitCheck should be called with true") + s.Require().True(callbackAfterFillProposalMock.isLaneLimitExceeded, "callbackAfterFillProposal should be called with true") } func (s *LaneTestSuite) TestLaneBlocked() { From 0e49a39fd29e95c9ab710960e24b8246ca0c075e Mon Sep 17 00:00:00 2001 From: Nattapat Iammelap Date: Tue, 22 Apr 2025 17:56:41 +0700 Subject: [PATCH 31/53] refactor return order --- app/lanes.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index 658474eb1..8f384cdfb 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -66,11 +66,10 @@ func isMsgSubmitSignalPrices( return false } } + return true default: return false } - - return true } // tssLaneMatchHandler is a function that returns the match function for the TSS lane. @@ -130,11 +129,10 @@ func isTssLaneMsg( return false } } + return true default: return false } - - return true } // oracleReportLaneMatchHandler is a function that returns the match function for the oracle report lane. @@ -187,11 +185,10 @@ func isMsgReportData( return false } } + return true default: return false } - - return true } // oracleRequestLaneMatchHandler is a function that returns the match function for the oracle request lane. @@ -234,11 +231,10 @@ func isMsgRequestData( return false } } + return true default: return false } - - return true } // DefaultLaneMatchHandler is a function that returns the match function for the default lane. From ad929da1320c242cf308a205763d15c8d0c2b2da Mon Sep 17 00:00:00 2001 From: Nattapat Iammelap Date: Wed, 23 Apr 2025 12:22:31 +0700 Subject: [PATCH 32/53] fix callbackAfterFillProposal comment --- app/mempool/lane.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/mempool/lane.go b/app/mempool/lane.go index f5b0435fc..a4ee492b6 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -33,13 +33,15 @@ type Lane struct { // currently in this lane's mempool. txIndex map[string]struct{} - // callbackAfterFillProposal is a callback function that is called when the lane exceeds its limit. + // callbackAfterFillProposal is a callback function that is called after + // filling the proposal with transactions from the lane. callbackAfterFillProposal func(isLaneLimitExceeded bool) - // blocked indicates whether the transaction in this lane should be excluded from the proposal for the current block. + // blocked indicates whether the transactions in this lane should be + // excluded from the proposal for the current block. blocked bool - // Add mutex for thread safety + // Add mutex for thread safety. mu sync.RWMutex } From 6ddcb3fac8ee214416b8e28cd5dbb4f2d56d9105 Mon Sep 17 00:00:00 2001 From: Ongart Pisansathienwong Date: Wed, 23 Apr 2025 17:49:35 +0700 Subject: [PATCH 33/53] [Mempool] Add Readme (#650) * add readme & make function private * fix typo * remove testing and dependency sections from readme --- app/mempool/README.md | 154 ++++++++++++++++++++++++++++++++++++++++++ app/mempool/lane.go | 14 ++-- 2 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 app/mempool/README.md diff --git a/app/mempool/README.md b/app/mempool/README.md new file mode 100644 index 000000000..a29dee256 --- /dev/null +++ b/app/mempool/README.md @@ -0,0 +1,154 @@ +# Mempool Package + +The mempool package implements a transaction mempool for the Cosmos SDK blockchain. It provides a sophisticated transaction management system that organizes transactions into different lanes based on their types and priorities. + +## Overview + +The mempool is designed to handle transaction processing and block proposal preparation in a Cosmos SDK-based blockchain. It implements the `sdkmempool.Mempool` interface and uses a lane-based architecture to manage different types of transactions. + +## Architecture + +### Core Components + +1. **Mempool**: The main structure that manages multiple transaction lanes and implements the core mempool interface. +2. **Lane**: A logical grouping of transactions with specific matching criteria and space allocation. +3. **Proposal**: Represents a block proposal under construction, managing transaction inclusion and space limits. +4. **BlockSpace**: Manages block space constraints including transaction bytes and gas limits. + +### Key Features + +- **Lane-based Organization**: Transactions are organized into different lanes based on their matching functions +- **Space Management**: Efficient management of block space and gas limits +- **Transaction Prioritization**: Support for different transaction priorities among and within lanes +- **Thread Safety**: Built-in mutex protection for concurrent access +- **Error Recovery**: Panic recovery mechanisms for robust operation + +## Usage + +### Creating a Lane + +A lane is created with specific matching criteria and space allocation. Here's an example of creating a lane for bank send transactions: + +```go +bankSendLane := NewLane( + logger, + txEncoder, + signerExtractor, + "bankSend", // Lane name + isBankSendTx, // Matching function + math.LegacyMustNewDecFromStr("0.2"), // Max transaction space ratio + math.LegacyMustNewDecFromStr("0.3"), // Max lane space ratio + sdkmempool.DefaultPriorityMempool(), // Underlying mempool implementation + nil, // Lane limit check handler +) + +// Example matching function for bank send transactions +func isBankSendTx(_ sdk.Context, tx sdk.Tx) bool { + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if _, ok := msg.(*banktypes.MsgSend); !ok { + return false + } + } + return true +} +``` + +Key parameters for lane creation: +- `logger`: Logger instance for lane operations +- `txEncoder`: Function to encode transactions +- `signerExtractor`: Adapter to extract signer information +- `name`: Unique identifier for the lane +- `matchFn`: Function to determine if a transaction belongs in this lane +- `maxTransactionSpace`: Maximum space ratio for individual transactions (relative to total block space) more details on [Space management](#space-management) +- `maxLaneSpace`: Maximum space ratio for the entire lane (relative to total block space) more details on [Space management](#space-management) +- `laneMempool`: Underlying Cosmos SDK mempool implementation that handles transaction storage and ordering within the lane. This determines how transactions are stored and selected within the lane +- `handleLaneLimitCheck`: Optional callback function that is called when the lane exceeds its space limit. This can be used to implement inter-lane dependencies, such as blocking other lanes when a lane exceeds its limit. + +#### Inter-Lane Dependencies + +Lanes can be configured to interact with each other through the `handleLaneLimitCheck` callback. This is useful for implementing priority systems or dependencies between different types of transactions. For example, you might want to block certain lanes when a high-priority lane exceeds its limit: + +```go +// Create a dependent lane that will be blocked when the dependency lane exceeds its limit +dependentLane := NewLane( + logger, + txEncoder, + signerExtractor, + "dependent", + isOtherTx, + math.LegacyMustNewDecFromStr("0.5"), + math.LegacyMustNewDecFromStr("0.5"), + sdkmempool.DefaultPriorityMempool(), + nil, +) + +// Create a dependency lane that controls the dependent lane +dependencyLane := NewLane( + logger, + txEncoder, + signerExtractor, + "dependency", + isBankSendTx, + math.LegacyMustNewDecFromStr("0.5"), + math.LegacyMustNewDecFromStr("0.5"), + sdkmempool.DefaultPriorityMempool(), + func(isLaneLimitExceeded bool) { + dependentLane.SetBlocked(isLaneLimitExceeded) + }, +) +``` + +In this example, when the dependency lane exceeds its space limit, it will block the dependent lane from processing transactions. This mechanism allows for sophisticated transaction prioritization and coordination between different types of transactions. + +### Creating a Mempool + +```go +mempool := NewMempool( + logger, + []*Lane{ + BankSendLane, + DelegationLane, + // Lane order is critical - first lane in the slice matches and processes the transaction first + }, +) + +// set the mempool in Chain application +app.SetMempool(mempool) +``` + +Key parameters for mempool creation: +- `logger`: Logger instance for mempool operations +- `lanes`: Array of lane configurations, where each lane is responsible for a specific type of transaction + - The order of lanes determines the priority of transaction types + - The sum of all lane space ratios can exceed 100% as it represents the maximum potential allocation for each lane, not a strict partition of the block space + +### Space Management + +#### Space Allocation +Both `maxTransactionSpace` and `maxLaneSpace` are expressed as ratios of the total block space and are used specifically during proposal preparation. For example, a `maxTransactionSpace` of 0.2 means a single transaction can use up to 20% of the total block space in a proposal, while a `maxLaneSpace` of 0.3 means the entire lane can use up to 30% of the total block space in a proposal. These ratios are used to ensure fair distribution of block space among different transaction types during proposal construction. + +#### Space Cap Behavior +- **Transaction Space Cap (Hard Cap)**: The `maxTransactionSpace` ratio enforces a strict limit on individual transaction sizes of each lane. For example, with a `maxTransactionSpace` of 0.2, a transaction requiring more than 20% of the total block space will be deleted from the lane. +- **Lane Space Cap (Soft Cap)**: The `maxLaneSpace` ratio serves as a guideline for space allocation during the first round of proposal construction. If a lane's `maxLaneSpace` is 0.3, it can still include one last transaction that would cause it to exceed this limit in the proposal, provided each individual transaction respects the `maxTransactionSpace` limit. For instance, a lane with a 0.3 `maxLaneSpace` could include two transactions each using 20% of the block space (totaling 40%) in the proposal, as long as both transactions individually respect the `maxTransactionSpace` limit. + +#### Proposal Preparation +The lane space cap (`maxLaneSpace`) is only enforced during the first round of proposal preparation. In subsequent rounds, when filling the remaining block space, the lane cap is not considered, allowing lanes to potentially use more space than their initial allocation if space is available. This two-phase approach ensures both fair initial distribution and efficient use of remaining block space. + +### Block Proposal Preparation + +The mempool provides functionality to prepare block proposals by: +1. Filling proposals with transactions from each lane with `maxLaneSpace` +2. Filling remaining proposal space with transactions from each lane in the same order without `maxLaneSpace` +3. Handling transaction removal of the transactions that violate the `maxTransactionSpace` from all lanes + +## Best Practices + +1. Configure appropriate lane ratios based on your application's needs +2. Implement proper transaction matching functions for each lane +3. Always place the default lane as the last lane to ensure every transaction type can be matched + +Note: Lane order is critical as it determines transaction processing priority. This is why the default lane should always be last, to ensure it only catches transactions that don't match any other lane. diff --git a/app/mempool/lane.go b/app/mempool/lane.go index a4ee492b6..5863b288a 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -77,7 +77,7 @@ func NewLane( // Insert inserts a transaction into the lane's mempool. func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { - txInfo, err := l.GetTxInfo(tx) + txInfo, err := l.getTxInfo(tx) if err != nil { return err } @@ -100,7 +100,7 @@ func (l *Lane) CountTx() int { // Remove removes a transaction from the lane's mempool. func (l *Lane) Remove(tx sdk.Tx) error { - txInfo, err := l.GetTxInfo(tx) + txInfo, err := l.getTxInfo(tx) if err != nil { return err } @@ -118,7 +118,7 @@ func (l *Lane) Remove(tx sdk.Tx) error { // Contains returns true if the lane's mempool contains the transaction. func (l *Lane) Contains(tx sdk.Tx) bool { - txInfo, err := l.GetTxInfo(tx) + txInfo, err := l.getTxInfo(tx) if err != nil { return false } @@ -162,7 +162,7 @@ func (l *Lane) FillProposal( } tx := iterator.Tx() - txInfo, err := l.GetTxInfo(tx) + txInfo, err := l.getTxInfo(tx) if err != nil { l.logger.Info("failed to get hash of tx", "err", err) @@ -230,7 +230,7 @@ func (l *Lane) FillProposalByIterator( } tx := iterator.Tx() - txInfo, err := l.GetTxInfo(tx) + txInfo, err := l.getTxInfo(tx) if err != nil { l.logger.Info("failed to get hash of tx", "err", err) @@ -269,10 +269,10 @@ func (l *Lane) FillProposalByIterator( return } -// GetTxInfo returns various information about the transaction that +// getTxInfo returns various information about the transaction that // belongs to the lane including its priority, signer's, sequence number, // size and more. -func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { +func (l *Lane) getTxInfo(tx sdk.Tx) (TxWithInfo, error) { txBytes, err := l.txEncoder(tx) if err != nil { return TxWithInfo{}, fmt.Errorf("failed to encode transaction: %w", err) From 08e56e2eb7c81e1262d833be7901183809d392df Mon Sep 17 00:00:00 2001 From: taobun Date: Thu, 24 Apr 2025 14:28:04 +0700 Subject: [PATCH 34/53] refactor lane and matchFn --- app/ante.go | 7 +- app/app.go | 7 +- app/lanes.go | 278 +++++------------------------------- app/match.go | 51 +++---- app/mempool/lane.go | 31 ++-- app/mempool/lane_test.go | 10 +- app/mempool/mempool_test.go | 9 -- app/mempool/types.go | 64 ++++++++- 8 files changed, 132 insertions(+), 325 deletions(-) diff --git a/app/ante.go b/app/ante.go index 6f139c40f..28630a1b0 100644 --- a/app/ante.go +++ b/app/ante.go @@ -11,6 +11,7 @@ import ( authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/bandprotocol/chain/v3/app/mempool" bandtsskeeper "github.com/bandprotocol/chain/v3/x/bandtss/keeper" feedskeeper "github.com/bandprotocol/chain/v3/x/feeds/keeper" "github.com/bandprotocol/chain/v3/x/globalfee/feechecker" @@ -32,7 +33,7 @@ type HandlerOptions struct { TSSKeeper *tsskeeper.Keeper BandtssKeeper *bandtsskeeper.Keeper FeedsKeeper *feedskeeper.Keeper - IgnoreDecoratorMatchFns []func(sdk.Context, sdk.Tx) bool + IgnoreDecoratorMatchFns []mempool.TxMatchFn } func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { @@ -124,11 +125,11 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { // for the AnteDecorator to be ignored for specified lanes. type IgnoreDecorator struct { decorator sdk.AnteDecorator - matchFns []func(sdk.Context, sdk.Tx) bool + matchFns []mempool.TxMatchFn } // NewIgnoreDecorator returns a new IgnoreDecorator instance. -func NewIgnoreDecorator(decorator sdk.AnteDecorator, matchFns ...func(sdk.Context, sdk.Tx) bool) *IgnoreDecorator { +func NewIgnoreDecorator(decorator sdk.AnteDecorator, matchFns ...mempool.TxMatchFn) *IgnoreDecorator { return &IgnoreDecorator{ decorator: decorator, matchFns: matchFns, diff --git a/app/app.go b/app/app.go index 2ffdb43c2..0ed58052a 100644 --- a/app/app.go +++ b/app/app.go @@ -257,11 +257,10 @@ func NewBandApp( app.sm.RegisterStoreDecoders() - feedsLane, tssLane, oracleReportLane, oracleRequestLane, defaultLane := CreateLanes(app) - bandLanes := []*mempool.Lane{feedsLane, tssLane, oracleReportLane, oracleRequestLane, defaultLane} + lanes := CreateLanes(app) // create Band mempool - bandMempool := mempool.NewMempool(app.Logger(), bandLanes) + bandMempool := mempool.NewMempool(app.Logger(), lanes) // set the mempool app.SetMempool(bandMempool) @@ -292,7 +291,7 @@ func NewBandApp( IBCKeeper: app.IBCKeeper, StakingKeeper: app.StakingKeeper, GlobalfeeKeeper: &app.GlobalFeeKeeper, - IgnoreDecoratorMatchFns: []func(sdk.Context, sdk.Tx) bool{ + IgnoreDecoratorMatchFns: []mempool.TxMatchFn{ feedsSubmitSignalPriceTxMatchHandler(app.appCodec, &app.AuthzKeeper, feedsMsgServer), tssTxMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper, tssMsgServer), oracleReportTxMatchHandler(app.appCodec, &app.AuthzKeeper, oracleMsgServer), diff --git a/app/lanes.go b/app/lanes.go index 8f384cdfb..bec3aa4de 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -3,262 +3,34 @@ package band import ( "cosmossdk.io/math" - "github.com/cosmos/cosmos-sdk/codec" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + sdk "github.com/cosmos/cosmos-sdk/types" sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" - "github.com/cosmos/cosmos-sdk/x/authz" - authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" "github.com/bandprotocol/chain/v3/app/mempool" - bandtsskeeper "github.com/bandprotocol/chain/v3/x/bandtss/keeper" feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" oracletypes "github.com/bandprotocol/chain/v3/x/oracle/types" tsstypes "github.com/bandprotocol/chain/v3/x/tss/types" ) -// feedsLaneMatchHandler is a function that returns the match function for the Feeds lane. -func feedsLaneMatchHandler( - cdc codec.Codec, - authzKeeper *authzkeeper.Keeper, -) func(sdk.Context, sdk.Tx) bool { - return func(ctx sdk.Context, tx sdk.Tx) bool { - // Feeds lane only matches fee-less transactions - gasTx, ok := tx.(sdk.FeeTx) - if !ok { - return false - } - - if !gasTx.GetFee().IsZero() { - return false - } - - msgs := tx.GetMsgs() - if len(msgs) == 0 { - return false - } - for _, msg := range msgs { - if !isMsgSubmitSignalPrices(ctx, msg, cdc, authzKeeper) { - return false - } - } - return true - } -} - -// isMsgSubmitSignalPrices return true if the message is a valid feeds' MsgSubmitSignalPrices. -func isMsgSubmitSignalPrices( - ctx sdk.Context, - msg sdk.Msg, - cdc codec.Codec, - authzKeeper *authzkeeper.Keeper, -) bool { - switch msg := msg.(type) { - case *feedstypes.MsgSubmitSignalPrices: - return true - case *authz.MsgExec: - msgs, err := msg.GetMessages() - if err != nil { - return false - } - - for _, m := range msgs { - if !isMsgSubmitSignalPrices(ctx, m, cdc, authzKeeper) { - return false - } - } - return true - default: - return false - } -} - -// tssLaneMatchHandler is a function that returns the match function for the TSS lane. -func tssLaneMatchHandler( - cdc codec.Codec, - authzKeeper *authzkeeper.Keeper, - bandtssKeeper *bandtsskeeper.Keeper, -) func(sdk.Context, sdk.Tx) bool { - return func(ctx sdk.Context, tx sdk.Tx) bool { - // TSS lane only matches fee-less transactions - gasTx, ok := tx.(sdk.FeeTx) - if !ok { - return false - } - - if !gasTx.GetFee().IsZero() { - return false - } - - msgs := tx.GetMsgs() - if len(msgs) == 0 { - return false - } - for _, msg := range msgs { - if !isTssLaneMsg(ctx, msg, cdc, authzKeeper, bandtssKeeper) { - return false - } - } - return true - } -} - -// isTssLaneMsg return true if the message is a valid for TSS lane. -func isTssLaneMsg( - ctx sdk.Context, - msg sdk.Msg, - cdc codec.Codec, - authzKeeper *authzkeeper.Keeper, - bandtssKeeper *bandtsskeeper.Keeper, -) bool { - switch msg := msg.(type) { - case *tsstypes.MsgSubmitDKGRound1, - *tsstypes.MsgSubmitDKGRound2, - *tsstypes.MsgConfirm, - *tsstypes.MsgComplain, - *tsstypes.MsgSubmitDEs, - *tsstypes.MsgSubmitSignature: - return true - case *authz.MsgExec: - msgs, err := msg.GetMessages() - if err != nil { - return false - } - - for _, m := range msgs { - if !isTssLaneMsg(ctx, m, cdc, authzKeeper, bandtssKeeper) { - return false - } - } - return true - default: - return false - } -} - -// oracleReportLaneMatchHandler is a function that returns the match function for the oracle report lane. -func oracleReportLaneMatchHandler( - cdc codec.Codec, - authzKeeper *authzkeeper.Keeper, -) func(sdk.Context, sdk.Tx) bool { - return func(ctx sdk.Context, tx sdk.Tx) bool { - // Oracle lane only matches fee-less transactions - gasTx, ok := tx.(sdk.FeeTx) - if !ok { - return false - } - - if !gasTx.GetFee().IsZero() { - return false - } - - msgs := tx.GetMsgs() - if len(msgs) == 0 { - return false - } - for _, msg := range msgs { - if !isMsgReportData(ctx, msg, cdc, authzKeeper) { - return false - } - } - return true - } -} - -// isMsgReportData return true if the message is a valid oracle's MsgReportData. -func isMsgReportData( - ctx sdk.Context, - msg sdk.Msg, - cdc codec.Codec, - authzKeeper *authzkeeper.Keeper, -) bool { - switch msg := msg.(type) { - case *oracletypes.MsgReportData: - return true - case *authz.MsgExec: - msgs, err := msg.GetMessages() - if err != nil { - return false - } - - for _, m := range msgs { - if !isMsgReportData(ctx, m, cdc, authzKeeper) { - return false - } - } - return true - default: - return false - } -} - -// oracleRequestLaneMatchHandler is a function that returns the match function for the oracle request lane. -func oracleRequestLaneMatchHandler( - cdc codec.Codec, - authzKeeper *authzkeeper.Keeper, -) func(sdk.Context, sdk.Tx) bool { - return func(ctx sdk.Context, tx sdk.Tx) bool { - msgs := tx.GetMsgs() - if len(msgs) == 0 { - return false - } - for _, msg := range msgs { - if !isMsgRequestData(ctx, msg, cdc, authzKeeper) { - return false - } - } - return true - } -} - -// isMsgRequestData return true if the message is a valid oracle's MsgRequestData. -func isMsgRequestData( - ctx sdk.Context, - msg sdk.Msg, - cdc codec.Codec, - authzKeeper *authzkeeper.Keeper, -) bool { - switch msg := msg.(type) { - case *oracletypes.MsgRequestData: - return true - case *authz.MsgExec: - msgs, err := msg.GetMessages() - if err != nil { - return false - } - - for _, m := range msgs { - if !isMsgRequestData(ctx, m, cdc, authzKeeper) { - return false - } - } - return true - default: - return false - } -} - // DefaultLaneMatchHandler is a function that returns the match function for the default lane. -func DefaultLaneMatchHandler() func(sdk.Context, sdk.Tx) bool { +func DefaultLaneMatchHandler() mempool.TxMatchFn { return func(_ sdk.Context, _ sdk.Tx) bool { return true } } // CreateLanes creates the lanes for the Band mempool. -func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequestLane, defaultLane *mempool.Lane) { - // Create the signer extractor. This is used to extract the expected signers from - // a transaction. Each lane can have a different signer extractor if needed. - signerExtractor := sdkmempool.NewDefaultSignerExtractionAdapter() - +func CreateLanes(app *BandApp) []*mempool.Lane { // feedsLane handles feeds submit signal price transactions. // Each transaction has a gas limit of 2%, and the total gas limit for the lane is 50%. // It uses SenderNonceMempool to ensure transactions are ordered by sender and nonce, with no per-sender tx limit. - feedsLane = mempool.NewLane( + feedsLane := mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), - signerExtractor, "feedsLane", - feedsLaneMatchHandler(app.appCodec, &app.AuthzKeeper), + mempool.NewTxMatchFn(app.appCodec, []sdk.Msg{&feedstypes.MsgSubmitSignalPrices{}}, true), math.LegacyMustNewDecFromStr("0.02"), math.LegacyMustNewDecFromStr("0.5"), sdkmempool.NewSenderNonceMempool(sdkmempool.SenderNonceMaxTxOpt(0)), @@ -267,12 +39,22 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ // tssLane handles TSS transactions. // Each transaction has a gas limit of 2%, and the total gas limit for the lane is 20%. - tssLane = mempool.NewLane( + tssLane := mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), - signerExtractor, "tssLane", - tssLaneMatchHandler(app.appCodec, &app.AuthzKeeper, &app.BandtssKeeper), + mempool.NewTxMatchFn( + app.appCodec, + []sdk.Msg{ + &tsstypes.MsgSubmitDKGRound1{}, + &tsstypes.MsgSubmitDKGRound2{}, + &tsstypes.MsgConfirm{}, + &tsstypes.MsgComplain{}, + &tsstypes.MsgSubmitDEs{}, + &tsstypes.MsgSubmitSignature{}, + }, + true, + ), math.LegacyMustNewDecFromStr("0.02"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), @@ -282,12 +64,18 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ // oracleRequestLane handles oracle request data transactions. // Each transaction has a gas limit of 10%, and the total gas limit for the lane is 10%. // It is blocked if the oracle report lane exceeds its limit. - oracleRequestLane = mempool.NewLane( + oracleRequestLane := mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), - signerExtractor, "oracleRequestLane", - oracleRequestLaneMatchHandler(app.appCodec, &app.AuthzKeeper), + mempool.NewTxMatchFn( + app.appCodec, + []sdk.Msg{ + &oracletypes.MsgRequestData{}, + &channeltypes.MsgRecvPacket{}, // TODO: Only match oracle request packet + }, + false, + ), math.LegacyMustNewDecFromStr("0.1"), math.LegacyMustNewDecFromStr("0.1"), sdkmempool.DefaultPriorityMempool(), @@ -297,12 +85,11 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ // oracleReportLane handles oracle report data transactions. // Each transaction has a gas limit of 5%, and the total gas limit for the lane is 20%. // It block the oracle request lane if it exceeds its limit. - oracleReportLane = mempool.NewLane( + oracleReportLane := mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), - signerExtractor, "oracleReportLane", - oracleReportLaneMatchHandler(app.appCodec, &app.AuthzKeeper), + mempool.NewTxMatchFn(app.appCodec, []sdk.Msg{&oracletypes.MsgReportData{}}, true), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), @@ -313,10 +100,9 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ // defaultLane handles all other transactions. // Each transaction has a gas limit of 10%, and the total gas limit for the lane is 10%. - defaultLane = mempool.NewLane( + defaultLane := mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), - signerExtractor, "defaultLane", DefaultLaneMatchHandler(), math.LegacyMustNewDecFromStr("0.1"), @@ -325,5 +111,5 @@ func CreateLanes(app *BandApp) (feedsLane, tssLane, oracleReportLane, oracleRequ nil, ) - return + return []*mempool.Lane{feedsLane, tssLane, oracleRequestLane, oracleReportLane, defaultLane} } diff --git a/app/match.go b/app/match.go index 3be550808..7895c6d30 100644 --- a/app/match.go +++ b/app/match.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/authz" authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + "github.com/bandprotocol/chain/v3/app/mempool" bandtsskeeper "github.com/bandprotocol/chain/v3/x/bandtss/keeper" feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" oracletypes "github.com/bandprotocol/chain/v3/x/oracle/types" @@ -17,7 +18,7 @@ func feedsSubmitSignalPriceTxMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, feedsMsgServer feedstypes.MsgServer, -) func(sdk.Context, sdk.Tx) bool { +) mempool.TxMatchFn { return func(ctx sdk.Context, tx sdk.Tx) bool { msgs := tx.GetMsgs() if len(msgs) == 0 { @@ -42,9 +43,7 @@ func isValidMsgSubmitSignalPrices( ) bool { switch msg := msg.(type) { case *feedstypes.MsgSubmitSignalPrices: - if _, err := feedsMsgServer.SubmitSignalPrices(ctx, msg); err != nil { - return false - } + return isSuccess(feedsMsgServer.SubmitSignalPrices(ctx, msg)) case *authz.MsgExec: msgs, err := msg.GetMessages() if err != nil { @@ -77,11 +76,10 @@ func isValidMsgSubmitSignalPrices( return false } } + return true default: return false } - - return true } // tssTxMatchHandler is a function that returns the match function for the TSS Tx. @@ -90,7 +88,7 @@ func tssTxMatchHandler( authzKeeper *authzkeeper.Keeper, bandtssKeeper *bandtsskeeper.Keeper, tssMsgServer tsstypes.MsgServer, -) func(sdk.Context, sdk.Tx) bool { +) mempool.TxMatchFn { return func(ctx sdk.Context, tx sdk.Tx) bool { msgs := tx.GetMsgs() if len(msgs) == 0 { @@ -116,21 +114,13 @@ func isValidTSSTxMsg( ) bool { switch msg := msg.(type) { case *tsstypes.MsgSubmitDKGRound1: - if _, err := tssMsgServer.SubmitDKGRound1(ctx, msg); err != nil { - return false - } + return isSuccess(tssMsgServer.SubmitDKGRound1(ctx, msg)) case *tsstypes.MsgSubmitDKGRound2: - if _, err := tssMsgServer.SubmitDKGRound2(ctx, msg); err != nil { - return false - } + return isSuccess(tssMsgServer.SubmitDKGRound2(ctx, msg)) case *tsstypes.MsgConfirm: - if _, err := tssMsgServer.Confirm(ctx, msg); err != nil { - return false - } + return isSuccess(tssMsgServer.Confirm(ctx, msg)) case *tsstypes.MsgComplain: - if _, err := tssMsgServer.Complain(ctx, msg); err != nil { - return false - } + return isSuccess(tssMsgServer.Complain(ctx, msg)) case *tsstypes.MsgSubmitDEs: acc, err := sdk.AccAddressFromBech32(msg.Sender) if err != nil { @@ -144,13 +134,9 @@ func isValidTSSTxMsg( return false } - if _, err := tssMsgServer.SubmitDEs(ctx, msg); err != nil { - return false - } + return isSuccess(tssMsgServer.SubmitDEs(ctx, msg)) case *tsstypes.MsgSubmitSignature: - if _, err := tssMsgServer.SubmitSignature(ctx, msg); err != nil { - return false - } + return isSuccess(tssMsgServer.SubmitSignature(ctx, msg)) case *authz.MsgExec: msgs, err := msg.GetMessages() if err != nil { @@ -183,11 +169,10 @@ func isValidTSSTxMsg( return false } } + return true default: return false } - - return true } // oracleReportTxMatchHandler is a function that returns the match function for the oracle report tx. @@ -195,7 +180,7 @@ func oracleReportTxMatchHandler( cdc codec.Codec, authzKeeper *authzkeeper.Keeper, oracleMsgServer oracletypes.MsgServer, -) func(sdk.Context, sdk.Tx) bool { +) mempool.TxMatchFn { return func(ctx sdk.Context, tx sdk.Tx) bool { msgs := tx.GetMsgs() if len(msgs) == 0 { @@ -220,9 +205,7 @@ func isValidMsgReportData( ) bool { switch msg := msg.(type) { case *oracletypes.MsgReportData: - if _, err := oracleMsgServer.ReportData(ctx, msg); err != nil { - return false - } + return isSuccess(oracleMsgServer.ReportData(ctx, msg)) case *authz.MsgExec: msgs, err := msg.GetMessages() if err != nil { @@ -255,9 +238,13 @@ func isValidMsgReportData( return false } } + + return true default: return false } +} - return true +func isSuccess(_ any, err error) bool { + return err == nil } diff --git a/app/mempool/lane.go b/app/mempool/lane.go index a4ee492b6..7d7e07bec 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -18,16 +18,15 @@ import ( // Lane defines a logical grouping of transactions within the mempool. type Lane struct { - logger log.Logger - txEncoder sdk.TxEncoder - signerExtractor sdkmempool.SignerExtractionAdapter - name string - matchFn func(ctx sdk.Context, tx sdk.Tx) bool + logger log.Logger + txEncoder sdk.TxEncoder + name string + matchFn func(ctx sdk.Context, tx sdk.Tx) bool maxTransactionSpace math.LegacyDec maxLaneSpace math.LegacyDec - laneMempool sdkmempool.Mempool + mempool sdkmempool.Mempool // txIndex holds the uppercase hex-encoded hash for every transaction // currently in this lane's mempool. @@ -49,9 +48,8 @@ type Lane struct { func NewLane( logger log.Logger, txEncoder sdk.TxEncoder, - signerExtractor sdkmempool.SignerExtractionAdapter, name string, - matchFn func(sdk.Context, sdk.Tx) bool, + matchFn TxMatchFn, maxTransactionSpace math.LegacyDec, maxLaneSpace math.LegacyDec, laneMempool sdkmempool.Mempool, @@ -60,12 +58,11 @@ func NewLane( return &Lane{ logger: logger, txEncoder: txEncoder, - signerExtractor: signerExtractor, name: name, matchFn: matchFn, maxTransactionSpace: maxTransactionSpace, maxLaneSpace: maxLaneSpace, - laneMempool: laneMempool, + mempool: laneMempool, callbackAfterFillProposal: callbackAfterFillProposal, // Initialize the txIndex. @@ -85,7 +82,7 @@ func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { l.mu.Lock() defer l.mu.Unlock() - if err = l.laneMempool.Insert(ctx, tx); err != nil { + if err = l.mempool.Insert(ctx, tx); err != nil { return err } @@ -95,7 +92,7 @@ func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { // CountTx returns the total number of transactions in the lane's mempool. func (l *Lane) CountTx() int { - return l.laneMempool.CountTx() + return l.mempool.CountTx() } // Remove removes a transaction from the lane's mempool. @@ -108,7 +105,7 @@ func (l *Lane) Remove(tx sdk.Tx) error { l.mu.Lock() defer l.mu.Unlock() - if err = l.laneMempool.Remove(tx); err != nil { + if err = l.mempool.Remove(tx); err != nil { return err } @@ -154,7 +151,7 @@ func (l *Lane) FillProposal( // Select all transactions in the mempool that are valid and not already in the // partial proposal. - for iterator = l.laneMempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() { + for iterator = l.mempool.Select(ctx, nil); iterator != nil; iterator = iterator.Next() { // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. // We can tolerate a few bytes/gas over the limit, since we limit the size of each transaction. if laneLimit.IsReachedBy(blockUsed) { @@ -283,18 +280,12 @@ func (l *Lane) GetTxInfo(tx sdk.Tx) (TxWithInfo, error) { return TxWithInfo{}, fmt.Errorf("failed to cast transaction to gas tx") } - signers, err := l.signerExtractor.GetSigners(tx) - if err != nil { - return TxWithInfo{}, err - } - blockSpace := NewBlockSpace(uint64(len(txBytes)), gasTx.GetGas()) return TxWithInfo{ Hash: strings.ToUpper(hex.EncodeToString(comettypes.Tx(txBytes).Hash())), BlockSpace: blockSpace, TxBytes: txBytes, - Signers: signers, }, nil } diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 73e8e3637..e5b667ac4 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -57,7 +57,6 @@ func (s *LaneTestSuite) TestLaneInsertAndCount() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.3"), @@ -82,7 +81,6 @@ func (s *LaneTestSuite) TestLaneRemove() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.3"), @@ -106,7 +104,6 @@ func (s *LaneTestSuite) TestLaneFillProposal() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.2"), @@ -188,7 +185,6 @@ func (s *LaneTestSuite) TestLaneCallbackAfterFillProposal() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.3"), @@ -258,7 +254,6 @@ func (s *LaneTestSuite) TestLaneExactlyFilled() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.3"), @@ -328,7 +323,6 @@ func (s *LaneTestSuite) TestLaneBlocked() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.2"), @@ -372,7 +366,7 @@ func (s *LaneTestSuite) TestLaneBlocked() { s.Require().Equal(0, len(proposal.txs), "no txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().Equal(lane.laneMempool.Select(s.ctx, nil).Tx(), tx1) + s.Require().Equal(lane.mempool.Select(s.ctx, nil).Tx(), tx1) // Calculate the remaining block space remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) @@ -391,7 +385,7 @@ func (s *LaneTestSuite) TestLaneBlocked() { s.Require().Equal(0, len(proposal.txs), "no txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.txs) - s.Require().Equal(lane.laneMempool.Select(s.ctx, nil).Tx(), tx1) + s.Require().Equal(lane.mempool.Select(s.ctx, nil).Tx(), tx1) } // ----------------------------------------------------------------------------- diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index bffea1f6e..0fc76de5b 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -146,12 +146,9 @@ func (s *MempoolTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { // ----------------------------------------------------------------------------- func (s *MempoolTestSuite) newMempool() *Mempool { - signerExtractor := sdkmempool.NewDefaultSignerExtractionAdapter() - BankSendLane := NewLane( log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), - signerExtractor, "bankSend", isBankSendTx, math.LegacyMustNewDecFromStr("0.2"), @@ -163,7 +160,6 @@ func (s *MempoolTestSuite) newMempool() *Mempool { DelegateLane := NewLane( log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), - signerExtractor, "delegate", isDelegateTx, math.LegacyMustNewDecFromStr("0.2"), @@ -175,7 +171,6 @@ func (s *MempoolTestSuite) newMempool() *Mempool { OtherLane := NewLane( log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), - signerExtractor, "other", isOtherTx, math.LegacyMustNewDecFromStr("0.4"), @@ -511,12 +506,9 @@ func (s *MempoolTestSuite) TestFillUpLeftOverSpace() { } func (s *MempoolTestSuite) TestDependencyBlockLane() { - signerAdapter := sdkmempool.NewDefaultSignerExtractionAdapter() - DependentLane := NewLane( log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), - signerAdapter, "dependent", isOtherTx, math.LegacyMustNewDecFromStr("0.5"), @@ -528,7 +520,6 @@ func (s *MempoolTestSuite) TestDependencyBlockLane() { DependencyLane := NewLane( log.NewTestLogger(s.T()), s.encodingConfig.TxConfig.TxEncoder(), - signerAdapter, "dependency", isBankSendTx, math.LegacyMustNewDecFromStr("0.5"), diff --git a/app/mempool/types.go b/app/mempool/types.go index 9bff486ea..739df46aa 100644 --- a/app/mempool/types.go +++ b/app/mempool/types.go @@ -1,7 +1,12 @@ package mempool import ( - sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" + "reflect" + "slices" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" ) // TxWithInfo holds metadata required for a transaction to be included in a proposal. @@ -12,6 +17,59 @@ type TxWithInfo struct { BlockSpace BlockSpace // TxBytes is the raw transaction bytes. TxBytes []byte - // Signers defines the signers of a transaction. - Signers []sdkmempool.SignerData +} + +type TxMatchFn func(sdk.Context, sdk.Tx) bool + +func NewTxMatchFn(cdc codec.Codec, msgs []sdk.Msg, onlyFree bool) TxMatchFn { + msgTypes := make([]reflect.Type, len(msgs)) + + for i, msg := range msgs { + msgTypes[i] = reflect.TypeOf(msg) + } + + var matchMsgFn func(sdk.Context, sdk.Msg, codec.Codec) bool + + matchMsgFn = func(ctx sdk.Context, msg sdk.Msg, cdc codec.Codec) bool { + msgExec, ok := msg.(*authz.MsgExec) + if ok { + subMsgs, err := msgExec.GetMessages() + if err != nil { + return false + } + for _, m := range subMsgs { + if !matchMsgFn(ctx, m, cdc) { + return false + } + } + return true + + } else { + return slices.Contains(msgTypes, reflect.TypeOf(msg)) + } + } + + return func(ctx sdk.Context, tx sdk.Tx) bool { + if onlyFree { + gasTx, ok := tx.(sdk.FeeTx) + if !ok { + return false + } + + if !gasTx.GetFee().IsZero() { + return false + } + } + + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return false + } + for _, msg := range msgs { + if !matchMsgFn(ctx, msg, cdc) { + return false + } + } + return true + } } From 4aed982f0606eb7522359ae0fac585f62f39b5ba Mon Sep 17 00:00:00 2001 From: taobun Date: Thu, 24 Apr 2025 14:43:15 +0700 Subject: [PATCH 35/53] remove cdc --- app/mempool/types.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/mempool/types.go b/app/mempool/types.go index 739df46aa..eaf44f508 100644 --- a/app/mempool/types.go +++ b/app/mempool/types.go @@ -4,7 +4,6 @@ import ( "reflect" "slices" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/authz" ) @@ -21,16 +20,16 @@ type TxWithInfo struct { type TxMatchFn func(sdk.Context, sdk.Tx) bool -func NewTxMatchFn(cdc codec.Codec, msgs []sdk.Msg, onlyFree bool) TxMatchFn { +func NewTxMatchFn(msgs []sdk.Msg, onlyFree bool) TxMatchFn { msgTypes := make([]reflect.Type, len(msgs)) for i, msg := range msgs { msgTypes[i] = reflect.TypeOf(msg) } - var matchMsgFn func(sdk.Context, sdk.Msg, codec.Codec) bool + var matchMsgFn func(sdk.Context, sdk.Msg) bool - matchMsgFn = func(ctx sdk.Context, msg sdk.Msg, cdc codec.Codec) bool { + matchMsgFn = func(ctx sdk.Context, msg sdk.Msg) bool { msgExec, ok := msg.(*authz.MsgExec) if ok { subMsgs, err := msgExec.GetMessages() @@ -38,12 +37,11 @@ func NewTxMatchFn(cdc codec.Codec, msgs []sdk.Msg, onlyFree bool) TxMatchFn { return false } for _, m := range subMsgs { - if !matchMsgFn(ctx, m, cdc) { + if !matchMsgFn(ctx, m) { return false } } return true - } else { return slices.Contains(msgTypes, reflect.TypeOf(msg)) } @@ -66,7 +64,7 @@ func NewTxMatchFn(cdc codec.Codec, msgs []sdk.Msg, onlyFree bool) TxMatchFn { return false } for _, msg := range msgs { - if !matchMsgFn(ctx, msg, cdc) { + if !matchMsgFn(ctx, msg) { return false } } From aa0f9378b3f969bb129759e74d88d4c59df88137 Mon Sep 17 00:00:00 2001 From: taobun Date: Thu, 24 Apr 2025 14:44:09 +0700 Subject: [PATCH 36/53] fix lanes.go --- app/lanes.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index bec3aa4de..0975cc45b 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -30,7 +30,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { app.Logger(), app.txConfig.TxEncoder(), "feedsLane", - mempool.NewTxMatchFn(app.appCodec, []sdk.Msg{&feedstypes.MsgSubmitSignalPrices{}}, true), + mempool.NewTxMatchFn([]sdk.Msg{&feedstypes.MsgSubmitSignalPrices{}}, true), math.LegacyMustNewDecFromStr("0.02"), math.LegacyMustNewDecFromStr("0.5"), sdkmempool.NewSenderNonceMempool(sdkmempool.SenderNonceMaxTxOpt(0)), @@ -44,7 +44,6 @@ func CreateLanes(app *BandApp) []*mempool.Lane { app.txConfig.TxEncoder(), "tssLane", mempool.NewTxMatchFn( - app.appCodec, []sdk.Msg{ &tsstypes.MsgSubmitDKGRound1{}, &tsstypes.MsgSubmitDKGRound2{}, @@ -69,7 +68,6 @@ func CreateLanes(app *BandApp) []*mempool.Lane { app.txConfig.TxEncoder(), "oracleRequestLane", mempool.NewTxMatchFn( - app.appCodec, []sdk.Msg{ &oracletypes.MsgRequestData{}, &channeltypes.MsgRecvPacket{}, // TODO: Only match oracle request packet @@ -89,7 +87,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { app.Logger(), app.txConfig.TxEncoder(), "oracleReportLane", - mempool.NewTxMatchFn(app.appCodec, []sdk.Msg{&oracletypes.MsgReportData{}}, true), + mempool.NewTxMatchFn([]sdk.Msg{&oracletypes.MsgReportData{}}, true), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), From fc3c6e636aabcdce1d4aa3988b0397bfa753e82c Mon Sep 17 00:00:00 2001 From: taobun Date: Thu, 24 Apr 2025 14:49:21 +0700 Subject: [PATCH 37/53] fix import --- app/lanes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index 0975cc45b..5ed67c620 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -1,10 +1,10 @@ package band import ( - "cosmossdk.io/math" - channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" sdkmempool "github.com/cosmos/cosmos-sdk/types/mempool" From 4256a932443a101beea43af2c970746bcabc33fe Mon Sep 17 00:00:00 2001 From: taobun Date: Thu, 24 Apr 2025 15:00:42 +0700 Subject: [PATCH 38/53] rename fn and remove unused ctx --- app/lanes.go | 8 ++++---- app/mempool/types.go | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index 5ed67c620..ae5e92fc8 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -30,7 +30,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { app.Logger(), app.txConfig.TxEncoder(), "feedsLane", - mempool.NewTxMatchFn([]sdk.Msg{&feedstypes.MsgSubmitSignalPrices{}}, true), + mempool.NewLaneTxMatchFn([]sdk.Msg{&feedstypes.MsgSubmitSignalPrices{}}, true), math.LegacyMustNewDecFromStr("0.02"), math.LegacyMustNewDecFromStr("0.5"), sdkmempool.NewSenderNonceMempool(sdkmempool.SenderNonceMaxTxOpt(0)), @@ -43,7 +43,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { app.Logger(), app.txConfig.TxEncoder(), "tssLane", - mempool.NewTxMatchFn( + mempool.NewLaneTxMatchFn( []sdk.Msg{ &tsstypes.MsgSubmitDKGRound1{}, &tsstypes.MsgSubmitDKGRound2{}, @@ -67,7 +67,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { app.Logger(), app.txConfig.TxEncoder(), "oracleRequestLane", - mempool.NewTxMatchFn( + mempool.NewLaneTxMatchFn( []sdk.Msg{ &oracletypes.MsgRequestData{}, &channeltypes.MsgRecvPacket{}, // TODO: Only match oracle request packet @@ -87,7 +87,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { app.Logger(), app.txConfig.TxEncoder(), "oracleReportLane", - mempool.NewTxMatchFn([]sdk.Msg{&oracletypes.MsgReportData{}}, true), + mempool.NewLaneTxMatchFn([]sdk.Msg{&oracletypes.MsgReportData{}}, true), math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), diff --git a/app/mempool/types.go b/app/mempool/types.go index eaf44f508..561462670 100644 --- a/app/mempool/types.go +++ b/app/mempool/types.go @@ -20,16 +20,16 @@ type TxWithInfo struct { type TxMatchFn func(sdk.Context, sdk.Tx) bool -func NewTxMatchFn(msgs []sdk.Msg, onlyFree bool) TxMatchFn { +func NewLaneTxMatchFn(msgs []sdk.Msg, onlyFree bool) TxMatchFn { msgTypes := make([]reflect.Type, len(msgs)) for i, msg := range msgs { msgTypes[i] = reflect.TypeOf(msg) } - var matchMsgFn func(sdk.Context, sdk.Msg) bool + var matchMsgFn func(sdk.Msg) bool - matchMsgFn = func(ctx sdk.Context, msg sdk.Msg) bool { + matchMsgFn = func(msg sdk.Msg) bool { msgExec, ok := msg.(*authz.MsgExec) if ok { subMsgs, err := msgExec.GetMessages() @@ -37,7 +37,7 @@ func NewTxMatchFn(msgs []sdk.Msg, onlyFree bool) TxMatchFn { return false } for _, m := range subMsgs { - if !matchMsgFn(ctx, m) { + if !matchMsgFn(m) { return false } } @@ -47,7 +47,7 @@ func NewTxMatchFn(msgs []sdk.Msg, onlyFree bool) TxMatchFn { } } - return func(ctx sdk.Context, tx sdk.Tx) bool { + return func(_ sdk.Context, tx sdk.Tx) bool { if onlyFree { gasTx, ok := tx.(sdk.FeeTx) if !ok { @@ -64,7 +64,7 @@ func NewTxMatchFn(msgs []sdk.Msg, onlyFree bool) TxMatchFn { return false } for _, msg := range msgs { - if !matchMsgFn(ctx, msg) { + if !matchMsgFn(msg) { return false } } From 5788d6332d05f0a0dc823d382c37cc03042e5907 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 24 Apr 2025 15:07:55 +0700 Subject: [PATCH 39/53] move tx size check logic to lane insert --- app/mempool/lane.go | 82 ++++++++++++++++--------------------- app/mempool/lane_test.go | 52 ++++++++++++----------- app/mempool/mempool.go | 37 +++-------------- app/mempool/mempool_test.go | 18 ++++---- app/mempool/proposal.go | 11 ----- 5 files changed, 78 insertions(+), 122 deletions(-) diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 5863b288a..b39c67d93 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -24,8 +24,8 @@ type Lane struct { name string matchFn func(ctx sdk.Context, tx sdk.Tx) bool - maxTransactionSpace math.LegacyDec - maxLaneSpace math.LegacyDec + maxTransactionBlockRatio math.LegacyDec + maxLaneBlockRatio math.LegacyDec laneMempool sdkmempool.Mempool @@ -52,8 +52,8 @@ func NewLane( signerExtractor sdkmempool.SignerExtractionAdapter, name string, matchFn func(sdk.Context, sdk.Tx) bool, - maxTransactionSpace math.LegacyDec, - maxLaneSpace math.LegacyDec, + maxTransactionBlockRatio math.LegacyDec, + maxLaneBlockRatio math.LegacyDec, laneMempool sdkmempool.Mempool, callbackAfterFillProposal func(isLaneLimitExceeded bool), ) *Lane { @@ -63,8 +63,8 @@ func NewLane( signerExtractor: signerExtractor, name: name, matchFn: matchFn, - maxTransactionSpace: maxTransactionSpace, - maxLaneSpace: maxLaneSpace, + maxTransactionBlockRatio: maxTransactionBlockRatio, + maxLaneBlockRatio: maxLaneBlockRatio, laneMempool: laneMempool, callbackAfterFillProposal: callbackAfterFillProposal, @@ -82,6 +82,23 @@ func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { return err } + sdkCtx := sdk.UnwrapSDKContext(ctx) + consensusParams := sdkCtx.ConsensusParams() + transactionLimit := NewBlockSpace( + uint64(consensusParams.Block.MaxBytes), + uint64(consensusParams.Block.MaxGas), + ).Scale(l.maxTransactionBlockRatio) + + if transactionLimit.IsExceededBy(txInfo.BlockSpace) { + return fmt.Errorf( + "transaction exceeds limit: tx_hash %s, lane %s, limit %s, tx_size %s", + txInfo.Hash, + l.name, + transactionLimit, + txInfo.BlockSpace, + ) + } + l.mu.Lock() defer l.mu.Unlock() @@ -137,20 +154,19 @@ func (l *Lane) Match(ctx sdk.Context, tx sdk.Tx) bool { // FillProposal fills the proposal with transactions from the lane mempool with its own limit. // It returns the total size and gas of the transactions added to the proposal. -// It also returns an iterator to the next transaction in the mempool and a list -// of transactions that were removed from the lane mempool. +// It also returns an iterator to the next transaction in the mempool. func (l *Lane) FillProposal( ctx sdk.Context, proposal *Proposal, -) (blockUsed BlockSpace, iterator sdkmempool.Iterator, txsToRemove []sdk.Tx) { +) (blockUsed BlockSpace, iterator sdkmempool.Iterator) { // if the lane is blocked, we do not add any transactions to the proposal. if l.blocked { + l.logger.Info("lane %s is blocked, skipping proposal filling", l.name) return } - // Get the transaction and lane limit for the lane. - transactionLimit := proposal.GetLimit(l.maxTransactionSpace) - laneLimit := proposal.GetLimit(l.maxLaneSpace) + // Get the lane limit for the lane. + laneLimit := proposal.maxBlockSpace.Scale(l.maxLaneBlockRatio) // Select all transactions in the mempool that are valid and not already in the // partial proposal. @@ -164,21 +180,9 @@ func (l *Lane) FillProposal( tx := iterator.Tx() txInfo, err := l.getTxInfo(tx) if err != nil { - l.logger.Info("failed to get hash of tx", "err", err) - - txsToRemove = append(txsToRemove, tx) - continue - } - - // if the transaction is exceed the limit, we remove it from the lane mempool. - if transactionLimit.IsExceededBy(txInfo.BlockSpace) { - l.logger.Info( - "failed to select tx for lane; tx exceeds limit", - "tx_hash", txInfo.Hash, - "lane", l.name, - ) - - txsToRemove = append(txsToRemove, tx) + // If the transaction is not valid, we skip it. + // This should never happen, but we log it for debugging purposes. + l.logger.Error("failed to get tx info", "err", err) continue } @@ -207,20 +211,16 @@ func (l *Lane) FillProposal( // FillProposalByIterator fills the proposal with transactions from the lane mempool with the given iterator and limit. // It returns the total size and gas of the transactions added to the proposal. -// It also returns a list of transactions that were removed from the lane mempool. func (l *Lane) FillProposalByIterator( proposal *Proposal, iterator sdkmempool.Iterator, laneLimit BlockSpace, -) (blockUsed BlockSpace, txsToRemove []sdk.Tx) { +) (blockUsed BlockSpace) { // if the lane is blocked, we do not add any transactions to the proposal. if l.blocked { return } - // get the transaction limit for the lane. - transactionLimit := proposal.GetLimit(l.maxTransactionSpace) - // Select all transactions in the mempool that are valid and not already in the partial proposal. for ; iterator != nil; iterator = iterator.Next() { // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. @@ -232,21 +232,9 @@ func (l *Lane) FillProposalByIterator( tx := iterator.Tx() txInfo, err := l.getTxInfo(tx) if err != nil { - l.logger.Info("failed to get hash of tx", "err", err) - - txsToRemove = append(txsToRemove, tx) - continue - } - - // if the transaction is exceed the limit, we remove it from the lane mempool. - if transactionLimit.IsExceededBy(txInfo.BlockSpace) { - l.logger.Info( - "failed to select tx for lane; tx exceeds limit", - "tx_hash", txInfo.Hash, - "lane", l.name, - ) - - txsToRemove = append(txsToRemove, tx) + // If the transaction is not valid, we skip it. + // This should never happen, but we log it for debugging purposes. + l.logger.Error("failed to get tx info", "err", err) continue } diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 73e8e3637..d70c6d2ec 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -6,6 +6,8 @@ import ( "github.com/stretchr/testify/suite" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "cosmossdk.io/log" "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" @@ -45,6 +47,12 @@ func (s *LaneTestSuite) SetupTest() { ) s.ctx = testCtx.Ctx.WithIsCheckTx(true) s.ctx = s.ctx.WithBlockHeight(1) + s.ctx = s.ctx.WithConsensusParams(cmtproto.ConsensusParams{ + Block: &cmtproto.BlockParams{ + MaxBytes: 1000000000000, + MaxGas: 100, + }, + }) } // ----------------------------------------------------------------------------- @@ -75,6 +83,13 @@ func (s *LaneTestSuite) TestLaneInsertAndCount() { // Ensure lane sees 2 transactions s.Require().Equal(2, lane.CountTx()) + + // Create over limit tx + tx3 := s.createSimpleTx(s.accounts[2], 0, 100) + s.Require().Error(lane.Insert(s.ctx, tx3)) + + // Ensure lane sees 2 transactions + s.Require().Equal(2, lane.CountTx()) } func (s *LaneTestSuite) TestLaneRemove() { @@ -118,16 +133,16 @@ func (s *LaneTestSuite) TestLaneFillProposal() { // Insert 3 transactions tx1 := s.createSimpleTx(s.accounts[0], 0, 20) tx2 := s.createSimpleTx(s.accounts[1], 1, 20) - tx3 := s.createSimpleTx(s.accounts[2], 2, 50) // This might be large - tx4 := s.createSimpleTx(s.accounts[2], 3, 30) // This might be large + tx3 := s.createSimpleTx(s.accounts[2], 2, 50) // This is too large + tx4 := s.createSimpleTx(s.accounts[2], 3, 30) // This is too large tx5 := s.createSimpleTx(s.accounts[2], 4, 20) tx6 := s.createSimpleTx(s.accounts[2], 5, 20) tx7 := s.createSimpleTx(s.accounts[2], 6, 10) tx8 := s.createSimpleTx(s.accounts[2], 7, 10) s.Require().NoError(lane.Insert(s.ctx, tx1)) s.Require().NoError(lane.Insert(s.ctx, tx2)) - s.Require().NoError(lane.Insert(s.ctx, tx3)) - s.Require().NoError(lane.Insert(s.ctx, tx4)) + s.Require().Error(lane.Insert(s.ctx, tx3)) + s.Require().Error(lane.Insert(s.ctx, tx4)) s.Require().NoError(lane.Insert(s.ctx, tx5)) s.Require().NoError(lane.Insert(s.ctx, tx6)) s.Require().NoError(lane.Insert(s.ctx, tx7)) @@ -141,15 +156,13 @@ func (s *LaneTestSuite) TestLaneFillProposal() { ) // FillProposal - blockUsed, iterator, txsToRemove := lane.FillProposal(s.ctx, &proposal) + blockUsed, iterator := lane.FillProposal(s.ctx, &proposal) // We expect tx1 and tx2 to be included in the proposal. // Then the gas should be over the limit, so tx3 is yet to be considered. s.Require().Equal(uint64(440), blockUsed.TxBytes()) s.Require().Equal(uint64(40), blockUsed.Gas(), "20 gas from tx1 and 20 gas from tx2") s.Require().NotNil(iterator) - s.Require(). - Len(txsToRemove, 0, "tx3 is yet to be considered") // The proposal should contain 2 transactions in Txs(). expectedIncludedTxs := s.getTxBytes(tx1, tx2) @@ -160,14 +173,11 @@ func (s *LaneTestSuite) TestLaneFillProposal() { remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) // Call FillProposalBy with the remainder limit and iterator from the previous call. - blockUsed, txsToRemove = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) + blockUsed = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) // We expect tx1, tx2, tx5, tx6, tx7, tx8 to be included in the proposal. s.Require().Equal(uint64(884), blockUsed.TxBytes()) s.Require().Equal(uint64(60), blockUsed.Gas()) - s.Require().Equal([]sdk.Tx{tx3, tx4}, txsToRemove) - s.Require(). - Len(txsToRemove, 2, "tx3 and tx4 are removed") // The proposal should contain 2 transactions in Txs(). expectedIncludedTxs = s.getTxBytes(tx1, tx2, tx5, tx6, tx7, tx8) @@ -210,7 +220,7 @@ func (s *LaneTestSuite) TestLaneCallbackAfterFillProposal() { ) // FillProposal - blockUsed, iterator, _ := lane.FillProposal(s.ctx, &proposal) + blockUsed, iterator := lane.FillProposal(s.ctx, &proposal) // We expect tx1 to be included in the proposal. s.Require().Equal(uint64(20), blockUsed.Gas(), "20 gas from tx1") @@ -238,7 +248,7 @@ func (s *LaneTestSuite) TestLaneCallbackAfterFillProposal() { ) // FillProposal - blockUsed, iterator, _ = lane.FillProposal(s.ctx, &proposal) + blockUsed, iterator = lane.FillProposal(s.ctx, &proposal) // We expect tx1 and tx2 to be included in the proposal. // Then the gas should be over the limit, so tx3 is yet to be considered. @@ -280,7 +290,7 @@ func (s *LaneTestSuite) TestLaneExactlyFilled() { ) // FillProposal - blockUsed, iterator, _ := lane.FillProposal(s.ctx, &proposal) + blockUsed, iterator := lane.FillProposal(s.ctx, &proposal) // We expect tx1 to be included in the proposal. s.Require().Equal(uint64(20), blockUsed.Gas(), "20 gas from tx1") @@ -308,7 +318,7 @@ func (s *LaneTestSuite) TestLaneExactlyFilled() { ) // FillProposal - blockUsed, iterator, _ = lane.FillProposal(s.ctx, &proposal) + blockUsed, iterator = lane.FillProposal(s.ctx, &proposal) // We expect tx1 and tx2 to be included in the proposal. // Then the gas should be over the limit, so tx3 is yet to be considered. @@ -342,11 +352,11 @@ func (s *LaneTestSuite) TestLaneBlocked() { // Insert 3 transactions tx1 := s.createSimpleTx(s.accounts[0], 0, 20) tx2 := s.createSimpleTx(s.accounts[1], 1, 20) - tx3 := s.createSimpleTx(s.accounts[2], 2, 30) + tx3 := s.createSimpleTx(s.accounts[2], 2, 30) // This is too large s.Require().NoError(lane.Insert(s.ctx, tx1)) s.Require().NoError(lane.Insert(s.ctx, tx2)) - s.Require().NoError(lane.Insert(s.ctx, tx3)) + s.Require().Error(lane.Insert(s.ctx, tx3)) // Create a proposal with block-limits proposal := NewProposal( @@ -356,7 +366,7 @@ func (s *LaneTestSuite) TestLaneBlocked() { ) // FillProposal - blockUsed, iterator, txsToRemove := lane.FillProposal(s.ctx, &proposal) + blockUsed, iterator := lane.FillProposal(s.ctx, &proposal) s.Require().True(lane.blocked) @@ -364,8 +374,6 @@ func (s *LaneTestSuite) TestLaneBlocked() { s.Require().Equal(uint64(0), blockUsed.TxBytes()) s.Require().Equal(uint64(0), blockUsed.Gas(), "0 gas") s.Require().Nil(iterator) - s.Require(). - Len(txsToRemove, 0, "no txs are removed") // The proposal should contain 0 transactions in Txs(). expectedIncludedTxs := [][]byte{} @@ -378,13 +386,11 @@ func (s *LaneTestSuite) TestLaneBlocked() { remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) // Call FillProposalBy with the remainder limit and iterator from the previous call. - blockUsed, txsToRemove = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) + blockUsed = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) // We expect no txs to be included in the proposal. s.Require().Equal(uint64(0), blockUsed.TxBytes()) s.Require().Equal(uint64(0), blockUsed.Gas()) - s.Require(). - Len(txsToRemove, 0, "no txs are removed") // The proposal should contain 0 transactions in Txs(). expectedIncludedTxs = [][]byte{} diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index e42d0545b..e25868573 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -97,23 +97,19 @@ func (m *Mempool) PrepareProposal(ctx sdk.Context, proposal Proposal) (Proposal, cacheCtx, _ := ctx.CacheContext() // 1) Perform the initial fill of proposals - laneIterators, txsToRemove, blockUsed := m.fillInitialProposals(cacheCtx, &proposal) + laneIterators, blockUsed := m.fillInitialProposals(cacheCtx, &proposal) // 2) Calculate the remaining block space remainderLimit := proposal.maxBlockSpace.Sub(blockUsed) // 3) Fill proposals with leftover space - m.fillRemainderProposals(&proposal, laneIterators, txsToRemove, remainderLimit) - - // 4) Remove the transactions that were invalidated from each lane - m.removeTxsFromLanes(txsToRemove) + m.fillRemainderProposals(&proposal, laneIterators, remainderLimit) return proposal, nil } // fillInitialProposals iterates over lanes, calling FillProposal. It returns: // - laneIterators: the Iterator for each lane -// - txsToRemove: slice-of-slice of transactions to be removed later // - totalSize: total block size used // - totalGas: total gas used func (m *Mempool) fillInitialProposals( @@ -121,23 +117,20 @@ func (m *Mempool) fillInitialProposals( proposal *Proposal, ) ( []sdkmempool.Iterator, - [][]sdk.Tx, BlockSpace, ) { totalBlockUsed := NewBlockSpace(0, 0) laneIterators := make([]sdkmempool.Iterator, len(m.lanes)) - txsToRemove := make([][]sdk.Tx, len(m.lanes)) for i, lane := range m.lanes { - blockUsed, iterator, txs := lane.FillProposal(ctx, proposal) + blockUsed, iterator := lane.FillProposal(ctx, proposal) totalBlockUsed = totalBlockUsed.Add(blockUsed) laneIterators[i] = iterator - txsToRemove[i] = txs } - return laneIterators, txsToRemove, totalBlockUsed + return laneIterators, totalBlockUsed } // fillRemainderProposals performs an additional fill on each lane using the leftover @@ -145,11 +138,10 @@ func (m *Mempool) fillInitialProposals( func (m *Mempool) fillRemainderProposals( proposal *Proposal, laneIterators []sdkmempool.Iterator, - txsToRemove [][]sdk.Tx, remainderLimit BlockSpace, ) { for i, lane := range m.lanes { - blockUsed, removedTxs := lane.FillProposalByIterator( + blockUsed := lane.FillProposalByIterator( proposal, laneIterators[i], remainderLimit, @@ -157,25 +149,6 @@ func (m *Mempool) fillRemainderProposals( // Decrement the remainder for subsequent lanes remainderLimit = remainderLimit.Sub(blockUsed) - - // Append any newly removed transactions to be removed - txsToRemove[i] = append(txsToRemove[i], removedTxs...) - } -} - -// removeTxsFromLanes loops through each lane and removes all transactions -// accumulated in txsToRemove. -func (m *Mempool) removeTxsFromLanes(txsToRemove [][]sdk.Tx) { - for i, lane := range m.lanes { - for _, tx := range txsToRemove[i] { - if err := lane.Remove(tx); err != nil { - m.logger.Error( - "failed to remove transactions from lane", - "lane", lane.name, - "err", err, - ) - } - } } } diff --git a/app/mempool/mempool_test.go b/app/mempool/mempool_test.go index bffea1f6e..ebd22cbb9 100644 --- a/app/mempool/mempool_test.go +++ b/app/mempool/mempool_test.go @@ -74,7 +74,7 @@ func (s *MempoolTestSuite) SetupTest() { s.ctx = s.ctx.WithBlockHeight(1) // Default consensus params - s.setBlockParams(100, 1000000000000) + s.setBlockParams(1000000000000, 100) } type EncodingConfig struct { @@ -130,12 +130,12 @@ func RandomAccounts(r *rand.Rand, n int) []Account { return accs } -func (s *MempoolTestSuite) setBlockParams(maxGasLimit, maxBlockSize int64) { +func (s *MempoolTestSuite) setBlockParams(maxBlockSize, maxBlockGas int64) { s.ctx = s.ctx.WithConsensusParams( tmprototypes.ConsensusParams{ Block: &tmprototypes.BlockParams{ MaxBytes: maxBlockSize, - MaxGas: maxGasLimit, + MaxGas: maxBlockGas, }, }, ) @@ -339,7 +339,12 @@ func (s *MempoolTestSuite) TestTxOverLimit() { s.Require().NoError(err) mem := s.newMempool() - s.Require().NoError(mem.Insert(s.ctx, tx)) + s.Require().Error(mem.Insert(s.ctx, tx)) + + // Ensure the tx is rejected + for _, lane := range mem.lanes { + s.Require().Equal(0, lane.CountTx()) + } proposal := NewProposal( log.NewTestLogger(s.T()), @@ -351,11 +356,6 @@ func (s *MempoolTestSuite) TestTxOverLimit() { s.Require().NoError(err) s.Require().Equal(0, len(result.txs)) - - // Ensure the tx is removed - for _, lane := range mem.lanes { - s.Require().Equal(0, lane.CountTx()) - } } // TestTxsOverGasLimit checks if txs over the gas limit are rejected diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index a39f400f8..cffa0b84c 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -7,7 +7,6 @@ import ( comettypes "github.com/cometbft/cometbft/types" "cosmossdk.io/log" - sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -71,16 +70,6 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { return nil } -// GetLimit returns the maximum block space available for a given ratio. -func (p *Proposal) GetLimit(ratio sdkmath.LegacyDec) BlockSpace { - // In the case where the ratio is zero, we return the max tx bytes remaining. - if ratio.IsZero() { - return p.maxBlockSpace.Sub(p.totalBlockSpace) - } - - return p.maxBlockSpace.Scale(ratio) -} - // GetBlockLimits retrieves the maximum block size and gas limit from context. func GetBlockLimits(ctx sdk.Context) (int64, uint64) { blockParams := ctx.ConsensusParams().Block From a6639419c9cea573bf68bb98795f1f762aca826a Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 24 Apr 2025 17:07:36 +0700 Subject: [PATCH 40/53] add test for check in insert, add test for tx bytes --- app/mempool/lane.go | 4 +- app/mempool/lane_test.go | 111 ++++++++++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 22 deletions(-) diff --git a/app/mempool/lane.go b/app/mempool/lane.go index b39c67d93..1dffa666f 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -214,7 +214,7 @@ func (l *Lane) FillProposal( func (l *Lane) FillProposalByIterator( proposal *Proposal, iterator sdkmempool.Iterator, - laneLimit BlockSpace, + limit BlockSpace, ) (blockUsed BlockSpace) { // if the lane is blocked, we do not add any transactions to the proposal. if l.blocked { @@ -225,7 +225,7 @@ func (l *Lane) FillProposalByIterator( for ; iterator != nil; iterator = iterator.Next() { // If the total size used or total gas used exceeds the limit, we break and do not attempt to include more txs. // We can tolerate a few bytes/gas over the limit, since we limit the size of each transaction. - if laneLimit.IsReachedBy(blockUsed) { + if limit.IsReachedBy(blockUsed) { break } diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index d70c6d2ec..46facaade 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -84,11 +84,26 @@ func (s *LaneTestSuite) TestLaneInsertAndCount() { // Ensure lane sees 2 transactions s.Require().Equal(2, lane.CountTx()) - // Create over limit tx + // Create over gas limit tx tx3 := s.createSimpleTx(s.accounts[2], 0, 100) s.Require().Error(lane.Insert(s.ctx, tx3)) - // Ensure lane sees 2 transactions + // Ensure lane does not insert tx3 + s.Require().Equal(2, lane.CountTx()) + + // set bytes limit to 1000 + s.ctx = s.ctx.WithConsensusParams(cmtproto.ConsensusParams{ + Block: &cmtproto.BlockParams{ + MaxBytes: 500, + MaxGas: 100, + }, + }) + + // Create over bytes limit tx + tx4 := s.createSimpleTx(s.accounts[2], 0, 0) // 217 bytes + s.Require().Error(lane.Insert(s.ctx, tx4)) + + // Ensure lane does not insert tx4 s.Require().Equal(2, lane.CountTx()) } @@ -116,7 +131,7 @@ func (s *LaneTestSuite) TestLaneRemove() { s.Require().Equal(0, lane.CountTx()) } -func (s *LaneTestSuite) TestLaneFillProposal() { +func (s *LaneTestSuite) TestLaneFillProposalWithGasLimit() { // Lane that matches all txs lane := NewLane( log.NewNopLogger(), @@ -133,20 +148,17 @@ func (s *LaneTestSuite) TestLaneFillProposal() { // Insert 3 transactions tx1 := s.createSimpleTx(s.accounts[0], 0, 20) tx2 := s.createSimpleTx(s.accounts[1], 1, 20) - tx3 := s.createSimpleTx(s.accounts[2], 2, 50) // This is too large - tx4 := s.createSimpleTx(s.accounts[2], 3, 30) // This is too large - tx5 := s.createSimpleTx(s.accounts[2], 4, 20) - tx6 := s.createSimpleTx(s.accounts[2], 5, 20) - tx7 := s.createSimpleTx(s.accounts[2], 6, 10) - tx8 := s.createSimpleTx(s.accounts[2], 7, 10) + tx3 := s.createSimpleTx(s.accounts[2], 2, 20) + tx4 := s.createSimpleTx(s.accounts[2], 3, 20) + tx5 := s.createSimpleTx(s.accounts[2], 4, 15) + tx6 := s.createSimpleTx(s.accounts[2], 5, 10) + s.Require().NoError(lane.Insert(s.ctx, tx1)) s.Require().NoError(lane.Insert(s.ctx, tx2)) - s.Require().Error(lane.Insert(s.ctx, tx3)) - s.Require().Error(lane.Insert(s.ctx, tx4)) + s.Require().NoError(lane.Insert(s.ctx, tx3)) + s.Require().NoError(lane.Insert(s.ctx, tx4)) s.Require().NoError(lane.Insert(s.ctx, tx5)) s.Require().NoError(lane.Insert(s.ctx, tx6)) - s.Require().NoError(lane.Insert(s.ctx, tx7)) - s.Require().NoError(lane.Insert(s.ctx, tx8)) // Create a proposal with block-limits proposal := NewProposal( @@ -159,8 +171,7 @@ func (s *LaneTestSuite) TestLaneFillProposal() { blockUsed, iterator := lane.FillProposal(s.ctx, &proposal) // We expect tx1 and tx2 to be included in the proposal. - // Then the gas should be over the limit, so tx3 is yet to be considered. - s.Require().Equal(uint64(440), blockUsed.TxBytes()) + // Since the 20% of 1000 is 200, the gas should be over the limit, so tx3 is yet to be considered. s.Require().Equal(uint64(40), blockUsed.Gas(), "20 gas from tx1 and 20 gas from tx2") s.Require().NotNil(iterator) @@ -175,13 +186,73 @@ func (s *LaneTestSuite) TestLaneFillProposal() { // Call FillProposalBy with the remainder limit and iterator from the previous call. blockUsed = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) - // We expect tx1, tx2, tx5, tx6, tx7, tx8 to be included in the proposal. - s.Require().Equal(uint64(884), blockUsed.TxBytes()) - s.Require().Equal(uint64(60), blockUsed.Gas()) + // We expect tx1, tx2, tx3, tx4, tx5 to be included in the proposal. + s.Require().Equal(uint64(55), blockUsed.Gas(), "20 gas from tx3 and 20 gas from tx4 + 15 gas from tx5") + + // The proposal should contain 5 transactions in Txs(). + expectedIncludedTxs = s.getTxBytes(tx1, tx2, tx3, tx4, tx5) + s.Require().Equal(5, len(proposal.txs), "five txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) +} + +func (s *LaneTestSuite) TestLaneFillProposalWithBytesLimit() { + // Lane that matches all txs + lane := NewLane( + log.NewNopLogger(), + s.encodingConfig.TxConfig.TxEncoder(), + sdkmempool.NewDefaultSignerExtractionAdapter(), + "testLane", + func(sdk.Context, sdk.Tx) bool { return true }, // accept all + math.LegacyMustNewDecFromStr("0.2"), + math.LegacyMustNewDecFromStr("0.3"), + sdkmempool.DefaultPriorityMempool(), + nil, + ) + + // Insert 3 transactions + tx1 := s.createSimpleTx(s.accounts[0], 0, 0) // 217 bytes + tx2 := s.createSimpleTx(s.accounts[1], 1, 0) // 219 bytes + tx3 := s.createSimpleTx(s.accounts[2], 2, 0) // 219 bytes + tx4 := s.createSimpleTx(s.accounts[2], 3, 0) // 219 bytes + tx5 := s.createSimpleTx(s.accounts[2], 4, 0) // 219 bytes + + s.Require().NoError(lane.Insert(s.ctx, tx1)) + s.Require().NoError(lane.Insert(s.ctx, tx2)) + s.Require().NoError(lane.Insert(s.ctx, tx3)) + s.Require().NoError(lane.Insert(s.ctx, tx4)) + s.Require().NoError(lane.Insert(s.ctx, tx5)) + + // Create a proposal with block-limits + proposal := NewProposal( + log.NewTestLogger(s.T()), + 1000, + 1000000000000, + ) + + // FillProposal + blockUsed, iterator := lane.FillProposal(s.ctx, &proposal) + + // We expect tx1 and tx2 to be included in the proposal. + // Since the 30% of 1000 is 300, the bytes should be over the limit, so tx3 is yet to be considered. + s.Require().Equal(uint64(436), blockUsed.TxBytes()) // The proposal should contain 2 transactions in Txs(). - expectedIncludedTxs = s.getTxBytes(tx1, tx2, tx5, tx6, tx7, tx8) - s.Require().Equal(6, len(proposal.txs), "two txs in the proposal") + expectedIncludedTxs := s.getTxBytes(tx1, tx2) + s.Require().Equal(2, len(proposal.txs), "two txs in the proposal") + s.Require().Equal(expectedIncludedTxs, proposal.txs) + + // Calculate the remaining block space + remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) + + // Call FillProposalBy with the remainder limit and iterator from the previous call. + blockUsed = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) + + // We expect tx1, tx2, tx3, tx4 to be included in the proposal. + s.Require().Equal(uint64(438), blockUsed.TxBytes()) + + // The proposal should contain 4 transactions in Txs(). + expectedIncludedTxs = s.getTxBytes(tx1, tx2, tx3, tx4) + s.Require().Equal(4, len(proposal.txs), "four txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.txs) } From 402fe340b72cf4061fdf97353c2715e1c4b40eca Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 24 Apr 2025 17:43:56 +0700 Subject: [PATCH 41/53] fix merge bug --- app/mempool/lane_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index c37743707..226dfeadc 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -197,7 +197,6 @@ func (s *LaneTestSuite) TestLaneFillProposalWithBytesLimit() { lane := NewLane( log.NewNopLogger(), s.encodingConfig.TxConfig.TxEncoder(), - sdkmempool.NewDefaultSignerExtractionAdapter(), "testLane", func(sdk.Context, sdk.Tx) bool { return true }, // accept all math.LegacyMustNewDecFromStr("0.2"), From f13a7b00335da4ec07fceefd66ac557fb9008531 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 24 Apr 2025 18:22:57 +0700 Subject: [PATCH 42/53] remove redundant test --- app/mempool/lane_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 226dfeadc..0476ddb25 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -416,11 +416,9 @@ func (s *LaneTestSuite) TestLaneBlocked() { // Insert 3 transactions tx1 := s.createSimpleTx(s.accounts[0], 0, 20) tx2 := s.createSimpleTx(s.accounts[1], 1, 20) - tx3 := s.createSimpleTx(s.accounts[2], 2, 30) // This is too large s.Require().NoError(lane.Insert(s.ctx, tx1)) s.Require().NoError(lane.Insert(s.ctx, tx2)) - s.Require().Error(lane.Insert(s.ctx, tx3)) // Create a proposal with block-limits proposal := NewProposal( From 3dd77375511e979a40d22bda7dd8bba111e00ea9 Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 25 Apr 2025 11:30:12 +0700 Subject: [PATCH 43/53] fix from comments --- app/mempool/proposal.go | 32 +++++++++++++++----------------- app/mempool/proposal_handler.go | 24 +++++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index a39f400f8..8d7cd1e33 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -12,17 +12,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// MaxUint64 is the maximum value of a uint64. -const MaxUint64 = math.MaxUint64 - // Proposal represents a block proposal under construction. type Proposal struct { logger log.Logger - // Txs is the list of transactions in the proposal. + // txs is the list of transactions in the proposal. txs [][]byte - // Cache helps quickly check for duplicates by tx hash. - cache map[string]struct{} + // seen helps quickly check for duplicates by tx hash. + seen map[string]struct{} // maxBlockSpace is the maximum block space available for this proposal. maxBlockSpace BlockSpace // totalBlockSpace is the total block space used by the proposal. @@ -34,7 +31,7 @@ func NewProposal(logger log.Logger, maxBlockSize uint64, maxGasLimit uint64) Pro return Proposal{ logger: logger, txs: make([][]byte, 0), - cache: make(map[string]struct{}), + seen: make(map[string]struct{}), maxBlockSpace: NewBlockSpace(maxBlockSize, maxGasLimit), totalBlockSpace: NewBlockSpace(0, 0), } @@ -42,7 +39,7 @@ func NewProposal(logger log.Logger, maxBlockSize uint64, maxGasLimit uint64) Pro // Contains returns true if the proposal already has a transaction with the given txHash. func (p *Proposal) Contains(txHash string) bool { - _, ok := p.cache[txHash] + _, ok := p.seen[txHash] return ok } @@ -64,7 +61,7 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { // Add transaction p.txs = append(p.txs, txInfo.TxBytes) - p.cache[txInfo.Hash] = struct{}{} + p.seen[txInfo.Hash] = struct{}{} p.totalBlockSpace = currentBlockSpace @@ -82,21 +79,22 @@ func (p *Proposal) GetLimit(ratio sdkmath.LegacyDec) BlockSpace { } // GetBlockLimits retrieves the maximum block size and gas limit from context. -func GetBlockLimits(ctx sdk.Context) (int64, uint64) { +func GetBlockLimits(ctx sdk.Context) (uint64, uint64) { blockParams := ctx.ConsensusParams().Block - var maxGasLimit uint64 + var maxBytesLimit uint64 + if blockParams.MaxBytes == -1 { + maxBytesLimit = uint64(comettypes.MaxBlockSizeBytes) + } else { + maxBytesLimit = uint64(blockParams.MaxBytes) + } + var maxGasLimit uint64 if blockParams.MaxGas == -1 { - maxGasLimit = MaxUint64 + maxGasLimit = math.MaxUint64 } else { maxGasLimit = uint64(blockParams.MaxGas) } - maxBytesLimit := blockParams.MaxBytes - if blockParams.MaxBytes == -1 { - maxBytesLimit = comettypes.MaxBlockSizeBytes - } - return maxBytesLimit, maxGasLimit } diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go index fd90dd932..87ada4334 100644 --- a/app/mempool/proposal_handler.go +++ b/app/mempool/proposal_handler.go @@ -13,10 +13,9 @@ import ( // ProposalHandler wraps ABCI++ PrepareProposal/ProcessProposal for the Mempool. type ProposalHandler struct { - logger log.Logger - txDecoder sdk.TxDecoder - Mempool *Mempool - useCustomProcessProposal bool + logger log.Logger + txDecoder sdk.TxDecoder + mempool *Mempool } // NewProposalHandler returns a new ABCI++ proposal handler for the Mempool. @@ -26,10 +25,9 @@ func NewProposalHandler( mempool *Mempool, ) *ProposalHandler { return &ProposalHandler{ - logger: logger, - txDecoder: txDecoder, - Mempool: mempool, - useCustomProcessProposal: false, // set to true if you want custom logic + logger: logger, + txDecoder: txDecoder, + mempool: mempool, } } @@ -52,11 +50,15 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { h.logger.Info("preparing proposal from Mempool", "height", req.Height) // Gather block limits - _, maxGasLimit := GetBlockLimits(ctx) - proposal := NewProposal(h.logger, uint64(req.MaxTxBytes), maxGasLimit) + maxBytesLimit, maxGasLimit := GetBlockLimits(ctx) + proposal := NewProposal( + h.logger, + min(uint64(req.MaxTxBytes), maxBytesLimit), + maxGasLimit, + ) // Populate proposal from Mempool - finalProposal, err := h.Mempool.PrepareProposal(ctx, proposal) + finalProposal, err := h.mempool.PrepareProposal(ctx, proposal) if err != nil { // If an error occurs, we can still return what we have or choose to return nothing h.logger.Error("failed to prepare proposal", "err", err) From 3b0097bb8994412dd6c53d841545647b0e4c748d Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 8 May 2025 14:37:35 +0700 Subject: [PATCH 44/53] fix from comments --- app/ante.go | 10 +--- app/lanes.go | 2 +- app/mempool/block_space.go | 27 +++++++---- app/mempool/lane.go | 28 +++++++---- app/mempool/lane_test.go | 15 ++---- app/mempool/mempool.go | 19 +++----- app/mempool/proposal.go | 45 +++++------------ app/mempool/proposal_handler.go | 42 +++++++++++++--- x/globalfee/feechecker/feechecker.go | 35 -------------- x/globalfee/feechecker/feechecker_test.go | 59 ----------------------- x/globalfee/feechecker/utils.go | 12 ----- 11 files changed, 95 insertions(+), 199 deletions(-) diff --git a/app/ante.go b/app/ante.go index 28630a1b0..4d5466268 100644 --- a/app/ante.go +++ b/app/ante.go @@ -81,14 +81,8 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { if options.TxFeeChecker == nil { feeChecker := feechecker.NewFeeChecker( - options.Cdc, - options.AuthzKeeper, - options.OracleKeeper, options.GlobalfeeKeeper, options.StakingKeeper, - options.TSSKeeper, - options.BandtssKeeper, - options.FeedsKeeper, ) options.TxFeeChecker = feeChecker.CheckTxFee } @@ -144,8 +138,8 @@ func NewIgnoreDecorator(decorator sdk.AnteDecorator, matchFns ...mempool.TxMatch func (ig IgnoreDecorator) AnteHandle( ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler, ) (sdk.Context, error) { - // IgnoreDecorator is only used for check tx. - if !ctx.IsCheckTx() { + // IgnoreDecorator is only used for check tx and re-check tx. + if !ctx.IsCheckTx() && !ctx.IsReCheckTx() { return ig.decorator.AnteHandle(ctx, tx, simulate, next) } diff --git a/app/lanes.go b/app/lanes.go index ae5e92fc8..e7885e32b 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -109,5 +109,5 @@ func CreateLanes(app *BandApp) []*mempool.Lane { nil, ) - return []*mempool.Lane{feedsLane, tssLane, oracleRequestLane, oracleReportLane, defaultLane} + return []*mempool.Lane{feedsLane, tssLane, oracleReportLane, oracleRequestLane, defaultLane} } diff --git a/app/mempool/block_space.go b/app/mempool/block_space.go index 69792d6d7..7fbd6d25d 100644 --- a/app/mempool/block_space.go +++ b/app/mempool/block_space.go @@ -4,6 +4,8 @@ import ( "fmt" "math" + ethmath "github.com/ethereum/go-ethereum/common/math" + sdkmath "cosmossdk.io/math" ) @@ -76,17 +78,15 @@ func (bs BlockSpace) Add(other BlockSpace) BlockSpace { var gas uint64 // Calculate txBytes - if bs.txBytes > math.MaxUint64-other.txBytes { + txBytes, carry := ethmath.SafeAdd(bs.txBytes, other.txBytes) + if carry { txBytes = math.MaxUint64 - } else { - txBytes = bs.txBytes + other.txBytes } // Calculate gas - if bs.gas > math.MaxUint64-other.gas { + gas, carry = ethmath.SafeAdd(bs.gas, other.gas) + if carry { gas = math.MaxUint64 - } else { - gas = bs.gas + other.gas } return BlockSpace{ @@ -96,11 +96,18 @@ func (bs BlockSpace) Add(other BlockSpace) BlockSpace { } // Scale returns a new BlockSpace with txBytes and gas multiplied by a decimal. -func (bs BlockSpace) Scale(dec sdkmath.LegacyDec) BlockSpace { - return BlockSpace{ - txBytes: dec.MulInt(sdkmath.NewIntFromUint64(bs.txBytes)).TruncateInt().Uint64(), - gas: dec.MulInt(sdkmath.NewIntFromUint64(bs.gas)).TruncateInt().Uint64(), +func (bs BlockSpace) Scale(dec sdkmath.LegacyDec) (BlockSpace, error) { + txBytes := dec.MulInt(sdkmath.NewIntFromUint64(bs.txBytes)).TruncateInt() + gas := dec.MulInt(sdkmath.NewIntFromUint64(bs.gas)).TruncateInt() + + if !txBytes.IsUint64() || !gas.IsUint64() { + return BlockSpace{}, fmt.Errorf("overflow") } + + return BlockSpace{ + txBytes: txBytes.Uint64(), + gas: gas.Uint64(), + }, nil } // --- Stringer --- diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 068b655b5..04b611c22 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -81,10 +81,14 @@ func (l *Lane) Insert(ctx context.Context, tx sdk.Tx) error { sdkCtx := sdk.UnwrapSDKContext(ctx) consensusParams := sdkCtx.ConsensusParams() - transactionLimit := NewBlockSpace( + + transactionLimit, err := NewBlockSpace( uint64(consensusParams.Block.MaxBytes), uint64(consensusParams.Block.MaxGas), ).Scale(l.maxTransactionBlockRatio) + if err != nil { + return err + } if transactionLimit.IsExceededBy(txInfo.BlockSpace) { return fmt.Errorf( @@ -163,7 +167,11 @@ func (l *Lane) FillProposal( } // Get the lane limit for the lane. - laneLimit := proposal.maxBlockSpace.Scale(l.maxLaneBlockRatio) + laneLimit, err := proposal.maxBlockSpace.Scale(l.maxLaneBlockRatio) + if err != nil { + l.logger.Error("failed to scale lane limit with err:", err) + return + } // Select all transactions in the mempool that are valid and not already in the // partial proposal. @@ -179,7 +187,7 @@ func (l *Lane) FillProposal( if err != nil { // If the transaction is not valid, we skip it. // This should never happen, but we log it for debugging purposes. - l.logger.Error("failed to get tx info", "err", err) + l.logger.Error("failed to get tx info with err:", err) continue } @@ -187,9 +195,9 @@ func (l *Lane) FillProposal( if err := proposal.Add(txInfo); err != nil { l.logger.Info( "failed to add tx to proposal", - "lane", l.name, - "tx_hash", txInfo.Hash, - "err", err, + "lane:", l.name, + "tx_hash:", txInfo.Hash, + "err:", err, ) break @@ -231,7 +239,7 @@ func (l *Lane) FillProposalByIterator( if err != nil { // If the transaction is not valid, we skip it. // This should never happen, but we log it for debugging purposes. - l.logger.Error("failed to get tx info", "err", err) + l.logger.Error("failed to get tx info with err:", err) continue } @@ -239,9 +247,9 @@ func (l *Lane) FillProposalByIterator( if err := proposal.Add(txInfo); err != nil { l.logger.Info( "failed to add tx to proposal", - "lane", l.name, - "tx_hash", txInfo.Hash, - "err", err, + "lane:", l.name, + "tx_hash:", txInfo.Hash, + "err:", err, ) break diff --git a/app/mempool/lane_test.go b/app/mempool/lane_test.go index 0476ddb25..b89948ee4 100644 --- a/app/mempool/lane_test.go +++ b/app/mempool/lane_test.go @@ -177,11 +177,8 @@ func (s *LaneTestSuite) TestLaneFillProposalWithGasLimit() { s.Require().Equal(2, len(proposal.txs), "two txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.txs) - // Calculate the remaining block space - remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) - // Call FillProposalBy with the remainder limit and iterator from the previous call. - blockUsed = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) + blockUsed = lane.FillProposalByIterator(&proposal, iterator, proposal.GetRemainingBlockSpace()) // We expect tx1, tx2, tx3, tx4, tx5 to be included in the proposal. s.Require().Equal(uint64(55), blockUsed.Gas(), "20 gas from tx3 and 20 gas from tx4 + 15 gas from tx5") @@ -237,11 +234,8 @@ func (s *LaneTestSuite) TestLaneFillProposalWithBytesLimit() { s.Require().Equal(2, len(proposal.txs), "two txs in the proposal") s.Require().Equal(expectedIncludedTxs, proposal.txs) - // Calculate the remaining block space - remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) - // Call FillProposalBy with the remainder limit and iterator from the previous call. - blockUsed = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) + blockUsed = lane.FillProposalByIterator(&proposal, iterator, proposal.GetRemainingBlockSpace()) // We expect tx1, tx2, tx3, tx4 to be included in the proposal. s.Require().Equal(uint64(438), blockUsed.TxBytes()) @@ -444,11 +438,8 @@ func (s *LaneTestSuite) TestLaneBlocked() { s.Require().Equal(lane.mempool.Select(s.ctx, nil).Tx(), tx1) - // Calculate the remaining block space - remainderLimit := proposal.maxBlockSpace.Sub(proposal.totalBlockSpace) - // Call FillProposalBy with the remainder limit and iterator from the previous call. - blockUsed = lane.FillProposalByIterator(&proposal, iterator, remainderLimit) + blockUsed = lane.FillProposalByIterator(&proposal, iterator, proposal.GetRemainingBlockSpace()) // We expect no txs to be included in the proposal. s.Require().Equal(uint64(0), blockUsed.TxBytes()) diff --git a/app/mempool/mempool.go b/app/mempool/mempool.go index e25868573..ee3869d5d 100644 --- a/app/mempool/mempool.go +++ b/app/mempool/mempool.go @@ -96,14 +96,11 @@ func (m *Mempool) Remove(tx sdk.Tx) (err error) { func (m *Mempool) PrepareProposal(ctx sdk.Context, proposal Proposal) (Proposal, error) { cacheCtx, _ := ctx.CacheContext() - // 1) Perform the initial fill of proposals - laneIterators, blockUsed := m.fillInitialProposals(cacheCtx, &proposal) + // Perform the initial fill of proposals + laneIterators, _ := m.fillInitialProposals(cacheCtx, &proposal) - // 2) Calculate the remaining block space - remainderLimit := proposal.maxBlockSpace.Sub(blockUsed) - - // 3) Fill proposals with leftover space - m.fillRemainderProposals(&proposal, laneIterators, remainderLimit) + // Fill proposals with leftover space + m.fillRemainderProposals(&proposal, laneIterators) return proposal, nil } @@ -138,17 +135,13 @@ func (m *Mempool) fillInitialProposals( func (m *Mempool) fillRemainderProposals( proposal *Proposal, laneIterators []sdkmempool.Iterator, - remainderLimit BlockSpace, ) { for i, lane := range m.lanes { - blockUsed := lane.FillProposalByIterator( + lane.FillProposalByIterator( proposal, laneIterators[i], - remainderLimit, + proposal.GetRemainingBlockSpace(), ) - - // Decrement the remainder for subsequent lanes - remainderLimit = remainderLimit.Sub(blockUsed) } } diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index 7083d7050..d63c6ccb2 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -2,13 +2,8 @@ package mempool import ( "fmt" - "math" - - comettypes "github.com/cometbft/cometbft/types" "cosmossdk.io/log" - - sdk "github.com/cosmos/cosmos-sdk/types" ) // Proposal represents a block proposal under construction. @@ -21,18 +16,18 @@ type Proposal struct { seen map[string]struct{} // maxBlockSpace is the maximum block space available for this proposal. maxBlockSpace BlockSpace - // totalBlockSpace is the total block space used by the proposal. - totalBlockSpace BlockSpace + // totalBlockSpaceUsed is the total block space used by the proposal. + totalBlockSpaceUsed BlockSpace } // NewProposal returns a new empty proposal constrained by max block size and max gas limit. func NewProposal(logger log.Logger, maxBlockSize uint64, maxGasLimit uint64) Proposal { return Proposal{ - logger: logger, - txs: make([][]byte, 0), - seen: make(map[string]struct{}), - maxBlockSpace: NewBlockSpace(maxBlockSize, maxGasLimit), - totalBlockSpace: NewBlockSpace(0, 0), + logger: logger, + txs: make([][]byte, 0), + seen: make(map[string]struct{}), + maxBlockSpace: NewBlockSpace(maxBlockSize, maxGasLimit), + totalBlockSpaceUsed: NewBlockSpace(0, 0), } } @@ -48,7 +43,7 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { return fmt.Errorf("transaction already in proposal: %s", txInfo.Hash) } - currentBlockSpace := p.totalBlockSpace.Add(txInfo.BlockSpace) + currentBlockSpace := p.totalBlockSpaceUsed.Add(txInfo.BlockSpace) // Check block size limit if p.maxBlockSpace.IsExceededBy(currentBlockSpace) { @@ -62,28 +57,12 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { p.txs = append(p.txs, txInfo.TxBytes) p.seen[txInfo.Hash] = struct{}{} - p.totalBlockSpace = currentBlockSpace + p.totalBlockSpaceUsed = currentBlockSpace return nil } -// GetBlockLimits retrieves the maximum block size and gas limit from context. -func GetBlockLimits(ctx sdk.Context) (uint64, uint64) { - blockParams := ctx.ConsensusParams().Block - - var maxBytesLimit uint64 - if blockParams.MaxBytes == -1 { - maxBytesLimit = uint64(comettypes.MaxBlockSizeBytes) - } else { - maxBytesLimit = uint64(blockParams.MaxBytes) - } - - var maxGasLimit uint64 - if blockParams.MaxGas == -1 { - maxGasLimit = math.MaxUint64 - } else { - maxGasLimit = uint64(blockParams.MaxGas) - } - - return maxBytesLimit, maxGasLimit +// GetRemainingBlockSpace returns the remaining block space available for the proposal. +func (p *Proposal) GetRemainingBlockSpace() BlockSpace { + return p.maxBlockSpace.Sub(p.totalBlockSpaceUsed) } diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go index 87ada4334..cbc44ab57 100644 --- a/app/mempool/proposal_handler.go +++ b/app/mempool/proposal_handler.go @@ -2,8 +2,10 @@ package mempool import ( "fmt" + "math" abci "github.com/cometbft/cometbft/abci/types" + comettypes "github.com/cometbft/cometbft/types" "cosmossdk.io/log" @@ -50,10 +52,17 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { h.logger.Info("preparing proposal from Mempool", "height", req.Height) // Gather block limits - maxBytesLimit, maxGasLimit := GetBlockLimits(ctx) + maxBytesLimit, maxGasLimit := getBlockLimits(ctx) + var maxTxBytes uint64 + if req.MaxTxBytes < 0 { + maxTxBytes = maxBytesLimit + } else { + maxTxBytes = uint64(req.MaxTxBytes) + } + proposal := NewProposal( h.logger, - min(uint64(req.MaxTxBytes), maxBytesLimit), + min(maxTxBytes, maxBytesLimit), maxGasLimit, ) @@ -67,10 +76,10 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { h.logger.Info( "prepared proposal", - "num_txs", len(finalProposal.txs), - "total_block_space", finalProposal.totalBlockSpace.String(), - "max_block_space", finalProposal.maxBlockSpace.String(), - "height", req.Height, + "num_txs:", len(finalProposal.txs), + "total_block_space:", finalProposal.totalBlockSpaceUsed.String(), + "max_block_space:", finalProposal.maxBlockSpace.String(), + "height:", req.Height, ) return &abci.ResponsePrepareProposal{ @@ -83,3 +92,24 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { func (h *ProposalHandler) ProcessProposalHandler() sdk.ProcessProposalHandler { return baseapp.NoOpProcessProposal() } + +// getBlockLimits retrieves the maximum block size and gas limit from context. +func getBlockLimits(ctx sdk.Context) (uint64, uint64) { + blockParams := ctx.ConsensusParams().Block + + var maxBytesLimit uint64 + if blockParams.MaxBytes == -1 { + maxBytesLimit = uint64(comettypes.MaxBlockSizeBytes) + } else { + maxBytesLimit = uint64(blockParams.MaxBytes) + } + + var maxGasLimit uint64 + if blockParams.MaxGas == -1 { + maxGasLimit = math.MaxUint64 + } else { + maxGasLimit = uint64(blockParams.MaxGas) + } + + return maxBytesLimit, maxGasLimit +} diff --git a/x/globalfee/feechecker/feechecker.go b/x/globalfee/feechecker/feechecker.go index 32a303437..13aefc5ec 100644 --- a/x/globalfee/feechecker/feechecker.go +++ b/x/globalfee/feechecker/feechecker.go @@ -3,60 +3,25 @@ package feechecker import ( sdkmath "cosmossdk.io/math" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - bandtsskeeper "github.com/bandprotocol/chain/v3/x/bandtss/keeper" - feedskeeper "github.com/bandprotocol/chain/v3/x/feeds/keeper" - feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" "github.com/bandprotocol/chain/v3/x/globalfee/keeper" - oraclekeeper "github.com/bandprotocol/chain/v3/x/oracle/keeper" - tsskeeper "github.com/bandprotocol/chain/v3/x/tss/keeper" - tsstypes "github.com/bandprotocol/chain/v3/x/tss/types" ) type FeeChecker struct { - cdc codec.Codec - - AuthzKeeper *authzkeeper.Keeper - OracleKeeper *oraclekeeper.Keeper GlobalfeeKeeper *keeper.Keeper StakingKeeper *stakingkeeper.Keeper - TSSKeeper *tsskeeper.Keeper - BandtssKeeper *bandtsskeeper.Keeper - FeedsKeeper *feedskeeper.Keeper - - TSSMsgServer tsstypes.MsgServer - FeedsMsgServer feedstypes.MsgServer } func NewFeeChecker( - cdc codec.Codec, - authzKeeper *authzkeeper.Keeper, - oracleKeeper *oraclekeeper.Keeper, globalfeeKeeper *keeper.Keeper, stakingKeeper *stakingkeeper.Keeper, - tssKeeper *tsskeeper.Keeper, - bandtssKeeper *bandtsskeeper.Keeper, - feedsKeeper *feedskeeper.Keeper, ) FeeChecker { - tssMsgServer := tsskeeper.NewMsgServerImpl(tssKeeper) - feedsMsgServer := feedskeeper.NewMsgServerImpl(*feedsKeeper) - return FeeChecker{ - cdc: cdc, - AuthzKeeper: authzKeeper, - OracleKeeper: oracleKeeper, GlobalfeeKeeper: globalfeeKeeper, StakingKeeper: stakingKeeper, - TSSKeeper: tssKeeper, - BandtssKeeper: bandtssKeeper, - FeedsKeeper: feedsKeeper, - TSSMsgServer: tssMsgServer, - FeedsMsgServer: feedsMsgServer, } } diff --git a/x/globalfee/feechecker/feechecker_test.go b/x/globalfee/feechecker/feechecker_test.go index dc879e797..c7245a8ad 100644 --- a/x/globalfee/feechecker/feechecker_test.go +++ b/x/globalfee/feechecker/feechecker_test.go @@ -2,7 +2,6 @@ package feechecker_test import ( "testing" - "time" "github.com/stretchr/testify/suite" protov2 "google.golang.org/protobuf/proto" @@ -15,14 +14,10 @@ import ( "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/authz" bandtesting "github.com/bandprotocol/chain/v3/testing" - bandtsstypes "github.com/bandprotocol/chain/v3/x/bandtss/types" - feedstypes "github.com/bandprotocol/chain/v3/x/feeds/types" "github.com/bandprotocol/chain/v3/x/globalfee/feechecker" oracletypes "github.com/bandprotocol/chain/v3/x/oracle/types" - tsstypes "github.com/bandprotocol/chain/v3/x/tss/types" ) var ( @@ -78,12 +73,6 @@ func (suite *FeeCheckerTestSuite) SetupTest() { app := bandtesting.SetupWithCustomHome(false, dir) ctx := app.BaseApp.NewUncachedContext(false, cmtproto.Header{}) - // Activate validators - for _, v := range bandtesting.Validators { - err := app.OracleKeeper.Activate(ctx, v.ValAddress) - suite.Require().NoError(err) - } - _, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: app.LastBlockHeight() + 1}) suite.Require().NoError(err) _, err = app.Commit() @@ -93,57 +82,9 @@ func (suite *FeeCheckerTestSuite) SetupTest() { WithIsCheckTx(true). WithMinGasPrices(sdk.DecCoins{{Denom: "uband", Amount: sdkmath.LegacyNewDecWithPrec(1, 4)}}) - err = app.OracleKeeper.GrantReporter(suite.ctx, bandtesting.Validators[0].ValAddress, bandtesting.Alice.Address) - suite.Require().NoError(err) - - expiration := ctx.BlockTime().Add(1000 * time.Hour) - - msgTypeURLs := []sdk.Msg{&tsstypes.MsgSubmitDEs{}, &feedstypes.MsgSubmitSignalPrices{}} - for _, msg := range msgTypeURLs { - err = app.AuthzKeeper.SaveGrant( - ctx, - bandtesting.Alice.Address, - bandtesting.Validators[0].Address, - authz.NewGenericAuthorization(sdk.MsgTypeURL(msg)), - &expiration, - ) - suite.Require().NoError(err) - } - - // mock setup bandtss module - app.BandtssKeeper.SetCurrentGroup(ctx, bandtsstypes.NewCurrentGroup(1, ctx.BlockTime())) - app.BandtssKeeper.SetMember(ctx, bandtsstypes.Member{ - Address: bandtesting.Validators[0].Address.String(), - IsActive: true, - GroupID: 1, - }) - - req := oracletypes.NewRequest( - 1, - BasicCalldata, - []sdk.ValAddress{bandtesting.Validators[0].ValAddress}, - 1, - 1, - bandtesting.ParseTime(0), - "", - nil, - nil, - 0, - 0, - bandtesting.FeePayer.Address.String(), - bandtesting.Coins100band, - ) - suite.requestID = app.OracleKeeper.AddRequest(suite.ctx, req) - suite.FeeChecker = feechecker.NewFeeChecker( - app.AppCodec(), - &app.AuthzKeeper, - &app.OracleKeeper, &app.GlobalFeeKeeper, app.StakingKeeper, - app.TSSKeeper, - &app.BandtssKeeper, - &app.FeedsKeeper, ) } diff --git a/x/globalfee/feechecker/utils.go b/x/globalfee/feechecker/utils.go index c44c0bca2..5c4986c98 100644 --- a/x/globalfee/feechecker/utils.go +++ b/x/globalfee/feechecker/utils.go @@ -4,9 +4,6 @@ import ( "math" sdk "github.com/cosmos/cosmos-sdk/types" - - oraclekeeper "github.com/bandprotocol/chain/v3/x/oracle/keeper" - oracletypes "github.com/bandprotocol/chain/v3/x/oracle/types" ) // getTxPriority returns priority of the provided fee based on gas prices of uband @@ -57,12 +54,3 @@ func CombinedGasPricesRequirement(globalMinGasPrices, minGasPrices sdk.DecCoins) return allGasPrices.Sort() } - -func checkValidMsgReport(ctx sdk.Context, oracleKeeper *oraclekeeper.Keeper, msg *oracletypes.MsgReportData) error { - validator, err := sdk.ValAddressFromBech32(msg.Validator) - if err != nil { - return err - } - - return oracleKeeper.CheckValidReport(ctx, msg.RequestID, validator, msg.RawReports) -} From 775ed3670b5eea901c06111ba670b78da613c79f Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 8 May 2025 15:48:19 +0700 Subject: [PATCH 45/53] use safesub and remove colon --- app/mempool/block_space.go | 10 ++++------ app/mempool/lane.go | 12 ++++++------ app/mempool/proposal.go | 8 ++++---- app/mempool/proposal_handler.go | 8 ++++---- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/app/mempool/block_space.go b/app/mempool/block_space.go index 7fbd6d25d..e68651415 100644 --- a/app/mempool/block_space.go +++ b/app/mempool/block_space.go @@ -53,17 +53,15 @@ func (bs BlockSpace) Sub(other BlockSpace) BlockSpace { var gas uint64 // Calculate txBytes - if other.txBytes > bs.txBytes { + txBytes, borrowOut := ethmath.SafeSub(bs.txBytes, other.txBytes) + if borrowOut { txBytes = 0 - } else { - txBytes = bs.txBytes - other.txBytes } // Calculate gas - if other.gas > bs.gas { + gas, borrowOut = ethmath.SafeSub(bs.gas, other.gas) + if borrowOut { gas = 0 - } else { - gas = bs.gas - other.gas } return BlockSpace{ diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 04b611c22..665927ea6 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -195,9 +195,9 @@ func (l *Lane) FillProposal( if err := proposal.Add(txInfo); err != nil { l.logger.Info( "failed to add tx to proposal", - "lane:", l.name, - "tx_hash:", txInfo.Hash, - "err:", err, + "lane", l.name, + "tx_hash", txInfo.Hash, + "err", err, ) break @@ -247,9 +247,9 @@ func (l *Lane) FillProposalByIterator( if err := proposal.Add(txInfo); err != nil { l.logger.Info( "failed to add tx to proposal", - "lane:", l.name, - "tx_hash:", txInfo.Hash, - "err:", err, + "lane", l.name, + "tx_hash", txInfo.Hash, + "err", err, ) break diff --git a/app/mempool/proposal.go b/app/mempool/proposal.go index d63c6ccb2..99dcc1811 100644 --- a/app/mempool/proposal.go +++ b/app/mempool/proposal.go @@ -43,13 +43,13 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { return fmt.Errorf("transaction already in proposal: %s", txInfo.Hash) } - currentBlockSpace := p.totalBlockSpaceUsed.Add(txInfo.BlockSpace) + currentBlockSpaceUsed := p.totalBlockSpaceUsed.Add(txInfo.BlockSpace) // Check block size limit - if p.maxBlockSpace.IsExceededBy(currentBlockSpace) { + if p.maxBlockSpace.IsExceededBy(currentBlockSpaceUsed) { return fmt.Errorf( "transaction space exceeds max block space: %s > %s", - currentBlockSpace.String(), p.maxBlockSpace.String(), + currentBlockSpaceUsed.String(), p.maxBlockSpace.String(), ) } @@ -57,7 +57,7 @@ func (p *Proposal) Add(txInfo TxWithInfo) error { p.txs = append(p.txs, txInfo.TxBytes) p.seen[txInfo.Hash] = struct{}{} - p.totalBlockSpaceUsed = currentBlockSpace + p.totalBlockSpaceUsed = currentBlockSpaceUsed return nil } diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go index cbc44ab57..4378492ca 100644 --- a/app/mempool/proposal_handler.go +++ b/app/mempool/proposal_handler.go @@ -76,10 +76,10 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { h.logger.Info( "prepared proposal", - "num_txs:", len(finalProposal.txs), - "total_block_space:", finalProposal.totalBlockSpaceUsed.String(), - "max_block_space:", finalProposal.maxBlockSpace.String(), - "height:", req.Height, + "num_txs", len(finalProposal.txs), + "total_block_space", finalProposal.totalBlockSpaceUsed.String(), + "max_block_space", finalProposal.maxBlockSpace.String(), + "height", req.Height, ) return &abci.ResponsePrepareProposal{ From adfd33283f85ef3780244d3804113270ba95ab40 Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 13 May 2025 15:47:42 +0700 Subject: [PATCH 46/53] change tx cap for tss to 5% --- app/lanes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lanes.go b/app/lanes.go index e7885e32b..64d256dd4 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -54,7 +54,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { }, true, ), - math.LegacyMustNewDecFromStr("0.02"), + math.LegacyMustNewDecFromStr("0.05"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), nil, From 164c6a0edc215fb3e811be0441766fb450e8fb95 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Tue, 13 May 2025 15:53:49 +0700 Subject: [PATCH 47/53] change from 20 to 10 (#687) --- cylinder/README.md | 2 +- scripts/bandtss/start_cylinder.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cylinder/README.md b/cylinder/README.md index 72750823e..27f8354d8 100644 --- a/cylinder/README.md +++ b/cylinder/README.md @@ -109,7 +109,7 @@ cylinder config chain-id $CHAIN_ID --home $CYLINDER_HOME_PATH cylinder config node $RPC_URL --home $CYLINDER_HOME_PATH cylinder config granter $(bandd keys show $WALLET_NAME -a --keyring-backend test) --home $CYLINDER_HOME_PATH cylinder config gas-prices "0uband" --home $CYLINDER_HOME_PATH -cylinder config max-messages 20 --home $CYLINDER_HOME_PATH +cylinder config max-messages 10 --home $CYLINDER_HOME_PATH cylinder config broadcast-timeout "5m" --home $CYLINDER_HOME_PATH cylinder config rpc-poll-interval "1s" --home $CYLINDER_HOME_PATH cylinder config max-try 5 --home $CYLINDER_HOME_PATH diff --git a/scripts/bandtss/start_cylinder.sh b/scripts/bandtss/start_cylinder.sh index 93c7d54e9..0247f6084 100755 --- a/scripts/bandtss/start_cylinder.sh +++ b/scripts/bandtss/start_cylinder.sh @@ -19,7 +19,7 @@ cylinder config chain-id bandchain --home $HOME_PATH cylinder config granter $(bandd keys show $KEY -a --keyring-backend test) --home $HOME_PATH # setup max-messages to cylinder config -cylinder config max-messages 20 --home $HOME_PATH +cylinder config max-messages 10 --home $HOME_PATH # setup broadcast-timeout to cylinder config cylinder config broadcast-timeout "5m" --home $HOME_PATH From da5fdd19761895414c2fd10cd02fdac53204da4a Mon Sep 17 00:00:00 2001 From: colmazia Date: Tue, 13 May 2025 17:08:13 +0700 Subject: [PATCH 48/53] fix comment --- app/lanes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lanes.go b/app/lanes.go index 64d256dd4..9ca58017a 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -38,7 +38,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { ) // tssLane handles TSS transactions. - // Each transaction has a gas limit of 2%, and the total gas limit for the lane is 20%. + // Each transaction has a gas limit of 5%, and the total gas limit for the lane is 20%. tssLane := mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), From bae8fef8cbd4b980996155488338d4cac1ed401c Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 16 May 2025 11:50:03 +0700 Subject: [PATCH 49/53] remove custom checktx --- app/app.go | 67 ------------------------------------------------------ 1 file changed, 67 deletions(-) diff --git a/app/app.go b/app/app.go index 6629c12a0..41a6ae21e 100644 --- a/app/app.go +++ b/app/app.go @@ -40,7 +40,6 @@ import ( "github.com/cosmos/cosmos-sdk/std" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/msgservice" sigtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" @@ -538,69 +537,3 @@ func (app *BandApp) AutoCliOpts() autocli.AppOptions { ConsensusAddressCodec: authcodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()), } } - -// CheckTx returns a CheckTx handler that wraps a given CheckTx handler and evicts txs that are not -// in the app-side mempool on ReCheckTx. -func (app *BandApp) CheckTx(req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { - // decode tx - tx, err := app.BaseApp.TxDecode(req.Tx) - if err != nil { - return sdkerrors.ResponseCheckTxWithEvents( - fmt.Errorf("failed to decode tx: %w", err), - 0, - 0, - nil, - false, - ), nil - } - - mempool := app.Mempool().(*mempool.Mempool) - - isRecheck := req.Type == abci.CheckTxType_Recheck - txInMempool := mempool.Contains(tx) - - // if the mode is Recheck and the app's mempool does not contain the given tx, we fail - // immediately, to purge the tx from the comet mempool. - if isRecheck && !txInMempool { - app.Logger().Debug( - "tx from comet mempool not found in app-side mempool", - "tx", tx, - ) - - return sdkerrors.ResponseCheckTxWithEvents( - fmt.Errorf("tx from comet mempool not found in app-side mempool"), - 0, - 0, - nil, - false, - ), nil - } - - // prepare cleanup closure to remove tx if marked - removeTx := false - defer func() { - if removeTx { - // remove the tx - if err := mempool.Remove(tx); err != nil { - app.Logger().Debug( - "failed to remove tx from app-side mempool when purging for re-check failure", - "removal-err", err, - ) - } - } - }() - - // run the checkTxHandler - res, checkTxError := app.BaseApp.CheckTx(req) - // if Recheck fails for a transaction, we'll need to explicitly purge the tx from - // the app-side mempool - if isInvalidCheckTxExecution(res, checkTxError) && isRecheck && txInMempool { - removeTx = true - } - - return res, checkTxError -} - -func isInvalidCheckTxExecution(resp *abci.ResponseCheckTx, checkTxErr error) bool { - return resp == nil || resp.Code != 0 || checkTxErr != nil -} From f25ffe77a7d49a5fa82bd868ea443d3f0ce0789b Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 16 May 2025 12:21:38 +0700 Subject: [PATCH 50/53] fix from comments --- app/mempool/block_space.go | 2 +- app/mempool/lane.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/mempool/block_space.go b/app/mempool/block_space.go index e68651415..78715832a 100644 --- a/app/mempool/block_space.go +++ b/app/mempool/block_space.go @@ -99,7 +99,7 @@ func (bs BlockSpace) Scale(dec sdkmath.LegacyDec) (BlockSpace, error) { gas := dec.MulInt(sdkmath.NewIntFromUint64(bs.gas)).TruncateInt() if !txBytes.IsUint64() || !gas.IsUint64() { - return BlockSpace{}, fmt.Errorf("overflow") + return BlockSpace{}, fmt.Errorf("block space scaling overflow: block_space %s, dec %s", bs, dec) } return BlockSpace{ diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 665927ea6..2e12dcccc 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -21,7 +21,7 @@ type Lane struct { logger log.Logger txEncoder sdk.TxEncoder name string - matchFn func(ctx sdk.Context, tx sdk.Tx) bool + txMatchFn func(ctx sdk.Context, tx sdk.Tx) bool maxTransactionBlockRatio math.LegacyDec maxLaneBlockRatio math.LegacyDec @@ -49,7 +49,7 @@ func NewLane( logger log.Logger, txEncoder sdk.TxEncoder, name string, - matchFn TxMatchFn, + txMatchFn TxMatchFn, maxTransactionBlockRatio math.LegacyDec, maxLaneBlockRatio math.LegacyDec, mempool sdkmempool.Mempool, @@ -59,7 +59,7 @@ func NewLane( logger: logger, txEncoder: txEncoder, name: name, - matchFn: matchFn, + txMatchFn: txMatchFn, maxTransactionBlockRatio: maxTransactionBlockRatio, maxLaneBlockRatio: maxLaneBlockRatio, mempool: mempool, @@ -150,7 +150,7 @@ func (l *Lane) Contains(tx sdk.Tx) bool { // Match returns true if the transaction belongs to the lane. func (l *Lane) Match(ctx sdk.Context, tx sdk.Tx) bool { - return l.matchFn(ctx, tx) + return l.txMatchFn(ctx, tx) } // FillProposal fills the proposal with transactions from the lane mempool with its own limit. From 1edd95fcf74ba0ac68734b9c51ee67bd4bbb3630 Mon Sep 17 00:00:00 2001 From: colmazia Date: Fri, 16 May 2025 14:52:34 +0700 Subject: [PATCH 51/53] update readme --- app/mempool/README.md | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/app/mempool/README.md b/app/mempool/README.md index a29dee256..73f9b0e4d 100644 --- a/app/mempool/README.md +++ b/app/mempool/README.md @@ -33,7 +33,6 @@ A lane is created with specific matching criteria and space allocation. Here's a bankSendLane := NewLane( logger, txEncoder, - signerExtractor, "bankSend", // Lane name isBankSendTx, // Matching function math.LegacyMustNewDecFromStr("0.2"), // Max transaction space ratio @@ -60,20 +59,19 @@ func isBankSendTx(_ sdk.Context, tx sdk.Tx) bool { Key parameters for lane creation: - `logger`: Logger instance for lane operations - `txEncoder`: Function to encode transactions -- `signerExtractor`: Adapter to extract signer information - `name`: Unique identifier for the lane - `matchFn`: Function to determine if a transaction belongs in this lane -- `maxTransactionSpace`: Maximum space ratio for individual transactions (relative to total block space) more details on [Space management](#space-management) -- `maxLaneSpace`: Maximum space ratio for the entire lane (relative to total block space) more details on [Space management](#space-management) -- `laneMempool`: Underlying Cosmos SDK mempool implementation that handles transaction storage and ordering within the lane. This determines how transactions are stored and selected within the lane -- `handleLaneLimitCheck`: Optional callback function that is called when the lane exceeds its space limit. This can be used to implement inter-lane dependencies, such as blocking other lanes when a lane exceeds its limit. +- `maxTransactionBlockRatio`: Maximum space ratio for individual transactions relative to total block space more details on [Space management](#space-management) +- `maxLaneBlockRatio`: Maximum space ratio for the entire lane relative to total block space more details on [Space management](#space-management) +- `mempool`: Underlying Cosmos SDK mempool implementation that handles transaction storage and ordering within the lane. This determines how transactions are stored and selected within the lane +- `callbackAfterFillProposal`: Optional callback function that is called when the lane fills its space limit. This can be used to implement inter-lane dependencies, such as blocking other lanes when a lane fills its limit. #### Inter-Lane Dependencies -Lanes can be configured to interact with each other through the `handleLaneLimitCheck` callback. This is useful for implementing priority systems or dependencies between different types of transactions. For example, you might want to block certain lanes when a high-priority lane exceeds its limit: +Lanes can be configured to interact with each other through the `callbackAfterFillProposal` callback. This is useful for implementing priority systems or dependencies between different types of transactions. For example, you might want to block certain lanes when a high-priority lane fills its limit: ```go -// Create a dependent lane that will be blocked when the dependency lane exceeds its limit +// Create a dependent lane that will be blocked when the dependency lane fills its limit dependentLane := NewLane( logger, txEncoder, @@ -102,7 +100,7 @@ dependencyLane := NewLane( ) ``` -In this example, when the dependency lane exceeds its space limit, it will block the dependent lane from processing transactions. This mechanism allows for sophisticated transaction prioritization and coordination between different types of transactions. +In this example, when the dependency lane fills its space limit, it will block the dependent lane from processing transactions. This mechanism allows for sophisticated transaction prioritization and coordination between different types of transactions. ### Creating a Mempool @@ -129,21 +127,20 @@ Key parameters for mempool creation: ### Space Management #### Space Allocation -Both `maxTransactionSpace` and `maxLaneSpace` are expressed as ratios of the total block space and are used specifically during proposal preparation. For example, a `maxTransactionSpace` of 0.2 means a single transaction can use up to 20% of the total block space in a proposal, while a `maxLaneSpace` of 0.3 means the entire lane can use up to 30% of the total block space in a proposal. These ratios are used to ensure fair distribution of block space among different transaction types during proposal construction. +Both `maxTransactionBlockRatio` and `maxLaneBlockRatio` are expressed as ratios of the total block space and are used specifically during proposal preparation. For example, a `maxTransactionBlockRatio` of 0.2 means a single transaction can use up to 20% of the total block space in a proposal, while a `maxLaneBlockRatio` of 0.3 means the entire lane can use up to 30% of the total block space in a proposal. These ratios are used to ensure fair distribution of block space among different transaction types during proposal construction. #### Space Cap Behavior -- **Transaction Space Cap (Hard Cap)**: The `maxTransactionSpace` ratio enforces a strict limit on individual transaction sizes of each lane. For example, with a `maxTransactionSpace` of 0.2, a transaction requiring more than 20% of the total block space will be deleted from the lane. -- **Lane Space Cap (Soft Cap)**: The `maxLaneSpace` ratio serves as a guideline for space allocation during the first round of proposal construction. If a lane's `maxLaneSpace` is 0.3, it can still include one last transaction that would cause it to exceed this limit in the proposal, provided each individual transaction respects the `maxTransactionSpace` limit. For instance, a lane with a 0.3 `maxLaneSpace` could include two transactions each using 20% of the block space (totaling 40%) in the proposal, as long as both transactions individually respect the `maxTransactionSpace` limit. +- **Transaction Space Cap (Hard Cap)**: The `maxTransactionBlockRatio` ratio enforces a strict limit on individual transaction sizes of each lane. For example, with a `maxTransactionBlockRatio` of 0.2, a transaction requiring more than 20% of the total block space will not be accepted. +- **Lane Space Cap (Soft Cap)**: The `maxLaneBlockRatio` ratio serves as a guideline for space allocation during the first round of proposal construction. If a lane's `maxLaneBlockRatio` is 0.3, it can still include one last transaction that would cause it to exceed this limit in the proposal, provided each individual transaction respects the `maxTransactionBlockRatio` limit. For instance, a lane with a 0.3 `maxLaneBlockRatio` could include two transactions each using 20% of the block space (totaling 40%) in the proposal, as long as both transactions individually respect the `maxTransactionBlockRatio` limit. #### Proposal Preparation -The lane space cap (`maxLaneSpace`) is only enforced during the first round of proposal preparation. In subsequent rounds, when filling the remaining block space, the lane cap is not considered, allowing lanes to potentially use more space than their initial allocation if space is available. This two-phase approach ensures both fair initial distribution and efficient use of remaining block space. +The lane space cap (`maxLaneBlockRatio`) is only enforced during the first round of proposal preparation. In subsequent rounds, when filling the remaining block space, the lane cap is not considered, allowing lanes to potentially use more space than their initial allocation if space is available. This two-phase approach ensures both fair initial distribution and efficient use of remaining block space. ### Block Proposal Preparation The mempool provides functionality to prepare block proposals by: -1. Filling proposals with transactions from each lane with `maxLaneSpace` -2. Filling remaining proposal space with transactions from each lane in the same order without `maxLaneSpace` -3. Handling transaction removal of the transactions that violate the `maxTransactionSpace` from all lanes +1. Filling proposals with transactions from each lane with `maxLaneBlockRatio` +2. Filling remaining proposal space with transactions from each lane in the same order without `maxLaneBlockRatio` ## Best Practices From 4eea2713141cbef0ec9481dc0f4f650d00d45320 Mon Sep 17 00:00:00 2001 From: Kitipong Sirirueangsakul Date: Sun, 18 May 2025 01:06:23 +0700 Subject: [PATCH 52/53] adjust tx limit size for tss and report lanes --- app/lanes.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/lanes.go b/app/lanes.go index 9ca58017a..22256a13e 100644 --- a/app/lanes.go +++ b/app/lanes.go @@ -38,7 +38,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { ) // tssLane handles TSS transactions. - // Each transaction has a gas limit of 5%, and the total gas limit for the lane is 20%. + // Each transaction has a gas limit of 10%, and the total gas limit for the lane is 20%. tssLane := mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), @@ -54,7 +54,7 @@ func CreateLanes(app *BandApp) []*mempool.Lane { }, true, ), - math.LegacyMustNewDecFromStr("0.05"), + math.LegacyMustNewDecFromStr("0.1"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), nil, @@ -81,14 +81,14 @@ func CreateLanes(app *BandApp) []*mempool.Lane { ) // oracleReportLane handles oracle report data transactions. - // Each transaction has a gas limit of 5%, and the total gas limit for the lane is 20%. + // Each transaction has a gas limit of 10%, and the total gas limit for the lane is 20%. // It block the oracle request lane if it exceeds its limit. oracleReportLane := mempool.NewLane( app.Logger(), app.txConfig.TxEncoder(), "oracleReportLane", mempool.NewLaneTxMatchFn([]sdk.Msg{&oracletypes.MsgReportData{}}, true), - math.LegacyMustNewDecFromStr("0.05"), + math.LegacyMustNewDecFromStr("0.1"), math.LegacyMustNewDecFromStr("0.2"), sdkmempool.DefaultPriorityMempool(), func(isLaneLimitExceeded bool) { From c2f9455002833cedfdb1dba14519d6f612740dd6 Mon Sep 17 00:00:00 2001 From: colmazia Date: Mon, 19 May 2025 15:53:46 +0700 Subject: [PATCH 53/53] fix from comments --- app/mempool/lane.go | 2 +- app/mempool/proposal_handler.go | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/mempool/lane.go b/app/mempool/lane.go index 2e12dcccc..1bee955f4 100644 --- a/app/mempool/lane.go +++ b/app/mempool/lane.go @@ -21,7 +21,7 @@ type Lane struct { logger log.Logger txEncoder sdk.TxEncoder name string - txMatchFn func(ctx sdk.Context, tx sdk.Tx) bool + txMatchFn TxMatchFn maxTransactionBlockRatio math.LegacyDec maxLaneBlockRatio math.LegacyDec diff --git a/app/mempool/proposal_handler.go b/app/mempool/proposal_handler.go index 4378492ca..0d82ca6e0 100644 --- a/app/mempool/proposal_handler.go +++ b/app/mempool/proposal_handler.go @@ -53,16 +53,13 @@ func (h *ProposalHandler) PrepareProposalHandler() sdk.PrepareProposalHandler { // Gather block limits maxBytesLimit, maxGasLimit := getBlockLimits(ctx) - var maxTxBytes uint64 - if req.MaxTxBytes < 0 { - maxTxBytes = maxBytesLimit - } else { - maxTxBytes = uint64(req.MaxTxBytes) + if req.MaxTxBytes >= 0 { + maxBytesLimit = min(uint64(req.MaxTxBytes), maxBytesLimit) } proposal := NewProposal( h.logger, - min(maxTxBytes, maxBytesLimit), + maxBytesLimit, maxGasLimit, )