# dig-rpc-protocol
The **canonical DIG-node JSON-RPC protocol** (formerly `dig-rpc-types`) — the single source of truth both
DIG node implementations (the digstore `dig-node` crate and the standalone
`dig-node` binary) depend on instead of hand-rolling `json!({…})` shapes and
ad-hoc error codes.
Pure types: **no I/O, no async, no server logic, no crypto.** The
[`dig-rpc`](https://github.com/DIG-Network/dig-rpc) server crate and the node
implementations build on these types.
## What's here
| `envelope` | JSON-RPC 2.0 request/response, `RequestId`, `Version` |
| `error` | the canonical `ErrorCode` taxonomy, the `RpcError` envelope (`{code, message, data:{code, origin}}`), and the one constructor helper |
| `method` | the `Method` enum — stable wire names, per-method `Tier`, the mTLS peer allowlist |
| `tier` | the three access tiers (`PublicRead` / `Peer` / `Control`) |
| `types` | every method's params + results, field-for-field with the canonical node |
| `frames` | the shape-dispatched DHT + PEX peer wires |
| `openrpc` | the OpenRPC 1.2.6 document generator (the single discovery source) |
The full contract is in [`SPEC.md`](./SPEC.md).
## Full Protocol Interface (at-a-glance LLM reference)
### All Methods (34 total)
| `GetContent` | `dig.getContent` | PublicRead | ✓ | Read a verified window of a resource's ciphertext |
| `GetCapsule` | `dig.getCapsule` | PublicRead | ✗ | Fetch the whole .dig module for (store, root) |
| `GetModule` | `dig.getModule` | PublicRead | ✗ | Alias of dig.getCapsule |
| `GetManifest` | `dig.getManifest` | PublicRead | ✗ | Fetch the public discovery manifest resource |
| `GetMetadata` | `dig.getMetadata` | PublicRead | ✗ | Fetch the plaintext metadata manifest |
| `ListCapsules` | `dig.listCapsules` | PublicRead | ✗ | List a store's confirmed capsules |
| `GetProof` | `dig.getProof` | PublicRead | ✗ | Get the real inclusion proof + execution-proof status |
| `GetProofStatus` | `dig.getProofStatus` | PublicRead | ✗ | Poll a real execution-proof job by id |
| `GetAnchoredRoot` | `dig.getAnchoredRoot` | PublicRead | ✓ | Resolve a store's chain-anchored tip root |
| `GetCollection` | `dig.getCollection` | PublicRead | ✓ | Get collection-level facts for NFT launcher ids |
| `ListCollectionItems` | `dig.listCollectionItems` | PublicRead | ✓ | List resolved NFT collection items (paginated) |
| `Health` | `dig.health` | PublicRead | ✗ | Liveness and capability summary |
| `Methods` | `dig.methods` | PublicRead | ✗ | List the method names this node implements |
| `GetNetworkInfo` | `dig.getNetworkInfo` | Peer | ✓ | This node's peer-network posture |
| `GetPeers` | `dig.getPeers` | Peer | ✓ | The peers this node currently knows |
| `Announce` | `dig.announce` | Peer | ✓ | Accept a peer announcement (peer_id + addresses) |
| `GetAvailability` | `dig.getAvailability` | Peer | ✓ | Batch-check whether this node holds items |
| `ListInventory` | `dig.listInventory` | Peer | ✓ | Enumerate the stores / roots this node serves |
| `FetchRange` | `dig.fetchRange` | Peer | ✓ | Fetch a single verified range frame of a resource |
| `Stage` | `dig.stage` | Control | ✗ | Compile a local folder into a capsule .dig in-process |
| `CacheGetConfig` | `cache.getConfig` | Control | ✗ | Get the local-cache configuration |
| `CacheSetCapBytes` | `cache.setCapBytes` | Control | ✗ | Set the local-cache size cap |
| `CacheClear` | `cache.clear` | Control | ✗ | Clear the local cache |
| `CacheListCached` | `cache.listCached` | Control | ✗ | List the durable cached modules |
| `CacheRemoveCached` | `cache.removeCached` | Control | ✗ | Remove one cached capsule |
| `CacheFetchAndCache` | `cache.fetchAndCache` | Control | ✗ | Fetch and cache one capsule |
| `CacheStats` | `cache.stats` | Control | ✗ | Cache telemetry: cap + usage, entry count + bytes, eviction + hit/miss |
| `ControlPeerStatus` | `control.peerStatus` | Control | ✗ | Snapshot the node's peer network |
| `ControlSubscribe` | `control.subscribe` | Control | ✗ | Subscribe the node to a store (persisted watch + gap-fill) |
| `ControlUnsubscribe` | `control.unsubscribe` | Control | ✗ | Unsubscribe the node from a store |
| `ControlListSubscriptions` | `control.listSubscriptions` | Control | ✗ | List the node's persisted store subscriptions |
| `ControlPeersConnect` | `control.peers.connect` | Control | ✗ | Dial a peer into the connected pool |
| `ControlPeersDisconnect` | `control.peers.disconnect` | Control | ✗ | Drop a pooled peer by peer_id |
| `RpcDiscover` | `rpc.discover` | Control | ✗ | Return the OpenRPC self-describe document |
**Access Tiers:** `PublicRead` = anonymous content reads (HTTPS / mTLS). `Peer` = mTLS peer surface (node↔node). `Control` = loopback / in-process only.
**Peer-Reachable Methods (10 total):** `dig.getContent`, `dig.getNetworkInfo`, `dig.getPeers`, `dig.announce`, `dig.getAvailability`, `dig.listInventory`, `dig.fetchRange`, `dig.getAnchoredRoot`, `dig.getCollection`, `dig.listCollectionItems`. All `Control` and non-peer `PublicRead` methods are never peer-reachable.
### Error Codes (22 total)
| `-32700` | `PARSE_ERROR` | Node | Invalid JSON received |
| `-32600` | `INVALID_REQUEST` | Node | JSON is not a valid Request object |
| `-32601` | `METHOD_NOT_FOUND` | Node | Method does not exist / is not available |
| `-32602` | `INVALID_PARAMS` | Node | Invalid or missing method parameters |
| `-32603` | `INTERNAL_ERROR` | Node | Node failed to satisfy a well-formed call |
| `-32000` | `SERVER_ERROR` | Node | Generic server error (config/IO/chain failure) |
| `-32004` | `RESOURCE_UNAVAILABLE` | Node | Resource not available at requested root (genuine miss) |
| `-32005` | `ROOT_NOT_ANCHORED` | Node | Requested/served root is not chain-anchored (fail-closed) |
| `-32006` | `PEER_UNREACHABLE` | Peer | No NAT-traversal strategy reached the peer |
| `-32007` | `RANGE_NOT_SATISFIABLE` | Node | Byte range lies outside the resource |
| `-32008` | `CONTENT_REDIRECT` | Node | Content held elsewhere — `data.redirect` names holders |
| `-32010` | `UPSTREAM_ERROR` | Upstream | Upstream/proxy fetch failed |
| `-32011` | `STAGE_INVALID_INPUT` | Node | `dig.stage`: dir unreadable / walk budget exceeded |
| `-32012` | `STAGE_NO_FILES` | Node | `dig.stage`: no files to compile |
| `-32013` | `STAGE_OVER_CAP` | Node | `dig.stage`: input exceeds store cap |
| `-32014` | `STAGE_COMPILE_FAILED` | Node | `dig.stage`: compile / IO failure |
| `-32020` | `ONION_CIRCUIT_UNAVAILABLE` | Onion | Private read circuit could not be built/kept |
| `-32021` | `PRIVACY_REQUIRES_LOCAL_NODE` | Onion | Privacy mode requires local originator |
| `-32022` | `ONION_HOPS_OUT_OF_RANGE` | Onion | Hop count outside `[2, 5]` |
| `-32030` | `UNAUTHORIZED` | Control | Control-plane call not authorized (loopback gate failed) |
| `-32031` | `NOT_SUPPORTED` | Control | Control-plane method recognized but not supported |
| `-32032` | `CONTROL_ERROR` | Control | Control-plane runtime error |
**Error Origins:** `Node` = node's own read/serve path. `Peer` = peer-network layer. `Upstream` = upstream fetch (proxy/whole-store sync). `Onion` = private-retrieval layer. `Control` = loopback control plane.
### Request/Response Envelope
**JSON-RPC 2.0 strict:**
```
```
**Error Envelope Structure:**
```rust
{
code: i32, // numeric wire code (e.g., -32004)
message: String, // human-readable summary
data: {
code: String, // stable machine code (e.g., "RESOURCE_UNAVAILABLE")
origin: String, // subsystem (node/peer/upstream/onion/control)
redirect?: RedirectInfo, // present only on CONTENT_REDIRECT
<extra fields> // method-specific fields flattened
}
}
```
### Interface Version
**`INTERFACE_VERSION`** is not yet versioned in the wire (future work). The protocol evolves via additive `Method` variants and `ErrorCode` assignments; both are `#[non_exhaustive]` so a client match-arm default (`_ => …`) handles future extensions.
## Error taxonomy at a glance
Standard JSON-RPC codes plus the DIG protocol codes. The onion codes
`-32020/-32021/-32022` are the published normative contract and keep their
numbers; the control-plane codes are `-32030/-32031/-32032`.
```rust
use dig_rpc_protocol::{RpcError, ErrorCode, ErrorOrigin};
let e = RpcError::of(ErrorCode::ResourceUnavailable, "not at this root");
assert_eq!(e.code, ErrorCode::ResourceUnavailable); // -32004
assert_eq!(e.data.code, "RESOURCE_UNAVAILABLE"); // stable branch key
assert_eq!(e.data.origin, ErrorOrigin::Node);
```
## Method catalogue + discovery
```rust
use dig_rpc_protocol::{Method, Tier, openrpc_document};
assert_eq!(Method::GetContent.name(), "dig.getContent");
assert_eq!(Method::GetContent.tier(), Tier::PublicRead);
assert!(Method::GetContent.is_peer_reachable());
assert!(!Method::CacheClear.is_peer_reachable()); // control tier
let doc = openrpc_document(env!("CARGO_PKG_VERSION")); // OpenRPC 1.2.6
```
## Features
| `schema-export` | off | derive `schemars::JsonSchema` on every wire type (Go/TS codegen) |
## Testing
```
cargo test --all-features # unit + conformance vectors
cargo llvm-cov --all-features # coverage (CI-gated ≥80%)
```
Licensed under Apache-2.0 OR MIT.