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 8 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
1 change: 1 addition & 0 deletions .github/workflows/protocol-build-and-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on: # yamllint disable-line rule:truthy
- main
- 'release/protocol/v[0-9]+.[0-9]+.x' # e.g. release/protocol/v0.1.x
- 'release/protocol/v[0-9]+.x' # e.g. release/protocol/v1.x
- 'chenyao/order-replacement-messages-for-vault'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

TODO: remove


jobs:
build-and-push-dev:
Expand Down
1 change: 1 addition & 0 deletions protocol/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,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
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 @@ -111,17 +112,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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

add a comment explaining why you are special casing here, and note that vault order indexer event logic is handled elsewhere

Copy link
Contributor

Choose a reason for hiding this comment

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

add cmt

k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewStatefulOrderRemovalEvent(
msg.OrderId,
indexershared.OrderRemovalReason_ORDER_REMOVAL_REASON_USER_CANCELED,
),
),
),
)
)
}

return nil
}
56 changes: 31 additions & 25 deletions protocol/x/clob/keeper/msg_server_place_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ func (k Keeper) HandleMsgPlaceOrder(

// 2. Return an error if an associated cancellation or removal already exists in the current block.
processProposerMatchesEvents := k.GetProcessProposerMatchesEvents(ctx)
cancelledOrderIds := lib.UniqueSliceToSet(processProposerMatchesEvents.PlacedStatefulCancellationOrderIds)
if _, found := cancelledOrderIds[order.GetOrderId()]; found {
return errorsmod.Wrapf(
types.ErrStatefulOrderPreviouslyCancelled,
"PlaceOrder: order (%+v)",
order,
)
if !isInternalOrder { // If vault order, we allow the order to replace a cancelled order with the same order ID
cancelledOrderIds := lib.UniqueSliceToSet(processProposerMatchesEvents.PlacedStatefulCancellationOrderIds)
if _, found := cancelledOrderIds[order.GetOrderId()]; found {
return errorsmod.Wrapf(
types.ErrStatefulOrderPreviouslyCancelled,
"PlaceOrder: order (%+v)",
order,
)
}
}
removedOrderIds := lib.UniqueSliceToSet(processProposerMatchesEvents.RemovedStatefulOrderIds)
if _, found := removedOrderIds[order.GetOrderId()]; found {
Expand All @@ -115,31 +117,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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

is this logic right? we never emit any order placements for vault orders?

Copy link
Contributor

Choose a reason for hiding this comment

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

by emitting a replacement event (instead of placement), does indexer / front-end behavior change in terms of storing / displaying vault orders?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dydxwill I think it would make sense here to check if ordersToCancel[i] is null (the first time vault places orders, nothing to cancel yet), then send an order place message instead of an order replacement

@tqin7 The goal here is to not have it change other than remove the flickering

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tqin7 would ordersToCancel[i] ever be nil here or will k.GetVaultClobOrders( ctx.WithBlockHeight(ctx.BlockHeight()-1), vaultId, ) always return something even if it's the first block?

Copy link
Contributor

Choose a reason for hiding this comment

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

ordersToCancel can be empty (for example if vault equity is <= 0 or clob pair doesn't exist). Good to check whether ordersToCancel is empty

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 {
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewLongTermOrderPlacementEvent(
order,
),
),
),
)
)
}
processProposerMatchesEvents.PlacedLongTermOrderIds = append(
processProposerMatchesEvents.PlacedLongTermOrderIds,
order.OrderId,
Expand Down
3 changes: 1 addition & 2 deletions protocol/x/clob/keeper/msg_server_place_order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,13 +435,12 @@ func TestHandleMsgPlaceOrder(t *testing.T) {
removalExists: false,
equityTierLimitExists: true,
},
"Error - Place an Internal Order, Order Already Cancelled": {
"Success - Place an Internal Order, Order Already Cancelled": {
isInternalOrder: true,
assetQuantums: -1_000_000_000,
cancellationExists: true,
removalExists: false,
equityTierLimitExists: true,
expectedError: types.ErrStatefulOrderPreviouslyCancelled,
},
"Error - Place an Internal Order, Order Already Removed": {
isInternalOrder: true,
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
61 changes: 48 additions & 13 deletions protocol/x/vault/keeper/orders.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ 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"
indexershared "github.com/dydxprotocol/v4-chain/protocol/indexer/shared/types"
"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 @@ -24,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 @@ -84,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 @@ -97,11 +101,12 @@ 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)
Expand All @@ -110,8 +115,46 @@ func (k Keeper) RefreshVaultClobOrders(ctx sdk.Context, vaultId types.VaultId) (
metrics.VaultPlaceOrder,
metrics.GetLabelForBoolValue(metrics.Success, err == nil),
)
}

// Send indexer messages.
// If the price of the old and new orders are different, send a cancel and a place message
// Otherwise, send an order place message only.
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.Subticks != order.Subticks {
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeStatefulOrder,
indexerevents.StatefulOrderEventVersion,
indexer_manager.GetBytes(
indexerevents.NewStatefulOrderRemovalEvent(
replacedOrder.OrderId,
indexershared.OrderRemovalReason_ORDER_REMOVAL_REASON_REPLACED,
),
),
)
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.NewLongTermOrderPlacementEvent(
*order,
),
),
)
}
}
return nil
}

Expand Down Expand Up @@ -335,11 +378,6 @@ func (k Keeper) GetVaultClobOrders(
// GetVaultClobOrderClientId returns the client ID for a CLOB order where
// - 1st bit is `side-1` (subtract 1 as buy_side = 1, sell_side = 2)
//
// - 2nd bit is `block height % 2`
// - block height bit alternates between 0 and 1 to ensure that client IDs
// are different in two consecutive blocks (otherwise, order placement would
// fail because the same order IDs are already marked for cancellation)
//
// - next 8 bits are `layer`
func (k Keeper) GetVaultClobOrderClientId(
ctx sdk.Context,
Expand All @@ -349,12 +387,9 @@ func (k Keeper) GetVaultClobOrderClientId(
sideBit := uint32(side - 1)
sideBit <<= 31

blockHeightBit := uint32(ctx.BlockHeight() % 2)
blockHeightBit <<= 30

layerBits := uint32(layer) << 22
layerBits := uint32(layer) << 23

return sideBit | blockHeightBit | layerBits
return sideBit | layerBits
}

// PlaceVaultClobOrder places a vault CLOB order as an order internal to the protocol,
Expand Down
Loading
Loading