Skip to content

Commit 2ddd5a9

Browse files
committed
wip
1 parent e0b1240 commit 2ddd5a9

14 files changed

Lines changed: 1144 additions & 248 deletions

File tree

cardano-rpc/cardano-rpc.cabal

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ library
5858
Cardano.Rpc.Server.Internal.Monad
5959
Cardano.Rpc.Server.Internal.Node
6060
Cardano.Rpc.Server.Internal.Tracing
61+
Cardano.Rpc.Server.Internal.UtxoRpc.Predicate
6162
Cardano.Rpc.Server.Internal.UtxoRpc.Query
6263
Cardano.Rpc.Server.Internal.UtxoRpc.Submit
6364
Cardano.Rpc.Server.Internal.UtxoRpc.Type
@@ -119,15 +120,18 @@ test-suite cardano-rpc-test
119120
type: exitcode-stdio-1.0
120121
build-depends:
121122
base,
123+
bytestring,
122124
cardano-api,
123125
cardano-api:gen,
124126
cardano-ledger-api,
125127
cardano-ledger-conway,
126128
cardano-ledger-core,
127129
cardano-rpc,
130+
cardano-rpc:gen,
128131
containers,
129132
hedgehog,
130133
hedgehog-extras,
134+
proto-lens,
131135
rio,
132136
tasty,
133137
tasty-hedgehog,
@@ -139,6 +143,7 @@ test-suite cardano-rpc-test
139143

140144
build-tool-depends: tasty-discover:tasty-discover
141145
other-modules:
146+
Test.Cardano.Rpc.Predicate
142147
Test.Cardano.Rpc.ProtocolParameters
143148
Test.Cardano.Rpc.TxOutput
144149
Test.Cardano.Rpc.Type

cardano-rpc/gen/Proto/Utxorpc/V1beta/Cardano/Cardano.hs

Lines changed: 222 additions & 153 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cardano-rpc/gen/Proto/Utxorpc/V1beta/Cardano/Cardano_Fields.hs

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cardano-rpc/gen/Proto/Utxorpc/V1beta/Query/Query.hs

