Skip to content

Commit 53297f1

Browse files
authored
Merge pull request #889 from IntersectMBO/mgalazyn/feature/add-utxorpc-readutxos-query
cardano-rpc | Add ReadUtxos query
2 parents 6e6486d + b2c9cea commit 53297f1

File tree

8 files changed

+230
-22
lines changed

8 files changed

+230
-22
lines changed

cardano-rpc/cardano-rpc.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,4 @@ library
9999
proto-lens-protobuf-types,
100100
proto-lens-runtime,
101101
rio,
102+
text,

cardano-rpc/proto/utxorpc/v1alpha/cardano/cardano.proto

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,47 @@ syntax = "proto3";
22

33
package utxorpc.v1alpha.cardano;
44

5+
// Represents a transaction output in the Cardano blockchain.
6+
message TxOutput {
7+
bytes address = 1; // Address receiving the output.
8+
uint64 coin = 2; // Amount of ADA in the output.
9+
repeated MultiAsset assets = 3; // Additional native (non-ADA) assets in the output.
10+
Datum datum = 4; // Plutus data associated with the output.
11+
Script script = 5; // Script associated with the output.
12+
}
13+
14+
// TODO u5c: replaced plutus_data with just bytes
15+
message Datum {
16+
bytes hash = 1; // Hash of this datum as seen on-chain
17+
bytes original_cbor = 3; // Original cbor-encoded data as seen on-chain
18+
}
19+
20+
// TODO u5c: mint_coin made optional
21+
// Represents a custom asset in the Cardano blockchain.
22+
message Asset {
23+
bytes name = 1; // Name of the custom asset.
24+
uint64 output_coin = 2; // Quantity of the custom asset in case of an output.
25+
optional int64 mint_coin = 3; // Quantity of the custom asset in case of a mint.
26+
}
27+
28+
// TODO u5c: redeemer was removed
29+
// Represents a multi-asset group in the Cardano blockchain.
30+
message MultiAsset {
31+
bytes policy_id = 1; // Policy ID governing the custom assets.
32+
repeated Asset assets = 2; // List of custom assets.
33+
}
34+
35+
// Represents a script in Cardano.
36+
// TODO u5c: removed native script representation
37+
message Script {
38+
oneof script {
39+
bytes native = 1; // Native script.
40+
bytes plutus_v1 = 2; // Plutus V1 script.
41+
bytes plutus_v2 = 3; // Plutus V2 script.
42+
bytes plutus_v3 = 4; // Plutus V3 script.
43+
}
44+
}
45+
546
// Represents a rational number as a fraction.
647
message RationalNumber {
748
int32 numerator = 1;
@@ -73,3 +114,4 @@ message PParams {
73114
uint64 drep_deposit = 30; // The drep deposit.
74115
uint64 drep_inactivity_period = 31; // The drep inactivity period.
75116
}
117+

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ message ChainPoint {
1515
uint64 timestamp = 4; // Block ms timestamp
1616
}
1717

18+
// Represents a reference to a transaction output
19+
message TxoRef {
20+
bytes hash = 1; // Tx hash.
21+
uint32 index = 2; // Output index.
22+
}
23+
24+
message TxoRefArray {
25+
// TODO u5c: changed to repeated
26+
repeated TxoRef items = 1;
27+
}
28+
1829
// Request to get the chain parameters
1930
message ReadParamsRequest {
2031
google.protobuf.FieldMask field_mask = 1; // Field mask to selectively return fields in the parsed response.
@@ -33,7 +44,38 @@ message ReadParamsResponse {
3344
ChainPoint ledger_tip = 2; // The chain point that represent the ledger current position.
3445
}
3546

47+
// TODO u5c: new type
48+
message AddressArray {
49+
repeated bytes items = 1;
50+
}
51+
52+
// An evenlope that holds an UTxO from any of compatible chains
53+
message AnyUtxoData {
54+
bytes native_bytes = 1; // Original bytes as defined by the chain
55+
TxoRef txo_ref = 2; // Hash of the previous transaction.
56+
oneof parsed_state {
57+
utxorpc.v1alpha.cardano.TxOutput cardano = 3; // A cardano UTxO
58+
}
59+
}
60+
61+
// Request to get specific UTxOs
62+
message ReadUtxosRequest {
63+
// TODO u5c: new oneof
64+
oneof query_args {
65+
TxoRefArray txoRefs = 1; // Array of Tx Output references
66+
AddressArray addresses = 2; // Array of addresses
67+
}
68+
google.protobuf.FieldMask field_mask = 3; // Field mask to selectively return fields.
69+
}
70+
71+
// Response containing the UTxOs associated with the requested addresses.
72+
message ReadUtxosResponse {
73+
repeated AnyUtxoData items = 1; // List of UTxOs.
74+
ChainPoint ledger_tip = 2; // The chain point that represent the ledger current position.
75+
}
76+
3677
// Service definition for querying the state of the chain.
3778
service QueryService {
3879
rpc ReadParams(ReadParamsRequest) returns (ReadParamsResponse); // Get overall chain state.
80+
rpc ReadUtxos(ReadUtxosRequest) returns (ReadUtxosResponse); // Read specific UTxOs by reference.
3981
}

cardano-rpc/src/Cardano/Rpc/Proto/Api/UtxoRpc/Query.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import Network.GRPC.Common.Protobuf
1414

1515
import Proto.Utxorpc.V1alpha.Cardano.Cardano
1616
import Proto.Utxorpc.V1alpha.Cardano.Cardano_Fields hiding
17-
( values
17+
( hash
18+
, values
1819
, vec'values
1920
)
2021
import Proto.Utxorpc.V1alpha.Query.Query

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ methodsUtxoRpc
5656
:: MonadRpc e m
5757
=> Methods m (ProtobufMethodsOf UtxoRpc.QueryService)
5858
methodsUtxoRpc =
59-
Method (mkNonStreaming readParamsMethod) NoMoreMethods
59+
Method (mkNonStreaming readParamsMethod)
60+
. Method (mkNonStreaming readUtxosMethod)
61+
$ NoMoreMethods
6062

6163
runRpcServer
6264
:: Tracer IO String
@@ -86,7 +88,7 @@ runRpcServer tracer loadRpcConfig = handleFatalExceptions $ do
8688

8789
-- TODO this is logged by node configuration already, so it would make sense to log it again when
8890
-- configuration gets reloaded
89-
-- traceWith $ "RPC configuration: " <> show rpcConfig
91+
-- traceWith tracer $ "RPC configuration: " <> show rpcConfig
9092

9193
when isEnabled $
9294
runRIO rpcEnv $

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

Lines changed: 94 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,41 @@
11
{-# LANGUAGE FlexibleInstances #-}
2+
{-# LANGUAGE GADTs #-}
23
{-# LANGUAGE MultiParamTypeClasses #-}
34
{-# LANGUAGE OverloadedLabels #-}
5+
{-# LANGUAGE ScopedTypeVariables #-}
6+
{-# LANGUAGE TypeApplications #-}
47
{-# OPTIONS_GHC -Wno-orphans #-}
58

69
module Cardano.Rpc.Server.Internal.Orphans () where
710

8-
import Cardano.Api.Block (ChainPoint (..), Hash (..), SlotNo (..))
9-
import Cardano.Api.Era (Inject (..))
11+
import Cardano.Api.Address
12+
import Cardano.Api.Era
13+
import Cardano.Api.Error
14+
import Cardano.Api.Ledger qualified as L
15+
import Cardano.Api.Plutus
16+
import Cardano.Api.Pretty
17+
import Cardano.Api.Serialise.Cbor
18+
import Cardano.Api.Serialise.Raw
19+
import Cardano.Api.Tx
20+
import Cardano.Api.Value
1021
import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as UtxoRpc
1122

12-
import Cardano.Ledger.Plutus qualified as L
23+
import RIO hiding (toList)
1324

14-
import RIO
15-
16-
import Data.ByteString.Short qualified as SBS
1725
import Data.ProtoLens (defMessage)
1826
import Data.Ratio (Ratio, denominator, numerator, (%))
27+
import Data.Text.Encoding qualified as T
28+
import GHC.IsList
1929
import Network.GRPC.Spec
2030

31+
---------------
32+
-- Conversion
33+
---------------
34+
35+
-- It's easier to use 'Proto a' wrappers for RPC types, because it makes lens automatically available.
36+
37+
-- TODO: write property tests for bijections
38+
2139
instance Inject (Proto UtxoRpc.RationalNumber) (Ratio Integer) where
2240
inject r = r ^. #numerator . to fromIntegral % r ^. #denominator . to fromIntegral
2341

@@ -40,12 +58,74 @@ instance Inject L.ExUnits (Proto UtxoRpc.ExUnits) where
4058
& #memory .~ fromIntegral mem
4159
& #steps .~ fromIntegral steps
4260

43-
instance Inject ChainPoint (Proto UtxoRpc.ChainPoint) where
44-
inject chainPoint = do
45-
let (slotNo, blockHash) =
46-
case chainPoint of
47-
ChainPointAtGenesis -> (0, mempty)
48-
ChainPoint (SlotNo slot) (HeaderHash hash) -> (slot, SBS.fromShort hash)
61+
-- | Note that conversion is not total in the other direction
62+
instance Inject TxIn (Proto UtxoRpc.TxoRef) where
63+
inject (TxIn txId' (TxIx txIx)) =
4964
defMessage
50-
& #slot .~ slotNo
51-
& #hash .~ blockHash
65+
& #hash .~ serialiseToRawBytes txId'
66+
& #index .~ fromIntegral txIx
67+
68+
instance Inject (ReferenceScript era) (Proto UtxoRpc.Script) where
69+
inject ReferenceScriptNone = defMessage
70+
inject (ReferenceScript _ (ScriptInAnyLang _ script)) =
71+
case script of
72+
SimpleScript _ ->
73+
defMessage & #native .~ serialiseToCBOR script
74+
PlutusScript PlutusScriptV1 ps ->
75+
defMessage & #plutusV1 .~ serialiseToRawBytes ps
76+
PlutusScript PlutusScriptV2 ps ->
77+
defMessage & #plutusV2 .~ serialiseToRawBytes ps
78+
PlutusScript PlutusScriptV3 ps ->
79+
defMessage & #plutusV3 .~ serialiseToRawBytes ps
80+
81+
instance IsCardanoEra era => Inject (UTxO era) [Proto UtxoRpc.AnyUtxoData] where
82+
inject utxo =
83+
toList utxo <&> \(txIn, TxOut addressInEra txOutValue datum script) -> do
84+
let multiAsset =
85+
fromList $
86+
toList (valueToPolicyAssets $ txOutValueToValue txOutValue) <&> \(pId, policyAssets) -> do
87+
let assets =
88+
toList policyAssets <&> \(assetName, Quantity qty) -> do
89+
defMessage
90+
& #name .~ serialiseToRawBytes assetName
91+
-- we don't have access to info it the coin was minted in the transaction,
92+
-- maybe we should add it later
93+
& #maybe'mintCoin .~ Nothing
94+
& #outputCoin .~ fromIntegral qty
95+
defMessage
96+
& #policyId .~ serialiseToRawBytes pId
97+
& #assets .~ assets
98+
datumRpc = case datum of
99+
TxOutDatumNone ->
100+
defMessage
101+
TxOutDatumHash _ scriptDataHash ->
102+
defMessage
103+
& #hash .~ serialiseToRawBytes scriptDataHash
104+
& #originalCbor .~ mempty -- we don't have it
105+
TxOutDatumInline _ hashableScriptData ->
106+
defMessage
107+
& #hash .~ serialiseToRawBytes (hashScriptDataBytes hashableScriptData)
108+
& #originalCbor .~ getOriginalScriptDataBytes hashableScriptData
109+
110+
protoTxOut =
111+
defMessage
112+
-- TODO we don't have serialiseToRawBytes for AddressInEra, so perhaps this is wrong, because 'address'
113+
-- has type bytes, but we're putting text there
114+
& #address .~ T.encodeUtf8 (cardanoEraConstraints (cardanoEra @era) $ serialiseAddress addressInEra)
115+
& #coin .~ fromIntegral (L.unCoin (txOutValueToLovelace txOutValue))
116+
& #assets .~ multiAsset
117+
& #datum .~ datumRpc
118+
& #script .~ inject script
119+
defMessage
120+
& #nativeBytes .~ "" -- TODO where to get that from? run cbor serialisation of utxos list?
121+
& #txoRef .~ inject txIn
122+
& #cardano .~ protoTxOut
123+
124+
-----------
125+
-- Errors
126+
-----------
127+
128+
-- TODO add RIO to cardano-api and move this instance there
129+
130+
instance Error StringException where
131+
prettyError = pshow

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

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@
33
{-# LANGUAGE DerivingVia #-}
44
{-# LANGUAGE FlexibleContexts #-}
55
{-# LANGUAGE GADTs #-}
6+
{-# LANGUAGE MultiWayIf #-}
67
{-# LANGUAGE OverloadedLabels #-}
78
{-# LANGUAGE QuantifiedConstraints #-}
89
{-# LANGUAGE RankNTypes #-}
910
{-# LANGUAGE ScopedTypeVariables #-}
1011

1112
module Cardano.Rpc.Server.Internal.UtxoRpc.Query
1213
( readParamsMethod
14+
, readUtxosMethod
1315
)
1416
where
1517

1618
import Cardano.Api
1719
import Cardano.Api.Ledger qualified as L
20+
import Cardano.Api.Parser.Text qualified as P
1821
import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as UtxoRpc
1922
import Cardano.Rpc.Server.Internal.Error
2023
import Cardano.Rpc.Server.Internal.Monad
@@ -27,11 +30,13 @@ import Cardano.Ledger.Conway.Core qualified as L
2730
import Cardano.Ledger.Conway.PParams qualified as L
2831
import Cardano.Ledger.Plutus qualified as L
2932

30-
import RIO
33+
import RIO hiding (toList)
3134

3235
import Data.ByteString.Short qualified as SBS
3336
import Data.Map.Strict qualified as M
3437
import Data.ProtoLens (defMessage)
38+
import Data.Text.Encoding qualified as T
39+
import GHC.IsList
3540
import Network.GRPC.Spec
3641

3742
readParamsMethod
@@ -63,8 +68,7 @@ readParamsMethod _req = do
6368
drepVotingThresholds :: L.DRepVotingThresholds =
6469
conwayEraOnwardsConstraints eon $
6570
pparams ^. L.ppDRepVotingThresholdsL
66-
67-
let pparamsMsg =
71+
pparamsMsg =
6872
conwayEraOnwardsConstraints eon $
6973
defMessage
7074
& #coinsPerUtxoByte .~ pparams ^. L.ppCoinsPerUTxOByteL . to L.unCoinPerByte . to fromIntegral
@@ -149,4 +153,40 @@ mkChainPointMsg chainPoint blockNo = do
149153
& #hash .~ blockHash
150154
& #height .~ blockHeight
151155

152-
-- & #timestamp .~ timestamp -- not supported currently
156+
readUtxosMethod
157+
:: MonadRpc e m
158+
=> Proto UtxoRpc.ReadUtxosRequest
159+
-> m (Proto UtxoRpc.ReadUtxosResponse)
160+
readUtxosMethod req = do
161+
utxoFilter <-
162+
if
163+
| Just txoRefs <- req ^. #maybe'txoRefs ->
164+
QueryUTxOByTxIn . fromList <$> mapM txoRefToTxIn (txoRefs ^. #items)
165+
| Just addressesProto <- req ^. #maybe'addresses ->
166+
QueryUTxOByAddress . fromList <$> mapM readAddress (addressesProto ^. #items)
167+
| otherwise -> pure QueryUTxOWhole
168+
169+
nodeConnInfo <- grab
170+
AnyCardanoEra era <- liftIO . throwExceptT $ determineEra nodeConnInfo
171+
eon <- forEraInEon era (error "Minimum Shelley era required") pure
172+
173+
let target = VolatileTip
174+
(utxo, chainPoint, blockNo) <- liftIO . (throwEither =<<) $ executeLocalStateQueryExpr nodeConnInfo target $ do
175+
utxo <- throwEither =<< throwEither =<< queryUtxo eon utxoFilter
176+
chainPoint <- throwEither =<< queryChainPoint
177+
blockNo <- throwEither =<< queryChainBlockNo
178+
pure (utxo, chainPoint, blockNo)
179+
180+
pure $
181+
defMessage
182+
& #ledgerTip .~ mkChainPointMsg chainPoint blockNo
183+
& #items .~ cardanoEraConstraints era (inject utxo)
184+
where
185+
txoRefToTxIn :: MonadRpc e m => Proto UtxoRpc.TxoRef -> m TxIn
186+
txoRefToTxIn r = do
187+
txId' <- throwEither $ deserialiseFromRawBytes AsTxId $ r ^. #hash
188+
pure $ TxIn txId' (TxIx . fromIntegral $ r ^. #index)
189+
190+
readAddress :: MonadRpc e m => ByteString -> m AddressAny
191+
readAddress =
192+
throwEither . first stringException . P.runParser parseAddressAny <=< throwEither . T.decodeUtf8'

fourmolu.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ sort-deriving-clauses: false
5555
trailing-section-operators: true
5656
unicode: never
5757
respectful: false
58-
fixities:
58+
fixities:
5959
- infixl 1 &
6060
- infixr 4 .~
6161
reexports: []

0 commit comments

Comments
 (0)