OXYGENHUB
# c7161ec1a607 spectra / crates / spectra-core / src / ledger.rs
22.4 KiB · Rust · last changed Spectra stands alone: its own identities, signatures and CBOR; projects of any kind of work (#26, #27) 1 day ago source blame history raw
1
//! The ledger (spec §9, §9.5): the append-only, hash-chained event log in
2
//! SQLite, its checkpoints signed by the instance key, its verification,
3
//! and the bus that tells everything else what was appended.
4
//!
5
//! One writer at a time (a lock around the chain head and SQLite's own),
6
//! so `seq` is gap-free and every `prev` is the hash of the event before.
7
//! Readers never wait for it (WAL).
8
9
use crate::event::{canonical_json, Auth, Event, Method, NewEvent};
10
use crate::hlc::{Clock, Hlc};
11
use crate::identity::KeyId;
12
use crate::identity::{Identity, Signature};
13
use crate::keys::KeyPair;
14
use crate::{ids, projections};
15
use anyhow::{bail, Context, Result};
16
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteRow, SqliteSynchronous};
17
use sqlx::{Row, SqlitePool};
18
use std::path::Path;
19
use std::sync::Arc;
20
use tokio::sync::{broadcast, Mutex};
21
22
/// Events between checkpoints (spec §9.5).
23
pub const CHECKPOINT_EVERY: u64 = 256;
24
/// The longest a written event waits for a checkpoint (spec §9.5).
25
pub const CHECKPOINT_INTERVAL_MS: u64 = 10 * 60 * 1000;
26
27
const CHECKPOINT_DOMAIN: &[u8] = b"spectra1 checkpoint ";
28
29
/// The ledger: the database, the chain head, the clock and the bus.
30
pub struct Ledger {
31
    pool: SqlitePool,
32
    /// Held for as long as this ledger may append: one writer per data
33
    /// directory, so the chain never forks (`None` when opened read-only).
34
    _lock: Option<std::fs::File>,
35
    w: Arc<Writer>,
36
}
37
38
/// What writing needs, shared with the task that does it. Every write runs
39
/// in a task of its own, so a caller that goes away (a client that hangs up
40
/// mid-request) can never leave a transaction half done: a cancelled
41
/// transaction on a pooled connection would swallow every later commit
42
/// made on that connection.
43
struct Writer {
44
    pool: SqlitePool,
45
    head: Mutex<Head>,
46
    clock: Clock,
47
    bus: broadcast::Sender<Arc<Event>>,
48
    instance: Option<KeyPair>,
49
}
50
51
#[derive(Clone, Copy, Debug)]
52
struct Head {
53
    seq: u64,
54
    hash: [u8; 32],
55
    since_checkpoint: u64,
56
    oldest_unchecked_ms: Option<u64>,
57
}
58
59
impl Ledger {
60
    /// Open (or create) `path`, apply migrations, and refuse a ledger
61
    /// written by a newer format. Checkpoints are signed with `instance`;
62
    /// without one (tests, read-only tools) none are made.
63
    pub async fn open(path: &Path, instance: Option<KeyPair>) -> Result<Ledger> {
64
        let lock_path = path.with_extension("lock");
65
        let lock = std::fs::OpenOptions::new()
66
            .create(true)
67
            .truncate(false)
68
            .write(true)
69
            .open(&lock_path)
70
            .with_context(|| format!("opening {}", lock_path.display()))?;
71
        match lock.try_lock() {
72
            Ok(()) => {}
73
            Err(std::fs::TryLockError::WouldBlock) => {
74
                bail!("another process is writing to {} (is `spectra serve` running? stop it, or do this through it)", path.display())
75
            }
76
            Err(std::fs::TryLockError::Error(e)) => return Err(e).with_context(|| format!("locking {}", lock_path.display())),
77
        }
78
        let mut l = Ledger::open_unlocked(path, instance).await?;
79
        l._lock = Some(lock);
80
        Ok(l)
81
    }
82
83
    /// Open for reading and verifying only, while another process may be
84
    /// writing: no lock, and `append` must not be called.
85
    pub async fn open_read_only(path: &Path) -> Result<Ledger> {
86
        Ledger::open_unlocked(path, None).await
87
    }
88
89
    async fn open_unlocked(path: &Path, instance: Option<KeyPair>) -> Result<Ledger> {
90
        let opts = SqliteConnectOptions::new()
91
            .filename(path)
92
            .create_if_missing(true)
93
            .journal_mode(SqliteJournalMode::Wal)
94
            .synchronous(SqliteSynchronous::Normal)
95
            .foreign_keys(true)
96
            .busy_timeout(std::time::Duration::from_secs(10));
97
        let pool =
98
            SqlitePoolOptions::new().max_connections(8).connect_with(opts).await.with_context(|| format!("opening {}", path.display()))?;
99
        sqlx::migrate!("./migrations").run(&pool).await.context("applying migrations")?;
100
        let format: Option<String> = sqlx::query_scalar("SELECT value FROM meta WHERE key = 'ledger_format'").fetch_optional(&pool).await?;
101
        match format.as_deref().map(str::parse::<u32>) {
102
            None => {
103
                sqlx::query("INSERT INTO meta (key, value) VALUES ('ledger_format', ?)")
104
                    .bind(crate::LEDGER_FORMAT.to_string())
105
                    .execute(&pool)
106
                    .await?;
107
            }
108
            Some(Ok(f)) if f <= crate::LEDGER_FORMAT => {}
109
            Some(_) => {
110
                bail!("{} was written by a newer Spectra (ledger format {}): upgrade spectra", path.display(), format.unwrap_or_default())
111
            }
112
        }
113
        let last =
114
            sqlx::query("SELECT seq, hash, hlc_wall, hlc_logical FROM events ORDER BY seq DESC LIMIT 1").fetch_optional(&pool).await?;
115
        let (seq, hash, hlc) = match last {
116
            Some(r) => (
117
                r.get::<i64, _>(0) as u64,
118
                to32(r.get::<Vec<u8>, _>(1))?,
119
                Hlc { wall_ms: r.get::<i64, _>(2) as u64, logical: r.get::<i64, _>(3) as u32 },
120
            ),
121
            None => (0, [0; 32], Hlc::default()),
122
        };
123
        let checkpoint_seq: i64 =
124
            sqlx::query_scalar("SELECT COALESCE(MAX(seq), 0) FROM events WHERE kind = 'ledger.checkpoint'").fetch_one(&pool).await?;
125
        let since = seq.saturating_sub(checkpoint_seq as u64);
126
        let (bus, _) = broadcast::channel(4096);
127
        Ok(Ledger {
128
            pool: pool.clone(),
129
            _lock: None,
130
            w: Arc::new(Writer {
131
                pool,
132
                head: Mutex::new(Head { seq, hash, since_checkpoint: since, oldest_unchecked_ms: (since > 0).then(ids::now_ms) }),
133
                clock: Clock::starting_at(hlc),
134
                bus,
135
                instance,
136
            }),
137
        })
138
    }
139
140
    /// The database, for projections' readers.
141
    pub fn pool(&self) -> &SqlitePool {
142
        &self.pool
143
    }
144
145
    /// Every appended event, as it commits.
146
    pub fn subscribe(&self) -> broadcast::Receiver<Arc<Event>> {
147
        self.w.bus.subscribe()
148
    }
149
150
    /// The instance key, when this ledger signs checkpoints.
151
    pub fn instance(&self) -> Option<&KeyPair> {
152
        self.w.instance.as_ref()
153
    }
154
155
    /// The newest event's sequence number and hash.
156
    pub async fn head(&self) -> (u64, [u8; 32]) {
157
        let h = self.w.head.lock().await;
158
        (h.seq, h.hash)
159
    }
160
161
    /// Append `events` in one transaction, chained and projected, then
162
    /// publish them; a checkpoint follows when one is due. The work runs to
163
    /// completion even if the caller is dropped.
164
    pub async fn append(&self, events: Vec<NewEvent>) -> Result<Vec<Arc<Event>>> {
165
        let w = self.w.clone();
166
        tokio::spawn(async move { w.append(events).await }).await.context("the ledger's writer stopped")?
167
    }
168
169
    /// Append one event.
170
    pub async fn append_one(&self, e: NewEvent) -> Result<Arc<Event>> {
171
        Ok(self.append(vec![e]).await?.remove(0))
172
    }
173
174
    /// Write a checkpoint now if any event is not covered by one (at
175
    /// shutdown, and from the ten-minute timer).
176
    pub async fn checkpoint(&self) -> Result<Option<Arc<Event>>> {
177
        let w = self.w.clone();
178
        tokio::spawn(async move { w.checkpoint().await }).await.context("the ledger's writer stopped")?
179
    }
180
181
    /// Events from `from` (inclusive), oldest first, at most `limit`.
182
    pub async fn events(&self, from: u64, limit: u32) -> Result<Vec<Event>> {
183
        let rows = sqlx::query(&format!("{SELECT_EVENT} WHERE seq >= ? ORDER BY seq LIMIT ?"))
184
            .bind(from as i64)
185
            .bind(limit as i64)
186
            .fetch_all(&self.pool)
187
            .await?;
188
        rows.iter().map(row_to_event).collect()
189
    }
190
191
    /// Events of the given kinds from `from` (inclusive), oldest first.
192
    pub async fn events_of_kinds(&self, from: u64, kinds: &[&str], limit: u32) -> Result<Vec<Event>> {
193
        let marks = vec!["?"; kinds.len()].join(",");
194
        let sql = format!("{SELECT_EVENT} WHERE seq >= ? AND kind IN ({marks}) ORDER BY seq LIMIT ?");
195
        let mut q = sqlx::query(&sql).bind(from as i64);
196
        for k in kinds {
197
            q = q.bind(*k);
198
        }
199
        let rows = q.bind(limit as i64).fetch_all(&self.pool).await?;
200
        rows.iter().map(row_to_event).collect()
201
    }
202
203
    /// Recompute the chain and check every checkpoint and every proof it
204
    /// can, from the ledger alone (spec §9.5, `spectra admin verify`).
205
    pub async fn verify(&self, keys: &mut dyn KeyResolver) -> Result<Verification> {
206
        let mut v = Verification::default();
207
        let mut prev = [0u8; 32];
208
        let mut expect = 1u64;
209
        let mut instance: Option<Identity> = None;
210
        let mut from = 1;
211
        loop {
212
            let batch = self.events(from, 2_000).await?;
213
            if batch.is_empty() {
214
                break;
215
            }
216
            for e in &batch {
217
                v.events += 1;
218
                if e.seq != expect {
219
                    v.finding(e.seq, Problem::Gap, format!("expected event {expect}"));
220
                    expect = e.seq;
221
                }
222
                if e.prev != prev {
223
                    v.finding(e.seq, Problem::ChainBreak, "prev is not the hash of the event before".into());
224
                }
225
                if e.payload.is_some() && e.computed_hash() != e.hash {
226
                    v.finding(e.seq, Problem::HashMismatch, "the event's fields do not give its hash: it was edited".into());
227
                }
228
                if e.kind == "identity.instance" {
229
                    instance = e
230
                        .json()
231
                        .get("identity")
232
                        .and_then(|h| h.as_str())
233
                        .and_then(|h| hex::decode(h).ok())
234
                        .and_then(|b| Identity::decode(&b).ok());
235
                }
236
                if e.kind == "ledger.checkpoint" {
237
                    v.checkpoints += 1;
238
                    match check_checkpoint(e, prev, instance.as_ref()) {
239
                        Ok(()) => v.last_checkpoint = Some(e.seq),
240
                        Err(why) => v.finding(e.seq, Problem::BadCheckpoint, why),
241
                    }
242
                } else if let Err(why) = keys.check(e) {
243
                    v.finding(e.seq, Problem::BadProof, why);
244
                }
245
                prev = e.hash;
246
                expect += 1;
247
            }
248
            from = batch.last().map(|e| e.seq + 1).unwrap_or(from);
249
        }
250
        v.head = prev;
251
        v.uncovered = v.events.saturating_sub(v.last_checkpoint.unwrap_or(0));
252
        Ok(v)
253
    }
254
255
    /// Clear every projection and replay the ledger into them.
256
    pub async fn rebuild(&self) -> Result<()> {
257
        let w = self.w.clone();
258
        tokio::spawn(async move { w.rebuild().await }).await.context("the ledger's writer stopped")?
259
    }
260
261
    /// Whether the projections equal a rebuild from the ledger: rebuild in
262
    /// a transaction, compare, and roll back. Returns the tables that differ.
263
    pub async fn check(&self) -> Result<Vec<String>> {
264
        let w = self.w.clone();
265
        tokio::spawn(async move { w.check().await }).await.context("the ledger's writer stopped")?
266
    }
267
}
268
269
impl Writer {
270
    /// Append `events` in one transaction, chained and projected, then
271
    /// publish them; a checkpoint follows when one is due.
272
    async fn append(&self, events: Vec<NewEvent>) -> Result<Vec<Arc<Event>>> {
273
        let mut head = self.head.lock().await;
274
        let mut tx = self.pool.begin().await?;
275
        let mut out = Vec::with_capacity(events.len() + 1);
276
        let mut h = *head;
277
        for n in events {
278
            let e = self.chain(&mut h, n);
279
            insert(&mut tx, &e).await?;
280
            projections::apply(&mut tx, &e).await?;
281
            h.since_checkpoint += 1;
282
            h.oldest_unchecked_ms.get_or_insert(e.at_ms);
283
            out.push(e);
284
            // Every 256 events exactly, even inside a large batch.
285
            if h.since_checkpoint >= CHECKPOINT_EVERY && self.instance.is_some() {
286
                let cp = self.checkpoint_event(&mut h);
287
                insert(&mut tx, &cp).await?;
288
                projections::apply(&mut tx, &cp).await?;
289
                out.push(cp);
290
            }
291
        }
292
        let due = h.oldest_unchecked_ms.is_some_and(|t| ids::now_ms().saturating_sub(t) >= CHECKPOINT_INTERVAL_MS);
293
        if due && self.instance.is_some() {
294
            let cp = self.checkpoint_event(&mut h);
295
            insert(&mut tx, &cp).await?;
296
            projections::apply(&mut tx, &cp).await?;
297
            out.push(cp);
298
        }
299
        tx.commit().await?;
300
        *head = h;
301
        drop(head);
302
        let out: Vec<Arc<Event>> = out.into_iter().map(Arc::new).collect();
303
        for e in &out {
304
            let _ = self.bus.send(e.clone());
305
        }
306
        Ok(out)
307
    }
308
309
    /// Write a checkpoint now if any event is not covered by one (at
310
    /// shutdown, and from the ten-minute timer).
311
    async fn checkpoint(&self) -> Result<Option<Arc<Event>>> {
312
        let Some(_) = &self.instance else { return Ok(None) };
313
        let mut head = self.head.lock().await;
314
        if head.since_checkpoint == 0 {
315
            return Ok(None);
316
        }
317
        let mut h = *head;
318
        let cp = self.checkpoint_event(&mut h);
319
        let mut tx = self.pool.begin().await?;
320
        insert(&mut tx, &cp).await?;
321
        projections::apply(&mut tx, &cp).await?;
322
        tx.commit().await?;
323
        *head = h;
324
        drop(head);
325
        let cp = Arc::new(cp);
326
        let _ = self.bus.send(cp.clone());
327
        Ok(Some(cp))
328
    }
329
330
    fn chain(&self, h: &mut Head, n: NewEvent) -> Event {
331
        let at_ms = ids::now_ms();
332
        let mut e = Event {
333
            seq: h.seq + 1,
334
            id: ids::ulid(),
335
            at_ms,
336
            hlc: self.clock.tick(at_ms),
337
            prev: h.hash,
338
            hash: [0; 32],
339
            auth: n.auth,
340
            kind: n.kind,
341
            actor: n.actor,
342
            session: n.session,
343
            project: n.project,
344
            subject: n.subject,
345
            payload: Some(canonical_json(&n.payload)),
346
            trace: n.trace,
347
        };
348
        e.hash = e.computed_hash();
349
        h.seq = e.seq;
350
        h.hash = e.hash;
351
        e
352
    }
353
354
    /// A checkpoint naming the head `h` holds, signed by the instance key
355
    /// over `"spectra1 checkpoint " || hash || seq` (spec §9.5).
356
    fn checkpoint_event(&self, h: &mut Head) -> Event {
357
        let instance = self.instance.as_ref().expect("checked by callers");
358
        let (seq, hash, count) = (h.seq, h.hash, h.since_checkpoint);
359
        let sig = instance.sign(&checkpoint_message(seq, &hash));
360
        let n = NewEvent::system("ledger.checkpoint", serde_json::json!({ "seq": seq, "hash": hex::encode(hash), "count": count })).by(
361
            "system",
362
            Auth { key: Some(instance.key.0), method: Method::Signature, assurance: 2, proof: Some(crate::cbor::encode(&sig.to_value())) },
363
        );
364
        let e = self.chain(h, n);
365
        h.since_checkpoint = 0;
366
        h.oldest_unchecked_ms = None;
367
        e
368
    }
369
370
    /// Clear every projection and replay the ledger into them.
371
    async fn rebuild(&self) -> Result<()> {
372
        let _head = self.head.lock().await;
373
        let mut tx = self.pool.begin().await?;
374
        replay(&mut tx).await?;
375
        tx.commit().await?;
376
        Ok(())
377
    }
378
379
    /// Whether the projections equal a rebuild from the ledger: rebuild in
380
    /// a transaction, compare, and roll back. Returns the tables that differ.
381
    async fn check(&self) -> Result<Vec<String>> {
382
        let _head = self.head.lock().await;
383
        let mut tx = self.pool.begin().await?;
384
        let mut before = Vec::new();
385
        for t in projections::TABLES {
386
            before.push(dump(&mut tx, t).await?);
387
        }
388
        replay(&mut tx).await?;
389
        let mut differ = Vec::new();
390
        for (t, b) in projections::TABLES.iter().zip(before) {
391
            if dump(&mut tx, t).await? != b {
392
                differ.push(t.to_string());
393
            }
394
        }
395
        tx.rollback().await?;
396
        Ok(differ)
397
    }
398
}
399
400
/// `"spectra1 checkpoint " || hash || seq (8 bytes, big-endian)`.
401
pub fn checkpoint_message(seq: u64, hash: &[u8; 32]) -> Vec<u8> {
402
    let mut m = CHECKPOINT_DOMAIN.to_vec();
403
    m.extend_from_slice(hash);
404
    m.extend_from_slice(&seq.to_be_bytes());
405
    m
406
}
407
408
fn check_checkpoint(e: &Event, prev: [u8; 32], instance: Option<&Identity>) -> std::result::Result<(), String> {
409
    let p = e.json();
410
    let seq = p.get("seq").and_then(|s| s.as_u64()).ok_or("no seq")?;
411
    let hash: [u8; 32] =
412
        p.get("hash").and_then(|h| h.as_str()).and_then(|h| hex::decode(h).ok()).and_then(|b| b.try_into().ok()).ok_or("no hash")?;
413
    if seq + 1 != e.seq || hash != prev {
414
        return Err("names a head other than the event before it".into());
415
    }
416
    let instance = instance.ok_or("no instance identity precedes it")?;
417
    if e.auth.key != Some(instance.hash().0) {
418
        return Err("signed by a key other than the instance's".into());
419
    }
420
    let proof = e.auth.proof.as_deref().ok_or("unsigned")?;
421
    let sig = crate::cbor::decode(proof).ok().and_then(|v| Signature::from_value(&v).ok()).ok_or("malformed signature")?;
422
    crate::identity::verify(instance, &sig, &checkpoint_message(seq, &hash)).map_err(|e| format!("signature does not verify: {e}"))
423
}
424
425
/// Checks the proof of a signed event: the identities and delegations that
426
/// say whose key it was (spec §8.5). Principals supply it (`principals`).
427
/// It is given every event but checkpoints, in order, so it can follow
428
/// identities as they are recorded.
429
pub trait KeyResolver: Send {
430
    fn check(&mut self, e: &Event) -> std::result::Result<(), String>;
431
}
432
433
/// A resolver that knows no keys: every signed event is reported.
434
pub struct NoKeys;
435
impl KeyResolver for NoKeys {
436
    fn check(&mut self, e: &Event) -> std::result::Result<(), String> {
437
        match e.auth.method {
438
            Method::Signature | Method::Passkey => Err("no identity is known for its key".into()),
439
            _ => Ok(()),
440
        }
441
    }
442
}
443
444
/// What `verify` found.
445
#[derive(Clone, Debug, Default, serde::Serialize)]
446
pub struct Verification {
447
    pub events: u64,
448
    pub checkpoints: u64,
449
    pub last_checkpoint: Option<u64>,
450
    /// Events after the last good checkpoint: chained, not yet signed.
451
    pub uncovered: u64,
452
    #[serde(serialize_with = "ser_hex")]
453
    pub head: [u8; 32],
454
    pub findings: Vec<Finding>,
455
}
456
457
fn ser_hex<S: serde::Serializer>(h: &[u8; 32], s: S) -> std::result::Result<S::Ok, S::Error> {
458
    s.serialize_str(&hex::encode(h))
459
}
460
461
impl Verification {
462
    pub fn clean(&self) -> bool {
463
        self.findings.is_empty()
464
    }
465
    fn finding(&mut self, seq: u64, problem: Problem, detail: String) {
466
        self.findings.push(Finding { seq, problem, detail });
467
    }
468
}
469
470
/// One thing wrong with the ledger.
471
#[derive(Clone, Debug, serde::Serialize)]
472
pub struct Finding {
473
    pub seq: u64,
474
    pub problem: Problem,
475
    pub detail: String,
476
}
477
478
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
479
#[serde(rename_all = "snake_case")]
480
pub enum Problem {
481
    Gap,
482
    ChainBreak,
483
    HashMismatch,
484
    BadCheckpoint,
485
    BadProof,
486
}
487
488
const SELECT_EVENT: &str = "SELECT seq, id, at_ms, hlc_wall, hlc_logical, prev, hash, auth_key, auth_method, auth_assurance, auth_proof, kind, actor, session, project, subject, payload, trace FROM events";
489
490
async fn insert(conn: &mut sqlx::SqliteConnection, e: &Event) -> sqlx::Result<()> {
491
    sqlx::query(
492
        "INSERT INTO events (seq, id, at_ms, hlc_wall, hlc_logical, prev, hash, auth_key, auth_method, auth_assurance, auth_proof, kind, actor, session, project, subject, payload, trace) \
493
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
494
    )
495
    .bind(e.seq as i64)
496
    .bind(&e.id)
497
    .bind(e.at_ms as i64)
498
    .bind(e.hlc.wall_ms as i64)
499
    .bind(e.hlc.logical as i64)
500
    .bind(e.prev.to_vec())
501
    .bind(e.hash.to_vec())
502
    .bind(e.auth.key.map(|k| k.to_vec()))
503
    .bind(e.auth.method as i64)
504
    .bind(e.auth.assurance as i64)
505
    .bind(e.auth.proof.clone())
506
    .bind(&e.kind)
507
    .bind(&e.actor)
508
    .bind(&e.session)
509
    .bind(&e.project)
510
    .bind(&e.subject)
511
    .bind(e.payload.clone())
512
    .bind(&e.trace)
513
    .execute(conn)
514
    .await?;
515
    Ok(())
516
}
517
518
fn row_to_event(r: &SqliteRow) -> Result<Event> {
519
    Ok(Event {
520
        seq: r.get::<i64, _>(0) as u64,
521
        id: r.get(1),
522
        at_ms: r.get::<i64, _>(2) as u64,
523
        hlc: Hlc { wall_ms: r.get::<i64, _>(3) as u64, logical: r.get::<i64, _>(4) as u32 },
524
        prev: to32(r.get(5))?,
525
        hash: to32(r.get(6))?,
526
        auth: Auth {
527
            key: r.get::<Option<Vec<u8>>, _>(7).map(to32).transpose()?,
528
            method: Method::from_u8(r.get::<i64, _>(8) as u8).context("unknown auth method")?,
529
            assurance: r.get::<i64, _>(9) as u8,
530
            proof: r.get(10),
531
        },
532
        kind: r.get(11),
533
        actor: r.get(12),
534
        session: r.get(13),
535
        project: r.get(14),
536
        subject: r.get(15),
537
        payload: r.get(16),
538
        trace: r.get(17),
539
    })
540
}
541
542
fn to32(v: Vec<u8>) -> Result<[u8; 32]> {
543
    v.try_into().map_err(|_| anyhow::anyhow!("a stored hash is not 32 bytes"))
544
}
545
546
async fn replay(conn: &mut sqlx::SqliteConnection) -> Result<()> {
547
    for t in projections::TABLES {
548
        sqlx::query(&format!("DELETE FROM {t}")).execute(&mut *conn).await?;
549
    }
550
    let mut from = 1i64;
551
    loop {
552
        let rows = sqlx::query(&format!("{SELECT_EVENT} WHERE seq >= ? ORDER BY seq LIMIT 2000")).bind(from).fetch_all(&mut *conn).await?;
553
        if rows.is_empty() {
554
            break;
555
        }
556
        for r in &rows {
557
            let e = row_to_event(r)?;
558
            projections::apply(conn, &e).await?;
559
            from = e.seq as i64 + 1;
560
        }
561
    }
562
    Ok(())
563
}
564
565
async fn dump(conn: &mut sqlx::SqliteConnection, table: &str) -> Result<Vec<String>> {
566
    // Every column as text, rows in a fixed order: enough to compare.
567
    let rows = sqlx::query(&format!("SELECT * FROM {table} ORDER BY 1, 2")).fetch_all(&mut *conn).await?;
568
    Ok(rows
569
        .iter()
570
        .map(|r| {
571
            (0..r.columns().len())
572
                .map(|i| match r.try_get::<Option<String>, _>(i) {
573
                    Ok(v) => format!("{v:?}"),
574
                    Err(_) => match r.try_get::<Option<i64>, _>(i) {
575
                        Ok(n) => format!("{n:?}"),
576
                        Err(_) => format!("{:?}", r.try_get::<Option<Vec<u8>>, _>(i).ok().flatten()),
577
                    },
578
                })
579
                .collect::<Vec<_>>()
580
                .join("|")
581
        })
582
        .collect())
583
}
584
585
/// The identity of `key` among `identities`, by KeyId.
586
pub fn find_identity<'a>(identities: &'a [Identity], key: &KeyId) -> Option<&'a Identity> {
587
    identities.iter().find(|i| &i.hash() == key)
588
}