@optimystic/db-p2p 0.24.2 → 0.25.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/{readme.md → README.md} +688 -668
- package/dist/src/cluster/block-transfer-service.d.ts +133 -10
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +155 -9
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/block-transfer.d.ts +67 -7
- package/dist/src/cluster/block-transfer.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer.js +127 -24
- package/dist/src/cluster/block-transfer.js.map +1 -1
- package/dist/src/cluster/certified-claims.d.ts +170 -0
- package/dist/src/cluster/certified-claims.d.ts.map +1 -0
- package/dist/src/cluster/certified-claims.js +200 -0
- package/dist/src/cluster/certified-claims.js.map +1 -0
- package/dist/src/cluster/client-signature-verifier.d.ts +23 -0
- package/dist/src/cluster/client-signature-verifier.d.ts.map +1 -0
- package/dist/src/cluster/client-signature-verifier.js +33 -0
- package/dist/src/cluster/client-signature-verifier.js.map +1 -0
- package/dist/src/cluster/cluster-policy.d.ts +9 -3
- package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
- package/dist/src/cluster/cluster-policy.js +111 -27
- package/dist/src/cluster/cluster-policy.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +140 -11
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +485 -29
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/commit-proof.d.ts +134 -0
- package/dist/src/cluster/commit-proof.d.ts.map +1 -0
- package/dist/src/cluster/commit-proof.js +263 -0
- package/dist/src/cluster/commit-proof.js.map +1 -0
- package/dist/src/cluster/quorum-restore.d.ts +95 -8
- package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
- package/dist/src/cluster/quorum-restore.js +113 -14
- package/dist/src/cluster/quorum-restore.js.map +1 -1
- package/dist/src/cluster/rebalance-monitor.d.ts +84 -0
- package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
- package/dist/src/cluster/rebalance-monitor.js +207 -5
- package/dist/src/cluster/rebalance-monitor.js.map +1 -1
- package/dist/src/cluster/reconcile-block.d.ts +27 -7
- package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
- package/dist/src/cluster/reconcile-block.js +179 -38
- package/dist/src/cluster/reconcile-block.js.map +1 -1
- package/dist/src/cluster/service.d.ts +13 -4
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +17 -18
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cluster/spread-on-churn.d.ts +8 -2
- package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
- package/dist/src/cluster/spread-on-churn.js +7 -7
- package/dist/src/cluster/spread-on-churn.js.map +1 -1
- package/dist/src/cohort-topic/host.d.ts.map +1 -1
- package/dist/src/cohort-topic/host.js +8 -7
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +7 -6
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +14 -41
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/cascade.d.ts +7 -8
- package/dist/src/dispute/cascade.d.ts.map +1 -1
- package/dist/src/dispute/cascade.js +24 -7
- package/dist/src/dispute/cascade.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts +5 -0
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +13 -3
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/dispute/invalidation.d.ts +33 -15
- package/dist/src/dispute/invalidation.d.ts.map +1 -1
- package/dist/src/dispute/invalidation.js +127 -34
- package/dist/src/dispute/invalidation.js.map +1 -1
- package/dist/src/dispute/service.d.ts.map +1 -1
- package/dist/src/dispute/service.js +2 -1
- package/dist/src/dispute/service.js.map +1 -1
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +14 -10
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +28 -58
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +38 -7
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +148 -63
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/network/open-protocol-stream.d.ts +56 -0
- package/dist/src/network/open-protocol-stream.d.ts.map +1 -0
- package/dist/src/network/open-protocol-stream.js +60 -0
- package/dist/src/network/open-protocol-stream.js.map +1 -0
- package/dist/src/network/register-protocol-handler.d.ts +66 -0
- package/dist/src/network/register-protocol-handler.d.ts.map +1 -0
- package/dist/src/network/register-protocol-handler.js +51 -0
- package/dist/src/network/register-protocol-handler.js.map +1 -0
- package/dist/src/peer-address-book.d.ts +60 -2
- package/dist/src/peer-address-book.d.ts.map +1 -1
- package/dist/src/peer-address-book.js +89 -1
- package/dist/src/peer-address-book.js.map +1 -1
- package/dist/src/pend-validation.d.ts +65 -0
- package/dist/src/pend-validation.d.ts.map +1 -0
- package/dist/src/pend-validation.js +69 -0
- package/dist/src/pend-validation.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts.map +1 -1
- package/dist/src/reactivity/notify-transport.js +2 -1
- package/dist/src/reactivity/notify-transport.js.map +1 -1
- package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -1
- package/dist/src/reactivity/push-state-gossip.js +2 -1
- package/dist/src/reactivity/push-state-gossip.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +32 -6
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +44 -10
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +181 -25
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +650 -62
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/served-repo-proxy.d.ts +30 -0
- package/dist/src/repo/served-repo-proxy.d.ts.map +1 -0
- package/dist/src/repo/served-repo-proxy.js +50 -0
- package/dist/src/repo/served-repo-proxy.js.map +1 -0
- package/dist/src/repo/service.d.ts +16 -1
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +18 -16
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/reputation/types.d.ts +3 -1
- package/dist/src/reputation/types.d.ts.map +1 -1
- package/dist/src/reputation/types.js +3 -1
- package/dist/src/reputation/types.js.map +1 -1
- package/dist/src/rn.d.ts +24 -2
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +33 -4
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/block-archive.d.ts +153 -0
- package/dist/src/storage/block-archive.d.ts.map +1 -0
- package/dist/src/storage/block-archive.js +194 -0
- package/dist/src/storage/block-archive.js.map +1 -0
- package/dist/src/storage/block-latch.d.ts +92 -0
- package/dist/src/storage/block-latch.d.ts.map +1 -0
- package/dist/src/storage/block-latch.js +132 -0
- package/dist/src/storage/block-latch.js.map +1 -0
- package/dist/src/storage/block-storage.d.ts +169 -12
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +415 -152
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +5 -1
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/cached-raw-storage.js +16 -1
- package/dist/src/storage/cached-raw-storage.js.map +1 -1
- package/dist/src/storage/cached-store-driver.d.ts +14 -4
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -1
- package/dist/src/storage/cached-store-driver.js +39 -5
- package/dist/src/storage/cached-store-driver.js.map +1 -1
- package/dist/src/storage/i-block-storage.d.ts +70 -19
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/i-block-storage.js +21 -1
- package/dist/src/storage/i-block-storage.js.map +1 -1
- package/dist/src/storage/i-raw-storage.d.ts +38 -0
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/kv-raw-storage.d.ts +14 -8
- package/dist/src/storage/kv-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/kv-raw-storage.js +29 -9
- package/dist/src/storage/kv-raw-storage.js.map +1 -1
- package/dist/src/storage/memory-store-driver.d.ts +4 -1
- package/dist/src/storage/memory-store-driver.d.ts.map +1 -1
- package/dist/src/storage/memory-store-driver.js +22 -2
- package/dist/src/storage/memory-store-driver.js.map +1 -1
- package/dist/src/storage/raw-store-codec.js +2 -2
- package/dist/src/storage/raw-store-driver.d.ts +44 -3
- package/dist/src/storage/raw-store-driver.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +60 -3
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.js +68 -4
- package/dist/src/storage/shared-cache-pool.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +204 -26
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +422 -119
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/storage/store-identity.d.ts +21 -0
- package/dist/src/storage/store-identity.d.ts.map +1 -0
- package/dist/src/storage/store-identity.js +33 -0
- package/dist/src/storage/store-identity.js.map +1 -0
- package/dist/src/storage/struct.d.ts +15 -0
- package/dist/src/storage/struct.d.ts.map +1 -1
- package/dist/src/storage/with-read-cache.d.ts +92 -0
- package/dist/src/storage/with-read-cache.d.ts.map +1 -0
- package/dist/src/storage/with-read-cache.js +132 -0
- package/dist/src/storage/with-read-cache.js.map +1 -0
- package/dist/src/sync/service.d.ts +13 -3
- package/dist/src/sync/service.d.ts.map +1 -1
- package/dist/src/sync/service.js +7 -30
- package/dist/src/sync/service.js.map +1 -1
- package/dist/src/testing/mesh-harness.d.ts +75 -2
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +193 -81
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +8 -0
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +97 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +5 -6
- package/src/cluster/block-transfer-service.ts +528 -312
- package/src/cluster/block-transfer.ts +547 -424
- package/src/cluster/certified-claims.ts +319 -0
- package/src/cluster/client-signature-verifier.ts +34 -0
- package/src/cluster/cluster-policy.ts +294 -203
- package/src/cluster/cluster-repo.ts +2501 -2034
- package/src/cluster/commit-proof.ts +344 -0
- package/src/cluster/quorum-restore.ts +348 -223
- package/src/cluster/rebalance-monitor.ts +539 -254
- package/src/cluster/reconcile-block.ts +404 -203
- package/src/cluster/service.ts +23 -20
- package/src/cluster/spread-on-churn.ts +352 -346
- package/src/cohort-topic/host.ts +8 -7
- package/src/cohort-topic/stream-util.ts +14 -43
- package/src/dispute/cascade.ts +31 -15
- package/src/dispute/dispute-service.ts +706 -696
- package/src/dispute/invalidation.ts +162 -52
- package/src/dispute/service.ts +126 -125
- package/src/index.ts +7 -0
- package/src/libp2p-key-network.ts +1204 -1235
- package/src/libp2p-node-base.ts +185 -70
- package/src/network/open-protocol-stream.ts +87 -0
- package/src/network/register-protocol-handler.ts +85 -0
- package/src/peer-address-book.ts +96 -2
- package/src/pend-validation.ts +89 -0
- package/src/reactivity/notify-transport.ts +2 -1
- package/src/reactivity/push-state-gossip.ts +2 -1
- package/src/repo/cluster-coordinator.ts +65 -11
- package/src/repo/coordinator-repo.ts +729 -71
- package/src/repo/served-repo-proxy.ts +55 -0
- package/src/repo/service.ts +25 -16
- package/src/reputation/types.ts +3 -1
- package/src/rn.ts +34 -4
- package/src/storage/block-archive.ts +242 -0
- package/src/storage/block-latch.ts +144 -0
- package/src/storage/block-storage.ts +787 -498
- package/src/storage/cached-raw-storage.ts +21 -1
- package/src/storage/cached-store-driver.ts +898 -859
- package/src/storage/i-block-storage.ts +172 -116
- package/src/storage/i-raw-storage.ts +40 -0
- package/src/storage/kv-raw-storage.ts +34 -9
- package/src/storage/memory-store-driver.ts +26 -2
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +45 -3
- package/src/storage/shared-cache-pool.ts +516 -452
- package/src/storage/storage-repo.ts +1421 -1014
- package/src/storage/store-identity.ts +41 -0
- package/src/storage/struct.ts +52 -34
- package/src/storage/with-read-cache.ts +182 -0
- package/src/sync/service.ts +178 -198
- package/src/testing/mesh-harness.ts +524 -344
- package/src/testing/raw-storage-conformance.ts +114 -2
|
@@ -1,223 +1,348 @@
|
|
|
1
|
-
import type { IBlock } from "@optimystic/db-core";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* wins
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
1
|
+
import type { IBlock } from "@optimystic/db-core";
|
|
2
|
+
import type { BlockCommitProof } from "./commit-proof.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Quorum-corroboration helpers shared by the two block-restoration paths:
|
|
6
|
+
* - read-repair (`CoordinatorRepo.queryClusterForLatest`)
|
|
7
|
+
* - reconcile (`libp2p-node-base.reconcileBlock`)
|
|
8
|
+
*
|
|
9
|
+
* Both paths previously trusted a single peer's self-reported "latest" (max rev
|
|
10
|
+
* wins), so one lying peer could steer restoration. These helpers replace
|
|
11
|
+
* "max wins" with "highest value corroborated by a quorum of distinct peers".
|
|
12
|
+
*
|
|
13
|
+
* NOTE: the quorum here is corroboration-of-a-claim, NOT Sybil-resistant cohort
|
|
14
|
+
* membership. A peer minting fresh keypairs can still cast a vote. Selection now
|
|
15
|
+
* additionally weighs *certified* claims — those whose cohort commit proof a
|
|
16
|
+
* caller has already verified (`cluster/certified-claims.ts`) and marked via the
|
|
17
|
+
* injected `certified` flag — so a lone honest holder with a valid proof is
|
|
18
|
+
* sufficient where uncertified claims still need distinct-peer corroboration.
|
|
19
|
+
* Verification itself never happens here: both selectors stay pure and
|
|
20
|
+
* synchronous; verdicts arrive as booleans.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** A single peer's self-reported (rev, actionId) for a block. */
|
|
24
|
+
export interface RevClaim {
|
|
25
|
+
/** Distinct voter identity (peer-id string). One vote per distinct peerId per group. */
|
|
26
|
+
peerId: string;
|
|
27
|
+
rev: number;
|
|
28
|
+
actionId: string;
|
|
29
|
+
/**
|
|
30
|
+
* The cohort commit proof the claiming peer attached, when it had one. Selection never reads
|
|
31
|
+
* this field: presence proves nothing on its own — the peer chose what to attach. A caller
|
|
32
|
+
* that verifies it (`certifyClaim` / `certifyContent` in `cluster/certified-claims.ts`, built
|
|
33
|
+
* on `verifyBlockCommitProofClaim`) records the verdict in {@link certified}, which is what
|
|
34
|
+
* {@link selectQuorumRev} weighs.
|
|
35
|
+
*/
|
|
36
|
+
proof?: BlockCommitProof;
|
|
37
|
+
/**
|
|
38
|
+
* Injected verdict: the caller verified this claim's cohort commit proof and it certifies this
|
|
39
|
+
* exact `(rev, actionId)`. A certified claim carries the cohort's signature set as its
|
|
40
|
+
* corroboration, so {@link selectQuorumRev} can select it without a second peer vouching.
|
|
41
|
+
* Never set this from the mere presence of {@link proof}.
|
|
42
|
+
*/
|
|
43
|
+
certified?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The (rev, actionId) pair a quorum agreed on, plus the peers that corroborated it. */
|
|
47
|
+
export interface QuorumRev {
|
|
48
|
+
rev: number;
|
|
49
|
+
actionId: string;
|
|
50
|
+
/**
|
|
51
|
+
* Distinct peer-ids that voted for this exact (rev, actionId). When {@link certified} is set,
|
|
52
|
+
* these are the certified claimants at that pair instead — possibly a single peer, whose
|
|
53
|
+
* corroboration is the proof's signature set rather than other voters.
|
|
54
|
+
*/
|
|
55
|
+
supporters: string[];
|
|
56
|
+
/** Set when the certified path selected this pair, so callers can log which rule won. */
|
|
57
|
+
certified?: true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Votes a claim needs when the cohort is big enough to supply them: a claim must be
|
|
62
|
+
* seconded by a second, independent peer. See {@link quorumSize} for the cap that
|
|
63
|
+
* applies when the cohort is smaller than this.
|
|
64
|
+
*/
|
|
65
|
+
export const CORROBORATION_FLOOR = 2;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Number of distinct corroborating votes required to accept a claim:
|
|
69
|
+
* `floor(simpleMajorityThreshold × responderCount)`, never below
|
|
70
|
+
* {@link CORROBORATION_FLOOR} — except that demanding more corroborators than the cohort
|
|
71
|
+
* can possibly supply is a deadlock, not a safety property, so the floor is additionally
|
|
72
|
+
* capped at `corroboratorCapacity`.
|
|
73
|
+
*
|
|
74
|
+
* `corroboratorCapacity` is how many peers OTHER than the asking node could answer at
|
|
75
|
+
* all. Omit it when the caller cannot state one; the absolute floor of two then applies,
|
|
76
|
+
* which is the conservative direction (a claim no one seconded is never accepted).
|
|
77
|
+
*
|
|
78
|
+
* Note the capacity caps the FLOOR only, never the proportional term: with many
|
|
79
|
+
* responders the majority requirement still grows past two.
|
|
80
|
+
*/
|
|
81
|
+
export function quorumSize(
|
|
82
|
+
responderCount: number,
|
|
83
|
+
simpleMajorityThreshold: number,
|
|
84
|
+
corroboratorCapacity: number = Number.POSITIVE_INFINITY
|
|
85
|
+
): number {
|
|
86
|
+
const floor = Math.max(1, Math.min(CORROBORATION_FLOOR, corroboratorCapacity));
|
|
87
|
+
return Math.max(floor, Math.floor(simpleMajorityThreshold * responderCount));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The `corroboratorCapacity` to hand {@link quorumSize}: how many peers other than the asking node
|
|
92
|
+
* could answer for a block at all, given `cohortPeerCount` peers currently visible (self already
|
|
93
|
+
* excluded) and `repairCorroborationClusterSize` — the cohort size this deployment is measured
|
|
94
|
+
* against, resolved by `resolveClusterPolicy` in `cluster/cluster-policy.ts`.
|
|
95
|
+
*
|
|
96
|
+
* Deliberately the MAX of the two: the corroboration floor may be relaxed only for a cohort that is
|
|
97
|
+
* *genuinely* small, never for one that merely looks small. Cohort views are unauthenticated — the
|
|
98
|
+
* read path takes them from `IKeyNetwork.findCluster`, the commit path from a coordinator-declared
|
|
99
|
+
* peer set — so a partition, a self-shrunk record, or an attacker with routing influence could
|
|
100
|
+
* otherwise talk the requirement down to a single voter. Measuring against the resolved size keeps a
|
|
101
|
+
* shrunken view out of the relaxed branch.
|
|
102
|
+
*
|
|
103
|
+
* An unconfigured node resolves this to its `clusterSize` (default 10), so the floor of two binds and
|
|
104
|
+
* a shrunken view gains nothing. The escape hatch for a real two-node deployment is one explicit
|
|
105
|
+
* operator declaration — `clusterPolicy.assumedClusterSize: 2`, which does NOT also drop the
|
|
106
|
+
* replication factor, or an honest `clusterSize: 2`.
|
|
107
|
+
*
|
|
108
|
+
* Shared by both restoration paths so the two can never drift apart on the rule that decides how
|
|
109
|
+
* much trust a lone peer gets.
|
|
110
|
+
*/
|
|
111
|
+
export function corroboratorCapacity(cohortPeerCount: number, repairCorroborationClusterSize: number): number {
|
|
112
|
+
return Math.max(cohortPeerCount, repairCorroborationClusterSize - 1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Select the highest revision corroborated by a quorum of distinct peers.
|
|
117
|
+
*
|
|
118
|
+
* Claims are grouped by the exact `(rev, actionId)` pair; a liar's fabricated
|
|
119
|
+
* pair lands in its own singleton group and is outvoted. The highest rev whose
|
|
120
|
+
* group has `>= quorum` distinct voters wins.
|
|
121
|
+
*
|
|
122
|
+
* **Claims must not include the asking node's own revision.** A node confirming its own
|
|
123
|
+
* answer is not evidence; its revision is the baseline being repaired, and counting it
|
|
124
|
+
* both inflates the responder count and lets a lone reader "corroborate" itself. Callers
|
|
125
|
+
* filter self out before calling (see `CoordinatorRepo.queryClusterForLatest`,
|
|
126
|
+
* `reconcileBlock`).
|
|
127
|
+
*
|
|
128
|
+
* `corroboratorCapacity` — how many peers other than the asking node could corroborate at
|
|
129
|
+
* all — lets a genuinely tiny cohort still converge: a cohort with exactly one other peer
|
|
130
|
+
* cannot produce two corroborators, so requiring two makes divergence permanent rather
|
|
131
|
+
* than making it safe. Pass a capacity that a shrunken view of the network cannot talk
|
|
132
|
+
* down (see {@link corroboratorCapacity}), or omit it to keep the floor at two.
|
|
133
|
+
*
|
|
134
|
+
* Returns `undefined` when nothing is corroborated — an uncorroborated claim
|
|
135
|
+
* must never drive restoration.
|
|
136
|
+
*
|
|
137
|
+
* **Certified claims** (`certified === true`, injected by a caller that verified the claim's
|
|
138
|
+
* cohort commit proof — see `cluster/certified-claims.ts`) short-circuit the distinct-peer rule,
|
|
139
|
+
* because the proof's signature set IS the corroboration:
|
|
140
|
+
*
|
|
141
|
+
* - No certified claims → today's corroboration result, unchanged.
|
|
142
|
+
* - A corroborated pair at a HIGHER rev than every certified claim wins — corroboration stays a
|
|
143
|
+
* legitimate weaker path, so a legacy uncertified tail written after the last proven rev
|
|
144
|
+
* remains readable.
|
|
145
|
+
* - Otherwise the highest certified rev wins: this covers a certified rev beaten in raw rev only
|
|
146
|
+
* by an *uncorroborated* claim (which failed quorum and is no evidence), and the equal-rev
|
|
147
|
+
* tie, where the proof outweighs votes.
|
|
148
|
+
* - Except: two distinct `actionId`s certified at that top rev is equivocation — the cohort
|
|
149
|
+
* provably signed two different actions into one revision — and the whole selection declines
|
|
150
|
+
* (`undefined`). Callers distinguish this decline from a plain no-quorum via
|
|
151
|
+
* {@link certifiedEquivocation}.
|
|
152
|
+
*/
|
|
153
|
+
export function selectQuorumRev(
|
|
154
|
+
claims: RevClaim[],
|
|
155
|
+
simpleMajorityThreshold: number,
|
|
156
|
+
corroboratorCapacity?: number
|
|
157
|
+
): QuorumRev | undefined {
|
|
158
|
+
if (claims.length === 0) return undefined;
|
|
159
|
+
|
|
160
|
+
const groups = new Map<string, { rev: number; actionId: string; supporters: Set<string> }>();
|
|
161
|
+
const responders = new Set<string>();
|
|
162
|
+
for (const c of claims) {
|
|
163
|
+
responders.add(c.peerId);
|
|
164
|
+
const key = `${c.rev}\0${c.actionId}`;
|
|
165
|
+
let g = groups.get(key);
|
|
166
|
+
if (!g) {
|
|
167
|
+
g = { rev: c.rev, actionId: c.actionId, supporters: new Set() };
|
|
168
|
+
groups.set(key, g);
|
|
169
|
+
}
|
|
170
|
+
g.supporters.add(c.peerId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const quorum = quorumSize(responders.size, simpleMajorityThreshold, corroboratorCapacity);
|
|
174
|
+
|
|
175
|
+
// Highest rev whose (rev, actionId) group meets quorum. There is deliberately no
|
|
176
|
+
// "too few responders, but they all agree" fallback: that rule fired at ANY cohort
|
|
177
|
+
// size, so a lone responder in a large cohort — where a second corroborator does
|
|
178
|
+
// exist and simply did not answer within the per-peer timeout — was accepted on its
|
|
179
|
+
// own word. `corroboratorCapacity` expresses the same permissiveness where it is
|
|
180
|
+
// actually justified (a cohort that cannot supply a second corroborator) and nowhere
|
|
181
|
+
// else.
|
|
182
|
+
let best: { rev: number; actionId: string; supporters: Set<string> } | undefined;
|
|
183
|
+
for (const g of groups.values()) {
|
|
184
|
+
if (g.supporters.size >= quorum && (!best || g.rev > best.rev)) {
|
|
185
|
+
best = g;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const corroborated = best
|
|
189
|
+
? { rev: best.rev, actionId: best.actionId, supporters: [...best.supporters] }
|
|
190
|
+
: undefined;
|
|
191
|
+
|
|
192
|
+
const certified = certifiedGroups(claims);
|
|
193
|
+
if (!certified) return corroborated;
|
|
194
|
+
// A corroborated pair strictly above every certified rev wins — a legacy uncertified tail
|
|
195
|
+
// must stay readable. A merely UNcorroborated higher rev never reaches here (it is not in
|
|
196
|
+
// `corroborated`), so it cannot outrank a proof.
|
|
197
|
+
if (corroborated && corroborated.rev > certified.rev) return corroborated;
|
|
198
|
+
// Two actions provably signed into the same top revision: decline the whole selection rather
|
|
199
|
+
// than pick a side. Callers log via certifiedEquivocation.
|
|
200
|
+
if (certified.byAction.size !== 1) return undefined;
|
|
201
|
+
const [entry] = certified.byAction;
|
|
202
|
+
const [actionId, supporters] = entry!; // size === 1 checked above
|
|
203
|
+
return { rev: certified.rev, actionId, supporters: [...supporters], certified: true };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Certified claims at the top certified rev, keyed by actionId → distinct certified claimants. */
|
|
207
|
+
function certifiedGroups(claims: RevClaim[]): { rev: number; byAction: Map<string, Set<string>> } | undefined {
|
|
208
|
+
let top: number | undefined;
|
|
209
|
+
for (const c of claims) {
|
|
210
|
+
if (c.certified === true && (top === undefined || c.rev > top)) top = c.rev;
|
|
211
|
+
}
|
|
212
|
+
if (top === undefined) return undefined;
|
|
213
|
+
const byAction = new Map<string, Set<string>>();
|
|
214
|
+
for (const c of claims) {
|
|
215
|
+
if (c.certified !== true || c.rev !== top) continue;
|
|
216
|
+
let s = byAction.get(c.actionId);
|
|
217
|
+
if (!s) {
|
|
218
|
+
s = new Set();
|
|
219
|
+
byAction.set(c.actionId, s);
|
|
220
|
+
}
|
|
221
|
+
s.add(c.peerId);
|
|
222
|
+
}
|
|
223
|
+
return { rev: top, byAction };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The conflicting certified set at the top certified rev, when there is one: two-plus distinct
|
|
228
|
+
* `actionId`s each carrying a verified cohort commit proof for the SAME revision. It deserves a
|
|
229
|
+
* distinct log line from a plain no-quorum — the cohort (or whoever holds its keys) provably
|
|
230
|
+
* signed both sides. Selection stays pure, so callers do the logging with what this reports.
|
|
231
|
+
*
|
|
232
|
+
* `undefined` when no certified claim exists or the top certified rev names a single action —
|
|
233
|
+
* conflicts at LOWER certified revs are history already superseded, not equivocation worth
|
|
234
|
+
* declining over.
|
|
235
|
+
*
|
|
236
|
+
* **Ask this only on a decline.** A non-`undefined` result does NOT imply {@link selectQuorumRev}
|
|
237
|
+
* returned `undefined`: a corroborated pair STRICTLY above the top certified rev still wins, and
|
|
238
|
+
* the equivocation below it is reported here while selection succeeded. Call it when selection
|
|
239
|
+
* declined, to say WHY it declined.
|
|
240
|
+
*/
|
|
241
|
+
export function certifiedEquivocation(claims: RevClaim[]): { rev: number; actionIds: string[] } | undefined {
|
|
242
|
+
const groups = certifiedGroups(claims);
|
|
243
|
+
if (!groups || groups.byAction.size < 2) return undefined;
|
|
244
|
+
return { rev: groups.rev, actionIds: [...groups.byAction.keys()] };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** One block candidate paired with its serving peer and canonical hash
|
|
248
|
+
* (`canonicalBlockHash` from `@optimystic/db-core`). */
|
|
249
|
+
export interface BlockHashCandidate {
|
|
250
|
+
peerId: string;
|
|
251
|
+
hash: string;
|
|
252
|
+
block: IBlock;
|
|
253
|
+
/**
|
|
254
|
+
* Injected verdict: the caller verified a cohort commit proof binding this candidate's CONTENT
|
|
255
|
+
* (`certifyContent` in `cluster/certified-claims.ts` — the declared digest matched these
|
|
256
|
+
* bytes), so the cohort's signatures stand in for other peers serving the same hash.
|
|
257
|
+
*/
|
|
258
|
+
certified?: boolean;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Among candidates already known to corroborate the target `(rev, actionId)`,
|
|
263
|
+
* pick the block content agreed by a quorum. Requires a UNIQUE hash group meeting
|
|
264
|
+
* `quorum` — a cohort split on content declines rather than picking a side.
|
|
265
|
+
* Returns the agreed block, or `undefined`.
|
|
266
|
+
*
|
|
267
|
+
* `corroboratorCapacity` caps the floor exactly as it does in {@link selectQuorumRev}, and for
|
|
268
|
+
* the same reason: a cohort holding one other peer cannot produce two block-carriers, so
|
|
269
|
+
* demanding two makes the block permanently unrestorable on this node rather than making it safe.
|
|
270
|
+
* Omit it to keep the floor at two.
|
|
271
|
+
*
|
|
272
|
+
* **What relaxing this does and does not cost.** Block ids are random 256-bit strings, not
|
|
273
|
+
* content-addressed, so `canonicalBlockHash` (db-core) is a cross-peer *agreement* hash and never a
|
|
274
|
+
* check against the requested id — nothing here re-derives an id from received bytes. At capacity
|
|
275
|
+
* one the sole peer's content is therefore taken on its word. That extends no trust the cohort had
|
|
276
|
+
* not already extended: the same peer's `(rev, actionId)` claim is equally uncorroborable at that
|
|
277
|
+
* size, and a two-member cohort has no honest majority to appeal to. Pass a capacity a shrunken
|
|
278
|
+
* view of the network cannot talk down (see {@link corroboratorCapacity}), so only a cohort that is
|
|
279
|
+
* *genuinely* that small reaches this branch.
|
|
280
|
+
*
|
|
281
|
+
* **Certified candidates** (`certified === true` — a caller verified a cohort commit proof whose
|
|
282
|
+
* declared digest matches these exact bytes) short-circuit the hash quorum: exactly one distinct
|
|
283
|
+
* certified hash → that block wins outright, however many peers served it. Two-plus distinct
|
|
284
|
+
* certified hashes is certified content equivocation → decline (`undefined`), mirroring the
|
|
285
|
+
* existing unique-hash-tie decline; callers name that decline via
|
|
286
|
+
* {@link certifiedContentEquivocation}. No certified candidate → the hash quorum below, unchanged.
|
|
287
|
+
*/
|
|
288
|
+
export function selectQuorumBlock(
|
|
289
|
+
candidates: BlockHashCandidate[],
|
|
290
|
+
simpleMajorityThreshold: number,
|
|
291
|
+
corroboratorCapacity?: number
|
|
292
|
+
): { block: IBlock; hash: string } | undefined {
|
|
293
|
+
if (candidates.length === 0) return undefined;
|
|
294
|
+
|
|
295
|
+
const certifiedByHash = certifiedHashes(candidates);
|
|
296
|
+
if (certifiedByHash.size === 1) {
|
|
297
|
+
const [entry] = certifiedByHash;
|
|
298
|
+
const [hash, block] = entry!;
|
|
299
|
+
return { block, hash };
|
|
300
|
+
}
|
|
301
|
+
if (certifiedByHash.size > 1) return undefined; // certified content equivocation — decline
|
|
302
|
+
|
|
303
|
+
// One vote per distinct peer per hash group, matching selectQuorumRev — a peer appearing twice
|
|
304
|
+
// must not be able to second itself into a content quorum.
|
|
305
|
+
const groups = new Map<string, { block: IBlock; supporters: Set<string> }>();
|
|
306
|
+
const voters = new Set<string>();
|
|
307
|
+
for (const c of candidates) {
|
|
308
|
+
voters.add(c.peerId);
|
|
309
|
+
let g = groups.get(c.hash);
|
|
310
|
+
if (!g) {
|
|
311
|
+
g = { block: c.block, supporters: new Set() };
|
|
312
|
+
groups.set(c.hash, g);
|
|
313
|
+
}
|
|
314
|
+
g.supporters.add(c.peerId);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const quorum = quorumSize(voters.size, simpleMajorityThreshold, corroboratorCapacity);
|
|
318
|
+
const meeting = [...groups.entries()].filter(([, g]) => g.supporters.size >= quorum);
|
|
319
|
+
// Exactly one hash may meet quorum; a tie is a genuine content disagreement → decline.
|
|
320
|
+
if (meeting.length !== 1) return undefined;
|
|
321
|
+
const [hash, group] = meeting[0]!;
|
|
322
|
+
return { block: group.block, hash };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Distinct hashes carried by certified candidates → the first block instance serving each. */
|
|
326
|
+
function certifiedHashes(candidates: BlockHashCandidate[]): Map<string, IBlock> {
|
|
327
|
+
const byHash = new Map<string, IBlock>();
|
|
328
|
+
for (const c of candidates) {
|
|
329
|
+
if (c.certified === true && !byHash.has(c.hash)) byHash.set(c.hash, c.block);
|
|
330
|
+
}
|
|
331
|
+
return byHash;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The conflicting certified hashes when there are two or more — the content-side sibling of
|
|
336
|
+
* {@link certifiedEquivocation}, and the reason {@link selectQuorumBlock} declines outright.
|
|
337
|
+
* Candidates reaching that selector all carry the SAME `(rev, actionId)`, so two certified hashes
|
|
338
|
+
* mean the cohort's keys signed two different digests into one revision: a provable compromise an
|
|
339
|
+
* operator must be able to tell apart from the routine "not enough carriers agreed" decline. Both
|
|
340
|
+
* declines return `undefined`, so without this they log identically.
|
|
341
|
+
*
|
|
342
|
+
* `undefined` when fewer than two distinct certified hashes exist — including the ordinary
|
|
343
|
+
* no-certified-candidate case, where a decline really is a plain content-quorum shortfall.
|
|
344
|
+
*/
|
|
345
|
+
export function certifiedContentEquivocation(candidates: BlockHashCandidate[]): { hashes: string[] } | undefined {
|
|
346
|
+
const byHash = certifiedHashes(candidates);
|
|
347
|
+
return byHash.size < 2 ? undefined : { hashes: [...byHash.keys()] };
|
|
348
|
+
}
|