Skip to content

Commit 1a48165

Browse files
committed
wip
1 parent a93650f commit 1a48165

7 files changed

Lines changed: 232 additions & 122 deletions

File tree

.claude/skills/build-fix/SKILL.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
name: build-fix
3+
description: Build a cabal component with nix, parse GHC errors and warnings, fix them, and rebuild until clean.
4+
disable-model-invocation: true
5+
argument-hint: [nix-flake-target]
6+
---
7+
8+
Build and iteratively fix GHC errors/warnings for the given nix flake target.
9+
10+
If no argument is given, default to `'.#cardano-rpc:lib:cardano-rpc'`.
11+
12+
Target: $ARGUMENTS
13+
14+
## Procedure
15+
16+
1. Run `nix build '<target>' 2>&1` and capture the output.
17+
2. Parse the output for GHC errors and warnings.
18+
3. If there are errors, fix them one category at a time:
19+
- **Missing imports**: Look up which module exports the symbol. Remember that RIO does NOT re-export everything from Prelude/Data.List (e.g. `sortBy`, `on` need explicit imports).
20+
- **Type mismatches**: Analyze carefully. In cardano-rpc, remember that `Proto msg` is a grapesy wrapper — internal functions use plain proto-lens types, `getProto`/`fmap getProto` at handler boundaries only.
21+
- **Not in scope**: Check if it's a missing import or a typo.
22+
- **Redundant constraints**: Remove them.
23+
- **Redundant imports**: Remove them.
24+
- **Deprecated functions**: Replace with the recommended alternative (e.g. `valueToList` -> `toList` from `GHC.IsList`).
25+
4. After fixing, rebuild.
26+
5. Repeat until the build succeeds with no errors.
27+
6. If there are warnings remaining, fix them too:
28+
- Redundant imports/constraints: remove
29+
- hlint-style suggestions: apply (e.g. lambda to infix)
30+
- Deprecated usage: replace
31+
7. Rebuild one final time to confirm clean output.
32+
33+
## Important rules
34+
- NEVER manually edit files under `gen/` — those are generated by proto-lens.
35+
- If proto generated code needs updating, use `/proto-gen` instead.
36+
- RIO's `^.` works with proto-lens lenses. Do NOT add `lens-family` as a dependency.
37+
- Verify fixes carefully before rebuilding to minimize nix build round-trips (each takes minutes).

.claude/skills/proto-gen/SKILL.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
name: proto-gen
3+
description: Regenerate proto-lens Haskell code from .proto files using buf in the nix dev shell.
4+
disable-model-invocation: true
5+
argument-hint: [package-dir]
6+
---
7+
8+
Regenerate proto-lens code from .proto files.
9+
10+
If no argument is given, default to `cardano-rpc`.
11+
12+
Package directory: $ARGUMENTS
13+
14+
## Procedure
15+
16+
1. Run: `nix develop --command bash -c "cd <package-dir> && buf generate proto"`
17+
2. Verify the command succeeded.
18+
3. Show a summary of which files were regenerated (check git status for changed files under `<package-dir>/gen/`).
19+
20+
## Important rules
21+
- NEVER manually edit files under `gen/` — they are overwritten by this command.
22+
- If buf or proto-lens-protoc are not found, it means you're not in the nix dev shell — always use `nix develop --command`.
23+
- After regeneration, you may need to run `/build-fix` to ensure everything compiles.

.claude/skills/rio-check/SKILL.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
name: rio-check
3+
description: Check if a Haskell function or type is re-exported by the RIO module.
4+
disable-model-invocation: true
5+
argument-hint: [symbol-name]
6+
---
7+
8+
Check whether the symbol `$ARGUMENTS` is re-exported by RIO.
9+
10+
## Procedure
11+
12+
1. Find the RIO package source in the nix store by searching for the RIO module file:
13+
```
14+
find /nix/store -path '*/RIO.hs' -name 'RIO.hs' 2>/dev/null | head -5
15+
```
16+
Or search the project's dependency tree:
17+
```
18+
grep -r 'module RIO' $(nix build '.#cardano-rpc:lib:cardano-rpc' --print-out-paths 2>/dev/null)/lib/ 2>/dev/null
19+
```
20+
2. Search for the symbol in RIO's module exports and re-exports.
21+
3. Report:
22+
- Whether the symbol is available from RIO
23+
- If NOT available, which module to import it from (e.g. `Data.List`, `Data.Map`, etc.)
24+
- Whether RIO hides it (some symbols are explicitly hidden, like `toList`)
25+
26+
## Known RIO gaps (common gotchas)
27+
- `sortBy` — NOT in RIO, import from `Data.List`
28+
- `on` — NOT in RIO, import from `Data.Function`
29+
- `toList` — RIO re-exports from GHC.Exts but some modules hide it; use `GHC.IsList` or import explicitly
30+
- `sortOn`, `sort` — available in RIO

