From 28da888b3cac78680e2dffd259f98b17516753e4 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 8 Sep 2022 16:03:03 -0500 Subject: [PATCH 001/196] working out interfaces --- baseapp/abci.go | 6 +++--- baseapp/baseapp.go | 14 +++++++++----- types/context.go | 2 +- types/mempool.go | 34 ++++++++++++++++++++++++++++++++++ types/tx_msg.go | 6 +++--- 5 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 types/mempool.go diff --git a/baseapp/abci.go b/baseapp/abci.go index 5e1c8f37c26c..31004fc5d97b 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -232,7 +232,7 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc return res } -// ProcessProposal implements the ability for the application to verify and/or modify transactions in a block proposal. +// PrepareProposal implements the ability for the application to verify and/or modify transactions in a block proposal. func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { // treated as a noop until app side mempool is implemented return abci.ResponsePrepareProposal{Txs: req.Txs} @@ -248,7 +248,7 @@ func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.Respon // CheckTx mode, messages are not executed. This means messages are only validated // and only the AnteHandler is executed. State is persisted to the BaseApp's // internal CheckTx state if the AnteHandler passes. Otherwise, the ResponseCheckTx -// will contain releveant error information. Regardless of tx execution outcome, +// will contain relevant error information. Regardless of tx execution outcome, // the ResponseCheckTx will contain relevant gas execution context. func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { var mode runTxMode @@ -281,7 +281,7 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { // DeliverTx implements the ABCI interface and executes a tx in DeliverTx mode. // State only gets persisted if all messages are valid and get executed successfully. -// Otherwise, the ResponseDeliverTx will contain releveant error information. +// Otherwise, the ResponseDeliverTx will contain relevant error information. // Regardless of tx execution outcome, the ResponseDeliverTx will contain relevant // gas execution context. func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index c946173dd438..b007066081fb 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -53,6 +53,7 @@ type BaseApp struct { //nolint: maligned interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx + mempool sdk.Mempool anteHandler sdk.AnteHandler // ante handler for fee and auth postHandler sdk.AnteHandler // post handler, optional, e.g. for tips initChainer sdk.InitChainer // initialize state with validators and state blob @@ -69,7 +70,7 @@ type BaseApp struct { //nolint: maligned // // checkState is set on InitChain and reset on Commit // deliverState is set on InitChain and BeginBlock and set to nil on Commit - checkState *state // for CheckTx + checkState *state // for CheckTx5 deliverState *state // for DeliverTx // an inter-block write-through cache provided to the context during deliverState @@ -484,7 +485,7 @@ func (app *BaseApp) validateHeight(req abci.RequestBeginBlock) error { // previous commit). The height we're expecting is the initial height. expectedHeight = app.initialHeight } else { - // This case can means two things: + // This case can mean two things: // - either there was already a previous commit in the store, in which // case we increment the version from there, // - or there was no previous commit, and initial version was not set, @@ -515,7 +516,7 @@ func validateBasicTxMsgs(msgs []sdk.Msg) error { return nil } -// Returns the applications's deliverState if app is in runTxModeDeliver, +// Returns the application's deliverState if app is in runTxModeDeliver, // otherwise it returns the application's checkstate. func (app *BaseApp) getState(mode runTxMode) *state { if mode == runTxModeDeliver { @@ -573,7 +574,7 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, priority int64, err error) { // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is // determined by the GasMeter. We need access to the context to get the gas - // meter so we initialize upfront. + // meter, so we initialize upfront. var gasWanted uint64 ctx := app.getContextForTx(mode, txBytes) @@ -595,7 +596,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re blockGasConsumed := false // consumeBlockGas makes sure block gas is consumed at most once. It must happen after - // tx processing, and must be execute even if tx processing fails. Hence we use trick with `defer` + // tx processing, and must be executed even if tx processing fails. Hence, we use trick with `defer` consumeBlockGas := func() { if !blockGasConsumed { blockGasConsumed = true @@ -675,6 +676,9 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re // Result if any single message fails or does not have a registered Handler. result, err = app.runMsgs(runMsgCtx, msgs, mode) if err == nil { + // TODO add the tx to the mempool + app.mempool.Insert(tx) + // Run optional postHandlers. // // Note: If the postHandler fails, we also revert the runMsgs state. diff --git a/types/context.go b/types/context.go index ce7895f5a85c..fddee22f950f 100644 --- a/types/context.go +++ b/types/context.go @@ -228,7 +228,7 @@ func (c Context) WithEventManager(em *EventManager) Context { return c } -// WithEventManager returns a Context with an updated tx priority +// WithPriority returns a Context with an updated tx priority func (c Context) WithPriority(p int64) Context { c.priority = p return c diff --git a/types/mempool.go b/types/mempool.go new file mode 100644 index 000000000000..5e9199639f6f --- /dev/null +++ b/types/mempool.go @@ -0,0 +1,34 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// MempoolTx we define an app-side mempool transaction interface that is as +// minimal as possible, only requiring applications to define the size of the +// transaction to be used when reaping and getting the transaction itself. +// Interface type casting can be used in the actual app-side mempool implementation. +type MempoolTx interface { + // Size returns the size of the transaction in bytes. + Size(codec.Codec) int + Tx() Tx +} + +type Mempool interface { + // Insert attempts to insert a MempoolTx into the app-side mempool returning + // an error upon failure. + Insert(Context, MempoolTx) error + + // Select returns the next set of available transactions from the app-side + // mempool, up to maxBytes or until the mempool is empty. The application can + // decide to return transactions from its own mempool, from the incoming + // txs, or some combination of both. + Select(ctx Context, txs [][]byte, maxBytes int) ([]MempoolTx, error) + + // CountTx returns the number of transactions currently in the mempool. + CountTx() int + + // Remove attempts to remove a transaction from the mempool, returning an error + // upon failure. + Remove(Context, MempoolTx) error +} diff --git a/types/tx_msg.go b/types/tx_msg.go index 5c893e6ed548..e76138cd7ef5 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -15,7 +15,7 @@ type ( // doesn't require access to any other information. ValidateBasic() error - // Signers returns the addrs of signers that must sign. + // GetSigners returns the addrs of signers that must sign. // CONTRACT: All signatures must be present to be valid. // CONTRACT: Returns addrs in some deterministic order. GetSigners() []AccAddress @@ -37,7 +37,7 @@ type ( // Tx defines the interface a transaction must fulfill. Tx interface { - // Gets the all the transaction's messages. + // GetMsgs gets the all the transaction's messages. GetMsgs() []Msg // ValidateBasic does a simple and lightweight validation check that doesn't @@ -54,7 +54,7 @@ type ( FeeGranter() AccAddress } - // Tx must have GetMemo() method to use ValidateMemoDecorator + // TxWithMemo must have GetMemo() method to use ValidateMemoDecorator TxWithMemo interface { Tx GetMemo() string From 37aac4060ee600aa9691b5570b4ff1fa5948a0d1 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 12 Sep 2022 09:39:55 -0500 Subject: [PATCH 002/196] integration to checkTx --- baseapp/baseapp.go | 11 +++++++++-- types/mempool.go | 9 +++------ x/auth/tx/builder.go | 5 +++++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index b007066081fb..697f8c462c1a 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -666,6 +666,14 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re anteEvents = events.ToABCIEvents() } + if mode == runTxModeCheck { + // TODO add the tx to the mempool + err = app.mempool.Insert(ctx, tx.(sdk.MempoolTx)) + if err != nil { + return gInfo, nil, anteEvents, priority, err + } + } + // Create a new Context based off of the existing Context with a MultiStore branch // in case message processing fails. At this point, the MultiStore // is a branch of a branch. @@ -675,9 +683,8 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re // and we're in DeliverTx. Note, runMsgs will never return a reference to a // Result if any single message fails or does not have a registered Handler. result, err = app.runMsgs(runMsgCtx, msgs, mode) + if err == nil { - // TODO add the tx to the mempool - app.mempool.Insert(tx) // Run optional postHandlers. // diff --git a/types/mempool.go b/types/mempool.go index 5e9199639f6f..1ea0b4da9c0c 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -1,17 +1,14 @@ package types -import ( - "github.com/cosmos/cosmos-sdk/codec" -) - // MempoolTx we define an app-side mempool transaction interface that is as // minimal as possible, only requiring applications to define the size of the // transaction to be used when reaping and getting the transaction itself. // Interface type casting can be used in the actual app-side mempool implementation. type MempoolTx interface { + Tx + // Size returns the size of the transaction in bytes. - Size(codec.Codec) int - Tx() Tx + Size() int } type Mempool interface { diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 479829cae685..0f541de13e20 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -40,6 +40,7 @@ var ( _ ante.HasExtensionOptionsTx = &wrapper{} _ ExtensionOptionsTxBuilder = &wrapper{} _ tx.TipTx = &wrapper{} + _ sdk.MempoolTx = &wrapper{} ) // ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions. @@ -62,6 +63,10 @@ func newBuilder(cdc codec.Codec) *wrapper { } } +func (w *wrapper) Size() int { + return len(w.getBodyBytes()) + len(w.getAuthInfoBytes()) +} + func (w *wrapper) GetMsgs() []sdk.Msg { return w.tx.GetMsgs() } From cb4aad6fceafce269e4e79d7e6ab078eeac19fed Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 12 Sep 2022 09:51:32 -0500 Subject: [PATCH 003/196] use struct fields directly in sz calculation --- x/auth/tx/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 0f541de13e20..93271151e6cf 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -64,7 +64,7 @@ func newBuilder(cdc codec.Codec) *wrapper { } func (w *wrapper) Size() int { - return len(w.getBodyBytes()) + len(w.getAuthInfoBytes()) + return len(w.bodyBz) + len(w.authInfoBz) } func (w *wrapper) GetMsgs() []sdk.Msg { From ec63c02522e39bdfd66fde99b9077aa240a449e7 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 12 Sep 2022 09:52:47 -0500 Subject: [PATCH 004/196] fix typo --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 697f8c462c1a..943ede7b8afd 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -70,7 +70,7 @@ type BaseApp struct { //nolint: maligned // // checkState is set on InitChain and reset on Commit // deliverState is set on InitChain and BeginBlock and set to nil on Commit - checkState *state // for CheckTx5 + checkState *state // for CheckTx deliverState *state // for DeliverTx // an inter-block write-through cache provided to the context during deliverState From a7cf9706f8f12032cbdf2939bb90f56e028e1bfc Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 12 Sep 2022 11:01:39 -0500 Subject: [PATCH 005/196] nil guard on mempool --- baseapp/baseapp.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 943ede7b8afd..dd020e8ec022 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -4,13 +4,14 @@ import ( "fmt" "strings" - "github.com/cosmos/gogoproto/proto" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" + "github.com/cosmos/gogoproto/proto" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/snapshots" "github.com/cosmos/cosmos-sdk/store" @@ -53,7 +54,7 @@ type BaseApp struct { //nolint: maligned interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx - mempool sdk.Mempool + mempool sdk.Mempool // application side mempool anteHandler sdk.AnteHandler // ante handler for fee and auth postHandler sdk.AnteHandler // post handler, optional, e.g. for tips initChainer sdk.InitChainer // initialize state with validators and state blob @@ -666,8 +667,8 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re anteEvents = events.ToABCIEvents() } - if mode == runTxModeCheck { - // TODO add the tx to the mempool + // TODO remove nil check when implemented + if mode == runTxModeCheck && app.mempool != nil { err = app.mempool.Insert(ctx, tx.(sdk.MempoolTx)) if err != nil { return gInfo, nil, anteEvents, priority, err From a2060e9ff05ffe33db34766004a0ed79fe8f691f Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 12 Sep 2022 17:55:33 -0500 Subject: [PATCH 006/196] very rough btree impl --- types/mempool.go | 115 +++++++++++++++++++++++++++++++++++++++++++ x/auth/tx/builder.go | 7 +++ x/auth/tx/decoder.go | 2 + 3 files changed, 124 insertions(+) diff --git a/types/mempool.go b/types/mempool.go index 1ea0b4da9c0c..e32c2b9f6742 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -1,5 +1,11 @@ package types +import ( + "fmt" + + "github.com/google/btree" +) + // MempoolTx we define an app-side mempool transaction interface that is as // minimal as possible, only requiring applications to define the size of the // transaction to be used when reaping and getting the transaction itself. @@ -11,6 +17,12 @@ type MempoolTx interface { Size() int } +// HashableTx defines an interface for a transaction that can be hashed. +// TODO Consider merging with MemPoolTx. +type HashableTx interface { + GetHash() [32]byte +} + type Mempool interface { // Insert attempts to insert a MempoolTx into the app-side mempool returning // an error upon failure. @@ -29,3 +41,106 @@ type Mempool interface { // upon failure. Remove(Context, MempoolTx) error } + +var _ Mempool = (*btreeMempool)(nil) +var _ btree.Item = (*btreeItem)(nil) + +type btreeMempool struct { + btree *btree.BTree + txBytes int + maxTxBytes int + txCount int + hashes map[[32]byte]int64 +} + +type btreeItem struct { + txs []MempoolTx + priority int64 +} + +func (bi *btreeItem) Less(than btree.Item) bool { + return bi.priority < than.(*btreeItem).priority +} + +func NewBTreeMempool(maxBytes int) *btreeMempool { + return &btreeMempool{ + btree: btree.New(2), + txBytes: 0, + maxTxBytes: maxBytes, + } +} + +var ErrMempoolIsFull = fmt.Errorf("mempool is full") +var ErrNoTxHash = fmt.Errorf("tx is not hashable") + +func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { + hashTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } + txSize := tx.Size() + if btm.txBytes+txSize > btm.maxTxBytes { + return ErrMempoolIsFull + } + + var bi *btreeItem + key := &btreeItem{priority: ctx.Priority()} + if btm.btree.Has(key) { + bi = btm.btree.Get(key).(*btreeItem) + bi.txs = append(bi.txs, tx) + } else { + bi = &btreeItem{txs: []MempoolTx{tx}, priority: ctx.Priority()} + } + + btm.btree.ReplaceOrInsert(bi) + btm.hashes[hashTx.GetHash()] = ctx.Priority() + btm.txBytes += txSize + btm.txCount++ + + return nil +} + +func (btm *btreeMempool) Select(ctx Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { + // TODO sequence no. validation + + txBytes := 0 + var selectedTxs []MempoolTx + btm.btree.Descend(func(i btree.Item) bool { + txs := i.(*btreeItem).txs + for _, tx := range txs { + txBytes += tx.Size() + if txBytes < maxBytes { + return false + } + selectedTxs = append(selectedTxs, tx) + } + + return true + }) + return selectedTxs, nil +} + +func (btm *btreeMempool) CountTx() int { + return btm.txCount +} + +func (btm *btreeMempool) Remove(context Context, tx MempoolTx) error { + hashTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } + hash := hashTx.GetHash() + + priority, txFound := btm.hashes[hash] + if !txFound { + return fmt.Errorf("tx %X not found", hash) + } + + i := btm.btree.Delete(&btreeItem{priority: priority}) + if i == nil { + return fmt.Errorf("tx with priority %v not found", priority) + } + + delete(btm.hashes, hash) + return nil +} diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 93271151e6cf..f7203533e73f 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -30,6 +30,8 @@ type wrapper struct { // from the client using TxRaw if the tx was decoded from the wire authInfoBz []byte + hash [32]byte + txBodyHasUnknownNonCriticals bool } @@ -41,6 +43,7 @@ var ( _ ExtensionOptionsTxBuilder = &wrapper{} _ tx.TipTx = &wrapper{} _ sdk.MempoolTx = &wrapper{} + _ sdk.HashableTx = &wrapper{} ) // ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions. @@ -67,6 +70,10 @@ func (w *wrapper) Size() int { return len(w.bodyBz) + len(w.authInfoBz) } +func (w *wrapper) GetHash() [32]byte { + return w.hash +} + func (w *wrapper) GetMsgs() []sdk.Msg { return w.tx.GetMsgs() } diff --git a/x/auth/tx/decoder.go b/x/auth/tx/decoder.go index 2fef6312b994..3d6b45b7e72a 100644 --- a/x/auth/tx/decoder.go +++ b/x/auth/tx/decoder.go @@ -1,6 +1,7 @@ package tx import ( + "crypto/sha256" "fmt" "google.golang.org/protobuf/encoding/protowire" @@ -70,6 +71,7 @@ func DefaultTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder { tx: theTx, bodyBz: raw.BodyBytes, authInfoBz: raw.AuthInfoBytes, + hash: sha256.Sum256(txBytes), txBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals, }, nil } From a3b8c255eac441408923d75e32794d9c8890a328 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 12 Sep 2022 18:11:52 -0500 Subject: [PATCH 007/196] todo note --- types/mempool.go | 1 + 1 file changed, 1 insertion(+) diff --git a/types/mempool.go b/types/mempool.go index e32c2b9f6742..df4079a4b79a 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -136,6 +136,7 @@ func (btm *btreeMempool) Remove(context Context, tx MempoolTx) error { return fmt.Errorf("tx %X not found", hash) } + // TODO handle tx arrays i := btm.btree.Delete(&btreeItem{priority: priority}) if i == nil { return fmt.Errorf("tx with priority %v not found", priority) From 7d5096e5142d66be5d9ca89502b2a8738df258d9 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 13 Sep 2022 09:06:28 -0500 Subject: [PATCH 008/196] clean up btree impl --- types/mempool.go | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/types/mempool.go b/types/mempool.go index df4079a4b79a..8edfb6977e8f 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -18,7 +18,7 @@ type MempoolTx interface { } // HashableTx defines an interface for a transaction that can be hashed. -// TODO Consider merging with MemPoolTx. +// TODO Consider merging with MemPoolTx or using signatures instead. type HashableTx interface { GetHash() [32]byte } @@ -54,6 +54,7 @@ type btreeMempool struct { } type btreeItem struct { + // TODO use linked list instead of slice if we opt for a Btree txs []MempoolTx priority int64 } @@ -79,28 +80,29 @@ func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { return ErrNoTxHash } txSize := tx.Size() + priority := ctx.Priority() + if btm.txBytes+txSize > btm.maxTxBytes { return ErrMempoolIsFull } - var bi *btreeItem - key := &btreeItem{priority: ctx.Priority()} - if btm.btree.Has(key) { - bi = btm.btree.Get(key).(*btreeItem) + key := &btreeItem{priority: priority} + var bi = btm.btree.Get(key).(*btreeItem) + if bi != nil { bi.txs = append(bi.txs, tx) } else { - bi = &btreeItem{txs: []MempoolTx{tx}, priority: ctx.Priority()} + bi = &btreeItem{txs: []MempoolTx{tx}, priority: priority} } btm.btree.ReplaceOrInsert(bi) - btm.hashes[hashTx.GetHash()] = ctx.Priority() + btm.hashes[hashTx.GetHash()] = priority btm.txBytes += txSize btm.txCount++ return nil } -func (btm *btreeMempool) Select(ctx Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { +func (btm *btreeMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { // TODO sequence no. validation txBytes := 0 @@ -124,7 +126,7 @@ func (btm *btreeMempool) CountTx() int { return btm.txCount } -func (btm *btreeMempool) Remove(context Context, tx MempoolTx) error { +func (btm *btreeMempool) Remove(_ Context, tx MempoolTx) error { hashTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash @@ -136,12 +138,30 @@ func (btm *btreeMempool) Remove(context Context, tx MempoolTx) error { return fmt.Errorf("tx %X not found", hash) } - // TODO handle tx arrays - i := btm.btree.Delete(&btreeItem{priority: priority}) + i := btm.btree.Get(&btreeItem{priority: priority}) if i == nil { return fmt.Errorf("tx with priority %v not found", priority) } + item := i.(*btreeItem) + if len(item.txs) == 1 { + btm.btree.Delete(i) + } else { + found := false + for j, t := range item.txs { + if t.(HashableTx).GetHash() == hash { + item.txs = append(item.txs[:j], item.txs[j+1:]...) + found = true + break + } + } + if !found { + return fmt.Errorf("tx %X not found at priority %v", hash, priority) + } + btm.btree.ReplaceOrInsert(item) + } + delete(btm.hashes, hash) + return nil } From 69c8cbf630c64e273a25afb26782cd7090df6fb5 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 13 Sep 2022 09:07:07 -0500 Subject: [PATCH 009/196] btree dep --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 579643fab702..3c3fca72fe3f 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/gogo/gateway v1.1.0 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.2 + github.com/google/btree v1.0.1 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 @@ -97,7 +98,6 @@ require ( github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.0.1 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/googleapis/gax-go/v2 v2.4.0 // indirect From d4d8512cd1725a05bc5cedb136da576d923f6cd4 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 13 Sep 2022 09:16:11 -0500 Subject: [PATCH 010/196] decrement on delete --- types/mempool.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/mempool.go b/types/mempool.go index 8edfb6977e8f..36a5544ed91f 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -162,6 +162,8 @@ func (btm *btreeMempool) Remove(_ Context, tx MempoolTx) error { } delete(btm.hashes, hash) + btm.txBytes -= tx.Size() + btm.txCount-- return nil } From 142e2196b932615d800af52056f07d100962b494 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 13 Sep 2022 09:24:35 -0500 Subject: [PATCH 011/196] add stub for sequence --- types/mempool.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/types/mempool.go b/types/mempool.go index 36a5544ed91f..775abd80d1bf 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -102,19 +102,26 @@ func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { return nil } -func (btm *btreeMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { - // TODO sequence no. validation +func (btm *btreeMempool) validateSequenceNumber(tx Tx) bool { + // TODO + return true +} +func (btm *btreeMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { txBytes := 0 var selectedTxs []MempoolTx btm.btree.Descend(func(i btree.Item) bool { txs := i.(*btreeItem).txs for _, tx := range txs { - txBytes += tx.Size() - if txBytes < maxBytes { + txSize := tx.Size() + if txBytes+txSize < maxBytes { return false } + if !btm.validateSequenceNumber(tx) { + continue + } selectedTxs = append(selectedTxs, tx) + txBytes += txSize } return true From d72d1a202d03b8aca1f75c025bc4705cb87b67d3 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 13 Sep 2022 10:03:19 -0500 Subject: [PATCH 012/196] size impl --- x/auth/tx/builder.go | 5 +++-- x/auth/tx/decoder.go | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index f7203533e73f..03a5877208b0 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -30,7 +30,8 @@ type wrapper struct { // from the client using TxRaw if the tx was decoded from the wire authInfoBz []byte - hash [32]byte + hash [32]byte + numBytes int txBodyHasUnknownNonCriticals bool } @@ -67,7 +68,7 @@ func newBuilder(cdc codec.Codec) *wrapper { } func (w *wrapper) Size() int { - return len(w.bodyBz) + len(w.authInfoBz) + return w.numBytes } func (w *wrapper) GetHash() [32]byte { diff --git a/x/auth/tx/decoder.go b/x/auth/tx/decoder.go index 3d6b45b7e72a..c4c7de45b628 100644 --- a/x/auth/tx/decoder.go +++ b/x/auth/tx/decoder.go @@ -72,6 +72,7 @@ func DefaultTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder { bodyBz: raw.BodyBytes, authInfoBz: raw.AuthInfoBytes, hash: sha256.Sum256(txBytes), + numBytes: len(txBytes), txBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals, }, nil } From 01ed963407739a19dabbff7c04941a813001da4c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 13 Sep 2022 10:14:13 -0500 Subject: [PATCH 013/196] cleanup --- types/mempool.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/types/mempool.go b/types/mempool.go index 775abd80d1bf..85ff2deff005 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -42,8 +42,15 @@ type Mempool interface { Remove(Context, MempoolTx) error } -var _ Mempool = (*btreeMempool)(nil) -var _ btree.Item = (*btreeItem)(nil) +var ( + _ Mempool = (*btreeMempool)(nil) + _ btree.Item = (*btreeItem)(nil) +) + +var ( + ErrMempoolIsFull = fmt.Errorf("mempool is full") + ErrNoTxHash = fmt.Errorf("tx is not hashable") +) type btreeMempool struct { btree *btree.BTree @@ -71,9 +78,6 @@ func NewBTreeMempool(maxBytes int) *btreeMempool { } } -var ErrMempoolIsFull = fmt.Errorf("mempool is full") -var ErrNoTxHash = fmt.Errorf("tx is not hashable") - func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { hashTx, ok := tx.(HashableTx) if !ok { @@ -87,7 +91,7 @@ func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { } key := &btreeItem{priority: priority} - var bi = btm.btree.Get(key).(*btreeItem) + bi := btm.btree.Get(key).(*btreeItem) if bi != nil { bi.txs = append(bi.txs, tx) } else { From 35ff6d688a25ce32521068677a8f32ff1b0cbd98 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Tue, 13 Sep 2022 22:04:39 -0600 Subject: [PATCH 014/196] fix nill pointer when checking if key exists --- types/mempool.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/types/mempool.go b/types/mempool.go index 85ff2deff005..f4fd3d88687b 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -91,8 +91,10 @@ func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { } key := &btreeItem{priority: priority} - bi := btm.btree.Get(key).(*btreeItem) + + bi := btm.btree.Get(key) if bi != nil { + bi := bi.(*btreeItem) bi.txs = append(bi.txs, tx) } else { bi = &btreeItem{txs: []MempoolTx{tx}, priority: priority} From bbd94057c27de6467ce9d8f055ee478445fb0cb4 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Tue, 13 Sep 2022 22:18:32 -0600 Subject: [PATCH 015/196] add init of hashes --- types/mempool.go | 1 + 1 file changed, 1 insertion(+) diff --git a/types/mempool.go b/types/mempool.go index f4fd3d88687b..c1e2b8ace3a4 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -75,6 +75,7 @@ func NewBTreeMempool(maxBytes int) *btreeMempool { btree: btree.New(2), txBytes: 0, maxTxBytes: maxBytes, + hashes: make(map[[32]byte]int64), } } From 3cc3fb15927083d8e6dbf0f9384242754408021c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 14 Sep 2022 09:26:03 -0500 Subject: [PATCH 016/196] notes --- types/mempool.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/mempool.go b/types/mempool.go index 85ff2deff005..a07c8d53aa9b 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -61,7 +61,7 @@ type btreeMempool struct { } type btreeItem struct { - // TODO use linked list instead of slice if we opt for a Btree + // TODO use linked list instead of slice txs []MempoolTx priority int64 } @@ -91,6 +91,7 @@ func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { } key := &btreeItem{priority: priority} + // TODO can avoid O(log n) lookup by maintaining a priority hash bi := btm.btree.Get(key).(*btreeItem) if bi != nil { bi.txs = append(bi.txs, tx) From 77a9b375d17c632227ba3667beaf3a9a9dcd42ff Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 14 Sep 2022 12:40:42 -0500 Subject: [PATCH 017/196] btree ordering --- go.mod | 1 + go.sum | 2 + types/mempool.go | 133 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 122 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 3c3fca72fe3f..2e4cc60721f6 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( cosmossdk.io/errors v1.0.0-beta.7 cosmossdk.io/math v1.0.0-beta.3 github.com/99designs/keyring v1.2.1 + github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 github.com/armon/go-metrics v0.4.1 github.com/bgentry/speakeasy v0.1.0 github.com/btcsuite/btcd v0.22.1 diff --git a/go.sum b/go.sum index a083f9a1c436..b22fec1a3386 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,8 @@ github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQ github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 h1:1yw6O62BReQ+uA1oyk9XaQTvLhcoHWmoQAgXmDFXpIY= +github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145/go.mod h1:877WBceefKn14QwVVn4xRFUsHsZb9clICgdeTj4XsUg= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= diff --git a/types/mempool.go b/types/mempool.go index 0572b6e0a98e..bf4bb610ec0d 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -1,8 +1,10 @@ package types import ( + "bytes" "fmt" + "github.com/MauriceGit/skiplist" "github.com/google/btree" ) @@ -18,8 +20,8 @@ type MempoolTx interface { } // HashableTx defines an interface for a transaction that can be hashed. -// TODO Consider merging with MemPoolTx or using signatures instead. type HashableTx interface { + MempoolTx GetHash() [32]byte } @@ -52,6 +54,10 @@ var ( ErrNoTxHash = fmt.Errorf("tx is not hashable") ) +// ---------------------------------------------------------------------------- +// BTree Implementation +// We use a BTree with order 2 to approximate a Red-Black Tree. + type btreeMempool struct { btree *btree.BTree txBytes int @@ -61,13 +67,20 @@ type btreeMempool struct { } type btreeItem struct { - // TODO use linked list instead of slice if we opt for a Btree - txs []MempoolTx + tx HashableTx priority int64 } func (bi *btreeItem) Less(than btree.Item) bool { - return bi.priority < than.(*btreeItem).priority + prA := bi.priority + prB := than.(*btreeItem).priority + if prA == prB { + // random, deterministic ordering + hashA := bi.tx.GetHash() + hashB := than.(*btreeItem).tx.GetHash() + return bytes.Compare(hashA[:], hashB[:]) < 0 + } + return prA < prB } func NewBTreeMempool(maxBytes int) *btreeMempool { @@ -90,17 +103,9 @@ func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { return ErrMempoolIsFull } - key := &btreeItem{priority: priority} - // TODO can avoid O(log n) lookup by maintaining a priority hash - bi := btm.btree.Get(key).(*btreeItem) - if bi != nil { - bi.txs = append(bi.txs, tx) - } else { - bi = &btreeItem{txs: []MempoolTx{tx}, priority: priority} - } - + bi := &btreeItem{priority: priority, tx: hashTx} btm.btree.ReplaceOrInsert(bi) - btm.hashes[hashTx.GetHash()] = priority + btm.txBytes += txSize btm.txCount++ @@ -116,6 +121,7 @@ func (btm *btreeMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolT txBytes := 0 var selectedTxs []MempoolTx btm.btree.Descend(func(i btree.Item) bool { + txs := i.(*btreeItem).txs for _, tx := range txs { txSize := tx.Size() @@ -179,3 +185,102 @@ func (btm *btreeMempool) Remove(_ Context, tx MempoolTx) error { return nil } + +// ---------------------------------------------------------------------------- +// Skip list implementation + +type skipListMempool struct { + list *skiplist.SkipList + txBytes int + maxTxBytes int + scores map[[32]byte]int64 +} + +func (item skipListItem) ExtractKey() float64 { + return float64(item.priority) +} + +func (item skipListItem) String() string { + return fmt.Sprintf("txHash %X", item.tx.(HashableTx).GetHash()) +} + +type skipListItem struct { + tx MempoolTx + priority int64 +} + +func (slm *skipListMempool) Insert(ctx Context, tx MempoolTx) error { + hashTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } + txSize := tx.Size() + priority := ctx.Priority() + + if slm.txBytes+txSize > slm.maxTxBytes { + return ErrMempoolIsFull + } + + item := skipListItem{tx: tx, priority: priority} + slm.list.Insert(item) + slm.scores[hashTx.GetHash()] = priority + slm.txBytes += txSize + + return nil +} + +func (slm *skipListMempool) validateSequenceNumber(tx Tx) bool { + // TODO + return true +} + +func (slm *skipListMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { + txBytes := 0 + var selectedTxs []MempoolTx + + n := slm.list.GetLargestNode() + cnt := slm.list.GetNodeCount() + for i := 0; i < cnt; i++ { + tx := n.GetValue().(skipListItem).tx + + if !slm.validateSequenceNumber(tx) { + continue + } + + selectedTxs = append(selectedTxs, tx) + txSize := tx.Size() + txBytes += txSize + if txBytes+txSize >= maxBytes { + break + } + + n = slm.list.Prev(n) + } + + return selectedTxs, nil +} + +func (slm *skipListMempool) CountTx() int { + return slm.list.GetNodeCount() +} + +func (slm *skipListMempool) Remove(_ Context, tx MempoolTx) error { + hashTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } + hash := hashTx.GetHash() + + priority, txFound := slm.scores[hash] + if !txFound { + return fmt.Errorf("tx %X not found", hash) + } + + item := skipListItem{tx: tx, priority: priority} + slm.list.Delete(item) + + delete(slm.scores, hash) + slm.txBytes -= tx.Size() + + return nil +} From 46c34ff04035d33c1872b4191ea9f89ba3f6fd31 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 14 Sep 2022 14:00:52 -0500 Subject: [PATCH 018/196] MauricGit/skiplist impl --- types/mempool.go | 50 ++++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/types/mempool.go b/types/mempool.go index bf4bb610ec0d..db431298da7a 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -56,14 +56,14 @@ var ( // ---------------------------------------------------------------------------- // BTree Implementation -// We use a BTree with order 2 to approximate a Red-Black Tree. +// We use a BTree with degree=2 to approximate a Red-Black Tree. type btreeMempool struct { btree *btree.BTree txBytes int maxTxBytes int txCount int - hashes map[[32]byte]int64 + scores map[[32]byte]int64 } type btreeItem struct { @@ -121,20 +121,15 @@ func (btm *btreeMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolT txBytes := 0 var selectedTxs []MempoolTx btm.btree.Descend(func(i btree.Item) bool { + tx := i.(*btreeItem).tx + if btm.validateSequenceNumber(tx) { + selectedTxs = append(selectedTxs, tx) - txs := i.(*btreeItem).txs - for _, tx := range txs { - txSize := tx.Size() - if txBytes+txSize < maxBytes { + txBytes += tx.Size() + if txBytes >= maxBytes { return false } - if !btm.validateSequenceNumber(tx) { - continue - } - selectedTxs = append(selectedTxs, tx) - txBytes += txSize } - return true }) return selectedTxs, nil @@ -151,35 +146,17 @@ func (btm *btreeMempool) Remove(_ Context, tx MempoolTx) error { } hash := hashTx.GetHash() - priority, txFound := btm.hashes[hash] + priority, txFound := btm.scores[hash] if !txFound { return fmt.Errorf("tx %X not found", hash) } - i := btm.btree.Get(&btreeItem{priority: priority}) - if i == nil { - return fmt.Errorf("tx with priority %v not found", priority) - } - - item := i.(*btreeItem) - if len(item.txs) == 1 { - btm.btree.Delete(i) - } else { - found := false - for j, t := range item.txs { - if t.(HashableTx).GetHash() == hash { - item.txs = append(item.txs[:j], item.txs[j+1:]...) - found = true - break - } - } - if !found { - return fmt.Errorf("tx %X not found at priority %v", hash, priority) - } - btm.btree.ReplaceOrInsert(item) + res := btm.btree.Delete(&btreeItem{priority: priority, tx: hashTx}) + if res == nil { + return fmt.Errorf("tx %X not in mempool", hash) } - delete(btm.hashes, hash) + delete(btm.scores, hash) btm.txBytes -= tx.Size() btm.txCount-- @@ -250,7 +227,7 @@ func (slm *skipListMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]Mempo selectedTxs = append(selectedTxs, tx) txSize := tx.Size() txBytes += txSize - if txBytes+txSize >= maxBytes { + if txBytes >= maxBytes { break } @@ -277,6 +254,7 @@ func (slm *skipListMempool) Remove(_ Context, tx MempoolTx) error { } item := skipListItem{tx: tx, priority: priority} + // TODO this is broken. Key needs hash bytes incorporated to keep it unique slm.list.Delete(item) delete(slm.scores, hash) From 9a4eeacb865e20d02f8f3710dd4da76fe8fbdd26 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 14 Sep 2022 14:22:29 -0500 Subject: [PATCH 019/196] add huandu skiplist impl --- go.mod | 1 + go.sum | 4 ++ types/mempool.go | 134 +++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 130 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 2e4cc60721f6..15a78f12b17d 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/hashicorp/go-getter v1.6.2 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 + github.com/huandu/skiplist v1.2.0 github.com/improbable-eng/grpc-web v0.15.0 github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b github.com/lazyledger/smt v0.2.1-0.20210709230900-03ea40719554 diff --git a/go.sum b/go.sum index b22fec1a3386..84dea3f873e9 100644 --- a/go.sum +++ b/go.sum @@ -486,6 +486,10 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw= +github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/types/mempool.go b/types/mempool.go index db431298da7a..e898db661ae9 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -4,8 +4,9 @@ import ( "bytes" "fmt" - "github.com/MauriceGit/skiplist" + maurice "github.com/MauriceGit/skiplist" "github.com/google/btree" + huandu "github.com/huandu/skiplist" ) // MempoolTx we define an app-side mempool transaction interface that is as @@ -164,10 +165,12 @@ func (btm *btreeMempool) Remove(_ Context, tx MempoolTx) error { } // ---------------------------------------------------------------------------- -// Skip list implementation +// Skip list implementations -type skipListMempool struct { - list *skiplist.SkipList +// mauriceGit + +type mauriceSkipListMempool struct { + list *maurice.SkipList txBytes int maxTxBytes int scores map[[32]byte]int64 @@ -186,7 +189,7 @@ type skipListItem struct { priority int64 } -func (slm *skipListMempool) Insert(ctx Context, tx MempoolTx) error { +func (slm *mauriceSkipListMempool) Insert(ctx Context, tx MempoolTx) error { hashTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash @@ -206,12 +209,12 @@ func (slm *skipListMempool) Insert(ctx Context, tx MempoolTx) error { return nil } -func (slm *skipListMempool) validateSequenceNumber(tx Tx) bool { +func (slm *mauriceSkipListMempool) validateSequenceNumber(tx Tx) bool { // TODO return true } -func (slm *skipListMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { +func (slm *mauriceSkipListMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { txBytes := 0 var selectedTxs []MempoolTx @@ -237,11 +240,11 @@ func (slm *skipListMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]Mempo return selectedTxs, nil } -func (slm *skipListMempool) CountTx() int { +func (slm *mauriceSkipListMempool) CountTx() int { return slm.list.GetNodeCount() } -func (slm *skipListMempool) Remove(_ Context, tx MempoolTx) error { +func (slm *mauriceSkipListMempool) Remove(_ Context, tx MempoolTx) error { hashTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash @@ -262,3 +265,116 @@ func (slm *skipListMempool) Remove(_ Context, tx MempoolTx) error { return nil } + +// huandu + +type huanduSkipListMempool struct { + list *huandu.SkipList + txBytes int + maxTxBytes int + scores map[[32]byte]int64 +} + +type skipListKey struct { + hash [32]byte + priority int64 +} + +func huanduLess(a, b interface{}) int { + keyA := a.(skipListKey) + keyB := b.(skipListKey) + if keyA.priority == keyB.priority { + return bytes.Compare(keyA.hash[:], keyB.hash[:]) + } else { + if keyA.priority < keyB.priority { + return -1 + } else { + return 1 + } + } +} + +func NewHuanduSkipListMempool() huanduSkipListMempool { + list := huandu.New(huandu.LessThanFunc(huanduLess)) + + return huanduSkipListMempool{ + list: list, + scores: make(map[[32]byte]int64), + } +} + +func (slm huanduSkipListMempool) Insert(ctx Context, tx MempoolTx) error { + hashTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } + txSize := tx.Size() + priority := ctx.Priority() + + if slm.txBytes+txSize > slm.maxTxBytes { + return ErrMempoolIsFull + } + + hash := hashTx.GetHash() + key := skipListKey{hash: hash, priority: priority} + slm.list.Set(key, tx) + slm.scores[hash] = priority + slm.txBytes += txSize + + return nil +} + +func (slm huanduSkipListMempool) validateSequenceNumber(tx Tx) bool { + // TODO + return true +} + +func (slm huanduSkipListMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { + txBytes := 0 + var selectedTxs []MempoolTx + + n := slm.list.Back() + for n != nil { + tx := n.Value.(MempoolTx) + + if !slm.validateSequenceNumber(tx) { + continue + } + + selectedTxs = append(selectedTxs, tx) + txSize := tx.Size() + txBytes += txSize + if txBytes >= maxBytes { + break + } + + n = n.Prev() + } + + return selectedTxs, nil +} + +func (slm huanduSkipListMempool) CountTx() int { + return slm.list.Len() +} + +func (slm huanduSkipListMempool) Remove(_ Context, tx MempoolTx) error { + hashTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } + hash := hashTx.GetHash() + + priority, txFound := slm.scores[hash] + if !txFound { + return fmt.Errorf("tx %X not found", hash) + } + + key := skipListKey{hash: hash, priority: priority} + slm.list.Remove(key) + + delete(slm.scores, hash) + slm.txBytes -= tx.Size() + + return nil +} From 09f4e6b0977f588acd6ccc77ea1093411f20d01a Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 15 Sep 2022 17:00:52 -0500 Subject: [PATCH 020/196] very rough draft of stateful ordering --- types/mempool.go | 107 +++++++++++++++++++++++++++++++++++++++--- types/mempool_test.go | 68 +++++++++++++++++++++++++++ x/auth/tx/builder.go | 19 +++++++- x/auth/tx/decoder.go | 4 +- 4 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 types/mempool_test.go diff --git a/types/mempool.go b/types/mempool.go index e898db661ae9..92eaad6526ed 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -7,6 +7,8 @@ import ( maurice "github.com/MauriceGit/skiplist" "github.com/google/btree" huandu "github.com/huandu/skiplist" + + "github.com/cosmos/cosmos-sdk/x/auth/signing" ) // MempoolTx we define an app-side mempool transaction interface that is as @@ -275,14 +277,14 @@ type huanduSkipListMempool struct { scores map[[32]byte]int64 } -type skipListKey struct { +type priorityKey struct { hash [32]byte priority int64 } func huanduLess(a, b interface{}) int { - keyA := a.(skipListKey) - keyB := b.(skipListKey) + keyA := a.(priorityKey) + keyB := b.(priorityKey) if keyA.priority == keyB.priority { return bytes.Compare(keyA.hash[:], keyB.hash[:]) } else { @@ -294,7 +296,7 @@ func huanduLess(a, b interface{}) int { } } -func NewHuanduSkipListMempool() huanduSkipListMempool { +func NewHuanduSkipListMempool() Mempool { list := huandu.New(huandu.LessThanFunc(huanduLess)) return huanduSkipListMempool{ @@ -316,7 +318,7 @@ func (slm huanduSkipListMempool) Insert(ctx Context, tx MempoolTx) error { } hash := hashTx.GetHash() - key := skipListKey{hash: hash, priority: priority} + key := priorityKey{hash: hash, priority: priority} slm.list.Set(key, tx) slm.scores[hash] = priority slm.txBytes += txSize @@ -370,7 +372,7 @@ func (slm huanduSkipListMempool) Remove(_ Context, tx MempoolTx) error { return fmt.Errorf("tx %X not found", hash) } - key := skipListKey{hash: hash, priority: priority} + key := priorityKey{hash: hash, priority: priority} slm.list.Remove(key) delete(slm.scores, hash) @@ -378,3 +380,96 @@ func (slm huanduSkipListMempool) Remove(_ Context, tx MempoolTx) error { return nil } + +// Statefully ordered mempool + +type statefulMempool struct { + priorities *huandu.SkipList + senders map[string]*huandu.SkipList +} + +type statefulMempoolTxKey struct { + nonce uint64 + priority int64 +} + +func NewStatefulMempool() Mempool { + return &statefulMempool{ + priorities: huandu.New(huandu.LessThanFunc(huanduLess)), + senders: make(map[string]*huandu.SkipList), + } +} + +func (smp statefulMempool) Insert(ctx Context, tx MempoolTx) error { + senders := tx.(signing.SigVerifiableTx).GetSigners() + nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + + if err != nil { + return err + } else if len(senders) != len(nonces) { + return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) + } + + // TODO multiple senders + sender := senders[0].String() + nonce := nonces[0].Sequence + + senderTxs, ok := smp.senders[sender] + if !ok { + senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { + uint64Compare := huandu.Uint64 + return uint64Compare.Compare(a.(statefulMempoolTxKey).nonce, b.(statefulMempoolTxKey).nonce) + })) + } + senderTxs.Set(nonce, tx) + + key := priorityKey{hash: tx.(HashableTx).GetHash(), priority: ctx.Priority()} + smp.priorities.Set(key, tx) + + return nil +} + +func (smp statefulMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { + var selectedTxs []MempoolTx + + // start with the highest priority sender + curPrio := smp.priorities.Back() + for curPrio != nil { + nextPrio := curPrio.Prev() + // TODO min priority on nil + // cp := curPrio.Key().(priorityKey).priority + np := nextPrio.Key().(priorityKey).priority + // TODO multiple senders + sender := curPrio.Value.(signing.SigVerifiableTx).GetSigners()[0].String() + + // iterate through the sender's transactions in nonce order + senderTx := smp.senders[sender].Front() + for senderTx != nil { + k := senderTx.Key().(statefulMempoolTxKey) + // break if we've reached a transaction with a priority lower than the next highest priority in the pool + if k.priority < np { + break + } + // otherwise, select the transaction and continue iteration + selectedTxs = append(selectedTxs, senderTx.Value.(MempoolTx)) + + senderTx = senderTx.Next() + // TODO size checking + } + + curPrio = nextPrio + } + + return selectedTxs, nil +} + +func (smp statefulMempool) CountTx() int { + //TODO implement me + panic("implement me") +} + +func (smp statefulMempool) Remove(context Context, tx MempoolTx) error { + //TODO implement me + // need hash tables retrieving skiplist keys for txs/senders + panic("implement me") +} diff --git a/types/mempool_test.go b/types/mempool_test.go new file mode 100644 index 000000000000..af252841a89d --- /dev/null +++ b/types/mempool_test.go @@ -0,0 +1,68 @@ +package types_test + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/group" +) + +func TestNewBTreeMempool(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + transactions := simulateManyTx(ctx, 1000) + require.Equal(t, 1000, len(transactions)) + mempool := sdk.NewBTreeMempool(1000) + + for _, tx := range transactions { + ctx.WithPriority(rand.Int63()) + err := mempool.Insert(ctx, tx.(sdk.MempoolTx)) + require.NoError(t, err) + } +} + +func simulateManyTx(ctx sdk.Context, n int) []sdk.Tx { + transactions := make([]sdk.Tx, n) + for i := 0; i < n; i++ { + tx := simulateTx(ctx) + transactions[i] = tx + } + return transactions +} + +func simulateTx(ctx sdk.Context) sdk.Tx { + acc := authtypes.NewEmptyModuleAccount("anaccount") + + s := rand.NewSource(1) + r := rand.New(s) + msg := group.MsgUpdateGroupMembers{ + GroupId: 1, + Admin: "test", + MemberUpdates: []group.MemberRequest{}, + } + fees, _ := simtypes.RandomFees(r, ctx, sdk.NewCoins(sdk.NewCoin("coin", sdk.NewInt(100000000)))) + + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig + accounts := simtypes.RandomAccounts(r, 2) + + tx, _ := simtestutil.GenSignedMockTx( + r, + txGen, + []sdk.Msg{&msg}, + fees, + simtestutil.DefaultGenTxGas, + ctx.ChainID(), + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence()}, + accounts[0].PrivKey, + ) + return tx +} diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 03a5877208b0..de2bae842774 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -1,6 +1,8 @@ package tx import ( + "crypto/sha256" + "github.com/cosmos/gogoproto/proto" "github.com/cosmos/cosmos-sdk/client" @@ -30,7 +32,7 @@ type wrapper struct { // from the client using TxRaw if the tx was decoded from the wire authInfoBz []byte - hash [32]byte + hash *[32]byte numBytes int txBodyHasUnknownNonCriticals bool @@ -71,8 +73,21 @@ func (w *wrapper) Size() int { return w.numBytes } +func (w *wrapper) hashFromSig() [32]byte { + var sigBytes []byte + for _, bs := range w.tx.Signatures { + sigBytes = append(sigBytes, bs...) + } + return sha256.Sum256(sigBytes) +} + func (w *wrapper) GetHash() [32]byte { - return w.hash + if w.hash == nil { + hash := w.hashFromSig() + w.hash = &hash + return hash + } + return *w.hash } func (w *wrapper) GetMsgs() []sdk.Msg { diff --git a/x/auth/tx/decoder.go b/x/auth/tx/decoder.go index c4c7de45b628..f7a46d8d9b4a 100644 --- a/x/auth/tx/decoder.go +++ b/x/auth/tx/decoder.go @@ -67,11 +67,13 @@ func DefaultTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder { Signatures: raw.Signatures, } + hash := sha256.Sum256(txBytes) + return &wrapper{ tx: theTx, bodyBz: raw.BodyBytes, authInfoBz: raw.AuthInfoBytes, - hash: sha256.Sum256(txBytes), + hash: &hash, numBytes: len(txBytes), txBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals, }, nil From a6fb1e5f78b3aaf181dfff48a104b9082eec8983 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 15 Sep 2022 20:29:06 -0500 Subject: [PATCH 021/196] some notes --- x/auth/tx/builder.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index de2bae842774..a3e4ec2fe08e 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -73,6 +73,7 @@ func (w *wrapper) Size() int { return w.numBytes } +// hashFromSig hashes the signature. Presently this is only used in test when w.hash is nil func (w *wrapper) hashFromSig() [32]byte { var sigBytes []byte for _, bs := range w.tx.Signatures { @@ -81,6 +82,9 @@ func (w *wrapper) hashFromSig() [32]byte { return sha256.Sum256(sigBytes) } +// GetHash used for secondary, deterministic tx ordering in the mempool +// when there are multiple txs with the same priority. +// TODO: This should be altered use either sig bytes or txBytes for the hash func (w *wrapper) GetHash() [32]byte { if w.hash == nil { hash := w.hashFromSig() From c8931c9c24c882d134f0c3911c59d88dcdbc1468 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 16 Sep 2022 15:09:03 -0500 Subject: [PATCH 022/196] fill out remove method --- types/mempool.go | 87 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/types/mempool.go b/types/mempool.go index 92eaad6526ed..89c330e9c30b 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -3,6 +3,7 @@ package types import ( "bytes" "fmt" + "math" maurice "github.com/MauriceGit/skiplist" "github.com/google/btree" @@ -386,6 +387,7 @@ func (slm huanduSkipListMempool) Remove(_ Context, tx MempoolTx) error { type statefulMempool struct { priorities *huandu.SkipList senders map[string]*huandu.SkipList + scores map[[32]byte]int64 } type statefulMempoolTxKey struct { @@ -403,6 +405,10 @@ func NewStatefulMempool() Mempool { func (smp statefulMempool) Insert(ctx Context, tx MempoolTx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + hashTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } if err != nil { return err @@ -415,61 +421,106 @@ func (smp statefulMempool) Insert(ctx Context, tx MempoolTx) error { nonce := nonces[0].Sequence senderTxs, ok := smp.senders[sender] + // initialize sender mempool if not found if !ok { senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { uint64Compare := huandu.Uint64 return uint64Compare.Compare(a.(statefulMempoolTxKey).nonce, b.(statefulMempoolTxKey).nonce) })) } - senderTxs.Set(nonce, tx) - key := priorityKey{hash: tx.(HashableTx).GetHash(), priority: ctx.Priority()} + // if a tx with the same nonce exists, replace it and delete from the priority list + nonceTx := senderTxs.Get(nonce) + if nonceTx != nil { + h := nonceTx.Value.(HashableTx).GetHash() + smp.priorities.Remove(priorityKey{hash: h, priority: smp.scores[h]}) + if err != nil { + return err + } + } + + senderTxs.Set(nonce, tx) + key := priorityKey{hash: hashTx.GetHash(), priority: ctx.Priority()} smp.priorities.Set(key, tx) + smp.scores[key.hash] = key.priority return nil } func (smp statefulMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { var selectedTxs []MempoolTx + var txBytes int // start with the highest priority sender - curPrio := smp.priorities.Back() - for curPrio != nil { - nextPrio := curPrio.Prev() - // TODO min priority on nil - // cp := curPrio.Key().(priorityKey).priority - np := nextPrio.Key().(priorityKey).priority + priorityNode := smp.priorities.Back() + for priorityNode != nil { + var nextPriority int64 + nextPriorityNode := priorityNode.Prev() + if nextPriorityNode != nil { + nextPriority = nextPriorityNode.Key().(priorityKey).priority + } else { + nextPriority = math.MinInt64 + } + // TODO multiple senders - sender := curPrio.Value.(signing.SigVerifiableTx).GetSigners()[0].String() + sender := priorityNode.Value.(signing.SigVerifiableTx).GetSigners()[0].String() // iterate through the sender's transactions in nonce order senderTx := smp.senders[sender].Front() for senderTx != nil { k := senderTx.Key().(statefulMempoolTxKey) // break if we've reached a transaction with a priority lower than the next highest priority in the pool - if k.priority < np { + if k.priority < nextPriority { break } + + mempoolTx, _ := senderTx.Value.(MempoolTx) // otherwise, select the transaction and continue iteration - selectedTxs = append(selectedTxs, senderTx.Value.(MempoolTx)) + selectedTxs = append(selectedTxs, mempoolTx) + if txBytes += mempoolTx.Size(); txBytes >= maxBytes { + return selectedTxs, nil + } senderTx = senderTx.Next() - // TODO size checking } - curPrio = nextPrio + priorityNode = nextPriorityNode } return selectedTxs, nil } func (smp statefulMempool) CountTx() int { - //TODO implement me - panic("implement me") + return smp.priorities.Len() } func (smp statefulMempool) Remove(context Context, tx MempoolTx) error { - //TODO implement me - // need hash tables retrieving skiplist keys for txs/senders - panic("implement me") + senders := tx.(signing.SigVerifiableTx).GetSigners() + nonces, _ := tx.(signing.SigVerifiableTx).GetSignaturesV2() + + hashTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } + txHash := hashTx.GetHash() + + priority, ok := smp.scores[txHash] + if !ok { + return fmt.Errorf("tx %X not found", txHash) + } + + // TODO multiple senders + sender := senders[0].String() + nonce := nonces[0].Sequence + + senderTxs, ok := smp.senders[sender] + if !ok { + return fmt.Errorf("sender %s not found", sender) + } + + smp.priorities.Remove(priorityKey{hash: txHash, priority: priority}) + senderTxs.Remove(nonce) + delete(smp.scores, txHash) + + return nil } From c3746c7b68c31d6b15d004bd776efe3fbbacc117 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 16 Sep 2022 15:49:06 -0500 Subject: [PATCH 023/196] some comments --- types/mempool.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/types/mempool.go b/types/mempool.go index 89c330e9c30b..6242f2ffc93e 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -463,6 +463,9 @@ func (smp statefulMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]Mempoo } // TODO multiple senders + // first clear out all txs from *all* senders which have a lower nonce *and* priority greater than or equal to the + // next priority + // when processing a tx with multi senders remove it from all other sender queues sender := priorityNode.Value.(signing.SigVerifiableTx).GetSigners()[0].String() // iterate through the sender's transactions in nonce order From 816dc70cc4a72c353a71aa563f71b9df9e625d9c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 18 Sep 2022 13:11:15 -0500 Subject: [PATCH 024/196] mempool to own package --- baseapp/baseapp.go | 5 ++- types/{ => mempool}/mempool.go | 63 +++++++++++++++-------------- types/{ => mempool}/mempool_test.go | 7 ++-- x/auth/tx/builder.go | 5 ++- 4 files changed, 42 insertions(+), 38 deletions(-) rename types/{ => mempool}/mempool.go (85%) rename types/{ => mempool}/mempool_test.go (92%) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index dd020e8ec022..90ef391f9235 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -2,6 +2,7 @@ package baseapp import ( "fmt" + "github.com/cosmos/cosmos-sdk/types/mempool" "strings" abci "github.com/tendermint/tendermint/abci/types" @@ -54,7 +55,7 @@ type BaseApp struct { //nolint: maligned interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx - mempool sdk.Mempool // application side mempool + mempool mempool.Mempool // application side mempool anteHandler sdk.AnteHandler // ante handler for fee and auth postHandler sdk.AnteHandler // post handler, optional, e.g. for tips initChainer sdk.InitChainer // initialize state with validators and state blob @@ -669,7 +670,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re // TODO remove nil check when implemented if mode == runTxModeCheck && app.mempool != nil { - err = app.mempool.Insert(ctx, tx.(sdk.MempoolTx)) + err = app.mempool.Insert(ctx, tx.(mempool.Tx)) if err != nil { return gInfo, nil, anteEvents, priority, err } diff --git a/types/mempool.go b/types/mempool/mempool.go similarity index 85% rename from types/mempool.go rename to types/mempool/mempool.go index 6242f2ffc93e..0684d9ecd1e2 100644 --- a/types/mempool.go +++ b/types/mempool/mempool.go @@ -1,8 +1,9 @@ -package types +package mempool import ( "bytes" "fmt" + "github.com/cosmos/cosmos-sdk/types" "math" maurice "github.com/MauriceGit/skiplist" @@ -12,12 +13,12 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/signing" ) -// MempoolTx we define an app-side mempool transaction interface that is as +// Tx we define an app-side mempool transaction interface that is as // minimal as possible, only requiring applications to define the size of the // transaction to be used when reaping and getting the transaction itself. // Interface type casting can be used in the actual app-side mempool implementation. -type MempoolTx interface { - Tx +type Tx interface { + types.Tx // Size returns the size of the transaction in bytes. Size() int @@ -25,27 +26,27 @@ type MempoolTx interface { // HashableTx defines an interface for a transaction that can be hashed. type HashableTx interface { - MempoolTx + Tx GetHash() [32]byte } type Mempool interface { - // Insert attempts to insert a MempoolTx into the app-side mempool returning + // Insert attempts to insert a Tx into the app-side mempool returning // an error upon failure. - Insert(Context, MempoolTx) error + Insert(types.Context, Tx) error // Select returns the next set of available transactions from the app-side // mempool, up to maxBytes or until the mempool is empty. The application can // decide to return transactions from its own mempool, from the incoming // txs, or some combination of both. - Select(ctx Context, txs [][]byte, maxBytes int) ([]MempoolTx, error) + Select(ctx types.Context, txs [][]byte, maxBytes int) ([]Tx, error) // CountTx returns the number of transactions currently in the mempool. CountTx() int // Remove attempts to remove a transaction from the mempool, returning an error // upon failure. - Remove(Context, MempoolTx) error + Remove(types.Context, Tx) error } var ( @@ -95,7 +96,7 @@ func NewBTreeMempool(maxBytes int) *btreeMempool { } } -func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { +func (btm *btreeMempool) Insert(ctx types.Context, tx Tx) error { hashTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash @@ -116,14 +117,14 @@ func (btm *btreeMempool) Insert(ctx Context, tx MempoolTx) error { return nil } -func (btm *btreeMempool) validateSequenceNumber(tx Tx) bool { +func (btm *btreeMempool) validateSequenceNumber(tx types.Tx) bool { // TODO return true } -func (btm *btreeMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { +func (btm *btreeMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { txBytes := 0 - var selectedTxs []MempoolTx + var selectedTxs []Tx btm.btree.Descend(func(i btree.Item) bool { tx := i.(*btreeItem).tx if btm.validateSequenceNumber(tx) { @@ -143,7 +144,7 @@ func (btm *btreeMempool) CountTx() int { return btm.txCount } -func (btm *btreeMempool) Remove(_ Context, tx MempoolTx) error { +func (btm *btreeMempool) Remove(_ types.Context, tx Tx) error { hashTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash @@ -188,11 +189,11 @@ func (item skipListItem) String() string { } type skipListItem struct { - tx MempoolTx + tx Tx priority int64 } -func (slm *mauriceSkipListMempool) Insert(ctx Context, tx MempoolTx) error { +func (slm *mauriceSkipListMempool) Insert(ctx types.Context, tx Tx) error { hashTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash @@ -212,14 +213,14 @@ func (slm *mauriceSkipListMempool) Insert(ctx Context, tx MempoolTx) error { return nil } -func (slm *mauriceSkipListMempool) validateSequenceNumber(tx Tx) bool { +func (slm *mauriceSkipListMempool) validateSequenceNumber(tx types.Tx) bool { // TODO return true } -func (slm *mauriceSkipListMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { +func (slm *mauriceSkipListMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { txBytes := 0 - var selectedTxs []MempoolTx + var selectedTxs []Tx n := slm.list.GetLargestNode() cnt := slm.list.GetNodeCount() @@ -247,7 +248,7 @@ func (slm *mauriceSkipListMempool) CountTx() int { return slm.list.GetNodeCount() } -func (slm *mauriceSkipListMempool) Remove(_ Context, tx MempoolTx) error { +func (slm *mauriceSkipListMempool) Remove(_ types.Context, tx Tx) error { hashTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash @@ -306,7 +307,7 @@ func NewHuanduSkipListMempool() Mempool { } } -func (slm huanduSkipListMempool) Insert(ctx Context, tx MempoolTx) error { +func (slm huanduSkipListMempool) Insert(ctx types.Context, tx Tx) error { hashTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash @@ -327,18 +328,18 @@ func (slm huanduSkipListMempool) Insert(ctx Context, tx MempoolTx) error { return nil } -func (slm huanduSkipListMempool) validateSequenceNumber(tx Tx) bool { +func (slm huanduSkipListMempool) validateSequenceNumber(tx types.Tx) bool { // TODO return true } -func (slm huanduSkipListMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { +func (slm huanduSkipListMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { txBytes := 0 - var selectedTxs []MempoolTx + var selectedTxs []Tx n := slm.list.Back() for n != nil { - tx := n.Value.(MempoolTx) + tx := n.Value.(Tx) if !slm.validateSequenceNumber(tx) { continue @@ -361,7 +362,7 @@ func (slm huanduSkipListMempool) CountTx() int { return slm.list.Len() } -func (slm huanduSkipListMempool) Remove(_ Context, tx MempoolTx) error { +func (slm huanduSkipListMempool) Remove(_ types.Context, tx Tx) error { hashTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash @@ -402,7 +403,7 @@ func NewStatefulMempool() Mempool { } } -func (smp statefulMempool) Insert(ctx Context, tx MempoolTx) error { +func (smp statefulMempool) Insert(ctx types.Context, tx Tx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() hashTx, ok := tx.(HashableTx) @@ -447,8 +448,8 @@ func (smp statefulMempool) Insert(ctx Context, tx MempoolTx) error { return nil } -func (smp statefulMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]MempoolTx, error) { - var selectedTxs []MempoolTx +func (smp statefulMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { + var selectedTxs []Tx var txBytes int // start with the highest priority sender @@ -477,7 +478,7 @@ func (smp statefulMempool) Select(_ Context, _ [][]byte, maxBytes int) ([]Mempoo break } - mempoolTx, _ := senderTx.Value.(MempoolTx) + mempoolTx, _ := senderTx.Value.(Tx) // otherwise, select the transaction and continue iteration selectedTxs = append(selectedTxs, mempoolTx) if txBytes += mempoolTx.Size(); txBytes >= maxBytes { @@ -497,7 +498,7 @@ func (smp statefulMempool) CountTx() int { return smp.priorities.Len() } -func (smp statefulMempool) Remove(context Context, tx MempoolTx) error { +func (smp statefulMempool) Remove(context types.Context, tx Tx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, _ := tx.(signing.SigVerifiableTx).GetSignaturesV2() diff --git a/types/mempool_test.go b/types/mempool/mempool_test.go similarity index 92% rename from types/mempool_test.go rename to types/mempool/mempool_test.go index af252841a89d..e96f54e0359c 100644 --- a/types/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -1,4 +1,4 @@ -package types_test +package mempool_test import ( "math/rand" @@ -10,6 +10,7 @@ import ( simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -20,11 +21,11 @@ func TestNewBTreeMempool(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) transactions := simulateManyTx(ctx, 1000) require.Equal(t, 1000, len(transactions)) - mempool := sdk.NewBTreeMempool(1000) + mp := mempool.NewBTreeMempool(1000) for _, tx := range transactions { ctx.WithPriority(rand.Int63()) - err := mempool.Insert(ctx, tx.(sdk.MempoolTx)) + err := mp.Insert(ctx, tx.(mempool.Tx)) require.NoError(t, err) } } diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index a3e4ec2fe08e..b0fc0c7307cb 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -2,6 +2,7 @@ package tx import ( "crypto/sha256" + "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/gogoproto/proto" @@ -45,8 +46,8 @@ var ( _ ante.HasExtensionOptionsTx = &wrapper{} _ ExtensionOptionsTxBuilder = &wrapper{} _ tx.TipTx = &wrapper{} - _ sdk.MempoolTx = &wrapper{} - _ sdk.HashableTx = &wrapper{} + _ mempool.Tx = &wrapper{} + _ mempool.HashableTx = &wrapper{} ) // ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions. From 85c5f1e6a241409644534d0be30d119464e1f85c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 18 Sep 2022 15:48:06 -0500 Subject: [PATCH 025/196] stateful mempool test passes --- types/mempool/graph.go | 1 + types/mempool/mempool.go | 26 +++++--- types/mempool/mempool_test.go | 117 ++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 types/mempool/graph.go diff --git a/types/mempool/graph.go b/types/mempool/graph.go new file mode 100644 index 000000000000..6a26ef9f2ff2 --- /dev/null +++ b/types/mempool/graph.go @@ -0,0 +1 @@ +package mempool diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 0684d9ecd1e2..67c5b6dad873 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -291,9 +291,9 @@ func huanduLess(a, b interface{}) int { return bytes.Compare(keyA.hash[:], keyB.hash[:]) } else { if keyA.priority < keyB.priority { - return -1 - } else { return 1 + } else { + return -1 } } } @@ -400,6 +400,7 @@ func NewStatefulMempool() Mempool { return &statefulMempool{ priorities: huandu.New(huandu.LessThanFunc(huanduLess)), senders: make(map[string]*huandu.SkipList), + scores: make(map[[32]byte]int64), } } @@ -420,27 +421,28 @@ func (smp statefulMempool) Insert(ctx types.Context, tx Tx) error { // TODO multiple senders sender := senders[0].String() nonce := nonces[0].Sequence + txKey := statefulMempoolTxKey{nonce: nonce, priority: ctx.Priority()} senderTxs, ok := smp.senders[sender] // initialize sender mempool if not found if !ok { senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { uint64Compare := huandu.Uint64 - return uint64Compare.Compare(a.(statefulMempoolTxKey).nonce, b.(statefulMempoolTxKey).nonce) + return uint64Compare.Compare(b.(statefulMempoolTxKey).nonce, a.(statefulMempoolTxKey).nonce) })) + smp.senders[sender] = senderTxs } // if a tx with the same nonce exists, replace it and delete from the priority list - nonceTx := senderTxs.Get(nonce) + nonceTx := senderTxs.Get(txKey) if nonceTx != nil { h := nonceTx.Value.(HashableTx).GetHash() + // remove at old priority smp.priorities.Remove(priorityKey{hash: h, priority: smp.scores[h]}) - if err != nil { - return err - } + delete(smp.scores, h) } - senderTxs.Set(nonce, tx) + senderTxs.Set(txKey, tx) key := priorityKey{hash: hashTx.GetHash(), priority: ctx.Priority()} smp.priorities.Set(key, tx) smp.scores[key.hash] = key.priority @@ -451,6 +453,7 @@ func (smp statefulMempool) Insert(ctx types.Context, tx Tx) error { func (smp statefulMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { var selectedTxs []Tx var txBytes int + senderTxCursors := make(map[string]*huandu.Element) // start with the highest priority sender priorityNode := smp.priorities.Back() @@ -470,7 +473,11 @@ func (smp statefulMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([] sender := priorityNode.Value.(signing.SigVerifiableTx).GetSigners()[0].String() // iterate through the sender's transactions in nonce order - senderTx := smp.senders[sender].Front() + senderTx, ok := senderTxCursors[sender] + if !ok { + senderTx = smp.senders[sender].Front() + } + for senderTx != nil { k := senderTx.Key().(statefulMempoolTxKey) // break if we've reached a transaction with a priority lower than the next highest priority in the pool @@ -486,6 +493,7 @@ func (smp statefulMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([] } senderTx = senderTx.Next() + senderTxCursors[sender] = senderTx } priorityNode = nextPriorityNode diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index e96f54e0359c..840786740cb4 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -1,6 +1,9 @@ package mempool_test import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/signing" "math/rand" "testing" @@ -17,6 +20,66 @@ import ( "github.com/cosmos/cosmos-sdk/x/group" ) +type testTx struct { + hash [32]byte + priority int64 + nonce uint64 + sender string +} + +func (tx testTx) GetSigners() []sdk.AccAddress { + // TODO multi sender + return []sdk.AccAddress{sdk.AccAddress(tx.sender)} +} + +func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { + panic("GetPubkeys not implemented") +} + +func (tx testTx) GetSignaturesV2() ([]signing2.SignatureV2, error) { + // TODO multi sender + return []signing2.SignatureV2{ + { + PubKey: nil, + Data: nil, + Sequence: tx.nonce, + }, + }, nil +} + +func newTestTx(priority int64, nonce uint64, sender string) testTx { + hash := make([]byte, 32) + rand.Read(hash) + return testTx{ + hash: *(*[32]byte)(hash), + priority: priority, + nonce: nonce, + sender: sender, + } +} + +var ( + _ sdk.Tx = (*testTx)(nil) + _ mempool.Tx = (*testTx)(nil) + _ signing.SigVerifiableTx = (*testTx)(nil) +) + +func (tx testTx) GetHash() [32]byte { + return tx.hash +} + +func (tx testTx) Size() int { + return 10 +} + +func (tx testTx) GetMsgs() []sdk.Msg { + return nil +} + +func (tx testTx) ValidateBasic() error { + return nil +} + func TestNewBTreeMempool(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) transactions := simulateManyTx(ctx, 1000) @@ -28,6 +91,60 @@ func TestNewBTreeMempool(t *testing.T) { err := mp.Insert(ctx, tx.(mempool.Tx)) require.NoError(t, err) } + require.Equal(t, 1000, mp.CountTx()) +} + +func TestNewStatefulMempool(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + + // general test + transactions := simulateManyTx(ctx, 1000) + require.Equal(t, 1000, len(transactions)) + mp := mempool.NewBTreeMempool(1000) + + for _, tx := range transactions { + ctx.WithPriority(rand.Int63()) + err := mp.Insert(ctx, tx.(mempool.Tx)) + require.NoError(t, err) + } + require.Equal(t, 1000, mp.CountTx()) +} + +func TestTxOrder(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + txs := []testTx{ + {hash: [32]byte{1}, priority: 21, nonce: 4, sender: "a"}, + {hash: [32]byte{2}, priority: 8, nonce: 3, sender: "a"}, + {hash: [32]byte{3}, priority: 6, nonce: 2, sender: "a"}, + {hash: [32]byte{4}, priority: 15, nonce: 1, sender: "b"}, + {hash: [32]byte{5}, priority: 20, nonce: 1, sender: "a"}, + } + order := []byte{5, 4, 3, 2, 1} + tests := []struct { + name string + txs []testTx + pool mempool.Mempool + }{ + {name: "BTreeMempool", txs: txs, pool: mempool.NewBTreeMempool(1000)}, + {name: "StatefulMempool", txs: txs, pool: mempool.NewStatefulMempool()}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, tx := range tt.txs { + c := ctx.WithPriority(tx.priority) + err := tt.pool.Insert(c, tx) + require.NoError(t, err) + } + require.Equal(t, len(txs), tt.pool.CountTx()) + + orderedTxs, err := tt.pool.Select(ctx, nil, 1000) + require.NoError(t, err) + require.Equal(t, len(txs), len(orderedTxs)) + for i, h := range order { + require.Equal(t, h, orderedTxs[i].(testTx).hash[0]) + } + }) + } } func simulateManyTx(ctx sdk.Context, n int) []sdk.Tx { From a10b0470432247d2cb959b71ea1d5b77a048d555 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 18 Sep 2022 21:23:43 -0500 Subject: [PATCH 026/196] dfs impl in graph test passing --- types/mempool/graph.go | 157 ++++++++++++++++++++++++++++++++++++ types/mempool/graph_test.go | 72 +++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 types/mempool/graph_test.go diff --git a/types/mempool/graph.go b/types/mempool/graph.go index 6a26ef9f2ff2..2d70588fe38c 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -1 +1,158 @@ package mempool + +import ( + "container/list" + "fmt" + + huandu "github.com/huandu/skiplist" +) + +type node struct { + priority int64 + nonce uint64 + sender string + tx Tx + outPriority map[string]bool + outNonce map[string]bool + inPriority map[string]bool + inNonce map[string]bool +} + +type graph struct { + priorities *huandu.SkipList + nodes map[string]node +} + +func (n node) key() string { + return fmt.Sprintf("%s-%d-%d", n.sender, n.priority, n.nonce) +} + +func (n node) String() string { + return n.key() +} + +func (g *graph) AddEdge(from node, to node) { + if from.sender == to.sender { + from.outNonce[to.key()] = true + to.inNonce[from.key()] = true + } else { + from.outPriority[to.key()] = true + to.inPriority[from.key()] = true + } +} + +func NewGraph() *graph { + return &graph{ + nodes: make(map[string]node), + priorities: huandu.New(huandu.Int64), + } +} + +func (g *graph) AddNode(n node) { + if !g.ContainsNode(n) { + g.nodes[n.key()] = n + } + pnode := g.priorities.Set(n.priority, n.key()) + if pnode.Prev() != nil { + + } +} + +func (g *graph) ContainsNode(n node) bool { + _, ok := g.nodes[n.key()] + return ok +} + +func (g *graph) TopologicalSort() ([]node, error) { + sn := g.priorities.Back() + var start node + for sn != nil { + start = g.nodes[sn.Value.(string)] + if len(start.inPriority) == 0 && len(start.inNonce) == 0 { + break + } + + sn = sn.Prev() + } + sorted := list.New() + err := g.visit(start, make(map[string]bool), make(map[string]bool), sorted) + if err != nil { + return nil, err + } + var res []node + for e := sorted.Front(); e != nil; e = e.Next() { + res = append(res, e.Value.(node)) + } + + return res, nil +} + +/* +Kahn's Algorithm +L ← Empty list that will contain the sorted elements +S ← Set of all nodes with no incoming edge + +while S is not empty do + remove a node n from S + add n to L + for each node m with an edge e from n to m do + remove edge e from the graph + if m has no other incoming edges then + insert m into S + +if graph has edges then + return error (graph has at least one cycle) +else + return L (a topologically sorted order) + +DFS +L ← Empty list that will contain the sorted nodes +while exists nodes without a permanent mark do + select an unmarked node n + visit(n) + +function visit(node n) + if n has a permanent mark then + return + if n has a temporary mark then + stop (not a DAG) + + mark n with a temporary mark + + for each node m with an edge from n to m do + visit(m) + + remove temporary mark from n + mark n with a permanent mark + add n to head of L + +*/ +func (g *graph) visit(n node, marked map[string]bool, tmp map[string]bool, sorted *list.List) error { + if _, ok := marked[n.key()]; ok { + return nil + } + if _, ok := tmp[n.key()]; ok { + return fmt.Errorf("not a DAG, cycling on %s", n.key()) + } + + tmp[n.key()] = true + + for m := range n.outPriority { + err := g.visit(g.nodes[m], marked, tmp, sorted) + if err != nil { + return err + } + } + for m := range n.outNonce { + err := g.visit(g.nodes[m], marked, tmp, sorted) + if err != nil { + return err + } + } + + delete(tmp, n.key()) + marked[n.key()] = true + sorted.PushFront(n) + + return nil +} diff --git a/types/mempool/graph_test.go b/types/mempool/graph_test.go new file mode 100644 index 000000000000..296682c9804e --- /dev/null +++ b/types/mempool/graph_test.go @@ -0,0 +1,72 @@ +package mempool + +import ( + "testing" +) + +func initGraph() *graph { + return NewGraph() +} + +func TestPoolCase(t *testing.T) { + ns := []*node{ + {priority: 21, nonce: 4, sender: "a"}, // tx0 + {priority: 8, nonce: 3, sender: "a"}, // tx1 + {priority: 6, nonce: 2, sender: "a"}, // tx2 + {priority: 15, nonce: 1, sender: "b"}, // tx3 + {priority: 20, nonce: 1, sender: "a"}, // tx4 + //{priority: 7, nonce: 2, sender: "b"}, // tx5 + } + var nodes []node + // TODO what this API look like? + for _, n := range ns { + n.inNonce = make(map[string]bool) + n.inPriority = make(map[string]bool) + n.outNonce = make(map[string]bool) + n.outPriority = make(map[string]bool) + nodes = append(nodes, *n) + } + + tests := []struct { + name string + start string + edges [][]int + expected []int + }{ + {"case 1", + "5", + [][]int{{4, 2}, {4, 3}, {3, 2}, {2, 1}, {1, 0}}, + []int{4, 3, 2, 1, 0}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + graph := initGraph() + for _, n := range nodes { + graph.AddNode(n) + } + for _, e := range tt.edges { + graph.AddEdge(nodes[e[0]], nodes[e[1]]) + } + + results, err := graph.TopologicalSort() + + if err != nil { + t.Error(err) + return + } + if len(results) != len(tt.expected) { + t.Errorf("Wrong number of results: %v", results) + return + } + + for i := 0; i < len(tt.expected); i++ { + if results[i].key() != nodes[tt.expected[i]].key() { + t.Errorf("Wrong sort order: %v", results) + break + } + } + }) + } +} From 59b219501bc7553378294ec7d1c6266d6622bd67 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 18 Sep 2022 21:56:43 -0500 Subject: [PATCH 027/196] comment --- types/mempool/graph.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/mempool/graph.go b/types/mempool/graph.go index 2d70588fe38c..0556f1dfc5b4 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -32,6 +32,8 @@ func (n node) String() string { } func (g *graph) AddEdge(from node, to node) { + // TODO transition in* to a count? only used in finding the start node + // or some other method for finding the top most node if from.sender == to.sender { from.outNonce[to.key()] = true to.inNonce[from.key()] = true From aafb4ce8891a5ab0e632d0806746f4300cedc276 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 19 Sep 2022 02:20:19 -0400 Subject: [PATCH 028/196] add test case --- types/mempool/graph.go | 2 ++ types/mempool/graph_test.go | 37 +++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/types/mempool/graph.go b/types/mempool/graph.go index 0556f1dfc5b4..da97db42368b 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -55,6 +55,8 @@ func (g *graph) AddNode(n node) { g.nodes[n.key()] = n } pnode := g.priorities.Set(n.priority, n.key()) + // find sender subgraph nonce order + // find priority order if pnode.Prev() != nil { } diff --git a/types/mempool/graph_test.go b/types/mempool/graph_test.go index 296682c9804e..449a383c283b 100644 --- a/types/mempool/graph_test.go +++ b/types/mempool/graph_test.go @@ -8,15 +8,7 @@ func initGraph() *graph { return NewGraph() } -func TestPoolCase(t *testing.T) { - ns := []*node{ - {priority: 21, nonce: 4, sender: "a"}, // tx0 - {priority: 8, nonce: 3, sender: "a"}, // tx1 - {priority: 6, nonce: 2, sender: "a"}, // tx2 - {priority: 15, nonce: 1, sender: "b"}, // tx3 - {priority: 20, nonce: 1, sender: "a"}, // tx4 - //{priority: 7, nonce: 2, sender: "b"}, // tx5 - } +func initNodes(ns []*node) []node { var nodes []node // TODO what this API look like? for _, n := range ns { @@ -27,24 +19,41 @@ func TestPoolCase(t *testing.T) { nodes = append(nodes, *n) } + return nodes +} + +func TestPoolCase(t *testing.T) { + ns := []*node{ + {priority: 21, nonce: 4, sender: "a"}, // tx0 + {priority: 6, nonce: 3, sender: "a"}, // tx1 + {priority: 8, nonce: 2, sender: "a"}, // tx2 + {priority: 15, nonce: 1, sender: "b"}, // tx3 + {priority: 20, nonce: 1, sender: "a"}, // tx4 + {priority: 7, nonce: 2, sender: "b"}, // tx5 + } + + nodes := initNodes(ns) tests := []struct { name string - start string + limit int edges [][]int expected []int }{ - {"case 1", - "5", + {"case 1", 5, [][]int{{4, 2}, {4, 3}, {3, 2}, {2, 1}, {1, 0}}, []int{4, 3, 2, 1, 0}, + }, { + "case 2", 6, + [][]int{{4, 2}, {4, 3}, {3, 2}, {2, 1}, {1, 0}, {4, 5}, {2, 5}, {5, 1}}, + []int{4, 3, 2, 5, 1, 0}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { graph := initGraph() - for _, n := range nodes { - graph.AddNode(n) + for i := 0; i < tt.limit; i++ { + graph.AddNode(nodes[i]) } for _, e := range tt.edges { graph.AddEdge(nodes[e[0]], nodes[e[1]]) From 1798d6867aceb9f95086673a2a7e5f7c0c23093c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 21 Sep 2022 20:56:30 -0400 Subject: [PATCH 029/196] runtime edge detection is a bad idea --- types/mempool/graph.go | 255 ++++++++++++++++++++++++---------- types/mempool/graph_test.go | 2 +- types/mempool/mempool_test.go | 25 ++-- 3 files changed, 201 insertions(+), 81 deletions(-) diff --git a/types/mempool/graph.go b/types/mempool/graph.go index da97db42368b..8bc40c8017d3 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -1,30 +1,78 @@ package mempool import ( - "container/list" "fmt" - + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/signing" huandu "github.com/huandu/skiplist" ) +var _ Mempool = (*graph)(nil) + type node struct { - priority int64 - nonce uint64 - sender string + priority int64 + nonce uint64 + sender string + tx Tx outPriority map[string]bool outNonce map[string]bool inPriority map[string]bool inNonce map[string]bool + + pElement *huandu.Element + nElement *huandu.Element } type graph struct { - priorities *huandu.SkipList - nodes map[string]node + priorities *huandu.SkipList + nodes map[string]*node + senderNodes map[string]*huandu.SkipList +} + +func (g *graph) Insert(context sdk.Context, tx Tx) error { + senders := tx.(signing.SigVerifiableTx).GetSigners() + nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + + if err != nil { + return err + } else if len(senders) != len(nonces) { + return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) + } + + // TODO multiple senders + sender := senders[0].String() + nonce := nonces[0].Sequence + node := &node{priority: context.Priority(), nonce: nonce, sender: sender, tx: tx} + g.AddNode(node) + return nil +} + +func (g *graph) Select(ctx sdk.Context, txs [][]byte, maxBytes int) ([]Tx, error) { + // todo collapse multiple iterations into kahns + sorted, err := g.TopologicalSort() + if err != nil { + return nil, err + } + var res []Tx + for _, n := range sorted { + res = append(res, n.tx) + } + return res, nil +} + +func (g *graph) CountTx() int { + //TODO implement me + panic("implement me") +} + +func (g *graph) Remove(context sdk.Context, tx Tx) error { + //TODO implement me + panic("implement me") } func (n node) key() string { - return fmt.Sprintf("%s-%d-%d", n.sender, n.priority, n.nonce) + return fmt.Sprintf("%d-%s-%d", n.priority, n.sender, n.nonce) } func (n node) String() string { @@ -43,23 +91,49 @@ func (g *graph) AddEdge(from node, to node) { } } +type nodePriorityKey struct { + priority int64 + sender string + nonce uint64 +} + +func nodePriorityKeyLess(a, b interface{}) int { + keyA := a.(nodePriorityKey) + keyB := b.(nodePriorityKey) + res := huandu.Int64.Compare(keyA.priority, keyB.priority) + if res != 0 { + return res + } + + res = huandu.Uint64.Compare(keyA.nonce, keyB.nonce) + if res != 0 { + return res + } + + return huandu.String.Compare(keyA.sender, keyB.sender) +} + func NewGraph() *graph { return &graph{ - nodes: make(map[string]node), - priorities: huandu.New(huandu.Int64), + nodes: make(map[string]*node), + priorities: huandu.New(huandu.GreaterThanFunc(nodePriorityKeyLess)), + senderNodes: make(map[string]*huandu.SkipList), } } -func (g *graph) AddNode(n node) { - if !g.ContainsNode(n) { - g.nodes[n.key()] = n - } - pnode := g.priorities.Set(n.priority, n.key()) - // find sender subgraph nonce order - // find priority order - if pnode.Prev() != nil { +func (g *graph) AddNode(n *node) { + g.nodes[n.key()] = n + pnode := g.priorities.Set(nodePriorityKey{priority: n.priority, sender: n.sender, nonce: n.nonce}, n) + sgs, ok := g.senderNodes[n.sender] + if !ok { + sgs = huandu.New(huandu.Uint64) + g.senderNodes[n.sender] = sgs } + + nnode := sgs.Set(n.nonce, n) + n.pElement = pnode + n.nElement = nnode } func (g *graph) ContainsNode(n node) bool { @@ -67,47 +141,22 @@ func (g *graph) ContainsNode(n node) bool { return ok } -func (g *graph) TopologicalSort() ([]node, error) { - sn := g.priorities.Back() - var start node - for sn != nil { - start = g.nodes[sn.Value.(string)] - if len(start.inPriority) == 0 && len(start.inNonce) == 0 { - break - } - - sn = sn.Prev() - } - sorted := list.New() - err := g.visit(start, make(map[string]bool), make(map[string]bool), sorted) +func (g *graph) TopologicalSort() ([]*node, error) { + maxPriority := g.priorities.Back().Value.(*node) + start := g.senderNodes[maxPriority.sender].Front().Value.(*node) + edgeless := []*node{start} + sorted, err := g.kahns(edgeless) if err != nil { return nil, err } - var res []node - for e := sorted.Front(); e != nil; e = e.Next() { - res = append(res, e.Value.(node)) - } + return sorted, nil +} - return res, nil +func nodeEdge(n *node, m *node) string { + return fmt.Sprintf("%s->%s", n.key(), m.key()) } /* -Kahn's Algorithm -L ← Empty list that will contain the sorted elements -S ← Set of all nodes with no incoming edge - -while S is not empty do - remove a node n from S - add n to L - for each node m with an edge e from n to m do - remove edge e from the graph - if m has no other incoming edges then - insert m into S - -if graph has edges then - return error (graph has at least one cycle) -else - return L (a topologically sorted order) DFS L ← Empty list that will contain the sorted nodes @@ -131,32 +180,94 @@ function visit(node n) add n to head of L */ -func (g *graph) visit(n node, marked map[string]bool, tmp map[string]bool, sorted *list.List) error { - if _, ok := marked[n.key()]; ok { - return nil - } - if _, ok := tmp[n.key()]; ok { - return fmt.Errorf("not a DAG, cycling on %s", n.key()) - } +func (g *graph) kahns(edgeless []*node) ([]*node, error) { + var sorted []*node + visited := make(map[string]bool) - tmp[n.key()] = true + /* + L ← Empty list that will contain the sorted elements + S ← Set of all nodes with no incoming edge - for m := range n.outPriority { - err := g.visit(g.nodes[m], marked, tmp, sorted) - if err != nil { - return err + while S is not empty do + remove a node n from S + add n to L + for each node m with an edge e from n to m do + remove edge e from the graph + if m has no other incoming edges then + insert m into S + + if graph has edges then + return error (graph has at least one cycle) + else + return L (a topologically sorted order) + */ + + // priority edge rules: + // - a node has an incoming priority edge if the next priority node in ascending order has p > this.p AND a different sender (in another tree) + // OR + // - a node has an incoming priority edge if n.priority < latest L_n.priority. + // - a node has an outgoing priority edge if the next priority node in descending order has p < this.p AND a different sender (in another tree) + + priorityCursor := edgeless[0].pElement + for i := 0; i < len(edgeless) && edgeless[i] != nil; i++ { + n := edgeless[i] + //nextPriority = n.pElement.Next().Value.(*node).priority + sorted = append(sorted, n) + if n.priority == priorityCursor.Value.(*node).priority { + priorityCursor = n.pElement + } + + // nonce edge + nextNonceNode := n.nElement.Next() + if nextNonceNode != nil { + m := nextNonceNode.Value.(*node) + nonceEdge := nodeEdge(n, m) + visited[nonceEdge] = true + if !hasIncomingEdges(m, priorityCursor, visited) { + edgeless = append(edgeless, m) + } + } + + // priority edge + nextPriorityNode := n.pElement.Prev() + if nextPriorityNode != nil { + m := nextPriorityNode.Value.(*node) + if m.sender != n.sender && + // no edge where priority is equal + m.priority < n.priority { + fmt.Println(nodeEdge(n, m)) + visited[nodeEdge(n, m)] = true + if !hasIncomingEdges(m, priorityCursor, visited) { + edgeless = append(edgeless, m) + } + } } } - for m := range n.outNonce { - err := g.visit(g.nodes[m], marked, tmp, sorted) - if err != nil { - return err + + return sorted, nil +} + +func hasIncomingEdges(n *node, pcursor *huandu.Element, visited map[string]bool) bool { + prevNonceNode := n.nElement.Prev() + if prevNonceNode != nil { + m := prevNonceNode.Value.(*node) + // if edge has not been visited return true + incoming := !visited[nodeEdge(m, n)] + if incoming { + return true } } - delete(tmp, n.key()) - marked[n.key()] = true - sorted.PushFront(n) + // priority edge + if pcursor != nil { + m := pcursor.Prev().Value.(*node) + if m.sender != n.sender && + // no edge where priority is equal + m.priority > n.priority { + incoming := !visited[nodeEdge(m, n)] + return incoming + } + } - return nil + return false } diff --git a/types/mempool/graph_test.go b/types/mempool/graph_test.go index 449a383c283b..fd98ea8b423a 100644 --- a/types/mempool/graph_test.go +++ b/types/mempool/graph_test.go @@ -53,7 +53,7 @@ func TestPoolCase(t *testing.T) { t.Run(tt.name, func(t *testing.T) { graph := initGraph() for i := 0; i < tt.limit; i++ { - graph.AddNode(nodes[i]) + //graph.AddNode(nodes[i]) } for _, e := range tt.edges { graph.AddEdge(nodes[e[0]], nodes[e[1]]) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 840786740cb4..d14a50ed6b17 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -121,12 +121,20 @@ func TestTxOrder(t *testing.T) { } order := []byte{5, 4, 3, 2, 1} tests := []struct { - name string - txs []testTx - pool mempool.Mempool + name string + txs []testTx + pool mempool.Mempool + order []byte }{ - {name: "BTreeMempool", txs: txs, pool: mempool.NewBTreeMempool(1000)}, - {name: "StatefulMempool", txs: txs, pool: mempool.NewStatefulMempool()}, + {name: "BTreeMempool", txs: txs, order: order, pool: mempool.NewBTreeMempool(1000)}, + {name: "StatefulMempool", txs: txs, order: order, pool: mempool.NewStatefulMempool()}, + {name: "Stateful_3nodes", txs: []testTx{ + {hash: [32]byte{1}, priority: 21, nonce: 4, sender: "a"}, + {hash: [32]byte{4}, priority: 15, nonce: 1, sender: "b"}, + {hash: [32]byte{5}, priority: 20, nonce: 1, sender: "a"}, + }, + order: []byte{5, 1, 4}, pool: mempool.NewStatefulMempool()}, + {name: "GraphMempool", txs: txs, order: order, pool: mempool.NewGraph()}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -135,12 +143,13 @@ func TestTxOrder(t *testing.T) { err := tt.pool.Insert(c, tx) require.NoError(t, err) } - require.Equal(t, len(txs), tt.pool.CountTx()) + // TODO uncomment + //require.Equal(t, len(tt.txs), tt.pool.CountTx()) orderedTxs, err := tt.pool.Select(ctx, nil, 1000) require.NoError(t, err) - require.Equal(t, len(txs), len(orderedTxs)) - for i, h := range order { + require.Equal(t, len(tt.txs), len(orderedTxs)) + for i, h := range tt.order { require.Equal(t, h, orderedTxs[i].(testTx).hash[0]) } }) From 6be3812ce1eae0aba3a90e42aab0c69a19b06056 Mon Sep 17 00:00:00 2001 From: Jeancarlo Barrios Date: Thu, 22 Sep 2022 13:56:19 -0600 Subject: [PATCH 030/196] feat(mempool): tests and benchmark for mempool (#13273) * feat(mempool): tests and benchmark for mempool * added base for select benchmark * added simple select mempool test * added select mempool test * insert 100 and 1000 * t * t * t * move mempool to its own package and fix a small nil pointer error * Minor bug fixes for the sender pool; * minor fixes to consume * test * t * t * t * added gen tx with order * t --- types/mempool/mempool.go | 138 ++++++++++++++++++++++++++++++++++ types/mempool/mempool_test.go | 82 ++++++++++++++++++++ 2 files changed, 220 insertions(+) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 67c5b6dad873..89e76340bb74 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -536,3 +536,141 @@ func (smp statefulMempool) Remove(context types.Context, tx Tx) error { return nil } + +// The complexity is O(log(N)). Implementation +type statefullPriorityKey struct { + hash [32]byte + priority int64 + nonce uint64 +} + +type accountsHeadsKey struct { + sender string + priority int64 + hash [32]byte +} + +type AccountMemPool struct { + transactions *huandu.SkipList + currentKey accountsHeadsKey + currentItem *huandu.Element + sender string +} + +// Push cannot be executed in the middle of a select +func (amp *AccountMemPool) Push(ctx types.Context, key statefullPriorityKey, tx Tx) { + amp.transactions.Set(key, tx) + amp.currentItem = amp.transactions.Back() + newKey := amp.currentItem.Key().(statefullPriorityKey) + amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} +} + +func (amp *AccountMemPool) Pop() *Tx { + if amp.currentItem == nil { + return nil + } + itemToPop := amp.currentItem + amp.currentItem = itemToPop.Prev() + if amp.currentItem != nil { + newKey := amp.currentItem.Key().(statefullPriorityKey) + amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} + } else { + amp.currentKey = accountsHeadsKey{} + } + tx := itemToPop.Value.(Tx) + return &tx +} + +type MemPoolI struct { + accountsHeads *huandu.SkipList + senders map[string]*AccountMemPool +} + +func NewMemPoolI() MemPoolI { + return MemPoolI{ + accountsHeads: huandu.New(huandu.LessThanFunc(priorityHuanduLess)), + senders: make(map[string]*AccountMemPool), + } +} + +func (amp *MemPoolI) Insert(ctx types.Context, tx Tx) error { + senders := tx.(signing.SigVerifiableTx).GetSigners() + nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + hashTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } + + if err != nil { + return err + } else if len(senders) != len(nonces) { + return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) + } + sender := senders[0].String() + nonce := nonces[0].Sequence + + accountMeempool, ok := amp.senders[sender] + if !ok { + accountMeempool = &AccountMemPool{ + transactions: huandu.New(huandu.LessThanFunc(nonceHuanduLess)), + sender: sender, + } + } + key := statefullPriorityKey{hash: hashTx.GetHash(), nonce: nonce, priority: ctx.Priority()} + + prevKey := accountMeempool.currentKey + accountMeempool.Push(ctx, key, tx) + + amp.accountsHeads.Remove(prevKey) + amp.accountsHeads.Set(accountMeempool.currentKey, accountMeempool) + amp.senders[sender] = accountMeempool + return nil + +} + +func (amp *MemPoolI) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { + var selectedTxs []Tx + var txBytes int + + currentAccount := amp.accountsHeads.Front() + for currentAccount != nil { + accountMemPool := currentAccount.Value.(*AccountMemPool) + //currentTx := accountMemPool.transactions.Front() + prevKey := accountMemPool.currentKey + tx := accountMemPool.Pop() + if tx == nil { + return selectedTxs, nil + } + mempoolTx := *tx + selectedTxs = append(selectedTxs, mempoolTx) + if txBytes += mempoolTx.Size(); txBytes >= maxBytes { + return selectedTxs, nil + } + + amp.accountsHeads.Remove(prevKey) + amp.accountsHeads.Set(accountMemPool.currentKey, accountMemPool) + currentAccount = amp.accountsHeads.Front() + } + return selectedTxs, nil +} + +func priorityHuanduLess(a, b interface{}) int { + keyA := a.(accountsHeadsKey) + keyB := b.(accountsHeadsKey) + if keyA.priority == keyB.priority { + return bytes.Compare(keyA.hash[:], keyB.hash[:]) + } else { + if keyA.priority < keyB.priority { + return -1 + } else { + return 1 + } + } +} + +func nonceHuanduLess(a, b interface{}) int { + keyA := a.(statefullPriorityKey) + keyB := b.(statefullPriorityKey) + uint64Compare := huandu.Uint64 + return uint64Compare.Compare(keyA.nonce, keyB.nonce) +} diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index d14a50ed6b17..e9c71f5fa74c 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -1,11 +1,13 @@ package mempool_test import ( + "fmt" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" "math/rand" "testing" + "time" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" @@ -156,6 +158,13 @@ func TestTxOrder(t *testing.T) { } } +func TestTxOrderN(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + + ordered, shuffled := GenTxOrder(ctx, 5, 2) + fmt.Println(ordered, shuffled) +} + func simulateManyTx(ctx sdk.Context, n int) []sdk.Tx { transactions := make([]sdk.Tx, n) for i := 0; i < n; i++ { @@ -193,3 +202,76 @@ func simulateTx(ctx sdk.Context) sdk.Tx { ) return tx } + +type txWithPriority struct { + priority int64 + tx sdk.Tx + address string +} + +func GenTxOrder(ctx sdk.Context, nTx int, nSenders int) (ordered []txWithPriority, shuffled []txWithPriority) { + s := rand.NewSource(time.Now().UnixNano()) + r := rand.New(s) + randomAccounts := simtypes.RandomAccounts(r, nSenders) + senderNonces := make(map[string]uint64) + senderLastPriority := make(map[string]int) + for _, acc := range randomAccounts { + address := acc.Address.String() + senderNonces[address] = 1 + senderLastPriority[address] = 999999 + } + + for i := 0; i < nTx; i++ { + acc := randomAccounts[r.Intn(nSenders)] + accAddress := acc.Address.String() + accNonce := senderNonces[accAddress] + senderNonces[accAddress] += 1 + lastPriority := senderLastPriority[accAddress] + txPriority := r.Intn(lastPriority) + if txPriority == 0 { + txPriority += 1 + } + senderLastPriority[accAddress] = txPriority + tx := txWithPriority{ + priority: int64(txPriority), + tx: simulateTx2(ctx, acc, accNonce), + address: accAddress, + } + ordered = append(ordered, tx) + } + for _, item := range ordered { + tx := txWithPriority{ + priority: item.priority, + tx: item.tx, + address: item.address, + } + shuffled = append(shuffled, tx) + } + rand.Shuffle(len(shuffled), func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] }) + return ordered, shuffled +} + +func simulateTx2(ctx sdk.Context, acc simtypes.Account, nonce uint64) sdk.Tx { + s := rand.NewSource(1) + r := rand.New(s) + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig + msg := group.MsgUpdateGroupMembers{ + GroupId: 1, + Admin: acc.Address.String(), + MemberUpdates: []group.MemberRequest{}, + } + fees, _ := simtypes.RandomFees(r, ctx, sdk.NewCoins(sdk.NewCoin("coin", sdk.NewInt(100000000)))) + + tx, _ := simtestutil.GenSignedMockTx( + r, + txGen, + []sdk.Msg{&msg}, + fees, + simtestutil.DefaultGenTxGas, + ctx.ChainID(), + []uint64{authtypes.NewBaseAccountWithAddress(acc.Address).GetAccountNumber()}, + []uint64{nonce}, + acc.PrivKey, + ) + return tx +} From 5f2b9cce62372460996b6ddc2c3e7a89c01fd8e7 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 22 Sep 2022 18:13:43 -0400 Subject: [PATCH 031/196] Refactor mempool impl --- types/mempool/mempool.go | 437 +++++----------------------------- types/mempool/mempool_test.go | 21 +- 2 files changed, 57 insertions(+), 401 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 67c5b6dad873..d7258aa782ac 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -1,13 +1,10 @@ package mempool import ( - "bytes" "fmt" "github.com/cosmos/cosmos-sdk/types" "math" - maurice "github.com/MauriceGit/skiplist" - "github.com/google/btree" huandu "github.com/huandu/skiplist" "github.com/cosmos/cosmos-sdk/x/auth/signing" @@ -50,8 +47,7 @@ type Mempool interface { } var ( - _ Mempool = (*btreeMempool)(nil) - _ btree.Item = (*btreeItem)(nil) + _ Mempool = (*defaultMempool)(nil) ) var ( @@ -59,358 +55,46 @@ var ( ErrNoTxHash = fmt.Errorf("tx is not hashable") ) -// ---------------------------------------------------------------------------- -// BTree Implementation -// We use a BTree with degree=2 to approximate a Red-Black Tree. - -type btreeMempool struct { - btree *btree.BTree - txBytes int - maxTxBytes int - txCount int - scores map[[32]byte]int64 -} - -type btreeItem struct { - tx HashableTx - priority int64 -} - -func (bi *btreeItem) Less(than btree.Item) bool { - prA := bi.priority - prB := than.(*btreeItem).priority - if prA == prB { - // random, deterministic ordering - hashA := bi.tx.GetHash() - hashB := than.(*btreeItem).tx.GetHash() - return bytes.Compare(hashA[:], hashB[:]) < 0 - } - return prA < prB -} - -func NewBTreeMempool(maxBytes int) *btreeMempool { - return &btreeMempool{ - btree: btree.New(2), - txBytes: 0, - maxTxBytes: maxBytes, - } -} - -func (btm *btreeMempool) Insert(ctx types.Context, tx Tx) error { - hashTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } - txSize := tx.Size() - priority := ctx.Priority() - - if btm.txBytes+txSize > btm.maxTxBytes { - return ErrMempoolIsFull - } - - bi := &btreeItem{priority: priority, tx: hashTx} - btm.btree.ReplaceOrInsert(bi) - - btm.txBytes += txSize - btm.txCount++ - - return nil -} - -func (btm *btreeMempool) validateSequenceNumber(tx types.Tx) bool { - // TODO - return true -} - -func (btm *btreeMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { - txBytes := 0 - var selectedTxs []Tx - btm.btree.Descend(func(i btree.Item) bool { - tx := i.(*btreeItem).tx - if btm.validateSequenceNumber(tx) { - selectedTxs = append(selectedTxs, tx) - - txBytes += tx.Size() - if txBytes >= maxBytes { - return false - } - } - return true - }) - return selectedTxs, nil -} - -func (btm *btreeMempool) CountTx() int { - return btm.txCount -} - -func (btm *btreeMempool) Remove(_ types.Context, tx Tx) error { - hashTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } - hash := hashTx.GetHash() - - priority, txFound := btm.scores[hash] - if !txFound { - return fmt.Errorf("tx %X not found", hash) - } - - res := btm.btree.Delete(&btreeItem{priority: priority, tx: hashTx}) - if res == nil { - return fmt.Errorf("tx %X not in mempool", hash) - } - - delete(btm.scores, hash) - btm.txBytes -= tx.Size() - btm.txCount-- - - return nil -} - -// ---------------------------------------------------------------------------- -// Skip list implementations - -// mauriceGit - -type mauriceSkipListMempool struct { - list *maurice.SkipList - txBytes int - maxTxBytes int - scores map[[32]byte]int64 -} - -func (item skipListItem) ExtractKey() float64 { - return float64(item.priority) -} - -func (item skipListItem) String() string { - return fmt.Sprintf("txHash %X", item.tx.(HashableTx).GetHash()) -} - -type skipListItem struct { - tx Tx - priority int64 -} - -func (slm *mauriceSkipListMempool) Insert(ctx types.Context, tx Tx) error { - hashTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } - txSize := tx.Size() - priority := ctx.Priority() - - if slm.txBytes+txSize > slm.maxTxBytes { - return ErrMempoolIsFull - } - - item := skipListItem{tx: tx, priority: priority} - slm.list.Insert(item) - slm.scores[hashTx.GetHash()] = priority - slm.txBytes += txSize - - return nil -} - -func (slm *mauriceSkipListMempool) validateSequenceNumber(tx types.Tx) bool { - // TODO - return true -} - -func (slm *mauriceSkipListMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { - txBytes := 0 - var selectedTxs []Tx - - n := slm.list.GetLargestNode() - cnt := slm.list.GetNodeCount() - for i := 0; i < cnt; i++ { - tx := n.GetValue().(skipListItem).tx - - if !slm.validateSequenceNumber(tx) { - continue - } - - selectedTxs = append(selectedTxs, tx) - txSize := tx.Size() - txBytes += txSize - if txBytes >= maxBytes { - break - } - - n = slm.list.Prev(n) - } - - return selectedTxs, nil -} - -func (slm *mauriceSkipListMempool) CountTx() int { - return slm.list.GetNodeCount() -} - -func (slm *mauriceSkipListMempool) Remove(_ types.Context, tx Tx) error { - hashTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } - hash := hashTx.GetHash() - - priority, txFound := slm.scores[hash] - if !txFound { - return fmt.Errorf("tx %X not found", hash) - } - - item := skipListItem{tx: tx, priority: priority} - // TODO this is broken. Key needs hash bytes incorporated to keep it unique - slm.list.Delete(item) - - delete(slm.scores, hash) - slm.txBytes -= tx.Size() - - return nil -} - -// huandu - -type huanduSkipListMempool struct { - list *huandu.SkipList - txBytes int - maxTxBytes int - scores map[[32]byte]int64 +type defaultMempool struct { + priorities *huandu.SkipList + senders map[string]*huandu.SkipList + scores map[txKey]int64 + iterations int } -type priorityKey struct { - hash [32]byte +type txKey struct { + nonce uint64 priority int64 + sender string } -func huanduLess(a, b interface{}) int { - keyA := a.(priorityKey) - keyB := b.(priorityKey) - if keyA.priority == keyB.priority { - return bytes.Compare(keyA.hash[:], keyB.hash[:]) - } else { - if keyA.priority < keyB.priority { - return 1 - } else { - return -1 - } +func txKeyLess(a, b interface{}) int { + keyA := a.(txKey) + keyB := b.(txKey) + res := huandu.Int64.Compare(keyA.priority, keyB.priority) + if res != 0 { + return res } -} - -func NewHuanduSkipListMempool() Mempool { - list := huandu.New(huandu.LessThanFunc(huanduLess)) - return huanduSkipListMempool{ - list: list, - scores: make(map[[32]byte]int64), + res = huandu.Uint64.Compare(keyA.nonce, keyB.nonce) + if res != 0 { + return res } -} - -func (slm huanduSkipListMempool) Insert(ctx types.Context, tx Tx) error { - hashTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } - txSize := tx.Size() - priority := ctx.Priority() - - if slm.txBytes+txSize > slm.maxTxBytes { - return ErrMempoolIsFull - } - - hash := hashTx.GetHash() - key := priorityKey{hash: hash, priority: priority} - slm.list.Set(key, tx) - slm.scores[hash] = priority - slm.txBytes += txSize - - return nil -} -func (slm huanduSkipListMempool) validateSequenceNumber(tx types.Tx) bool { - // TODO - return true + return huandu.String.Compare(keyA.sender, keyB.sender) } -func (slm huanduSkipListMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { - txBytes := 0 - var selectedTxs []Tx - - n := slm.list.Back() - for n != nil { - tx := n.Value.(Tx) - - if !slm.validateSequenceNumber(tx) { - continue - } - - selectedTxs = append(selectedTxs, tx) - txSize := tx.Size() - txBytes += txSize - if txBytes >= maxBytes { - break - } - - n = n.Prev() - } - - return selectedTxs, nil -} - -func (slm huanduSkipListMempool) CountTx() int { - return slm.list.Len() -} - -func (slm huanduSkipListMempool) Remove(_ types.Context, tx Tx) error { - hashTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } - hash := hashTx.GetHash() - - priority, txFound := slm.scores[hash] - if !txFound { - return fmt.Errorf("tx %X not found", hash) - } - - key := priorityKey{hash: hash, priority: priority} - slm.list.Remove(key) - - delete(slm.scores, hash) - slm.txBytes -= tx.Size() - - return nil -} - -// Statefully ordered mempool - -type statefulMempool struct { - priorities *huandu.SkipList - senders map[string]*huandu.SkipList - scores map[[32]byte]int64 -} - -type statefulMempoolTxKey struct { - nonce uint64 - priority int64 -} - -func NewStatefulMempool() Mempool { - return &statefulMempool{ - priorities: huandu.New(huandu.LessThanFunc(huanduLess)), +func NewDefaultMempool() Mempool { + return &defaultMempool{ + priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), senders: make(map[string]*huandu.SkipList), - scores: make(map[[32]byte]int64), + scores: make(map[txKey]int64), } } -func (smp statefulMempool) Insert(ctx types.Context, tx Tx) error { +func (mp defaultMempool) Insert(ctx types.Context, tx Tx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - hashTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } if err != nil { return err @@ -421,47 +105,38 @@ func (smp statefulMempool) Insert(ctx types.Context, tx Tx) error { // TODO multiple senders sender := senders[0].String() nonce := nonces[0].Sequence - txKey := statefulMempoolTxKey{nonce: nonce, priority: ctx.Priority()} + tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender} - senderTxs, ok := smp.senders[sender] + senderTxs, ok := mp.senders[sender] // initialize sender mempool if not found if !ok { senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { - uint64Compare := huandu.Uint64 - return uint64Compare.Compare(b.(statefulMempoolTxKey).nonce, a.(statefulMempoolTxKey).nonce) + return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) })) - smp.senders[sender] = senderTxs + mp.senders[sender] = senderTxs } // if a tx with the same nonce exists, replace it and delete from the priority list - nonceTx := senderTxs.Get(txKey) - if nonceTx != nil { - h := nonceTx.Value.(HashableTx).GetHash() - // remove at old priority - smp.priorities.Remove(priorityKey{hash: h, priority: smp.scores[h]}) - delete(smp.scores, h) - } - - senderTxs.Set(txKey, tx) - key := priorityKey{hash: hashTx.GetHash(), priority: ctx.Priority()} - smp.priorities.Set(key, tx) - smp.scores[key.hash] = key.priority + senderTxs.Set(tk, tx) + // TODO for each sender/nonce + mp.scores[txKey{nonce: nonce, sender: sender}] = ctx.Priority() + mp.priorities.Set(tk, tx) return nil } -func (smp statefulMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { +func (mp defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { var selectedTxs []Tx var txBytes int - senderTxCursors := make(map[string]*huandu.Element) + senderCursors := make(map[string]*huandu.Element) // start with the highest priority sender - priorityNode := smp.priorities.Back() + priorityNode := mp.priorities.Front() for priorityNode != nil { var nextPriority int64 - nextPriorityNode := priorityNode.Prev() + nextPriorityNode := priorityNode.Next() if nextPriorityNode != nil { - nextPriority = nextPriorityNode.Key().(priorityKey).priority + nextPriority = nextPriorityNode.Key().(txKey).priority } else { nextPriority = math.MinInt64 } @@ -473,13 +148,13 @@ func (smp statefulMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([] sender := priorityNode.Value.(signing.SigVerifiableTx).GetSigners()[0].String() // iterate through the sender's transactions in nonce order - senderTx, ok := senderTxCursors[sender] + senderTx, ok := senderCursors[sender] if !ok { - senderTx = smp.senders[sender].Front() + senderTx = mp.senders[sender].Front() } for senderTx != nil { - k := senderTx.Key().(statefulMempoolTxKey) + k := senderTx.Key().(txKey) // break if we've reached a transaction with a priority lower than the next highest priority in the pool if k.priority < nextPriority { break @@ -493,7 +168,7 @@ func (smp statefulMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([] } senderTx = senderTx.Next() - senderTxCursors[sender] = senderTx + senderCursors[sender] = senderTx } priorityNode = nextPriorityNode @@ -502,37 +177,33 @@ func (smp statefulMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([] return selectedTxs, nil } -func (smp statefulMempool) CountTx() int { - return smp.priorities.Len() +func (mp defaultMempool) CountTx() int { + return mp.priorities.Len() } -func (smp statefulMempool) Remove(context types.Context, tx Tx) error { +func (mp defaultMempool) Remove(context types.Context, tx Tx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, _ := tx.(signing.SigVerifiableTx).GetSignaturesV2() + // TODO multiple senders + sender := senders[0].String() + nonce := nonces[0].Sequence - hashTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } - txHash := hashTx.GetHash() + // TODO multiple senders + tk := txKey{sender: sender, nonce: nonce} - priority, ok := smp.scores[txHash] + priority, ok := mp.scores[tk] if !ok { - return fmt.Errorf("tx %X not found", txHash) + return fmt.Errorf("tx %v not found", tk) } - // TODO multiple senders - sender := senders[0].String() - nonce := nonces[0].Sequence - - senderTxs, ok := smp.senders[sender] + senderTxs, ok := mp.senders[sender] if !ok { return fmt.Errorf("sender %s not found", sender) } - smp.priorities.Remove(priorityKey{hash: txHash, priority: priority}) + mp.priorities.Remove(txKey{priority: priority, sender: sender, nonce: nonce}) senderTxs.Remove(nonce) - delete(smp.scores, txHash) + delete(mp.scores, tk) return nil } diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index d14a50ed6b17..2df5037eff5d 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -80,27 +80,13 @@ func (tx testTx) ValidateBasic() error { return nil } -func TestNewBTreeMempool(t *testing.T) { - ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - transactions := simulateManyTx(ctx, 1000) - require.Equal(t, 1000, len(transactions)) - mp := mempool.NewBTreeMempool(1000) - - for _, tx := range transactions { - ctx.WithPriority(rand.Int63()) - err := mp.Insert(ctx, tx.(mempool.Tx)) - require.NoError(t, err) - } - require.Equal(t, 1000, mp.CountTx()) -} - func TestNewStatefulMempool(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) // general test transactions := simulateManyTx(ctx, 1000) require.Equal(t, 1000, len(transactions)) - mp := mempool.NewBTreeMempool(1000) + mp := mempool.NewDefaultMempool() for _, tx := range transactions { ctx.WithPriority(rand.Int63()) @@ -126,14 +112,13 @@ func TestTxOrder(t *testing.T) { pool mempool.Mempool order []byte }{ - {name: "BTreeMempool", txs: txs, order: order, pool: mempool.NewBTreeMempool(1000)}, - {name: "StatefulMempool", txs: txs, order: order, pool: mempool.NewStatefulMempool()}, + {name: "StatefulMempool", txs: txs, order: order, pool: mempool.NewDefaultMempool()}, {name: "Stateful_3nodes", txs: []testTx{ {hash: [32]byte{1}, priority: 21, nonce: 4, sender: "a"}, {hash: [32]byte{4}, priority: 15, nonce: 1, sender: "b"}, {hash: [32]byte{5}, priority: 20, nonce: 1, sender: "a"}, }, - order: []byte{5, 1, 4}, pool: mempool.NewStatefulMempool()}, + order: []byte{5, 1, 4}, pool: mempool.NewDefaultMempool()}, {name: "GraphMempool", txs: txs, order: order, pool: mempool.NewGraph()}, } for _, tt := range tests { From ee97a2568406ada33f439bcf36561aac5efe0ce7 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 23 Sep 2022 16:45:13 -0400 Subject: [PATCH 032/196] tx generation --- types/mempool/mempool.go | 1 + types/mempool/mempool_test.go | 83 ++++++++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 7d4fa84b98ee..b2327a4792ad 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -1,6 +1,7 @@ package mempool import ( + "bytes" "fmt" "github.com/cosmos/cosmos-sdk/types" "math" diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index e96fc312f3f3..cd0453e8727e 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -130,8 +130,7 @@ func TestTxOrder(t *testing.T) { err := tt.pool.Insert(c, tx) require.NoError(t, err) } - // TODO uncomment - //require.Equal(t, len(tt.txs), tt.pool.CountTx()) + require.Equal(t, len(tt.txs), tt.pool.CountTx()) orderedTxs, err := tt.pool.Select(ctx, nil, 1000) require.NoError(t, err) @@ -143,11 +142,82 @@ func TestTxOrder(t *testing.T) { } } +type txKey struct { + sender string + nonce uint64 + priority int64 +} + +func genOrderedTxs(maxTx int, numAcc int) (ordered []txKey, shuffled []txKey) { + s := rand.NewSource(time.Now().UnixNano()) + r := rand.New(s) + accountNonces := make(map[string]uint64) + prange := 10 + randomAccounts := simtypes.RandomAccounts(r, numAcc) + for _, account := range randomAccounts { + accountNonces[account.Address.String()] = 0 + } + + getRandAccount := func(lastAcc string) simtypes.Account { + for { + res := randomAccounts[r.Intn(len(randomAccounts))] + if res.Address.String() != lastAcc { + return res + } + } + } + + txCursor := int64(10000) + ptx := txKey{sender: getRandAccount("").Address.String(), nonce: 0, priority: txCursor} + for i := 0; i < maxTx; i++ { + var tx txKey + txType := r.Intn(5) + switch txType { + case 0: + nonce := ptx.nonce + 1 + tx = txKey{nonce: nonce, sender: ptx.sender, priority: ptx.priority - int64(r.Intn(prange)+1)} + txCursor = tx.priority + case 1: + nonce := ptx.nonce + 1 + tx = txKey{nonce: nonce, sender: ptx.sender, priority: ptx.priority} + case 2: + nonce := ptx.nonce + 1 + tx = txKey{nonce: nonce, sender: ptx.sender, priority: ptx.priority + int64(r.Intn(prange)+1)} + case 3: + sender := getRandAccount(ptx.sender).Address.String() + nonce := accountNonces[sender] + 1 + tx = txKey{nonce: nonce, sender: sender, priority: txCursor - int64(r.Intn(prange)+1)} + txCursor = tx.priority + case 4: + sender := getRandAccount(ptx.sender).Address.String() + nonce := accountNonces[sender] + 1 + tx = txKey{nonce: nonce, sender: sender, priority: ptx.priority} + } + accountNonces[tx.sender] = tx.nonce + ordered = append(ordered, tx) + ptx = tx + } + + return ordered, nil +} + func TestTxOrderN(t *testing.T) { - ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + numTx := 10 + + //ordered, shuffled := GenTxOrder(ctx, numTx, 2) + ordered, shuffled := genOrderedTxs(numTx, 3) + require.Equal(t, numTx, len(ordered)) + //require.Equal(t, numTx, len(shuffled)) - ordered, shuffled := GenTxOrder(ctx, 5, 2) - fmt.Println(ordered, shuffled) + fmt.Println("ordered") + for _, tx := range ordered { + fmt.Printf("%s, %d, %d\n", tx.sender, tx.priority, tx.nonce) + } + + fmt.Println("shuffled") + for _, tx := range shuffled { + fmt.Printf("%s, %d, %d\n", tx.sender, tx.priority, tx.nonce) + } } func simulateManyTx(ctx sdk.Context, n int) []sdk.Tx { @@ -192,6 +262,7 @@ type txWithPriority struct { priority int64 tx sdk.Tx address string + nonce uint64 // duplicate from tx.address.sequence } func GenTxOrder(ctx sdk.Context, nTx int, nSenders int) (ordered []txWithPriority, shuffled []txWithPriority) { @@ -220,6 +291,7 @@ func GenTxOrder(ctx sdk.Context, nTx int, nSenders int) (ordered []txWithPriorit tx := txWithPriority{ priority: int64(txPriority), tx: simulateTx2(ctx, acc, accNonce), + nonce: accNonce, address: accAddress, } ordered = append(ordered, tx) @@ -228,6 +300,7 @@ func GenTxOrder(ctx sdk.Context, nTx int, nSenders int) (ordered []txWithPriorit tx := txWithPriority{ priority: item.priority, tx: item.tx, + nonce: item.nonce, address: item.address, } shuffled = append(shuffled, tx) From bfda138774c6b4f3bed429cb9fd10bc732d1195f Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 23 Sep 2022 16:49:38 -0400 Subject: [PATCH 033/196] shuffle return --- types/mempool/mempool_test.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index cd0453e8727e..1652ae09b585 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -198,16 +198,24 @@ func genOrderedTxs(maxTx int, numAcc int) (ordered []txKey, shuffled []txKey) { ptx = tx } - return ordered, nil + for _, item := range ordered { + tx := txKey{ + priority: item.priority, + nonce: item.nonce, + sender: item.sender, + } + shuffled = append(shuffled, tx) + } + rand.Shuffle(len(shuffled), func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] }) + return ordered, shuffled } func TestTxOrderN(t *testing.T) { numTx := 10 - //ordered, shuffled := GenTxOrder(ctx, numTx, 2) ordered, shuffled := genOrderedTxs(numTx, 3) require.Equal(t, numTx, len(ordered)) - //require.Equal(t, numTx, len(shuffled)) + require.Equal(t, numTx, len(shuffled)) fmt.Println("ordered") for _, tx := range ordered { From ee2ed7e52d10c7f6ed21b44af1f58be5cdd3a342 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 23 Sep 2022 23:36:12 -0400 Subject: [PATCH 034/196] transaction generating --- types/mempool/mempool.go | 28 +++++- types/mempool/mempool_test.go | 165 ++++++++++++++++++++++++---------- 2 files changed, 144 insertions(+), 49 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index b2327a4792ad..3d448c4cd928 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -67,6 +67,7 @@ type txKey struct { nonce uint64 priority int64 sender string + hash [32]byte } func txKeyLess(a, b interface{}) int { @@ -77,12 +78,18 @@ func txKeyLess(a, b interface{}) int { return res } - res = huandu.Uint64.Compare(keyA.nonce, keyB.nonce) + res = bytes.Compare(keyB.hash[:], keyA.hash[:]) + //res = huandu.Bytes.Compare(keyA.hash[:], keyB.hash[:]) + if res != 0 { + return res + } + + res = huandu.String.Compare(keyA.sender, keyB.sender) if res != 0 { return res } - return huandu.String.Compare(keyA.sender, keyB.sender) + return huandu.Uint64.Compare(keyA.nonce, keyB.nonce) } func NewDefaultMempool() Mempool { @@ -97,6 +104,11 @@ func (mp defaultMempool) Insert(ctx types.Context, tx Tx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + hashableTx, ok := tx.(HashableTx) + if !ok { + return ErrNoTxHash + } + if err != nil { return err } else if len(senders) != len(nonces) { @@ -106,7 +118,7 @@ func (mp defaultMempool) Insert(ctx types.Context, tx Tx) error { // TODO multiple senders sender := senders[0].String() nonce := nonces[0].Sequence - tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender} + tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender, hash: hashableTx.GetHash()} senderTxs, ok := mp.senders[sender] // initialize sender mempool if not found @@ -209,6 +221,16 @@ func (mp defaultMempool) Remove(context types.Context, tx Tx) error { return nil } +func DebugPrintKeys(mempool Mempool) { + mp := mempool.(*defaultMempool) + n := mp.priorities.Front() + for n != nil { + k := n.Key().(txKey) + fmt.Printf("%s, %d, %d; %d\n", k.sender, k.priority, k.nonce, k.hash[0]) + n = n.Next() + } +} + // The complexity is O(log(N)). Implementation type statefullPriorityKey struct { hash [32]byte diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 1652ae09b585..7fe889d3bd84 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -5,6 +5,7 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" + "math" "math/rand" "testing" "time" @@ -26,12 +27,12 @@ type testTx struct { hash [32]byte priority int64 nonce uint64 - sender string + address sdk.AccAddress } func (tx testTx) GetSigners() []sdk.AccAddress { // TODO multi sender - return []sdk.AccAddress{sdk.AccAddress(tx.sender)} + return []sdk.AccAddress{tx.address} } func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { @@ -49,17 +50,6 @@ func (tx testTx) GetSignaturesV2() ([]signing2.SignatureV2, error) { }, nil } -func newTestTx(priority int64, nonce uint64, sender string) testTx { - hash := make([]byte, 32) - rand.Read(hash) - return testTx{ - hash: *(*[32]byte)(hash), - priority: priority, - nonce: nonce, - sender: sender, - } -} - var ( _ sdk.Tx = (*testTx)(nil) _ mempool.Tx = (*testTx)(nil) @@ -82,6 +72,10 @@ func (tx testTx) ValidateBasic() error { return nil } +func (tx testTx) String() string { + return fmt.Sprintf("tx %s, %d, %d", tx.address, tx.priority, tx.nonce) +} + func TestNewStatefulMempool(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) @@ -100,13 +94,17 @@ func TestNewStatefulMempool(t *testing.T) { func TestTxOrder(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 2) + senderA := accounts[0].Address + senderB := accounts[1].Address txs := []testTx{ - {hash: [32]byte{1}, priority: 21, nonce: 4, sender: "a"}, - {hash: [32]byte{2}, priority: 8, nonce: 3, sender: "a"}, - {hash: [32]byte{3}, priority: 6, nonce: 2, sender: "a"}, - {hash: [32]byte{4}, priority: 15, nonce: 1, sender: "b"}, - {hash: [32]byte{5}, priority: 20, nonce: 1, sender: "a"}, + {hash: [32]byte{1}, priority: 21, nonce: 4, address: senderA}, + {hash: [32]byte{2}, priority: 8, nonce: 3, address: senderA}, + {hash: [32]byte{3}, priority: 6, nonce: 2, address: senderA}, + {hash: [32]byte{4}, priority: 15, nonce: 1, address: senderB}, + {hash: [32]byte{5}, priority: 20, nonce: 1, address: senderA}, } + order := []byte{5, 4, 3, 2, 1} tests := []struct { name string @@ -116,9 +114,9 @@ func TestTxOrder(t *testing.T) { }{ {name: "StatefulMempool", txs: txs, order: order, pool: mempool.NewDefaultMempool()}, {name: "Stateful_3nodes", txs: []testTx{ - {hash: [32]byte{1}, priority: 21, nonce: 4, sender: "a"}, - {hash: [32]byte{4}, priority: 15, nonce: 1, sender: "b"}, - {hash: [32]byte{5}, priority: 20, nonce: 1, sender: "a"}, + {hash: [32]byte{1}, priority: 21, nonce: 4, address: senderA}, + {hash: [32]byte{4}, priority: 15, nonce: 1, address: senderB}, + {hash: [32]byte{5}, priority: 20, nonce: 1, address: senderA}, }, order: []byte{5, 1, 4}, pool: mempool.NewDefaultMempool()}, {name: "GraphMempool", txs: txs, order: order, pool: mempool.NewGraph()}, @@ -142,15 +140,71 @@ func TestTxOrder(t *testing.T) { } } +func TestRandomTxOrderManyTimes(t *testing.T) { + for i := 0; i < 100; i++ { + t.Log("iteration", i) + TestRandomTxOrder(t) + } +} + +func TestRandomTxOrder(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + numTx := 10 + + //seed := time.Now().UnixNano() + // interesting failing seeds: + // seed := int64(1663971399133628000) + seed := int64(1663989445512438000) + // + + ordered, shuffled := genOrderedTxs(seed, numTx, 3) + mp := mempool.NewDefaultMempool() + + for _, otx := range shuffled { + tx := testTx{otx.hash, otx.priority, otx.nonce, otx.address} + c := ctx.WithPriority(tx.priority) + err := mp.Insert(c, tx) + require.NoError(t, err) + } + + require.Equal(t, numTx, mp.CountTx()) + + selected, err := mp.Select(ctx, nil, math.MaxInt) + var orderedStr, selectedStr string + + for i := 0; i < numTx; i++ { + otx := ordered[i] + stx := selected[i].(testTx) + orderedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", orderedStr, otx.address, otx.priority, otx.nonce, otx.hash[0]) + selectedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", selectedStr, stx.address, stx.priority, stx.nonce, stx.hash[0]) + } + + require.NoError(t, err) + require.Equal(t, numTx, len(selected)) + + errMsg := fmt.Sprintf("Expected order: %v\nGot order: %v\nSeed: %v", orderedStr, selectedStr, seed) + + mempool.DebugPrintKeys(mp) + + for i, tx := range selected { + msg := fmt.Sprintf("Failed tx at index %d\n%s", i, errMsg) + require.Equal(t, ordered[i], tx.(testTx), msg) + require.Equal(t, tx.(testTx).priority, ordered[i].priority, msg) + require.Equal(t, tx.(testTx).nonce, ordered[i].nonce, msg) + require.Equal(t, tx.(testTx).address, ordered[i].address, msg) + } + +} + type txKey struct { sender string nonce uint64 priority int64 + hash [32]byte } -func genOrderedTxs(maxTx int, numAcc int) (ordered []txKey, shuffled []txKey) { - s := rand.NewSource(time.Now().UnixNano()) - r := rand.New(s) +func genOrderedTxs(seed int64, maxTx int, numAcc int) (ordered []testTx, shuffled []testTx) { + r := rand.New(rand.NewSource(seed)) accountNonces := make(map[string]uint64) prange := 10 randomAccounts := simtypes.RandomAccounts(r, numAcc) @@ -158,51 +212,69 @@ func genOrderedTxs(maxTx int, numAcc int) (ordered []txKey, shuffled []txKey) { accountNonces[account.Address.String()] = 0 } - getRandAccount := func(lastAcc string) simtypes.Account { + getRandAccount := func(notAddress string) simtypes.Account { for { res := randomAccounts[r.Intn(len(randomAccounts))] - if res.Address.String() != lastAcc { + if res.Address.String() != notAddress { return res } } } txCursor := int64(10000) - ptx := txKey{sender: getRandAccount("").Address.String(), nonce: 0, priority: txCursor} - for i := 0; i < maxTx; i++ { - var tx txKey - txType := r.Intn(5) - switch txType { + ptx := testTx{address: getRandAccount("").Address, nonce: 0, priority: txCursor} + samepChain := make(map[string]bool) + for i := 0; i < maxTx; { + var tx testTx + move := r.Intn(5) + switch move { case 0: + // same sender, less p nonce := ptx.nonce + 1 - tx = txKey{nonce: nonce, sender: ptx.sender, priority: ptx.priority - int64(r.Intn(prange)+1)} + tx = testTx{nonce: nonce, address: ptx.address, priority: txCursor - int64(r.Intn(prange)+1)} txCursor = tx.priority case 1: + // same sender, same p nonce := ptx.nonce + 1 - tx = txKey{nonce: nonce, sender: ptx.sender, priority: ptx.priority} + tx = testTx{nonce: nonce, address: ptx.address, priority: ptx.priority} case 2: + // same sender, greater p nonce := ptx.nonce + 1 - tx = txKey{nonce: nonce, sender: ptx.sender, priority: ptx.priority + int64(r.Intn(prange)+1)} + tx = testTx{nonce: nonce, address: ptx.address, priority: ptx.priority + int64(r.Intn(prange)+1)} case 3: - sender := getRandAccount(ptx.sender).Address.String() - nonce := accountNonces[sender] + 1 - tx = txKey{nonce: nonce, sender: sender, priority: txCursor - int64(r.Intn(prange)+1)} + // different sender, less p + sender := getRandAccount(ptx.address.String()).Address + nonce := accountNonces[sender.String()] + 1 + tx = testTx{nonce: nonce, address: sender, priority: txCursor - int64(r.Intn(prange)+1)} txCursor = tx.priority case 4: - sender := getRandAccount(ptx.sender).Address.String() - nonce := accountNonces[sender] + 1 - tx = txKey{nonce: nonce, sender: sender, priority: ptx.priority} + // different sender, same p + sender := getRandAccount(ptx.address.String()).Address + // disallow generating cycles of same p txs. this is an invalid processing order according to our + // algorithm decision. + if _, ok := samepChain[sender.String()]; ok { + continue + } + nonce := accountNonces[sender.String()] + 1 + tx = testTx{nonce: nonce, address: sender, priority: txCursor} + samepChain[sender.String()] = true } - accountNonces[tx.sender] = tx.nonce + tx.hash = [32]byte{byte(i)} + accountNonces[tx.address.String()] = tx.nonce ordered = append(ordered, tx) ptx = tx + i++ + if move != 4 { + samepChain = make(map[string]bool) + } } for _, item := range ordered { - tx := txKey{ + tx := testTx{ priority: item.priority, nonce: item.nonce, - sender: item.sender, + address: item.address, + hash: item.hash, } shuffled = append(shuffled, tx) } @@ -213,18 +285,19 @@ func genOrderedTxs(maxTx int, numAcc int) (ordered []txKey, shuffled []txKey) { func TestTxOrderN(t *testing.T) { numTx := 10 - ordered, shuffled := genOrderedTxs(numTx, 3) + seed := time.Now().UnixNano() + ordered, shuffled := genOrderedTxs(seed, numTx, 3) require.Equal(t, numTx, len(ordered)) require.Equal(t, numTx, len(shuffled)) fmt.Println("ordered") for _, tx := range ordered { - fmt.Printf("%s, %d, %d\n", tx.sender, tx.priority, tx.nonce) + fmt.Printf("%s, %d, %d\n", tx.address, tx.priority, tx.nonce) } fmt.Println("shuffled") for _, tx := range shuffled { - fmt.Printf("%s, %d, %d\n", tx.sender, tx.priority, tx.nonce) + fmt.Printf("%s, %d, %d\n", tx.address, tx.priority, tx.nonce) } } From afc04eb90a3b2f5127fd386d50c7ae2ec705f669 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 25 Sep 2022 07:36:26 -0500 Subject: [PATCH 035/196] clean up tx order tests --- types/mempool/mempool_test.go | 94 +++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 7fe889d3bd84..73c73d4a1baa 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -92,50 +92,82 @@ func TestNewStatefulMempool(t *testing.T) { require.Equal(t, 1000, mp.CountTx()) } +type txSpec struct { + h int + p int + n int + a sdk.AccAddress +} + func TestTxOrder(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 2) - senderA := accounts[0].Address - senderB := accounts[1].Address - txs := []testTx{ - {hash: [32]byte{1}, priority: 21, nonce: 4, address: senderA}, - {hash: [32]byte{2}, priority: 8, nonce: 3, address: senderA}, - {hash: [32]byte{3}, priority: 6, nonce: 2, address: senderA}, - {hash: [32]byte{4}, priority: 15, nonce: 1, address: senderB}, - {hash: [32]byte{5}, priority: 20, nonce: 1, address: senderA}, - } + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 5) + sa := accounts[0].Address + sb := accounts[1].Address + //sc := accounts[2].Address + //sd := accounts[3].Address + //se := accounts[4].Address - order := []byte{5, 4, 3, 2, 1} tests := []struct { - name string - txs []testTx - pool mempool.Mempool - order []byte + txs []txSpec + order []int }{ - {name: "StatefulMempool", txs: txs, order: order, pool: mempool.NewDefaultMempool()}, - {name: "Stateful_3nodes", txs: []testTx{ - {hash: [32]byte{1}, priority: 21, nonce: 4, address: senderA}, - {hash: [32]byte{4}, priority: 15, nonce: 1, address: senderB}, - {hash: [32]byte{5}, priority: 20, nonce: 1, address: senderA}, + { + txs: []txSpec{ + {h: 1, p: 21, n: 4, a: sa}, + {h: 2, p: 8, n: 3, a: sa}, + {h: 3, p: 6, n: 2, a: sa}, + {h: 4, p: 15, n: 1, a: sb}, + {h: 5, p: 20, n: 1, a: sa}, + }, + order: []int{5, 4, 3, 2, 1}, + }, + { + txs: []txSpec{ + {h: 1, p: 21, n: 4, a: sa}, + {h: 4, p: 15, n: 1, a: sb}, + {h: 5, p: 20, n: 1, a: sa}, + }, + order: []int{5, 1, 4}}, + { + txs: []txSpec{ + {h: 1, p: 50, n: 3, a: sa}, + {h: 2, p: 30, n: 2, a: sa}, + {h: 3, p: 10, n: 1, a: sa}, + {h: 4, p: 15, n: 1, a: sb}, + {h: 5, p: 21, n: 2, a: sb}, + }, + order: []int{4, 5, 3, 2, 1}, + }, + { + txs: []txSpec{ + {h: 1, p: 50, n: 3, a: sa}, + {h: 2, p: 10, n: 2, a: sa}, + {h: 3, p: 99, n: 1, a: sa}, + {h: 4, p: 15, n: 1, a: sb}, + {h: 5, p: 8, n: 2, a: sb}, + }, + order: []int{4, 3, 5, 2, 1}, }, - order: []byte{5, 1, 4}, pool: mempool.NewDefaultMempool()}, - {name: "GraphMempool", txs: txs, order: order, pool: mempool.NewGraph()}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - for _, tx := range tt.txs { + for i, tt := range tests { + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + pool := mempool.NewDefaultMempool() + for _, ts := range tt.txs { + tx := testTx{hash: [32]byte{byte(ts.h)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} c := ctx.WithPriority(tx.priority) - err := tt.pool.Insert(c, tx) + err := pool.Insert(c, tx) require.NoError(t, err) } - require.Equal(t, len(tt.txs), tt.pool.CountTx()) + require.Equal(t, len(tt.txs), pool.CountTx()) - orderedTxs, err := tt.pool.Select(ctx, nil, 1000) + orderedTxs, err := pool.Select(ctx, nil, 1000) require.NoError(t, err) - require.Equal(t, len(tt.txs), len(orderedTxs)) - for i, h := range tt.order { - require.Equal(t, h, orderedTxs[i].(testTx).hash[0]) + var txOrder []int + for _, tx := range orderedTxs { + txOrder = append(txOrder, int(tx.(testTx).hash[0])) } + require.Equal(t, tt.order, txOrder) }) } } From a9a8fa3e48c45b3d0a2b6fd820cde8de156b55fc Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 25 Sep 2022 16:07:16 -0400 Subject: [PATCH 036/196] generative tests are passing --- types/mempool/mempool.go | 1 + types/mempool/mempool_test.go | 107 +++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 3d448c4cd928..ee7e5760bd6f 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -167,6 +167,7 @@ func (mp defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx } for senderTx != nil { + mp.iterations++ k := senderTx.Key().(txKey) // break if we've reached a transaction with a priority lower than the next highest priority in the pool if k.priority < nextPriority { diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 73c73d4a1baa..f445fb367313 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -73,7 +73,7 @@ func (tx testTx) ValidateBasic() error { } func (tx testTx) String() string { - return fmt.Sprintf("tx %s, %d, %d", tx.address, tx.priority, tx.nonce) + return fmt.Sprintf("tx a: %s, p: %d, n: %d", tx.address, tx.priority, tx.nonce) } func TestNewStatefulMempool(t *testing.T) { @@ -93,12 +93,47 @@ func TestNewStatefulMempool(t *testing.T) { } type txSpec struct { + i int h int p int n int a sdk.AccAddress } +func (tx txSpec) String() string { + return fmt.Sprintf("[tx i: %d, a: %s, p: %d, n: %d]", tx.i, tx.a, tx.p, tx.n) +} + +func TestOutOfOrder(t *testing.T) { + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 2) + sa := accounts[0].Address + sb := accounts[1].Address + + outOfOrders := [][]testTx{ + { + {priority: 20, nonce: 1, address: sa}, + {priority: 21, nonce: 4, address: sa}, + {priority: 15, nonce: 1, address: sb}, + {priority: 8, nonce: 3, address: sa}, + {priority: 6, nonce: 2, address: sa}, + }, + { + {priority: 15, nonce: 1, address: sb}, + {priority: 20, nonce: 1, address: sa}, + {priority: 21, nonce: 4, address: sa}, + {priority: 8, nonce: 3, address: sa}, + {priority: 6, nonce: 2, address: sa}, + }} + + for _, outOfOrder := range outOfOrders { + var mtxs []mempool.Tx + for _, mtx := range outOfOrder { + mtxs = append(mtxs, mtx) + } + require.Error(t, validateOrder(mtxs)) + } +} + func TestTxOrder(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 5) @@ -111,6 +146,7 @@ func TestTxOrder(t *testing.T) { tests := []struct { txs []txSpec order []int + fail bool }{ { txs: []txSpec{ @@ -147,7 +183,7 @@ func TestTxOrder(t *testing.T) { {h: 4, p: 15, n: 1, a: sb}, {h: 5, p: 8, n: 2, a: sb}, }, - order: []int{4, 3, 5, 2, 1}, + order: []int{3, 4, 2, 1, 5}, }, } for i, tt := range tests { @@ -168,6 +204,7 @@ func TestTxOrder(t *testing.T) { txOrder = append(txOrder, int(tx.(testTx).hash[0])) } require.Equal(t, tt.order, txOrder) + require.NoError(t, validateOrder(orderedTxs)) }) } } @@ -179,14 +216,66 @@ func TestRandomTxOrderManyTimes(t *testing.T) { } } +// validateOrder checks that the txs are ordered by priority and nonce +// in O(n^n) time by checking each tx against all the other txs +func validateOrder(mtxs []mempool.Tx) error { + var itxs []txSpec + for i, mtx := range mtxs { + tx := mtx.(testTx) + itxs = append(itxs, txSpec{p: int(tx.priority), n: int(tx.nonce), a: tx.address, i: i}) + } + + // Given 2 transactions t1 and t2, where t2.p > t1.p but t2.i < t1.i + // Then if t2.sender have the same sender then t2.nonce > t1.nonce + // or + // If t1 and t2 have different senders then there must be some t3 with + // t3.sender == t2.sender and t3.n < t2.n and t3.p <= t1.p + + for _, a := range itxs { + for _, b := range itxs { + // when b is before a + + // when a is before b + if a.i < b.i { + // same sender + if a.a.Equals(b.a) { + // same sender + if a.n == b.n { + return fmt.Errorf("same sender tx have the same nonce\n%v\n%v", a, b) + } + if a.n > b.n { + return fmt.Errorf("same sender tx have wrong nonce order\n%v\n%v", a, b) + } + } else { + // different sender + if a.p < b.p { + // find a tx with same sender as b and lower nonce + found := false + for _, c := range itxs { + if c.a.Equals(b.a) && c.n < b.n && c.p <= a.p { + found = true + break + } + } + if !found { + return fmt.Errorf("different sender tx have wrong order\n%v\n%v", b, a) + } + } + } + } + } + } + return nil +} + func TestRandomTxOrder(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - numTx := 10 + numTx := 100 - //seed := time.Now().UnixNano() + seed := time.Now().UnixNano() // interesting failing seeds: // seed := int64(1663971399133628000) - seed := int64(1663989445512438000) + // seed := int64(1663989445512438000) // ordered, shuffled := genOrderedTxs(seed, numTx, 3) @@ -216,15 +305,17 @@ func TestRandomTxOrder(t *testing.T) { errMsg := fmt.Sprintf("Expected order: %v\nGot order: %v\nSeed: %v", orderedStr, selectedStr, seed) - mempool.DebugPrintKeys(mp) + //mempool.DebugPrintKeys(mp) + + require.NoError(t, validateOrder(selected), errMsg) - for i, tx := range selected { + /*for i, tx := range selected { msg := fmt.Sprintf("Failed tx at index %d\n%s", i, errMsg) require.Equal(t, ordered[i], tx.(testTx), msg) require.Equal(t, tx.(testTx).priority, ordered[i].priority, msg) require.Equal(t, tx.(testTx).nonce, ordered[i].nonce, msg) require.Equal(t, tx.(testTx).address, ordered[i].address, msg) - } + }*/ } From 9556effd5b8035df18b8a08846f1a62204fffc0c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 25 Sep 2022 16:14:18 -0400 Subject: [PATCH 037/196] add comment --- types/mempool/mempool_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index f445fb367313..d18dd524775c 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -326,6 +326,8 @@ type txKey struct { hash [32]byte } +// since there are multiple valid ordered graph traversals for a given set of txs strict +// validation against the ordered txs generated from this function is not possible as written func genOrderedTxs(seed int64, maxTx int, numAcc int) (ordered []testTx, shuffled []testTx) { r := rand.New(rand.NewSource(seed)) accountNonces := make(map[string]uint64) From 87bf3b65491af76f04dc398191e682a9839e4e25 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 25 Sep 2022 16:26:58 -0400 Subject: [PATCH 038/196] more iterations --- types/mempool/mempool.go | 14 +++++++++----- types/mempool/mempool_test.go | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index ee7e5760bd6f..d67ece6b5020 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -100,7 +100,7 @@ func NewDefaultMempool() Mempool { } } -func (mp defaultMempool) Insert(ctx types.Context, tx Tx) error { +func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() @@ -138,7 +138,7 @@ func (mp defaultMempool) Insert(ctx types.Context, tx Tx) error { return nil } -func (mp defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { +func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { var selectedTxs []Tx var txBytes int senderCursors := make(map[string]*huandu.Element) @@ -167,7 +167,7 @@ func (mp defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx } for senderTx != nil { - mp.iterations++ + mp.iterations = mp.iterations + 1 k := senderTx.Key().(txKey) // break if we've reached a transaction with a priority lower than the next highest priority in the pool if k.priority < nextPriority { @@ -191,11 +191,11 @@ func (mp defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx return selectedTxs, nil } -func (mp defaultMempool) CountTx() int { +func (mp *defaultMempool) CountTx() int { return mp.priorities.Len() } -func (mp defaultMempool) Remove(context types.Context, tx Tx) error { +func (mp *defaultMempool) Remove(context types.Context, tx Tx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, _ := tx.(signing.SigVerifiableTx).GetSignaturesV2() // TODO multiple senders @@ -232,6 +232,10 @@ func DebugPrintKeys(mempool Mempool) { } } +func Iterations(mempool Mempool) int { + return mempool.(*defaultMempool).iterations +} + // The complexity is O(log(N)). Implementation type statefullPriorityKey struct { hash [32]byte diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index d18dd524775c..53188539053f 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -211,7 +211,6 @@ func TestTxOrder(t *testing.T) { func TestRandomTxOrderManyTimes(t *testing.T) { for i := 0; i < 100; i++ { - t.Log("iteration", i) TestRandomTxOrder(t) } } @@ -270,7 +269,7 @@ func validateOrder(mtxs []mempool.Tx) error { func TestRandomTxOrder(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - numTx := 100 + numTx := 1000 seed := time.Now().UnixNano() // interesting failing seeds: @@ -317,6 +316,7 @@ func TestRandomTxOrder(t *testing.T) { require.Equal(t, tx.(testTx).address, ordered[i].address, msg) }*/ + fmt.Printf("seed: %d completed in %d iterations\n", seed, mempool.Iterations(mp)) } type txKey struct { From 5b647eb9dbd1ed14a523740a327fe1d100c031c2 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 25 Sep 2022 16:27:59 -0400 Subject: [PATCH 039/196] clean up --- types/mempool/mempool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index d67ece6b5020..53a7b6d7528c 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -167,7 +167,7 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T } for senderTx != nil { - mp.iterations = mp.iterations + 1 + mp.iterations++ k := senderTx.Key().(txKey) // break if we've reached a transaction with a priority lower than the next highest priority in the pool if k.priority < nextPriority { From 6407c12a2104f4511e440f808682a3f9c68ca32e Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 25 Sep 2022 19:15:10 -0400 Subject: [PATCH 040/196] even more tests of tests --- types/mempool/mempool_test.go | 66 +++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 53188539053f..d64807614953 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -132,6 +132,15 @@ func TestOutOfOrder(t *testing.T) { } require.Error(t, validateOrder(mtxs)) } + + seed := time.Now().UnixNano() + randomTxs := genRandomTxs(seed, 1000, 10) + var rmtxs []mempool.Tx + for _, rtx := range randomTxs { + rmtxs = append(rmtxs, rtx) + } + + require.Error(t, validateOrder(rmtxs)) } func TestTxOrder(t *testing.T) { @@ -212,6 +221,7 @@ func TestTxOrder(t *testing.T) { func TestRandomTxOrderManyTimes(t *testing.T) { for i := 0; i < 100; i++ { TestRandomTxOrder(t) + TestRandomGeneratedTx(t) } } @@ -267,6 +277,30 @@ func validateOrder(mtxs []mempool.Tx) error { return nil } +func TestRandomGeneratedTx(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + numTx := 1000 + numAccounts := 10 + seed := time.Now().UnixNano() + + generated := genRandomTxs(seed, numTx, numAccounts) + mp := mempool.NewDefaultMempool() + + for _, otx := range generated { + tx := testTx{otx.hash, otx.priority, otx.nonce, otx.address} + c := ctx.WithPriority(tx.priority) + err := mp.Insert(c, tx) + require.NoError(t, err) + } + + selected, err := mp.Select(ctx, nil, 100000) + require.Equal(t, len(generated), len(selected)) + require.NoError(t, err) + require.NoError(t, validateOrder(selected)) + + fmt.Printf("seed: %d completed in %d iterations\n", seed, mempool.Iterations(mp)) +} + func TestRandomTxOrder(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) numTx := 1000 @@ -277,7 +311,7 @@ func TestRandomTxOrder(t *testing.T) { // seed := int64(1663989445512438000) // - ordered, shuffled := genOrderedTxs(seed, numTx, 3) + ordered, shuffled := genOrderedTxs(seed, numTx, 12) mp := mempool.NewDefaultMempool() for _, otx := range shuffled { @@ -295,8 +329,10 @@ func TestRandomTxOrder(t *testing.T) { for i := 0; i < numTx; i++ { otx := ordered[i] stx := selected[i].(testTx) - orderedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", orderedStr, otx.address, otx.priority, otx.nonce, otx.hash[0]) - selectedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", selectedStr, stx.address, stx.priority, stx.nonce, stx.hash[0]) + orderedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", + orderedStr, otx.address, otx.priority, otx.nonce, otx.hash[0]) + selectedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", + selectedStr, stx.address, stx.priority, stx.nonce, stx.hash[0]) } require.NoError(t, err) @@ -326,6 +362,30 @@ type txKey struct { hash [32]byte } +func genRandomTxs(seed int64, countTx int, countAccount int) (res []testTx) { + maxPriority := 100 + r := rand.New(rand.NewSource(seed)) + accounts := simtypes.RandomAccounts(r, countAccount) + accountNonces := make(map[string]uint64) + for _, account := range accounts { + accountNonces[account.Address.String()] = 0 + } + + for i := 0; i < countTx; i++ { + addr := accounts[r.Intn(countAccount)].Address + priority := int64(r.Intn(maxPriority + 1)) + nonce := accountNonces[addr.String()] + accountNonces[addr.String()] = nonce + 1 + res = append(res, testTx{ + priority: priority, + nonce: nonce, + address: addr, + hash: [32]byte{byte(i)}}) + } + + return res +} + // since there are multiple valid ordered graph traversals for a given set of txs strict // validation against the ordered txs generated from this function is not possible as written func genOrderedTxs(seed int64, maxTx int, numAcc int) (ordered []testTx, shuffled []testTx) { From 629d17bd31211eec071f8540bb48221d157a63f1 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 25 Sep 2022 19:28:11 -0400 Subject: [PATCH 041/196] notes on multi sender handling --- types/mempool/mempool.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 53a7b6d7528c..e27073bac2b6 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -112,7 +112,8 @@ func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { if err != nil { return err } else if len(senders) != len(nonces) { - return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) + return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", + len(senders), len(nonces)) } // TODO multiple senders @@ -155,9 +156,10 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T } // TODO multiple senders - // first clear out all txs from *all* senders which have a lower nonce *and* priority greater than or equal to the - // next priority - // when processing a tx with multi senders remove it from all other sender queues + // A multi sender tx may only be selected once all antecedent (nonce-wise) txs for each sender account + // have been selected. + // When selecting a tx with multi senders signal it as selected so it doesn't show up multiple times + // in the output list. Skip when already selected. sender := priorityNode.Value.(signing.SigVerifiableTx).GetSigners()[0].String() // iterate through the sender's transactions in nonce order From 0bd11ff7c1ea2064ea031956da4ff48febaef6ac Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 28 Sep 2022 08:52:13 -0500 Subject: [PATCH 042/196] rework tests --- types/mempool/mempool_test.go | 65 +++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index d64807614953..e6e05c3c81da 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -159,47 +159,59 @@ func TestTxOrder(t *testing.T) { }{ { txs: []txSpec{ - {h: 1, p: 21, n: 4, a: sa}, - {h: 2, p: 8, n: 3, a: sa}, - {h: 3, p: 6, n: 2, a: sa}, - {h: 4, p: 15, n: 1, a: sb}, - {h: 5, p: 20, n: 1, a: sa}, + {p: 21, n: 4, a: sa}, + {p: 8, n: 3, a: sa}, + {p: 6, n: 2, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 20, n: 1, a: sa}, }, - order: []int{5, 4, 3, 2, 1}, + order: []int{4, 3, 2, 1, 0}, }, { txs: []txSpec{ - {h: 1, p: 21, n: 4, a: sa}, - {h: 4, p: 15, n: 1, a: sb}, - {h: 5, p: 20, n: 1, a: sa}, + {p: 21, n: 4, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 20, n: 1, a: sa}, }, - order: []int{5, 1, 4}}, + order: []int{2, 0, 1}}, { txs: []txSpec{ - {h: 1, p: 50, n: 3, a: sa}, - {h: 2, p: 30, n: 2, a: sa}, - {h: 3, p: 10, n: 1, a: sa}, - {h: 4, p: 15, n: 1, a: sb}, - {h: 5, p: 21, n: 2, a: sb}, + {p: 50, n: 3, a: sa}, + {p: 30, n: 2, a: sa}, + {p: 10, n: 1, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 21, n: 2, a: sb}, }, - order: []int{4, 5, 3, 2, 1}, + order: []int{3, 4, 2, 1, 0}, }, { txs: []txSpec{ - {h: 1, p: 50, n: 3, a: sa}, - {h: 2, p: 10, n: 2, a: sa}, - {h: 3, p: 99, n: 1, a: sa}, - {h: 4, p: 15, n: 1, a: sb}, - {h: 5, p: 8, n: 2, a: sb}, + {p: 50, n: 3, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 99, n: 1, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 8, n: 2, a: sb}, }, - order: []int{3, 4, 2, 1, 5}, + order: []int{2, 3, 1, 0, 4}, + }, + { + txs: []txSpec{ + {p: 30, a: sa, n: 2}, + {p: 20, a: sb, n: 1}, + {p: 15, a: sa, n: 1}, + {p: 10, a: sa, n: 0}, + {p: 8, a: sb, n: 0}, + {p: 6, a: sa, n: 3}, + {p: 4, a: sb, n: 3}, + }, + order: []int{3, 2, 0, 4, 1, 5, 6}, }, } for i, tt := range tests { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { pool := mempool.NewDefaultMempool() - for _, ts := range tt.txs { - tx := testTx{hash: [32]byte{byte(ts.h)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} + for i, ts := range tt.txs { + tx := testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} c := ctx.WithPriority(tx.priority) err := pool.Insert(c, tx) require.NoError(t, err) @@ -226,7 +238,7 @@ func TestRandomTxOrderManyTimes(t *testing.T) { } // validateOrder checks that the txs are ordered by priority and nonce -// in O(n^n) time by checking each tx against all the other txs +// in O(n^2) time by checking each tx against all the other txs func validateOrder(mtxs []mempool.Tx) error { var itxs []txSpec for i, mtx := range mtxs { @@ -304,6 +316,7 @@ func TestRandomGeneratedTx(t *testing.T) { func TestRandomTxOrder(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) numTx := 1000 + numAccounts := 10 seed := time.Now().UnixNano() // interesting failing seeds: @@ -311,7 +324,7 @@ func TestRandomTxOrder(t *testing.T) { // seed := int64(1663989445512438000) // - ordered, shuffled := genOrderedTxs(seed, numTx, 12) + ordered, shuffled := genOrderedTxs(seed, numTx, numAccounts) mp := mempool.NewDefaultMempool() for _, otx := range shuffled { From 01858d2ceccf9c1d5ff608623598b5680e265c3b Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 28 Sep 2022 08:56:57 -0500 Subject: [PATCH 043/196] hypothesis for multi sender --- types/mempool/mempool.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index e27073bac2b6..696a5f9d326d 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -143,6 +143,7 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T var selectedTxs []Tx var txBytes int senderCursors := make(map[string]*huandu.Element) + //mutltiSenders := make(map[string][]*huandu.Element) // start with the highest priority sender priorityNode := mp.priorities.Front() @@ -155,12 +156,8 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T nextPriority = math.MinInt64 } - // TODO multiple senders - // A multi sender tx may only be selected once all antecedent (nonce-wise) txs for each sender account - // have been selected. - // When selecting a tx with multi senders signal it as selected so it doesn't show up multiple times - // in the output list. Skip when already selected. - sender := priorityNode.Value.(signing.SigVerifiableTx).GetSigners()[0].String() + senders := priorityNode.Value.(signing.SigVerifiableTx).GetSigners() + sender := priorityNode.Key().(txKey).sender // iterate through the sender's transactions in nonce order senderTx, ok := senderCursors[sender] @@ -168,6 +165,17 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T senderTx = mp.senders[sender].Front() } + // this is a multi sender tx + if len(senders) > 1 { + for _, s := range senders { + sc, ok := senderCursors[s.String()] + if !ok || sc.Key().(txKey).nonce < priorityNode.Key().(txKey).nonce { + // dependent txs for this multi sender tx have not yet been satisfied + continue + } + } + } + for senderTx != nil { mp.iterations++ k := senderTx.Key().(txKey) From ed23ead9a98b776a29aedc3917ecac2acd441c8d Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 28 Sep 2022 12:18:40 -0400 Subject: [PATCH 044/196] refactoring into multi sender testing --- types/mempool/mempool.go | 70 ++++++++-------- types/mempool/mempool_test.go | 151 ++++++++++++---------------------- 2 files changed, 88 insertions(+), 133 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 696a5f9d326d..002983c19c85 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -116,26 +116,25 @@ func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { len(senders), len(nonces)) } - // TODO multiple senders - sender := senders[0].String() - nonce := nonces[0].Sequence - tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender, hash: hashableTx.GetHash()} - - senderTxs, ok := mp.senders[sender] - // initialize sender mempool if not found - if !ok { - senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { - return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) - })) - mp.senders[sender] = senderTxs - } + for i, senderAddr := range senders { + sender := senderAddr.String() + nonce := nonces[i].Sequence + tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender, hash: hashableTx.GetHash()} - // if a tx with the same nonce exists, replace it and delete from the priority list - senderTxs.Set(tk, tx) - // TODO for each sender/nonce - mp.scores[txKey{nonce: nonce, sender: sender}] = ctx.Priority() - mp.priorities.Set(tk, tx) + senderTxs, ok := mp.senders[sender] + // initialize sender mempool if not found + if !ok { + senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { + return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) + })) + mp.senders[sender] = senderTxs + } + // if a tx with the same nonce exists, replace it and delete from the priority list + senderTxs.Set(tk, tx) + mp.scores[txKey{nonce: nonce, sender: sender}] = ctx.Priority() + mp.priorities.Set(tk, tx) + } return nil } @@ -143,7 +142,6 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T var selectedTxs []Tx var txBytes int senderCursors := make(map[string]*huandu.Element) - //mutltiSenders := make(map[string][]*huandu.Element) // start with the highest priority sender priorityNode := mp.priorities.Front() @@ -165,7 +163,7 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T senderTx = mp.senders[sender].Front() } - // this is a multi sender tx + // if this is a multi sender tx if len(senders) > 1 { for _, s := range senders { sc, ok := senderCursors[s.String()] @@ -208,27 +206,27 @@ func (mp *defaultMempool) CountTx() int { func (mp *defaultMempool) Remove(context types.Context, tx Tx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, _ := tx.(signing.SigVerifiableTx).GetSignaturesV2() - // TODO multiple senders - sender := senders[0].String() - nonce := nonces[0].Sequence - // TODO multiple senders - tk := txKey{sender: sender, nonce: nonce} + for i, senderAddr := range senders { + sender := senderAddr.String() + nonce := nonces[i].Sequence - priority, ok := mp.scores[tk] - if !ok { - return fmt.Errorf("tx %v not found", tk) - } + tk := txKey{sender: sender, nonce: nonce} - senderTxs, ok := mp.senders[sender] - if !ok { - return fmt.Errorf("sender %s not found", sender) - } + priority, ok := mp.scores[tk] + if !ok { + return fmt.Errorf("tx %v not found", tk) + } - mp.priorities.Remove(txKey{priority: priority, sender: sender, nonce: nonce}) - senderTxs.Remove(nonce) - delete(mp.scores, tk) + senderTxs, ok := mp.senders[sender] + if !ok { + return fmt.Errorf("sender %s not found", sender) + } + mp.priorities.Remove(txKey{priority: priority, sender: sender, nonce: nonce}) + senderTxs.Remove(nonce) + delete(mp.scores, tk) + } return nil } diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index e6e05c3c81da..ef24f7283313 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -24,30 +24,42 @@ import ( ) type testTx struct { - hash [32]byte - priority int64 - nonce uint64 - address sdk.AccAddress + hash [32]byte + priority int64 + nonce uint64 + address sdk.AccAddress + multiAddress []sdk.AccAddress + multiNonces []uint64 } func (tx testTx) GetSigners() []sdk.AccAddress { - // TODO multi sender - return []sdk.AccAddress{tx.address} + if len(tx.multiAddress) == 0 { + return []sdk.AccAddress{tx.address} + } + return tx.multiAddress } func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { panic("GetPubkeys not implemented") } -func (tx testTx) GetSignaturesV2() ([]signing2.SignatureV2, error) { - // TODO multi sender - return []signing2.SignatureV2{ - { +func (tx testTx) GetSignaturesV2() (res []signing2.SignatureV2, err error) { + if len(tx.multiNonces) == 0 { + res = append(res, signing2.SignatureV2{ PubKey: nil, Data: nil, Sequence: tx.nonce, - }, - }, nil + }) + } else { + for _, nonce := range tx.multiNonces { + res = append(res, signing2.SignatureV2{ + PubKey: nil, + Data: nil, + Sequence: nonce, + }) + } + } + return res, nil } var ( @@ -93,11 +105,12 @@ func TestNewStatefulMempool(t *testing.T) { } type txSpec struct { - i int - h int - p int - n int - a sdk.AccAddress + i int + h int + p int + n int + a sdk.AccAddress + multi []txSpec } func (tx txSpec) String() string { @@ -209,9 +222,29 @@ func TestTxOrder(t *testing.T) { } for i, tt := range tests { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + // create fresh mempool pool := mempool.NewDefaultMempool() + + // create test txs and insert into mempool for i, ts := range tt.txs { - tx := testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} + var tx testTx + if len(ts.multi) == 0 { + tx = testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} + } else { + var nonces []uint64 + var addresses []sdk.AccAddress + for _, ms := range ts.multi { + nonces = append(nonces, uint64(ms.n)) + addresses = append(addresses, ms.a) + } + tx = testTx{ + hash: [32]byte{byte(i)}, + priority: int64(ts.p), + multiNonces: nonces, + multiAddress: addresses, + } + } + c := ctx.WithPriority(tx.priority) err := pool.Insert(c, tx) require.NoError(t, err) @@ -231,7 +264,7 @@ func TestTxOrder(t *testing.T) { } func TestRandomTxOrderManyTimes(t *testing.T) { - for i := 0; i < 100; i++ { + for i := 0; i < 10; i++ { TestRandomTxOrder(t) TestRandomGeneratedTx(t) } @@ -299,7 +332,7 @@ func TestRandomGeneratedTx(t *testing.T) { mp := mempool.NewDefaultMempool() for _, otx := range generated { - tx := testTx{otx.hash, otx.priority, otx.nonce, otx.address} + tx := testTx{hash: otx.hash, priority: otx.priority, nonce: otx.nonce, address: otx.address} c := ctx.WithPriority(tx.priority) err := mp.Insert(c, tx) require.NoError(t, err) @@ -328,7 +361,7 @@ func TestRandomTxOrder(t *testing.T) { mp := mempool.NewDefaultMempool() for _, otx := range shuffled { - tx := testTx{otx.hash, otx.priority, otx.nonce, otx.address} + tx := testTx{hash: otx.hash, priority: otx.priority, nonce: otx.nonce, address: otx.address} c := ctx.WithPriority(tx.priority) err := mp.Insert(c, tx) require.NoError(t, err) @@ -536,79 +569,3 @@ func simulateTx(ctx sdk.Context) sdk.Tx { ) return tx } - -type txWithPriority struct { - priority int64 - tx sdk.Tx - address string - nonce uint64 // duplicate from tx.address.sequence -} - -func GenTxOrder(ctx sdk.Context, nTx int, nSenders int) (ordered []txWithPriority, shuffled []txWithPriority) { - s := rand.NewSource(time.Now().UnixNano()) - r := rand.New(s) - randomAccounts := simtypes.RandomAccounts(r, nSenders) - senderNonces := make(map[string]uint64) - senderLastPriority := make(map[string]int) - for _, acc := range randomAccounts { - address := acc.Address.String() - senderNonces[address] = 1 - senderLastPriority[address] = 999999 - } - - for i := 0; i < nTx; i++ { - acc := randomAccounts[r.Intn(nSenders)] - accAddress := acc.Address.String() - accNonce := senderNonces[accAddress] - senderNonces[accAddress] += 1 - lastPriority := senderLastPriority[accAddress] - txPriority := r.Intn(lastPriority) - if txPriority == 0 { - txPriority += 1 - } - senderLastPriority[accAddress] = txPriority - tx := txWithPriority{ - priority: int64(txPriority), - tx: simulateTx2(ctx, acc, accNonce), - nonce: accNonce, - address: accAddress, - } - ordered = append(ordered, tx) - } - for _, item := range ordered { - tx := txWithPriority{ - priority: item.priority, - tx: item.tx, - nonce: item.nonce, - address: item.address, - } - shuffled = append(shuffled, tx) - } - rand.Shuffle(len(shuffled), func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] }) - return ordered, shuffled -} - -func simulateTx2(ctx sdk.Context, acc simtypes.Account, nonce uint64) sdk.Tx { - s := rand.NewSource(1) - r := rand.New(s) - txGen := moduletestutil.MakeTestEncodingConfig().TxConfig - msg := group.MsgUpdateGroupMembers{ - GroupId: 1, - Admin: acc.Address.String(), - MemberUpdates: []group.MemberRequest{}, - } - fees, _ := simtypes.RandomFees(r, ctx, sdk.NewCoins(sdk.NewCoin("coin", sdk.NewInt(100000000)))) - - tx, _ := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{&msg}, - fees, - simtestutil.DefaultGenTxGas, - ctx.ChainID(), - []uint64{authtypes.NewBaseAccountWithAddress(acc.Address).GetAccountNumber()}, - []uint64{nonce}, - acc.PrivKey, - ) - return tx -} From fc784dc263a41c519af6c9d6c7504aa65e193555 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 28 Sep 2022 13:29:28 -0400 Subject: [PATCH 045/196] this approach for multi sender is not excellent --- types/mempool/mempool.go | 78 +++++++++++++++++++++++------------ types/mempool/mempool_test.go | 16 ++++++- 2 files changed, 66 insertions(+), 28 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 002983c19c85..419fcd95a41b 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -146,39 +146,46 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T // start with the highest priority sender priorityNode := mp.priorities.Front() for priorityNode != nil { - var nextPriority int64 - nextPriorityNode := priorityNode.Next() - if nextPriorityNode != nil { - nextPriority = nextPriorityNode.Key().(txKey).priority - } else { - nextPriority = math.MinInt64 - } - - senders := priorityNode.Value.(signing.SigVerifiableTx).GetSigners() - sender := priorityNode.Key().(txKey).sender + priorityKey := priorityNode.Key().(txKey) + nextHighestPriority, nextPriorityNode := nextPriority(priorityNode) + sender := priorityKey.sender + senderTx := mp.fetchSenderCursor(senderCursors, sender) // iterate through the sender's transactions in nonce order - senderTx, ok := senderCursors[sender] - if !ok { - senderTx = mp.senders[sender].Front() - } + for senderTx != nil { + // time complexity tracking + mp.iterations++ + k := senderTx.Key().(txKey) + senders := senderTx.Value.(signing.SigVerifiableTx).GetSigners() + + // conditional skipping of multi sender txs + if len(senders) > 1 { + skip := false + for _, s := range senders { + sc, ok := senderCursors[s.String()] + if !ok { + skip = true + break + } + + // nil acts a null terminator for the sender cursor; iteration has completed, so we are + // surely beyond the nonce + if sc != nil { + otherSenderKey := sc.Key().(txKey) + if otherSenderKey.sender != sender && otherSenderKey.nonce < k.nonce { + skip = true + break + } + } + } - // if this is a multi sender tx - if len(senders) > 1 { - for _, s := range senders { - sc, ok := senderCursors[s.String()] - if !ok || sc.Key().(txKey).nonce < priorityNode.Key().(txKey).nonce { - // dependent txs for this multi sender tx have not yet been satisfied - continue + if skip { + break } } - } - for senderTx != nil { - mp.iterations++ - k := senderTx.Key().(txKey) // break if we've reached a transaction with a priority lower than the next highest priority in the pool - if k.priority < nextPriority { + if k.priority < nextHighestPriority { break } @@ -199,6 +206,25 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T return selectedTxs, nil } +func (mp *defaultMempool) fetchSenderCursor(senderCursors map[string]*huandu.Element, sender string) *huandu.Element { + senderTx, ok := senderCursors[sender] + if !ok { + senderTx = mp.senders[sender].Front() + } + return senderTx +} + +func nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { + var np int64 + nextPriorityNode := priorityNode.Next() + if nextPriorityNode != nil { + np = nextPriorityNode.Key().(txKey).priority + } else { + np = math.MinInt64 + } + return np, nextPriorityNode +} + func (mp *defaultMempool) CountTx() int { return mp.priorities.Len() } diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index ef24f7283313..8bffb1c9e050 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -161,7 +161,7 @@ func TestTxOrder(t *testing.T) { accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 5) sa := accounts[0].Address sb := accounts[1].Address - //sc := accounts[2].Address + sc := accounts[2].Address //sd := accounts[3].Address //se := accounts[4].Address @@ -219,6 +219,19 @@ func TestTxOrder(t *testing.T) { }, order: []int{3, 2, 0, 4, 1, 5, 6}, }, + { + txs: []txSpec{ + {p: 30, multi: []txSpec{{n: 2, a: sa}, {n: 1, a: sc}}}, + {p: 20, a: sb, n: 1}, + {p: 15, a: sa, n: 1}, + {p: 10, a: sa, n: 0}, + {p: 8, a: sb, n: 0}, + {p: 6, a: sa, n: 3}, + {p: 4, a: sb, n: 3}, + {p: 2, a: sc, n: 0}, + }, + order: []int{3, 2, 4, 1, 6, 7, 0, 5}, + }, } for i, tt := range tests { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { @@ -249,7 +262,6 @@ func TestTxOrder(t *testing.T) { err := pool.Insert(c, tx) require.NoError(t, err) } - require.Equal(t, len(tt.txs), pool.CountTx()) orderedTxs, err := pool.Select(ctx, nil, 1000) require.NoError(t, err) From 1758aaeb8cb0a026eef97444d5ae891429a54b1e Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 28 Sep 2022 17:23:02 -0400 Subject: [PATCH 046/196] one more test case --- types/mempool/mempool.go | 2 +- types/mempool/mempool_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 419fcd95a41b..e83ddccacfe8 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -142,7 +142,7 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T var selectedTxs []Tx var txBytes int senderCursors := make(map[string]*huandu.Element) - + // start with the highest priority sender priorityNode := mp.priorities.Front() for priorityNode != nil { diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 8bffb1c9e050..290d5fb62010 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -229,6 +229,7 @@ func TestTxOrder(t *testing.T) { {p: 6, a: sa, n: 3}, {p: 4, a: sb, n: 3}, {p: 2, a: sc, n: 0}, + {p: 7, a: sc, n: 3}, }, order: []int{3, 2, 4, 1, 6, 7, 0, 5}, }, From 0ec9335763af105485e8c6b567d74dc8118e45e9 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 28 Sep 2022 22:07:31 -0400 Subject: [PATCH 047/196] ate a bag of peanut butter m&ms, re-writing the graph.go impl --- types/mempool/graph.go | 172 ++++++++++-------------------------- types/mempool/graph_test.go | 100 +++++++-------------- types/mempool/mempool.go | 2 +- 3 files changed, 80 insertions(+), 194 deletions(-) diff --git a/types/mempool/graph.go b/types/mempool/graph.go index 8bc40c8017d3..55b6ee66308d 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -13,21 +13,17 @@ type node struct { priority int64 nonce uint64 sender string + tx Tx - tx Tx - outPriority map[string]bool - outNonce map[string]bool - inPriority map[string]bool - inNonce map[string]bool - - pElement *huandu.Element - nElement *huandu.Element + nonceNode *huandu.Element + priorityNode *huandu.Element + in map[string]bool } type graph struct { - priorities *huandu.SkipList - nodes map[string]*node - senderNodes map[string]*huandu.SkipList + priorities *huandu.SkipList + nodes map[string]*node + senderGraphs map[string]*huandu.SkipList } func (g *graph) Insert(context sdk.Context, tx Tx) error { @@ -48,6 +44,32 @@ func (g *graph) Insert(context sdk.Context, tx Tx) error { return nil } +func (g *graph) AddNode(n *node) { + g.nodes[n.key()] = n + + n.priorityNode = g.priorities.Set(txKey{priority: n.priority, sender: n.sender, nonce: n.nonce}, n) + sgs, ok := g.senderGraphs[n.sender] + if !ok { + sgs = huandu.New(huandu.Uint64) + g.senderGraphs[n.sender] = sgs + } + + n.nonceNode = sgs.Set(n.nonce, n) +} + +func (g *graph) drawPriorityEdges(n *node) (edges []*node) { + pnode := n.nonceNode.Prev().Value.(*node).priorityNode + for pnode != nil { + node := pnode.Value.(*node) + if node.sender != n.sender { + edges = append(edges, node) + } + + pnode = pnode.Prev() + } + return edges +} + func (g *graph) Select(ctx sdk.Context, txs [][]byte, maxBytes int) ([]Tx, error) { // todo collapse multiple iterations into kahns sorted, err := g.TopologicalSort() @@ -79,63 +101,14 @@ func (n node) String() string { return n.key() } -func (g *graph) AddEdge(from node, to node) { - // TODO transition in* to a count? only used in finding the start node - // or some other method for finding the top most node - if from.sender == to.sender { - from.outNonce[to.key()] = true - to.inNonce[from.key()] = true - } else { - from.outPriority[to.key()] = true - to.inPriority[from.key()] = true - } -} - -type nodePriorityKey struct { - priority int64 - sender string - nonce uint64 -} - -func nodePriorityKeyLess(a, b interface{}) int { - keyA := a.(nodePriorityKey) - keyB := b.(nodePriorityKey) - res := huandu.Int64.Compare(keyA.priority, keyB.priority) - if res != 0 { - return res - } - - res = huandu.Uint64.Compare(keyA.nonce, keyB.nonce) - if res != 0 { - return res - } - - return huandu.String.Compare(keyA.sender, keyB.sender) -} - func NewGraph() *graph { return &graph{ - nodes: make(map[string]*node), - priorities: huandu.New(huandu.GreaterThanFunc(nodePriorityKeyLess)), - senderNodes: make(map[string]*huandu.SkipList), + nodes: make(map[string]*node), + priorities: huandu.New(huandu.GreaterThanFunc(txKeyLess)), + senderGraphs: make(map[string]*huandu.SkipList), } } -func (g *graph) AddNode(n *node) { - g.nodes[n.key()] = n - - pnode := g.priorities.Set(nodePriorityKey{priority: n.priority, sender: n.sender, nonce: n.nonce}, n) - sgs, ok := g.senderNodes[n.sender] - if !ok { - sgs = huandu.New(huandu.Uint64) - g.senderNodes[n.sender] = sgs - } - - nnode := sgs.Set(n.nonce, n) - n.pElement = pnode - n.nElement = nnode -} - func (g *graph) ContainsNode(n node) bool { _, ok := g.nodes[n.key()] return ok @@ -143,7 +116,7 @@ func (g *graph) ContainsNode(n node) bool { func (g *graph) TopologicalSort() ([]*node, error) { maxPriority := g.priorities.Back().Value.(*node) - start := g.senderNodes[maxPriority.sender].Front().Value.(*node) + start := g.senderGraphs[maxPriority.sender].Front().Value.(*node) edgeless := []*node{start} sorted, err := g.kahns(edgeless) if err != nil { @@ -201,73 +174,18 @@ func (g *graph) kahns(edgeless []*node) ([]*node, error) { else return L (a topologically sorted order) */ - - // priority edge rules: - // - a node has an incoming priority edge if the next priority node in ascending order has p > this.p AND a different sender (in another tree) - // OR - // - a node has an incoming priority edge if n.priority < latest L_n.priority. - // - a node has an outgoing priority edge if the next priority node in descending order has p < this.p AND a different sender (in another tree) - - priorityCursor := edgeless[0].pElement + var n *node + //n := edgeless[0] for i := 0; i < len(edgeless) && edgeless[i] != nil; i++ { - n := edgeless[i] - //nextPriority = n.pElement.Next().Value.(*node).priority - sorted = append(sorted, n) - if n.priority == priorityCursor.Value.(*node).priority { - priorityCursor = n.pElement - } - - // nonce edge - nextNonceNode := n.nElement.Next() - if nextNonceNode != nil { - m := nextNonceNode.Value.(*node) - nonceEdge := nodeEdge(n, m) - visited[nonceEdge] = true - if !hasIncomingEdges(m, priorityCursor, visited) { - edgeless = append(edgeless, m) - } - } - - // priority edge - nextPriorityNode := n.pElement.Prev() - if nextPriorityNode != nil { - m := nextPriorityNode.Value.(*node) - if m.sender != n.sender && - // no edge where priority is equal - m.priority < n.priority { - fmt.Println(nodeEdge(n, m)) - visited[nodeEdge(n, m)] = true - if !hasIncomingEdges(m, priorityCursor, visited) { - edgeless = append(edgeless, m) - } + // enumerate the priority list drawing priority edges along the way + pnodes := g.drawPriorityEdges(n.priorityNode.Value.(*node)) + for _, m := range pnodes { + edge := nodeEdge(n, m) + if !visited[edge] { + visited[edge] = true } } } return sorted, nil } - -func hasIncomingEdges(n *node, pcursor *huandu.Element, visited map[string]bool) bool { - prevNonceNode := n.nElement.Prev() - if prevNonceNode != nil { - m := prevNonceNode.Value.(*node) - // if edge has not been visited return true - incoming := !visited[nodeEdge(m, n)] - if incoming { - return true - } - } - - // priority edge - if pcursor != nil { - m := pcursor.Prev().Value.(*node) - if m.sender != n.sender && - // no edge where priority is equal - m.priority > n.priority { - incoming := !visited[nodeEdge(m, n)] - return incoming - } - } - - return false -} diff --git a/types/mempool/graph_test.go b/types/mempool/graph_test.go index fd98ea8b423a..813bb4638b15 100644 --- a/types/mempool/graph_test.go +++ b/types/mempool/graph_test.go @@ -1,81 +1,49 @@ -package mempool +package mempool_test import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "math/rand" "testing" ) -func initGraph() *graph { - return NewGraph() -} - -func initNodes(ns []*node) []node { - var nodes []node - // TODO what this API look like? - for _, n := range ns { - n.inNonce = make(map[string]bool) - n.inPriority = make(map[string]bool) - n.outNonce = make(map[string]bool) - n.outPriority = make(map[string]bool) - nodes = append(nodes, *n) - } - - return nodes -} - -func TestPoolCase(t *testing.T) { - ns := []*node{ - {priority: 21, nonce: 4, sender: "a"}, // tx0 - {priority: 6, nonce: 3, sender: "a"}, // tx1 - {priority: 8, nonce: 2, sender: "a"}, // tx2 - {priority: 15, nonce: 1, sender: "b"}, // tx3 - {priority: 20, nonce: 1, sender: "a"}, // tx4 - {priority: 7, nonce: 2, sender: "b"}, // tx5 - } +func TestDrawEdges(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 2) + sa := accounts[0].Address + sb := accounts[1].Address - nodes := initNodes(ns) tests := []struct { - name string - limit int - edges [][]int - expected []int + txs []txSpec + order []int + fail bool }{ - {"case 1", 5, - [][]int{{4, 2}, {4, 3}, {3, 2}, {2, 1}, {1, 0}}, - []int{4, 3, 2, 1, 0}, - }, { - "case 2", 6, - [][]int{{4, 2}, {4, 3}, {3, 2}, {2, 1}, {1, 0}, {4, 5}, {2, 5}, {5, 1}}, - []int{4, 3, 2, 5, 1, 0}, + { + txs: []txSpec{ + {p: 21, n: 4, a: sa}, + {p: 8, n: 3, a: sa}, + {p: 6, n: 2, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 20, n: 1, a: sa}, + }, + order: []int{4, 3, 2, 1, 0}, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - graph := initGraph() - for i := 0; i < tt.limit; i++ { - //graph.AddNode(nodes[i]) - } - for _, e := range tt.edges { - graph.AddEdge(nodes[e[0]], nodes[e[1]]) - } - - results, err := graph.TopologicalSort() - - if err != nil { - t.Error(err) - return - } - if len(results) != len(tt.expected) { - t.Errorf("Wrong number of results: %v", results) - return - } - - for i := 0; i < len(tt.expected); i++ { - if results[i].key() != nodes[tt.expected[i]].key() { - t.Errorf("Wrong sort order: %v", results) - break - } + for i, tt := range tests { + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + graph := mempool.NewGraph() + for _, ts := range tt.txs { + tx := testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} + c := ctx.WithPriority(int64(ts.p)) + require.NoError(t, graph.Insert(c, tx)) } }) } + } diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index e83ddccacfe8..419fcd95a41b 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -142,7 +142,7 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T var selectedTxs []Tx var txBytes int senderCursors := make(map[string]*huandu.Element) - + // start with the highest priority sender priorityNode := mp.priorities.Front() for priorityNode != nil { From 1520b5db1e8e62d9799d01c08b8f7e4841ed4373 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 29 Sep 2022 11:33:40 -0500 Subject: [PATCH 048/196] rewrite graph --- types/mempool/graph.go | 4 ++-- types/mempool/graph_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/types/mempool/graph.go b/types/mempool/graph.go index 55b6ee66308d..e634691e4c10 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -57,7 +57,7 @@ func (g *graph) AddNode(n *node) { n.nonceNode = sgs.Set(n.nonce, n) } -func (g *graph) drawPriorityEdges(n *node) (edges []*node) { +func (g *graph) DrawPriorityEdges(n *node) (edges []*node) { pnode := n.nonceNode.Prev().Value.(*node).priorityNode for pnode != nil { node := pnode.Value.(*node) @@ -178,7 +178,7 @@ func (g *graph) kahns(edgeless []*node) ([]*node, error) { //n := edgeless[0] for i := 0; i < len(edgeless) && edgeless[i] != nil; i++ { // enumerate the priority list drawing priority edges along the way - pnodes := g.drawPriorityEdges(n.priorityNode.Value.(*node)) + pnodes := g.DrawPriorityEdges(n.priorityNode.Value.(*node)) for _, m := range pnodes { edge := nodeEdge(n, m) if !visited[edge] { diff --git a/types/mempool/graph_test.go b/types/mempool/graph_test.go index 813bb4638b15..0a139885f96d 100644 --- a/types/mempool/graph_test.go +++ b/types/mempool/graph_test.go @@ -1,9 +1,8 @@ -package mempool_test +package mempool import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/mempool" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" @@ -42,6 +41,7 @@ func TestDrawEdges(t *testing.T) { tx := testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} c := ctx.WithPriority(int64(ts.p)) require.NoError(t, graph.Insert(c, tx)) + } }) } From fa7cc9aa266237dd4cf0ef830bb61dee923500af Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 29 Sep 2022 16:48:40 -0500 Subject: [PATCH 049/196] add an interesting case which breaks current mempool impl --- types/mempool/graph_test.go | 13 ++++++++----- types/mempool/mempool_test.go | 33 ++++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/types/mempool/graph_test.go b/types/mempool/graph_test.go index 0a139885f96d..a79cc72638ab 100644 --- a/types/mempool/graph_test.go +++ b/types/mempool/graph_test.go @@ -1,14 +1,17 @@ -package mempool +package mempool_test import ( "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "math/rand" + "testing" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - "math/rand" - "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) func TestDrawEdges(t *testing.T) { diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 290d5fb62010..5f50321cd5c3 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -2,14 +2,15 @@ package mempool_test import ( "fmt" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" - "github.com/cosmos/cosmos-sdk/x/auth/signing" "math" "math/rand" "testing" "time" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -180,6 +181,18 @@ func TestTxOrder(t *testing.T) { }, order: []int{4, 3, 2, 1, 0}, }, + { + // a very interesting breaking case + txs: []txSpec{ + {p: 3, n: 0, a: sa}, + {p: 5, n: 1, a: sa}, + {p: 9, n: 2, a: sa}, + {p: 6, n: 0, a: sb}, + {p: 5, n: 1, a: sb}, + {p: 8, n: 2, a: sb}, + }, + order: []int{3, 4, 0, 1, 2, 5}, + }, { txs: []txSpec{ {p: 21, n: 4, a: sa}, @@ -221,7 +234,9 @@ func TestTxOrder(t *testing.T) { }, { txs: []txSpec{ - {p: 30, multi: []txSpec{{n: 2, a: sa}, {n: 1, a: sc}}}, + {p: 30, multi: []txSpec{ + {n: 2, a: sa}, + {n: 1, a: sc}}}, {p: 20, a: sb, n: 1}, {p: 15, a: sa, n: 1}, {p: 10, a: sa, n: 0}, @@ -231,7 +246,7 @@ func TestTxOrder(t *testing.T) { {p: 2, a: sc, n: 0}, {p: 7, a: sc, n: 3}, }, - order: []int{3, 2, 4, 1, 6, 7, 0, 5}, + order: []int{3, 2, 4, 1, 6, 7, 0, 5, 8}, }, } for i, tt := range tests { @@ -277,7 +292,7 @@ func TestTxOrder(t *testing.T) { } func TestRandomTxOrderManyTimes(t *testing.T) { - for i := 0; i < 10; i++ { + for i := 0; i < 30; i++ { TestRandomTxOrder(t) TestRandomGeneratedTx(t) } @@ -354,9 +369,13 @@ func TestRandomGeneratedTx(t *testing.T) { selected, err := mp.Select(ctx, nil, 100000) require.Equal(t, len(generated), len(selected)) require.NoError(t, err) + + start := time.Now() require.NoError(t, validateOrder(selected)) + duration := time.Since(start) - fmt.Printf("seed: %d completed in %d iterations\n", seed, mempool.Iterations(mp)) + fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", + seed, mempool.Iterations(mp), duration.Milliseconds()) } func TestRandomTxOrder(t *testing.T) { From c4495fdd6fd015a5ac3f29976445afe21feca1f5 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 30 Sep 2022 09:09:48 -0500 Subject: [PATCH 050/196] all the edges --- types/mempool/graph.go | 111 ++++++++++++++++++++++++++-------- types/mempool/mempool_test.go | 3 +- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/types/mempool/graph.go b/types/mempool/graph.go index e634691e4c10..114309c3ad7e 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -2,9 +2,11 @@ package mempool import ( "fmt" + + huandu "github.com/huandu/skiplist" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/signing" - huandu "github.com/huandu/skiplist" ) var _ Mempool = (*graph)(nil) @@ -20,6 +22,27 @@ type node struct { in map[string]bool } +type senderGraph struct { + byNonce *huandu.SkipList + byPriority *huandu.SkipList +} + +func (g *senderGraph) canDrawEdge(priority int64, nonce uint64) bool { + // optimization: if n is _sufficiently_ small we can just iterate the nonce list + // otherwise we use the skip list by priority + // + min := g.byPriority.Front() + for min != nil { + n := min.Value.(*node) + if n.priority > priority { + return true + } + if n.priority < priority && n.nonce < nonce { + break + } + } +} + type graph struct { priorities *huandu.SkipList nodes map[string]*node @@ -57,19 +80,6 @@ func (g *graph) AddNode(n *node) { n.nonceNode = sgs.Set(n.nonce, n) } -func (g *graph) DrawPriorityEdges(n *node) (edges []*node) { - pnode := n.nonceNode.Prev().Value.(*node).priorityNode - for pnode != nil { - node := pnode.Value.(*node) - if node.sender != n.sender { - edges = append(edges, node) - } - - pnode = pnode.Prev() - } - return edges -} - func (g *graph) Select(ctx sdk.Context, txs [][]byte, maxBytes int) ([]Tx, error) { // todo collapse multiple iterations into kahns sorted, err := g.TopologicalSort() @@ -104,7 +114,7 @@ func (n node) String() string { func NewGraph() *graph { return &graph{ nodes: make(map[string]*node), - priorities: huandu.New(huandu.GreaterThanFunc(txKeyLess)), + priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), senderGraphs: make(map[string]*huandu.SkipList), } } @@ -115,7 +125,7 @@ func (g *graph) ContainsNode(n node) bool { } func (g *graph) TopologicalSort() ([]*node, error) { - maxPriority := g.priorities.Back().Value.(*node) + maxPriority := g.priorities.Front().Value.(*node) start := g.senderGraphs[maxPriority.sender].Front().Value.(*node) edgeless := []*node{start} sorted, err := g.kahns(edgeless) @@ -153,9 +163,56 @@ function visit(node n) add n to head of L */ + +// DrawPriorityEdges is O(n^2) hopefully n is not too large +// Given n_a, need an answer the question: +// "Is there node n_b in my sender tree with n_b.nonce < n_a.nonce AND n_b.priority < n_a.priority?" +// If yes, don't draw any priority edges to nodes (or possibly just to nodes with a priority < n_b.priority) +// +func (g *graph) DrawPriorityEdges() (in map[string]map[string]bool, out map[string]map[string]bool) { + pn := g.priorities.Front() + in = make(map[string]map[string]bool) + out = make(map[string]map[string]bool) + + for pn != nil { + n := pn.Value.(*node) + nk := n.key() + out[nk] = make(map[string]bool) + if n.nonceNode.Next() != nil { + m := n.nonceNode.Next().Value.(*node) + mk := m.key() + if _, ok := in[mk]; !ok { + in[mk] = make(map[string]bool) + } + + out[nk][mk] = true + in[mk][nk] = true + } + + pm := pn.Next() + for pm != nil { + m := pm.Value.(*node) + mk := m.key() + if _, ok := in[mk]; !ok { + in[mk] = make(map[string]bool) + } + + if n.sender != m.sender { + out[nk][mk] = true + in[mk][nk] = true + } + + pm = pm.Next() + } + pn = pn.Next() + } + + return in, out +} + func (g *graph) kahns(edgeless []*node) ([]*node, error) { var sorted []*node - visited := make(map[string]bool) + inEdges, outEdges := g.DrawPriorityEdges() /* L ← Empty list that will contain the sorted elements @@ -174,17 +231,21 @@ func (g *graph) kahns(edgeless []*node) ([]*node, error) { else return L (a topologically sorted order) */ - var n *node - //n := edgeless[0] + for i := 0; i < len(edgeless) && edgeless[i] != nil; i++ { - // enumerate the priority list drawing priority edges along the way - pnodes := g.DrawPriorityEdges(n.priorityNode.Value.(*node)) - for _, m := range pnodes { - edge := nodeEdge(n, m) - if !visited[edge] { - visited[edge] = true + n := edgeless[i] + nk := n.key() + sorted = append(sorted, n) + + for mk, _ := range outEdges[nk] { + delete(outEdges[nk], mk) + delete(inEdges[mk], nk) + + if len(inEdges[mk]) == 0 { + edgeless = append(edgeless, g.nodes[mk]) } } + } return sorted, nil diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 5f50321cd5c3..9f8b160f3a90 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -252,7 +252,8 @@ func TestTxOrder(t *testing.T) { for i, tt := range tests { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { // create fresh mempool - pool := mempool.NewDefaultMempool() + //pool := mempool.NewDefaultMempool() + pool := mempool.NewGraph() // create test txs and insert into mempool for i, ts := range tt.txs { From f4ff6c6c36744582fb2de9666334019b6829c469 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 30 Sep 2022 13:40:35 -0500 Subject: [PATCH 051/196] work --- types/mempool/graph.go | 68 +++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/types/mempool/graph.go b/types/mempool/graph.go index 114309c3ad7e..cfae1ee2d966 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -27,26 +27,37 @@ type senderGraph struct { byPriority *huandu.SkipList } -func (g *senderGraph) canDrawEdge(priority int64, nonce uint64) bool { +// MaxPriorityEdge returns the maximum out of tree priority which the node with +// the given priority and nonce can draw edges to by finding the minimum priority +// in this tree with a lower nonce +func (sg senderGraph) MaxPriorityEdge(priority int64, nonce uint64) int64 { // optimization: if n is _sufficiently_ small we can just iterate the nonce list // otherwise we use the skip list by priority // - min := g.byPriority.Front() + min := sg.byPriority.Front() for min != nil { n := min.Value.(*node) - if n.priority > priority { - return true - } if n.priority < priority && n.nonce < nonce { - break + // the minimum priority in the tree with a lower nonce + return n.priority } + min = min.Next() + } + // otherwise we can draw to anything + return priority - 1 +} + +func newSenderGraph() senderGraph { + return senderGraph{ + byNonce: huandu.New(huandu.Uint64), + byPriority: huandu.New(huandu.Int64), } } type graph struct { priorities *huandu.SkipList nodes map[string]*node - senderGraphs map[string]*huandu.SkipList + senderGraphs map[string]senderGraph } func (g *graph) Insert(context sdk.Context, tx Tx) error { @@ -71,13 +82,14 @@ func (g *graph) AddNode(n *node) { g.nodes[n.key()] = n n.priorityNode = g.priorities.Set(txKey{priority: n.priority, sender: n.sender, nonce: n.nonce}, n) - sgs, ok := g.senderGraphs[n.sender] + sg, ok := g.senderGraphs[n.sender] if !ok { - sgs = huandu.New(huandu.Uint64) - g.senderGraphs[n.sender] = sgs + sg = newSenderGraph() + g.senderGraphs[n.sender] = sg } - n.nonceNode = sgs.Set(n.nonce, n) + n.nonceNode = sg.byNonce.Set(n.nonce, n) + sg.byPriority.Set(n.priority, n) } func (g *graph) Select(ctx sdk.Context, txs [][]byte, maxBytes int) ([]Tx, error) { @@ -115,7 +127,7 @@ func NewGraph() *graph { return &graph{ nodes: make(map[string]*node), priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), - senderGraphs: make(map[string]*huandu.SkipList), + senderGraphs: make(map[string]senderGraph), } } @@ -125,10 +137,14 @@ func (g *graph) ContainsNode(n node) bool { } func (g *graph) TopologicalSort() ([]*node, error) { - maxPriority := g.priorities.Front().Value.(*node) - start := g.senderGraphs[maxPriority.sender].Front().Value.(*node) - edgeless := []*node{start} - sorted, err := g.kahns(edgeless) + in, out := g.DrawPriorityEdges() + var edgeless []*node + for _, n := range g.nodes { + if _, ok := in[n.key()]; !ok { + edgeless = append(edgeless, n) + } + } + sorted, err := g.kahns(edgeless, in, out) if err != nil { return nil, err } @@ -164,20 +180,24 @@ function visit(node n) */ +type nodeEdges map[string]map[string]bool + // DrawPriorityEdges is O(n^2) hopefully n is not too large // Given n_a, need an answer the question: // "Is there node n_b in my sender tree with n_b.nonce < n_a.nonce AND n_b.priority < n_a.priority?" // If yes, don't draw any priority edges to nodes (or possibly just to nodes with a priority < n_b.priority) // -func (g *graph) DrawPriorityEdges() (in map[string]map[string]bool, out map[string]map[string]bool) { +func (g *graph) DrawPriorityEdges() (in nodeEdges, out nodeEdges) { pn := g.priorities.Front() - in = make(map[string]map[string]bool) - out = make(map[string]map[string]bool) + in = make(nodeEdges) + out = make(nodeEdges) for pn != nil { n := pn.Value.(*node) nk := n.key() out[nk] = make(map[string]bool) + + // draw nonce edge (if there is one) if n.nonceNode.Next() != nil { m := n.nonceNode.Next().Value.(*node) mk := m.key() @@ -189,9 +209,16 @@ func (g *graph) DrawPriorityEdges() (in map[string]map[string]bool, out map[stri in[mk][nk] = true } + // beginning with the next lowest priority node, draw priority edges + maxp := g.senderGraphs[n.sender].MaxPriorityEdge(n.priority, n.nonce) pm := pn.Next() for pm != nil { m := pm.Value.(*node) + // skip these nodes + if m.priority > maxp { + pm = pm.Next() + continue + } mk := m.key() if _, ok := in[mk]; !ok { in[mk] = make(map[string]bool) @@ -210,9 +237,8 @@ func (g *graph) DrawPriorityEdges() (in map[string]map[string]bool, out map[stri return in, out } -func (g *graph) kahns(edgeless []*node) ([]*node, error) { +func (g *graph) kahns(edgeless []*node, inEdges nodeEdges, outEdges nodeEdges) ([]*node, error) { var sorted []*node - inEdges, outEdges := g.DrawPriorityEdges() /* L ← Empty list that will contain the sorted elements From 82f701c8afb6f68bc2e9a6fb4abebf2cc769caa8 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 2 Oct 2022 16:47:09 -0500 Subject: [PATCH 052/196] go mod tidy --- go.mod | 3 +-- go.sum | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index be2e9ab370d4..b554ed418b1e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( cosmossdk.io/errors v1.0.0-beta.7 cosmossdk.io/math v1.0.0-beta.3 github.com/99designs/keyring v1.2.1 - github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 github.com/armon/go-metrics v0.4.1 github.com/bgentry/speakeasy v0.1.0 github.com/btcsuite/btcd v0.22.1 @@ -28,7 +27,6 @@ require ( github.com/gogo/gateway v1.1.0 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.2 - github.com/google/btree v1.0.1 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 @@ -101,6 +99,7 @@ require ( github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.0.1 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/googleapis/gax-go/v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index b7cc28f11130..96ce39b4d5c1 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,6 @@ github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQ github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 h1:1yw6O62BReQ+uA1oyk9XaQTvLhcoHWmoQAgXmDFXpIY= -github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145/go.mod h1:877WBceefKn14QwVVn4xRFUsHsZb9clICgdeTj4XsUg= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= From 16100f98de811c5d25f753a68321fb54d60f4819 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Sun, 2 Oct 2022 20:47:40 -0500 Subject: [PATCH 053/196] fix graph initial conditions finding --- types/mempool/graph.go | 4 ++-- types/mempool/mempool_test.go | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/types/mempool/graph.go b/types/mempool/graph.go index cfae1ee2d966..c8220708e8be 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -140,7 +140,8 @@ func (g *graph) TopologicalSort() ([]*node, error) { in, out := g.DrawPriorityEdges() var edgeless []*node for _, n := range g.nodes { - if _, ok := in[n.key()]; !ok { + nk := n.key() + if _, ok := in[nk]; !ok || len(in[nk]) == 0 { edgeless = append(edgeless, n) } } @@ -186,7 +187,6 @@ type nodeEdges map[string]map[string]bool // Given n_a, need an answer the question: // "Is there node n_b in my sender tree with n_b.nonce < n_a.nonce AND n_b.priority < n_a.priority?" // If yes, don't draw any priority edges to nodes (or possibly just to nodes with a priority < n_b.priority) -// func (g *graph) DrawPriorityEdges() (in nodeEdges, out nodeEdges) { pn := g.priorities.Front() in = make(nodeEdges) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 9f8b160f3a90..d5b953c8cb2a 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -182,7 +182,6 @@ func TestTxOrder(t *testing.T) { order: []int{4, 3, 2, 1, 0}, }, { - // a very interesting breaking case txs: []txSpec{ {p: 3, n: 0, a: sa}, {p: 5, n: 1, a: sa}, @@ -191,7 +190,7 @@ func TestTxOrder(t *testing.T) { {p: 5, n: 1, a: sb}, {p: 8, n: 2, a: sb}, }, - order: []int{3, 4, 0, 1, 2, 5}, + order: []int{3, 4, 5, 0, 1, 2}, }, { txs: []txSpec{ @@ -252,6 +251,8 @@ func TestTxOrder(t *testing.T) { for i, tt := range tests { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { // create fresh mempool + // TODO test both? + //pool := mempool.NewDefaultMempool() pool := mempool.NewGraph() From 582e9143f3175143f97bfd965439e735be907437 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 3 Oct 2022 11:36:35 -0500 Subject: [PATCH 054/196] refactoring tests --- types/mempool/graph.go | 9 ++++- types/mempool/mempool.go | 11 +++++- types/mempool/mempool_test.go | 72 +++++++++++++++++++++++------------ 3 files changed, 64 insertions(+), 28 deletions(-) diff --git a/types/mempool/graph.go b/types/mempool/graph.go index c8220708e8be..f7ec586f45c6 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -58,6 +58,7 @@ type graph struct { priorities *huandu.SkipList nodes map[string]*node senderGraphs map[string]senderGraph + iterations int } func (g *graph) Insert(context sdk.Context, tx Tx) error { @@ -106,8 +107,7 @@ func (g *graph) Select(ctx sdk.Context, txs [][]byte, maxBytes int) ([]Tx, error } func (g *graph) CountTx() int { - //TODO implement me - panic("implement me") + return len(g.nodes) } func (g *graph) Remove(context sdk.Context, tx Tx) error { @@ -140,6 +140,7 @@ func (g *graph) TopologicalSort() ([]*node, error) { in, out := g.DrawPriorityEdges() var edgeless []*node for _, n := range g.nodes { + g.iterations++ nk := n.key() if _, ok := in[nk]; !ok || len(in[nk]) == 0 { edgeless = append(edgeless, n) @@ -193,6 +194,7 @@ func (g *graph) DrawPriorityEdges() (in nodeEdges, out nodeEdges) { out = make(nodeEdges) for pn != nil { + g.iterations++ n := pn.Value.(*node) nk := n.key() out[nk] = make(map[string]bool) @@ -213,6 +215,7 @@ func (g *graph) DrawPriorityEdges() (in nodeEdges, out nodeEdges) { maxp := g.senderGraphs[n.sender].MaxPriorityEdge(n.priority, n.nonce) pm := pn.Next() for pm != nil { + g.iterations++ m := pm.Value.(*node) // skip these nodes if m.priority > maxp { @@ -264,6 +267,8 @@ func (g *graph) kahns(edgeless []*node, inEdges nodeEdges, outEdges nodeEdges) ( sorted = append(sorted, n) for mk, _ := range outEdges[nk] { + g.iterations++ + delete(outEdges[nk], mk) delete(inEdges[mk], nk) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 419fcd95a41b..c19161268a8d 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -3,9 +3,10 @@ package mempool import ( "bytes" "fmt" - "github.com/cosmos/cosmos-sdk/types" "math" + "github.com/cosmos/cosmos-sdk/types" + huandu "github.com/huandu/skiplist" "github.com/cosmos/cosmos-sdk/x/auth/signing" @@ -267,7 +268,13 @@ func DebugPrintKeys(mempool Mempool) { } func Iterations(mempool Mempool) int { - return mempool.(*defaultMempool).iterations + switch v := mempool.(type) { + case *defaultMempool: + return v.iterations + case *graph: + return v.iterations + } + panic("unknown mempool type") } // The complexity is O(log(N)). Implementation diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index d5b953c8cb2a..6115be0c9340 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/suite" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" @@ -293,10 +295,37 @@ func TestTxOrder(t *testing.T) { } } -func TestRandomTxOrderManyTimes(t *testing.T) { +type MempoolTestSuite struct { + suite.Suite + numTxs int + numAccounts int + mempool mempool.Mempool +} + +func (s *MempoolTestSuite) resetMempool() { + s.mempool = mempool.NewDefaultMempool() +} + +func (s *MempoolTestSuite) SetupTest() { + s.numTxs = 1000 + s.numAccounts = 100 + s.resetMempool() +} + +func TestMempoolTestSuite(t *testing.T) { + suite.Run(t, new(MempoolTestSuite)) +} + +func (s *MempoolTestSuite) TestRandomTxOrderManyTimes() { for i := 0; i < 30; i++ { - TestRandomTxOrder(t) - TestRandomGeneratedTx(t) + s.Run("TestRandomGeneratedTxs", func() { + s.TestRandomGeneratedTxs() + }) + s.resetMempool() + s.Run("TestRandomWalkTxs", func() { + s.TestRandomWalkTxs() + }) + s.resetMempool() } } @@ -352,14 +381,13 @@ func validateOrder(mtxs []mempool.Tx) error { return nil } -func TestRandomGeneratedTx(t *testing.T) { +func (s *MempoolTestSuite) TestRandomGeneratedTxs() { + t := s.T() ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - numTx := 1000 - numAccounts := 10 seed := time.Now().UnixNano() - generated := genRandomTxs(seed, numTx, numAccounts) - mp := mempool.NewDefaultMempool() + generated := genRandomTxs(seed, s.numTxs, s.numAccounts) + mp := s.mempool for _, otx := range generated { tx := testTx{hash: otx.hash, priority: otx.priority, nonce: otx.nonce, address: otx.address} @@ -380,10 +408,9 @@ func TestRandomGeneratedTx(t *testing.T) { seed, mempool.Iterations(mp), duration.Milliseconds()) } -func TestRandomTxOrder(t *testing.T) { +func (s *MempoolTestSuite) TestRandomWalkTxs() { + t := s.T() ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - numTx := 1000 - numAccounts := 10 seed := time.Now().UnixNano() // interesting failing seeds: @@ -391,8 +418,8 @@ func TestRandomTxOrder(t *testing.T) { // seed := int64(1663989445512438000) // - ordered, shuffled := genOrderedTxs(seed, numTx, numAccounts) - mp := mempool.NewDefaultMempool() + ordered, shuffled := genOrderedTxs(seed, s.numTxs, s.numAccounts) + mp := s.mempool for _, otx := range shuffled { tx := testTx{hash: otx.hash, priority: otx.priority, nonce: otx.nonce, address: otx.address} @@ -401,12 +428,13 @@ func TestRandomTxOrder(t *testing.T) { require.NoError(t, err) } - require.Equal(t, numTx, mp.CountTx()) + require.Equal(t, s.numTxs, mp.CountTx()) selected, err := mp.Select(ctx, nil, math.MaxInt) + require.Equal(t, len(ordered), len(selected)) var orderedStr, selectedStr string - for i := 0; i < numTx; i++ { + for i := 0; i < s.numTxs; i++ { otx := ordered[i] stx := selected[i].(testTx) orderedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", @@ -416,13 +444,15 @@ func TestRandomTxOrder(t *testing.T) { } require.NoError(t, err) - require.Equal(t, numTx, len(selected)) + require.Equal(t, s.numTxs, len(selected)) errMsg := fmt.Sprintf("Expected order: %v\nGot order: %v\nSeed: %v", orderedStr, selectedStr, seed) //mempool.DebugPrintKeys(mp) + start := time.Now() require.NoError(t, validateOrder(selected), errMsg) + duration := time.Since(start) /*for i, tx := range selected { msg := fmt.Sprintf("Failed tx at index %d\n%s", i, errMsg) @@ -432,14 +462,8 @@ func TestRandomTxOrder(t *testing.T) { require.Equal(t, tx.(testTx).address, ordered[i].address, msg) }*/ - fmt.Printf("seed: %d completed in %d iterations\n", seed, mempool.Iterations(mp)) -} - -type txKey struct { - sender string - nonce uint64 - priority int64 - hash [32]byte + fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", + seed, mempool.Iterations(mp), duration.Milliseconds()) } func genRandomTxs(seed int64, countTx int, countAccount int) (res []testTx) { From a8d58dd702c9d2754e98004951b48bfe3ba01c88 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 3 Oct 2022 15:02:37 -0500 Subject: [PATCH 055/196] derive sender from pubkey in signer_infos --- types/mempool/mempool.go | 74 +++++++++++------------------------ types/mempool/mempool_test.go | 71 ++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 54 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index c19161268a8d..6c799f0c3abd 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -102,40 +102,37 @@ func NewDefaultMempool() Mempool { } func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { - senders := tx.(signing.SigVerifiableTx).GetSigners() - nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + if err != nil { + return err + } + + // TODO better selection criteria here + sig := sigs[0] + sender := sig.PubKey.Address().String() + nonce := sig.Sequence hashableTx, ok := tx.(HashableTx) if !ok { return ErrNoTxHash } - if err != nil { - return err - } else if len(senders) != len(nonces) { - return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", - len(senders), len(nonces)) - } + tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender, hash: hashableTx.GetHash()} - for i, senderAddr := range senders { - sender := senderAddr.String() - nonce := nonces[i].Sequence - tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender, hash: hashableTx.GetHash()} + senderTxs, ok := mp.senders[sender] + // initialize sender mempool if not found + if !ok { + senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { + return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) + })) + mp.senders[sender] = senderTxs + } - senderTxs, ok := mp.senders[sender] - // initialize sender mempool if not found - if !ok { - senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { - return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) - })) - mp.senders[sender] = senderTxs - } + // if a tx with the same nonce exists, replace it and delete from the priority list + senderTxs.Set(tk, tx) + mp.scores[txKey{nonce: nonce, sender: sender}] = ctx.Priority() + mp.priorities.Set(tk, tx) - // if a tx with the same nonce exists, replace it and delete from the priority list - senderTxs.Set(tk, tx) - mp.scores[txKey{nonce: nonce, sender: sender}] = ctx.Priority() - mp.priorities.Set(tk, tx) - } return nil } @@ -157,33 +154,6 @@ func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]T // time complexity tracking mp.iterations++ k := senderTx.Key().(txKey) - senders := senderTx.Value.(signing.SigVerifiableTx).GetSigners() - - // conditional skipping of multi sender txs - if len(senders) > 1 { - skip := false - for _, s := range senders { - sc, ok := senderCursors[s.String()] - if !ok { - skip = true - break - } - - // nil acts a null terminator for the sender cursor; iteration has completed, so we are - // surely beyond the nonce - if sc != nil { - otherSenderKey := sc.Key().(txKey) - if otherSenderKey.sender != sender && otherSenderKey.nonce < k.nonce { - skip = true - break - } - } - } - - if skip { - break - } - } // break if we've reached a transaction with a priority lower than the next highest priority in the pool if k.priority < nextHighestPriority { diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 6115be0c9340..eeb183fca6a5 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -12,6 +12,7 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" @@ -26,6 +27,51 @@ import ( "github.com/cosmos/cosmos-sdk/x/group" ) +// testPubKey is a dummy implementation of PubKey used for testing. +type testPubKey struct { + address sdk.AccAddress +} + +func (t testPubKey) Reset() { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) String() string { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) ProtoMessage() { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) Address() cryptotypes.Address { + return t.address.Bytes() +} + +func (t testPubKey) Bytes() []byte { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) VerifySignature(msg []byte, sig []byte) bool { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) Equals(key cryptotypes.PubKey) bool { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) Type() string { + //TODO implement me + panic("implement me") +} + +// testTx is a dummy implementation of Tx used for testing. type testTx struct { hash [32]byte priority int64 @@ -49,14 +95,14 @@ func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { func (tx testTx) GetSignaturesV2() (res []signing2.SignatureV2, err error) { if len(tx.multiNonces) == 0 { res = append(res, signing2.SignatureV2{ - PubKey: nil, + PubKey: testPubKey{address: tx.address}, Data: nil, Sequence: tx.nonce, }) } else { for _, nonce := range tx.multiNonces { res = append(res, signing2.SignatureV2{ - PubKey: nil, + PubKey: testPubKey{address: tx.address}, Data: nil, Sequence: nonce, }) @@ -69,6 +115,7 @@ var ( _ sdk.Tx = (*testTx)(nil) _ mempool.Tx = (*testTx)(nil) _ signing.SigVerifiableTx = (*testTx)(nil) + _ cryptotypes.PubKey = (*testPubKey)(nil) ) func (tx testTx) GetHash() [32]byte { @@ -247,6 +294,7 @@ func TestTxOrder(t *testing.T) { {p: 2, a: sc, n: 0}, {p: 7, a: sc, n: 3}, }, + // TODO check this order with single sender logic (sc in i=0 should be ignored) order: []int{3, 2, 4, 1, 6, 7, 0, 5, 8}, }, } @@ -466,6 +514,18 @@ func (s *MempoolTestSuite) TestRandomWalkTxs() { seed, mempool.Iterations(mp), duration.Milliseconds()) } +func (s *MempoolTestSuite) TestSampleTxs() { + ctxt := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + t := s.T() + s.resetMempool() + mp := s.mempool + delegatorTx, err := unmarshalTx(msgWithdrawDelegatorReward) + + require.NoError(t, err) + require.NoError(t, mp.Insert(ctxt, delegatorTx.(mempool.Tx))) + require.Equal(t, 1, mp.CountTx()) +} + func genRandomTxs(seed int64, countTx int, countAccount int) (res []testTx) { maxPriority := 100 r := rand.New(rand.NewSource(seed)) @@ -627,3 +687,10 @@ func simulateTx(ctx sdk.Context) sdk.Tx { ) return tx } + +func unmarshalTx(txBytes []byte) (sdk.Tx, error) { + txConfig := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}).TxConfig + return txConfig.TxJSONDecoder()(txBytes) +} + +var msgWithdrawDelegatorReward = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1lzhlnpahvznwfv4jmay2tgaha5kmz5qxerarrl\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1k2d9ed9vgfuk2m58a2d80q9u6qljkh4vfaqjfq\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1vygmh344ldv9qefss9ek7ggsnxparljlmj56q5\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1ej2es5fjztqjcd4pwa0zyvaevtjd2y5wxxp9gd\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AmbXAy10a0SerEefTYQzqyGQdX5kiTEWJZ1PZKX1oswX\"},\"mode_info\":{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},\"sequence\":\"119\"}],\"fee\":{\"amount\":[{\"denom\":\"uatom\",\"amount\":\"15968\"}],\"gas_limit\":\"638717\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"ji+inUo4xGlN9piRQLdLCeJWa7irwnqzrMVPcmzJyG5y6NPc+ZuNaIc3uvk5NLDJytRB8AHX0GqNETR\\/Q8fz4Q==\"]}") From 1e1be0e93b1ae6714fecb1128500bebe677bc9ef Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 4 Oct 2022 10:45:33 -0500 Subject: [PATCH 056/196] refactoring - include multisig tx in tests, CommunityProposal must impl Content - remove HashableTx (no need) - fix .Remove impl - clarify multisig approach --- types/mempool/graph.go | 12 +- types/mempool/mempool.go | 206 ++++--------------------------- types/mempool/mempool_test.go | 106 ++++++---------- types/mempool/stateful.go | 147 ++++++++++++++++++++++ x/auth/tx/builder.go | 24 ---- x/auth/tx/decoder.go | 4 - x/distribution/types/codec.go | 5 + x/distribution/types/proposal.go | 50 ++++++++ 8 files changed, 269 insertions(+), 285 deletions(-) create mode 100644 types/mempool/stateful.go diff --git a/types/mempool/graph.go b/types/mempool/graph.go index f7ec586f45c6..0dc774d359ae 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -62,18 +62,14 @@ type graph struct { } func (g *graph) Insert(context sdk.Context, tx Tx) error { - senders := tx.(signing.SigVerifiableTx).GetSigners() - nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - + sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { return err - } else if len(senders) != len(nonces) { - return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) } - // TODO multiple senders - sender := senders[0].String() - nonce := nonces[0].Sequence + sig := sigs[0] + sender := sig.PubKey.Address().String() + nonce := sig.Sequence node := &node{priority: context.Priority(), nonce: nonce, sender: sender, tx: tx} g.AddNode(node) return nil diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 6c799f0c3abd..5e648e67fe21 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -1,7 +1,6 @@ package mempool import ( - "bytes" "fmt" "math" @@ -23,12 +22,6 @@ type Tx interface { Size() int } -// HashableTx defines an interface for a transaction that can be hashed. -type HashableTx interface { - Tx - GetHash() [32]byte -} - type Mempool interface { // Insert attempts to insert a Tx into the app-side mempool returning // an error upon failure. @@ -54,7 +47,6 @@ var ( var ( ErrMempoolIsFull = fmt.Errorf("mempool is full") - ErrNoTxHash = fmt.Errorf("tx is not hashable") ) type defaultMempool struct { @@ -68,7 +60,6 @@ type txKey struct { nonce uint64 priority int64 sender string - hash [32]byte } func txKeyLess(a, b interface{}) int { @@ -79,12 +70,6 @@ func txKeyLess(a, b interface{}) int { return res } - res = bytes.Compare(keyB.hash[:], keyA.hash[:]) - //res = huandu.Bytes.Compare(keyA.hash[:], keyB.hash[:]) - if res != 0 { - return res - } - res = huandu.String.Compare(keyA.sender, keyB.sender) if res != 0 { return res @@ -107,17 +92,10 @@ func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { return err } - // TODO better selection criteria here sig := sigs[0] sender := sig.PubKey.Address().String() nonce := sig.Sequence - - hashableTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } - - tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender, hash: hashableTx.GetHash()} + tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender} senderTxs, ok := mp.senders[sender] // initialize sender mempool if not found @@ -128,7 +106,6 @@ func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { mp.senders[sender] = senderTxs } - // if a tx with the same nonce exists, replace it and delete from the priority list senderTxs.Set(tk, tx) mp.scores[txKey{nonce: nonce, sender: sender}] = ctx.Priority() mp.priorities.Set(tk, tx) @@ -200,30 +177,31 @@ func (mp *defaultMempool) CountTx() int { return mp.priorities.Len() } -func (mp *defaultMempool) Remove(context types.Context, tx Tx) error { - senders := tx.(signing.SigVerifiableTx).GetSigners() - nonces, _ := tx.(signing.SigVerifiableTx).GetSignaturesV2() - - for i, senderAddr := range senders { - sender := senderAddr.String() - nonce := nonces[i].Sequence +func (mp *defaultMempool) Remove(_ types.Context, tx Tx) error { + sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + if err != nil { + return err + } + sig := sigs[0] + sender := sig.PubKey.Address().String() + nonce := sig.Sequence - tk := txKey{sender: sender, nonce: nonce} + sk := txKey{nonce: nonce, sender: sender} + priority, ok := mp.scores[sk] + if !ok { + return fmt.Errorf("tx %v not found", sk) + } + tk := txKey{nonce: nonce, priority: priority, sender: sender} - priority, ok := mp.scores[tk] - if !ok { - return fmt.Errorf("tx %v not found", tk) - } + senderTxs, ok := mp.senders[sender] + if !ok { + return fmt.Errorf("sender %s not found", sender) + } - senderTxs, ok := mp.senders[sender] - if !ok { - return fmt.Errorf("sender %s not found", sender) - } + mp.priorities.Remove(tk) + senderTxs.Remove(tk) + delete(mp.scores, sk) - mp.priorities.Remove(txKey{priority: priority, sender: sender, nonce: nonce}) - senderTxs.Remove(nonce) - delete(mp.scores, tk) - } return nil } @@ -232,7 +210,7 @@ func DebugPrintKeys(mempool Mempool) { n := mp.priorities.Front() for n != nil { k := n.Key().(txKey) - fmt.Printf("%s, %d, %d; %d\n", k.sender, k.priority, k.nonce, k.hash[0]) + fmt.Printf("%s, %d, %d\n", k.sender, k.priority, k.nonce) n = n.Next() } } @@ -246,141 +224,3 @@ func Iterations(mempool Mempool) int { } panic("unknown mempool type") } - -// The complexity is O(log(N)). Implementation -type statefullPriorityKey struct { - hash [32]byte - priority int64 - nonce uint64 -} - -type accountsHeadsKey struct { - sender string - priority int64 - hash [32]byte -} - -type AccountMemPool struct { - transactions *huandu.SkipList - currentKey accountsHeadsKey - currentItem *huandu.Element - sender string -} - -// Push cannot be executed in the middle of a select -func (amp *AccountMemPool) Push(ctx types.Context, key statefullPriorityKey, tx Tx) { - amp.transactions.Set(key, tx) - amp.currentItem = amp.transactions.Back() - newKey := amp.currentItem.Key().(statefullPriorityKey) - amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} -} - -func (amp *AccountMemPool) Pop() *Tx { - if amp.currentItem == nil { - return nil - } - itemToPop := amp.currentItem - amp.currentItem = itemToPop.Prev() - if amp.currentItem != nil { - newKey := amp.currentItem.Key().(statefullPriorityKey) - amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} - } else { - amp.currentKey = accountsHeadsKey{} - } - tx := itemToPop.Value.(Tx) - return &tx -} - -type MemPoolI struct { - accountsHeads *huandu.SkipList - senders map[string]*AccountMemPool -} - -func NewMemPoolI() MemPoolI { - return MemPoolI{ - accountsHeads: huandu.New(huandu.LessThanFunc(priorityHuanduLess)), - senders: make(map[string]*AccountMemPool), - } -} - -func (amp *MemPoolI) Insert(ctx types.Context, tx Tx) error { - senders := tx.(signing.SigVerifiableTx).GetSigners() - nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - hashTx, ok := tx.(HashableTx) - if !ok { - return ErrNoTxHash - } - - if err != nil { - return err - } else if len(senders) != len(nonces) { - return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) - } - sender := senders[0].String() - nonce := nonces[0].Sequence - - accountMeempool, ok := amp.senders[sender] - if !ok { - accountMeempool = &AccountMemPool{ - transactions: huandu.New(huandu.LessThanFunc(nonceHuanduLess)), - sender: sender, - } - } - key := statefullPriorityKey{hash: hashTx.GetHash(), nonce: nonce, priority: ctx.Priority()} - - prevKey := accountMeempool.currentKey - accountMeempool.Push(ctx, key, tx) - - amp.accountsHeads.Remove(prevKey) - amp.accountsHeads.Set(accountMeempool.currentKey, accountMeempool) - amp.senders[sender] = accountMeempool - return nil - -} - -func (amp *MemPoolI) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { - var selectedTxs []Tx - var txBytes int - - currentAccount := amp.accountsHeads.Front() - for currentAccount != nil { - accountMemPool := currentAccount.Value.(*AccountMemPool) - //currentTx := accountMemPool.transactions.Front() - prevKey := accountMemPool.currentKey - tx := accountMemPool.Pop() - if tx == nil { - return selectedTxs, nil - } - mempoolTx := *tx - selectedTxs = append(selectedTxs, mempoolTx) - if txBytes += mempoolTx.Size(); txBytes >= maxBytes { - return selectedTxs, nil - } - - amp.accountsHeads.Remove(prevKey) - amp.accountsHeads.Set(accountMemPool.currentKey, accountMemPool) - currentAccount = amp.accountsHeads.Front() - } - return selectedTxs, nil -} - -func priorityHuanduLess(a, b interface{}) int { - keyA := a.(accountsHeadsKey) - keyB := b.(accountsHeadsKey) - if keyA.priority == keyB.priority { - return bytes.Compare(keyA.hash[:], keyB.hash[:]) - } else { - if keyA.priority < keyB.priority { - return -1 - } else { - return 1 - } - } -} - -func nonceHuanduLess(a, b interface{}) int { - keyA := a.(statefullPriorityKey) - keyB := b.(statefullPriorityKey) - uint64Compare := huandu.Uint64 - return uint64Compare.Compare(keyA.nonce, keyB.nonce) -} diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index eeb183fca6a5..ea0777c930ad 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -9,14 +9,15 @@ import ( "github.com/stretchr/testify/suite" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/cosmos-sdk/x/distribution" - - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/x/gov" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" @@ -73,19 +74,14 @@ func (t testPubKey) Type() string { // testTx is a dummy implementation of Tx used for testing. type testTx struct { - hash [32]byte - priority int64 - nonce uint64 - address sdk.AccAddress - multiAddress []sdk.AccAddress - multiNonces []uint64 + hash [32]byte + priority int64 + nonce uint64 + address sdk.AccAddress } func (tx testTx) GetSigners() []sdk.AccAddress { - if len(tx.multiAddress) == 0 { - return []sdk.AccAddress{tx.address} - } - return tx.multiAddress + panic("implement me") } func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { @@ -93,21 +89,11 @@ func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { } func (tx testTx) GetSignaturesV2() (res []signing2.SignatureV2, err error) { - if len(tx.multiNonces) == 0 { - res = append(res, signing2.SignatureV2{ - PubKey: testPubKey{address: tx.address}, - Data: nil, - Sequence: tx.nonce, - }) - } else { - for _, nonce := range tx.multiNonces { - res = append(res, signing2.SignatureV2{ - PubKey: testPubKey{address: tx.address}, - Data: nil, - Sequence: nonce, - }) - } - } + res = append(res, signing2.SignatureV2{ + PubKey: testPubKey{address: tx.address}, + Data: nil, + Sequence: tx.nonce}) + return res, nil } @@ -155,12 +141,11 @@ func TestNewStatefulMempool(t *testing.T) { } type txSpec struct { - i int - h int - p int - n int - a sdk.AccAddress - multi []txSpec + i int + h int + p int + n int + a sdk.AccAddress } func (tx txSpec) String() string { @@ -206,7 +191,8 @@ func TestOutOfOrder(t *testing.T) { require.Error(t, validateOrder(rmtxs)) } -func TestTxOrder(t *testing.T) { +func (s *MempoolTestSuite) TestTxOrder() { + t := s.T() ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 5) sa := accounts[0].Address @@ -282,9 +268,7 @@ func TestTxOrder(t *testing.T) { }, { txs: []txSpec{ - {p: 30, multi: []txSpec{ - {n: 2, a: sa}, - {n: 1, a: sc}}}, + {p: 30, n: 2, a: sa}, {p: 20, a: sb, n: 1}, {p: 15, a: sa, n: 1}, {p: 10, a: sa, n: 0}, @@ -294,38 +278,16 @@ func TestTxOrder(t *testing.T) { {p: 2, a: sc, n: 0}, {p: 7, a: sc, n: 3}, }, - // TODO check this order with single sender logic (sc in i=0 should be ignored) - order: []int{3, 2, 4, 1, 6, 7, 0, 5, 8}, + order: []int{3, 2, 0, 4, 1, 5, 6, 7, 8}, }, } for i, tt := range tests { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { - // create fresh mempool - // TODO test both? - - //pool := mempool.NewDefaultMempool() - pool := mempool.NewGraph() + pool := s.mempool // create test txs and insert into mempool for i, ts := range tt.txs { - var tx testTx - if len(ts.multi) == 0 { - tx = testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} - } else { - var nonces []uint64 - var addresses []sdk.AccAddress - for _, ms := range ts.multi { - nonces = append(nonces, uint64(ms.n)) - addresses = append(addresses, ms.a) - } - tx = testTx{ - hash: [32]byte{byte(i)}, - priority: int64(ts.p), - multiNonces: nonces, - multiAddress: addresses, - } - } - + tx := testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} c := ctx.WithPriority(tx.priority) err := pool.Insert(c, tx) require.NoError(t, err) @@ -339,6 +301,12 @@ func TestTxOrder(t *testing.T) { } require.Equal(t, tt.order, txOrder) require.NoError(t, validateOrder(orderedTxs)) + + for _, tx := range orderedTxs { + require.NoError(t, pool.Remove(ctx, tx)) + } + + require.Equal(t, 0, pool.CountTx()) }) } } @@ -524,6 +492,11 @@ func (s *MempoolTestSuite) TestSampleTxs() { require.NoError(t, err) require.NoError(t, mp.Insert(ctxt, delegatorTx.(mempool.Tx))) require.Equal(t, 1, mp.CountTx()) + + proposalTx, err := unmarshalTx(msgMultiSigMsgSubmitProposal) + require.NoError(t, err) + require.NoError(t, mp.Insert(ctxt, proposalTx.(mempool.Tx))) + require.Equal(t, 2, mp.CountTx()) } func genRandomTxs(seed int64, countTx int, countAccount int) (res []testTx) { @@ -689,8 +662,9 @@ func simulateTx(ctx sdk.Context) sdk.Tx { } func unmarshalTx(txBytes []byte) (sdk.Tx, error) { - txConfig := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}).TxConfig - return txConfig.TxJSONDecoder()(txBytes) + cfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}, gov.AppModuleBasic{}) + return cfg.TxConfig.TxJSONDecoder()(txBytes) } var msgWithdrawDelegatorReward = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1lzhlnpahvznwfv4jmay2tgaha5kmz5qxerarrl\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1k2d9ed9vgfuk2m58a2d80q9u6qljkh4vfaqjfq\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1vygmh344ldv9qefss9ek7ggsnxparljlmj56q5\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1ej2es5fjztqjcd4pwa0zyvaevtjd2y5wxxp9gd\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AmbXAy10a0SerEefTYQzqyGQdX5kiTEWJZ1PZKX1oswX\"},\"mode_info\":{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},\"sequence\":\"119\"}],\"fee\":{\"amount\":[{\"denom\":\"uatom\",\"amount\":\"15968\"}],\"gas_limit\":\"638717\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"ji+inUo4xGlN9piRQLdLCeJWa7irwnqzrMVPcmzJyG5y6NPc+ZuNaIc3uvk5NLDJytRB8AHX0GqNETR\\/Q8fz4Q==\"]}") +var msgMultiSigMsgSubmitProposal = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.gov.v1beta1.MsgSubmitProposal\",\"content\":{\"@type\":\"\\/cosmos.distribution.v1beta1.CommunityPoolSpendProposal\",\"title\":\"ATOM \\ud83e\\udd1d Osmosis: Allocate Community Pool to ATOM Liquidity Incentives\",\"description\":\"ATOMs should be the base money of Cosmos, just like ETH is the base money of the entire Ethereum DeFi ecosystem. ATOM is currently well positioned to play this role among Cosmos assets because it has the highest market cap, most liquidity, largest brand, and many integrations with fiat onramps. ATOM is the gateway to Cosmos.\\n\\nIn the Cosmos Hub Port City vision, ATOMs are pitched as equity in the Cosmos Hub. However, this alone is insufficient to establish ATOM as the base currency of the Cosmos ecosystem as a whole. Instead, the ATOM community must work to actively promote the use of ATOMs throughout the Cosmos ecosystem, rather than passively relying on the Hub's reputation to create ATOM's value.\\n\\nIn order to cement the role of ATOMs in Cosmos DeFi, the Cosmos Hub should leverage its community pool to help align incentives with other protocols within the Cosmos ecosystem. We propose beginning this initiative by using the community pool ATOMs to incentivize deep ATOM base pair liquidity pools on the Osmosis Network.\\n\\nOsmosis is the first IBC-enabled DeFi application. Within its 3 weeks of existence, it has already 100x\\u2019d the number of IBC transactions ever created, demonstrating the power of IBC and the ability of the Cosmos SDK to bootstrap DeFi protocols with $100M+ TVL in a short period of time. Since its announcement Osmosis has helped bring renewed attention and interest to Cosmos from the crypto community at large and kickstarted the era of Cosmos DeFi.\\n\\nOsmosis has already helped in establishing ATOM as the Schelling Point of the Cosmos ecosystem. The genesis distribution of OSMO was primarily based on an airdrop to ATOM holders specifically, acknowledging the importance of ATOM to all future projects within the Cosmos. Furthermore, the Osmosis LP rewards currently incentivize ATOMs to be one of the main base pairs of the platform.\\n\\nOsmosis has the ability to incentivize AMM liquidity, a feature not available on any other IBC-enabled DEX. Osmosis already uses its own native OSMO liquidity rewards to incentivize ATOMs to be one of the main base pairs, leading to ~2.2 million ATOMs already providing liquidity on the platform.\\n\\nIn addition to these native OSMO LP Rewards, the platform also includes a feature called \\u201cexternal incentives\\u201d that allows anyone to permissionlessly add additional incentives in any token to the LPs of any AMM pools they wish. You can read more about this mechanism here: https:\\/\\/medium.com\\/osmosis\\/osmosis-liquidity-mining-101-2fa58d0e9d4d#f413 . Pools containing Cosmos assets such as AKT and XPRT are already planned to receive incentives from their respective community pools and\\/or foundations.\\n\\nWe propose the Cosmos Hub dedicate 100,000 ATOMs from its Community Pool to be allocated towards liquidity incentives on Osmosis over the next 3 months. This community fund proposal will transfer 100,000 ATOMs to a multisig group who will then allocate the ATOMs to bonded liquidity gauges on Osmosis on a biweekly basis, according to direction given by Cosmos Hub governance. For simplicity, we propose setting the liquidity incentives to initially point to Osmosis Pool #1, the ATOM\\/OSMO pool, which is the pool with by far the highest TVL and Volume. Cosmos Hub governance can then use Text Proposals to further direct the multisig members to reallocate incentives to new pools.\\n\\nThe multisig will consist of a 2\\/3 key holder set consisting of the following individuals whom have all agreed to participate in this process shall this proposal pass:\\n\\n- Zaki Manian\\n- Federico Kunze\\n- Marko Baricevic\\n\\nThis is one small step for the Hub, but one giant leap for ATOM-aligned.\\n\",\"recipient\":\"cosmos157n0d38vwn5dvh64rc39q3lyqez0a689g45rkc\",\"amount\":[{\"denom\":\"uatom\",\"amount\":\"100000000000\"}]},\"initial_deposit\":[{\"denom\":\"uatom\",\"amount\":\"64000000\"}],\"proposer\":\"cosmos1ey69r37gfxvxg62sh4r0ktpuc46pzjrmz29g45\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.multisig.LegacyAminoPubKey\",\"threshold\":2,\"public_keys\":[{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AldOvgv8dU9ZZzuhGydQD5FYreLhfhoBgrDKi8ZSTbCQ\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AxUMR\\/GKoycWplR+2otzaQZ9zhHRQWJFt3h1bPg1ltha\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AlI9yVj2Aejow6bYl2nTRylfU+9LjQLEl3keq0sERx9+\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"A0UvHPcvCCaIoFY9Ygh0Pxq9SZTAWtduOyinit\\/8uo+Q\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"As7R9fDUnwsUVLDr1cxspp+cY9UfXfUf7i9\\/w+N0EzKA\"}]},\"mode_info\":{\"multi\":{\"bitarray\":{\"extra_bits_stored\":5,\"elems\":\"SA==\"},\"mode_infos\":[{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}}]}},\"sequence\":\"102\"}],\"fee\":{\"amount\":[],\"gas_limit\":\"10000000\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"CkB\\/KKWTFntEWbg1A0vu7DCHffJ4x4db\\/EI8dIVzRFFW7iuZBzvq+jYBtrcTlVpEVfmCY3ggIMnWfbMbb1egIlYbCkAmDf6Eaj1NbyXY8JZZtYAX3Qj81ZuKZUBeLW1ZvH1XqAg9sl\\/sqpLMnsJzKfmqEXvhoMwu1YxcSzrY6CJfuYL6\"]}") diff --git a/types/mempool/stateful.go b/types/mempool/stateful.go new file mode 100644 index 000000000000..aa7621a50f3a --- /dev/null +++ b/types/mempool/stateful.go @@ -0,0 +1,147 @@ +package mempool + +import ( + "bytes" + "crypto/sha256" + "fmt" + + huandu "github.com/huandu/skiplist" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +// The complexity is O(log(N)). Implementation +type statefullPriorityKey struct { + hash [32]byte + priority int64 + nonce uint64 +} + +type accountsHeadsKey struct { + sender string + priority int64 + hash [32]byte +} + +type AccountMemPool struct { + transactions *huandu.SkipList + currentKey accountsHeadsKey + currentItem *huandu.Element + sender string +} + +// Push cannot be executed in the middle of a select +func (amp *AccountMemPool) Push(ctx sdk.Context, key statefullPriorityKey, tx Tx) { + amp.transactions.Set(key, tx) + amp.currentItem = amp.transactions.Back() + newKey := amp.currentItem.Key().(statefullPriorityKey) + amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} +} + +func (amp *AccountMemPool) Pop() *Tx { + if amp.currentItem == nil { + return nil + } + itemToPop := amp.currentItem + amp.currentItem = itemToPop.Prev() + if amp.currentItem != nil { + newKey := amp.currentItem.Key().(statefullPriorityKey) + amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} + } else { + amp.currentKey = accountsHeadsKey{} + } + tx := itemToPop.Value.(Tx) + return &tx +} + +type MemPoolI struct { + accountsHeads *huandu.SkipList + senders map[string]*AccountMemPool +} + +func NewMemPoolI() MemPoolI { + return MemPoolI{ + accountsHeads: huandu.New(huandu.LessThanFunc(priorityHuanduLess)), + senders: make(map[string]*AccountMemPool), + } +} + +func (amp *MemPoolI) Insert(ctx sdk.Context, tx Tx) error { + senders := tx.(signing.SigVerifiableTx).GetSigners() + nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + + if err != nil { + return err + } else if len(senders) != len(nonces) { + return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) + } + sender := senders[0].String() + nonce := nonces[0].Sequence + + accountMeempool, ok := amp.senders[sender] + if !ok { + accountMeempool = &AccountMemPool{ + transactions: huandu.New(huandu.LessThanFunc(nonceHuanduLess)), + sender: sender, + } + } + hash := sha256.Sum256(senders[0].Bytes()) + key := statefullPriorityKey{hash: hash, nonce: nonce, priority: ctx.Priority()} + + prevKey := accountMeempool.currentKey + accountMeempool.Push(ctx, key, tx) + + amp.accountsHeads.Remove(prevKey) + amp.accountsHeads.Set(accountMeempool.currentKey, accountMeempool) + amp.senders[sender] = accountMeempool + return nil + +} + +func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int) ([]Tx, error) { + var selectedTxs []Tx + var txBytes int + + currentAccount := amp.accountsHeads.Front() + for currentAccount != nil { + accountMemPool := currentAccount.Value.(*AccountMemPool) + //currentTx := accountMemPool.transactions.Front() + prevKey := accountMemPool.currentKey + tx := accountMemPool.Pop() + if tx == nil { + return selectedTxs, nil + } + mempoolTx := *tx + selectedTxs = append(selectedTxs, mempoolTx) + if txBytes += mempoolTx.Size(); txBytes >= maxBytes { + return selectedTxs, nil + } + + amp.accountsHeads.Remove(prevKey) + amp.accountsHeads.Set(accountMemPool.currentKey, accountMemPool) + currentAccount = amp.accountsHeads.Front() + } + return selectedTxs, nil +} + +func priorityHuanduLess(a, b interface{}) int { + keyA := a.(accountsHeadsKey) + keyB := b.(accountsHeadsKey) + if keyA.priority == keyB.priority { + return bytes.Compare(keyA.hash[:], keyB.hash[:]) + } else { + if keyA.priority < keyB.priority { + return -1 + } else { + return 1 + } + } +} + +func nonceHuanduLess(a, b interface{}) int { + keyA := a.(statefullPriorityKey) + keyB := b.(statefullPriorityKey) + uint64Compare := huandu.Uint64 + return uint64Compare.Compare(keyA.nonce, keyB.nonce) +} diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index b0fc0c7307cb..2ad4647f8512 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -1,7 +1,6 @@ package tx import ( - "crypto/sha256" "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/gogoproto/proto" @@ -33,7 +32,6 @@ type wrapper struct { // from the client using TxRaw if the tx was decoded from the wire authInfoBz []byte - hash *[32]byte numBytes int txBodyHasUnknownNonCriticals bool @@ -47,7 +45,6 @@ var ( _ ExtensionOptionsTxBuilder = &wrapper{} _ tx.TipTx = &wrapper{} _ mempool.Tx = &wrapper{} - _ mempool.HashableTx = &wrapper{} ) // ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions. @@ -74,27 +71,6 @@ func (w *wrapper) Size() int { return w.numBytes } -// hashFromSig hashes the signature. Presently this is only used in test when w.hash is nil -func (w *wrapper) hashFromSig() [32]byte { - var sigBytes []byte - for _, bs := range w.tx.Signatures { - sigBytes = append(sigBytes, bs...) - } - return sha256.Sum256(sigBytes) -} - -// GetHash used for secondary, deterministic tx ordering in the mempool -// when there are multiple txs with the same priority. -// TODO: This should be altered use either sig bytes or txBytes for the hash -func (w *wrapper) GetHash() [32]byte { - if w.hash == nil { - hash := w.hashFromSig() - w.hash = &hash - return hash - } - return *w.hash -} - func (w *wrapper) GetMsgs() []sdk.Msg { return w.tx.GetMsgs() } diff --git a/x/auth/tx/decoder.go b/x/auth/tx/decoder.go index f7a46d8d9b4a..65dc7bf1ffb3 100644 --- a/x/auth/tx/decoder.go +++ b/x/auth/tx/decoder.go @@ -1,7 +1,6 @@ package tx import ( - "crypto/sha256" "fmt" "google.golang.org/protobuf/encoding/protowire" @@ -67,13 +66,10 @@ func DefaultTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder { Signatures: raw.Signatures, } - hash := sha256.Sum256(txBytes) - return &wrapper{ tx: theTx, bodyBz: raw.BodyBytes, authInfoBz: raw.AuthInfoBytes, - hash: &hash, numBytes: len(txBytes), txBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals, }, nil diff --git a/x/distribution/types/codec.go b/x/distribution/types/codec.go index 5c5b21f0b401..9d5118a938f8 100644 --- a/x/distribution/types/codec.go +++ b/x/distribution/types/codec.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/msgservice" authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec" govcodec "github.com/cosmos/cosmos-sdk/x/gov/codec" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" groupcodec "github.com/cosmos/cosmos-sdk/x/group/codec" ) @@ -37,6 +38,10 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { &MsgCommunityPoolSpend{}, ) + registry.RegisterImplementations( + (*govtypes.Content)(nil), + &CommunityPoolSpendProposal{}) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } diff --git a/x/distribution/types/proposal.go b/x/distribution/types/proposal.go index 925738d3086a..a19e6b8feefd 100644 --- a/x/distribution/types/proposal.go +++ b/x/distribution/types/proposal.go @@ -3,8 +3,58 @@ package types import ( "fmt" "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" +) + +const ( + // ProposalTypeCommunityPoolSpend defines the type for a CommunityPoolSpendProposal + ProposalTypeCommunityPoolSpend = "CommunityPoolSpend" ) +// Assert CommunityPoolSpendProposal implements govtypes.Content at compile-time +var _ govtypes.Content = &CommunityPoolSpendProposal{} + +func init() { + govtypes.RegisterProposalType(ProposalTypeCommunityPoolSpend) +} + +// NewCommunityPoolSpendProposal creates a new community pool spend proposal. +// +//nolint:interfacer +func NewCommunityPoolSpendProposal(title, description string, recipient sdk.AccAddress, amount sdk.Coins) *CommunityPoolSpendProposal { + return &CommunityPoolSpendProposal{title, description, recipient.String(), amount} +} + +// GetTitle returns the title of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) GetTitle() string { return csp.Title } + +// GetDescription returns the description of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) GetDescription() string { return csp.Description } + +// GetDescription returns the routing key of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) ProposalRoute() string { return RouterKey } + +// ProposalType returns the type of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) ProposalType() string { return ProposalTypeCommunityPoolSpend } + +// ValidateBasic runs basic stateless validity checks +func (csp *CommunityPoolSpendProposal) ValidateBasic() error { + err := govtypes.ValidateAbstract(csp) + if err != nil { + return err + } + if !csp.Amount.IsValid() { + return ErrInvalidProposalAmount + } + if csp.Recipient == "" { + return ErrEmptyProposalRecipient + } + + return nil +} + // String implements the Stringer interface. func (csp CommunityPoolSpendProposal) String() string { var b strings.Builder From b749c6fbe27bc724295b02b231eea4fd6f6d690a Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 4 Oct 2022 12:15:23 -0500 Subject: [PATCH 057/196] baseapp integration --- baseapp/abci.go | 20 ++++++++++++++++---- baseapp/baseapp.go | 7 ++++--- baseapp/options.go | 13 +++++++++++++ runtime/module.go | 5 +++++ simapp/app.go | 6 ++++-- types/mempool/graph.go | 6 +++--- types/mempool/mempool.go | 19 +++++++++---------- types/mempool/mempool_test.go | 12 ++++++------ x/auth/tx/builder.go | 4 ++-- x/auth/tx/decoder.go | 2 +- x/auth/tx/module/module.go | 1 + 11 files changed, 64 insertions(+), 31 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 4f52136fac10..707e57a30d08 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -10,12 +10,13 @@ import ( "syscall" "time" - "github.com/cosmos/gogoproto/proto" abci "github.com/tendermint/tendermint/abci/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "google.golang.org/grpc/codes" grpcstatus "google.golang.org/grpc/status" + "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/cosmos-sdk/codec" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" "github.com/cosmos/cosmos-sdk/telemetry" @@ -249,8 +250,19 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { - // TODO: Implement. - return abci.ResponsePrepareProposal{Txs: req.Txs} + memTxs, selectErr := app.mempool.Select(req.Txs, req.MaxTxBytes) + if selectErr != nil { + panic(selectErr) + } + var txs [][]byte + for _, memTx := range memTxs { + bz, encErr := app.txEncoder(memTx) + if encErr != nil { + panic(encErr) + } + txs = append(txs, bz) + } + return abci.ResponsePrepareProposal{Txs: txs} } // ProcessProposal implements the ProcessProposal ABCI method and returns a @@ -266,7 +278,7 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { - // TODO: Implement. + // TODO return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} } diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 12fa23b68b3e..9c542d8586f7 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -2,7 +2,7 @@ package baseapp import ( "fmt" - "github.com/cosmos/cosmos-sdk/types/mempool" + "sort" "strings" @@ -22,6 +22,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/mempool" ) const ( @@ -56,6 +57,7 @@ type BaseApp struct { //nolint: maligned msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx + txEncoder sdk.TxEncoder // marshal sdk.Tx into []byte mempool mempool.Mempool // application side mempool anteHandler sdk.AnteHandler // ante handler for fee and auth @@ -674,8 +676,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re anteEvents = events.ToABCIEvents() } - // TODO remove nil check when implemented - if mode == runTxModeCheck && app.mempool != nil { + if mode == runTxModeCheck { err = app.mempool.Insert(ctx, tx.(mempool.Tx)) if err != nil { return gInfo, nil, anteEvents, priority, err diff --git a/baseapp/options.go b/baseapp/options.go index f9a67f186d7c..b7e4aa218f14 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -12,6 +12,7 @@ import ( snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" ) // File for storing in-package BaseApp optional functions, @@ -240,3 +241,15 @@ func (app *BaseApp) SetStreamingService(s StreamingService) { func (app *BaseApp) SetTxDecoder(txDecoder sdk.TxDecoder) { app.txDecoder = txDecoder } + +func (app *BaseApp) SetTxEncoder(txEncoder sdk.TxEncoder) { + app.txEncoder = txEncoder +} + +func (app *BaseApp) SetMempool(mempool mempool.Mempool) { + if app.sealed { + panic("SetMempool() on sealed BaseApp") + } + + app.mempool = mempool +} diff --git a/runtime/module.go b/runtime/module.go index 07ff9a430d73..1f5c8e11a903 100644 --- a/runtime/module.go +++ b/runtime/module.go @@ -13,6 +13,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/std" storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/cosmos-sdk/types/module" ) @@ -80,6 +81,7 @@ type appInputs struct { depinject.In Config *runtimev1alpha1.Module + MempoolFn mempool.Factory App appWrapper Modules map[string]AppModuleWrapper BaseAppOptions []BaseAppOption @@ -92,6 +94,9 @@ func provideAppBuilder(inputs appInputs) *AppBuilder { } app := inputs.App app.baseAppOptions = inputs.BaseAppOptions + app.baseAppOptions = append(app.baseAppOptions, func(app *baseapp.BaseApp) { + app.SetMempool(inputs.MempoolFn()) + }) app.config = inputs.Config app.ModuleManager = mm return &AppBuilder{app: app} diff --git a/simapp/app.go b/simapp/app.go index 2784de096a2d..b5fa17ded233 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -14,6 +14,7 @@ import ( dbm "github.com/tendermint/tm-db" "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" @@ -191,8 +192,9 @@ func NewSimApp( depinject.Supply( // supply the application options appOpts, - - // for providing a custom inflaction function for x/mint + // use a different mempool implementation by changing this factory fn + mempool.NewDefaultMempool, + // for providing a custom inflation function for x/mint // add here your custom function that implements the minttypes.InflationCalculationFn interface. // for providing a custom authority to a module simply add it below. By default the governance module is the default authority. diff --git a/types/mempool/graph.go b/types/mempool/graph.go index 0dc774d359ae..60575a900710 100644 --- a/types/mempool/graph.go +++ b/types/mempool/graph.go @@ -89,7 +89,7 @@ func (g *graph) AddNode(n *node) { sg.byPriority.Set(n.priority, n) } -func (g *graph) Select(ctx sdk.Context, txs [][]byte, maxBytes int) ([]Tx, error) { +func (g *graph) Select(txs [][]byte, maxBytes int64) ([]Tx, error) { // todo collapse multiple iterations into kahns sorted, err := g.TopologicalSort() if err != nil { @@ -106,7 +106,7 @@ func (g *graph) CountTx() int { return len(g.nodes) } -func (g *graph) Remove(context sdk.Context, tx Tx) error { +func (g *graph) Remove(tx Tx) error { //TODO implement me panic("implement me") } @@ -119,7 +119,7 @@ func (n node) String() string { return n.key() } -func NewGraph() *graph { +func NewGraph() Mempool { return &graph{ nodes: make(map[string]*node), priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 5e648e67fe21..985b4aab9b86 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -19,7 +19,7 @@ type Tx interface { types.Tx // Size returns the size of the transaction in bytes. - Size() int + Size() int64 } type Mempool interface { @@ -31,22 +31,21 @@ type Mempool interface { // mempool, up to maxBytes or until the mempool is empty. The application can // decide to return transactions from its own mempool, from the incoming // txs, or some combination of both. - Select(ctx types.Context, txs [][]byte, maxBytes int) ([]Tx, error) + Select(txs [][]byte, maxBytes int64) ([]Tx, error) // CountTx returns the number of transactions currently in the mempool. CountTx() int // Remove attempts to remove a transaction from the mempool, returning an error // upon failure. - Remove(types.Context, Tx) error + Remove(Tx) error } -var ( - _ Mempool = (*defaultMempool)(nil) -) +type Factory func() Mempool var ( - ErrMempoolIsFull = fmt.Errorf("mempool is full") + _ Mempool = (*defaultMempool)(nil) + //ErrMempoolIsFull = fmt.Errorf("mempool is full") ) type defaultMempool struct { @@ -113,9 +112,9 @@ func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { return nil } -func (mp *defaultMempool) Select(_ types.Context, _ [][]byte, maxBytes int) ([]Tx, error) { +func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { var selectedTxs []Tx - var txBytes int + var txBytes int64 senderCursors := make(map[string]*huandu.Element) // start with the highest priority sender @@ -177,7 +176,7 @@ func (mp *defaultMempool) CountTx() int { return mp.priorities.Len() } -func (mp *defaultMempool) Remove(_ types.Context, tx Tx) error { +func (mp *defaultMempool) Remove(tx Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { return err diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index ea0777c930ad..c7cf7280a9bc 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -108,8 +108,8 @@ func (tx testTx) GetHash() [32]byte { return tx.hash } -func (tx testTx) Size() int { - return 10 +func (tx testTx) Size() int64 { + return 1 } func (tx testTx) GetMsgs() []sdk.Msg { @@ -293,7 +293,7 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, err) } - orderedTxs, err := pool.Select(ctx, nil, 1000) + orderedTxs, err := pool.Select(nil, 1000) require.NoError(t, err) var txOrder []int for _, tx := range orderedTxs { @@ -303,7 +303,7 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, validateOrder(orderedTxs)) for _, tx := range orderedTxs { - require.NoError(t, pool.Remove(ctx, tx)) + require.NoError(t, pool.Remove(tx)) } require.Equal(t, 0, pool.CountTx()) @@ -412,7 +412,7 @@ func (s *MempoolTestSuite) TestRandomGeneratedTxs() { require.NoError(t, err) } - selected, err := mp.Select(ctx, nil, 100000) + selected, err := mp.Select(nil, 100000) require.Equal(t, len(generated), len(selected)) require.NoError(t, err) @@ -446,7 +446,7 @@ func (s *MempoolTestSuite) TestRandomWalkTxs() { require.Equal(t, s.numTxs, mp.CountTx()) - selected, err := mp.Select(ctx, nil, math.MaxInt) + selected, err := mp.Select(nil, math.MaxInt) require.Equal(t, len(ordered), len(selected)) var orderedStr, selectedStr string diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 2ad4647f8512..f3741cb9cbee 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -32,7 +32,7 @@ type wrapper struct { // from the client using TxRaw if the tx was decoded from the wire authInfoBz []byte - numBytes int + numBytes int64 txBodyHasUnknownNonCriticals bool } @@ -67,7 +67,7 @@ func newBuilder(cdc codec.Codec) *wrapper { } } -func (w *wrapper) Size() int { +func (w *wrapper) Size() int64 { return w.numBytes } diff --git a/x/auth/tx/decoder.go b/x/auth/tx/decoder.go index 65dc7bf1ffb3..fcf322c1c89f 100644 --- a/x/auth/tx/decoder.go +++ b/x/auth/tx/decoder.go @@ -70,7 +70,7 @@ func DefaultTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder { tx: theTx, bodyBz: raw.BodyBytes, authInfoBz: raw.AuthInfoBytes, - numBytes: len(txBytes), + numBytes: int64(len(txBytes)), txBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals, }, nil } diff --git a/x/auth/tx/module/module.go b/x/auth/tx/module/module.go index b005d6cadc16..52194469db3c 100644 --- a/x/auth/tx/module/module.go +++ b/x/auth/tx/module/module.go @@ -81,6 +81,7 @@ func provideModule(in txInputs) txOutputs { // TxDecoder app.SetTxDecoder(txConfig.TxDecoder()) + app.SetTxEncoder(txConfig.TxEncoder()) } return txOutputs{TxConfig: txConfig, BaseAppOption: baseAppOption} From 26eb530b92fc7df2eebcd422b24094c0bafe4005 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Tue, 4 Oct 2022 16:59:23 -0600 Subject: [PATCH 058/196] test --- types/mempool/mempool_test.go | 136 ++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 9f8b160f3a90..3ae5b64ba9dc 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -292,6 +292,142 @@ func TestTxOrder(t *testing.T) { } } +func TestTxOrder2(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 5) + sa := accounts[0].Address + sb := accounts[1].Address + sc := accounts[2].Address + //sd := accounts[3].Address + //se := accounts[4].Address + + tests := []struct { + txs []txSpec + order []int + fail bool + }{ + { + txs: []txSpec{ + {p: 21, n: 4, a: sa}, + {p: 8, n: 3, a: sa}, + {p: 6, n: 2, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 20, n: 1, a: sa}, + }, + order: []int{4, 3, 2, 1, 0}, + }, + { + // a very interesting breaking case + txs: []txSpec{ + {p: 3, n: 0, a: sa}, + {p: 5, n: 1, a: sa}, + {p: 9, n: 2, a: sa}, + {p: 6, n: 0, a: sb}, + {p: 5, n: 1, a: sb}, + {p: 8, n: 2, a: sb}, + }, + order: []int{3, 4, 5, 0, 1, 2}, + }, + { + txs: []txSpec{ + {p: 21, n: 4, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 20, n: 1, a: sa}, + }, + order: []int{2, 0, 1}}, + { + txs: []txSpec{ + {p: 50, n: 3, a: sa}, + {p: 30, n: 2, a: sa}, + {p: 10, n: 1, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 21, n: 2, a: sb}, + }, + order: []int{3, 4, 2, 1, 0}, + }, + { + txs: []txSpec{ + {p: 50, n: 3, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 99, n: 1, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 8, n: 2, a: sb}, + }, + order: []int{2, 3, 1, 0, 4}, + }, + { + txs: []txSpec{ + {p: 30, a: sa, n: 2}, + {p: 20, a: sb, n: 1}, + {p: 15, a: sa, n: 1}, + {p: 10, a: sa, n: 0}, + {p: 8, a: sb, n: 0}, + {p: 6, a: sa, n: 3}, + {p: 4, a: sb, n: 3}, + }, + order: []int{3, 2, 0, 4, 1, 5, 6}, + }, + { + txs: []txSpec{ + {p: 30, multi: []txSpec{ + {n: 2, a: sa}, + {n: 1, a: sc}}}, + {p: 20, a: sb, n: 1}, + {p: 15, a: sa, n: 1}, + {p: 10, a: sa, n: 0}, + {p: 8, a: sb, n: 0}, + {p: 6, a: sa, n: 3}, + {p: 4, a: sb, n: 3}, + {p: 2, a: sc, n: 0}, + {p: 7, a: sc, n: 3}, + }, + //order: []int{3, 2, 4, 1, 6, 7, 0, 5, 8}, + order: []int{3, 2, 4, 1, 6, 7, 0, 8, 5}, + }, + } + for i, tt := range tests { + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + // create fresh mempool + //pool := mempool.NewDefaultMempool() + pool := mempool.NewMemPoolI() + + // create test txs and insert into mempool + for i, ts := range tt.txs { + var tx testTx + if len(ts.multi) == 0 { + tx = testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} + } else { + var nonces []uint64 + var addresses []sdk.AccAddress + for _, ms := range ts.multi { + nonces = append(nonces, uint64(ms.n)) + addresses = append(addresses, ms.a) + } + tx = testTx{ + hash: [32]byte{byte(i)}, + priority: int64(ts.p), + multiNonces: nonces, + multiAddress: addresses, + } + } + + c := ctx.WithPriority(tx.priority) + err := pool.Insert(c, tx) + require.NoError(t, err) + } + + orderedTxs, err := pool.Select(ctx, nil, 10000) + require.NoError(t, err) + var txOrder []int + for _, tx := range orderedTxs { + txOrder = append(txOrder, int(tx.(testTx).hash[0])) + } + require.Equal(t, tt.order, txOrder) + require.NoError(t, validateOrder(orderedTxs)) + }) + } +} + func TestRandomTxOrderManyTimes(t *testing.T) { for i := 0; i < 30; i++ { TestRandomTxOrder(t) From 90996c7b4091f65fbd29eb79e963a93604073878 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Tue, 4 Oct 2022 17:34:58 -0600 Subject: [PATCH 059/196] Procees proposal base --- baseapp/abci.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 707e57a30d08..a65b5bdd3ba8 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -278,7 +278,18 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { - // TODO + ctx := app.deliverState.ctx + //TODO Explore parallel execution contraints + for _, txByte := range req.Txs { + tx, err := app.txDecoder(txByte) + if err != nil { + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + } + ctx, err = app.anteHandler(ctx, tx, false) + if err != nil { + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + } + } return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} } From 5be329f178d28bf423df448764c3784687210f8b Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 10:31:58 -0500 Subject: [PATCH 060/196] more baseapp integration --- baseapp/abci.go | 9 ++- baseapp/baseapp.go | 11 +++ server/types/app.go | 5 +- types/mempool/mempool_test.go | 136 ---------------------------------- types/mempool/stateful.go | 4 +- 5 files changed, 21 insertions(+), 144 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index a65b5bdd3ba8..2aa46601fc9e 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -279,13 +279,14 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { ctx := app.deliverState.ctx - //TODO Explore parallel execution contraints - for _, txByte := range req.Txs { - tx, err := app.txDecoder(txByte) + + for _, txBytes := range req.Txs { + anteCtx, _ := app.cacheTxContext(ctx, txBytes) + tx, err := app.txDecoder(txBytes) if err != nil { return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} } - ctx, err = app.anteHandler(ctx, tx, false) + ctx, err = app.anteHandler(anteCtx, tx, false) if err != nil { return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} } diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 9c542d8586f7..ab0c1cff00e9 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -681,6 +681,17 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re if err != nil { return gInfo, nil, anteEvents, priority, err } + } else if mode == runTxModeDeliver { + // TODO + // Most conservative behavior; if removal of the tx from the mempool fails then DeliverTx + // will fail as well. + // + // Should we be less conservative and still return a successful DeliverTx? + err = app.mempool.Remove(tx.(mempool.Tx)) + if err != nil { + return gInfo, nil, anteEvents, priority, + fmt.Errorf("failed to remove tx from mempool: %w", err) + } } // Create a new Context based off of the existing Context with a MultiStore branch diff --git a/server/types/app.go b/server/types/app.go index d727e2c81509..9ce6c7a9b30e 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -5,7 +5,6 @@ import ( "io" "time" - "github.com/cosmos/gogoproto/grpc" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" @@ -13,6 +12,8 @@ import ( tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" + "github.com/cosmos/gogoproto/grpc" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server/api" "github.com/cosmos/cosmos-sdk/server/config" @@ -54,7 +55,7 @@ type ( // RegisterTendermintService registers the gRPC Query service for tendermint queries. RegisterTendermintService(clientCtx client.Context) - // Return the multistore instance + // CommitMultiStore return the multistore instance CommitMultiStore() sdk.CommitMultiStore } diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 3ef765e51324..c7cf7280a9bc 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -311,142 +311,6 @@ func (s *MempoolTestSuite) TestTxOrder() { } } -func TestTxOrder2(t *testing.T) { - ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 5) - sa := accounts[0].Address - sb := accounts[1].Address - sc := accounts[2].Address - //sd := accounts[3].Address - //se := accounts[4].Address - - tests := []struct { - txs []txSpec - order []int - fail bool - }{ - { - txs: []txSpec{ - {p: 21, n: 4, a: sa}, - {p: 8, n: 3, a: sa}, - {p: 6, n: 2, a: sa}, - {p: 15, n: 1, a: sb}, - {p: 20, n: 1, a: sa}, - }, - order: []int{4, 3, 2, 1, 0}, - }, - { - // a very interesting breaking case - txs: []txSpec{ - {p: 3, n: 0, a: sa}, - {p: 5, n: 1, a: sa}, - {p: 9, n: 2, a: sa}, - {p: 6, n: 0, a: sb}, - {p: 5, n: 1, a: sb}, - {p: 8, n: 2, a: sb}, - }, - order: []int{3, 4, 5, 0, 1, 2}, - }, - { - txs: []txSpec{ - {p: 21, n: 4, a: sa}, - {p: 15, n: 1, a: sb}, - {p: 20, n: 1, a: sa}, - }, - order: []int{2, 0, 1}}, - { - txs: []txSpec{ - {p: 50, n: 3, a: sa}, - {p: 30, n: 2, a: sa}, - {p: 10, n: 1, a: sa}, - {p: 15, n: 1, a: sb}, - {p: 21, n: 2, a: sb}, - }, - order: []int{3, 4, 2, 1, 0}, - }, - { - txs: []txSpec{ - {p: 50, n: 3, a: sa}, - {p: 10, n: 2, a: sa}, - {p: 99, n: 1, a: sa}, - {p: 15, n: 1, a: sb}, - {p: 8, n: 2, a: sb}, - }, - order: []int{2, 3, 1, 0, 4}, - }, - { - txs: []txSpec{ - {p: 30, a: sa, n: 2}, - {p: 20, a: sb, n: 1}, - {p: 15, a: sa, n: 1}, - {p: 10, a: sa, n: 0}, - {p: 8, a: sb, n: 0}, - {p: 6, a: sa, n: 3}, - {p: 4, a: sb, n: 3}, - }, - order: []int{3, 2, 0, 4, 1, 5, 6}, - }, - { - txs: []txSpec{ - {p: 30, multi: []txSpec{ - {n: 2, a: sa}, - {n: 1, a: sc}}}, - {p: 20, a: sb, n: 1}, - {p: 15, a: sa, n: 1}, - {p: 10, a: sa, n: 0}, - {p: 8, a: sb, n: 0}, - {p: 6, a: sa, n: 3}, - {p: 4, a: sb, n: 3}, - {p: 2, a: sc, n: 0}, - {p: 7, a: sc, n: 3}, - }, - //order: []int{3, 2, 4, 1, 6, 7, 0, 5, 8}, - order: []int{3, 2, 4, 1, 6, 7, 0, 8, 5}, - }, - } - for i, tt := range tests { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { - // create fresh mempool - //pool := mempool.NewDefaultMempool() - pool := mempool.NewMemPoolI() - - // create test txs and insert into mempool - for i, ts := range tt.txs { - var tx testTx - if len(ts.multi) == 0 { - tx = testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} - } else { - var nonces []uint64 - var addresses []sdk.AccAddress - for _, ms := range ts.multi { - nonces = append(nonces, uint64(ms.n)) - addresses = append(addresses, ms.a) - } - tx = testTx{ - hash: [32]byte{byte(i)}, - priority: int64(ts.p), - multiNonces: nonces, - multiAddress: addresses, - } - } - - c := ctx.WithPriority(tx.priority) - err := pool.Insert(c, tx) - require.NoError(t, err) - } - - orderedTxs, err := pool.Select(ctx, nil, 10000) - require.NoError(t, err) - var txOrder []int - for _, tx := range orderedTxs { - txOrder = append(txOrder, int(tx.(testTx).hash[0])) - } - require.Equal(t, tt.order, txOrder) - require.NoError(t, validateOrder(orderedTxs)) - }) - } -} - type MempoolTestSuite struct { suite.Suite numTxs int diff --git a/types/mempool/stateful.go b/types/mempool/stateful.go index aa7621a50f3a..612d37a2c43f 100644 --- a/types/mempool/stateful.go +++ b/types/mempool/stateful.go @@ -99,9 +99,9 @@ func (amp *MemPoolI) Insert(ctx sdk.Context, tx Tx) error { } -func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int) ([]Tx, error) { +func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int64) ([]Tx, error) { var selectedTxs []Tx - var txBytes int + var txBytes int64 currentAccount := amp.accountsHeads.Front() for currentAccount != nil { From c25385ade1cefa081b5b88d36f2a7d9a1bf086b1 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 10:39:35 -0500 Subject: [PATCH 061/196] cd simapp && go mod tidy --- simapp/go.mod | 1 + simapp/go.sum | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/simapp/go.mod b/simapp/go.mod index d80103332baf..46432dacb731 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -90,6 +90,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect + github.com/huandu/skiplist v1.2.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index 9814f56ad0c8..abf1dacde8fa 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -482,6 +482,10 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw= +github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= From 1725e8d66d94e071696713e2b8c3c1a63301c27f Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 12:21:35 -0500 Subject: [PATCH 062/196] fixing tests --- baseapp/deliver_tx_test.go | 17 +++++++++++------ baseapp/util_test.go | 21 ++++++++++++--------- runtime/module.go | 9 +++++++-- types/mempool/mempool.go | 11 +++++++++-- x/genutil/gentx.go | 2 +- 5 files changed, 40 insertions(+), 20 deletions(-) diff --git a/baseapp/deliver_tx_test.go b/baseapp/deliver_tx_test.go index 453ddab35c48..fad6b82f739d 100644 --- a/baseapp/deliver_tx_test.go +++ b/baseapp/deliver_tx_test.go @@ -16,6 +16,13 @@ import ( "time" "unsafe" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + dbm "github.com/tendermint/tm-db" + "cosmossdk.io/depinject" "github.com/cosmos/cosmos-sdk/baseapp" baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil" @@ -32,15 +39,11 @@ import ( "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/mempool" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" "github.com/cosmos/gogoproto/jsonpb" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - dbm "github.com/tendermint/tm-db" ) var ( @@ -77,6 +80,7 @@ func setupBaseApp(t *testing.T, options ...func(*baseapp.BaseApp)) *baseapp.Base app.MountStores(capKey1, capKey2) app.SetParamStore(¶mStore{db: dbm.NewMemDB()}) + app.SetMempool(mempool.NewDefaultMempool()) // stores are mounted err := app.LoadLatestVersion() @@ -124,6 +128,7 @@ func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*baseapp.Base builder := txConfig.NewTxBuilder() builder.SetMsgs(msgs...) + builder.SetSignatures(signingtypes.SignatureV2{}) txBytes, err := txConfig.TxEncoder()(builder.GetTx()) require.NoError(t, err) diff --git a/baseapp/util_test.go b/baseapp/util_test.go index 55d6137212c2..633f0422342b 100644 --- a/baseapp/util_test.go +++ b/baseapp/util_test.go @@ -23,6 +23,7 @@ import ( "github.com/cosmos/cosmos-sdk/testutil/mock" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" _ "github.com/cosmos/cosmos-sdk/x/auth" _ "github.com/cosmos/cosmos-sdk/x/auth/tx/module" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -139,14 +140,16 @@ func makeTestConfig() depinject.Config { } func makeMinimalConfig() depinject.Config { - return appconfig.Compose(&appv1alpha1.Config{ - Modules: []*appv1alpha1.ModuleConfig{ - { - Name: "runtime", - Config: appconfig.WrapAny(&runtimev1alpha1.Module{ - AppName: "BaseAppApp", - }), + return depinject.Configs( + depinject.Supply(mempool.DefaultMempoolFactory), + appconfig.Compose(&appv1alpha1.Config{ + Modules: []*appv1alpha1.ModuleConfig{ + { + Name: "runtime", + Config: appconfig.WrapAny(&runtimev1alpha1.Module{ + AppName: "BaseAppApp", + }), + }, }, - }, - }) + })) } diff --git a/runtime/module.go b/runtime/module.go index 1f5c8e11a903..0d4c95858367 100644 --- a/runtime/module.go +++ b/runtime/module.go @@ -81,7 +81,7 @@ type appInputs struct { depinject.In Config *runtimev1alpha1.Module - MempoolFn mempool.Factory + MempoolFn mempool.Factory `optional:"true"` App appWrapper Modules map[string]AppModuleWrapper BaseAppOptions []BaseAppOption @@ -95,7 +95,12 @@ func provideAppBuilder(inputs appInputs) *AppBuilder { app := inputs.App app.baseAppOptions = inputs.BaseAppOptions app.baseAppOptions = append(app.baseAppOptions, func(app *baseapp.BaseApp) { - app.SetMempool(inputs.MempoolFn()) + factory := inputs.MempoolFn + if factory == nil { + app.SetMempool(mempool.DefaultMempoolFactory()) + } else { + app.SetMempool(factory()) + } }) app.config = inputs.Config app.ModuleManager = mm diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 985b4aab9b86..00083d402dd4 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -44,7 +44,8 @@ type Mempool interface { type Factory func() Mempool var ( - _ Mempool = (*defaultMempool)(nil) + _ Mempool = (*defaultMempool)(nil) + DefaultMempoolFactory Factory = NewDefaultMempool //ErrMempoolIsFull = fmt.Errorf("mempool is full") ) @@ -181,6 +182,9 @@ func (mp *defaultMempool) Remove(tx Tx) error { if err != nil { return err } + if len(sigs) == 0 { + return fmt.Errorf("attempted to remove a tx with no signatures") + } sig := sigs[0] sender := sig.PubKey.Address().String() nonce := sig.Sequence @@ -188,7 +192,10 @@ func (mp *defaultMempool) Remove(tx Tx) error { sk := txKey{nonce: nonce, sender: sender} priority, ok := mp.scores[sk] if !ok { - return fmt.Errorf("tx %v not found", sk) + //return fmt.Errorf("tx %v not found", sk) + // TODO + // permit this for now + return nil } tk := txKey{nonce: nonce, priority: priority, sender: sender} diff --git a/x/genutil/gentx.go b/x/genutil/gentx.go index f7e4250def1d..1598ccd603a5 100644 --- a/x/genutil/gentx.go +++ b/x/genutil/gentx.go @@ -107,7 +107,7 @@ func DeliverGenTxs( res := deliverTx(abci.RequestDeliverTx{Tx: bz}) if !res.IsOK() { - return nil, fmt.Errorf("failed to execute DelverTx for '%s': %s", genTx, res.Log) + return nil, fmt.Errorf("failed to execute DeliverTx for '%s': %s", genTx, res.Log) } } From 7cf3a5277dbbfe5599895b0330ad1a344e5c5525 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 12:29:16 -0500 Subject: [PATCH 063/196] remove graph impl --- types/mempool/graph.go | 279 ------------------------------------ types/mempool/graph_test.go | 52 ------- 2 files changed, 331 deletions(-) delete mode 100644 types/mempool/graph.go delete mode 100644 types/mempool/graph_test.go diff --git a/types/mempool/graph.go b/types/mempool/graph.go deleted file mode 100644 index 60575a900710..000000000000 --- a/types/mempool/graph.go +++ /dev/null @@ -1,279 +0,0 @@ -package mempool - -import ( - "fmt" - - huandu "github.com/huandu/skiplist" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/signing" -) - -var _ Mempool = (*graph)(nil) - -type node struct { - priority int64 - nonce uint64 - sender string - tx Tx - - nonceNode *huandu.Element - priorityNode *huandu.Element - in map[string]bool -} - -type senderGraph struct { - byNonce *huandu.SkipList - byPriority *huandu.SkipList -} - -// MaxPriorityEdge returns the maximum out of tree priority which the node with -// the given priority and nonce can draw edges to by finding the minimum priority -// in this tree with a lower nonce -func (sg senderGraph) MaxPriorityEdge(priority int64, nonce uint64) int64 { - // optimization: if n is _sufficiently_ small we can just iterate the nonce list - // otherwise we use the skip list by priority - // - min := sg.byPriority.Front() - for min != nil { - n := min.Value.(*node) - if n.priority < priority && n.nonce < nonce { - // the minimum priority in the tree with a lower nonce - return n.priority - } - min = min.Next() - } - // otherwise we can draw to anything - return priority - 1 -} - -func newSenderGraph() senderGraph { - return senderGraph{ - byNonce: huandu.New(huandu.Uint64), - byPriority: huandu.New(huandu.Int64), - } -} - -type graph struct { - priorities *huandu.SkipList - nodes map[string]*node - senderGraphs map[string]senderGraph - iterations int -} - -func (g *graph) Insert(context sdk.Context, tx Tx) error { - sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - if err != nil { - return err - } - - sig := sigs[0] - sender := sig.PubKey.Address().String() - nonce := sig.Sequence - node := &node{priority: context.Priority(), nonce: nonce, sender: sender, tx: tx} - g.AddNode(node) - return nil -} - -func (g *graph) AddNode(n *node) { - g.nodes[n.key()] = n - - n.priorityNode = g.priorities.Set(txKey{priority: n.priority, sender: n.sender, nonce: n.nonce}, n) - sg, ok := g.senderGraphs[n.sender] - if !ok { - sg = newSenderGraph() - g.senderGraphs[n.sender] = sg - } - - n.nonceNode = sg.byNonce.Set(n.nonce, n) - sg.byPriority.Set(n.priority, n) -} - -func (g *graph) Select(txs [][]byte, maxBytes int64) ([]Tx, error) { - // todo collapse multiple iterations into kahns - sorted, err := g.TopologicalSort() - if err != nil { - return nil, err - } - var res []Tx - for _, n := range sorted { - res = append(res, n.tx) - } - return res, nil -} - -func (g *graph) CountTx() int { - return len(g.nodes) -} - -func (g *graph) Remove(tx Tx) error { - //TODO implement me - panic("implement me") -} - -func (n node) key() string { - return fmt.Sprintf("%d-%s-%d", n.priority, n.sender, n.nonce) -} - -func (n node) String() string { - return n.key() -} - -func NewGraph() Mempool { - return &graph{ - nodes: make(map[string]*node), - priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), - senderGraphs: make(map[string]senderGraph), - } -} - -func (g *graph) ContainsNode(n node) bool { - _, ok := g.nodes[n.key()] - return ok -} - -func (g *graph) TopologicalSort() ([]*node, error) { - in, out := g.DrawPriorityEdges() - var edgeless []*node - for _, n := range g.nodes { - g.iterations++ - nk := n.key() - if _, ok := in[nk]; !ok || len(in[nk]) == 0 { - edgeless = append(edgeless, n) - } - } - sorted, err := g.kahns(edgeless, in, out) - if err != nil { - return nil, err - } - return sorted, nil -} - -func nodeEdge(n *node, m *node) string { - return fmt.Sprintf("%s->%s", n.key(), m.key()) -} - -/* - -DFS -L ← Empty list that will contain the sorted nodes -while exists nodes without a permanent mark do - select an unmarked node n - visit(n) - -function visit(node n) - if n has a permanent mark then - return - if n has a temporary mark then - stop (not a DAG) - - mark n with a temporary mark - - for each node m with an edge from n to m do - visit(m) - - remove temporary mark from n - mark n with a permanent mark - add n to head of L - -*/ - -type nodeEdges map[string]map[string]bool - -// DrawPriorityEdges is O(n^2) hopefully n is not too large -// Given n_a, need an answer the question: -// "Is there node n_b in my sender tree with n_b.nonce < n_a.nonce AND n_b.priority < n_a.priority?" -// If yes, don't draw any priority edges to nodes (or possibly just to nodes with a priority < n_b.priority) -func (g *graph) DrawPriorityEdges() (in nodeEdges, out nodeEdges) { - pn := g.priorities.Front() - in = make(nodeEdges) - out = make(nodeEdges) - - for pn != nil { - g.iterations++ - n := pn.Value.(*node) - nk := n.key() - out[nk] = make(map[string]bool) - - // draw nonce edge (if there is one) - if n.nonceNode.Next() != nil { - m := n.nonceNode.Next().Value.(*node) - mk := m.key() - if _, ok := in[mk]; !ok { - in[mk] = make(map[string]bool) - } - - out[nk][mk] = true - in[mk][nk] = true - } - - // beginning with the next lowest priority node, draw priority edges - maxp := g.senderGraphs[n.sender].MaxPriorityEdge(n.priority, n.nonce) - pm := pn.Next() - for pm != nil { - g.iterations++ - m := pm.Value.(*node) - // skip these nodes - if m.priority > maxp { - pm = pm.Next() - continue - } - mk := m.key() - if _, ok := in[mk]; !ok { - in[mk] = make(map[string]bool) - } - - if n.sender != m.sender { - out[nk][mk] = true - in[mk][nk] = true - } - - pm = pm.Next() - } - pn = pn.Next() - } - - return in, out -} - -func (g *graph) kahns(edgeless []*node, inEdges nodeEdges, outEdges nodeEdges) ([]*node, error) { - var sorted []*node - - /* - L ← Empty list that will contain the sorted elements - S ← Set of all nodes with no incoming edge - - while S is not empty do - remove a node n from S - add n to L - for each node m with an edge e from n to m do - remove edge e from the graph - if m has no other incoming edges then - insert m into S - - if graph has edges then - return error (graph has at least one cycle) - else - return L (a topologically sorted order) - */ - - for i := 0; i < len(edgeless) && edgeless[i] != nil; i++ { - n := edgeless[i] - nk := n.key() - sorted = append(sorted, n) - - for mk, _ := range outEdges[nk] { - g.iterations++ - - delete(outEdges[nk], mk) - delete(inEdges[mk], nk) - - if len(inEdges[mk]) == 0 { - edgeless = append(edgeless, g.nodes[mk]) - } - } - - } - - return sorted, nil -} diff --git a/types/mempool/graph_test.go b/types/mempool/graph_test.go deleted file mode 100644 index a79cc72638ab..000000000000 --- a/types/mempool/graph_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package mempool_test - -import ( - "fmt" - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/mempool" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -func TestDrawEdges(t *testing.T) { - ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 2) - sa := accounts[0].Address - sb := accounts[1].Address - - tests := []struct { - txs []txSpec - order []int - fail bool - }{ - { - txs: []txSpec{ - {p: 21, n: 4, a: sa}, - {p: 8, n: 3, a: sa}, - {p: 6, n: 2, a: sa}, - {p: 15, n: 1, a: sb}, - {p: 20, n: 1, a: sa}, - }, - order: []int{4, 3, 2, 1, 0}, - }, - } - - for i, tt := range tests { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { - graph := mempool.NewGraph() - for _, ts := range tt.txs { - tx := testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} - c := ctx.WithPriority(int64(ts.p)) - require.NoError(t, graph.Insert(c, tx)) - - } - }) - } - -} From 9c4443341b9190e2c3ef44aadf5c9a2c51e5f427 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 12:30:26 -0500 Subject: [PATCH 064/196] Default mempool implementation --- types/mempool/mempool.go | 232 ++++++++++++ types/mempool/mempool_test.go | 670 ++++++++++++++++++++++++++++++++++ types/mempool/stateful.go | 147 ++++++++ 3 files changed, 1049 insertions(+) create mode 100644 types/mempool/mempool.go create mode 100644 types/mempool/mempool_test.go create mode 100644 types/mempool/stateful.go diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go new file mode 100644 index 000000000000..00083d402dd4 --- /dev/null +++ b/types/mempool/mempool.go @@ -0,0 +1,232 @@ +package mempool + +import ( + "fmt" + "math" + + "github.com/cosmos/cosmos-sdk/types" + + huandu "github.com/huandu/skiplist" + + "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +// Tx we define an app-side mempool transaction interface that is as +// minimal as possible, only requiring applications to define the size of the +// transaction to be used when reaping and getting the transaction itself. +// Interface type casting can be used in the actual app-side mempool implementation. +type Tx interface { + types.Tx + + // Size returns the size of the transaction in bytes. + Size() int64 +} + +type Mempool interface { + // Insert attempts to insert a Tx into the app-side mempool returning + // an error upon failure. + Insert(types.Context, Tx) error + + // Select returns the next set of available transactions from the app-side + // mempool, up to maxBytes or until the mempool is empty. The application can + // decide to return transactions from its own mempool, from the incoming + // txs, or some combination of both. + Select(txs [][]byte, maxBytes int64) ([]Tx, error) + + // CountTx returns the number of transactions currently in the mempool. + CountTx() int + + // Remove attempts to remove a transaction from the mempool, returning an error + // upon failure. + Remove(Tx) error +} + +type Factory func() Mempool + +var ( + _ Mempool = (*defaultMempool)(nil) + DefaultMempoolFactory Factory = NewDefaultMempool + //ErrMempoolIsFull = fmt.Errorf("mempool is full") +) + +type defaultMempool struct { + priorities *huandu.SkipList + senders map[string]*huandu.SkipList + scores map[txKey]int64 + iterations int +} + +type txKey struct { + nonce uint64 + priority int64 + sender string +} + +func txKeyLess(a, b interface{}) int { + keyA := a.(txKey) + keyB := b.(txKey) + res := huandu.Int64.Compare(keyA.priority, keyB.priority) + if res != 0 { + return res + } + + res = huandu.String.Compare(keyA.sender, keyB.sender) + if res != 0 { + return res + } + + return huandu.Uint64.Compare(keyA.nonce, keyB.nonce) +} + +func NewDefaultMempool() Mempool { + return &defaultMempool{ + priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), + senders: make(map[string]*huandu.SkipList), + scores: make(map[txKey]int64), + } +} + +func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { + sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + if err != nil { + return err + } + + sig := sigs[0] + sender := sig.PubKey.Address().String() + nonce := sig.Sequence + tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender} + + senderTxs, ok := mp.senders[sender] + // initialize sender mempool if not found + if !ok { + senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { + return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) + })) + mp.senders[sender] = senderTxs + } + + senderTxs.Set(tk, tx) + mp.scores[txKey{nonce: nonce, sender: sender}] = ctx.Priority() + mp.priorities.Set(tk, tx) + + return nil +} + +func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { + var selectedTxs []Tx + var txBytes int64 + senderCursors := make(map[string]*huandu.Element) + + // start with the highest priority sender + priorityNode := mp.priorities.Front() + for priorityNode != nil { + priorityKey := priorityNode.Key().(txKey) + nextHighestPriority, nextPriorityNode := nextPriority(priorityNode) + sender := priorityKey.sender + senderTx := mp.fetchSenderCursor(senderCursors, sender) + + // iterate through the sender's transactions in nonce order + for senderTx != nil { + // time complexity tracking + mp.iterations++ + k := senderTx.Key().(txKey) + + // break if we've reached a transaction with a priority lower than the next highest priority in the pool + if k.priority < nextHighestPriority { + break + } + + mempoolTx, _ := senderTx.Value.(Tx) + // otherwise, select the transaction and continue iteration + selectedTxs = append(selectedTxs, mempoolTx) + if txBytes += mempoolTx.Size(); txBytes >= maxBytes { + return selectedTxs, nil + } + + senderTx = senderTx.Next() + senderCursors[sender] = senderTx + } + + priorityNode = nextPriorityNode + } + + return selectedTxs, nil +} + +func (mp *defaultMempool) fetchSenderCursor(senderCursors map[string]*huandu.Element, sender string) *huandu.Element { + senderTx, ok := senderCursors[sender] + if !ok { + senderTx = mp.senders[sender].Front() + } + return senderTx +} + +func nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { + var np int64 + nextPriorityNode := priorityNode.Next() + if nextPriorityNode != nil { + np = nextPriorityNode.Key().(txKey).priority + } else { + np = math.MinInt64 + } + return np, nextPriorityNode +} + +func (mp *defaultMempool) CountTx() int { + return mp.priorities.Len() +} + +func (mp *defaultMempool) Remove(tx Tx) error { + sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + if err != nil { + return err + } + if len(sigs) == 0 { + return fmt.Errorf("attempted to remove a tx with no signatures") + } + sig := sigs[0] + sender := sig.PubKey.Address().String() + nonce := sig.Sequence + + sk := txKey{nonce: nonce, sender: sender} + priority, ok := mp.scores[sk] + if !ok { + //return fmt.Errorf("tx %v not found", sk) + // TODO + // permit this for now + return nil + } + tk := txKey{nonce: nonce, priority: priority, sender: sender} + + senderTxs, ok := mp.senders[sender] + if !ok { + return fmt.Errorf("sender %s not found", sender) + } + + mp.priorities.Remove(tk) + senderTxs.Remove(tk) + delete(mp.scores, sk) + + return nil +} + +func DebugPrintKeys(mempool Mempool) { + mp := mempool.(*defaultMempool) + n := mp.priorities.Front() + for n != nil { + k := n.Key().(txKey) + fmt.Printf("%s, %d, %d\n", k.sender, k.priority, k.nonce) + n = n.Next() + } +} + +func Iterations(mempool Mempool) int { + switch v := mempool.(type) { + case *defaultMempool: + return v.iterations + case *graph: + return v.iterations + } + panic("unknown mempool type") +} diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go new file mode 100644 index 000000000000..c7cf7280a9bc --- /dev/null +++ b/types/mempool/mempool_test.go @@ -0,0 +1,670 @@ +package mempool_test + +import ( + "fmt" + "math" + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/log" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" + + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/group" +) + +// testPubKey is a dummy implementation of PubKey used for testing. +type testPubKey struct { + address sdk.AccAddress +} + +func (t testPubKey) Reset() { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) String() string { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) ProtoMessage() { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) Address() cryptotypes.Address { + return t.address.Bytes() +} + +func (t testPubKey) Bytes() []byte { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) VerifySignature(msg []byte, sig []byte) bool { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) Equals(key cryptotypes.PubKey) bool { + //TODO implement me + panic("implement me") +} + +func (t testPubKey) Type() string { + //TODO implement me + panic("implement me") +} + +// testTx is a dummy implementation of Tx used for testing. +type testTx struct { + hash [32]byte + priority int64 + nonce uint64 + address sdk.AccAddress +} + +func (tx testTx) GetSigners() []sdk.AccAddress { + panic("implement me") +} + +func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { + panic("GetPubkeys not implemented") +} + +func (tx testTx) GetSignaturesV2() (res []signing2.SignatureV2, err error) { + res = append(res, signing2.SignatureV2{ + PubKey: testPubKey{address: tx.address}, + Data: nil, + Sequence: tx.nonce}) + + return res, nil +} + +var ( + _ sdk.Tx = (*testTx)(nil) + _ mempool.Tx = (*testTx)(nil) + _ signing.SigVerifiableTx = (*testTx)(nil) + _ cryptotypes.PubKey = (*testPubKey)(nil) +) + +func (tx testTx) GetHash() [32]byte { + return tx.hash +} + +func (tx testTx) Size() int64 { + return 1 +} + +func (tx testTx) GetMsgs() []sdk.Msg { + return nil +} + +func (tx testTx) ValidateBasic() error { + return nil +} + +func (tx testTx) String() string { + return fmt.Sprintf("tx a: %s, p: %d, n: %d", tx.address, tx.priority, tx.nonce) +} + +func TestNewStatefulMempool(t *testing.T) { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + + // general test + transactions := simulateManyTx(ctx, 1000) + require.Equal(t, 1000, len(transactions)) + mp := mempool.NewDefaultMempool() + + for _, tx := range transactions { + ctx.WithPriority(rand.Int63()) + err := mp.Insert(ctx, tx.(mempool.Tx)) + require.NoError(t, err) + } + require.Equal(t, 1000, mp.CountTx()) +} + +type txSpec struct { + i int + h int + p int + n int + a sdk.AccAddress +} + +func (tx txSpec) String() string { + return fmt.Sprintf("[tx i: %d, a: %s, p: %d, n: %d]", tx.i, tx.a, tx.p, tx.n) +} + +func TestOutOfOrder(t *testing.T) { + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 2) + sa := accounts[0].Address + sb := accounts[1].Address + + outOfOrders := [][]testTx{ + { + {priority: 20, nonce: 1, address: sa}, + {priority: 21, nonce: 4, address: sa}, + {priority: 15, nonce: 1, address: sb}, + {priority: 8, nonce: 3, address: sa}, + {priority: 6, nonce: 2, address: sa}, + }, + { + {priority: 15, nonce: 1, address: sb}, + {priority: 20, nonce: 1, address: sa}, + {priority: 21, nonce: 4, address: sa}, + {priority: 8, nonce: 3, address: sa}, + {priority: 6, nonce: 2, address: sa}, + }} + + for _, outOfOrder := range outOfOrders { + var mtxs []mempool.Tx + for _, mtx := range outOfOrder { + mtxs = append(mtxs, mtx) + } + require.Error(t, validateOrder(mtxs)) + } + + seed := time.Now().UnixNano() + randomTxs := genRandomTxs(seed, 1000, 10) + var rmtxs []mempool.Tx + for _, rtx := range randomTxs { + rmtxs = append(rmtxs, rtx) + } + + require.Error(t, validateOrder(rmtxs)) +} + +func (s *MempoolTestSuite) TestTxOrder() { + t := s.T() + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 5) + sa := accounts[0].Address + sb := accounts[1].Address + sc := accounts[2].Address + //sd := accounts[3].Address + //se := accounts[4].Address + + tests := []struct { + txs []txSpec + order []int + fail bool + }{ + { + txs: []txSpec{ + {p: 21, n: 4, a: sa}, + {p: 8, n: 3, a: sa}, + {p: 6, n: 2, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 20, n: 1, a: sa}, + }, + order: []int{4, 3, 2, 1, 0}, + }, + { + txs: []txSpec{ + {p: 3, n: 0, a: sa}, + {p: 5, n: 1, a: sa}, + {p: 9, n: 2, a: sa}, + {p: 6, n: 0, a: sb}, + {p: 5, n: 1, a: sb}, + {p: 8, n: 2, a: sb}, + }, + order: []int{3, 4, 5, 0, 1, 2}, + }, + { + txs: []txSpec{ + {p: 21, n: 4, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 20, n: 1, a: sa}, + }, + order: []int{2, 0, 1}}, + { + txs: []txSpec{ + {p: 50, n: 3, a: sa}, + {p: 30, n: 2, a: sa}, + {p: 10, n: 1, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 21, n: 2, a: sb}, + }, + order: []int{3, 4, 2, 1, 0}, + }, + { + txs: []txSpec{ + {p: 50, n: 3, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 99, n: 1, a: sa}, + {p: 15, n: 1, a: sb}, + {p: 8, n: 2, a: sb}, + }, + order: []int{2, 3, 1, 0, 4}, + }, + { + txs: []txSpec{ + {p: 30, a: sa, n: 2}, + {p: 20, a: sb, n: 1}, + {p: 15, a: sa, n: 1}, + {p: 10, a: sa, n: 0}, + {p: 8, a: sb, n: 0}, + {p: 6, a: sa, n: 3}, + {p: 4, a: sb, n: 3}, + }, + order: []int{3, 2, 0, 4, 1, 5, 6}, + }, + { + txs: []txSpec{ + {p: 30, n: 2, a: sa}, + {p: 20, a: sb, n: 1}, + {p: 15, a: sa, n: 1}, + {p: 10, a: sa, n: 0}, + {p: 8, a: sb, n: 0}, + {p: 6, a: sa, n: 3}, + {p: 4, a: sb, n: 3}, + {p: 2, a: sc, n: 0}, + {p: 7, a: sc, n: 3}, + }, + order: []int{3, 2, 0, 4, 1, 5, 6, 7, 8}, + }, + } + for i, tt := range tests { + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + pool := s.mempool + + // create test txs and insert into mempool + for i, ts := range tt.txs { + tx := testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} + c := ctx.WithPriority(tx.priority) + err := pool.Insert(c, tx) + require.NoError(t, err) + } + + orderedTxs, err := pool.Select(nil, 1000) + require.NoError(t, err) + var txOrder []int + for _, tx := range orderedTxs { + txOrder = append(txOrder, int(tx.(testTx).hash[0])) + } + require.Equal(t, tt.order, txOrder) + require.NoError(t, validateOrder(orderedTxs)) + + for _, tx := range orderedTxs { + require.NoError(t, pool.Remove(tx)) + } + + require.Equal(t, 0, pool.CountTx()) + }) + } +} + +type MempoolTestSuite struct { + suite.Suite + numTxs int + numAccounts int + mempool mempool.Mempool +} + +func (s *MempoolTestSuite) resetMempool() { + s.mempool = mempool.NewDefaultMempool() +} + +func (s *MempoolTestSuite) SetupTest() { + s.numTxs = 1000 + s.numAccounts = 100 + s.resetMempool() +} + +func TestMempoolTestSuite(t *testing.T) { + suite.Run(t, new(MempoolTestSuite)) +} + +func (s *MempoolTestSuite) TestRandomTxOrderManyTimes() { + for i := 0; i < 30; i++ { + s.Run("TestRandomGeneratedTxs", func() { + s.TestRandomGeneratedTxs() + }) + s.resetMempool() + s.Run("TestRandomWalkTxs", func() { + s.TestRandomWalkTxs() + }) + s.resetMempool() + } +} + +// validateOrder checks that the txs are ordered by priority and nonce +// in O(n^2) time by checking each tx against all the other txs +func validateOrder(mtxs []mempool.Tx) error { + var itxs []txSpec + for i, mtx := range mtxs { + tx := mtx.(testTx) + itxs = append(itxs, txSpec{p: int(tx.priority), n: int(tx.nonce), a: tx.address, i: i}) + } + + // Given 2 transactions t1 and t2, where t2.p > t1.p but t2.i < t1.i + // Then if t2.sender have the same sender then t2.nonce > t1.nonce + // or + // If t1 and t2 have different senders then there must be some t3 with + // t3.sender == t2.sender and t3.n < t2.n and t3.p <= t1.p + + for _, a := range itxs { + for _, b := range itxs { + // when b is before a + + // when a is before b + if a.i < b.i { + // same sender + if a.a.Equals(b.a) { + // same sender + if a.n == b.n { + return fmt.Errorf("same sender tx have the same nonce\n%v\n%v", a, b) + } + if a.n > b.n { + return fmt.Errorf("same sender tx have wrong nonce order\n%v\n%v", a, b) + } + } else { + // different sender + if a.p < b.p { + // find a tx with same sender as b and lower nonce + found := false + for _, c := range itxs { + if c.a.Equals(b.a) && c.n < b.n && c.p <= a.p { + found = true + break + } + } + if !found { + return fmt.Errorf("different sender tx have wrong order\n%v\n%v", b, a) + } + } + } + } + } + } + return nil +} + +func (s *MempoolTestSuite) TestRandomGeneratedTxs() { + t := s.T() + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + seed := time.Now().UnixNano() + + generated := genRandomTxs(seed, s.numTxs, s.numAccounts) + mp := s.mempool + + for _, otx := range generated { + tx := testTx{hash: otx.hash, priority: otx.priority, nonce: otx.nonce, address: otx.address} + c := ctx.WithPriority(tx.priority) + err := mp.Insert(c, tx) + require.NoError(t, err) + } + + selected, err := mp.Select(nil, 100000) + require.Equal(t, len(generated), len(selected)) + require.NoError(t, err) + + start := time.Now() + require.NoError(t, validateOrder(selected)) + duration := time.Since(start) + + fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", + seed, mempool.Iterations(mp), duration.Milliseconds()) +} + +func (s *MempoolTestSuite) TestRandomWalkTxs() { + t := s.T() + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + + seed := time.Now().UnixNano() + // interesting failing seeds: + // seed := int64(1663971399133628000) + // seed := int64(1663989445512438000) + // + + ordered, shuffled := genOrderedTxs(seed, s.numTxs, s.numAccounts) + mp := s.mempool + + for _, otx := range shuffled { + tx := testTx{hash: otx.hash, priority: otx.priority, nonce: otx.nonce, address: otx.address} + c := ctx.WithPriority(tx.priority) + err := mp.Insert(c, tx) + require.NoError(t, err) + } + + require.Equal(t, s.numTxs, mp.CountTx()) + + selected, err := mp.Select(nil, math.MaxInt) + require.Equal(t, len(ordered), len(selected)) + var orderedStr, selectedStr string + + for i := 0; i < s.numTxs; i++ { + otx := ordered[i] + stx := selected[i].(testTx) + orderedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", + orderedStr, otx.address, otx.priority, otx.nonce, otx.hash[0]) + selectedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", + selectedStr, stx.address, stx.priority, stx.nonce, stx.hash[0]) + } + + require.NoError(t, err) + require.Equal(t, s.numTxs, len(selected)) + + errMsg := fmt.Sprintf("Expected order: %v\nGot order: %v\nSeed: %v", orderedStr, selectedStr, seed) + + //mempool.DebugPrintKeys(mp) + + start := time.Now() + require.NoError(t, validateOrder(selected), errMsg) + duration := time.Since(start) + + /*for i, tx := range selected { + msg := fmt.Sprintf("Failed tx at index %d\n%s", i, errMsg) + require.Equal(t, ordered[i], tx.(testTx), msg) + require.Equal(t, tx.(testTx).priority, ordered[i].priority, msg) + require.Equal(t, tx.(testTx).nonce, ordered[i].nonce, msg) + require.Equal(t, tx.(testTx).address, ordered[i].address, msg) + }*/ + + fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", + seed, mempool.Iterations(mp), duration.Milliseconds()) +} + +func (s *MempoolTestSuite) TestSampleTxs() { + ctxt := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + t := s.T() + s.resetMempool() + mp := s.mempool + delegatorTx, err := unmarshalTx(msgWithdrawDelegatorReward) + + require.NoError(t, err) + require.NoError(t, mp.Insert(ctxt, delegatorTx.(mempool.Tx))) + require.Equal(t, 1, mp.CountTx()) + + proposalTx, err := unmarshalTx(msgMultiSigMsgSubmitProposal) + require.NoError(t, err) + require.NoError(t, mp.Insert(ctxt, proposalTx.(mempool.Tx))) + require.Equal(t, 2, mp.CountTx()) +} + +func genRandomTxs(seed int64, countTx int, countAccount int) (res []testTx) { + maxPriority := 100 + r := rand.New(rand.NewSource(seed)) + accounts := simtypes.RandomAccounts(r, countAccount) + accountNonces := make(map[string]uint64) + for _, account := range accounts { + accountNonces[account.Address.String()] = 0 + } + + for i := 0; i < countTx; i++ { + addr := accounts[r.Intn(countAccount)].Address + priority := int64(r.Intn(maxPriority + 1)) + nonce := accountNonces[addr.String()] + accountNonces[addr.String()] = nonce + 1 + res = append(res, testTx{ + priority: priority, + nonce: nonce, + address: addr, + hash: [32]byte{byte(i)}}) + } + + return res +} + +// since there are multiple valid ordered graph traversals for a given set of txs strict +// validation against the ordered txs generated from this function is not possible as written +func genOrderedTxs(seed int64, maxTx int, numAcc int) (ordered []testTx, shuffled []testTx) { + r := rand.New(rand.NewSource(seed)) + accountNonces := make(map[string]uint64) + prange := 10 + randomAccounts := simtypes.RandomAccounts(r, numAcc) + for _, account := range randomAccounts { + accountNonces[account.Address.String()] = 0 + } + + getRandAccount := func(notAddress string) simtypes.Account { + for { + res := randomAccounts[r.Intn(len(randomAccounts))] + if res.Address.String() != notAddress { + return res + } + } + } + + txCursor := int64(10000) + ptx := testTx{address: getRandAccount("").Address, nonce: 0, priority: txCursor} + samepChain := make(map[string]bool) + for i := 0; i < maxTx; { + var tx testTx + move := r.Intn(5) + switch move { + case 0: + // same sender, less p + nonce := ptx.nonce + 1 + tx = testTx{nonce: nonce, address: ptx.address, priority: txCursor - int64(r.Intn(prange)+1)} + txCursor = tx.priority + case 1: + // same sender, same p + nonce := ptx.nonce + 1 + tx = testTx{nonce: nonce, address: ptx.address, priority: ptx.priority} + case 2: + // same sender, greater p + nonce := ptx.nonce + 1 + tx = testTx{nonce: nonce, address: ptx.address, priority: ptx.priority + int64(r.Intn(prange)+1)} + case 3: + // different sender, less p + sender := getRandAccount(ptx.address.String()).Address + nonce := accountNonces[sender.String()] + 1 + tx = testTx{nonce: nonce, address: sender, priority: txCursor - int64(r.Intn(prange)+1)} + txCursor = tx.priority + case 4: + // different sender, same p + sender := getRandAccount(ptx.address.String()).Address + // disallow generating cycles of same p txs. this is an invalid processing order according to our + // algorithm decision. + if _, ok := samepChain[sender.String()]; ok { + continue + } + nonce := accountNonces[sender.String()] + 1 + tx = testTx{nonce: nonce, address: sender, priority: txCursor} + samepChain[sender.String()] = true + } + tx.hash = [32]byte{byte(i)} + accountNonces[tx.address.String()] = tx.nonce + ordered = append(ordered, tx) + ptx = tx + i++ + if move != 4 { + samepChain = make(map[string]bool) + } + } + + for _, item := range ordered { + tx := testTx{ + priority: item.priority, + nonce: item.nonce, + address: item.address, + hash: item.hash, + } + shuffled = append(shuffled, tx) + } + rand.Shuffle(len(shuffled), func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] }) + return ordered, shuffled +} + +func TestTxOrderN(t *testing.T) { + numTx := 10 + + seed := time.Now().UnixNano() + ordered, shuffled := genOrderedTxs(seed, numTx, 3) + require.Equal(t, numTx, len(ordered)) + require.Equal(t, numTx, len(shuffled)) + + fmt.Println("ordered") + for _, tx := range ordered { + fmt.Printf("%s, %d, %d\n", tx.address, tx.priority, tx.nonce) + } + + fmt.Println("shuffled") + for _, tx := range shuffled { + fmt.Printf("%s, %d, %d\n", tx.address, tx.priority, tx.nonce) + } +} + +func simulateManyTx(ctx sdk.Context, n int) []sdk.Tx { + transactions := make([]sdk.Tx, n) + for i := 0; i < n; i++ { + tx := simulateTx(ctx) + transactions[i] = tx + } + return transactions +} + +func simulateTx(ctx sdk.Context) sdk.Tx { + acc := authtypes.NewEmptyModuleAccount("anaccount") + + s := rand.NewSource(1) + r := rand.New(s) + msg := group.MsgUpdateGroupMembers{ + GroupId: 1, + Admin: "test", + MemberUpdates: []group.MemberRequest{}, + } + fees, _ := simtypes.RandomFees(r, ctx, sdk.NewCoins(sdk.NewCoin("coin", sdk.NewInt(100000000)))) + + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig + accounts := simtypes.RandomAccounts(r, 2) + + tx, _ := simtestutil.GenSignedMockTx( + r, + txGen, + []sdk.Msg{&msg}, + fees, + simtestutil.DefaultGenTxGas, + ctx.ChainID(), + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence()}, + accounts[0].PrivKey, + ) + return tx +} + +func unmarshalTx(txBytes []byte) (sdk.Tx, error) { + cfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}, gov.AppModuleBasic{}) + return cfg.TxConfig.TxJSONDecoder()(txBytes) +} + +var msgWithdrawDelegatorReward = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1lzhlnpahvznwfv4jmay2tgaha5kmz5qxerarrl\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1k2d9ed9vgfuk2m58a2d80q9u6qljkh4vfaqjfq\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1vygmh344ldv9qefss9ek7ggsnxparljlmj56q5\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1ej2es5fjztqjcd4pwa0zyvaevtjd2y5wxxp9gd\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AmbXAy10a0SerEefTYQzqyGQdX5kiTEWJZ1PZKX1oswX\"},\"mode_info\":{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},\"sequence\":\"119\"}],\"fee\":{\"amount\":[{\"denom\":\"uatom\",\"amount\":\"15968\"}],\"gas_limit\":\"638717\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"ji+inUo4xGlN9piRQLdLCeJWa7irwnqzrMVPcmzJyG5y6NPc+ZuNaIc3uvk5NLDJytRB8AHX0GqNETR\\/Q8fz4Q==\"]}") +var msgMultiSigMsgSubmitProposal = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.gov.v1beta1.MsgSubmitProposal\",\"content\":{\"@type\":\"\\/cosmos.distribution.v1beta1.CommunityPoolSpendProposal\",\"title\":\"ATOM \\ud83e\\udd1d Osmosis: Allocate Community Pool to ATOM Liquidity Incentives\",\"description\":\"ATOMs should be the base money of Cosmos, just like ETH is the base money of the entire Ethereum DeFi ecosystem. ATOM is currently well positioned to play this role among Cosmos assets because it has the highest market cap, most liquidity, largest brand, and many integrations with fiat onramps. ATOM is the gateway to Cosmos.\\n\\nIn the Cosmos Hub Port City vision, ATOMs are pitched as equity in the Cosmos Hub. However, this alone is insufficient to establish ATOM as the base currency of the Cosmos ecosystem as a whole. Instead, the ATOM community must work to actively promote the use of ATOMs throughout the Cosmos ecosystem, rather than passively relying on the Hub's reputation to create ATOM's value.\\n\\nIn order to cement the role of ATOMs in Cosmos DeFi, the Cosmos Hub should leverage its community pool to help align incentives with other protocols within the Cosmos ecosystem. We propose beginning this initiative by using the community pool ATOMs to incentivize deep ATOM base pair liquidity pools on the Osmosis Network.\\n\\nOsmosis is the first IBC-enabled DeFi application. Within its 3 weeks of existence, it has already 100x\\u2019d the number of IBC transactions ever created, demonstrating the power of IBC and the ability of the Cosmos SDK to bootstrap DeFi protocols with $100M+ TVL in a short period of time. Since its announcement Osmosis has helped bring renewed attention and interest to Cosmos from the crypto community at large and kickstarted the era of Cosmos DeFi.\\n\\nOsmosis has already helped in establishing ATOM as the Schelling Point of the Cosmos ecosystem. The genesis distribution of OSMO was primarily based on an airdrop to ATOM holders specifically, acknowledging the importance of ATOM to all future projects within the Cosmos. Furthermore, the Osmosis LP rewards currently incentivize ATOMs to be one of the main base pairs of the platform.\\n\\nOsmosis has the ability to incentivize AMM liquidity, a feature not available on any other IBC-enabled DEX. Osmosis already uses its own native OSMO liquidity rewards to incentivize ATOMs to be one of the main base pairs, leading to ~2.2 million ATOMs already providing liquidity on the platform.\\n\\nIn addition to these native OSMO LP Rewards, the platform also includes a feature called \\u201cexternal incentives\\u201d that allows anyone to permissionlessly add additional incentives in any token to the LPs of any AMM pools they wish. You can read more about this mechanism here: https:\\/\\/medium.com\\/osmosis\\/osmosis-liquidity-mining-101-2fa58d0e9d4d#f413 . Pools containing Cosmos assets such as AKT and XPRT are already planned to receive incentives from their respective community pools and\\/or foundations.\\n\\nWe propose the Cosmos Hub dedicate 100,000 ATOMs from its Community Pool to be allocated towards liquidity incentives on Osmosis over the next 3 months. This community fund proposal will transfer 100,000 ATOMs to a multisig group who will then allocate the ATOMs to bonded liquidity gauges on Osmosis on a biweekly basis, according to direction given by Cosmos Hub governance. For simplicity, we propose setting the liquidity incentives to initially point to Osmosis Pool #1, the ATOM\\/OSMO pool, which is the pool with by far the highest TVL and Volume. Cosmos Hub governance can then use Text Proposals to further direct the multisig members to reallocate incentives to new pools.\\n\\nThe multisig will consist of a 2\\/3 key holder set consisting of the following individuals whom have all agreed to participate in this process shall this proposal pass:\\n\\n- Zaki Manian\\n- Federico Kunze\\n- Marko Baricevic\\n\\nThis is one small step for the Hub, but one giant leap for ATOM-aligned.\\n\",\"recipient\":\"cosmos157n0d38vwn5dvh64rc39q3lyqez0a689g45rkc\",\"amount\":[{\"denom\":\"uatom\",\"amount\":\"100000000000\"}]},\"initial_deposit\":[{\"denom\":\"uatom\",\"amount\":\"64000000\"}],\"proposer\":\"cosmos1ey69r37gfxvxg62sh4r0ktpuc46pzjrmz29g45\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.multisig.LegacyAminoPubKey\",\"threshold\":2,\"public_keys\":[{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AldOvgv8dU9ZZzuhGydQD5FYreLhfhoBgrDKi8ZSTbCQ\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AxUMR\\/GKoycWplR+2otzaQZ9zhHRQWJFt3h1bPg1ltha\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AlI9yVj2Aejow6bYl2nTRylfU+9LjQLEl3keq0sERx9+\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"A0UvHPcvCCaIoFY9Ygh0Pxq9SZTAWtduOyinit\\/8uo+Q\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"As7R9fDUnwsUVLDr1cxspp+cY9UfXfUf7i9\\/w+N0EzKA\"}]},\"mode_info\":{\"multi\":{\"bitarray\":{\"extra_bits_stored\":5,\"elems\":\"SA==\"},\"mode_infos\":[{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}}]}},\"sequence\":\"102\"}],\"fee\":{\"amount\":[],\"gas_limit\":\"10000000\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"CkB\\/KKWTFntEWbg1A0vu7DCHffJ4x4db\\/EI8dIVzRFFW7iuZBzvq+jYBtrcTlVpEVfmCY3ggIMnWfbMbb1egIlYbCkAmDf6Eaj1NbyXY8JZZtYAX3Qj81ZuKZUBeLW1ZvH1XqAg9sl\\/sqpLMnsJzKfmqEXvhoMwu1YxcSzrY6CJfuYL6\"]}") diff --git a/types/mempool/stateful.go b/types/mempool/stateful.go new file mode 100644 index 000000000000..612d37a2c43f --- /dev/null +++ b/types/mempool/stateful.go @@ -0,0 +1,147 @@ +package mempool + +import ( + "bytes" + "crypto/sha256" + "fmt" + + huandu "github.com/huandu/skiplist" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +// The complexity is O(log(N)). Implementation +type statefullPriorityKey struct { + hash [32]byte + priority int64 + nonce uint64 +} + +type accountsHeadsKey struct { + sender string + priority int64 + hash [32]byte +} + +type AccountMemPool struct { + transactions *huandu.SkipList + currentKey accountsHeadsKey + currentItem *huandu.Element + sender string +} + +// Push cannot be executed in the middle of a select +func (amp *AccountMemPool) Push(ctx sdk.Context, key statefullPriorityKey, tx Tx) { + amp.transactions.Set(key, tx) + amp.currentItem = amp.transactions.Back() + newKey := amp.currentItem.Key().(statefullPriorityKey) + amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} +} + +func (amp *AccountMemPool) Pop() *Tx { + if amp.currentItem == nil { + return nil + } + itemToPop := amp.currentItem + amp.currentItem = itemToPop.Prev() + if amp.currentItem != nil { + newKey := amp.currentItem.Key().(statefullPriorityKey) + amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} + } else { + amp.currentKey = accountsHeadsKey{} + } + tx := itemToPop.Value.(Tx) + return &tx +} + +type MemPoolI struct { + accountsHeads *huandu.SkipList + senders map[string]*AccountMemPool +} + +func NewMemPoolI() MemPoolI { + return MemPoolI{ + accountsHeads: huandu.New(huandu.LessThanFunc(priorityHuanduLess)), + senders: make(map[string]*AccountMemPool), + } +} + +func (amp *MemPoolI) Insert(ctx sdk.Context, tx Tx) error { + senders := tx.(signing.SigVerifiableTx).GetSigners() + nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + + if err != nil { + return err + } else if len(senders) != len(nonces) { + return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) + } + sender := senders[0].String() + nonce := nonces[0].Sequence + + accountMeempool, ok := amp.senders[sender] + if !ok { + accountMeempool = &AccountMemPool{ + transactions: huandu.New(huandu.LessThanFunc(nonceHuanduLess)), + sender: sender, + } + } + hash := sha256.Sum256(senders[0].Bytes()) + key := statefullPriorityKey{hash: hash, nonce: nonce, priority: ctx.Priority()} + + prevKey := accountMeempool.currentKey + accountMeempool.Push(ctx, key, tx) + + amp.accountsHeads.Remove(prevKey) + amp.accountsHeads.Set(accountMeempool.currentKey, accountMeempool) + amp.senders[sender] = accountMeempool + return nil + +} + +func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int64) ([]Tx, error) { + var selectedTxs []Tx + var txBytes int64 + + currentAccount := amp.accountsHeads.Front() + for currentAccount != nil { + accountMemPool := currentAccount.Value.(*AccountMemPool) + //currentTx := accountMemPool.transactions.Front() + prevKey := accountMemPool.currentKey + tx := accountMemPool.Pop() + if tx == nil { + return selectedTxs, nil + } + mempoolTx := *tx + selectedTxs = append(selectedTxs, mempoolTx) + if txBytes += mempoolTx.Size(); txBytes >= maxBytes { + return selectedTxs, nil + } + + amp.accountsHeads.Remove(prevKey) + amp.accountsHeads.Set(accountMemPool.currentKey, accountMemPool) + currentAccount = amp.accountsHeads.Front() + } + return selectedTxs, nil +} + +func priorityHuanduLess(a, b interface{}) int { + keyA := a.(accountsHeadsKey) + keyB := b.(accountsHeadsKey) + if keyA.priority == keyB.priority { + return bytes.Compare(keyA.hash[:], keyB.hash[:]) + } else { + if keyA.priority < keyB.priority { + return -1 + } else { + return 1 + } + } +} + +func nonceHuanduLess(a, b interface{}) int { + keyA := a.(statefullPriorityKey) + keyB := b.(statefullPriorityKey) + uint64Compare := huandu.Uint64 + return uint64Compare.Compare(keyA.nonce, keyB.nonce) +} From 15834d7e4854f64d4b66b70e31854769fb826f56 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 12:34:31 -0500 Subject: [PATCH 065/196] Update go.mod --- go.mod | 1 + go.sum | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/go.mod b/go.mod index dab3baf3719e..b554ed418b1e 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/hashicorp/go-getter v1.6.2 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 + github.com/huandu/skiplist v1.2.0 github.com/improbable-eng/grpc-web v0.15.0 github.com/jhump/protoreflect v1.12.1-0.20220721211354-060cc04fc18b github.com/magiconair/properties v1.8.6 diff --git a/go.sum b/go.sum index b34c7f26a2b4..96ce39b4d5c1 100644 --- a/go.sum +++ b/go.sum @@ -487,6 +487,10 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw= +github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= From 7457271c5dc1d685df6a3cd52fae01c0104feb77 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 12:43:20 -0500 Subject: [PATCH 066/196] Remove moved file --- types/mempool.go | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 types/mempool.go diff --git a/types/mempool.go b/types/mempool.go deleted file mode 100644 index 1ea0b4da9c0c..000000000000 --- a/types/mempool.go +++ /dev/null @@ -1,31 +0,0 @@ -package types - -// MempoolTx we define an app-side mempool transaction interface that is as -// minimal as possible, only requiring applications to define the size of the -// transaction to be used when reaping and getting the transaction itself. -// Interface type casting can be used in the actual app-side mempool implementation. -type MempoolTx interface { - Tx - - // Size returns the size of the transaction in bytes. - Size() int -} - -type Mempool interface { - // Insert attempts to insert a MempoolTx into the app-side mempool returning - // an error upon failure. - Insert(Context, MempoolTx) error - - // Select returns the next set of available transactions from the app-side - // mempool, up to maxBytes or until the mempool is empty. The application can - // decide to return transactions from its own mempool, from the incoming - // txs, or some combination of both. - Select(ctx Context, txs [][]byte, maxBytes int) ([]MempoolTx, error) - - // CountTx returns the number of transactions currently in the mempool. - CountTx() int - - // Remove attempts to remove a transaction from the mempool, returning an error - // upon failure. - Remove(Context, MempoolTx) error -} From b0b8b27f249622c886257462e0827c9fbbd07a79 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 12:51:35 -0500 Subject: [PATCH 067/196] backporting from integration branch --- baseapp/baseapp.go | 5 +++-- simapp/go.mod | 1 + simapp/go.sum | 4 ++++ types/mempool/mempool.go | 2 -- x/auth/tx/builder.go | 12 +++++++++--- x/auth/tx/decoder.go | 1 + 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 247099390985..e8e1b037a3d7 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -12,6 +12,7 @@ import ( dbm "github.com/tendermint/tm-db" "golang.org/x/exp/maps" + "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/gogoproto/proto" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -56,7 +57,7 @@ type BaseApp struct { //nolint: maligned interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx - mempool sdk.Mempool // application side mempool + mempool mempool.Mempool // application side mempool anteHandler sdk.AnteHandler // ante handler for fee and auth postHandler sdk.AnteHandler // post handler, optional, e.g. for tips initChainer sdk.InitChainer // initialize state with validators and state blob @@ -675,7 +676,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re // TODO remove nil check when implemented if mode == runTxModeCheck && app.mempool != nil { - err = app.mempool.Insert(ctx, tx.(sdk.MempoolTx)) + err = app.mempool.Insert(ctx, tx.(mempool.Tx)) if err != nil { return gInfo, nil, anteEvents, priority, err } diff --git a/simapp/go.mod b/simapp/go.mod index d80103332baf..46432dacb731 100644 --- a/simapp/go.mod +++ b/simapp/go.mod @@ -90,6 +90,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect + github.com/huandu/skiplist v1.2.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/simapp/go.sum b/simapp/go.sum index 9814f56ad0c8..abf1dacde8fa 100644 --- a/simapp/go.sum +++ b/simapp/go.sum @@ -482,6 +482,10 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw= +github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 00083d402dd4..9006fefef4f4 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -225,8 +225,6 @@ func Iterations(mempool Mempool) int { switch v := mempool.(type) { case *defaultMempool: return v.iterations - case *graph: - return v.iterations } panic("unknown mempool type") } diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 9579865d9fd2..8408acac8667 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -1,6 +1,7 @@ package tx import ( + "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/gogoproto/proto" "github.com/cosmos/cosmos-sdk/client" @@ -31,6 +32,8 @@ type wrapper struct { authInfoBz []byte txBodyHasUnknownNonCriticals bool + + txSize int64 } var ( @@ -40,7 +43,7 @@ var ( _ ante.HasExtensionOptionsTx = &wrapper{} _ ExtensionOptionsTxBuilder = &wrapper{} _ tx.TipTx = &wrapper{} - _ sdk.MempoolTx = &wrapper{} + _ mempool.Tx = &wrapper{} ) // ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions. @@ -63,8 +66,11 @@ func newBuilder(cdc codec.Codec) *wrapper { } } -func (w *wrapper) Size() int { - panic("not yet implemented") +func (w *wrapper) Size() int64 { + if w.txSize == 0 { + w.txSize = int64(proto.Size(w.tx)) + } + return w.txSize } func (w *wrapper) GetMsgs() []sdk.Msg { diff --git a/x/auth/tx/decoder.go b/x/auth/tx/decoder.go index 2fef6312b994..8b821bc00320 100644 --- a/x/auth/tx/decoder.go +++ b/x/auth/tx/decoder.go @@ -70,6 +70,7 @@ func DefaultTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder { tx: theTx, bodyBz: raw.BodyBytes, authInfoBz: raw.AuthInfoBytes, + txSize: int64(len(txBytes)), txBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals, }, nil } From 5eac4b7140c662df1208ba1d706033e65097d031 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 12:53:29 -0500 Subject: [PATCH 068/196] cd tests && go mod tidy --- tests/go.mod | 1 + tests/go.sum | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/tests/go.mod b/tests/go.mod index 1796abb2164a..0299cce2296d 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -89,6 +89,7 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect + github.com/huandu/skiplist v1.2.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/tests/go.sum b/tests/go.sum index 98efb3c5b3d4..e7e81396b018 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -484,6 +484,10 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw= +github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= From 834f85f30b2fb0bf972363fed2970a8daab83494 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 13:25:03 -0500 Subject: [PATCH 069/196] test refactor --- types/mempool/mempool.go | 14 +++-- types/mempool/mempool_test.go | 112 ++++++++++++++++++---------------- 2 files changed, 67 insertions(+), 59 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 9006fefef4f4..6fda5f60b0aa 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -41,11 +41,14 @@ type Mempool interface { Remove(Tx) error } +type ErrTxNotFound struct { + error +} + type Factory func() Mempool var ( - _ Mempool = (*defaultMempool)(nil) - DefaultMempoolFactory Factory = NewDefaultMempool + _ Mempool = (*defaultMempool)(nil) //ErrMempoolIsFull = fmt.Errorf("mempool is full") ) @@ -78,6 +81,8 @@ func txKeyLess(a, b interface{}) int { return huandu.Uint64.Compare(keyA.nonce, keyB.nonce) } +// NewDefaultMempool returns the SDK's default mempool implementation which returns txs in a partial order +// by 2 dimensions; priority, and sender-nonce. func NewDefaultMempool() Mempool { return &defaultMempool{ priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), @@ -192,10 +197,7 @@ func (mp *defaultMempool) Remove(tx Tx) error { sk := txKey{nonce: nonce, sender: sender} priority, ok := mp.scores[sk] if !ok { - //return fmt.Errorf("tx %v not found", sk) - // TODO - // permit this for now - return nil + return ErrTxNotFound{fmt.Errorf("tx %v not found in mempool", tx)} } tk := txKey{nonce: nonce, priority: priority, sender: sender} diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index c7cf7280a9bc..7d7b711ff0b5 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -14,18 +14,17 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - signing2 "github.com/cosmos/cosmos-sdk/types/tx/signing" + txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/cosmos-sdk/x/distribution" + disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/gov" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/mempool" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/group" ) // testPubKey is a dummy implementation of PubKey used for testing. @@ -88,8 +87,8 @@ func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { panic("GetPubkeys not implemented") } -func (tx testTx) GetSignaturesV2() (res []signing2.SignatureV2, err error) { - res = append(res, signing2.SignatureV2{ +func (tx testTx) GetSignaturesV2() (res []txsigning.SignatureV2, err error) { + res = append(res, txsigning.SignatureV2{ PubKey: testPubKey{address: tx.address}, Data: nil, Sequence: tx.nonce}) @@ -124,20 +123,38 @@ func (tx testTx) String() string { return fmt.Sprintf("tx a: %s, p: %d, n: %d", tx.address, tx.priority, tx.nonce) } -func TestNewStatefulMempool(t *testing.T) { +func TestDefaultMempool(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 10) + txCount := 1000 + var txs []testTx - // general test - transactions := simulateManyTx(ctx, 1000) - require.Equal(t, 1000, len(transactions)) + for i := 0; i < txCount; i++ { + acc := accounts[i%len(accounts)] + tx := testTx{ + address: acc.Address, + priority: rand.Int63(), + } + txs = append(txs, tx) + } + + // same sender-nonce just overwrites a tx mp := mempool.NewDefaultMempool() + for _, tx := range txs { + ctx.WithPriority(tx.priority) + err := mp.Insert(ctx, tx) + require.NoError(t, err) + } + require.Equal(t, len(accounts), mp.CountTx()) - for _, tx := range transactions { - ctx.WithPriority(rand.Int63()) - err := mp.Insert(ctx, tx.(mempool.Tx)) + // distinct sender-nonce should not overwrite a tx + mp = mempool.NewDefaultMempool() + for i, tx := range txs { + tx.nonce = uint64(i) + err := mp.Insert(ctx, tx) require.NoError(t, err) } - require.Equal(t, 1000, mp.CountTx()) + require.Equal(t, txCount, mp.CountTx()) } type txSpec struct { @@ -333,7 +350,7 @@ func TestMempoolTestSuite(t *testing.T) { } func (s *MempoolTestSuite) TestRandomTxOrderManyTimes() { - for i := 0; i < 30; i++ { + for i := 0; i < 3; i++ { s.Run("TestRandomGeneratedTxs", func() { s.TestRandomGeneratedTxs() }) @@ -623,44 +640,6 @@ func TestTxOrderN(t *testing.T) { } } -func simulateManyTx(ctx sdk.Context, n int) []sdk.Tx { - transactions := make([]sdk.Tx, n) - for i := 0; i < n; i++ { - tx := simulateTx(ctx) - transactions[i] = tx - } - return transactions -} - -func simulateTx(ctx sdk.Context) sdk.Tx { - acc := authtypes.NewEmptyModuleAccount("anaccount") - - s := rand.NewSource(1) - r := rand.New(s) - msg := group.MsgUpdateGroupMembers{ - GroupId: 1, - Admin: "test", - MemberUpdates: []group.MemberRequest{}, - } - fees, _ := simtypes.RandomFees(r, ctx, sdk.NewCoins(sdk.NewCoin("coin", sdk.NewInt(100000000)))) - - txGen := moduletestutil.MakeTestEncodingConfig().TxConfig - accounts := simtypes.RandomAccounts(r, 2) - - tx, _ := simtestutil.GenSignedMockTx( - r, - txGen, - []sdk.Msg{&msg}, - fees, - simtestutil.DefaultGenTxGas, - ctx.ChainID(), - []uint64{acc.GetAccountNumber()}, - []uint64{acc.GetSequence()}, - accounts[0].PrivKey, - ) - return tx -} - func unmarshalTx(txBytes []byte) (sdk.Tx, error) { cfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}, gov.AppModuleBasic{}) return cfg.TxConfig.TxJSONDecoder()(txBytes) @@ -668,3 +647,30 @@ func unmarshalTx(txBytes []byte) (sdk.Tx, error) { var msgWithdrawDelegatorReward = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1lzhlnpahvznwfv4jmay2tgaha5kmz5qxerarrl\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1k2d9ed9vgfuk2m58a2d80q9u6qljkh4vfaqjfq\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1vygmh344ldv9qefss9ek7ggsnxparljlmj56q5\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1ej2es5fjztqjcd4pwa0zyvaevtjd2y5wxxp9gd\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AmbXAy10a0SerEefTYQzqyGQdX5kiTEWJZ1PZKX1oswX\"},\"mode_info\":{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},\"sequence\":\"119\"}],\"fee\":{\"amount\":[{\"denom\":\"uatom\",\"amount\":\"15968\"}],\"gas_limit\":\"638717\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"ji+inUo4xGlN9piRQLdLCeJWa7irwnqzrMVPcmzJyG5y6NPc+ZuNaIc3uvk5NLDJytRB8AHX0GqNETR\\/Q8fz4Q==\"]}") var msgMultiSigMsgSubmitProposal = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.gov.v1beta1.MsgSubmitProposal\",\"content\":{\"@type\":\"\\/cosmos.distribution.v1beta1.CommunityPoolSpendProposal\",\"title\":\"ATOM \\ud83e\\udd1d Osmosis: Allocate Community Pool to ATOM Liquidity Incentives\",\"description\":\"ATOMs should be the base money of Cosmos, just like ETH is the base money of the entire Ethereum DeFi ecosystem. ATOM is currently well positioned to play this role among Cosmos assets because it has the highest market cap, most liquidity, largest brand, and many integrations with fiat onramps. ATOM is the gateway to Cosmos.\\n\\nIn the Cosmos Hub Port City vision, ATOMs are pitched as equity in the Cosmos Hub. However, this alone is insufficient to establish ATOM as the base currency of the Cosmos ecosystem as a whole. Instead, the ATOM community must work to actively promote the use of ATOMs throughout the Cosmos ecosystem, rather than passively relying on the Hub's reputation to create ATOM's value.\\n\\nIn order to cement the role of ATOMs in Cosmos DeFi, the Cosmos Hub should leverage its community pool to help align incentives with other protocols within the Cosmos ecosystem. We propose beginning this initiative by using the community pool ATOMs to incentivize deep ATOM base pair liquidity pools on the Osmosis Network.\\n\\nOsmosis is the first IBC-enabled DeFi application. Within its 3 weeks of existence, it has already 100x\\u2019d the number of IBC transactions ever created, demonstrating the power of IBC and the ability of the Cosmos SDK to bootstrap DeFi protocols with $100M+ TVL in a short period of time. Since its announcement Osmosis has helped bring renewed attention and interest to Cosmos from the crypto community at large and kickstarted the era of Cosmos DeFi.\\n\\nOsmosis has already helped in establishing ATOM as the Schelling Point of the Cosmos ecosystem. The genesis distribution of OSMO was primarily based on an airdrop to ATOM holders specifically, acknowledging the importance of ATOM to all future projects within the Cosmos. Furthermore, the Osmosis LP rewards currently incentivize ATOMs to be one of the main base pairs of the platform.\\n\\nOsmosis has the ability to incentivize AMM liquidity, a feature not available on any other IBC-enabled DEX. Osmosis already uses its own native OSMO liquidity rewards to incentivize ATOMs to be one of the main base pairs, leading to ~2.2 million ATOMs already providing liquidity on the platform.\\n\\nIn addition to these native OSMO LP Rewards, the platform also includes a feature called \\u201cexternal incentives\\u201d that allows anyone to permissionlessly add additional incentives in any token to the LPs of any AMM pools they wish. You can read more about this mechanism here: https:\\/\\/medium.com\\/osmosis\\/osmosis-liquidity-mining-101-2fa58d0e9d4d#f413 . Pools containing Cosmos assets such as AKT and XPRT are already planned to receive incentives from their respective community pools and\\/or foundations.\\n\\nWe propose the Cosmos Hub dedicate 100,000 ATOMs from its Community Pool to be allocated towards liquidity incentives on Osmosis over the next 3 months. This community fund proposal will transfer 100,000 ATOMs to a multisig group who will then allocate the ATOMs to bonded liquidity gauges on Osmosis on a biweekly basis, according to direction given by Cosmos Hub governance. For simplicity, we propose setting the liquidity incentives to initially point to Osmosis Pool #1, the ATOM\\/OSMO pool, which is the pool with by far the highest TVL and Volume. Cosmos Hub governance can then use Text Proposals to further direct the multisig members to reallocate incentives to new pools.\\n\\nThe multisig will consist of a 2\\/3 key holder set consisting of the following individuals whom have all agreed to participate in this process shall this proposal pass:\\n\\n- Zaki Manian\\n- Federico Kunze\\n- Marko Baricevic\\n\\nThis is one small step for the Hub, but one giant leap for ATOM-aligned.\\n\",\"recipient\":\"cosmos157n0d38vwn5dvh64rc39q3lyqez0a689g45rkc\",\"amount\":[{\"denom\":\"uatom\",\"amount\":\"100000000000\"}]},\"initial_deposit\":[{\"denom\":\"uatom\",\"amount\":\"64000000\"}],\"proposer\":\"cosmos1ey69r37gfxvxg62sh4r0ktpuc46pzjrmz29g45\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.multisig.LegacyAminoPubKey\",\"threshold\":2,\"public_keys\":[{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AldOvgv8dU9ZZzuhGydQD5FYreLhfhoBgrDKi8ZSTbCQ\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AxUMR\\/GKoycWplR+2otzaQZ9zhHRQWJFt3h1bPg1ltha\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AlI9yVj2Aejow6bYl2nTRylfU+9LjQLEl3keq0sERx9+\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"A0UvHPcvCCaIoFY9Ygh0Pxq9SZTAWtduOyinit\\/8uo+Q\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"As7R9fDUnwsUVLDr1cxspp+cY9UfXfUf7i9\\/w+N0EzKA\"}]},\"mode_info\":{\"multi\":{\"bitarray\":{\"extra_bits_stored\":5,\"elems\":\"SA==\"},\"mode_infos\":[{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}}]}},\"sequence\":\"102\"}],\"fee\":{\"amount\":[],\"gas_limit\":\"10000000\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"CkB\\/KKWTFntEWbg1A0vu7DCHffJ4x4db\\/EI8dIVzRFFW7iuZBzvq+jYBtrcTlVpEVfmCY3ggIMnWfbMbb1egIlYbCkAmDf6Eaj1NbyXY8JZZtYAX3Qj81ZuKZUBeLW1ZvH1XqAg9sl\\/sqpLMnsJzKfmqEXvhoMwu1YxcSzrY6CJfuYL6\"]}") + +func (csp *disttypes.CommunityPoolSpendProposal) GetTitle() string { return csp.Title } + +// GetDescription returns the description of a community pool spend proposal. +func (csp *disttypes.CommunityPoolSpendProposal) GetDescription() string { return csp.Description } + +// GetDescription returns the routing key of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) ProposalRoute() string { return RouterKey } + +// ProposalType returns the type of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) ProposalType() string { return ProposalTypeCommunityPoolSpend } + +// ValidateBasic runs basic stateless validity checks +func (csp *CommunityPoolSpendProposal) ValidateBasic() error { + err := govtypes.ValidateAbstract(csp) + if err != nil { + return err + } + if !csp.Amount.IsValid() { + return ErrInvalidProposalAmount + } + if csp.Recipient == "" { + return ErrEmptyProposalRecipient + } + + return nil +} From fbe1bb4ac999e89b6d065365fe01fb8b2697c57c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 14:46:52 -0500 Subject: [PATCH 070/196] resolve conflicts --- baseapp/baseapp.go | 1 + x/auth/tx/builder.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 53ee004d4a48..0160e1de7115 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -20,6 +20,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/mempool" ) const ( diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 8408acac8667..d224a3df42e0 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -68,7 +68,8 @@ func newBuilder(cdc codec.Codec) *wrapper { func (w *wrapper) Size() int64 { if w.txSize == 0 { - w.txSize = int64(proto.Size(w.tx)) + psz := proto.Size(w.tx) + w.txSize = int64(psz) } return w.txSize } From ffe535dd100a8ed57e86e96069c845f79ad4613e Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 15:20:11 -0500 Subject: [PATCH 071/196] mempool tests passing --- types/mempool/mempool_test.go | 40 ++++--------------------- x/distribution/types/proposal.go | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 7d7b711ff0b5..fca2484fc9be 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -7,24 +7,22 @@ import ( "testing" "time" - "github.com/stretchr/testify/suite" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/cosmos-sdk/x/distribution" disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/gov" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/mempool" - moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) // testPubKey is a dummy implementation of PubKey used for testing. @@ -642,35 +640,9 @@ func TestTxOrderN(t *testing.T) { func unmarshalTx(txBytes []byte) (sdk.Tx, error) { cfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}, gov.AppModuleBasic{}) + cfg.InterfaceRegistry.RegisterImplementations((*govtypes.Content)(nil), &disttypes.CommunityPoolSpendProposal{}) return cfg.TxConfig.TxJSONDecoder()(txBytes) } var msgWithdrawDelegatorReward = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1lzhlnpahvznwfv4jmay2tgaha5kmz5qxerarrl\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1k2d9ed9vgfuk2m58a2d80q9u6qljkh4vfaqjfq\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1vygmh344ldv9qefss9ek7ggsnxparljlmj56q5\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1ej2es5fjztqjcd4pwa0zyvaevtjd2y5wxxp9gd\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AmbXAy10a0SerEefTYQzqyGQdX5kiTEWJZ1PZKX1oswX\"},\"mode_info\":{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},\"sequence\":\"119\"}],\"fee\":{\"amount\":[{\"denom\":\"uatom\",\"amount\":\"15968\"}],\"gas_limit\":\"638717\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"ji+inUo4xGlN9piRQLdLCeJWa7irwnqzrMVPcmzJyG5y6NPc+ZuNaIc3uvk5NLDJytRB8AHX0GqNETR\\/Q8fz4Q==\"]}") var msgMultiSigMsgSubmitProposal = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.gov.v1beta1.MsgSubmitProposal\",\"content\":{\"@type\":\"\\/cosmos.distribution.v1beta1.CommunityPoolSpendProposal\",\"title\":\"ATOM \\ud83e\\udd1d Osmosis: Allocate Community Pool to ATOM Liquidity Incentives\",\"description\":\"ATOMs should be the base money of Cosmos, just like ETH is the base money of the entire Ethereum DeFi ecosystem. ATOM is currently well positioned to play this role among Cosmos assets because it has the highest market cap, most liquidity, largest brand, and many integrations with fiat onramps. ATOM is the gateway to Cosmos.\\n\\nIn the Cosmos Hub Port City vision, ATOMs are pitched as equity in the Cosmos Hub. However, this alone is insufficient to establish ATOM as the base currency of the Cosmos ecosystem as a whole. Instead, the ATOM community must work to actively promote the use of ATOMs throughout the Cosmos ecosystem, rather than passively relying on the Hub's reputation to create ATOM's value.\\n\\nIn order to cement the role of ATOMs in Cosmos DeFi, the Cosmos Hub should leverage its community pool to help align incentives with other protocols within the Cosmos ecosystem. We propose beginning this initiative by using the community pool ATOMs to incentivize deep ATOM base pair liquidity pools on the Osmosis Network.\\n\\nOsmosis is the first IBC-enabled DeFi application. Within its 3 weeks of existence, it has already 100x\\u2019d the number of IBC transactions ever created, demonstrating the power of IBC and the ability of the Cosmos SDK to bootstrap DeFi protocols with $100M+ TVL in a short period of time. Since its announcement Osmosis has helped bring renewed attention and interest to Cosmos from the crypto community at large and kickstarted the era of Cosmos DeFi.\\n\\nOsmosis has already helped in establishing ATOM as the Schelling Point of the Cosmos ecosystem. The genesis distribution of OSMO was primarily based on an airdrop to ATOM holders specifically, acknowledging the importance of ATOM to all future projects within the Cosmos. Furthermore, the Osmosis LP rewards currently incentivize ATOMs to be one of the main base pairs of the platform.\\n\\nOsmosis has the ability to incentivize AMM liquidity, a feature not available on any other IBC-enabled DEX. Osmosis already uses its own native OSMO liquidity rewards to incentivize ATOMs to be one of the main base pairs, leading to ~2.2 million ATOMs already providing liquidity on the platform.\\n\\nIn addition to these native OSMO LP Rewards, the platform also includes a feature called \\u201cexternal incentives\\u201d that allows anyone to permissionlessly add additional incentives in any token to the LPs of any AMM pools they wish. You can read more about this mechanism here: https:\\/\\/medium.com\\/osmosis\\/osmosis-liquidity-mining-101-2fa58d0e9d4d#f413 . Pools containing Cosmos assets such as AKT and XPRT are already planned to receive incentives from their respective community pools and\\/or foundations.\\n\\nWe propose the Cosmos Hub dedicate 100,000 ATOMs from its Community Pool to be allocated towards liquidity incentives on Osmosis over the next 3 months. This community fund proposal will transfer 100,000 ATOMs to a multisig group who will then allocate the ATOMs to bonded liquidity gauges on Osmosis on a biweekly basis, according to direction given by Cosmos Hub governance. For simplicity, we propose setting the liquidity incentives to initially point to Osmosis Pool #1, the ATOM\\/OSMO pool, which is the pool with by far the highest TVL and Volume. Cosmos Hub governance can then use Text Proposals to further direct the multisig members to reallocate incentives to new pools.\\n\\nThe multisig will consist of a 2\\/3 key holder set consisting of the following individuals whom have all agreed to participate in this process shall this proposal pass:\\n\\n- Zaki Manian\\n- Federico Kunze\\n- Marko Baricevic\\n\\nThis is one small step for the Hub, but one giant leap for ATOM-aligned.\\n\",\"recipient\":\"cosmos157n0d38vwn5dvh64rc39q3lyqez0a689g45rkc\",\"amount\":[{\"denom\":\"uatom\",\"amount\":\"100000000000\"}]},\"initial_deposit\":[{\"denom\":\"uatom\",\"amount\":\"64000000\"}],\"proposer\":\"cosmos1ey69r37gfxvxg62sh4r0ktpuc46pzjrmz29g45\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.multisig.LegacyAminoPubKey\",\"threshold\":2,\"public_keys\":[{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AldOvgv8dU9ZZzuhGydQD5FYreLhfhoBgrDKi8ZSTbCQ\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AxUMR\\/GKoycWplR+2otzaQZ9zhHRQWJFt3h1bPg1ltha\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AlI9yVj2Aejow6bYl2nTRylfU+9LjQLEl3keq0sERx9+\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"A0UvHPcvCCaIoFY9Ygh0Pxq9SZTAWtduOyinit\\/8uo+Q\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"As7R9fDUnwsUVLDr1cxspp+cY9UfXfUf7i9\\/w+N0EzKA\"}]},\"mode_info\":{\"multi\":{\"bitarray\":{\"extra_bits_stored\":5,\"elems\":\"SA==\"},\"mode_infos\":[{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}}]}},\"sequence\":\"102\"}],\"fee\":{\"amount\":[],\"gas_limit\":\"10000000\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"CkB\\/KKWTFntEWbg1A0vu7DCHffJ4x4db\\/EI8dIVzRFFW7iuZBzvq+jYBtrcTlVpEVfmCY3ggIMnWfbMbb1egIlYbCkAmDf6Eaj1NbyXY8JZZtYAX3Qj81ZuKZUBeLW1ZvH1XqAg9sl\\/sqpLMnsJzKfmqEXvhoMwu1YxcSzrY6CJfuYL6\"]}") - -func (csp *disttypes.CommunityPoolSpendProposal) GetTitle() string { return csp.Title } - -// GetDescription returns the description of a community pool spend proposal. -func (csp *disttypes.CommunityPoolSpendProposal) GetDescription() string { return csp.Description } - -// GetDescription returns the routing key of a community pool spend proposal. -func (csp *CommunityPoolSpendProposal) ProposalRoute() string { return RouterKey } - -// ProposalType returns the type of a community pool spend proposal. -func (csp *CommunityPoolSpendProposal) ProposalType() string { return ProposalTypeCommunityPoolSpend } - -// ValidateBasic runs basic stateless validity checks -func (csp *CommunityPoolSpendProposal) ValidateBasic() error { - err := govtypes.ValidateAbstract(csp) - if err != nil { - return err - } - if !csp.Amount.IsValid() { - return ErrInvalidProposalAmount - } - if csp.Recipient == "" { - return ErrEmptyProposalRecipient - } - - return nil -} diff --git a/x/distribution/types/proposal.go b/x/distribution/types/proposal.go index 925738d3086a..a19e6b8feefd 100644 --- a/x/distribution/types/proposal.go +++ b/x/distribution/types/proposal.go @@ -3,8 +3,58 @@ package types import ( "fmt" "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" +) + +const ( + // ProposalTypeCommunityPoolSpend defines the type for a CommunityPoolSpendProposal + ProposalTypeCommunityPoolSpend = "CommunityPoolSpend" ) +// Assert CommunityPoolSpendProposal implements govtypes.Content at compile-time +var _ govtypes.Content = &CommunityPoolSpendProposal{} + +func init() { + govtypes.RegisterProposalType(ProposalTypeCommunityPoolSpend) +} + +// NewCommunityPoolSpendProposal creates a new community pool spend proposal. +// +//nolint:interfacer +func NewCommunityPoolSpendProposal(title, description string, recipient sdk.AccAddress, amount sdk.Coins) *CommunityPoolSpendProposal { + return &CommunityPoolSpendProposal{title, description, recipient.String(), amount} +} + +// GetTitle returns the title of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) GetTitle() string { return csp.Title } + +// GetDescription returns the description of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) GetDescription() string { return csp.Description } + +// GetDescription returns the routing key of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) ProposalRoute() string { return RouterKey } + +// ProposalType returns the type of a community pool spend proposal. +func (csp *CommunityPoolSpendProposal) ProposalType() string { return ProposalTypeCommunityPoolSpend } + +// ValidateBasic runs basic stateless validity checks +func (csp *CommunityPoolSpendProposal) ValidateBasic() error { + err := govtypes.ValidateAbstract(csp) + if err != nil { + return err + } + if !csp.Amount.IsValid() { + return ErrInvalidProposalAmount + } + if csp.Recipient == "" { + return ErrEmptyProposalRecipient + } + + return nil +} + // String implements the Stringer interface. func (csp CommunityPoolSpendProposal) String() string { var b strings.Builder From 056bca180687319428685f81d8a95ea61789c520 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 15:25:28 -0500 Subject: [PATCH 072/196] remove type conversion --- x/auth/tx/builder.go | 4 ---- x/distribution/types/proposal.go | 22 +--------------------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index d224a3df42e0..5cb5939c6cbe 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -67,10 +67,6 @@ func newBuilder(cdc codec.Codec) *wrapper { } func (w *wrapper) Size() int64 { - if w.txSize == 0 { - psz := proto.Size(w.tx) - w.txSize = int64(psz) - } return w.txSize } diff --git a/x/distribution/types/proposal.go b/x/distribution/types/proposal.go index a19e6b8feefd..84dce599f7cc 100644 --- a/x/distribution/types/proposal.go +++ b/x/distribution/types/proposal.go @@ -4,29 +4,9 @@ import ( "fmt" "strings" - sdk "github.com/cosmos/cosmos-sdk/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" ) -const ( - // ProposalTypeCommunityPoolSpend defines the type for a CommunityPoolSpendProposal - ProposalTypeCommunityPoolSpend = "CommunityPoolSpend" -) - -// Assert CommunityPoolSpendProposal implements govtypes.Content at compile-time -var _ govtypes.Content = &CommunityPoolSpendProposal{} - -func init() { - govtypes.RegisterProposalType(ProposalTypeCommunityPoolSpend) -} - -// NewCommunityPoolSpendProposal creates a new community pool spend proposal. -// -//nolint:interfacer -func NewCommunityPoolSpendProposal(title, description string, recipient sdk.AccAddress, amount sdk.Coins) *CommunityPoolSpendProposal { - return &CommunityPoolSpendProposal{title, description, recipient.String(), amount} -} - // GetTitle returns the title of a community pool spend proposal. func (csp *CommunityPoolSpendProposal) GetTitle() string { return csp.Title } @@ -37,7 +17,7 @@ func (csp *CommunityPoolSpendProposal) GetDescription() string { return csp.Desc func (csp *CommunityPoolSpendProposal) ProposalRoute() string { return RouterKey } // ProposalType returns the type of a community pool spend proposal. -func (csp *CommunityPoolSpendProposal) ProposalType() string { return ProposalTypeCommunityPoolSpend } +func (csp *CommunityPoolSpendProposal) ProposalType() string { return "CommunityPoolSpend" } // ValidateBasic runs basic stateless validity checks func (csp *CommunityPoolSpendProposal) ValidateBasic() error { From 78b774c80204462b0e52969bd126dac7a39f59c8 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 15:38:54 -0500 Subject: [PATCH 073/196] test cleanup --- types/mempool/mempool.go | 9 ++++----- types/mempool/mempool_test.go | 23 ++++------------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 6fda5f60b0aa..c8dba6a8495b 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -49,7 +49,6 @@ type Factory func() Mempool var ( _ Mempool = (*defaultMempool)(nil) - //ErrMempoolIsFull = fmt.Errorf("mempool is full") ) type defaultMempool struct { @@ -224,9 +223,9 @@ func DebugPrintKeys(mempool Mempool) { } func Iterations(mempool Mempool) int { - switch v := mempool.(type) { - case *defaultMempool: - return v.iterations + mp, ok := mempool.(*defaultMempool) + if !ok { + panic("unknown mempool type") } - panic("unknown mempool type") + return mp.iterations } diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index fca2484fc9be..42fb10d1d049 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -31,17 +31,14 @@ type testPubKey struct { } func (t testPubKey) Reset() { - //TODO implement me panic("implement me") } func (t testPubKey) String() string { - //TODO implement me panic("implement me") } func (t testPubKey) ProtoMessage() { - //TODO implement me panic("implement me") } @@ -50,22 +47,18 @@ func (t testPubKey) Address() cryptotypes.Address { } func (t testPubKey) Bytes() []byte { - //TODO implement me panic("implement me") } func (t testPubKey) VerifySignature(msg []byte, sig []byte) bool { - //TODO implement me panic("implement me") } func (t testPubKey) Equals(key cryptotypes.PubKey) bool { - //TODO implement me panic("implement me") } func (t testPubKey) Type() string { - //TODO implement me panic("implement me") } @@ -153,6 +146,10 @@ func TestDefaultMempool(t *testing.T) { require.NoError(t, err) } require.Equal(t, txCount, mp.CountTx()) + + sel, err := mp.Select(nil, 13) + require.NoError(t, err) + require.Equal(t, 13, len(sel)) } type txSpec struct { @@ -213,8 +210,6 @@ func (s *MempoolTestSuite) TestTxOrder() { sa := accounts[0].Address sb := accounts[1].Address sc := accounts[2].Address - //sd := accounts[3].Address - //se := accounts[4].Address tests := []struct { txs []txSpec @@ -479,20 +474,10 @@ func (s *MempoolTestSuite) TestRandomWalkTxs() { errMsg := fmt.Sprintf("Expected order: %v\nGot order: %v\nSeed: %v", orderedStr, selectedStr, seed) - //mempool.DebugPrintKeys(mp) - start := time.Now() require.NoError(t, validateOrder(selected), errMsg) duration := time.Since(start) - /*for i, tx := range selected { - msg := fmt.Sprintf("Failed tx at index %d\n%s", i, errMsg) - require.Equal(t, ordered[i], tx.(testTx), msg) - require.Equal(t, tx.(testTx).priority, ordered[i].priority, msg) - require.Equal(t, tx.(testTx).nonce, ordered[i].nonce, msg) - require.Equal(t, tx.(testTx).address, ordered[i].address, msg) - }*/ - fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", seed, mempool.Iterations(mp), duration.Milliseconds()) } From 192d421d1ef2309b09ffef5d70d83ae0818cf14f Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 16:26:49 -0500 Subject: [PATCH 074/196] rename parallel impl --- types/mempool/{stateful.go => stateful_test.go} | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) rename types/mempool/{stateful.go => stateful_test.go} (92%) diff --git a/types/mempool/stateful.go b/types/mempool/stateful_test.go similarity index 92% rename from types/mempool/stateful.go rename to types/mempool/stateful_test.go index 612d37a2c43f..3a4b758e4e31 100644 --- a/types/mempool/stateful.go +++ b/types/mempool/stateful_test.go @@ -1,4 +1,4 @@ -package mempool +package mempool_test import ( "bytes" @@ -8,6 +8,7 @@ import ( huandu "github.com/huandu/skiplist" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/cosmos-sdk/x/auth/signing" ) @@ -32,14 +33,14 @@ type AccountMemPool struct { } // Push cannot be executed in the middle of a select -func (amp *AccountMemPool) Push(ctx sdk.Context, key statefullPriorityKey, tx Tx) { +func (amp *AccountMemPool) Push(ctx sdk.Context, key statefullPriorityKey, tx mempool.Tx) { amp.transactions.Set(key, tx) amp.currentItem = amp.transactions.Back() newKey := amp.currentItem.Key().(statefullPriorityKey) amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} } -func (amp *AccountMemPool) Pop() *Tx { +func (amp *AccountMemPool) Pop() *mempool.Tx { if amp.currentItem == nil { return nil } @@ -51,7 +52,7 @@ func (amp *AccountMemPool) Pop() *Tx { } else { amp.currentKey = accountsHeadsKey{} } - tx := itemToPop.Value.(Tx) + tx := itemToPop.Value.(mempool.Tx) return &tx } @@ -67,7 +68,7 @@ func NewMemPoolI() MemPoolI { } } -func (amp *MemPoolI) Insert(ctx sdk.Context, tx Tx) error { +func (amp *MemPoolI) Insert(ctx sdk.Context, tx mempool.Tx) error { senders := tx.(signing.SigVerifiableTx).GetSigners() nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() @@ -99,8 +100,8 @@ func (amp *MemPoolI) Insert(ctx sdk.Context, tx Tx) error { } -func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int64) ([]Tx, error) { - var selectedTxs []Tx +func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int64) ([]mempool.Tx, error) { + var selectedTxs []mempool.Tx var txBytes int64 currentAccount := amp.accountsHeads.Front() From 7adc7bf73cf477a58d45e7a9164ebf1add1e9005 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 16:47:22 -0500 Subject: [PATCH 075/196] more cleanup --- types/mempool/mempool.go | 14 ++++++------- types/mempool/mempool_test.go | 38 +++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index c8dba6a8495b..c46e07d4911a 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -47,9 +47,7 @@ type ErrTxNotFound struct { type Factory func() Mempool -var ( - _ Mempool = (*defaultMempool)(nil) -) +var _ Mempool = (*defaultMempool)(nil) type defaultMempool struct { priorities *huandu.SkipList @@ -95,6 +93,9 @@ func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { if err != nil { return err } + if len(sigs) == 0 { + return fmt.Errorf("tx must have at least one signer") + } sig := sigs[0] sender := sig.PubKey.Address().String() @@ -222,10 +223,7 @@ func DebugPrintKeys(mempool Mempool) { } } -func Iterations(mempool Mempool) int { - mp, ok := mempool.(*defaultMempool) - if !ok { - panic("unknown mempool type") - } +func DebugIterations(mempool Mempool) int { + mp := mempool.(*defaultMempool) return mp.iterations } diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 42fb10d1d049..f06dd9ea9055 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -114,6 +114,22 @@ func (tx testTx) String() string { return fmt.Sprintf("tx a: %s, p: %d, n: %d", tx.address, tx.priority, tx.nonce) } +type sigErrTx struct { + getSigs func() ([]txsigning.SignatureV2, error) +} + +func (_ sigErrTx) Size() int64 { return 0 } + +func (_ sigErrTx) GetMsgs() []sdk.Msg { return nil } + +func (_ sigErrTx) ValidateBasic() error { return nil } + +func (_ sigErrTx) GetSigners() []sdk.AccAddress { return nil } + +func (_ sigErrTx) GetPubKeys() ([]cryptotypes.PubKey, error) { return nil, nil } + +func (t sigErrTx) GetSignaturesV2() ([]txsigning.SignatureV2, error) { return t.getSigs() } + func TestDefaultMempool(t *testing.T) { ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 10) @@ -150,6 +166,23 @@ func TestDefaultMempool(t *testing.T) { sel, err := mp.Select(nil, 13) require.NoError(t, err) require.Equal(t, 13, len(sel)) + + // a tx which does not implement SigVerifiableTx should not be inserted + tx := &sigErrTx{getSigs: func() ([]txsigning.SignatureV2, error) { + return nil, fmt.Errorf("error") + }} + require.Error(t, mp.Insert(ctx, tx)) + require.Error(t, mp.Remove(tx)) + tx.getSigs = func() ([]txsigning.SignatureV2, error) { + return nil, nil + } + require.Error(t, mp.Insert(ctx, tx)) + require.Error(t, mp.Remove(tx)) + + mp = mempool.NewDefaultMempool() + require.NoError(t, mp.Insert(ctx, txs[0])) + require.ErrorAs(t, mp.Remove(txs[1]), &mempool.ErrTxNotFound{}) + mempool.DebugPrintKeys(mp) } type txSpec struct { @@ -201,6 +234,7 @@ func TestOutOfOrder(t *testing.T) { } require.Error(t, validateOrder(rmtxs)) + } func (s *MempoolTestSuite) TestTxOrder() { @@ -431,7 +465,7 @@ func (s *MempoolTestSuite) TestRandomGeneratedTxs() { duration := time.Since(start) fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", - seed, mempool.Iterations(mp), duration.Milliseconds()) + seed, mempool.DebugIterations(mp), duration.Milliseconds()) } func (s *MempoolTestSuite) TestRandomWalkTxs() { @@ -479,7 +513,7 @@ func (s *MempoolTestSuite) TestRandomWalkTxs() { duration := time.Since(start) fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", - seed, mempool.Iterations(mp), duration.Milliseconds()) + seed, mempool.DebugIterations(mp), duration.Milliseconds()) } func (s *MempoolTestSuite) TestSampleTxs() { From bd1b55f2149030ee4029b8a1ad98dd4749e8fa71 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 16:48:02 -0500 Subject: [PATCH 076/196] go imports --- types/mempool/mempool.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index c46e07d4911a..598ed35c9231 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -4,10 +4,9 @@ import ( "fmt" "math" - "github.com/cosmos/cosmos-sdk/types" - huandu "github.com/huandu/skiplist" + "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/signing" ) From 96cd0c7993d32ebf6f4e6a53b52d28d6bfa766b4 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 18:18:23 -0500 Subject: [PATCH 077/196] Add some godoc strings --- types/mempool/mempool.go | 26 +++++++++++++++++++++++++- types/mempool/mempool_test.go | 10 ++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 598ed35c9231..241ac89470da 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -48,6 +48,10 @@ type Factory func() Mempool var _ Mempool = (*defaultMempool)(nil) +// defaultMempool is the SDK's default mempool implementation which returns txs in a partial order +// by 2 dimensions; priority, and sender-nonce. Internally it uses one priority ordered skip list and one skip list +// per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, they are not +// always comparable by priority other sender txs and must be partially ordered by both sender-nonce and priority. type defaultMempool struct { priorities *huandu.SkipList senders map[string]*huandu.SkipList @@ -61,6 +65,8 @@ type txKey struct { sender string } +// txKeyLess is a comparator for txKeys that first compares priority, then nonce, then sender, uniquely identifying +// a transaction. func txKeyLess(a, b interface{}) int { keyA := a.(txKey) keyB := b.(txKey) @@ -87,6 +93,10 @@ func NewDefaultMempool() Mempool { } } +// Insert attempts to insert a Tx into the app-side mempool in O(log n) time, returning an error if unsuccessful. +// Sender and nonce are derived from the transaction's first signature. +// Inserting a duplicate tx is an O(log n) no-op. +// Inserting a duplicate tx with a different priority overwrites the existing tx. func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { @@ -110,13 +120,25 @@ func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { mp.senders[sender] = senderTxs } + // Since senderTxs is scored by nonce, a changed priority will overwrite the existing txKey. senderTxs.Set(tk, tx) - mp.scores[txKey{nonce: nonce, sender: sender}] = ctx.Priority() + + // Since mp.priorities is scored by priority, then sender, then nonce, a changed priority will create a new key, + // so we must remove the old key and re-insert it to avoid having the same tx with different priorities indexed + // twice in the mempool. This O(log n) remove operation is rare and only happens when a tx's priority changes. + sk := txKey{nonce: nonce, sender: sender} + if oldScore, txExists := mp.scores[sk]; txExists { + mp.priorities.Remove(txKey{nonce: nonce, priority: oldScore, sender: sender}) + } + mp.scores[sk] = ctx.Priority() mp.priorities.Set(tk, tx) return nil } +// Select returns a set of transactions from the mempool, prioritized by priority and sender-nonce in O(n) time. +// The passed in list of transactions are ignored. This is a readonly operation, the mempool is not modified. +// maxBytes is the maximum number of bytes of transactions to return. func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { var selectedTxs []Tx var txBytes int64 @@ -177,10 +199,12 @@ func nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { return np, nextPriorityNode } +// CountTx returns the number of transactions in the mempool. func (mp *defaultMempool) CountTx() int { return mp.priorities.Len() } +// Remove removes a transaction from the mempool in O(log n) time, returning an error if unsuccessful. func (mp *defaultMempool) Remove(tx Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index f06dd9ea9055..263288b97262 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -179,10 +179,20 @@ func TestDefaultMempool(t *testing.T) { require.Error(t, mp.Insert(ctx, tx)) require.Error(t, mp.Remove(tx)) + // removing a tx not in the mempool should error mp = mempool.NewDefaultMempool() require.NoError(t, mp.Insert(ctx, txs[0])) require.ErrorAs(t, mp.Remove(txs[1]), &mempool.ErrTxNotFound{}) mempool.DebugPrintKeys(mp) + + // inserting a tx with a different priority should overwrite the old tx + newPriorityTx := testTx{ + address: txs[0].address, + priority: txs[0].priority + 1, + nonce: txs[0].nonce, + } + require.NoError(t, mp.Insert(ctx, newPriorityTx)) + require.Equal(t, 1, mp.CountTx()) } type txSpec struct { From 83dcbc7a05c97be43f717c64d46c42c26a44dc08 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 18:20:30 -0500 Subject: [PATCH 078/196] typo --- types/mempool/mempool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 241ac89470da..d865bc97ffed 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -51,7 +51,7 @@ var _ Mempool = (*defaultMempool)(nil) // defaultMempool is the SDK's default mempool implementation which returns txs in a partial order // by 2 dimensions; priority, and sender-nonce. Internally it uses one priority ordered skip list and one skip list // per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, they are not -// always comparable by priority other sender txs and must be partially ordered by both sender-nonce and priority. +// always comparable by priority to other sender txs and must be partially ordered by both sender-nonce and priority. type defaultMempool struct { priorities *huandu.SkipList senders map[string]*huandu.SkipList From e473bcf6830b612a3c8e01d3d56246a3aaac7406 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 18:24:12 -0500 Subject: [PATCH 079/196] more godoc --- types/mempool/mempool.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index d865bc97ffed..85540e9fc949 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -95,8 +95,10 @@ func NewDefaultMempool() Mempool { // Insert attempts to insert a Tx into the app-side mempool in O(log n) time, returning an error if unsuccessful. // Sender and nonce are derived from the transaction's first signature. -// Inserting a duplicate tx is an O(log n) no-op. -// Inserting a duplicate tx with a different priority overwrites the existing tx. +// - Transactions are unique by sender and nonce. +// - Inserting a duplicate tx is an O(log n) no-op. +// - Inserting a duplicate tx with a different priority overwrites the existing tx, changing the total order of +// the mempool. func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { From 613019af41a1ac4036d8928ea6e90941015b60c3 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 18:29:53 -0500 Subject: [PATCH 080/196] comment format --- types/mempool/mempool.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 85540e9fc949..64f042c3603c 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -95,10 +95,10 @@ func NewDefaultMempool() Mempool { // Insert attempts to insert a Tx into the app-side mempool in O(log n) time, returning an error if unsuccessful. // Sender and nonce are derived from the transaction's first signature. -// - Transactions are unique by sender and nonce. -// - Inserting a duplicate tx is an O(log n) no-op. -// - Inserting a duplicate tx with a different priority overwrites the existing tx, changing the total order of -// the mempool. +// Transactions are unique by sender and nonce. +// Inserting a duplicate tx is an O(log n) no-op. +// Inserting a duplicate tx with a different priority overwrites the existing tx, changing the total order of +// the mempool. func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { From 30990398688221ee433844684943614f9dc25c10 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 18:33:39 -0500 Subject: [PATCH 081/196] typo --- types/mempool/mempool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 64f042c3603c..d634a139fd23 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -48,7 +48,7 @@ type Factory func() Mempool var _ Mempool = (*defaultMempool)(nil) -// defaultMempool is the SDK's default mempool implementation which returns txs in a partial order +// defaultMempool is the SDK's default mempool implementation which stores txs in a partial ordered set // by 2 dimensions; priority, and sender-nonce. Internally it uses one priority ordered skip list and one skip list // per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, they are not // always comparable by priority to other sender txs and must be partially ordered by both sender-nonce and priority. From 295703fbefaf6fd3d75830da3447666c3ab467b9 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 5 Oct 2022 19:44:38 -0500 Subject: [PATCH 082/196] update godoc --- types/mempool/mempool.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index d634a139fd23..ed3706565324 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -48,7 +48,7 @@ type Factory func() Mempool var _ Mempool = (*defaultMempool)(nil) -// defaultMempool is the SDK's default mempool implementation which stores txs in a partial ordered set +// defaultMempool is the SDK's default mempool implementation which stores txs in a partially ordered set // by 2 dimensions; priority, and sender-nonce. Internally it uses one priority ordered skip list and one skip list // per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, they are not // always comparable by priority to other sender txs and must be partially ordered by both sender-nonce and priority. @@ -65,7 +65,7 @@ type txKey struct { sender string } -// txKeyLess is a comparator for txKeys that first compares priority, then nonce, then sender, uniquely identifying +// txKeyLess is a comparator for txKeys that first compares priority, then sender, then nonce, uniquely identifying // a transaction. func txKeyLess(a, b interface{}) int { keyA := a.(txKey) From 302f81e062227d08f8503e1dfd63aa6d41651599 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 6 Oct 2022 08:33:14 -0600 Subject: [PATCH 083/196] simple benchmark --- types/mempool/mempool_test.go | 172 ++++++++++++++++++++++++++++------ 1 file changed, 141 insertions(+), 31 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index c7cf7280a9bc..8eff1662bb0a 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -124,21 +124,27 @@ func (tx testTx) String() string { return fmt.Sprintf("tx a: %s, p: %d, n: %d", tx.address, tx.priority, tx.nonce) } -func TestNewStatefulMempool(t *testing.T) { - ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) - - // general test - transactions := simulateManyTx(ctx, 1000) - require.Equal(t, 1000, len(transactions)) - mp := mempool.NewDefaultMempool() - - for _, tx := range transactions { - ctx.WithPriority(rand.Int63()) - err := mp.Insert(ctx, tx.(mempool.Tx)) - require.NoError(t, err) - } - require.Equal(t, 1000, mp.CountTx()) -} +type txWithPriority struct { + priority int64 + tx sdk.Tx + address string +} + +//func TestNewStatefulMempool(t *testing.T) { +// ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) +// +// // general test +// transactions := simulateManyTx(ctx, 1000) +// require.Equal(t, 1000, len(transactions)) +// mp := mempool.NewDefaultMempool() +// +// for _, tx := range transactions { +// ctx.WithPriority(rand.Int63()) +// err := mp.Insert(ctx, tx.(mempool.Tx)) +// require.NoError(t, err) +// } +// require.Equal(t, 1000, mp.CountTx()) +//} type txSpec struct { i int @@ -623,30 +629,104 @@ func TestTxOrderN(t *testing.T) { } } -func simulateManyTx(ctx sdk.Context, n int) []sdk.Tx { - transactions := make([]sdk.Tx, n) - for i := 0; i < n; i++ { - tx := simulateTx(ctx) - transactions[i] = tx +func BenchmarkDefaultMempool_Insert(b *testing.B) { + var inputs = []struct { + txN int + addressN int + }{ + {txN: 100, addressN: 4}, + {txN: 1000, addressN: 10}, + {txN: 100000, addressN: 1000}, + } + + for _, v := range inputs { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + genedTx := genManyTestTx(ctx, v.txN, v.addressN) + pool := mempool.NewDefaultMempool() + b.Run(fmt.Sprintf("txs: %d, addreses: %d", v.txN, v.addressN), func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, txP := range genedTx { + newCtx := ctx.WithPriority(txP.priority) + pool.Insert(newCtx, txP.tx.(mempool.Tx)) + } + + } + }) } - return transactions } -func simulateTx(ctx sdk.Context) sdk.Tx { - acc := authtypes.NewEmptyModuleAccount("anaccount") +func BenchmarkDefaultMempool_Select(b *testing.B) { + var inputs = []struct { + txN int + addressN int + }{ + {txN: 100, addressN: 4}, + {txN: 1000, addressN: 10}, + {txN: 100000, addressN: 1000}, + } - s := rand.NewSource(1) + for _, v := range inputs { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + genedTx := genManyTestTx(ctx, v.txN, v.addressN) + pool := mempool.NewDefaultMempool() + for _, txP := range genedTx { + newCtx := ctx.WithPriority(txP.priority) + pool.Insert(newCtx, txP.tx.(mempool.Tx)) + } + b.Run(fmt.Sprintf("txs: %d, addreses: %d", v.txN, v.addressN), func(b *testing.B) { + for i := 0; i < b.N; i++ { + pool.Select(nil, int64(v.txN)) + + } + }) + } +} + +func genManyTestTx(ctx sdk.Context, txN int, addressN int) []txWithPriority { + s1 := rand.NewSource(time.Now().UnixNano()) + r1 := rand.New(s1) + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), addressN) + accountsNonce := make(map[string]int) + var genTx []txWithPriority + for _, acc := range accounts { + accountsNonce[acc.Address.String()] = 0 + } + for i := 0; i < txN; i++ { + acc := accounts[r1.Intn(addressN)] + p := r1.Intn(1000) + n := accountsNonce[acc.Address.String()] + accountsNonce[acc.Address.String()] = n + 1 + tx := txWithPriority{ + priority: int64(p), + tx: simulateTx(ctx, acc, uint64(n)), + address: acc.Address.String(), + } + genTx = append(genTx, tx) + } + return genTx +} + +// +//func simulateManyTx(ctx sdk.Context, n int) []sdk.Tx { +// transactions := make([]sdk.Tx, n) +// for i := 0; i < n; i++ { +// tx := simulateTx(ctx) +// transactions[i] = tx +// } +// return transactions +//} + +func simulateTx(ctx sdk.Context, acc simtypes.Account, nonce uint64) sdk.Tx { + s := rand.NewSource(time.Now().UnixNano()) r := rand.New(s) + txGen := moduletestutil.MakeTestEncodingConfig().TxConfig msg := group.MsgUpdateGroupMembers{ GroupId: 1, - Admin: "test", + Admin: acc.Address.String(), MemberUpdates: []group.MemberRequest{}, } fees, _ := simtypes.RandomFees(r, ctx, sdk.NewCoins(sdk.NewCoin("coin", sdk.NewInt(100000000)))) - txGen := moduletestutil.MakeTestEncodingConfig().TxConfig - accounts := simtypes.RandomAccounts(r, 2) - tx, _ := simtestutil.GenSignedMockTx( r, txGen, @@ -654,13 +734,43 @@ func simulateTx(ctx sdk.Context) sdk.Tx { fees, simtestutil.DefaultGenTxGas, ctx.ChainID(), - []uint64{acc.GetAccountNumber()}, - []uint64{acc.GetSequence()}, - accounts[0].PrivKey, + []uint64{authtypes.NewBaseAccountWithAddress(acc.Address).GetAccountNumber()}, + []uint64{nonce}, + acc.PrivKey, ) return tx } +// +//func simulateTx(ctx sdk.Context) sdk.Tx { +// acc := authtypes.NewEmptyModuleAccount("anaccount") +// +// s := rand.NewSource(1) +// r := rand.New(s) +// msg := group.MsgUpdateGroupMembers{ +// GroupId: 1, +// Admin: "test", +// MemberUpdates: []group.MemberRequest{}, +// } +// fees, _ := simtypes.RandomFees(r, ctx, sdk.NewCoins(sdk.NewCoin("coin", sdk.NewInt(100000000)))) +// +// txGen := moduletestutil.MakeTestEncodingConfig().TxConfig +// accounts := simtypes.RandomAccounts(r, 2) +// +// tx, _ := simtestutil.GenSignedMockTx( +// r, +// txGen, +// []sdk.Msg{&msg}, +// fees, +// simtestutil.DefaultGenTxGas, +// ctx.ChainID(), +// []uint64{acc.GetAccountNumber()}, +// []uint64{acc.GetSequence()}, +// accounts[0].PrivKey, +// ) +// return tx +//} + func unmarshalTx(txBytes []byte) (sdk.Tx, error) { cfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}, gov.AppModuleBasic{}) return cfg.TxConfig.TxJSONDecoder()(txBytes) From 83d63206a5dbd61b5919f96042a8a421707f3f94 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 6 Oct 2022 10:26:17 -0500 Subject: [PATCH 084/196] test cleanup --- types/mempool/mempool_test.go | 64 ++++++++++++----------------------- 1 file changed, 21 insertions(+), 43 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 263288b97262..ff3b09197f43 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -30,37 +30,21 @@ type testPubKey struct { address sdk.AccAddress } -func (t testPubKey) Reset() { - panic("implement me") -} +func (t testPubKey) Reset() { panic("implement me") } -func (t testPubKey) String() string { - panic("implement me") -} +func (t testPubKey) String() string { panic("implement me") } -func (t testPubKey) ProtoMessage() { - panic("implement me") -} +func (t testPubKey) ProtoMessage() { panic("implement me") } -func (t testPubKey) Address() cryptotypes.Address { - return t.address.Bytes() -} +func (t testPubKey) Address() cryptotypes.Address { return t.address.Bytes() } -func (t testPubKey) Bytes() []byte { - panic("implement me") -} +func (t testPubKey) Bytes() []byte { panic("implement me") } -func (t testPubKey) VerifySignature(msg []byte, sig []byte) bool { - panic("implement me") -} +func (t testPubKey) VerifySignature(msg []byte, sig []byte) bool { panic("implement me") } -func (t testPubKey) Equals(key cryptotypes.PubKey) bool { - panic("implement me") -} +func (t testPubKey) Equals(key cryptotypes.PubKey) bool { panic("implement me") } -func (t testPubKey) Type() string { - panic("implement me") -} +func (t testPubKey) Type() string { panic("implement me") } // testTx is a dummy implementation of Tx used for testing. type testTx struct { @@ -70,13 +54,9 @@ type testTx struct { address sdk.AccAddress } -func (tx testTx) GetSigners() []sdk.AccAddress { - panic("implement me") -} +func (tx testTx) GetSigners() []sdk.AccAddress { panic("implement me") } -func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { - panic("GetPubkeys not implemented") -} +func (tx testTx) GetPubKeys() ([]cryptotypes.PubKey, error) { panic("GetPubKeys not implemented") } func (tx testTx) GetSignaturesV2() (res []txsigning.SignatureV2, err error) { res = append(res, txsigning.SignatureV2{ @@ -94,21 +74,13 @@ var ( _ cryptotypes.PubKey = (*testPubKey)(nil) ) -func (tx testTx) GetHash() [32]byte { - return tx.hash -} +func (tx testTx) GetHash() [32]byte { return tx.hash } -func (tx testTx) Size() int64 { - return 1 -} +func (tx testTx) Size() int64 { return 1 } -func (tx testTx) GetMsgs() []sdk.Msg { - return nil -} +func (tx testTx) GetMsgs() []sdk.Msg { return nil } -func (tx testTx) ValidateBasic() error { - return nil -} +func (tx testTx) ValidateBasic() error { return nil } func (tx testTx) String() string { return fmt.Sprintf("tx a: %s, p: %d, n: %d", tx.address, tx.priority, tx.nonce) @@ -233,7 +205,8 @@ func TestOutOfOrder(t *testing.T) { for _, mtx := range outOfOrder { mtxs = append(mtxs, mtx) } - require.Error(t, validateOrder(mtxs)) + err := validateOrder(mtxs) + require.Error(t, err) } seed := time.Now().UnixNano() @@ -402,8 +375,10 @@ func (s *MempoolTestSuite) TestRandomTxOrderManyTimes() { // validateOrder checks that the txs are ordered by priority and nonce // in O(n^2) time by checking each tx against all the other txs func validateOrder(mtxs []mempool.Tx) error { + iterations := 0 var itxs []txSpec for i, mtx := range mtxs { + iterations++ tx := mtx.(testTx) itxs = append(itxs, txSpec{p: int(tx.priority), n: int(tx.nonce), a: tx.address, i: i}) } @@ -416,6 +391,7 @@ func validateOrder(mtxs []mempool.Tx) error { for _, a := range itxs { for _, b := range itxs { + iterations++ // when b is before a // when a is before b @@ -435,6 +411,7 @@ func validateOrder(mtxs []mempool.Tx) error { // find a tx with same sender as b and lower nonce found := false for _, c := range itxs { + iterations++ if c.a.Equals(b.a) && c.n < b.n && c.p <= a.p { found = true break @@ -448,6 +425,7 @@ func validateOrder(mtxs []mempool.Tx) error { } } } + // fmt.Printf("validation in iterations: %d\n", iterations) return nil } From e61ceeabc32153b5aaf6beec405aefc4bcc69351 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 10 Oct 2022 09:25:26 -0500 Subject: [PATCH 085/196] move default implementation to default.go --- types/mempool/default.go | 218 +++++++++++++++++++++++++++++++++++++++ types/mempool/mempool.go | 217 +------------------------------------- 2 files changed, 220 insertions(+), 215 deletions(-) create mode 100644 types/mempool/default.go diff --git a/types/mempool/default.go b/types/mempool/default.go new file mode 100644 index 000000000000..ab1d3c103f19 --- /dev/null +++ b/types/mempool/default.go @@ -0,0 +1,218 @@ +package mempool + +import ( + "fmt" + "math" + + huandu "github.com/huandu/skiplist" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +var _ Mempool = (*defaultMempool)(nil) + +// defaultMempool is the SDK's default mempool implementation which stores txs in a partially ordered set +// by 2 dimensions; priority, and sender-nonce. Internally it uses one priority ordered skip list and one skip list +// per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, they are not +// always comparable by priority to other sender txs and must be partially ordered by both sender-nonce and priority. +type defaultMempool struct { + priorities *huandu.SkipList + senders map[string]*huandu.SkipList + scores map[txKey]int64 + iterations int +} + +type txKey struct { + nonce uint64 + priority int64 + sender string +} + +// txKeyLess is a comparator for txKeys that first compares priority, then sender, then nonce, uniquely identifying +// a transaction. +func txKeyLess(a, b interface{}) int { + keyA := a.(txKey) + keyB := b.(txKey) + res := huandu.Int64.Compare(keyA.priority, keyB.priority) + if res != 0 { + return res + } + + res = huandu.String.Compare(keyA.sender, keyB.sender) + if res != 0 { + return res + } + + return huandu.Uint64.Compare(keyA.nonce, keyB.nonce) +} + +// NewDefaultMempool returns the SDK's default mempool implementation which returns txs in a partial order +// by 2 dimensions; priority, and sender-nonce. +func NewDefaultMempool() Mempool { + return &defaultMempool{ + priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), + senders: make(map[string]*huandu.SkipList), + scores: make(map[txKey]int64), + } +} + +// Insert attempts to insert a Tx into the app-side mempool in O(log n) time, returning an error if unsuccessful. +// Sender and nonce are derived from the transaction's first signature. +// Transactions are unique by sender and nonce. +// Inserting a duplicate tx is an O(log n) no-op. +// Inserting a duplicate tx with a different priority overwrites the existing tx, changing the total order of +// the mempool. +func (mp *defaultMempool) Insert(ctx sdk.Context, tx Tx) error { + sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + if err != nil { + return err + } + if len(sigs) == 0 { + return fmt.Errorf("tx must have at least one signer") + } + + sig := sigs[0] + sender := sig.PubKey.Address().String() + nonce := sig.Sequence + tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender} + + senderTxs, ok := mp.senders[sender] + // initialize sender mempool if not found + if !ok { + senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { + return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) + })) + mp.senders[sender] = senderTxs + } + + // Since senderTxs is scored by nonce, a changed priority will overwrite the existing txKey. + senderTxs.Set(tk, tx) + + // Since mp.priorities is scored by priority, then sender, then nonce, a changed priority will create a new key, + // so we must remove the old key and re-insert it to avoid having the same tx with different priorities indexed + // twice in the mempool. This O(log n) remove operation is rare and only happens when a tx's priority changes. + sk := txKey{nonce: nonce, sender: sender} + if oldScore, txExists := mp.scores[sk]; txExists { + mp.priorities.Remove(txKey{nonce: nonce, priority: oldScore, sender: sender}) + } + mp.scores[sk] = ctx.Priority() + mp.priorities.Set(tk, tx) + + return nil +} + +// Select returns a set of transactions from the mempool, prioritized by priority and sender-nonce in O(n) time. +// The passed in list of transactions are ignored. This is a readonly operation, the mempool is not modified. +// maxBytes is the maximum number of bytes of transactions to return. +func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { + var selectedTxs []Tx + var txBytes int64 + senderCursors := make(map[string]*huandu.Element) + + // start with the highest priority sender + priorityNode := mp.priorities.Front() + for priorityNode != nil { + priorityKey := priorityNode.Key().(txKey) + nextHighestPriority, nextPriorityNode := nextPriority(priorityNode) + sender := priorityKey.sender + senderTx := mp.fetchSenderCursor(senderCursors, sender) + + // iterate through the sender's transactions in nonce order + for senderTx != nil { + // time complexity tracking + mp.iterations++ + k := senderTx.Key().(txKey) + + // break if we've reached a transaction with a priority lower than the next highest priority in the pool + if k.priority < nextHighestPriority { + break + } + + mempoolTx, _ := senderTx.Value.(Tx) + // otherwise, select the transaction and continue iteration + selectedTxs = append(selectedTxs, mempoolTx) + if txBytes += mempoolTx.Size(); txBytes >= maxBytes { + return selectedTxs, nil + } + + senderTx = senderTx.Next() + senderCursors[sender] = senderTx + } + + priorityNode = nextPriorityNode + } + + return selectedTxs, nil +} + +func (mp *defaultMempool) fetchSenderCursor(senderCursors map[string]*huandu.Element, sender string) *huandu.Element { + senderTx, ok := senderCursors[sender] + if !ok { + senderTx = mp.senders[sender].Front() + } + return senderTx +} + +func nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { + var np int64 + nextPriorityNode := priorityNode.Next() + if nextPriorityNode != nil { + np = nextPriorityNode.Key().(txKey).priority + } else { + np = math.MinInt64 + } + return np, nextPriorityNode +} + +// CountTx returns the number of transactions in the mempool. +func (mp *defaultMempool) CountTx() int { + return mp.priorities.Len() +} + +// Remove removes a transaction from the mempool in O(log n) time, returning an error if unsuccessful. +func (mp *defaultMempool) Remove(tx Tx) error { + sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + if err != nil { + return err + } + if len(sigs) == 0 { + return fmt.Errorf("attempted to remove a tx with no signatures") + } + sig := sigs[0] + sender := sig.PubKey.Address().String() + nonce := sig.Sequence + + sk := txKey{nonce: nonce, sender: sender} + priority, ok := mp.scores[sk] + if !ok { + return ErrTxNotFound{fmt.Errorf("tx %v not found in mempool", tx)} + } + tk := txKey{nonce: nonce, priority: priority, sender: sender} + + senderTxs, ok := mp.senders[sender] + if !ok { + return fmt.Errorf("sender %s not found", sender) + } + + mp.priorities.Remove(tk) + senderTxs.Remove(tk) + delete(mp.scores, sk) + + return nil +} + +func DebugPrintKeys(mempool Mempool) { + mp := mempool.(*defaultMempool) + n := mp.priorities.Front() + for n != nil { + k := n.Key().(txKey) + fmt.Printf("%s, %d, %d\n", k.sender, k.priority, k.nonce) + n = n.Next() + } +} + +func DebugIterations(mempool Mempool) int { + mp := mempool.(*defaultMempool) + return mp.iterations +} diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index ed3706565324..dc9a1224941c 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -1,18 +1,12 @@ package mempool import ( - "fmt" - "math" - - huandu "github.com/huandu/skiplist" - "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/signing" ) -// Tx we define an app-side mempool transaction interface that is as +// Tx defines an app-side mempool transaction interface that is as // minimal as possible, only requiring applications to define the size of the -// transaction to be used when reaping and getting the transaction itself. +// transaction to be used when inserting, selecting, and deleting the transaction. // Interface type casting can be used in the actual app-side mempool implementation. type Tx interface { types.Tx @@ -45,210 +39,3 @@ type ErrTxNotFound struct { } type Factory func() Mempool - -var _ Mempool = (*defaultMempool)(nil) - -// defaultMempool is the SDK's default mempool implementation which stores txs in a partially ordered set -// by 2 dimensions; priority, and sender-nonce. Internally it uses one priority ordered skip list and one skip list -// per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, they are not -// always comparable by priority to other sender txs and must be partially ordered by both sender-nonce and priority. -type defaultMempool struct { - priorities *huandu.SkipList - senders map[string]*huandu.SkipList - scores map[txKey]int64 - iterations int -} - -type txKey struct { - nonce uint64 - priority int64 - sender string -} - -// txKeyLess is a comparator for txKeys that first compares priority, then sender, then nonce, uniquely identifying -// a transaction. -func txKeyLess(a, b interface{}) int { - keyA := a.(txKey) - keyB := b.(txKey) - res := huandu.Int64.Compare(keyA.priority, keyB.priority) - if res != 0 { - return res - } - - res = huandu.String.Compare(keyA.sender, keyB.sender) - if res != 0 { - return res - } - - return huandu.Uint64.Compare(keyA.nonce, keyB.nonce) -} - -// NewDefaultMempool returns the SDK's default mempool implementation which returns txs in a partial order -// by 2 dimensions; priority, and sender-nonce. -func NewDefaultMempool() Mempool { - return &defaultMempool{ - priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), - senders: make(map[string]*huandu.SkipList), - scores: make(map[txKey]int64), - } -} - -// Insert attempts to insert a Tx into the app-side mempool in O(log n) time, returning an error if unsuccessful. -// Sender and nonce are derived from the transaction's first signature. -// Transactions are unique by sender and nonce. -// Inserting a duplicate tx is an O(log n) no-op. -// Inserting a duplicate tx with a different priority overwrites the existing tx, changing the total order of -// the mempool. -func (mp *defaultMempool) Insert(ctx types.Context, tx Tx) error { - sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - if err != nil { - return err - } - if len(sigs) == 0 { - return fmt.Errorf("tx must have at least one signer") - } - - sig := sigs[0] - sender := sig.PubKey.Address().String() - nonce := sig.Sequence - tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender} - - senderTxs, ok := mp.senders[sender] - // initialize sender mempool if not found - if !ok { - senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { - return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) - })) - mp.senders[sender] = senderTxs - } - - // Since senderTxs is scored by nonce, a changed priority will overwrite the existing txKey. - senderTxs.Set(tk, tx) - - // Since mp.priorities is scored by priority, then sender, then nonce, a changed priority will create a new key, - // so we must remove the old key and re-insert it to avoid having the same tx with different priorities indexed - // twice in the mempool. This O(log n) remove operation is rare and only happens when a tx's priority changes. - sk := txKey{nonce: nonce, sender: sender} - if oldScore, txExists := mp.scores[sk]; txExists { - mp.priorities.Remove(txKey{nonce: nonce, priority: oldScore, sender: sender}) - } - mp.scores[sk] = ctx.Priority() - mp.priorities.Set(tk, tx) - - return nil -} - -// Select returns a set of transactions from the mempool, prioritized by priority and sender-nonce in O(n) time. -// The passed in list of transactions are ignored. This is a readonly operation, the mempool is not modified. -// maxBytes is the maximum number of bytes of transactions to return. -func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { - var selectedTxs []Tx - var txBytes int64 - senderCursors := make(map[string]*huandu.Element) - - // start with the highest priority sender - priorityNode := mp.priorities.Front() - for priorityNode != nil { - priorityKey := priorityNode.Key().(txKey) - nextHighestPriority, nextPriorityNode := nextPriority(priorityNode) - sender := priorityKey.sender - senderTx := mp.fetchSenderCursor(senderCursors, sender) - - // iterate through the sender's transactions in nonce order - for senderTx != nil { - // time complexity tracking - mp.iterations++ - k := senderTx.Key().(txKey) - - // break if we've reached a transaction with a priority lower than the next highest priority in the pool - if k.priority < nextHighestPriority { - break - } - - mempoolTx, _ := senderTx.Value.(Tx) - // otherwise, select the transaction and continue iteration - selectedTxs = append(selectedTxs, mempoolTx) - if txBytes += mempoolTx.Size(); txBytes >= maxBytes { - return selectedTxs, nil - } - - senderTx = senderTx.Next() - senderCursors[sender] = senderTx - } - - priorityNode = nextPriorityNode - } - - return selectedTxs, nil -} - -func (mp *defaultMempool) fetchSenderCursor(senderCursors map[string]*huandu.Element, sender string) *huandu.Element { - senderTx, ok := senderCursors[sender] - if !ok { - senderTx = mp.senders[sender].Front() - } - return senderTx -} - -func nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { - var np int64 - nextPriorityNode := priorityNode.Next() - if nextPriorityNode != nil { - np = nextPriorityNode.Key().(txKey).priority - } else { - np = math.MinInt64 - } - return np, nextPriorityNode -} - -// CountTx returns the number of transactions in the mempool. -func (mp *defaultMempool) CountTx() int { - return mp.priorities.Len() -} - -// Remove removes a transaction from the mempool in O(log n) time, returning an error if unsuccessful. -func (mp *defaultMempool) Remove(tx Tx) error { - sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - if err != nil { - return err - } - if len(sigs) == 0 { - return fmt.Errorf("attempted to remove a tx with no signatures") - } - sig := sigs[0] - sender := sig.PubKey.Address().String() - nonce := sig.Sequence - - sk := txKey{nonce: nonce, sender: sender} - priority, ok := mp.scores[sk] - if !ok { - return ErrTxNotFound{fmt.Errorf("tx %v not found in mempool", tx)} - } - tk := txKey{nonce: nonce, priority: priority, sender: sender} - - senderTxs, ok := mp.senders[sender] - if !ok { - return fmt.Errorf("sender %s not found", sender) - } - - mp.priorities.Remove(tk) - senderTxs.Remove(tk) - delete(mp.scores, sk) - - return nil -} - -func DebugPrintKeys(mempool Mempool) { - mp := mempool.(*defaultMempool) - n := mp.priorities.Front() - for n != nil { - k := n.Key().(txKey) - fmt.Printf("%s, %d, %d\n", k.sender, k.priority, k.nonce) - n = n.Next() - } -} - -func DebugIterations(mempool Mempool) int { - mp := mempool.(*defaultMempool) - return mp.iterations -} From 48ce41cbecc486d265c6ea6a7e383b1f2643c373 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 10 Oct 2022 09:42:59 -0500 Subject: [PATCH 086/196] minor clean up to default impl --- types/mempool/default.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/mempool/default.go b/types/mempool/default.go index ab1d3c103f19..311442366c45 100644 --- a/types/mempool/default.go +++ b/types/mempool/default.go @@ -31,7 +31,7 @@ type txKey struct { // txKeyLess is a comparator for txKeys that first compares priority, then sender, then nonce, uniquely identifying // a transaction. -func txKeyLess(a, b interface{}) int { +func txKeyLess(a, b any) int { keyA := a.(txKey) keyB := b.(txKey) res := huandu.Int64.Compare(keyA.priority, keyB.priority) @@ -129,7 +129,7 @@ func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { break } - mempoolTx, _ := senderTx.Value.(Tx) + mempoolTx := senderTx.Value.(Tx) // otherwise, select the transaction and continue iteration selectedTxs = append(selectedTxs, mempoolTx) if txBytes += mempoolTx.Size(); txBytes >= maxBytes { From c24a15248f10f8aa39e4d9a044e005cf693d9e13 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 10 Oct 2022 11:14:18 -0500 Subject: [PATCH 087/196] minor refactoring, add skip list test --- types/mempool/default.go | 53 ++++++++++++++++---------------- types/mempool/skip_list_test.go | 54 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 types/mempool/skip_list_test.go diff --git a/types/mempool/default.go b/types/mempool/default.go index 311442366c45..4aa5b0a9a5e4 100644 --- a/types/mempool/default.go +++ b/types/mempool/default.go @@ -13,14 +13,15 @@ import ( var _ Mempool = (*defaultMempool)(nil) // defaultMempool is the SDK's default mempool implementation which stores txs in a partially ordered set -// by 2 dimensions; priority, and sender-nonce. Internally it uses one priority ordered skip list and one skip list -// per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, they are not -// always comparable by priority to other sender txs and must be partially ordered by both sender-nonce and priority. +// by 2 dimensions: priority, and sender-nonce (sequence number). Internally it uses one priority ordered skip list +// and one skip per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, +// they are not always comparable by priority to other sender txs and must be partially ordered by both sender-nonce +// and priority. type defaultMempool struct { - priorities *huandu.SkipList - senders map[string]*huandu.SkipList - scores map[txKey]int64 - iterations int + priorityIndex *huandu.SkipList + senderIndices map[string]*huandu.SkipList + scores map[txKey]int64 + iterations int } type txKey struct { @@ -51,9 +52,9 @@ func txKeyLess(a, b any) int { // by 2 dimensions; priority, and sender-nonce. func NewDefaultMempool() Mempool { return &defaultMempool{ - priorities: huandu.New(huandu.LessThanFunc(txKeyLess)), - senders: make(map[string]*huandu.SkipList), - scores: make(map[txKey]int64), + priorityIndex: huandu.New(huandu.LessThanFunc(txKeyLess)), + senderIndices: make(map[string]*huandu.SkipList), + scores: make(map[txKey]int64), } } @@ -77,27 +78,27 @@ func (mp *defaultMempool) Insert(ctx sdk.Context, tx Tx) error { nonce := sig.Sequence tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender} - senderTxs, ok := mp.senders[sender] - // initialize sender mempool if not found + senderIndex, ok := mp.senderIndices[sender] if !ok { - senderTxs = huandu.New(huandu.LessThanFunc(func(a, b interface{}) int { + senderIndex = huandu.New(huandu.LessThanFunc(func(a, b any) int { return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) })) - mp.senders[sender] = senderTxs + // initialize sender index if not found + mp.senderIndices[sender] = senderIndex } - // Since senderTxs is scored by nonce, a changed priority will overwrite the existing txKey. - senderTxs.Set(tk, tx) + // Since senderIndex is scored by nonce, a changed priority will overwrite the existing txKey. + senderIndex.Set(tk, tx) - // Since mp.priorities is scored by priority, then sender, then nonce, a changed priority will create a new key, - // so we must remove the old key and re-insert it to avoid having the same tx with different priorities indexed + // Since mp.priorityIndex is scored by priority, then sender, then nonce, a changed priority will create a new key, + // so we must remove the old key and re-insert it to avoid having the same tx with different priorityIndex indexed // twice in the mempool. This O(log n) remove operation is rare and only happens when a tx's priority changes. sk := txKey{nonce: nonce, sender: sender} if oldScore, txExists := mp.scores[sk]; txExists { - mp.priorities.Remove(txKey{nonce: nonce, priority: oldScore, sender: sender}) + mp.priorityIndex.Remove(txKey{nonce: nonce, priority: oldScore, sender: sender}) } mp.scores[sk] = ctx.Priority() - mp.priorities.Set(tk, tx) + mp.priorityIndex.Set(tk, tx) return nil } @@ -111,7 +112,7 @@ func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { senderCursors := make(map[string]*huandu.Element) // start with the highest priority sender - priorityNode := mp.priorities.Front() + priorityNode := mp.priorityIndex.Front() for priorityNode != nil { priorityKey := priorityNode.Key().(txKey) nextHighestPriority, nextPriorityNode := nextPriority(priorityNode) @@ -149,7 +150,7 @@ func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { func (mp *defaultMempool) fetchSenderCursor(senderCursors map[string]*huandu.Element, sender string) *huandu.Element { senderTx, ok := senderCursors[sender] if !ok { - senderTx = mp.senders[sender].Front() + senderTx = mp.senderIndices[sender].Front() } return senderTx } @@ -167,7 +168,7 @@ func nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { // CountTx returns the number of transactions in the mempool. func (mp *defaultMempool) CountTx() int { - return mp.priorities.Len() + return mp.priorityIndex.Len() } // Remove removes a transaction from the mempool in O(log n) time, returning an error if unsuccessful. @@ -190,12 +191,12 @@ func (mp *defaultMempool) Remove(tx Tx) error { } tk := txKey{nonce: nonce, priority: priority, sender: sender} - senderTxs, ok := mp.senders[sender] + senderTxs, ok := mp.senderIndices[sender] if !ok { return fmt.Errorf("sender %s not found", sender) } - mp.priorities.Remove(tk) + mp.priorityIndex.Remove(tk) senderTxs.Remove(tk) delete(mp.scores, sk) @@ -204,7 +205,7 @@ func (mp *defaultMempool) Remove(tx Tx) error { func DebugPrintKeys(mempool Mempool) { mp := mempool.(*defaultMempool) - n := mp.priorities.Front() + n := mp.priorityIndex.Front() for n != nil { k := n.Key().(txKey) fmt.Printf("%s, %d, %d\n", k.sender, k.priority, k.nonce) diff --git a/types/mempool/skip_list_test.go b/types/mempool/skip_list_test.go new file mode 100644 index 000000000000..d834c10cdf13 --- /dev/null +++ b/types/mempool/skip_list_test.go @@ -0,0 +1,54 @@ +package mempool_test + +import ( + huandu "github.com/huandu/skiplist" + "github.com/stretchr/testify/require" + "testing" +) + +type collisionKey struct { + a int + b int +} + +func TestSkipListCollisions(t *testing.T) { + integerList := huandu.New(huandu.Int) + + integerList.Set(1, 1) + integerList.Set(2, 2) + integerList.Set(3, 3) + + k := integerList.Front() + i := 1 + for k != nil { + require.Equal(t, i, k.Key()) + require.Equal(t, i, k.Value) + i++ + k = k.Next() + } + + // a duplicate key will overwrite the previous value + integerList.Set(1, 4) + require.Equal(t, 3, integerList.Len()) + require.Equal(t, 4, integerList.Get(1).Value) + + // prove this again with a compound key + compoundList := huandu.New(huandu.LessThanFunc(func(x, y any) int { + kx := x.(collisionKey) + ky := y.(collisionKey) + if kx.a == ky.a { + return huandu.Int.Compare(kx.b, ky.b) + } + return huandu.Int.Compare(kx.a, ky.a) + })) + + compoundList.Set(collisionKey{a: 1, b: 1}, 1) + compoundList.Set(collisionKey{a: 1, b: 2}, 2) + compoundList.Set(collisionKey{a: 1, b: 3}, 3) + + require.Equal(t, 3, compoundList.Len()) + compoundList.Set(collisionKey{a: 1, b: 2}, 4) + require.Equal(t, 4, compoundList.Get(collisionKey{a: 1, b: 2}).Value) + compoundList.Set(collisionKey{a: 2, b: 2}, 5) + require.Equal(t, 4, compoundList.Len()) +} From ad829d4e9300f78f50d909e32658c2957b3b662d Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 10 Oct 2022 11:51:50 -0500 Subject: [PATCH 088/196] refactor away iterations tracking --- types/mempool/default.go | 34 ++++++++++++++++++++++------------ types/mempool/mempool_test.go | 10 +++++++--- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/types/mempool/default.go b/types/mempool/default.go index 4aa5b0a9a5e4..fe8f6cd2240b 100644 --- a/types/mempool/default.go +++ b/types/mempool/default.go @@ -21,7 +21,7 @@ type defaultMempool struct { priorityIndex *huandu.SkipList senderIndices map[string]*huandu.SkipList scores map[txKey]int64 - iterations int + onRead func(tx Tx) } type txKey struct { @@ -48,14 +48,27 @@ func txKeyLess(a, b any) int { return huandu.Uint64.Compare(keyA.nonce, keyB.nonce) } +type DefaultMempoolOption func(*defaultMempool) + +// WithOnRead sets a callback to be called when a tx is read from the mempool. +func WithOnRead(onRead func(tx Tx)) DefaultMempoolOption { + return func(mp *defaultMempool) { + mp.onRead = onRead + } +} + // NewDefaultMempool returns the SDK's default mempool implementation which returns txs in a partial order // by 2 dimensions; priority, and sender-nonce. -func NewDefaultMempool() Mempool { - return &defaultMempool{ +func NewDefaultMempool(opts ...DefaultMempoolOption) Mempool { + mp := &defaultMempool{ priorityIndex: huandu.New(huandu.LessThanFunc(txKeyLess)), senderIndices: make(map[string]*huandu.SkipList), scores: make(map[txKey]int64), } + for _, opt := range opts { + opt(mp) + } + return mp } // Insert attempts to insert a Tx into the app-side mempool in O(log n) time, returning an error if unsuccessful. @@ -103,7 +116,7 @@ func (mp *defaultMempool) Insert(ctx sdk.Context, tx Tx) error { return nil } -// Select returns a set of transactions from the mempool, prioritized by priority and sender-nonce in O(n) time. +// Select returns a set of transactions from the mempool, ordered by priority and sender-nonce in O(n) time. // The passed in list of transactions are ignored. This is a readonly operation, the mempool is not modified. // maxBytes is the maximum number of bytes of transactions to return. func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { @@ -121,8 +134,11 @@ func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { // iterate through the sender's transactions in nonce order for senderTx != nil { - // time complexity tracking - mp.iterations++ + mempoolTx := senderTx.Value.(Tx) + if mp.onRead != nil { + mp.onRead(mempoolTx) + } + k := senderTx.Key().(txKey) // break if we've reached a transaction with a priority lower than the next highest priority in the pool @@ -130,7 +146,6 @@ func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { break } - mempoolTx := senderTx.Value.(Tx) // otherwise, select the transaction and continue iteration selectedTxs = append(selectedTxs, mempoolTx) if txBytes += mempoolTx.Size(); txBytes >= maxBytes { @@ -212,8 +227,3 @@ func DebugPrintKeys(mempool Mempool) { n = n.Next() } } - -func DebugIterations(mempool Mempool) int { - mp := mempool.(*defaultMempool) - return mp.iterations -} diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index ff3b09197f43..ed1810b5a695 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -342,11 +342,15 @@ type MempoolTestSuite struct { suite.Suite numTxs int numAccounts int + iterations int mempool mempool.Mempool } func (s *MempoolTestSuite) resetMempool() { - s.mempool = mempool.NewDefaultMempool() + s.iterations = 0 + s.mempool = mempool.NewDefaultMempool(mempool.WithOnRead(func(tx mempool.Tx) { + s.iterations++ + })) } func (s *MempoolTestSuite) SetupTest() { @@ -453,7 +457,7 @@ func (s *MempoolTestSuite) TestRandomGeneratedTxs() { duration := time.Since(start) fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", - seed, mempool.DebugIterations(mp), duration.Milliseconds()) + seed, s.iterations, duration.Milliseconds()) } func (s *MempoolTestSuite) TestRandomWalkTxs() { @@ -501,7 +505,7 @@ func (s *MempoolTestSuite) TestRandomWalkTxs() { duration := time.Since(start) fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", - seed, mempool.DebugIterations(mp), duration.Milliseconds()) + seed, s.iterations, duration.Milliseconds()) } func (s *MempoolTestSuite) TestSampleTxs() { From 869b524923fe5219e403c4bc566699e17e224548 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 10 Oct 2022 12:16:28 -0500 Subject: [PATCH 089/196] begin baseapp integration --- baseapp/baseapp.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 0160e1de7115..f968d78f8a58 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -1,6 +1,7 @@ package baseapp import ( + "errors" "fmt" "sort" "strings" @@ -653,6 +654,19 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re anteEvents = events.ToABCIEvents() } + if mode == runTxModeCheck { + err = app.mempool.Insert(ctx, tx.(mempool.Tx)) + if err != nil { + return gInfo, nil, anteEvents, priority, err + } + } else if mode == runTxModeDeliver { + err = app.mempool.Remove(tx.(mempool.Tx)) + if err != nil && !errors.Is(err, mempool.ErrTxNotFound) { + return gInfo, nil, anteEvents, priority, + fmt.Errorf("failed to remove tx from mempool: %w", err) + } + } + // TODO remove nil check when implemented if mode == runTxModeCheck && app.mempool != nil { err = app.mempool.Insert(ctx, tx.(mempool.Tx)) From 47ba2c2dd16541ca9c97db8d5988470de1c12caf Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 10 Oct 2022 12:20:42 -0500 Subject: [PATCH 090/196] change not found error --- types/mempool/default.go | 2 +- types/mempool/mempool.go | 5 ++--- types/mempool/mempool_test.go | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/types/mempool/default.go b/types/mempool/default.go index fe8f6cd2240b..e134049932e5 100644 --- a/types/mempool/default.go +++ b/types/mempool/default.go @@ -202,7 +202,7 @@ func (mp *defaultMempool) Remove(tx Tx) error { sk := txKey{nonce: nonce, sender: sender} priority, ok := mp.scores[sk] if !ok { - return ErrTxNotFound{fmt.Errorf("tx %v not found in mempool", tx)} + return ErrTxNotFound } tk := txKey{nonce: nonce, priority: priority, sender: sender} diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index dc9a1224941c..5fb167192847 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -1,6 +1,7 @@ package mempool import ( + "errors" "github.com/cosmos/cosmos-sdk/types" ) @@ -34,8 +35,6 @@ type Mempool interface { Remove(Tx) error } -type ErrTxNotFound struct { - error -} +var ErrTxNotFound = errors.New("tx not found in mempool") type Factory func() Mempool diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index ed1810b5a695..fce362ccefce 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -154,7 +154,7 @@ func TestDefaultMempool(t *testing.T) { // removing a tx not in the mempool should error mp = mempool.NewDefaultMempool() require.NoError(t, mp.Insert(ctx, txs[0])) - require.ErrorAs(t, mp.Remove(txs[1]), &mempool.ErrTxNotFound{}) + require.ErrorIs(t, mp.Remove(txs[1]), mempool.ErrTxNotFound) mempool.DebugPrintKeys(mp) // inserting a tx with a different priority should overwrite the old tx From af4f5bc0c697bfa132dd445a63a2e1cea0f917bf Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 10 Oct 2022 12:28:19 -0500 Subject: [PATCH 091/196] abci1.0 methods --- baseapp/abci.go | 30 ++++++++++++++++++++++++++---- baseapp/baseapp.go | 1 + 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 4f52136fac10..6b0eafa958d6 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -249,8 +249,19 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { - // TODO: Implement. - return abci.ResponsePrepareProposal{Txs: req.Txs} + memTxs, selectErr := app.mempool.Select(req.Txs, req.MaxTxBytes) + if selectErr != nil { + panic(selectErr) + } + var txs [][]byte + for _, memTx := range memTxs { + bz, encErr := app.txEncoder(memTx) + if encErr != nil { + panic(encErr) + } + txs = append(txs, bz) + } + return abci.ResponsePrepareProposal{Txs: txs} } // ProcessProposal implements the ProcessProposal ABCI method and returns a @@ -266,8 +277,19 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { - // TODO: Implement. - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} + ctx := app.deliverState.ctx + + for _, txBytes := range req.Txs { + anteCtx, _ := app.cacheTxContext(ctx, txBytes) + tx, err := app.txDecoder(txBytes) + if err != nil { + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + } + ctx, err = app.anteHandler(anteCtx, tx, false) + if err != nil { + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + } + } } // CheckTx implements the ABCI interface and executes a tx in CheckTx mode. In diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index f968d78f8a58..91c2dbcbf8c0 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -56,6 +56,7 @@ type BaseApp struct { //nolint: maligned msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx + txEncoder sdk.TxEncoder // marshal sdk.Tx into []byte mempool mempool.Mempool // application side mempool anteHandler sdk.AnteHandler // ante handler for fee and auth From 1d5b1d4ea18526afafa0289623aa9e6e8cf7eef5 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 10 Oct 2022 12:28:38 -0500 Subject: [PATCH 092/196] fix import format --- types/mempool/mempool.go | 1 + 1 file changed, 1 insertion(+) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 5fb167192847..f32150d6f41e 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -2,6 +2,7 @@ package mempool import ( "errors" + "github.com/cosmos/cosmos-sdk/types" ) From 5be06eb9f6b1f086fda1d52ae0ca738310ad8f96 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 11 Oct 2022 08:33:20 -0500 Subject: [PATCH 093/196] more integration --- baseapp/abci.go | 2 ++ baseapp/baseapp.go | 8 ++++++++ baseapp/options.go | 5 +++++ simapp/app_legacy.go | 1 + x/auth/tx/module/module.go | 3 ++- 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 6b0eafa958d6..a7f95d042138 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -290,6 +290,8 @@ func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.Respon return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} } } + + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} } // CheckTx implements the ABCI interface and executes a tx in CheckTx mode. In diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 91c2dbcbf8c0..eb77b892a094 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -162,6 +162,14 @@ func NewBaseApp( option(app) } + // if execution of options has left certain required fields nil, let's set them to default values + if app.txEncoder == nil { + app.txEncoder = DefaultTxEncoder(app.cdc) + } + if app.mempool == nil { + app.mempool = mempool.NewDefaultMempool() + } + if app.interBlockCache != nil { app.cms.SetInterBlockCache(app.interBlockCache) } diff --git a/baseapp/options.go b/baseapp/options.go index f9a67f186d7c..ac3fff1d843a 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -240,3 +240,8 @@ func (app *BaseApp) SetStreamingService(s StreamingService) { func (app *BaseApp) SetTxDecoder(txDecoder sdk.TxDecoder) { app.txDecoder = txDecoder } + +// SetTxEncoder sets the TxEncoder if it wasn't provided in the BaseApp constructor. +func (app *BaseApp) SetTxEncoder(txEncoder sdk.TxEncoder) { + app.txEncoder = txEncoder +} diff --git a/simapp/app_legacy.go b/simapp/app_legacy.go index f3b8c85f3dd7..df7458811cc0 100644 --- a/simapp/app_legacy.go +++ b/simapp/app_legacy.go @@ -225,6 +225,7 @@ func NewSimApp( bApp.SetCommitMultiStoreTracer(traceStore) bApp.SetVersion(version.Version) bApp.SetInterfaceRegistry(interfaceRegistry) + bApp.SetTxEncoder(txConfig.TxEncoder()) keys := sdk.NewKVStoreKeys( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, crisistypes.StoreKey, diff --git a/x/auth/tx/module/module.go b/x/auth/tx/module/module.go index b005d6cadc16..8f142dc4ba95 100644 --- a/x/auth/tx/module/module.go +++ b/x/auth/tx/module/module.go @@ -79,8 +79,9 @@ func provideModule(in txInputs) txOutputs { app.SetPostHandler(postHandler) } - // TxDecoder + // TxDecoder/TxEncoder app.SetTxDecoder(txConfig.TxDecoder()) + app.SetTxEncoder(txConfig.TxEncoder()) } return txOutputs{TxConfig: txConfig, BaseAppOption: baseAppOption} From c2e7069752fcf29c0d33b78ee046f21bd139e459 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 11 Oct 2022 08:54:38 -0500 Subject: [PATCH 094/196] rename default impl -> priority --- types/mempool/{default.go => priority.go} | 28 +++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) rename types/mempool/{default.go => priority.go} (88%) diff --git a/types/mempool/default.go b/types/mempool/priority.go similarity index 88% rename from types/mempool/default.go rename to types/mempool/priority.go index e134049932e5..003918815bf3 100644 --- a/types/mempool/default.go +++ b/types/mempool/priority.go @@ -10,14 +10,14 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/signing" ) -var _ Mempool = (*defaultMempool)(nil) +var _ Mempool = (*priorityMempool)(nil) -// defaultMempool is the SDK's default mempool implementation which stores txs in a partially ordered set +// priorityMempool is the SDK's default mempool implementation which stores txs in a partially ordered set // by 2 dimensions: priority, and sender-nonce (sequence number). Internally it uses one priority ordered skip list // and one skip per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, // they are not always comparable by priority to other sender txs and must be partially ordered by both sender-nonce // and priority. -type defaultMempool struct { +type priorityMempool struct { priorityIndex *huandu.SkipList senderIndices map[string]*huandu.SkipList scores map[txKey]int64 @@ -48,19 +48,19 @@ func txKeyLess(a, b any) int { return huandu.Uint64.Compare(keyA.nonce, keyB.nonce) } -type DefaultMempoolOption func(*defaultMempool) +type PriorityMempoolOption func(*priorityMempool) // WithOnRead sets a callback to be called when a tx is read from the mempool. -func WithOnRead(onRead func(tx Tx)) DefaultMempoolOption { - return func(mp *defaultMempool) { +func WithOnRead(onRead func(tx Tx)) PriorityMempoolOption { + return func(mp *priorityMempool) { mp.onRead = onRead } } // NewDefaultMempool returns the SDK's default mempool implementation which returns txs in a partial order // by 2 dimensions; priority, and sender-nonce. -func NewDefaultMempool(opts ...DefaultMempoolOption) Mempool { - mp := &defaultMempool{ +func NewDefaultMempool(opts ...PriorityMempoolOption) Mempool { + mp := &priorityMempool{ priorityIndex: huandu.New(huandu.LessThanFunc(txKeyLess)), senderIndices: make(map[string]*huandu.SkipList), scores: make(map[txKey]int64), @@ -77,7 +77,7 @@ func NewDefaultMempool(opts ...DefaultMempoolOption) Mempool { // Inserting a duplicate tx is an O(log n) no-op. // Inserting a duplicate tx with a different priority overwrites the existing tx, changing the total order of // the mempool. -func (mp *defaultMempool) Insert(ctx sdk.Context, tx Tx) error { +func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { return err @@ -119,7 +119,7 @@ func (mp *defaultMempool) Insert(ctx sdk.Context, tx Tx) error { // Select returns a set of transactions from the mempool, ordered by priority and sender-nonce in O(n) time. // The passed in list of transactions are ignored. This is a readonly operation, the mempool is not modified. // maxBytes is the maximum number of bytes of transactions to return. -func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { +func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { var selectedTxs []Tx var txBytes int64 senderCursors := make(map[string]*huandu.Element) @@ -162,7 +162,7 @@ func (mp *defaultMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { return selectedTxs, nil } -func (mp *defaultMempool) fetchSenderCursor(senderCursors map[string]*huandu.Element, sender string) *huandu.Element { +func (mp *priorityMempool) fetchSenderCursor(senderCursors map[string]*huandu.Element, sender string) *huandu.Element { senderTx, ok := senderCursors[sender] if !ok { senderTx = mp.senderIndices[sender].Front() @@ -182,12 +182,12 @@ func nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { } // CountTx returns the number of transactions in the mempool. -func (mp *defaultMempool) CountTx() int { +func (mp *priorityMempool) CountTx() int { return mp.priorityIndex.Len() } // Remove removes a transaction from the mempool in O(log n) time, returning an error if unsuccessful. -func (mp *defaultMempool) Remove(tx Tx) error { +func (mp *priorityMempool) Remove(tx Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { return err @@ -219,7 +219,7 @@ func (mp *defaultMempool) Remove(tx Tx) error { } func DebugPrintKeys(mempool Mempool) { - mp := mempool.(*defaultMempool) + mp := mempool.(*priorityMempool) n := mp.priorityIndex.Front() for n != nil { k := n.Key().(txKey) From 42d52c11950ced6c4f18557d8518ff3f0ce75992 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 11 Oct 2022 08:56:35 -0500 Subject: [PATCH 095/196] add default constructor --- types/mempool/mempool_test.go | 8 ++++---- types/mempool/priority.go | 9 +++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index fce362ccefce..feb2ed1511d3 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -118,7 +118,7 @@ func TestDefaultMempool(t *testing.T) { } // same sender-nonce just overwrites a tx - mp := mempool.NewDefaultMempool() + mp := mempool.NewPriorityMempool() for _, tx := range txs { ctx.WithPriority(tx.priority) err := mp.Insert(ctx, tx) @@ -127,7 +127,7 @@ func TestDefaultMempool(t *testing.T) { require.Equal(t, len(accounts), mp.CountTx()) // distinct sender-nonce should not overwrite a tx - mp = mempool.NewDefaultMempool() + mp = mempool.NewPriorityMempool() for i, tx := range txs { tx.nonce = uint64(i) err := mp.Insert(ctx, tx) @@ -152,7 +152,7 @@ func TestDefaultMempool(t *testing.T) { require.Error(t, mp.Remove(tx)) // removing a tx not in the mempool should error - mp = mempool.NewDefaultMempool() + mp = mempool.NewPriorityMempool() require.NoError(t, mp.Insert(ctx, txs[0])) require.ErrorIs(t, mp.Remove(txs[1]), mempool.ErrTxNotFound) mempool.DebugPrintKeys(mp) @@ -348,7 +348,7 @@ type MempoolTestSuite struct { func (s *MempoolTestSuite) resetMempool() { s.iterations = 0 - s.mempool = mempool.NewDefaultMempool(mempool.WithOnRead(func(tx mempool.Tx) { + s.mempool = mempool.NewPriorityMempool(mempool.WithOnRead(func(tx mempool.Tx) { s.iterations++ })) } diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 003918815bf3..5a0d0d43bbf7 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -57,9 +57,14 @@ func WithOnRead(onRead func(tx Tx)) PriorityMempoolOption { } } -// NewDefaultMempool returns the SDK's default mempool implementation which returns txs in a partial order +// DefaultPriorityMempool returns a priorityMempool with no options. +func DefaultPriorityMempool() Mempool { + return NewPriorityMempool() +} + +// NewPriorityMempool returns the SDK's default mempool implementation which returns txs in a partial order // by 2 dimensions; priority, and sender-nonce. -func NewDefaultMempool(opts ...PriorityMempoolOption) Mempool { +func NewPriorityMempool(opts ...PriorityMempoolOption) Mempool { mp := &priorityMempool{ priorityIndex: huandu.New(huandu.LessThanFunc(txKeyLess)), senderIndices: make(map[string]*huandu.SkipList), From 00a7a8fb61cb71e88d2497d8491f3e377033b914 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 11 Oct 2022 09:02:09 -0500 Subject: [PATCH 096/196] default supplier in simapp --- baseapp/baseapp.go | 5 +---- simapp/app.go | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index eb77b892a094..8ff9fa1d3f4f 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -163,11 +163,8 @@ func NewBaseApp( } // if execution of options has left certain required fields nil, let's set them to default values - if app.txEncoder == nil { - app.txEncoder = DefaultTxEncoder(app.cdc) - } if app.mempool == nil { - app.mempool = mempool.NewDefaultMempool() + app.mempool = mempool.DefaultPriorityMempool() } if app.interBlockCache != nil { diff --git a/simapp/app.go b/simapp/app.go index 44a2f5e6e110..8b3cf7177ee9 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -5,6 +5,7 @@ package simapp import ( _ "embed" "fmt" + "github.com/cosmos/cosmos-sdk/types/mempool" "io" "os" "path/filepath" @@ -196,7 +197,7 @@ func NewSimApp( depinject.Supply( // supply the application options appOpts, - + mempool.DefaultPriorityMempool, // For providing a custom inflation function for x/mint add here your // custom function that implements the minttypes.InflationCalculationFn // interface. From be180e883893f3acec8d60fdbf9164cbf57b02be Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 11 Oct 2022 09:32:03 -0500 Subject: [PATCH 097/196] need a signature for removal in tx tests --- baseapp/deliver_tx_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/baseapp/deliver_tx_test.go b/baseapp/deliver_tx_test.go index b7daf0505bfc..905c2d4ae2a2 100644 --- a/baseapp/deliver_tx_test.go +++ b/baseapp/deliver_tx_test.go @@ -7,6 +7,8 @@ import ( "encoding/json" "errors" "fmt" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "math/rand" "net/url" "os" @@ -114,6 +116,7 @@ func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*baseapp.Base r := rand.New(rand.NewSource(3920758213583)) keyCounter := 0 + privKey := secp256k1.GenPrivKeyFromSecret([]byte("test")) for height := int64(1); height <= int64(config.blocks); height++ { app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: height}}) for txNum := 0; txNum < config.blockTxs; txNum++ { @@ -129,6 +132,8 @@ func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*baseapp.Base builder := txConfig.NewTxBuilder() builder.SetMsgs(msgs...) + // TODO generate pubkey with account so that this resolves + builder.SetSignatures(signingtypes.SignatureV2{}) txBytes, err := txConfig.TxEncoder()(builder.GetTx()) require.NoError(t, err) From 9d120c1b22608c7e322cd5bd4834ca5ec4611f64 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 11 Oct 2022 10:56:10 -0500 Subject: [PATCH 098/196] all tx in deliver_tx_test now have a sig --- baseapp/deliver_tx_test.go | 26 ++++++++++++++++++-------- baseapp/testutil/messages.go | 3 +++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/baseapp/deliver_tx_test.go b/baseapp/deliver_tx_test.go index 905c2d4ae2a2..c3f15f810531 100644 --- a/baseapp/deliver_tx_test.go +++ b/baseapp/deliver_tx_test.go @@ -7,8 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "math/rand" "net/url" "os" @@ -19,8 +17,9 @@ import ( "time" "unsafe" - "cosmossdk.io/depinject" - "github.com/cosmos/gogoproto/jsonpb" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -28,6 +27,9 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" + "cosmossdk.io/depinject" + "github.com/cosmos/gogoproto/jsonpb" + "github.com/cosmos/cosmos-sdk/baseapp" baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil" "github.com/cosmos/cosmos-sdk/client" @@ -116,7 +118,6 @@ func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*baseapp.Base r := rand.New(rand.NewSource(3920758213583)) keyCounter := 0 - privKey := secp256k1.GenPrivKeyFromSecret([]byte("test")) for height := int64(1); height <= int64(config.blocks); height++ { app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: height}}) for txNum := 0; txNum < config.blockTxs; txNum++ { @@ -132,8 +133,7 @@ func setupBaseAppWithSnapshots(t *testing.T, config *setupConfig) (*baseapp.Base builder := txConfig.NewTxBuilder() builder.SetMsgs(msgs...) - // TODO generate pubkey with account so that this resolves - builder.SetSignatures(signingtypes.SignatureV2{}) + setTxSignature(builder, 0) txBytes, err := txConfig.TxEncoder()(builder.GetTx()) require.NoError(t, err) @@ -1066,9 +1066,9 @@ func TestCheckTx(t *testing.T) { require.NoError(t, err) r := app.CheckTx(abci.RequestCheckTx{Tx: txBytes}) + require.True(t, r.IsOK(), fmt.Sprintf("%v", r)) require.Equal(t, testTxPriority, r.Priority) require.Empty(t, r.GetEvents()) - require.True(t, r.IsOK(), fmt.Sprintf("%v", r)) } checkStateStore := getCheckStateCtx(app.BaseApp).KVStore(capKey1) @@ -1213,6 +1213,7 @@ func TestMultiMsgDeliverTx(t *testing.T) { builder.SetMsgs(msgs...) builder.SetMemo(tx.GetMemo()) + setTxSignature(builder, 0) txBytes, err = txConfig.TxEncoder()(builder.GetTx()) require.NoError(t, err) @@ -1394,6 +1395,7 @@ func TestRunInvalidTransaction(t *testing.T) { { txBuilder := txConfig.NewTxBuilder() txBuilder.SetMsgs(&baseapptestutil.MsgCounter2{}) + setTxSignature(txBuilder, 0) unknownRouteTx := txBuilder.GetTx() _, result, err := app.SimDeliver(txConfig.TxEncoder(), unknownRouteTx) @@ -1406,6 +1408,7 @@ func TestRunInvalidTransaction(t *testing.T) { txBuilder = txConfig.NewTxBuilder() txBuilder.SetMsgs(&baseapptestutil.MsgCounter{}, &baseapptestutil.MsgCounter2{}) + setTxSignature(txBuilder, 0) unknownRouteTx = txBuilder.GetTx() _, result, err = app.SimDeliver(txConfig.TxEncoder(), unknownRouteTx) require.Error(t, err) @@ -1994,6 +1997,7 @@ func newTxCounter(cfg client.TxConfig, counter int64, msgCounters ...int64) sign builder := cfg.NewTxBuilder() builder.SetMsgs(msgs...) builder.SetMemo("counter=" + strconv.FormatInt(counter, 10) + "&failOnAnte=false") + setTxSignature(builder, uint64(counter)) return builder.GetTx() } @@ -2206,3 +2210,9 @@ func (ps paramStore) Get(ctx sdk.Context) (*tmproto.ConsensusParams, error) { return ¶ms, nil } + +func setTxSignature(builder client.TxBuilder, nonce uint64) { + privKey := secp256k1.GenPrivKeyFromSecret([]byte("test")) + pubKey := privKey.PubKey() + builder.SetSignatures(signingtypes.SignatureV2{PubKey: pubKey, Sequence: nonce}) +} diff --git a/baseapp/testutil/messages.go b/baseapp/testutil/messages.go index 54f72f30d77b..bc6a177ac3b9 100644 --- a/baseapp/testutil/messages.go +++ b/baseapp/testutil/messages.go @@ -2,6 +2,7 @@ package testutil import ( "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/msgservice" @@ -17,6 +18,8 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { msgservice.RegisterMsgServiceDesc(registry, &_Counter_serviceDesc) msgservice.RegisterMsgServiceDesc(registry, &_Counter2_serviceDesc) msgservice.RegisterMsgServiceDesc(registry, &_KeyValue_serviceDesc) + + codec.RegisterInterfaces(registry) } var _ sdk.Msg = &MsgCounter{} From 91e30d74e365047641d81abf19908bd6e01d348e Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 12 Oct 2022 10:05:24 -0500 Subject: [PATCH 099/196] remove unused test file --- types/mempool/stateful_test.go | 148 --------------------------------- 1 file changed, 148 deletions(-) delete mode 100644 types/mempool/stateful_test.go diff --git a/types/mempool/stateful_test.go b/types/mempool/stateful_test.go deleted file mode 100644 index 3a4b758e4e31..000000000000 --- a/types/mempool/stateful_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package mempool_test - -import ( - "bytes" - "crypto/sha256" - "fmt" - - huandu "github.com/huandu/skiplist" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/mempool" - "github.com/cosmos/cosmos-sdk/x/auth/signing" -) - -// The complexity is O(log(N)). Implementation -type statefullPriorityKey struct { - hash [32]byte - priority int64 - nonce uint64 -} - -type accountsHeadsKey struct { - sender string - priority int64 - hash [32]byte -} - -type AccountMemPool struct { - transactions *huandu.SkipList - currentKey accountsHeadsKey - currentItem *huandu.Element - sender string -} - -// Push cannot be executed in the middle of a select -func (amp *AccountMemPool) Push(ctx sdk.Context, key statefullPriorityKey, tx mempool.Tx) { - amp.transactions.Set(key, tx) - amp.currentItem = amp.transactions.Back() - newKey := amp.currentItem.Key().(statefullPriorityKey) - amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} -} - -func (amp *AccountMemPool) Pop() *mempool.Tx { - if amp.currentItem == nil { - return nil - } - itemToPop := amp.currentItem - amp.currentItem = itemToPop.Prev() - if amp.currentItem != nil { - newKey := amp.currentItem.Key().(statefullPriorityKey) - amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} - } else { - amp.currentKey = accountsHeadsKey{} - } - tx := itemToPop.Value.(mempool.Tx) - return &tx -} - -type MemPoolI struct { - accountsHeads *huandu.SkipList - senders map[string]*AccountMemPool -} - -func NewMemPoolI() MemPoolI { - return MemPoolI{ - accountsHeads: huandu.New(huandu.LessThanFunc(priorityHuanduLess)), - senders: make(map[string]*AccountMemPool), - } -} - -func (amp *MemPoolI) Insert(ctx sdk.Context, tx mempool.Tx) error { - senders := tx.(signing.SigVerifiableTx).GetSigners() - nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - - if err != nil { - return err - } else if len(senders) != len(nonces) { - return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) - } - sender := senders[0].String() - nonce := nonces[0].Sequence - - accountMeempool, ok := amp.senders[sender] - if !ok { - accountMeempool = &AccountMemPool{ - transactions: huandu.New(huandu.LessThanFunc(nonceHuanduLess)), - sender: sender, - } - } - hash := sha256.Sum256(senders[0].Bytes()) - key := statefullPriorityKey{hash: hash, nonce: nonce, priority: ctx.Priority()} - - prevKey := accountMeempool.currentKey - accountMeempool.Push(ctx, key, tx) - - amp.accountsHeads.Remove(prevKey) - amp.accountsHeads.Set(accountMeempool.currentKey, accountMeempool) - amp.senders[sender] = accountMeempool - return nil - -} - -func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int64) ([]mempool.Tx, error) { - var selectedTxs []mempool.Tx - var txBytes int64 - - currentAccount := amp.accountsHeads.Front() - for currentAccount != nil { - accountMemPool := currentAccount.Value.(*AccountMemPool) - //currentTx := accountMemPool.transactions.Front() - prevKey := accountMemPool.currentKey - tx := accountMemPool.Pop() - if tx == nil { - return selectedTxs, nil - } - mempoolTx := *tx - selectedTxs = append(selectedTxs, mempoolTx) - if txBytes += mempoolTx.Size(); txBytes >= maxBytes { - return selectedTxs, nil - } - - amp.accountsHeads.Remove(prevKey) - amp.accountsHeads.Set(accountMemPool.currentKey, accountMemPool) - currentAccount = amp.accountsHeads.Front() - } - return selectedTxs, nil -} - -func priorityHuanduLess(a, b interface{}) int { - keyA := a.(accountsHeadsKey) - keyB := b.(accountsHeadsKey) - if keyA.priority == keyB.priority { - return bytes.Compare(keyA.hash[:], keyB.hash[:]) - } else { - if keyA.priority < keyB.priority { - return -1 - } else { - return 1 - } - } -} - -func nonceHuanduLess(a, b interface{}) int { - keyA := a.(statefullPriorityKey) - keyB := b.(statefullPriorityKey) - uint64Compare := huandu.Uint64 - return uint64Compare.Compare(keyA.nonce, keyB.nonce) -} From 7d2fd41cc29bdbc8a3c1c604b9023082594bf1c8 Mon Sep 17 00:00:00 2001 From: Jeancarlo Barrios Date: Wed, 12 Oct 2022 11:08:31 -0600 Subject: [PATCH 100/196] Update types/mempool/mempool_test.go Co-authored-by: Amaury <1293565+amaurym@users.noreply.github.com> --- types/mempool/mempool_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index feb2ed1511d3..c23e645adcfc 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -120,7 +120,7 @@ func TestDefaultMempool(t *testing.T) { // same sender-nonce just overwrites a tx mp := mempool.NewPriorityMempool() for _, tx := range txs { - ctx.WithPriority(tx.priority) + ctx = ctx.WithPriority(tx.priority) err := mp.Insert(ctx, tx) require.NoError(t, err) } From b9a2e05538e094753bae0f64821354386041c362 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Wed, 12 Oct 2022 15:04:08 -0600 Subject: [PATCH 101/196] smiple benchmarks --- types/mempool/mempool_test.go | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index feb2ed1511d3..452325dfb136 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -649,6 +649,59 @@ func TestTxOrderN(t *testing.T) { } } +func BenchmarkDefaultMempool_Insert(b *testing.B) { + var inputs = []struct { + txN int + addressN int + }{ + {txN: 100, addressN: 4}, + {txN: 1000, addressN: 10}, + {txN: 100000, addressN: 1000}, + } + + for _, v := range inputs { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + genedTx := genRandomTxs(time.Now().UnixNano(), v.txN, v.addressN) + pool := mempool.NewPriorityMempool() + b.Run(fmt.Sprintf("txs: %d, addreses: %d", v.txN, v.addressN), func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, txP := range genedTx { + newCtx := ctx.WithPriority(txP.priority) + pool.Insert(newCtx, txP) + } + + } + }) + } +} + +func BenchmarkDefaultMempool_Select(b *testing.B) { + var inputs = []struct { + txN int + addressN int + }{ + {txN: 100, addressN: 4}, + {txN: 1000, addressN: 10}, + {txN: 100000, addressN: 1000}, + } + + for _, v := range inputs { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + genedTx := genRandomTxs(time.Now().UnixNano(), v.txN, v.addressN) + pool := mempool.NewPriorityMempool() + for _, tx := range genedTx { + newCtx := ctx.WithPriority(tx.priority) + pool.Insert(newCtx, tx) + } + b.Run(fmt.Sprintf("txs: %d, addreses: %d", v.txN, v.addressN), func(b *testing.B) { + for i := 0; i < b.N; i++ { + pool.Select(nil, int64(v.txN)) + + } + }) + } +} + func unmarshalTx(txBytes []byte) (sdk.Tx, error) { cfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}, gov.AppModuleBasic{}) cfg.InterfaceRegistry.RegisterImplementations((*govtypes.Content)(nil), &disttypes.CommunityPoolSpendProposal{}) From a1f2ae0dc1a6bff619bd298c10ff7bfa9ecd0d67 Mon Sep 17 00:00:00 2001 From: Facundo Medica <14063057+facundomedica@users.noreply.github.com> Date: Sun, 16 Oct 2022 00:08:30 -0300 Subject: [PATCH 102/196] Use checkState --- baseapp/abci.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 2aa46601fc9e..f51ca5a341ee 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -278,7 +278,7 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { - ctx := app.deliverState.ctx + ctx := app.checkState.ctx for _, txBytes := range req.Txs { anteCtx, _ := app.cacheTxContext(ctx, txBytes) From 5bd05f810f12e280b2ff3f9db11edbac61537008 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Mon, 17 Oct 2022 08:17:17 -0600 Subject: [PATCH 103/196] simple fix --- baseapp/util_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/util_test.go b/baseapp/util_test.go index 79690c6e24ec..3c39684c8f63 100644 --- a/baseapp/util_test.go +++ b/baseapp/util_test.go @@ -150,7 +150,7 @@ func makeTestConfig() depinject.Config { func makeMinimalConfig() depinject.Config { return depinject.Configs( - depinject.Supply(mempool.DefaultMempoolFactory), + depinject.Supply(mempool.DefaultPriorityMempool()), appconfig.Compose(&appv1alpha1.Config{ Modules: []*appv1alpha1.ModuleConfig{ { From 19e6e3e601bb5e741aedf5a75aae9c88b76db68c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 17 Oct 2022 20:43:24 -0500 Subject: [PATCH 104/196] update algorithm for failing test case --- types/mempool/mempool_test.go | 41 ++++++++- types/mempool/priority.go | 164 +++++++++++++++++++++++++--------- 2 files changed, 162 insertions(+), 43 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index c23e645adcfc..f26dfb537b29 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -307,6 +307,39 @@ func (s *MempoolTestSuite) TestTxOrder() { }, order: []int{3, 2, 0, 4, 1, 5, 6, 7, 8}, }, + { + txs: []txSpec{ + {p: 5, n: 1, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 5, n: 1, a: sb}, + {p: 99, n: 2, a: sb}, + }, + order: []int{2, 3, 0, 1}, + }, + { + txs: []txSpec{ + {p: 5, n: 1, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 20, n: 2, a: sb}, + {p: 5, n: 1, a: sb}, + {p: 99, n: 2, a: sc}, + {p: 5, n: 1, a: sc}, + }, + order: []int{5, 4, 3, 2, 0, 1}, + }, + // TODO find tx insert order which fails + // create test with random orders of these txs + { + txs: []txSpec{ + {p: 5, n: 1, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 5, n: 1, a: sb}, + {p: 20, n: 2, a: sb}, + {p: 5, n: 1, a: sc}, + {p: 99, n: 2, a: sc}, + }, + order: []int{4, 5, 2, 3, 0, 1}, + }, } for i, tt := range tests { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { @@ -320,6 +353,8 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, err) } + mempool.DebugPrintKeys(pool) + orderedTxs, err := pool.Select(nil, 1000) require.NoError(t, err) var txOrder []int @@ -333,11 +368,15 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, pool.Remove(tx)) } - require.Equal(t, 0, pool.CountTx()) + require.NoError(t, mempool.IsEmpty(pool)) }) } } +func (s *MempoolTestSuite) TestPriorityTies() { + +} + type MempoolTestSuite struct { suite.Suite numTxs int diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 5a0d0d43bbf7..c2fdd015fe6b 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -18,28 +18,38 @@ var _ Mempool = (*priorityMempool)(nil) // they are not always comparable by priority to other sender txs and must be partially ordered by both sender-nonce // and priority. type priorityMempool struct { - priorityIndex *huandu.SkipList - senderIndices map[string]*huandu.SkipList - scores map[txKey]int64 - onRead func(tx Tx) + priorityIndex *huandu.SkipList + priorityCounts map[int64]int + senderIndices map[string]*huandu.SkipList + senderCursors map[string]*huandu.Element + scores map[txMeta]txMeta + onRead func(tx Tx) } -type txKey struct { - nonce uint64 - priority int64 - sender string +// txMeta stores transaction metadata used in indices +type txMeta struct { + nonce uint64 + priority int64 + sender string + weight int64 + senderElement *huandu.Element } -// txKeyLess is a comparator for txKeys that first compares priority, then sender, then nonce, uniquely identifying +// txMetaLess is a comparator for txKeys that first compares priority, then sender, then nonce, uniquely identifying // a transaction. -func txKeyLess(a, b any) int { - keyA := a.(txKey) - keyB := b.(txKey) +func txMetaLess(a, b any) int { + keyA := a.(txMeta) + keyB := b.(txMeta) res := huandu.Int64.Compare(keyA.priority, keyB.priority) if res != 0 { return res } + res = huandu.Int64.Compare(keyA.weight, keyB.weight) + if res != 0 { + return res + } + res = huandu.String.Compare(keyA.sender, keyB.sender) if res != 0 { return res @@ -66,9 +76,11 @@ func DefaultPriorityMempool() Mempool { // by 2 dimensions; priority, and sender-nonce. func NewPriorityMempool(opts ...PriorityMempoolOption) Mempool { mp := &priorityMempool{ - priorityIndex: huandu.New(huandu.LessThanFunc(txKeyLess)), - senderIndices: make(map[string]*huandu.SkipList), - scores: make(map[txKey]int64), + priorityIndex: huandu.New(huandu.LessThanFunc(txMetaLess)), + priorityCounts: make(map[int64]int), + senderIndices: make(map[string]*huandu.SkipList), + senderCursors: make(map[string]*huandu.Element), + scores: make(map[txMeta]txMeta), } for _, opt := range opts { opt(mp) @@ -91,31 +103,50 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { return fmt.Errorf("tx must have at least one signer") } + priority := ctx.Priority() sig := sigs[0] sender := sig.PubKey.Address().String() nonce := sig.Sequence - tk := txKey{nonce: nonce, priority: ctx.Priority(), sender: sender} + tk := txMeta{nonce: nonce, priority: priority, sender: sender} + weight := int64(0) senderIndex, ok := mp.senderIndices[sender] if !ok { senderIndex = huandu.New(huandu.LessThanFunc(func(a, b any) int { - return huandu.Uint64.Compare(b.(txKey).nonce, a.(txKey).nonce) + return huandu.Uint64.Compare(b.(txMeta).nonce, a.(txMeta).nonce) })) // initialize sender index if not found mp.senderIndices[sender] = senderIndex } + mp.priorityCounts[priority] = mp.priorityCounts[priority] + 1 // Since senderIndex is scored by nonce, a changed priority will overwrite the existing txKey. - senderIndex.Set(tk, tx) + senderElement := senderIndex.Set(tk, tx) + if mp.priorityCounts[priority] > 1 { + // needs a weight + weight = senderWeight(senderElement) + } + if senderElement.Prev() != nil && mp.priorityCounts[senderElement.Prev().Key().(txMeta).priority] > 1 { + // previous element needs its weight updated + // delete/insert + } + + tk.weight = weight // Since mp.priorityIndex is scored by priority, then sender, then nonce, a changed priority will create a new key, // so we must remove the old key and re-insert it to avoid having the same tx with different priorityIndex indexed // twice in the mempool. This O(log n) remove operation is rare and only happens when a tx's priority changes. - sk := txKey{nonce: nonce, sender: sender} + sk := txMeta{nonce: nonce, sender: sender} if oldScore, txExists := mp.scores[sk]; txExists { - mp.priorityIndex.Remove(txKey{nonce: nonce, priority: oldScore, sender: sender}) + mp.priorityIndex.Remove(txMeta{ + nonce: nonce, + priority: oldScore.priority, + sender: sender, + weight: oldScore.weight, + }) } - mp.scores[sk] = ctx.Priority() + mp.scores[sk] = txMeta{priority: priority, weight: weight} + tk.senderElement = senderElement mp.priorityIndex.Set(tk, tx) return nil @@ -127,15 +158,14 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { var selectedTxs []Tx var txBytes int64 - senderCursors := make(map[string]*huandu.Element) + mp.senderCursors = make(map[string]*huandu.Element) - // start with the highest priority sender priorityNode := mp.priorityIndex.Front() for priorityNode != nil { - priorityKey := priorityNode.Key().(txKey) - nextHighestPriority, nextPriorityNode := nextPriority(priorityNode) + priorityKey := priorityNode.Key().(txMeta) + nextHighestPriority, nextPriorityNode := mp.nextPriority(priorityNode) sender := priorityKey.sender - senderTx := mp.fetchSenderCursor(senderCursors, sender) + senderTx := mp.fetchSenderCursor(sender) // iterate through the sender's transactions in nonce order for senderTx != nil { @@ -144,11 +174,19 @@ func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { mp.onRead(mempoolTx) } - k := senderTx.Key().(txKey) + key := senderTx.Key().(txMeta) + + fmt.Printf("read key: %v\n", key) // break if we've reached a transaction with a priority lower than the next highest priority in the pool - if k.priority < nextHighestPriority { + if key.priority < nextHighestPriority { break + } else if key.priority == nextHighestPriority { + weight := mp.scores[txMeta{nonce: key.nonce, sender: key.sender}].weight + if weight < nextPriorityNode.Key().(txMeta).weight { + fmt.Printf("skipping %v due to weight %v\n", key, weight) + break + } } // otherwise, select the transaction and continue iteration @@ -158,7 +196,7 @@ func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { } senderTx = senderTx.Next() - senderCursors[sender] = senderTx + mp.senderCursors[sender] = senderTx } priorityNode = nextPriorityNode @@ -167,22 +205,45 @@ func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { return selectedTxs, nil } -func (mp *priorityMempool) fetchSenderCursor(senderCursors map[string]*huandu.Element, sender string) *huandu.Element { - senderTx, ok := senderCursors[sender] +func senderWeight(senderCursor *huandu.Element) int64 { + if senderCursor == nil { + return 0 + } + weight := senderCursor.Key().(txMeta).priority + senderCursor = senderCursor.Next() + for senderCursor != nil { + p := senderCursor.Key().(txMeta).priority + if p != weight { + weight = p + } + senderCursor = senderCursor.Next() + } + + return weight +} + +func (mp *priorityMempool) fetchSenderCursor(sender string) *huandu.Element { + senderTx, ok := mp.senderCursors[sender] if !ok { senderTx = mp.senderIndices[sender].Front() } return senderTx } -func nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { - var np int64 - nextPriorityNode := priorityNode.Next() - if nextPriorityNode != nil { - np = nextPriorityNode.Key().(txKey).priority +func (mp *priorityMempool) nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { + var nextPriorityNode *huandu.Element + if priorityNode == nil { + nextPriorityNode = mp.priorityIndex.Front() } else { - np = math.MinInt64 + nextPriorityNode = priorityNode.Next() } + + if nextPriorityNode == nil { + return math.MinInt64, nil + } + + np := nextPriorityNode.Key().(txMeta).priority + return np, nextPriorityNode } @@ -204,12 +265,12 @@ func (mp *priorityMempool) Remove(tx Tx) error { sender := sig.PubKey.Address().String() nonce := sig.Sequence - sk := txKey{nonce: nonce, sender: sender} - priority, ok := mp.scores[sk] + sk := txMeta{nonce: nonce, sender: sender} + score, ok := mp.scores[sk] if !ok { return ErrTxNotFound } - tk := txKey{nonce: nonce, priority: priority, sender: sender} + tk := txMeta{nonce: nonce, priority: score.priority, sender: sender, weight: score.weight} senderTxs, ok := mp.senderIndices[sender] if !ok { @@ -219,7 +280,26 @@ func (mp *priorityMempool) Remove(tx Tx) error { mp.priorityIndex.Remove(tk) senderTxs.Remove(tk) delete(mp.scores, sk) + mp.priorityCounts[score.priority] = mp.priorityCounts[score.priority] - 1 + + return nil +} +func IsEmpty(mempool Mempool) error { + mp := mempool.(*priorityMempool) + if mp.priorityIndex.Len() != 0 { + return fmt.Errorf("priorityIndex not empty") + } + for k, v := range mp.priorityCounts { + if v != 0 { + return fmt.Errorf("priorityCounts not zero at %v, got %v", k, v) + } + } + for k, v := range mp.senderIndices { + if v.Len() != 0 { + return fmt.Errorf("senderIndex not empty for sender %v", k) + } + } return nil } @@ -227,8 +307,8 @@ func DebugPrintKeys(mempool Mempool) { mp := mempool.(*priorityMempool) n := mp.priorityIndex.Front() for n != nil { - k := n.Key().(txKey) - fmt.Printf("%s, %d, %d\n", k.sender, k.priority, k.nonce) + k := n.Key().(txMeta) + fmt.Printf("%s, %d, %d, %d\n", k.sender, k.priority, k.nonce, k.weight) n = n.Next() } } From 5cc97f4ff8e1e82b85cfc20016e5b7856de94294 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 17 Oct 2022 21:21:04 -0500 Subject: [PATCH 105/196] golint fix --- types/mempool/priority.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index c2fdd015fe6b..5f5227cf21d8 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -290,14 +290,14 @@ func IsEmpty(mempool Mempool) error { if mp.priorityIndex.Len() != 0 { return fmt.Errorf("priorityIndex not empty") } - for k, v := range mp.priorityCounts { + for key, v := range mp.priorityCounts { if v != 0 { - return fmt.Errorf("priorityCounts not zero at %v, got %v", k, v) + return fmt.Errorf("priorityCounts not zero at %v, got %v", key, v) } } - for k, v := range mp.senderIndices { + for key, v := range mp.senderIndices { if v.Len() != 0 { - return fmt.Errorf("senderIndex not empty for sender %v", k) + return fmt.Errorf("senderIndex not empty for sender %v", key) } } return nil From 1366a87d0de1411606d0db2571fe3f498ee74f07 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 18 Oct 2022 09:04:57 -0500 Subject: [PATCH 106/196] fix gosec warning --- types/mempool/priority.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 5f5227cf21d8..2f5fbf31ddc2 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -290,14 +290,16 @@ func IsEmpty(mempool Mempool) error { if mp.priorityIndex.Len() != 0 { return fmt.Errorf("priorityIndex not empty") } - for key, v := range mp.priorityCounts { + for k := range mp.priorityCounts { + v := mp.priorityCounts[k] if v != 0 { - return fmt.Errorf("priorityCounts not zero at %v, got %v", key, v) + return fmt.Errorf("priorityCounts not zero at %v, got %v", k, v) } } - for key, v := range mp.senderIndices { + for k := range mp.senderIndices { + v := mp.senderIndices[k] if v.Len() != 0 { - return fmt.Errorf("senderIndex not empty for sender %v", key) + return fmt.Errorf("senderIndex not empty for sender %v", k) } } return nil From f2457faa3439b79b50426fcbc2417e2632ca85e8 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 18 Oct 2022 10:33:28 -0500 Subject: [PATCH 107/196] working solution --- types/mempool/mempool_test.go | 13 +++++-- types/mempool/priority.go | 65 ++++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index f26dfb537b29..d6c53dbf6e0f 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -327,8 +327,6 @@ func (s *MempoolTestSuite) TestTxOrder() { }, order: []int{5, 4, 3, 2, 0, 1}, }, - // TODO find tx insert order which fails - // create test with random orders of these txs { txs: []txSpec{ {p: 5, n: 1, a: sa}, @@ -340,6 +338,17 @@ func (s *MempoolTestSuite) TestTxOrder() { }, order: []int{4, 5, 2, 3, 0, 1}, }, + { + txs: []txSpec{ + {p: 5, n: 1, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 5, n: 1, a: sc}, + {p: 20, n: 2, a: sc}, + {p: 5, n: 1, a: sb}, + {p: 99, n: 2, a: sb}, + }, + order: []int{4, 5, 2, 3, 0, 1}, + }, } for i, tt := range tests { t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 2f5fbf31ddc2..27db98821585 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -107,8 +107,7 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { sig := sigs[0] sender := sig.PubKey.Address().String() nonce := sig.Sequence - tk := txMeta{nonce: nonce, priority: priority, sender: sender} - weight := int64(0) + key := txMeta{nonce: nonce, priority: priority, sender: sender} senderIndex, ok := mp.senderIndices[sender] if !ok { @@ -120,18 +119,22 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { } mp.priorityCounts[priority] = mp.priorityCounts[priority] + 1 - // Since senderIndex is scored by nonce, a changed priority will overwrite the existing txKey. - senderElement := senderIndex.Set(tk, tx) - if mp.priorityCounts[priority] > 1 { - // needs a weight - weight = senderWeight(senderElement) - } - if senderElement.Prev() != nil && mp.priorityCounts[senderElement.Prev().Key().(txMeta).priority] > 1 { - // previous element needs its weight updated - // delete/insert - } - - tk.weight = weight + // Since senderIndex is scored by nonce, a changed priority will overwrite the existing key. + senderTx := senderIndex.Set(key, tx) + + // multiple txs at the same priority require a weight to differentiate them. + //if mp.priorityCounts[priority] > 1 { + // // needs a weight + // key.weight = senderWeight(senderTx) + //} + //if mp.priorityCounts[priority] == 2 { + // + //} + //prevSenderTx := senderTx.Prev() + //if prevSenderTx != nil && mp.priorityCounts[prevSenderTx.Key().(txMeta).priority] > 1 { + // // previous elements need their weights updated now + // // delete/insert + //} // Since mp.priorityIndex is scored by priority, then sender, then nonce, a changed priority will create a new key, // so we must remove the old key and re-insert it to avoid having the same tx with different priorityIndex indexed @@ -145,9 +148,9 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { weight: oldScore.weight, }) } - mp.scores[sk] = txMeta{priority: priority, weight: weight} - tk.senderElement = senderElement - mp.priorityIndex.Set(tk, tx) + mp.scores[sk] = txMeta{priority: priority, weight: key.weight} + key.senderElement = senderTx + mp.priorityIndex.Set(key, tx) return nil } @@ -159,6 +162,7 @@ func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { var selectedTxs []Tx var txBytes int64 mp.senderCursors = make(map[string]*huandu.Element) + mp.reorderByWeight() priorityNode := mp.priorityIndex.Front() for priorityNode != nil { @@ -205,6 +209,33 @@ func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { return selectedTxs, nil } +type reorderKey struct { + deleteKey txMeta + insertKey txMeta + tx Tx +} + +func (mp *priorityMempool) reorderByWeight() { + node := mp.priorityIndex.Front() + var reordering []reorderKey + for node != nil { + key := node.Key().(txMeta) + if mp.priorityCounts[key.priority] > 1 { + newKey := key + newKey.weight = senderWeight(key.senderElement) + reordering = append(reordering, reorderKey{deleteKey: key, insertKey: newKey, tx: node.Value.(Tx)}) + } + node = node.Next() + } + + for _, k := range reordering { + mp.priorityIndex.Remove(k.deleteKey) + delete(mp.scores, txMeta{nonce: k.deleteKey.nonce, sender: k.deleteKey.sender}) + mp.priorityIndex.Set(k.insertKey, k.tx) + mp.scores[txMeta{nonce: k.insertKey.nonce, sender: k.insertKey.sender}] = k.insertKey + } +} + func senderWeight(senderCursor *huandu.Element) int64 { if senderCursor == nil { return 0 From d7dc83a405809b941aee1833ac5e0ecde0f5c9fd Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 18 Oct 2022 10:33:51 -0500 Subject: [PATCH 108/196] remove debug prints --- types/mempool/priority.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 27db98821585..b34d7fddf9f9 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -180,15 +180,12 @@ func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { key := senderTx.Key().(txMeta) - fmt.Printf("read key: %v\n", key) - // break if we've reached a transaction with a priority lower than the next highest priority in the pool if key.priority < nextHighestPriority { break } else if key.priority == nextHighestPriority { weight := mp.scores[txMeta{nonce: key.nonce, sender: key.sender}].weight if weight < nextPriorityNode.Key().(txMeta).weight { - fmt.Printf("skipping %v due to weight %v\n", key, weight) break } } From bfee335fb4121fcbe4d9a71c4e69884c570d0478 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 18 Oct 2022 11:29:01 -0500 Subject: [PATCH 109/196] add priority ties test --- types/mempool/mempool_test.go | 80 ++++++++++++++++++++++++++++------- types/mempool/priority.go | 19 ++------- 2 files changed, 68 insertions(+), 31 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index d6c53dbf6e0f..c29f1e45a2c5 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -48,7 +48,7 @@ func (t testPubKey) Type() string { panic("implement me") } // testTx is a dummy implementation of Tx used for testing. type testTx struct { - hash [32]byte + id int priority int64 nonce uint64 address sdk.AccAddress @@ -74,8 +74,6 @@ var ( _ cryptotypes.PubKey = (*testPubKey)(nil) ) -func (tx testTx) GetHash() [32]byte { return tx.hash } - func (tx testTx) Size() int64 { return 1 } func (tx testTx) GetMsgs() []sdk.Msg { return nil } @@ -169,7 +167,6 @@ func TestDefaultMempool(t *testing.T) { type txSpec struct { i int - h int p int n int a sdk.AccAddress @@ -307,6 +304,18 @@ func (s *MempoolTestSuite) TestTxOrder() { }, order: []int{3, 2, 0, 4, 1, 5, 6, 7, 8}, }, + /* + The next 4 tests are different permutations of the same set: + + {p: 5, n: 1, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 20, n: 2, a: sb}, + {p: 5, n: 1, a: sb}, + {p: 99, n: 2, a: sc}, + {p: 5, n: 1, a: sc}, + + which exercises the actions required to resolve priority ties. + */ { txs: []txSpec{ {p: 5, n: 1, a: sa}, @@ -356,19 +365,17 @@ func (s *MempoolTestSuite) TestTxOrder() { // create test txs and insert into mempool for i, ts := range tt.txs { - tx := testTx{hash: [32]byte{byte(i)}, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} + tx := testTx{id: i, priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a} c := ctx.WithPriority(tx.priority) err := pool.Insert(c, tx) require.NoError(t, err) } - mempool.DebugPrintKeys(pool) - orderedTxs, err := pool.Select(nil, 1000) require.NoError(t, err) var txOrder []int for _, tx := range orderedTxs { - txOrder = append(txOrder, int(tx.(testTx).hash[0])) + txOrder = append(txOrder, tx.(testTx).id) } require.Equal(t, tt.order, txOrder) require.NoError(t, validateOrder(orderedTxs)) @@ -383,7 +390,50 @@ func (s *MempoolTestSuite) TestTxOrder() { } func (s *MempoolTestSuite) TestPriorityTies() { + ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 3) + sa := accounts[0].Address + sb := accounts[1].Address + sc := accounts[2].Address + + txSet := []txSpec{ + {p: 5, n: 1, a: sc}, + {p: 99, n: 2, a: sc}, + {p: 5, n: 1, a: sb}, + {p: 20, n: 2, a: sb}, + {p: 5, n: 1, a: sa}, + {p: 10, n: 2, a: sa}, + } + for i := 0; i < 100; i++ { + s.resetMempool() + var shuffled []txSpec + for _, t := range txSet { + tx := txSpec{ + p: t.p, + n: t.n, + a: t.a, + } + shuffled = append(shuffled, tx) + } + rand.Shuffle(len(shuffled), func(i, j int) { shuffled[i], shuffled[j] = shuffled[j], shuffled[i] }) + + for id, ts := range shuffled { + tx := testTx{priority: int64(ts.p), nonce: uint64(ts.n), address: ts.a, id: id} + c := ctx.WithPriority(tx.priority) + err := s.mempool.Insert(c, tx) + s.NoError(err) + } + selected, err := s.mempool.Select(nil, 1000) + s.NoError(err) + var orderedTxs []txSpec + for _, tx := range selected { + ttx := tx.(testTx) + ts := txSpec{p: int(ttx.priority), n: int(ttx.nonce), a: ttx.address} + orderedTxs = append(orderedTxs, ts) + } + s.Equal(txSet, orderedTxs) + } } type MempoolTestSuite struct { @@ -490,7 +540,7 @@ func (s *MempoolTestSuite) TestRandomGeneratedTxs() { mp := s.mempool for _, otx := range generated { - tx := testTx{hash: otx.hash, priority: otx.priority, nonce: otx.nonce, address: otx.address} + tx := testTx{id: otx.id, priority: otx.priority, nonce: otx.nonce, address: otx.address} c := ctx.WithPriority(tx.priority) err := mp.Insert(c, tx) require.NoError(t, err) @@ -522,7 +572,7 @@ func (s *MempoolTestSuite) TestRandomWalkTxs() { mp := s.mempool for _, otx := range shuffled { - tx := testTx{hash: otx.hash, priority: otx.priority, nonce: otx.nonce, address: otx.address} + tx := testTx{id: otx.id, priority: otx.priority, nonce: otx.nonce, address: otx.address} c := ctx.WithPriority(tx.priority) err := mp.Insert(c, tx) require.NoError(t, err) @@ -538,9 +588,9 @@ func (s *MempoolTestSuite) TestRandomWalkTxs() { otx := ordered[i] stx := selected[i].(testTx) orderedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", - orderedStr, otx.address, otx.priority, otx.nonce, otx.hash[0]) + orderedStr, otx.address, otx.priority, otx.nonce, otx.id) selectedStr = fmt.Sprintf("%s\n%s, %d, %d; %d", - selectedStr, stx.address, stx.priority, stx.nonce, stx.hash[0]) + selectedStr, stx.address, stx.priority, stx.nonce, stx.id) } require.NoError(t, err) @@ -591,7 +641,7 @@ func genRandomTxs(seed int64, countTx int, countAccount int) (res []testTx) { priority: priority, nonce: nonce, address: addr, - hash: [32]byte{byte(i)}}) + id: i}) } return res @@ -655,7 +705,7 @@ func genOrderedTxs(seed int64, maxTx int, numAcc int) (ordered []testTx, shuffle tx = testTx{nonce: nonce, address: sender, priority: txCursor} samepChain[sender.String()] = true } - tx.hash = [32]byte{byte(i)} + tx.id = i accountNonces[tx.address.String()] = tx.nonce ordered = append(ordered, tx) ptx = tx @@ -670,7 +720,7 @@ func genOrderedTxs(seed int64, maxTx int, numAcc int) (ordered []testTx, shuffle priority: item.priority, nonce: item.nonce, address: item.address, - hash: item.hash, + id: item.id, } shuffled = append(shuffled, tx) } diff --git a/types/mempool/priority.go b/types/mempool/priority.go index b34d7fddf9f9..2cb64e292989 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -119,23 +119,10 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { } mp.priorityCounts[priority] = mp.priorityCounts[priority] + 1 + // Since senderIndex is scored by nonce, a changed priority will overwrite the existing key. senderTx := senderIndex.Set(key, tx) - // multiple txs at the same priority require a weight to differentiate them. - //if mp.priorityCounts[priority] > 1 { - // // needs a weight - // key.weight = senderWeight(senderTx) - //} - //if mp.priorityCounts[priority] == 2 { - // - //} - //prevSenderTx := senderTx.Prev() - //if prevSenderTx != nil && mp.priorityCounts[prevSenderTx.Key().(txMeta).priority] > 1 { - // // previous elements need their weights updated now - // // delete/insert - //} - // Since mp.priorityIndex is scored by priority, then sender, then nonce, a changed priority will create a new key, // so we must remove the old key and re-insert it to avoid having the same tx with different priorityIndex indexed // twice in the mempool. This O(log n) remove operation is rare and only happens when a tx's priority changes. @@ -162,7 +149,7 @@ func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { var selectedTxs []Tx var txBytes int64 mp.senderCursors = make(map[string]*huandu.Element) - mp.reorderByWeight() + mp.reorderPriorityTies() priorityNode := mp.priorityIndex.Front() for priorityNode != nil { @@ -212,7 +199,7 @@ type reorderKey struct { tx Tx } -func (mp *priorityMempool) reorderByWeight() { +func (mp *priorityMempool) reorderPriorityTies() { node := mp.priorityIndex.Front() var reordering []reorderKey for node != nil { From 52e10e6d6f5bf0736a48e9b0cbfb2a582201aa0c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 18 Oct 2022 11:56:58 -0500 Subject: [PATCH 110/196] comments --- types/mempool/mempool_test.go | 12 +++++++++++- types/mempool/priority.go | 24 ++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index c29f1e45a2c5..5fa36070b150 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -257,7 +257,8 @@ func (s *MempoolTestSuite) TestTxOrder() { {p: 15, n: 1, a: sb}, {p: 20, n: 1, a: sa}, }, - order: []int{2, 0, 1}}, + order: []int{2, 0, 1}, + }, { txs: []txSpec{ {p: 50, n: 3, a: sa}, @@ -304,6 +305,15 @@ func (s *MempoolTestSuite) TestTxOrder() { }, order: []int{3, 2, 0, 4, 1, 5, 6, 7, 8}, }, + { + txs: []txSpec{ + {p: 6, n: 1, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 5, n: 1, a: sb}, + {p: 99, n: 2, a: sb}, + }, + order: []int{0, 1, 2, 3}, + }, /* The next 4 tests are different permutations of the same set: diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 2cb64e292989..414873a875b0 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -35,8 +35,10 @@ type txMeta struct { senderElement *huandu.Element } -// txMetaLess is a comparator for txKeys that first compares priority, then sender, then nonce, uniquely identifying -// a transaction. +// txMetaLess is a comparator for txKeys that first compares priority, then weight, then sender, then nonce, +// uniquely identifying a transaction. +// +// Note, txMetaLess is used as the comparator in the priority index. func txMetaLess(a, b any) int { keyA := a.(txMeta) keyB := b.(txMeta) @@ -220,6 +222,9 @@ func (mp *priorityMempool) reorderPriorityTies() { } } +// senderWeight returns the weight of a given tx (t) at senderCursor. Weight is defined as the first (nonce-wise) +// same sender tx with a priority not equal to t. It is used to resolve priority collisions, that is when 2 or more +// txs from different senders have the same priority. func senderWeight(senderCursor *huandu.Element) int64 { if senderCursor == nil { return 0 @@ -276,12 +281,13 @@ func (mp *priorityMempool) Remove(tx Tx) error { if len(sigs) == 0 { return fmt.Errorf("attempted to remove a tx with no signatures") } + sig := sigs[0] sender := sig.PubKey.Address().String() nonce := sig.Sequence - sk := txMeta{nonce: nonce, sender: sender} - score, ok := mp.scores[sk] + scoreKey := txMeta{nonce: nonce, sender: sender} + score, ok := mp.scores[scoreKey] if !ok { return ErrTxNotFound } @@ -294,7 +300,7 @@ func (mp *priorityMempool) Remove(tx Tx) error { mp.priorityIndex.Remove(tk) senderTxs.Remove(tk) - delete(mp.scores, sk) + delete(mp.scores, scoreKey) mp.priorityCounts[score.priority] = mp.priorityCounts[score.priority] - 1 return nil @@ -306,14 +312,12 @@ func IsEmpty(mempool Mempool) error { return fmt.Errorf("priorityIndex not empty") } for k := range mp.priorityCounts { - v := mp.priorityCounts[k] - if v != 0 { - return fmt.Errorf("priorityCounts not zero at %v, got %v", k, v) + if mp.priorityCounts[k] != 0 { + return fmt.Errorf("priorityCounts not zero at %v, got %v", k, mp.priorityCounts[k]) } } for k := range mp.senderIndices { - v := mp.senderIndices[k] - if v.Len() != 0 { + if mp.senderIndices[k].Len() != 0 { return fmt.Errorf("senderIndex not empty for sender %v", k) } } From 4e429dd69298879f3d27f31b594311d81d875ceb Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 18 Oct 2022 12:02:45 -0500 Subject: [PATCH 111/196] Add godoc to mempool.Size implementation --- x/auth/tx/builder.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 5cb5939c6cbe..a76afc175d31 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -66,6 +66,8 @@ func newBuilder(cdc codec.Codec) *wrapper { } } +// Size returns the size of the transaction, but is only correct immediately after decoding a proto-marshal transaction. +// It should not be used in any other cases. func (w *wrapper) Size() int64 { return w.txSize } From 5c80aaf5a5fe37af3e3a24ae9e493726d79a788d Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 18 Oct 2022 12:05:30 -0500 Subject: [PATCH 112/196] increment --- types/mempool/priority.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 414873a875b0..319bd8bcd7cd 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -120,7 +120,7 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { mp.senderIndices[sender] = senderIndex } - mp.priorityCounts[priority] = mp.priorityCounts[priority] + 1 + mp.priorityCounts[priority]++ // Since senderIndex is scored by nonce, a changed priority will overwrite the existing key. senderTx := senderIndex.Set(key, tx) @@ -301,7 +301,7 @@ func (mp *priorityMempool) Remove(tx Tx) error { mp.priorityIndex.Remove(tk) senderTxs.Remove(tk) delete(mp.scores, scoreKey) - mp.priorityCounts[score.priority] = mp.priorityCounts[score.priority] - 1 + mp.priorityCounts[score.priority]-- return nil } From 46fb8c401d86c0ccddfe89c1fff61cdcb048aec2 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 18 Oct 2022 12:16:06 -0500 Subject: [PATCH 113/196] add another test case --- types/mempool/mempool_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 5fa36070b150..68a9a22656a1 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -314,6 +314,22 @@ func (s *MempoolTestSuite) TestTxOrder() { }, order: []int{0, 1, 2, 3}, }, + { + // if all txs have the same priority they will be ordered lexically sender address, and nonce with the + // sender. + txs: []txSpec{ + {p: 10, n: 7, a: sc}, + {p: 10, n: 8, a: sc}, + {p: 10, n: 9, a: sc}, + {p: 10, n: 1, a: sa}, + {p: 10, n: 2, a: sa}, + {p: 10, n: 3, a: sa}, + {p: 10, n: 4, a: sb}, + {p: 10, n: 5, a: sb}, + {p: 10, n: 6, a: sb}, + }, + order: []int{0, 1, 2, 3, 4, 5, 6, 7, 8}, + }, /* The next 4 tests are different permutations of the same set: @@ -381,6 +397,8 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, err) } + mempool.DebugPrintKeys(pool) + orderedTxs, err := pool.Select(nil, 1000) require.NoError(t, err) var txOrder []int From 078168bde9cc008c59241fdde063ac3bd025adb3 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 18 Oct 2022 12:22:17 -0500 Subject: [PATCH 114/196] fix gosec error --- types/mempool/priority.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 319bd8bcd7cd..4961deeef435 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -311,16 +311,27 @@ func IsEmpty(mempool Mempool) error { if mp.priorityIndex.Len() != 0 { return fmt.Errorf("priorityIndex not empty") } + + var countKeys []int64 for k := range mp.priorityCounts { + countKeys = append(countKeys, k) + } + for _, k := range countKeys { if mp.priorityCounts[k] != 0 { return fmt.Errorf("priorityCounts not zero at %v, got %v", k, mp.priorityCounts[k]) } } + + var senderKeys []string for k := range mp.senderIndices { + senderKeys = append(senderKeys, k) + } + for _, k := range senderKeys { if mp.senderIndices[k].Len() != 0 { return fmt.Errorf("senderIndex not empty for sender %v", k) } } + return nil } From e321ec2c9cd734690cfce34fa5ef10efaf9eaa8f Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Tue, 18 Oct 2022 18:10:33 -0600 Subject: [PATCH 115/196] implement missing methods for tx to pass app test --- server/mock/app_test.go | 10 ++++- server/mock/tx.go | 84 +++++++++++++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/server/mock/app_test.go b/server/mock/app_test.go index 94362c887068..6ae13632d8ea 100644 --- a/server/mock/app_test.go +++ b/server/mock/app_test.go @@ -1,6 +1,10 @@ package mock import ( + "math/rand" + "time" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "testing" "github.com/stretchr/testify/require" @@ -53,7 +57,11 @@ func TestDeliverTx(t *testing.T) { key := "my-special-key" value := "top-secret-data!!" - tx := NewTx(key, value) + + r := rand.New(rand.NewSource(time.Now().UnixNano())) + randomAccounts := simtypes.RandomAccounts(r, 1) + + tx := NewTx(key, value, randomAccounts[0].Address) txBytes := tx.GetSignBytes() header := tmproto.Header{ diff --git a/server/mock/tx.go b/server/mock/tx.go index 40555e8724e8..082dbb8d8f26 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -4,15 +4,67 @@ import ( "bytes" "fmt" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/types/mempool" + txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // An sdk.Tx which is its own sdk.Msg. type kvstoreTx struct { - key []byte - value []byte - bytes []byte + key []byte + value []byte + bytes []byte + address sdk.AccAddress +} +type testPubKey struct { + address sdk.AccAddress +} + +func (t testPubKey) Reset() { panic("implement me") } + +func (t testPubKey) String() string { panic("implement me") } + +func (t testPubKey) ProtoMessage() { panic("implement me") } + +func (t testPubKey) Address() cryptotypes.Address { return t.address.Bytes() } + +func (t testPubKey) Bytes() []byte { panic("implement me") } + +func (t testPubKey) VerifySignature(msg []byte, sig []byte) bool { panic("implement me") } + +func (t testPubKey) Equals(key cryptotypes.PubKey) bool { panic("implement me") } + +func (t testPubKey) Type() string { panic("implement me") } + +func (msg *kvstoreTx) GetSignaturesV2() (res []txsigning.SignatureV2, err error) { + res = append(res, txsigning.SignatureV2{ + PubKey: testPubKey{address: msg.address}, + Data: nil, + Sequence: 1, + }) + + return res, nil +} + +func (msg *kvstoreTx) VerifySignature(msgByte []byte, sig []byte) bool { + panic("implement me") +} + +func (msg *kvstoreTx) Address() cryptotypes.Address { + panic("implement me") +} + +func (msg *kvstoreTx) Bytes() []byte { + panic("implement me") +} + +func (msg *kvstoreTx) Equals(key cryptotypes.PubKey) bool { + panic("implement me") } // dummy implementation of proto.Message @@ -21,16 +73,20 @@ func (msg *kvstoreTx) String() string { return "TODO" } func (msg *kvstoreTx) ProtoMessage() {} var ( - _ sdk.Tx = &kvstoreTx{} - _ sdk.Msg = &kvstoreTx{} + _ sdk.Tx = &kvstoreTx{} + _ mempool.Tx = &kvstoreTx{} + _ sdk.Msg = &kvstoreTx{} + _ signing.SigVerifiableTx = &kvstoreTx{} + _ cryptotypes.PubKey = &kvstoreTx{} ) -func NewTx(key, value string) *kvstoreTx { +func NewTx(key, value string, accAddress sdk.AccAddress) *kvstoreTx { bytes := fmt.Sprintf("%s=%s", key, value) return &kvstoreTx{ - key: []byte(key), - value: []byte(value), - bytes: []byte(bytes), + key: []byte(key), + value: []byte(value), + bytes: []byte(bytes), + address: accAddress, } } @@ -55,6 +111,12 @@ func (tx *kvstoreTx) GetSigners() []sdk.AccAddress { return nil } +func (tx *kvstoreTx) Size() int64 { + return int64(len(tx.bytes)) +} + +func (tx *kvstoreTx) GetPubKeys() ([]cryptotypes.PubKey, error) { panic("GetPubKeys not implemented") } + // takes raw transaction bytes and decodes them into an sdk.Tx. An sdk.Tx has // all the signatures and can be used to authenticate. func decodeTx(txBytes []byte) (sdk.Tx, error) { @@ -63,10 +125,10 @@ func decodeTx(txBytes []byte) (sdk.Tx, error) { split := bytes.Split(txBytes, []byte("=")) if len(split) == 1 { //nolint:gocritic k := split[0] - tx = &kvstoreTx{k, k, txBytes} + tx = &kvstoreTx{k, k, txBytes, nil} } else if len(split) == 2 { k, v := split[0], split[1] - tx = &kvstoreTx{k, v, txBytes} + tx = &kvstoreTx{k, v, txBytes, nil} } else { return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "too many '='") } From 227ea17b020fbe34fd687c048171aae8892d1b25 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 19 Oct 2022 13:04:07 -0400 Subject: [PATCH 116/196] Update types/mempool/priority.go --- types/mempool/priority.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 4961deeef435..8a0883e471d3 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -46,7 +46,7 @@ func txMetaLess(a, b any) int { if res != 0 { return res } - +// below we compare by sender and then by nonce if necessary, when conflicting priorities are found res = huandu.Int64.Compare(keyA.weight, keyB.weight) if res != 0 { return res From 522aaab4a5ab3cf5fbed3eec28469c1531fa238a Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 19 Oct 2022 10:13:44 -0700 Subject: [PATCH 117/196] bez: lint/godoc --- types/mempool/priority.go | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 8a0883e471d3..32c27c9ba635 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -12,10 +12,12 @@ import ( var _ Mempool = (*priorityMempool)(nil) -// priorityMempool is the SDK's default mempool implementation which stores txs in a partially ordered set -// by 2 dimensions: priority, and sender-nonce (sequence number). Internally it uses one priority ordered skip list -// and one skip per sender ordered by nonce (sender-nonce). When there are multiple txs from the same sender, -// they are not always comparable by priority to other sender txs and must be partially ordered by both sender-nonce +// priorityMempool defines the SDK's default mempool implementation which stores +// txs in a partially ordered set by 2 dimensions: priority, and sender-nonce +// (sequence number). Internally it uses one priority ordered skip list and one +// skip list per sender ordered by sender-nonce (sequence number). When there +// are multiple txs from the same sender, they are not always comparable by +// priority to other sender txs and must be partially ordered by both sender-nonce // and priority. type priorityMempool struct { priorityIndex *huandu.SkipList @@ -35,8 +37,8 @@ type txMeta struct { senderElement *huandu.Element } -// txMetaLess is a comparator for txKeys that first compares priority, then weight, then sender, then nonce, -// uniquely identifying a transaction. +// txMetaLess is a comparator for txKeys that first compares priority, then weight, +// then sender, then nonce, uniquely identifying a transaction. // // Note, txMetaLess is used as the comparator in the priority index. func txMetaLess(a, b any) int { @@ -46,7 +48,9 @@ func txMetaLess(a, b any) int { if res != 0 { return res } -// below we compare by sender and then by nonce if necessary, when conflicting priorities are found + + // Below we compare by sender and then by nonce if necessary, when conflicting + // priorities are found. res = huandu.Int64.Compare(keyA.weight, keyB.weight) if res != 0 { return res @@ -74,8 +78,8 @@ func DefaultPriorityMempool() Mempool { return NewPriorityMempool() } -// NewPriorityMempool returns the SDK's default mempool implementation which returns txs in a partial order -// by 2 dimensions; priority, and sender-nonce. +// NewPriorityMempool returns the SDK's default mempool implementation which +// returns txs in a partial order by 2 dimensions; priority, and sender-nonce. func NewPriorityMempool(opts ...PriorityMempoolOption) Mempool { mp := &priorityMempool{ priorityIndex: huandu.New(huandu.LessThanFunc(txMetaLess)), @@ -84,18 +88,23 @@ func NewPriorityMempool(opts ...PriorityMempoolOption) Mempool { senderCursors: make(map[string]*huandu.Element), scores: make(map[txMeta]txMeta), } + for _, opt := range opts { opt(mp) } + return mp } -// Insert attempts to insert a Tx into the app-side mempool in O(log n) time, returning an error if unsuccessful. -// Sender and nonce are derived from the transaction's first signature. -// Transactions are unique by sender and nonce. -// Inserting a duplicate tx is an O(log n) no-op. -// Inserting a duplicate tx with a different priority overwrites the existing tx, changing the total order of -// the mempool. +// Insert attempts to insert a Tx into the app-side mempool in O(log n) time, +// returning an error if unsuccessful. Sender and nonce are derived from the +// transaction's first signature. +// +// Transactions are unique by sender and nonce. Inserting a duplicate tx is an +// O(log n) no-op. +// +// Inserting a duplicate tx with a different priority overwrites the existing tx, +// changing the total order of the mempool. func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { From 7021bddce91db1a88f9a4b9f616222565f9039b0 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 19 Oct 2022 10:20:06 -0700 Subject: [PATCH 118/196] bez: lint/godoc --- types/mempool/priority.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 32c27c9ba635..08dad7fd79a9 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -125,28 +125,36 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { senderIndex = huandu.New(huandu.LessThanFunc(func(a, b any) int { return huandu.Uint64.Compare(b.(txMeta).nonce, a.(txMeta).nonce) })) + // initialize sender index if not found mp.senderIndices[sender] = senderIndex } mp.priorityCounts[priority]++ - // Since senderIndex is scored by nonce, a changed priority will overwrite the existing key. + // Since senderIndex is scored by nonce, a changed priority will overwrite the + // existing key. senderTx := senderIndex.Set(key, tx) - // Since mp.priorityIndex is scored by priority, then sender, then nonce, a changed priority will create a new key, - // so we must remove the old key and re-insert it to avoid having the same tx with different priorityIndex indexed - // twice in the mempool. This O(log n) remove operation is rare and only happens when a tx's priority changes. + // Since mp.priorityIndex is scored by priority, then sender, then nonce, a + // changed priority will create a new key, so we must remove the old key and + // re-insert it to avoid having the same tx with different priorityIndex indexed + // twice in the mempool. + // + // This O(log n) remove operation is rare and only happens when a tx's priority + // changes. sk := txMeta{nonce: nonce, sender: sender} if oldScore, txExists := mp.scores[sk]; txExists { mp.priorityIndex.Remove(txMeta{ nonce: nonce, - priority: oldScore.priority, sender: sender, + priority: oldScore.priority, weight: oldScore.weight, }) } + mp.scores[sk] = txMeta{priority: priority, weight: key.weight} + key.senderElement = senderTx mp.priorityIndex.Set(key, tx) From 81c67f7e1b3ddc599ab1a1d1f3f02e2d7756e4df Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 19 Oct 2022 13:23:10 -0700 Subject: [PATCH 119/196] bez: lint/godoc --- types/mempool/priority.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 08dad7fd79a9..af402df5da8d 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -161,12 +161,18 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { return nil } -// Select returns a set of transactions from the mempool, ordered by priority and sender-nonce in O(n) time. -// The passed in list of transactions are ignored. This is a readonly operation, the mempool is not modified. -// maxBytes is the maximum number of bytes of transactions to return. +// Select returns a set of transactions from the mempool, ordered by priority +// and sender-nonce in O(n) time. The passed in list of transactions are ignored. +// This is a readonly operation, the mempool is not modified. +// +// The maxBytes parameter defines the maximum number of bytes of transactions to +// return. func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { - var selectedTxs []Tx - var txBytes int64 + var ( + selectedTxs []Tx + txBytes int64 + ) + mp.senderCursors = make(map[string]*huandu.Element) mp.reorderPriorityTies() From af919d3723dacc269d0e2e951cda7450f3bffcdb Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 19 Oct 2022 16:28:03 -0500 Subject: [PATCH 120/196] Update types/mempool/priority.go Co-authored-by: Aleksandr Bezobchuk --- types/mempool/priority.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index af402df5da8d..865a549e758b 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -134,7 +134,7 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { // Since senderIndex is scored by nonce, a changed priority will overwrite the // existing key. - senderTx := senderIndex.Set(key, tx) + key.senderElement = senderIndex.Set(key, tx) // Since mp.priorityIndex is scored by priority, then sender, then nonce, a // changed priority will create a new key, so we must remove the old key and From 54837e87d8a96e52a5811f5ceb77ee5f618e6f26 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 19 Oct 2022 16:41:58 -0500 Subject: [PATCH 121/196] remove line --- types/mempool/priority.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 865a549e758b..d5de8a7a4e63 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -154,8 +154,6 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { } mp.scores[sk] = txMeta{priority: priority, weight: key.weight} - - key.senderElement = senderTx mp.priorityIndex.Set(key, tx) return nil From e9a72322a8dd50be8081e760cb7e7605b783df90 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 19 Oct 2022 16:54:18 -0500 Subject: [PATCH 122/196] set txSize to 0 in tx/builder.go setters --- types/mempool/priority.go | 2 +- x/auth/tx/builder.go | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index d5de8a7a4e63..b2d68c3f4853 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -153,7 +153,7 @@ func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { }) } - mp.scores[sk] = txMeta{priority: priority, weight: key.weight} + mp.scores[sk] = txMeta{priority: priority} mp.priorityIndex.Set(key, tx) return nil diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index a76afc175d31..0063b0b3d10d 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -221,6 +221,8 @@ func (w *wrapper) SetMsgs(msgs ...sdk.Msg) error { // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 return nil } @@ -231,6 +233,8 @@ func (w *wrapper) SetTimeoutHeight(height uint64) { // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) SetMemo(memo string) { @@ -238,6 +242,8 @@ func (w *wrapper) SetMemo(memo string) { // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) SetGasLimit(limit uint64) { @@ -249,6 +255,8 @@ func (w *wrapper) SetGasLimit(limit uint64) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) SetFeeAmount(coins sdk.Coins) { @@ -260,6 +268,8 @@ func (w *wrapper) SetFeeAmount(coins sdk.Coins) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) SetTip(tip *tx.Tip) { @@ -267,6 +277,8 @@ func (w *wrapper) SetTip(tip *tx.Tip) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) SetFeePayer(feePayer sdk.AccAddress) { @@ -278,6 +290,8 @@ func (w *wrapper) SetFeePayer(feePayer sdk.AccAddress) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) { @@ -289,6 +303,8 @@ func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) SetSignatures(signatures ...signing.SignatureV2) error { @@ -320,6 +336,8 @@ func (w *wrapper) setSignerInfos(infos []*tx.SignerInfo) { w.tx.AuthInfo.SignerInfos = infos // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { @@ -330,6 +348,8 @@ func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { w.tx.AuthInfo.SignerInfos[index] = info // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) setSignatures(sigs [][]byte) { @@ -376,11 +396,15 @@ func (w *wrapper) GetNonCriticalExtensionOptions() []*codectypes.Any { func (w *wrapper) SetExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.ExtensionOptions = extOpts w.bodyBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.NonCriticalExtensionOptions = extOpts w.bodyBz = nil + // set txSize to 0 because it is no longer correct + w.txSize = 0 } func (w *wrapper) AddAuxSignerData(data tx.AuxSignerData) error { From f63430668a6f5e53f39d448cce1c2530528e4605 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 20 Oct 2022 08:21:28 -0500 Subject: [PATCH 123/196] some comments --- types/mempool/priority.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index b2d68c3f4853..691018b71737 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -30,10 +30,15 @@ type priorityMempool struct { // txMeta stores transaction metadata used in indices type txMeta struct { - nonce uint64 - priority int64 - sender string - weight int64 + // nonce is the sender's sequence number + nonce uint64 + // priority is the transaction's priority + priority int64 + // sender is the transaction's sender + sender string + // weight is the transaction's weight, used as a tiebreaker for transactions with the same priority + weight int64 + // senderElement is a pointer to the transaction's element in the sender index senderElement *huandu.Element } From fb6eb6429f5bbfd49130ed8db375016195ae1450 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 20 Oct 2022 10:51:42 -0500 Subject: [PATCH 124/196] log out seeds in tests --- types/mempool/mempool_test.go | 6 ++++-- types/mempool/priority.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 68a9a22656a1..f6ee76be6e5f 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -207,6 +207,7 @@ func TestOutOfOrder(t *testing.T) { } seed := time.Now().UnixNano() + t.Logf("running with seed: %d", seed) randomTxs := genRandomTxs(seed, 1000, 10) var rmtxs []mempool.Tx for _, rtx := range randomTxs { @@ -563,7 +564,7 @@ func (s *MempoolTestSuite) TestRandomGeneratedTxs() { t := s.T() ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) seed := time.Now().UnixNano() - + t.Logf("running with seed: %d", seed) generated := genRandomTxs(seed, s.numTxs, s.numAccounts) mp := s.mempool @@ -595,6 +596,7 @@ func (s *MempoolTestSuite) TestRandomWalkTxs() { // seed := int64(1663971399133628000) // seed := int64(1663989445512438000) // + t.Logf("running with seed: %d", seed) ordered, shuffled := genOrderedTxs(seed, s.numTxs, s.numAccounts) mp := s.mempool @@ -630,7 +632,7 @@ func (s *MempoolTestSuite) TestRandomWalkTxs() { require.NoError(t, validateOrder(selected), errMsg) duration := time.Since(start) - fmt.Printf("seed: %d completed in %d iterations; validation in %dms\n", + t.Logf("seed: %d completed in %d iterations; validation in %dms\n", seed, s.iterations, duration.Milliseconds()) } diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 691018b71737..17b38a1d5a28 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -54,13 +54,15 @@ func txMetaLess(a, b any) int { return res } - // Below we compare by sender and then by nonce if necessary, when conflicting - // priorities are found. + // weight is used as a tiebreaker for transactions with the same priority. weight is calculated in a single + // pass in .Select(...) and so will be 0 on .Insert(...) res = huandu.Int64.Compare(keyA.weight, keyB.weight) if res != 0 { return res } + // Because weight will be 0 on .Insert(...), we must also compare sender and nonce to resolve priority collisions. + // If we didn't then transactions with the same priority would overwrite each other in the priority index. res = huandu.String.Compare(keyA.sender, keyB.sender) if res != 0 { return res From 45044bd4e627c776b3f02d771bdd34e68da60f06 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 20 Oct 2022 20:50:57 -0500 Subject: [PATCH 125/196] add a comment --- types/mempool/priority.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 17b38a1d5a28..905aa503b790 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -201,6 +201,8 @@ func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { if key.priority < nextHighestPriority { break } else if key.priority == nextHighestPriority { + // weight is incorporated into the priority index key only (not sender index) so we must fetch it here + // from the scores map. weight := mp.scores[txMeta{nonce: key.nonce, sender: key.sender}].weight if weight < nextPriorityNode.Key().(txMeta).weight { break From 0528e05f1d8deec240c28f17144f662f95c4c336 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 21 Oct 2022 14:31:01 -0500 Subject: [PATCH 126/196] Fix inconsistency in setting baseapp mempool --- baseapp/baseapp.go | 7 ++++--- baseapp/options.go | 11 +++++++++++ baseapp/util_test.go | 4 +++- simapp/app.go | 6 ++++-- types/mempool/priority.go | 5 ----- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 27d1afdcf55f..0a0f5edded4b 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -6,7 +6,6 @@ import ( "sort" "strings" - "github.com/cosmos/gogoproto/proto" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" @@ -14,6 +13,8 @@ import ( dbm "github.com/tendermint/tm-db" "golang.org/x/exp/maps" + "github.com/cosmos/gogoproto/proto" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/snapshots" "github.com/cosmos/cosmos-sdk/store" @@ -163,9 +164,9 @@ func NewBaseApp( option(app) } - // if execution of options has left certain required fields nil, let's set them to default values + // if execution of options has left certain required fields nil, set them to sane default values if app.mempool == nil { - app.mempool = mempool.DefaultPriorityMempool() + app.mempool = mempool.NewPriorityMempool() } if app.interBlockCache != nil { diff --git a/baseapp/options.go b/baseapp/options.go index d824d64947bd..4e3afe74fec0 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -12,6 +12,7 @@ import ( snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" ) // File for storing in-package BaseApp optional functions, @@ -80,6 +81,11 @@ func SetSnapshot(snapshotStore *snapshots.Store, opts snapshottypes.SnapshotOpti return func(app *BaseApp) { app.SetSnapshot(snapshotStore, opts) } } +// SetMempool sets the mempool on BaseApp. +func SetMempool(mempool mempool.Mempool) func(*BaseApp) { + return func(app *BaseApp) { app.SetMempool(mempool) } +} + func (app *BaseApp) SetName(name string) { if app.sealed { panic("SetName() on sealed BaseApp") @@ -252,3 +258,8 @@ func (app *BaseApp) SetTxEncoder(txEncoder sdk.TxEncoder) { func (app *BaseApp) SetQueryMultiStore(ms sdk.MultiStore) { app.qms = ms } + +// SetMempool sets the mempool for the BaseApp and is required for the app to start up. +func (app *BaseApp) SetMempool(mempool mempool.Mempool) { + app.mempool = mempool +} diff --git a/baseapp/util_test.go b/baseapp/util_test.go index 3c39684c8f63..c38daab18602 100644 --- a/baseapp/util_test.go +++ b/baseapp/util_test.go @@ -18,6 +18,7 @@ import ( txmodulev1 "cosmossdk.io/api/cosmos/tx/module/v1" "cosmossdk.io/core/appconfig" "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/cosmos/cosmos-sdk/runtime" @@ -149,8 +150,9 @@ func makeTestConfig() depinject.Config { } func makeMinimalConfig() depinject.Config { + var mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewPriorityMempool()) return depinject.Configs( - depinject.Supply(mempool.DefaultPriorityMempool()), + depinject.Supply(mempoolOpt), appconfig.Compose(&appv1alpha1.Config{ Modules: []*appv1alpha1.ModuleConfig{ { diff --git a/simapp/app.go b/simapp/app.go index 8b3cf7177ee9..aba7cdacdc5f 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -5,11 +5,12 @@ package simapp import ( _ "embed" "fmt" - "github.com/cosmos/cosmos-sdk/types/mempool" "io" "os" "path/filepath" + "github.com/cosmos/cosmos-sdk/types/mempool" + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" @@ -191,13 +192,14 @@ func NewSimApp( app = &SimApp{} appBuilder *runtime.AppBuilder + mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewPriorityMempool()) // merge the AppConfig and other configuration in one config appConfig = depinject.Configs( AppConfig, depinject.Supply( // supply the application options appOpts, - mempool.DefaultPriorityMempool, + mempoolOpt, // For providing a custom inflation function for x/mint add here your // custom function that implements the minttypes.InflationCalculationFn // interface. diff --git a/types/mempool/priority.go b/types/mempool/priority.go index 5a0d0d43bbf7..dbe0f8ee68f8 100644 --- a/types/mempool/priority.go +++ b/types/mempool/priority.go @@ -57,11 +57,6 @@ func WithOnRead(onRead func(tx Tx)) PriorityMempoolOption { } } -// DefaultPriorityMempool returns a priorityMempool with no options. -func DefaultPriorityMempool() Mempool { - return NewPriorityMempool() -} - // NewPriorityMempool returns the SDK's default mempool implementation which returns txs in a partial order // by 2 dimensions; priority, and sender-nonce. func NewPriorityMempool(opts ...PriorityMempoolOption) Mempool { From cc608b6e0802dda3b0466e61248fae74078649b9 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Sun, 23 Oct 2022 23:11:02 -0600 Subject: [PATCH 127/196] added function for abci integration --- baseapp/baseapp.go | 61 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 27d1afdcf55f..1490c36b1d2e 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -165,7 +165,7 @@ func NewBaseApp( // if execution of options has left certain required fields nil, let's set them to default values if app.mempool == nil { - app.mempool = mempool.DefaultPriorityMempool() + app.mempool = mempool.DefaultSimpleMempool() } if app.interBlockCache != nil { @@ -674,14 +674,6 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re } } - // TODO remove nil check when implemented - if mode == runTxModeCheck && app.mempool != nil { - err = app.mempool.Insert(ctx, tx.(mempool.Tx)) - if err != nil { - return gInfo, nil, anteEvents, priority, err - } - } - // Create a new Context based off of the existing Context with a MultiStore branch // in case message processing fails. At this point, the MultiStore // is a branch of a branch. @@ -810,3 +802,54 @@ func createEvents(msg sdk.Msg) sdk.Events { return sdk.Events{msgEvent} } + +func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, error) { + memTxs, selectErr := app.mempool.Select(req.Txs, req.MaxTxBytes) + if selectErr != nil { + panic(selectErr) + } + var txsBytes [][]byte + var txs []sdk.Tx + for _, memTx := range memTxs { + bz, encErr := app.txEncoder(memTx) + if encErr != nil { + panic(encErr) + } + txsBytes = append(txsBytes, bz) + txs = append(txs, memTx.(sdk.Tx)) + } + ctx := app.checkState.ctx + err := app.checkTxsValidity(ctx, txs, txsBytes) + if err != nil { + return nil, err + } + return txsBytes, nil +} + +func (app *BaseApp) processProposal(req abci.RequestProcessProposal) error { + ctx := app.checkState.ctx + var txs []sdk.Tx + for _, txBytes := range req.Txs { + tx, err := app.txDecoder(txBytes) + if err != nil { + return err + } + txs = append(txs, tx) + } + err := app.checkTxsValidity(ctx, txs, req.Txs) + if err != nil { + return err + } + return nil +} + +func (app *BaseApp) checkTxsValidity(ctx sdk.Context, txs []sdk.Tx, txBytes [][]byte) error { + for i, tx := range txs { + anteCtx, _ := app.cacheTxContext(ctx, txBytes[i]) + _, err := app.anteHandler(anteCtx, tx, false) + if err != nil { + return err + } + } + return nil +} From 2f56b6deb4bd823b04e2eada376050aa1a9a7b5c Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Sun, 23 Oct 2022 23:12:03 -0600 Subject: [PATCH 128/196] abci base methods for abci++ --- baseapp/abci.go | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 2214d00c74ff..5315b98bb7c4 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -250,17 +250,9 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { - memTxs, selectErr := app.mempool.Select(req.Txs, req.MaxTxBytes) - if selectErr != nil { - panic(selectErr) - } - var txs [][]byte - for _, memTx := range memTxs { - bz, encErr := app.txEncoder(memTx) - if encErr != nil { - panic(encErr) - } - txs = append(txs, bz) + txs, err := app.prepareProposal(req) + if err != nil { + fmt.Println("do something go error en prepare propossal", err) } return abci.ResponsePrepareProposal{Txs: txs} } @@ -278,18 +270,9 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { - ctx := app.checkState.ctx - - for _, txBytes := range req.Txs { - anteCtx, _ := app.cacheTxContext(ctx, txBytes) - tx, err := app.txDecoder(txBytes) - if err != nil { - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} - } - ctx, err = app.anteHandler(anteCtx, tx, false) - if err != nil { - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} - } + err := app.processProposal(req) + if err != nil { + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} } return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} } From bab393a645a2228aee4475d40f9e68c82573aac2 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Sun, 23 Oct 2022 23:13:00 -0600 Subject: [PATCH 129/196] priority mempool --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 1490c36b1d2e..c29656bf44ba 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -165,7 +165,7 @@ func NewBaseApp( // if execution of options has left certain required fields nil, let's set them to default values if app.mempool == nil { - app.mempool = mempool.DefaultSimpleMempool() + app.mempool = mempool.DefaultPriorityMempool() } if app.interBlockCache != nil { From 5fbee3859cd02964c28f9789980a935b4a751309 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Mon, 24 Oct 2022 08:07:22 -0600 Subject: [PATCH 130/196] simple for testing purposes --- types/mempool/simple.go | 93 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 types/mempool/simple.go diff --git a/types/mempool/simple.go b/types/mempool/simple.go new file mode 100644 index 000000000000..834a9044effd --- /dev/null +++ b/types/mempool/simple.go @@ -0,0 +1,93 @@ +package mempool + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/signing" + huandu "github.com/huandu/skiplist" +) + +type simpleMempool struct { + txQueue *huandu.SkipList +} + +type txKey struct { + nonce uint64 + sender string +} + +func txKeyLessNonce(a, b any) int { + keyA := a.(txKey) + keyB := b.(txKey) + + return huandu.Uint64.Compare(keyB.nonce, keyA.nonce) +} + +func DefaultSimpleMempool() Mempool { + return NewPriorityMempool() +} + +func NewSimpleMempool(opts ...PriorityMempoolOption) Mempool { + sp := &simpleMempool{ + txQueue: huandu.New(huandu.LessThanFunc(txKeyLessNonce)), + } + + return sp +} + +func (sp simpleMempool) Insert(_ sdk.Context, tx Tx) error { + sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + if err != nil { + return err + } + if len(sigs) == 0 { + return fmt.Errorf("tx must have at least one signer") + } + + sig := sigs[0] + sender := sig.PubKey.Address().String() + nonce := sig.Sequence + tk := txKey{nonce: nonce, sender: sender} + fmt.Println("key:", tk) + sp.txQueue.Set(tk, tx) + fmt.Println("length of queue", sp.CountTx()) + return nil +} + +func (sp simpleMempool) Select(txs [][]byte, maxBytes int64) ([]Tx, error) { + var selectedTxs []Tx + + currentTx := sp.txQueue.Front() + for currentTx != nil { + mempoolTx := currentTx.Value.(Tx) + + selectedTxs = append(selectedTxs, mempoolTx) + // if txBytes += mempoolTx.Size(); txBytes >= maxBytes { + // return selectedTxs, nil + //} + currentTx = currentTx.Next() + } + return selectedTxs, nil +} + +func (sp simpleMempool) CountTx() int { + return sp.txQueue.Len() +} + +func (sp simpleMempool) Remove(tx Tx) error { + sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() + if err != nil { + return err + } + if len(sigs) == 0 { + return fmt.Errorf("tx must have at least one signer") + } + + sig := sigs[0] + sender := sig.PubKey.Address().String() + nonce := sig.Sequence + tk := txKey{nonce: nonce, sender: sender} + sp.txQueue.Remove(tk) + return nil +} From 94ddfa242118248cc7fb34adabd4e198f6b17ded Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Mon, 24 Oct 2022 15:44:01 -0600 Subject: [PATCH 131/196] prepare and process propossal have the same logic if a tx fails they remove it from the mempool --- baseapp/abci.go | 33 ++++++++++++++++++-- baseapp/baseapp.go | 76 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 5315b98bb7c4..12e1640c7c48 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -53,6 +53,8 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC // initialize the deliver state and check state with a correct header app.setDeliverState(initHeader) app.setCheckState(initHeader) + app.setPrepareProposalState(initHeader) + app.setProcessProposalState(initHeader) // Store the consensus params in the BaseApp's paramstore. Note, this must be // done after the deliver state and context have been set as it's persisted @@ -167,6 +169,20 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg WithBlockHeader(req.Header). WithBlockHeight(req.Header.Height) } + if app.prepareProposalState == nil { + app.setPrepareProposalState(req.Header) + } else { + app.prepareProposalState.ctx = app.prepareProposalState.ctx. + WithBlockHeader(req.Header). + WithBlockHeight(req.Header.Height) + } + if app.processProposalState == nil { + app.setProcessProposalState(req.Header) + } else { + app.processProposalState.ctx = app.processProposalState.ctx. + WithBlockHeader(req.Header). + WithBlockHeight(req.Header.Height) + } // add block gas meter var gasMeter sdk.GasMeter @@ -183,8 +199,17 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg WithHeaderHash(req.Hash). WithConsensusParams(app.GetConsensusParams(app.deliverState.ctx)) - // we also set block gas meter to checkState in case the application needs to - // verify gas consumption during (Re)CheckTx + app.prepareProposalState.ctx = app.prepareProposalState.ctx. + WithBlockGasMeter(gasMeter). + WithHeaderHash(req.Hash). + WithConsensusParams(app.GetConsensusParams(app.prepareProposalState.ctx)) + + app.processProposalState.ctx = app.processProposalState.ctx. + WithBlockGasMeter(gasMeter). + WithHeaderHash(req.Hash). + WithConsensusParams(app.GetConsensusParams(app.processProposalState.ctx)) + + // we if app.checkState != nil { app.checkState.ctx = app.checkState.ctx. WithBlockGasMeter(gasMeter). @@ -375,8 +400,10 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) { // Commit. Use the header from this latest block. app.setCheckState(header) - // empty/reset the deliver state + // empty/reset the deliver, process and prepare states app.deliverState = nil + app.processProposalState = nil + app.prepareProposalState = nil var halt bool diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 5f0e6fe2b812..ac01c9ef5d73 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -30,6 +30,8 @@ const ( runTxModeReCheck // Recheck a (pending) transaction after a commit runTxModeSimulate // Simulate a transaction runTxModeDeliver // Deliver a transaction + runTxPrepareProposal + runTxProcessProposal ) var _ abci.Application = (*BaseApp)(nil) @@ -58,6 +60,7 @@ type BaseApp struct { //nolint: maligned msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx + txEncoder sdk.TxEncoder mempool mempool.Mempool // application side mempool anteHandler sdk.AnteHandler // ante handler for fee and auth @@ -76,8 +79,10 @@ type BaseApp struct { //nolint: maligned // // checkState is set on InitChain and reset on Commit // deliverState is set on InitChain and BeginBlock and set to nil on Commit - checkState *state // for CheckTx - deliverState *state // for DeliverTx + checkState *state // for CheckTx + deliverState *state // for DeliverTx + processProposalState *state // for CheckTx + prepareProposalState *state // for DeliverTx // an inter-block write-through cache provided to the context during deliverState interBlockCache sdk.MultiStorePersistentCache @@ -165,7 +170,7 @@ func NewBaseApp( // if execution of options has left certain required fields nil, set them to sane default values if app.mempool == nil { - app.mempool = mempool.NewPriorityMempool() + app.mempool = mempool.NewSimpleMempool() } if app.interBlockCache != nil { @@ -408,6 +413,30 @@ func (app *BaseApp) setDeliverState(header tmproto.Header) { } } +// setPrepareProposalState sets the BaseApp's deliverState with a branched multi-store +// (i.e. a CacheMultiStore) and a new Context with the same multi-store branch, +// and provided header. It is set on InitChain and BeginBlock and set to nil on +// Commit. +func (app *BaseApp) setPrepareProposalState(header tmproto.Header) { + ms := app.cms.CacheMultiStore() + app.prepareProposalState = &state{ + ms: ms, + ctx: sdk.NewContext(ms, header, false, app.logger), + } +} + +// setProcessProposalState sets the BaseApp's deliverState with a branched multi-store +// (i.e. a CacheMultiStore) and a new Context with the same multi-store branch, +// and provided header. It is set on InitChain and BeginBlock and set to nil on +// Commit. +func (app *BaseApp) setProcessProposalState(header tmproto.Header) { + ms := app.cms.CacheMultiStore() + app.processProposalState = &state{ + ms: ms, + ctx: sdk.NewContext(ms, header, false, app.logger), + } +} + // GetConsensusParams returns the current consensus parameters from the BaseApp's // ParamStore. If the BaseApp has no ParamStore defined, nil is returned. func (app *BaseApp) GetConsensusParams(ctx sdk.Context) *tmproto.ConsensusParams { @@ -516,6 +545,10 @@ func validateBasicTxMsgs(msgs []sdk.Msg) error { func (app *BaseApp) getState(mode runTxMode) *state { if mode == runTxModeDeliver { return app.deliverState + } else if mode == runTxPrepareProposal { + return app.prepareProposalState + } else if mode == runTxProcessProposal { + return app.processProposalState } return app.checkState @@ -809,46 +842,43 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, panic(selectErr) } var txsBytes [][]byte - var txs []sdk.Tx for _, memTx := range memTxs { bz, encErr := app.txEncoder(memTx) if encErr != nil { panic(encErr) } - txsBytes = append(txsBytes, bz) - txs = append(txs, memTx.(sdk.Tx)) - } - ctx := app.checkState.ctx - err := app.checkTxsValidity(ctx, txs, txsBytes) - if err != nil { - return nil, err + _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) + if err != nil { + _ = app.mempool.Remove(memTx) + } else { + txsBytes = append(txsBytes, bz) + } + } return txsBytes, nil } func (app *BaseApp) processProposal(req abci.RequestProcessProposal) error { - ctx := app.checkState.ctx - var txs []sdk.Tx for _, txBytes := range req.Txs { tx, err := app.txDecoder(txBytes) if err != nil { return err } - txs = append(txs, tx) - } - err := app.checkTxsValidity(ctx, txs, req.Txs) - if err != nil { - return err + + _, _, _, _, err = app.runTx(runTxPrepareProposal, txBytes) + if err != nil { + _ = app.mempool.Remove(tx.(mempool.Tx)) + } } return nil } -func (app *BaseApp) checkTxsValidity(ctx sdk.Context, txs []sdk.Tx, txBytes [][]byte) error { - for i, tx := range txs { - anteCtx, _ := app.cacheTxContext(ctx, txBytes[i]) - _, err := app.anteHandler(anteCtx, tx, false) +func (app *BaseApp) checkTxsValidity(txMode runTxMode, txBytes [][]byte) error { + for _, txByte := range txBytes { + _, _, _, _, err := app.runTx(txMode, txByte) if err != nil { - return err + tx, _ := app.txDecoder(txByte) + _ = app.mempool.Remove(tx.(mempool.Tx)) } } return nil From f6c073fe9a4dbbfe658fbafcca937031175728ae Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Mon, 24 Oct 2022 15:48:08 -0600 Subject: [PATCH 132/196] small fix --- baseapp/baseapp.go | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index ac01c9ef5d73..cb065b99a0aa 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -543,15 +543,16 @@ func validateBasicTxMsgs(msgs []sdk.Msg) error { // Returns the application's deliverState if app is in runTxModeDeliver, // otherwise it returns the application's checkstate. func (app *BaseApp) getState(mode runTxMode) *state { - if mode == runTxModeDeliver { + switch mode { + case runTxModeDeliver: return app.deliverState - } else if mode == runTxPrepareProposal { + case runTxPrepareProposal: return app.prepareProposalState - } else if mode == runTxProcessProposal { + case runTxProcessProposal: return app.processProposalState + default: + return app.checkState } - - return app.checkState } // retrieve the context for the tx w/ txBytes and other memoized values. @@ -872,14 +873,3 @@ func (app *BaseApp) processProposal(req abci.RequestProcessProposal) error { } return nil } - -func (app *BaseApp) checkTxsValidity(txMode runTxMode, txBytes [][]byte) error { - for _, txByte := range txBytes { - _, _, _, _, err := app.runTx(txMode, txByte) - if err != nil { - tx, _ := app.txDecoder(txByte) - _ = app.mempool.Remove(tx.(mempool.Tx)) - } - } - return nil -} From b1c84cb45f3629e2add5c777ce71fdf56eaac1e7 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Wed, 26 Oct 2022 17:57:58 -0600 Subject: [PATCH 133/196] base abci changes --- baseapp/abci.go | 2 ++ baseapp/baseapp.go | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 12e1640c7c48..61c971bd183d 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -275,6 +275,7 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { + fmt.Println("prepare propossal") txs, err := app.prepareProposal(req) if err != nil { fmt.Println("do something go error en prepare propossal", err) @@ -295,6 +296,7 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { + fmt.Println("process propossal") err := app.processProposal(req) if err != nil { return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index cb065b99a0aa..6ee754e40bc5 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -170,7 +170,7 @@ func NewBaseApp( // if execution of options has left certain required fields nil, set them to sane default values if app.mempool == nil { - app.mempool = mempool.NewSimpleMempool() + app.mempool = mempool.NewNonceMempool() } if app.interBlockCache != nil { @@ -696,6 +696,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re } if mode == runTxModeCheck { + fmt.Println("inserting tx:", tx.GetMsgs()) err = app.mempool.Insert(ctx, tx.(mempool.Tx)) if err != nil { return gInfo, nil, anteEvents, priority, err @@ -760,7 +761,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { // skip actual execution for (Re)CheckTx mode - if mode == runTxModeCheck || mode == runTxModeReCheck { + if mode == runTxModeCheck || mode == runTxModeReCheck || mode == runTxPrepareProposal || mode == runTxProcessProposal { break } @@ -843,13 +844,16 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, panic(selectErr) } var txsBytes [][]byte + fmt.Println("memtx", memTxs) for _, memTx := range memTxs { bz, encErr := app.txEncoder(memTx) if encErr != nil { panic(encErr) } + fmt.Println("messages:", memTx.GetMsgs()) _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) if err != nil { + fmt.Println("error un prepare propossal", memTx) _ = app.mempool.Remove(memTx) } else { txsBytes = append(txsBytes, bz) @@ -865,9 +869,11 @@ func (app *BaseApp) processProposal(req abci.RequestProcessProposal) error { if err != nil { return err } + fmt.Println(tx) - _, _, _, _, err = app.runTx(runTxPrepareProposal, txBytes) + _, _, _, _, err = app.runTx(runTxProcessProposal, txBytes) if err != nil { + fmt.Println("error run tx process", tx) _ = app.mempool.Remove(tx.(mempool.Tx)) } } From e514f7bb6108a0618c754ec680068e89a06473de Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 27 Oct 2022 07:34:55 -0600 Subject: [PATCH 134/196] t --- baseapp/options.go | 4 +--- simapp/app.go | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/baseapp/options.go b/baseapp/options.go index 44726a698647..9691c2301bf9 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/store" pruningtypes "github.com/cosmos/cosmos-sdk/store/pruning/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" ) // File for storing in-package BaseApp optional functions, @@ -262,6 +263,3 @@ func (app *BaseApp) SetQueryMultiStore(ms sdk.MultiStore) { func (app *BaseApp) SetMempool(mempool mempool.Mempool) { app.mempool = mempool } - - - diff --git a/simapp/app.go b/simapp/app.go index ea932c42eabc..857d822c5ea4 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -9,8 +9,6 @@ import ( "os" "path/filepath" - "github.com/cosmos/cosmos-sdk/types/mempool" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" @@ -192,7 +190,7 @@ func NewSimApp( app = &SimApp{} appBuilder *runtime.AppBuilder - mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewPriorityMempool()) + //mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewPriorityMempool()) // merge the AppConfig and other configuration in one config appConfig = depinject.Configs( AppConfig, @@ -200,7 +198,7 @@ func NewSimApp( // supply the application options appOpts, - mempoolOpt, + //mempoolOpt, // For providing a custom inflation function for x/mint add here your // custom function that implements the minttypes.InflationCalculationFn // interface. From c834047f09072b5b9aa4c8336d565f11342e65e4 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 27 Oct 2022 07:37:32 -0600 Subject: [PATCH 135/196] t --- types/mempool/simple.go | 22 +++++++++++----------- types/mempool/stateful.go | 3 +-- x/auth/tx/builder.go | 1 - 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/types/mempool/simple.go b/types/mempool/simple.go index 834a9044effd..1d991378ac02 100644 --- a/types/mempool/simple.go +++ b/types/mempool/simple.go @@ -12,17 +12,17 @@ type simpleMempool struct { txQueue *huandu.SkipList } -type txKey struct { - nonce uint64 - sender string -} - -func txKeyLessNonce(a, b any) int { - keyA := a.(txKey) - keyB := b.(txKey) - - return huandu.Uint64.Compare(keyB.nonce, keyA.nonce) -} +// type txKey struct { +// nonce uint64 +// sender string +//} +// +//func txKeyLessNonce(a, b any) int { +// keyA := a.(txKey) +// keyB := b.(txKey) +// +// return huandu.Uint64.Compare(keyB.nonce, keyA.nonce) +//} func DefaultSimpleMempool() Mempool { return NewPriorityMempool() diff --git a/types/mempool/stateful.go b/types/mempool/stateful.go index 612d37a2c43f..57456876eadb 100644 --- a/types/mempool/stateful.go +++ b/types/mempool/stateful.go @@ -96,7 +96,6 @@ func (amp *MemPoolI) Insert(ctx sdk.Context, tx Tx) error { amp.accountsHeads.Set(accountMeempool.currentKey, accountMeempool) amp.senders[sender] = accountMeempool return nil - } func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int64) ([]Tx, error) { @@ -106,7 +105,7 @@ func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int64) ([]Tx, er currentAccount := amp.accountsHeads.Front() for currentAccount != nil { accountMemPool := currentAccount.Value.(*AccountMemPool) - //currentTx := accountMemPool.transactions.Front() + prevKey := accountMemPool.currentKey tx := accountMemPool.Pop() if tx == nil { diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 9c95afa50845..604123167034 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -1,7 +1,6 @@ package tx import ( - "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/gogoproto/proto" "github.com/cosmos/cosmos-sdk/client" From 4d11de2a77af6f2c4e9035871c1b843ee3b8aa08 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 27 Oct 2022 07:54:45 -0600 Subject: [PATCH 136/196] set prepare and process like check state --- baseapp/abci.go | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 61c971bd183d..1cf4d86f02ca 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -169,20 +169,6 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg WithBlockHeader(req.Header). WithBlockHeight(req.Header.Height) } - if app.prepareProposalState == nil { - app.setPrepareProposalState(req.Header) - } else { - app.prepareProposalState.ctx = app.prepareProposalState.ctx. - WithBlockHeader(req.Header). - WithBlockHeight(req.Header.Height) - } - if app.processProposalState == nil { - app.setProcessProposalState(req.Header) - } else { - app.processProposalState.ctx = app.processProposalState.ctx. - WithBlockHeader(req.Header). - WithBlockHeight(req.Header.Height) - } // add block gas meter var gasMeter sdk.GasMeter @@ -199,22 +185,22 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg WithHeaderHash(req.Hash). WithConsensusParams(app.GetConsensusParams(app.deliverState.ctx)) - app.prepareProposalState.ctx = app.prepareProposalState.ctx. - WithBlockGasMeter(gasMeter). - WithHeaderHash(req.Hash). - WithConsensusParams(app.GetConsensusParams(app.prepareProposalState.ctx)) - - app.processProposalState.ctx = app.processProposalState.ctx. - WithBlockGasMeter(gasMeter). - WithHeaderHash(req.Hash). - WithConsensusParams(app.GetConsensusParams(app.processProposalState.ctx)) - // we if app.checkState != nil { app.checkState.ctx = app.checkState.ctx. WithBlockGasMeter(gasMeter). WithHeaderHash(req.Hash) } + if app.prepareProposalState != nil { + app.prepareProposalState.ctx = app.prepareProposalState.ctx. + WithBlockGasMeter(gasMeter). + WithHeaderHash(req.Hash) + } + if app.processProposalState != nil { + app.processProposalState.ctx = app.processProposalState.ctx. + WithBlockGasMeter(gasMeter). + WithHeaderHash(req.Hash) + } if app.beginBlocker != nil { res = app.beginBlocker(app.deliverState.ctx, req) @@ -401,11 +387,11 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) { // NOTE: This is safe because Tendermint holds a lock on the mempool for // Commit. Use the header from this latest block. app.setCheckState(header) + app.setPrepareProposalState(header) + app.setProcessProposalState(header) - // empty/reset the deliver, process and prepare states + // empty/reset the deliver app.deliverState = nil - app.processProposalState = nil - app.prepareProposalState = nil var halt bool From 4d295b177ce20af4e6c14e509eb70cbf9401d658 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 27 Oct 2022 07:57:47 -0600 Subject: [PATCH 137/196] deletead simple due to it being merge to main in a separate PR --- types/mempool/simple.go | 93 ----------------------------------------- 1 file changed, 93 deletions(-) delete mode 100644 types/mempool/simple.go diff --git a/types/mempool/simple.go b/types/mempool/simple.go deleted file mode 100644 index 1d991378ac02..000000000000 --- a/types/mempool/simple.go +++ /dev/null @@ -1,93 +0,0 @@ -package mempool - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/signing" - huandu "github.com/huandu/skiplist" -) - -type simpleMempool struct { - txQueue *huandu.SkipList -} - -// type txKey struct { -// nonce uint64 -// sender string -//} -// -//func txKeyLessNonce(a, b any) int { -// keyA := a.(txKey) -// keyB := b.(txKey) -// -// return huandu.Uint64.Compare(keyB.nonce, keyA.nonce) -//} - -func DefaultSimpleMempool() Mempool { - return NewPriorityMempool() -} - -func NewSimpleMempool(opts ...PriorityMempoolOption) Mempool { - sp := &simpleMempool{ - txQueue: huandu.New(huandu.LessThanFunc(txKeyLessNonce)), - } - - return sp -} - -func (sp simpleMempool) Insert(_ sdk.Context, tx Tx) error { - sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - if err != nil { - return err - } - if len(sigs) == 0 { - return fmt.Errorf("tx must have at least one signer") - } - - sig := sigs[0] - sender := sig.PubKey.Address().String() - nonce := sig.Sequence - tk := txKey{nonce: nonce, sender: sender} - fmt.Println("key:", tk) - sp.txQueue.Set(tk, tx) - fmt.Println("length of queue", sp.CountTx()) - return nil -} - -func (sp simpleMempool) Select(txs [][]byte, maxBytes int64) ([]Tx, error) { - var selectedTxs []Tx - - currentTx := sp.txQueue.Front() - for currentTx != nil { - mempoolTx := currentTx.Value.(Tx) - - selectedTxs = append(selectedTxs, mempoolTx) - // if txBytes += mempoolTx.Size(); txBytes >= maxBytes { - // return selectedTxs, nil - //} - currentTx = currentTx.Next() - } - return selectedTxs, nil -} - -func (sp simpleMempool) CountTx() int { - return sp.txQueue.Len() -} - -func (sp simpleMempool) Remove(tx Tx) error { - sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - if err != nil { - return err - } - if len(sigs) == 0 { - return fmt.Errorf("tx must have at least one signer") - } - - sig := sigs[0] - sender := sig.PubKey.Address().String() - nonce := sig.Sequence - tk := txKey{nonce: nonce, sender: sender} - sp.txQueue.Remove(tk) - return nil -} From ffdb9347e25183722c36a06c14f42222cef14446 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 27 Oct 2022 12:10:46 -0500 Subject: [PATCH 138/196] draft of select cursor --- baseapp/baseapp.go | 23 ++- types/mempool/mempool.go | 10 +- types/mempool/mempool_test.go | 22 +- types/mempool/nonce.go | 43 ++-- types/mempool/nonce_test.go | 3 +- types/mempool/priority.go | 376 ---------------------------------- types/mempool/stateful.go | 146 ------------- 7 files changed, 77 insertions(+), 546 deletions(-) delete mode 100644 types/mempool/priority.go delete mode 100644 types/mempool/stateful.go diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 6ee754e40bc5..0496abec94b6 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -839,17 +839,26 @@ func createEvents(msg sdk.Msg) sdk.Events { } func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, error) { - memTxs, selectErr := app.mempool.Select(req.Txs, req.MaxTxBytes) + cursor, selectErr := app.mempool.Select(req.Txs) if selectErr != nil { panic(selectErr) } - var txsBytes [][]byte - fmt.Println("memtx", memTxs) - for _, memTx := range memTxs { + var ( + txsBytes [][]byte + byteCount int64 + ) + for cursor != nil { + memTx := cursor.Tx() + + if byteCount += memTx.Size(); byteCount > req.MaxTxBytes { + break + } + bz, encErr := app.txEncoder(memTx) if encErr != nil { panic(encErr) } + fmt.Println("messages:", memTx.GetMsgs()) _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) if err != nil { @@ -859,7 +868,13 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, txsBytes = append(txsBytes, bz) } + next, cursorErr := cursor.Next() + if cursorErr != nil { + return nil, cursorErr + } + cursor = next } + return txsBytes, nil } diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index f32150d6f41e..0e08423748ca 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -26,7 +26,7 @@ type Mempool interface { // mempool, up to maxBytes or until the mempool is empty. The application can // decide to return transactions from its own mempool, from the incoming // txs, or some combination of both. - Select(txs [][]byte, maxBytes int64) ([]Tx, error) + Select(txs [][]byte) (SelectCursor, error) // CountTx returns the number of transactions currently in the mempool. CountTx() int @@ -36,6 +36,14 @@ type Mempool interface { Remove(Tx) error } +type SelectCursor interface { + // Next returns the next transaction from the cursor. + Next() (SelectCursor, error) + + // Tx returns the transaction from the cursor. + Tx() Tx +} + var ErrTxNotFound = errors.New("tx not found in mempool") type Factory func() Mempool diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index d6f8e8134679..b08273866046 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -104,6 +104,25 @@ func (tx txSpec) String() string { return fmt.Sprintf("[tx i: %d, a: %s, p: %d, n: %d]", tx.i, tx.a, tx.p, tx.n) } +func fetchTxs(cursor mempool.SelectCursor, maxBytes int64) []mempool.Tx { + var ( + txs []mempool.Tx + numBytes int64 + ) + for cursor != nil { + if numBytes += cursor.Tx().Size(); numBytes > maxBytes { + break + } + txs = append(txs, cursor.Tx()) + c, err := cursor.Next() + if err != nil { + panic(err) + } + cursor = c + } + return txs +} + func (s *MempoolTestSuite) TestDefaultMempool() { t := s.T() ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) @@ -137,8 +156,9 @@ func (s *MempoolTestSuite) TestDefaultMempool() { } require.Equal(t, txCount, s.mempool.CountTx()) - sel, err := s.mempool.Select(nil, 13) + selCursor, err := s.mempool.Select(nil) require.NoError(t, err) + sel := fetchTxs(selCursor, 13) require.Equal(t, 13, len(sel)) // a tx which does not implement SigVerifiableTx should not be inserted diff --git a/types/mempool/nonce.go b/types/mempool/nonce.go index da7f10d8d193..e69dff7174b2 100644 --- a/types/mempool/nonce.go +++ b/types/mempool/nonce.go @@ -9,6 +9,11 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/signing" ) +var ( + _ Mempool = (*nonceMempool)(nil) + _ SelectCursor = (*nonceMempoolIterator)(nil) +) + // nonceMempool is a mempool that keeps transactions sorted by nonce. Transactions with the lowest nonce globally // are prioritized. Transactions with the same nonce are prioritized by sender address. Fee/gas based // prioritization is not supported. @@ -16,6 +21,24 @@ type nonceMempool struct { txQueue *huandu.SkipList } +type nonceMempoolIterator struct { + currentTx *huandu.Element +} + +func (i nonceMempoolIterator) Next() (SelectCursor, error) { + if i.currentTx == nil { + return nil, nil + } else if n := i.currentTx.Next(); n != nil { + return nonceMempoolIterator{currentTx: n}, nil + } else { + return nil, nil + } +} + +func (i nonceMempoolIterator) Tx() Tx { + return i.currentTx.Value.(Tx) +} + type txKey struct { nonce uint64 sender string @@ -63,24 +86,10 @@ func (sp nonceMempool) Insert(_ sdk.Context, tx Tx) error { // Select returns txs from the mempool with the lowest nonce globally first. A sender's txs will always be returned // in nonce order. -func (sp nonceMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { - var ( - txBytes int64 - selectedTxs []Tx - ) - +func (sp nonceMempool) Select(_ [][]byte) (SelectCursor, error) { currentTx := sp.txQueue.Front() - for currentTx != nil { - mempoolTx := currentTx.Value.(Tx) - - if txBytes += mempoolTx.Size(); txBytes <= maxBytes { - selectedTxs = append(selectedTxs, mempoolTx) - } else { - return selectedTxs, nil - } - currentTx = currentTx.Next() - } - return selectedTxs, nil + cursor := &nonceMempoolIterator{currentTx: currentTx} + return cursor, nil } // CountTx returns the number of txs in the mempool. diff --git a/types/mempool/nonce_test.go b/types/mempool/nonce_test.go index e152ce0990dd..1443da69e2be 100644 --- a/types/mempool/nonce_test.go +++ b/types/mempool/nonce_test.go @@ -193,8 +193,9 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, err) } - orderedTxs, err := pool.Select(nil, 1000) + selCursor, err := pool.Select(nil) require.NoError(t, err) + orderedTxs := fetchTxs(selCursor, 1000) var txOrder []int for _, tx := range orderedTxs { txOrder = append(txOrder, tx.(testTx).id) diff --git a/types/mempool/priority.go b/types/mempool/priority.go deleted file mode 100644 index 905aa503b790..000000000000 --- a/types/mempool/priority.go +++ /dev/null @@ -1,376 +0,0 @@ -package mempool - -import ( - "fmt" - "math" - - huandu "github.com/huandu/skiplist" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/signing" -) - -var _ Mempool = (*priorityMempool)(nil) - -// priorityMempool defines the SDK's default mempool implementation which stores -// txs in a partially ordered set by 2 dimensions: priority, and sender-nonce -// (sequence number). Internally it uses one priority ordered skip list and one -// skip list per sender ordered by sender-nonce (sequence number). When there -// are multiple txs from the same sender, they are not always comparable by -// priority to other sender txs and must be partially ordered by both sender-nonce -// and priority. -type priorityMempool struct { - priorityIndex *huandu.SkipList - priorityCounts map[int64]int - senderIndices map[string]*huandu.SkipList - senderCursors map[string]*huandu.Element - scores map[txMeta]txMeta - onRead func(tx Tx) -} - -// txMeta stores transaction metadata used in indices -type txMeta struct { - // nonce is the sender's sequence number - nonce uint64 - // priority is the transaction's priority - priority int64 - // sender is the transaction's sender - sender string - // weight is the transaction's weight, used as a tiebreaker for transactions with the same priority - weight int64 - // senderElement is a pointer to the transaction's element in the sender index - senderElement *huandu.Element -} - -// txMetaLess is a comparator for txKeys that first compares priority, then weight, -// then sender, then nonce, uniquely identifying a transaction. -// -// Note, txMetaLess is used as the comparator in the priority index. -func txMetaLess(a, b any) int { - keyA := a.(txMeta) - keyB := b.(txMeta) - res := huandu.Int64.Compare(keyA.priority, keyB.priority) - if res != 0 { - return res - } - - // weight is used as a tiebreaker for transactions with the same priority. weight is calculated in a single - // pass in .Select(...) and so will be 0 on .Insert(...) - res = huandu.Int64.Compare(keyA.weight, keyB.weight) - if res != 0 { - return res - } - - // Because weight will be 0 on .Insert(...), we must also compare sender and nonce to resolve priority collisions. - // If we didn't then transactions with the same priority would overwrite each other in the priority index. - res = huandu.String.Compare(keyA.sender, keyB.sender) - if res != 0 { - return res - } - - return huandu.Uint64.Compare(keyA.nonce, keyB.nonce) -} - -type PriorityMempoolOption func(*priorityMempool) - -// WithOnRead sets a callback to be called when a tx is read from the mempool. -func WithOnRead(onRead func(tx Tx)) PriorityMempoolOption { - return func(mp *priorityMempool) { - mp.onRead = onRead - } -} - -// DefaultPriorityMempool returns a priorityMempool with no options. -func DefaultPriorityMempool() Mempool { - return NewPriorityMempool() -} - -// NewPriorityMempool returns the SDK's default mempool implementation which -// returns txs in a partial order by 2 dimensions; priority, and sender-nonce. -func NewPriorityMempool(opts ...PriorityMempoolOption) Mempool { - mp := &priorityMempool{ - priorityIndex: huandu.New(huandu.LessThanFunc(txMetaLess)), - priorityCounts: make(map[int64]int), - senderIndices: make(map[string]*huandu.SkipList), - senderCursors: make(map[string]*huandu.Element), - scores: make(map[txMeta]txMeta), - } - - for _, opt := range opts { - opt(mp) - } - - return mp -} - -// Insert attempts to insert a Tx into the app-side mempool in O(log n) time, -// returning an error if unsuccessful. Sender and nonce are derived from the -// transaction's first signature. -// -// Transactions are unique by sender and nonce. Inserting a duplicate tx is an -// O(log n) no-op. -// -// Inserting a duplicate tx with a different priority overwrites the existing tx, -// changing the total order of the mempool. -func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { - sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - if err != nil { - return err - } - if len(sigs) == 0 { - return fmt.Errorf("tx must have at least one signer") - } - - priority := ctx.Priority() - sig := sigs[0] - sender := sig.PubKey.Address().String() - nonce := sig.Sequence - key := txMeta{nonce: nonce, priority: priority, sender: sender} - - senderIndex, ok := mp.senderIndices[sender] - if !ok { - senderIndex = huandu.New(huandu.LessThanFunc(func(a, b any) int { - return huandu.Uint64.Compare(b.(txMeta).nonce, a.(txMeta).nonce) - })) - - // initialize sender index if not found - mp.senderIndices[sender] = senderIndex - } - - mp.priorityCounts[priority]++ - - // Since senderIndex is scored by nonce, a changed priority will overwrite the - // existing key. - key.senderElement = senderIndex.Set(key, tx) - - // Since mp.priorityIndex is scored by priority, then sender, then nonce, a - // changed priority will create a new key, so we must remove the old key and - // re-insert it to avoid having the same tx with different priorityIndex indexed - // twice in the mempool. - // - // This O(log n) remove operation is rare and only happens when a tx's priority - // changes. - sk := txMeta{nonce: nonce, sender: sender} - if oldScore, txExists := mp.scores[sk]; txExists { - mp.priorityIndex.Remove(txMeta{ - nonce: nonce, - sender: sender, - priority: oldScore.priority, - weight: oldScore.weight, - }) - } - - mp.scores[sk] = txMeta{priority: priority} - mp.priorityIndex.Set(key, tx) - - return nil -} - -// Select returns a set of transactions from the mempool, ordered by priority -// and sender-nonce in O(n) time. The passed in list of transactions are ignored. -// This is a readonly operation, the mempool is not modified. -// -// The maxBytes parameter defines the maximum number of bytes of transactions to -// return. -func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { - var ( - selectedTxs []Tx - txBytes int64 - ) - - mp.senderCursors = make(map[string]*huandu.Element) - mp.reorderPriorityTies() - - priorityNode := mp.priorityIndex.Front() - for priorityNode != nil { - priorityKey := priorityNode.Key().(txMeta) - nextHighestPriority, nextPriorityNode := mp.nextPriority(priorityNode) - sender := priorityKey.sender - senderTx := mp.fetchSenderCursor(sender) - - // iterate through the sender's transactions in nonce order - for senderTx != nil { - mempoolTx := senderTx.Value.(Tx) - if mp.onRead != nil { - mp.onRead(mempoolTx) - } - - key := senderTx.Key().(txMeta) - - // break if we've reached a transaction with a priority lower than the next highest priority in the pool - if key.priority < nextHighestPriority { - break - } else if key.priority == nextHighestPriority { - // weight is incorporated into the priority index key only (not sender index) so we must fetch it here - // from the scores map. - weight := mp.scores[txMeta{nonce: key.nonce, sender: key.sender}].weight - if weight < nextPriorityNode.Key().(txMeta).weight { - break - } - } - - // otherwise, select the transaction and continue iteration - selectedTxs = append(selectedTxs, mempoolTx) - if txBytes += mempoolTx.Size(); txBytes >= maxBytes { - return selectedTxs, nil - } - - senderTx = senderTx.Next() - mp.senderCursors[sender] = senderTx - } - - priorityNode = nextPriorityNode - } - - return selectedTxs, nil -} - -type reorderKey struct { - deleteKey txMeta - insertKey txMeta - tx Tx -} - -func (mp *priorityMempool) reorderPriorityTies() { - node := mp.priorityIndex.Front() - var reordering []reorderKey - for node != nil { - key := node.Key().(txMeta) - if mp.priorityCounts[key.priority] > 1 { - newKey := key - newKey.weight = senderWeight(key.senderElement) - reordering = append(reordering, reorderKey{deleteKey: key, insertKey: newKey, tx: node.Value.(Tx)}) - } - node = node.Next() - } - - for _, k := range reordering { - mp.priorityIndex.Remove(k.deleteKey) - delete(mp.scores, txMeta{nonce: k.deleteKey.nonce, sender: k.deleteKey.sender}) - mp.priorityIndex.Set(k.insertKey, k.tx) - mp.scores[txMeta{nonce: k.insertKey.nonce, sender: k.insertKey.sender}] = k.insertKey - } -} - -// senderWeight returns the weight of a given tx (t) at senderCursor. Weight is defined as the first (nonce-wise) -// same sender tx with a priority not equal to t. It is used to resolve priority collisions, that is when 2 or more -// txs from different senders have the same priority. -func senderWeight(senderCursor *huandu.Element) int64 { - if senderCursor == nil { - return 0 - } - weight := senderCursor.Key().(txMeta).priority - senderCursor = senderCursor.Next() - for senderCursor != nil { - p := senderCursor.Key().(txMeta).priority - if p != weight { - weight = p - } - senderCursor = senderCursor.Next() - } - - return weight -} - -func (mp *priorityMempool) fetchSenderCursor(sender string) *huandu.Element { - senderTx, ok := mp.senderCursors[sender] - if !ok { - senderTx = mp.senderIndices[sender].Front() - } - return senderTx -} - -func (mp *priorityMempool) nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { - var nextPriorityNode *huandu.Element - if priorityNode == nil { - nextPriorityNode = mp.priorityIndex.Front() - } else { - nextPriorityNode = priorityNode.Next() - } - - if nextPriorityNode == nil { - return math.MinInt64, nil - } - - np := nextPriorityNode.Key().(txMeta).priority - - return np, nextPriorityNode -} - -// CountTx returns the number of transactions in the mempool. -func (mp *priorityMempool) CountTx() int { - return mp.priorityIndex.Len() -} - -// Remove removes a transaction from the mempool in O(log n) time, returning an error if unsuccessful. -func (mp *priorityMempool) Remove(tx Tx) error { - sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - if err != nil { - return err - } - if len(sigs) == 0 { - return fmt.Errorf("attempted to remove a tx with no signatures") - } - - sig := sigs[0] - sender := sig.PubKey.Address().String() - nonce := sig.Sequence - - scoreKey := txMeta{nonce: nonce, sender: sender} - score, ok := mp.scores[scoreKey] - if !ok { - return ErrTxNotFound - } - tk := txMeta{nonce: nonce, priority: score.priority, sender: sender, weight: score.weight} - - senderTxs, ok := mp.senderIndices[sender] - if !ok { - return fmt.Errorf("sender %s not found", sender) - } - - mp.priorityIndex.Remove(tk) - senderTxs.Remove(tk) - delete(mp.scores, scoreKey) - mp.priorityCounts[score.priority]-- - - return nil -} - -func IsEmpty(mempool Mempool) error { - mp := mempool.(*priorityMempool) - if mp.priorityIndex.Len() != 0 { - return fmt.Errorf("priorityIndex not empty") - } - - var countKeys []int64 - for k := range mp.priorityCounts { - countKeys = append(countKeys, k) - } - for _, k := range countKeys { - if mp.priorityCounts[k] != 0 { - return fmt.Errorf("priorityCounts not zero at %v, got %v", k, mp.priorityCounts[k]) - } - } - - var senderKeys []string - for k := range mp.senderIndices { - senderKeys = append(senderKeys, k) - } - for _, k := range senderKeys { - if mp.senderIndices[k].Len() != 0 { - return fmt.Errorf("senderIndex not empty for sender %v", k) - } - } - - return nil -} - -func DebugPrintKeys(mempool Mempool) { - mp := mempool.(*priorityMempool) - n := mp.priorityIndex.Front() - for n != nil { - k := n.Key().(txMeta) - fmt.Printf("%s, %d, %d, %d\n", k.sender, k.priority, k.nonce, k.weight) - n = n.Next() - } -} diff --git a/types/mempool/stateful.go b/types/mempool/stateful.go deleted file mode 100644 index 57456876eadb..000000000000 --- a/types/mempool/stateful.go +++ /dev/null @@ -1,146 +0,0 @@ -package mempool - -import ( - "bytes" - "crypto/sha256" - "fmt" - - huandu "github.com/huandu/skiplist" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/signing" -) - -// The complexity is O(log(N)). Implementation -type statefullPriorityKey struct { - hash [32]byte - priority int64 - nonce uint64 -} - -type accountsHeadsKey struct { - sender string - priority int64 - hash [32]byte -} - -type AccountMemPool struct { - transactions *huandu.SkipList - currentKey accountsHeadsKey - currentItem *huandu.Element - sender string -} - -// Push cannot be executed in the middle of a select -func (amp *AccountMemPool) Push(ctx sdk.Context, key statefullPriorityKey, tx Tx) { - amp.transactions.Set(key, tx) - amp.currentItem = amp.transactions.Back() - newKey := amp.currentItem.Key().(statefullPriorityKey) - amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} -} - -func (amp *AccountMemPool) Pop() *Tx { - if amp.currentItem == nil { - return nil - } - itemToPop := amp.currentItem - amp.currentItem = itemToPop.Prev() - if amp.currentItem != nil { - newKey := amp.currentItem.Key().(statefullPriorityKey) - amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} - } else { - amp.currentKey = accountsHeadsKey{} - } - tx := itemToPop.Value.(Tx) - return &tx -} - -type MemPoolI struct { - accountsHeads *huandu.SkipList - senders map[string]*AccountMemPool -} - -func NewMemPoolI() MemPoolI { - return MemPoolI{ - accountsHeads: huandu.New(huandu.LessThanFunc(priorityHuanduLess)), - senders: make(map[string]*AccountMemPool), - } -} - -func (amp *MemPoolI) Insert(ctx sdk.Context, tx Tx) error { - senders := tx.(signing.SigVerifiableTx).GetSigners() - nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - - if err != nil { - return err - } else if len(senders) != len(nonces) { - return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) - } - sender := senders[0].String() - nonce := nonces[0].Sequence - - accountMeempool, ok := amp.senders[sender] - if !ok { - accountMeempool = &AccountMemPool{ - transactions: huandu.New(huandu.LessThanFunc(nonceHuanduLess)), - sender: sender, - } - } - hash := sha256.Sum256(senders[0].Bytes()) - key := statefullPriorityKey{hash: hash, nonce: nonce, priority: ctx.Priority()} - - prevKey := accountMeempool.currentKey - accountMeempool.Push(ctx, key, tx) - - amp.accountsHeads.Remove(prevKey) - amp.accountsHeads.Set(accountMeempool.currentKey, accountMeempool) - amp.senders[sender] = accountMeempool - return nil -} - -func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int64) ([]Tx, error) { - var selectedTxs []Tx - var txBytes int64 - - currentAccount := amp.accountsHeads.Front() - for currentAccount != nil { - accountMemPool := currentAccount.Value.(*AccountMemPool) - - prevKey := accountMemPool.currentKey - tx := accountMemPool.Pop() - if tx == nil { - return selectedTxs, nil - } - mempoolTx := *tx - selectedTxs = append(selectedTxs, mempoolTx) - if txBytes += mempoolTx.Size(); txBytes >= maxBytes { - return selectedTxs, nil - } - - amp.accountsHeads.Remove(prevKey) - amp.accountsHeads.Set(accountMemPool.currentKey, accountMemPool) - currentAccount = amp.accountsHeads.Front() - } - return selectedTxs, nil -} - -func priorityHuanduLess(a, b interface{}) int { - keyA := a.(accountsHeadsKey) - keyB := b.(accountsHeadsKey) - if keyA.priority == keyB.priority { - return bytes.Compare(keyA.hash[:], keyB.hash[:]) - } else { - if keyA.priority < keyB.priority { - return -1 - } else { - return 1 - } - } -} - -func nonceHuanduLess(a, b interface{}) int { - keyA := a.(statefullPriorityKey) - keyB := b.(statefullPriorityKey) - uint64Compare := huandu.Uint64 - return uint64Compare.Compare(keyA.nonce, keyB.nonce) -} From 55d13d77f3c77ce0bc0790c30ee3149596a96c3a Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 27 Oct 2022 12:13:41 -0500 Subject: [PATCH 139/196] fix reference to deleted file --- baseapp/util_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/util_test.go b/baseapp/util_test.go index c38daab18602..91a23b8f3177 100644 --- a/baseapp/util_test.go +++ b/baseapp/util_test.go @@ -150,7 +150,7 @@ func makeTestConfig() depinject.Config { } func makeMinimalConfig() depinject.Config { - var mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewPriorityMempool()) + var mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewNonceMempool()) return depinject.Configs( depinject.Supply(mempoolOpt), appconfig.Compose(&appv1alpha1.Config{ From 3f76cb619c4fbdd010ca8c65479aee21737bed96 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 27 Oct 2022 12:26:20 -0500 Subject: [PATCH 140/196] panic --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 0496abec94b6..d2cafce717fa 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -870,7 +870,7 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, next, cursorErr := cursor.Next() if cursorErr != nil { - return nil, cursorErr + panic(cursorErr) } cursor = next } From cb147a3c0e6947287979b2b440d1dff741793445 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 27 Oct 2022 15:08:19 -0500 Subject: [PATCH 141/196] handle empty case properly --- types/mempool/mempool_test.go | 8 +++++++- types/mempool/nonce.go | 7 +++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index b08273866046..6a52b480b10a 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -139,6 +139,12 @@ func (s *MempoolTestSuite) TestDefaultMempool() { txs = append(txs, tx) } + // empty mempool behavior + require.Equal(t, 0, s.mempool.CountTx()) + selCursor, err := s.mempool.Select(nil) + require.NoError(t, err) + require.Nil(t, selCursor) + // same sender-nonce just overwrites a tx for _, tx := range txs { ctx = ctx.WithPriority(tx.priority) @@ -156,7 +162,7 @@ func (s *MempoolTestSuite) TestDefaultMempool() { } require.Equal(t, txCount, s.mempool.CountTx()) - selCursor, err := s.mempool.Select(nil) + selCursor, err = s.mempool.Select(nil) require.NoError(t, err) sel := fetchTxs(selCursor, 13) require.Equal(t, 13, len(sel)) diff --git a/types/mempool/nonce.go b/types/mempool/nonce.go index e69dff7174b2..98d7d725f2ee 100644 --- a/types/mempool/nonce.go +++ b/types/mempool/nonce.go @@ -88,8 +88,11 @@ func (sp nonceMempool) Insert(_ sdk.Context, tx Tx) error { // in nonce order. func (sp nonceMempool) Select(_ [][]byte) (SelectCursor, error) { currentTx := sp.txQueue.Front() - cursor := &nonceMempoolIterator{currentTx: currentTx} - return cursor, nil + if currentTx == nil { + return nil, nil + } + + return &nonceMempoolIterator{currentTx: currentTx}, nil } // CountTx returns the number of txs in the mempool. From b04e0513be297b93f48c73b78d7b321a1b140292 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 27 Oct 2022 15:19:41 -0600 Subject: [PATCH 142/196] feat: mempool select iterator (#13677) * draft of select cursor * fix reference to deleted file * panic * handle empty case properly --- baseapp/baseapp.go | 23 ++- baseapp/util_test.go | 2 +- types/mempool/mempool.go | 10 +- types/mempool/mempool_test.go | 28 ++- types/mempool/nonce.go | 44 ++-- types/mempool/nonce_test.go | 3 +- types/mempool/priority.go | 376 ---------------------------------- types/mempool/stateful.go | 146 ------------- 8 files changed, 86 insertions(+), 546 deletions(-) delete mode 100644 types/mempool/priority.go delete mode 100644 types/mempool/stateful.go diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 6ee754e40bc5..d2cafce717fa 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -839,17 +839,26 @@ func createEvents(msg sdk.Msg) sdk.Events { } func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, error) { - memTxs, selectErr := app.mempool.Select(req.Txs, req.MaxTxBytes) + cursor, selectErr := app.mempool.Select(req.Txs) if selectErr != nil { panic(selectErr) } - var txsBytes [][]byte - fmt.Println("memtx", memTxs) - for _, memTx := range memTxs { + var ( + txsBytes [][]byte + byteCount int64 + ) + for cursor != nil { + memTx := cursor.Tx() + + if byteCount += memTx.Size(); byteCount > req.MaxTxBytes { + break + } + bz, encErr := app.txEncoder(memTx) if encErr != nil { panic(encErr) } + fmt.Println("messages:", memTx.GetMsgs()) _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) if err != nil { @@ -859,7 +868,13 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, txsBytes = append(txsBytes, bz) } + next, cursorErr := cursor.Next() + if cursorErr != nil { + panic(cursorErr) + } + cursor = next } + return txsBytes, nil } diff --git a/baseapp/util_test.go b/baseapp/util_test.go index c38daab18602..91a23b8f3177 100644 --- a/baseapp/util_test.go +++ b/baseapp/util_test.go @@ -150,7 +150,7 @@ func makeTestConfig() depinject.Config { } func makeMinimalConfig() depinject.Config { - var mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewPriorityMempool()) + var mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewNonceMempool()) return depinject.Configs( depinject.Supply(mempoolOpt), appconfig.Compose(&appv1alpha1.Config{ diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index f32150d6f41e..0e08423748ca 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -26,7 +26,7 @@ type Mempool interface { // mempool, up to maxBytes or until the mempool is empty. The application can // decide to return transactions from its own mempool, from the incoming // txs, or some combination of both. - Select(txs [][]byte, maxBytes int64) ([]Tx, error) + Select(txs [][]byte) (SelectCursor, error) // CountTx returns the number of transactions currently in the mempool. CountTx() int @@ -36,6 +36,14 @@ type Mempool interface { Remove(Tx) error } +type SelectCursor interface { + // Next returns the next transaction from the cursor. + Next() (SelectCursor, error) + + // Tx returns the transaction from the cursor. + Tx() Tx +} + var ErrTxNotFound = errors.New("tx not found in mempool") type Factory func() Mempool diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index d6f8e8134679..6a52b480b10a 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -104,6 +104,25 @@ func (tx txSpec) String() string { return fmt.Sprintf("[tx i: %d, a: %s, p: %d, n: %d]", tx.i, tx.a, tx.p, tx.n) } +func fetchTxs(cursor mempool.SelectCursor, maxBytes int64) []mempool.Tx { + var ( + txs []mempool.Tx + numBytes int64 + ) + for cursor != nil { + if numBytes += cursor.Tx().Size(); numBytes > maxBytes { + break + } + txs = append(txs, cursor.Tx()) + c, err := cursor.Next() + if err != nil { + panic(err) + } + cursor = c + } + return txs +} + func (s *MempoolTestSuite) TestDefaultMempool() { t := s.T() ctx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) @@ -120,6 +139,12 @@ func (s *MempoolTestSuite) TestDefaultMempool() { txs = append(txs, tx) } + // empty mempool behavior + require.Equal(t, 0, s.mempool.CountTx()) + selCursor, err := s.mempool.Select(nil) + require.NoError(t, err) + require.Nil(t, selCursor) + // same sender-nonce just overwrites a tx for _, tx := range txs { ctx = ctx.WithPriority(tx.priority) @@ -137,8 +162,9 @@ func (s *MempoolTestSuite) TestDefaultMempool() { } require.Equal(t, txCount, s.mempool.CountTx()) - sel, err := s.mempool.Select(nil, 13) + selCursor, err = s.mempool.Select(nil) require.NoError(t, err) + sel := fetchTxs(selCursor, 13) require.Equal(t, 13, len(sel)) // a tx which does not implement SigVerifiableTx should not be inserted diff --git a/types/mempool/nonce.go b/types/mempool/nonce.go index da7f10d8d193..98d7d725f2ee 100644 --- a/types/mempool/nonce.go +++ b/types/mempool/nonce.go @@ -9,6 +9,11 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/signing" ) +var ( + _ Mempool = (*nonceMempool)(nil) + _ SelectCursor = (*nonceMempoolIterator)(nil) +) + // nonceMempool is a mempool that keeps transactions sorted by nonce. Transactions with the lowest nonce globally // are prioritized. Transactions with the same nonce are prioritized by sender address. Fee/gas based // prioritization is not supported. @@ -16,6 +21,24 @@ type nonceMempool struct { txQueue *huandu.SkipList } +type nonceMempoolIterator struct { + currentTx *huandu.Element +} + +func (i nonceMempoolIterator) Next() (SelectCursor, error) { + if i.currentTx == nil { + return nil, nil + } else if n := i.currentTx.Next(); n != nil { + return nonceMempoolIterator{currentTx: n}, nil + } else { + return nil, nil + } +} + +func (i nonceMempoolIterator) Tx() Tx { + return i.currentTx.Value.(Tx) +} + type txKey struct { nonce uint64 sender string @@ -63,24 +86,13 @@ func (sp nonceMempool) Insert(_ sdk.Context, tx Tx) error { // Select returns txs from the mempool with the lowest nonce globally first. A sender's txs will always be returned // in nonce order. -func (sp nonceMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { - var ( - txBytes int64 - selectedTxs []Tx - ) - +func (sp nonceMempool) Select(_ [][]byte) (SelectCursor, error) { currentTx := sp.txQueue.Front() - for currentTx != nil { - mempoolTx := currentTx.Value.(Tx) - - if txBytes += mempoolTx.Size(); txBytes <= maxBytes { - selectedTxs = append(selectedTxs, mempoolTx) - } else { - return selectedTxs, nil - } - currentTx = currentTx.Next() + if currentTx == nil { + return nil, nil } - return selectedTxs, nil + + return &nonceMempoolIterator{currentTx: currentTx}, nil } // CountTx returns the number of txs in the mempool. diff --git a/types/mempool/nonce_test.go b/types/mempool/nonce_test.go index e152ce0990dd..1443da69e2be 100644 --- a/types/mempool/nonce_test.go +++ b/types/mempool/nonce_test.go @@ -193,8 +193,9 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, err) } - orderedTxs, err := pool.Select(nil, 1000) + selCursor, err := pool.Select(nil) require.NoError(t, err) + orderedTxs := fetchTxs(selCursor, 1000) var txOrder []int for _, tx := range orderedTxs { txOrder = append(txOrder, tx.(testTx).id) diff --git a/types/mempool/priority.go b/types/mempool/priority.go deleted file mode 100644 index 905aa503b790..000000000000 --- a/types/mempool/priority.go +++ /dev/null @@ -1,376 +0,0 @@ -package mempool - -import ( - "fmt" - "math" - - huandu "github.com/huandu/skiplist" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/signing" -) - -var _ Mempool = (*priorityMempool)(nil) - -// priorityMempool defines the SDK's default mempool implementation which stores -// txs in a partially ordered set by 2 dimensions: priority, and sender-nonce -// (sequence number). Internally it uses one priority ordered skip list and one -// skip list per sender ordered by sender-nonce (sequence number). When there -// are multiple txs from the same sender, they are not always comparable by -// priority to other sender txs and must be partially ordered by both sender-nonce -// and priority. -type priorityMempool struct { - priorityIndex *huandu.SkipList - priorityCounts map[int64]int - senderIndices map[string]*huandu.SkipList - senderCursors map[string]*huandu.Element - scores map[txMeta]txMeta - onRead func(tx Tx) -} - -// txMeta stores transaction metadata used in indices -type txMeta struct { - // nonce is the sender's sequence number - nonce uint64 - // priority is the transaction's priority - priority int64 - // sender is the transaction's sender - sender string - // weight is the transaction's weight, used as a tiebreaker for transactions with the same priority - weight int64 - // senderElement is a pointer to the transaction's element in the sender index - senderElement *huandu.Element -} - -// txMetaLess is a comparator for txKeys that first compares priority, then weight, -// then sender, then nonce, uniquely identifying a transaction. -// -// Note, txMetaLess is used as the comparator in the priority index. -func txMetaLess(a, b any) int { - keyA := a.(txMeta) - keyB := b.(txMeta) - res := huandu.Int64.Compare(keyA.priority, keyB.priority) - if res != 0 { - return res - } - - // weight is used as a tiebreaker for transactions with the same priority. weight is calculated in a single - // pass in .Select(...) and so will be 0 on .Insert(...) - res = huandu.Int64.Compare(keyA.weight, keyB.weight) - if res != 0 { - return res - } - - // Because weight will be 0 on .Insert(...), we must also compare sender and nonce to resolve priority collisions. - // If we didn't then transactions with the same priority would overwrite each other in the priority index. - res = huandu.String.Compare(keyA.sender, keyB.sender) - if res != 0 { - return res - } - - return huandu.Uint64.Compare(keyA.nonce, keyB.nonce) -} - -type PriorityMempoolOption func(*priorityMempool) - -// WithOnRead sets a callback to be called when a tx is read from the mempool. -func WithOnRead(onRead func(tx Tx)) PriorityMempoolOption { - return func(mp *priorityMempool) { - mp.onRead = onRead - } -} - -// DefaultPriorityMempool returns a priorityMempool with no options. -func DefaultPriorityMempool() Mempool { - return NewPriorityMempool() -} - -// NewPriorityMempool returns the SDK's default mempool implementation which -// returns txs in a partial order by 2 dimensions; priority, and sender-nonce. -func NewPriorityMempool(opts ...PriorityMempoolOption) Mempool { - mp := &priorityMempool{ - priorityIndex: huandu.New(huandu.LessThanFunc(txMetaLess)), - priorityCounts: make(map[int64]int), - senderIndices: make(map[string]*huandu.SkipList), - senderCursors: make(map[string]*huandu.Element), - scores: make(map[txMeta]txMeta), - } - - for _, opt := range opts { - opt(mp) - } - - return mp -} - -// Insert attempts to insert a Tx into the app-side mempool in O(log n) time, -// returning an error if unsuccessful. Sender and nonce are derived from the -// transaction's first signature. -// -// Transactions are unique by sender and nonce. Inserting a duplicate tx is an -// O(log n) no-op. -// -// Inserting a duplicate tx with a different priority overwrites the existing tx, -// changing the total order of the mempool. -func (mp *priorityMempool) Insert(ctx sdk.Context, tx Tx) error { - sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - if err != nil { - return err - } - if len(sigs) == 0 { - return fmt.Errorf("tx must have at least one signer") - } - - priority := ctx.Priority() - sig := sigs[0] - sender := sig.PubKey.Address().String() - nonce := sig.Sequence - key := txMeta{nonce: nonce, priority: priority, sender: sender} - - senderIndex, ok := mp.senderIndices[sender] - if !ok { - senderIndex = huandu.New(huandu.LessThanFunc(func(a, b any) int { - return huandu.Uint64.Compare(b.(txMeta).nonce, a.(txMeta).nonce) - })) - - // initialize sender index if not found - mp.senderIndices[sender] = senderIndex - } - - mp.priorityCounts[priority]++ - - // Since senderIndex is scored by nonce, a changed priority will overwrite the - // existing key. - key.senderElement = senderIndex.Set(key, tx) - - // Since mp.priorityIndex is scored by priority, then sender, then nonce, a - // changed priority will create a new key, so we must remove the old key and - // re-insert it to avoid having the same tx with different priorityIndex indexed - // twice in the mempool. - // - // This O(log n) remove operation is rare and only happens when a tx's priority - // changes. - sk := txMeta{nonce: nonce, sender: sender} - if oldScore, txExists := mp.scores[sk]; txExists { - mp.priorityIndex.Remove(txMeta{ - nonce: nonce, - sender: sender, - priority: oldScore.priority, - weight: oldScore.weight, - }) - } - - mp.scores[sk] = txMeta{priority: priority} - mp.priorityIndex.Set(key, tx) - - return nil -} - -// Select returns a set of transactions from the mempool, ordered by priority -// and sender-nonce in O(n) time. The passed in list of transactions are ignored. -// This is a readonly operation, the mempool is not modified. -// -// The maxBytes parameter defines the maximum number of bytes of transactions to -// return. -func (mp *priorityMempool) Select(_ [][]byte, maxBytes int64) ([]Tx, error) { - var ( - selectedTxs []Tx - txBytes int64 - ) - - mp.senderCursors = make(map[string]*huandu.Element) - mp.reorderPriorityTies() - - priorityNode := mp.priorityIndex.Front() - for priorityNode != nil { - priorityKey := priorityNode.Key().(txMeta) - nextHighestPriority, nextPriorityNode := mp.nextPriority(priorityNode) - sender := priorityKey.sender - senderTx := mp.fetchSenderCursor(sender) - - // iterate through the sender's transactions in nonce order - for senderTx != nil { - mempoolTx := senderTx.Value.(Tx) - if mp.onRead != nil { - mp.onRead(mempoolTx) - } - - key := senderTx.Key().(txMeta) - - // break if we've reached a transaction with a priority lower than the next highest priority in the pool - if key.priority < nextHighestPriority { - break - } else if key.priority == nextHighestPriority { - // weight is incorporated into the priority index key only (not sender index) so we must fetch it here - // from the scores map. - weight := mp.scores[txMeta{nonce: key.nonce, sender: key.sender}].weight - if weight < nextPriorityNode.Key().(txMeta).weight { - break - } - } - - // otherwise, select the transaction and continue iteration - selectedTxs = append(selectedTxs, mempoolTx) - if txBytes += mempoolTx.Size(); txBytes >= maxBytes { - return selectedTxs, nil - } - - senderTx = senderTx.Next() - mp.senderCursors[sender] = senderTx - } - - priorityNode = nextPriorityNode - } - - return selectedTxs, nil -} - -type reorderKey struct { - deleteKey txMeta - insertKey txMeta - tx Tx -} - -func (mp *priorityMempool) reorderPriorityTies() { - node := mp.priorityIndex.Front() - var reordering []reorderKey - for node != nil { - key := node.Key().(txMeta) - if mp.priorityCounts[key.priority] > 1 { - newKey := key - newKey.weight = senderWeight(key.senderElement) - reordering = append(reordering, reorderKey{deleteKey: key, insertKey: newKey, tx: node.Value.(Tx)}) - } - node = node.Next() - } - - for _, k := range reordering { - mp.priorityIndex.Remove(k.deleteKey) - delete(mp.scores, txMeta{nonce: k.deleteKey.nonce, sender: k.deleteKey.sender}) - mp.priorityIndex.Set(k.insertKey, k.tx) - mp.scores[txMeta{nonce: k.insertKey.nonce, sender: k.insertKey.sender}] = k.insertKey - } -} - -// senderWeight returns the weight of a given tx (t) at senderCursor. Weight is defined as the first (nonce-wise) -// same sender tx with a priority not equal to t. It is used to resolve priority collisions, that is when 2 or more -// txs from different senders have the same priority. -func senderWeight(senderCursor *huandu.Element) int64 { - if senderCursor == nil { - return 0 - } - weight := senderCursor.Key().(txMeta).priority - senderCursor = senderCursor.Next() - for senderCursor != nil { - p := senderCursor.Key().(txMeta).priority - if p != weight { - weight = p - } - senderCursor = senderCursor.Next() - } - - return weight -} - -func (mp *priorityMempool) fetchSenderCursor(sender string) *huandu.Element { - senderTx, ok := mp.senderCursors[sender] - if !ok { - senderTx = mp.senderIndices[sender].Front() - } - return senderTx -} - -func (mp *priorityMempool) nextPriority(priorityNode *huandu.Element) (int64, *huandu.Element) { - var nextPriorityNode *huandu.Element - if priorityNode == nil { - nextPriorityNode = mp.priorityIndex.Front() - } else { - nextPriorityNode = priorityNode.Next() - } - - if nextPriorityNode == nil { - return math.MinInt64, nil - } - - np := nextPriorityNode.Key().(txMeta).priority - - return np, nextPriorityNode -} - -// CountTx returns the number of transactions in the mempool. -func (mp *priorityMempool) CountTx() int { - return mp.priorityIndex.Len() -} - -// Remove removes a transaction from the mempool in O(log n) time, returning an error if unsuccessful. -func (mp *priorityMempool) Remove(tx Tx) error { - sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - if err != nil { - return err - } - if len(sigs) == 0 { - return fmt.Errorf("attempted to remove a tx with no signatures") - } - - sig := sigs[0] - sender := sig.PubKey.Address().String() - nonce := sig.Sequence - - scoreKey := txMeta{nonce: nonce, sender: sender} - score, ok := mp.scores[scoreKey] - if !ok { - return ErrTxNotFound - } - tk := txMeta{nonce: nonce, priority: score.priority, sender: sender, weight: score.weight} - - senderTxs, ok := mp.senderIndices[sender] - if !ok { - return fmt.Errorf("sender %s not found", sender) - } - - mp.priorityIndex.Remove(tk) - senderTxs.Remove(tk) - delete(mp.scores, scoreKey) - mp.priorityCounts[score.priority]-- - - return nil -} - -func IsEmpty(mempool Mempool) error { - mp := mempool.(*priorityMempool) - if mp.priorityIndex.Len() != 0 { - return fmt.Errorf("priorityIndex not empty") - } - - var countKeys []int64 - for k := range mp.priorityCounts { - countKeys = append(countKeys, k) - } - for _, k := range countKeys { - if mp.priorityCounts[k] != 0 { - return fmt.Errorf("priorityCounts not zero at %v, got %v", k, mp.priorityCounts[k]) - } - } - - var senderKeys []string - for k := range mp.senderIndices { - senderKeys = append(senderKeys, k) - } - for _, k := range senderKeys { - if mp.senderIndices[k].Len() != 0 { - return fmt.Errorf("senderIndex not empty for sender %v", k) - } - } - - return nil -} - -func DebugPrintKeys(mempool Mempool) { - mp := mempool.(*priorityMempool) - n := mp.priorityIndex.Front() - for n != nil { - k := n.Key().(txMeta) - fmt.Printf("%s, %d, %d, %d\n", k.sender, k.priority, k.nonce, k.weight) - n = n.Next() - } -} diff --git a/types/mempool/stateful.go b/types/mempool/stateful.go deleted file mode 100644 index 57456876eadb..000000000000 --- a/types/mempool/stateful.go +++ /dev/null @@ -1,146 +0,0 @@ -package mempool - -import ( - "bytes" - "crypto/sha256" - "fmt" - - huandu "github.com/huandu/skiplist" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/signing" -) - -// The complexity is O(log(N)). Implementation -type statefullPriorityKey struct { - hash [32]byte - priority int64 - nonce uint64 -} - -type accountsHeadsKey struct { - sender string - priority int64 - hash [32]byte -} - -type AccountMemPool struct { - transactions *huandu.SkipList - currentKey accountsHeadsKey - currentItem *huandu.Element - sender string -} - -// Push cannot be executed in the middle of a select -func (amp *AccountMemPool) Push(ctx sdk.Context, key statefullPriorityKey, tx Tx) { - amp.transactions.Set(key, tx) - amp.currentItem = amp.transactions.Back() - newKey := amp.currentItem.Key().(statefullPriorityKey) - amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} -} - -func (amp *AccountMemPool) Pop() *Tx { - if amp.currentItem == nil { - return nil - } - itemToPop := amp.currentItem - amp.currentItem = itemToPop.Prev() - if amp.currentItem != nil { - newKey := amp.currentItem.Key().(statefullPriorityKey) - amp.currentKey = accountsHeadsKey{hash: newKey.hash, sender: amp.sender, priority: newKey.priority} - } else { - amp.currentKey = accountsHeadsKey{} - } - tx := itemToPop.Value.(Tx) - return &tx -} - -type MemPoolI struct { - accountsHeads *huandu.SkipList - senders map[string]*AccountMemPool -} - -func NewMemPoolI() MemPoolI { - return MemPoolI{ - accountsHeads: huandu.New(huandu.LessThanFunc(priorityHuanduLess)), - senders: make(map[string]*AccountMemPool), - } -} - -func (amp *MemPoolI) Insert(ctx sdk.Context, tx Tx) error { - senders := tx.(signing.SigVerifiableTx).GetSigners() - nonces, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() - - if err != nil { - return err - } else if len(senders) != len(nonces) { - return fmt.Errorf("number of senders (%d) does not match number of nonces (%d)", len(senders), len(nonces)) - } - sender := senders[0].String() - nonce := nonces[0].Sequence - - accountMeempool, ok := amp.senders[sender] - if !ok { - accountMeempool = &AccountMemPool{ - transactions: huandu.New(huandu.LessThanFunc(nonceHuanduLess)), - sender: sender, - } - } - hash := sha256.Sum256(senders[0].Bytes()) - key := statefullPriorityKey{hash: hash, nonce: nonce, priority: ctx.Priority()} - - prevKey := accountMeempool.currentKey - accountMeempool.Push(ctx, key, tx) - - amp.accountsHeads.Remove(prevKey) - amp.accountsHeads.Set(accountMeempool.currentKey, accountMeempool) - amp.senders[sender] = accountMeempool - return nil -} - -func (amp *MemPoolI) Select(_ sdk.Context, _ [][]byte, maxBytes int64) ([]Tx, error) { - var selectedTxs []Tx - var txBytes int64 - - currentAccount := amp.accountsHeads.Front() - for currentAccount != nil { - accountMemPool := currentAccount.Value.(*AccountMemPool) - - prevKey := accountMemPool.currentKey - tx := accountMemPool.Pop() - if tx == nil { - return selectedTxs, nil - } - mempoolTx := *tx - selectedTxs = append(selectedTxs, mempoolTx) - if txBytes += mempoolTx.Size(); txBytes >= maxBytes { - return selectedTxs, nil - } - - amp.accountsHeads.Remove(prevKey) - amp.accountsHeads.Set(accountMemPool.currentKey, accountMemPool) - currentAccount = amp.accountsHeads.Front() - } - return selectedTxs, nil -} - -func priorityHuanduLess(a, b interface{}) int { - keyA := a.(accountsHeadsKey) - keyB := b.(accountsHeadsKey) - if keyA.priority == keyB.priority { - return bytes.Compare(keyA.hash[:], keyB.hash[:]) - } else { - if keyA.priority < keyB.priority { - return -1 - } else { - return 1 - } - } -} - -func nonceHuanduLess(a, b interface{}) int { - keyA := a.(statefullPriorityKey) - keyB := b.(statefullPriorityKey) - uint64Compare := huandu.Uint64 - return uint64Compare.Compare(keyA.nonce, keyB.nonce) -} From 6f71213323901006df2bd7f43281dde237162c0b Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 27 Oct 2022 15:41:45 -0600 Subject: [PATCH 143/196] t --- baseapp/abci.go | 2 -- baseapp/baseapp.go | 6 ------ 2 files changed, 8 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 1cf4d86f02ca..1c6b42ba0b24 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -261,7 +261,6 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { - fmt.Println("prepare propossal") txs, err := app.prepareProposal(req) if err != nil { fmt.Println("do something go error en prepare propossal", err) @@ -282,7 +281,6 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { - fmt.Println("process propossal") err := app.processProposal(req) if err != nil { return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 6ee754e40bc5..2daebf2df9f2 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -696,7 +696,6 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re } if mode == runTxModeCheck { - fmt.Println("inserting tx:", tx.GetMsgs()) err = app.mempool.Insert(ctx, tx.(mempool.Tx)) if err != nil { return gInfo, nil, anteEvents, priority, err @@ -844,16 +843,13 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, panic(selectErr) } var txsBytes [][]byte - fmt.Println("memtx", memTxs) for _, memTx := range memTxs { bz, encErr := app.txEncoder(memTx) if encErr != nil { panic(encErr) } - fmt.Println("messages:", memTx.GetMsgs()) _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) if err != nil { - fmt.Println("error un prepare propossal", memTx) _ = app.mempool.Remove(memTx) } else { txsBytes = append(txsBytes, bz) @@ -869,11 +865,9 @@ func (app *BaseApp) processProposal(req abci.RequestProcessProposal) error { if err != nil { return err } - fmt.Println(tx) _, _, _, _, err = app.runTx(runTxProcessProposal, txBytes) if err != nil { - fmt.Println("error run tx process", tx) _ = app.mempool.Remove(tx.(mempool.Tx)) } } From 0cc1aaab9d7c604f6f86a9a2a6005b77c2597135 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 27 Oct 2022 17:12:30 -0500 Subject: [PATCH 144/196] refactor iterator --- baseapp/baseapp.go | 31 +++++++++++++------------------ types/mempool/mempool.go | 18 +++++++++--------- types/mempool/mempool_test.go | 25 ++++++++++--------------- types/mempool/nonce.go | 18 +++++++++--------- types/mempool/nonce_test.go | 5 ++--- 5 files changed, 43 insertions(+), 54 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index d2cafce717fa..c443a58ab371 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -839,40 +839,35 @@ func createEvents(msg sdk.Msg) sdk.Events { } func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, error) { - cursor, selectErr := app.mempool.Select(req.Txs) - if selectErr != nil { - panic(selectErr) - } + iterator := app.mempool.Select(req.Txs) var ( txsBytes [][]byte byteCount int64 ) - for cursor != nil { - memTx := cursor.Tx() - - if byteCount += memTx.Size(); byteCount > req.MaxTxBytes { - break - } + for iterator != nil { + memTx := iterator.Tx() bz, encErr := app.txEncoder(memTx) if encErr != nil { - panic(encErr) + return nil, encErr } fmt.Println("messages:", memTx.GetMsgs()) _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) if err != nil { fmt.Println("error un prepare propossal", memTx) - _ = app.mempool.Remove(memTx) - } else { + removeErr := app.mempool.Remove(memTx) + if removeErr != nil { + return nil, removeErr + } + continue + } else if byteCount += memTx.Size(); byteCount <= req.MaxTxBytes { txsBytes = append(txsBytes, bz) + } else { + break } - next, cursorErr := cursor.Next() - if cursorErr != nil { - panic(cursorErr) - } - cursor = next + iterator = iterator.Next() } return txsBytes, nil diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 0e08423748ca..ebce7d8e468b 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -22,11 +22,9 @@ type Mempool interface { // an error upon failure. Insert(types.Context, Tx) error - // Select returns the next set of available transactions from the app-side - // mempool, up to maxBytes or until the mempool is empty. The application can - // decide to return transactions from its own mempool, from the incoming - // txs, or some combination of both. - Select(txs [][]byte) (SelectCursor, error) + // Select returns an Iterator over the app-side mempool. If txs are specified, then they shall be incorporated + // into the Iterator. The Iterator must be closed by the caller. + Select(txs [][]byte) Iterator // CountTx returns the number of transactions currently in the mempool. CountTx() int @@ -36,11 +34,13 @@ type Mempool interface { Remove(Tx) error } -type SelectCursor interface { - // Next returns the next transaction from the cursor. - Next() (SelectCursor, error) +// Iterator defines an app-side mempool iterator interface that is as minimal as possible. The order of iteration +// is determined by the app-side mempool implementation. +type Iterator interface { + // Next returns the next transaction from the mempool. If there are no more transactions, it returns nil. + Next() Iterator - // Tx returns the transaction from the cursor. + // Tx returns the transaction at the current position of the iterator. Tx() Tx } diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 6a52b480b10a..5892cc6e6d35 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -104,21 +104,18 @@ func (tx txSpec) String() string { return fmt.Sprintf("[tx i: %d, a: %s, p: %d, n: %d]", tx.i, tx.a, tx.p, tx.n) } -func fetchTxs(cursor mempool.SelectCursor, maxBytes int64) []mempool.Tx { +func fetchTxs(iterator mempool.Iterator, maxBytes int64) []mempool.Tx { var ( txs []mempool.Tx numBytes int64 ) - for cursor != nil { - if numBytes += cursor.Tx().Size(); numBytes > maxBytes { + for iterator != nil { + if numBytes += iterator.Tx().Size(); numBytes > maxBytes { break } - txs = append(txs, cursor.Tx()) - c, err := cursor.Next() - if err != nil { - panic(err) - } - cursor = c + txs = append(txs, iterator.Tx()) + i := iterator.Next() + iterator = i } return txs } @@ -141,9 +138,8 @@ func (s *MempoolTestSuite) TestDefaultMempool() { // empty mempool behavior require.Equal(t, 0, s.mempool.CountTx()) - selCursor, err := s.mempool.Select(nil) - require.NoError(t, err) - require.Nil(t, selCursor) + itr := s.mempool.Select(nil) + require.Nil(t, itr) // same sender-nonce just overwrites a tx for _, tx := range txs { @@ -162,9 +158,8 @@ func (s *MempoolTestSuite) TestDefaultMempool() { } require.Equal(t, txCount, s.mempool.CountTx()) - selCursor, err = s.mempool.Select(nil) - require.NoError(t, err) - sel := fetchTxs(selCursor, 13) + itr = s.mempool.Select(nil) + sel := fetchTxs(itr, 13) require.Equal(t, 13, len(sel)) // a tx which does not implement SigVerifiableTx should not be inserted diff --git a/types/mempool/nonce.go b/types/mempool/nonce.go index 98d7d725f2ee..8241233b5c37 100644 --- a/types/mempool/nonce.go +++ b/types/mempool/nonce.go @@ -10,8 +10,8 @@ import ( ) var ( - _ Mempool = (*nonceMempool)(nil) - _ SelectCursor = (*nonceMempoolIterator)(nil) + _ Mempool = (*nonceMempool)(nil) + _ Iterator = (*nonceMempoolIterator)(nil) ) // nonceMempool is a mempool that keeps transactions sorted by nonce. Transactions with the lowest nonce globally @@ -25,13 +25,13 @@ type nonceMempoolIterator struct { currentTx *huandu.Element } -func (i nonceMempoolIterator) Next() (SelectCursor, error) { +func (i nonceMempoolIterator) Next() Iterator { if i.currentTx == nil { - return nil, nil + return nil } else if n := i.currentTx.Next(); n != nil { - return nonceMempoolIterator{currentTx: n}, nil + return nonceMempoolIterator{currentTx: n} } else { - return nil, nil + return nil } } @@ -86,13 +86,13 @@ func (sp nonceMempool) Insert(_ sdk.Context, tx Tx) error { // Select returns txs from the mempool with the lowest nonce globally first. A sender's txs will always be returned // in nonce order. -func (sp nonceMempool) Select(_ [][]byte) (SelectCursor, error) { +func (sp nonceMempool) Select(_ [][]byte) Iterator { currentTx := sp.txQueue.Front() if currentTx == nil { - return nil, nil + return nil } - return &nonceMempoolIterator{currentTx: currentTx}, nil + return &nonceMempoolIterator{currentTx: currentTx} } // CountTx returns the number of txs in the mempool. diff --git a/types/mempool/nonce_test.go b/types/mempool/nonce_test.go index 1443da69e2be..65565e8802cd 100644 --- a/types/mempool/nonce_test.go +++ b/types/mempool/nonce_test.go @@ -193,9 +193,8 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, err) } - selCursor, err := pool.Select(nil) - require.NoError(t, err) - orderedTxs := fetchTxs(selCursor, 1000) + itr := pool.Select(nil) + orderedTxs := fetchTxs(itr, 1000) var txOrder []int for _, tx := range orderedTxs { txOrder = append(txOrder, tx.(testTx).id) From 96cb90bdef40f65a253eb00304e34c4f90420ad5 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 31 Oct 2022 09:52:32 -0500 Subject: [PATCH 145/196] Remove mempool.Tx interface - baseapp now counts bytes for PrepareProposal --- baseapp/baseapp.go | 9 ++++---- server/mock/tx.go | 2 -- types/mempool/mempool.go | 19 ++++------------- types/mempool/mempool_test.go | 10 ++++----- types/mempool/nonce.go | 8 +++---- x/auth/tx/builder.go | 40 ++++++----------------------------- x/auth/tx/decoder.go | 1 - 7 files changed, 23 insertions(+), 66 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index c443a58ab371..787ef4ce378c 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -697,12 +697,12 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re if mode == runTxModeCheck { fmt.Println("inserting tx:", tx.GetMsgs()) - err = app.mempool.Insert(ctx, tx.(mempool.Tx)) + err = app.mempool.Insert(ctx, tx) if err != nil { return gInfo, nil, anteEvents, priority, err } } else if mode == runTxModeDeliver { - err = app.mempool.Remove(tx.(mempool.Tx)) + err = app.mempool.Remove(tx) if err != nil && !errors.Is(err, mempool.ErrTxNotFound) { return gInfo, nil, anteEvents, priority, fmt.Errorf("failed to remove tx from mempool: %w", err) @@ -848,6 +848,7 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, memTx := iterator.Tx() bz, encErr := app.txEncoder(memTx) + txSize := int64(len(bz)) if encErr != nil { return nil, encErr } @@ -861,7 +862,7 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, return nil, removeErr } continue - } else if byteCount += memTx.Size(); byteCount <= req.MaxTxBytes { + } else if byteCount += txSize; byteCount <= req.MaxTxBytes { txsBytes = append(txsBytes, bz) } else { break @@ -884,7 +885,7 @@ func (app *BaseApp) processProposal(req abci.RequestProcessProposal) error { _, _, _, _, err = app.runTx(runTxProcessProposal, txBytes) if err != nil { fmt.Println("error run tx process", tx) - _ = app.mempool.Remove(tx.(mempool.Tx)) + _ = app.mempool.Remove(tx) } } return nil diff --git a/server/mock/tx.go b/server/mock/tx.go index 082dbb8d8f26..f40d697b8edf 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -7,7 +7,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/signing" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/types/mempool" txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -74,7 +73,6 @@ func (msg *kvstoreTx) ProtoMessage() {} var ( _ sdk.Tx = &kvstoreTx{} - _ mempool.Tx = &kvstoreTx{} _ sdk.Msg = &kvstoreTx{} _ signing.SigVerifiableTx = &kvstoreTx{} _ cryptotypes.PubKey = &kvstoreTx{} diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index ebce7d8e468b..82e72f0e5e6d 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -3,24 +3,13 @@ package mempool import ( "errors" - "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) -// Tx defines an app-side mempool transaction interface that is as -// minimal as possible, only requiring applications to define the size of the -// transaction to be used when inserting, selecting, and deleting the transaction. -// Interface type casting can be used in the actual app-side mempool implementation. -type Tx interface { - types.Tx - - // Size returns the size of the transaction in bytes. - Size() int64 -} - type Mempool interface { // Insert attempts to insert a Tx into the app-side mempool returning // an error upon failure. - Insert(types.Context, Tx) error + Insert(sdk.Context, sdk.Tx) error // Select returns an Iterator over the app-side mempool. If txs are specified, then they shall be incorporated // into the Iterator. The Iterator must be closed by the caller. @@ -31,7 +20,7 @@ type Mempool interface { // Remove attempts to remove a transaction from the mempool, returning an error // upon failure. - Remove(Tx) error + Remove(tx sdk.Tx) error } // Iterator defines an app-side mempool iterator interface that is as minimal as possible. The order of iteration @@ -41,7 +30,7 @@ type Iterator interface { Next() Iterator // Tx returns the transaction at the current position of the iterator. - Tx() Tx + Tx() sdk.Tx } var ErrTxNotFound = errors.New("tx not found in mempool") diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index 5892cc6e6d35..ac9dbde24a50 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -62,13 +62,10 @@ func (tx testTx) GetSignaturesV2() (res []txsigning.SignatureV2, err error) { var ( _ sdk.Tx = (*testTx)(nil) - _ mempool.Tx = (*testTx)(nil) _ signing.SigVerifiableTx = (*testTx)(nil) _ cryptotypes.PubKey = (*testPubKey)(nil) ) -func (tx testTx) Size() int64 { return 1 } - func (tx testTx) GetMsgs() []sdk.Msg { return nil } func (tx testTx) ValidateBasic() error { return nil } @@ -104,13 +101,14 @@ func (tx txSpec) String() string { return fmt.Sprintf("[tx i: %d, a: %s, p: %d, n: %d]", tx.i, tx.a, tx.p, tx.n) } -func fetchTxs(iterator mempool.Iterator, maxBytes int64) []mempool.Tx { +func fetchTxs(iterator mempool.Iterator, maxBytes int64) []sdk.Tx { + const txSize = 1 var ( - txs []mempool.Tx + txs []sdk.Tx numBytes int64 ) for iterator != nil { - if numBytes += iterator.Tx().Size(); numBytes > maxBytes { + if numBytes += txSize; numBytes > maxBytes { break } txs = append(txs, iterator.Tx()) diff --git a/types/mempool/nonce.go b/types/mempool/nonce.go index 8241233b5c37..00b54adee7c6 100644 --- a/types/mempool/nonce.go +++ b/types/mempool/nonce.go @@ -35,8 +35,8 @@ func (i nonceMempoolIterator) Next() Iterator { } } -func (i nonceMempoolIterator) Tx() Tx { - return i.currentTx.Value.(Tx) +func (i nonceMempoolIterator) Tx() sdk.Tx { + return i.currentTx.Value.(sdk.Tx) } type txKey struct { @@ -67,7 +67,7 @@ func NewNonceMempool() Mempool { // Insert adds a tx to the mempool. It returns an error if the tx does not have at least one signer. // priority is ignored. -func (sp nonceMempool) Insert(_ sdk.Context, tx Tx) error { +func (sp nonceMempool) Insert(_ sdk.Context, tx sdk.Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { return err @@ -102,7 +102,7 @@ func (sp nonceMempool) CountTx() int { // Remove removes a tx from the mempool. It returns an error if the tx does not have at least one signer or the tx // was not found in the pool. -func (sp nonceMempool) Remove(tx Tx) error { +func (sp nonceMempool) Remove(tx sdk.Tx) error { sigs, err := tx.(signing.SigVerifiableTx).GetSignaturesV2() if err != nil { return err diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 604123167034..e1dbe180b8af 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -9,7 +9,6 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/mempool" "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/ante" @@ -32,8 +31,6 @@ type wrapper struct { authInfoBz []byte txBodyHasUnknownNonCriticals bool - - txSize int64 } var ( @@ -43,7 +40,6 @@ var ( _ ante.HasExtensionOptionsTx = &wrapper{} _ ExtensionOptionsTxBuilder = &wrapper{} _ tx.TipTx = &wrapper{} - _ mempool.Tx = &wrapper{} ) // ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions. @@ -66,12 +62,6 @@ func newBuilder(cdc codec.Codec) *wrapper { } } -// Size returns the size of the transaction, but is only correct immediately after decoding a proto-marshal transaction. -// It should not be used in any other cases. -func (w *wrapper) Size() int64 { - return w.txSize -} - func (w *wrapper) GetMsgs() []sdk.Msg { return w.tx.GetMsgs() } @@ -221,8 +211,6 @@ func (w *wrapper) SetMsgs(msgs ...sdk.Msg) error { // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 return nil } @@ -233,8 +221,6 @@ func (w *wrapper) SetTimeoutHeight(height uint64) { // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetMemo(memo string) { @@ -242,8 +228,6 @@ func (w *wrapper) SetMemo(memo string) { // set bodyBz to nil because the cached bodyBz no longer matches tx.Body w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetGasLimit(limit uint64) { @@ -255,8 +239,6 @@ func (w *wrapper) SetGasLimit(limit uint64) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetFeeAmount(coins sdk.Coins) { @@ -268,8 +250,6 @@ func (w *wrapper) SetFeeAmount(coins sdk.Coins) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) SetTip(tip *tx.Tip) { @@ -277,8 +257,7 @@ func (w *wrapper) SetTip(tip *tx.Tip) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 + } func (w *wrapper) SetFeePayer(feePayer sdk.AccAddress) { @@ -290,8 +269,7 @@ func (w *wrapper) SetFeePayer(feePayer sdk.AccAddress) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 + } func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) { @@ -303,8 +281,7 @@ func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 + } func (w *wrapper) SetSignatures(signatures ...signing.SignatureV2) error { @@ -336,8 +313,7 @@ func (w *wrapper) setSignerInfos(infos []*tx.SignerInfo) { w.tx.AuthInfo.SignerInfos = infos // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 + } func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { @@ -348,8 +324,7 @@ func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { w.tx.AuthInfo.SignerInfos[index] = info // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 + } func (w *wrapper) setSignatures(sigs [][]byte) { @@ -396,15 +371,12 @@ func (w *wrapper) GetNonCriticalExtensionOptions() []*codectypes.Any { func (w *wrapper) SetExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.ExtensionOptions = extOpts w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 + } func (w *wrapper) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.NonCriticalExtensionOptions = extOpts w.bodyBz = nil - // set txSize to 0 because it is no longer correct - w.txSize = 0 } func (w *wrapper) AddAuxSignerData(data tx.AuxSignerData) error { diff --git a/x/auth/tx/decoder.go b/x/auth/tx/decoder.go index 8b821bc00320..2fef6312b994 100644 --- a/x/auth/tx/decoder.go +++ b/x/auth/tx/decoder.go @@ -70,7 +70,6 @@ func DefaultTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder { tx: theTx, bodyBz: raw.BodyBytes, authInfoBz: raw.AuthInfoBytes, - txSize: int64(len(txBytes)), txBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals, }, nil } From 657ce24573ecd958ec93244503bc05c7afc6187f Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 31 Oct 2022 15:07:29 -0500 Subject: [PATCH 146/196] error handling --- baseapp/abci.go | 4 +--- baseapp/baseapp.go | 11 +++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 1cf4d86f02ca..4062c73f9990 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -261,10 +261,9 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { - fmt.Println("prepare propossal") txs, err := app.prepareProposal(req) if err != nil { - fmt.Println("do something go error en prepare propossal", err) + panic(err) } return abci.ResponsePrepareProposal{Txs: txs} } @@ -282,7 +281,6 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { - fmt.Println("process propossal") err := app.processProposal(req) if err != nil { return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 787ef4ce378c..d731c1b7e122 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -853,12 +853,10 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, return nil, encErr } - fmt.Println("messages:", memTx.GetMsgs()) _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) if err != nil { - fmt.Println("error un prepare propossal", memTx) removeErr := app.mempool.Remove(memTx) - if removeErr != nil { + if removeErr != nil && err != mempool.ErrTxNotFound { return nil, removeErr } continue @@ -880,12 +878,13 @@ func (app *BaseApp) processProposal(req abci.RequestProcessProposal) error { if err != nil { return err } - fmt.Println(tx) _, _, _, _, err = app.runTx(runTxProcessProposal, txBytes) if err != nil { - fmt.Println("error run tx process", tx) - _ = app.mempool.Remove(tx) + err = app.mempool.Remove(tx) + if err != nil && err != mempool.ErrTxNotFound { + return err + } } } return nil From 736306c483e606284eaaac5ba11da221cf8ac7d0 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 1 Nov 2022 08:33:43 -0500 Subject: [PATCH 147/196] Add sdk.Context to Mempool.Select() --- baseapp/baseapp.go | 5 ++++- types/mempool/mempool.go | 4 ++-- types/mempool/mempool_test.go | 4 ++-- types/mempool/nonce.go | 2 +- types/mempool/nonce_test.go | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index d731c1b7e122..99bc1ad5cb2b 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -839,11 +839,14 @@ func createEvents(msg sdk.Msg) sdk.Events { } func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, error) { - iterator := app.mempool.Select(req.Txs) var ( txsBytes [][]byte byteCount int64 ) + + ctx := app.getContextForTx(runTxPrepareProposal, []byte{}) + iterator := app.mempool.Select(ctx, req.Txs) + for iterator != nil { memTx := iterator.Tx() diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index 82e72f0e5e6d..aa2e218bf2a6 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -13,14 +13,14 @@ type Mempool interface { // Select returns an Iterator over the app-side mempool. If txs are specified, then they shall be incorporated // into the Iterator. The Iterator must be closed by the caller. - Select(txs [][]byte) Iterator + Select(sdk.Context, [][]byte) Iterator // CountTx returns the number of transactions currently in the mempool. CountTx() int // Remove attempts to remove a transaction from the mempool, returning an error // upon failure. - Remove(tx sdk.Tx) error + Remove(sdk.Tx) error } // Iterator defines an app-side mempool iterator interface that is as minimal as possible. The order of iteration diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index ac9dbde24a50..c208e8b921d9 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -136,7 +136,7 @@ func (s *MempoolTestSuite) TestDefaultMempool() { // empty mempool behavior require.Equal(t, 0, s.mempool.CountTx()) - itr := s.mempool.Select(nil) + itr := s.mempool.Select(ctx, nil) require.Nil(t, itr) // same sender-nonce just overwrites a tx @@ -156,7 +156,7 @@ func (s *MempoolTestSuite) TestDefaultMempool() { } require.Equal(t, txCount, s.mempool.CountTx()) - itr = s.mempool.Select(nil) + itr = s.mempool.Select(ctx, nil) sel := fetchTxs(itr, 13) require.Equal(t, 13, len(sel)) diff --git a/types/mempool/nonce.go b/types/mempool/nonce.go index 00b54adee7c6..4d9a1bf4d655 100644 --- a/types/mempool/nonce.go +++ b/types/mempool/nonce.go @@ -86,7 +86,7 @@ func (sp nonceMempool) Insert(_ sdk.Context, tx sdk.Tx) error { // Select returns txs from the mempool with the lowest nonce globally first. A sender's txs will always be returned // in nonce order. -func (sp nonceMempool) Select(_ [][]byte) Iterator { +func (sp nonceMempool) Select(_ sdk.Context, _ [][]byte) Iterator { currentTx := sp.txQueue.Front() if currentTx == nil { return nil diff --git a/types/mempool/nonce_test.go b/types/mempool/nonce_test.go index 65565e8802cd..663a17c770de 100644 --- a/types/mempool/nonce_test.go +++ b/types/mempool/nonce_test.go @@ -193,7 +193,7 @@ func (s *MempoolTestSuite) TestTxOrder() { require.NoError(t, err) } - itr := pool.Select(nil) + itr := pool.Select(ctx, nil) orderedTxs := fetchTxs(itr, 1000) var txOrder []int for _, tx := range orderedTxs { From 61bd98a0698fd00f5f47bb18c5853ee25144a294 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 1 Nov 2022 10:09:10 -0500 Subject: [PATCH 148/196] fix whitespace --- x/auth/tx/builder.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index e1dbe180b8af..63596e8034fa 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -257,7 +257,6 @@ func (w *wrapper) SetTip(tip *tx.Tip) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - } func (w *wrapper) SetFeePayer(feePayer sdk.AccAddress) { @@ -269,7 +268,6 @@ func (w *wrapper) SetFeePayer(feePayer sdk.AccAddress) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - } func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) { @@ -281,7 +279,6 @@ func (w *wrapper) SetFeeGranter(feeGranter sdk.AccAddress) { // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - } func (w *wrapper) SetSignatures(signatures ...signing.SignatureV2) error { @@ -324,7 +321,6 @@ func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { w.tx.AuthInfo.SignerInfos[index] = info // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - } func (w *wrapper) setSignatures(sigs [][]byte) { From 1f53170cb4b536c67dd7544e7292d6faa0f07f77 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 1 Nov 2022 10:11:00 -0500 Subject: [PATCH 149/196] revert simapp/app.go --- simapp/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simapp/app.go b/simapp/app.go index 5b0e9f7547cd..bb5328e99351 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -190,13 +190,13 @@ func NewSimApp( app = &SimApp{} appBuilder *runtime.AppBuilder - //mempoolOpt runtime.BaseAppOption = baseapp.SetMempool(mempool.NewPriorityMempool()) // merge the AppConfig and other configuration in one config appConfig = depinject.Configs( AppConfig, depinject.Supply( // supply the application options appOpts, + // ADVANCED CONFIGURATION // From 369c62256cde45709b6d89caaa9d0d24e448d43d Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 1 Nov 2022 10:14:26 -0500 Subject: [PATCH 150/196] fix whitespace --- x/auth/tx/builder.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 63596e8034fa..479829cae685 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -310,7 +310,6 @@ func (w *wrapper) setSignerInfos(infos []*tx.SignerInfo) { w.tx.AuthInfo.SignerInfos = infos // set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo w.authInfoBz = nil - } func (w *wrapper) setSignerInfoAtIndex(index int, info *tx.SignerInfo) { @@ -367,7 +366,6 @@ func (w *wrapper) GetNonCriticalExtensionOptions() []*codectypes.Any { func (w *wrapper) SetExtensionOptions(extOpts ...*codectypes.Any) { w.tx.Body.ExtensionOptions = extOpts w.bodyBz = nil - } func (w *wrapper) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) { From 32899a599bdb293ba903d0b45d0430df8010c365 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 1 Nov 2022 14:07:10 -0500 Subject: [PATCH 151/196] use setter for consistency --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 99bc1ad5cb2b..9a645e0ec53c 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -170,7 +170,7 @@ func NewBaseApp( // if execution of options has left certain required fields nil, set them to sane default values if app.mempool == nil { - app.mempool = mempool.NewNonceMempool() + app.SetMempool(mempool.NewNonceMempool()) } if app.interBlockCache != nil { From d4234d81843afa18b2e3fe7f16ccf3637b532e7c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 2 Nov 2022 11:02:01 -0500 Subject: [PATCH 152/196] test construction --- baseapp/deliver_tx_test.go | 7 +++++-- types/mempool/mempool.go | 2 -- types/mempool/nonce.go | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/baseapp/deliver_tx_test.go b/baseapp/deliver_tx_test.go index 85954eaad6b7..be54e088c2d6 100644 --- a/baseapp/deliver_tx_test.go +++ b/baseapp/deliver_tx_test.go @@ -18,6 +18,7 @@ import ( "unsafe" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/types/mempool" signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/stretchr/testify/assert" @@ -27,9 +28,10 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" - "cosmossdk.io/depinject" "github.com/cosmos/gogoproto/jsonpb" + "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/baseapp" baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil" "github.com/cosmos/cosmos-sdk/client" @@ -1946,6 +1948,7 @@ func TestQuery(t *testing.T) { func TestBaseApp_PrepareProposal(t *testing.T) { anteKey := []byte("ante-key") + mempool := mempool.NewNonceMempool() anteOpt := func(bapp *baseapp.BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) } @@ -1963,7 +1966,7 @@ func TestBaseApp_PrepareProposal(t *testing.T) { //} //app := appBuilder.Build(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), testCtx.DB, nil, anteOpt) - app := setupBaseApp(t, anteOpt) + app := setupBaseApp(t, anteOpt, baseapp.SetMempool(mempool)) registry := codectypes.NewInterfaceRegistry() cdc = codec.NewProtoCodec(registry) baseapptestutil.RegisterInterfaces(cdc.InterfaceRegistry()) diff --git a/types/mempool/mempool.go b/types/mempool/mempool.go index aa2e218bf2a6..b2aa54a86762 100644 --- a/types/mempool/mempool.go +++ b/types/mempool/mempool.go @@ -34,5 +34,3 @@ type Iterator interface { } var ErrTxNotFound = errors.New("tx not found in mempool") - -type Factory func() Mempool diff --git a/types/mempool/nonce.go b/types/mempool/nonce.go index 4d9a1bf4d655..a8a2f0413c5e 100644 --- a/types/mempool/nonce.go +++ b/types/mempool/nonce.go @@ -57,6 +57,7 @@ func txKeyLessNonce(a, b any) int { return huandu.String.Compare(keyB.sender, keyA.sender) } +// NewNonceMempool creates a new mempool that prioritizes transactions by nonce, the lowest first. func NewNonceMempool() Mempool { sp := &nonceMempool{ txQueue: huandu.New(huandu.LessThanFunc(txKeyLessNonce)), @@ -84,8 +85,8 @@ func (sp nonceMempool) Insert(_ sdk.Context, tx sdk.Tx) error { return nil } -// Select returns txs from the mempool with the lowest nonce globally first. A sender's txs will always be returned -// in nonce order. +// Select returns an iterator ordering transactions the mempool with the lowest nonce globally first. A sender's txs +// will always be returned in nonce order. func (sp nonceMempool) Select(_ sdk.Context, _ [][]byte) Iterator { currentTx := sp.txQueue.Front() if currentTx == nil { From 580c8b336f91f7bf9e91699ac9177bdbd0e9f891 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Wed, 2 Nov 2022 11:24:57 -0600 Subject: [PATCH 153/196] remove prints --- baseapp/baseapp.go | 1 - baseapp/deliver_tx_test.go | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 9a645e0ec53c..f1ca20f5af3f 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -696,7 +696,6 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re } if mode == runTxModeCheck { - fmt.Println("inserting tx:", tx.GetMsgs()) err = app.mempool.Insert(ctx, tx) if err != nil { return gInfo, nil, anteEvents, priority, err diff --git a/baseapp/deliver_tx_test.go b/baseapp/deliver_tx_test.go index be54e088c2d6..349ae51e8b9f 100644 --- a/baseapp/deliver_tx_test.go +++ b/baseapp/deliver_tx_test.go @@ -2007,12 +2007,25 @@ func TestBaseApp_PrepareProposal(t *testing.T) { } app.CheckTx(reqCheckTx) + badTx := newTxCounter(txConfig, 1, 1) + + _, err = txConfig.TxEncoder()(tx) + require.NoError(t, err) + + //checkTx := abci.RequestCheckTx{ + // Tx: txBytes, + // Type: abci.CheckTxType_New, + //} + //app.CheckTx(reqCheckTx) + //badTx = setFailOnAnte(txConfig, badTx, true) + err = mempool.Insert(sdk.Context{}, badTx) + require.NoError(t, err) reqPreparePropossal := abci.RequestPrepareProposal{ MaxTxBytes: 1000, } resPreparePropossal := app.PrepareProposal(reqPreparePropossal) - assert.Equal(t, len(txBytes), len(resPreparePropossal.Txs)) + assert.Equal(t, 1, len(resPreparePropossal.Txs)) res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) require.Empty(t, res.Events) @@ -2094,6 +2107,7 @@ func setFailOnAnte(cfg client.TxConfig, tx signing.Tx, failOnAnte bool) signing. vals.Set("failOnAnte", strconv.FormatBool(failOnAnte)) memo = vals.Encode() builder.SetMemo(memo) + setTxSignature(builder, 1) return builder.GetTx() } From 3c8640e731b10e6d7ecfd5175c1f94473f62791c Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Wed, 2 Nov 2022 11:26:28 -0600 Subject: [PATCH 154/196] fixed comment --- baseapp/abci.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 4062c73f9990..a192a5ee2616 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -250,14 +250,11 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // work in a block before proposing it. // // Transactions can be modified, removed, or added by the application. Since the -// application maintains it's own local mempool, it will ignore the transactions +// application maintains its own local mempool, it will ignore the transactions // provided to it in RequestPrepareProposal. Instead, it will determine which // transactions to return based on the mempool's semantics and the MaxTxBytes // provided by the client's request. // -// Note, there is no need to execute the transactions for validity as they have -// already passed CheckTx. -// // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { From d1a1cf8d2312f3faae0e6fc16a0a1170ccdc1595 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 2 Nov 2022 14:31:49 -0500 Subject: [PATCH 155/196] add tests exercising multiple signers with real messages --- types/mempool/mempool_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/types/mempool/mempool_test.go b/types/mempool/mempool_test.go index c564e17838e3..44a9103d524d 100644 --- a/types/mempool/mempool_test.go +++ b/types/mempool/mempool_test.go @@ -13,9 +13,12 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/mempool" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" ) // testPubKey is a dummy implementation of PubKey used for testing. @@ -210,3 +213,28 @@ func (s *MempoolTestSuite) SetupTest() { func TestMempoolTestSuite(t *testing.T) { suite.Run(t, new(MempoolTestSuite)) } + +func (s *MempoolTestSuite) TestSampleTxs() { + ctxt := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger()) + t := s.T() + s.resetMempool() + mp := s.mempool + delegatorTx, err := unmarshalTx(msgWithdrawDelegatorReward) + + require.NoError(t, err) + require.NoError(t, mp.Insert(ctxt, delegatorTx)) + require.Equal(t, 1, mp.CountTx()) + + proposalTx, err := unmarshalTx(msgMultiSigMsgSubmitProposal) + require.NoError(t, err) + require.NoError(t, mp.Insert(ctxt, proposalTx)) + require.Equal(t, 2, mp.CountTx()) +} + +func unmarshalTx(txBytes []byte) (sdk.Tx, error) { + cfg := moduletestutil.MakeTestEncodingConfig(distribution.AppModuleBasic{}, gov.AppModuleBasic{}) + return cfg.TxConfig.TxJSONDecoder()(txBytes) +} + +var msgWithdrawDelegatorReward = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1lzhlnpahvznwfv4jmay2tgaha5kmz5qxerarrl\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1sjllsnramtg3ewxqwwrwjxfgc4n4ef9u2lcnj0\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper196ax4vc0lwpxndu9dyhvca7jhxp70rmcvrj90c\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1k2d9ed9vgfuk2m58a2d80q9u6qljkh4vfaqjfq\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1vygmh344ldv9qefss9ek7ggsnxparljlmj56q5\"},{\"@type\":\"\\/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward\",\"delegator_address\":\"cosmos16w6g0whmw703t8h2m9qmq2fd9dwaw6fjszzjsw\",\"validator_address\":\"cosmosvaloper1ej2es5fjztqjcd4pwa0zyvaevtjd2y5wxxp9gd\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AmbXAy10a0SerEefTYQzqyGQdX5kiTEWJZ1PZKX1oswX\"},\"mode_info\":{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},\"sequence\":\"119\"}],\"fee\":{\"amount\":[{\"denom\":\"uatom\",\"amount\":\"15968\"}],\"gas_limit\":\"638717\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"ji+inUo4xGlN9piRQLdLCeJWa7irwnqzrMVPcmzJyG5y6NPc+ZuNaIc3uvk5NLDJytRB8AHX0GqNETR\\/Q8fz4Q==\"]}") +var msgMultiSigMsgSubmitProposal = []byte("{\"body\":{\"messages\":[{\"@type\":\"\\/cosmos.gov.v1beta1.MsgSubmitProposal\",\"content\":{\"@type\":\"\\/cosmos.distribution.v1beta1.CommunityPoolSpendProposal\",\"title\":\"ATOM \\ud83e\\udd1d Osmosis: Allocate Community Pool to ATOM Liquidity Incentives\",\"description\":\"ATOMs should be the base money of Cosmos, just like ETH is the base money of the entire Ethereum DeFi ecosystem. ATOM is currently well positioned to play this role among Cosmos assets because it has the highest market cap, most liquidity, largest brand, and many integrations with fiat onramps. ATOM is the gateway to Cosmos.\\n\\nIn the Cosmos Hub Port City vision, ATOMs are pitched as equity in the Cosmos Hub. However, this alone is insufficient to establish ATOM as the base currency of the Cosmos ecosystem as a whole. Instead, the ATOM community must work to actively promote the use of ATOMs throughout the Cosmos ecosystem, rather than passively relying on the Hub's reputation to create ATOM's value.\\n\\nIn order to cement the role of ATOMs in Cosmos DeFi, the Cosmos Hub should leverage its community pool to help align incentives with other protocols within the Cosmos ecosystem. We propose beginning this initiative by using the community pool ATOMs to incentivize deep ATOM base pair liquidity pools on the Osmosis Network.\\n\\nOsmosis is the first IBC-enabled DeFi application. Within its 3 weeks of existence, it has already 100x\\u2019d the number of IBC transactions ever created, demonstrating the power of IBC and the ability of the Cosmos SDK to bootstrap DeFi protocols with $100M+ TVL in a short period of time. Since its announcement Osmosis has helped bring renewed attention and interest to Cosmos from the crypto community at large and kickstarted the era of Cosmos DeFi.\\n\\nOsmosis has already helped in establishing ATOM as the Schelling Point of the Cosmos ecosystem. The genesis distribution of OSMO was primarily based on an airdrop to ATOM holders specifically, acknowledging the importance of ATOM to all future projects within the Cosmos. Furthermore, the Osmosis LP rewards currently incentivize ATOMs to be one of the main base pairs of the platform.\\n\\nOsmosis has the ability to incentivize AMM liquidity, a feature not available on any other IBC-enabled DEX. Osmosis already uses its own native OSMO liquidity rewards to incentivize ATOMs to be one of the main base pairs, leading to ~2.2 million ATOMs already providing liquidity on the platform.\\n\\nIn addition to these native OSMO LP Rewards, the platform also includes a feature called \\u201cexternal incentives\\u201d that allows anyone to permissionlessly add additional incentives in any token to the LPs of any AMM pools they wish. You can read more about this mechanism here: https:\\/\\/medium.com\\/osmosis\\/osmosis-liquidity-mining-101-2fa58d0e9d4d#f413 . Pools containing Cosmos assets such as AKT and XPRT are already planned to receive incentives from their respective community pools and\\/or foundations.\\n\\nWe propose the Cosmos Hub dedicate 100,000 ATOMs from its Community Pool to be allocated towards liquidity incentives on Osmosis over the next 3 months. This community fund proposal will transfer 100,000 ATOMs to a multisig group who will then allocate the ATOMs to bonded liquidity gauges on Osmosis on a biweekly basis, according to direction given by Cosmos Hub governance. For simplicity, we propose setting the liquidity incentives to initially point to Osmosis Pool #1, the ATOM\\/OSMO pool, which is the pool with by far the highest TVL and Volume. Cosmos Hub governance can then use Text Proposals to further direct the multisig members to reallocate incentives to new pools.\\n\\nThe multisig will consist of a 2\\/3 key holder set consisting of the following individuals whom have all agreed to participate in this process shall this proposal pass:\\n\\n- Zaki Manian\\n- Federico Kunze\\n- Marko Baricevic\\n\\nThis is one small step for the Hub, but one giant leap for ATOM-aligned.\\n\",\"recipient\":\"cosmos157n0d38vwn5dvh64rc39q3lyqez0a689g45rkc\",\"amount\":[{\"denom\":\"uatom\",\"amount\":\"100000000000\"}]},\"initial_deposit\":[{\"denom\":\"uatom\",\"amount\":\"64000000\"}],\"proposer\":\"cosmos1ey69r37gfxvxg62sh4r0ktpuc46pzjrmz29g45\"}],\"memo\":\"\",\"timeout_height\":\"0\",\"extension_options\":[],\"non_critical_extension_options\":[]},\"auth_info\":{\"signer_infos\":[{\"public_key\":{\"@type\":\"\\/cosmos.crypto.multisig.LegacyAminoPubKey\",\"threshold\":2,\"public_keys\":[{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AldOvgv8dU9ZZzuhGydQD5FYreLhfhoBgrDKi8ZSTbCQ\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AxUMR\\/GKoycWplR+2otzaQZ9zhHRQWJFt3h1bPg1ltha\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AlI9yVj2Aejow6bYl2nTRylfU+9LjQLEl3keq0sERx9+\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"A0UvHPcvCCaIoFY9Ygh0Pxq9SZTAWtduOyinit\\/8uo+Q\"},{\"@type\":\"\\/cosmos.crypto.secp256k1.PubKey\",\"key\":\"As7R9fDUnwsUVLDr1cxspp+cY9UfXfUf7i9\\/w+N0EzKA\"}]},\"mode_info\":{\"multi\":{\"bitarray\":{\"extra_bits_stored\":5,\"elems\":\"SA==\"},\"mode_infos\":[{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}},{\"single\":{\"mode\":\"SIGN_MODE_LEGACY_AMINO_JSON\"}}]}},\"sequence\":\"102\"}],\"fee\":{\"amount\":[],\"gas_limit\":\"10000000\",\"payer\":\"\",\"granter\":\"\"}},\"signatures\":[\"CkB\\/KKWTFntEWbg1A0vu7DCHffJ4x4db\\/EI8dIVzRFFW7iuZBzvq+jYBtrcTlVpEVfmCY3ggIMnWfbMbb1egIlYbCkAmDf6Eaj1NbyXY8JZZtYAX3Qj81ZuKZUBeLW1ZvH1XqAg9sl\\/sqpLMnsJzKfmqEXvhoMwu1YxcSzrY6CJfuYL6\"]}") From fc4bccb43f60512cfec2e6c182751145feebf682 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 2 Nov 2022 15:05:44 -0500 Subject: [PATCH 156/196] happy path test --- baseapp/baseapp.go | 3 ++- baseapp/deliver_tx_test.go | 24 +++++++++++++----------- x/auth/tx/builder.go | 4 +++- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index f1ca20f5af3f..c87b229bb578 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -858,9 +858,10 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) if err != nil { removeErr := app.mempool.Remove(memTx) - if removeErr != nil && err != mempool.ErrTxNotFound { + if removeErr != nil && !errors.Is(removeErr, mempool.ErrTxNotFound) { return nil, removeErr } + iterator = iterator.Next() continue } else if byteCount += txSize; byteCount <= req.MaxTxBytes { txsBytes = append(txsBytes, bz) diff --git a/baseapp/deliver_tx_test.go b/baseapp/deliver_tx_test.go index 349ae51e8b9f..e3c53f28315f 100644 --- a/baseapp/deliver_tx_test.go +++ b/baseapp/deliver_tx_test.go @@ -1948,7 +1948,7 @@ func TestQuery(t *testing.T) { func TestBaseApp_PrepareProposal(t *testing.T) { anteKey := []byte("ante-key") - mempool := mempool.NewNonceMempool() + pool := mempool.NewNonceMempool() anteOpt := func(bapp *baseapp.BaseApp) { bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) } @@ -1966,7 +1966,7 @@ func TestBaseApp_PrepareProposal(t *testing.T) { //} //app := appBuilder.Build(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), testCtx.DB, nil, anteOpt) - app := setupBaseApp(t, anteOpt, baseapp.SetMempool(mempool)) + app := setupBaseApp(t, anteOpt, baseapp.SetMempool(pool)) registry := codectypes.NewInterfaceRegistry() cdc = codec.NewProtoCodec(registry) baseapptestutil.RegisterInterfaces(cdc.InterfaceRegistry()) @@ -1981,7 +1981,6 @@ func TestBaseApp_PrepareProposal(t *testing.T) { header := tmproto.Header{Height: app.LastBlockHeight() + 1} // Begin Block ABCI call - //err = app.Init() require.NoError(t, err) app.InitChain(abci.RequestInitChain{ @@ -2018,21 +2017,18 @@ func TestBaseApp_PrepareProposal(t *testing.T) { //} //app.CheckTx(reqCheckTx) //badTx = setFailOnAnte(txConfig, badTx, true) - err = mempool.Insert(sdk.Context{}, badTx) + err = pool.Insert(sdk.Context{}, badTx) require.NoError(t, err) reqPreparePropossal := abci.RequestPrepareProposal{ MaxTxBytes: 1000, } resPreparePropossal := app.PrepareProposal(reqPreparePropossal) - assert.Equal(t, 1, len(resPreparePropossal.Txs)) + assert.Equal(t, 2, len(resPreparePropossal.Txs)) res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) - require.Empty(t, res.Events) - require.False(t, res.IsOK(), fmt.Sprintf("%v", res)) - - fmt.Println(res) - + require.NotEmpty(t, res.Events) + require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) } func getCheckStateCtx(app *baseapp.BaseApp) sdk.Context { @@ -2307,5 +2303,11 @@ func (ps paramStore) Get(ctx sdk.Context) (*tmproto.ConsensusParams, error) { func setTxSignature(builder client.TxBuilder, nonce uint64) { privKey := secp256k1.GenPrivKeyFromSecret([]byte("test")) pubKey := privKey.PubKey() - builder.SetSignatures(signingtypes.SignatureV2{PubKey: pubKey, Sequence: nonce}) + err := builder.SetSignatures( + signingtypes.SignatureV2{ + PubKey: pubKey, Sequence: nonce, Data: &signingtypes.SingleSignatureData{}, + }) + if err != nil { + panic(err) + } } diff --git a/x/auth/tx/builder.go b/x/auth/tx/builder.go index 479829cae685..2bee58151cff 100644 --- a/x/auth/tx/builder.go +++ b/x/auth/tx/builder.go @@ -189,10 +189,12 @@ func (w *wrapper) GetSignaturesV2() ([]signing.SignatureV2, error) { if err != nil { return nil, err } + // sequence number is functionally a transaction nonce and referred to as such in the SDK + nonce := si.GetSequence() res[i] = signing.SignatureV2{ PubKey: pubKeys[i], Data: sigData, - Sequence: si.GetSequence(), + Sequence: nonce, } } From 10cd7474a1e1f7a97f108a6c37df6bbe62d14bb3 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 2 Nov 2022 15:23:01 -0500 Subject: [PATCH 157/196] move to test suite --- baseapp/abci_v1_test.go | 116 +++++++++++++++++++++++++++++++++++++ baseapp/deliver_tx_test.go | 86 --------------------------- 2 files changed, 116 insertions(+), 86 deletions(-) create mode 100644 baseapp/abci_v1_test.go diff --git a/baseapp/abci_v1_test.go b/baseapp/abci_v1_test.go new file mode 100644 index 000000000000..c3ec6ee1079c --- /dev/null +++ b/baseapp/abci_v1_test.go @@ -0,0 +1,116 @@ +package baseapp_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "cosmossdk.io/depinject" + "github.com/cosmos/cosmos-sdk/baseapp" + baseapptestutil "github.com/cosmos/cosmos-sdk/baseapp/testutil" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/mempool" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +type ABCIv1TestSuite struct { + suite.Suite + baseApp *baseapp.BaseApp + mempool mempool.Mempool + txConfig client.TxConfig +} + +func TestABCIv1TestSuite(t *testing.T) { + suite.Run(t, new(ABCIv1TestSuite)) +} + +func (s *ABCIv1TestSuite) SetupTest() { + t := s.T() + anteKey := []byte("ante-key") + pool := mempool.NewNonceMempool() + anteOpt := func(bapp *baseapp.BaseApp) { + bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) + } + + var ( + appBuilder *runtime.AppBuilder + cdc codec.ProtoCodecMarshaler + registry codectypes.InterfaceRegistry + ) + err := depinject.Inject(makeMinimalConfig(), &appBuilder, &cdc, ®istry) + require.NoError(t, err) + + app := setupBaseApp(t, anteOpt, baseapp.SetMempool(pool)) + baseapptestutil.RegisterInterfaces(registry) + app.SetMsgServiceRouter(baseapp.NewMsgServiceRouter()) + app.SetInterfaceRegistry(registry) + + baseapptestutil.RegisterKeyValueServer(app.MsgServiceRouter(), MsgKeyValueImpl{}) + deliverKey := []byte("deliver-key") + baseapptestutil.RegisterCounterServer(app.MsgServiceRouter(), CounterServerImpl{t, capKey1, deliverKey}) + header := tmproto.Header{Height: app.LastBlockHeight() + 1} + + app.InitChain(abci.RequestInitChain{ + ConsensusParams: &tmproto.ConsensusParams{}, + }) + + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + + // patch in TxConfig insted of using an output from x/auth/tx + txConfig := authtx.NewTxConfig(cdc, authtx.DefaultSignModes) + + app.SetTxDecoder(txConfig.TxDecoder()) + app.SetTxEncoder(txConfig.TxEncoder()) + + s.baseApp = app + s.mempool = pool + s.txConfig = txConfig +} + +func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_HappyPath() { + txConfig := s.txConfig + t := s.T() + + tx := newTxCounter(txConfig, 0, 0) + //tx = setFailOnAnte(txConfig, tx, true) + txBytes, err := txConfig.TxEncoder()(tx) + require.NoError(t, err) + + reqCheckTx := abci.RequestCheckTx{ + Tx: txBytes, + Type: abci.CheckTxType_New, + } + s.baseApp.CheckTx(reqCheckTx) + + tx2 := newTxCounter(txConfig, 1, 1) + + _, err = txConfig.TxEncoder()(tx) + require.NoError(t, err) + + //checkTx := abci.RequestCheckTx{ + // Tx: txBytes, + // Type: abci.CheckTxType_New, + //} + //app.CheckTx(reqCheckTx) + //tx2 = setFailOnAnte(txConfig, tx2, true) + err = s.mempool.Insert(sdk.Context{}, tx2) + require.NoError(t, err) + reqPreparePropossal := abci.RequestPrepareProposal{ + MaxTxBytes: 1000, + } + resPreparePropossal := s.baseApp.PrepareProposal(reqPreparePropossal) + + require.Equal(t, 2, len(resPreparePropossal.Txs)) + res := s.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) + + require.NotEmpty(t, res.Events) + require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) +} diff --git a/baseapp/deliver_tx_test.go b/baseapp/deliver_tx_test.go index e3c53f28315f..79be7771981d 100644 --- a/baseapp/deliver_tx_test.go +++ b/baseapp/deliver_tx_test.go @@ -18,7 +18,6 @@ import ( "unsafe" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - "github.com/cosmos/cosmos-sdk/types/mempool" signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/stretchr/testify/assert" @@ -1946,91 +1945,6 @@ func TestQuery(t *testing.T) { require.Equal(t, value, res.Value) } -func TestBaseApp_PrepareProposal(t *testing.T) { - anteKey := []byte("ante-key") - pool := mempool.NewNonceMempool() - anteOpt := func(bapp *baseapp.BaseApp) { - bapp.SetAnteHandler(anteHandlerTxTest(t, capKey1, anteKey)) - } - - var ( - appBuilder *runtime.AppBuilder - cdc codec.ProtoCodecMarshaler - ) - err := depinject.Inject(makeMinimalConfig(), &appBuilder, &cdc) - require.NoError(t, err) - - //testCtx := testutil.DefaultContextWithDB(t, capKey1, sdk.NewTransientStoreKey("transient_test")) - //csmOpt := func(bapp *baseapp.BaseApp) { - // bapp.SetCMS(testCtx.CMS) - //} - //app := appBuilder.Build(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), testCtx.DB, nil, anteOpt) - - app := setupBaseApp(t, anteOpt, baseapp.SetMempool(pool)) - registry := codectypes.NewInterfaceRegistry() - cdc = codec.NewProtoCodec(registry) - baseapptestutil.RegisterInterfaces(cdc.InterfaceRegistry()) - app.SetMsgServiceRouter(baseapp.NewMsgServiceRouter()) - app.SetInterfaceRegistry(registry) - - baseapptestutil.RegisterKeyValueServer(app.MsgServiceRouter(), MsgKeyValueImpl{}) - //setParamStore - //baseapptestutil.RegisterInterfaces(cdc.InterfaceRegistry()) - deliverKey := []byte("deliver-key") - baseapptestutil.RegisterCounterServer(app.MsgServiceRouter(), CounterServerImpl{t, capKey1, deliverKey}) - header := tmproto.Header{Height: app.LastBlockHeight() + 1} - // Begin Block ABCI call - - require.NoError(t, err) - - app.InitChain(abci.RequestInitChain{ - ConsensusParams: &tmproto.ConsensusParams{}, - }) - - app.BeginBlock(abci.RequestBeginBlock{Header: header}) - - // patch in TxConfig insted of using an output from x/auth/tx - txConfig := authtx.NewTxConfig(cdc, authtx.DefaultSignModes) - - app.SetTxDecoder(txConfig.TxDecoder()) - app.SetTxEncoder(txConfig.TxEncoder()) - - tx := newTxCounter(txConfig, 0, 0) - //tx = setFailOnAnte(txConfig, tx, true) - txBytes, err := txConfig.TxEncoder()(tx) - require.NoError(t, err) - - reqCheckTx := abci.RequestCheckTx{ - Tx: txBytes, - Type: abci.CheckTxType_New, - } - app.CheckTx(reqCheckTx) - - badTx := newTxCounter(txConfig, 1, 1) - - _, err = txConfig.TxEncoder()(tx) - require.NoError(t, err) - - //checkTx := abci.RequestCheckTx{ - // Tx: txBytes, - // Type: abci.CheckTxType_New, - //} - //app.CheckTx(reqCheckTx) - //badTx = setFailOnAnte(txConfig, badTx, true) - err = pool.Insert(sdk.Context{}, badTx) - require.NoError(t, err) - reqPreparePropossal := abci.RequestPrepareProposal{ - MaxTxBytes: 1000, - } - resPreparePropossal := app.PrepareProposal(reqPreparePropossal) - - assert.Equal(t, 2, len(resPreparePropossal.Txs)) - res := app.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) - - require.NotEmpty(t, res.Events) - require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) -} - func getCheckStateCtx(app *baseapp.BaseApp) sdk.Context { v := reflect.ValueOf(app).Elem() f := v.FieldByName("checkState") From bfe7faf18fc9b92172fbf7e0bb700fbc99a41ef9 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 2 Nov 2022 15:39:13 -0500 Subject: [PATCH 158/196] add failing antehandler test --- baseapp/abci_v1_test.go | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/baseapp/abci_v1_test.go b/baseapp/abci_v1_test.go index c3ec6ee1079c..c642d578b1f9 100644 --- a/baseapp/abci_v1_test.go +++ b/baseapp/abci_v1_test.go @@ -80,7 +80,6 @@ func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_HappyPath() { t := s.T() tx := newTxCounter(txConfig, 0, 0) - //tx = setFailOnAnte(txConfig, tx, true) txBytes, err := txConfig.TxEncoder()(tx) require.NoError(t, err) @@ -95,12 +94,6 @@ func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_HappyPath() { _, err = txConfig.TxEncoder()(tx) require.NoError(t, err) - //checkTx := abci.RequestCheckTx{ - // Tx: txBytes, - // Type: abci.CheckTxType_New, - //} - //app.CheckTx(reqCheckTx) - //tx2 = setFailOnAnte(txConfig, tx2, true) err = s.mempool.Insert(sdk.Context{}, tx2) require.NoError(t, err) reqPreparePropossal := abci.RequestPrepareProposal{ @@ -114,3 +107,28 @@ func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_HappyPath() { require.NotEmpty(t, res.Events) require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) } + +func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_Failures() { + tx := newTxCounter(s.txConfig, 0, 0) + txBytes, err := s.txConfig.TxEncoder()(tx) + s.NoError(err) + + reqCheckTx := abci.RequestCheckTx{ + Tx: txBytes, + Type: abci.CheckTxType_New, + } + checkTxRes := s.baseApp.CheckTx(reqCheckTx) + s.True(checkTxRes.IsOK()) + + failTx := newTxCounter(s.txConfig, 1, 1) + failTx = setFailOnAnte(s.txConfig, failTx, true) + err = s.mempool.Insert(sdk.Context{}, failTx) + s.NoError(err) + s.Equal(2, s.mempool.CountTx()) + + req := abci.RequestPrepareProposal{ + MaxTxBytes: 1000, + } + res := s.baseApp.PrepareProposal(req) + s.Equal(1, len(res.Txs)) +} From de876ca5d7e4b7d9b669221f17124b4d3b75ff6b Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Wed, 2 Nov 2022 16:53:57 -0600 Subject: [PATCH 159/196] test max byte sizae --- baseapp/abci_v1_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/baseapp/abci_v1_test.go b/baseapp/abci_v1_test.go index c642d578b1f9..5b0a7f000f8f 100644 --- a/baseapp/abci_v1_test.go +++ b/baseapp/abci_v1_test.go @@ -108,6 +108,24 @@ func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_HappyPath() { require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) } +func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_ReachedMaxBytes() { + txConfig := s.txConfig + t := s.T() + + for i := 0; i < 100; i++ { + tx2 := newTxCounter(txConfig, int64(i), int64(i)) + err := s.mempool.Insert(sdk.Context{}, tx2) + require.NoError(t, err) + } + + reqPreparePropossal := abci.RequestPrepareProposal{ + MaxTxBytes: 1500, + } + resPreparePropossal := s.baseApp.PrepareProposal(reqPreparePropossal) + + require.Equal(t, 10, len(resPreparePropossal.Txs)) +} + func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_Failures() { tx := newTxCounter(s.txConfig, 0, 0) txBytes, err := s.txConfig.TxEncoder()(tx) From 7dc06842a7573b3c1af7724140c9cf3b35514c6a Mon Sep 17 00:00:00 2001 From: Jeancarlo Barrios Date: Thu, 3 Nov 2022 08:47:25 -0600 Subject: [PATCH 160/196] Update baseapp/abci.go Co-authored-by: Aleksandr Bezobchuk --- baseapp/abci.go | 1 - 1 file changed, 1 deletion(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index a192a5ee2616..cd756d818e33 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -185,7 +185,6 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg WithHeaderHash(req.Hash). WithConsensusParams(app.GetConsensusParams(app.deliverState.ctx)) - // we if app.checkState != nil { app.checkState.ctx = app.checkState.ctx. WithBlockGasMeter(gasMeter). From 72a5c3ce42c22dd8177bba511117091e108e1c6a Mon Sep 17 00:00:00 2001 From: Jeancarlo Barrios Date: Thu, 3 Nov 2022 09:01:58 -0600 Subject: [PATCH 161/196] Update server/types/app.go Co-authored-by: Aleksandr Bezobchuk --- server/types/app.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/types/app.go b/server/types/app.go index 6c4673a626e3..6cd64051dd07 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -13,7 +13,6 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/cosmos/gogoproto/grpc" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server/api" "github.com/cosmos/cosmos-sdk/server/config" From 201d52e87dd3217dbe0fb9aa51c6f2f00986b580 Mon Sep 17 00:00:00 2001 From: Jeancarlo Barrios Date: Thu, 3 Nov 2022 10:57:51 -0600 Subject: [PATCH 162/196] Update baseapp/baseapp.go Co-authored-by: Aleksandr Bezobchuk --- baseapp/baseapp.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index c87b229bb578..ba8fd6913ebb 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -849,12 +849,13 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, for iterator != nil { memTx := iterator.Tx() - bz, encErr := app.txEncoder(memTx) - txSize := int64(len(bz)) - if encErr != nil { - return nil, encErr + bz, err := app.txEncoder(memTx) + if err != nil { + return nil, err } + txSize := int64(len(bz)) + _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) if err != nil { removeErr := app.mempool.Remove(memTx) From dea715f33026824d6aa872a2af3f796011af7556 Mon Sep 17 00:00:00 2001 From: Jeancarlo Barrios Date: Thu, 3 Nov 2022 10:58:12 -0600 Subject: [PATCH 163/196] Update baseapp/baseapp.go Co-authored-by: Aleksandr Bezobchuk --- baseapp/baseapp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index ba8fd6913ebb..93463829b5ee 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -858,9 +858,9 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) if err != nil { - removeErr := app.mempool.Remove(memTx) - if removeErr != nil && !errors.Is(removeErr, mempool.ErrTxNotFound) { - return nil, removeErr + err := app.mempool.Remove(memTx) + if err != nil && !errors.Is(err, mempool.ErrTxNotFound) { + return nil, err } iterator = iterator.Next() continue From 0af189923bf01691191fc166e3f2dc2b66d92ab3 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 3 Nov 2022 10:59:52 -0600 Subject: [PATCH 164/196] remove interface implemantation that is no longer needed --- server/mock/tx.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/server/mock/tx.go b/server/mock/tx.go index f40d697b8edf..6b4c499c8380 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -109,10 +109,6 @@ func (tx *kvstoreTx) GetSigners() []sdk.AccAddress { return nil } -func (tx *kvstoreTx) Size() int64 { - return int64(len(tx.bytes)) -} - func (tx *kvstoreTx) GetPubKeys() ([]cryptotypes.PubKey, error) { panic("GetPubKeys not implemented") } // takes raw transaction bytes and decodes them into an sdk.Tx. An sdk.Tx has From 3134207652447a116a2e73d8e493009917d7cdba Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 3 Nov 2022 11:17:19 -0600 Subject: [PATCH 165/196] fix --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 93463829b5ee..6dc842190e8f 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -856,7 +856,7 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, txSize := int64(len(bz)) - _, _, _, _, err := app.runTx(runTxPrepareProposal, bz) + _, _, _, _, err = app.runTx(runTxPrepareProposal, bz) if err != nil { err := app.mempool.Remove(memTx) if err != nil && !errors.Is(err, mempool.ErrTxNotFound) { From 9e3048f9452d6bc308c7b9767b9d98700a3333eb Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 3 Nov 2022 12:53:07 -0500 Subject: [PATCH 166/196] Allow applications to define process proposal --- baseapp/abci.go | 11 +++++---- baseapp/abci_v1_test.go | 29 ++++++++++++++++------ baseapp/baseapp.go | 53 +++++++++++++++++++++++------------------ baseapp/options.go | 9 +++++++ types/abci.go | 3 +++ 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index cd756d818e33..08f264650969 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -277,11 +277,14 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { - err := app.processProposal(req) - if err != nil { - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} + if app.processProposal == nil { + panic("app.ProcessProposal is not set") } - return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} + + ctx := app.prepareProposalState.ctx.WithVoteInfos(app.voteInfos) + ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx)) + + return app.processProposal(ctx, req) } // CheckTx implements the ABCI interface and executes a tx in CheckTx mode. In diff --git a/baseapp/abci_v1_test.go b/baseapp/abci_v1_test.go index 5b0a7f000f8f..779265525cd3 100644 --- a/baseapp/abci_v1_test.go +++ b/baseapp/abci_v1_test.go @@ -1,7 +1,6 @@ package baseapp_test import ( - "fmt" "testing" "github.com/stretchr/testify/require" @@ -75,11 +74,11 @@ func (s *ABCIv1TestSuite) SetupTest() { s.txConfig = txConfig } -func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_HappyPath() { +func (s *ABCIv1TestSuite) TestABCIv1_HappyPath() { txConfig := s.txConfig t := s.T() - tx := newTxCounter(txConfig, 0, 0) + tx := newTxCounter(txConfig, 0, 1) txBytes, err := txConfig.TxEncoder()(tx) require.NoError(t, err) @@ -91,7 +90,7 @@ func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_HappyPath() { tx2 := newTxCounter(txConfig, 1, 1) - _, err = txConfig.TxEncoder()(tx) + tx2Bytes, err := txConfig.TxEncoder()(tx2) require.NoError(t, err) err = s.mempool.Insert(sdk.Context{}, tx2) @@ -102,10 +101,26 @@ func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_HappyPath() { resPreparePropossal := s.baseApp.PrepareProposal(reqPreparePropossal) require.Equal(t, 2, len(resPreparePropossal.Txs)) - res := s.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) - require.NotEmpty(t, res.Events) - require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) + var reqProposalTxBytes [2][]byte + reqProposalTxBytes[0] = txBytes + reqProposalTxBytes[1] = tx2Bytes + reqProcessProposal := abci.RequestProcessProposal{ + Txs: reqProposalTxBytes[:], + } + + s.baseApp.SetProcessProposal(nil) + require.Panics(t, func() { s.baseApp.ProcessProposal(reqProcessProposal) }) + s.baseApp.SetProcessProposal(s.baseApp.DefaultProcessProposal()) + + resProcessProposal := s.baseApp.ProcessProposal(reqProcessProposal) + require.Equal(t, abci.ResponseProcessProposal_ACCEPT, resProcessProposal.Status) + + //res := s.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) + //require.Equal(t, 1, s.mempool.CountTx()) + + //require.NotEmpty(t, res.Events) + //require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) } func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_ReachedMaxBytes() { diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index c87b229bb578..109659d29122 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -60,17 +60,18 @@ type BaseApp struct { //nolint: maligned msgServiceRouter *MsgServiceRouter // router for redirecting Msg service messages interfaceRegistry codectypes.InterfaceRegistry txDecoder sdk.TxDecoder // unmarshal []byte into sdk.Tx - txEncoder sdk.TxEncoder - - mempool mempool.Mempool // application side mempool - anteHandler sdk.AnteHandler // ante handler for fee and auth - postHandler sdk.AnteHandler // post handler, optional, e.g. for tips - initChainer sdk.InitChainer // initialize state with validators and state blob - beginBlocker sdk.BeginBlocker // logic to run before any txs - endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes - addrPeerFilter sdk.PeerFilter // filter peers by address and port - idPeerFilter sdk.PeerFilter // filter peers by node ID - fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed. + txEncoder sdk.TxEncoder // marshal sdk.Tx into []byte + + mempool mempool.Mempool // application side mempool + anteHandler sdk.AnteHandler // ante handler for fee and auth + postHandler sdk.AnteHandler // post handler, optional, e.g. for tips + initChainer sdk.InitChainer // initialize state with validators and state blob + beginBlocker sdk.BeginBlocker // logic to run before any txs + processProposal sdk.ProcessProposalHandler // the handler which runs on ABCI ProcessProposal + endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes + addrPeerFilter sdk.PeerFilter // filter peers by address and port + idPeerFilter sdk.PeerFilter // filter peers by node ID + fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed. // manages snapshots, i.e. dumps of app state at certain intervals snapshotManager *snapshots.Manager @@ -173,6 +174,10 @@ func NewBaseApp( app.SetMempool(mempool.NewNonceMempool()) } + if app.processProposal == nil { + app.SetProcessProposal(app.DefaultProcessProposal()) + } + if app.interBlockCache != nil { app.cms.SetInterBlockCache(app.interBlockCache) } @@ -875,20 +880,22 @@ func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, return txsBytes, nil } -func (app *BaseApp) processProposal(req abci.RequestProcessProposal) error { - for _, txBytes := range req.Txs { - tx, err := app.txDecoder(txBytes) - if err != nil { - return err - } +func (app *BaseApp) DefaultProcessProposal() sdk.ProcessProposalHandler { + return func(ctx sdk.Context, req abci.RequestProcessProposal) abci.ResponseProcessProposal { + for _, txBytes := range req.Txs { + tx, err := app.txDecoder(txBytes) + if err != nil { + panic(err) + } - _, _, _, _, err = app.runTx(runTxProcessProposal, txBytes) - if err != nil { - err = app.mempool.Remove(tx) - if err != nil && err != mempool.ErrTxNotFound { - return err + _, _, _, _, err = app.runTx(runTxProcessProposal, txBytes) + if err != nil { + err = app.mempool.Remove(tx) + if err != nil && err != mempool.ErrTxNotFound { + panic(err) + } } } + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} } - return nil } diff --git a/baseapp/options.go b/baseapp/options.go index 9691c2301bf9..d655a0e756c8 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -86,6 +86,11 @@ func SetMempool(mempool mempool.Mempool) func(*BaseApp) { return func(app *BaseApp) { app.SetMempool(mempool) } } +// SetProcessProposal sets the ProcessProposal handler. +func SetProcessProposal(proposalHandler sdk.ProcessProposalHandler) func(*BaseApp) { + return func(app *BaseApp) { app.SetProcessProposal(proposalHandler) } +} + func (app *BaseApp) SetName(name string) { if app.sealed { panic("SetName() on sealed BaseApp") @@ -263,3 +268,7 @@ func (app *BaseApp) SetQueryMultiStore(ms sdk.MultiStore) { func (app *BaseApp) SetMempool(mempool mempool.Mempool) { app.mempool = mempool } + +func (app *BaseApp) SetProcessProposal(handler sdk.ProcessProposalHandler) { + app.processProposal = handler +} diff --git a/types/abci.go b/types/abci.go index 8f71362eda6d..e08cbe449f5d 100644 --- a/types/abci.go +++ b/types/abci.go @@ -19,3 +19,6 @@ type EndBlocker func(ctx Context, req abci.RequestEndBlock) abci.ResponseEndBloc // PeerFilter responds to p2p filtering queries from Tendermint type PeerFilter func(info string) abci.ResponseQuery + +// ProcessProposalHandler defines a function type alias for processing a proposer +type ProcessProposalHandler func(ctx Context, proposal abci.RequestProcessProposal) abci.ResponseProcessProposal From 678b555b943fa2a2b3c762a98febfbd7e836b432 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 3 Nov 2022 12:55:16 -0500 Subject: [PATCH 167/196] wrapping method --- baseapp/abci.go | 40 ++++++++++++++++++++++++++++++++++++---- baseapp/baseapp.go | 39 --------------------------------------- 2 files changed, 36 insertions(+), 43 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 08f264650969..d8c2110c71fa 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -22,6 +22,7 @@ import ( "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/mempool" ) // Supported ABCI Query prefixes @@ -257,11 +258,42 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal { - txs, err := app.prepareProposal(req) - if err != nil { - panic(err) + var ( + txsBytes [][]byte + byteCount int64 + ) + + ctx := app.getContextForTx(runTxPrepareProposal, []byte{}) + iterator := app.mempool.Select(ctx, req.Txs) + + for iterator != nil { + memTx := iterator.Tx() + + bz, err := app.txEncoder(memTx) + if err != nil { + panic(err) + } + + txSize := int64(len(bz)) + + _, _, _, _, err = app.runTx(runTxPrepareProposal, bz) + if err != nil { + err := app.mempool.Remove(memTx) + if err != nil && !errors.Is(err, mempool.ErrTxNotFound) { + panic(err) + } + iterator = iterator.Next() + continue + } else if byteCount += txSize; byteCount <= req.MaxTxBytes { + txsBytes = append(txsBytes, bz) + } else { + break + } + + iterator = iterator.Next() } - return abci.ResponsePrepareProposal{Txs: txs} + + return abci.ResponsePrepareProposal{Txs: txsBytes} } // ProcessProposal implements the ProcessProposal ABCI method and returns a diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 44d16ce6ddf0..bb00c1635fbb 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -842,45 +842,6 @@ func createEvents(msg sdk.Msg) sdk.Events { return sdk.Events{msgEvent} } -func (app *BaseApp) prepareProposal(req abci.RequestPrepareProposal) ([][]byte, error) { - var ( - txsBytes [][]byte - byteCount int64 - ) - - ctx := app.getContextForTx(runTxPrepareProposal, []byte{}) - iterator := app.mempool.Select(ctx, req.Txs) - - for iterator != nil { - memTx := iterator.Tx() - - bz, err := app.txEncoder(memTx) - if err != nil { - return nil, err - } - - txSize := int64(len(bz)) - - _, _, _, _, err = app.runTx(runTxPrepareProposal, bz) - if err != nil { - err := app.mempool.Remove(memTx) - if err != nil && !errors.Is(err, mempool.ErrTxNotFound) { - return nil, err - } - iterator = iterator.Next() - continue - } else if byteCount += txSize; byteCount <= req.MaxTxBytes { - txsBytes = append(txsBytes, bz) - } else { - break - } - - iterator = iterator.Next() - } - - return txsBytes, nil -} - func (app *BaseApp) DefaultProcessProposal() sdk.ProcessProposalHandler { return func(ctx sdk.Context, req abci.RequestProcessProposal) abci.ResponseProcessProposal { for _, txBytes := range req.Txs { From a75e4ef95771f8df39f9490f78f6be9cdf02b1aa Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 3 Nov 2022 12:11:34 -0600 Subject: [PATCH 168/196] t --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 6dc842190e8f..1ce746c36dba 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -760,7 +760,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { // skip actual execution for (Re)CheckTx mode - if mode == runTxModeCheck || mode == runTxModeReCheck || mode == runTxPrepareProposal || mode == runTxProcessProposal { + if mode != runTxModeDeliver { break } From 215d2a6116d7e852054998f873276897c43e5013 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 3 Nov 2022 12:13:40 -0600 Subject: [PATCH 169/196] t --- baseapp/abci_v1_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/baseapp/abci_v1_test.go b/baseapp/abci_v1_test.go index 5b0a7f000f8f..3f57eba41cbd 100644 --- a/baseapp/abci_v1_test.go +++ b/baseapp/abci_v1_test.go @@ -26,6 +26,7 @@ type ABCIv1TestSuite struct { baseApp *baseapp.BaseApp mempool mempool.Mempool txConfig client.TxConfig + cdc codec.ProtoCodecMarshaler } func TestABCIv1TestSuite(t *testing.T) { @@ -73,6 +74,7 @@ func (s *ABCIv1TestSuite) SetupTest() { s.baseApp = app s.mempool = pool s.txConfig = txConfig + s.cdc = cdc } func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_HappyPath() { @@ -126,6 +128,23 @@ func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_ReachedMaxBytes() { require.Equal(t, 10, len(resPreparePropossal.Txs)) } +func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_BadEncoding() { + txConfig := authtx.NewTxConfig(s.cdc, authtx.DefaultSignModes) + + t := s.T() + + tx := newTxCounter(txConfig, 0, 0) + err := s.mempool.Insert(sdk.Context{}, tx) + require.NoError(t, err) + + reqPrepareProposal := abci.RequestPrepareProposal{ + MaxTxBytes: 1000, + } + resPrepareProposal := s.baseApp.PrepareProposal(reqPrepareProposal) + + require.Equal(t, 1, len(resPrepareProposal.Txs)) +} + func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_Failures() { tx := newTxCounter(s.txConfig, 0, 0) txBytes, err := s.txConfig.TxEncoder()(tx) From 88f574dd13d328b128652e3156b24a83f749012f Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 3 Nov 2022 14:10:38 -0500 Subject: [PATCH 170/196] use a no-op message server --- baseapp/abci_v1_test.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/baseapp/abci_v1_test.go b/baseapp/abci_v1_test.go index 0d89cbecbda9..83cb7c6a75b0 100644 --- a/baseapp/abci_v1_test.go +++ b/baseapp/abci_v1_test.go @@ -1,6 +1,8 @@ package baseapp_test import ( + "context" + "fmt" "testing" "github.com/stretchr/testify/require" @@ -20,6 +22,15 @@ import ( authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" ) +type NoopCounterServerImpl struct{} + +func (m NoopCounterServerImpl) IncrementCounter( + _ context.Context, + _ *baseapptestutil.MsgCounter, +) (*baseapptestutil.MsgCreateCounterResponse, error) { + return &baseapptestutil.MsgCreateCounterResponse{}, nil +} + type ABCIv1TestSuite struct { suite.Suite baseApp *baseapp.BaseApp @@ -54,8 +65,7 @@ func (s *ABCIv1TestSuite) SetupTest() { app.SetInterfaceRegistry(registry) baseapptestutil.RegisterKeyValueServer(app.MsgServiceRouter(), MsgKeyValueImpl{}) - deliverKey := []byte("deliver-key") - baseapptestutil.RegisterCounterServer(app.MsgServiceRouter(), CounterServerImpl{t, capKey1, deliverKey}) + baseapptestutil.RegisterCounterServer(app.MsgServiceRouter(), NoopCounterServerImpl{}) header := tmproto.Header{Height: app.LastBlockHeight() + 1} app.InitChain(abci.RequestInitChain{ @@ -118,11 +128,11 @@ func (s *ABCIv1TestSuite) TestABCIv1_HappyPath() { resProcessProposal := s.baseApp.ProcessProposal(reqProcessProposal) require.Equal(t, abci.ResponseProcessProposal_ACCEPT, resProcessProposal.Status) - //res := s.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) - //require.Equal(t, 1, s.mempool.CountTx()) + res := s.baseApp.DeliverTx(abci.RequestDeliverTx{Tx: txBytes}) + require.Equal(t, 1, s.mempool.CountTx()) - //require.NotEmpty(t, res.Events) - //require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) + require.NotEmpty(t, res.Events) + require.True(t, res.IsOK(), fmt.Sprintf("%v", res)) } func (s *ABCIv1TestSuite) TestABCIv1_PrepareProposal_ReachedMaxBytes() { From 7180e9464f6d9c2e373aacf40119b3ac6e1aad78 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Thu, 3 Nov 2022 14:15:23 -0500 Subject: [PATCH 171/196] clean up imports --- server/types/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/types/app.go b/server/types/app.go index 6cd64051dd07..8eb941c57b12 100644 --- a/server/types/app.go +++ b/server/types/app.go @@ -5,6 +5,7 @@ import ( "io" "time" + "github.com/cosmos/gogoproto/grpc" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" @@ -12,7 +13,6 @@ import ( tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" - "github.com/cosmos/gogoproto/grpc" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/server/api" "github.com/cosmos/cosmos-sdk/server/config" From 97a9c765d071893d2c0d4fe410f3e1cbdc6bf517 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 3 Nov 2022 16:01:20 -0600 Subject: [PATCH 172/196] removed unesesary state set --- baseapp/abci.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index d8c2110c71fa..0811f0aa7f21 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -191,16 +191,6 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg WithBlockGasMeter(gasMeter). WithHeaderHash(req.Hash) } - if app.prepareProposalState != nil { - app.prepareProposalState.ctx = app.prepareProposalState.ctx. - WithBlockGasMeter(gasMeter). - WithHeaderHash(req.Hash) - } - if app.processProposalState != nil { - app.processProposalState.ctx = app.processProposalState.ctx. - WithBlockGasMeter(gasMeter). - WithHeaderHash(req.Hash) - } if app.beginBlocker != nil { res = app.beginBlocker(app.deliverState.ctx, req) From 6e5ff1ca3f08111703c2b8df451dcc0bb7b58e53 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 3 Nov 2022 16:05:42 -0600 Subject: [PATCH 173/196] t --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index f58c4bd0566d..da9c45d13808 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -765,7 +765,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { // skip actual execution for (Re)CheckTx mode - if mode != runTxModeDeliver { + if mode != runTxModeDeliver && mode != runTxModeSimulate { break } From e1d5ed426c334cd7cc36c0d3082e6f21e045612d Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 3 Nov 2022 16:06:18 -0600 Subject: [PATCH 174/196] t --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index da9c45d13808..8f7b51424747 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -764,7 +764,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { - // skip actual execution for (Re)CheckTx mode + // skip actual execution for (Re)CheckTx Prepare and Processes mode if mode != runTxModeDeliver && mode != runTxModeSimulate { break } From 8d4e97b9aae77a4979d68ae4ec68f5009e0c1b95 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Thu, 3 Nov 2022 16:12:34 -0600 Subject: [PATCH 175/196] t --- baseapp/baseapp.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 8f7b51424747..1125ad4b7fdd 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -765,7 +765,10 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { // skip actual execution for (Re)CheckTx Prepare and Processes mode - if mode != runTxModeDeliver && mode != runTxModeSimulate { + // if mode != runTxModeDeliver && mode != runTxModeSimulate { + // break + //} + if mode == runTxModeCheck || mode == runTxModeReCheck || mode == runTxPrepareProposal || mode == runTxProcessProposal { break } From 99566ceba08445cf9f6625164319016dd63e7413 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 4 Nov 2022 10:42:23 -0500 Subject: [PATCH 176/196] Use correct state in ProcessProposal --- baseapp/abci.go | 2 +- baseapp/baseapp.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 0811f0aa7f21..5b74fff7a7cf 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -303,7 +303,7 @@ func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.Respon panic("app.ProcessProposal is not set") } - ctx := app.prepareProposalState.ctx.WithVoteInfos(app.voteInfos) + ctx := app.processProposalState.ctx.WithVoteInfos(app.voteInfos) ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx)) return app.processProposal(ctx, req) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 1125ad4b7fdd..a785195a6d82 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -765,9 +765,10 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { // skip actual execution for (Re)CheckTx Prepare and Processes mode - // if mode != runTxModeDeliver && mode != runTxModeSimulate { + //if mode != runTxModeDeliver && mode != runTxModeSimulate { // break //} + if mode == runTxModeCheck || mode == runTxModeReCheck || mode == runTxPrepareProposal || mode == runTxProcessProposal { break } From 16e98c90b755ca89e8d8a6abe9e3e94fde8f7546 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 4 Nov 2022 10:42:39 -0500 Subject: [PATCH 177/196] Revert "removed unesesary state set" This reverts commit 97a9c765d071893d2c0d4fe410f3e1cbdc6bf517. --- baseapp/abci.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/baseapp/abci.go b/baseapp/abci.go index 5b74fff7a7cf..94110154d869 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -191,6 +191,16 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg WithBlockGasMeter(gasMeter). WithHeaderHash(req.Hash) } + if app.prepareProposalState != nil { + app.prepareProposalState.ctx = app.prepareProposalState.ctx. + WithBlockGasMeter(gasMeter). + WithHeaderHash(req.Hash) + } + if app.processProposalState != nil { + app.processProposalState.ctx = app.processProposalState.ctx. + WithBlockGasMeter(gasMeter). + WithHeaderHash(req.Hash) + } if app.beginBlocker != nil { res = app.beginBlocker(app.deliverState.ctx, req) From a15d9f0d7208571994af5597eba02efb2ec0cc76 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 4 Nov 2022 11:10:17 -0500 Subject: [PATCH 178/196] Set Block height --- baseapp/abci.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 94110154d869..c5306384dab3 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -199,7 +199,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg if app.processProposalState != nil { app.processProposalState.ctx = app.processProposalState.ctx. WithBlockGasMeter(gasMeter). - WithHeaderHash(req.Hash) + WithHeaderHash(req.Hash). + WithBlockHeight(req.Header.Height) } if app.beginBlocker != nil { From cf88b0446c7aa5decf7d43312172d8a226cce18b Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 4 Nov 2022 11:12:49 -0500 Subject: [PATCH 179/196] Fix processProposal handler --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index a785195a6d82..822f5b58137d 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -851,7 +851,7 @@ func (app *BaseApp) DefaultProcessProposal() sdk.ProcessProposalHandler { for _, txBytes := range req.Txs { tx, err := app.txDecoder(txBytes) if err != nil { - panic(err) + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} } _, _, _, _, err = app.runTx(runTxProcessProposal, txBytes) From 5db4724e34b851d327ff95c239f58b246085110c Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 4 Nov 2022 11:25:25 -0500 Subject: [PATCH 180/196] Remove context modification in BeginBlock --- baseapp/abci.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index c5306384dab3..5b74fff7a7cf 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -191,17 +191,6 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg WithBlockGasMeter(gasMeter). WithHeaderHash(req.Hash) } - if app.prepareProposalState != nil { - app.prepareProposalState.ctx = app.prepareProposalState.ctx. - WithBlockGasMeter(gasMeter). - WithHeaderHash(req.Hash) - } - if app.processProposalState != nil { - app.processProposalState.ctx = app.processProposalState.ctx. - WithBlockGasMeter(gasMeter). - WithHeaderHash(req.Hash). - WithBlockHeight(req.Header.Height) - } if app.beginBlocker != nil { res = app.beginBlocker(app.deliverState.ctx, req) From 0696b822b2774fbf773e031fd4acc5fd4faa073a Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Fri, 4 Nov 2022 11:27:49 -0500 Subject: [PATCH 181/196] minor cleanup --- baseapp/abci.go | 2 +- baseapp/baseapp.go | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 5b74fff7a7cf..e2f6e679e0b2 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -409,7 +409,7 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) { app.setPrepareProposalState(header) app.setProcessProposalState(header) - // empty/reset the deliver + // empty/reset the deliver state app.deliverState = nil var halt bool diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 822f5b58137d..c11e440f3853 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -6,6 +6,7 @@ import ( "sort" "strings" + "github.com/cosmos/gogoproto/proto" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" @@ -13,8 +14,6 @@ import ( dbm "github.com/tendermint/tm-db" "golang.org/x/exp/maps" - "github.com/cosmos/gogoproto/proto" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/snapshots" "github.com/cosmos/cosmos-sdk/store" @@ -82,8 +81,8 @@ type BaseApp struct { //nolint: maligned // deliverState is set on InitChain and BeginBlock and set to nil on Commit checkState *state // for CheckTx deliverState *state // for DeliverTx - processProposalState *state // for CheckTx - prepareProposalState *state // for DeliverTx + processProposalState *state // for PrepareProposal + prepareProposalState *state // for ProcessProposal // an inter-block write-through cache provided to the context during deliverState interBlockCache sdk.MultiStorePersistentCache From ef5a6259aa2cd9bf0f1668f03b6bb4ae6f2ddd78 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Fri, 4 Nov 2022 14:25:41 -0600 Subject: [PATCH 182/196] t --- baseapp/baseapp.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index c11e440f3853..4f525482c6f7 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -763,12 +763,8 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { - // skip actual execution for (Re)CheckTx Prepare and Processes mode - //if mode != runTxModeDeliver && mode != runTxModeSimulate { - // break - //} - if mode == runTxModeCheck || mode == runTxModeReCheck || mode == runTxPrepareProposal || mode == runTxProcessProposal { + if mode != runTxModeDeliver && mode != runTxModeSimulate { break } From 3ac1d30ca7163551304f33d9f33cfcd83c91fa23 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 7 Nov 2022 09:11:37 -0600 Subject: [PATCH 183/196] fix go imports --- baseapp/abci.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index e2f6e679e0b2..f6c981be11de 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -10,13 +10,12 @@ import ( "syscall" "time" + "github.com/cosmos/gogoproto/proto" abci "github.com/tendermint/tendermint/abci/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "google.golang.org/grpc/codes" grpcstatus "google.golang.org/grpc/status" - "github.com/cosmos/gogoproto/proto" - "github.com/cosmos/cosmos-sdk/codec" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" "github.com/cosmos/cosmos-sdk/telemetry" From 74775b6c9139918091cc619bcd53b914b72fb82a Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 7 Nov 2022 11:43:11 -0500 Subject: [PATCH 184/196] Update baseapp/baseapp.go --- baseapp/baseapp.go | 1 - 1 file changed, 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 4f525482c6f7..57da3cd921c8 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -168,7 +168,6 @@ func NewBaseApp( option(app) } - // if execution of options has left certain required fields nil, set them to sane default values if app.mempool == nil { app.SetMempool(mempool.NewNonceMempool()) } From 1e36da8bf8c481a6f4dee17d842a0101c4f17cda Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 7 Nov 2022 11:43:26 -0500 Subject: [PATCH 185/196] Apply suggestions from code review --- baseapp/baseapp.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 57da3cd921c8..0be498ec0096 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -416,10 +416,9 @@ func (app *BaseApp) setDeliverState(header tmproto.Header) { } } -// setPrepareProposalState sets the BaseApp's deliverState with a branched multi-store -// (i.e. a CacheMultiStore) and a new Context with the same multi-store branch, -// and provided header. It is set on InitChain and BeginBlock and set to nil on -// Commit. +// setPrepareProposalState sets the BaseApp's prepareProposalState with a +// branched multi-store (i.e. a CacheMultiStore) and a new Context with the +// same multi-store branch, and provided header. It is set on InitChain and Commit. func (app *BaseApp) setPrepareProposalState(header tmproto.Header) { ms := app.cms.CacheMultiStore() app.prepareProposalState = &state{ @@ -428,10 +427,9 @@ func (app *BaseApp) setPrepareProposalState(header tmproto.Header) { } } -// setProcessProposalState sets the BaseApp's deliverState with a branched multi-store -// (i.e. a CacheMultiStore) and a new Context with the same multi-store branch, -// and provided header. It is set on InitChain and BeginBlock and set to nil on -// Commit. +// setProcessProposalState sets the BaseApp's processProposalState with a +// branched multi-store (i.e. a CacheMultiStore) and a new Context with the +// same multi-store branch, and provided header. It is set on InitChain and Commit. func (app *BaseApp) setProcessProposalState(header tmproto.Header) { ms := app.cms.CacheMultiStore() app.processProposalState = &state{ From a3bc195f1d9eb347a12d85841846ed7b9ebcc78b Mon Sep 17 00:00:00 2001 From: Jeancarlo Barrios Date: Mon, 7 Nov 2022 10:58:56 -0600 Subject: [PATCH 186/196] Update baseapp/baseapp.go Co-authored-by: Aleksandr Bezobchuk --- baseapp/baseapp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 0be498ec0096..4030f38e2caa 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -81,8 +81,8 @@ type BaseApp struct { //nolint: maligned // deliverState is set on InitChain and BeginBlock and set to nil on Commit checkState *state // for CheckTx deliverState *state // for DeliverTx - processProposalState *state // for PrepareProposal - prepareProposalState *state // for ProcessProposal + processProposalState *state // for ProcessProposal + prepareProposalState *state // for PrepareProposal // an inter-block write-through cache provided to the context during deliverState interBlockCache sdk.MultiStorePersistentCache From ccc00f1a5adc95a000ab807a089c7d34541e467d Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 7 Nov 2022 11:00:54 -0600 Subject: [PATCH 187/196] duplicate ProcessProposal request fields into context object --- baseapp/abci.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index f6c981be11de..57749e6e0711 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -302,7 +302,12 @@ func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.Respon panic("app.ProcessProposal is not set") } - ctx := app.processProposalState.ctx.WithVoteInfos(app.voteInfos) + ctx := app.processProposalState.ctx. + WithVoteInfos(app.voteInfos). + WithBlockHeight(req.Height). + WithBlockTime(req.Time). + WithHeaderHash(req.Hash). + WithProposer(req.ProposerAddress) ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx)) return app.processProposal(ctx, req) From 061e68eb04caf682eb64f4d1c4e10f5a5dd6ec90 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 7 Nov 2022 19:09:08 -0600 Subject: [PATCH 188/196] debug and fix bug surfaced in liveness tests --- baseapp/abci.go | 12 +++++++++++- baseapp/baseapp.go | 6 +++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 57749e6e0711..6018eef663bd 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -252,6 +252,11 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon byteCount int64 ) + // this state may occur when the application is replaying consensus messages after a crash + if app.prepareProposalState == nil { + app.setPrepareProposalState(tmproto.Header{Height: req.Height, Time: req.Time}) + } + ctx := app.getContextForTx(runTxPrepareProposal, []byte{}) iterator := app.mempool.Select(ctx, req.Txs) @@ -299,7 +304,12 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { if app.processProposal == nil { - panic("app.ProcessProposal is not set") + panic("app.ProcessProposal handler is not set") + } + + // this state may occur when the application is replaying consensus messages after a crash + if app.processProposalState == nil { + app.setProcessProposalState(tmproto.Header{Height: req.Height, Time: req.Time}) } ctx := app.processProposalState.ctx. diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 4030f38e2caa..6992e6122ee8 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -558,7 +558,11 @@ func (app *BaseApp) getState(mode runTxMode) *state { // retrieve the context for the tx w/ txBytes and other memoized values. func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) sdk.Context { - ctx := app.getState(mode).ctx. + modeState := app.getState(mode) + if modeState == nil { + panic(fmt.Sprintf("state is nil for mode %v", mode)) + } + ctx := modeState.ctx. WithTxBytes(txBytes). WithVoteInfos(app.voteInfos) From e594b596ecafb8f970d2da5dab843962d54e4eeb Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 8 Nov 2022 09:12:17 -0600 Subject: [PATCH 189/196] init prepare/process proposal state in .Init --- baseapp/abci.go | 16 ++++------------ baseapp/baseapp.go | 8 +++++++- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 6018eef663bd..34faaddff27d 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -39,6 +39,8 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC // req.InitialHeight is 1 by default. initHeader := tmproto.Header{ChainID: req.ChainId, Time: req.Time} + app.logger.Info("InitChain", "initialHeight", req.InitialHeight, "chainID", req.ChainId) + // If req.InitialHeight is > 1, then we set the initial version in the // stores. if req.InitialHeight > 1 { @@ -50,7 +52,7 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC } } - // initialize the deliver state and check state with a correct header + // initialize states with a correct header app.setDeliverState(initHeader) app.setCheckState(initHeader) app.setPrepareProposalState(initHeader) @@ -252,11 +254,6 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon byteCount int64 ) - // this state may occur when the application is replaying consensus messages after a crash - if app.prepareProposalState == nil { - app.setPrepareProposalState(tmproto.Header{Height: req.Height, Time: req.Time}) - } - ctx := app.getContextForTx(runTxPrepareProposal, []byte{}) iterator := app.mempool.Select(ctx, req.Txs) @@ -304,12 +301,7 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon // Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal { if app.processProposal == nil { - panic("app.ProcessProposal handler is not set") - } - - // this state may occur when the application is replaying consensus messages after a crash - if app.processProposalState == nil { - app.setProcessProposalState(tmproto.Header{Height: req.Height, Time: req.Time}) + panic("app.ProcessProposal is not set") } ctx := app.processProposalState.ctx. diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 6992e6122ee8..7ff193f4308c 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -343,8 +343,14 @@ func (app *BaseApp) Init() error { panic("cannot call initFromMainStore: baseapp already sealed") } + emptyHeader := tmproto.Header{} + // needed for the export command which inits from store but never calls initchain - app.setCheckState(tmproto.Header{}) + app.setCheckState(emptyHeader) + + // needed for ABCI Replay Blocks mode which calls Prepare/Process proposal (InitChain is not called) + app.setPrepareProposalState(emptyHeader) + app.setProcessProposalState(emptyHeader) app.Seal() rms, ok := app.cms.(*rootmulti.Store) From 40a5b21f5c71717c9146a2a671a48e829dfcc26c Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Tue, 8 Nov 2022 11:00:57 -0600 Subject: [PATCH 190/196] reject when checktx fails on process propossal --- baseapp/baseapp.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 7ff193f4308c..0b3724edd698 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -858,10 +858,7 @@ func (app *BaseApp) DefaultProcessProposal() sdk.ProcessProposalHandler { _, _, _, _, err = app.runTx(runTxProcessProposal, txBytes) if err != nil { - err = app.mempool.Remove(tx) - if err != nil && err != mempool.ErrTxNotFound { - panic(err) - } + return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} } } return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT} From e50ef7dbfa1bd07bfcfb096d55bde190871d7d06 Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Tue, 8 Nov 2022 11:03:32 -0600 Subject: [PATCH 191/196] reject when checktx fails on process propossal --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 0b3724edd698..006f9fb3f678 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -851,7 +851,7 @@ func createEvents(msg sdk.Msg) sdk.Events { func (app *BaseApp) DefaultProcessProposal() sdk.ProcessProposalHandler { return func(ctx sdk.Context, req abci.RequestProcessProposal) abci.ResponseProcessProposal { for _, txBytes := range req.Txs { - tx, err := app.txDecoder(txBytes) + _, err := app.txDecoder(txBytes) if err != nil { return abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT} } From 5ce1d3ac87d16be100f7353265795b1e8f108cd5 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 8 Nov 2022 11:40:55 -0600 Subject: [PATCH 192/196] Add 2 comments --- baseapp/abci.go | 2 ++ baseapp/baseapp.go | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/baseapp/abci.go b/baseapp/abci.go index 34faaddff27d..40ca8d810b74 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -267,6 +267,8 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon txSize := int64(len(bz)) + // NOTE: runTx was already run in CheckTx which calls mempool. Insert so ideally everything in the pool + // should be valid. But some mempool implementations may provide invalid txs so we check again. _, _, _, _, err = app.runTx(runTxPrepareProposal, bz) if err != nil { err := app.mempool.Remove(memTx) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 006f9fb3f678..19120a447520 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -848,6 +848,13 @@ func createEvents(msg sdk.Msg) sdk.Events { return sdk.Events{msgEvent} } +// DefaultProcessProposal returns the default implementation for processing an ABCI proposal. +// Every transaction in the proposal must pass 2 conditions: +// +// 1. The transaction bytes must decode to a valid transaction. +// 2. The transaction must be valid (i.e. pass runTx) +// +// If any transaction fails to pass either condition, the proposal is rejected. func (app *BaseApp) DefaultProcessProposal() sdk.ProcessProposalHandler { return func(ctx sdk.Context, req abci.RequestProcessProposal) abci.ResponseProcessProposal { for _, txBytes := range req.Txs { From c10e85762ffb3badd48f117df820a0317c746bda Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 8 Nov 2022 11:41:59 -0600 Subject: [PATCH 193/196] Update baseapp/abci.go Co-authored-by: Aleksandr Bezobchuk --- baseapp/abci.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 40ca8d810b74..ec8672e52810 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -311,8 +311,8 @@ func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.Respon WithBlockHeight(req.Height). WithBlockTime(req.Time). WithHeaderHash(req.Hash). - WithProposer(req.ProposerAddress) - ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx)) + WithProposer(req.ProposerAddress). + WithConsensusParams(app.GetConsensusParams(app.processProposalState.ctx)) return app.processProposal(ctx, req) } From 323b9f72e905dc0380a9902e0d32092036cbe68a Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 8 Nov 2022 12:26:36 -0600 Subject: [PATCH 194/196] fix comment --- baseapp/abci.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index ec8672e52810..247efa0a8bfa 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -267,8 +267,8 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon txSize := int64(len(bz)) - // NOTE: runTx was already run in CheckTx which calls mempool. Insert so ideally everything in the pool - // should be valid. But some mempool implementations may provide invalid txs so we check again. + // NOTE: runTx was already run in CheckTx which calls mempool.Insert so ideally everything in the pool + // should be valid. But some mempool implementations may provide invalid txs, so we check again. _, _, _, _, err = app.runTx(runTxPrepareProposal, bz) if err != nil { err := app.mempool.Remove(memTx) From 357557979e50876872acc966a9a672d17fbcc6dc Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Tue, 8 Nov 2022 15:27:22 -0500 Subject: [PATCH 195/196] Update baseapp/abci.go --- baseapp/abci.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/abci.go b/baseapp/abci.go index 247efa0a8bfa..def13328aaa7 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -268,7 +268,7 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon txSize := int64(len(bz)) // NOTE: runTx was already run in CheckTx which calls mempool.Insert so ideally everything in the pool - // should be valid. But some mempool implementations may provide invalid txs, so we check again. + // should be valid. But some mempool implementations may insert invalid txs, so we check again. _, _, _, _, err = app.runTx(runTxPrepareProposal, bz) if err != nil { err := app.mempool.Remove(memTx) From 45763052ad5baec54702a20bd63e2d63c4f54a76 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Tue, 8 Nov 2022 17:05:59 -0600 Subject: [PATCH 196/196] Update baseapp/baseapp.go Co-authored-by: Aleksandr Bezobchuk --- baseapp/baseapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 19120a447520..08a8746589c5 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -852,7 +852,7 @@ func createEvents(msg sdk.Msg) sdk.Events { // Every transaction in the proposal must pass 2 conditions: // // 1. The transaction bytes must decode to a valid transaction. -// 2. The transaction must be valid (i.e. pass runTx) +// 2. The transaction must be valid (i.e. pass runTx, AnteHandler only) // // If any transaction fails to pass either condition, the proposal is rejected. func (app *BaseApp) DefaultProcessProposal() sdk.ProcessProposalHandler {