Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CT-856] Add order replacement to fix vault causing orderbook flickering #1602

Merged
merged 57 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
2196eb5
order replacement messages for vault
chenyaoy May 29, 2024
53f021b
fix tests
chenyaoy May 29, 2024
feddcca
fix
chenyaoy May 29, 2024
e087c19
publish protocol image
chenyaoy May 29, 2024
557be43
Merge branch 'main' into chenyao/order-replacement-messages-for-vault
chenyaoy Jun 3, 2024
751bae7
Merge branch 'main' into chenyao/order-replacement-messages-for-vault
chenyaoy Jun 3, 2024
5f3e64e
clean up test
chenyaoy Jun 3, 2024
b534c21
Merge branch 'main' into chenyao/order-replacement-messages-for-vault
chenyaoy Jun 3, 2024
92e19d3
Add new metric
chenyaoy Jun 4, 2024
078b321
Add metric
chenyaoy Jun 4, 2024
83cc892
temp metrics change
chenyaoy Jun 4, 2024
fc81475
Revert "temp metrics change"
chenyaoy Jun 4, 2024
a35fe85
get cancelled order placement to get correct subticks
chenyaoy Jun 5, 2024
ff5df30
Merge branch 'main' into chenyao/order-replacement-messages-for-vault
chenyaoy Jun 5, 2024
a114fac
fix
chenyaoy Jun 5, 2024
0ae3f40
Fix order removal reason
chenyaoy Jun 5, 2024
adddd22
change back vault order ID
chenyaoy Jun 6, 2024
be7e698
send replacement order message from vault
chenyaoy Jun 6, 2024
1f18e20
update proto
chenyaoy Jun 6, 2024
31acb76
update proto
chenyaoy Jun 6, 2024
176d37c
ender stateful order replacement handler
chenyaoy Jun 6, 2024
062ff0d
update sql handler
chenyaoy Jun 6, 2024
c43565a
vulcan order replace handler
chenyaoy Jun 6, 2024
4d13eb5
comment
chenyaoy Jun 6, 2024
c1388bb
lint proto
chenyaoy Jun 6, 2024
23f0c10
fix sql script
chenyaoy Jun 6, 2024
6ea9d26
fix test
chenyaoy Jun 6, 2024
6f0ab7d
fix
chenyaoy Jun 6, 2024
66a55c9
clean
chenyaoy Jun 6, 2024
cf1f888
revert
chenyaoy Jun 6, 2024
fe03736
refactor
chenyaoy Jun 6, 2024
7a2afea
remove
chenyaoy Jun 6, 2024
8dee4c9
add tests
chenyaoy Jun 6, 2024
53fe081
fix test
chenyaoy Jun 6, 2024
915e31f
lint
chenyaoy Jun 6, 2024
e4bb2dd
undo
chenyaoy Jun 6, 2024
15061e2
fix
chenyaoy Jun 6, 2024
25a445e
compare subticks instead of price
chenyaoy Jun 6, 2024
1489677
add log
chenyaoy Jun 10, 2024
b332cd9
Merge branch 'main' into chenyao/order-replacement-messages-for-vault
chenyaoy Jun 10, 2024
e99086c
fix comparison
chenyaoy Jun 10, 2024
366e7de
Merge branch 'main' into chenyao/order-replacement-messages-for-vault
chenyaoy Jun 11, 2024
747ffaa
Merge branch 'main' into chenyao/order-replacement-messages-for-vault
chenyaoy Jun 11, 2024
4f0415e
address comments
chenyaoy Jun 11, 2024
a61803a
Merge branch 'main' into chenyao/order-replacement-messages-for-vault
chenyaoy Jun 14, 2024
aff6855
ci
chenyaoy Jun 14, 2024
4ca5ed4
fix
chenyaoy Jun 14, 2024
060ba2e
add vulcan test
chenyaoy Jun 14, 2024
27641a4
address comment
chenyaoy Jun 14, 2024
2993902
lint
chenyaoy Jun 14, 2024
e2f6aa1
address comments
chenyaoy Jun 14, 2024
ee19948
fix
chenyaoy Jun 14, 2024
dbb28d8
simplify
chenyaoy Jun 18, 2024
5c6f5ba
Test vault indexer events
chenyaoy Jun 19, 2024
2be3558
lint
chenyaoy Jun 19, 2024
61022c6
Merge branch 'main' into chenyao/order-replacement-messages-for-vault
chenyaoy Jun 19, 2024
4e6c761
pull out indexer changes
chenyaoy Jun 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ describe('order-replace-handler', () => {
...replacementOrderGoodTilBlockTime,
subticks: replacementOrderGoodTilBlockTime.subticks.mul(2),
};

const replacedOrder: RedisOrder = redisPackage.convertToRedisOrder(
replacementOrder,
testConstants.defaultPerpetualMarket,
Expand Down Expand Up @@ -185,6 +184,7 @@ describe('order-replace-handler', () => {
jest.spyOn(CanceledOrdersCache, 'removeOrderFromCaches');
jest.spyOn(stats, 'increment');
jest.spyOn(redisPackage, 'placeOrder');
jest.spyOn(redisPackage, 'removeOrder');
jest.spyOn(logger, 'error');
jest.spyOn(logger, 'info');
});
Expand Down Expand Up @@ -559,6 +559,22 @@ describe('order-replace-handler', () => {
},
);

it('replaces order successfully where old order does not exist', async () => {
synchronizeWrapBackgroundTask(wrapBackgroundTask);
const producerSendSpy: jest.SpyInstance = jest.spyOn(producer, 'send').mockReturnThis();
await onMessage(replacementMessage);

expect(redisPackage.removeOrder).toHaveBeenCalled();
expect(redisPackage.placeOrder).toHaveBeenCalled();
expect(logger.info).toHaveBeenCalledWith(expect.objectContaining({
at: 'OrderReplaceHandler#handle',
message: 'Old order not found in cache',
oldOrderId: redisTestConstants.defaultOrderId,
}));
expect(OrderbookLevelsCache.updatePriceLevel).not.toHaveBeenCalled();
expectWebsocketMessagesNotSent(producerSendSpy);
});

it.each([
[
'missing order',
Expand Down Expand Up @@ -675,9 +691,11 @@ async function checkOrderReplace(
placedSubaccountId: string,
expectedOrder: RedisOrder,
): Promise<void> {
expect(redisPackage.removeOrder).toHaveBeenCalled();
const oldRedisOrder: RedisOrder | null = await OrdersCache.getOrder(oldOrderId, client);
expect(oldRedisOrder).toBeNull();

expect(redisPackage.placeOrder).toHaveBeenCalled();
const newRedisOrder: RedisOrder | null = await OrdersCache.getOrder(placedOrderId, client);
const orderIdsForSubaccount: string[] = await SubaccountOrderIdsCache.getOrderIdsForSubaccount(
placedSubaccountId,
Expand Down
11 changes: 11 additions & 0 deletions indexer/services/vulcan/src/handlers/order-replace-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ export class OrderReplaceHandler extends Handler {
/* Remove old order */
const removeOrderResult: RemoveOrderResult = await this.removeOldOrder(oldOrderId);

/* We don't want to fail if old order is not found (new order should still be placed),
so log and track metric */
if (!removeOrderResult.removed) {
logger.info({
chenyaoy marked this conversation as resolved.
Show resolved Hide resolved
at: 'OrderReplaceHandler#handle',
message: 'Old order not found in cache',
oldOrderId,
});
stats.increment(`${config.SERVICE_NAME}.replace_order_handler.old_order_not_found_in_cache`, 1);
}

/* Place new order */
const order: IndexerOrder = orderReplace.order!;
const placementStatus: OrderPlaceV1_OrderPlacementStatus = orderReplace.placementStatus;
Expand Down
1 change: 1 addition & 0 deletions protocol/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,7 @@ func New(
app.PricesKeeper,
app.SendingKeeper,
app.SubaccountsKeeper,
app.IndexerEventManager,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add documentation for IndexerEventManager.

The addition of IndexerEventManager to the App struct is significant, especially given its role in event management. It would be beneficial for maintainability and clarity to add a comment explaining its purpose and usage in the context of the application.

[]string{
lib.GovModuleAddress.String(),
delaymsgmoduletypes.ModuleAddress.String(),
Expand Down
17 changes: 17 additions & 0 deletions protocol/indexer/events/stateful_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ func NewLongTermOrderPlacementEvent(
}
}

func NewLongTermOrderReplacementEvent(
oldOrderId clobtypes.OrderId,
order clobtypes.Order,
) *StatefulOrderEventV1 {
oldIndexerOrderId := v1.OrderIdToIndexerOrderId(oldOrderId)
indexerOrder := v1.OrderToIndexerOrder(order)
orderReplace := StatefulOrderEventV1_LongTermOrderReplacementV1{
OldOrderId: &oldIndexerOrderId,
Order: &indexerOrder,
}
return &StatefulOrderEventV1{
Event: &StatefulOrderEventV1_OrderReplacement{
OrderReplacement: &orderReplace,
},
}
}

func NewStatefulOrderRemovalEvent(
removedOrderId clobtypes.OrderId,
reason sharedtypes.OrderRemovalReason,
Expand Down
8 changes: 4 additions & 4 deletions protocol/mocks/ClobKeeper.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions protocol/testutil/keeper/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func createVaultKeeper(
&mocks.PricesKeeper{},
&mocks.SendingKeeper{},
&mocks.SubaccountsKeeper{},
&mocks.IndexerEventManager{},
[]string{
lib.GovModuleAddress.String(),
delaymsgtypes.ModuleAddress.String(),
Expand Down
25 changes: 14 additions & 11 deletions protocol/x/clob/keeper/msg_server_cancel_orders.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (k msgServer) CancelOrder(
) (resp *types.MsgCancelOrderResponse, err error) {
ctx := lib.UnwrapSDKContext(goCtx, types.ModuleName)

if err := k.Keeper.HandleMsgCancelOrder(ctx, msg); err != nil {
if err := k.Keeper.HandleMsgCancelOrder(ctx, msg, false); err != nil {
return nil, err
}

Expand All @@ -37,6 +37,7 @@ func (k msgServer) CancelOrder(
func (k Keeper) HandleMsgCancelOrder(
ctx sdk.Context,
msg *types.MsgCancelOrder,
isInternalOrder bool,
chenyaoy marked this conversation as resolved.
Show resolved Hide resolved
) (err error) {
lib.AssertDeliverTxMode(ctx)

Expand Down Expand Up @@ -110,17 +111,19 @@ func (k Keeper) HandleMsgCancelOrder(
k.MustSetProcessProposerMatchesEvents(ctx, processProposerMatchesEvents)

// 4. Add the relevant on-chain Indexer event for the cancellation.
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewStatefulOrderRemovalEvent(
msg.OrderId,
indexershared.OrderRemovalReason_ORDER_REMOVAL_REASON_USER_CANCELED,
if !isInternalOrder { // vault order indexer event logic is handled elsewhere
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewStatefulOrderRemovalEvent(
msg.OrderId,
indexershared.OrderRemovalReason_ORDER_REMOVAL_REASON_USER_CANCELED,
),
),
),
)
)
}

return nil
}
41 changes: 23 additions & 18 deletions protocol/x/clob/keeper/msg_server_place_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func (k Keeper) HandleMsgPlaceOrder(
order,
)
}

Copy link
Contributor

@coderabbitai coderabbitai bot Jun 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for handling internal and external orders is well-implemented. Consider adding more detailed logging for the steps involved, especially around the conditions that lead to early returns.

Also applies to: 118-146

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove new line

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dydxwill, the new line you mentioned has been noted for removal. If there's anything else you need, feel free to let me know!

removedOrderIds := lib.UniqueSliceToSet(processProposerMatchesEvents.RemovedStatefulOrderIds)
if _, found := removedOrderIds[order.GetOrderId()]; found {
return errorsmod.Wrapf(
Expand All @@ -114,31 +115,35 @@ func (k Keeper) HandleMsgPlaceOrder(

// 4. Emit the new order placement indexer event.
if order.IsConditionalOrder() {
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewConditionalOrderPlacementEvent(
order,
if !isInternalOrder { // vault order indexer event logic is handled elsewhere
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewConditionalOrderPlacementEvent(
order,
),
),
),
)
)
}
processProposerMatchesEvents.PlacedConditionalOrderIds = append(
processProposerMatchesEvents.PlacedConditionalOrderIds,
order.OrderId,
)
} else {
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewLongTermOrderPlacementEvent(
order,
if !isInternalOrder { // vault order indexer event logic is handled elsewhere
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewLongTermOrderPlacementEvent(
order,
),
),
),
)
)
}
processProposerMatchesEvents.PlacedLongTermOrderIds = append(
processProposerMatchesEvents.PlacedLongTermOrderIds,
order.OrderId,
Expand Down
1 change: 1 addition & 0 deletions protocol/x/clob/types/clob_keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type ClobKeeper interface {
HandleMsgCancelOrder(
ctx sdk.Context,
msg *MsgCancelOrder,
isInternalOrder bool,
) (err error)
HandleMsgPlaceOrder(
ctx sdk.Context,
Expand Down
40 changes: 24 additions & 16 deletions protocol/x/vault/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ import (
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager"
"github.com/dydxprotocol/v4-chain/protocol/lib"
"github.com/dydxprotocol/v4-chain/protocol/x/vault/types"
)

type (
Keeper struct {
cdc codec.BinaryCodec
storeKey storetypes.StoreKey
clobKeeper types.ClobKeeper
perpetualsKeeper types.PerpetualsKeeper
pricesKeeper types.PricesKeeper
sendingKeeper types.SendingKeeper
subaccountsKeeper types.SubaccountsKeeper
authorities map[string]struct{}
cdc codec.BinaryCodec
storeKey storetypes.StoreKey
clobKeeper types.ClobKeeper
perpetualsKeeper types.PerpetualsKeeper
pricesKeeper types.PricesKeeper
sendingKeeper types.SendingKeeper
subaccountsKeeper types.SubaccountsKeeper
indexerEventManager indexer_manager.IndexerEventManager
authorities map[string]struct{}
}
)

Expand All @@ -32,20 +34,26 @@ func NewKeeper(
pricesKeeper types.PricesKeeper,
sendingKeeper types.SendingKeeper,
subaccountsKeeper types.SubaccountsKeeper,
indexerEventManager indexer_manager.IndexerEventManager,
authorities []string,
) *Keeper {
return &Keeper{
cdc: cdc,
storeKey: storeKey,
clobKeeper: clobKeeper,
perpetualsKeeper: perpetualsKeeper,
pricesKeeper: pricesKeeper,
sendingKeeper: sendingKeeper,
subaccountsKeeper: subaccountsKeeper,
authorities: lib.UniqueSliceToSet(authorities),
cdc: cdc,
storeKey: storeKey,
clobKeeper: clobKeeper,
perpetualsKeeper: perpetualsKeeper,
pricesKeeper: pricesKeeper,
sendingKeeper: sendingKeeper,
subaccountsKeeper: subaccountsKeeper,
indexerEventManager: indexerEventManager,
authorities: lib.UniqueSliceToSet(authorities),
}
}

func (k Keeper) GetIndexerEventManager() indexer_manager.IndexerEventManager {
return k.indexerEventManager
}

func (k Keeper) HasAuthority(authority string) bool {
_, ok := k.authorities[authority]
return ok
Expand Down
39 changes: 35 additions & 4 deletions protocol/x/vault/keeper/orders.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events"
"github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager"
"github.com/dydxprotocol/v4-chain/protocol/lib"
"github.com/dydxprotocol/v4-chain/protocol/lib/log"
"github.com/dydxprotocol/v4-chain/protocol/lib/metrics"
Expand All @@ -25,6 +27,7 @@ func (k Keeper) RefreshAllVaultOrders(ctx sdk.Context) {
defer totalSharesIterator.Close()
for ; totalSharesIterator.Valid(); totalSharesIterator.Next() {
vaultId, err := types.GetVaultIdFromStateKey(totalSharesIterator.Key())

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding metrics or more detailed logging for vaults that are skipped.

This could provide better visibility into the operation of this function, especially when diagnosing issues in production environments.

if err != nil {
log.ErrorLogWithError(ctx, "Failed to get vault ID from state key", err)
continue
Expand Down Expand Up @@ -85,7 +88,7 @@ func (k Keeper) RefreshVaultClobOrders(ctx sdk.Context, vaultId types.VaultId) (
err := k.clobKeeper.HandleMsgCancelOrder(ctx, clobtypes.NewMsgCancelOrderStateful(
order.OrderId,
uint32(ctx.BlockTime().Unix())+orderExpirationSeconds,
))
), true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for refreshing CLOB orders is complex but correctly handles the cancellation and placement of orders. Consider adding more detailed logging for the steps involved, especially around the conditions that lead to early returns.

Also applies to: 107-159

if err != nil {
log.ErrorLogWithError(ctx, "Failed to cancel order", err, "order", order, "vaultId", vaultId)
}
Expand All @@ -95,24 +98,52 @@ func (k Keeper) RefreshVaultClobOrders(ctx sdk.Context, vaultId types.VaultId) (
)
}
}

// Place new CLOB orders.
ordersToPlace, err := k.GetVaultClobOrders(ctx, vaultId)
if err != nil {
log.ErrorLogWithError(ctx, "Failed to get vault clob orders to place", err, "vaultId", vaultId)
return err
}
for _, order := range ordersToPlace {

for i, order := range ordersToPlace {
err := k.PlaceVaultClobOrder(ctx, order)
if err != nil {
log.ErrorLogWithError(ctx, "Failed to place order", err, "order", order, "vaultId", vaultId)
}

vaultId.IncrCounterWithLabels(
metrics.VaultPlaceOrder,
metrics.GetLabelForBoolValue(metrics.Success, err == nil),
)
}

// Send indexer messages. We expect ordersToCancel and ordersToPlace to have the same length
// and the order to place at each index to be a replacement of the order to cancel at the same index.
replacedOrder := ordersToCancel[i]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed above, might be good to check whether i is a valid index

if replacedOrder == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what would happen in ender/vulcan/socks if we simplify this clause to always send OrderReplacementEvent?

k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewLongTermOrderPlacementEvent(
*order,
),
),
)
} else {
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewLongTermOrderReplacementEvent(
replacedOrder.OrderId,
*order,
),
),
)
}
}
return nil
}

Expand Down
Loading
Loading