cardano-rpc/cardano-rpc.cabal

Lines changed: 1 addition & 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
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
{-# LANGUAGE FlexibleContexts #-}
2+
{-# LANGUAGE GADTs #-}
3+
{-# LANGUAGE ScopedTypeVariables #-}
4+
5+
module Cardano.Rpc.Server.Internal.UtxoRpc.Predicate
6+
( matchesUtxoPredicate
7+
, extractAddressesFromPredicate
8+
)
9+
where
10+
11+
import Cardano.Api.Address
12+
import Cardano.Api.Serialise.Raw
13+
import Cardano.Api.Tx
14+
import Cardano.Api.Value
15+
import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as U5c
16+
import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as UtxoRpc
17+
18+
import RIO hiding (toList)
19+
20+
import Data.ByteString qualified as BS
21+
import Data.Set (Set)
22+
import Data.Set qualified as Set
23+
import GHC.IsList
24+
25+
-- | Check if a UTxO entry matches a 'UtxoPredicate'.
26+
-- All present fields are combined with AND logic.
27+
matchesUtxoPredicate
28+
:: UtxoRpc.UtxoPredicate
29+
-> TxOut CtxUTxO era
30+
-> Bool
31+
matchesUtxoPredicate p txOut =
32+
matchField && notField && allOfField && anyOfField
33+
where
34+
matchField = case p ^. U5c.maybe'match of
35+
Nothing -> True
36+
Just pat -> matchesAnyUtxoPattern pat txOut
37+
notField = all (\sub -> Prelude.not $ matchesUtxoPredicate sub txOut) (p ^. U5c.not)
38+
allOfField = case p ^. U5c.allOf of
39+
[] -> True
40+
ps -> all (`matchesUtxoPredicate` txOut) ps
41+
anyOfField = case p ^. U5c.anyOf of
42+
[] -> True
43+
ps -> any (`matchesUtxoPredicate` txOut) ps
44+
45+
matchesAnyUtxoPattern
46+
:: UtxoRpc.AnyUtxoPattern
47+
-> TxOut CtxUTxO era
48+
-> Bool
49+
matchesAnyUtxoPattern pat txOut =
50+
case pat ^. U5c.maybe'utxoPattern of
51+
Just (UtxoRpc.AnyUtxoPattern'Cardano txOutputPattern) ->
52+
matchesTxOutputPattern txOutputPattern txOut
53+
Nothing -> True
54+
55+
matchesTxOutputPattern
56+
:: UtxoRpc.TxOutputPattern
57+
-> TxOut CtxUTxO era
58+
-> Bool
59+
matchesTxOutputPattern pat (TxOut addrInEra txOutValue _datum _script) =
60+
addressMatches && assetMatches
61+
where
62+
addressMatches = case pat ^. U5c.maybe'address of
63+
Nothing -> True
64+
Just addrPat -> matchesAddressPattern addrPat addrInEra
65+
assetMatches = case pat ^. U5c.maybe'asset of
66+
Nothing -> True
67+
Just assetPat -> matchesAssetPattern assetPat (txOutValueToValue txOutValue)
68+
69+
matchesAddressPattern
70+
:: UtxoRpc.AddressPattern
71+
-> AddressInEra era
72+
-> Bool
73+
matchesAddressPattern pat addr =
74+
exactMatch && paymentMatch && delegationMatch
75+
where
76+
exact = pat ^. U5c.exactAddress
77+
exactMatch = case addr of
78+
AddressInEra ByronAddressInAnyEra a -> BS.null exact || serialiseToRawBytes a == exact
79+
AddressInEra ShelleyAddressInEra{} a -> BS.null exact || serialiseToRawBytes a == exact
80+
payment = pat ^. U5c.paymentPart
81+
paymentMatch = case addr of
82+
AddressInEra ShelleyAddressInEra{} (ShelleyAddress _ payCred _) ->
83+
BS.null payment || serialisePaymentCredential (fromShelleyPaymentCredential payCred) == payment
84+
_ -> BS.null payment
85+
deleg = pat ^. U5c.delegationPart
86+
delegationMatch = case addr of
87+
AddressInEra ShelleyAddressInEra{} (ShelleyAddress _ _ stakeRef) ->
88+
case fromShelleyStakeReference stakeRef of
89+
StakeAddressByValue cred ->
90+
BS.null deleg || serialiseStakeCredential cred == deleg
91+
_ -> BS.null deleg
92+
_ -> BS.null deleg
93+
94+
serialisePaymentCredential :: PaymentCredential -> ByteString
95+
serialisePaymentCredential (PaymentCredentialByKey h) = serialiseToRawBytes h
96+
serialisePaymentCredential (PaymentCredentialByScript h) = serialiseToRawBytes h
97+
98+
serialiseStakeCredential :: StakeCredential -> ByteString
99+
serialiseStakeCredential (StakeCredentialByKey h) = serialiseToRawBytes h
100+
serialiseStakeCredential (StakeCredentialByScript h) = serialiseToRawBytes h
101+
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+
-- Simple match with exact_address only
123+
(Just pat, [], [], []) -> extractAddressFromPattern pat
124+
-- any_of where each has an exact address
125+
(Nothing, [], [], anyPreds@(_ : _)) ->
126+
foldM (\acc sub -> Set.union acc <$> extractAddressesFromPredicate sub) Set.empty anyPreds
127+
_ -> Nothing
128+
where
129+
extractAddressFromPattern :: UtxoRpc.AnyUtxoPattern -> Maybe (Set AddressAny)
130+
extractAddressFromPattern pat =
131+
case pat ^. U5c.maybe'utxoPattern of
132+
Just (UtxoRpc.AnyUtxoPattern'Cardano txoPat) -> do
133+
addrPat <- txoPat ^. U5c.maybe'address
134+
let exact = addrPat ^. U5c.exactAddress
135+
if BS.null exact
136+
then Nothing
137+
else do
138+
addrAny <- either (const Nothing) Just $ deserialiseFromRawBytes AsAddressAny exact
139+
Just (Set.singleton addrAny)
140+
Nothing -> Nothing

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as UtxoRpc
2222
import Cardano.Rpc.Server.Internal.Error
2323
import Cardano.Rpc.Server.Internal.Monad
2424
import Cardano.Rpc.Server.Internal.Orphans ()
25+
import Cardano.Rpc.Server.Internal.UtxoRpc.Predicate
2526
import Cardano.Rpc.Server.Internal.UtxoRpc.Type
2627

2728
import RIO hiding (toList)

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

Lines changed: 0 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ module Cardano.Rpc.Server.Internal.UtxoRpc.Type
1919
, simpleScriptToUtxoRpcNativeScript
2020
, utxoRpcBigIntToInteger
2121
, mkChainPointMsg
22-
, matchesUtxoPredicate
23-
, extractAddressesFromPredicate
2422
)
2523
where
2624

@@ -51,13 +49,10 @@ import Cardano.Ledger.Plutus qualified as L
5149

5250
import RIO hiding (toList)
5351

54-
import Data.ByteString qualified as BS
5552
import Data.ByteString.Short qualified as SBS
5653
import Data.Default
5754
import Data.Map.Strict qualified as M
5855
import Data.ProtoLens (defMessage)
59-
import Data.Set (Set)
60-
import Data.Set qualified as Set
6156
import Data.Text qualified as T
6257
import Data.Text.Encoding qualified as T
6358
import GHC.IsList
@@ -601,120 +596,3 @@ utxoRpcBigIntToInteger bigInt
601596
| Just bytes <- bigInt ^. U5c.maybe'bigUInt =
602597
fmap fromIntegral . liftEitherError $ deserialiseFromRawBytes AsNatural bytes
603598
| otherwise = pure 0 -- assume default value
604-
605-
-- | Check if a UTxO entry matches a 'UtxoPredicate'.
606-
-- All present fields are combined with AND logic.
607-
matchesUtxoPredicate
608-
:: UtxoRpc.UtxoPredicate
609-
-> TxOut CtxUTxO era
610-
-> Bool
611-
matchesUtxoPredicate p txOut =
612-
matchField && notField && allOfField && anyOfField
613-
where
614-
matchField = case p ^. U5c.maybe'match of
615-
Nothing -> True
616-
Just pat -> matchesAnyUtxoPattern pat txOut
617-
notField = all (\sub -> Prelude.not $ matchesUtxoPredicate sub txOut) (p ^. U5c.not)
618-
allOfField = case p ^. U5c.allOf of
619-
[] -> True
620-
ps -> all (`matchesUtxoPredicate` txOut) ps
621-
anyOfField = case p ^. U5c.anyOf of
622-
[] -> True
623-
ps -> any (`matchesUtxoPredicate` txOut) ps
624-
625-
matchesAnyUtxoPattern
626-
:: UtxoRpc.AnyUtxoPattern
627-
-> TxOut CtxUTxO era
628-
-> Bool
629-
matchesAnyUtxoPattern pat txOut =
630-
case pat ^. U5c.maybe'utxoPattern of
631-
Just (UtxoRpc.AnyUtxoPattern'Cardano txOutputPattern) ->
632-
matchesTxOutputPattern txOutputPattern txOut
633-
Nothing -> True
634-
635-
matchesTxOutputPattern
636-
:: UtxoRpc.TxOutputPattern
637-
-> TxOut CtxUTxO era
638-
-> Bool
639-
matchesTxOutputPattern pat (TxOut addrInEra txOutValue _datum _script) =
640-
addressMatches && assetMatches
641-
where
642-
addressMatches = case pat ^. U5c.maybe'address of
643-
Nothing -> True
644-
Just addrPat -> matchesAddressPattern addrPat addrInEra
645-
assetMatches = case pat ^. U5c.maybe'asset of
646-
Nothing -> True
647-
Just assetPat -> matchesAssetPattern assetPat (txOutValueToValue txOutValue)
648-
649-
matchesAddressPattern
650-
:: UtxoRpc.AddressPattern
651-
-> AddressInEra era
652-
-> Bool
653-
matchesAddressPattern pat addr =
654-
exactMatch && paymentMatch && delegationMatch
655-
where
656-
exact = pat ^. U5c.exactAddress
657-
exactMatch = case addr of
658-
AddressInEra ByronAddressInAnyEra a -> BS.null exact || serialiseToRawBytes a == exact
659-
AddressInEra ShelleyAddressInEra{} a -> BS.null exact || serialiseToRawBytes a == exact
660-
payment = pat ^. U5c.paymentPart
661-
paymentMatch = case addr of
662-
AddressInEra ShelleyAddressInEra{} (ShelleyAddress _ payCred _) ->
663-
BS.null payment || serialisePaymentCredential (fromShelleyPaymentCredential payCred) == payment
664-
_ -> BS.null payment
665-
deleg = pat ^. U5c.delegationPart
666-
delegationMatch = case addr of
667-
AddressInEra ShelleyAddressInEra{} (ShelleyAddress _ _ stakeRef) ->
668-
case fromShelleyStakeReference stakeRef of
669-
StakeAddressByValue cred ->
670-
BS.null deleg || serialiseStakeCredential cred == deleg
671-
_ -> BS.null deleg
672-
_ -> BS.null deleg
673-
674-
serialisePaymentCredential :: PaymentCredential -> ByteString
675-
serialisePaymentCredential (PaymentCredentialByKey h) = serialiseToRawBytes h
676-
serialisePaymentCredential (PaymentCredentialByScript h) = serialiseToRawBytes h
677-
678-
serialiseStakeCredential :: StakeCredential -> ByteString
679-
serialiseStakeCredential (StakeCredentialByKey h) = serialiseToRawBytes h
680-
serialiseStakeCredential (StakeCredentialByScript h) = serialiseToRawBytes h
681-
682-
matchesAssetPattern
683-
:: UtxoRpc.AssetPattern
684-
-> Value
685-
-> Bool
686-
matchesAssetPattern pat value =
687-
any matchesEntry (toList value)
688-
where
689-
pid = pat ^. U5c.policyId
690-
aname = pat ^. U5c.assetName
691-
matchesEntry (AssetId pId aName, Quantity qty) =
692-
(BS.null pid || serialiseToRawBytes pId == pid)
693-
&& (BS.null aname || serialiseToRawBytes aName == aname)
694-
&& qty > 0
695-
matchesEntry (AdaAssetId, _) = False
696-
697-
-- | Try to extract a set of exact addresses from the predicate for use with 'QueryUTxOByAddress'.
698-
-- Returns 'Just' if the optimization is applicable, 'Nothing' otherwise.
699-
extractAddressesFromPredicate :: UtxoRpc.UtxoPredicate -> Maybe (Set AddressAny)
700-
extractAddressesFromPredicate p =
701-
case (p ^. U5c.maybe'match, p ^. U5c.not, p ^. U5c.allOf, p ^. U5c.anyOf) of
702-
-- Simple match with exact_address only
703-
(Just pat, [], [], []) -> extractAddressFromPattern pat
704-
-- any_of where each has an exact address
705-
(Nothing, [], [], anyPreds@(_ : _)) ->
706-
foldM (\acc sub -> Set.union acc <$> extractAddressesFromPredicate sub) Set.empty anyPreds
707-
_ -> Nothing
708-
where
709-
extractAddressFromPattern :: UtxoRpc.AnyUtxoPattern -> Maybe (Set AddressAny)
710-
extractAddressFromPattern pat =
711-
case pat ^. U5c.maybe'utxoPattern of
712-
Just (UtxoRpc.AnyUtxoPattern'Cardano txoPat) -> do
713-
addrPat <- txoPat ^. U5c.maybe'address
714-
let exact = addrPat ^. U5c.exactAddress
715-
if BS.null exact
716-
then Nothing
717-
else do
718-
addrAny <- either (const Nothing) Just $ deserialiseFromRawBytes AsAddressAny exact
719-
Just (Set.singleton addrAny)
720-
Nothing -> Nothing

0 commit comments

Comments
 (0)