Lines changed: 119 additions & 70 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cardano-rpc/gen/Proto/Utxorpc/V1beta/Query/Query_Fields.hs

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cardano-rpc/proto/utxorpc/v1beta/cardano/cardano.proto

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -516,15 +516,15 @@ message UpdateDRepCert {
516516

517517
// Pattern of an address that can be used to evaluate matching predicates.
518518
message AddressPattern {
519-
bytes exact_address = 1; // The address should match this exact address value.
520-
bytes payment_part = 2; // The payment part of the address should match this value.
521-
bytes delegation_part = 3; // The delegation part of the address should match this value.
519+
optional bytes exact_address = 1; // The address should match this exact address value.
520+
optional bytes payment_part = 2; // The payment part of the address should match this value.
521+
optional bytes delegation_part = 3; // The delegation part of the address should match this value.
522522
}
523523

524524
// Pattern of a native asset that can be used to evaluate matching predicates.
525525
message AssetPattern {
526-
bytes policy_id = 1; // The asset should belong to this policy id
527-
bytes asset_name = 2; // The asset should present this name
526+
optional bytes policy_id = 1; // The asset should belong to this policy id
527+
optional bytes asset_name = 2; // The asset should present this name
528528
}
529529

530530
// Pattern of a certificate that can be used to evaluate matching predicates.
@@ -561,8 +561,8 @@ message PoolRetirementPattern {
561561

562562
// Pattern of a tx output that can be used to evaluate matching predicates.
563563
message TxOutputPattern {
564-
AddressPattern address = 1; // Match any address in the output that exhibits this pattern.
565-
AssetPattern asset = 2; // Match any asset in the output that exhibits this pattern.
564+
optional AddressPattern address = 1; // Match any address in the output that exhibits this pattern.
565+
optional AssetPattern asset = 2; // Match any asset in the output that exhibits this pattern.
566566
}
567567

568568
// Pattern of a Tx that can be used to evaluate matching predicates.

cardano-rpc/proto/utxorpc/v1beta/query/query.proto

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ message ReadUtxosResponse {
111111

112112
// Request to search for UTxO based on a pattern.
113113
message SearchUtxosRequest {
114-
UtxoPredicate predicate = 1; // Pattern to match UTxOs by.
114+
optional UtxoPredicate predicate = 1; // Pattern to match UTxOs by.
115115
google.protobuf.FieldMask field_mask = 2; // Field mask to selectively return fields.
116-
int32 max_items = 3; // The maximum number of items to return.
117-
string start_token = 4; // The next_page_token value returned from a previous request, if any.
116+
optional int32 max_items = 3; // The maximum number of items to return.
117+
optional string start_token = 4; // The next_page_token value returned from a previous request, if any.
118118
}
119119

120120
// Response containing the UTxOs that match the requested addresses.
@@ -170,6 +170,7 @@ message ReadTxResponse {
170170
service QueryService {
171171
rpc ReadParams(ReadParamsRequest) returns (ReadParamsResponse); // Get overall chain state.
172172
rpc ReadUtxos(ReadUtxosRequest) returns (ReadUtxosResponse); // Read specific UTxOs by reference.
173+
rpc SearchUtxos(SearchUtxosRequest) returns (SearchUtxosResponse); // Search for UTxO based on a pattern.
173174

174175
// TODO: decide if we want to expand the scope
175176
// rpc DumpUtxos(ReadUtxosRequest) returns (stream ReadUtxosResponse); // Dump all available utxos

cardano-rpc/src/Cardano/Rpc/Server.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ methodsUtxoRpc
5656
methodsUtxoRpc =
5757
Method (mkNonStreaming $ wrapInSpan TraceRpcQueryParamsSpan . readParamsMethod)
5858
. Method (mkNonStreaming $ wrapInSpan TraceRpcQueryReadUtxosSpan . readUtxosMethod)
59+
. Method (mkNonStreaming $ wrapInSpan TraceRpcQuerySearchUtxosSpan . searchUtxosMethod)
5960
$ NoMoreMethods
6061

6162
methodsUtxoRpcSubmit

cardano-rpc/src/Cardano/Rpc/Server/Internal/Tracing.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ data TraceRpcQuery
2727
TraceRpcQueryParamsSpan TraceSpanEvent
2828
| -- | Span trace marking ReadUtxos query
2929
TraceRpcQueryReadUtxosSpan TraceSpanEvent
30+
| -- | Span trace marking SearchUtxos query
31+
TraceRpcQuerySearchUtxosSpan TraceSpanEvent
3032
deriving Show
3133

3234
instance Pretty TraceRpc where
@@ -53,6 +55,8 @@ instance Pretty TraceRpcQuery where
5355
TraceRpcQueryParamsSpan (SpanEnd _) -> "Finished query params method"
5456
TraceRpcQueryReadUtxosSpan (SpanBegin _) -> "Started query read UTXO method"
5557
TraceRpcQueryReadUtxosSpan (SpanEnd _) -> "Finished query read UTXO method"
58+
TraceRpcQuerySearchUtxosSpan (SpanBegin _) -> "Started query search UTXO method"
59+
TraceRpcQuerySearchUtxosSpan (SpanEnd _) -> "Finished query search UTXO method"
5660

5761
instance Error TraceRpcQuery where
5862
prettyError = pretty
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
{-# LANGUAGE FlexibleContexts #-}
2+
{-# LANGUAGE GADTs #-}
3+
{-# LANGUAGE ScopedTypeVariables #-}
4+
5+
module Cardano.Rpc.Server.Internal.UtxoRpc.Predicate
6+
( matchesUtxoPredicate
7+
, extractAddressesFromPredicate
8+
, matchesAddressPattern
9+
, matchesAssetPattern
10+
, matchesTxOutputPattern
11+
, matchesAnyUtxoPattern
12+
, serialisePaymentCredential
13+
, serialiseStakeCredential
14+
)
15+
where
16+
17+
import Cardano.Api.Address
18+
import Cardano.Api.Serialise.Raw
19+
import Cardano.Api.Tx
20+
import Cardano.Api.Value
21+
import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as U5c
22+
import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as UtxoRpc
23+
24+
import RIO hiding (toList)
25+
26+
import Control.Monad (guard)
27+
import Data.ByteString qualified as BS
28+
import Data.Set (Set)
29+
import Data.Set qualified as Set
30+
import GHC.IsList
31+
32+
-- | Check if a UTxO entry matches a 'UtxoPredicate'.
33+
-- All present fields are combined with AND logic.
34+
matchesUtxoPredicate
35+
:: UtxoRpc.UtxoPredicate
36+
-> TxOut CtxUTxO era
37+
-> Bool
38+
matchesUtxoPredicate p txOut =
39+
all (`matchesAnyUtxoPattern` txOut) (p ^. U5c.maybe'match)
40+
&& not (any (`matchesUtxoPredicate` txOut) (p ^. U5c.not))
41+
&& all (`matchesUtxoPredicate` txOut) (p ^. U5c.allOf)
42+
&& (null (p ^. U5c.anyOf) || any (`matchesUtxoPredicate` txOut) (p ^. U5c.anyOf))
43+
44+
-- | Check if a UTxO entry matches an 'AnyUtxoPattern'.
45+
-- Delegates to the Cardano-specific 'TxOutputPattern' if present.
46+
matchesAnyUtxoPattern
47+
:: UtxoRpc.AnyUtxoPattern
48+
-> TxOut CtxUTxO era
49+
-> Bool
50+
matchesAnyUtxoPattern pat txOut =
51+
all (`matchesTxOutputPattern` txOut) (pat ^. U5c.maybe'cardano)
52+
53+
-- | Check if a tx output matches a 'TxOutputPattern'.
54+
-- Address and asset filters are combined with AND; absent fields are vacuously true.
55+
matchesTxOutputPattern
56+
:: UtxoRpc.TxOutputPattern
57+
-> TxOut CtxUTxO era
58+
-> Bool
59+
matchesTxOutputPattern pat (TxOut addrInEra txOutValue _datum _script) =
60+
all (`matchesAddressPattern` addrInEra) (pat ^. U5c.maybe'address)
61+
&& all (`matchesAssetPattern` txOutValueToValue txOutValue) (pat ^. U5c.maybe'asset)
62+
63+
-- | Check if an address matches an 'AddressPattern'.
64+
-- All present fields (exact, payment, delegation) must match (AND logic).
65+
-- Byron addresses only support exact matching; payment\/delegation filters reject them.
66+
matchesAddressPattern
67+
:: UtxoRpc.AddressPattern
68+
-> AddressInEra era
69+
-> Bool
70+
matchesAddressPattern pat addr =
71+
exactMatch && paymentMatch && delegationMatch
72+
where
73+
matchesRawField field actual = BS.null field || field == actual
74+
75+
exactMatch = case addr of
76+
AddressInEra ByronAddressInAnyEra a -> matchesRawField (pat ^. U5c.exactAddress) $ serialiseToRawBytes a
77+
AddressInEra ShelleyAddressInEra{} a -> matchesRawField (pat ^. U5c.exactAddress) $ serialiseToRawBytes a
78+
paymentMatch = case addr of
79+
AddressInEra ShelleyAddressInEra{} (ShelleyAddress _ payCred _) ->
80+
matchesRawField (pat ^. U5c.paymentPart) . serialisePaymentCredential $ fromShelleyPaymentCredential payCred
81+
_ -> BS.null $ pat ^. U5c.paymentPart
82+
delegationMatch = case addr of
83+
AddressInEra ShelleyAddressInEra{} (ShelleyAddress _ _ stakeRef) ->
84+
case fromShelleyStakeReference stakeRef of
85+
StakeAddressByValue cred ->
86+
matchesRawField (pat ^. U5c.delegationPart) $ serialiseStakeCredential cred
87+
_ -> BS.null $ pat ^. U5c.delegationPart
88+
_ -> BS.null $ pat ^. U5c.delegationPart
89+
90+
-- | Serialise a 'PaymentCredential' to raw bytes (the key or script hash).
91+
serialisePaymentCredential :: PaymentCredential -> ByteString
92+
serialisePaymentCredential (PaymentCredentialByKey h) = serialiseToRawBytes h
93+
serialisePaymentCredential (PaymentCredentialByScript h) = serialiseToRawBytes h
94+
95+
-- | Serialise a 'StakeCredential' to raw bytes (the key or script hash).
96+
serialiseStakeCredential :: StakeCredential -> ByteString
97+
serialiseStakeCredential (StakeCredentialByKey h) = serialiseToRawBytes h
98+
serialiseStakeCredential (StakeCredentialByScript h) = serialiseToRawBytes h
99+
100+
-- | Check if a 'Value' contains a native asset matching an 'AssetPattern'.
101+
-- Ada entries are always skipped; zero-quantity entries do not match.
102+
matchesAssetPattern
103+
:: UtxoRpc.AssetPattern
104+
-> Value
105+
-> Bool
106+
matchesAssetPattern pat value =
107+
any matchesEntry (toList value)
108+
where
109+
pid = pat ^. U5c.policyId
110+
aname = pat ^. U5c.assetName
111+
matchesEntry (AssetId pId aName, Quantity qty) =
112+
(BS.null pid || serialiseToRawBytes pId == pid)
113+
&& (BS.null aname || serialiseToRawBytes aName == aname)
114+
&& qty > 0
115+
matchesEntry (AdaAssetId, _) = False
116+
117+
-- | Try to extract a set of exact addresses from the predicate for use with 'QueryUTxOByAddress'.
118+
-- Returns 'Just' if the optimization is applicable, 'Nothing' otherwise.
119+
extractAddressesFromPredicate :: UtxoRpc.UtxoPredicate -> Maybe (Set AddressAny)
120+
extractAddressesFromPredicate p =
121+
case (p ^. U5c.maybe'match, p ^. U5c.not, p ^. U5c.allOf, p ^. U5c.anyOf) of
122+
(Just pat, [], [], []) -> extractAddressFromPattern pat
123+
(Nothing, [], [], anyPreds@(_ : _)) ->
124+
Set.unions <$> traverse extractAddressesFromPredicate anyPreds
125+
_ -> Nothing
126+
where
127+
extractAddressFromPattern :: UtxoRpc.AnyUtxoPattern -> Maybe (Set AddressAny)
128+
extractAddressFromPattern pat = do
129+
txoPat <- pat ^. U5c.maybe'cardano
130+
addrPat <- txoPat ^. U5c.maybe'address
131+
let exact = addrPat ^. U5c.exactAddress
132+
guard $ not (BS.null exact)
133+
addrAny <- either (const Nothing) Just $ deserialiseFromRawBytes AsAddressAny exact
134+
pure $ Set.singleton addrAny

0 commit comments

Comments
 (0)