Skip to content
Draft
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
5 changes: 5 additions & 0 deletions cardano-rpc/cardano-rpc.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ library
Cardano.Rpc.Server.Internal.Monad
Cardano.Rpc.Server.Internal.Node
Cardano.Rpc.Server.Internal.Tracing
Cardano.Rpc.Server.Internal.UtxoRpc.Predicate
Cardano.Rpc.Server.Internal.UtxoRpc.Query
Cardano.Rpc.Server.Internal.UtxoRpc.Submit
Cardano.Rpc.Server.Internal.UtxoRpc.Type
Expand Down Expand Up @@ -121,15 +122,18 @@ test-suite cardano-rpc-test
type: exitcode-stdio-1.0
build-depends:
base,
bytestring,
cardano-api,
cardano-api:gen,
cardano-ledger-api,
cardano-ledger-conway,
cardano-ledger-core >=1.19,
cardano-rpc,
cardano-rpc:gen,
containers,
hedgehog,
hedgehog-extras,
proto-lens,
rio,
tasty,
tasty-hedgehog,
Expand All @@ -142,6 +146,7 @@ test-suite cardano-rpc-test

build-tool-depends: tasty-discover:tasty-discover
other-modules:
Test.Cardano.Rpc.Predicate
Test.Cardano.Rpc.ProtocolParameters
Test.Cardano.Rpc.TxOutput
Test.Cardano.Rpc.Type
375 changes: 222 additions & 153 deletions cardano-rpc/gen/Proto/Utxorpc/V1beta/Cardano/Cardano.hs

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions cardano-rpc/gen/Proto/Utxorpc/V1beta/Cardano/Cardano_Fields.hs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

