keri-ts 2.2.2 → 2.2.4
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/esm/keri/npm/src/app/bootstrap/authenticating.js +2 -2
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +2 -2
- package/esm/keri/npm/src/app/delivery/forwarding.js +11 -4
- package/esm/keri/npm/src/app/identity/delegating.js +2 -5
- package/esm/keri/npm/src/app/identity/habbing.js +48 -5
- package/esm/keri/npm/src/app/ipex/issuance.js +11 -0
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +10 -0
- package/esm/keri/npm/src/app/multisig/counseling.js +4 -0
- package/esm/keri/npm/src/app/multisig/group-members.js +22 -0
- package/esm/keri/npm/src/app/multisig/replace-member.js +34 -0
- package/esm/keri/npm/src/app/protocol/protocol-processing.js +15 -6
- package/esm/keri/npm/src/app/queries/witness-inquisitor.js +10 -6
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +11 -7
- package/esm/keri/npm/src/app/runtime/doing.js +25 -0
- package/esm/keri/npm/src/db/core/lmdber.js +25 -6
- package/esm/keri/npm/src/db/core/memstore.js +4 -0
- package/esm/keri/npm/src/db/core/store-owner.js +4 -0
- package/esm/keri/npm/src/runtime/index.js +1 -0
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +1 -1
- package/types/app/bootstrap/authenticating.d.ts.map +1 -1
- package/types/app/bootstrap/oobiery.d.ts.map +1 -1
- package/types/app/delivery/forwarding.d.ts +6 -2
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/habbing.d.ts +11 -0
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +5 -0
- package/types/app/ipex/issuance.d.ts.map +1 -1
- package/types/app/ipex/unblinding-workflow.d.ts +5 -0
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +2 -0
- package/types/app/multisig/counseling.d.ts.map +1 -1
- package/types/app/multisig/group-members.d.ts +6 -0
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/replace-member.d.ts +30 -0
- package/types/app/multisig/replace-member.d.ts.map +1 -0
- package/types/app/protocol/protocol-processing.d.ts +6 -1
- package/types/app/protocol/protocol-processing.d.ts.map +1 -1
- package/types/app/queries/witness-inquisitor.d.ts +1 -1
- package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/doing.d.ts +17 -0
- package/types/app/runtime/doing.d.ts.map +1 -1
- package/types/db/core/dber.d.ts +5 -0
- package/types/db/core/dber.d.ts.map +1 -1
- package/types/db/core/lmdber.d.ts +6 -0
- package/types/db/core/lmdber.d.ts.map +1 -1
- package/types/db/core/memstore.d.ts +2 -0
- package/types/db/core/memstore.d.ts.map +1 -1
- package/types/db/core/store-owner.d.ts +2 -0
- package/types/db/core/store-owner.d.ts.map +1 -1
- package/types/runtime/index.d.ts +1 -0
- package/types/runtime/index.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { call, scoped } from "effection";
|
|
2
2
|
import { persistResolvedContact } from "../identity/organizing.js";
|
|
3
|
-
import {
|
|
3
|
+
import { doEveryOnWrite, PROTOCOL_PACING } from "../runtime/doing.js";
|
|
4
4
|
import { systemClock } from "../runtime/runtime-clock.js";
|
|
5
5
|
import { isWellKnownOobiUrl, parseOobiUrl } from "./oobiery.js";
|
|
6
6
|
/**
|
|
@@ -70,7 +70,7 @@ export class Authenticator {
|
|
|
70
70
|
}
|
|
71
71
|
/** Continuous auth doer for long-lived runtime hosts. */
|
|
72
72
|
*authDo(clock = systemClock, pacing = PROTOCOL_PACING) {
|
|
73
|
-
return yield*
|
|
73
|
+
return yield* doEveryOnWrite(clock, pacing, this.hby.db, () => this.processOnce());
|
|
74
74
|
}
|
|
75
75
|
/** Promote one eligible `woobi.` record into the fetchable `mfa.` queue. */
|
|
76
76
|
promoteQueuedWellKnown() {
|
|
@@ -8,7 +8,7 @@ import { acceptReplyDecision, unverifiedReplyDecision } from "../../core/routing
|
|
|
8
8
|
import { Schemer } from "../../core/scheming.js";
|
|
9
9
|
import { persistResolvedContact } from "../identity/organizing.js";
|
|
10
10
|
import { abortable } from "../runtime/abortable.js";
|
|
11
|
-
import {
|
|
11
|
+
import { doEveryOnWrite, PROTOCOL_PACING } from "../runtime/doing.js";
|
|
12
12
|
import { systemClock } from "../runtime/runtime-clock.js";
|
|
13
13
|
import { collectBytes } from "../transport/byte-source.js";
|
|
14
14
|
import { processMessages } from "../transport/message-source.js";
|
|
@@ -207,7 +207,7 @@ export class Oobiery {
|
|
|
207
207
|
* doer rather than root-owned queue plumbing.
|
|
208
208
|
*/
|
|
209
209
|
*oobiDo(clock = systemClock, pacing = PROTOCOL_PACING) {
|
|
210
|
-
return yield*
|
|
210
|
+
return yield* doEveryOnWrite(clock, pacing, this.hby.db, () => this.processOnce());
|
|
211
211
|
}
|
|
212
212
|
/** Select queued work, including a surviving in-flight row whose former process is gone. */
|
|
213
213
|
nextQueued() {
|
|
@@ -9,7 +9,9 @@ import { mailboxTopicKey, preferredUrl } from "../mailbox/mailboxing.js";
|
|
|
9
9
|
import { transportMailboxTopic } from "../mailbox/topic-routing.js";
|
|
10
10
|
import { localGroupMember } from "../multisig/group-members.js";
|
|
11
11
|
import { abortable } from "../runtime/abortable.js";
|
|
12
|
+
import { PROTOCOL_TOCK_MS } from "../runtime/doing.js";
|
|
12
13
|
import { runtimeTurn } from "../runtime/runtime-turn.js";
|
|
14
|
+
import { waitOnClock } from "../runtime/workflow-contract.js";
|
|
13
15
|
import { CesrHttpRefusal } from "../transport/cesr-egress.js";
|
|
14
16
|
import { messengerFrom, streamMessengerFrom } from "../transport/messengering.js";
|
|
15
17
|
import { requireTcpDialer } from "../transport/tcp-dialer.js";
|
|
@@ -283,11 +285,16 @@ export class Poster {
|
|
|
283
285
|
}
|
|
284
286
|
return found;
|
|
285
287
|
}
|
|
286
|
-
/**
|
|
287
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Run queued delivery independently of query response intake and producer lifetimes, as a fixed-tock doer:
|
|
290
|
+
* a pass only when something is queued (an emptiness check, no store read), then one tock. A message queued
|
|
291
|
+
* between tocks waits at most `tockMs` before its delivery starts.
|
|
292
|
+
*/
|
|
293
|
+
*deliverDo(tockMs = PROTOCOL_TOCK_MS) {
|
|
288
294
|
while (!this.closed) {
|
|
289
|
-
|
|
290
|
-
|
|
295
|
+
if (!this.evts.empty)
|
|
296
|
+
yield* this.process();
|
|
297
|
+
yield* waitOnClock(this.clock, tockMs);
|
|
291
298
|
}
|
|
292
299
|
}
|
|
293
300
|
/** Runnable and active work determine convergence; consumable observations do not. */
|
|
@@ -18,6 +18,7 @@ import { DELEGATE_MAILBOX_TOPIC, REPLAY_MAILBOX_TOPIC } from "../../core/mailbox
|
|
|
18
18
|
import { Schemes } from "../../core/schemes.js";
|
|
19
19
|
import { dgKey } from "../../db/core/keys.js";
|
|
20
20
|
import { createExchangeMessage } from "../exchanges/exchange-messaging.js";
|
|
21
|
+
import { isLocalGroupHab, localGroupMember } from "../multisig/group-members.js";
|
|
21
22
|
import { sendWitnessMessage } from "../witness/receipt-client.js";
|
|
22
23
|
import { eventPayloadMessage, eventReplayMessage } from "./habbing.js";
|
|
23
24
|
export const DELEGATE_REQUEST_ROUTE = "/delegate/request";
|
|
@@ -63,10 +64,6 @@ function localHabByPre(hby, pre, context) {
|
|
|
63
64
|
}
|
|
64
65
|
return hab;
|
|
65
66
|
}
|
|
66
|
-
function localGroupMember(hby, groupPre) {
|
|
67
|
-
const record = hby.db.getHab(groupPre);
|
|
68
|
-
return record?.mid ? hby.habs.get(record.mid) ?? null : null;
|
|
69
|
-
}
|
|
70
67
|
function workflowSaid(serder) {
|
|
71
68
|
if (!serder.said) {
|
|
72
69
|
throw new ValidationError("Delegated workflow event is missing a SAID.");
|
|
@@ -720,7 +717,7 @@ export class Anchorer {
|
|
|
720
717
|
if (pinned) {
|
|
721
718
|
return localHabByPre(this.hby, pinned, `Delegation workflow ${workflowId(serder)}`);
|
|
722
719
|
}
|
|
723
|
-
return localGroupMember(this.hby, hab.pre)
|
|
720
|
+
return isLocalGroupHab(this.hby, hab) ? localGroupMember(this.hby, hab.pre) : this.communicationHab(serder, hab);
|
|
724
721
|
}
|
|
725
722
|
/**
|
|
726
723
|
* Send the delegation chain before the approving event, awaiting actual Poster or witness transmission outcomes.
|
|
@@ -30,7 +30,7 @@ import { Keeper, PreSit } from "../../db/keeping.js";
|
|
|
30
30
|
import { makeNowIso8601 } from "../../time/mod.js";
|
|
31
31
|
import { oobiReplyMessages } from "../bootstrap/oobi-replies.js";
|
|
32
32
|
import { loadStoredReply } from "../delivery/stored-replies.js";
|
|
33
|
-
import { acceptedGroupMembers, latestGroupContribution } from "../multisig/group-members.js";
|
|
33
|
+
import { acceptedGroupMembers, isAcceptedGroupMember, latestGroupContribution } from "../multisig/group-members.js";
|
|
34
34
|
import { branToSaltQb64, encodeHugeNumber, ensureKeeperCryptoReady, Manager, normalizeSaltQb64, saltySigner, } from "./keeping.js";
|
|
35
35
|
/** Reserved alias for the local signatory habitat record. */
|
|
36
36
|
export const SIGNER = "__signatory__";
|
|
@@ -1547,6 +1547,10 @@ export class Habery {
|
|
|
1547
1547
|
* - this is the TypeScript analogue of `Habery.joinGroupHab()`
|
|
1548
1548
|
* - it does not fabricate key state; accepted KEL state must still arrive
|
|
1549
1549
|
* through `Kevery.processEvent()`
|
|
1550
|
+
* - KERIpy overwrites the recorded member unconditionally. Here a recorded member that is still a
|
|
1551
|
+
* signing or rotation member of the group's accepted key state is kept (a Habery holding two valid
|
|
1552
|
+
* members must not switch which one signs), and one that is in neither list is replaced by `mhab`.
|
|
1553
|
+
* Replacing creates no event and signs nothing; membership itself changes only by rotation.
|
|
1550
1554
|
*/
|
|
1551
1555
|
joinGroupHab(pre, group, mhab, smids, rmids, ns) {
|
|
1552
1556
|
if (!pre) {
|
|
@@ -1569,18 +1573,54 @@ export class Habery {
|
|
|
1569
1573
|
throw new ValidationError(`Group alias ${group} is already bound to ${existingName}.`);
|
|
1570
1574
|
}
|
|
1571
1575
|
const existingRecord = this.db.getHab(pre);
|
|
1572
|
-
|
|
1573
|
-
|
|
1576
|
+
const replacing = !!existingRecord?.mid && existingRecord.mid !== mhab.pre;
|
|
1577
|
+
if (replacing) {
|
|
1578
|
+
// Membership is decided by accepted key state, never by the caller's lists or a proposal.
|
|
1579
|
+
if (!this.db.getKever(pre)) {
|
|
1580
|
+
throw new ValidationError(`Group prefix ${pre} is already joined as ${existingRecord.name}; whether member ${existingRecord.mid} `
|
|
1581
|
+
+ `is still a member cannot be judged until the group has accepted key state.`);
|
|
1582
|
+
}
|
|
1583
|
+
if (isAcceptedGroupMember(this.db, pre, existingRecord.mid)) {
|
|
1584
|
+
throw new ValidationError(`Group prefix ${pre} is already joined as ${existingRecord.name} by member ${existingRecord.mid}, `
|
|
1585
|
+
+ `which is still a member in its accepted key state.`);
|
|
1586
|
+
}
|
|
1587
|
+
if (!isAcceptedGroupMember(this.db, pre, mhab.pre)) {
|
|
1588
|
+
throw new ValidationError(`Local member identifier ${mhab.pre} is not a member in the accepted key state of group ${pre}.`);
|
|
1589
|
+
}
|
|
1574
1590
|
}
|
|
1575
1591
|
const hab = this.habs.get(pre) ?? new Hab(group, this.db, this.ks, this.mgr, this.cf, this.rtr, this.rvy, this.kevery, ns, pre);
|
|
1576
|
-
this.persistGroupHabRecord(pre, group, mhab.pre, signingMembers, rotationMembers, ns);
|
|
1592
|
+
this.persistGroupHabRecord(pre, group, mhab.pre, signingMembers, rotationMembers, ns, existingRecord ?? undefined);
|
|
1577
1593
|
this.db.groups.add(pre);
|
|
1578
1594
|
if (hab.accepted) {
|
|
1579
1595
|
this.markAcceptedGroupHab(pre);
|
|
1580
1596
|
}
|
|
1581
1597
|
this.habs.set(pre, hab);
|
|
1598
|
+
if (replacing && !this.readonly) {
|
|
1599
|
+
// The stored lists must follow accepted state, which the old member's record kept from being committed.
|
|
1600
|
+
this.recoverAcceptedGroupMembers(pre);
|
|
1601
|
+
this.retireGroupMemberState(pre, existingRecord.mid);
|
|
1602
|
+
}
|
|
1582
1603
|
return hab;
|
|
1583
1604
|
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Drop the durable rows that address `group` to its former local member `old`: registry root copies
|
|
1607
|
+
* escrowed for `old` (the holder check now refuses them, so a retry would only fail) and the markers
|
|
1608
|
+
* of copies sent to `old` as a recipient. Rows keyed by the group itself stay. Live operations are
|
|
1609
|
+
* owned by the runtime and are not reached from here.
|
|
1610
|
+
*/
|
|
1611
|
+
retireGroupMemberState(group, old) {
|
|
1612
|
+
for (const [keys] of [...this.db.rrpe.getTopItemIter()]) {
|
|
1613
|
+
const serder = this.db.exns.get([keys.join(".")]);
|
|
1614
|
+
const data = serder?.sad?.a;
|
|
1615
|
+
if (serder?.route === "/registry/root" && serder.sad?.ri === old && data?.gid === group) {
|
|
1616
|
+
this.db.rrpe.rem(keys);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
for (const [keys] of [...this.db.rrsn.getTopItemIter([group])]) {
|
|
1620
|
+
if (keys[2] === old)
|
|
1621
|
+
this.db.rrsn.rem(keys);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1584
1624
|
/** Commit accepted group member metadata after the corresponding group event is accepted. */
|
|
1585
1625
|
commitGroupHabMembers(pre, smids, rmids = smids) {
|
|
1586
1626
|
const record = this.db.getHab(pre);
|
|
@@ -1774,12 +1814,15 @@ export class Habery {
|
|
|
1774
1814
|
return sigers;
|
|
1775
1815
|
}
|
|
1776
1816
|
/** Persist group habitat metadata and member ordinals. */
|
|
1777
|
-
persistGroupHabRecord(pre, group, mid, smids, rmids, ns) {
|
|
1817
|
+
persistGroupHabRecord(pre, group, mid, smids, rmids, ns, replaced) {
|
|
1778
1818
|
this.db.pinHab(pre, new HabitatRecord({
|
|
1779
1819
|
hid: pre,
|
|
1780
1820
|
name: group,
|
|
1781
1821
|
domain: ns,
|
|
1782
1822
|
mid,
|
|
1823
|
+
// A rejoin or replacement keeps the rest of the record.
|
|
1824
|
+
sid: replaced?.sid,
|
|
1825
|
+
watchers: replaced?.watchers ? [...replaced.watchers] : undefined,
|
|
1783
1826
|
smids: [...smids],
|
|
1784
1827
|
rmids: [...rmids],
|
|
1785
1828
|
}));
|
|
@@ -266,6 +266,17 @@ export class IssuanceOperations {
|
|
|
266
266
|
this.ended.set(partyKey(op.grant, op.role), { role: op.role, state, reason });
|
|
267
267
|
this.deps.changes?.push({ kind: "issuance", id: op.grant });
|
|
268
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* End, as abandoned with `reason`, every live operation of local group `group` whose party member or frozen
|
|
271
|
+
* DH custodian is `member`. Releasing the approval drops the DH owner's held query. Returns the Grants ended.
|
|
272
|
+
*/
|
|
273
|
+
endBoundToMember(group, member, reason) {
|
|
274
|
+
const bound = [...this.live.values()].filter(op => op.group?.party.group.pre === group
|
|
275
|
+
&& (op.group.party.member.pre === member || op.group.party.custodian === member));
|
|
276
|
+
for (const op of bound)
|
|
277
|
+
this.end(op, "abandoned", reason);
|
|
278
|
+
return bound.map(op => op.grant);
|
|
279
|
+
}
|
|
269
280
|
/** Drop the live entry and the DH approval; a missing approval means the owner already closed. */
|
|
270
281
|
release(op) {
|
|
271
282
|
this.live.delete(partyKey(op.grant, op.role));
|
|
@@ -287,6 +287,16 @@ export class UnblindingOperations {
|
|
|
287
287
|
this.settled.set(unblindingKey(op.grant, op.role), { role: op.role, state, reason });
|
|
288
288
|
this.deps.changes?.push({ kind: "unblinding", id: op.grant });
|
|
289
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* End, as abandoned with `reason`, every live Unblind of local group `group` whose party member is `member`.
|
|
292
|
+
* Releasing the approval drops the owner's held query. Returns the Grants ended.
|
|
293
|
+
*/
|
|
294
|
+
endBoundToMember(group, member, reason) {
|
|
295
|
+
const bound = [...this.live.values()].filter(op => op.hab.pre === group && op.group?.member.pre === member);
|
|
296
|
+
for (const op of bound)
|
|
297
|
+
this.end(op, "abandoned", reason);
|
|
298
|
+
return bound.map(op => op.grant);
|
|
299
|
+
}
|
|
290
300
|
/** Drop the live entry and the owner's approval and frozen messages; delivered bytes stay the peer's. */
|
|
291
301
|
release(op) {
|
|
292
302
|
this.live.delete(unblindingKey(op.grant, op.role));
|
|
@@ -282,6 +282,10 @@ export class Counselor {
|
|
|
282
282
|
for (const sn of removed)
|
|
283
283
|
this.changes?.push({ kind: "group", id: `${pre}:${sn}` });
|
|
284
284
|
}
|
|
285
|
+
/** Whether completed events are held in memory for forwarding, which a pass retries without any store write. */
|
|
286
|
+
get holdsForwards() {
|
|
287
|
+
return this.forwards.size > 0;
|
|
288
|
+
}
|
|
285
289
|
/** One pass over all stages, as KERIpy `processEscrows`. */
|
|
286
290
|
*processOnce() {
|
|
287
291
|
// A runtime calls this every turn; with nothing counseled, three row counts are the whole cost.
|
|
@@ -128,6 +128,28 @@ export function groupMemberHasKey(db, member, key) {
|
|
|
128
128
|
}
|
|
129
129
|
return false;
|
|
130
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* True when `aid` is a signing or rotation member of `group`'s latest accepted establishment event: one of its
|
|
133
|
+
* establishment events supplied a key in that event's key list or committed to a digest in its next list.
|
|
134
|
+
* Decided from accepted key state only. False when the group has no accepted state.
|
|
135
|
+
*/
|
|
136
|
+
export function isAcceptedGroupMember(db, group, aid) {
|
|
137
|
+
const kever = db.getKever(group);
|
|
138
|
+
const event = kever ? db.getEvtSerder(group, kever.lastEst.d) : null;
|
|
139
|
+
if (!event)
|
|
140
|
+
return false;
|
|
141
|
+
const keys = new Set((event.verfers ?? []).map(verfer => verfer.qb64));
|
|
142
|
+
const digs = new Set((event.ndigers ?? []).map(diger => diger.qb64));
|
|
143
|
+
for (const [, said] of db.getKelItemIter(aid)) {
|
|
144
|
+
const member = db.getEvtSerder(aid, said);
|
|
145
|
+
if (!member?.estive)
|
|
146
|
+
continue;
|
|
147
|
+
if ((member.verfers ?? []).some(verfer => keys.has(verfer.qb64))
|
|
148
|
+
|| (member.ndigers ?? []).some(diger => digs.has(diger.qb64)))
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
131
153
|
/**
|
|
132
154
|
* Select only a cryptographically valid local partial at an accepted group establishment.
|
|
133
155
|
* The shared esigs table can hold unverified escrow candidates, so a matching
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replace a local group's recorded member after a rotation removed it, and end the runtime's work bound to it.
|
|
3
|
+
*
|
|
4
|
+
* `Habery.joinGroupHab` re-points the record and clears the durable rows addressed to the old member. The
|
|
5
|
+
* runtime owns the live operations, so this is the one call that also ends them: an issuance whose party
|
|
6
|
+
* member or frozen DH custodian is the old member, and an Unblind holding it. Each ends through its owner's
|
|
7
|
+
* own end path, which releases the approval and drops the held `heldq`/`heldu` rows. Group IPEX messages and
|
|
8
|
+
* presentations keep no member binding (their contributions are already signed and the member is read live),
|
|
9
|
+
* so they are left to run. Nothing is signed and no event is made.
|
|
10
|
+
*/
|
|
11
|
+
import { ValidationError } from "../../core/errors.js";
|
|
12
|
+
/**
|
|
13
|
+
* Replace the local member of `input.group`, then end live operations bound to the previous one.
|
|
14
|
+
*
|
|
15
|
+
* @throws ValidationError when `joinGroupHab` refuses: the group has no accepted state, the previous member is
|
|
16
|
+
* still a member of it, or the new one is not.
|
|
17
|
+
*/
|
|
18
|
+
export function replaceGroupMember(runtime, input) {
|
|
19
|
+
const { hby } = runtime, { group, member } = input;
|
|
20
|
+
const record = hby.db.getHab(group), alias = record?.name;
|
|
21
|
+
if (!record?.mid || !alias)
|
|
22
|
+
throw new ValidationError(`Group ${group} is not a local group habitat.`);
|
|
23
|
+
const previous = record.mid;
|
|
24
|
+
if (previous === member.pre)
|
|
25
|
+
throw new ValidationError(`Member ${previous} is already the local member of ${group}.`);
|
|
26
|
+
hby.joinGroupHab(group, alias, member, input.smids, input.rmids ?? input.smids, record.domain);
|
|
27
|
+
const reason = `Local member ${previous} of group ${group} was replaced by ${member.pre}`;
|
|
28
|
+
const ops = runtime.operations;
|
|
29
|
+
return {
|
|
30
|
+
previous,
|
|
31
|
+
issuance: ops?.issuance.endBoundToMember(group, previous, reason) ?? [],
|
|
32
|
+
unblinding: ops?.unblinding.endBoundToMember(group, previous, reason) ?? [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -6,8 +6,8 @@ import { consoleLogger } from "../../core/logger.js";
|
|
|
6
6
|
import { BasicReplyRouteHandler, Revery } from "../../core/routing.js";
|
|
7
7
|
import { Exchanger } from "../exchanges/exchanging.js";
|
|
8
8
|
import { QueryReplies } from "../queries/query-replies.js";
|
|
9
|
+
import { doEveryOnWrite, PROTOCOL_PACING } from "../runtime/doing.js";
|
|
9
10
|
import { systemClock } from "../runtime/runtime-clock.js";
|
|
10
|
-
import { waitOnClock } from "../runtime/workflow-contract.js";
|
|
11
11
|
import { dispatchEnvelope, envelopeFromFrame, queryEnvelopeFromDispatch, } from "./parsering.js";
|
|
12
12
|
/**
|
|
13
13
|
* Wait after every escrow pass, as KERIpy's Doist cycle (`tock` 0.03125 s, app/directing.py) paces its `escrowDo`,
|
|
@@ -260,12 +260,21 @@ export class ProtocolProcessor {
|
|
|
260
260
|
* KERIpy correspondence:
|
|
261
261
|
* - mirrors the dedicated `escrowDo` loop rather than folding escrow
|
|
262
262
|
* processing into parser ingress
|
|
263
|
+
*
|
|
264
|
+
* A pass is a function of the store and of time, so this is a {@link doEveryOnWrite} doer: a pass on tock 0,
|
|
265
|
+
* after any store write, and every `pacing.idleTocks` tocks for what only time releases (timeouts, KRAM
|
|
266
|
+
* windows). Data-driven work waits at most one tock.
|
|
263
267
|
*/
|
|
264
|
-
*escrowDo(clock = systemClock) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
*escrowDo(clock = systemClock, pacing = PROTOCOL_PACING) {
|
|
269
|
+
// Escrows live in the KEL store and, for TEL and registry work, in the registry stores; a write to any of them
|
|
270
|
+
// runs the next tock's pass.
|
|
271
|
+
const stores = [this.kvy.db, this.tvy?.reger, this.registry?.store.baser].filter((store) => !!store);
|
|
272
|
+
const written = {
|
|
273
|
+
get writeActivity() {
|
|
274
|
+
return stores.reduce((sum, store) => sum + store.writeActivity, 0);
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
return yield* doEveryOnWrite(clock, pacing, written, () => this.processEscrowsOnce());
|
|
269
278
|
}
|
|
270
279
|
}
|
|
271
280
|
/**
|
|
@@ -16,7 +16,8 @@ import { consoleLogger } from "../../core/logger.js";
|
|
|
16
16
|
import { Roles } from "../../core/roles.js";
|
|
17
17
|
import { Schemes } from "../../core/schemes.js";
|
|
18
18
|
import { introduce } from "../delivery/introducing.js";
|
|
19
|
-
import {
|
|
19
|
+
import { PROTOCOL_TOCK_MS } from "../runtime/doing.js";
|
|
20
|
+
import { waitOnClock } from "../runtime/workflow-contract.js";
|
|
20
21
|
import { CesrHttpRefusal } from "../transport/cesr-egress.js";
|
|
21
22
|
import { HTTPMessenger } from "../transport/http-messenger.js";
|
|
22
23
|
import { requireTcpDialer } from "../transport/tcp-dialer.js";
|
|
@@ -339,12 +340,15 @@ export class WitnessInquisitor {
|
|
|
339
340
|
* preparation/cleanup faults terminate the worker. Caller cancellation joins
|
|
340
341
|
* the current attempt; it does not close the borrowed state or HTTP service.
|
|
341
342
|
*/
|
|
342
|
-
*deliverDo() {
|
|
343
|
+
*deliverDo(tockMs = PROTOCOL_TOCK_MS) {
|
|
343
344
|
while (true) {
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
345
|
+
// Fixed tock: a pass only when a request is queued; a request waits at most `tockMs` to start.
|
|
346
|
+
if (!this.requests.empty) {
|
|
347
|
+
const failure = yield* this.deliveryPass();
|
|
348
|
+
if (failure)
|
|
349
|
+
consoleLogger.warn("Query delivery failed", failure);
|
|
350
|
+
}
|
|
351
|
+
yield* waitOnClock(this.clock, tockMs);
|
|
348
352
|
}
|
|
349
353
|
}
|
|
350
354
|
/**
|
|
@@ -44,7 +44,7 @@ import { QueryReplies } from "../queries/query-replies.js";
|
|
|
44
44
|
import { WitnessInquisitor } from "../queries/witness-inquisitor.js";
|
|
45
45
|
import { Receiptor } from "../witness/receipting.js";
|
|
46
46
|
import { cueDo } from "./cue-runtime.js";
|
|
47
|
-
import {
|
|
47
|
+
import { doEveryOnWrite, PROTOCOL_PACING } from "./doing.js";
|
|
48
48
|
import { IpexAuxiliaries } from "./ipex-auxiliaries.js";
|
|
49
49
|
import { composeOwners, operationOwnersDo, OWNER_PACING, } from "./operation-owners.js";
|
|
50
50
|
import { SignerConditions } from "./signer-conditions.js";
|
|
@@ -632,15 +632,19 @@ export function* runAgentRuntime(runtime, options = {}) {
|
|
|
632
632
|
const alive = (loop, make) => spawn(() => surviving(runtime, loop, make));
|
|
633
633
|
const stopHoldWatch = runtime.hby.mgr.onHold(() => runtime.signerConditions.held());
|
|
634
634
|
const tasks = [
|
|
635
|
-
yield* alive("querying", () => runtime.querying.deliverDo()),
|
|
636
|
-
yield* alive("postman", () => runtime.postman.deliverDo()),
|
|
635
|
+
yield* alive("querying", () => runtime.querying.deliverDo(pacing.tockMs)),
|
|
636
|
+
yield* alive("postman", () => runtime.postman.deliverDo(pacing.tockMs)),
|
|
637
637
|
...(runtime.vdr && telReceiptor
|
|
638
638
|
? [
|
|
639
|
-
yield* alive("tel-publication", () =>
|
|
639
|
+
yield* alive("tel-publication", () => doEveryOnWrite(runtime.clock, pacing, {
|
|
640
|
+
get writeActivity() {
|
|
641
|
+
return runtime.hby.db.writeActivity + runtime.vdr.publication.reger.writeActivity;
|
|
642
|
+
},
|
|
643
|
+
}, () => publishPendingTel(runtime.hby, runtime.vdr.publication, telReceiptor, runtime.http, runtime.clock))),
|
|
640
644
|
]
|
|
641
645
|
: []),
|
|
642
646
|
yield* alive("cues", () => cueDo(runtime, options, pacing)),
|
|
643
|
-
yield* alive("escrows", () => runtime.protocol.escrowDo(runtime.clock)),
|
|
647
|
+
yield* alive("escrows", () => runtime.protocol.escrowDo(runtime.clock, pacing)),
|
|
644
648
|
yield* alive("oobi", () => runtime.oobiery.oobiDo(runtime.clock, pacing)),
|
|
645
649
|
yield* alive("authentication", () => runtime.authenticator.authDo(runtime.clock, pacing)),
|
|
646
650
|
...(runtime.signaler
|
|
@@ -655,8 +659,8 @@ export function* runAgentRuntime(runtime, options = {}) {
|
|
|
655
659
|
}),
|
|
656
660
|
]
|
|
657
661
|
: []),
|
|
658
|
-
yield* alive("delegation", () =>
|
|
659
|
-
yield* alive("counselor", () =>
|
|
662
|
+
yield* alive("delegation", () => doEveryOnWrite(runtime.clock, pacing, runtime.hby.db, () => runtime.delegating.processAllOnce())),
|
|
663
|
+
yield* alive("counselor", () => doEveryOnWrite(runtime.clock, pacing, runtime.hby.db, () => runtime.counselor.processOnce(), () => runtime.counselor.holdsForwards)),
|
|
660
664
|
yield* alive("kel-publisher", () => kelPublisherDo(runtime.kelPublisher, runtime.clock, runtime.publisherPacing)),
|
|
661
665
|
...(runtime.operations && runtime.ipex
|
|
662
666
|
? [
|
|
@@ -3,10 +3,13 @@ import { waitOnClock } from "./workflow-contract.js";
|
|
|
3
3
|
export const PROTOCOL_TOCK_MS = 31.25;
|
|
4
4
|
/** Tocks between full sweeps of a loop that has one: 1920 tocks of 31.25 ms is a minute. */
|
|
5
5
|
export const PROTOCOL_SWEEP_TOCKS = 1920;
|
|
6
|
+
/** Tocks between passes of a write-driven loop when the store did not change: 32 tocks of 31.25 ms is one second. */
|
|
7
|
+
export const PROTOCOL_IDLE_TOCKS = 32;
|
|
6
8
|
/** The production pacing of the protocol-facing background loops. */
|
|
7
9
|
export const PROTOCOL_PACING = Object.freeze({
|
|
8
10
|
tockMs: PROTOCOL_TOCK_MS,
|
|
9
11
|
sweepTocks: PROTOCOL_SWEEP_TOCKS,
|
|
12
|
+
idleTocks: PROTOCOL_IDLE_TOCKS,
|
|
10
13
|
});
|
|
11
14
|
/** Run `pass` forever: pass 0, wait `tockMs` on `clock`, pass 1, and so on. A thrown error ends the doer. */
|
|
12
15
|
export function* doEvery(clock, tockMs, pass) {
|
|
@@ -15,3 +18,25 @@ export function* doEvery(clock, tockMs, pass) {
|
|
|
15
18
|
yield* waitOnClock(clock, tockMs);
|
|
16
19
|
}
|
|
17
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* A fixed-tock doer for a loop whose work lives in store rows: pass 0, then a pass only on a tock after the
|
|
23
|
+
* store's write counter moved (a pass's own writes included, so it repeats until the store is quiet),
|
|
24
|
+
* after `busy()` says work is held in memory, or every `idleTocks`-th tock for what only time releases
|
|
25
|
+
* (timeouts, retry delays). A tock with nothing new costs a counter read, which touches no store, and no table scan. Rows written by
|
|
26
|
+
* another loop are seen on the next tock; time-driven work waits at most `idleTocks` tocks.
|
|
27
|
+
*/
|
|
28
|
+
export function* doEveryOnWrite(clock, pacing, db, pass, busy = () => false) {
|
|
29
|
+
const idle = pacing.idleTocks ?? PROTOCOL_IDLE_TOCKS;
|
|
30
|
+
let seen = -1;
|
|
31
|
+
for (let tock = 0;; tock++) {
|
|
32
|
+
if (tock % idle === 0 || db.writeActivity !== seen || busy()) {
|
|
33
|
+
try {
|
|
34
|
+
yield* pass(tock);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
seen = db.writeActivity;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
yield* waitOnClock(clock, pacing.tockMs);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -254,6 +254,13 @@ export class LMDBer extends BaseDBer {
|
|
|
254
254
|
writable: true,
|
|
255
255
|
value: false
|
|
256
256
|
});
|
|
257
|
+
/** Write attempts made through this handle; a process-local counter, so reading it touches no store. */
|
|
258
|
+
Object.defineProperty(this, "commits", {
|
|
259
|
+
enumerable: true,
|
|
260
|
+
configurable: true,
|
|
261
|
+
writable: true,
|
|
262
|
+
value: 0
|
|
263
|
+
});
|
|
257
264
|
this.defaults = { ...LMDBER_DEFAULTS, ...defaults };
|
|
258
265
|
this.logger = options.logger ?? consoleLogger;
|
|
259
266
|
this.configuredMapSize = options.mapSize;
|
|
@@ -482,7 +489,7 @@ export class LMDBer extends BaseDBer {
|
|
|
482
489
|
setVer(val) {
|
|
483
490
|
const env = this.requireEnv();
|
|
484
491
|
try {
|
|
485
|
-
env.transactionSync(() => {
|
|
492
|
+
this.writing(env).transactionSync(() => {
|
|
486
493
|
env.putSync(b("__version__"), b(val));
|
|
487
494
|
});
|
|
488
495
|
}
|
|
@@ -521,6 +528,7 @@ export class LMDBer extends BaseDBer {
|
|
|
521
528
|
}
|
|
522
529
|
/** Run `fn` in one native synchronous write transaction; nesting is a child transaction. */
|
|
523
530
|
transaction(fn) {
|
|
531
|
+
// A transaction is not itself a write: the puts and removes inside it count, so one that writes nothing does not.
|
|
524
532
|
return this.requireEnv().transactionSync(fn);
|
|
525
533
|
}
|
|
526
534
|
/**
|
|
@@ -538,6 +546,15 @@ export class LMDBer extends BaseDBer {
|
|
|
538
546
|
yield { key, value: copyDbBytes(entry.value) };
|
|
539
547
|
}
|
|
540
548
|
}
|
|
549
|
+
/** Count one write attempt through this handle and return the environment to write through. */
|
|
550
|
+
writing(env) {
|
|
551
|
+
this.commits++;
|
|
552
|
+
return env;
|
|
553
|
+
}
|
|
554
|
+
/** Counts this handle's own writes without reading the store; other processes' commits are not seen. */
|
|
555
|
+
get writeActivity() {
|
|
556
|
+
return this.commits;
|
|
557
|
+
}
|
|
541
558
|
/** LMDB's committed transaction ID, which also counts other processes' commits. */
|
|
542
559
|
get writeVersion() {
|
|
543
560
|
const version = this.requireEnv().getStats().lastTxnId;
|
|
@@ -610,9 +627,11 @@ export class LMDBer extends BaseDBer {
|
|
|
610
627
|
}
|
|
611
628
|
putRow(table, key, val) {
|
|
612
629
|
assertRowFits(table, key, val);
|
|
630
|
+
this.commits++;
|
|
613
631
|
lmdbTable(table).putSync(key, val);
|
|
614
632
|
}
|
|
615
633
|
removeRow(table, key, val) {
|
|
634
|
+
this.commits++;
|
|
616
635
|
return val === undefined ? lmdbTable(table).removeSync(key) : lmdbTable(table).removeSync(key, val);
|
|
617
636
|
}
|
|
618
637
|
/*
|
|
@@ -632,7 +651,7 @@ export class LMDBer extends BaseDBer {
|
|
|
632
651
|
const db = lmdbTable(table);
|
|
633
652
|
const env = this.requireEnv();
|
|
634
653
|
try {
|
|
635
|
-
const result = env.transactionSync(() => {
|
|
654
|
+
const result = this.writing(env).transactionSync(() => {
|
|
636
655
|
const existing = db.get(key);
|
|
637
656
|
if (existing !== null && existing !== undefined) {
|
|
638
657
|
return false;
|
|
@@ -657,7 +676,7 @@ export class LMDBer extends BaseDBer {
|
|
|
657
676
|
const db = lmdbTable(table);
|
|
658
677
|
const env = this.requireEnv();
|
|
659
678
|
try {
|
|
660
|
-
env.transactionSync(() => {
|
|
679
|
+
this.writing(env).transactionSync(() => {
|
|
661
680
|
assertRowFits(db, key, val);
|
|
662
681
|
db.put(key, val);
|
|
663
682
|
});
|
|
@@ -700,7 +719,7 @@ export class LMDBer extends BaseDBer {
|
|
|
700
719
|
const db = lmdbTable(table);
|
|
701
720
|
const env = this.requireEnv();
|
|
702
721
|
try {
|
|
703
|
-
const result = env.transactionSync(() => {
|
|
722
|
+
const result = this.writing(env).transactionSync(() => {
|
|
704
723
|
if (val !== undefined) {
|
|
705
724
|
// An empty key must still fail, but an over-long one can only be absent.
|
|
706
725
|
if (key.length > MAX_KEY_BYTES)
|
|
@@ -809,7 +828,7 @@ export class LMDBer extends BaseDBer {
|
|
|
809
828
|
if (keys.length === 0) {
|
|
810
829
|
return false;
|
|
811
830
|
}
|
|
812
|
-
env.transactionSync(() => {
|
|
831
|
+
this.writing(env).transactionSync(() => {
|
|
813
832
|
for (const key of keys) {
|
|
814
833
|
db.remove(key);
|
|
815
834
|
}
|
|
@@ -870,7 +889,7 @@ export class LMDBer extends BaseDBer {
|
|
|
870
889
|
let result = true;
|
|
871
890
|
const existing = new Set(this.getVals(db, key).map((val) => bytesHex(val)));
|
|
872
891
|
const env = this.requireEnv();
|
|
873
|
-
env.transactionSync(() => {
|
|
892
|
+
this.writing(env).transactionSync(() => {
|
|
874
893
|
for (const val of vals) {
|
|
875
894
|
const vh = bytesHex(val);
|
|
876
895
|
if (existing.has(vh)) {
|
|
@@ -173,6 +173,10 @@ export class MemStore extends BaseDBer {
|
|
|
173
173
|
this.assertOpen();
|
|
174
174
|
return __classPrivateFieldGet(this, _MemStore_writeVersion, "f");
|
|
175
175
|
}
|
|
176
|
+
/** Same count as {@link writeVersion}; in memory, so reading it is free. */
|
|
177
|
+
get writeActivity() {
|
|
178
|
+
return __classPrivateFieldGet(this, _MemStore_writeVersion, "f");
|
|
179
|
+
}
|
|
176
180
|
/** Open or reopen the contents. A readonly open of never-created contents reports `false`. */
|
|
177
181
|
*reopen(options = {}) {
|
|
178
182
|
this.readonly = options.readonly ?? this.readonly;
|
|
@@ -45,6 +45,10 @@ export class StoreOwner {
|
|
|
45
45
|
get writeVersion() {
|
|
46
46
|
return this.store.writeVersion;
|
|
47
47
|
}
|
|
48
|
+
/** Writes made through this handle, counted without a store read (see `Dber.writeActivity`). */
|
|
49
|
+
get writeActivity() {
|
|
50
|
+
return this.store.writeActivity;
|
|
51
|
+
}
|
|
48
52
|
transaction(fn) {
|
|
49
53
|
return this.store.transaction(fn);
|
|
50
54
|
}
|
|
@@ -65,6 +65,7 @@ export { signGroupMessage } from "../app/multisig/multisig-messaging.js";
|
|
|
65
65
|
export { approveAcdcV2Registry, publishAcdcV2Registry } from "../app/multisig/multisig-workflows.js";
|
|
66
66
|
export { approveGroupProposal, dismissGroupProposal, findGroupProposal, listGroupProposals, } from "../app/multisig/proposals.js";
|
|
67
67
|
export { proposeCredentialIssue, proposeCredentialRegistry, proposeCredentialRevocation, proposeGroupInception, proposeGroupInteraction, proposeGroupRotation, proposeRegistryEvent, settleProposedRotation, } from "../app/multisig/proposing.js";
|
|
68
|
+
export { replaceGroupMember, } from "../app/multisig/replace-member.js";
|
|
68
69
|
export { Notifier, } from "../app/notifications/notifying.js";
|
|
69
70
|
export { Signal, Signaler, } from "../app/notifications/signaling.js";
|
|
70
71
|
export { SignerConditions } from "../app/runtime/signer-conditions.js";
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* `src/version.ts` files by hand; edit this template instead.
|
|
7
7
|
*/
|
|
8
8
|
/** Package semantic version copied from the owning package manifest. */
|
|
9
|
-
export const PACKAGE_VERSION = "2.2.
|
|
9
|
+
export const PACKAGE_VERSION = "2.2.4";
|
|
10
10
|
/** Optional build metadata stamp injected by release/CI workflows. */
|
|
11
|
-
export const BUILD_METADATA = "build.
|
|
11
|
+
export const BUILD_METADATA = "build.41.494decc08a02e94e55c83a49945ecaa6df7ad701";
|
|
12
12
|
/** User-facing version string with build metadata appended when present. */
|
|
13
13
|
export const DISPLAY_VERSION = BUILD_METADATA
|
|
14
14
|
? `${PACKAGE_VERSION}+${BUILD_METADATA}`
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authenticating.d.ts","sourceRoot":"","sources":["../../../src/app/bootstrap/authenticating.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAU,MAAM,WAAW,CAAC;AAEzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,OAAO,EAAE,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"authenticating.d.ts","sourceRoot":"","sources":["../../../src/app/bootstrap/authenticating.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAU,MAAM,WAAW,CAAC;AAEzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,OAAO,EAAE,KAAK,UAAU,EAAmC,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAG9D;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,KAAK,EACT,GAAG,EAAE,MAAM,GACV,OAAO,CAMT;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,aAAa;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAE1B;;;;;OAKG;gBACS,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU;IAKzC,sEAAsE;IACrE,WAAW,IAAI,SAAS,CAAC,IAAI,CAAC;IAe/B,yDAAyD;IACxD,MAAM,CAAC,KAAK,GAAE,YAA0B,EAAE,MAAM,GAAE,QAAQ,CAAC,UAAU,CAAmB,GAAG,SAAS,CAAC,KAAK,CAAC;IAI5G,4EAA4E;IAC5E,OAAO,CAAC,sBAAsB;IAsC9B,0DAA0D;IAC1D,OAAO,CAAC,eAAe;IAYvB;;;;;OAKG;IACH,OAAO,CAAE,kBAAkB;CAgC5B"}
|