Skip to content

Commit f6c2746

Browse files
committed
wip
1 parent e8c3657 commit f6c2746

File tree

8 files changed

+252
-15
lines changed

8 files changed

+252
-15
lines changed

CLAUDE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Claude Code notes
2+
3+
## Rules
4+
- Never manually edit generated code (e.g. proto-lens output in `gen/`). Use nix dev shell to run code generation tools (e.g. `nix develop --command bash -c "cd cardano-rpc && buf generate proto"`)
5+
- Never modify the nix store
6+
7+
## cardano-rpc patterns
8+
- `Proto msg` is a grapesy newtype wrapper. Internal functions should use plain proto-lens types, not `Proto`-wrapped. Use `getProto`/`fmap getProto` only at the RPC handler boundary.
9+
- RIO hides many Prelude functions. `sortBy` is NOT re-exported by RIO -- import from `Data.List`. Check RIO re-exports before assuming standard functions are in scope.
10+
- RIO's `^.` works with proto-lens van Laarhoven lenses. No need for `lens-family` dependency.
11+
- Use `toList` (from `GHC.IsList`) instead of deprecated `valueToList` for `Value`.
12+
13+
## Mistakes and wrong assumptions (lessons learned)
14+
- Assumed generated code could be patched by hand -- WRONG. Always use the project's code generation pipeline.
15+
- Assumed proto-lens types would be used wrapped in `Proto` everywhere -- WRONG. `Proto` is only at the gRPC handler boundary. Internal logic uses raw proto-lens types.
16+
- Assumed RIO re-exports all of `Data.List` -- WRONG. `sortBy`, `on`, and others need explicit imports.
17+
- Assumed I needed `lens-family` for proto-lens field access -- WRONG. RIO's `^.` (from `microlens`) is compatible with proto-lens van Laarhoven lenses.
18+
- Used deprecated `valueToList` instead of checking for the current API (`toList` via `GHC.IsList`).
19+
- Left redundant `IsEra` constraint on `matchesTxOutputPattern` -- should check if constraints are actually needed before adding them.
20+
- Wrote lambdas where infix notation was cleaner (`\sub -> f sub x` vs `` `f` x ``). hlint caught this.
21+
- Minimize nix build round-trips: verify types, imports, and constraints carefully before building.

cardano-rpc/cardano-rpc.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ library
7070
bytestring,
7171
cardano-api >=10.17,
7272
cardano-binary,
73+
cardano-crypto-class,
7374
cardano-ledger-api,
7475
cardano-ledger-conway,
7576
cardano-ledger-core,

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

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Query.hs

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
module Cardano.Rpc.Server.Internal.UtxoRpc.Query
1212
( readParamsMethod
1313
, readUtxosMethod
14+
, searchUtxosMethod
1415
)
1516
where
1617

@@ -26,8 +27,11 @@ import Cardano.Rpc.Server.Internal.UtxoRpc.Type
2627
import RIO hiding (toList)
2728

2829
import Data.Default
30+
import Data.List (sortBy)
2931
import Data.ProtoLens (defMessage)
32+
import Data.Text qualified as T
3033
import GHC.IsList
34+
import Network.GRPC.Common.Protobuf (getProto)
3135
import Network.GRPC.Spec
3236

3337
readParamsMethod
@@ -63,9 +67,6 @@ readUtxosMethod req = do
6367
utxoFilter <-
6468
if not (null $ req ^. U5c.keys)
6569
then QueryUTxOByTxIn . fromList <$> mapM txoRefToTxIn (req ^. U5c.keys)
66-
-- TODO: reimplement this part as SearchUtxosRequest
67-
-- \| Just addressesProto <- req ^. U5c.maybe'cardanoAddresses ->
68-
-- QueryUTxOByAddress . fromList <$> mapM readAddress (addressesProto ^. U5c.items)
6970
else pure QueryUTxOWhole
7071

7172
nodeConnInfo <- grab
@@ -89,7 +90,69 @@ readUtxosMethod req = do
8990
txId' <- throwEither $ deserialiseFromRawBytes AsTxId $ r ^. U5c.hash
9091
pure $ TxIn txId' (TxIx . fromIntegral $ r ^. U5c.index)
9192