189 changes: 119 additions & 70 deletions cardano-rpc/gen/Proto/Utxorpc/V1beta/Query/Query.hs

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions cardano-rpc/gen/Proto/Utxorpc/V1beta/Query/Query_Fields.hs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions cardano-rpc/proto/utxorpc/v1beta/cardano/cardano.proto
Original file line number Diff line number Diff line change
Expand Up @@ -516,15 +516,15 @@ message UpdateDRepCert {

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

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

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

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

// Pattern of a Tx that can be used to evaluate matching predicates.
Expand Down
7 changes: 4 additions & 3 deletions cardano-rpc/proto/utxorpc/v1beta/query/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ message ReadUtxosResponse {

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

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

// TODO: decide if we want to expand the scope
// rpc DumpUtxos(ReadUtxosRequest) returns (stream ReadUtxosResponse); // Dump all available utxos
Expand Down
1 change: 1 addition & 0 deletions cardano-rpc/src/Cardano/Rpc/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ methodsUtxoRpc
methodsUtxoRpc =
Method (mkNonStreaming $ wrapInSpan TraceRpcQueryParamsSpan . readParamsMethod)
. Method (mkNonStreaming $ wrapInSpan TraceRpcQueryReadUtxosSpan . readUtxosMethod)
. Method (mkNonStreaming $ wrapInSpan TraceRpcQuerySearchUtxosSpan . searchUtxosMethod)
$ NoMoreMethods

methodsUtxoRpcSubmit
Expand Down
4 changes: 4 additions & 0 deletions cardano-rpc/src/Cardano/Rpc/Server/Internal/Tracing.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ data TraceRpcQuery
TraceRpcQueryParamsSpan TraceSpanEvent
| -- | Span trace marking ReadUtxos query
TraceRpcQueryReadUtxosSpan TraceSpanEvent
| -- | Span trace marking SearchUtxos query
TraceRpcQuerySearchUtxosSpan TraceSpanEvent
deriving Show

instance Pretty TraceRpc where
Expand All @@ -53,6 +55,8 @@ instance Pretty TraceRpcQuery where
TraceRpcQueryParamsSpan (SpanEnd _) -> "Finished query params method"
TraceRpcQueryReadUtxosSpan (SpanBegin _) -> "Started query read UTXO method"
TraceRpcQueryReadUtxosSpan (SpanEnd _) -> "Finished query read UTXO method"
TraceRpcQuerySearchUtxosSpan (SpanBegin _) -> "Started query search UTXO method"
TraceRpcQuerySearchUtxosSpan (SpanEnd _) -> "Finished query search UTXO method"

instance Error TraceRpcQuery where
prettyError = pretty
Expand Down
134 changes: 134 additions & 0 deletions cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Predicate.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Cardano.Rpc.Server.Internal.UtxoRpc.Predicate
( matchesUtxoPredicate
, extractAddressesFromPredicate
, matchesAddressPattern
, matchesAssetPattern
, matchesTxOutputPattern
, matchesAnyUtxoPattern
, serialisePaymentCredential
, serialiseStakeCredential
)
where

import Cardano.Api.Address
import Cardano.Api.Serialise.Raw
import Cardano.Api.Tx
import Cardano.Api.Value
import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as U5c
import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as UtxoRpc

import RIO hiding (toList)

import Control.Monad (guard)
import Data.ByteString qualified as BS
import Data.Set (Set)
import Data.Set qualified as Set
import GHC.IsList

-- | Check if a UTxO entry matches a 'UtxoPredicate'.
-- All present fields are combined with AND logic.
matchesUtxoPredicate
:: UtxoRpc.UtxoPredicate
-> TxOut CtxUTxO era
-> Bool
matchesUtxoPredicate p txOut =
all (`matchesAnyUtxoPattern` txOut) (p ^. U5c.maybe'match)
&& not (any (`matchesUtxoPredicate` txOut) (p ^. U5c.not))
&& all (`matchesUtxoPredicate` txOut) (p ^. U5c.allOf)
&& (null (p ^. U5c.anyOf) || any (`matchesUtxoPredicate` txOut) (p ^. U5c.anyOf))

-- | Check if a UTxO entry matches an 'AnyUtxoPattern'.
-- Delegates to the Cardano-specific 'TxOutputPattern' if present.
matchesAnyUtxoPattern
:: UtxoRpc.AnyUtxoPattern
-> TxOut CtxUTxO era
-> Bool
matchesAnyUtxoPattern pat txOut =
all (`matchesTxOutputPattern` txOut) (pat ^. U5c.maybe'cardano)

-- | Check if a tx output matches a 'TxOutputPattern'.
-- Address and asset filters are combined with AND; absent fields are vacuously true.
matchesTxOutputPattern
:: UtxoRpc.TxOutputPattern
-> TxOut CtxUTxO era
-> Bool
matchesTxOutputPattern pat (TxOut addrInEra txOutValue _datum _script) =
all (`matchesAddressPattern` addrInEra) (pat ^. U5c.maybe'address)
&& all (`matchesAssetPattern` txOutValueToValue txOutValue) (pat ^. U5c.maybe'asset)

-- | Check if an address matches an 'AddressPattern'.
-- All present fields (exact, payment, delegation) must match (AND logic).
-- Byron addresses only support exact matching; payment\/delegation filters reject them.
matchesAddressPattern
:: UtxoRpc.AddressPattern
-> AddressInEra era
-> Bool
matchesAddressPattern pat addr =
exactMatch && paymentMatch && delegationMatch
where
matchesRawField field actual = BS.null field || field == actual

exactMatch = case addr of
AddressInEra ByronAddressInAnyEra a -> matchesRawField (pat ^. U5c.exactAddress) $ serialiseToRawBytes a
AddressInEra ShelleyAddressInEra{} a -> matchesRawField (pat ^. U5c.exactAddress) $ serialiseToRawBytes a
paymentMatch = case addr of
AddressInEra ShelleyAddressInEra{} (ShelleyAddress _ payCred _) ->
matchesRawField (pat ^. U5c.paymentPart) . serialisePaymentCredential $ fromShelleyPaymentCredential payCred
_ -> BS.null $ pat ^. U5c.paymentPart
delegationMatch = case addr of
AddressInEra ShelleyAddressInEra{} (ShelleyAddress _ _ stakeRef) ->
case fromShelleyStakeReference stakeRef of
StakeAddressByValue cred ->
matchesRawField (pat ^. U5c.delegationPart) $ serialiseStakeCredential cred
_ -> BS.null $ pat ^. U5c.delegationPart
_ -> BS.null $ pat ^. U5c.delegationPart

-- | Serialise a 'PaymentCredential' to raw bytes (the key or script hash).
serialisePaymentCredential :: PaymentCredential -> ByteString
serialisePaymentCredential (PaymentCredentialByKey h) = serialiseToRawBytes h
serialisePaymentCredential (PaymentCredentialByScript h) = serialiseToRawBytes h

-- | Serialise a 'StakeCredential' to raw bytes (the key or script hash).
serialiseStakeCredential :: StakeCredential -> ByteString
serialiseStakeCredential (StakeCredentialByKey h) = serialiseToRawBytes h
serialiseStakeCredential (StakeCredentialByScript h) = serialiseToRawBytes h

-- | Check if a 'Value' contains a native asset matching an 'AssetPattern'.
-- Ada entries are always skipped; zero-quantity entries do not match.
matchesAssetPattern
:: UtxoRpc.AssetPattern
-> Value
-> Bool
matchesAssetPattern pat value =
any matchesEntry (toList value)
where
pid = pat ^. U5c.policyId
aname = pat ^. U5c.assetName
matchesEntry (AssetId pId aName, Quantity qty) =
(BS.null pid || serialiseToRawBytes pId == pid)
&& (BS.null aname || serialiseToRawBytes aName == aname)
&& qty > 0
matchesEntry (AdaAssetId, _) = False

-- | Try to extract a set of exact addresses from the predicate for use with 'QueryUTxOByAddress'.
-- Returns 'Just' if the optimization is applicable, 'Nothing' otherwise.
extractAddressesFromPredicate :: UtxoRpc.UtxoPredicate -> Maybe (Set AddressAny)
extractAddressesFromPredicate p =
case (p ^. U5c.maybe'match, p ^. U5c.not, p ^. U5c.allOf, p ^. U5c.anyOf) of
(Just pat, [], [], []) -> extractAddressFromPattern pat
(Nothing, [], [], anyPreds@(_ : _)) ->
Set.unions <$> traverse extractAddressesFromPredicate anyPreds
_ -> Nothing
where
extractAddressFromPattern :: UtxoRpc.AnyUtxoPattern -> Maybe (Set AddressAny)
extractAddressFromPattern pat = do
txoPat <- pat ^. U5c.maybe'cardano
addrPat <- txoPat ^. U5c.maybe'address
let exact = addrPat ^. U5c.exactAddress
guard $ not (BS.null exact)
addrAny <- either (const Nothing) Just $ deserialiseFromRawBytes AsAddressAny exact
pure $ Set.singleton addrAny
Loading
Loading