@cero-base/core 1.10.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cero-base/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "cero p2p primitives — identity, storage, network, database, blobs, rpc, pairing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
},
|
|
145
145
|
"dependencies": {
|
|
146
146
|
"@hyperswarm/secret-stream": "^6.9.1",
|
|
147
|
-
"autobee": "2.0.0-rc.
|
|
147
|
+
"autobee": "2.0.0-rc.14",
|
|
148
148
|
"autobee-encryption": "0.1.3",
|
|
149
149
|
"b4a": "^1.8.1",
|
|
150
150
|
"bare-crypto": "^1.15.3",
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import b4a from 'b4a'
|
|
2
1
|
import Hypercore from 'hypercore'
|
|
3
2
|
import { Identity } from '../identity/index.js'
|
|
4
3
|
import { CeroError } from '../lib/errors.js'
|
|
5
4
|
import { toId, addWriterPayload } from '../lib/utils.js'
|
|
5
|
+
import { wrap } from './envelope.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* First-run device provisioning: mint a device writer keypair, persist it as a
|
|
9
9
|
* writer, then swap the autobee's local core over to it. With `recovering`,
|
|
10
|
-
*
|
|
10
|
+
* the swap happens FIRST and admission rides an optimistic append — the
|
|
11
|
+
* backfilled genesis core is never appended to (autobee's contract: a writable
|
|
12
|
+
* core has exactly one author, ever; resuming a replicated core is unsupported
|
|
13
|
+
* and drops appends silently).
|
|
11
14
|
*
|
|
12
15
|
* @param {import('./index.js').Database} db
|
|
13
16
|
* @param {{ name?: string | null, isMobile?: boolean, recovering?: boolean }} [opts]
|
|
@@ -22,87 +25,64 @@ export async function bootstrap(db, { name, isMobile, recovering = false, timeou
|
|
|
22
25
|
const writerKey = Hypercore.key(manifest)
|
|
23
26
|
|
|
24
27
|
if (recovering) {
|
|
25
|
-
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
await
|
|
30
|
-
|
|
31
|
-
if (recovering) {
|
|
32
|
-
// gate the swap on the admission actually applying: write() can resolve
|
|
33
|
-
// with the ops stalled in the drain (update() bumps heal that) or dropped
|
|
34
|
-
// outright when they were stamped behind a mid-replication core — each
|
|
35
|
-
// retry stamps past the collision, and the ops are idempotent
|
|
36
|
-
await admitWriter(db, writerKey, { name, isMobile }, timeout)
|
|
28
|
+
// swap before anything else: the genesis core then replicates as a REMOTE
|
|
29
|
+
// writer, so its backfilled ops apply through the normal download path
|
|
30
|
+
await swapWriter(db, keyPair, manifest)
|
|
31
|
+
await waitForBackfill(db, timeout)
|
|
32
|
+
await claimWriter(db, writerKey, timeout)
|
|
33
|
+
await saveDevice(db, writerKey, { name, isMobile })
|
|
37
34
|
} else {
|
|
38
35
|
await saveWriter(db, writerKey, { name, isMobile })
|
|
36
|
+
await swapWriter(db, keyPair, manifest)
|
|
39
37
|
}
|
|
40
|
-
await swapWriter(db, keyPair, manifest)
|
|
41
38
|
|
|
42
39
|
return { id: writerKey, writer: keyPair }
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// the first block may not be the whole core — writes stamped below the
|
|
50
|
-
// length the system already attributes to this writer are silently dropped
|
|
51
|
-
const info = await db.bee.system.get(db.bee.local.key).catch(() => null)
|
|
52
|
-
if (info && info.length > db.bee.local.length) await waitForLength(db, info.length, timeout)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function waitForLength(db, length, timeout) {
|
|
56
|
-
return new Promise((resolve, reject) => {
|
|
57
|
-
let done = false
|
|
58
|
-
const finish = (err) => {
|
|
59
|
-
if (done) return
|
|
60
|
-
done = true
|
|
61
|
-
clearTimeout(timer)
|
|
62
|
-
db.bee.local.off('append', onAppend)
|
|
63
|
-
db.off('close', onClose)
|
|
64
|
-
if (err) reject(err)
|
|
65
|
-
else resolve()
|
|
66
|
-
}
|
|
67
|
-
// timeout + close handling so a peer that never replicates can't hang the
|
|
68
|
-
// recovery forever, and the append listener is always removed.
|
|
69
|
-
const onAppend = () => db.bee.local.length >= length && finish()
|
|
70
|
-
const onClose = () => finish(CeroError.CLOSED('Database'))
|
|
71
|
-
const timer = setTimeout(
|
|
72
|
-
() => finish(CeroError.TIMED_OUT('recovery — no peer append')),
|
|
73
|
-
timeout
|
|
74
|
-
)
|
|
75
|
-
db.bee.local.on('append', onAppend)
|
|
76
|
-
db.on('close', onClose)
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function admitWriter(db, writerKey, opts, timeout) {
|
|
81
|
-
const deadline = Date.now() + timeout
|
|
82
|
-
const id = toId(writerKey)
|
|
83
|
-
while (true) {
|
|
84
|
-
await saveWriter(db, writerKey, opts)
|
|
85
|
-
if (await deviceApplied(db, id, Math.min(3000, deadline - Date.now()))) return
|
|
86
|
-
if (Date.now() >= deadline) throw CeroError.TIMED_OUT('recovery — writer not admitted')
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function deviceApplied(db, id, timeout) {
|
|
42
|
+
// recovery has state to recover by definition — wait until replication has
|
|
43
|
+
// delivered and applied it (the genesis device row is the first thing every
|
|
44
|
+
// db writes), so a peer that never replicates fails loud instead of hanging
|
|
45
|
+
async function waitForBackfill(db, timeout) {
|
|
91
46
|
const deadline = Date.now() + timeout
|
|
92
47
|
while (Date.now() < deadline) {
|
|
93
|
-
|
|
94
|
-
|
|
48
|
+
if (db.closing || db.closed) throw CeroError.CLOSED('Database')
|
|
49
|
+
const { data } = await db.get('devices')
|
|
50
|
+
if (data.length > 0) return
|
|
95
51
|
await db.bee.update()
|
|
96
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
52
|
+
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
97
53
|
}
|
|
98
|
-
|
|
54
|
+
throw CeroError.TIMED_OUT('recovery — no peer replicated')
|
|
99
55
|
}
|
|
100
56
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
57
|
+
// self-admission from the fresh core: the same master-signed add-writer op a
|
|
58
|
+
// live device would write, appended optimistically so an unadmitted writer
|
|
59
|
+
// can carry it — apply verifies the attestation and admits the core
|
|
60
|
+
async function claimWriter(db, writerKey, timeout) {
|
|
61
|
+
const encoded = db.spec.dispatch.encode(`@${db.ns}/add-writer`, {
|
|
62
|
+
master: db.identity.publicKey,
|
|
63
|
+
writer: writerKey,
|
|
64
|
+
sig: db.identity.sign(addWriterPayload(db.key, writerKey, db.writerKey)),
|
|
65
|
+
ts: Date.now()
|
|
66
|
+
})
|
|
67
|
+
await db.bee.append(wrap(db.version, encoded), { optimistic: true })
|
|
68
|
+
await db.bee.update()
|
|
69
|
+
if (!db.bee.writable) await db.whenWritable({ timeout })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function saveDevice(db, writerKey, { name, isMobile }) {
|
|
73
|
+
const ts = Date.now()
|
|
74
|
+
await db.write([
|
|
75
|
+
[
|
|
76
|
+
'set-device',
|
|
77
|
+
{
|
|
78
|
+
id: toId(writerKey),
|
|
79
|
+
name: name || null,
|
|
80
|
+
isMobile: isMobile === true,
|
|
81
|
+
createdAt: ts,
|
|
82
|
+
updatedAt: ts
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
])
|
|
106
86
|
}
|
|
107
87
|
|
|
108
88
|
async function saveWriter(db, writerKey, { name, isMobile }) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* First-run device provisioning: mint a device writer keypair, persist it as a
|
|
3
3
|
* writer, then swap the autobee's local core over to it. With `recovering`,
|
|
4
|
-
*
|
|
4
|
+
* the swap happens FIRST and admission rides an optimistic append — the
|
|
5
|
+
* backfilled genesis core is never appended to (autobee's contract: a writable
|
|
6
|
+
* core has exactly one author, ever; resuming a replicated core is unsupported
|
|
7
|
+
* and drops appends silently).
|
|
5
8
|
*
|
|
6
9
|
* @param {import('./index.js').Database} db
|
|
7
10
|
* @param {{ name?: string | null, isMobile?: boolean, recovering?: boolean }} [opts]
|