-
Notifications
You must be signed in to change notification settings - Fork 0
feat(searchUtxosByAsset): changes to add support for searchUtxosByAsset #510
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,13 +18,15 @@ import ( | |||||||||||||||||||||||||
| "bytes" | ||||||||||||||||||||||||||
| "context" | ||||||||||||||||||||||||||
| "encoding/hex" | ||||||||||||||||||||||||||
| "errors" | ||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||
| "log" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| connect "connectrpc.com/connect" | ||||||||||||||||||||||||||
| "github.com/blinklabs-io/cardano-node-api/internal/node" | ||||||||||||||||||||||||||
| "github.com/blinklabs-io/gouroboros/ledger" | ||||||||||||||||||||||||||
| "github.com/blinklabs-io/gouroboros/ledger/common" | ||||||||||||||||||||||||||
| "github.com/blinklabs-io/gouroboros/protocol/localstatequery" | ||||||||||||||||||||||||||
| query "github.com/utxorpc/go-codegen/utxorpc/v1alpha/query" | ||||||||||||||||||||||||||
| "github.com/utxorpc/go-codegen/utxorpc/v1alpha/query/queryconnect" | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
|
@@ -199,8 +201,29 @@ func (s *queryServiceServer) SearchUtxos( | |||||||||||||||||||||||||
| addressPattern := predicate.GetMatch().GetCardano().GetAddress() | ||||||||||||||||||||||||||
| assetPattern := predicate.GetMatch().GetCardano().GetAsset() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| var addresses []common.Address | ||||||||||||||||||||||||||
| // A Match can only contain EITHER addressPattern OR assetPattern, not both | ||||||||||||||||||||||||||
| if addressPattern != nil && assetPattern != nil { | ||||||||||||||||||||||||||
| return nil, errors.New( | ||||||||||||||||||||||||||
| "ERROR: Match cannot contain both address and asset patterns. Use AllOf predicate to combine them", | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Connect to node | ||||||||||||||||||||||||||
| oConn, err := node.GetConnection(nil) | ||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| defer func() { | ||||||||||||||||||||||||||
| oConn.Close() | ||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||
| oConn.LocalStateQuery().Client.Start() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| var utxos *localstatequery.UTxOsResult | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Handle address-only search | ||||||||||||||||||||||||||
| if addressPattern != nil { | ||||||||||||||||||||||||||
| var addresses []common.Address | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Handle Exact Address | ||||||||||||||||||||||||||
| exactAddressBytes := addressPattern.GetExactAddress() | ||||||||||||||||||||||||||
| if exactAddressBytes != nil { | ||||||||||||||||||||||||||
|
|
@@ -241,25 +264,22 @@ func (s *queryServiceServer) SearchUtxos( | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| addresses = append(addresses, delegationAddr) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Connect to node | ||||||||||||||||||||||||||
| oConn, err := node.GetConnection(nil) | ||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| defer func() { | ||||||||||||||||||||||||||
| // Close Ouroboros connection | ||||||||||||||||||||||||||
| oConn.Close() | ||||||||||||||||||||||||||
| }() | ||||||||||||||||||||||||||
| // Start client | ||||||||||||||||||||||||||
| oConn.LocalStateQuery().Client.Start() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Get UTxOs | ||||||||||||||||||||||||||
| utxos, err := oConn.LocalStateQuery().Client.GetUTxOByAddress(addresses) | ||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||
| log.Printf("ERROR: %s", err) | ||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||
| // Get UTxOs by address | ||||||||||||||||||||||||||
| utxos, err = oConn.LocalStateQuery().Client.GetUTxOByAddress(addresses) | ||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||
| log.Printf("ERROR: %s", err) | ||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } else if assetPattern != nil { | ||||||||||||||||||||||||||
| // Handle asset-only search - get all UTxOs and filter by asset | ||||||||||||||||||||||||||
| utxos, err = oConn.LocalStateQuery().Client.GetUTxOWhole() | ||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||
| log.Printf("ERROR: %s", err) | ||||||||||||||||||||||||||
| return nil, err | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+274
to
+280
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential DoS: On mainnet, the UTxO set contains tens of millions of entries. Without an address filter, this call can exhaust memory and cause significant latency. The previous review flagged this exact concern. You must:
As implemented, this endpoint can be trivially exploited to cause service degradation. 🔎 Recommended fix: Require address for asset searches } else if assetPattern != nil {
- // Handle asset-only search - get all UTxOs and filter by asset
- utxos, err = oConn.LocalStateQuery().Client.GetUTxOWhole()
- if err != nil {
- log.Printf("ERROR: %s", err)
- return nil, err
- }
+ // Asset-only search without address is not supported due to DoS risk
+ return nil, errors.New(
+ "ERROR: Asset search requires an address filter. Use AllOf predicate to combine address and asset patterns",
+ )📝 Committable suggestion
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no server side pagination in the data from the Cardano node. There's no way to search for an asset when the owner address is unknown without the UTxO whole set. This is a known limitation of the Haskell cardano-node and the current capabilities of the ouroboros protocols. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @wolf31o2, thank you for the clarification. I understand that this is an inherent limitation of the Cardano node's current protocol capabilities, and fetching the entire UTxO set is unavoidable for asset searches without a known owner address. Given this constraint, I'd recommend considering API-level protective measures:
These won't eliminate the risk but can help manage it at the application layer. The implementation is appropriate given the protocol limitations you've described. ✏️ Learnings added
|
||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| return nil, errors.New("ERROR: Match must contain either address or asset pattern") | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Get chain point (slot and hash) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.