Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 15 additions & 1 deletion cmd/rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2562,10 +2562,13 @@ $ curl -X POST localhost:50002/v1/query/order \

- **height**: `uint64` – the block height to read data from (optional: use 0 to read from the latest block)
- **committee**: `uint64` – the unique identifier of the committee to filter by (optional: use 0 to get all committees)
- **sellersSendAddress**: `hex-string` – the seller address to filter orders by (optional: when provided, uses indexed lookup for efficient querying)
- **sellersSendAddress**: `hex-string` – the seller address to filter orders by (optional: use "" to get all seller addresses)
- **buyerSendAddress**: `hex-string` – the buyer address to filter locked orders by (optional: use "" to get all buyer addresses)
- **pageNumber**: `int` – the page number to retrieve (optional: starts at 1)
- **perPage**: `int` – the number of orders per page (optional: defaults to system default)

**Note**: `sellersSendAddress` and `buyerSendAddress` are mutually exclusive filters. You cannot use both in the same request.

**Response**:
- **pageNumber**: `int` - the current page number
- **perPage**: `int` - the number of items per page
Expand Down Expand Up @@ -2657,6 +2660,17 @@ $ curl -X POST localhost:50002/v1/query/orders \
}'
```

**Example 5: Filter by buyerSendAddress with pagination (locked orders only)**
```
$ curl -X POST localhost:50002/v1/query/orders \
-H "Content-Type: application/json" \
-d '{
"buyerSendAddress": "aaac0b3d64c12c6f164545545b2ba2ab4d80deff",
"pageNumber": 1,
"perPage": 10
}'
```

## Dex Batch
**Route:** `/v1/query/dex-batch`
**Description**: view the locked dex batch for a committee or all dex batches
Expand Down
15 changes: 14 additions & 1 deletion cmd/rpc/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ func (s *Server) Order(w http.ResponseWriter, r *http.Request, _ httprouter.Para
func (s *Server) Orders(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Invoke helper with the HTTP request, response writer and an inline callback
s.ordersParams(w, r, func(s *fsm.StateMachine, req *ordersRequest) (any, lib.ErrorI) {
// validate mutual exclusion: cannot filter by both seller and buyer address
if req.SellersSendAddress != "" && req.BuyerSendAddress != "" {
return nil, lib.NewError(lib.CodeInvalidArgument, lib.RPCModule, "cannot filter by both sellersSendAddress and buyerSendAddress")
}
// convert seller address if provided
var sellerAddr []byte
if req.SellersSendAddress != "" {
Expand All @@ -294,8 +298,17 @@ func (s *Server) Orders(w http.ResponseWriter, r *http.Request, _ httprouter.Par
return nil, err
}
}
// convert buyer address if provided
var buyerAddr []byte
if req.BuyerSendAddress != "" {
var err lib.ErrorI
buyerAddr, err = lib.StringToBytes(req.BuyerSendAddress)
if err != nil {
return nil, err
}
}
// use paginated query
return s.GetOrdersPaginated(sellerAddr, req.Committee, req.PageParams)
return s.GetOrdersPaginated(sellerAddr, buyerAddr, req.Committee, req.PageParams)
})
}

Expand Down
1 change: 1 addition & 0 deletions cmd/rpc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type orderRequest struct {
type ordersRequest struct {
Committee uint64 `json:"committee"`
SellersSendAddress string `json:"sellersSendAddress"`
BuyerSendAddress string `json:"buyerSendAddress"`
heightRequest
lib.PageParams
}
Expand Down
11 changes: 11 additions & 0 deletions fsm/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ var (
retiredCommitteePrefix = []byte{14} // store key prefix for 'retired' (dead) committees
dexPrefix = []byte{15} // store key prefix for 'dex' functionality
orderBySellerPrefix = []byte{16} // store key prefix for 'sell orders' indexed by seller address
orderByBuyerPrefix = []byte{17} // store key prefix for 'sell orders' indexed by buyer address

lockedBatchSegment = []byte{1}
nextBatchSement = []byte{2}
Expand Down Expand Up @@ -89,6 +90,16 @@ func OrderBySellerAndChainPrefix(seller []byte, chainId uint64) []byte {
func KeyForOrderBySeller(seller []byte, chainId uint64, orderId []byte) []byte {
return append(OrderBySellerAndChainPrefix(seller, chainId), lib.JoinLenPrefix(orderId)...)
}

func OrderByBuyerPrefix(buyer []byte) []byte {
return lib.JoinLenPrefix(orderByBuyerPrefix, buyer)
}
func OrderByBuyerAndChainPrefix(buyer []byte, chainId uint64) []byte {
return append(OrderByBuyerPrefix(buyer), lib.JoinLenPrefix(formatUint64(chainId))...)
}
func KeyForOrderByBuyer(buyer []byte, chainId uint64, orderId []byte) []byte {
return append(OrderByBuyerAndChainPrefix(buyer, chainId), lib.JoinLenPrefix(orderId)...)
}
func KeyForUnstaking(height uint64, address crypto.AddressI) []byte {
return append(UnstakingPrefix(height), lib.JoinLenPrefix(address.Bytes())...)
}
Expand Down
101 changes: 95 additions & 6 deletions fsm/swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"bytes"
"encoding/binary"
"encoding/json"
"sort"

"github.com/canopy-network/canopy/lib"
"github.com/canopy-network/canopy/lib/crypto"
"sort"
)

/* This file contains state machine changes related to 'token swapping' */
Expand Down Expand Up @@ -262,6 +263,10 @@ func (s *StateMachine) CloseOrder(orderId []byte, chainId uint64) (err lib.Error

// SetOrder() sets the sell order in state
func (s *StateMachine) SetOrder(order *lib.SellOrder, chainId uint64) (err lib.ErrorI) {
// clean up stale buyer index if buyer changed or was removed
if err = s.cleanupStaleBuyerIndex(order, chainId); err != nil {
return
}
// convert the order into proto bytes
protoBytes, err := s.marshalOrder(order)
if err != nil {
Expand All @@ -272,12 +277,42 @@ func (s *StateMachine) SetOrder(order *lib.SellOrder, chainId uint64) (err lib.E
return
}
// set the secondary index for seller address lookup (value is empty, key is sufficient)
return s.Set(KeyForOrderBySeller(order.SellersSendAddress, chainId, order.Id), []byte{})
if err = s.Set(KeyForOrderBySeller(order.SellersSendAddress, chainId, order.Id), []byte{}); err != nil {
return
}
// set the secondary index for buyer address lookup if buyer exists (locked order)
if len(order.BuyerSendAddress) > 0 {
if err = s.Set(KeyForOrderByBuyer(order.BuyerSendAddress, chainId, order.Id), []byte{}); err != nil {
return
}
}
return
}

// cleanupStaleBuyerIndex() removes the old buyer index entry if the buyer changed or was removed
func (s *StateMachine) cleanupStaleBuyerIndex(order *lib.SellOrder, chainId uint64) lib.ErrorI {
// check if order already exists
existingOrder, err := s.GetOrder(order.Id, chainId)
// if order not found, nothing to clean up
if err != nil && err.Code() == lib.CodeOrderNotFound {
return nil
}
// if other error, return it
if err != nil {
return err
}
// if existing order has a buyer and it differs from the new order's buyer, clean up the old index
if existingOrder != nil && len(existingOrder.BuyerSendAddress) > 0 {
if !bytes.Equal(existingOrder.BuyerSendAddress, order.BuyerSendAddress) {
return s.Delete(KeyForOrderByBuyer(existingOrder.BuyerSendAddress, chainId, order.Id))
}
}
return nil
}

// DeleteOrder() deletes an existing order in the order book for a committee in the state db
func (s *StateMachine) DeleteOrder(orderId []byte, chainId uint64) (err lib.ErrorI) {
// get the order first to retrieve the seller address for index cleanup
// get the order first to retrieve addresses for index cleanup
order, err := s.GetOrder(orderId, chainId)
if err != nil {
return
Expand All @@ -286,8 +321,17 @@ func (s *StateMachine) DeleteOrder(orderId []byte, chainId uint64) (err lib.Erro
if err = s.Delete(KeyForOrder(chainId, orderId)); err != nil {
return
}
// delete the secondary index entry
return s.Delete(KeyForOrderBySeller(order.SellersSendAddress, chainId, orderId))
// delete the seller secondary index entry
if err = s.Delete(KeyForOrderBySeller(order.SellersSendAddress, chainId, orderId)); err != nil {
return
}
// delete the buyer secondary index entry if buyer exists
if len(order.BuyerSendAddress) > 0 {
if err = s.Delete(KeyForOrderByBuyer(order.BuyerSendAddress, chainId, orderId)); err != nil {
return
}
}
return
}

// GetOrder() gets the sell order from state
Expand Down Expand Up @@ -472,8 +516,22 @@ func (s *StateMachine) parseOrderBySellerKey(key []byte) (chainId uint64, orderI
return
}

// parseOrderByBuyerKey() extracts chainId and orderId from an order-by-buyer index key
func (s *StateMachine) parseOrderByBuyerKey(key []byte) (chainId uint64, orderId []byte, err lib.ErrorI) {
// key format: orderByBuyerPrefix + buyer + chainId + orderId (all length-prefixed)
segments := lib.DecodeLengthPrefixed(key)
if len(segments) < 4 {
return 0, nil, ErrInvalidKey(key)
}
// segments[0] = prefix, segments[1] = buyer, segments[2] = chainId, segments[3] = orderId
chainId = binary.BigEndian.Uint64(segments[2])
orderId = segments[3]
return
}

// GetOrdersPaginated() retrieves orders with optional filters and pagination
func (s *StateMachine) GetOrdersPaginated(seller []byte, chainId uint64, p lib.PageParams) (*lib.Page, lib.ErrorI) {
// Note: seller and buyer filters are mutually exclusive
func (s *StateMachine) GetOrdersPaginated(seller, buyer []byte, chainId uint64, p lib.PageParams) (*lib.Page, lib.ErrorI) {
// create the page object
page := lib.NewPage(p, "orders")
results := &lib.SellOrders{}
Expand All @@ -482,6 +540,10 @@ func (s *StateMachine) GetOrdersPaginated(seller []byte, chainId uint64, p lib.P
// use seller index for efficient lookup
return s.getOrdersBySellerPaginated(seller, chainId, page, results)
}
if len(buyer) > 0 {
// use buyer index for efficient lookup
return s.getOrdersByBuyerPaginated(buyer, chainId, page, results)
}
if chainId != 0 {
// filter by chainId only - iterate orders for that chain
return s.getOrdersByChainPaginated(chainId, page, results)
Expand Down Expand Up @@ -517,6 +579,33 @@ func (s *StateMachine) getOrdersBySellerPaginated(seller []byte, chainId uint64,
return page, err
}

// getOrdersByBuyerPaginated() retrieves paginated orders using the buyer index
func (s *StateMachine) getOrdersByBuyerPaginated(buyer []byte, chainId uint64, page *lib.Page, results *lib.SellOrders) (*lib.Page, lib.ErrorI) {
// determine the prefix based on whether chainId filter is provided
var prefix []byte
if chainId == 0 {
prefix = OrderByBuyerPrefix(buyer)
} else {
prefix = OrderByBuyerAndChainPrefix(buyer, chainId)
}
// use the page Load function with the index prefix
err := page.Load(prefix, false, results, s, func(k, v []byte) lib.ErrorI {
// extract chainId and orderId from the index key
cId, orderId, e := s.parseOrderByBuyerKey(k)
if e != nil {
return e
}
// get the actual order from the primary store
order, e := s.GetOrder(orderId, cId)
if e != nil {
return e
}
*results = append(*results, order)
return nil
})
return page, err
}

// getOrdersByChainPaginated() retrieves paginated orders for a specific chain
func (s *StateMachine) getOrdersByChainPaginated(chainId uint64, page *lib.Page, results *lib.SellOrders) (*lib.Page, lib.ErrorI) {
// use the page Load function with the order book prefix
Expand Down
Loading