Skip to main content

datumesh_store/
lib.rs

1//! A PostgreSQL store for [Datumesh](https://datumesh.com): function descriptors, receipts,
2//! blobs, versions of cells over transaction time, lineage, namespaces and function names.
3//! [`PgStore`] implements [`Store`], and through it `datumesh_core`'s [`Resolver`], so
4//! `datumesh_core::verify` can recompute receipts of stored cells from it.
5//!
6//! The API is synchronous (the `postgres` driver, a small connection pool). Async callers run
7//! it on a blocking thread, e.g. `tokio::task::spawn_blocking`.
8//!
9//! What is stored, and how:
10//!
11//! * Descriptors are checked by the core (`Descriptor::from_json`) and stored in canonical
12//!   form (JCS) under their hash. Receipts are verified (`datumesh_core::verify`, SPEC 10.1
13//!   steps 1-6) against the key document stored for their signer host, so nothing
14//!   unverified enters the store, and are stored canonical under their hash. Storing the
15//!   same thing twice stores it once. A second, different valid signature over the same
16//!   payload (possible only with a randomised signer: the payload names the key, so a
17//!   rotated key gives a new hash) is refused ([`Error::Conflict`]) and the stored receipt
18//!   kept.
19//! * Blobs are stored in the database, under the SHA-256 of their content.
20//! * Versions: from transaction time `tx` on, cell `cell_id` has the value of a receipt.
21//!   Append-only and in tx order per cell, enforced by the database (triggers) as well as by
22//!   [`Store::put_version`], which also checks that the receipt is stored and is of that
23//!   cell at that tx.
24//! * Lineage: `put_receipt` records an edge to each of the receipt's `inputs`, held or not.
25//! * Namespaces: a host's key document (checked as SPEC 9.2 requires), with when it was
26//!   validated. Store it before the host's receipts. Replacing it later (a key revoked or
27//!   dropped) does not touch receipts already stored: they verified when they were stored,
28//!   and putting one of them again is still idempotent.
29//! * Names: which function hash a fully qualified function name meant from a transaction
30//!   time on. Append-only, like versions.
31//!
32//! Instants are compared as text (fixed width, `COLLATE "C"`), which is exact; each is also
33//! stored as `timestamptz`, which round-trips every instant of the specification exactly.
34//!
35//! Every [`Store`] method returns a `Result`: a malformed argument is [`Error::Invalid`] with
36//! the core's code, a database failure [`Error::Database`]. The [`Resolver`] methods keep the
37//! core's `Option` signatures; there a failure is logged with `tracing` (target
38//! `datumesh_store`) and answered `None`, which `verify` reports as unavailable.
39//!
40//! The schema comes from [`migrate::apply`] (the `datumesh-store migrate` command).
41
42#![forbid(unsafe_code)]
43#![deny(missing_docs)]
44
45use std::borrow::Cow;
46
47pub use datumesh_core::Resolver;
48pub use postgres;
49
50mod error;
51pub mod migrate;
52mod pg;
53mod pool;
54mod sql;
55
56pub use error::{Error, Result};
57pub use pg::PgStore;
58pub use pool::PoolOptions;
59
60/// A version of a cell: from transaction time `tx` on, the cell's value is that of the
61/// receipt `receipt_hash`.
62#[non_exhaustive]
63#[derive(Clone, Debug, PartialEq, Eq, Hash)]
64pub struct Version {
65    /// The cell id.
66    pub cell_id: String,
67    /// The transaction time (an instant, SPEC 3.3).
68    pub tx: String,
69    /// The receipt stating the value.
70    pub receipt_hash: String,
71}
72
73/// A namespace's host and its key document.
74#[non_exhaustive]
75#[derive(Clone, Debug, PartialEq, Eq, Hash)]
76pub struct Namespace {
77    /// The host (a DNS name).
78    pub host: String,
79    /// The key document, canonical (JCS).
80    pub keydoc: Vec<u8>,
81    /// When the document was last retrieved and checked.
82    pub validated_at: String,
83    /// When the namespace was suspended, if it is. Nothing in this version sets it.
84    pub suspended_at: Option<String>,
85}
86
87/// A Datumesh store: a [`Resolver`] that can also be written to.
88///
89/// Instants are SPEC 3.3 instants (`YYYY-MM-DDTHH:MM:SS.ffffffZ`), hashes and cell ids 64
90/// lowercase hex digits, hosts DNS names and function names fully qualified. Every method,
91/// read or write, refuses anything else with the core's error code ([`Error::Invalid`]),
92/// and reports a database failure as [`Error::Database`] (or [`Error::PoolTimeout`]): a read
93/// answers `Ok(None)` / `Ok(vec![])` only when the store really holds nothing.
94///
95/// The [`Resolver`] methods keep the core's `Option` signatures: there a malformed argument
96/// or a database failure is `None` (logged with `tracing` when it is a failure), which
97/// `verify` reports as unavailable.
98pub trait Store: Resolver {
99    /// Stores a descriptor (checked and canonicalised by the core); returns its function
100    /// hash. Idempotent.
101    fn put_descriptor(&self, bytes: &[u8]) -> Result<String>;
102    /// Stores a receipt that verifies (SPEC 10.1 steps 1-6) against the key document stored
103    /// for its signer host with [`put_namespace`](Store::put_namespace), in canonical form,
104    /// with an edge to each of its inputs; returns its hash. A receipt that does not verify
105    /// is refused with `verify`'s code (no key document for the host: `E_KEY_UNAVAILABLE`).
106    /// Idempotent: bytes whose canonical form is the stored copy return its hash without
107    /// checking the signature again (it verified when stored), even if the key has since
108    /// been revoked. Other bytes with a stored hash are verified first, then refused as
109    /// [`Error::Conflict`].
110    fn put_receipt(&self, bytes: &[u8]) -> Result<String>;
111    /// Stores blob content; returns its SHA-256 (lowercase hex). Idempotent.
112    fn put_blob(&self, bytes: &[u8]) -> Result<String>;
113    /// Blob content by SHA-256.
114    fn blob(&self, sha256: &str) -> Result<Option<Cow<'_, [u8]>>>;
115    /// Appends a version: from `tx` on, `cell_id` has the value of receipt `receipt_hash`,
116    /// which must be stored, of that cell and at that tx. `tx` must be later than the cell's
117    /// latest version ([`Error::NotLater`]).
118    fn put_version(&self, cell_id: &str, tx: &str, receipt_hash: &str) -> Result<()>;
119    /// The cell's latest version with `tx <= asof`.
120    fn version_asof(&self, cell_id: &str, asof: &str) -> Result<Option<Version>>;
121    /// The cell's versions with `from <= tx <= to`, in tx order.
122    fn versions(&self, cell_id: &str, from: &str, to: &str) -> Result<Vec<Version>>;
123    /// The cell's latest version.
124    fn latest(&self, cell_id: &str) -> Result<Option<Version>>;
125    /// The stored receipts that list `receipt_hash` among their inputs, by hash.
126    fn dependents(&self, receipt_hash: &str) -> Result<Vec<String>>;
127    /// The inputs of a stored receipt, by hash (whether or not they are stored).
128    fn inputs(&self, receipt_hash: &str) -> Result<Vec<String>>;
129    /// Records a host's key document, checked as a 200 response for that host would be
130    /// (SPEC 9.2, 9.3), validated at `validated_at`. Re-validation replaces the document; a
131    /// `validated_at` earlier than the recorded one is [`Error::NotLater`]. Receipts already
132    /// stored are not re-checked: they verified when they were stored (and putting one again
133    /// stays idempotent).
134    fn put_namespace(&self, host: &str, keydoc_bytes: &[u8], validated_at: &str) -> Result<()>;
135    /// A host's recorded key document.
136    fn namespace(&self, host: &str) -> Result<Option<Namespace>>;
137    /// Appends a binding: from `tx` on, the fully qualified function name `fn_name` means
138    /// `fn_hash`, whose descriptor must be stored. `tx` must be later than the name's latest
139    /// binding ([`Error::NotLater`]).
140    fn bind_name(&self, fn_name: &str, fn_hash: &str, tx: &str) -> Result<()>;
141    /// The function hash `fn_name` meant as of `asof`.
142    fn resolve_name(&self, fn_name: &str, asof: &str) -> Result<Option<String>>;
143}