From c83475c1b3cc5deec85f5cfdf1c319f06868fa1a Mon Sep 17 00:00:00 2001 From: Luke Rogerson Date: Sat, 9 Dec 2023 12:50:51 +0000 Subject: [PATCH] Store filled orders WIP --- data/redisrepo/cancel_orders_for_user.go | 2 +- data/redisrepo/cancel_orders_for_user_test.go | 20 ++-- data/redisrepo/get_orders_for_user.go | 2 +- data/redisrepo/get_orders_for_user_test.go | 10 +- data/redisrepo/remove_order.go | 2 +- data/redisrepo/remove_order_test.go | 1 + data/redisrepo/store_filled_orders.go | 57 ++++++++++ data/redisrepo/store_filled_orders_test.go | 104 ++++++++++++++++++ .../{store_order.go => store_open_order.go} | 84 +++++++------- ...order_test.go => store_open_order_test.go} | 22 ++-- data/redisrepo/utils.go | 9 +- data/store/store.go | 5 +- mocks/service.go | 8 +- models/order.go | 14 +++ scripts/redis-repo/main.go | 2 +- service/auction.go | 14 +-- service/create_order.go | 2 +- 17 files changed, 274 insertions(+), 84 deletions(-) create mode 100644 data/redisrepo/store_filled_orders.go create mode 100644 data/redisrepo/store_filled_orders_test.go rename data/redisrepo/{store_order.go => store_open_order.go} (68%) rename data/redisrepo/{store_order_test.go => store_open_order_test.go} (74%) diff --git a/data/redisrepo/cancel_orders_for_user.go b/data/redisrepo/cancel_orders_for_user.go index 0eaf475..7b649d6 100644 --- a/data/redisrepo/cancel_orders_for_user.go +++ b/data/redisrepo/cancel_orders_for_user.go @@ -11,7 +11,7 @@ import ( ) func (r *redisRepository) CancelOrdersForUser(ctx context.Context, userId uuid.UUID) ([]uuid.UUID, error) { - userOrdersKey := CreateUserOrdersKey(userId) + userOrdersKey := CreateUserOpenOrdersKey(userId) // Fetch all order IDs for the user orderIdStrs, err := r.client.ZRange(ctx, userOrdersKey, 0, -1).Result() diff --git a/data/redisrepo/cancel_orders_for_user_test.go b/data/redisrepo/cancel_orders_for_user_test.go index a32aead..a464869 100644 --- a/data/redisrepo/cancel_orders_for_user_test.go +++ b/data/redisrepo/cancel_orders_for_user_test.go @@ -23,13 +23,13 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) { client: db, } - mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String()}) + mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String()}) mock.ExpectHGetAll(CreateOrderIDKey(mocks.OrderId)).SetVal(mocks.Order.OrderToMap()) mock.ExpectTxPipeline() mock.ExpectDel(CreateClientOIDKey(mocks.ClientOId)).SetVal(1) mock.ExpectZRem(CreateBuySidePricesKey(mocks.Symbol), mocks.OrderId.String()).SetVal(1) mock.ExpectDel(CreateOrderIDKey(mocks.OrderId)).SetVal(1) - mock.ExpectDel(CreateUserOrdersKey(mocks.UserId)).SetVal(1) + mock.ExpectDel(CreateUserOpenOrdersKey(mocks.UserId)).SetVal(1) mock.ExpectTxPipelineExec() orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId) @@ -45,7 +45,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) { client: db, } - mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String(), orderTwo.Id.String()}) + mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String(), orderTwo.Id.String()}) mock.ExpectHGetAll(CreateOrderIDKey(mocks.OrderId)).SetVal(mocks.Order.OrderToMap()) mock.ExpectHGetAll(CreateOrderIDKey(orderTwo.Id)).SetVal(orderTwo.OrderToMap()) mock.ExpectTxPipeline() @@ -55,7 +55,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) { mock.ExpectDel(CreateClientOIDKey(orderTwo.ClientOId)).SetVal(1) mock.ExpectZRem(CreateSellSidePricesKey(orderTwo.Symbol), orderTwo.Id.String()).SetVal(1) mock.ExpectDel(CreateOrderIDKey(orderTwo.Id)).SetVal(1) - mock.ExpectDel(CreateUserOrdersKey(mocks.UserId)).SetVal(1) + mock.ExpectDel(CreateUserOpenOrdersKey(mocks.UserId)).SetVal(1) mock.ExpectTxPipelineExec() orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId) @@ -72,7 +72,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) { client: db, } - mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetErr(assert.AnError) + mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetErr(assert.AnError) orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId) assert.Empty(t, orderIds) @@ -86,7 +86,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) { client: db, } - mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{}) + mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{}) orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId) assert.Empty(t, orderIds) @@ -100,7 +100,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) { client: db, } - mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{"invalid"}) + mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{"invalid"}) _, err := repo.CancelOrdersForUser(ctx, mocks.UserId) assert.ErrorContains(t, err, "failed to parse order ID: invalid UUID length: 7") }) @@ -112,7 +112,7 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) { client: db, } - mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String(), orderTwo.Id.String()}) + mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String(), orderTwo.Id.String()}) mock.ExpectHGetAll(CreateOrderIDKey(orderTwo.Id)).SetErr(models.ErrNotFound) // order not found - break out of loop iteration orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId) @@ -127,13 +127,13 @@ func TestRedisRepository_CancelAllOrdersForUser(t *testing.T) { client: db, } - mock.ExpectZRange(CreateUserOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String()}) + mock.ExpectZRange(CreateUserOpenOrdersKey(mocks.UserId), 0, -1).SetVal([]string{mocks.OrderId.String()}) mock.ExpectHGetAll(CreateOrderIDKey(mocks.OrderId)).SetVal(mocks.Order.OrderToMap()) mock.ExpectTxPipeline() mock.ExpectDel(CreateClientOIDKey(mocks.ClientOId)).SetVal(1) mock.ExpectZRem(CreateBuySidePricesKey(mocks.Symbol), mocks.OrderId.String()).SetVal(1) mock.ExpectDel(CreateOrderIDKey(mocks.OrderId)).SetVal(1) - mock.ExpectDel(CreateUserOrdersKey(mocks.UserId)).SetErr(assert.AnError) + mock.ExpectDel(CreateUserOpenOrdersKey(mocks.UserId)).SetErr(assert.AnError) mock.ExpectTxPipelineExec().SetErr(assert.AnError) orderIds, err := repo.CancelOrdersForUser(ctx, mocks.UserId) diff --git a/data/redisrepo/get_orders_for_user.go b/data/redisrepo/get_orders_for_user.go index 21c7909..5ec584c 100644 --- a/data/redisrepo/get_orders_for_user.go +++ b/data/redisrepo/get_orders_for_user.go @@ -18,7 +18,7 @@ import ( func (r *redisRepository) GetOrdersForUser(ctx context.Context, userId uuid.UUID) (orders []models.Order, totalOrders int, err error) { start, stop := utils.PaginationBounds(ctx) - key := CreateUserOrdersKey(userId) + key := CreateUserOpenOrdersKey(userId) count, err := r.client.ZCard(ctx, key).Result() diff --git a/data/redisrepo/get_orders_for_user_test.go b/data/redisrepo/get_orders_for_user_test.go index 7435c18..dc3e3df 100644 --- a/data/redisrepo/get_orders_for_user_test.go +++ b/data/redisrepo/get_orders_for_user_test.go @@ -31,7 +31,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) { ctx := mocks.AddPaginationToCtx(1, 10) - key := CreateUserOrdersKey(userId) + key := CreateUserOpenOrdersKey(userId) mock.ExpectZCard(key).SetVal(1) mock.ExpectZRange(key, int64(0), int64(10)).SetVal([]string{"00000000-0000-0000-0000-000000000001"}) @@ -56,7 +56,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) { ctx := mocks.AddPaginationToCtx(1, 10) - key := CreateUserOrdersKey(userId) + key := CreateUserOpenOrdersKey(userId) mock.ExpectZCard(key).SetErr(assert.AnError) @@ -77,7 +77,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) { ctx := mocks.AddPaginationToCtx(1, 10) - key := CreateUserOrdersKey(userId) + key := CreateUserOpenOrdersKey(userId) mock.ExpectZCard(key).SetVal(1) mock.ExpectZRange(key, int64(0), int64(10)).SetErr(assert.AnError) @@ -99,7 +99,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) { ctx := mocks.AddPaginationToCtx(1, 10) - key := CreateUserOrdersKey(userId) + key := CreateUserOpenOrdersKey(userId) mock.ExpectZCard(key).SetVal(1) mock.ExpectZRange(key, int64(0), int64(10)).SetVal([]string{"bad-uuid"}) @@ -121,7 +121,7 @@ func TestRedisRepository_GetOrdersForUser(t *testing.T) { ctx := mocks.AddPaginationToCtx(1, 10) - key := CreateUserOrdersKey(userId) + key := CreateUserOpenOrdersKey(userId) mock.ExpectZCard(key).SetVal(1) mock.ExpectZRange(key, int64(0), int64(10)).SetVal([]string{"00000000-0000-0000-0000-000000000001"}) diff --git a/data/redisrepo/remove_order.go b/data/redisrepo/remove_order.go index 50c55b0..11f44b1 100644 --- a/data/redisrepo/remove_order.go +++ b/data/redisrepo/remove_order.go @@ -28,7 +28,7 @@ func (r *redisRepository) RemoveOrder(ctx context.Context, order models.Order) e transaction.ZRem(ctx, sellPricesKey, order.Id.String()) } - userOrdersKey := CreateUserOrdersKey(order.UserId) + userOrdersKey := CreateUserOpenOrdersKey(order.UserId) transaction.ZRem(ctx, userOrdersKey, order.Id.String()) clientOIdKey := CreateClientOIDKey(order.ClientOId) diff --git a/data/redisrepo/remove_order_test.go b/data/redisrepo/remove_order_test.go index 3dbb83c..a05c46b 100644 --- a/data/redisrepo/remove_order_test.go +++ b/data/redisrepo/remove_order_test.go @@ -14,6 +14,7 @@ import ( var ctx = context.Background() var orderId = uuid.MustParse("00000000-0000-0000-0000-000000000001") var clientOId = uuid.MustParse("00000000-0000-0000-0000-000000000002") +var userId = uuid.MustParse("00000000-0000-0000-0000-000000000003") var size, _ = decimal.NewFromString("10000324.123456789") var order = models.Order{ diff --git a/data/redisrepo/store_filled_orders.go b/data/redisrepo/store_filled_orders.go new file mode 100644 index 0000000..ca28004 --- /dev/null +++ b/data/redisrepo/store_filled_orders.go @@ -0,0 +1,57 @@ +package redisrepo + +import ( + "context" + "fmt" + + "github.com/orbs-network/order-book/models" + "github.com/orbs-network/order-book/utils/logger" + "github.com/orbs-network/order-book/utils/logger/logctx" + "github.com/redis/go-redis/v9" +) + +// This should be used to store FILLED orders in Redis. +// +// `StoreOpenOrder` or `StoreOpenOrders` should be used to store unfilled or partially filled orders. +func (r *redisRepository) StoreFilledOrders(ctx context.Context, orders []models.Order) error { + transaction := r.client.TxPipeline() + + for _, order := range orders { + err := storeFilledOrderTx(ctx, transaction, order) + if err != nil { + return err + } + + } + _, err := transaction.Exec(ctx) + if err != nil { + logctx.Error(ctx, "failed to store filled orders in Redis", logger.Error(err), logger.Strings("orderIds", models.OrderIdsToStrings(ctx, &orders))) + return fmt.Errorf("failed to store filled orders in Redis: %v", err) + } + return nil +} + +func storeFilledOrderTx(ctx context.Context, transaction redis.Pipeliner, order models.Order) error { + // 1. Remove the order from the user's open orders set + userOrdersKey := CreateUserOpenOrdersKey(order.UserId) + transaction.ZRem(ctx, userOrdersKey, order.Id.String()) + + // 2. Store the order in the filled orders set + userFilledOrdersKey := CreateUserFilledOrdersKey(order.UserId) + userFilledOrdersScore := float64(order.Timestamp.UTC().UnixNano()) + transaction.ZAdd(ctx, userFilledOrdersKey, redis.Z{ + Score: userFilledOrdersScore, + Member: order.Id.String(), + }) + + // 3. Remove the order from the buy/sell prices set for that pair + buyPricesKey := CreateBuySidePricesKey(order.Symbol) + sellPricesKey := CreateSellSidePricesKey(order.Symbol) + if order.Side == models.BUY { + transaction.ZRem(ctx, buyPricesKey, order.Id.String()) + } else { + transaction.ZRem(ctx, sellPricesKey, order.Id.String()) + } + + return nil +} diff --git a/data/redisrepo/store_filled_orders_test.go b/data/redisrepo/store_filled_orders_test.go new file mode 100644 index 0000000..e79e601 --- /dev/null +++ b/data/redisrepo/store_filled_orders_test.go @@ -0,0 +1,104 @@ +package redisrepo + +import ( + "testing" + "time" + + "github.com/go-redis/redismock/v9" + "github.com/orbs-network/order-book/models" + "github.com/redis/go-redis/v9" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" +) + +func TestRedisRepository_StoreFilledOrders(t *testing.T) { + timestamp := time.Date(2023, 10, 10, 12, 0, 0, 0, time.UTC) + + t.Run("store filled order success (buy side) - should remove order from user open orders set, add order to user filled orders set, remove order from buy prices sorted set", func(t *testing.T) { + var buyOrder = models.Order{ + Id: orderId, + ClientOId: clientOId, + Price: price, + Size: size, + Symbol: symbol, + Side: models.BUY, + Timestamp: timestamp, + SizePending: decimal.Zero, + SizeFilled: size, + UserId: userId, + } + + db, mock := redismock.NewClientMock() + + repo := &redisRepository{ + client: db, + } + + mock.ExpectTxPipeline() + mock.ExpectZRem(CreateUserOpenOrdersKey(buyOrder.UserId), buyOrder.Id.String()).SetVal(1) + mock.ExpectZAdd(CreateUserFilledOrdersKey(buyOrder.UserId), redis.Z{ + Score: float64(timestamp.UnixNano()), + Member: buyOrder.Id.String(), + }).SetVal(1) + mock.ExpectZRem(CreateBuySidePricesKey(buyOrder.Symbol), buyOrder.Id.String()).SetVal(1) + mock.ExpectTxPipelineExec() + + err := repo.StoreFilledOrders(ctx, []models.Order{buyOrder}) + + assert.NoError(t, err, "should not return error") + }) + + t.Run("store filled order success (sell side) - should remove order from user open orders set, add order to user filled orders set, remove order from sell prices sorted set", func(t *testing.T) { + var sellOrder = models.Order{ + Id: orderId, + ClientOId: clientOId, + Price: price, + Size: size, + Symbol: symbol, + Side: models.SELL, + Timestamp: timestamp, + SizePending: decimal.Zero, + SizeFilled: size, + UserId: userId, + } + + db, mock := redismock.NewClientMock() + + repo := &redisRepository{ + client: db, + } + + mock.ExpectTxPipeline() + mock.ExpectZRem(CreateUserOpenOrdersKey(sellOrder.UserId), sellOrder.Id.String()).SetVal(1) + mock.ExpectZAdd(CreateUserFilledOrdersKey(sellOrder.UserId), redis.Z{ + Score: float64(timestamp.UnixNano()), + Member: sellOrder.Id.String(), + }).SetVal(1) + mock.ExpectZRem(CreateSellSidePricesKey(sellOrder.Symbol), sellOrder.Id.String()).SetVal(1) + mock.ExpectTxPipelineExec() + + err := repo.StoreFilledOrders(ctx, []models.Order{sellOrder}) + + assert.NoError(t, err, "should not return error") + }) + + t.Run("store filled order unexpected error - should return error", func(t *testing.T) { + var sellOrder = models.Order{ + UserId: userId, + } + + db, mock := redismock.NewClientMock() + + repo := &redisRepository{ + client: db, + } + + mock.ExpectTxPipeline() + mock.ExpectZRem(CreateUserOpenOrdersKey(sellOrder.UserId), sellOrder.Id.String()).SetErr(assert.AnError) + + err := repo.StoreFilledOrders(ctx, []models.Order{sellOrder}) + + assert.ErrorContains(t, err, "failed to store filled orders in Redis") + assert.NoError(t, mock.ExpectationsWereMet()) + }) +} diff --git a/data/redisrepo/store_order.go b/data/redisrepo/store_open_order.go similarity index 68% rename from data/redisrepo/store_order.go rename to data/redisrepo/store_open_order.go index 8fbdf7d..d577d7b 100644 --- a/data/redisrepo/store_order.go +++ b/data/redisrepo/store_open_order.go @@ -10,11 +10,55 @@ import ( "github.com/redis/go-redis/v9" ) +// These methods should be used to store UNFILLED or PARTIALLY FILLED orders in Redis. +// +// `StoreFilledOrders` should be used to store completely filled orders. + +func (r *redisRepository) StoreOpenOrder(ctx context.Context, order models.Order) error { + + // --- START TRANSACTION --- + transaction := r.client.TxPipeline() + + err := storeOrderTX(ctx, transaction, &order) + if err != nil { + return err + } + + _, err = transaction.Exec(ctx) + if err != nil { + logctx.Error(ctx, "failed to store open order in Redis", logger.Error(err), logger.String("orderId", order.Id.String())) + return fmt.Errorf("failed to stores open order in Redis: %v", err) + } + logctx.Info(ctx, "stored order", logger.String("orderId", order.Id.String()), logger.String("price", order.Price.String()), logger.String("size", order.Size.String()), logger.String("side", order.Side.String())) + return nil +} + +func (r *redisRepository) StoreOpenOrders(ctx context.Context, orders []models.Order) error { + // --- START TRANSACTION --- + transaction := r.client.TxPipeline() + + for _, order := range orders { + err := storeOrderTX(ctx, transaction, &order) + if err != nil { + return err + } + + logctx.Info(ctx, "stored order", logger.String("orderId", order.Id.String()), logger.String("price", order.Price.String()), logger.String("size", order.Size.String()), logger.String("side", order.Side.String())) + + } + _, err := transaction.Exec(ctx) + if err != nil { + logctx.Error(ctx, "failed to stores open order in Redis", logger.Error(err), logger.Strings("orderIds", models.OrderIdsToStrings(ctx, &orders))) + return fmt.Errorf("failed to stores open order in Redis: %v", err) + } + return nil +} + func storeOrderTX(ctx context.Context, transaction redis.Pipeliner, order *models.Order) error { orderMap := order.OrderToMap() // Keep track of that user's orders - userOrdersKey := CreateUserOrdersKey(order.UserId) + userOrdersKey := CreateUserOpenOrdersKey(order.UserId) userOrdersScore := float64(order.Timestamp.UTC().UnixNano()) transaction.ZAdd(ctx, userOrdersKey, redis.Z{ Score: userOrdersScore, @@ -48,43 +92,5 @@ func storeOrderTX(ctx context.Context, transaction redis.Pipeliner, order *model }) } - logctx.Info(ctx, "stored order", logger.String("orderId", order.Id.String()), logger.String("price", order.Price.String()), logger.String("size", order.Size.String()), logger.String("side", order.Side.String())) - return nil -} -func (r *redisRepository) StoreOrder(ctx context.Context, order models.Order) error { - - // --- START TRANSACTION --- - transaction := r.client.TxPipeline() - - err := storeOrderTX(ctx, transaction, &order) - if err != nil { - return err - } - - _, err = transaction.Exec(ctx) - if err != nil { - logctx.Error(ctx, "failed to store order in Redis", logger.Error(err), logger.String("orderId", order.Id.String())) - return fmt.Errorf("transaction failed. Reason: %v", err) - } - // --- END TRANSACTION --- - return nil -} - -func (r *redisRepository) StoreOrders(ctx context.Context, orders []models.Order) error { - // --- START TRANSACTION --- - transaction := r.client.TxPipeline() - - for _, order := range orders { - err := storeOrderTX(ctx, transaction, &order) - if err != nil { - return err - } - - } - _, err := transaction.Exec(ctx) - if err != nil { - logctx.Error(ctx, "failed to stores order in Redis", logger.Error(err)) - return fmt.Errorf("transaction failed. Reason: %v", err) - } return nil } diff --git a/data/redisrepo/store_order_test.go b/data/redisrepo/store_open_order_test.go similarity index 74% rename from data/redisrepo/store_order_test.go rename to data/redisrepo/store_open_order_test.go index 757629a..be9c08b 100644 --- a/data/redisrepo/store_order_test.go +++ b/data/redisrepo/store_open_order_test.go @@ -11,11 +11,11 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRedisRepository_StoreOrder(t *testing.T) { +func TestRedisRepository_StoreOpenOrder(t *testing.T) { timestamp := time.Date(2023, 10, 10, 12, 0, 0, 0, time.UTC) - t.Run("store order success (buy side) - should set user orders set, order ID hash, buy prices sorted set", func(t *testing.T) { + t.Run("store open order success (buy side) - should set user orders set, order ID hash, buy prices sorted set", func(t *testing.T) { var buyOrder = models.Order{ Id: orderId, ClientOId: clientOId, @@ -33,7 +33,7 @@ func TestRedisRepository_StoreOrder(t *testing.T) { } mock.ExpectTxPipeline() - mock.ExpectZAdd(CreateUserOrdersKey(buyOrder.UserId), redis.Z{ + mock.ExpectZAdd(CreateUserOpenOrdersKey(buyOrder.UserId), redis.Z{ Score: float64(timestamp.UnixNano()), Member: buyOrder.Id.String(), }).SetVal(1) @@ -45,12 +45,12 @@ func TestRedisRepository_StoreOrder(t *testing.T) { }).SetVal(1) mock.ExpectTxPipelineExec() - err := repo.StoreOrder(ctx, buyOrder) + err := repo.StoreOpenOrder(ctx, buyOrder) assert.NoError(t, err, "should not return error") }) - t.Run("store order success (sell side) - should set user orders set, order ID hash, sell prices sorted set", func(t *testing.T) { + t.Run("store open order success (sell side) - should set user orders set, order ID hash, sell prices sorted set", func(t *testing.T) { var sellOrder = models.Order{ Id: orderId, ClientOId: clientOId, @@ -68,7 +68,7 @@ func TestRedisRepository_StoreOrder(t *testing.T) { } mock.ExpectTxPipeline() - mock.ExpectZAdd(CreateUserOrdersKey(sellOrder.UserId), redis.Z{ + mock.ExpectZAdd(CreateUserOpenOrdersKey(sellOrder.UserId), redis.Z{ Score: float64(timestamp.UnixNano()), Member: sellOrder.Id.String(), }).SetVal(1) @@ -80,12 +80,12 @@ func TestRedisRepository_StoreOrder(t *testing.T) { }).SetVal(1) mock.ExpectTxPipelineExec() - err := repo.StoreOrder(ctx, sellOrder) + err := repo.StoreOpenOrder(ctx, sellOrder) assert.NoError(t, err, "should not return error") }) - t.Run("store order fail - should return error when transaction fails", func(t *testing.T) { + t.Run("store open order fail - should return error when transaction fails", func(t *testing.T) { db, mock := redismock.NewClientMock() @@ -94,7 +94,7 @@ func TestRedisRepository_StoreOrder(t *testing.T) { } mock.ExpectTxPipeline() - mock.ExpectZAdd(CreateUserOrdersKey(order.UserId), redis.Z{ + mock.ExpectZAdd(CreateUserOpenOrdersKey(order.UserId), redis.Z{ Score: float64(order.Timestamp.UnixNano()), Member: order.Id.String(), }).SetErr(assert.AnError) @@ -106,9 +106,9 @@ func TestRedisRepository_StoreOrder(t *testing.T) { }).SetVal(1) mock.ExpectTxPipelineExec().SetErr(fmt.Errorf("transaction failed")) - err := repo.StoreOrder(ctx, order) + err := repo.StoreOpenOrder(ctx, order) - assert.ErrorContains(t, err, fmt.Sprintf("transaction failed. Reason: %v", assert.AnError), "should return error") + assert.ErrorContains(t, err, "failed to stores open order in Redis", "should return error") }) } diff --git a/data/redisrepo/utils.go b/data/redisrepo/utils.go index e3348e6..e9a3a3e 100644 --- a/data/redisrepo/utils.go +++ b/data/redisrepo/utils.go @@ -9,11 +9,16 @@ import ( "github.com/redis/go-redis/v9" ) -// CreateOrderIDKey creates a Redis key for storing the user's orders -func CreateUserOrdersKey(userId uuid.UUID) string { +// CreateUserOpenOrdersKey creates a Redis key for storing the user's open orders +func CreateUserOpenOrdersKey(userId uuid.UUID) string { return fmt.Sprintf("userId:%s:orders", userId) } +// CreateAuctionTrackerKey creates a Redis key for storing the user's filled orders +func CreateUserFilledOrdersKey(userId uuid.UUID) string { + return fmt.Sprintf("userId:%s:filledOrders", userId) +} + // CreateOrderIDKey creates a Redis key for a single order ID func CreateOrderIDKey(orderId uuid.UUID) string { return fmt.Sprintf("orderID:%s", orderId) diff --git a/data/store/store.go b/data/store/store.go index 2cdf931..5a15a42 100644 --- a/data/store/store.go +++ b/data/store/store.go @@ -10,8 +10,9 @@ import ( type OrderBookStore interface { // MM side - StoreOrder(ctx context.Context, order models.Order) error - StoreOrders(ctx context.Context, orders []models.Order) error + StoreOpenOrder(ctx context.Context, order models.Order) error + StoreOpenOrders(ctx context.Context, orders []models.Order) error + StoreFilledOrders(ctx context.Context, orders []models.Order) error RemoveOrder(ctx context.Context, order models.Order) error FindOrderById(ctx context.Context, id uuid.UUID, isClientOId bool) (*models.Order, error) FindOrdersByIds(ctx context.Context, ids []uuid.UUID) ([]models.Order, error) diff --git a/mocks/service.go b/mocks/service.go index c154b9c..ae960eb 100644 --- a/mocks/service.go +++ b/mocks/service.go @@ -26,12 +26,16 @@ type MockOrderBookStore struct { Sets map[string]map[string]struct{} } -func (m *MockOrderBookStore) StoreOrder(ctx context.Context, order models.Order) error { +func (m *MockOrderBookStore) StoreOpenOrder(ctx context.Context, order models.Order) error { return m.Error } -func (m *MockOrderBookStore) StoreOrders(ctx context.Context, orders []models.Order) error { +func (m *MockOrderBookStore) StoreOpenOrders(ctx context.Context, orders []models.Order) error { + return m.Error +} + +func (m *MockOrderBookStore) StoreFilledOrders(ctx context.Context, orders []models.Order) error { return m.Error } diff --git a/models/order.go b/models/order.go index 5f059ab..30458e4 100644 --- a/models/order.go +++ b/models/order.go @@ -1,6 +1,7 @@ package models import ( + "context" "encoding/json" "fmt" "time" @@ -203,3 +204,16 @@ func (o *Order) IsFilled() bool { func (o *Order) IsPending() bool { return o.SizePending.GreaterThan(decimal.Zero) } + +// OrderIdsToStrings return a list of string order IDs from a list of orders +func OrderIdsToStrings(ctx context.Context, orders *[]Order) []string { + if orders == nil { + return []string{} + } + + var orderIds []string + for _, order := range *orders { + orderIds = append(orderIds, order.Id.String()) + } + return orderIds +} diff --git a/scripts/redis-repo/main.go b/scripts/redis-repo/main.go index 4ec5282..a9da5ff 100644 --- a/scripts/redis-repo/main.go +++ b/scripts/redis-repo/main.go @@ -246,7 +246,7 @@ func createOrders() { Timestamp: time.Now().UTC(), } - err = repository.StoreOrder(ctx, order) + err = repository.StoreOpenOrder(ctx, order) if err != nil { log.Fatalf("error storing order: %v", err) } diff --git a/service/auction.go b/service/auction.go index a140436..763d54f 100644 --- a/service/auction.go +++ b/service/auction.go @@ -99,9 +99,9 @@ func (s *Service) ConfirmAuction(ctx context.Context, auctionId uuid.UUID) (Conf // lock frag.Amount as pending per order - no STATUS_PENDING is needed res.Orders[i].SizePending = res.Fragments[i].Size } - err = s.orderBookStore.StoreOrders(ctx, res.Orders) + err = s.orderBookStore.StoreOpenOrders(ctx, res.Orders) if err != nil { - logctx.Warn(ctx, "StoreOrders Failed", logger.Error(err)) + logctx.Warn(ctx, "StoreOpenOrders Failed", logger.Error(err)) return ConfirmAuctionRes{}, err } @@ -148,9 +148,9 @@ func (s *Service) RevertAuction(ctx context.Context, auctionId uuid.UUID) error } } // store orders - err = s.orderBookStore.StoreOrders(ctx, orders) + err = s.orderBookStore.StoreOpenOrders(ctx, orders) if err != nil { - logctx.Warn(ctx, "StoreOrders Failed", logger.Error(err)) + logctx.Warn(ctx, "StoreOpenOrders Failed", logger.Error(err)) return err } @@ -215,13 +215,11 @@ func (s *Service) AuctionMined(ctx context.Context, auctionId uuid.UUID) error { // Add them to "filled" storage - can be done withn StoreOrders() // store orders - err = s.orderBookStore.StoreOrders(ctx, filledOrders) + err = s.orderBookStore.StoreFilledOrders(ctx, filledOrders) if err != nil { - logctx.Error(ctx, "StoreOrders Failed", logger.Error(err)) + logctx.Error(ctx, "StoreFilledOrders Failed", logger.Error(err)) return err } - // TODO: remove filled orders - return s.orderBookStore.RemoveAuction(ctx, auctionId) // no need to revert pending its done in line 124 } diff --git a/service/create_order.go b/service/create_order.go index 94bf26f..2c7ad30 100644 --- a/service/create_order.go +++ b/service/create_order.go @@ -100,7 +100,7 @@ func (s *Service) createNewOrder(ctx context.Context, input CreateOrderInput, us Timestamp: time.Now().UTC(), } - if err := s.orderBookStore.StoreOrder(ctx, order); err != nil { + if err := s.orderBookStore.StoreOpenOrder(ctx, order); err != nil { logctx.Error(ctx, "failed to add order", logger.Error(err)) return models.Order{}, err }