92-
-- TODO: reimplement this part as SearchUtxosRequest
93-
-- readAddress :: MonadRpc e m => ByteString -> m AddressAny
94-
-- readAddress =
95-
-- throwEither . first stringException . P.runParser parseAddressAny <=< throwEither . T.decodeUtf8'
93+
searchUtxosMethod
94+
:: MonadRpc e m
95+
=> Proto UtxoRpc.SearchUtxosRequest
96+
-> m (Proto UtxoRpc.SearchUtxosResponse)
97+
searchUtxosMethod req = do
98+
let mPredicate = fmap getProto $ req ^. U5c.maybe'predicate
99+
maxItems = req ^. U5c.maxItems
100+
startToken = req ^. U5c.startToken
101+
102+
-- Determine query strategy: use address-based query if possible, otherwise fetch whole UTxO
103+
let utxoFilter = case mPredicate >>= extractAddressesFromPredicate of
104+
Just addrs | not (null addrs) -> QueryUTxOByAddress addrs
105+
_ -> QueryUTxOWhole
106+
107+
nodeConnInfo <- grab
108+
AnyCardanoEra era <- liftIO . throwExceptT $ determineEra nodeConnInfo
109+
eon <- forEraInEon @Era era (error "Minimum Conway era required") pure
110+
111+
let target = VolatileTip
112+
(utxo, chainPoint, blockNo) <- liftIO . (throwEither =<<) $ executeLocalStateQueryExpr nodeConnInfo target $ do
113+
utxo <- throwEither =<< throwEither =<< queryUtxo (convert eon) utxoFilter
114+
chainPoint <- throwEither =<< queryChainPoint
115+
blockNo <- throwEither =<< queryChainBlockNo
116+
pure (utxo, chainPoint, blockNo)
117+
118+
-- Apply predicate filtering on the result
119+
let filtered = case mPredicate of
120+
Nothing -> toList utxo
121+
Just p ->
122+
filter (\(_txIn, txOut) -> obtainCommonConstraints eon (matchesUtxoPredicate p txOut)) (toList utxo)
123+
124+
-- Sort by TxIn for deterministic pagination
125+
let sorted = sortBy (compare `on` fst) filtered
126+
127+
-- Apply pagination: skip past start_token, then take max_items
128+
let afterToken = case startToken of
129+
t | T.null t -> sorted
130+
_ -> dropWhile (\(txIn, _) -> encodeTxInToken txIn <= startToken) sorted
131+
limit = if maxItems > 0 then fromIntegral maxItems else 100 -- default page size
132+
page = take limit afterToken
133+
nextTok =
134+
if length page >= limit && length afterToken > limit
135+
then case lastMaybe page of
136+
Just (lastTxIn, _) -> encodeTxInToken lastTxIn
137+
Nothing -> ""
138+
else ""
139+
140+
pure $
141+
defMessage
142+
& U5c.ledgerTip .~ mkChainPointMsg chainPoint blockNo
143+
& U5c.items .~ obtainCommonConstraints eon (map (uncurry txInTxOutToAnyUtxoData) page)
144+
& U5c.nextToken .~ nextTok
145+
where
146+
txInTxOutToAnyUtxoData :: IsEra era => TxIn -> TxOut CtxUTxO era -> Proto UtxoRpc.AnyUtxoData
147+
txInTxOutToAnyUtxoData txIn txOut =
148+
defMessage
149+
& U5c.txoRef .~ inject txIn
150+
& U5c.cardano .~ txOutToUtxoRpcTxOutput txOut
151+
152+
encodeTxInToken :: TxIn -> T.Text
153+
encodeTxInToken (TxIn txId (TxIx ix)) =
154+
serialiseToRawBytesHexText txId <> "#" <> T.pack (show ix)
155+
156+
lastMaybe :: [a] -> Maybe a
157+
lastMaybe [] = Nothing
158+
lastMaybe xs = Just (last xs)

0 commit comments

Comments
 (0)