From 5f7af25a348daa7807712a8c1cb97717283b9d5f Mon Sep 17 00:00:00 2001 From: Jay Yu <103467857+jayy04@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:09:29 -0400 Subject: [PATCH 1/9] Move clob hydration to preblocker --- protocol/app/app.go | 51 +--------------------------- protocol/lib/metrics/constants.go | 1 + protocol/mocks/ClobKeeper.go | 23 +++++++++++++ protocol/x/clob/abci.go | 8 +++++ protocol/x/clob/ante/clob.go | 8 +++++ protocol/x/clob/keeper/keeper.go | 31 +++++++++++++++++ protocol/x/clob/module.go | 11 ++++++ protocol/x/clob/types/clob_keeper.go | 3 ++ protocol/x/clob/types/errors.go | 5 +++ 9 files changed, 91 insertions(+), 50 deletions(-) diff --git a/protocol/app/app.go b/protocol/app/app.go index 821c74a4a3..f82fd9492f 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -1165,6 +1165,7 @@ func New( app.ModuleManager.SetOrderPreBlockers( upgradetypes.ModuleName, + clobmoduletypes.ModuleName, ) // During begin block slashing happens after distr.BeginBlocker so that @@ -1400,16 +1401,6 @@ func New( tmos.Exit(err.Error()) } - // Hydrate memStores used for caching state. - app.hydrateMemStores() - - // Hydrate the `memclob` with all ordersbooks from state, - // and hydrate the next `checkState` as well as the `memclob` with stateful orders. - app.hydrateMemclobWithOrderbooksAndStatefulOrders() - - // Hydrate the keeper in-memory data structures. - app.hydrateKeeperInMemoryDataStructures() - // load the x/prices keeper currency-pair ID cache app.loadCurrencyPairIDsForMarkets() } @@ -1627,15 +1618,6 @@ func (app *App) loadCurrencyPairIDsForMarkets() { app.PricesKeeper.LoadCurrencyPairIDCache(uncachedCtx) } -// hydrateMemStores hydrates the memStores used for caching state. -func (app *App) hydrateMemStores() { - // Create an `uncachedCtx` where the underlying MultiStore is the `rootMultiStore`. - // We use this to hydrate the `memStore` state with values from the underlying `rootMultiStore`. - uncachedCtx := app.BaseApp.NewUncachedContext(true, tmproto.Header{}) - // Initialize memstore in clobKeeper with order fill amounts and stateful orders. - app.ClobKeeper.InitMemStore(uncachedCtx) -} - // initializeRateLimiters initializes the rate limiters from state if the application is // not started from genesis. func (app *App) initializeRateLimiters() { @@ -1645,37 +1627,6 @@ func (app *App) initializeRateLimiters() { app.ClobKeeper.InitalizeBlockRateLimitFromStateIfExists(uncachedCtx) } -// hydrateMemclobWithOrderbooksAndStatefulOrders hydrates the memclob with orderbooks and stateful orders -// from state. -func (app *App) hydrateMemclobWithOrderbooksAndStatefulOrders() { - // Create a `checkStateCtx` where the underlying MultiStore is the `CacheMultiStore` for - // the `checkState`. We do this to avoid performing any state writes to the `rootMultiStore` - // directly. - checkStateCtx := app.BaseApp.NewContext(true) - - // Initialize memclob in clobKeeper with orderbooks using `ClobPairs` in state. - app.ClobKeeper.InitMemClobOrderbooks(checkStateCtx) - // Initialize memclob with all existing stateful orders. - // TODO(DEC-1348): Emit indexer messages to indicate that application restarted. - app.ClobKeeper.InitStatefulOrders(checkStateCtx) -} - -// hydrateKeeperInMemoryDataStructures hydrates the keeper with ClobPairId and PerpetualId mapping -// and untriggered conditional orders from state. -func (app *App) hydrateKeeperInMemoryDataStructures() { - // Create a `checkStateCtx` where the underlying MultiStore is the `CacheMultiStore` for - // the `checkState`. We do this to avoid performing any state writes to the `rootMultiStore` - // directly. - checkStateCtx := app.BaseApp.NewContext(true) - - // Initialize the untriggered conditional orders data structure with untriggered - // conditional orders in state. - app.ClobKeeper.HydrateClobPairAndPerpetualMapping(checkStateCtx) - // Initialize the untriggered conditional orders data structure with untriggered - // conditional orders in state. - app.ClobKeeper.HydrateUntriggeredConditionalOrders(checkStateCtx) -} - // GetBaseApp returns the base app of the application func (app *App) GetBaseApp() *baseapp.BaseApp { return app.BaseApp } diff --git a/protocol/lib/metrics/constants.go b/protocol/lib/metrics/constants.go index 084a3de08d..00d5bca83c 100644 --- a/protocol/lib/metrics/constants.go +++ b/protocol/lib/metrics/constants.go @@ -56,6 +56,7 @@ const ( OriginalNumTxs = "original_num_txs" OtherTxs = "other_txs" RemoveDisallowMsgs = "remove_disallow_msgs" + PreBlocker = "pre_blocker" PrepareProposalTxs = "prepare_proposal_txs" PrepareCheckState = "prepare_check_state" PricesTx = "prices_tx" diff --git a/protocol/mocks/ClobKeeper.go b/protocol/mocks/ClobKeeper.go index ddea077d81..0d251ab800 100644 --- a/protocol/mocks/ClobKeeper.go +++ b/protocol/mocks/ClobKeeper.go @@ -679,6 +679,11 @@ func (_m *ClobKeeper) HasAuthority(authority string) bool { return r0 } +// Hydrate provides a mock function with given fields: ctx +func (_m *ClobKeeper) Hydrate(ctx types.Context) { + _m.Called(ctx) +} + // InitializeBlockRateLimit provides a mock function with given fields: ctx, config func (_m *ClobKeeper) InitializeBlockRateLimit(ctx types.Context, config clobtypes.BlockRateLimitConfiguration) error { ret := _m.Called(ctx, config) @@ -720,6 +725,24 @@ func (_m *ClobKeeper) InitializeNewGrpcStreams(ctx types.Context) { _m.Called(ctx) } +// IsHydrated provides a mock function with given fields: +func (_m *ClobKeeper) IsHydrated() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsHydrated") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // IsLiquidatable provides a mock function with given fields: ctx, subaccountId func (_m *ClobKeeper) IsLiquidatable(ctx types.Context, subaccountId subaccountstypes.SubaccountId) (bool, error) { ret := _m.Called(ctx, subaccountId) diff --git a/protocol/x/clob/abci.go b/protocol/x/clob/abci.go index af57f847ad..d28bab58bd 100644 --- a/protocol/x/clob/abci.go +++ b/protocol/x/clob/abci.go @@ -16,6 +16,14 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" ) +// PreBlocker executes all ABCI PreBlock logic respective to the clob module. +func PreBlocker( + ctx sdk.Context, + keeper types.ClobKeeper, +) { + keeper.Hydrate(ctx) +} + // BeginBlocker executes all ABCI BeginBlock logic respective to the clob module. func BeginBlocker( ctx sdk.Context, diff --git a/protocol/x/clob/ante/clob.go b/protocol/x/clob/ante/clob.go index f5db4f0736..654b5f75e7 100644 --- a/protocol/x/clob/ante/clob.go +++ b/protocol/x/clob/ante/clob.go @@ -60,6 +60,14 @@ func (cd ClobDecorator) AnteHandle( return next(ctx, tx, simulate) } + // Disable order placement and cancelation processing if the clob keeper is not hydrated. + if !cd.clobKeeper.IsHydrated() { + return ctx, errorsmod.Wrap( + types.ErrClobNotHydrated, + "clob keeper is not hydrated. Please wait for the next block.", + ) + } + msgs := tx.GetMsgs() var msg = msgs[0] diff --git a/protocol/x/clob/keeper/keeper.go b/protocol/x/clob/keeper/keeper.go index 49c405176c..ac95045a5e 100644 --- a/protocol/x/clob/keeper/keeper.go +++ b/protocol/x/clob/keeper/keeper.go @@ -45,6 +45,7 @@ type ( indexerEventManager indexer_manager.IndexerEventManager streamingManager streamingtypes.GrpcStreamingManager + hydrated *atomic.Bool memStoreInitialized *atomic.Bool Flags flags.ClobFlags @@ -111,6 +112,7 @@ func NewKeeper( indexerEventManager: indexerEventManager, streamingManager: grpcStreamingManager, memStoreInitialized: &atomic.Bool{}, + hydrated: &atomic.Bool{}, txDecoder: txDecoder, mevTelemetryConfig: MevTelemetryConfig{ Enabled: clobFlags.MevTelemetryEnabled, @@ -154,6 +156,35 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { func (k Keeper) InitializeForGenesis(ctx sdk.Context) { } +// IsHydrated returns whether the clob keeper has been hydrated. +func (k Keeper) IsHydrated() bool { + return k.hydrated.Load() +} + +// Hydrate hydrates the clob keeper with the necessary in memory data structures. +func (k Keeper) Hydrate(ctx sdk.Context) { + alreadyInitialized := k.hydrated.Swap(true) + if alreadyInitialized { + return + } + + // Initialize memstore in clobKeeper with order fill amounts and stateful orders. + k.InitMemStore(ctx) + + // Initialize memclob in clobKeeper with orderbooks using `ClobPairs` in state. + k.InitMemClobOrderbooks(ctx) + // Initialize memclob with all existing stateful orders. + // TODO(DEC-1348): Emit indexer messages to indicate that application restarted. + k.InitStatefulOrders(ctx) + + // Initialize the untriggered conditional orders data structure with untriggered + // conditional orders in state. + k.HydrateClobPairAndPerpetualMapping(ctx) + // Initialize the untriggered conditional orders data structure with untriggered + // conditional orders in state. + k.HydrateUntriggeredConditionalOrders(ctx) +} + // InitMemStore initializes the memstore of the `clob` keeper. // This is called during app initialization in `app.go`, before any ABCI calls are received. func (k Keeper) InitMemStore(ctx sdk.Context) { diff --git a/protocol/x/clob/module.go b/protocol/x/clob/module.go index e817440275..6573d92ca0 100644 --- a/protocol/x/clob/module.go +++ b/protocol/x/clob/module.go @@ -20,6 +20,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" "github.com/dydxprotocol/v4-chain/protocol/x/clob/client/cli" "github.com/dydxprotocol/v4-chain/protocol/x/clob/keeper" "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" @@ -164,6 +165,16 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // ConsensusVersion implements ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return 1 } +// PreBlock executes all ABCI PreBlock logic respective to the clob module. +func (am AppModule) PreBlock(ctx context.Context) error { + defer telemetry.ModuleMeasureSince(am.Name(), time.Now(), metrics.PreBlocker) + PreBlocker( + lib.UnwrapSDKContext(ctx, types.ModuleName), + am.keeper, + ) + return nil +} + // BeginBlock executes all ABCI BeginBlock logic respective to the clob module. func (am AppModule) BeginBlock(ctx context.Context) error { defer telemetry.ModuleMeasureSince(am.Name(), time.Now(), telemetry.MetricKeyBeginBlocker) diff --git a/protocol/x/clob/types/clob_keeper.go b/protocol/x/clob/types/clob_keeper.go index 522bdd879e..baf586c1f8 100644 --- a/protocol/x/clob/types/clob_keeper.go +++ b/protocol/x/clob/types/clob_keeper.go @@ -14,6 +14,9 @@ type ClobKeeper interface { LiquidationsKeeper LiquidationsConfigKeeper + IsHydrated() bool + Hydrate(ctx sdk.Context) + AddOrderToOrderbookSubaccountUpdatesCheck( ctx sdk.Context, clobPairId ClobPairId, diff --git a/protocol/x/clob/types/errors.go b/protocol/x/clob/types/errors.go index ebe536fd4b..233d6abb79 100644 --- a/protocol/x/clob/types/errors.go +++ b/protocol/x/clob/types/errors.go @@ -216,6 +216,11 @@ var ( 46, "Batch cancel has failed", ) + ErrClobNotHydrated = errorsmod.Register( + ModuleName, + 47, + "CLOB has not been hydrated", + ) // Liquidations errors. ErrInvalidLiquidationsConfig = errorsmod.Register( From 89c6793572541f15ee8ffb4725c682208f8cbf43 Mon Sep 17 00:00:00 2001 From: Jay Yu <103467857+jayy04@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:49:25 -0400 Subject: [PATCH 2/9] updates --- protocol/mocks/MemClob.go | 5 +++++ protocol/x/clob/keeper/clob_pair.go | 9 +++++++-- protocol/x/clob/keeper/keeper.go | 11 +++++++---- protocol/x/clob/memclob/memclob.go | 11 +++++++++++ protocol/x/clob/module.go | 6 ++++-- protocol/x/clob/types/memclob.go | 4 ++++ 6 files changed, 38 insertions(+), 8 deletions(-) diff --git a/protocol/mocks/MemClob.go b/protocol/mocks/MemClob.go index 2f05441f35..382f5a8b30 100644 --- a/protocol/mocks/MemClob.go +++ b/protocol/mocks/MemClob.go @@ -442,6 +442,11 @@ func (_m *MemClob) InsertZeroFillDeleveragingIntoOperationsQueue(ctx types.Conte _m.Called(ctx, subaccountId, perpetualId) } +// MaybeCreateOrderbook provides a mock function with given fields: ctx, clobPair +func (_m *MemClob) MaybeCreateOrderbook(ctx types.Context, clobPair clobtypes.ClobPair) { + _m.Called(ctx, clobPair) +} + // PlaceOrder provides a mock function with given fields: ctx, order func (_m *MemClob) PlaceOrder(ctx types.Context, order clobtypes.Order) (subaccountstypes.BaseQuantums, clobtypes.OrderStatus, *clobtypes.OffchainUpdates, error) { ret := _m.Called(ctx, order) diff --git a/protocol/x/clob/keeper/clob_pair.go b/protocol/x/clob/keeper/clob_pair.go index 12a3f6fef1..c5917e5d2c 100644 --- a/protocol/x/clob/keeper/clob_pair.go +++ b/protocol/x/clob/keeper/clob_pair.go @@ -155,6 +155,12 @@ func (k Keeper) validateClobPair(ctx sdk.Context, clobPair *types.ClobPair) erro return nil } +// maybeCreateOrderbook creates a new orderbook in the memclob. +func (k Keeper) maybeCreateOrderbook(ctx sdk.Context, clobPair types.ClobPair) { + // Create the corresponding orderbook in the memclob. + k.MemClob.MaybeCreateOrderbook(ctx, clobPair) +} + // createOrderbook creates a new orderbook in the memclob. func (k Keeper) createOrderbook(ctx sdk.Context, clobPair types.ClobPair) { // Create the corresponding orderbook in the memclob. @@ -192,12 +198,11 @@ func (k Keeper) setClobPair(ctx sdk.Context, clobPair types.ClobPair) { } // InitMemClobOrderbooks initializes the memclob with `ClobPair`s from state. -// This is called during app initialization in `app.go`, before any ABCI calls are received. func (k Keeper) InitMemClobOrderbooks(ctx sdk.Context) { clobPairs := k.GetAllClobPairs(ctx) for _, clobPair := range clobPairs { // Create the corresponding orderbook in the memclob. - k.createOrderbook( + k.maybeCreateOrderbook( ctx, clobPair, ) diff --git a/protocol/x/clob/keeper/keeper.go b/protocol/x/clob/keeper/keeper.go index ac95045a5e..178d70332f 100644 --- a/protocol/x/clob/keeper/keeper.go +++ b/protocol/x/clob/keeper/keeper.go @@ -171,18 +171,21 @@ func (k Keeper) Hydrate(ctx sdk.Context) { // Initialize memstore in clobKeeper with order fill amounts and stateful orders. k.InitMemStore(ctx) + checkCtx, _ := ctx.CacheContext() + checkCtx = checkCtx.WithIsCheckTx(true) + // Initialize memclob in clobKeeper with orderbooks using `ClobPairs` in state. - k.InitMemClobOrderbooks(ctx) + k.InitMemClobOrderbooks(checkCtx) // Initialize memclob with all existing stateful orders. // TODO(DEC-1348): Emit indexer messages to indicate that application restarted. - k.InitStatefulOrders(ctx) + k.InitStatefulOrders(checkCtx) // Initialize the untriggered conditional orders data structure with untriggered // conditional orders in state. - k.HydrateClobPairAndPerpetualMapping(ctx) + k.HydrateClobPairAndPerpetualMapping(checkCtx) // Initialize the untriggered conditional orders data structure with untriggered // conditional orders in state. - k.HydrateUntriggeredConditionalOrders(ctx) + k.HydrateUntriggeredConditionalOrders(checkCtx) } // InitMemStore initializes the memstore of the `clob` keeper. diff --git a/protocol/x/clob/memclob/memclob.go b/protocol/x/clob/memclob/memclob.go index 4b5b93de06..01181193c3 100644 --- a/protocol/x/clob/memclob/memclob.go +++ b/protocol/x/clob/memclob/memclob.go @@ -148,6 +148,17 @@ func (m *MemClobPriceTimePriority) CancelOrder( return offchainUpdates, nil } +// MaybeCreateOrderbook is used for updating memclob internal data structures to mark an orderbook as created. +func (m *MemClobPriceTimePriority) MaybeCreateOrderbook( + ctx sdk.Context, + clobPair types.ClobPair, +) { + if _, exists := m.openOrders.orderbooksMap[clobPair.GetClobPairId()]; exists { + return + } + m.CreateOrderbook(ctx, clobPair) +} + // CreateOrderbook is used for updating memclob internal data structures to mark an orderbook as created. // This function will panic if `clobPairId` already exists in any of the memclob's internal data structures. func (m *MemClobPriceTimePriority) CreateOrderbook( diff --git a/protocol/x/clob/module.go b/protocol/x/clob/module.go index 6573d92ca0..2b61ed37c7 100644 --- a/protocol/x/clob/module.go +++ b/protocol/x/clob/module.go @@ -166,13 +166,15 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 1 } // PreBlock executes all ABCI PreBlock logic respective to the clob module. -func (am AppModule) PreBlock(ctx context.Context) error { +func (am AppModule) PreBlock(ctx context.Context) (appmodule.ResponsePreBlock, error) { defer telemetry.ModuleMeasureSince(am.Name(), time.Now(), metrics.PreBlocker) PreBlocker( lib.UnwrapSDKContext(ctx, types.ModuleName), am.keeper, ) - return nil + return sdk.ResponsePreBlock{ + ConsensusParamsChanged: false, + }, nil } // BeginBlock executes all ABCI BeginBlock logic respective to the clob module. diff --git a/protocol/x/clob/types/memclob.go b/protocol/x/clob/types/memclob.go index 3196320e39..8a1ec71488 100644 --- a/protocol/x/clob/types/memclob.go +++ b/protocol/x/clob/types/memclob.go @@ -22,6 +22,10 @@ type MemClob interface { ctx sdk.Context, clobPair ClobPair, ) + MaybeCreateOrderbook( + ctx sdk.Context, + clobPair ClobPair, + ) CountSubaccountShortTermOrders( ctx sdk.Context, subaccountId satypes.SubaccountId, From 242f36a0e790773a7b2ab70c9c95ff355e585e7c Mon Sep 17 00:00:00 2001 From: Jay Yu <103467857+jayy04@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:58:53 -0400 Subject: [PATCH 3/9] comments --- protocol/app/app.go | 2 +- protocol/mocks/ClobKeeper.go | 10 +++++----- protocol/x/clob/abci.go | 2 +- protocol/x/clob/ante/clob.go | 6 +++--- protocol/x/clob/keeper/keeper.go | 21 +++++++++++++-------- protocol/x/clob/types/clob_keeper.go | 4 ++-- protocol/x/clob/types/errors.go | 4 ++-- 7 files changed, 27 insertions(+), 22 deletions(-) diff --git a/protocol/app/app.go b/protocol/app/app.go index f82fd9492f..587b1d2850 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -1164,7 +1164,7 @@ func New( ) app.ModuleManager.SetOrderPreBlockers( - upgradetypes.ModuleName, + upgradetypes.ModuleName, // Must be first since upgrades may be state schema breaking. clobmoduletypes.ModuleName, ) diff --git a/protocol/mocks/ClobKeeper.go b/protocol/mocks/ClobKeeper.go index 0d251ab800..bd18fc7cbf 100644 --- a/protocol/mocks/ClobKeeper.go +++ b/protocol/mocks/ClobKeeper.go @@ -679,8 +679,8 @@ func (_m *ClobKeeper) HasAuthority(authority string) bool { return r0 } -// Hydrate provides a mock function with given fields: ctx -func (_m *ClobKeeper) Hydrate(ctx types.Context) { +// Initialize provides a mock function with given fields: ctx +func (_m *ClobKeeper) Initialize(ctx types.Context) { _m.Called(ctx) } @@ -725,12 +725,12 @@ func (_m *ClobKeeper) InitializeNewGrpcStreams(ctx types.Context) { _m.Called(ctx) } -// IsHydrated provides a mock function with given fields: -func (_m *ClobKeeper) IsHydrated() bool { +// IsInitialized provides a mock function with given fields: +func (_m *ClobKeeper) IsInitialized() bool { ret := _m.Called() if len(ret) == 0 { - panic("no return value specified for IsHydrated") + panic("no return value specified for IsInitialized") } var r0 bool diff --git a/protocol/x/clob/abci.go b/protocol/x/clob/abci.go index d28bab58bd..e5d47e8150 100644 --- a/protocol/x/clob/abci.go +++ b/protocol/x/clob/abci.go @@ -21,7 +21,7 @@ func PreBlocker( ctx sdk.Context, keeper types.ClobKeeper, ) { - keeper.Hydrate(ctx) + keeper.Initialize(ctx) } // BeginBlocker executes all ABCI BeginBlock logic respective to the clob module. diff --git a/protocol/x/clob/ante/clob.go b/protocol/x/clob/ante/clob.go index 654b5f75e7..8d18baacaf 100644 --- a/protocol/x/clob/ante/clob.go +++ b/protocol/x/clob/ante/clob.go @@ -61,10 +61,10 @@ func (cd ClobDecorator) AnteHandle( } // Disable order placement and cancelation processing if the clob keeper is not hydrated. - if !cd.clobKeeper.IsHydrated() { + if !cd.clobKeeper.IsInitialized() { return ctx, errorsmod.Wrap( - types.ErrClobNotHydrated, - "clob keeper is not hydrated. Please wait for the next block.", + types.ErrClobNotInitialized, + "clob keeper is not initialized. Please wait for the next block.", ) } diff --git a/protocol/x/clob/keeper/keeper.go b/protocol/x/clob/keeper/keeper.go index 178d70332f..0cddd2eb6c 100644 --- a/protocol/x/clob/keeper/keeper.go +++ b/protocol/x/clob/keeper/keeper.go @@ -45,7 +45,7 @@ type ( indexerEventManager indexer_manager.IndexerEventManager streamingManager streamingtypes.GrpcStreamingManager - hydrated *atomic.Bool + initialized *atomic.Bool memStoreInitialized *atomic.Bool Flags flags.ClobFlags @@ -112,7 +112,7 @@ func NewKeeper( indexerEventManager: indexerEventManager, streamingManager: grpcStreamingManager, memStoreInitialized: &atomic.Bool{}, - hydrated: &atomic.Bool{}, + initialized: &atomic.Bool{}, txDecoder: txDecoder, mevTelemetryConfig: MevTelemetryConfig{ Enabled: clobFlags.MevTelemetryEnabled, @@ -156,14 +156,14 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { func (k Keeper) InitializeForGenesis(ctx sdk.Context) { } -// IsHydrated returns whether the clob keeper has been hydrated. -func (k Keeper) IsHydrated() bool { - return k.hydrated.Load() +// IsInitialized returns whether the clob keeper has been hydrated. +func (k Keeper) IsInitialized() bool { + return k.initialized.Load() } -// Hydrate hydrates the clob keeper with the necessary in memory data structures. -func (k Keeper) Hydrate(ctx sdk.Context) { - alreadyInitialized := k.hydrated.Swap(true) +// Initialize hydrates the clob keeper with the necessary in memory data structures. +func (k Keeper) Initialize(ctx sdk.Context) { + alreadyInitialized := k.initialized.Swap(true) if alreadyInitialized { return } @@ -171,6 +171,11 @@ func (k Keeper) Hydrate(ctx sdk.Context) { // Initialize memstore in clobKeeper with order fill amounts and stateful orders. k.InitMemStore(ctx) + // Branch the context for hydration. + // This means that new order matches from hydration will get added to the operations + // queue but the corresponding state changes will be discarded. + // This is because we are currently in the deliver state so writing optimistic matches + // breaks consensus. checkCtx, _ := ctx.CacheContext() checkCtx = checkCtx.WithIsCheckTx(true) diff --git a/protocol/x/clob/types/clob_keeper.go b/protocol/x/clob/types/clob_keeper.go index baf586c1f8..0a755d72ab 100644 --- a/protocol/x/clob/types/clob_keeper.go +++ b/protocol/x/clob/types/clob_keeper.go @@ -14,8 +14,8 @@ type ClobKeeper interface { LiquidationsKeeper LiquidationsConfigKeeper - IsHydrated() bool - Hydrate(ctx sdk.Context) + IsInitialized() bool + Initialize(ctx sdk.Context) AddOrderToOrderbookSubaccountUpdatesCheck( ctx sdk.Context, diff --git a/protocol/x/clob/types/errors.go b/protocol/x/clob/types/errors.go index 233d6abb79..cc7ac4f4b3 100644 --- a/protocol/x/clob/types/errors.go +++ b/protocol/x/clob/types/errors.go @@ -216,10 +216,10 @@ var ( 46, "Batch cancel has failed", ) - ErrClobNotHydrated = errorsmod.Register( + ErrClobNotInitialized = errorsmod.Register( ModuleName, 47, - "CLOB has not been hydrated", + "CLOB has not been initialized", ) // Liquidations errors. From 7ca674fc57bbff253a852344dcca81d7000890d3 Mon Sep 17 00:00:00 2001 From: Jay Yu <103467857+jayy04@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:59:48 -0400 Subject: [PATCH 4/9] nit --- protocol/x/clob/keeper/keeper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocol/x/clob/keeper/keeper.go b/protocol/x/clob/keeper/keeper.go index 0cddd2eb6c..8511337dd0 100644 --- a/protocol/x/clob/keeper/keeper.go +++ b/protocol/x/clob/keeper/keeper.go @@ -111,8 +111,8 @@ func NewKeeper( rewardsKeeper: rewardsKeeper, indexerEventManager: indexerEventManager, streamingManager: grpcStreamingManager, - memStoreInitialized: &atomic.Bool{}, - initialized: &atomic.Bool{}, + memStoreInitialized: &atomic.Bool{}, // False by default. + initialized: &atomic.Bool{}, // False by default. txDecoder: txDecoder, mevTelemetryConfig: MevTelemetryConfig{ Enabled: clobFlags.MevTelemetryEnabled, From e06b1aaa0552d49a7a4c0440aaa6b06ab5b3e664 Mon Sep 17 00:00:00 2001 From: Jay Yu <103467857+jayy04@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:49:49 -0400 Subject: [PATCH 5/9] reset gas meter; comments --- protocol/app/app.go | 10 ++++++++++ protocol/app/app_test.go | 10 ---------- protocol/x/clob/ante/clob.go | 2 +- protocol/x/clob/keeper/keeper.go | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/protocol/app/app.go b/protocol/app/app.go index 680d452158..fdca905f24 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -89,6 +89,7 @@ import ( "github.com/cosmos/ibc-go/modules/capability" capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + antetypes "github.com/dydxprotocol/v4-chain/protocol/app/ante/types" "github.com/gorilla/mux" "github.com/rakyll/statik/fs" "github.com/spf13/cast" @@ -1619,6 +1620,15 @@ func (app *App) GetBaseApp() *baseapp.BaseApp { return app.BaseApp } // PreBlocker application updates before each begin block. func (app *App) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { + // Set gas meter to the free gas meter. + // This is because there is currently non-deterministic gas usage in the + // pre-blocker, e.g. due to hydration of in-memory data structures. + currentGasMeter := ctx.GasMeter() + ctx = ctx.WithGasMeter(antetypes.NewFreeInfiniteGasMeter()) + defer func() { + ctx.WithGasMeter(currentGasMeter) + }() + return app.ModuleManager.PreBlock(ctx) } diff --git a/protocol/app/app_test.go b/protocol/app/app_test.go index 44748c78b5..9bc21b9d88 100644 --- a/protocol/app/app_test.go +++ b/protocol/app/app_test.go @@ -9,7 +9,6 @@ import ( evidencemodule "cosmossdk.io/x/evidence" feegrantmodule "cosmossdk.io/x/feegrant/module" "cosmossdk.io/x/upgrade" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/auth" authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" @@ -137,15 +136,6 @@ func TestAppPanicsWithGrpcDisabled(t *testing.T) { require.Panics(t, func() { testapp.DefaultTestApp(customFlags) }) } -func TestClobKeeperMemStoreHasBeenInitialized(t *testing.T) { - dydxApp := testapp.DefaultTestApp(nil) - ctx := dydxApp.NewUncachedContext(true, tmproto.Header{}) - - // The memstore panics if initialized twice so initializing again outside of application - // start-up should cause a panic. - require.Panics(t, func() { dydxApp.ClobKeeper.InitMemStore(ctx) }) -} - func TestBaseApp(t *testing.T) { dydxApp := testapp.DefaultTestApp(nil) require.NotNil(t, dydxApp.GetBaseApp(), "Expected non-nil BaseApp") diff --git a/protocol/x/clob/ante/clob.go b/protocol/x/clob/ante/clob.go index 8d18baacaf..8578f179f5 100644 --- a/protocol/x/clob/ante/clob.go +++ b/protocol/x/clob/ante/clob.go @@ -60,7 +60,7 @@ func (cd ClobDecorator) AnteHandle( return next(ctx, tx, simulate) } - // Disable order placement and cancelation processing if the clob keeper is not hydrated. + // Disable order placement and cancelation processing if the clob keeper is not initialized. if !cd.clobKeeper.IsInitialized() { return ctx, errorsmod.Wrap( types.ErrClobNotInitialized, diff --git a/protocol/x/clob/keeper/keeper.go b/protocol/x/clob/keeper/keeper.go index 8511337dd0..7ff636916f 100644 --- a/protocol/x/clob/keeper/keeper.go +++ b/protocol/x/clob/keeper/keeper.go @@ -174,8 +174,8 @@ func (k Keeper) Initialize(ctx sdk.Context) { // Branch the context for hydration. // This means that new order matches from hydration will get added to the operations // queue but the corresponding state changes will be discarded. - // This is because we are currently in the deliver state so writing optimistic matches - // breaks consensus. + // This is needed because we are hydrating in memory structures in PreBlock + // which operates on deliver state. Writing optimistic matches breaks consensus. checkCtx, _ := ctx.CacheContext() checkCtx = checkCtx.WithIsCheckTx(true) From f254b77a3bf7e4ba0dc29163207975b262059b21 Mon Sep 17 00:00:00 2001 From: Jay Yu <103467857+jayy04@users.noreply.github.com> Date: Mon, 29 Apr 2024 14:41:44 -0400 Subject: [PATCH 6/9] remove defer --- protocol/app/app.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/protocol/app/app.go b/protocol/app/app.go index fdca905f24..6f10767eb4 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -1623,12 +1623,10 @@ func (app *App) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk. // Set gas meter to the free gas meter. // This is because there is currently non-deterministic gas usage in the // pre-blocker, e.g. due to hydration of in-memory data structures. - currentGasMeter := ctx.GasMeter() + // + // Note that we don't need to reset the gas meter after the pre-blocker + // because Go is pass by value. ctx = ctx.WithGasMeter(antetypes.NewFreeInfiniteGasMeter()) - defer func() { - ctx.WithGasMeter(currentGasMeter) - }() - return app.ModuleManager.PreBlock(ctx) } From 56c9db2751eb9cf6c9c4bcbe520315fe059404ab Mon Sep 17 00:00:00 2001 From: Jay Yu <103467857+jayy04@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:41:50 -0400 Subject: [PATCH 7/9] fix test --- protocol/x/clob/keeper/clob_pair.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/protocol/x/clob/keeper/clob_pair.go b/protocol/x/clob/keeper/clob_pair.go index c5917e5d2c..a1d448a813 100644 --- a/protocol/x/clob/keeper/clob_pair.go +++ b/protocol/x/clob/keeper/clob_pair.go @@ -231,6 +231,14 @@ func (k Keeper) SetClobPairIdForPerpetual(ctx sdk.Context, clobPair types.ClobPa if !exists { clobPairIds = make([]types.ClobPairId, 0) } + + for _, clobPairId := range clobPairIds { + if clobPairId == clobPair.GetClobPairId() { + // The mapping already exists, so return. + return + } + } + k.PerpetualIdToClobPairId[perpetualId] = append( clobPairIds, clobPair.GetClobPairId(), From f8d83c39520fe954f3bdcdc749747534ad7d919c Mon Sep 17 00:00:00 2001 From: Jay Yu <103467857+jayy04@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:57:04 -0400 Subject: [PATCH 8/9] fix test --- protocol/x/clob/ante/clob_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol/x/clob/ante/clob_test.go b/protocol/x/clob/ante/clob_test.go index 08a194d3ad..7aa69aa6b3 100644 --- a/protocol/x/clob/ante/clob_test.go +++ b/protocol/x/clob/ante/clob_test.go @@ -50,6 +50,7 @@ func runTestCase(t *testing.T, tc TestCase) { // Setup AnteHandler. mockClobKeeper := &mocks.ClobKeeper{} mockClobKeeper.On("Logger", mock.Anything).Return(log.NewNopLogger()).Maybe() + mockClobKeeper.On("IsInitialized").Return(true).Maybe() cd := ante.NewClobDecorator(mockClobKeeper) antehandler := sdk.ChainAnteDecorators(cd) if tc.setupMocks != nil { From 9a42425cec5240d6232f52f126e35e6356c7bbe2 Mon Sep 17 00:00:00 2001 From: jayy04 <103467857+jayy04@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:15:48 -0400 Subject: [PATCH 9/9] e2e test for hydration in preblocker (#1437) * e2e test for hydration in preblocker * fix lint --- protocol/x/clob/e2e/app_test.go | 257 +++++++++++++++++++++++++++++++- 1 file changed, 254 insertions(+), 3 deletions(-) diff --git a/protocol/x/clob/e2e/app_test.go b/protocol/x/clob/e2e/app_test.go index e2ed8f1952..998b0b8c38 100644 --- a/protocol/x/clob/e2e/app_test.go +++ b/protocol/x/clob/e2e/app_test.go @@ -7,17 +7,18 @@ import ( "time" abcitypes "github.com/cometbft/cometbft/abci/types" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmtypes "github.com/cometbft/cometbft/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdktypes "github.com/cosmos/cosmos-sdk/types" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" auth "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/dydxprotocol/v4-chain/protocol/app/config" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" "github.com/dydxprotocol/v4-chain/protocol/testutil/rand" "gopkg.in/typ.v4/slices" - "github.com/cometbft/cometbft/types" - "github.com/dydxprotocol/v4-chain/protocol/indexer" "github.com/dydxprotocol/v4-chain/protocol/indexer/msgsender" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" @@ -26,6 +27,9 @@ import ( testtx "github.com/dydxprotocol/v4-chain/protocol/testutil/tx" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" epochtypes "github.com/dydxprotocol/v4-chain/protocol/x/epochs/types" + feetierstypes "github.com/dydxprotocol/v4-chain/protocol/x/feetiers/types" + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + prices "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" stattypes "github.com/dydxprotocol/v4-chain/protocol/x/stats/types" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" "github.com/stretchr/testify/require" @@ -312,13 +316,260 @@ var ( ) ) +func TestHydrationInPreBlocker(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis tmtypes.GenesisDoc) { + genesis = testapp.DefaultGenesis() + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *prices.GenesisState) { + *genesisState = constants.TestPricesGenesisState + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *perptypes.GenesisState) { + genesisState.Params = constants.PerpetualsGenesisParams + genesisState.LiquidityTiers = constants.LiquidityTiers + genesisState.Perpetuals = []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + } + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + genesisState.Subaccounts = []satypes.Subaccount{ + constants.Carl_Num0_100000USD, + constants.Dave_Num0_10000USD, + } + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *clobtypes.GenesisState) { + genesisState.ClobPairs = []clobtypes.ClobPair{ + constants.ClobPair_Btc, + } + genesisState.LiquidationsConfig = clobtypes.LiquidationsConfig_Default + }, + ) + return genesis + }).WithNonDeterminismChecksEnabled(false).Build() + + // Let's add some pre-existing orders to state. + // Note that the order is not added to memclob. + tApp.App.ClobKeeper.SetLongTermOrderPlacement( + tApp.App.NewUncachedContext(false, tmproto.Header{}), + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10, + 1, + ) + tApp.App.ClobKeeper.MustAddOrderToStatefulOrdersTimeSlice( + tApp.App.NewUncachedContext(false, tmproto.Header{}), + time.Unix(50, 0), + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10.OrderId, + ) + + // Advance one block so that pre blocker is called and clob is hydrated. + _ = tApp.InitChain() + ctx := tApp.AdvanceToBlock(2, testapp.AdvanceToBlockOptions{}) + + // Order should exist in state + _, found := tApp.App.ClobKeeper.GetLongTermOrderPlacement( + ctx, + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10.OrderId, + ) + require.True(t, found) + + // Order should be on the orderbook + _, found = tApp.App.ClobKeeper.MemClob.GetOrder( + ctx, + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10.OrderId, + ) + require.True(t, found) +} + +func TestHydrationWithMatchPreBlocker(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis tmtypes.GenesisDoc) { + genesis = testapp.DefaultGenesis() + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *prices.GenesisState) { + *genesisState = constants.TestPricesGenesisState + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *perptypes.GenesisState) { + genesisState.Params = constants.PerpetualsGenesisParams + genesisState.LiquidityTiers = constants.LiquidityTiers + genesisState.Perpetuals = []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + } + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + genesisState.Subaccounts = []satypes.Subaccount{ + constants.Carl_Num0_100000USD, + constants.Dave_Num0_500000USD, + } + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *clobtypes.GenesisState) { + genesisState.ClobPairs = []clobtypes.ClobPair{ + constants.ClobPair_Btc, + } + genesisState.LiquidationsConfig = clobtypes.LiquidationsConfig_Default + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *feetierstypes.GenesisState) { + genesisState.Params = constants.PerpetualFeeParamsNoFee + }, + ) + return genesis + }).WithNonDeterminismChecksEnabled(false).Build() + + // 1. Let's add some pre-existing orders to state before clob is initialized. + tApp.App.ClobKeeper.SetLongTermOrderPlacement( + tApp.App.NewUncachedContext(false, tmproto.Header{}), + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10, + 1, + ) + tApp.App.ClobKeeper.MustAddOrderToStatefulOrdersTimeSlice( + tApp.App.NewUncachedContext(false, tmproto.Header{}), + time.Unix(10, 0), + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10.OrderId, + ) + + // Let's add a crossing order to state. + tApp.App.ClobKeeper.SetLongTermOrderPlacement( + tApp.App.NewUncachedContext(false, tmproto.Header{}), + constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10, + 1, + ) + tApp.App.ClobKeeper.MustAddOrderToStatefulOrdersTimeSlice( + tApp.App.NewUncachedContext(false, tmproto.Header{}), + time.Unix(10, 0), + constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10.OrderId, + ) + + // 2. Advance one block so that pre blocker is called and clob is hydrated. + ctx := tApp.InitChain() + + // Here, PreBlocker has been called and Carl's and Dave's orders are placed against the orderbook. + // They should generate a match in the local operations queue, but their state changes should've been discarded since + // preblocker happens during deliver state and context was cached with IsCheckTx set to true. + + // Make sure order still exists in state, with a fill amount of 0. + // Note that `ctx` is the check tx context, so need to read from the uncached cms to make sure changes are discarded. + uncachedCtx := tApp.App.NewUncachedContext(false, tmproto.Header{}) + _, found := tApp.App.ClobKeeper.GetLongTermOrderPlacement( + uncachedCtx, + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10.OrderId, + ) + require.True(t, found) + fillAmount := tApp.App.ClobKeeper.MemClob.GetOrderFilledAmount( + uncachedCtx, + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10.OrderId, + ) + require.Equal(t, satypes.BaseQuantums(0), fillAmount) + + _, found = tApp.App.ClobKeeper.GetLongTermOrderPlacement( + uncachedCtx, + constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10.OrderId, + ) + require.True(t, found) + fillAmount = tApp.App.ClobKeeper.MemClob.GetOrderFilledAmount( + uncachedCtx, + constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10.OrderId, + ) + require.Equal(t, satypes.BaseQuantums(0), fillAmount) + + // Make sure orders are not on the orderbook. + _, found = tApp.App.ClobKeeper.MemClob.GetOrder( + ctx, + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10.OrderId, + ) + require.False(t, found) + + _, found = tApp.App.ClobKeeper.MemClob.GetOrder( + ctx, + constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10.OrderId, + ) + require.False(t, found) + + // Make sure match is in the operations queue. + operations := tApp.App.ClobKeeper.MemClob.GetOperationsRaw(ctx) + require.Len(t, operations, 1) + + // Advance to the next block to persist the matches. + ctx = tApp.AdvanceToBlock(2, testapp.AdvanceToBlockOptions{}) + + // Order should not exist in state because they are filly filled. + _, found = tApp.App.ClobKeeper.GetLongTermOrderPlacement( + ctx, + constants.LongTermOrder_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10.OrderId, + ) + require.False(t, found) + + _, found = tApp.App.ClobKeeper.GetLongTermOrderPlacement( + ctx, + constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10.OrderId, + ) + require.False(t, found) + + // Carl and Dave's state should get updated accordingly. + carl := tApp.App.SubaccountsKeeper.GetSubaccount(ctx, constants.Carl_Num0) + require.Equal(t, satypes.Subaccount{ + Id: &constants.Carl_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(100_000_000_000 - 50_000_000_000), + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 0, + Quantums: dtypes.NewInt(100_000_000), + FundingIndex: dtypes.NewInt(0), + }, + }, + }, carl) + + dave := tApp.App.SubaccountsKeeper.GetSubaccount(ctx, constants.Dave_Num0) + require.Equal(t, satypes.Subaccount{ + Id: &constants.Dave_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(500_000_000_000 + 50_000_000_000), + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 0, + Quantums: dtypes.NewInt(-100_000_000), + FundingIndex: dtypes.NewInt(0), + }, + }, + }, dave) + + require.Empty(t, tApp.App.ClobKeeper.MemClob.GetOperationsRaw(ctx)) +} + // We place 300 orders that match and 700 orders followed by their cancellations concurrently. // // This test heavily relies on golangs race detector to validate memory reads and writes are properly ordered. func TestConcurrentMatchesAndCancels(t *testing.T) { r := rand.NewRand() simAccounts := simtypes.RandomAccounts(r, 1000) - tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis types.GenesisDoc) { + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis tmtypes.GenesisDoc) { genesis = testapp.DefaultGenesis() testapp.UpdateGenesisDocWithAppStateForModule( &genesis,