diff --git a/.changes/20260602_cardano_api_consensus_reexports.yml b/.changes/20260602_cardano_api_consensus_reexports.yml new file mode 100644 index 0000000000..1521a3f271 --- /dev/null +++ b/.changes/20260602_cardano_api_consensus_reexports.yml @@ -0,0 +1,6 @@ +project: cardano-api +pr: 1232 +kind: + - compatible +description: | + Re-export consensus types from Cardano.Api.Consensus for node kernel access: NodeKernel, ChainDB, BlockComponent, RealPoint, OneEraHash, HeaderHash, HasHeader, BlockType, StandardCrypto, CardanoBlock, blockNo. diff --git a/.changes/20260602_cardano_rpc_fetchblock.yml b/.changes/20260602_cardano_rpc_fetchblock.yml new file mode 100644 index 0000000000..46a84ae39c --- /dev/null +++ b/.changes/20260602_cardano_rpc_fetchblock.yml @@ -0,0 +1,6 @@ +project: cardano-rpc +pr: 1232 +kind: + - feature +description: | + Implement FetchBlock SyncService method via node kernel access. Fetches blocks from ChainDB with raw CBOR bytes and cardano block header (slot, hash, height). diff --git a/.gitignore b/.gitignore index ee16bc3aa7..2fa5cba418 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,4 @@ cardano-wasm/examples/*/main.js cardano-wasm/examples/*/cardano_node_grpc_web_pb.js .ghc-wasm/ +.serena/ diff --git a/cardano-api/src/Cardano/Api/Consensus.hs b/cardano-api/src/Cardano/Api/Consensus.hs index db9377bb28..957069f0fe 100644 --- a/cardano-api/src/Cardano/Api/Consensus.hs +++ b/cardano-api/src/Cardano/Api/Consensus.hs @@ -47,18 +47,38 @@ module Cardano.Api.Consensus , ProtocolClientInfoArgs (..) -- * Reexports from @ouroboros-consensus@ + , BlockComponent (..) , ByronBlock + , CardanoBlock + , ChainDB.ChainDB + , ChainDB.getBlockComponent + , ChainDB.getCurrentLedger + , ConfigSupportsNode + , nodeSystemStart , ChainDepState , GenTx (..) , EraMismatch (..) + , HasHardForkHistory (..) + , HasHeader + , HeaderHash + , NodeKernel (..) + , OneEraHash (..) , PastHorizonException , PraosProtocolSupportsNode , PraosProtocolSupportsNodeCrypto + , RealPoint (..) , ShelleyGenesisStaking (..) + , StandardCrypto + , TopLevelConfig + , ledgerState + , blockNo , byronIdTx + , configBlock + , configLedger , condense , getOpCertCounters , interpreterToEpochInfo + , mkInterpreter , unsafeExtendSafeZone , txId ) @@ -68,3 +88,5 @@ import Cardano.Api.Consensus.Internal.InMode import Cardano.Api.Consensus.Internal.Mode import Cardano.Api.Consensus.Internal.Protocol import Cardano.Api.Consensus.Internal.Reexport + +import Ouroboros.Consensus.Storage.ChainDB qualified as ChainDB diff --git a/cardano-api/src/Cardano/Api/Consensus/Internal/Reexport.hs b/cardano-api/src/Cardano/Api/Consensus/Internal/Reexport.hs index 58b069dc0c..296fa79fb3 100644 --- a/cardano-api/src/Cardano/Api/Consensus/Internal/Reexport.hs +++ b/cardano-api/src/Cardano/Api/Consensus/Internal/Reexport.hs @@ -1,29 +1,56 @@ module Cardano.Api.Consensus.Internal.Reexport - ( ByronBlock + ( BlockComponent (..) + , ByronBlock + , CardanoBlock + , ConfigSupportsNode + , nodeSystemStart , ChainDepState , GenTx (..) + , HasHeader + , HeaderHash , EraMismatch (..) + , NodeKernel (..) + , OneEraHash (..) + , HasHardForkHistory (..) , PastHorizonException , PraosProtocolSupportsNode , PraosProtocolSupportsNodeCrypto + , RealPoint (..) , ShelleyGenesisStaking (..) + , StandardCrypto + , TopLevelConfig + , ledgerState + , blockNo , byronIdTx + , configBlock + , configLedger , condense , getOpCertCounters , interpreterToEpochInfo + , mkInterpreter , unsafeExtendSafeZone , txId ) where +import Cardano.Protocol.Crypto (StandardCrypto) +import Cardano.Slotting.Time (SystemStart) +import Ouroboros.Consensus.Block (HasHeader, HeaderHash, RealPoint (..), blockNo) import Ouroboros.Consensus.Byron.Ledger (ByronBlock, GenTx (..), byronIdTx) -import Ouroboros.Consensus.Cardano.Block (EraMismatch (..)) +import Ouroboros.Consensus.Cardano.Block (CardanoBlock, EraMismatch (..)) +import Ouroboros.Consensus.Config (TopLevelConfig, configBlock, configLedger) +import Ouroboros.Consensus.Config.SupportsNode (ConfigSupportsNode (getSystemStart)) +import Ouroboros.Consensus.HardFork.Abstract (HasHardForkHistory (..)) +import Ouroboros.Consensus.HardFork.Combinator.AcrossEras (OneEraHash (..)) import Ouroboros.Consensus.HardFork.History.EpochInfo (interpreterToEpochInfo) import Ouroboros.Consensus.HardFork.History.Qry ( PastHorizonException + , mkInterpreter , unsafeExtendSafeZone ) +import Ouroboros.Consensus.Ledger.Extended (ledgerState) import Ouroboros.Consensus.Ledger.SupportsMempool (txId) +import Ouroboros.Consensus.Node (NodeKernel (..)) import Ouroboros.Consensus.Protocol.Abstract (ChainDepState) import Ouroboros.Consensus.Protocol.Praos.Common ( PraosProtocolSupportsNode @@ -31,4 +58,9 @@ import Ouroboros.Consensus.Protocol.Praos.Common , getOpCertCounters ) import Ouroboros.Consensus.Shelley.Node (ShelleyGenesisStaking (..)) +import Ouroboros.Consensus.Storage.Common (BlockComponent (..)) import Ouroboros.Consensus.Util.Condense (condense) + +-- | Extract the network system start time from the node's top-level configuration. +nodeSystemStart :: ConfigSupportsNode blk => TopLevelConfig blk -> SystemStart +nodeSystemStart = getSystemStart . configBlock diff --git a/cardano-rpc/README.md b/cardano-rpc/README.md index d48dbc0429..599d5321f6 100644 --- a/cardano-rpc/README.md +++ b/cardano-rpc/README.md @@ -33,7 +33,7 @@ It implements [UTxO RPC](https://utxorpc.org/introduction) protobuf communicatio | Method | Status | |--------|--------| -| [FetchBlock](https://utxorpc.org/sync/spec/#fetchblockrequest) | ⬜ Not supported | +| [FetchBlock](https://utxorpc.org/sync/spec/#fetchblockrequest) | 🚧 In progress (missing: `Block.body.tx`) | | [DumpHistory](https://utxorpc.org/sync/spec/#dumphistoryrequest) | ⬜ Not supported | | [FollowTip](https://utxorpc.org/sync/spec/#followtiprequest) | ⬜ Not supported | | [ReadTip](https://utxorpc.org/sync/spec/#readtiprequest) | ⬜ Not supported | diff --git a/cardano-rpc/cardano-rpc.cabal b/cardano-rpc/cardano-rpc.cabal index a9e53ce75f..c212b62e92 100644 --- a/cardano-rpc/cardano-rpc.cabal +++ b/cardano-rpc/cardano-rpc.cabal @@ -51,6 +51,7 @@ library Cardano.Rpc.Proto.Api.Node Cardano.Rpc.Proto.Api.UtxoRpc.Query Cardano.Rpc.Proto.Api.UtxoRpc.Submit + Cardano.Rpc.Proto.Api.UtxoRpc.Sync Cardano.Rpc.Server Cardano.Rpc.Server.Config Cardano.Rpc.Server.Internal.Env @@ -62,7 +63,10 @@ library Cardano.Rpc.Server.Internal.UtxoRpc.Predicate Cardano.Rpc.Server.Internal.UtxoRpc.Query Cardano.Rpc.Server.Internal.UtxoRpc.Submit + Cardano.Rpc.Server.Internal.UtxoRpc.Sync Cardano.Rpc.Server.Internal.UtxoRpc.Type + Cardano.Rpc.Server.NodeKernelAccess + Cardano.Rpc.Server.NodeKernelAccess.Internal other-modules: Cardano.Rpc.Server.Internal.Orphans @@ -111,6 +115,8 @@ library gen Proto.Utxorpc.V1beta.Query.Query_Fields Proto.Utxorpc.V1beta.Submit.Submit Proto.Utxorpc.V1beta.Submit.Submit_Fields + Proto.Utxorpc.V1beta.Sync.Sync + Proto.Utxorpc.V1beta.Sync.Sync_Fields build-depends: proto-lens-protobuf-types, diff --git a/cardano-rpc/docs/node-kernel-access/01-node-access-types.md b/cardano-rpc/docs/node-kernel-access/01-node-access-types.md new file mode 100644 index 0000000000..c9a02c8a96 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/01-node-access-types.md @@ -0,0 +1,148 @@ +# Piece 1: NodeKernelAccess types and cardano-rpc plumbing + +## Problem + +cardano-rpc currently threads `LocalNodeConnectInfo` through its environment and `MonadRpc` constraint. +Every RPC method grabs the connection info and opens a fresh N2C socket connection per request. +To support direct ledger state access (ADR-019), we need a new abstraction that replaces this pattern with an `IORef (Maybe NodeKernelAccess)` passed in by cardano-node at startup. + +## Why + +This piece creates the `NodeKernelAccess` abstraction and wires it through the cardano-rpc infrastructure. +After this piece, cardano-rpc compiles with the new types threaded through, but no new RPC methods exist yet (piece 2) and no node-side implementation exists yet (piece 3). +Separating the plumbing from the method rewrites and node-side implementation keeps each piece small and reviewable. + +## User value + +As a cardano-rpc developer, I want the `NodeKernelAccess` abstraction and environment wiring in place so that I can rewrite individual RPC methods to use node kernel access in subsequent pieces. + +## Acceptance criteria + +1. **AC1: NodeKernelAccess module** - A new module `Cardano.Rpc.Server.Internal.NodeKernelAccess` exists at `src/Cardano/Rpc/Server/Internal/NodeKernelAccess.hs`, exporting `NodeKernelAccess(..)`, `LedgerSnapshot(..)`, and `withNodeKernelAccess`. + `NodeKernelAccess` is a record with three fields: `nkaWithSnapshot :: forall a. (LedgerSnapshot -> IO a) -> IO a`, `nkaSubmitTx :: TxInMode -> IO (SubmitResult TxValidationErrorInCardanoMode)`, and `nkaFetchBlock :: SlotNo -> ByteString -> IO (Maybe ByteString)`. + `LedgerSnapshot` is a newtype wrapping `runQuery :: forall result. QueryInMode result -> IO result`. + The module is listed in `exposed-modules` in `cardano-rpc.cabal`. + - Test: unit - compiles and is importable from the test suite + +2. **AC2: withNodeKernelAccess unavailable behaviour** - `withNodeKernelAccess` reads the `IORef (Maybe NodeKernelAccess)`; when the value is `Nothing`, it throws a `GrpcException` with `grpcError = GrpcUnavailable` and message containing "not yet initialised". + When the value is `Just na`, it passes `na` to the callback and returns the callback's result. + - Test: unit - `H.propertyOnce`: create `IORef Nothing`, call `withNodeKernelAccess`, assert `GrpcException` with `GrpcUnavailable` is thrown; create `IORef (Just mockNodeKernelAccess)`, call `withNodeKernelAccess`, assert callback receives the value and its return value is propagated + +3. **AC3: Server.hs signature change** - `runRpcServer` signature changes from `Tracer IO TraceRpc -> (RpcConfig, NetworkMagic) -> IO ()` to `Tracer IO TraceRpc -> RpcConfig -> NetworkMagic -> IORef (Maybe NodeKernelAccess) -> IO ()`. + The module re-exports `NodeKernelAccess(..)` and `LedgerSnapshot(..)`. + `RpcEnv` construction is updated to include both `rpcNodeKernelAccess` (from the new parameter) and `rpcLocalNodeConnectInfo` (preserved temporarily). + Note: `methodsSyncRpc` is NOT registered in this piece - that happens in piece 2 when the SyncService proto and handler exist. + - Test: unit - compiles (API change verified by build) + +4. **AC4: Environment and MonadRpc wiring** - `RpcEnv` in `Env.hs` gains a new field `rpcNodeKernelAccess :: !(IORef (Maybe NodeKernelAccess))`. + A `Has (IORef (Maybe NodeKernelAccess)) RpcEnv` instance is added to `Monad.hs`. + `MonadRpc` constraint includes `Has (IORef (Maybe NodeKernelAccess)) e`. + The old `rpcLocalNodeConnectInfo` field and `Has LocalNodeConnectInfo RpcEnv` instance are kept temporarily so that method files compile unchanged. + - Test: unit - compiles; the new constraint is exercised by `withNodeKernelAccess` usage in the test from AC2 + +5. **AC5: Tracing for new trace types** - `Tracing.hs` gains a `TraceRpcSync` sum type with constructors: `TraceRpcFetchBlockSpan TraceSpanEvent` (span begin/end), `TraceRpcFetchBlockNotFound SlotNo` (block not on chain). + `TraceRpc` gains a `TraceRpcSync TraceRpcSync` constructor. + `Pretty` instances render the span events as "Started fetch block method" / "Finished fetch block method" and the not-found as "Block not found at slot ". + An `Inject TraceRpcSync TraceRpc` instance is provided. + `TraceRpcSubmitN2cConnectionError SomeException` is replaced by `TraceRpcNodeKernelAccessUnavailable` (no payload) and `TraceRpcForkerError String`. + `Pretty TraceRpcSubmit` renders them as `"Ledger access unavailable (node kernel not yet initialised)"` and `"Forker error: "` respectively. + The corresponding one-line update in `Submit.hs` (replacing `Left $ TraceRpcSubmitN2cConnectionError e` with `Left $ TraceRpcNodeKernelAccessUnavailable`) is included so that the build stays clean. + - Test: unit - `H.propertyOnce` asserting the `Pretty` output of each new constructor contains the expected substrings + +## Out of scope + +- Populating the `cardano` oneof field in `AnyChainBlock` (requires protobuf block type mapping, a separate piece of work). +- Streaming RPCs from the sync proto (`FollowTip`, `DumpHistory`). +- Rewriting existing RPC methods (Query, Submit, Eval, Node) to use `NodeKernelAccess` (pieces 4-7). +- Removing `rpcLocalNodeConnectInfo` and `Has LocalNodeConnectInfo` from `RpcEnv` / `MonadRpc` (happens when the last N2C method is rewritten in pieces 4-7). +- Removing `mkLocalNodeConnectInfo` (removed alongside `rpcLocalNodeConnectInfo`). +- Removing `nodeSocketPath` from `RpcConfig` (still needed for `nodeSocketPathToRpcSocketPath`). +- Proto definitions and codegen (piece 2). +- FetchBlock handler (piece 2). +- `mkNodeKernelAccess` in cardano-node (piece 3). +- Node startup wiring (piece 3). +- Adding new E2E tests (no runtime behaviour changes in this piece). + +## Definition of done + +- [ ] All AC tests written (compile, fail on stubs) +- [ ] Implementation complete (all tests pass via `cabal test`) +- [ ] `cabal build cardano-rpc` succeeds from `/work` with no warnings +- [ ] Nix CI checks pass +- [ ] haskell-reviewer agent finds no critical or style issues +- [ ] fourmolu clean (`scripts/devshell/prettify` run on changed files) +- [ ] No build warnings + +## Notes + +### Design decision: keep both fields temporarily + +This piece adds `rpcNodeKernelAccess :: IORef (Maybe NodeKernelAccess)` to `RpcEnv` alongside the existing `rpcLocalNodeConnectInfo :: LocalNodeConnectInfo`. +Removing `rpcLocalNodeConnectInfo` would break every existing method file (`Node.hs`, `Query.hs`, `Submit.hs`, `Eval.hs`) because they all use `nodeConnInfo <- grab` to obtain a `LocalNodeConnectInfo`. +Rewriting those method bodies is the work of pieces 4-7. + +Both fields coexist in `RpcEnv` and both `Has` instances exist in `MonadRpc`. +This means: +- Existing method files compile without any changes. +- Runtime behaviour of existing methods is unchanged (they still use N2C). +- Pieces 4-7 each rewrite one method's N2C usage; the last piece to land removes the old field and instance. + +### Files affected + +| File | Change | +|---|---| +| `src/Cardano/Rpc/Server/Internal/NodeKernelAccess.hs` | **New.** `NodeKernelAccess`, `LedgerSnapshot`, `withNodeKernelAccess`. | +| `src/Cardano/Rpc/Server/Internal/Env.hs` | Add `rpcNodeKernelAccess` field alongside existing `rpcLocalNodeConnectInfo`. | +| `src/Cardano/Rpc/Server/Internal/Monad.hs` | Add `Has (IORef (Maybe NodeKernelAccess)) RpcEnv` instance. Add constraint to `MonadRpc`. | +| `src/Cardano/Rpc/Server/Internal/Tracing.hs` | Add `TraceRpcSync` type and constructors. Replace `TraceRpcSubmitN2cConnectionError` with `TraceRpcNodeKernelAccessUnavailable` and `TraceRpcForkerError`. | +| `src/Cardano/Rpc/Server/Internal/UtxoRpc/Submit.hs` | One-line trace constructor update. | +| `src/Cardano/Rpc/Server.hs` | New signature, re-exports, updated `RpcEnv` construction. | +| `cardano-rpc.cabal` | Add `NodeKernelAccess` module to `exposed-modules`. | + +**cardano-node** (must update in lockstep to keep `-Werror` clean): + +| File | Change | +|---|---| +| `src/Cardano/Node/Tracing/Tracers/Rpc.hs` | Handle renamed `TraceRpcNodeKernelAccessUnavailable`/`TraceRpcForkerError` and new `TraceRpcSync` constructors in `forMachine`, `asMetrics`, `namespaceFor`, `severityFor`, `documentFor`, `allNamespaces`. | +| `src/Cardano/Node/Run.hs` | Create `nodeKernelAccessRef <- newIORef Nothing`, pass through `rpcServerLoop` to `runRpcServer`. Update `rpcServerLoop` signature. | + +### Gotchas for the implementer + +- **Import narrowing in `Monad.hs`**: when adding the new `Has` instance, ensure `Inject` (used by `putTrace`) is still available. + Currently it comes from `import Cardano.Api`; if imports are narrowed, import it explicitly from `Cardano.Api.Era`. + +- **`RankNTypes` extension.** Both `NodeKernelAccess` and `LedgerSnapshot` use higher-rank fields, requiring the `RankNTypes` extension in `NodeKernelAccess.hs`. + +- **`runRpcServer` keeps `NetworkMagic`.** The old `rpcLocalNodeConnectInfo` is still used by existing methods, so `mkLocalNodeConnectInfo` still needs `NetworkMagic`. + It is dropped only when `rpcLocalNodeConnectInfo` is finally removed in a later piece. + +- **`Submit.hs` trace constructor.** `Submit.hs` currently references `TraceRpcSubmitN2cConnectionError` in its `submitTx` helper. + The trace constructor rename requires a corresponding one-line update in `Submit.hs`: replace `Left $ TraceRpcSubmitN2cConnectionError e` with `Left $ TraceRpcNodeKernelAccessUnavailable` (dropping the exception payload, since the new constructor carries no payload). + +- **`SomeException` import**: `Control.Exception` is still needed in `Tracing.hs` because `TraceRpcError` and `TraceRpcFatalError` use `SomeException`. + +- **`GrpcException` import**: `withNodeKernelAccess` throws `GrpcException` from `Network.GRPC.Spec`. + `grpc-spec` is already a dependency of `cardano-rpc`. + +- **`RpcConfig.nodeSocketPath` stays**: ADR-019 explicitly notes this. + The config field remains for deriving `rpcSocketPath` via `nodeSocketPathToRpcSocketPath`. + +### Dependencies + +- **Upstream:** none (this is the first piece). +- **Downstream:** all pieces 2-8 depend on this (for the `NodeKernelAccess` record, environment wiring, and tracing). + +### Testing approach + +This piece is primarily a wiring/structural change. +Two ACs have genuine Hedgehog property tests: +- AC2 (`withNodeKernelAccess` behaviour): `H.propertyOnce` covering the `Nothing` and `Just` branches. +- AC5 (tracing pretty-print): `H.propertyOnce` asserting rendered output of the new constructors. + +AC1, AC3, AC4 are verified by successful compilation. + +## Reference docs + +- [Consensus protocol and snapshots](analysis-consensus-protocol.md) - snapshot consistency rationale +- [API signatures](prereqs-api-signatures.md) - `NodeKernelAccess` type design context +- [Implementation details](prereqs-implementation-details.md) - subtle gotchas for the interface diff --git a/cardano-rpc/docs/node-kernel-access/02-fetchblock-proto-and-handler.md b/cardano-rpc/docs/node-kernel-access/02-fetchblock-proto-and-handler.md new file mode 100644 index 0000000000..5c393d307e --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/02-fetchblock-proto-and-handler.md @@ -0,0 +1,134 @@ +# Piece 2: FetchBlock proto and handler + +## Problem + +cardano-rpc has no `FetchBlock` implementation and no direct ChainDB access path. +Before rewriting existing N2C-based RPCs, we need a clean end-to-end proof that direct node access works, using a new method with no legacy code to break. + +## Why + +This piece adds the SyncService proto definition and the `fetchBlockMethod` handler. +After this piece, the proto bindings exist, the handler compiles, but it cannot run end-to-end yet (needs piece 3 for node-side implementation). +Starting with a new RPC method de-risks the node kernel access architecture without touching any existing N2C code. + +## User value + +As a dApp developer, I want to fetch raw block bytes by slot and hash via gRPC so that I can decode blocks client-side without running a separate chain indexer. + +## Acceptance criteria + +1. **AC1: sync.proto and codegen** - A new proto file `cardano-rpc/proto/utxorpc/v1beta/sync/sync.proto` exists, containing the `SyncService` with only the `FetchBlock` RPC and its message types (`BlockRef`, `FetchBlockRequest`, `FetchBlockResponse`, `AnyChainBlock`). + Running `buf generate proto` in the nix dev shell produces proto-lens bindings under `gen/` (`Proto.Utxorpc.V1beta.Sync.Sync` and `Proto.Utxorpc.V1beta.Sync.Sync_Fields`). + Both generated modules are listed in `cardano-rpc.cabal` under `library gen`. + - Test: unit - `cabal build cardano-rpc` compiles the generated modules + +2. **AC2: Proto API wrapper for SyncService** - A new module `Cardano.Rpc.Proto.Api.UtxoRpc.Sync` exists, following the same pattern as `Query.hs` and `Submit.hs` (re-exports generated proto modules, declares `RequestMetadata`, `ResponseInitialMetadata`, `ResponseTrailingMetadata` type instances for `Protobuf SyncService`). + The module is listed in `exposed-modules` in `cardano-rpc.cabal`. + - Test: unit - compiles + +3. **AC3: fetchBlockMethod implementation** - A new module `Cardano.Rpc.Server.Internal.UtxoRpc.Sync` exists with `fetchBlockMethod :: FetchBlockRequest -> RpcHandler FetchBlockResponse`. + For each `BlockRef` in the request, the method extracts `slot` and `hash` fields. + It calls `nkaFetchBlock slotNo hashBytes` via `withNodeKernelAccess`. + If the result is `Just rawBytes`, the block is wrapped in an `AnyChainBlock` with `native_bytes` set to `rawBytes`. + If the result is `Nothing`, the block is omitted from the response (not-found blocks are skipped silently) and a `TraceRpcFetchBlockNotFound` trace is emitted with the slot number. + Only the `native_bytes` field is populated; the `cardano` oneof field is left empty. + - Test: E2E - `hprop_rpc_fetch_block` verifies a produced block can be fetched and its raw bytes are non-empty + +4. **AC4: Missing slot returns INVALID_ARGUMENT** - When a `BlockRef` has `slot == 0` (the proto default, meaning unset) but a non-empty `hash`, `fetchBlockMethod` returns a gRPC `INVALID_ARGUMENT` error with a message containing "slot is required". + A `RealPoint` cannot be constructed without both slot and hash; the method validates this before calling `nkaFetchBlock`. + - Test: unit - `H.propertyOnce`: construct a `FetchBlockRequest` with a `BlockRef` whose slot is 0 and hash is non-empty, call `fetchBlockMethod`, assert `GrpcException` with `GrpcInvalidArgument` is thrown + +5. **AC5: Empty hash returns INVALID_ARGUMENT** - When a `BlockRef` has a non-zero `slot` but an empty `hash`, `fetchBlockMethod` returns a gRPC `INVALID_ARGUMENT` error with a message containing "hash is required". + - Test: unit - `H.propertyOnce`: construct a `FetchBlockRequest` with a `BlockRef` whose slot is non-zero and hash is empty, call `fetchBlockMethod`, assert `GrpcException` with `GrpcInvalidArgument` is thrown + +6. **AC6: Server.hs registers SyncService** - `Server.hs` registers `methodsSyncRpc` alongside `methodsNodeRpc`, `methodsUtxoRpc`, and `methodsUtxoRpcSubmit`. + - Test: unit - compiles (verified by build) + +## Out of scope + +- Populating the `cardano` oneof field in `AnyChainBlock` (requires protobuf block type mapping, a separate piece of work). +- Streaming RPCs from the sync proto (`FollowTip`, `DumpHistory`). +- `mkNodeKernelAccess` in cardano-node (piece 3). +- Node startup wiring in `Run.hs` (piece 3). +- E2E test infrastructure (piece 3 provides the node-side implementation needed to run FetchBlock end-to-end). +- `field_mask` support on `FetchBlockRequest` (parse-into-proto would need the `cardano` field populated first). +- Handling of `BlockRef.height` and `BlockRef.timestamp` fields (not needed for `RealPoint` construction; reserved for future use). + +## Definition of done + +- [ ] All AC tests written (compile, fail on stubs) +- [ ] Implementation complete (all tests pass via `cabal test`) +- [ ] `cabal build cardano-rpc` succeeds from `/work` with no warnings +- [ ] Nix CI checks pass +- [ ] haskell-reviewer agent finds no critical or style issues +- [ ] fourmolu clean (`scripts/devshell/prettify` run on changed files) +- [ ] No build warnings + +## Notes + +### Design decision: skip not-found blocks (not NOT_FOUND status) + +The proto `FetchBlockResponse` has `repeated AnyChainBlock block` with no per-element status field. +A missing block can only be expressed by omission from the list or by failing the entire request with a gRPC NOT_FOUND status. +Failing the entire batch because one block is missing would be surprising and unhelpful for clients requesting multiple blocks. +Therefore, blocks not found in ChainDB are silently omitted from the response. +Clients can compare the count of returned blocks against the count of requested `BlockRef` entries to detect missing blocks. + +### Files affected + +| File | Change | +|---|---| +| `proto/utxorpc/v1beta/sync/sync.proto` | **New.** FetchBlock RPC and message types. | +| `gen/Proto/Utxorpc/V1beta/Sync/Sync.hs` | **Generated.** Proto-lens bindings (do not edit manually). | +| `gen/Proto/Utxorpc/V1beta/Sync/Sync_Fields.hs` | **Generated.** Proto-lens field accessors (do not edit manually). | +| `src/Cardano/Rpc/Proto/Api/UtxoRpc/Sync.hs` | **New.** Proto API wrapper for SyncService. | +| `src/Cardano/Rpc/Server/Internal/UtxoRpc/Sync.hs` | **New.** `fetchBlockMethod`. | +| `src/Cardano/Rpc/Server.hs` | Register `methodsSyncRpc`. | +| `cardano-rpc.cabal` | Add new modules to `exposed-modules` and `library gen`. | + +### Gotchas for the implementer + +- **Proto codegen requires nix dev shell.** `buf` is not available outside it. + Run `nix develop --command bash -c "cd cardano-rpc && buf generate proto"`. + +- **`AnyChainBlock` has both `native_bytes` and `cardano`.** For this piece, only populate `native_bytes`. + The parsed `cardano` block field is a separate, complex piece of work. + +- **`GetRawBlock` returns `Lazy.ByteString`.** The proto `native_bytes` field is strict `ByteString`. + Convert via `Data.ByteString.Lazy.toStrict`. + +- **Hash bytes conversion.** The proto hash is raw bytes (`ByteString`). + `HeaderHash (CardanoBlock StandardCrypto)` is `OneEraHash` wrapping `ShortByteString`. + Convert via `OneEraHash . SBS.toShort . BS.toStrict` (if the input is lazy) or `OneEraHash . SBS.toShort` (if strict). + No CBOR wrapping is needed; it is raw hash bytes. + +- **`RealPoint` requires both slot and hash.** If the proto `BlockRef` has `slot == 0` (proto default for unset) but a non-empty hash, or a non-zero slot but empty hash, we cannot construct a `RealPoint`. + Validate both fields and return `INVALID_ARGUMENT` if either is missing. + +### Dependencies + +- **Upstream:** piece 1 (for `NodeKernelAccess`, `withNodeKernelAccess`, environment wiring, and tracing types). +- **Downstream:** piece 3 (provides the node-side `mkNodeKernelAccess` implementation needed for E2E). + +### Testing approach + +| AC | Type | What it tests | +|---|---|---| +| AC1 | unit | Proto codegen compiles | +| AC2 | unit | Proto API wrapper compiles | +| AC3 | E2E | `fetchBlockMethod` end-to-end happy path (requires piece 3) | +| AC4 | unit | Missing slot returns `INVALID_ARGUMENT` | +| AC5 | unit | Empty hash returns `INVALID_ARGUMENT` | +| AC6 | unit | Server registration compiles | + +### Open questions + +- Should `nkaFetchBlock` also support fetching by hash alone (without slot)? + `ChainDB` has `getBlockComponent` which takes a `RealPoint` (requiring both), so hash-only lookup would need a different API (`iteratorNext` or similar). + For now, both slot and hash are required. + Hash-only lookup can be a follow-up story if needed. + +## Reference docs + +- [Architecture and current state](analysis-architecture.md) - cardano-rpc overview and spec coverage +- [Build and conventions](prereqs-build-and-conventions.md) - proto codegen instructions, nix build commands diff --git a/cardano-rpc/docs/node-kernel-access/03-mk-node-access-and-wiring.md b/cardano-rpc/docs/node-kernel-access/03-mk-node-access-and-wiring.md new file mode 100644 index 0000000000..7e0d3d18ed --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/03-mk-node-access-and-wiring.md @@ -0,0 +1,169 @@ +# Piece 3: mkNodeKernelAccess and node wiring + +## Problem + +Pieces 1 and 2 define the `NodeKernelAccess` interface in cardano-rpc and add the FetchBlock handler, but no concrete implementation exists yet. +The `IORef (Maybe NodeKernelAccess)` is always `Nothing` at runtime, so every gRPC request returns `UNAVAILABLE`. +This piece provides the real implementation by constructing `NodeKernelAccess` from `NodeKernel` internals and wiring it into the node startup sequence. + +## Why + +Without this piece, the node kernel access migration is incomplete: cardano-rpc has a working interface with no backing implementation. +This is the single piece that makes the entire N2C-to-direct-access transition functional at runtime. +After this piece, FetchBlock works end-to-end. + +## User value + +As a cardano-node operator, I want the gRPC server to query ledger state directly through the node kernel so that queries are faster and do not require a separate N2C socket connection. + +## Acceptance criteria + +1. **AC1: New module exists** - A new module `Cardano.Node.Rpc.NodeKernelAccess` exists at `cardano-node/cardano-node/src/Cardano/Node/Rpc/NodeKernelAccess.hs` and exports `mkNodeKernelAccess`. + - Test: unit - `cabal build cardano-node` compiles with the new module + +2. **AC2: mkNodeKernelAccess signature** - `mkNodeKernelAccess` has the signature `NodeKernel IO RemoteAddress LocalConnectionId (CardanoBlock StandardCrypto) -> NodeKernelAccess`. + - Test: unit - compiles with correct type; incorrect type would cause a build failure + +3. **AC3: nkaFetchBlock implementation** - `nkaFetchBlock` converts the hash bytes to `HeaderHash (CardanoBlock StandardCrypto)` (via `OneEraHash` wrapping `ShortByteString`), constructs a `RealPoint`, calls `ChainDB.getBlockComponent GetRawBlock`, and returns the result as strict `ByteString` (converted from lazy via `BS.toStrict`). + - Test: E2E - `hprop_rpc_fetch_block` verifies a produced block can be fetched + +4. **AC4: Snapshot acquisition with bracket** - `nkaWithSnapshot` acquires a `ReadOnlyForker` via `getReadOnlyForkerAtPoint chainDB VolatileTip` inside `withRegistry`, and uses `bracket` to ensure `roforkerClose` is called even when the callback throws an exception. + - Test: E2E - existing `hprop_rpc_query_pparams` exercises the snapshot path end-to-end + +5. **AC5: Forker error handling** - When `getReadOnlyForkerAtPoint` returns `Left err`, `nkaWithSnapshot` throws a descriptive error containing the stringified `GetForkerError`. + - Test: unit - compiles; this is a defensive guard whose `Left` path is not exercised by E2E tests (`VolatileTip` is always available in a running testnet) + +6. **AC6: Query round-trip** - `LedgerSnapshot.runQuery` converts a `QueryInMode` to a consensus `Query` via `toConsensusQuery`, calls `answerQuery` with the forker and `ExtLedgerCfg`, then converts the result back via `fromConsensusQueryResult`. + - Test: E2E - `hprop_rpc_query_pparams` verifies the full query round-trip returns valid protocol parameters + +7. **AC7: Transaction submission** - `nkaSubmitTx` converts a `TxInMode` to a consensus `GenTx` via `toConsensusGenTx`, submits it through `addLocalTxs` using the `MkSolo` constructor (GHC 9.10), and maps `MempoolTxAdded` to `SubmitSuccess` and `MempoolTxRejected` to `SubmitFail` (converting the error via `fromConsensusApplyTxErr`). + - Test: E2E - `hprop_rpc_transaction` submits a transaction and verifies success + +8. **AC8: IORef wiring in Run.hs** - `Run.hs` creates `nodeKernelAccessRef <- newIORef Nothing` before the RPC server `withAsync`, passes it to `rpcServerLoop`, and in `rnNodeKernelHook` calls `writeIORef nodeKernelAccessRef (Just (mkNodeKernelAccess nodeKernel))`. + A comment at the `writeIORef` site documents the single-writer/many-readers safety invariant. + `rpcServerLoop` calls `runRpcServer` with the new signature, threading through the `IORef`. + - Test: E2E - any successful gRPC request in the testnet suite proves the IORef was populated + +9. **AC9: Tracing - LogFormatting and MetaTrace** - In `Cardano.Node.Tracing.Tracers.Rpc`, the `LogFormatting` instance handles `TraceRpcSync` constructors. + The `MetaTrace TraceRpc` instance is updated: `namespaceFor` maps the new constructors, `severityFor` assigns appropriate severity, `documentFor` provides descriptions, and `allNamespaces` lists the new namespaces. + Note: the initial `TraceRpcSync` constructors and the renamed submit constructors are already added in piece 1 (which must update this file in lockstep with `Tracing.hs`). + This AC covers any additional trace handling needed for `mkNodeKernelAccess`-specific paths. + - Test: unit - compiles; `-Wincomplete-patterns` (via `-Werror`) catches missing branches + +10. **AC10: E2E test for FetchBlock** - A new E2E test `hprop_rpc_fetch_block` starts a testnet, produces at least one block, fetches it via the `FetchBlock` gRPC method using the block's slot and hash, and verifies the response contains an `AnyChainBlock` whose `native_bytes` is non-empty. + The test is added to the testnet test runner alongside the existing RPC tests. + - Test: E2E - `TASTY_PATTERN='/RPC FetchBlock/' cabal test cardano-testnet-test` + +11. **AC11: Build clean and existing tests pass** - `cabal build cardano-rpc` and `cabal build cardano-node` both succeed with no errors or warnings. + `cabal test cardano-rpc:test:cardano-rpc-test` passes with no failures. + Existing E2E tests (`hprop_rpc_query_pparams`, `hprop_rpc_transaction`, `hprop_rpc_search_utxos`) pass without modification. + - Test: E2E - full CI pass + +12. **AC12: Cabal module listing** - `cardano-node.cabal` lists `Cardano.Node.Rpc.NodeKernelAccess` in `exposed-modules`. + - Test: unit - `cabal build cardano-node` would fail if the module is missing from the listing + +## Out of scope + +- All cardano-rpc-side type and interface changes (pieces 1 and 2 define `NodeKernelAccess`, `LedgerSnapshot`, `withNodeKernelAccess`, `fetchBlockMethod`). +- Rewriting existing RPC method bodies (pieces 4-7). +- New integration test coverage beyond `hprop_rpc_fetch_block` or conformance tests (piece 8). +- Dynamic era handling in `getEraMethod` (remains hardcoded to Conway). +- Removal of `nodeSocketPath` from `RpcConfig` (kept for backward compatibility with testnet and POM configuration). +- Performance benchmarking of direct access versus N2C. +- Streaming or ChainSync support. + +## Definition of done + +- [ ] All AC tests written (compile, fail on stubs) +- [ ] Implementation complete (all tests pass) +- [ ] `cabal build cardano-node` succeeds with no warnings +- [ ] `cabal build cardano-rpc` succeeds with no warnings +- [ ] E2E tests pass: `cabal test cardano-testnet-test --test-option='-p /RPC/'` +- [ ] Nix CI checks pass +- [ ] haskell-reviewer agent finds no critical or style issues +- [ ] fourmolu clean +- [ ] No build warnings + +## Notes + +### Key imports for mkNodeKernelAccess + +- `Cardano.Api.Query.Internal.Type.QueryInMode (toConsensusQuery, fromConsensusQueryResult)` +- `Cardano.Api.Consensus.Internal.InMode (toConsensusGenTx, fromConsensusApplyTxErr)` +- `Ouroboros.Consensus.Ledger.Query (answerQuery)` +- `Ouroboros.Consensus.Storage.ChainDB (getReadOnlyForkerAtPoint)` +- `Control.ResourceRegistry (withRegistry)` +- `Ouroboros.Consensus.Mempool.API (addLocalTxs, MempoolAddTxResult (..))` + +### Files affected + +**cardano-node:** + +| File | Change | +|---|---| +| `src/Cardano/Node/Rpc/NodeKernelAccess.hs` | **New.** `mkNodeKernelAccess` from `NodeKernel`. | +| `src/Cardano/Node/Run.hs` | IORef creation, kernel hook population, `rpcServerLoop` signature update. | +| `src/Cardano/Node/Tracing/Tracers/Rpc.hs` | `LogFormatting` and `MetaTrace` instances for `TraceRpcSync`. | +| `cardano-node.cabal` | Add `Cardano.Node.Rpc.NodeKernelAccess` to `exposed-modules`. | + +**cardano-testnet:** + +| File | Change | +|---|---| +| `test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs` | **New.** `hprop_rpc_fetch_block` E2E test. | +| `test/cardano-testnet-test/cardano-testnet-test.hs` | Register new test. | + +### Gotchas for the implementer + +- **`ReadOnlyForker` must be closed even on exception;** use `bracket` with `roforkerClose` inside `withRegistry`. + +- **`getReadOnlyForkerAtPoint` can return `Left GetForkerError`** if the point is not on chain or too old; throw a descriptive error. + +- **`MkSolo` constructor.** On GHC 9.10, use `MkSolo` (not `Solo`) from `Data.Tuple` when calling `addLocalTxs`. + +- **`toConsensusQuery` returns `Some (Query (CardanoBlock c))`;** pattern match with `Some`. + +- **`fromConsensusQueryResult` for era-specific queries returns `Either EraMismatch result`;** the `runQuery` implementation must handle the `Left` case. + +- **`RealPoint` requires both slot and hash.** The `nkaFetchBlock` implementation must construct `RealPoint` from the slot and hash provided by the handler. + +- **`rpcServerLoop` currently calls `runRpcServer rpcTracer (config, networkMagic)`.** The signature change adds the `IORef` parameter and uncurries the tuple (pieces 1 and 2 handle the cardano-rpc side of this change). + +- **The `rpcServerLoop` signature change touches a function that also handles SIGHUP reconfiguration.** Care needed not to break the config reload path. + +### Risks + +| Risk | Status | Mitigation | +|------|--------|------------| +| `answerQuery` API differs from expected signature | **Verified** (2026-05-22) | Signature matches: `ExtLedgerCfg blk -> ReadOnlyForker' m blk -> Query blk result -> m result` | +| Forker lifecycle - leaking forkers on exceptions | **Addressed** | `bracket` pattern in `nkaWithSnapshot`; `withRegistry` provides additional safety net | +| `addLocalTxs` API changed in recent consensus | **Verified** (2026-05-22) | Name and signature confirmed; use `MkSolo` constructor on GHC 9.10 | +| Concurrent reads during kernel initialisation race | Low risk | `IORef` write in `rnNodeKernelHook` happens before any RPC can succeed; `withNodeKernelAccess` checks atomically | + +### Dependencies + +- **Upstream:** pieces 1 and 2 (for `NodeKernelAccess` types, environment wiring, proto definitions, and handler). +- **Downstream:** pieces 4-8 (method rewrites and integration testing). + +### Testing approach + +| AC | Type | What it tests | +|---|---|---| +| AC1 | unit | New module compiles | +| AC2 | unit | `mkNodeKernelAccess` type signature | +| AC3 | E2E | `nkaFetchBlock` via `hprop_rpc_fetch_block` | +| AC4 | E2E | Snapshot path via `hprop_rpc_query_pparams` | +| AC5 | unit | Defensive forker error guard (compiles) | +| AC6 | E2E | Full query round-trip | +| AC7 | E2E | Transaction submission via `hprop_rpc_transaction` | +| AC8 | E2E | IORef wiring (proven by any successful gRPC request) | +| AC9 | unit | Tracing instances compile | +| AC10 | E2E | `hprop_rpc_fetch_block` dedicated integration test | +| AC11 | E2E | Full regression (all existing tests pass) | +| AC12 | unit | Cabal listing (compiles) | + +## Reference docs + +- [API signatures](prereqs-api-signatures.md) - `answerQuery`, `getReadOnlyForkerAtPoint`, `toConsensusQuery` signatures +- [UTxO-HD internals](analysis-utxohd-internals.md) - forker lifecycle, backing store mechanics +- [Build and conventions](prereqs-build-and-conventions.md) - nix build commands for cardano-node diff --git a/cardano-rpc/docs/node-kernel-access/04-rewrite-query-methods.md b/cardano-rpc/docs/node-kernel-access/04-rewrite-query-methods.md new file mode 100644 index 0000000000..f54356d643 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/04-rewrite-query-methods.md @@ -0,0 +1,86 @@ +# Piece 4: Rewrite query methods to use NodeKernelAccess + +## Problem + +The three query methods in `Query.hs` (`readParamsMethod`, `readUtxosMethod`, `searchUtxosMethod`) use the Node-to-Client IPC pattern (`executeLocalStateQueryExpr` via `LocalNodeConnectInfo`). +This creates a new socket connection per request and double-serialises data through CBOR. +These methods need to be rewritten to use the snapshot-based `NodeKernelAccess` interface for direct ledger state access. + +## Why + +Eliminating the N2C round-trip reduces latency and resource overhead for every query request. +Using a single `nkaWithSnapshot` call per request preserves the consistency guarantee (all queries see the same ledger state) while removing the socket and serialisation costs. + +## User value + +As a dApp developer querying protocol parameters or UTxOs via gRPC, I want responses served directly from the ledger state so that queries are faster and I do not pay the overhead of a Node-to-Client IPC round-trip. + +## Acceptance criteria + +1. **AC1: readParamsMethod uses NodeKernelAccess** - `readParamsMethod` acquires a `LedgerSnapshot` via `withNodeKernelAccess` and `nkaWithSnapshot`, then runs all queries (`QueryCurrentEra`, `QueryProtocolParameters`, `QuerySystemStart`, `QueryEraHistory`, `QueryChainPoint`, `QueryChainBlockNo`) against that snapshot. + No references to `executeLocalStateQueryExpr`, `determineEra`, or `VolatileTip` remain in this method. + - Test: E2E - `hprop_rpc_query_pparams` passes, validating all 44 protocol parameter fields match the ledger and the ledger tip (slot, hash, height) is correct. + +2. **AC2: readUtxosMethod uses NodeKernelAccess** - `readUtxosMethod` follows the same snapshot pattern as AC1, querying UTxOs via `QueryUTxO` inside `nkaWithSnapshot`. + The `txoRefToTxIn` helper and protobuf conversion logic remain unchanged. + - Test: E2E - `hprop_rpc_query_pparams` readUtxos assertion passes (UTxO set matches epoch state view). + +3. **AC3: searchUtxosMethod uses NodeKernelAccess** - `searchUtxosMethod` follows the same snapshot pattern, with post-query filtering and pagination logic unchanged. + - Test: E2E - `hprop_rpc_search_utxos` passes (exact address, payment credential, empty, and whole-set predicates all return correct results). + +4. **AC4: Snapshot consistency** - Each query method opens exactly one `nkaWithSnapshot` call, and all queries within that call (era detection, domain query, systemStart, eraHistory, chainPoint, blockNo) execute against the same `LedgerSnapshot`. + This preserves the consistency guarantee of the previous `executeLocalStateQueryExpr` pattern. + - Test: unit - code review; verified structurally by the single `nkaWithSnapshot` callback per method. A Haddock note on each method documents this invariant. + +5. **AC5: systemStart and eraHistory inside snapshot** - Every query method queries `QuerySystemStart` and `QueryEraHistory` inside the snapshot callback (not outside it), so that `slotToTimestamp` receives values consistent with the chain point. + - Test: unit - compile-time verification; if either query is moved outside the snapshot callback, the types enforce that the values are not available. Code review confirms placement. + +6. **AC6: Unused N2C imports removed** - The following symbols are no longer imported in `Query.hs`: `throwExceptT`, `executeLocalStateQueryExpr`, `queryProtocolParameters`, `queryChainPoint`, `queryChainBlockNo`, `queryUtxo`, `VolatileTip`. + `Cardano.Rpc.Server.Internal.NodeKernelAccess` is added as an import. + - Test: unit - `-Wall` clean build with no unused-import warnings. + +7. **AC7: Pagination unit tests unaffected** - The `paginateByTxIn` function and its six unit tests in `Test.Cardano.Rpc.Pagination` remain unchanged and pass. + - Test: unit - `cabal test cardano-rpc-test` passes with all pagination properties green. + +8. **AC8: Build clean** - `cabal build cardano-rpc` compiles with no errors or warnings. + - Test: unit - successful build under `-Wall -Werror` (or project-level warning settings). + +## Out of scope + +- Rewriting `evalTxMethod` in `Eval.hs` (separate piece). +- Rewriting `submitTxMethod` in `Submit.hs` (separate piece). +- Rewriting `getProtocolParamsJsonMethod` in `Node.hs` (separate piece). +- Changes to `Env.hs`, `Monad.hs`, or `Server.hs` (covered by piece 1: NodeKernelAccess types). +- Changes to `Tracing.hs` trace constructors (covered by piece 1). +- Updating the `hprop_rpc_query_pparams` timestamp assertion (currently hardcoded to `=== 0` with a TODO comment; this piece preserves existing behaviour). +- Creating `mkNodeKernelAccess` in cardano-node (piece 3). +- Integration test infrastructure changes (piece 8). + +## Definition of done + +- [ ] All AC tests written (compile, fail on stubs) +- [ ] Implementation complete (all tests pass via `cabal test`) +- [ ] Nix CI checks pass (`nix build 'path:/work#checks.x86_64-linux.test'` and `e2e`) +- [ ] haskell-reviewer agent finds no critical or style issues +- [ ] fourmolu clean +- [ ] No build warnings + +## Notes + +- **Piece 1 prerequisite assumption:** this story assumes piece 1 delivers the `Has (IORef (Maybe NodeKernelAccess)) RpcEnv` instance in `Monad.hs`, the `MonadRpc` constraint update, and the `RpcEnv` field change in `Env.hs`. + If piece 1 is scoped more narrowly (only `NodeKernelAccess.hs` itself), those wiring changes must be pulled into this piece or an intermediate one. +- **Integration tests require piece 3:** the E2E tests (`hprop_rpc_query_pparams`, `hprop_rpc_search_utxos`, `hprop_rpc_transaction`) spin up a real testnet node, which needs `mkNodeKernelAccess` wired in `Run.hs` (piece 3). + This piece can be validated at the build and unit-test level independently; full E2E validation happens after piece 3 lands. +- **Timestamp field:** `hprop_rpc_query_pparams` asserts `timestamp === 0` (line 102) with the comment "not possible to implement at this moment". + After this rewrite, `systemStart` and `eraHistory` are available inside the snapshot, so `slotToTimestamp` should produce real values. + However, updating the test assertion is out of scope for this piece; it should be addressed in a follow-up once the full pipeline is wired end-to-end. +- **Mechanical transformation:** all three methods follow the identical old-to-new pattern. + The only difference is the domain query (`QueryProtocolParameters` vs `QueryUTxO`). + Consider extracting a shared `withQuerySnapshot` helper if the duplication becomes unwieldy, but this is an implementation decision, not an AC. +- **File changed:** `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Query.hs` (single file). + +## Reference docs + +- [Consensus protocol and snapshots](analysis-consensus-protocol.md) - snapshot consistency and `answerQuery` dispatch +- [Implementation details](prereqs-implementation-details.md) - query inventory for ReadParams/ReadUtxos/SearchUtxos +- [UTxO-HD internals](analysis-utxohd-internals.md) - forker mechanics for UTxO queries diff --git a/cardano-rpc/docs/node-kernel-access/05-rewrite-submit-method.md b/cardano-rpc/docs/node-kernel-access/05-rewrite-submit-method.md new file mode 100644 index 0000000000..8ccef47acf --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/05-rewrite-submit-method.md @@ -0,0 +1,82 @@ +# Piece 5: Rewrite SubmitTx to use NodeKernelAccess + +## Problem + +`submitTxMethod` in `Submit.hs` uses Node-to-Client IPC for both era detection (`determineEra`) and transaction submission (`submitTxToNodeLocal`). +Each call opens a separate N2C connection, adding latency and requiring `tryAny` wrapping to handle connection-level exceptions. +This must be replaced with the `NodeKernelAccess` interface introduced in piece 1. + +## Why + +Eliminating N2C from the submit path removes a connection per request, avoids double serialisation, and brings `submitTxMethod` in line with the node kernel access architecture (ADR-019). + +## User value + +As a cardano-rpc operator, I want transaction submission to use in-process ledger access so that submit latency is lower and N2C connection failures are no longer a failure mode. + +## Acceptance criteria + +1. **AC1: Era detection via snapshot** - `submitTxMethod` determines the current era by calling `withNodeKernelAccess laRef` then `nkaWithSnapshot` and `runQuery snapshot QueryCurrentEra`, replacing the previous `determineEra nodeConnInfo` N2C call. + - Test: manual - verify by code inspection that `determineEra` is no longer called in `Submit.hs` + +2. **AC2: Submission via nkaSubmitTx** - Transaction submission calls `withNodeKernelAccess laRef $ \la -> nkaSubmitTx la (TxInMode sbe tx)`, a separate `withNodeKernelAccess` invocation from the era detection in AC1. + Era detection requires a ledger state snapshot; submission goes through the mempool and does not. + These are intentionally two separate `withNodeKernelAccess` calls to avoid holding a forker open during mempool insertion. + - Test: manual - verify by code inspection that `submitTxToNodeLocal` is no longer called and that era detection and submission use separate `withNodeKernelAccess` calls + +3. **AC3: N2C error wrapping removed** - The `tryAny` wrapping and `first TraceRpcSubmitN2cConnectionError` mapping are removed from the `submitTx` helper. + N2C connection errors are no longer possible via the `NodeKernelAccess` path, so this error category does not apply. + - Test: manual - `grep -En 'TraceRpcSubmitN2cConnectionError|tryAny' Submit.hs` returns no results + +4. **AC4: Submit result pattern matching preserved** - The `SubmitFail`/`SubmitSuccess` (or equivalent `TxSubmitFail`/`TxSubmitSuccess`) pattern matching on the submission result is preserved. + Validation errors are still wrapped as `TraceRpcSubmitTxValidationError` and the transaction ID is still extracted from the ledger transaction on success. + - Test: E2E - `hprop_rpc_transaction` verifies the full submit-then-query round trip (existing test, no changes needed) + +5. **AC5: Import housekeeping** - The following import changes are made in `Submit.hs`: + - Added: `Cardano.Rpc.Server.Internal.NodeKernelAccess` (for `withNodeKernelAccess`, `nkaWithSnapshot`, `runQuery`, `nkaSubmitTx`) + - Removed: `submitTxToNodeLocal` usage (from `Cardano.Api`) + - Removed: `determineEra` usage (from `Cardano.Api`) + - Removed: `throwExceptT` usage (from `Cardano.Rpc.Server.Internal.Error`) + - Test: unit - `cabal build cardano-rpc` compiles with no errors and no new warnings + +6. **AC6: Existing E2E test passes** - `hprop_rpc_transaction` in `cardano-testnet-test` passes without modification, confirming that observable behaviour is unchanged. + - Test: E2E - run `hprop_rpc_transaction` via the testnet test suite + +## Out of scope + +- Removal of the `TraceRpcSubmitN2cConnectionError` constructor from `Tracing.hs` (covered by piece 1) +- Updates to `cardano-node` tracer references in `Rpc.hs` (piece 3) +- Changes to any other RPC method (`Query.hs`, `Eval.hs`, `Node.hs`) +- New tracing constructors for ledger-access errors (piece 1) +- Changes to `Monad.hs`, `Env.hs`, or `Server.hs` (piece 1: NodeKernelAccess types) +- The `NodeKernelAccess` module itself (piece 1) + +## Definition of done + +- [ ] All AC verifications completed (code inspection, compilation, E2E) +- [ ] `cabal build cardano-rpc` compiles with no errors or warnings +- [ ] `hprop_rpc_transaction` E2E test passes +- [ ] Nix CI checks pass (`nix build 'path:.#checks.x86_64-linux.test'` and `e2e`) +- [ ] haskell-reviewer agent finds no critical or style issues +- [ ] fourmolu clean +- [ ] No build warnings + +## Notes + +- **Hard compile-time dependency on piece 1:** This piece imports `Cardano.Rpc.Server.Internal.NodeKernelAccess` and calls `withNodeKernelAccess`, `nkaWithSnapshot`, `runQuery`, and `nkaSubmitTx`. + None of these exist until piece 1 is landed. + The `Has (IORef (Maybe NodeKernelAccess)) RpcEnv` instance and the updated `MonadRpc` constraint are also delivered by piece 1. +- **TDD fit:** This is a behaviour-preserving refactor. + The existing E2E test (`hprop_rpc_transaction`) is the primary verification. + No new unit tests are introduced because testing `submitTxMethod` in isolation would require either a real node or a `NodeKernelAccess` mock, neither of which is established in this codebase yet. + Compile success and the existing E2E are the practical gates. +- **Two withNodeKernelAccess calls by design:** Era detection opens a forker (via `nkaWithSnapshot`) to query the ledger state. + Submission goes through the mempool (via `nkaSubmitTx`) and does not need a forker. + Bundling them in a single `nkaWithSnapshot` callback would hold the forker open during mempool insertion, which is unnecessary and blocks other forker consumers. +- **Current file:** `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Submit.hs` +- **E2E test file:** `cardano-node/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Transaction.hs` + +## Reference docs + +- [Architecture and current state](analysis-architecture.md) - current N2C submit path +- [API signatures](prereqs-api-signatures.md) - `addLocalTxs` signature diff --git a/cardano-rpc/docs/node-kernel-access/06-rewrite-eval-method.md b/cardano-rpc/docs/node-kernel-access/06-rewrite-eval-method.md new file mode 100644 index 0000000000..f199cf49f7 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/06-rewrite-eval-method.md @@ -0,0 +1,110 @@ +# Piece 6: Rewrite EvalTx to use NodeKernelAccess + +## Problem + +`evalTxMethod` in `Eval.hs` queries seven different ledger state values via Node-to-Client IPC using `executeLocalStateQueryExpr`. +This incurs a connection-per-request cost and CBOR serialisation round-trips that are unnecessary when the RPC server runs in-process with the node. + +## Why + +EvalTx is the most query-intensive RPC method. +Replacing N2C with direct `NodeKernelAccess` removes IPC overhead and double serialisation for all seven queries in a single call. + +## User value + +As a DApp developer calling `EvalTx`, I want transaction evaluation to use node kernel access so that execution unit estimates return faster and with lower resource cost. + +## Acceptance criteria + +1. **AC1: Single snapshot for all queries** - All seven queries (protocol parameters, UTxO by TxIn, system start, era history, stake delegation deposits, DRep state, stake pool parameters) execute inside a single `nkaWithSnapshot` callback, ensuring they share one `ReadOnlyForker` and see the same ledger state. + - Test: manual - Code review confirms `evalTxMethod` calls `nkaWithSnapshot` exactly once and all `runQuery` calls are inside that callback. + +2. **AC2: IORef NodeKernelAccess from environment** - `evalTxMethod` obtains `IORef (Maybe NodeKernelAccess)` via `grab` instead of `LocalNodeConnectInfo`. + - Test: unit - The module compiles against the updated `MonadRpc` constraint that provides `Has (IORef (Maybe NodeKernelAccess)) e` instead of `Has LocalNodeConnectInfo e`. + +3. **AC3: Era detection inside snapshot** - The current era is determined via `runQuery snapshot QueryCurrentEra` inside the snapshot callback, replacing the separate `determineEra` call over N2C. + - Test: unit - Compilation succeeds; `determineEra` and `throwExceptT` are no longer called. + +4. **AC4: Eon escapes the snapshot** - The `eon` existential (from `forEraInEon @Era`) is returned as part of the snapshot callback's result tuple, making it available for post-snapshot protobuf conversion and `evaluateTransaction`. + - Test: unit - Compilation succeeds; the `obtainCommonConstraints eon $ do ...` block after the snapshot uses the `eon` value returned from the callback. + +5. **AC5: No N2C imports remain** - `executeLocalStateQueryExpr`, `queryProtocolParameters`, `queryUtxo`, `querySystemStart`, `queryEraHistory`, `queryStakeDelegDeposits`, `queryDRepState`, `queryStakePoolParameters`, `VolatileTip`, and `determineEra` are no longer imported or called in `Eval.hs`. + - Test: manual - Code review and `grep` confirm none of these names appear in `Eval.hs`. + +6. **AC6: NodeKernelAccess import added** - `Eval.hs` imports `Cardano.Rpc.Server.Internal.NodeKernelAccess` (for `withNodeKernelAccess`, `LedgerSnapshot`, and `runQuery`). + - Test: unit - The module compiles with the new import; no unused-import warning. + +7. **AC7: Post-snapshot logic unchanged** - The `evaluateTransaction` call, redeemer data assembly, balance check, and protobuf response construction remain unchanged from the current implementation. + - Test: manual - Code review confirms the `obtainCommonConstraints eon $ do ...` block is identical to the pre-rewrite version. + +8. **AC8: Compiles without warnings** - `cabal build cardano-rpc` completes with no errors and no warnings (including `-Wunused-packages` and `-Wredundant-constraints`). + - Test: unit - Build succeeds cleanly. + +9. **AC9: Full test suite passes** - All tests in `cardano-rpc-test` pass after the rewrite, including the existing protobuf conversion tests (`hprop_mkProtoTxEval_success`, `hprop_mkProtoTxEval_with_errors`, `hprop_scriptWitnessIndex_to_redeemerPurpose`, `hprop_mkProtoRedeemer`, `hprop_scriptExecutionError_to_evalReport`). + - Test: unit - `cabal test cardano-rpc-test` exits successfully with no failures. + +10. **AC10: ExBudget results match baseline** - Submitting a transaction evaluation request to the rewritten method produces execution unit estimates consistent with known mainnet baselines or prior N2C results. + - Test: manual - Run a known transaction evaluation against a local testnet and verify the returned `ExBudget` values, fee, and balance check results are reasonable and match expectations. + +## Out of scope + +- Changes to `extractBalanceCheckCreds` (pure helper, unaffected by the data source change). +- Changes to `mkProtoTxEval`, `mkProtoRedeemer`, `scriptExecutionErrorToEvalReport`, or any protobuf conversion code in `Type.hs`. +- Changes to the post-snapshot evaluation block (`obtainCommonConstraints eon $ do ...` containing `evaluateTransaction`, redeemer assembly, balance checking, and response construction). +- Adding an automated integration test for `EvalTx` (this is a known gap; piece 8 validates the full method end-to-end). +- Changes to other RPC methods (`Query.hs`, `Submit.hs`, `Node.hs`); those are covered by separate pieces. +- Removing `import Cardano.Api` from `Eval.hs`; the module uses many `Cardano.Api` names for era handling and serialisation, so the blanket import stays. +- Removing `import Cardano.Rpc.Server.Internal.Error` from `Eval.hs`; `throwEither` is still used by `putTraceThrowEither`. + +## Definition of done + +- [ ] All AC tests written (compile, fail on stubs) +- [ ] Implementation complete (all tests pass via `cabal test`) +- [ ] Nix CI checks pass (`nix build 'path:.#cardano-rpc:lib:cardano-rpc'` and `nix build 'path:.#cardano-rpc:test:cardano-rpc-test'`) +- [ ] haskell-reviewer agent finds no critical or style issues +- [ ] fourmolu clean +- [ ] No build warnings + +## Notes + +### Dependency + +This piece requires piece 1 (NodeKernelAccess types) to be complete. +Without `Cardano.Rpc.Server.Internal.NodeKernelAccess` and the updated `MonadRpc` constraint (from piece 1), this code cannot compile. + +### Snapshot consistency invariant + +Snapshot consistency is the most critical invariant in this rewrite. +If protocol parameters come from block N but UTxOs come from block N+1, transaction evaluation will produce invalid results (wrong fee calculations, incorrect script execution budgets, or spurious failures). +The `nkaWithSnapshot` pattern guarantees all seven queries share one `ReadOnlyForker`, preserving the same consistency guarantee as the current `executeLocalStateQueryExpr` block. + +### Eon existential escape + +The `eon` value determined inside the snapshot callback must be returned as part of the result tuple. +It is needed by the post-snapshot `obtainCommonConstraints eon $ do ...` block for `evaluateTransaction` and protobuf conversion. +See the prerequisites document (section 4.1) for details on why this works with existential types. + +### Integration test gap + +No existing automated test exercises `evalTxMethod` end-to-end. +The unit tests in `Test.Cardano.Rpc.Eval` cover protobuf conversion helpers (`mkProtoTxEval`, `mkProtoRedeemer`, `scriptExecutionErrorToEvalReport`) but not the method itself. +Piece 8 (integration testing) is expected to close this gap. +AC10 covers a manual verification in the interim. + +### Query arguments unchanged + +The seven query arguments come from `extractBalanceCheckCreds` and `allInputs` exactly as in the current implementation. +The rewrite changes only the call surface (`runQuery snapshot (QueryInMode ...)` instead of `throwEither =<< throwEither =<< queryXxx sbe ...`), not the arguments themselves. +Specifically: `QueryUTxOByTxIn allInputs`, `apiStakeCreds` (for stake delegation deposits), `unregDRepCreds` (for DRep state), and `apiPoolIds` (for stake pool parameters). + +### Import changes summary + +- Add: `Cardano.Rpc.Server.Internal.NodeKernelAccess (withNodeKernelAccess, LedgerSnapshot (..))` +- Remove: `Cardano.Rpc.Server.Internal.Error` is kept (still used by `putTraceThrowEither`) +- The duplicate qualified import (`U5c` and `UtxoRpc` both pointing to `Cardano.Rpc.Proto.Api.UtxoRpc.Submit`) is pre-existing and not addressed by this piece. + +## Reference docs + +- [Consensus protocol and snapshots](analysis-consensus-protocol.md) - snapshot consistency (critical for 7-query EvalTx) +- [Implementation details](prereqs-implementation-details.md) - full EvalTx query inventory +- [API signatures](prereqs-api-signatures.md) - query type signatures diff --git a/cardano-rpc/docs/node-kernel-access/07-rewrite-node-methods.md b/cardano-rpc/docs/node-kernel-access/07-rewrite-node-methods.md new file mode 100644 index 0000000000..68416ce49c --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/07-rewrite-node-methods.md @@ -0,0 +1,72 @@ +# Piece 7: Rewrite Node methods to use NodeKernelAccess + +## Problem + +`getProtocolParamsJsonMethod` in `Node.hs` uses the N2C pattern (`determineEra` + `executeLocalStateQueryExpr` + `queryProtocolParameters`) which opens a new socket connection per request and double-serialises data. +This is the last remaining method in `Node.hs` that needs migrating to the snapshot-based `NodeKernelAccess` interface. + +## Why + +Replacing the N2C call path with node kernel access removes per-request socket overhead and double serialisation for this method, bringing `Node.hs` in line with the new architecture defined in ADR-019. + +## User value + +As a cardano-rpc maintainer, I want `getProtocolParamsJsonMethod` to use the snapshot-based `NodeKernelAccess` interface so that the Node service no longer depends on N2C for protocol parameter queries. + +## Acceptance criteria + +1. **AC1: `getEraMethod` unchanged** - The body of `getEraMethod` remains byte-identical to its current form (hardcoded Conway return). + Making it dynamic is out of scope until new eras are added. + - Test: manual - `git diff` shows no changes to `getEraMethod` + +2. **AC2: Snapshot-based protocol parameter query** - `getProtocolParamsJsonMethod` obtains a `NodeKernelAccess` via `grab @(IORef (Maybe NodeKernelAccess))` and queries protocol parameters inside a single `nkaWithSnapshot` callback using `runQuery`. + Both `pparams` and `eon` are returned from the snapshot block so that `obtainCommonConstraints eon $ A.encode pparams` continues to work outside it. + - Test: manual - code review confirms the snapshot pattern is used correctly + +3. **AC3: Era detection inside snapshot** - The current era is determined via `runQuery snapshot QueryCurrentEra` inside the snapshot callback, replacing the N2C `determineEra` call. + The `forEraInEon @Era` guard and `convert eon` pattern are preserved. + - Test: E2E - `hprop_rpc_query_pparams` validates the returned era-specific protocol parameters match the ledger + +4. **AC4: Import cleanup and clean compilation** - `Cardano.Rpc.Server.Internal.Error` is removed from imports (no more `throwExceptT`, `throwEither`). + `Cardano.Rpc.Server.Internal.NodeKernelAccess` is added. + `cabal build cardano-rpc` succeeds with no warnings related to `Node.hs`. + - Test: unit - `cabal build cardano-rpc` exits with code 0 and no warnings + +5. **AC5: E2E behavioural equivalence** - `hprop_rpc_query_pparams` passes, confirming that the rewritten method returns identical protocol parameters, ledger tip slot, block hash, and block number to the N2C implementation. + - Test: E2E - `hprop_rpc_query_pparams` in `cardano-testnet-test` + +## Out of scope + +- Making `getEraMethod` dynamic (future work when new eras arrive). +- Changes to `Env.hs`, `Monad.hs`, or `Server.hs` (covered by piece 1). +- Creating `NodeKernelAccess.hs` itself (piece 1 prerequisite). +- Rewriting methods in `Query.hs`, `Eval.hs`, or `Submit.hs` (pieces 4-6). +- Tracing changes in `Tracing.hs` or `Rpc.hs` (piece 1 and piece 3). +- Changes to `cardano-rpc.cabal` (piece 1 adds the new module). + +## Definition of done + +- [ ] All AC verifications mapped to existing tests or confirmed via code review +- [ ] Implementation complete (all existing tests still pass via `cabal test`) +- [ ] Nix CI checks pass +- [ ] haskell-reviewer agent finds no critical or style issues +- [ ] fourmolu clean +- [ ] No build warnings + +## Notes + +- **Dependency:** piece 1 (NodeKernelAccess types) must be merged first. + The environment and monad wiring that puts `IORef (Maybe NodeKernelAccess)` into `RpcEnv` and `MonadRpc` is delivered by piece 1 and must be in place before this piece compiles. +- **`eon` escapes the snapshot block** because it is needed at line 50 of the current code: `obtainCommonConstraints eon $ A.encode pparams`. + The implementer must return `(pparams, eon)` from the `nkaWithSnapshot` callback, not just `pparams`. +- **Error handling change:** The current code uses `throwExceptT` and nested `throwEither` chains for N2C error conversion. + The new code relies on `withNodeKernelAccess` throwing `GrpcException` with `GrpcUnavailable` if the kernel is not yet initialised, and on `runQuery` propagating exceptions directly. + This is simpler but changes the exception type from `RpcException` to `GrpcException` for the "not initialised" case. +- **Smallest piece in the plan:** This rewrites one method in one file. + It is a good candidate for a quick early win and a template for the larger Query/Eval/Submit rewrites. +- **File:** `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/Node.hs` + +## Reference docs + +- [Architecture and current state](analysis-architecture.md) - current N2C query path +- [API signatures](prereqs-api-signatures.md) - query type signatures diff --git a/cardano-rpc/docs/node-kernel-access/08-integration-testing.md b/cardano-rpc/docs/node-kernel-access/08-integration-testing.md new file mode 100644 index 0000000000..e7cd23b4d1 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/08-integration-testing.md @@ -0,0 +1,97 @@ +# Piece 8: Integration testing and validation + +## Problem + +All seven preceding pieces replace the N2C IPC path with node kernel access, but there is no explicit validation that the existing integration tests still pass, that no N2C artefacts remain in the codebase, and that new tracing constructors appear in logs. +Without this validation gate, the ADR-019 migration could regress end-to-end behaviour silently. + +## Why + +This is the final piece in the ADR-019 node kernel access migration. +It gates the merge of the whole effort by confirming that the observable behaviour of cardano-rpc is unchanged from the perspective of gRPC clients, while verifying that the internal wiring has shifted entirely to in-process ledger access. + +## User value + +As a cardano-rpc maintainer, I want to confirm that the node kernel access migration preserves all existing integration test behaviour and identify any new test coverage gaps, so that the ADR-019 work can be merged with confidence. + +## Acceptance criteria + +1. **AC1: `hprop_rpc_query_pparams` passes** - The existing E2E test that validates all 44 protocol parameters match between the gRPC response and the ledger state passes without modification. + The test connects via `Rpc.withConnection` to the gRPC socket, which is unchanged by this migration. + - Test: E2E - `TASTY_PATTERN='/RPC Query Protocol Params/' cabal test cardano-testnet-test` + +2. **AC2: `hprop_rpc_transaction` passes** - The existing E2E test that performs a full round-trip (fetch UTxOs via SearchUtxos, build transaction, submit via SubmitTx, confirm on-chain via SearchUtxos) passes without modification. + - Test: E2E - `TASTY_PATTERN='/RPC Transaction Submit/' cabal test cardano-testnet-test` + +3. **AC3: `hprop_rpc_search_utxos` passes** - The existing E2E test that exercises SearchUtxos with exact-address predicates, payment-credential predicates, non-matching predicates, and predicate-less queries passes without modification. + - Test: E2E - `TASTY_PATTERN='/RPC SearchUtxos/' cabal test cardano-testnet-test` + +4. **AC4: No RPC test file changes** - No files under `cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/` are modified by the ADR-019 branch relative to the base branch. + `Testnet/Types.hs` is also unchanged; `nodeRpcSocketPath` still delegates to `nodeSocketPathToRpcSocketPath`. + - Test: manual - `git diff main -- cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/ cardano-testnet/src/Testnet/Types.hs` shows zero changes + +5. **AC5: No N2C trace constructors remain** - `TraceRpcSubmitN2cConnectionError` does not appear anywhere in the cardano-rpc or cardano-node source trees. + The constructor was removed in piece 1 and replaced by `TraceRpcNodeKernelAccessUnavailable` and `TraceRpcForkerError`. + - Test: unit - `grep -r 'TraceRpcSubmitN2cConnectionError' cardano-api/cardano-rpc/src/ cardano-node/cardano-node/src/` returns no matches + +6. **AC6: No N2C connection imports in RPC methods** - The modules `Query.hs`, `Submit.hs`, `Eval.hs`, and `Node.hs` no longer import `executeLocalStateQueryExpr`, `submitTxToNodeLocal`, `determineEra`, or `VolatileTip` from `Cardano.Api`. + - Test: unit - `grep -l 'executeLocalStateQueryExpr\|submitTxToNodeLocal\|determineEra\|VolatileTip' cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/*.hs cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/Node.hs` returns no matches + +7. **AC7: No runtime N2C connection from cardano-rpc** - The absence of `executeLocalStateQueryExpr`, `submitTxToNodeLocal`, and `determineEra` imports (AC6) is the static proof that cardano-rpc no longer opens N2C connections at runtime. + As additional confirmation, a manual testnet run should show no `connectToLocalNode` traces originating from cardano-rpc in the node logs. + - Test: manual - inspect structured trace output from a testnet run and confirm no N2C connection traces are attributed to cardano-rpc + +8. **AC8: New trace constructors appear in trace configuration** - `NodeKernelAccessUnavailable` and `ForkerError` appear in the `allNamespaces` list of `MetaTrace TraceRpc` in `cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs`. + - Test: unit - `grep -c 'NodeKernelAccessUnavailable\|ForkerError' cardano-node/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs` returns at least 2 + +9. **AC9: Coverage gaps documented** - A note is added to this story (or a follow-up ticket is filed) listing the known integration test coverage gaps: + (a) No E2E test for EvalTx (the `evalTxMethod` is exercised only by unit tests on the protobuf conversion layer, not end-to-end). + (b) No E2E test for the UNAVAILABLE gRPC response during startup before `NodeKernel` is ready. + (c) No E2E test for forker acquisition failure. + (d) No E2E test for snapshot consistency under concurrent ledger state changes. + - Test: manual - reviewer confirms the gaps are documented + +## Out of scope + +- Writing new E2E tests for EvalTx, UNAVAILABLE startup response, forker errors, or snapshot consistency (documented as gaps in AC9; to be addressed in separate stories). +- Performance benchmarking of node kernel access vs N2C (separate initiative). +- Changes to `Testnet/Types.hs`, `Testnet/Start/Types.hs`, or any RPC test file. +- Modifications to the gRPC client library (`Cardano.Rpc.Client`). +- Changes to protobuf definitions or generated code. + +## Definition of done + +- [ ] All three E2E tests pass: `hprop_rpc_query_pparams`, `hprop_rpc_transaction`, `hprop_rpc_search_utxos` +- [ ] N2C artefact checks pass (AC5, AC6) +- [ ] New trace constructors verified (AC8) +- [ ] No RPC test files modified (AC4) +- [ ] Coverage gaps documented (AC9) +- [ ] Nix CI checks pass (`nix build 'path:/work/cardano-api#checks.x86_64-linux.test'` and `e2e`) +- [ ] haskell-reviewer agent finds no critical or style issues across all ADR-019 changes +- [ ] fourmolu clean +- [ ] No build warnings + +## Notes + +- **Dependency:** all pieces 1-7 must be complete before this piece can be validated. + This is the merge gate for the entire ADR-019 node kernel access effort. +- **Three E2E tests, not two:** The implementation plan's Step 13 mentions two tests (`hprop_rpc_query_pparams` and `hprop_rpc_transaction`), but `hprop_rpc_search_utxos` also exists in `cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/SearchUtxos.hs` and exercises the same gRPC client path. + All three must pass. +- **Testnet wiring is automatic:** The testnet creates a real `cardano-node` with `runtimeEnableRpc = RpcEnabled` (or `cardanoEnableRpc = RpcEnabled` in `SearchUtxos`). + The `Run.hs` wiring in piece 3 populates the `IORef (Maybe NodeKernelAccess)` via `rnNodeKernelHook`, so the RPC server transitions from UNAVAILABLE to serving automatically. +- **gRPC client path is unchanged:** Tests connect via `Rpc.withConnection def (Rpc.ServerUnix rpcSocket)`, which talks to the gRPC Unix socket. + The internal switch from N2C to node kernel access is invisible at this layer. +- **Unit tests in cardano-rpc-test:** The unit tests in `cardano-rpc/test/` (protocol parameter conversion, predicate matching, eval protobuf construction, pagination, type conversion) test the conversion layer which is unaffected by this migration. + They should continue to pass without changes. +- **File locations:** + - E2E tests: `cardano-node/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/{Query,Transaction,SearchUtxos}.hs` + - Test runner: `cardano-node/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs` + - Testnet types: `cardano-node/cardano-testnet/src/Testnet/Types.hs` (exports `nodeRpcSocketPath`) + - Node wiring: `cardano-node/cardano-node/src/Cardano/Node/Run.hs` + - Trace instances: `cardano-node/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs` + +## Reference docs + +- [Implementation details](prereqs-implementation-details.md) - verification checklist +- [Build and conventions](prereqs-build-and-conventions.md) - test commands +- [Architecture and current state](analysis-architecture.md) - spec coverage gaps diff --git a/cardano-rpc/docs/node-kernel-access/09-follow-tip.md b/cardano-rpc/docs/node-kernel-access/09-follow-tip.md new file mode 100644 index 0000000000..b9716ff956 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/09-follow-tip.md @@ -0,0 +1,199 @@ +# Piece 9: FollowTip streaming endpoint + +## Problem + +Chain-following clients (Kupo, Scrolls, Oura, custom indexers) currently need a direct N2C ChainSync connection to stream blocks from the node. +This requires local socket access, a Haskell-compatible Ouroboros implementation, and version negotiation. +There is no gRPC equivalent. + +## Why + +`FollowTip` is the gRPC equivalent of the Ouroboros ChainSync mini-protocol - the most architecturally significant method in the UTxO RPC spec. +It is the first server-streaming RPC in cardano-rpc and the first to use `ChainDB` follower capabilities. +After this piece, remote clients can follow the chain over TCP using standard gRPC libraries in any language. + +## User value + +As a chain indexer developer, I want to follow the chain tip over gRPC so that I can stream blocks without implementing the Ouroboros ChainSync protocol or having local socket access to the node. + +## Acceptance criteria + +1. **AC1: NodeKernelAccess follower capability** - `NodeKernelAccess` gains a new field `nkaWithFollower :: forall a. ([ChainPoint] -> ChainFollower -> IO a) -> IO a`. + The callback receives an intersection function and a `ChainFollower` handle. + `nkaWithFollower` uses bracket semantics: the follower is closed when the callback returns or throws. + - Test: unit - compiles + +2. **AC2: ChainFollower type** - A new type `ChainFollower` is defined in the `NodeKernelAccess` module with: + - `cfNextChange :: IO ChainChange` - blocks until the next chain event is available + - `cfFindIntersect :: [ChainPoint] -> IO (Maybe ChainPoint)` - finds the most recent point from the list that is on the current chain + `ChainChange` is a sum type with `ChainApply ChainPoint ByteString` (slot/hash + raw block CBOR) and `ChainUndo ChainPoint ByteString` (rollback with full block data). + - Test: unit - compiles + +3. **AC3: mkNodeKernelAccess implements nkaWithFollower** - `mkNodeKernelAccess` implements `nkaWithFollower` by calling `ChainDB.newFollower` inside `withRegistry`. + The follower is created with `GetRawBlock` as the block component. + `cfNextChange` wraps `followerInstruction` (blocking variant), mapping `AddBlock` to `ChainApply` and `RollBack` to `ChainUndo`. + `cfFindIntersect` wraps the follower's intersection-finding capability. + The follower is closed via `followerClose` when the bracket exits. + - Test: E2E - `hprop_rpc_follow_tip` + +4. **AC4: sync.proto FollowTip RPC** - The existing `sync.proto` (from piece 2) is extended with `rpc FollowTip(FollowTipRequest) returns (stream FollowTipResponse)`. + `FollowTipRequest` has `repeated BlockRef intersect`. + `FollowTipResponse` has `oneof action { AnyChainBlock apply = 1; AnyChainBlock undo = 2; BlockRef reset = 3; }` and a `BlockRef tip` field. + Proto-lens bindings are regenerated. + - Test: unit - compiles + +5. **AC5: followTipMethod handler** - A new function `followTipMethod` in `Cardano.Rpc.Server.Internal.UtxoRpc.Sync` implements the server-streaming handler. + On stream open: + - Calls `withNodeKernelAccess` then `nkaWithFollower`. + - Converts the client's `repeated BlockRef intersect` to `[ChainPoint]`. + - Calls `cfFindIntersect`. If no intersection found, sends a `reset` action to genesis. + On each iteration: + - Calls `cfNextChange` (blocks until next event). + - Maps `ChainApply` to an `apply` action and `ChainUndo` to an `undo` action. + - Populates `native_bytes` in the `AnyChainBlock`. + - Queries the current tip via `nkaWithSnapshot` and populates the `tip` field. + - Sends the `FollowTipResponse` on the stream. + On stream close: + - The bracket in `nkaWithFollower` ensures the follower is cleaned up. + - Test: E2E - `hprop_rpc_follow_tip` + +6. **AC6: Server.hs registers FollowTip** - `Server.hs` registers `followTipMethod` in the `SyncService` alongside `fetchBlockMethod`. + - Test: unit - compiles + +7. **AC7: Tracing** - New trace constructors `TraceRpcFollowTipStarted`, `TraceRpcFollowTipEnded`, `TraceRpcFollowTipApply`, `TraceRpcFollowTipUndo`, `TraceRpcFollowTipReset` are added. + `LogFormatting` and `MetaTrace` instances are updated in `Cardano.Node.Tracing.Tracers.Rpc`. + - Test: unit - compiles (`-Wincomplete-patterns` catches missing branches) + +8. **AC8: E2E test** - A new test `hprop_rpc_follow_tip` starts a testnet, opens a `FollowTip` stream with an empty intersect list, submits a transaction, reads events from the stream until a block containing the transaction arrives as an `apply` action, and verifies the `native_bytes` is non-empty and the `tip` field has a valid slot. + - Test: E2E - `TASTY_PATTERN='/RPC FollowTip/' cabal test cardano-testnet-test` + +9. **AC9: Build clean** - `cabal build cardano-rpc`, `cabal build cardano-node`, and all existing tests pass with no errors or warnings. + - Test: E2E - full CI pass + +## Out of scope + +- Parsed `cardano.Block` population in `AnyChainBlock` (native_bytes only). +- `FieldMask` support. +- Concurrent follower limits or idle timeouts (follow-up). +- `DumpHistory` (separate piece). +- ReadTip (separate issue, can use existing N2C path). +- Backpressure tuning beyond gRPC's built-in HTTP/2 flow control. + +## Definition of done + +- [ ] All AC tests written (compile, fail on stubs) +- [ ] Implementation complete (all tests pass) +- [ ] `cabal build cardano-rpc` succeeds with no warnings +- [ ] `cabal build cardano-node` succeeds with no warnings +- [ ] E2E tests pass: `cabal test cardano-testnet-test --test-option='-p /RPC/'` +- [ ] Nix CI checks pass +- [ ] haskell-reviewer agent finds no critical or style issues +- [ ] fourmolu clean (`scripts/devshell/prettify` run on changed files) +- [ ] No build warnings + +## Notes + +### Key differences from Ouroboros ChainSync + +| Aspect | ChainSync | FollowTip | +|--------|-----------|-----------| +| Transport | Unix socket (N2C) | TCP/gRPC | +| Rollback data | Only the rollback point | Full block included in `undo` | +| No-intersect | `MsgIntersectNotFound` | `reset` action | +| Pipelining | Client-driven (`MsgRequestNextPipelined`) | Server-driven (gRPC stream backpressure) | +| Block format | CBOR | Proto (`AnyChainBlock` with `native_bytes`) | + +### Why N2C ChainSync is insufficient + +Each gRPC `FollowTip` client would need its own N2C socket connection with a dedicated ChainSync follower. +The node creates a fresh follower per N2C connection with no way to multiplex. +In-process access via `ChainDB.newFollower` lets cardano-rpc create followers directly without socket overhead. + +### Tip reporting + +Each `FollowTipResponse` includes the current chain tip via `nkaWithSnapshot` querying `VolatileTip`. +This lets clients measure sync lag (difference between their position and the tip). + +### Follower lifecycle + +The `nkaWithFollower` bracket ensures cleanup on all exit paths: +- Normal stream completion +- Client disconnect (gRPC cancellation) +- Server-side exception + +Each active follower holds a pointer into the `ChainDB`. +The node already supports multiple concurrent followers (one per N2C ChainSync connection). + +### Files affected + +**cardano-rpc:** + +| File | Change | +|---|---| +| `src/Cardano/Rpc/NodeKernelAccess.hs` | Add `nkaWithFollower`, `ChainFollower`, `ChainChange` types. | +| `proto/utxorpc/v1beta/sync/sync.proto` | Add `FollowTip` RPC and response types. | +| `gen/Proto/Utxorpc/V1beta/Sync/Sync.hs` | **Regenerated.** | +| `gen/Proto/Utxorpc/V1beta/Sync/Sync_Fields.hs` | **Regenerated.** | +| `src/Cardano/Rpc/Server/Internal/UtxoRpc/Sync.hs` | Add `followTipMethod`. | +| `src/Cardano/Rpc/Server.hs` | Register `followTipMethod` in SyncService. | +| `cardano-rpc.cabal` | Update module listings if needed. | + +**cardano-node:** + +| File | Change | +|---|---| +| `src/Cardano/Node/Rpc/NodeKernelAccess.hs` | Implement `nkaWithFollower` via `ChainDB.newFollower`. | +| `src/Cardano/Node/Tracing/Tracers/Rpc.hs` | Add `FollowTip` trace constructors. | + +**cardano-testnet:** + +| File | Change | +|---|---| +| `test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FollowTip.hs` | **New.** `hprop_rpc_follow_tip`. | +| `test/cardano-testnet-test/cardano-testnet-test.hs` | Register new test. | + +### Gotchas for the implementer + +- **grapesy server-streaming API.** This is the first streaming handler in cardano-rpc. Study how grapesy exposes server-streaming methods - the handler signature differs from unary RPCs. + +- **`followerInstruction` vs `followerInstructionBlocking`.** Use the blocking variant to avoid busy-waiting. It returns when the next chain event is available. + +- **Rollback includes full block data.** The UTxO RPC spec includes the full block in `undo` actions, unlike ChainSync which only sends the rollback point. The follower must be created with `GetRawBlock` to have block data available for both directions. + +- **`ChainDB.newFollower` requires `ResourceRegistry`.** Use `withRegistry` from `Control.ResourceRegistry` to manage the follower's lifecycle. + +- **Proto codegen after modifying sync.proto.** Run `nix develop --command bash -c "cd cardano-rpc && buf generate proto"` to regenerate bindings. + +### Risks + +| Risk | Mitigation | +|------|------------| +| grapesy server-streaming API unfamiliar | Study grapesy examples and docs before implementation | +| Follower leak on unclean client disconnect | Bracket semantics in `nkaWithFollower`; gRPC cancellation triggers cleanup | +| High memory per follower with many clients | Out of scope for this piece; concurrent limits can be added later | +| `followerInstructionBlocking` semantics may differ across consensus versions | Verify the blocking variant exists and behaves as expected | + +### Dependencies + +- **Upstream:** pieces 1-3 (NodeKernelAccess types, sync.proto, mkNodeKernelAccess, node wiring). +- **Downstream:** piece 8 (integration testing). + +### Testing approach + +| AC | Type | What it tests | +|---|---|---| +| AC1 | unit | NodeKernelAccess follower field compiles | +| AC2 | unit | ChainFollower types compile | +| AC3 | E2E | Follower creation via mkNodeKernelAccess | +| AC4 | unit | Proto codegen compiles | +| AC5 | E2E | Full streaming handler end-to-end | +| AC6 | unit | Server registration compiles | +| AC7 | unit | Tracing instances compile | +| AC8 | E2E | Dedicated integration test | +| AC9 | E2E | Full regression | + +## Reference docs + +- [Architecture and current state](analysis-architecture.md) - cardano-rpc overview and spec coverage +- [Build and conventions](prereqs-build-and-conventions.md) - proto codegen instructions +- GitHub issue: #1219 diff --git a/cardano-rpc/docs/node-kernel-access/README.md b/cardano-rpc/docs/node-kernel-access/README.md new file mode 100644 index 0000000000..cd21c5a618 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/README.md @@ -0,0 +1,66 @@ +# Direct Node Access for cardano-rpc + +ADR: [ADR-019](../../../../cardano-node-wiki/docs/ADR-019-node-kernel-access-for-cardano-rpc.md) + +## Overview + +Replace N2C IPC (Unix socket queries to cardano-node) with in-process access to `NodeKernel`. +This covers ledger state queries (via `answerQuery`), block retrieval (via `ChainDB`), and transaction submission (via `Mempool`). +See ADR-019 for the full rationale. + +## Reference documents + +Technical analysis (split by topic): +- [Architecture and current state](analysis-architecture.md) - cardano-rpc overview, spec coverage, data path +- [UTxO-HD internals](analysis-utxohd-internals.md) - backing store, LedgerTables, forker mechanics +- [Consensus protocol and snapshots](analysis-consensus-protocol.md) - LocalStateQuery, in-memory state, snapshot consistency + +Prerequisites (split by topic): +- [API signatures](prereqs-api-signatures.md) - verified consensus and cardano-api type signatures +- [Build and conventions](prereqs-build-and-conventions.md) - project layout, nix build, codebase gotchas +- [Implementation details](prereqs-implementation-details.md) - subtle gotchas, query inventory, verification checklist + +Cross-cutting: +- [Implementation plan](implementation-plan.md) - file summary, verification, risks + +## Deliverables + +Each piece is independently deliverable and testable. +Piece 1 creates the `NodeKernelAccess` abstraction and wires it through cardano-rpc. +Piece 2 adds the FetchBlock proto and handler. +Piece 3 implements `mkNodeKernelAccess` in cardano-node and wires it into the startup sequence, making FetchBlock work end-to-end. +Pieces 4-7 rewrite existing N2C methods to use `NodeKernelAccess`. +Piece 9 adds the FollowTip server-streaming endpoint. +Piece 8 is the final validation. + +``` +1 (NodeKernelAccess types) ─► 2 (FetchBlock proto) ─► 3 (mkNodeKernelAccess + wiring) ─► 4 (query methods) ─┐ + ├► 5 (submit method) │ + ├► 6 (eval method) ├► 8 (integration testing) + ├► 7 (node methods) │ + └► 9 (FollowTip) ─┘ +``` + +| # | Deliverable | Scope | Repo | +|---|-------------|-------|------| +| 1 | [NodeKernelAccess types and plumbing](01-node-access-types.md) | `NodeKernelAccess` record, env/monad/tracing/server wiring | cardano-rpc | +| 2 | [FetchBlock proto and handler](02-fetchblock-proto-and-handler.md) | SyncService proto, codegen, `fetchBlockMethod`, server registration | cardano-rpc | +| 3 | [mkNodeKernelAccess and node wiring](03-mk-node-access-and-wiring.md) | `mkNodeKernelAccess` from `NodeKernel`, `Run.hs` IORef wiring, E2E test | cardano-node | +| 4 | [Rewrite query methods](04-rewrite-query-methods.md) | ReadParams, ReadUtxos, SearchUtxos switch to snapshot pattern | cardano-rpc | +| 5 | [Rewrite submit method](05-rewrite-submit-method.md) | SubmitTx switches to nkaSubmitTx | cardano-rpc | +| 6 | [Rewrite eval method](06-rewrite-eval-method.md) | EvalTx (7 queries) switches to snapshot | cardano-rpc | +| 7 | [Rewrite node methods](07-rewrite-node-methods.md) | GetProtocolParamsJson switches to snapshot | cardano-rpc | +| 8 | [Integration testing](08-integration-testing.md) | Validate all tests pass, document coverage gaps | both | +| 9 | [FollowTip streaming](09-follow-tip.md) | Server-streaming chain follower via gRPC | both | + +## NodeKernel coverage + +`NodeKernel` provides access to three subsystems: + +| Subsystem | Access | RPCs covered | +|-----------|--------|-------------| +| **ChainDB** | `getBlockComponent`, `getReadOnlyForkerAtPoint`, `newFollower` | FetchBlock, DumpHistory, FollowTip, ReadTip, all ledger queries | +| **Mempool** | `addLocalTxs`, `getSnapshot` | SubmitTx, ReadMempool | +| **TopLevelConfig** | `getTopLevelConfig` | ReadGenesis, `ExtLedgerCfg` for `answerQuery` | + +Two spec RPCs (ReadTx, ReadData) require indexes the node does not build - these need an external indexer. diff --git a/cardano-rpc/docs/node-kernel-access/analysis-architecture.md b/cardano-rpc/docs/node-kernel-access/analysis-architecture.md new file mode 100644 index 0000000000..d1963b62e4 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/analysis-architecture.md @@ -0,0 +1,167 @@ +# Architecture and Current State + +This document covers cardano-rpc's current architecture, spec coverage, and data flow. + +--- + +## cardano-rpc Implementation Overview + +**Three gRPC services** defined in proto files: + +| Service | Methods | Proto | +|---------|---------|-------| +| **Node** (custom) | `GetEra`, `GetProtocolParamsJson` | `cardano/rpc/node.proto` | +| **QueryService** (UTxO RPC) | `ReadParams`, `ReadUtxos`, `SearchUtxos` | `utxorpc/v1beta/query/query.proto` | +| **SubmitService** (UTxO RPC) | `SubmitTx`, `EvalTx` | `utxorpc/v1beta/submit/submit.proto` | + +**Architecture:** +- `RpcEnv` holds config, tracer, and node connection info - injected via `Has` typeclass + `MonadRpc` constraint. +- Server runs on a **Unix domain socket** (insecure/local IPC), default `rpc.sock` next to the node socket. +- Queries the node via standard Node-to-Client local state queries. +- Approximately 2000 lines across 16 modules plus generated proto-lens code in `gen/`. + +**Key modules:** +- `Server.hs` - entry point, registers method groups, top-level exception handler. +- `Type.hs` (~600 lines) - bidirectional conversions between cardano-api/ledger types and protobuf. +- `Predicate.hs` - UTxO pattern matching (address, asset, composite predicates) with address extraction optimisation. +- `Query.hs` - pagination (token = `TxId#OutputIndex`, default 100 items), deterministic sort by TxIn. +- `Submit.hs` - CBOR deserialisation -> era validation -> node submission. + +**Node integration** (in the worktree): +- `--grpc-enable` / `--grpc-socket-path` CLI flags. +- `EnableRpc` / `RpcSocketPath` config keys. +- Runs concurrently via `withAsync` in `Cardano.Node.Run`. +- Structured tracing with metrics (`rpc.request.QueryService.*`, etc.). + +**Integration tests** in `cardano-testnet/test/`: +- `hprop_rpc_query_pparams` - validates all 44 protocol params match ledger. +- `hprop_rpc_transaction` - full round-trip: fetch UTxOs -> build tx -> submit via RPC -> confirm on-chain. + +--- + +### ADR-018 + +**Primary document:** `ADR-018-cardano-rpc-grpc-server.md` + +**Status:** Proposed (2026-03-11) + +**Key decisions:** +- Implements the **UTxO RPC** standard (`utxorpc.org`) plus custom Cardano extensions. +- **Opt-in and experimental** - must be explicitly enabled. +- Unix socket keeps the same local-access-only security model as the existing node socket. +- Remote access via **Envoy reverse proxy** with TLS, rate limiting, and mTLS auth. + +**Current limitations acknowledged:** +- Serialisation overhead (CBOR encode/decode round-trips). +- Connection-per-request cost (Ouroboros handshake). +- Sequential mini-protocol bottleneck. + +**Planned improvements:** +- Direct ledger state access (in-memory TVar/STM reads, bypassing IPC). +- Connection pooling over persistent IPC connections. + +**Dependencies:** `grapesy`, `grpc-spec`, `proto-lens`. + +--- + +## UTxO RPC Spec Coverage + +Current cardano-rpc implements a subset of the UTxO RPC v1beta specification. +The full spec defines four services; cardano-rpc currently covers two partially. + +**QueryService** (8 spec RPCs): + +| RPC | Implemented | Notes | +|-----|-------------|-------| +| ReadParams | Yes | Protocol parameters | +| ReadUtxos | Yes | UTxO lookup by TxIn | +| SearchUtxos | Yes | UTxO search by predicate | +| ReadData | No | Datum by hash - needs PlutusData lookup | +| ReadTx | No | Transaction by hash - needs ChainDB index | +| ReadGenesis | No | Genesis config - available from TopLevelConfig | +| ReadEraSummary | No | Era history - available via QueryEraHistory | +| ReadState | No | Generic state query (e.g. GetStakePoolDistribution) | + +**SubmitService** (5 spec RPCs): + +| RPC | Implemented | Notes | +|-----|-------------|-------| +| SubmitTx | Yes | Transaction submission | +| EvalTx | Yes | Transaction evaluation (7 N2C queries) | +| WaitForTx | No | Streaming - watch for tx confirmation | +| ReadMempool | No | Mempool snapshot - Mempool.getSnapshot | +| WatchMempool | No | Streaming - mempool changes | + +**SyncService** (4 spec RPCs) and **WatchService** (1 spec RPC) are not yet in cardano-rpc's proto files. + +The node kernel access design must support all currently implemented RPCs. +Future RPCs (ReadMempool, ReadGenesis, ReadEraSummary, ReadState) will also benefit from direct access. + +--- + +## Data Path: gRPC to Ledger State + +cardano-rpc does **not** read ledger state directly. +It acts as a **Node-to-Client (N2C) IPC client** that connects to the same node it's running inside, via a Unix domain socket. + +### The connection chain + +``` +gRPC client + -> cardano-rpc server (Unix socket: rpc.sock) + -> cardano-api IPC layer (Unix socket: node.sock) + -> cardano-node's Ouroboros mini-protocol server + -> in-memory ledger state +``` + +### Step by step + +**1. Fresh connection per request** (`Env.hs:16-29`) + +`RpcEnv` holds a `LocalNodeConnectInfo` (socket path + network magic + consensus mode params). +The TODO on line 19 confirms: there's currently **one connection per RPC request** - no connection pooling yet. + +**2. Era determination** - every handler starts with: +```haskell +AnyCardanoEra era <- liftIO . throwExceptT $ determineEra nodeConnInfo +``` +This opens a N2C connection and runs the `QueryCurrentEra` mini-protocol query. + +**3. Local State Query mini-protocol** (`Query.hs:52`, `Node.hs:47`) + +The core mechanism is `executeLocalStateQueryExpr` from `cardano-api`. +This function (`IPC/Internal/Monad.hs:71-93`): +1. Opens a **new** N2C connection via `connectToLocalNodeWithVersion` (Ouroboros network layer -> `Net.connectTo` on the local Unix socket). +2. Negotiates the Node-to-Client protocol version. +3. Runs the **LocalStateQuery** mini-protocol: + - Sends `MsgAcquire` targeting `VolatileTip` (latest ledger state). + - The node acquires a **read snapshot** of ledger state at the tip. + - Sends `MsgQuery` for each query (protocol params, UTxOs, chain point, block number). + - Receives results. + - Sends `MsgRelease` then `MsgDone`. + +**4. Specific queries used:** + +| RPC Method | Ouroboros Queries | +|---|---| +| `ReadParams` | `queryProtocolParameters` + `querySystemStart` + `queryEraHistory` + `queryChainPoint` + `queryChainBlockNo` | +| `ReadUtxos` | `queryUtxo` (by TxIn set) + `querySystemStart` + `queryEraHistory` + chain point + block no | +| `SearchUtxos` | `queryUtxo` (by address set or whole) + `querySystemStart` + `queryEraHistory` + chain point + block no, then client-side predicate filtering + pagination | +| `GetProtocolParamsJson` | `queryProtocolParameters` | +| `SubmitTx` | `submitTxToNodeLocal` (LocalTxSubmission mini-protocol) | +| `EvalTx` | `queryProtocolParameters` + `queryUtxo` (by TxIn) + `querySystemStart` + `queryEraHistory` + `queryStakeDelegDeposits` + `queryDRepState` + `queryStakePoolParameters` | + +**5. Query optimisation in SearchUtxos** (`Query.hs:104-106`) + +Before querying, `extractAddressesFromPredicate` inspects the predicate tree. +If addresses can be extracted, it uses `QueryUTxOByAddress` (indexed lookup in the ledger). +Otherwise it falls back to `QueryUTxOWhole` (fetches entire UTxO set - expensive). + +### Key implications + +- **Every RPC request opens a new Ouroboros connection** - full handshake + protocol negotiation each time. +- **Queries are sequential** within a connection (the LocalStateQuery protocol is inherently sequential). +- **All data passes through CBOR serialisation** - the node serialises ledger state into CBOR over the socket, cardano-api deserialises it, then cardano-rpc re-serialises to protobuf. +- **The node acquires a consistent snapshot** at `VolatileTip` - so protocol params, UTxOs, and chain point within one `executeLocalStateQueryExpr` call are all from the same ledger state. + +This is the architecture the ADR acknowledges as having overhead, with the planned improvement being **direct ledger state access** via TVar/STM reads (bypassing IPC entirely). diff --git a/cardano-rpc/docs/node-kernel-access/analysis-consensus-protocol.md b/cardano-rpc/docs/node-kernel-access/analysis-consensus-protocol.md new file mode 100644 index 0000000000..fb535cc6c1 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/analysis-consensus-protocol.md @@ -0,0 +1,188 @@ +# Consensus Protocol and Snapshot Consistency + +This document covers the consensus query protocol, in-memory ledger state architecture, and snapshot consistency requirements. + +--- + +## LocalStateQuery Server-Side Protocol + +### Protocol Setup + +The server is initialised in `NodeToClient.hs`: +```haskell +hStateQueryServer = + localStateQueryServer (ExtLedgerCfg cfg) + . ChainDB.getReadOnlyForkerAtPoint getChainDB +``` + +### Protocol States + +``` +StIdle --> Acquire --> StAcquiring --> Acquired --> StAcquired +StAcquired --> Query --> StQuerying --> Result --> StAcquired +StAcquired --> Release --> StIdle +StAcquired --> ReAcquire --> StAcquiring +``` + +### Query Answering + +```haskell +answerQuery config forker query = case query of + BlockQuery blockQuery -> + case sing :: Sing footprint of + SQFNoTables -> + answerPureBlockQuery config blockQuery + <$> atomically (roforkerGetLedgerState forker) + SQFLookupTables -> + answerBlockQueryLookup config blockQuery forker + SQFTraverseTables -> + answerBlockQueryTraverse config blockQuery forker + GetSystemStart -> + pure $ getSystemStart config + GetChainBlockNo -> + headerStateBlockNo . headerState + <$> atomically (roforkerGetLedgerState forker) + GetChainPoint -> + headerStatePoint . headerState + <$> atomically (roforkerGetLedgerState forker) +``` + +### Acquiring: ReadOnlyForker + +When a client sends "Acquire" with a target point: +```haskell +getReadOnlyForkerAtPoint :: + ResourceRegistry m -> + Target (Point blk) -> + m (Either GetForkerError (ReadOnlyForker' m blk)) +``` + +Returns a read-only forker providing: +- Consistent view of ledger state at the requested point. +- Access to ledger tables for queries that need them. +- Automatic resource cleanup. + +### What Node Kernel Access Must Replicate + +1. Accessing the ChainDB (from NodeKernel). +2. Reading current ledger state atomically: `atomically (ChainDB.getCurrentLedger chainDB)`. +3. For specific point queries: `ChainDB.getReadOnlyForkerAtPoint chainDB reg target`. +4. Reading ledger tables if needed. +5. Converting query results to appropriate response types. + +--- + +## Ledger State In-Memory Architecture + +### Node Kernel Structure + +```haskell +data NodeKernel m addrNTN addrNTC blk = NodeKernel + { getChainDB :: ChainDB m blk + , getMempool :: Mempool m blk + , getTopLevelConfig :: TopLevelConfig blk + , ... (peer management, tracers, block forging state, etc.) + } +``` + +Wrapped in `NodeKernelData`: +```haskell +newtype NodeKernelData blk = + NodeKernelData + { unNodeKernelData :: IORef (StrictMaybe (NodeKernel IO RemoteAddress LocalConnectionId blk)) + } +``` + +### Access Points to Ledger State + +**Via ChainDB API:** +```haskell +getCurrentLedger :: STM m (ExtLedgerState blk EmptyMK) +getImmutableLedger :: STM m (ExtLedgerState blk EmptyMK) +getPastLedger :: Point blk -> STM m (Maybe (ExtLedgerState blk EmptyMK)) +getCurrentChain :: STM m (AnchoredFragment (Header blk)) +getReadOnlyForkerAtPoint :: ResourceRegistry m -> Target (Point blk) -> m (Either GetForkerError (ReadOnlyForker' m blk)) +``` + +**Via ReadOnlyForker:** +```haskell +data ReadOnlyForker m l = ReadOnlyForker + { roforkerGetLedgerState :: STM m (l EmptyMK) + , roforkerReadTables :: LedgerTables l KeysMK -> m (LedgerTables l ValuesMK) + , roforkerRangeReadTables :: RangeQueryPrevious l -> m (LedgerTables l ValuesMK, Maybe (TxIn l)) + } +``` + +### Concrete Ledger State Types (from Shelley) + +```haskell +data NewEpochState era = NewEpochState + { nesEL :: EpochNo, nesEs :: EpochState era, nesRu :: PulsingRewUpdate era + , nesPd :: PoolDistr, nesBprev :: BlocksMade, nesBcur :: BlocksMade } + +data UTxOState era = UTxOState + { _utxosUtxo :: UTxO era, _utxosDeposited :: Coin, _utxosFees :: Coin + , _utxosGovState :: GovState era, _utxosDonation :: Coin } +``` + +### Existing In-Process Access Patterns + +**LedgerMetrics Tracer** - traces metrics every N slots using `mapNodeKernelDataIO` and `nkQueryLedger`. + +**Forging Loop** - gets ledger state for a specific point using `getReadOnlyForkerAtPoint`. + +**Peer Selection** - reads immutable ledger via `getImmutableLedger`. + +--- + +## cardano-rpc Implementation Details + +### Protocol Definitions + +1. **`cardano/rpc/node.proto`** - `GetEra()`, `GetProtocolParamsJson()`, Era enum. +2. **`utxorpc/v1beta/query/query.proto`** - `ReadParams()`, `ReadUtxos()`, `SearchUtxos()` with pagination. +3. **`utxorpc/v1beta/submit/submit.proto`** - `SubmitTx()`, `EvalTx()` with CBOR-serialised transactions. +4. **`utxorpc/v1beta/cardano/cardano.proto`** - Complete Cardano data structures (Tx, TxOutput, PParams with 44 fields, certificates, scripts PlutusV1-V4, governance). + +### Key Architectural Patterns + +- **Dependency Injection**: `RpcEnv` + `Has` typeclass + `MonadRpc` constraint + `RIO` monad. +- **Error Handling**: `RpcException` GADT, `throwEither`/`throwExceptT` helpers, top-level handler. +- **Tracing**: Span-based with random IDs, metrics emission, integration with cardano-node tracers. +- **Query Optimisation**: Address extraction from predicates for indexed lookup (`QueryUTxOByAddress`). +- **Pagination**: Token = `TxId#OutputIndex`, deterministic sort by TxIn, default 100 items. +- **Number Representation**: BigInt follows RFC 8949 CBOR encoding (int64, big_u_int, big_n_int). + +### Integration Tests + +1. `hprop_rpc_query_pparams` - validates all 44 protocol params match ledger. +2. `hprop_rpc_transaction` - full round-trip: fetch UTxOs -> build tx -> submit -> confirm on-chain. + +--- + +## Snapshot Consistency + +The current N2C code runs all queries within one `executeLocalStateQueryExpr` call, which acquires a single ledger snapshot at `VolatileTip`. +Protocol parameters, UTxO results, chain tip, and block number all come from the same ledger state. + +This is critical for correctness: +- `EvalTx` queries 7 different things (pparams, UTxOs, system start, era history, stake delegations, DRep state, stake pool params) that must be mutually consistent for transaction evaluation to be valid. +- Query methods use chain tip alongside query results; a mismatch would return UTxOs from one block with a chain tip from another. + +A naive `NodeKernelAccess` design with individual callbacks per query type would break this guarantee. +Each callback would acquire its own `ReadOnlyForker` against a potentially different chain tip. + +The solution is a snapshot-based design: + +```haskell +data NodeKernelAccess = NodeKernelAccess + { nkaWithSnapshot :: forall a. (LedgerSnapshot -> IO a) -> IO a + , nkaSubmitTx :: TxInMode -> IO (SubmitResult TxValidationErrorInCardanoMode) + } + +newtype LedgerSnapshot = LedgerSnapshot + { runQuery :: forall result. QueryInMode result -> IO result } +``` + +`nkaWithSnapshot` acquires a single `ReadOnlyForker` and wraps it. +All queries within one `nkaWithSnapshot` call share the same forker and therefore the same ledger state. diff --git a/cardano-rpc/docs/node-kernel-access/analysis-utxohd-internals.md b/cardano-rpc/docs/node-kernel-access/analysis-utxohd-internals.md new file mode 100644 index 0000000000..57f07486a3 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/analysis-utxohd-internals.md @@ -0,0 +1,287 @@ +# UTxO-HD Internals + +This document covers the UTxO-HD backing store architecture and its implications for node kernel access. + +--- + +## UTxO-HD Architecture Impact + +UTxO-HD fundamentally changes what "direct access" means for UTxO data. + +The key insight is that **the UTxO set is no longer in the in-memory ledger state**. +When you call: + +```haskell +atomically (ChainDB.getCurrentLedger chainDB) + :: STM IO (ExtLedgerState blk EmptyMK) +``` + +That `EmptyMK` is literal - the `shelleyLedgerTables` field is `EmptyMK`, meaning **no UTxO data**. +The UTxO lives in a backing store (either an in-memory `TVar` map or an on-disk LSM tree), accessed only through `ReadOnlyForker`. + +### The three tiers of data access + +**Tier 1: Pure ledger state (EmptyMK) - free, STM** +- Protocol parameters (`getPParams`), epoch number, stake distribution, chain point, block number. +- These live in `NewEpochState` which is fully in-memory regardless of UTxO-HD. +- Access: `atomically (getCurrentLedger chainDB)` -> pure extraction. +- No forker needed, no IO, no disk. + +**Tier 2: Point lookups (KeysMK -> ValuesMK) - cheap, needs forker** +- `GetUTxOByTxIn`: look up specific TxIns. +- Access: `roforkerReadTables forker (LedgerTables (KeysMK txinSet))`. +- O(m log n) - goes to backing store for the specific keys. +- On V2/LSM: disk reads for specific keys. +- On V1/InMemory: map lookups in TVar. + +**Tier 3: Table traversals (RangeRead) - expensive, needs forker + iteration** +- `GetUTxOByAddress`, `GetUTxOWhole`: full or filtered scan. +- Access: loop calling `roforkerRangeReadTables` with `QueryBatchSize` (default 100k entries per batch). +- O(n) - must scan entire backing store, applying a predicate filter per batch. +- On V2/LSM: sequential range reads through LSM tree levels, deserialising `TxOutBytes` back to `Core.TxOut era`. +- The existing consensus code in `answerShelleyTraversingQueries` does exactly this loop. + +### What this means for the direct access design + +**For Tier 1 (ReadParams pparams):** The win is massive and simple. +Replace the entire N2C round-trip with a single STM read. +No forker, no table access, no disk. +This is the easy win. + +**For Tier 2 (ReadUtxos by TxIn):** The win is real but more nuanced. +You skip the N2C + CBOR overhead, but you still need: +1. Acquire a `ReadOnlyForker` via `ChainDB.getReadOnlyForkerAtPoint` (allocates resources). +2. Call `roforkerReadTables` (may hit disk on LSM backend). +3. Close the forker. + +The backing store IO is the same cost either way - you just eliminate the IPC + serialisation wrapper. +For small lookups this is a big relative improvement. + +**For Tier 3 (SearchUtxos by address / whole UTxO):** The most interesting case. +Currently: +- N2C server does the full range-read loop internally, accumulates the entire result, CBOR-serialises the whole `UTxO era`, sends it over the socket. +- cardano-api deserialises the whole thing. +- cardano-rpc converts to protobuf. + +With direct access: +- cardano-rpc can call `roforkerRangeReadTables` directly in a loop. +- Can apply predicate filtering **per batch** (already happens on the consensus side, now happens in-process). +- Can implement **true streaming pagination** - instead of reading the entire filtered result and then slicing, stop after collecting enough items for one page. +- Converts ledger types -> protobuf directly per batch, no intermediate CBOR or cardano-api types. + +This is where the design gets the most interesting improvement: **pagination can be pushed down to the backing store level**. +Currently `SearchUtxos` fetches the entire filtered UTxO set, sorts it, and slices. +With direct range reads, you can stop early after filling a page. + +**For tx submission (Mempool.addTx):** The tx needs to be a `GenTx blk` (consensus type), not the cardano-api `TxInMode` wrapper. +The current path is: CBOR bytes -> `deserialiseFromCBOR` -> cardano-api `Tx era` -> `toConsensusGenTx` -> N2C LocalTxSubmission -> mempool. +Direct path: CBOR bytes -> `deserialiseFromCBOR` -> `GenTx blk` -> `Mempool.addTx`. +Skips the entire protocol layer. + +### The HFC complication + +`ChainDB` and `NodeKernel` are parameterised by `CardanoBlock StandardCrypto`, which is a `HardForkBlock` across all eras. +The `LedgerTables` use `CanonicalTxIn` and `HardForkTxOut` - canonical representations that abstract across eras. +To get era-specific `Core.TxOut era`, you need `ejectLedgerTables` from the HFC layer. + +The existing `answerShelleyLookupQueries` and `answerShelleyTraversingQueries` already handle this via injection/ejection functions passed as parameters. +cardano-rpc's direct access layer would need to replicate this pattern - or better, reuse the existing `answerQuery` function from consensus with a directly-obtained forker rather than one obtained through the protocol. + +### Depth of integration + +There's a spectrum of how deep cardano-rpc reaches into consensus: + +1. **Shallow**: Reuse `answerQuery` from consensus - get a forker, call `answerQuery cfg forker query`, get back the same types the N2C server would produce. + Eliminates IPC + CBOR serialisation but still uses the consensus query dispatch. + Simplest, lowest risk. + +2. **Medium**: Call `roforkerReadTables` / `roforkerRangeReadTables` directly, handle HFC ejection, convert ledger types -> protobuf. + More code but enables streaming pagination optimisation. + +3. **Deep**: Bypass forkers for Tier 1 queries entirely (just STM reads), use forkers only for Tier 2/3. + Mix-and-match per query type. + +Option 3 is what was outlined in the earlier design. +The risk is coupling to consensus internals. +But Tier 1 queries via STM are so simple and stable that the coupling is minimal, and the existing `nkQueryLedger` helper in `Queries.hs` already does exactly this for metrics. + +--- + +## LedgerTables Type Family System + +### Core Type Definition + +**File:** `ouroboros-consensus/Ouroboros/Consensus/Ledger/Tables/Basics.hs` + +```haskell +type LedgerTables :: LedgerStateKind -> MapKind -> Type +newtype LedgerTables l mk = LedgerTables + { getLedgerTables :: mk (TxIn l) (TxOut l) + } +``` + +Where `MapKind` is `Type -> Type -> Type` (key and value parameters). + +### MapKind Definitions + +**File:** `ouroboros-consensus/Ouroboros/Consensus/Ledger/Tables/MapKind.hs` + +- **EmptyMK**: `data EmptyMK k v = EmptyMK` - no UTxO data present. +- **KeysMK**: `newtype KeysMK k v = KeysMK (Set k)` - only key set. +- **ValuesMK**: `newtype ValuesMK k v = ValuesMK {getValuesMK :: Map k v}` - full key-value map. +- **DiffMK**: `newtype DiffMK k v = DiffMK {getDiffMK :: Diff k v}` - changes (Insert v | Delete). +- **TrackingMK**: `data TrackingMK k v = TrackingMK !(Map k v) !(Diff k v)` - values + accumulated diffs. +- **SeqDiffMK**: finger tree of diffs. + +### What EmptyMK Means Concretely + +When you have `LedgerState era EmptyMK`: +- The `EmptyMK` represents that **the UTxO map data is literally not present** in this ledger state. +- The Consensus layer stores actual UTxO data separately on disk (in the backing store). +- The UTxO can be "stowed" (extracted from the ledger) or "unstowed" (injected back into the ledger). + +### Shelley-Specific Instance + +```haskell +type instance TxIn (LedgerState (ShelleyBlock proto era)) = BigEndianTxIn +type instance TxOut (LedgerState (ShelleyBlock proto era)) = Core.TxOut era +``` + +`BigEndianTxIn` is a wrapper that ensures proper byte ordering for serialisation. + +### Cardano HFC Handling + +```haskell +type instance TxIn (LedgerState (HardForkBlock xs)) = CanonicalTxIn xs +type instance TxOut (LedgerState (HardForkBlock xs)) = HardForkTxOut xs +``` + +Key functions: `injectLedgerTables` and `ejectLedgerTables` transform between era-specific and canonical representations via `bimapLedgerTables`. + +### How cardano-api Gets UTxO from Queries + +The flow: +1. cardano-api calls `queryUtxo` with a filter. +2. Converts to `toConsensusQuery` -> `GetUTxOWhole` / `GetUTxOByAddress` / `GetUTxOByTxIn`. +3. Consensus queries in-memory LedgerState (with `ValuesMK`). +4. Result comes as `Shelley.UTxO (ShelleyLedgerEra era)`. +5. Converts via `fromLedgerUTxO` to cardano-api `UTxO era`. + +--- + +## UTxO-HD Architecture Report + +### LedgerDB and Backing Store + +The LedgerDB is responsible for: +- Maintaining in-memory ledger state at the tip. +- Maintaining past k in-memory ledger states (supports rollback). +- Providing LedgerTables at any of the last k states. +- Storing snapshots on disk. +- Flushing LedgerTable differences to backing store. + +### Backing Store Abstraction + +```haskell +data BackingStore m keys key values diff = BackingStore + { bsClose :: m () + , bsCopy :: SerializeTablesHint values -> FS.FsPath -> m () + , bsValueHandle :: m (BackingStoreValueHandle m keys key values) + , bsWrite :: SlotNo -> WriteHint diff -> diff -> m () + , bsSnapshotBackend :: SnapshotBackend + } +``` + +### Two Backend Implementations + +**V1 Backend - InMemory:** +- Uses a `TVar` holding full map. +- All data stays in memory. +- Used for testing or small deployments. + +**V2 Backend - LSM (Log-Structured Merge):** +- Uses LSM trees from the `lsm-tree` library. +- Keys and values serialised to bytes. +- Uses indexed packing (`IndexedMemPack`) for efficient serialisation. + +### The Forker + +```haskell +data Forker m l = Forker + { forkerReadTables :: LedgerTables l KeysMK -> m (LedgerTables l ValuesMK) + , forkerRangeReadTables :: RangeQueryPrevious l -> m (LedgerTables l ValuesMK, Maybe (TxIn l)) + , forkerGetLedgerState :: STM m (l EmptyMK) -- NO UTxO + , forkerReadStatistics :: m Statistics + , forkerPush :: l DiffMK -> m () + , forkerCommit :: STM m () + } +``` + +**Critical insight:** `forkerGetLedgerState` returns `l EmptyMK` - **the UTxO data is NOT included**. +You must explicitly call `forkerReadTables`. + +### Range Queries and BatchSize + +`QueryBatchSize` defaults to 100,000 entries. +`roforkerRangeReadTables` returns up to that many entries plus one additional key for pagination. +Client iterates until `Nothing` (end of table). + +### ReadOnlyForker vs getCurrentLedger + +| Aspect | getCurrentLedger | getReadOnlyForker | +|--------|------------------|------------------| +| Returns | `l EmptyMK` (STM) | `Forker m l` (IO) | +| UTxO access | NO | YES via `roforkerReadTables` | +| Speed | Fast (STM, in-memory) | Slower (allocates, I/O) | +| Use case | Ledger state queries, consensus | Query answering requiring UTxO | +| Resource management | None | Must close forker | + +### Three Footprint Categories for Shelley Queries + +**QFNoTables:** `GetLedgerTip`, `GetEpochNo`, `GetCurrentPParams`, etc. - uses `answerPureBlockQuery`, just in-memory ledger state. + +**QFLookupTables:** `GetUTxOByTxIn` - uses `answerShelleyLookupQueries`, O(m * log n) for m inputs. + +**QFTraverseTables:** `GetUTxOByAddress`, `GetUTxOWhole` - uses `answerShelleyTraversingQueries`, O(n) full scan with batched range reads. + +### UTxO-HD Pipeline Diagram + +``` +Client Query (e.g., GetUTxOByAddress) + | + v +answerShelleyTraversingQueries + | + v +getReadOnlyForker(cfg, pt) -> Forker + | + v +roforkerRangeReadTables (QueryBatchSize = 100k) + | + v +BackingStore.bsvhRangeRead + |- V1: reads from TVar (InMemory) + |- V2: reads from LSM tree + | + v +Apply DbChangelog diffs (forward apply) [V1 only] + | + v +Filter by predicate (e.g., address match) + | + v +Accumulate into result (loop until end) + | + v +Return UTxO era to client +``` + +### Why MapKind Matters + +The MapKind parameter elegantly separates concerns: +- **`EmptyMK`** = "I have the ledger rules state but no UTxO". +- **`ValuesMK`** = "I have loaded UTxO from disk". +- **`KeysMK`** = "Here are the keys I want to read". +- **`DiffMK`** = "Here are the changes since last checkpoint". + +This allows the type system to enforce that you cannot accidentally use UTxO when you have not loaded it. diff --git a/cardano-rpc/docs/node-kernel-access/implementation-plan.md b/cardano-rpc/docs/node-kernel-access/implementation-plan.md new file mode 100644 index 0000000000..653d46d844 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/implementation-plan.md @@ -0,0 +1,141 @@ +# Node Kernel Access for cardano-rpc: Implementation Plan + +Related ADR: [ADR-019](./ADR-019-node-kernel-access-for-cardano-rpc.md) + +--- + +## Overview + +Changes span two submodules: **cardano-api** (contains cardano-rpc) and **cardano-node**. +Work the cardano-rpc side first, then the cardano-node side. + +Detailed step-by-step implementation is in the numbered story files: + +- [01-node-access-types.md](01-node-access-types.md) - `NodeKernelAccess` types, `Env`, `Monad`, `Tracing`, `Server`, and cabal wiring +- [02-fetchblock-proto-and-handler.md](02-fetchblock-proto-and-handler.md) - SyncService proto, codegen, `fetchBlockMethod`, server registration +- [03-mk-node-access-and-wiring.md](03-mk-node-access-and-wiring.md) - `mkNodeKernelAccess` in cardano-node, `Run.hs` wiring, E2E test, tracing +- [04-rewrite-query-methods.md](04-rewrite-query-methods.md) - Rewrite `Query.hs` to use snapshot pattern +- [05-rewrite-submit-method.md](05-rewrite-submit-method.md) - Rewrite `Submit.hs` to use `NodeKernelAccess` +- [06-rewrite-eval-method.md](06-rewrite-eval-method.md) - Rewrite `Eval.hs` to use snapshot pattern +- [07-rewrite-node-methods.md](07-rewrite-node-methods.md) - Rewrite `Node.hs` to use snapshot pattern +- [08-integration-testing.md](08-integration-testing.md) - Integration testing and validation + +--- + +## Current state analysis + +### Key code paths being replaced + +All query/submit methods currently follow the same N2C pattern: + +```haskell +-- 1. Grab the LocalNodeConnectInfo from the reader env +nodeConnInfo <- grab + +-- 2. Determine the current era via N2C +AnyCardanoEra era <- liftIO . throwExceptT $ determineEra nodeConnInfo + +-- 3. Execute a local state query expression over N2C +(result, ...) <- liftIO . (throwEither =<<) $ + executeLocalStateQueryExpr nodeConnInfo VolatileTip $ do + result <- throwEither =<< throwEither =<< queryXxx sbe ... + chainPoint <- throwEither =<< queryChainPoint + blockNo <- throwEither =<< queryChainBlockNo + pure (result, chainPoint, blockNo) +``` + +Every query method also runs `querySystemStart` and `queryEraHistory` inside the same `executeLocalStateQueryExpr` call for slot-to-timestamp conversion. + +This pattern appears in: +- `Query.hs`: `readParamsMethod`, `readUtxosMethod`, `searchUtxosMethod` +- `Eval.hs`: `evalTxMethod` (queries 7 things: protocol parameters, UTxO by TxIn set, system start, era history, stake deleg deposits, DRep state, stake pool parameters) +- `Submit.hs`: `submitTxMethod` (era detection + `submitTxToNodeLocal`) +- `Node.hs`: `getProtocolParamsJsonMethod` (era detection + query) + +### Current environment wiring + +``` +RpcEnv (Env.hs) + |- config :: RpcConfig + |- tracer :: Tracer m TraceRpc + +- rpcLocalNodeConnectInfo :: LocalNodeConnectInfo <- REPLACE with IORef + +Monad.hs: + instance Has LocalNodeConnectInfo RpcEnv <- REPLACE + type MonadRpc: Has LocalNodeConnectInfo e <- REPLACE + +Server.hs: + runRpcServer :: Tracer IO TraceRpc -> (RpcConfig, NetworkMagic) -> IO () + calls mkLocalNodeConnectInfo to build the env <- REPLACE +``` + +### Current tracing (Tracing.hs + Rpc.hs) + +``` +TraceRpcSubmit + |- TraceRpcSubmitN2cConnectionError SomeException <- REMOVE + |- TraceRpcSubmitTxDecodingError DecoderError (keep) + |- TraceRpcSubmitTxValidationError ... (keep) + +- TraceRpcSubmitSpan TraceSpanEvent (keep) +``` + +The N2C error is wrapped in `Submit.hs` via `tryAny` + `first TraceRpcSubmitN2cConnectionError`. + +--- + +## File summary + +### New files (2) + +| File | Purpose | +|------|---------| +| `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/NodeKernelAccess.hs` | `NodeKernelAccess` record + `withNodeKernelAccess` | +| `cardano-node/cardano-node/src/Cardano/Node/Rpc/NodeKernelAccess.hs` | `mkNodeKernelAccess` from `NodeKernel` | + +### Modified files (12) + +| File | Change | Story | +|------|--------|-------| +| `cardano-api/cardano-rpc/src/Cardano/Rpc/Server.hs` | New signature, re-export `NodeKernelAccess` + `LedgerSnapshot`, IORef wiring, register `methodsSyncRpc` | [01](01-node-access-types.md), [02](02-fetchblock-proto-and-handler.md) | +| `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/Env.hs` | `RpcEnv` holds `IORef`, drop `LocalNodeConnectInfo` | [01](01-node-access-types.md) | +| `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/Monad.hs` | `Has` instance + `MonadRpc` update | [01](01-node-access-types.md) | +| `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/Tracing.hs` | Replace N2C trace with ledger-access traces, add `TraceRpcSync` | [01](01-node-access-types.md) | +| `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Query.hs` | Rewrite 3 query methods to use snapshot pattern | [04](04-rewrite-query-methods.md) | +| `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Eval.hs` | Rewrite `evalTxMethod` to use snapshot pattern (7 queries) | [06](06-rewrite-eval-method.md) | +| `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Submit.hs` | Rewrite submit with snapshot for era detection | [05](05-rewrite-submit-method.md) | +| `cardano-api/cardano-rpc/src/Cardano/Rpc/Server/Internal/Node.hs` | Rewrite `getProtocolParamsJsonMethod` to use snapshot pattern | [07](07-rewrite-node-methods.md) | +| `cardano-api/cardano-rpc/cardano-rpc.cabal` | Add `NodeKernelAccess` module, proto gen modules | [01](01-node-access-types.md), [02](02-fetchblock-proto-and-handler.md) | +| `cardano-node/cardano-node/src/Cardano/Node/Run.hs` | IORef + writeIORef in kernel hook | [03](03-mk-node-access-and-wiring.md) | +| `cardano-node/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs` | Update trace instances | [03](03-mk-node-access-and-wiring.md) | +| `cardano-node/cardano-node/cardano-node.cabal` | Add new module | [03](03-mk-node-access-and-wiring.md) | + +### Unchanged + +| File | Why | +|------|-----| +| `cardano-rpc/src/Cardano/Rpc/Server/Config.hs` | `nodeSocketPath` still needed for socket path derivation | +| `cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Type.hs` | Conversion code untouched | +| `cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Predicate.hs` | Filtering untouched | +| Testnet test files | gRPC client API unchanged | + +--- + +## Verification + +1. `nix build .#cardano-rpc` in cardano-api submodule +2. `nix build .#cardano-rpc:test:cardano-rpc-test` - unit tests +3. `nix build .#cardano-node` in cardano-node submodule +4. Integration tests via testnet (if available) + +--- + +## Risks and mitigations + +| Risk | Status | Mitigation | +|------|--------|------------| +| `answerQuery` API differs from expected signature | **Verified** (2026-05-22) | Signature matches: `ExtLedgerCfg blk -> ReadOnlyForker' m blk -> Query blk result -> m result` | +| Forker lifecycle - leaking forkers on exceptions | **Addressed** | `bracket` pattern in `nkaWithSnapshot`; `withRegistry` provides additional safety net | +| `QueryCurrentEra` not available via `toConsensusQuery` | **Verified: no risk** | `toConsensusQuery` handles `QueryCurrentEra` by wrapping as `BlockQuery (QueryHardFork GetCurrentEra)` | +| `addLocalTxs` API changed in recent consensus | **Verified** (2026-05-22) | Name and signature confirmed; use `MkSolo` constructor on GHC 9.10 | +| Concurrent reads during kernel initialisation race | Low risk | `IORef` write in `rnNodeKernelHook` happens before any RPC can succeed; `withNodeKernelAccess` checks atomically | +| `Target` / `withRegistry` import paths wrong | **Fixed** | `Target` from `Ouroboros.Network.Protocol.LocalStateQuery.Type`; `withRegistry` from `Control.ResourceRegistry` | diff --git a/cardano-rpc/docs/node-kernel-access/plan-fetchblock-phases.md b/cardano-rpc/docs/node-kernel-access/plan-fetchblock-phases.md new file mode 100644 index 0000000000..e42a6d7518 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/plan-fetchblock-phases.md @@ -0,0 +1,89 @@ +# FetchBlock implementation phases + +Covers pieces 1-3 from the delivery plan. +Each phase compiles independently and can be reviewed as a separate commit. + +## Phase 1: NodeKernelAccess types module ✅ + +New file only, no existing code touched. + +- Created `cardano-rpc/src/Cardano/Rpc/Server/NodeKernelAccess.hs` + - `NodeKernelAccessF` record (parametric in monad `m`): `nkaWithSnapshot`, `nkaSubmitTx`, `nkaFetchBlock` + - `LedgerSnapshot` newtype: `runQuery :: forall result. QueryInMode result -> m result` + - `hoistNodeKernelAccess` for mapping both covariant and contravariant monad occurrences +- Added module to `cardano-rpc.cabal` + +## Phase 2: Wire NodeKernelAccess into Env/Monad/Server ✅ + +Plumbing change, no behaviour change. + +- `Env.hs`: added `type NodeKernelAccess = NodeKernelAccessF (RIO RpcEnv)` alias and `rpcNodeKernelAccess :: !(IORef (Maybe NodeKernelAccess))` field to `RpcEnv` +- `Monad.hs`: added `instance (m ~ RIO RpcEnv) => Has (IORef (Maybe (NodeKernelAccessF m))) RpcEnv` using type equality trick +- `Server.hs`: `runRpcServer` accepts `IORef (Maybe NodeKernelAccess)`, constructs `RpcEnv` with it +- `Run.hs` (cardano-node): `nodeKernelAccessRef <- newIORef Nothing` before `withAsync`, passed through `rpcServerLoop` to `runRpcServer` + +## Phase 3: Trace constructors ✅ + +Updated both packages atomically. + +- Added `TraceRpcSync` sum type with `TraceRpcFetchBlockSpan`, `TraceRpcFetchBlockNotFound`, `TraceRpcNodeKernelAccessUnavailable`, `TraceRpcForkerError` +- Added `TraceRpcSync` constructor to `TraceRpc` +- Kept `TraceRpcSubmitN2cConnectionError` (N2C tracers remain for incremental migration) +- Updated cardano-node `Tracers/Rpc.hs` with all new trace constructors + +## Phase 4: sync.proto and codegen ✅ + +- Copied `sync.proto` from UTxO RPC spec (`@utxorpc-spec`) +- Ran codegen via `buf generate proto` +- Created `src/Cardano/Rpc/Proto/Api/UtxoRpc/Sync.hs` wrapper (re-exports Sync and Cardano proto types/fields) +- Added generated modules and wrapper to `cardano-rpc.cabal` + +## Phase 5: fetchBlockMethod handler and server registration ✅ + +- Created `src/Cardano/Rpc/Server/Internal/UtxoRpc/Sync.hs` with `fetchBlockMethod` + - Validates hash is non-empty (`INVALID_ARGUMENT`) + - Calls `withNodeKernelAccess` then `nkaFetchBlock` + - `Just (rawBytes, blockNo)` -> populates `AnyChainBlock` with `nativeBytes` and `cardano.header` (slot, hash, height) + - `Nothing` -> throws `NOT_FOUND` with slot in message +- `Server.hs`: added `methodsSyncRpc` with stubs for unimplemented methods (dumpHistory, followTip, readTip), registered alongside existing method tables + +## Phase 6: mkNodeKernelAccess in cardano-rpc ✅ + +Lives in cardano-rpc (not cardano-node) - only depends on consensus types re-exported via `Cardano.Api.Consensus`. + +- Module structure per ADR-009: + - `Cardano.Rpc.Server.NodeKernelAccess` - datatypes only (`NodeKernelAccessF`, `LedgerSnapshot`) + - `Cardano.Rpc.Server.NodeKernelAccess.Internal` - functions (`mkNodeKernelAccess`, `withNodeKernelAccess`, `fetchBlock`) +- `mkNodeKernelAccess` takes `BlockType blk` + `NodeKernel IO addrNTN addrNTC blk`, polymorphic in block type +- `fetchBlock` uses GADT pattern match on `BlockType` via `withBlockTypeConstraints` to bring `HeaderHash blk ~ OneEraHash xs` into scope +- Returns `Maybe (ByteString, BlockNo)` - raw CBOR from `GetRawBlock` and block number from `GetBlock` +- `nkaWithSnapshot` and `nkaSubmitTx` are stubbed (NKA query/submit migration pending) +- `Run.hs`: `case blockType of CardanoBlockType -> writeIORef ref (Just (mkNodeKernelAccess blockType nodeKernel))` +- Re-exported consensus types in `Cardano.Api.Consensus`: `NodeKernel`, `ChainDB`, `BlockComponent`, `RealPoint`, `OneEraHash`, `HeaderHash`, `HasHeader`, `blockNo`, `StandardCrypto`, `CardanoBlock` + +## Phase 7: E2E test ✅ + +- Created `cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs` + - `hprop_rpc_fetch_block`: starts testnet with `RpcEnabled`, queries tip via CLI, calls FetchBlock via gRPC + - Asserts: one block returned, non-empty `nativeBytes`, `cardano.header.slot` matches tip, `cardano.header.hash` matches tip, `cardano.header.height` matches tip block number +- Registered in `cardano-testnet-test.hs` test runner + +## Remaining work + +### Response fields not yet populated + +| Field | Status | Blocker | +|-------|--------|---------| +| `AnyChainBlock.nativeBytes` | ✅ Set | - | +| `Block.header.slot` | ✅ Set | - | +| `Block.header.hash` | ✅ Set | - | +| `Block.header.height` | ✅ Set | - | +| `Block.timestamp` | ⬜ Not set | Needs `EraHistory` from ledger state (snapshot/query migration) | +| `Block.body.tx` | ⬜ Not set | Requires full block deserialisation + UTxO RPC tx mapping | + +### Stubbed callbacks + +| Callback | Blocker | +|----------|---------| +| `nkaWithSnapshot` | Separate story: query/snapshot migration | +| `nkaSubmitTx` | Separate story: submit migration | diff --git a/cardano-rpc/docs/node-kernel-access/plan-fetchblock-timestamp-phases.md b/cardano-rpc/docs/node-kernel-access/plan-fetchblock-timestamp-phases.md new file mode 100644 index 0000000000..144437eaae --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/plan-fetchblock-timestamp-phases.md @@ -0,0 +1,93 @@ +# FetchBlock timestamp implementation phases + +Adds `Block.timestamp` to the FetchBlock response. +Each phase compiles independently and can be reviewed as a separate commit. + +## Background + +Cardano blocks are slot-indexed, not timestamped. +Converting slot to wall-clock time requires `SystemStart` + `EraHistory`. +`SystemStart` is a genesis parameter, constant for the lifetime of the chain. +`EraHistory` depends on ledger state (hard fork transitions). + +The current ledger state is available via `ChainDB.getCurrentLedger :: STM m (ExtLedgerState blk EmptyMK)` - a cheap STM read, no forker needed. +`hardForkSummary cfg ledger` computes the era summary from the in-memory ledger state. + +## Phase 1: Add `nkaSystemStart` and `nkaEraHistory` to `NodeKernelAccessF` ✅ + +Provides the ingredients for time calculations without prescribing the recipe. + +- Add two fields to `NodeKernelAccessF`: + - `nkaSystemStart :: SystemStart` - pure field, extracted from `TopLevelConfig` at construction time + - `nkaEraHistory :: m EraHistory` - callback, reads current ledger state on each call via `getCurrentLedger` + `hardForkSummary` + `mkInterpreter` +- `SystemStart` passed to `mkNodeKernelAccess` from `Run.hs` (where consensus imports are available) +- `mkNodeKernelAccess` stays pure - the `m` effect happens when `nkaEraHistory` is called +- Re-exports added to `Cardano.Api.Consensus`: `getCurrentLedger`, `hardForkSummary`, `mkInterpreter`, `TopLevelConfig`, `configBlock`, `configLedger`, `ledgerState`, `HasHardForkHistory(..)`, `ConfigSupportsNode` +- Unsupported block types throw `GrpcInternal` with the block type name in the message +- Stubbed callbacks (`nkaWithSnapshot`, `nkaSubmitTx`) throw `GrpcUnimplemented` + +Build: `cabal build cardano-rpc && cabal build cardano-node` + +## Phase 2: Compute timestamp in `fetchBlockMethod` + +Callers compose `nkaSystemStart` + `nkaEraHistory` with `slotToUTCTime` (cardano-api). + +- In `fetchBlockMethod` (Sync.hs), after fetching the block: + 1. Read `nkaSystemStart nodeKernelAccess` (pure) + 2. Call `nkaEraHistory nodeKernelAccess` (reads ledger state) + 3. Compute: `slotToUTCTime systemStart eraHistory slot` + 4. On `Right utcTime`: convert to milliseconds: `round . (* 1000) . utcTimeToPOSIXSeconds` + 5. On `Left PastHorizonException`: throw `GrpcInternal` - a fetched block's slot is in-horizon by construction, so this is a bug + 6. Set `U5c.cardano . U5c.timestamp .~ timestampMs` on the response +- Remove the `-- TODO: timestamp` comment + +Build: `cabal build cardano-rpc` + +## Phase 3: Update E2E test + +- Update `FetchBlock.hs` test: + - Remove `block ^. U5c.cardano . U5c.timestamp H.=== 0` + - Compute expected timestamp from `SystemStart` + `EraHistory` via N2C IPC (same as Query.hs test does) + - Assert actual timestamp is within 1000ms tolerance of expected: `H.assertWithinTolerance` +- Verify all RPC tests still pass (no regressions) + +Build: `TASTY_PATTERN='/RPC/' cabal test cardano-testnet-test` + +## Phase dependency graph + +``` +Phase 1 (nkaSystemStart + nkaEraHistory) ✅ + | + v +Phase 2 (timestamp in handler) + | + v +Phase 3 (E2E test) +``` + +## Design decisions + +- **Provide ingredients, not the recipe.** + `nkaSystemStart` and `nkaEraHistory` are general-purpose. + Callers compose with `slotToUTCTime`, `getProgress`, `slotToEpoch`, or any other era-history function. + Not limited to timestamp computation. + +- **No forker needed.** + `getCurrentLedger` (STM) gives the current ledger state directly. + The forker API is reserved for the full query migration where consistent multi-query snapshots matter. + +- **Live recompute.** + `hardForkSummary` is cheap (in-memory traversal, O(number_of_eras)). + Caching (`RunWithCachedSummary`) is available as a future optimisation if profiling warrants it. + +- **Same `slotToUTCTime` as Query.hs.** + One conversion path across handler and test. + +- **`PastHorizonException` on a fetched block is a bug.** + A block in ChainDB was produced in a known era. + The current tip's summary covers all slots up to the tip. + +## Scope + +This plan adds timestamp to FetchBlock and provides `SystemStart` + `EraHistory` on `NodeKernelAccessF`. +These fields also enable future use by ReadGenesis, ReadEraSummary, and any method needing time or era information. diff --git a/cardano-rpc/docs/node-kernel-access/plan-readgenesis-phases.md b/cardano-rpc/docs/node-kernel-access/plan-readgenesis-phases.md new file mode 100644 index 0000000000..e9cc1a9108 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/plan-readgenesis-phases.md @@ -0,0 +1,141 @@ +# ReadGenesis implementation phases + +Implements the ReadGenesis QueryService method. +Each phase compiles independently and can be reviewed as a separate commit. + +Genesis data is static - it never changes after node startup. +All data is extracted once from `TopLevelConfig` in `mkNodeKernelAccess` and stored as a pure field on `NodeKernelAccessF`. + +## Phase 1: Proto service method + stub handler + registration + tracing + +Atomic: proto change requires matching handler registration. + +- Add `rpc ReadGenesis(ReadGenesisRequest) returns (ReadGenesisResponse)` to the `QueryService` block in `proto/utxorpc/v1beta/query/query.proto` +- Regenerate: `nix develop --command bash -c "cd cardano-rpc && buf generate proto"` +- Add `TraceRpcQueryReadGenesisSpan TraceSpanEvent` constructor to `TraceRpcQuery` in `Tracing.hs` +- Add stub `readGenesisMethod` in `Query.hs` returning `GrpcUnimplemented` +- Register in `methodsUtxoRpc` in `Server.hs` - position must match `ServiceMethods` order (verify against generated code) +- Update cardano-node `Tracers/Rpc.hs` with new trace constructor + +Build: `cabal build cardano-rpc && cabal build cardano-node` + +## Phase 2: NKA genesis bundle + envelope fields + +Adds the static genesis data to `NodeKernelAccessF` and populates the response envelope. + +- Add `GenesisBundle` record to `NodeKernelAccess.hs`: + - `gbGenesisHashBytes :: !ByteString` (Byron genesis hash - the CIP-34 chain identifier) + - `gbNetworkMagic :: !Word32` + - `gbShelleyGenesis :: !ShelleyGenesis` + - `gbByronGenesisData :: !GenesisData` (Byron) + - `gbAlonzoGenesis :: !AlonzoGenesis` + - `gbConwayGenesis :: !ConwayGenesis` +- Add pure field `nkaGenesisConfig :: GenesisBundle` to `NodeKernelAccessF` +- In `mkNodeKernelAccess`, extract from `getTopLevelConfig nodeKernel`: + - Byron hash: `configBlock` -> `byronGenesisHash` -> `abstractHashToBytes` + - Byron data: `configBlock` -> `byronGenesisConfig` -> `configGenesisData` + - Shelley: `configLedger` -> Shelley partial config -> `shelleyLedgerGenesis` + - Alonzo: `configLedger` -> Alonzo partial config -> `shelleyLedgerTranslationContext` + - Conway: `configLedger` -> Conway partial config -> `shelleyLedgerTranslationContext` + - Network magic: `shelleyLedgerGenesis` -> `sgNetworkMagic` +- Replace stub handler: populate `genesis` (hash bytes) and `caip2` (CIP-34 ID) +- `cardano` oneof remains empty (valid proto default) +- Add consensus re-exports to `Cardano.Api.Consensus` as needed (`TopLevelConfig`, `configBlock`, `configLedger`, etc.) + +Build: `cabal build cardano-rpc && cabal build cardano-node` + +### Blockers (must resolve before implementing Phase 2) + +- **Which genesis hash?** Proto says "genesis hash for the chain" (singular). + Byron genesis hash is the CIP-34 chain identifier and is available from `TopLevelConfig`. + Shelley/Alonzo/Conway hashes are NOT recoverable from `TopLevelConfig` (CompactGenesis is lossy) - they live in `NodeConfiguration` which `mkNodeKernelAccess` does not receive. + If the spec requires the Shelley hash, `mkNodeKernelAccess` needs an additional parameter, changing its signature. + **Action:** run `/utxorpc-compare` to check what Dolos/other implementations return. +- **CAIP-2 format**: exact derivation per CIP-34 (genesis hash prefix + network magic?). + **Action:** read CIP-34 and cross-reference with Dolos output. + +## Phase 3: Shelley genesis fields mapping (proto fields 10-23) + +Independent of phases 4-6. + +- Add `shelleyGenesisToProto :: ShelleyGenesis -> U5c.Genesis` (or lens-based builder) in a new mapping module or `Type.hs` +- Map: `sgActiveSlotsCoeff`, `sgEpochLength`, `sgGenDelegs`, `sgMaxKESEvolutions`, `sgMaxLovelaceSupply`, `sgNetworkId`, `sgNetworkMagic`, `sgProtocolParams`, `sgSecurityParam`, `sgSlotLength`, `sgSlotsPerKESPeriod`, `sgSystemStart`, `sgUpdateQuorum` +- Note: `sgInitialFunds` will be empty (`CompactGenesis` erases it - document with comment) +- Wire into `readGenesisMethod` + +Build: `cabal build cardano-rpc` + +## Phase 4: Byron genesis fields mapping (proto fields 1-9) + +Independent of phases 3, 5, 6. + +- Add `byronGenesisToProto :: GenesisData -> U5c.Genesis` mapping +- Map: `gdAvvmDistr`, `gdProtocolParameters` (-> `protocolConsts` + `blockVersionData`), `gdStartTime`, `gdBootStakeholders`, `gdHeavyDelegation`, `gdNonAvvmBalances`, `gdVssCerts`, `gdFtsSeed` +- Wire into `readGenesisMethod` + +Build: `cabal build cardano-rpc` + +## Phase 5: Alonzo genesis fields mapping (proto fields 24-31) + +Independent of phases 3, 4, 6. + +- Add `alonzoGenesisToProto :: AlonzoGenesis -> U5c.Genesis` mapping +- Map: `agCoinsPerUTxOWord`, `agPrices`, `agMaxTxExUnits`, `agMaxBlockExUnits`, `agMaxValSize`, `agCollateralPercentage`, `agMaxCollateralInputs`, cost models (PlutusV1 from `agPlutusV1CostModel`, V2/V3 from extra config if available) + +Build: `cabal build cardano-rpc` + +## Phase 6 (optional): Conway genesis fields mapping (proto fields 32-42) + +Independent of phases 3-5. May be deferred depending on priority. + +- Add `conwayGenesisToProto :: ConwayGenesis -> U5c.Genesis` mapping +- Map: `cgUpgradePParams` (pool/DRep voting thresholds, committee min size, committee max term length, gov action lifetime/deposit, DRep deposit/activity, min fee ref script cost per byte), `cgConstitution`, `cgCommittee` + +Build: `cabal build cardano-rpc` + +## Phase 7: E2E test + +- Create `cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Genesis.hs` + - `hprop_rpc_read_genesis`: start testnet with `RpcEnabled`, call ReadGenesis via gRPC + - Assert: `genesis` is 32 bytes, `caip2` is non-empty, `cardano` is set + - Assert Shelley fields: `epochLength > 0`, `networkMagic` matches testnet magic, `systemStart` non-empty + - Assert Byron fields: `protocolConsts` present, `startTime > 0` + - Assert Alonzo fields: `executionPrices` present, `maxTxExUnits` non-zero +- Register in `cardano-testnet-test.hs` test runner + +Build: `TASTY_PATTERN='/RPC ReadGenesis/' cabal test cardano-testnet-test` + +## Phase dependency graph + +``` +Phase 1 (proto + stub) + | + v +Phase 2 (NKA bundle + envelope) + | + +---> Phase 3 (Shelley) --+ + | | + +---> Phase 4 (Byron) --+--> Phase 7 (E2E test) + | | + +---> Phase 5 (Alonzo) --+ + | | + +---> Phase 6 (Conway) --+ (optional) +``` + +Phases 3-6 are independent of each other. +Phase 7 depends on at least phases 3-5 for meaningful assertions. + +## Design notes + +- **No cardano-api boundary violation.** + All genesis types (`ShelleyGenesis`, `AlonzoGenesis`, `ConwayGenesis`, `ByronGenesisConfig`) are already re-exported from `Cardano.Api.Genesis`. + `GenesisBundle` can live in `NodeKernelAccess.hs` without importing consensus directly. +- **Navigating `HardForkLedgerConfig`** to extract per-era configs requires `blk ~ CardanoBlock StandardCrypto`. + The existing `withBlockTypeConstraints`/`CardanoBlockType` GADT pattern (from FetchBlock) already solves this. + +## Known limitations + +- `sgInitialFunds` and `sgStaking` are erased by `CompactGenesis` at runtime. + The corresponding proto fields will be empty. +- Genesis hashes for Shelley/Alonzo/Conway cannot be recomputed from `CompactGenesis` (lossy compaction). + Only the Byron genesis hash is available from `TopLevelConfig`. diff --git a/cardano-rpc/docs/node-kernel-access/prereqs-api-signatures.md b/cardano-rpc/docs/node-kernel-access/prereqs-api-signatures.md new file mode 100644 index 0000000000..506d6125e0 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/prereqs-api-signatures.md @@ -0,0 +1,163 @@ +# API Signatures and Type References + +Verified consensus and cardano-api type signatures needed for the node kernel access implementation. + +--- + +## 1. Consensus API Signatures (Verified) + +All signatures below were verified against the checked-out source as of 2026-05-22. + +### 1.1 `answerQuery` + +**Module:** `Ouroboros.Consensus.Ledger.Query` +**File:** `ouroboros-consensus/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Ledger/Query.hs` + +```haskell +answerQuery + :: forall blk m result. + (BlockSupportsLedgerQuery blk, ConfigSupportsNode blk, HasAnnTip blk, MonadSTM m) + => ExtLedgerCfg blk + -> ReadOnlyForker' m blk + -> Query blk result + -> m result +``` + +- Takes `ExtLedgerCfg blk` (construct via `ExtLedgerCfg (getTopLevelConfig nk)`) +- Takes `ReadOnlyForker' m blk` (the primed alias: `ReadOnlyForker m (ExtLedgerState blk) blk`) +- `Query blk result` is a GADT with constructors: `BlockQuery`, `GetSystemStart`, `GetChainBlockNo`, `GetChainPoint`, `DebugLedgerConfig` +- Handles all three footprints internally via `sing :: Sing footprint` dispatch: `SQFNoTables` -> `answerPureBlockQuery`, `SQFLookupTables` -> `answerBlockQueryLookup`, `SQFTraverseTables` -> `answerBlockQueryTraverse` + +**Footprint dispatch in detail:** +- `SQFNoTables` - pure ledger state queries (pparams, epoch, etc.) - calls `roforkerGetLedgerState` via STM. + No table access, no IO beyond the STM read. +- `SQFLookupTables` - point lookups (UTxO by TxIn) - calls `roforkerReadTables`. + O(m log n) for m keys in a store of n entries. +- `SQFTraverseTables` - table traversals (UTxO by address, whole UTxO) - calls `roforkerRangeReadTables`. + O(n) full scan with batched range reads (default batch size 100k entries). + +`answerQuery` returns plain Haskell values - no serialisation. + +The `GetSystemStart` and `GetChainBlockNo`/`GetChainPoint` queries are handled directly at +the top level (not via `BlockQuery`). +`GetSystemStart` extracts the value from `ExtLedgerCfg` (the config, not the state). +`GetChainBlockNo` and `GetChainPoint` read from `headerState` via STM. + +### 1.2 `getReadOnlyForkerAtPoint` + +**Module:** `Ouroboros.Consensus.Storage.ChainDB.API` (API), `Ouroboros.Consensus.Storage.ChainDB.Impl.Query` (implementation) + +```haskell +getReadOnlyForkerAtPoint + :: IOLike m + => ChainDbEnv m blk + -> ResourceRegistry m + -> Target (Point blk) + -> m (Either GetForkerError (ReadOnlyForker' m blk)) +``` + +- **`Target`** is from `Ouroboros.Network.Protocol.LocalStateQuery.Type` (NOT `Ouroboros.Consensus.Block`) + ```haskell + data Target point = VolatileTip | SpecificPoint point | ImmutableTip + ``` +- **`GetForkerError`** is from `Ouroboros.Consensus.Storage.LedgerDB.Forker`: + ```haskell + data GetForkerError = PointNotOnChain | PointTooOld !(Maybe ExceededRollback) + ``` +- Returns `ReadOnlyForker' m blk` (the primed alias) +- **`roforkerClose`** is a record field of `ReadOnlyForker`, type `!(m ())` + +### 1.3 `toConsensusQuery` / `fromConsensusQueryResult` + +**Module:** `Cardano.Api.Query.Internal.Type.QueryInMode` (re-exported via `Cardano.Api.Query`) + +```haskell +toConsensusQuery + :: (HasCallStack, Consensus.CardanoBlock StandardCrypto ~ block) + => QueryInMode result + -> Some (Consensus.Query block) + +fromConsensusQueryResult + :: (HasCallStack, Consensus.CardanoBlock StandardCrypto ~ block) + => QueryInMode result + -> Consensus.Query block result' + -> result' + -> result +``` + +- Both exported. +- `toConsensusQuery` returns `Some (Consensus.Query block)` (existential wrapper). +- `fromConsensusQueryResult` needs the original `QueryInMode` for type-level dispatch. +- **`QueryCurrentEra` routes through `toConsensusQuery` correctly.** + It wraps internally as `BlockQuery (QueryHardFork GetCurrentEra)`. + No special handling needed. + +### 1.4 `toConsensusGenTx` / `fromConsensusApplyTxErr` + +**Module:** `Cardano.Api.Consensus.Internal.InMode` (re-exported via `Cardano.Api.Consensus`) + +```haskell +toConsensusGenTx + :: Consensus.CardanoBlock StandardCrypto ~ block + => TxInMode -> Consensus.GenTx block + +fromConsensusApplyTxErr + :: Consensus.CardanoBlock StandardCrypto ~ block + => Consensus.ApplyTxErr block -> TxValidationErrorInCardanoMode +``` + +- Both exported from `Cardano.Api.Consensus`. +- `SubmitResult` is from `Ouroboros.Network.Protocol.LocalTxSubmission.Type`, re-exported via `Cardano.Api.Network`. + +### 1.5 `addLocalTxs` (Mempool API) + +**Module:** `Ouroboros.Consensus.Mempool.API` + +```haskell +addLocalTxs + :: forall m blk t. (MonadSTM m, Traversable t) + => Mempool m blk -> t (GenTx blk) -> m (t (MempoolAddTxResult blk)) +``` + +- Name is `addLocalTxs` (wrapper around lower-level `addTx` field with `AddTxForLocalClient`). +- On GHC 9.10, use **`MkSolo`** (not `Solo`) as the constructor: + ```haskell + import Data.Tuple (Solo (..)) + MkSolo addTxRes <- addLocalTxs mempool (MkSolo genTx) + ``` +- Result type: + ```haskell + data MempoolAddTxResult blk + = MempoolTxAdded !(Validated (GenTx blk)) + | MempoolTxRejected !(GenTx blk) !(ApplyTxErr blk) + ``` +- Error type in rejection is `ApplyTxErr blk` (a type family; for Shelley blocks: `SL.ApplyTxError era`). + +### 1.6 `withRegistry` + +**Module:** `Control.ResourceRegistry` (NOT `Ouroboros.Consensus.Util.ResourceRegistry`) + +```haskell +withRegistry :: (ResourceRegistry m -> m a) -> m a +``` + +- Registry cleanup automatically closes registered forkers when the registry scope exits. +- This means `bracket` around `roforkerClose` is still good practice for explicit cleanup, but `withRegistry` provides a safety net if an exception bypasses the explicit close. + +### 1.7 `NodeKernel` type parameters + +**Module:** `Ouroboros.Consensus.NodeKernel` (in ouroboros-consensus-diffusion) + +```haskell +data NodeKernel m addrNTN addrNTC blk = NodeKernel + { getChainDB :: ChainDB m blk + , getMempool :: Mempool m blk + , getTopLevelConfig :: TopLevelConfig blk + , getFetchClientRegistry :: FetchClientRegistry (ConnectionId addrNTN) (HeaderWithTime blk) blk m + , ... + } +``` + +- In cardano-node (`Run.hs` line 404): `NodeKernel IO RemoteAddress LocalConnectionId blk` +- `getChainDB`, `getMempool`, `getTopLevelConfig` are all **record fields** (not standalone functions). +- `rnNodeKernelHook` receives the kernel in the callback: `\registry nodeKernel -> do ...` diff --git a/cardano-rpc/docs/node-kernel-access/prereqs-build-and-conventions.md b/cardano-rpc/docs/node-kernel-access/prereqs-build-and-conventions.md new file mode 100644 index 0000000000..7907416249 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/prereqs-build-and-conventions.md @@ -0,0 +1,263 @@ +# Build Instructions and Conventions + +Project layout, build commands, and codebase conventions for the node kernel access work. + +--- + +## 0. Project Layout + +The working directory is `/work/`. +It contains git submodules, each a separate Haskell project with its own flake/cabal setup. +Only the submodules relevant to this task are listed here. + +### Submodules relevant to this task + +``` +/work/ +├── cardano-api/ ← cardano-api repo (contains cardano-rpc) +│ ├── cardano-api/ ← the cardano-api library package +│ │ └── src/Cardano/Api/ ← cardano-api source (Query, Network.IPC, Era, Consensus, etc.) +│ └── cardano-rpc/ ← the cardano-rpc package (THIS IS THE MAIN TARGET) +│ ├── src/Cardano/Rpc/ +│ │ ├── Client.hs ← gRPC client (not modified) +│ │ ├── Proto/Api/ ← proto-lens API wrappers (not modified) +│ │ └── Server/ +│ │ ├── Config.hs ← RpcConfig (kept unchanged) +│ │ ├── Server.hs ← runRpcServer entry point (MODIFIED) +│ │ └── Internal/ +│ │ ├── Env.hs ← RpcEnv record (MODIFIED) +│ │ ├── Monad.hs ← Has typeclass, MonadRpc (MODIFIED) +│ │ ├── Error.hs ← throwEither, throwExceptT (kept) +│ │ ├── Node.hs ← getEra, getProtocolParamsJson (MODIFIED) +│ │ ├── Tracing.hs ← TraceRpc types (MODIFIED) +│ │ ├── NodeKernelAccess.hs ← NEW FILE +│ │ ├── Orphans.hs ← (not modified) +│ │ └── UtxoRpc/ +│ │ ├── Query.hs ← readParams, readUtxos, searchUtxos (MODIFIED) +│ │ ├── Submit.hs ← submitTx (MODIFIED) +│ │ ├── Type.hs ← protobuf conversion (not modified) +│ │ └── Predicate.hs ← UTxO filtering (not modified) +│ ├── gen/ ← generated proto-lens code (NEVER edit by hand) +│ ├── proto/ ← .proto definitions +│ ├── test/ ← unit tests +│ └── cardano-rpc.cabal ← (MODIFIED - add NodeKernelAccess module) +│ +├── cardano-node/ ← cardano-node repo +│ ├── cardano-node/ ← the cardano-node package +│ │ ├── src/Cardano/Node/ +│ │ │ ├── Run.hs ← node startup, withAsync runRpcServer (MODIFIED) +│ │ │ ├── Queries.hs ← existing nkQueryLedger pattern (reference only) +│ │ │ ├── Rpc/ +│ │ │ │ └── NodeKernelAccess.hs ← NEW FILE - mkNodeKernelAccess from NodeKernel +│ │ │ └── Tracing/Tracers/ +│ │ │ └── Rpc.hs ← LogFormatting/MetaTrace instances (MODIFIED) +│ │ └── cardano-node.cabal ← (MODIFIED - add new module) +│ ├── cardano-testnet/ ← integration test infrastructure +│ │ └── test/Cardano/Testnet/Test/Rpc/ ← gRPC integration tests (not modified) +│ └── @worktree/add-grpc-interface/ ← worktree for the gRPC feature branch +│ +├── ouroboros-consensus/ ← consensus repo (READ ONLY - verify APIs here) +│ ├── ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/ +│ │ ├── Ledger/Query.hs ← answerQuery function +│ │ ├── Ledger/Tables/ ← MapKind, LedgerTables definitions +│ │ ├── Storage/ChainDB/API.hs ← getReadOnlyForkerAtPoint, getCurrentLedger +│ │ ├── Storage/LedgerDB/ ← Forker, BackingStore +│ │ ├── Mempool/API.hs ← addLocalTxs +│ │ └── MiniProtocol/LocalStateQuery/Server.hs ← existing N2C query server (reference) +│ ├── ouroboros-consensus-diffusion/src/.../ +│ │ ├── NodeKernel.hs ← NodeKernel type definition +│ │ └── Network/NodeToClient.hs ← existing N2C wiring (reference) +│ └── ouroboros-consensus-cardano/src/shelley/.../ +│ └── Shelley/Ledger/Query.hs ← Shelley-specific query answering +│ +├── cardano-ledger/ ← ledger repo (READ ONLY) +│ └── eras/shelley/impl/src/Cardano/Ledger/Shelley/LedgerState.hs +│ +├── cardano-node-wiki/ ← documentation (THIS DOCUMENTATION SET) +│ └── docs/ +│ ├── ADR-018-cardano-rpc-grpc-server.md +│ ├── ADR-019-node-kernel-access-for-cardano-rpc.md +│ ├── implementation-plan.md +│ ├── analysis-architecture.md +│ ├── analysis-utxohd-internals.md +│ ├── analysis-consensus-protocol.md +│ ├── prereqs-api-signatures.md +│ ├── prereqs-build-and-conventions.md ← THIS FILE +│ └── prereqs-implementation-details.md +│ +└── ouroboros-network/ ← network repo (READ ONLY - RemoteAddress, etc.) +``` + +### Key relationships + +- **cardano-rpc** depends on **cardano-api** (types, re-exports) but NOT on consensus +- **cardano-node** depends on both **cardano-rpc** and **ouroboros-consensus** +- The `NodeKernelAccess` record lives in **cardano-rpc** (cardano-api types only) +- The `mkNodeKernelAccess` implementation lives in **cardano-node** (knows consensus types) +- This is dependency inversion: cardano-rpc defines the interface, cardano-node implements it + +### NodeKernelAccess type (snapshot-based design) + +```haskell +data NodeKernelAccess = NodeKernelAccess + { nkaWithSnapshot :: forall a. (LedgerSnapshot -> IO a) -> IO a + , nkaSubmitTx :: TxInMode -> IO (SubmitResult TxValidationErrorInCardanoMode) + } + +newtype LedgerSnapshot = LedgerSnapshot + { runQuery :: forall result. QueryInMode result -> IO result } +``` + +`nkaWithSnapshot` acquires a single `ReadOnlyForker` and provides a `LedgerSnapshot` that +runs any `QueryInMode` against that forker. +All queries within one `nkaWithSnapshot` call see the same ledger state. +`nkaSubmitTx` goes directly to the mempool and does not need a snapshot. + +### Path convention in the implementation plan + +The implementation plan uses paths relative to `/work/`: +- `cardano-api/cardano-rpc/src/...` means `/work/cardano-api/cardano-rpc/src/...` +- `cardano-node/cardano-node/src/...` means `/work/cardano-node/cardano-node/src/...` + +--- + +## 2. Nix Build Instructions + +### The git submodule problem + +`/work` contains git submodules. +Each submodule's `.git` file points to a parent modules directory that doesn't exist in this environment. +This breaks `nix build .#...` because nix's git fetcher fails. + +### Workaround: use `path:` instead of `.` + +```bash +# In cardano-api submodule: +cd /work/cardano-api +nix build 'path:/work/cardano-api#cardano-rpc:lib:cardano-rpc' \ + --allow-import-from-derivation \ + --accept-flake-config + +# In cardano-node submodule: +cd /work/cardano-node +nix build 'path:/work/cardano-node#cardano-node:lib:cardano-node' \ + --allow-import-from-derivation \ + --accept-flake-config +``` + +### Required flags (always) + +- `--allow-import-from-derivation` - haskell.nix needs IFD to enumerate packages +- `--accept-flake-config` - to trust the flake's nixConfig settings + +### Package names (haskell.nix component style) + +| Package | Nix attribute | +|---------|---------------| +| cardano-rpc library | `cardano-rpc:lib:cardano-rpc` | +| cardano-rpc gen library | `cardano-rpc:lib:gen` | +| cardano-rpc tests | `cardano-rpc:test:cardano-rpc-test` | +| cardano-api library | `cardano-api:lib:cardano-api` | +| cardano-node library | `cardano-node:lib:cardano-node` | + +### Listing all packages + +```bash +nix eval 'path:/work/cardano-api#packages.x86_64-linux' --apply 'builtins.attrNames' \ + --allow-import-from-derivation --accept-flake-config +``` + +### Running code generation (proto-lens) + +Never manually edit files in `gen/`. +Use: +```bash +nix develop --command bash -c "cd cardano-rpc && buf generate proto" +``` + +--- + +## 3. Codebase Conventions and Gotchas + +### RIO import hiding + +RIO re-exports most of Prelude but **hides some common functions**: +- `sortBy` - import from `Data.List` +- `on` - import from `Data.Function` or `Data.Ord` +- `toList` - import from `GHC.IsList` (not `Data.Foldable`) + +Always check what RIO re-exports before assuming a function is in scope. + +### Proto wrapper: `Proto msg` + +`Proto` is a grapesy newtype wrapper. +Rules: +- Handler signatures use `Proto` in parameters and return types +- Internal functions use **plain proto-lens types** (unwrapped) +- Use `getProto` / `fmap getProto` only at the RPC handler boundary +- Never wrap intermediate values in `Proto` + +### `Inject` typeclass + +After removing `import Cardano.Api` from `Monad.hs`, the `Inject` typeclass (used by +`putTrace`) must be imported explicitly: +```haskell +import Cardano.Api.Era (Inject (..)) +``` + +### hlint preferences + +- Prefer backtick-infix sections over lambdas: `` (`f` y) `` not `\x -> f x y` +- Don't mix styles in the same function +- hlint will catch these + +### GHC version + +The project uses GHC 9.10 (from the `inotifywait.sh` commit: "bump ghc to 9.10"). + +### `-Wunused-packages` is enabled + +The cabal file has `-Wunused-packages`. +If you remove an import that was the only use of a dependency, the build will fail. +Conversely, don't add deps without checking if they're already transitively available. + +### `-Wredundant-constraints` is enabled + +Don't add typeclass constraints that aren't actually used. +The `IsEra` constraint mistake in the past was caught by this. + +--- + +## 6. Directory layout and worktrees + +### Main checkout paths + +The cardano-node files are available in the main checkout: +``` +/work/cardano-node/cardano-node/src/Cardano/Node/Run.hs +/work/cardano-node/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs +/work/cardano-node/cardano-node/cardano-node.cabal +``` + +The cardano-rpc files are in the cardano-api submodule: +``` +/work/cardano-api/cardano-rpc/src/Cardano/Rpc/Server/... +/work/cardano-api/cardano-rpc/cardano-rpc.cabal +``` + +### Worktree for the gRPC feature branch + +A worktree exists at `/work/cardano-node/@worktree/add-grpc-interface/`. +This is the branch where the gRPC feature was originally developed. +The analysis file references files in this worktree path - they may differ from the main checkout if the branch has been merged or rebased. + +**When implementing:** Check whether to work in the main checkout or the worktree, depending on the current git branch state. +Use `git branch` and `git log` to determine which is current. + +### Worktree rules (from AGENTS.md) + +Worktrees ALWAYS reside in each subproject's `@worktree/` directory. +After creating a git worktree inside a submodule, update its `.git` configuration to use relative paths. +`git worktree add` writes absolute paths in submodules in two places that must both be fixed: +1. `/.git` - the `gitdir:` line pointing to the worktree metadata +2. `.git/modules//worktrees//gitdir` - the back-pointer to the worktree diff --git a/cardano-rpc/docs/node-kernel-access/prereqs-implementation-details.md b/cardano-rpc/docs/node-kernel-access/prereqs-implementation-details.md new file mode 100644 index 0000000000..f1fe7f38a2 --- /dev/null +++ b/cardano-rpc/docs/node-kernel-access/prereqs-implementation-details.md @@ -0,0 +1,321 @@ +# Implementation Details and Verification + +Subtle implementation gotchas, per-method query inventory, and verification checklist. + +--- + +## 4. Subtle Implementation Details + +### Snapshot Consistency (IMPORTANT) + +The current N2C code runs all queries within one `executeLocalStateQueryExpr` call, acquiring a single ledger snapshot. +Protocol parameters, UTxO results, chain tip, and block number all come from the same ledger state. + +The `NodeKernelAccess` design must preserve this: use `nkaWithSnapshot` to group all queries for a single RPC handler under one `ReadOnlyForker`. +Do NOT call `nkaWithSnapshot` multiple times within a single handler - that would break consistency. + +This is especially critical for `EvalTx`, which queries 7 different things that must be mutually consistent: +1. `queryProtocolParameters` - needed for tx evaluation +2. `queryUtxo` (by TxIn set) - inputs being spent +3. `querySystemStart` - for slot-to-time conversion +4. `queryEraHistory` - for slot-to-time conversion +5. `queryStakeDelegDeposits` - for deposit tracking +6. `queryDRepState` - for governance-related evaluation +7. `queryStakePoolParameters` - for delegation-related evaluation + +If these came from different ledger states, tx evaluation could produce wrong results or spurious failures. + +### 4.1 The `eon` existential must escape the snapshot callback + +The `eon` value (from `forEraInEon @Era era ...`) is needed after the `nkaWithSnapshot` +callback for protobuf conversion. +The callback must return it as part of its result tuple: + +```haskell +-- CORRECT: eon escapes the callback +(pparams, chainPoint, blockNo, eon) <- liftIO $ withNodeKernelAccess laRef $ \la -> do + nkaWithSnapshot la $ \snapshot -> do + AnyCardanoEra era <- runQuery snapshot QueryCurrentEra + eon <- forEraInEon @Era era (error "Minimum Conway era required") pure + ... + pure (pparams, chainPoint, blockNo, eon) + +-- WRONG: eon would be scoped inside the callback only +liftIO $ withNodeKernelAccess laRef $ \la -> do + nkaWithSnapshot la $ \snapshot -> do + ... + -- can't use eon out here for protobuf conversion +``` + +This works because `eon` is an existential that's pattern-matched later. +The tuple captures the existential witness. + +### 4.2 `RankNTypes` in `NodeKernelAccess` and `LedgerSnapshot` + +Both `NodeKernelAccess` and `LedgerSnapshot` use higher-rank quantification: +```haskell +nkaWithSnapshot :: forall a. (LedgerSnapshot -> IO a) -> IO a +runQuery :: forall result. QueryInMode result -> IO result +``` + +This requires `{-# LANGUAGE RankNTypes #-}` on `NodeKernelAccess.hs`. +The `forall a.` on `nkaWithSnapshot` ensures the snapshot cannot escape the callback scope. +The `forall result.` on `runQuery` lets callers pass any `QueryInMode` variant and get the corresponding result type back. + +### 4.3 `throwEither` / `throwExceptT` still needed in Submit.hs + +After the rewrite, `Query.hs` and `Node.hs` no longer need `throwExceptT` or the double `throwEither` pattern (because `NodeKernelAccess` callbacks throw directly on error). +But `Submit.hs` still uses `throwEither` via `putTraceThrowEither` for tx validation errors. +Don't remove the `Error` module import from `Submit.hs`. + +### 4.4 `SomeException` import in Tracing.hs + +After removing `TraceRpcSubmitN2cConnectionError SomeException`, the `Control.Exception` import for `SomeException` is **still needed** because `TraceRpc` (defined in the same module) uses `SomeException` in `TraceRpcError` and `TraceRpcFatalError`. + +### 4.5 The `SearchUtxos` tracing gap (verified: no bug) + +`TraceRpcQuerySearchUtxosSpan` is already fully wired in `forMachine`, `asMetrics`, and the `MetaTrace` instance in `cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs`. +The originally reported pre-existing bug does not exist in the current codebase. +Step 11 of the implementation plan only needs to swap the N2C trace constructors. + +### 4.6 `Net.Tx.SubmitFail` / `Net.Tx.SubmitSuccess` still needed + +After the rewrite, `Submit.hs` still pattern-matches on `SubmitFail` / `SubmitSuccess` from `Cardano.Api.Network.IPC`. +Don't remove that qualified import. + +### 4.7 Config.hs backward compatibility + +The plan **keeps** `nodeSocketPath` in `RpcConfig`. +The earlier design iteration wanted to remove it, but analysis showed it would break: +- `cardano-node/cardano-testnet/src/Testnet/Types.hs` (line 155: `nodeRpcSocketPath`) +- `cardano-node/cardano-node/src/Cardano/Node/Configuration/POM.hs` +- `makeRpcConfig` uses it to derive the default `rpcSocketPath` + +The field just isn't used at runtime for N2C connections anymore. + +### 4.8 Submit.hs uses both `nkaWithSnapshot` and `nkaSubmitTx` + +The submit method needs two separate interactions with `NodeKernelAccess`: +1. First, `nkaWithSnapshot` to query the current era (via `runQuery QueryCurrentEra`) for tx deserialisation. +2. Then `nkaSubmitTx` (after the tx is deserialised and validated) to submit the transaction. + +Era determination now happens inside the snapshot callback via `runQuery` rather than a dedicated `laDetermineEra` field. +Tx deserialisation is pure/monadic and happens between the two calls. +Do not fold both into a single `nkaWithSnapshot` - `nkaSubmitTx` is a separate operation that goes to the mempool, not the ledger snapshot. + +### 4.9 Era mismatch in `runQuery` result unwrapping + +`fromConsensusQueryResult` for era-specific queries (pparams, utxo) returns `Either EraMismatch result`. +The `runQuery` implementation inside `mkNodeKernelAccess` must unwrap this: + +```haskell +-- Inside the runQuery implementation for era-specific queries: +raw <- answerQuery cfg forker consensusQuery +case fromConsensusQueryResult qim consensusQuery raw of + Left eraMismatch -> throwIO (userError $ "Era mismatch: " <> show eraMismatch) + Right result -> pure result +``` + +With the snapshot-based design, all queries within one `nkaWithSnapshot` call share the same `ReadOnlyForker`, so era mismatches between queries inside a single snapshot are impossible. +An era mismatch can still occur if the node transitions between the era determination (outside the snapshot) and the snapshot acquisition, but this is an extremely rare race at hard fork boundaries. +Throwing is acceptable since the client can retry. + +### 4.10 `QueryCurrentEra` routes through `toConsensusQuery` (verified) + +`toConsensusQuery` handles `QueryCurrentEra` by wrapping it as `BlockQuery (QueryHardFork GetCurrentEra)` internally. +No special handling is needed - just call `runQuery snapshot QueryCurrentEra` inside the `nkaWithSnapshot` callback. + +### 4.11 `runQuery` helper must use `bracket` for forker lifecycle (fixed in plan) + +The implementation plan's `runQuery` snippet now uses `bracket` for exception-safe forker cleanup. +`withRegistry` also provides a safety net (auto-closes registered resources when its scope exits), but explicit `bracket` around `roforkerClose` is still preferred for deterministic cleanup. + +### 4.12 IORef thread safety during startup + +The `IORef (Maybe NodeKernelAccess)` has one writer (`rnNodeKernelHook`) and multiple readers (gRPC handler threads). +This is safe because: +- GHC's `IORef` guarantees atomicity for single-word writes +- The write is `Nothing -> Just la` (a single pointer write) +- Readers see either `Nothing` (return UNAVAILABLE) or `Just la` (proceed) +- No read-modify-write cycle exists + +There is NO race condition here. +But if someone later adds a second write site, this assumption breaks. +Document it in a comment in `Run.hs`. + +### 4.13 `Solo` constructor name on GHC 9.10 (verified) + +GHC 9.10 uses `MkSolo` as the constructor. +Import from `Data.Tuple` (not `GHC.Tuple`): +```haskell +import Data.Tuple (Solo (..)) + +MkSolo addTxRes <- addLocalTxs mempool (MkSolo genTx) +``` + +This matches the pattern used in ouroboros-consensus's own `LocalTxSubmission/Server.hs`. + +### 4.14 `getReadOnlyForkerAtPoint` takes `ResourceRegistry` from where? (verified) + +Each call to `getReadOnlyForkerAtPoint` needs a `ResourceRegistry`. +Use `withRegistry` from `Control.ResourceRegistry` to create a fresh one per query. +This matches the pattern used by the existing N2C LocalStateQuery server in `ouroboros-consensus-diffusion/Network/NodeToClient.hs`. + +Registry cleanup does automatically close registered forkers when the registry scope exits. +Using `bracket` with `roforkerClose` inside `withRegistry` is still good practice for explicit cleanup, but the registry provides a safety net if an exception bypasses it. + +### 4.15 The `asType` in `deserialiseTx` comes from where? + +In `Submit.hs`, the existing code uses `asType` (line 52): +```haskell +deserialiseTx sbe = shelleyBasedEraConstraints sbe $ deserialiseFromCBOR asType +``` + +After the rewrite, this code is unchanged but `asType` comes from `Cardano.Api` (re-exported by the blanket `import Cardano.Api`). +If you change the `Cardano.Api` import to be more specific, make sure `AsType` / `asType` is still in scope. +It's from `Cardano.Api.Serialise.SerialiseUsing` or `Cardano.Api.Serialise.Cbor`. + +### 4.16 `NoFieldSelectors` on `Env.hs` + +`Env.hs` has `{-# LANGUAGE NoFieldSelectors #-}`. +This means `RpcEnv` fields like `rpcNodeKernelAccess` are NOT available as accessor functions. +The `Has` instance in `Monad.hs` must use `NamedFieldPuns`: + +```haskell +-- This works (NamedFieldPuns): +instance Has (IORef (Maybe NodeKernelAccess)) RpcEnv where + obtain RpcEnv{rpcNodeKernelAccess} = rpcNodeKernelAccess + +-- This does NOT work (NoFieldSelectors blocks it): +instance Has (IORef (Maybe NodeKernelAccess)) RpcEnv where + obtain = rpcNodeKernelAccess -- ERROR: not a function +``` + +The existing `Has LocalNodeConnectInfo RpcEnv` instance already uses `NamedFieldPuns`, so just follow the same pattern. + +### 4.17 Removing `import Cardano.Api` is high-risk + +Three files (Env.hs, Monad.hs, Server.hs) currently have blanket `import Cardano.Api` which re-exports hundreds of names. +The plan replaces these with specific imports. +This is the most likely source of compilation errors because it's easy to miss a name that was silently in scope. + +**Strategy:** For each file where `import Cardano.Api` is removed: +1. Remove the import +2. Try to build +3. Add specific imports for each "not in scope" error +4. Repeat until clean + +Files that **keep** `import Cardano.Api` (because they use many names from it): +- `Query.hs` - uses `AnyCardanoEra`, `forEraInEon`, `Era`, `convert`, `ShelleyBasedEra`, `UTxO`, `TxIn`, `TxIx`, `TxOut`, `CtxUTxO`, `QueryUTxOFilter`, `ChainPoint`, `BlockNo`, `WithOrigin`, `serialiseToRawBytesHexText`, `serialiseToRawBytes`, `deserialiseFromRawBytes`, `AsTxId`, `IsEra`, `obtainCommonConstraints`, `fromList`, etc. +- `Submit.hs` - uses `AnyCardanoEra`, `forEraInEon`, `ShelleyBasedEra`, `Tx`, `TxId`, `TxInMode`, `shelleyBasedEraConstraints`, `deserialiseFromCBOR`, `getTxId`, `getTxBody`, `serialiseToRawBytes`, etc. +- `Node.hs` - uses `AnyCardanoEra`, `forEraInEon`, `Era`, `convert`, `obtainCommonConstraints`, etc. + +For these files, **keep `import Cardano.Api`** and just add the `NodeKernelAccess` import alongside it. + +### 4.18 `SubmitResult` and `TxValidationErrorInCardanoMode` imports + +`SubmitResult` and `TxValidationErrorInCardanoMode` need explicit imports - they are NOT re-exported from the top-level `Cardano.Api` module. +Import both from `Cardano.Api.Network.IPC` (or alternatively, `SubmitResult` is available via `Cardano.Api.Network` and originates from `Ouroboros.Network.Protocol.LocalTxSubmission.Type`). + +This matters for `NodeKernelAccess.hs` which uses both in its type signature. + +### 4.19 No new dependencies required + +`cardano-ledger-core` and `grpc-spec` are already listed in the `cardano-rpc.cabal` dependencies. +The `NodeKernelAccess` interface uses only cardano-api types, so no consensus dependencies are needed in cardano-rpc. +The `mkNodeKernelAccess` implementation in cardano-node already has all necessary consensus dependencies. + +### 4.20 `withNodeKernelAccess` throws gRPC `UNAVAILABLE` during startup + +The chosen variant is `withNodeAccessOrUnavailable`, which reads the `IORef (Maybe NodeKernelAccess)` and throws a gRPC `UNAVAILABLE` status code if the value is `Nothing`. +This cleanly handles the startup window before `rnNodeKernelHook` writes the `Just` value. +The gRPC client can retry on `UNAVAILABLE`, which is the standard practice for transient unavailability. + +--- + +## 5. RPC Method Query Inventory + +This section lists the exact queries each RPC method executes within a single `executeLocalStateQueryExpr` call (i.e. a single ledger snapshot). +All queries within one call are consistent - they see the same ledger state. + +### ReadParams (in `Query.hs`) + +1. `queryProtocolParameters` - protocol parameters for the current era +2. `queryChainPoint` - chain tip +3. `queryChainBlockNo` - block number at tip +4. `querySystemStart` - for slot-to-timestamp conversion (via `slotToTimestamp`) +5. `queryEraHistory` - for slot-to-timestamp conversion (via `slotToTimestamp`) + +### ReadUtxos (in `Query.hs`) + +1. `queryUtxo` (by TxIn set or whole) - the requested UTxOs +2. `queryChainPoint` - chain tip +3. `queryChainBlockNo` - block number at tip +4. `querySystemStart` - for slot-to-timestamp conversion (via `slotToTimestamp`) +5. `queryEraHistory` - for slot-to-timestamp conversion (via `slotToTimestamp`) + +### SearchUtxos (in `Query.hs`) + +1. `queryUtxo` (by address set or whole, then client-side predicate filtering + pagination) +2. `queryChainPoint` - chain tip +3. `queryChainBlockNo` - block number at tip +4. `querySystemStart` - for slot-to-timestamp conversion (via `slotToTimestamp`) +5. `queryEraHistory` - for slot-to-timestamp conversion (via `slotToTimestamp`) + +### EvalTx (in `Eval.hs`) + +This is the most query-intensive method, requiring 7 queries within one snapshot: + +1. `queryProtocolParameters` - needed for tx evaluation +2. `queryUtxo` (by TxIn set) - inputs being spent +3. `querySystemStart` - for slot-to-time conversion +4. `queryEraHistory` - for slot-to-time conversion +5. `queryStakeDelegDeposits` - for deposit tracking +6. `queryDRepState` - for governance-related evaluation +7. `queryStakePoolParameters` - for delegation-related evaluation + +### GetProtocolParamsJson (in `Node.hs`) + +1. `queryProtocolParameters` - protocol parameters as JSON + +### SubmitTx (in `Submit.hs`) + +Uses `submitTxToNodeLocal` (LocalTxSubmission mini-protocol), not LocalStateQuery. +In the direct access design, this maps to `nkaSubmitTx` (mempool `addLocalTxs`). + +### Slot-to-timestamp conversion note + +`ReadParams`, `ReadUtxos`, and `SearchUtxos` all query `querySystemStart` and `queryEraHistory` for slot-to-timestamp conversion via the `slotToTimestamp` helper. +`EvalTx` also queries both for slot-to-time conversion. +The original plan did not account for these - they must be included in the `LedgerSnapshot` queries. +Both are handled by `answerQuery`: `GetSystemStart` is extracted from config, and `queryEraHistory` maps to `BlockQuery (QueryHardFork GetInterpreter)`. + +--- + +## 7. Verification Checklist + +After implementing all steps: + +- [ ] `nix build 'path:/work/cardano-api#cardano-rpc:lib:cardano-rpc' --allow-import-from-derivation --accept-flake-config` +- [ ] `nix build 'path:/work/cardano-api#cardano-rpc:test:cardano-rpc-test' --allow-import-from-derivation --accept-flake-config` +- [ ] `nix build 'path:/work/cardano-node#cardano-node:lib:cardano-node' --allow-import-from-derivation --accept-flake-config` (adjust path for worktree) +- [ ] No `-Wunused-packages` warnings +- [ ] No `-Wredundant-constraints` warnings +- [ ] hlint passes (backtick-infix, no mixed styles) +- [ ] Integration tests pass (testnet gRPC tests exercise the full path) +- [ ] Startup window test: gRPC query before kernel init returns `UNAVAILABLE` + +--- + +## 8. Files in this documentation set + +| File | Purpose | +|------|---------| +| `ADR-019-node-kernel-access-for-cardano-rpc.md` | Architecture Decision Record | +| `implementation-plan.md` | Step-by-step implementation plan with before/after code | +| `analysis-architecture.md` | Architecture and current state | +| `analysis-utxohd-internals.md` | UTxO-HD internals | +| `analysis-consensus-protocol.md` | Consensus protocol and snapshot consistency | +| `prereqs-api-signatures.md` | API signatures and type references | +| `prereqs-build-and-conventions.md` | Build instructions and conventions | +| `prereqs-implementation-details.md` | **This file** - implementation details, query inventory, and verification checklist | diff --git a/cardano-rpc/docs/story-read-genesis.md b/cardano-rpc/docs/story-read-genesis.md new file mode 100644 index 0000000000..8c2613a7e5 --- /dev/null +++ b/cardano-rpc/docs/story-read-genesis.md @@ -0,0 +1,117 @@ +# Implement ReadGenesis UTxO RPC method + +## Problem + +The UTxO RPC spec defines a `ReadGenesis` method on `QueryService` that returns genesis configuration data (genesis hash, CAIP-2 network identifier, and structured genesis parameters across all eras). +cardano-rpc's local `query.proto` already has the `ReadGenesisRequest` and `ReadGenesisResponse` message types, but the `rpc ReadGenesis` line is missing from the `QueryService` block and no handler exists. + +## Why + +Genesis configuration is static, well-defined data that tooling and wallets need to discover network parameters at startup. +Without `ReadGenesis`, clients must parse genesis JSON files out-of-band, duplicating logic that the node already has. + +## User value + +As a UTxO RPC client developer, I want to call `ReadGenesis` on the query service so that I can programmatically obtain the network's genesis configuration (hash, CAIP-2 ID, and era-specific parameters) without parsing raw genesis files. + +## Acceptance criteria + +1. **AC1: Proto service registration** - The `rpc ReadGenesis(ReadGenesisRequest) returns (ReadGenesisResponse)` line is added to the `QueryService` block in `proto/utxorpc/v1beta/query/query.proto`. + Generated code is regenerated via `buf generate` in the nix devshell. + The handler is registered in `methodsUtxoRpc` in `Server.hs` in the correct positional slot matching `ServiceMethods` order. + - Test: unit - compiles; the new method slot is exercised by the gRPC method table construction + +2. **AC2: NodeKernelAccessF genesis field** - `NodeKernelAccessF` gains a new pure field (e.g. `nkaGenesisConfig :: GenesisBundle`) that holds the genesis configuration data as Haskell-native types (not proto types). + Being a pure (non-`m`) field reflects the fact that genesis data is static and never changes after node startup. + `GenesisBundle` carries the Byron genesis hash, network magic, and per-era genesis configs (Shelley, Alonzo, Conway) needed to populate the response. + `mkNodeKernelAccess` extracts this data from `TopLevelConfig` via `getTopLevelConfig`. + - Test: unit - compiles; the new field is present on the record + +3. **AC3: Genesis-to-proto conversion** - A pure conversion function (e.g. `genesisToUtxoRpcGenesis`) in the `Type` module converts the Haskell genesis config values into the proto-lens `Genesis` message. + The function maps fields from all four eras: Byron (avvm_distr, block_version_data, start_time, protocol_consts, etc.), Shelley (active_slots_coeff, epoch_length, network_magic, protocol_params, system_start, etc.), Alonzo (lovelace_per_utxo_word, execution_prices, max_tx_ex_units, etc.), and Conway (committee, constitution, drep fields, voting thresholds, etc.). + - Test: unit - `H.propertyOnce` golden test: feed known testnet genesis fixture values, assert specific proto fields are populated correctly (e.g. network_magic, system_start, epoch_length, active_slots_coeff). + This is the primary unit-testable surface for the feature. + +4. **AC4: Handler assembly** - A `readGenesisMethod` handler in `Cardano.Rpc.Server.Internal.UtxoRpc.Query` (or a new module if warranted) grabs the `NodeKernelAccessF` via `grabNodeKernelAccess`, calls the genesis callback, runs the conversion function, and assembles the `ReadGenesisResponse`. + The response populates all three top-level fields: `genesis` (hash bytes), `caip2` (CIP-34 network identifier), and `cardano` (structured `Genesis` message). + - Test: E2E - calling `ReadGenesis` on a running node returns a response with all three top-level fields populated and non-empty + +5. **AC5: Tracing** - `TraceRpcQuery` gains a `TraceRpcQueryGenesisSpan TraceSpanEvent` constructor. + `Pretty` renders it as "Started ReadGenesis method" / "Finished ReadGenesis method". + The handler is wrapped in `wrapInSpan TraceRpcQueryGenesisSpan`. + - Test: unit - `H.propertyOnce` asserting the `Pretty` output of the new constructor contains the expected substrings + +6. **AC6: Node kernel unavailable** - When the node kernel is not yet initialised (`IORef` contains `Nothing`), calling `ReadGenesis` returns gRPC `UNAVAILABLE` with a message containing "not yet initialised". + This is consistent with the existing `FetchBlock` behaviour via `grabNodeKernelAccess`. + - Test: unit - `H.propertyOnce`: create `IORef Nothing`, call through the handler path, assert `GrpcException` with `GrpcUnavailable` is thrown (same pattern as the `NodeKernelAccess` unavailability unit test in piece 1) + +## Out of scope + +- **Field mask support**: `field_mask` in `ReadGenesisRequest` is ignored, consistent with `ReadParams`, `SearchUtxos`, and all other existing handlers. + Field mask normalisation and filtering is a cross-cutting concern for a separate story. +- **Byron genesis file fields requiring file I/O**: some Byron genesis fields (e.g. `vss_certs`, `heavy_delegation` maps with complex nested structures) may be deferred to a follow-up if the ledger types do not expose them directly through `TopLevelConfig`. +- **Inverse proto-to-genesis conversion**: unlike protocol parameters, `ReadGenesis` is read-only with no need for round-tripping. + No `utxoRpcGenesisToGenesis` function is needed. +- **ReadEraSummary**: this is a separate `QueryService` method defined in the upstream spec; it warrants its own story. +- **Minimum era guard**: unlike `ReadParams` which requires Conway, `ReadGenesis` returns static configuration from all eras and does not need an era check. + +## Definition of done + +- [ ] All AC tests written (compile, fail on stubs) +- [ ] Implementation complete (all tests pass via `cabal test`) +- [ ] Nix CI checks pass (`nix build 'path:.#checks.x86_64-linux.test'` and `e2e`) +- [ ] haskell-reviewer agent finds no critical or style issues +- [ ] fourmolu clean +- [ ] No build warnings + +## Notes + +### Open questions + +- **Which genesis hash for the `genesis` bytes field?** + Cardano has four genesis files (Byron, Shelley, Alonzo, Conway), each with its own hash. + The proto spec says "genesis hash for the chain" (singular). + Need to check what Dolos and other UTxO RPC implementations return here. + Most likely the Shelley genesis hash, as it is the one that identifies the network, but this must be confirmed against the spec or reference implementations before implementation. + The `utxorpc-compare` skill can verify what other servers return. + +- **CAIP-2 format and derivation.** + CIP-34 defines Cardano's CAIP-2 identifier. + The exact format and how to derive it from genesis data (genesis hash prefix? network magic?) needs confirmation from CIP-34 and cross-referencing with Dolos output. + +### Implementation risks + +- **Extracting per-era genesis from `TopLevelConfig`/`HardForkBlock`.** + The `mkNodeKernelAccess` function currently only uses `getChainDB` from the `NodeKernel`. + Accessing genesis configs likely requires `getTopLevelConfig` and then navigating the `HardForkLedgerConfig` to extract per-era genesis. + This may need consensus-specific accessors or new cardano-api surface. + Worth spiking early. + +- **Proto field coverage.** + The `Genesis` proto message has ~42 fields across four eras. + Some map directly to ledger types (epoch_length, network_magic); others (avvm_distr, vss_certs) may require non-trivial extraction from Byron genesis. + The golden test in AC3 should cover the high-value fields first, with explicit TODOs for any deferred fields. + +### Design decisions + +- **Genesis data is static.** + Unlike protocol parameters or UTxO state, genesis configs never change after node startup. + This means the handler can cache the converted proto message on first call, avoiding repeated conversion. + However, caching is an optimisation and not required for correctness. + +- **Follows FetchBlock pattern, with a key difference.** + FetchBlock uses an `m`-wrapped callback because it queries ChainDB per request. + ReadGenesis uses a pure field because genesis data is static. + The conversion layer (AC3) maps to proto, handler (AC4) assembles the response. + This keeps the node-kernel boundary clean and the conversion testable in isolation. + +### E2E harness + +- The E2E test in AC4 depends on having a running cardano-node with the gRPC server enabled. + The exact E2E harness location needs confirming (likely `cardano-node-tests` or nix-level integration tests). + +### Dependencies + +- Upstream proto: the `ReadGenesis` service line must be added to the local `query.proto` and code regenerated (AC1). +- cardano-node wiring: `mkNodeKernelAccess` in `NodeKernelAccess.Internal` must be updated to implement the new callback. + The node-side call site in `cardano-node/src/Cardano/Node/Run.hs` (line ~590) may need adjustment if the callback requires data beyond what `NodeKernel` provides directly. diff --git a/cardano-rpc/gen/Proto/Utxorpc/V1beta/Sync/Sync.hs b/cardano-rpc/gen/Proto/Utxorpc/V1beta/Sync/Sync.hs new file mode 100644 index 0000000000..dbc61f182a --- /dev/null +++ b/cardano-rpc/gen/Proto/Utxorpc/V1beta/Sync/Sync.hs @@ -0,0 +1,2324 @@ +{- This file was auto-generated from utxorpc/v1beta/sync/sync.proto by the proto-lens-protoc program. -} +{-# LANGUAGE ScopedTypeVariables, DataKinds, TypeFamilies, UndecidableInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, FlexibleContexts, FlexibleInstances, PatternSynonyms, MagicHash, NoImplicitPrelude, DataKinds, BangPatterns, TypeApplications, OverloadedStrings, DerivingStrategies#-} +{-# OPTIONS_GHC -Wno-unused-imports#-} +{-# OPTIONS_GHC -Wno-duplicate-exports#-} +{-# OPTIONS_GHC -Wno-dodgy-exports#-} +module Proto.Utxorpc.V1beta.Sync.Sync ( + SyncService(..), AnyChainBlock(), AnyChainBlock'Chain(..), + _AnyChainBlock'Cardano, BlockRef(), DumpHistoryRequest(), + DumpHistoryResponse(), FetchBlockRequest(), FetchBlockResponse(), + FollowTipRequest(), FollowTipResponse(), + FollowTipResponse'Action(..), _FollowTipResponse'Apply, + _FollowTipResponse'Undo, _FollowTipResponse'Reset, + ReadTipRequest(), ReadTipResponse() + ) where +import qualified Data.ProtoLens.Runtime.Control.DeepSeq as Control.DeepSeq +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Prism as Data.ProtoLens.Prism +import qualified Data.ProtoLens.Runtime.Prelude as Prelude +import qualified Data.ProtoLens.Runtime.Data.Int as Data.Int +import qualified Data.ProtoLens.Runtime.Data.Monoid as Data.Monoid +import qualified Data.ProtoLens.Runtime.Data.Word as Data.Word +import qualified Data.ProtoLens.Runtime.Data.ProtoLens as Data.ProtoLens +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Encoding.Bytes as Data.ProtoLens.Encoding.Bytes +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Encoding.Growing as Data.ProtoLens.Encoding.Growing +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Encoding.Parser.Unsafe as Data.ProtoLens.Encoding.Parser.Unsafe +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Encoding.Wire as Data.ProtoLens.Encoding.Wire +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Field as Data.ProtoLens.Field +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Message.Enum as Data.ProtoLens.Message.Enum +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Service.Types as Data.ProtoLens.Service.Types +import qualified Data.ProtoLens.Runtime.Lens.Family2 as Lens.Family2 +import qualified Data.ProtoLens.Runtime.Lens.Family2.Unchecked as Lens.Family2.Unchecked +import qualified Data.ProtoLens.Runtime.Data.Text as Data.Text +import qualified Data.ProtoLens.Runtime.Data.Map as Data.Map +import qualified Data.ProtoLens.Runtime.Data.ByteString as Data.ByteString +import qualified Data.ProtoLens.Runtime.Data.ByteString.Char8 as Data.ByteString.Char8 +import qualified Data.ProtoLens.Runtime.Data.Text.Encoding as Data.Text.Encoding +import qualified Data.ProtoLens.Runtime.Data.Vector as Data.Vector +import qualified Data.ProtoLens.Runtime.Data.Vector.Generic as Data.Vector.Generic +import qualified Data.ProtoLens.Runtime.Data.Vector.Unboxed as Data.Vector.Unboxed +import qualified Data.ProtoLens.Runtime.Text.Read as Text.Read +import qualified Proto.Google.Protobuf.FieldMask +import qualified Proto.Utxorpc.V1beta.Cardano.Cardano +{- | Fields : + + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.nativeBytes' @:: Lens' AnyChainBlock Data.ByteString.ByteString@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'chain' @:: Lens' AnyChainBlock (Prelude.Maybe AnyChainBlock'Chain)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'cardano' @:: Lens' AnyChainBlock (Prelude.Maybe Proto.Utxorpc.V1beta.Cardano.Cardano.Block)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.cardano' @:: Lens' AnyChainBlock Proto.Utxorpc.V1beta.Cardano.Cardano.Block@ -} +data AnyChainBlock + = AnyChainBlock'_constructor {_AnyChainBlock'nativeBytes :: !Data.ByteString.ByteString, + _AnyChainBlock'chain :: !(Prelude.Maybe AnyChainBlock'Chain), + _AnyChainBlock'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show AnyChainBlock where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +data AnyChainBlock'Chain + = AnyChainBlock'Cardano !Proto.Utxorpc.V1beta.Cardano.Cardano.Block + deriving stock (Prelude.Show, Prelude.Eq, Prelude.Ord) +instance Data.ProtoLens.Field.HasField AnyChainBlock "nativeBytes" Data.ByteString.ByteString where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _AnyChainBlock'nativeBytes + (\ x__ y__ -> x__ {_AnyChainBlock'nativeBytes = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField AnyChainBlock "maybe'chain" (Prelude.Maybe AnyChainBlock'Chain) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _AnyChainBlock'chain + (\ x__ y__ -> x__ {_AnyChainBlock'chain = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField AnyChainBlock "maybe'cardano" (Prelude.Maybe Proto.Utxorpc.V1beta.Cardano.Cardano.Block) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _AnyChainBlock'chain + (\ x__ y__ -> x__ {_AnyChainBlock'chain = y__})) + (Lens.Family2.Unchecked.lens + (\ x__ + -> case x__ of + (Prelude.Just (AnyChainBlock'Cardano x__val)) + -> Prelude.Just x__val + _otherwise -> Prelude.Nothing) + (\ _ y__ -> Prelude.fmap AnyChainBlock'Cardano y__)) +instance Data.ProtoLens.Field.HasField AnyChainBlock "cardano" Proto.Utxorpc.V1beta.Cardano.Cardano.Block where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _AnyChainBlock'chain + (\ x__ y__ -> x__ {_AnyChainBlock'chain = y__})) + ((Prelude..) + (Lens.Family2.Unchecked.lens + (\ x__ + -> case x__ of + (Prelude.Just (AnyChainBlock'Cardano x__val)) + -> Prelude.Just x__val + _otherwise -> Prelude.Nothing) + (\ _ y__ -> Prelude.fmap AnyChainBlock'Cardano y__)) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage)) +instance Data.ProtoLens.Message AnyChainBlock where + messageName _ = Data.Text.pack "utxorpc.v1beta.sync.AnyChainBlock" + packedMessageDescriptor _ + = "\n\ + \\rAnyChainBlock\DC2!\n\ + \\fnative_bytes\CAN\SOH \SOH(\fR\vnativeBytes\DC29\n\ + \\acardano\CAN\STX \SOH(\v2\GS.utxorpc.v1beta.cardano.BlockH\NULR\acardanoB\a\n\ + \\ENQchain" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag + = let + nativeBytes__field_descriptor + = Data.ProtoLens.FieldDescriptor + "native_bytes" + (Data.ProtoLens.ScalarField Data.ProtoLens.BytesField :: + Data.ProtoLens.FieldTypeDescriptor Data.ByteString.ByteString) + (Data.ProtoLens.PlainField + Data.ProtoLens.Optional + (Data.ProtoLens.Field.field @"nativeBytes")) :: + Data.ProtoLens.FieldDescriptor AnyChainBlock + cardano__field_descriptor + = Data.ProtoLens.FieldDescriptor + "cardano" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor Proto.Utxorpc.V1beta.Cardano.Cardano.Block) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'cardano")) :: + Data.ProtoLens.FieldDescriptor AnyChainBlock + in + Data.Map.fromList + [(Data.ProtoLens.Tag 1, nativeBytes__field_descriptor), + (Data.ProtoLens.Tag 2, cardano__field_descriptor)] + unknownFields + = Lens.Family2.Unchecked.lens + _AnyChainBlock'_unknownFields + (\ x__ y__ -> x__ {_AnyChainBlock'_unknownFields = y__}) + defMessage + = AnyChainBlock'_constructor + {_AnyChainBlock'nativeBytes = Data.ProtoLens.fieldDefault, + _AnyChainBlock'chain = Prelude.Nothing, + _AnyChainBlock'_unknownFields = []} + parseMessage + = let + loop :: + AnyChainBlock -> Data.ProtoLens.Encoding.Bytes.Parser AnyChainBlock + loop x + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) x) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + 10 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.getBytes + (Prelude.fromIntegral len)) + "native_bytes" + loop + (Lens.Family2.set (Data.ProtoLens.Field.field @"nativeBytes") y x) + 18 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "cardano" + loop (Lens.Family2.set (Data.ProtoLens.Field.field @"cardano") y x) + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + in + (Data.ProtoLens.Encoding.Bytes.) + (do loop Data.ProtoLens.defMessage) "AnyChainBlock" + buildMessage + = \ _x + -> (Data.Monoid.<>) + (let + _v + = Lens.Family2.view (Data.ProtoLens.Field.field @"nativeBytes") _x + in + if (Prelude.==) _v Data.ProtoLens.fieldDefault then + Data.Monoid.mempty + else + (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 10) + ((\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + _v)) + ((Data.Monoid.<>) + (case + Lens.Family2.view (Data.ProtoLens.Field.field @"maybe'chain") _x + of + Prelude.Nothing -> Data.Monoid.mempty + (Prelude.Just (AnyChainBlock'Cardano v)) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 18) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage v)) + (Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x))) +instance Control.DeepSeq.NFData AnyChainBlock where + rnf + = \ x__ + -> Control.DeepSeq.deepseq + (_AnyChainBlock'_unknownFields x__) + (Control.DeepSeq.deepseq + (_AnyChainBlock'nativeBytes x__) + (Control.DeepSeq.deepseq (_AnyChainBlock'chain x__) ())) +instance Control.DeepSeq.NFData AnyChainBlock'Chain where + rnf (AnyChainBlock'Cardano x__) = Control.DeepSeq.rnf x__ +_AnyChainBlock'Cardano :: + Data.ProtoLens.Prism.Prism' AnyChainBlock'Chain Proto.Utxorpc.V1beta.Cardano.Cardano.Block +_AnyChainBlock'Cardano + = Data.ProtoLens.Prism.prism' + AnyChainBlock'Cardano + (\ p__ + -> case p__ of + (AnyChainBlock'Cardano p__val) -> Prelude.Just p__val) +{- | Fields : + + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.slot' @:: Lens' BlockRef Data.Word.Word64@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.hash' @:: Lens' BlockRef Data.ByteString.ByteString@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.height' @:: Lens' BlockRef Data.Word.Word64@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.timestamp' @:: Lens' BlockRef Data.Word.Word64@ -} +data BlockRef + = BlockRef'_constructor {_BlockRef'slot :: !Data.Word.Word64, + _BlockRef'hash :: !Data.ByteString.ByteString, + _BlockRef'height :: !Data.Word.Word64, + _BlockRef'timestamp :: !Data.Word.Word64, + _BlockRef'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show BlockRef where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +instance Data.ProtoLens.Field.HasField BlockRef "slot" Data.Word.Word64 where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _BlockRef'slot (\ x__ y__ -> x__ {_BlockRef'slot = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField BlockRef "hash" Data.ByteString.ByteString where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _BlockRef'hash (\ x__ y__ -> x__ {_BlockRef'hash = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField BlockRef "height" Data.Word.Word64 where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _BlockRef'height (\ x__ y__ -> x__ {_BlockRef'height = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField BlockRef "timestamp" Data.Word.Word64 where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _BlockRef'timestamp (\ x__ y__ -> x__ {_BlockRef'timestamp = y__})) + Prelude.id +instance Data.ProtoLens.Message BlockRef where + messageName _ = Data.Text.pack "utxorpc.v1beta.sync.BlockRef" + packedMessageDescriptor _ + = "\n\ + \\bBlockRef\DC2\DC2\n\ + \\EOTslot\CAN\SOH \SOH(\EOTR\EOTslot\DC2\DC2\n\ + \\EOThash\CAN\STX \SOH(\fR\EOThash\DC2\SYN\n\ + \\ACKheight\CAN\ETX \SOH(\EOTR\ACKheight\DC2\FS\n\ + \\ttimestamp\CAN\EOT \SOH(\EOTR\ttimestamp" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag + = let + slot__field_descriptor + = Data.ProtoLens.FieldDescriptor + "slot" + (Data.ProtoLens.ScalarField Data.ProtoLens.UInt64Field :: + Data.ProtoLens.FieldTypeDescriptor Data.Word.Word64) + (Data.ProtoLens.PlainField + Data.ProtoLens.Optional (Data.ProtoLens.Field.field @"slot")) :: + Data.ProtoLens.FieldDescriptor BlockRef + hash__field_descriptor + = Data.ProtoLens.FieldDescriptor + "hash" + (Data.ProtoLens.ScalarField Data.ProtoLens.BytesField :: + Data.ProtoLens.FieldTypeDescriptor Data.ByteString.ByteString) + (Data.ProtoLens.PlainField + Data.ProtoLens.Optional (Data.ProtoLens.Field.field @"hash")) :: + Data.ProtoLens.FieldDescriptor BlockRef + height__field_descriptor + = Data.ProtoLens.FieldDescriptor + "height" + (Data.ProtoLens.ScalarField Data.ProtoLens.UInt64Field :: + Data.ProtoLens.FieldTypeDescriptor Data.Word.Word64) + (Data.ProtoLens.PlainField + Data.ProtoLens.Optional (Data.ProtoLens.Field.field @"height")) :: + Data.ProtoLens.FieldDescriptor BlockRef + timestamp__field_descriptor + = Data.ProtoLens.FieldDescriptor + "timestamp" + (Data.ProtoLens.ScalarField Data.ProtoLens.UInt64Field :: + Data.ProtoLens.FieldTypeDescriptor Data.Word.Word64) + (Data.ProtoLens.PlainField + Data.ProtoLens.Optional + (Data.ProtoLens.Field.field @"timestamp")) :: + Data.ProtoLens.FieldDescriptor BlockRef + in + Data.Map.fromList + [(Data.ProtoLens.Tag 1, slot__field_descriptor), + (Data.ProtoLens.Tag 2, hash__field_descriptor), + (Data.ProtoLens.Tag 3, height__field_descriptor), + (Data.ProtoLens.Tag 4, timestamp__field_descriptor)] + unknownFields + = Lens.Family2.Unchecked.lens + _BlockRef'_unknownFields + (\ x__ y__ -> x__ {_BlockRef'_unknownFields = y__}) + defMessage + = BlockRef'_constructor + {_BlockRef'slot = Data.ProtoLens.fieldDefault, + _BlockRef'hash = Data.ProtoLens.fieldDefault, + _BlockRef'height = Data.ProtoLens.fieldDefault, + _BlockRef'timestamp = Data.ProtoLens.fieldDefault, + _BlockRef'_unknownFields = []} + parseMessage + = let + loop :: BlockRef -> Data.ProtoLens.Encoding.Bytes.Parser BlockRef + loop x + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) x) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + 8 -> do y <- (Data.ProtoLens.Encoding.Bytes.) + Data.ProtoLens.Encoding.Bytes.getVarInt "slot" + loop (Lens.Family2.set (Data.ProtoLens.Field.field @"slot") y x) + 18 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.getBytes + (Prelude.fromIntegral len)) + "hash" + loop (Lens.Family2.set (Data.ProtoLens.Field.field @"hash") y x) + 24 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + Data.ProtoLens.Encoding.Bytes.getVarInt "height" + loop (Lens.Family2.set (Data.ProtoLens.Field.field @"height") y x) + 32 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + Data.ProtoLens.Encoding.Bytes.getVarInt "timestamp" + loop + (Lens.Family2.set (Data.ProtoLens.Field.field @"timestamp") y x) + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + in + (Data.ProtoLens.Encoding.Bytes.) + (do loop Data.ProtoLens.defMessage) "BlockRef" + buildMessage + = \ _x + -> (Data.Monoid.<>) + (let _v = Lens.Family2.view (Data.ProtoLens.Field.field @"slot") _x + in + if (Prelude.==) _v Data.ProtoLens.fieldDefault then + Data.Monoid.mempty + else + (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 8) + (Data.ProtoLens.Encoding.Bytes.putVarInt _v)) + ((Data.Monoid.<>) + (let _v = Lens.Family2.view (Data.ProtoLens.Field.field @"hash") _x + in + if (Prelude.==) _v Data.ProtoLens.fieldDefault then + Data.Monoid.mempty + else + (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 18) + ((\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + _v)) + ((Data.Monoid.<>) + (let + _v = Lens.Family2.view (Data.ProtoLens.Field.field @"height") _x + in + if (Prelude.==) _v Data.ProtoLens.fieldDefault then + Data.Monoid.mempty + else + (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 24) + (Data.ProtoLens.Encoding.Bytes.putVarInt _v)) + ((Data.Monoid.<>) + (let + _v = Lens.Family2.view (Data.ProtoLens.Field.field @"timestamp") _x + in + if (Prelude.==) _v Data.ProtoLens.fieldDefault then + Data.Monoid.mempty + else + (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 32) + (Data.ProtoLens.Encoding.Bytes.putVarInt _v)) + (Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x))))) +instance Control.DeepSeq.NFData BlockRef where + rnf + = \ x__ + -> Control.DeepSeq.deepseq + (_BlockRef'_unknownFields x__) + (Control.DeepSeq.deepseq + (_BlockRef'slot x__) + (Control.DeepSeq.deepseq + (_BlockRef'hash x__) + (Control.DeepSeq.deepseq + (_BlockRef'height x__) + (Control.DeepSeq.deepseq (_BlockRef'timestamp x__) ())))) +{- | Fields : + + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.startToken' @:: Lens' DumpHistoryRequest BlockRef@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'startToken' @:: Lens' DumpHistoryRequest (Prelude.Maybe BlockRef)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maxItems' @:: Lens' DumpHistoryRequest Data.Word.Word32@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.fieldMask' @:: Lens' DumpHistoryRequest Proto.Google.Protobuf.FieldMask.FieldMask@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'fieldMask' @:: Lens' DumpHistoryRequest (Prelude.Maybe Proto.Google.Protobuf.FieldMask.FieldMask)@ -} +data DumpHistoryRequest + = DumpHistoryRequest'_constructor {_DumpHistoryRequest'startToken :: !(Prelude.Maybe BlockRef), + _DumpHistoryRequest'maxItems :: !Data.Word.Word32, + _DumpHistoryRequest'fieldMask :: !(Prelude.Maybe Proto.Google.Protobuf.FieldMask.FieldMask), + _DumpHistoryRequest'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show DumpHistoryRequest where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +instance Data.ProtoLens.Field.HasField DumpHistoryRequest "startToken" BlockRef where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _DumpHistoryRequest'startToken + (\ x__ y__ -> x__ {_DumpHistoryRequest'startToken = y__})) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage) +instance Data.ProtoLens.Field.HasField DumpHistoryRequest "maybe'startToken" (Prelude.Maybe BlockRef) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _DumpHistoryRequest'startToken + (\ x__ y__ -> x__ {_DumpHistoryRequest'startToken = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField DumpHistoryRequest "maxItems" Data.Word.Word32 where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _DumpHistoryRequest'maxItems + (\ x__ y__ -> x__ {_DumpHistoryRequest'maxItems = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField DumpHistoryRequest "fieldMask" Proto.Google.Protobuf.FieldMask.FieldMask where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _DumpHistoryRequest'fieldMask + (\ x__ y__ -> x__ {_DumpHistoryRequest'fieldMask = y__})) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage) +instance Data.ProtoLens.Field.HasField DumpHistoryRequest "maybe'fieldMask" (Prelude.Maybe Proto.Google.Protobuf.FieldMask.FieldMask) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _DumpHistoryRequest'fieldMask + (\ x__ y__ -> x__ {_DumpHistoryRequest'fieldMask = y__})) + Prelude.id +instance Data.ProtoLens.Message DumpHistoryRequest where + messageName _ + = Data.Text.pack "utxorpc.v1beta.sync.DumpHistoryRequest" + packedMessageDescriptor _ + = "\n\ + \\DC2DumpHistoryRequest\DC2>\n\ + \\vstart_token\CAN\STX \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefR\n\ + \startToken\DC2\ESC\n\ + \\tmax_items\CAN\ETX \SOH(\rR\bmaxItems\DC29\n\ + \\n\ + \field_mask\CAN\EOT \SOH(\v2\SUB.google.protobuf.FieldMaskR\tfieldMask" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag + = let + startToken__field_descriptor + = Data.ProtoLens.FieldDescriptor + "start_token" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor BlockRef) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'startToken")) :: + Data.ProtoLens.FieldDescriptor DumpHistoryRequest + maxItems__field_descriptor + = Data.ProtoLens.FieldDescriptor + "max_items" + (Data.ProtoLens.ScalarField Data.ProtoLens.UInt32Field :: + Data.ProtoLens.FieldTypeDescriptor Data.Word.Word32) + (Data.ProtoLens.PlainField + Data.ProtoLens.Optional + (Data.ProtoLens.Field.field @"maxItems")) :: + Data.ProtoLens.FieldDescriptor DumpHistoryRequest + fieldMask__field_descriptor + = Data.ProtoLens.FieldDescriptor + "field_mask" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor Proto.Google.Protobuf.FieldMask.FieldMask) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'fieldMask")) :: + Data.ProtoLens.FieldDescriptor DumpHistoryRequest + in + Data.Map.fromList + [(Data.ProtoLens.Tag 2, startToken__field_descriptor), + (Data.ProtoLens.Tag 3, maxItems__field_descriptor), + (Data.ProtoLens.Tag 4, fieldMask__field_descriptor)] + unknownFields + = Lens.Family2.Unchecked.lens + _DumpHistoryRequest'_unknownFields + (\ x__ y__ -> x__ {_DumpHistoryRequest'_unknownFields = y__}) + defMessage + = DumpHistoryRequest'_constructor + {_DumpHistoryRequest'startToken = Prelude.Nothing, + _DumpHistoryRequest'maxItems = Data.ProtoLens.fieldDefault, + _DumpHistoryRequest'fieldMask = Prelude.Nothing, + _DumpHistoryRequest'_unknownFields = []} + parseMessage + = let + loop :: + DumpHistoryRequest + -> Data.ProtoLens.Encoding.Bytes.Parser DumpHistoryRequest + loop x + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) x) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + 18 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "start_token" + loop + (Lens.Family2.set (Data.ProtoLens.Field.field @"startToken") y x) + 24 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (Prelude.fmap + Prelude.fromIntegral + Data.ProtoLens.Encoding.Bytes.getVarInt) + "max_items" + loop + (Lens.Family2.set (Data.ProtoLens.Field.field @"maxItems") y x) + 34 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "field_mask" + loop + (Lens.Family2.set (Data.ProtoLens.Field.field @"fieldMask") y x) + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + in + (Data.ProtoLens.Encoding.Bytes.) + (do loop Data.ProtoLens.defMessage) "DumpHistoryRequest" + buildMessage + = \ _x + -> (Data.Monoid.<>) + (case + Lens.Family2.view + (Data.ProtoLens.Field.field @"maybe'startToken") _x + of + Prelude.Nothing -> Data.Monoid.mempty + (Prelude.Just _v) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 18) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + ((Data.Monoid.<>) + (let + _v = Lens.Family2.view (Data.ProtoLens.Field.field @"maxItems") _x + in + if (Prelude.==) _v Data.ProtoLens.fieldDefault then + Data.Monoid.mempty + else + (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 24) + ((Prelude..) + Data.ProtoLens.Encoding.Bytes.putVarInt Prelude.fromIntegral _v)) + ((Data.Monoid.<>) + (case + Lens.Family2.view + (Data.ProtoLens.Field.field @"maybe'fieldMask") _x + of + Prelude.Nothing -> Data.Monoid.mempty + (Prelude.Just _v) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 34) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + (Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x)))) +instance Control.DeepSeq.NFData DumpHistoryRequest where + rnf + = \ x__ + -> Control.DeepSeq.deepseq + (_DumpHistoryRequest'_unknownFields x__) + (Control.DeepSeq.deepseq + (_DumpHistoryRequest'startToken x__) + (Control.DeepSeq.deepseq + (_DumpHistoryRequest'maxItems x__) + (Control.DeepSeq.deepseq (_DumpHistoryRequest'fieldMask x__) ()))) +{- | Fields : + + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.block' @:: Lens' DumpHistoryResponse [AnyChainBlock]@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.vec'block' @:: Lens' DumpHistoryResponse (Data.Vector.Vector AnyChainBlock)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.nextToken' @:: Lens' DumpHistoryResponse BlockRef@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'nextToken' @:: Lens' DumpHistoryResponse (Prelude.Maybe BlockRef)@ -} +data DumpHistoryResponse + = DumpHistoryResponse'_constructor {_DumpHistoryResponse'block :: !(Data.Vector.Vector AnyChainBlock), + _DumpHistoryResponse'nextToken :: !(Prelude.Maybe BlockRef), + _DumpHistoryResponse'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show DumpHistoryResponse where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +instance Data.ProtoLens.Field.HasField DumpHistoryResponse "block" [AnyChainBlock] where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _DumpHistoryResponse'block + (\ x__ y__ -> x__ {_DumpHistoryResponse'block = y__})) + (Lens.Family2.Unchecked.lens + Data.Vector.Generic.toList + (\ _ y__ -> Data.Vector.Generic.fromList y__)) +instance Data.ProtoLens.Field.HasField DumpHistoryResponse "vec'block" (Data.Vector.Vector AnyChainBlock) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _DumpHistoryResponse'block + (\ x__ y__ -> x__ {_DumpHistoryResponse'block = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField DumpHistoryResponse "nextToken" BlockRef where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _DumpHistoryResponse'nextToken + (\ x__ y__ -> x__ {_DumpHistoryResponse'nextToken = y__})) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage) +instance Data.ProtoLens.Field.HasField DumpHistoryResponse "maybe'nextToken" (Prelude.Maybe BlockRef) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _DumpHistoryResponse'nextToken + (\ x__ y__ -> x__ {_DumpHistoryResponse'nextToken = y__})) + Prelude.id +instance Data.ProtoLens.Message DumpHistoryResponse where + messageName _ + = Data.Text.pack "utxorpc.v1beta.sync.DumpHistoryResponse" + packedMessageDescriptor _ + = "\n\ + \\DC3DumpHistoryResponse\DC28\n\ + \\ENQblock\CAN\SOH \ETX(\v2\".utxorpc.v1beta.sync.AnyChainBlockR\ENQblock\DC2<\n\ + \\n\ + \next_token\CAN\STX \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefR\tnextToken" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag + = let + block__field_descriptor + = Data.ProtoLens.FieldDescriptor + "block" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor AnyChainBlock) + (Data.ProtoLens.RepeatedField + Data.ProtoLens.Unpacked (Data.ProtoLens.Field.field @"block")) :: + Data.ProtoLens.FieldDescriptor DumpHistoryResponse + nextToken__field_descriptor + = Data.ProtoLens.FieldDescriptor + "next_token" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor BlockRef) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'nextToken")) :: + Data.ProtoLens.FieldDescriptor DumpHistoryResponse + in + Data.Map.fromList + [(Data.ProtoLens.Tag 1, block__field_descriptor), + (Data.ProtoLens.Tag 2, nextToken__field_descriptor)] + unknownFields + = Lens.Family2.Unchecked.lens + _DumpHistoryResponse'_unknownFields + (\ x__ y__ -> x__ {_DumpHistoryResponse'_unknownFields = y__}) + defMessage + = DumpHistoryResponse'_constructor + {_DumpHistoryResponse'block = Data.Vector.Generic.empty, + _DumpHistoryResponse'nextToken = Prelude.Nothing, + _DumpHistoryResponse'_unknownFields = []} + parseMessage + = let + loop :: + DumpHistoryResponse + -> Data.ProtoLens.Encoding.Growing.Growing Data.Vector.Vector Data.ProtoLens.Encoding.Growing.RealWorld AnyChainBlock + -> Data.ProtoLens.Encoding.Bytes.Parser DumpHistoryResponse + loop x mutable'block + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do frozen'block <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + (Data.ProtoLens.Encoding.Growing.unsafeFreeze mutable'block) + (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) + (Lens.Family2.set + (Data.ProtoLens.Field.field @"vec'block") frozen'block x)) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + 10 + -> do !y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) + Data.ProtoLens.parseMessage) + "block" + v <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + (Data.ProtoLens.Encoding.Growing.append mutable'block y) + loop x v + 18 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "next_token" + loop + (Lens.Family2.set (Data.ProtoLens.Field.field @"nextToken") y x) + mutable'block + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + mutable'block + in + (Data.ProtoLens.Encoding.Bytes.) + (do mutable'block <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + Data.ProtoLens.Encoding.Growing.new + loop Data.ProtoLens.defMessage mutable'block) + "DumpHistoryResponse" + buildMessage + = \ _x + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.foldMapBuilder + (\ _v + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 10) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + (Lens.Family2.view (Data.ProtoLens.Field.field @"vec'block") _x)) + ((Data.Monoid.<>) + (case + Lens.Family2.view + (Data.ProtoLens.Field.field @"maybe'nextToken") _x + of + Prelude.Nothing -> Data.Monoid.mempty + (Prelude.Just _v) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 18) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + (Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x))) +instance Control.DeepSeq.NFData DumpHistoryResponse where + rnf + = \ x__ + -> Control.DeepSeq.deepseq + (_DumpHistoryResponse'_unknownFields x__) + (Control.DeepSeq.deepseq + (_DumpHistoryResponse'block x__) + (Control.DeepSeq.deepseq (_DumpHistoryResponse'nextToken x__) ())) +{- | Fields : + + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.ref' @:: Lens' FetchBlockRequest [BlockRef]@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.vec'ref' @:: Lens' FetchBlockRequest (Data.Vector.Vector BlockRef)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.fieldMask' @:: Lens' FetchBlockRequest Proto.Google.Protobuf.FieldMask.FieldMask@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'fieldMask' @:: Lens' FetchBlockRequest (Prelude.Maybe Proto.Google.Protobuf.FieldMask.FieldMask)@ -} +data FetchBlockRequest + = FetchBlockRequest'_constructor {_FetchBlockRequest'ref :: !(Data.Vector.Vector BlockRef), + _FetchBlockRequest'fieldMask :: !(Prelude.Maybe Proto.Google.Protobuf.FieldMask.FieldMask), + _FetchBlockRequest'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show FetchBlockRequest where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +instance Data.ProtoLens.Field.HasField FetchBlockRequest "ref" [BlockRef] where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FetchBlockRequest'ref + (\ x__ y__ -> x__ {_FetchBlockRequest'ref = y__})) + (Lens.Family2.Unchecked.lens + Data.Vector.Generic.toList + (\ _ y__ -> Data.Vector.Generic.fromList y__)) +instance Data.ProtoLens.Field.HasField FetchBlockRequest "vec'ref" (Data.Vector.Vector BlockRef) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FetchBlockRequest'ref + (\ x__ y__ -> x__ {_FetchBlockRequest'ref = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField FetchBlockRequest "fieldMask" Proto.Google.Protobuf.FieldMask.FieldMask where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FetchBlockRequest'fieldMask + (\ x__ y__ -> x__ {_FetchBlockRequest'fieldMask = y__})) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage) +instance Data.ProtoLens.Field.HasField FetchBlockRequest "maybe'fieldMask" (Prelude.Maybe Proto.Google.Protobuf.FieldMask.FieldMask) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FetchBlockRequest'fieldMask + (\ x__ y__ -> x__ {_FetchBlockRequest'fieldMask = y__})) + Prelude.id +instance Data.ProtoLens.Message FetchBlockRequest where + messageName _ + = Data.Text.pack "utxorpc.v1beta.sync.FetchBlockRequest" + packedMessageDescriptor _ + = "\n\ + \\DC1FetchBlockRequest\DC2/\n\ + \\ETXref\CAN\SOH \ETX(\v2\GS.utxorpc.v1beta.sync.BlockRefR\ETXref\DC29\n\ + \\n\ + \field_mask\CAN\STX \SOH(\v2\SUB.google.protobuf.FieldMaskR\tfieldMask" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag + = let + ref__field_descriptor + = Data.ProtoLens.FieldDescriptor + "ref" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor BlockRef) + (Data.ProtoLens.RepeatedField + Data.ProtoLens.Unpacked (Data.ProtoLens.Field.field @"ref")) :: + Data.ProtoLens.FieldDescriptor FetchBlockRequest + fieldMask__field_descriptor + = Data.ProtoLens.FieldDescriptor + "field_mask" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor Proto.Google.Protobuf.FieldMask.FieldMask) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'fieldMask")) :: + Data.ProtoLens.FieldDescriptor FetchBlockRequest + in + Data.Map.fromList + [(Data.ProtoLens.Tag 1, ref__field_descriptor), + (Data.ProtoLens.Tag 2, fieldMask__field_descriptor)] + unknownFields + = Lens.Family2.Unchecked.lens + _FetchBlockRequest'_unknownFields + (\ x__ y__ -> x__ {_FetchBlockRequest'_unknownFields = y__}) + defMessage + = FetchBlockRequest'_constructor + {_FetchBlockRequest'ref = Data.Vector.Generic.empty, + _FetchBlockRequest'fieldMask = Prelude.Nothing, + _FetchBlockRequest'_unknownFields = []} + parseMessage + = let + loop :: + FetchBlockRequest + -> Data.ProtoLens.Encoding.Growing.Growing Data.Vector.Vector Data.ProtoLens.Encoding.Growing.RealWorld BlockRef + -> Data.ProtoLens.Encoding.Bytes.Parser FetchBlockRequest + loop x mutable'ref + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do frozen'ref <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + (Data.ProtoLens.Encoding.Growing.unsafeFreeze mutable'ref) + (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) + (Lens.Family2.set + (Data.ProtoLens.Field.field @"vec'ref") frozen'ref x)) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + 10 + -> do !y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) + Data.ProtoLens.parseMessage) + "ref" + v <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + (Data.ProtoLens.Encoding.Growing.append mutable'ref y) + loop x v + 18 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "field_mask" + loop + (Lens.Family2.set (Data.ProtoLens.Field.field @"fieldMask") y x) + mutable'ref + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + mutable'ref + in + (Data.ProtoLens.Encoding.Bytes.) + (do mutable'ref <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + Data.ProtoLens.Encoding.Growing.new + loop Data.ProtoLens.defMessage mutable'ref) + "FetchBlockRequest" + buildMessage + = \ _x + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.foldMapBuilder + (\ _v + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 10) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + (Lens.Family2.view (Data.ProtoLens.Field.field @"vec'ref") _x)) + ((Data.Monoid.<>) + (case + Lens.Family2.view + (Data.ProtoLens.Field.field @"maybe'fieldMask") _x + of + Prelude.Nothing -> Data.Monoid.mempty + (Prelude.Just _v) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 18) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + (Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x))) +instance Control.DeepSeq.NFData FetchBlockRequest where + rnf + = \ x__ + -> Control.DeepSeq.deepseq + (_FetchBlockRequest'_unknownFields x__) + (Control.DeepSeq.deepseq + (_FetchBlockRequest'ref x__) + (Control.DeepSeq.deepseq (_FetchBlockRequest'fieldMask x__) ())) +{- | Fields : + + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.block' @:: Lens' FetchBlockResponse [AnyChainBlock]@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.vec'block' @:: Lens' FetchBlockResponse (Data.Vector.Vector AnyChainBlock)@ -} +data FetchBlockResponse + = FetchBlockResponse'_constructor {_FetchBlockResponse'block :: !(Data.Vector.Vector AnyChainBlock), + _FetchBlockResponse'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show FetchBlockResponse where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +instance Data.ProtoLens.Field.HasField FetchBlockResponse "block" [AnyChainBlock] where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FetchBlockResponse'block + (\ x__ y__ -> x__ {_FetchBlockResponse'block = y__})) + (Lens.Family2.Unchecked.lens + Data.Vector.Generic.toList + (\ _ y__ -> Data.Vector.Generic.fromList y__)) +instance Data.ProtoLens.Field.HasField FetchBlockResponse "vec'block" (Data.Vector.Vector AnyChainBlock) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FetchBlockResponse'block + (\ x__ y__ -> x__ {_FetchBlockResponse'block = y__})) + Prelude.id +instance Data.ProtoLens.Message FetchBlockResponse where + messageName _ + = Data.Text.pack "utxorpc.v1beta.sync.FetchBlockResponse" + packedMessageDescriptor _ + = "\n\ + \\DC2FetchBlockResponse\DC28\n\ + \\ENQblock\CAN\SOH \ETX(\v2\".utxorpc.v1beta.sync.AnyChainBlockR\ENQblock" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag + = let + block__field_descriptor + = Data.ProtoLens.FieldDescriptor + "block" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor AnyChainBlock) + (Data.ProtoLens.RepeatedField + Data.ProtoLens.Unpacked (Data.ProtoLens.Field.field @"block")) :: + Data.ProtoLens.FieldDescriptor FetchBlockResponse + in + Data.Map.fromList [(Data.ProtoLens.Tag 1, block__field_descriptor)] + unknownFields + = Lens.Family2.Unchecked.lens + _FetchBlockResponse'_unknownFields + (\ x__ y__ -> x__ {_FetchBlockResponse'_unknownFields = y__}) + defMessage + = FetchBlockResponse'_constructor + {_FetchBlockResponse'block = Data.Vector.Generic.empty, + _FetchBlockResponse'_unknownFields = []} + parseMessage + = let + loop :: + FetchBlockResponse + -> Data.ProtoLens.Encoding.Growing.Growing Data.Vector.Vector Data.ProtoLens.Encoding.Growing.RealWorld AnyChainBlock + -> Data.ProtoLens.Encoding.Bytes.Parser FetchBlockResponse + loop x mutable'block + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do frozen'block <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + (Data.ProtoLens.Encoding.Growing.unsafeFreeze mutable'block) + (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) + (Lens.Family2.set + (Data.ProtoLens.Field.field @"vec'block") frozen'block x)) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + 10 + -> do !y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) + Data.ProtoLens.parseMessage) + "block" + v <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + (Data.ProtoLens.Encoding.Growing.append mutable'block y) + loop x v + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + mutable'block + in + (Data.ProtoLens.Encoding.Bytes.) + (do mutable'block <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + Data.ProtoLens.Encoding.Growing.new + loop Data.ProtoLens.defMessage mutable'block) + "FetchBlockResponse" + buildMessage + = \ _x + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.foldMapBuilder + (\ _v + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 10) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + (Lens.Family2.view (Data.ProtoLens.Field.field @"vec'block") _x)) + (Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x)) +instance Control.DeepSeq.NFData FetchBlockResponse where + rnf + = \ x__ + -> Control.DeepSeq.deepseq + (_FetchBlockResponse'_unknownFields x__) + (Control.DeepSeq.deepseq (_FetchBlockResponse'block x__) ()) +{- | Fields : + + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.intersect' @:: Lens' FollowTipRequest [BlockRef]@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.vec'intersect' @:: Lens' FollowTipRequest (Data.Vector.Vector BlockRef)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.fieldMask' @:: Lens' FollowTipRequest Proto.Google.Protobuf.FieldMask.FieldMask@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'fieldMask' @:: Lens' FollowTipRequest (Prelude.Maybe Proto.Google.Protobuf.FieldMask.FieldMask)@ -} +data FollowTipRequest + = FollowTipRequest'_constructor {_FollowTipRequest'intersect :: !(Data.Vector.Vector BlockRef), + _FollowTipRequest'fieldMask :: !(Prelude.Maybe Proto.Google.Protobuf.FieldMask.FieldMask), + _FollowTipRequest'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show FollowTipRequest where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +instance Data.ProtoLens.Field.HasField FollowTipRequest "intersect" [BlockRef] where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipRequest'intersect + (\ x__ y__ -> x__ {_FollowTipRequest'intersect = y__})) + (Lens.Family2.Unchecked.lens + Data.Vector.Generic.toList + (\ _ y__ -> Data.Vector.Generic.fromList y__)) +instance Data.ProtoLens.Field.HasField FollowTipRequest "vec'intersect" (Data.Vector.Vector BlockRef) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipRequest'intersect + (\ x__ y__ -> x__ {_FollowTipRequest'intersect = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField FollowTipRequest "fieldMask" Proto.Google.Protobuf.FieldMask.FieldMask where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipRequest'fieldMask + (\ x__ y__ -> x__ {_FollowTipRequest'fieldMask = y__})) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage) +instance Data.ProtoLens.Field.HasField FollowTipRequest "maybe'fieldMask" (Prelude.Maybe Proto.Google.Protobuf.FieldMask.FieldMask) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipRequest'fieldMask + (\ x__ y__ -> x__ {_FollowTipRequest'fieldMask = y__})) + Prelude.id +instance Data.ProtoLens.Message FollowTipRequest where + messageName _ + = Data.Text.pack "utxorpc.v1beta.sync.FollowTipRequest" + packedMessageDescriptor _ + = "\n\ + \\DLEFollowTipRequest\DC2;\n\ + \\tintersect\CAN\SOH \ETX(\v2\GS.utxorpc.v1beta.sync.BlockRefR\tintersect\DC29\n\ + \\n\ + \field_mask\CAN\STX \SOH(\v2\SUB.google.protobuf.FieldMaskR\tfieldMask" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag + = let + intersect__field_descriptor + = Data.ProtoLens.FieldDescriptor + "intersect" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor BlockRef) + (Data.ProtoLens.RepeatedField + Data.ProtoLens.Unpacked + (Data.ProtoLens.Field.field @"intersect")) :: + Data.ProtoLens.FieldDescriptor FollowTipRequest + fieldMask__field_descriptor + = Data.ProtoLens.FieldDescriptor + "field_mask" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor Proto.Google.Protobuf.FieldMask.FieldMask) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'fieldMask")) :: + Data.ProtoLens.FieldDescriptor FollowTipRequest + in + Data.Map.fromList + [(Data.ProtoLens.Tag 1, intersect__field_descriptor), + (Data.ProtoLens.Tag 2, fieldMask__field_descriptor)] + unknownFields + = Lens.Family2.Unchecked.lens + _FollowTipRequest'_unknownFields + (\ x__ y__ -> x__ {_FollowTipRequest'_unknownFields = y__}) + defMessage + = FollowTipRequest'_constructor + {_FollowTipRequest'intersect = Data.Vector.Generic.empty, + _FollowTipRequest'fieldMask = Prelude.Nothing, + _FollowTipRequest'_unknownFields = []} + parseMessage + = let + loop :: + FollowTipRequest + -> Data.ProtoLens.Encoding.Growing.Growing Data.Vector.Vector Data.ProtoLens.Encoding.Growing.RealWorld BlockRef + -> Data.ProtoLens.Encoding.Bytes.Parser FollowTipRequest + loop x mutable'intersect + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do frozen'intersect <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + (Data.ProtoLens.Encoding.Growing.unsafeFreeze + mutable'intersect) + (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) + (Lens.Family2.set + (Data.ProtoLens.Field.field @"vec'intersect") frozen'intersect x)) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + 10 + -> do !y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) + Data.ProtoLens.parseMessage) + "intersect" + v <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + (Data.ProtoLens.Encoding.Growing.append mutable'intersect y) + loop x v + 18 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "field_mask" + loop + (Lens.Family2.set (Data.ProtoLens.Field.field @"fieldMask") y x) + mutable'intersect + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + mutable'intersect + in + (Data.ProtoLens.Encoding.Bytes.) + (do mutable'intersect <- Data.ProtoLens.Encoding.Parser.Unsafe.unsafeLiftIO + Data.ProtoLens.Encoding.Growing.new + loop Data.ProtoLens.defMessage mutable'intersect) + "FollowTipRequest" + buildMessage + = \ _x + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.foldMapBuilder + (\ _v + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 10) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + (Lens.Family2.view + (Data.ProtoLens.Field.field @"vec'intersect") _x)) + ((Data.Monoid.<>) + (case + Lens.Family2.view + (Data.ProtoLens.Field.field @"maybe'fieldMask") _x + of + Prelude.Nothing -> Data.Monoid.mempty + (Prelude.Just _v) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 18) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + (Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x))) +instance Control.DeepSeq.NFData FollowTipRequest where + rnf + = \ x__ + -> Control.DeepSeq.deepseq + (_FollowTipRequest'_unknownFields x__) + (Control.DeepSeq.deepseq + (_FollowTipRequest'intersect x__) + (Control.DeepSeq.deepseq (_FollowTipRequest'fieldMask x__) ())) +{- | Fields : + + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.tip' @:: Lens' FollowTipResponse BlockRef@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'tip' @:: Lens' FollowTipResponse (Prelude.Maybe BlockRef)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'action' @:: Lens' FollowTipResponse (Prelude.Maybe FollowTipResponse'Action)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'apply' @:: Lens' FollowTipResponse (Prelude.Maybe AnyChainBlock)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.apply' @:: Lens' FollowTipResponse AnyChainBlock@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'undo' @:: Lens' FollowTipResponse (Prelude.Maybe AnyChainBlock)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.undo' @:: Lens' FollowTipResponse AnyChainBlock@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'reset' @:: Lens' FollowTipResponse (Prelude.Maybe BlockRef)@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.reset' @:: Lens' FollowTipResponse BlockRef@ -} +data FollowTipResponse + = FollowTipResponse'_constructor {_FollowTipResponse'tip :: !(Prelude.Maybe BlockRef), + _FollowTipResponse'action :: !(Prelude.Maybe FollowTipResponse'Action), + _FollowTipResponse'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show FollowTipResponse where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +data FollowTipResponse'Action + = FollowTipResponse'Apply !AnyChainBlock | + FollowTipResponse'Undo !AnyChainBlock | + FollowTipResponse'Reset !BlockRef + deriving stock (Prelude.Show, Prelude.Eq, Prelude.Ord) +instance Data.ProtoLens.Field.HasField FollowTipResponse "tip" BlockRef where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipResponse'tip + (\ x__ y__ -> x__ {_FollowTipResponse'tip = y__})) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage) +instance Data.ProtoLens.Field.HasField FollowTipResponse "maybe'tip" (Prelude.Maybe BlockRef) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipResponse'tip + (\ x__ y__ -> x__ {_FollowTipResponse'tip = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField FollowTipResponse "maybe'action" (Prelude.Maybe FollowTipResponse'Action) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipResponse'action + (\ x__ y__ -> x__ {_FollowTipResponse'action = y__})) + Prelude.id +instance Data.ProtoLens.Field.HasField FollowTipResponse "maybe'apply" (Prelude.Maybe AnyChainBlock) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipResponse'action + (\ x__ y__ -> x__ {_FollowTipResponse'action = y__})) + (Lens.Family2.Unchecked.lens + (\ x__ + -> case x__ of + (Prelude.Just (FollowTipResponse'Apply x__val)) + -> Prelude.Just x__val + _otherwise -> Prelude.Nothing) + (\ _ y__ -> Prelude.fmap FollowTipResponse'Apply y__)) +instance Data.ProtoLens.Field.HasField FollowTipResponse "apply" AnyChainBlock where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipResponse'action + (\ x__ y__ -> x__ {_FollowTipResponse'action = y__})) + ((Prelude..) + (Lens.Family2.Unchecked.lens + (\ x__ + -> case x__ of + (Prelude.Just (FollowTipResponse'Apply x__val)) + -> Prelude.Just x__val + _otherwise -> Prelude.Nothing) + (\ _ y__ -> Prelude.fmap FollowTipResponse'Apply y__)) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage)) +instance Data.ProtoLens.Field.HasField FollowTipResponse "maybe'undo" (Prelude.Maybe AnyChainBlock) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipResponse'action + (\ x__ y__ -> x__ {_FollowTipResponse'action = y__})) + (Lens.Family2.Unchecked.lens + (\ x__ + -> case x__ of + (Prelude.Just (FollowTipResponse'Undo x__val)) + -> Prelude.Just x__val + _otherwise -> Prelude.Nothing) + (\ _ y__ -> Prelude.fmap FollowTipResponse'Undo y__)) +instance Data.ProtoLens.Field.HasField FollowTipResponse "undo" AnyChainBlock where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipResponse'action + (\ x__ y__ -> x__ {_FollowTipResponse'action = y__})) + ((Prelude..) + (Lens.Family2.Unchecked.lens + (\ x__ + -> case x__ of + (Prelude.Just (FollowTipResponse'Undo x__val)) + -> Prelude.Just x__val + _otherwise -> Prelude.Nothing) + (\ _ y__ -> Prelude.fmap FollowTipResponse'Undo y__)) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage)) +instance Data.ProtoLens.Field.HasField FollowTipResponse "maybe'reset" (Prelude.Maybe BlockRef) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipResponse'action + (\ x__ y__ -> x__ {_FollowTipResponse'action = y__})) + (Lens.Family2.Unchecked.lens + (\ x__ + -> case x__ of + (Prelude.Just (FollowTipResponse'Reset x__val)) + -> Prelude.Just x__val + _otherwise -> Prelude.Nothing) + (\ _ y__ -> Prelude.fmap FollowTipResponse'Reset y__)) +instance Data.ProtoLens.Field.HasField FollowTipResponse "reset" BlockRef where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _FollowTipResponse'action + (\ x__ y__ -> x__ {_FollowTipResponse'action = y__})) + ((Prelude..) + (Lens.Family2.Unchecked.lens + (\ x__ + -> case x__ of + (Prelude.Just (FollowTipResponse'Reset x__val)) + -> Prelude.Just x__val + _otherwise -> Prelude.Nothing) + (\ _ y__ -> Prelude.fmap FollowTipResponse'Reset y__)) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage)) +instance Data.ProtoLens.Message FollowTipResponse where + messageName _ + = Data.Text.pack "utxorpc.v1beta.sync.FollowTipResponse" + packedMessageDescriptor _ + = "\n\ + \\DC1FollowTipResponse\DC2:\n\ + \\ENQapply\CAN\SOH \SOH(\v2\".utxorpc.v1beta.sync.AnyChainBlockH\NULR\ENQapply\DC28\n\ + \\EOTundo\CAN\STX \SOH(\v2\".utxorpc.v1beta.sync.AnyChainBlockH\NULR\EOTundo\DC25\n\ + \\ENQreset\CAN\ETX \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefH\NULR\ENQreset\DC2/\n\ + \\ETXtip\CAN\EOT \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefR\ETXtipB\b\n\ + \\ACKaction" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag + = let + tip__field_descriptor + = Data.ProtoLens.FieldDescriptor + "tip" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor BlockRef) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'tip")) :: + Data.ProtoLens.FieldDescriptor FollowTipResponse + apply__field_descriptor + = Data.ProtoLens.FieldDescriptor + "apply" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor AnyChainBlock) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'apply")) :: + Data.ProtoLens.FieldDescriptor FollowTipResponse + undo__field_descriptor + = Data.ProtoLens.FieldDescriptor + "undo" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor AnyChainBlock) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'undo")) :: + Data.ProtoLens.FieldDescriptor FollowTipResponse + reset__field_descriptor + = Data.ProtoLens.FieldDescriptor + "reset" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor BlockRef) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'reset")) :: + Data.ProtoLens.FieldDescriptor FollowTipResponse + in + Data.Map.fromList + [(Data.ProtoLens.Tag 4, tip__field_descriptor), + (Data.ProtoLens.Tag 1, apply__field_descriptor), + (Data.ProtoLens.Tag 2, undo__field_descriptor), + (Data.ProtoLens.Tag 3, reset__field_descriptor)] + unknownFields + = Lens.Family2.Unchecked.lens + _FollowTipResponse'_unknownFields + (\ x__ y__ -> x__ {_FollowTipResponse'_unknownFields = y__}) + defMessage + = FollowTipResponse'_constructor + {_FollowTipResponse'tip = Prelude.Nothing, + _FollowTipResponse'action = Prelude.Nothing, + _FollowTipResponse'_unknownFields = []} + parseMessage + = let + loop :: + FollowTipResponse + -> Data.ProtoLens.Encoding.Bytes.Parser FollowTipResponse + loop x + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) x) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + 34 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "tip" + loop (Lens.Family2.set (Data.ProtoLens.Field.field @"tip") y x) + 10 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "apply" + loop (Lens.Family2.set (Data.ProtoLens.Field.field @"apply") y x) + 18 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "undo" + loop (Lens.Family2.set (Data.ProtoLens.Field.field @"undo") y x) + 26 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "reset" + loop (Lens.Family2.set (Data.ProtoLens.Field.field @"reset") y x) + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + in + (Data.ProtoLens.Encoding.Bytes.) + (do loop Data.ProtoLens.defMessage) "FollowTipResponse" + buildMessage + = \ _x + -> (Data.Monoid.<>) + (case + Lens.Family2.view (Data.ProtoLens.Field.field @"maybe'tip") _x + of + Prelude.Nothing -> Data.Monoid.mempty + (Prelude.Just _v) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 34) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + ((Data.Monoid.<>) + (case + Lens.Family2.view (Data.ProtoLens.Field.field @"maybe'action") _x + of + Prelude.Nothing -> Data.Monoid.mempty + (Prelude.Just (FollowTipResponse'Apply v)) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 10) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage v) + (Prelude.Just (FollowTipResponse'Undo v)) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 18) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage v) + (Prelude.Just (FollowTipResponse'Reset v)) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 26) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage v)) + (Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x))) +instance Control.DeepSeq.NFData FollowTipResponse where + rnf + = \ x__ + -> Control.DeepSeq.deepseq + (_FollowTipResponse'_unknownFields x__) + (Control.DeepSeq.deepseq + (_FollowTipResponse'tip x__) + (Control.DeepSeq.deepseq (_FollowTipResponse'action x__) ())) +instance Control.DeepSeq.NFData FollowTipResponse'Action where + rnf (FollowTipResponse'Apply x__) = Control.DeepSeq.rnf x__ + rnf (FollowTipResponse'Undo x__) = Control.DeepSeq.rnf x__ + rnf (FollowTipResponse'Reset x__) = Control.DeepSeq.rnf x__ +_FollowTipResponse'Apply :: + Data.ProtoLens.Prism.Prism' FollowTipResponse'Action AnyChainBlock +_FollowTipResponse'Apply + = Data.ProtoLens.Prism.prism' + FollowTipResponse'Apply + (\ p__ + -> case p__ of + (FollowTipResponse'Apply p__val) -> Prelude.Just p__val + _otherwise -> Prelude.Nothing) +_FollowTipResponse'Undo :: + Data.ProtoLens.Prism.Prism' FollowTipResponse'Action AnyChainBlock +_FollowTipResponse'Undo + = Data.ProtoLens.Prism.prism' + FollowTipResponse'Undo + (\ p__ + -> case p__ of + (FollowTipResponse'Undo p__val) -> Prelude.Just p__val + _otherwise -> Prelude.Nothing) +_FollowTipResponse'Reset :: + Data.ProtoLens.Prism.Prism' FollowTipResponse'Action BlockRef +_FollowTipResponse'Reset + = Data.ProtoLens.Prism.prism' + FollowTipResponse'Reset + (\ p__ + -> case p__ of + (FollowTipResponse'Reset p__val) -> Prelude.Just p__val + _otherwise -> Prelude.Nothing) +{- | Fields : + -} +data ReadTipRequest + = ReadTipRequest'_constructor {_ReadTipRequest'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show ReadTipRequest where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +instance Data.ProtoLens.Message ReadTipRequest where + messageName _ = Data.Text.pack "utxorpc.v1beta.sync.ReadTipRequest" + packedMessageDescriptor _ + = "\n\ + \\SOReadTipRequest" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag = let in Data.Map.fromList [] + unknownFields + = Lens.Family2.Unchecked.lens + _ReadTipRequest'_unknownFields + (\ x__ y__ -> x__ {_ReadTipRequest'_unknownFields = y__}) + defMessage + = ReadTipRequest'_constructor {_ReadTipRequest'_unknownFields = []} + parseMessage + = let + loop :: + ReadTipRequest + -> Data.ProtoLens.Encoding.Bytes.Parser ReadTipRequest + loop x + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) x) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + in + (Data.ProtoLens.Encoding.Bytes.) + (do loop Data.ProtoLens.defMessage) "ReadTipRequest" + buildMessage + = \ _x + -> Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x) +instance Control.DeepSeq.NFData ReadTipRequest where + rnf + = \ x__ + -> Control.DeepSeq.deepseq (_ReadTipRequest'_unknownFields x__) () +{- | Fields : + + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.tip' @:: Lens' ReadTipResponse BlockRef@ + * 'Proto.Utxorpc.V1beta.Sync.Sync_Fields.maybe'tip' @:: Lens' ReadTipResponse (Prelude.Maybe BlockRef)@ -} +data ReadTipResponse + = ReadTipResponse'_constructor {_ReadTipResponse'tip :: !(Prelude.Maybe BlockRef), + _ReadTipResponse'_unknownFields :: !Data.ProtoLens.FieldSet} + deriving stock (Prelude.Eq, Prelude.Ord) +instance Prelude.Show ReadTipResponse where + showsPrec _ __x __s + = Prelude.showChar + '{' + (Prelude.showString + (Data.ProtoLens.showMessageShort __x) (Prelude.showChar '}' __s)) +instance Data.ProtoLens.Field.HasField ReadTipResponse "tip" BlockRef where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _ReadTipResponse'tip + (\ x__ y__ -> x__ {_ReadTipResponse'tip = y__})) + (Data.ProtoLens.maybeLens Data.ProtoLens.defMessage) +instance Data.ProtoLens.Field.HasField ReadTipResponse "maybe'tip" (Prelude.Maybe BlockRef) where + fieldOf _ + = (Prelude..) + (Lens.Family2.Unchecked.lens + _ReadTipResponse'tip + (\ x__ y__ -> x__ {_ReadTipResponse'tip = y__})) + Prelude.id +instance Data.ProtoLens.Message ReadTipResponse where + messageName _ + = Data.Text.pack "utxorpc.v1beta.sync.ReadTipResponse" + packedMessageDescriptor _ + = "\n\ + \\SIReadTipResponse\DC2/\n\ + \\ETXtip\CAN\SOH \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefR\ETXtip" + packedFileDescriptor _ = packedFileDescriptor + fieldsByTag + = let + tip__field_descriptor + = Data.ProtoLens.FieldDescriptor + "tip" + (Data.ProtoLens.MessageField Data.ProtoLens.MessageType :: + Data.ProtoLens.FieldTypeDescriptor BlockRef) + (Data.ProtoLens.OptionalField + (Data.ProtoLens.Field.field @"maybe'tip")) :: + Data.ProtoLens.FieldDescriptor ReadTipResponse + in + Data.Map.fromList [(Data.ProtoLens.Tag 1, tip__field_descriptor)] + unknownFields + = Lens.Family2.Unchecked.lens + _ReadTipResponse'_unknownFields + (\ x__ y__ -> x__ {_ReadTipResponse'_unknownFields = y__}) + defMessage + = ReadTipResponse'_constructor + {_ReadTipResponse'tip = Prelude.Nothing, + _ReadTipResponse'_unknownFields = []} + parseMessage + = let + loop :: + ReadTipResponse + -> Data.ProtoLens.Encoding.Bytes.Parser ReadTipResponse + loop x + = do end <- Data.ProtoLens.Encoding.Bytes.atEnd + if end then + do (let missing = [] + in + if Prelude.null missing then + Prelude.return () + else + Prelude.fail + ((Prelude.++) + "Missing required fields: " + (Prelude.show (missing :: [Prelude.String])))) + Prelude.return + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> Prelude.reverse t) x) + else + do tag <- Data.ProtoLens.Encoding.Bytes.getVarInt + case tag of + 10 + -> do y <- (Data.ProtoLens.Encoding.Bytes.) + (do len <- Data.ProtoLens.Encoding.Bytes.getVarInt + Data.ProtoLens.Encoding.Bytes.isolate + (Prelude.fromIntegral len) Data.ProtoLens.parseMessage) + "tip" + loop (Lens.Family2.set (Data.ProtoLens.Field.field @"tip") y x) + wire + -> do !y <- Data.ProtoLens.Encoding.Wire.parseTaggedValueFromWire + wire + loop + (Lens.Family2.over + Data.ProtoLens.unknownFields (\ !t -> (:) y t) x) + in + (Data.ProtoLens.Encoding.Bytes.) + (do loop Data.ProtoLens.defMessage) "ReadTipResponse" + buildMessage + = \ _x + -> (Data.Monoid.<>) + (case + Lens.Family2.view (Data.ProtoLens.Field.field @"maybe'tip") _x + of + Prelude.Nothing -> Data.Monoid.mempty + (Prelude.Just _v) + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt 10) + ((Prelude..) + (\ bs + -> (Data.Monoid.<>) + (Data.ProtoLens.Encoding.Bytes.putVarInt + (Prelude.fromIntegral (Data.ByteString.length bs))) + (Data.ProtoLens.Encoding.Bytes.putBytes bs)) + Data.ProtoLens.encodeMessage _v)) + (Data.ProtoLens.Encoding.Wire.buildFieldSet + (Lens.Family2.view Data.ProtoLens.unknownFields _x)) +instance Control.DeepSeq.NFData ReadTipResponse where + rnf + = \ x__ + -> Control.DeepSeq.deepseq + (_ReadTipResponse'_unknownFields x__) + (Control.DeepSeq.deepseq (_ReadTipResponse'tip x__) ()) +data SyncService = SyncService {} +instance Data.ProtoLens.Service.Types.Service SyncService where + type ServiceName SyncService = "SyncService" + type ServicePackage SyncService = "utxorpc.v1beta.sync" + type ServiceMethods SyncService = '["dumpHistory", + "fetchBlock", + "followTip", + "readTip"] + packedServiceDescriptor _ + = "\n\ + \\vSyncService\DC2]\n\ + \\n\ + \FetchBlock\DC2&.utxorpc.v1beta.sync.FetchBlockRequest\SUB'.utxorpc.v1beta.sync.FetchBlockResponse\DC2`\n\ + \\vDumpHistory\DC2'.utxorpc.v1beta.sync.DumpHistoryRequest\SUB(.utxorpc.v1beta.sync.DumpHistoryResponse\DC2\\\n\ + \\tFollowTip\DC2%.utxorpc.v1beta.sync.FollowTipRequest\SUB&.utxorpc.v1beta.sync.FollowTipResponse0\SOH\DC2T\n\ + \\aReadTip\DC2#.utxorpc.v1beta.sync.ReadTipRequest\SUB$.utxorpc.v1beta.sync.ReadTipResponse" +instance Data.ProtoLens.Service.Types.HasMethodImpl SyncService "fetchBlock" where + type MethodName SyncService "fetchBlock" = "FetchBlock" + type MethodInput SyncService "fetchBlock" = FetchBlockRequest + type MethodOutput SyncService "fetchBlock" = FetchBlockResponse + type MethodStreamingType SyncService "fetchBlock" = 'Data.ProtoLens.Service.Types.NonStreaming +instance Data.ProtoLens.Service.Types.HasMethodImpl SyncService "dumpHistory" where + type MethodName SyncService "dumpHistory" = "DumpHistory" + type MethodInput SyncService "dumpHistory" = DumpHistoryRequest + type MethodOutput SyncService "dumpHistory" = DumpHistoryResponse + type MethodStreamingType SyncService "dumpHistory" = 'Data.ProtoLens.Service.Types.NonStreaming +instance Data.ProtoLens.Service.Types.HasMethodImpl SyncService "followTip" where + type MethodName SyncService "followTip" = "FollowTip" + type MethodInput SyncService "followTip" = FollowTipRequest + type MethodOutput SyncService "followTip" = FollowTipResponse + type MethodStreamingType SyncService "followTip" = 'Data.ProtoLens.Service.Types.ServerStreaming +instance Data.ProtoLens.Service.Types.HasMethodImpl SyncService "readTip" where + type MethodName SyncService "readTip" = "ReadTip" + type MethodInput SyncService "readTip" = ReadTipRequest + type MethodOutput SyncService "readTip" = ReadTipResponse + type MethodStreamingType SyncService "readTip" = 'Data.ProtoLens.Service.Types.NonStreaming +packedFileDescriptor :: Data.ByteString.ByteString +packedFileDescriptor + = "\n\ + \\RSutxorpc/v1beta/sync/sync.proto\DC2\DC3utxorpc.v1beta.sync\SUB google/protobuf/field_mask.proto\SUB$utxorpc/v1beta/cardano/cardano.proto\"h\n\ + \\bBlockRef\DC2\DC2\n\ + \\EOTslot\CAN\SOH \SOH(\EOTR\EOTslot\DC2\DC2\n\ + \\EOThash\CAN\STX \SOH(\fR\EOThash\DC2\SYN\n\ + \\ACKheight\CAN\ETX \SOH(\EOTR\ACKheight\DC2\FS\n\ + \\ttimestamp\CAN\EOT \SOH(\EOTR\ttimestamp\"v\n\ + \\rAnyChainBlock\DC2!\n\ + \\fnative_bytes\CAN\SOH \SOH(\fR\vnativeBytes\DC29\n\ + \\acardano\CAN\STX \SOH(\v2\GS.utxorpc.v1beta.cardano.BlockH\NULR\acardanoB\a\n\ + \\ENQchain\"\DEL\n\ + \\DC1FetchBlockRequest\DC2/\n\ + \\ETXref\CAN\SOH \ETX(\v2\GS.utxorpc.v1beta.sync.BlockRefR\ETXref\DC29\n\ + \\n\ + \field_mask\CAN\STX \SOH(\v2\SUB.google.protobuf.FieldMaskR\tfieldMask\"N\n\ + \\DC2FetchBlockResponse\DC28\n\ + \\ENQblock\CAN\SOH \ETX(\v2\".utxorpc.v1beta.sync.AnyChainBlockR\ENQblock\"\172\SOH\n\ + \\DC2DumpHistoryRequest\DC2>\n\ + \\vstart_token\CAN\STX \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefR\n\ + \startToken\DC2\ESC\n\ + \\tmax_items\CAN\ETX \SOH(\rR\bmaxItems\DC29\n\ + \\n\ + \field_mask\CAN\EOT \SOH(\v2\SUB.google.protobuf.FieldMaskR\tfieldMask\"\141\SOH\n\ + \\DC3DumpHistoryResponse\DC28\n\ + \\ENQblock\CAN\SOH \ETX(\v2\".utxorpc.v1beta.sync.AnyChainBlockR\ENQblock\DC2<\n\ + \\n\ + \next_token\CAN\STX \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefR\tnextToken\"\138\SOH\n\ + \\DLEFollowTipRequest\DC2;\n\ + \\tintersect\CAN\SOH \ETX(\v2\GS.utxorpc.v1beta.sync.BlockRefR\tintersect\DC29\n\ + \\n\ + \field_mask\CAN\STX \SOH(\v2\SUB.google.protobuf.FieldMaskR\tfieldMask\"\251\SOH\n\ + \\DC1FollowTipResponse\DC2:\n\ + \\ENQapply\CAN\SOH \SOH(\v2\".utxorpc.v1beta.sync.AnyChainBlockH\NULR\ENQapply\DC28\n\ + \\EOTundo\CAN\STX \SOH(\v2\".utxorpc.v1beta.sync.AnyChainBlockH\NULR\EOTundo\DC25\n\ + \\ENQreset\CAN\ETX \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefH\NULR\ENQreset\DC2/\n\ + \\ETXtip\CAN\EOT \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefR\ETXtipB\b\n\ + \\ACKaction\"\DLE\n\ + \\SOReadTipRequest\"B\n\ + \\SIReadTipResponse\DC2/\n\ + \\ETXtip\CAN\SOH \SOH(\v2\GS.utxorpc.v1beta.sync.BlockRefR\ETXtip2\130\ETX\n\ + \\vSyncService\DC2]\n\ + \\n\ + \FetchBlock\DC2&.utxorpc.v1beta.sync.FetchBlockRequest\SUB'.utxorpc.v1beta.sync.FetchBlockResponse\DC2`\n\ + \\vDumpHistory\DC2'.utxorpc.v1beta.sync.DumpHistoryRequest\SUB(.utxorpc.v1beta.sync.DumpHistoryResponse\DC2\\\n\ + \\tFollowTip\DC2%.utxorpc.v1beta.sync.FollowTipRequest\SUB&.utxorpc.v1beta.sync.FollowTipResponse0\SOH\DC2T\n\ + \\aReadTip\DC2#.utxorpc.v1beta.sync.ReadTipRequest\SUB$.utxorpc.v1beta.sync.ReadTipResponseB\146\SOH\n\ + \\ETBcom.utxorpc.v1beta.syncB\tSyncProtoP\SOH\162\STX\ETXUVS\170\STX\DC3Utxorpc.V1beta.Sync\202\STX\DC3Utxorpc\\V1beta\\Sync\226\STX\USUtxorpc\\V1beta\\Sync\\GPBMetadata\234\STX\NAKUtxorpc::V1beta::SyncJ\173\EM\n\ + \\ACK\DC2\EOT\NUL\NULL\SOH\n\ + \\b\n\ + \\SOH\f\DC2\ETX\NUL\NUL\DC2\n\ + \\b\n\ + \\SOH\STX\DC2\ETX\STX\NUL\FS\n\ + \\t\n\ + \\STX\ETX\NUL\DC2\ETX\EOT\NUL*\n\ + \\t\n\ + \\STX\ETX\SOH\DC2\ETX\ENQ\NUL.\n\ + \Z\n\ + \\STX\EOT\NUL\DC2\EOT\b\NUL\r\SOH\SUBN Represents a reference to a specific block by a chosen combination of fields\n\ + \\n\ + \\n\ + \\n\ + \\ETX\EOT\NUL\SOH\DC2\ETX\b\b\DLE\n\ + \B\n\ + \\EOT\EOT\NUL\STX\NUL\DC2\ETX\t\STX\DC2\"5 Height or slot number (depending on the blockchain)\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\NUL\ENQ\DC2\ETX\t\STX\b\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\NUL\SOH\DC2\ETX\t\t\r\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\NUL\ETX\DC2\ETX\t\DLE\DC1\n\ + \/\n\ + \\EOT\EOT\NUL\STX\SOH\DC2\ETX\n\ + \\STX\DC1\"\" Hash of the content of the block\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\SOH\ENQ\DC2\ETX\n\ + \\STX\a\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\SOH\SOH\DC2\ETX\n\ + \\b\f\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\SOH\ETX\DC2\ETX\n\ + \\SI\DLE\n\ + \\ESC\n\ + \\EOT\EOT\NUL\STX\STX\DC2\ETX\v\STX\DC4\"\SO Block height\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\STX\ENQ\DC2\ETX\v\STX\b\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\STX\SOH\DC2\ETX\v\t\SI\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\STX\ETX\DC2\ETX\v\DC2\DC3\n\ + \!\n\ + \\EOT\EOT\NUL\STX\ETX\DC2\ETX\f\STX\ETB\"\DC4 Block ms timestamp\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\ETX\ENQ\DC2\ETX\f\STX\b\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\ETX\SOH\DC2\ETX\f\t\DC2\n\ + \\f\n\ + \\ENQ\EOT\NUL\STX\ETX\ETX\DC2\ETX\f\NAK\SYN\n\ + \\n\ + \\n\ + \\STX\EOT\SOH\DC2\EOT\SI\NUL\DC4\SOH\n\ + \\n\ + \\n\ + \\ETX\EOT\SOH\SOH\DC2\ETX\SI\b\NAK\n\ + \5\n\ + \\EOT\EOT\SOH\STX\NUL\DC2\ETX\DLE\STX\EM\"( Original bytes as defined by the chain\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\SOH\STX\NUL\ENQ\DC2\ETX\DLE\STX\a\n\ + \\f\n\ + \\ENQ\EOT\SOH\STX\NUL\SOH\DC2\ETX\DLE\b\DC4\n\ + \\f\n\ + \\ENQ\EOT\SOH\STX\NUL\ETX\DC2\ETX\DLE\ETB\CAN\n\ + \\f\n\ + \\EOT\EOT\SOH\b\NUL\DC2\EOT\DC1\STX\DC3\ETX\n\ + \\f\n\ + \\ENQ\EOT\SOH\b\NUL\SOH\DC2\ETX\DC1\b\r\n\ + \&\n\ + \\EOT\EOT\SOH\STX\SOH\DC2\ETX\DC2\EOT-\"\EM A parsed Cardano block.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\SOH\STX\SOH\ACK\DC2\ETX\DC2\EOT \n\ + \\f\n\ + \\ENQ\EOT\SOH\STX\SOH\SOH\DC2\ETX\DC2!(\n\ + \\f\n\ + \\ENQ\EOT\SOH\STX\SOH\ETX\DC2\ETX\DC2+,\n\ + \8\n\ + \\STX\EOT\STX\DC2\EOT\ETB\NUL\SUB\SOH\SUB, Request to fetch a block by its reference.\n\ + \\n\ + \\n\ + \\n\ + \\ETX\EOT\STX\SOH\DC2\ETX\ETB\b\EM\n\ + \(\n\ + \\EOT\EOT\STX\STX\NUL\DC2\ETX\CAN\STX\FS\"\ESC List of block references.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\STX\STX\NUL\EOT\DC2\ETX\CAN\STX\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\STX\STX\NUL\ACK\DC2\ETX\CAN\v\DC3\n\ + \\f\n\ + \\ENQ\EOT\STX\STX\NUL\SOH\DC2\ETX\CAN\DC4\ETB\n\ + \\f\n\ + \\ENQ\EOT\STX\STX\NUL\ETX\DC2\ETX\CAN\SUB\ESC\n\ + \7\n\ + \\EOT\EOT\STX\STX\SOH\DC2\ETX\EM\STX+\"* Field mask to selectively return fields.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\STX\STX\SOH\ACK\DC2\ETX\EM\STX\ESC\n\ + \\f\n\ + \\ENQ\EOT\STX\STX\SOH\SOH\DC2\ETX\EM\FS&\n\ + \\f\n\ + \\ENQ\EOT\STX\STX\SOH\ETX\DC2\ETX\EM)*\n\ + \5\n\ + \\STX\EOT\ETX\DC2\EOT\GS\NUL\US\SOH\SUB) Response containing the fetched blocks.\n\ + \\n\ + \\n\ + \\n\ + \\ETX\EOT\ETX\SOH\DC2\ETX\GS\b\SUB\n\ + \&\n\ + \\EOT\EOT\ETX\STX\NUL\DC2\ETX\RS\STX#\"\EM List of fetched blocks.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\ETX\STX\NUL\EOT\DC2\ETX\RS\STX\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\ETX\STX\NUL\ACK\DC2\ETX\RS\v\CAN\n\ + \\f\n\ + \\ENQ\EOT\ETX\STX\NUL\SOH\DC2\ETX\RS\EM\RS\n\ + \\f\n\ + \\ENQ\EOT\ETX\STX\NUL\ETX\DC2\ETX\RS!\"\n\ + \0\n\ + \\STX\EOT\EOT\DC2\EOT\"\NUL&\SOH\SUB$ Request to dump the block history.\n\ + \\n\ + \\n\ + \\n\ + \\ETX\EOT\EOT\SOH\DC2\ETX\"\b\SUB\n\ + \9\n\ + \\EOT\EOT\EOT\STX\NUL\DC2\ETX#\STX\ESC\", Starting point for the block history dump.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\EOT\STX\NUL\ACK\DC2\ETX#\STX\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\EOT\STX\NUL\SOH\DC2\ETX#\v\SYN\n\ + \\f\n\ + \\ENQ\EOT\EOT\STX\NUL\ETX\DC2\ETX#\EM\SUB\n\ + \1\n\ + \\EOT\EOT\EOT\STX\SOH\DC2\ETX$\STX\ETB\"$ Maximum number of items to return.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\EOT\STX\SOH\ENQ\DC2\ETX$\STX\b\n\ + \\f\n\ + \\ENQ\EOT\EOT\STX\SOH\SOH\DC2\ETX$\t\DC2\n\ + \\f\n\ + \\ENQ\EOT\EOT\STX\SOH\ETX\DC2\ETX$\NAK\SYN\n\ + \7\n\ + \\EOT\EOT\EOT\STX\STX\DC2\ETX%\STX+\"* Field mask to selectively return fields.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\EOT\STX\STX\ACK\DC2\ETX%\STX\ESC\n\ + \\f\n\ + \\ENQ\EOT\EOT\STX\STX\SOH\DC2\ETX%\FS&\n\ + \\f\n\ + \\ENQ\EOT\EOT\STX\STX\ETX\DC2\ETX%)*\n\ + \;\n\ + \\STX\EOT\ENQ\DC2\EOT)\NUL,\SOH\SUB/ Response containing the dumped block history.\n\ + \\n\ + \\n\ + \\n\ + \\ETX\EOT\ENQ\SOH\DC2\ETX)\b\ESC\n\ + \-\n\ + \\EOT\EOT\ENQ\STX\NUL\DC2\ETX*\STX#\" List of blocks in the history.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\ENQ\STX\NUL\EOT\DC2\ETX*\STX\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\ENQ\STX\NUL\ACK\DC2\ETX*\v\CAN\n\ + \\f\n\ + \\ENQ\EOT\ENQ\STX\NUL\SOH\DC2\ETX*\EM\RS\n\ + \\f\n\ + \\ENQ\EOT\ENQ\STX\NUL\ETX\DC2\ETX*!\"\n\ + \)\n\ + \\EOT\EOT\ENQ\STX\SOH\DC2\ETX+\STX\SUB\"\FS Next token for pagination.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\ENQ\STX\SOH\ACK\DC2\ETX+\STX\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\ENQ\STX\SOH\SOH\DC2\ETX+\v\NAK\n\ + \\f\n\ + \\ENQ\EOT\ENQ\STX\SOH\ETX\DC2\ETX+\CAN\EM\n\ + \:\n\ + \\STX\EOT\ACK\DC2\EOT/\NUL2\SOH\SUB. Request to follow the tip of the blockchain.\n\ + \\n\ + \\n\ + \\n\ + \\ETX\EOT\ACK\SOH\DC2\ETX/\b\CAN\n\ + \A\n\ + \\EOT\EOT\ACK\STX\NUL\DC2\ETX0\STX\"\"4 List of block references to find the intersection.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\ACK\STX\NUL\EOT\DC2\ETX0\STX\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\ACK\STX\NUL\ACK\DC2\ETX0\v\DC3\n\ + \\f\n\ + \\ENQ\EOT\ACK\STX\NUL\SOH\DC2\ETX0\DC4\GS\n\ + \\f\n\ + \\ENQ\EOT\ACK\STX\NUL\ETX\DC2\ETX0 !\n\ + \7\n\ + \\EOT\EOT\ACK\STX\SOH\DC2\ETX1\STX+\"* Field mask to selectively return fields.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\ACK\STX\SOH\ACK\DC2\ETX1\STX\ESC\n\ + \\f\n\ + \\ENQ\EOT\ACK\STX\SOH\SOH\DC2\ETX1\FS&\n\ + \\f\n\ + \\ENQ\EOT\ACK\STX\SOH\ETX\DC2\ETX1)*\n\ + \P\n\ + \\STX\EOT\a\DC2\EOT5\NUL<\SOH\SUBD Response containing the action to perform while following the tip.\n\ + \\n\ + \\n\ + \\n\ + \\ETX\EOT\a\SOH\DC2\ETX5\b\EM\n\ + \\f\n\ + \\EOT\EOT\a\b\NUL\DC2\EOT6\STX:\ETX\n\ + \\f\n\ + \\ENQ\EOT\a\b\NUL\SOH\DC2\ETX6\b\SO\n\ + \ \n\ + \\EOT\EOT\a\STX\NUL\DC2\ETX7\EOT\FS\"\DC3 Apply this block.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\a\STX\NUL\ACK\DC2\ETX7\EOT\DC1\n\ + \\f\n\ + \\ENQ\EOT\a\STX\NUL\SOH\DC2\ETX7\DC2\ETB\n\ + \\f\n\ + \\ENQ\EOT\a\STX\NUL\ETX\DC2\ETX7\SUB\ESC\n\ + \\US\n\ + \\EOT\EOT\a\STX\SOH\DC2\ETX8\EOT\ESC\"\DC2 Undo this block.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\a\STX\SOH\ACK\DC2\ETX8\EOT\DC1\n\ + \\f\n\ + \\ENQ\EOT\a\STX\SOH\SOH\DC2\ETX8\DC2\SYN\n\ + \\f\n\ + \\ENQ\EOT\a\STX\SOH\ETX\DC2\ETX8\EM\SUB\n\ + \-\n\ + \\EOT\EOT\a\STX\STX\DC2\ETX9\EOT\ETB\" Reset to this block reference.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\a\STX\STX\ACK\DC2\ETX9\EOT\f\n\ + \\f\n\ + \\ENQ\EOT\a\STX\STX\SOH\DC2\ETX9\r\DC2\n\ + \\f\n\ + \\ENQ\EOT\a\STX\STX\ETX\DC2\ETX9\NAK\SYN\n\ + \C\n\ + \\EOT\EOT\a\STX\ETX\DC2\ETX;\STX\DC3\"6 The current tip of the blockchain after this action.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\a\STX\ETX\ACK\DC2\ETX;\STX\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\a\STX\ETX\SOH\DC2\ETX;\v\SO\n\ + \\f\n\ + \\ENQ\EOT\a\STX\ETX\ETX\DC2\ETX;\DC1\DC2\n\ + \?\n\ + \\STX\EOT\b\DC2\ETX?\NUL\EM\SUB4 Request to read the current tip of the blockchain.\n\ + \\n\ + \\n\ + \\n\ + \\ETX\EOT\b\SOH\DC2\ETX?\b\SYN\n\ + \D\n\ + \\STX\EOT\t\DC2\EOTB\NULD\SOH\SUB8 Response containing the current tip of the blockchain.\n\ + \\n\ + \\n\ + \\n\ + \\ETX\EOT\t\SOH\DC2\ETXB\b\ETB\n\ + \1\n\ + \\EOT\EOT\t\STX\NUL\DC2\ETXC\STX\DC3\"$ The current tip of the blockchain.\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\t\STX\NUL\ACK\DC2\ETXC\STX\n\ + \\n\ + \\f\n\ + \\ENQ\EOT\t\STX\NUL\SOH\DC2\ETXC\v\SO\n\ + \\f\n\ + \\ENQ\EOT\t\STX\NUL\ETX\DC2\ETXC\DC1\DC2\n\ + \8\n\ + \\STX\ACK\NUL\DC2\EOTG\NULL\SOH\SUB, Service definition for syncing chain data.\n\ + \\n\ + \\n\ + \\n\ + \\ETX\ACK\NUL\SOH\DC2\ETXG\b\DC3\n\ + \.\n\ + \\EOT\ACK\NUL\STX\NUL\DC2\ETXH\STXA\"! Fetch a block by its reference.\n\ + \\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\NUL\SOH\DC2\ETXH\ACK\DLE\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\NUL\STX\DC2\ETXH\DC1\"\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\NUL\ETX\DC2\ETXH-?\n\ + \&\n\ + \\EOT\ACK\NUL\STX\SOH\DC2\ETXI\STXD\"\EM Dump the block history.\n\ + \\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\SOH\SOH\DC2\ETXI\ACK\DC1\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\SOH\STX\DC2\ETXI\DC2$\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\SOH\ETX\DC2\ETXI/B\n\ + \0\n\ + \\EOT\ACK\NUL\STX\STX\DC2\ETXJ\STXE\"# Follow the tip of the blockchain.\n\ + \\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\STX\SOH\DC2\ETXJ\ACK\SI\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\STX\STX\DC2\ETXJ\DLE \n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\STX\ACK\DC2\ETXJ+1\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\STX\ETX\DC2\ETXJ2C\n\ + \6\n\ + \\EOT\ACK\NUL\STX\ETX\DC2\ETXK\STX8\") Read the current tip of the blockchain.\n\ + \\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\ETX\SOH\DC2\ETXK\ACK\r\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\ETX\STX\DC2\ETXK\SO\FS\n\ + \\f\n\ + \\ENQ\ACK\NUL\STX\ETX\ETX\DC2\ETXK'6b\ACKproto3" \ No newline at end of file diff --git a/cardano-rpc/gen/Proto/Utxorpc/V1beta/Sync/Sync_Fields.hs b/cardano-rpc/gen/Proto/Utxorpc/V1beta/Sync/Sync_Fields.hs new file mode 100644 index 0000000000..697ab23c73 --- /dev/null +++ b/cardano-rpc/gen/Proto/Utxorpc/V1beta/Sync/Sync_Fields.hs @@ -0,0 +1,200 @@ +{- This file was auto-generated from utxorpc/v1beta/sync/sync.proto by the proto-lens-protoc program. -} +{-# LANGUAGE ScopedTypeVariables, DataKinds, TypeFamilies, UndecidableInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, FlexibleContexts, FlexibleInstances, PatternSynonyms, MagicHash, NoImplicitPrelude, DataKinds, BangPatterns, TypeApplications, OverloadedStrings, DerivingStrategies#-} +{-# OPTIONS_GHC -Wno-unused-imports#-} +{-# OPTIONS_GHC -Wno-duplicate-exports#-} +{-# OPTIONS_GHC -Wno-dodgy-exports#-} +module Proto.Utxorpc.V1beta.Sync.Sync_Fields where +import qualified Data.ProtoLens.Runtime.Prelude as Prelude +import qualified Data.ProtoLens.Runtime.Data.Int as Data.Int +import qualified Data.ProtoLens.Runtime.Data.Monoid as Data.Monoid +import qualified Data.ProtoLens.Runtime.Data.Word as Data.Word +import qualified Data.ProtoLens.Runtime.Data.ProtoLens as Data.ProtoLens +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Encoding.Bytes as Data.ProtoLens.Encoding.Bytes +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Encoding.Growing as Data.ProtoLens.Encoding.Growing +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Encoding.Parser.Unsafe as Data.ProtoLens.Encoding.Parser.Unsafe +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Encoding.Wire as Data.ProtoLens.Encoding.Wire +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Field as Data.ProtoLens.Field +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Message.Enum as Data.ProtoLens.Message.Enum +import qualified Data.ProtoLens.Runtime.Data.ProtoLens.Service.Types as Data.ProtoLens.Service.Types +import qualified Data.ProtoLens.Runtime.Lens.Family2 as Lens.Family2 +import qualified Data.ProtoLens.Runtime.Lens.Family2.Unchecked as Lens.Family2.Unchecked +import qualified Data.ProtoLens.Runtime.Data.Text as Data.Text +import qualified Data.ProtoLens.Runtime.Data.Map as Data.Map +import qualified Data.ProtoLens.Runtime.Data.ByteString as Data.ByteString +import qualified Data.ProtoLens.Runtime.Data.ByteString.Char8 as Data.ByteString.Char8 +import qualified Data.ProtoLens.Runtime.Data.Text.Encoding as Data.Text.Encoding +import qualified Data.ProtoLens.Runtime.Data.Vector as Data.Vector +import qualified Data.ProtoLens.Runtime.Data.Vector.Generic as Data.Vector.Generic +import qualified Data.ProtoLens.Runtime.Data.Vector.Unboxed as Data.Vector.Unboxed +import qualified Data.ProtoLens.Runtime.Text.Read as Text.Read +import qualified Proto.Google.Protobuf.FieldMask +import qualified Proto.Utxorpc.V1beta.Cardano.Cardano +apply :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "apply" a) => + Lens.Family2.LensLike' f s a +apply = Data.ProtoLens.Field.field @"apply" +block :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "block" a) => + Lens.Family2.LensLike' f s a +block = Data.ProtoLens.Field.field @"block" +cardano :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "cardano" a) => + Lens.Family2.LensLike' f s a +cardano = Data.ProtoLens.Field.field @"cardano" +fieldMask :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "fieldMask" a) => + Lens.Family2.LensLike' f s a +fieldMask = Data.ProtoLens.Field.field @"fieldMask" +hash :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "hash" a) => + Lens.Family2.LensLike' f s a +hash = Data.ProtoLens.Field.field @"hash" +height :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "height" a) => + Lens.Family2.LensLike' f s a +height = Data.ProtoLens.Field.field @"height" +intersect :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "intersect" a) => + Lens.Family2.LensLike' f s a +intersect = Data.ProtoLens.Field.field @"intersect" +maxItems :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maxItems" a) => + Lens.Family2.LensLike' f s a +maxItems = Data.ProtoLens.Field.field @"maxItems" +maybe'action :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'action" a) => + Lens.Family2.LensLike' f s a +maybe'action = Data.ProtoLens.Field.field @"maybe'action" +maybe'apply :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'apply" a) => + Lens.Family2.LensLike' f s a +maybe'apply = Data.ProtoLens.Field.field @"maybe'apply" +maybe'cardano :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'cardano" a) => + Lens.Family2.LensLike' f s a +maybe'cardano = Data.ProtoLens.Field.field @"maybe'cardano" +maybe'chain :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'chain" a) => + Lens.Family2.LensLike' f s a +maybe'chain = Data.ProtoLens.Field.field @"maybe'chain" +maybe'fieldMask :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'fieldMask" a) => + Lens.Family2.LensLike' f s a +maybe'fieldMask = Data.ProtoLens.Field.field @"maybe'fieldMask" +maybe'nextToken :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'nextToken" a) => + Lens.Family2.LensLike' f s a +maybe'nextToken = Data.ProtoLens.Field.field @"maybe'nextToken" +maybe'reset :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'reset" a) => + Lens.Family2.LensLike' f s a +maybe'reset = Data.ProtoLens.Field.field @"maybe'reset" +maybe'startToken :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'startToken" a) => + Lens.Family2.LensLike' f s a +maybe'startToken = Data.ProtoLens.Field.field @"maybe'startToken" +maybe'tip :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'tip" a) => + Lens.Family2.LensLike' f s a +maybe'tip = Data.ProtoLens.Field.field @"maybe'tip" +maybe'undo :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "maybe'undo" a) => + Lens.Family2.LensLike' f s a +maybe'undo = Data.ProtoLens.Field.field @"maybe'undo" +nativeBytes :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "nativeBytes" a) => + Lens.Family2.LensLike' f s a +nativeBytes = Data.ProtoLens.Field.field @"nativeBytes" +nextToken :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "nextToken" a) => + Lens.Family2.LensLike' f s a +nextToken = Data.ProtoLens.Field.field @"nextToken" +ref :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "ref" a) => + Lens.Family2.LensLike' f s a +ref = Data.ProtoLens.Field.field @"ref" +reset :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "reset" a) => + Lens.Family2.LensLike' f s a +reset = Data.ProtoLens.Field.field @"reset" +slot :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "slot" a) => + Lens.Family2.LensLike' f s a +slot = Data.ProtoLens.Field.field @"slot" +startToken :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "startToken" a) => + Lens.Family2.LensLike' f s a +startToken = Data.ProtoLens.Field.field @"startToken" +timestamp :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "timestamp" a) => + Lens.Family2.LensLike' f s a +timestamp = Data.ProtoLens.Field.field @"timestamp" +tip :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "tip" a) => + Lens.Family2.LensLike' f s a +tip = Data.ProtoLens.Field.field @"tip" +undo :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "undo" a) => + Lens.Family2.LensLike' f s a +undo = Data.ProtoLens.Field.field @"undo" +vec'block :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "vec'block" a) => + Lens.Family2.LensLike' f s a +vec'block = Data.ProtoLens.Field.field @"vec'block" +vec'intersect :: + forall f s a. + (Prelude.Functor f, + Data.ProtoLens.Field.HasField s "vec'intersect" a) => + Lens.Family2.LensLike' f s a +vec'intersect = Data.ProtoLens.Field.field @"vec'intersect" +vec'ref :: + forall f s a. + (Prelude.Functor f, Data.ProtoLens.Field.HasField s "vec'ref" a) => + Lens.Family2.LensLike' f s a +vec'ref = Data.ProtoLens.Field.field @"vec'ref" \ No newline at end of file diff --git a/cardano-rpc/proto/utxorpc/v1beta/sync/sync.proto b/cardano-rpc/proto/utxorpc/v1beta/sync/sync.proto new file mode 100644 index 0000000000..8ce911544a --- /dev/null +++ b/cardano-rpc/proto/utxorpc/v1beta/sync/sync.proto @@ -0,0 +1,77 @@ +syntax = "proto3"; + +package utxorpc.v1beta.sync; + +import "google/protobuf/field_mask.proto"; +import "utxorpc/v1beta/cardano/cardano.proto"; + +// Represents a reference to a specific block by a chosen combination of fields +message BlockRef { + uint64 slot = 1; // Height or slot number (depending on the blockchain) + bytes hash = 2; // Hash of the content of the block + uint64 height = 3; // Block height + uint64 timestamp = 4; // Block ms timestamp +} + +message AnyChainBlock { + bytes native_bytes = 1; // Original bytes as defined by the chain + oneof chain { + utxorpc.v1beta.cardano.Block cardano = 2; // A parsed Cardano block. + } +} + +// Request to fetch a block by its reference. +message FetchBlockRequest { + repeated BlockRef ref = 1; // List of block references. + google.protobuf.FieldMask field_mask = 2; // Field mask to selectively return fields. +} + +// Response containing the fetched blocks. +message FetchBlockResponse { + repeated AnyChainBlock block = 1; // List of fetched blocks. +} + +// Request to dump the block history. +message DumpHistoryRequest { + BlockRef start_token = 2; // Starting point for the block history dump. + uint32 max_items = 3; // Maximum number of items to return. + google.protobuf.FieldMask field_mask = 4; // Field mask to selectively return fields. +} + +// Response containing the dumped block history. +message DumpHistoryResponse { + repeated AnyChainBlock block = 1; // List of blocks in the history. + BlockRef next_token = 2; // Next token for pagination. +} + +// Request to follow the tip of the blockchain. +message FollowTipRequest { + repeated BlockRef intersect = 1; // List of block references to find the intersection. + google.protobuf.FieldMask field_mask = 2; // Field mask to selectively return fields. +} + +// Response containing the action to perform while following the tip. +message FollowTipResponse { + oneof action { + AnyChainBlock apply = 1; // Apply this block. + AnyChainBlock undo = 2; // Undo this block. + BlockRef reset = 3; // Reset to this block reference. + } + BlockRef tip = 4; // The current tip of the blockchain after this action. +} + +// Request to read the current tip of the blockchain. +message ReadTipRequest {} + +// Response containing the current tip of the blockchain. +message ReadTipResponse { + BlockRef tip = 1; // The current tip of the blockchain. +} + +// Service definition for syncing chain data. +service SyncService { + rpc FetchBlock(FetchBlockRequest) returns (FetchBlockResponse); // Fetch a block by its reference. + rpc DumpHistory(DumpHistoryRequest) returns (DumpHistoryResponse); // Dump the block history. + rpc FollowTip(FollowTipRequest) returns (stream FollowTipResponse); // Follow the tip of the blockchain. + rpc ReadTip(ReadTipRequest) returns (ReadTipResponse); // Read the current tip of the blockchain. +} diff --git a/cardano-rpc/src/Cardano/Rpc/Proto/Api/UtxoRpc/Sync.hs b/cardano-rpc/src/Cardano/Rpc/Proto/Api/UtxoRpc/Sync.hs new file mode 100644 index 0000000000..9da5248272 --- /dev/null +++ b/cardano-rpc/src/Cardano/Rpc/Proto/Api/UtxoRpc/Sync.hs @@ -0,0 +1,24 @@ +{-# LANGUAGE TypeFamilies #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +module Cardano.Rpc.Proto.Api.UtxoRpc.Sync + ( module Proto.Utxorpc.V1beta.Sync.Sync + , module Proto.Utxorpc.V1beta.Sync.Sync_Fields + , module Proto.Utxorpc.V1beta.Cardano.Cardano + , header + ) +where + +import Network.GRPC.Common +import Network.GRPC.Common.Protobuf + +import Proto.Utxorpc.V1beta.Cardano.Cardano +import Proto.Utxorpc.V1beta.Cardano.Cardano_Fields (header) +import Proto.Utxorpc.V1beta.Sync.Sync +import Proto.Utxorpc.V1beta.Sync.Sync_Fields + +type instance RequestMetadata (Protobuf SyncService meth) = NoMetadata + +type instance ResponseInitialMetadata (Protobuf SyncService meth) = NoMetadata + +type instance ResponseTrailingMetadata (Protobuf SyncService meth) = NoMetadata diff --git a/cardano-rpc/src/Cardano/Rpc/Server.hs b/cardano-rpc/src/Cardano/Rpc/Server.hs index b8c40cdc27..84b1120c69 100644 --- a/cardano-rpc/src/Cardano/Rpc/Server.hs +++ b/cardano-rpc/src/Cardano/Rpc/Server.hs @@ -15,6 +15,7 @@ module Cardano.Rpc.Server , TraceRpc (..) , TraceRpcSubmit (..) , TraceRpcQuery (..) + , TraceRpcSync (..) , TraceSpanEvent (..) ) where @@ -23,6 +24,7 @@ import Cardano.Api import Cardano.Rpc.Proto.Api.Node qualified as Rpc import Cardano.Rpc.Proto.Api.UtxoRpc.Query qualified as UtxoRpc import Cardano.Rpc.Proto.Api.UtxoRpc.Submit qualified as UtxoRpc +import Cardano.Rpc.Proto.Api.UtxoRpc.Sync qualified as UtxoRpc import Cardano.Rpc.Server.Config import Cardano.Rpc.Server.Internal.Env import Cardano.Rpc.Server.Internal.Monad @@ -32,6 +34,7 @@ import Cardano.Rpc.Server.Internal.Tracing import Cardano.Rpc.Server.Internal.UtxoRpc.Eval import Cardano.Rpc.Server.Internal.UtxoRpc.Query import Cardano.Rpc.Server.Internal.UtxoRpc.Submit +import Cardano.Rpc.Server.Internal.UtxoRpc.Sync import RIO @@ -70,15 +73,40 @@ methodsUtxoRpcSubmit = . Method (mkNonStreaming $ wrapInSpan TraceRpcSubmitSpan . submitTxMethod) $ NoMoreMethods +-- | gRPC method table for the UTxO RPC @SyncService@. +-- Method order must match 'ServiceMethods': dumpHistory, fetchBlock, followTip, readTip. +methodsSyncRpc + :: MonadRpc e m + => Methods m (ProtobufMethodsOf UtxoRpc.SyncService) +methodsSyncRpc = + Method (mkNonStreaming $ const unimplemented) -- dumpHistory + . Method (mkNonStreaming $ wrapInSpan TraceRpcFetchBlockSpan . fetchBlockMethod) + . Method (mkServerStreaming $ \_ _ -> unimplemented) -- followTip + . Method (mkNonStreaming $ const unimplemented) -- readTip + $ NoMoreMethods + where + unimplemented = + throwIO + GrpcException + { grpcError = GrpcUnimplemented + , grpcErrorMessage = Just "Not yet implemented" + , grpcErrorDetails = Nothing + , grpcErrorMetadata = [] + } + -- | Start the gRPC server, registering all RPC service handlers. -- Does nothing when the RPC server is disabled in configuration. runRpcServer :: Tracer IO TraceRpc -- ^ Tracer for RPC lifecycle and error events - -> (RpcConfig, NetworkMagic) - -- ^ Server configuration and network discriminant + -> RpcConfig + -- ^ Server configuration + -> NetworkMagic + -- ^ Network discriminant + -> IORef (Maybe NodeKernelAccess) + -- ^ Node kernel access, populated when the kernel is ready -> IO () -runRpcServer tracer (rpcConfig, networkMagic) = handleFatalExceptions $ do +runRpcServer tracer rpcConfig networkMagic nodeKernelAccessRef = handleFatalExceptions $ do let RpcConfig { isEnabled = Identity isEnabled , rpcSocketPath = Identity (File rpcSocketPathFp) @@ -94,6 +122,7 @@ runRpcServer tracer (rpcConfig, networkMagic) = handleFatalExceptions $ do { config = rpcConfig , tracer = natTracer liftIO tracer , rpcLocalNodeConnectInfo = mkLocalNodeConnectInfo nodeSocketPath networkMagic + , rpcNodeKernelAccess = nodeKernelAccessRef } when isEnabled $ @@ -104,6 +133,7 @@ runRpcServer tracer (rpcConfig, networkMagic) = handleFatalExceptions $ do [ fromMethods methodsNodeRpc , fromMethods methodsUtxoRpc , fromMethods methodsUtxoRpcSubmit + , fromMethods methodsSyncRpc ] where serverParams :: ServerParams diff --git a/cardano-rpc/src/Cardano/Rpc/Server/Internal/Env.hs b/cardano-rpc/src/Cardano/Rpc/Server/Internal/Env.hs index b8f5e0367a..b2342f4484 100644 --- a/cardano-rpc/src/Cardano/Rpc/Server/Internal/Env.hs +++ b/cardano-rpc/src/Cardano/Rpc/Server/Internal/Env.hs @@ -2,7 +2,8 @@ {-# LANGUAGE NoFieldSelectors #-} module Cardano.Rpc.Server.Internal.Env - ( RpcEnv (..) + ( NodeKernelAccess + , RpcEnv (..) , mkLocalNodeConnectInfo ) where @@ -10,14 +11,21 @@ where import Cardano.Api import Cardano.Rpc.Server.Config import Cardano.Rpc.Server.Internal.Tracing +import Cardano.Rpc.Server.NodeKernelAccess (NodeKernelAccessF) + +import RIO (RIO) import Control.Tracer (Tracer) +import Data.IORef + +type NodeKernelAccess = NodeKernelAccessF (RIO RpcEnv) data RpcEnv = RpcEnv { config :: !RpcConfig , tracer :: forall m. MonadIO m => Tracer m TraceRpc , -- TODO replace with better connection management than one connection per rpc request rpcLocalNodeConnectInfo :: !LocalNodeConnectInfo + , rpcNodeKernelAccess :: !(IORef (Maybe NodeKernelAccess)) } mkLocalNodeConnectInfo :: SocketPath -> NetworkMagic -> LocalNodeConnectInfo diff --git a/cardano-rpc/src/Cardano/Rpc/Server/Internal/Monad.hs b/cardano-rpc/src/Cardano/Rpc/Server/Internal/Monad.hs index c3b38a69f8..ed8aec9887 100644 --- a/cardano-rpc/src/Cardano/Rpc/Server/Internal/Monad.hs +++ b/cardano-rpc/src/Cardano/Rpc/Server/Internal/Monad.hs @@ -24,6 +24,7 @@ where import Cardano.Api import Cardano.Rpc.Server.Internal.Env import Cardano.Rpc.Server.Internal.Tracing +import Cardano.Rpc.Server.NodeKernelAccess (NodeKernelAccessF) import RIO @@ -41,6 +42,9 @@ instance Has a a where instance Has LocalNodeConnectInfo RpcEnv where obtain RpcEnv{rpcLocalNodeConnectInfo} = rpcLocalNodeConnectInfo +instance m ~ RIO RpcEnv => Has (IORef (Maybe (NodeKernelAccessF m))) RpcEnv where + obtain RpcEnv{rpcNodeKernelAccess} = rpcNodeKernelAccess + instance MonadIO m => Has (Tracer m TraceRpc) RpcEnv where obtain RpcEnv{tracer} = tracer @@ -92,6 +96,7 @@ wrapInSpan spanConstructor act = do type MonadRpc e m = ( Has (Tracer m TraceRpc) e , Has LocalNodeConnectInfo e + , Has (IORef (Maybe (NodeKernelAccessF m))) e , HasCallStack , MonadReader e m , MonadUnliftIO m diff --git a/cardano-rpc/src/Cardano/Rpc/Server/Internal/Tracing.hs b/cardano-rpc/src/Cardano/Rpc/Server/Internal/Tracing.hs index dc853d86b1..bdae8930e3 100644 --- a/cardano-rpc/src/Cardano/Rpc/Server/Internal/Tracing.hs +++ b/cardano-rpc/src/Cardano/Rpc/Server/Internal/Tracing.hs @@ -4,6 +4,7 @@ -- | Provides datatypes used in tracing module Cardano.Rpc.Server.Internal.Tracing where +import Cardano.Api (SlotNo) import Cardano.Api.Consensus (TxValidationErrorInCardanoMode) import Cardano.Api.Era (Inject (..)) import Cardano.Api.Error @@ -19,6 +20,7 @@ import Data.Word (Word64) data TraceRpc = TraceRpcQuery TraceRpcQuery | TraceRpcSubmit TraceRpcSubmit + | TraceRpcSync TraceRpcSync | TraceRpcError SomeException | TraceRpcFatalError SomeException @@ -36,6 +38,7 @@ instance Pretty TraceRpc where pretty = \case TraceRpcQuery t -> pretty t TraceRpcSubmit t -> pretty t + TraceRpcSync t -> pretty t TraceRpcError e -> "Exception when processing RPC request:\n" <> prettyException e TraceRpcFatalError e -> "RPC server fatal error: " <> prettyException e @@ -64,7 +67,7 @@ instance Error TraceRpcQuery where -- | Traces used in SubmitTx service data TraceRpcSubmit - = -- | Node-to-client exception + = -- | Node-to-client connection error during submission TraceRpcSubmitN2cConnectionError SomeException | -- | Transaction deserialisation error TraceRpcSubmitTxDecodingError DecoderError @@ -92,8 +95,34 @@ instance Pretty TraceRpcSubmit where instance Error TraceRpcSubmit where prettyError = pretty +-- | Traces used in SyncService (FetchBlock, FollowTip) +data TraceRpcSync + = -- | FetchBlock span + TraceRpcFetchBlockSpan TraceSpanEvent + | -- | Requested block was not found + TraceRpcFetchBlockNotFound SlotNo + | -- | Node kernel access is not yet available + TraceRpcNodeKernelAccessUnavailable + | -- | Ledger forker error + TraceRpcForkerError String + deriving Show + +instance Pretty TraceRpcSync where + pretty = \case + TraceRpcFetchBlockSpan (SpanBegin _) -> "Started FetchBlock method" + TraceRpcFetchBlockSpan (SpanEnd _) -> "Finished FetchBlock method" + TraceRpcFetchBlockNotFound slot -> "Block not found at slot " <> pshow slot + TraceRpcNodeKernelAccessUnavailable -> "Node kernel access not yet initialised" + TraceRpcForkerError e -> "Ledger forker error: " <> pretty e + +instance Error TraceRpcSync where + prettyError = pretty + instance Inject TraceRpcSubmit TraceRpc where inject = TraceRpcSubmit instance Inject TraceRpcQuery TraceRpc where inject = TraceRpcQuery + +instance Inject TraceRpcSync TraceRpc where + inject = TraceRpcSync diff --git a/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Query.hs b/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Query.hs index df453c87b1..84c0575ea7 100644 --- a/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Query.hs +++ b/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Query.hs @@ -56,19 +56,19 @@ readParamsMethod _req = do let sbe = convert eon let target = VolatileTip - (pparams, chainPoint, blockNo, systemStart, eraHistory) <- liftIO . (throwEither =<<) $ executeLocalStateQueryExpr nodeConnInfo target $ do + (pparams, chainPoint, chainBlockNo, systemStart, eraHistory) <- liftIO . (throwEither =<<) $ executeLocalStateQueryExpr nodeConnInfo target $ do pparams <- throwEither =<< throwEither =<< queryProtocolParameters sbe chainPoint <- throwEither =<< queryChainPoint - blockNo <- throwEither =<< queryChainBlockNo + chainBlockNo <- throwEither =<< queryChainBlockNo systemStart <- throwEither =<< querySystemStart eraHistory <- throwEither =<< queryEraHistory - pure (pparams, chainPoint, blockNo, systemStart, eraHistory) + pure (pparams, chainPoint, chainBlockNo, systemStart, eraHistory) timestamp <- slotToTimestamp systemStart eraHistory chainPoint pure $ def - & U5c.ledgerTip .~ mkChainPointMsg chainPoint blockNo timestamp + & U5c.ledgerTip .~ mkChainPointMsg chainPoint chainBlockNo timestamp & U5c.values . U5c.cardano .~ obtainCommonConstraints eon (protocolParamsToUtxoRpcPParams eon pparams) -- | Handle the @ReadUtxos@ RPC method. @@ -90,19 +90,19 @@ readUtxosMethod req eon <- forEraInEon @Era era (error "Minimum Conway era required") pure let target = VolatileTip - (utxo, chainPoint, blockNo, systemStart, eraHistory) <- liftIO . (throwEither =<<) $ executeLocalStateQueryExpr nodeConnInfo target $ do + (utxo, chainPoint, chainBlockNo, systemStart, eraHistory) <- liftIO . (throwEither =<<) $ executeLocalStateQueryExpr nodeConnInfo target $ do utxo <- throwEither =<< throwEither =<< queryUtxo (convert eon) utxoFilter chainPoint <- throwEither =<< queryChainPoint - blockNo <- throwEither =<< queryChainBlockNo + chainBlockNo <- throwEither =<< queryChainBlockNo systemStart <- throwEither =<< querySystemStart eraHistory <- throwEither =<< queryEraHistory - pure (utxo, chainPoint, blockNo, systemStart, eraHistory) + pure (utxo, chainPoint, chainBlockNo, systemStart, eraHistory) timestamp <- slotToTimestamp systemStart eraHistory chainPoint pure $ defMessage - & U5c.ledgerTip .~ mkChainPointMsg chainPoint blockNo timestamp + & U5c.ledgerTip .~ mkChainPointMsg chainPoint chainBlockNo timestamp & U5c.items .~ obtainCommonConstraints eon (utxoToUtxoRpcAnyUtxoData utxo) where txoRefToTxIn :: MonadRpc e m => Proto UtxoRpc.TxoRef -> m TxIn @@ -136,13 +136,13 @@ searchUtxosMethod req = do eon <- forEraInEon @Era era (error "Minimum Conway era required") pure let target = VolatileTip - (utxo, chainPoint, blockNo, systemStart, eraHistory) <- liftIO . (throwEither =<<) $ executeLocalStateQueryExpr nodeConnInfo target $ do + (utxo, chainPoint, chainBlockNo, systemStart, eraHistory) <- liftIO . (throwEither =<<) $ executeLocalStateQueryExpr nodeConnInfo target $ do utxo <- throwEither =<< throwEither =<< queryUtxo (convert eon) utxoFilter chainPoint <- throwEither =<< queryChainPoint - blockNo <- throwEither =<< queryChainBlockNo + chainBlockNo <- throwEither =<< queryChainBlockNo systemStart <- throwEither =<< querySystemStart eraHistory <- throwEither =<< queryEraHistory - pure (utxo, chainPoint, blockNo, systemStart, eraHistory) + pure (utxo, chainPoint, chainBlockNo, systemStart, eraHistory) timestamp <- slotToTimestamp systemStart eraHistory chainPoint @@ -155,7 +155,7 @@ searchUtxosMethod req = do pure $ defMessage - & U5c.ledgerTip .~ mkChainPointMsg chainPoint blockNo timestamp + & U5c.ledgerTip .~ mkChainPointMsg chainPoint chainBlockNo timestamp & U5c.items .~ map (uncurry txInTxOutToAnyUtxoData) page & U5c.maybe'nextToken .~ nextTok diff --git a/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Sync.hs b/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Sync.hs new file mode 100644 index 0000000000..4b3a0c01e4 --- /dev/null +++ b/cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Sync.hs @@ -0,0 +1,89 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeApplications #-} + +module Cardano.Rpc.Server.Internal.UtxoRpc.Sync + ( fetchBlockMethod + ) +where + +import Cardano.Api +import Cardano.Rpc.Proto.Api.UtxoRpc.Sync qualified as U5c +import Cardano.Rpc.Server.Internal.Error +import Cardano.Rpc.Server.Internal.Monad +import Cardano.Rpc.Server.Internal.Tracing () +import Cardano.Rpc.Server.NodeKernelAccess +import Cardano.Rpc.Server.NodeKernelAccess.Internal + +import RIO + +import Data.ByteString qualified as BS +import Data.ProtoLens (defMessage) +import Data.Time.Clock (UTCTime) +import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) +import Network.GRPC.Spec (GrpcError (GrpcInternal, GrpcInvalidArgument, GrpcNotFound), Proto) + +-- | Handle the @FetchBlock@ SyncService RPC method. +-- Fetches blocks from ChainDB by slot and header hash. +-- Returns @NOT_FOUND@ if a requested block is missing. +-- Returns @INVALID_ARGUMENT@ if a block reference has an invalid hash. +fetchBlockMethod + :: MonadRpc e m + => Proto U5c.FetchBlockRequest + -- ^ Request containing block references (slot + hash) + -> m (Proto U5c.FetchBlockResponse) + -- ^ Response containing fetched blocks with raw CBOR and cardano header +fetchBlockMethod request = do + nodeKernelAccess <- grabNodeKernelAccess + let blockRefs = request ^. U5c.ref + blocks <- mapM (fetchOne nodeKernelAccess) blockRefs + pure $ defMessage & U5c.block .~ blocks + where + fetchOne + :: MonadRpc e m + => NodeKernelAccessF m + -> Proto U5c.BlockRef + -> m (Proto U5c.AnyChainBlock) + fetchOne + NodeKernelAccessF + { nkaFetchBlock = fetchBlock + , nkaSystemStart = systemStart + , nkaEraHistory = getEraHistory + } + blockRef = do + let slot = SlotNo $ blockRef ^. U5c.slot + hashBytes = blockRef ^. U5c.hash + throwInvalidHash = + throwGrpcErrorWithMessage GrpcInvalidArgument $ + "invalid block header hash (" <> tshow (BS.length hashBytes) <> " bytes)" + throwNotFound = + throwGrpcErrorWithMessage GrpcNotFound $ + "block not found at slot " <> tshow (unSlotNo slot) + headerHash <- + deserialiseFromRawBytes (proxyToAsType (Proxy @(Hash BlockHeader))) hashBytes + & either (const throwInvalidHash) pure + (rawBytes, BlockNo height) <- + fetchBlock slot headerHash >>= maybe throwNotFound pure + eraHistory <- getEraHistory + timestampMs <- + slotToUTCTime systemStart eraHistory slot + & either + ( const . throwGrpcErrorWithMessage GrpcInternal $ + "failed to compute timestamp for slot " <> tshow (unSlotNo slot) + ) + (pure . utcTimeToMs) + let blockHeader = + defMessage + & U5c.slot .~ unSlotNo slot + & U5c.hash .~ hashBytes + & U5c.height .~ height + pure $ + defMessage + & U5c.nativeBytes .~ rawBytes + & U5c.cardano . U5c.header .~ blockHeader + & U5c.cardano . U5c.timestamp .~ timestampMs + +utcTimeToMs :: UTCTime -> Word64 +utcTimeToMs = round . (* 1000) . utcTimeToPOSIXSeconds + +-- TODO: cardano.body.tx - needs full block deserialisation + UTxO RPC tx mapping diff --git a/cardano-rpc/src/Cardano/Rpc/Server/NodeKernelAccess.hs b/cardano-rpc/src/Cardano/Rpc/Server/NodeKernelAccess.hs new file mode 100644 index 0000000000..ce16bc35ef --- /dev/null +++ b/cardano-rpc/src/Cardano/Rpc/Server/NodeKernelAccess.hs @@ -0,0 +1,34 @@ +{-# LANGUAGE RankNTypes #-} + +module Cardano.Rpc.Server.NodeKernelAccess + ( NodeKernelAccessF (..) + , LedgerSnapshot (..) + ) +where + +import Cardano.Api + +import Data.ByteString (ByteString) + +-- | Record of callbacks for in-process access to the node kernel. +-- Constructed by cardano-node once consensus initialisation completes. +data NodeKernelAccessF m = NodeKernelAccessF + { nkaWithSnapshot :: forall a. (LedgerSnapshot m -> m a) -> m a + -- ^ Acquire a consistent ledger snapshot and run queries against it. + -- All queries within one callback see the same chain tip. + , nkaSubmitTx :: TxInMode -> m (SubmitResult TxValidationErrorInCardanoMode) + -- ^ Submit a transaction to the mempool. + , nkaFetchBlock :: SlotNo -> Hash BlockHeader -> m (Maybe (ByteString, BlockNo)) + -- ^ Fetch raw block CBOR and block number by slot and header hash. + -- Returns 'Nothing' if the block is not found. + , nkaSystemStart :: SystemStart + -- ^ Network system start time. + , nkaEraHistory :: m EraHistory + -- ^ Current era history, computed from the live ledger state. + } + +-- | A consistent, read-only view of ledger state at a single chain tip. +newtype LedgerSnapshot m = LedgerSnapshot + { runQuery :: forall result. QueryInMode result -> m result + -- ^ Run a ledger query against this snapshot. + } diff --git a/cardano-rpc/src/Cardano/Rpc/Server/NodeKernelAccess/Internal.hs b/cardano-rpc/src/Cardano/Rpc/Server/NodeKernelAccess/Internal.hs new file mode 100644 index 0000000000..5ff2ed8a9d --- /dev/null +++ b/cardano-rpc/src/Cardano/Rpc/Server/NodeKernelAccess/Internal.hs @@ -0,0 +1,118 @@ +{-# LANGUAGE ApplicativeDo #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} + +module Cardano.Rpc.Server.NodeKernelAccess.Internal + ( mkNodeKernelAccess + , grabNodeKernelAccess + ) +where + +import Cardano.Api +import Cardano.Api.Consensus qualified as Consensus +import Cardano.Rpc.Server.Internal.Monad (MonadRpc, grab) +import Cardano.Rpc.Server.NodeKernelAccess + +import RIO (HasCallStack, atomically, throwIO, tshow) + +import Data.ByteString (ByteString) +import Data.ByteString.Lazy qualified as BSL +import Data.IORef +import Network.GRPC.Spec + +-- | Construct 'NodeKernelAccessF' from a consensus 'Consensus.NodeKernel'. +-- Only 'nkaFetchBlock' and 'nkaSlotToUTCTime' are implemented; snapshot and submit callbacks +-- throw 'GrpcUnimplemented'. Non-Cardano block types throw 'GrpcInternal'. +mkNodeKernelAccess + :: (HasCallStack, MonadIO m) + => Consensus.BlockType blk + -- ^ Block type witness + -> Consensus.NodeKernel IO addrNTN addrNTC blk + -- ^ Consensus node kernel + -> NodeKernelAccessF m +mkNodeKernelAccess Consensus.CardanoBlockType nodeKernel = do + let chainDb = Consensus.getChainDB nodeKernel + topConfig = Consensus.getTopLevelConfig nodeKernel + NodeKernelAccessF + { nkaWithSnapshot = \_ -> throwUnimplemented "nkaWithSnapshot" + , nkaSubmitTx = \_ -> throwUnimplemented "nkaSubmitTx" + , nkaFetchBlock = fetchBlock chainDb + , nkaSystemStart = Consensus.nodeSystemStart topConfig + , nkaEraHistory = getEraHistory chainDb topConfig + } +mkNodeKernelAccess blockType _ = + let ex = + GrpcException + { grpcError = GrpcInternal + , grpcErrorMessage = Just $ "RPC requires CardanoBlockType, got " <> tshow blockType + , grpcErrorDetails = Nothing + , grpcErrorMetadata = [] + } + in NodeKernelAccessF + { nkaWithSnapshot = \_ -> throwIO ex + , nkaSubmitTx = \_ -> throwIO ex + , nkaFetchBlock = \_ _ -> throwIO ex + , nkaSystemStart = error $ "RPC requires CardanoBlockType, got " <> show blockType + , nkaEraHistory = throwIO ex + } + +-- | Throw a gRPC UNIMPLEMENTED error for callbacks not yet migrated to NKA. +throwUnimplemented :: (HasCallStack, MonadIO m) => Text -> m a +throwUnimplemented name = + throwIO + GrpcException + { grpcError = GrpcUnimplemented + , grpcErrorMessage = Just $ name <> ": not yet implemented" + , grpcErrorDetails = Nothing + , grpcErrorMetadata = [] + } + +-- | Grab the current 'NodeKernelAccessF' from the environment, or throw +-- gRPC UNAVAILABLE if the node kernel has not yet initialised. +grabNodeKernelAccess + :: (HasCallStack, MonadRpc e m) + => m (NodeKernelAccessF m) +grabNodeKernelAccess = + grab >>= liftIO . readIORef >>= \case + Nothing -> + throwIO + GrpcException + { grpcError = GrpcUnavailable + , grpcErrorMessage = Just "Node kernel not yet initialised" + , grpcErrorDetails = Nothing + , grpcErrorMetadata = [] + } + Just nodeKernelAccess -> + pure nodeKernelAccess + +-- | Read the current era history from the live ledger state. +getEraHistory + :: MonadIO m + => Consensus.ChainDB IO (Consensus.CardanoBlock Consensus.StandardCrypto) + -> Consensus.TopLevelConfig (Consensus.CardanoBlock Consensus.StandardCrypto) + -> m EraHistory +getEraHistory chainDb topConfig = do + extLedgerState <- liftIO . atomically $ Consensus.getCurrentLedger chainDb + let summary = + Consensus.hardForkSummary (Consensus.configLedger topConfig) (Consensus.ledgerState extLedgerState) + pure . EraHistory $ Consensus.mkInterpreter summary + +-- | Fetch a raw block and its block number from ChainDB by slot and header hash. +fetchBlock + :: MonadIO m + => Consensus.ChainDB IO (Consensus.CardanoBlock Consensus.StandardCrypto) + -- ^ Chain database + -> SlotNo + -- ^ Block slot number + -> Hash BlockHeader + -- ^ Block header hash + -> m (Maybe (ByteString, BlockNo)) + -- ^ Raw CBOR bytes and block number, or 'Nothing' if not found +fetchBlock chainDb slot (HeaderHash shortHash) = do + let point = Consensus.RealPoint slot (Consensus.OneEraHash shortHash) + getComponent component = liftIO $ Consensus.getBlockComponent chainDb component point + getComponent $ do + rawBytes <- BSL.toStrict <$> Consensus.GetRawBlock + blockNumber <- Consensus.blockNo <$> Consensus.GetBlock + pure (rawBytes, blockNumber)