keri-ts 2.2.3 → 2.2.5
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/_dnt.polyfills.js +14 -14
- package/esm/keri/npm/src/acdc/registering.js +19 -0
- package/esm/keri/npm/src/acdc/salting.js +4 -0
- package/esm/keri/npm/src/app/bootstrap/authenticating.js +34 -12
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +365 -32
- package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
- package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
- package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
- package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
- package/esm/keri/npm/src/app/delivery/forwarding.js +188 -47
- package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
- package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
- package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
- package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
- package/esm/keri/npm/src/app/identity/delegating.js +66 -9
- package/esm/keri/npm/src/app/identity/habbing.js +190 -71
- package/esm/keri/npm/src/app/identity/keeping.js +111 -39
- package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
- package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
- package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
- package/esm/keri/npm/src/app/ipex/group-issuance.js +109 -23
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +36 -22
- package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
- package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
- package/esm/keri/npm/src/app/ipex/issuance.js +131 -35
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
- package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
- package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +16 -3
- package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
- package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
- package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
- package/esm/keri/npm/src/app/multisig/counseling.js +75 -27
- package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
- package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
- package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
- package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
- package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
- package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
- package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
- package/esm/keri/npm/src/app/protocol/protocol-processing.js +62 -27
- package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
- package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
- package/esm/keri/npm/src/app/queries/witness-inquisitor.js +190 -40
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +46 -73
- package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
- package/esm/keri/npm/src/app/runtime/doing.js +25 -0
- package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
- package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
- package/esm/keri/npm/src/app/transport/http-client.js +20 -2
- package/esm/keri/npm/src/app/transport/message-source.js +2 -1
- package/esm/keri/npm/src/app/transport/messengering.js +39 -8
- package/esm/keri/npm/src/app/witness/ingress.js +7 -0
- package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
- package/esm/keri/npm/src/app/witness/receipting.js +62 -20
- package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
- package/esm/keri/npm/src/core/errors.js +73 -0
- package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
- package/esm/keri/npm/src/core/kel/eventing.js +25 -28
- package/esm/keri/npm/src/core/kel/kever.js +15 -9
- package/esm/keri/npm/src/core/mailbox/kram-policy.js +8 -0
- package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
- package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
- package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
- package/esm/keri/npm/src/core/routing.js +18 -25
- package/esm/keri/npm/src/core/scheming.js +12 -5
- package/esm/keri/npm/src/core/wire-fields.js +126 -0
- package/esm/keri/npm/src/db/basing.js +4 -13
- package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
- 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/db/core/webdber.js +85 -27
- package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
- package/esm/keri/npm/src/db/subing.js +4 -0
- package/esm/keri/npm/src/runtime/index.js +11 -5
- package/esm/keri/npm/src/time/mod.js +5 -0
- package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
- package/esm/keri/npm/src/vdr/eventing.js +28 -19
- package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +1 -1
- package/types/_dnt.polyfills.d.ts +9 -9
- package/types/_dnt.polyfills.d.ts.map +1 -1
- package/types/acdc/registering.d.ts +6 -0
- package/types/acdc/registering.d.ts.map +1 -1
- package/types/acdc/salting.d.ts.map +1 -1
- package/types/app/bootstrap/authenticating.d.ts +12 -2
- package/types/app/bootstrap/authenticating.d.ts.map +1 -1
- package/types/app/bootstrap/oobiery.d.ts +144 -7
- package/types/app/bootstrap/oobiery.d.ts.map +1 -1
- package/types/app/credentials/credential-importing.d.ts +8 -0
- package/types/app/credentials/credential-importing.d.ts.map +1 -1
- package/types/app/credentials/root-replicator.d.ts +19 -8
- package/types/app/credentials/root-replicator.d.ts.map +1 -1
- package/types/app/credentials/schema-resolving.d.ts.map +1 -1
- package/types/app/credentials/tel-publishing.d.ts +4 -2
- package/types/app/credentials/tel-publishing.d.ts.map +1 -1
- package/types/app/delivery/forwarding.d.ts +75 -7
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/delivery/relaying.d.ts +144 -0
- package/types/app/delivery/relaying.d.ts.map +1 -0
- package/types/app/delivery/respondant.d.ts +9 -2
- package/types/app/delivery/respondant.d.ts.map +1 -1
- package/types/app/exchanges/exchanging.d.ts +36 -0
- package/types/app/exchanges/exchanging.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts +6 -0
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/habbing.d.ts +106 -19
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +54 -19
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/identity/retire-identifier.d.ts +24 -0
- package/types/app/identity/retire-identifier.d.ts.map +1 -0
- package/types/app/ipex/disclosure-replaying.d.ts +61 -0
- package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
- package/types/app/ipex/group-ipex.d.ts +60 -14
- package/types/app/ipex/group-ipex.d.ts.map +1 -1
- package/types/app/ipex/group-issuance.d.ts +48 -6
- package/types/app/ipex/group-issuance.d.ts.map +1 -1
- package/types/app/ipex/ipex-discloser.d.ts +14 -2
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
- package/types/app/ipex/ipex-grouping.d.ts +23 -2
- package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
- package/types/app/ipex/ipex-v2.d.ts +5 -2
- package/types/app/ipex/ipex-v2.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +35 -11
- package/types/app/ipex/issuance.d.ts.map +1 -1
- package/types/app/ipex/pending-presentations.d.ts +4 -2
- package/types/app/ipex/pending-presentations.d.ts.map +1 -1
- package/types/app/ipex/presenting.d.ts.map +1 -1
- package/types/app/ipex/receive-issuance.d.ts +2 -1
- package/types/app/ipex/receive-issuance.d.ts.map +1 -1
- package/types/app/ipex/unblinding-workflow.d.ts +4 -1
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
- package/types/app/mailbox/agent-inbox.d.ts +126 -0
- package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
- package/types/app/mailbox/forward-handler.d.ts +114 -14
- package/types/app/mailbox/forward-handler.d.ts.map +1 -1
- package/types/app/mailbox/kel-publisher.d.ts +12 -22
- package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
- package/types/app/mailbox/mailbox-poller.d.ts +40 -24
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +19 -5
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +34 -11
- package/types/app/multisig/counseling.d.ts.map +1 -1
- package/types/app/multisig/group-members.d.ts +17 -1
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/grouping.d.ts +17 -1
- package/types/app/multisig/grouping.d.ts.map +1 -1
- package/types/app/multisig/multisig-workflows.d.ts +0 -8
- package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
- package/types/app/multisig/proposing.d.ts +4 -19
- package/types/app/multisig/proposing.d.ts.map +1 -1
- package/types/app/multisig/rejoin-group.d.ts +119 -0
- package/types/app/multisig/rejoin-group.d.ts.map +1 -0
- package/types/app/multisig/replace-member.d.ts +15 -1
- package/types/app/multisig/replace-member.d.ts.map +1 -1
- package/types/app/protocol/parsering.d.ts +21 -2
- package/types/app/protocol/parsering.d.ts.map +1 -1
- package/types/app/protocol/protocol-processing.d.ts +31 -6
- package/types/app/protocol/protocol-processing.d.ts.map +1 -1
- package/types/app/queries/query-replies.d.ts +4 -8
- package/types/app/queries/query-replies.d.ts.map +1 -1
- package/types/app/queries/relay-refresh.d.ts +26 -0
- package/types/app/queries/relay-refresh.d.ts.map +1 -0
- package/types/app/queries/witness-inquisitor.d.ts +57 -6
- package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
- package/types/app/runtime/agent-runtime.d.ts +34 -3
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/cue-runtime.d.ts +15 -3
- package/types/app/runtime/cue-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/app/runtime/operation-owners.d.ts +16 -26
- package/types/app/runtime/operation-owners.d.ts.map +1 -1
- package/types/app/runtime/workflow-contract.d.ts +21 -6
- package/types/app/runtime/workflow-contract.d.ts.map +1 -1
- package/types/app/transport/http-client.d.ts +9 -0
- package/types/app/transport/http-client.d.ts.map +1 -1
- package/types/app/transport/message-source.d.ts +6 -3
- package/types/app/transport/message-source.d.ts.map +1 -1
- package/types/app/transport/messengering.d.ts +10 -1
- package/types/app/transport/messengering.d.ts.map +1 -1
- package/types/app/witness/ingress.d.ts +1 -0
- package/types/app/witness/ingress.d.ts.map +1 -1
- package/types/app/witness/receipt-client.d.ts +19 -1
- package/types/app/witness/receipt-client.d.ts.map +1 -1
- package/types/app/witness/receipting.d.ts +24 -1
- package/types/app/witness/receipting.d.ts.map +1 -1
- package/types/cli/messaging/multisig.d.ts.map +1 -1
- package/types/core/cues.d.ts +53 -1
- package/types/core/cues.d.ts.map +1 -1
- package/types/core/errors.d.ts +32 -0
- package/types/core/errors.d.ts.map +1 -1
- package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
- package/types/core/kel/eventing.d.ts.map +1 -1
- package/types/core/kel/kever-decisions.d.ts +3 -1
- package/types/core/kel/kever-decisions.d.ts.map +1 -1
- package/types/core/kel/kever.d.ts +7 -1
- package/types/core/kel/kever.d.ts.map +1 -1
- package/types/core/mailbox/kram-policy.d.ts +6 -0
- package/types/core/mailbox/kram-policy.d.ts.map +1 -1
- package/types/core/mailbox/kram-pruning.d.ts +5 -1
- package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
- package/types/core/mailbox/kram-time.d.ts +26 -2
- package/types/core/mailbox/kram-time.d.ts.map +1 -1
- package/types/core/mailbox/kraming.d.ts +14 -1
- package/types/core/mailbox/kraming.d.ts.map +1 -1
- package/types/core/routing.d.ts +3 -7
- package/types/core/routing.d.ts.map +1 -1
- package/types/core/scheming.d.ts.map +1 -1
- package/types/core/wire-fields.d.ts +56 -0
- package/types/core/wire-fields.d.ts.map +1 -0
- package/types/db/basing.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/indexeddb-persistence.d.ts +3 -2
- package/types/db/core/indexeddb-persistence.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/db/core/webdber.d.ts +32 -6
- package/types/db/core/webdber.d.ts.map +1 -1
- package/types/db/exchange/exchange-records.d.ts +4 -0
- package/types/db/exchange/exchange-records.d.ts.map +1 -1
- package/types/db/mailbox/mailboxing.d.ts +10 -1
- package/types/db/mailbox/mailboxing.d.ts.map +1 -1
- package/types/db/subing.d.ts +2 -0
- package/types/db/subing.d.ts.map +1 -1
- package/types/runtime/index.d.ts +20 -14
- package/types/runtime/index.d.ts.map +1 -1
- package/types/time/mod.d.ts +2 -0
- package/types/time/mod.d.ts.map +1 -1
- package/types/vdr/credential-tsn-record.d.ts.map +1 -1
- package/types/vdr/eventing.d.ts.map +1 -1
- package/types/vdr/registry-tsn-record.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
- package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
- package/types/app/runtime/signer-conditions.d.ts +0 -32
- package/types/app/runtime/signer-conditions.d.ts.map +0 -1
|
@@ -6,7 +6,6 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
6
6
|
var _MailboxPoller_dirty;
|
|
7
7
|
/** Recipient mailbox polling: signed queries, awaited SSE intake and persisted cursor progression. */
|
|
8
8
|
import { call, scoped, spawn } from "effection";
|
|
9
|
-
import { SignerNotHeldError } from "../identity/keeping.js";
|
|
10
9
|
import { abortable } from "../runtime/abortable.js";
|
|
11
10
|
import { doEvery, PROTOCOL_TOCK_MS } from "../runtime/doing.js";
|
|
12
11
|
import { runtimeTurn } from "../runtime/runtime-turn.js";
|
|
@@ -15,6 +14,7 @@ import { isLegacyCesrMediaRejection, readResponseDetail } from "../transport/ces
|
|
|
15
14
|
import { buildCesrRequest, CESR_CONTENT_TYPE, KERIPY_CESR_JSON_CONTENT_TYPE, } from "../transport/cesr-http.js";
|
|
16
15
|
import { requestWithHeaderTimeout } from "../transport/httping.js";
|
|
17
16
|
import { TransportError } from "../transport/transport-error.js";
|
|
17
|
+
import { keepsForPickup } from "./agent-inbox.js";
|
|
18
18
|
import { consumeMailboxSse } from "./mailbox-sse.js";
|
|
19
19
|
import { mailboxPollEndpoints } from "./mailboxing.js";
|
|
20
20
|
/** Tocks between the poller's full re-selection of workers: 160 tocks of 31.25 ms is five seconds. */
|
|
@@ -35,7 +35,8 @@ export const MAILBOX_POLLER_PACING = Object.freeze({
|
|
|
35
35
|
*
|
|
36
36
|
* Each endpoint has an independent worker and durable progress in the director.
|
|
37
37
|
* Retrieved records are lent to the awaited consumer; protocol intake stays outside.
|
|
38
|
-
* A provider store in the same runtime is never scanned for local recipient input
|
|
38
|
+
* A provider store in the same runtime is never scanned for local recipient input, and this runtime never polls its
|
|
39
|
+
* own endpoint for a local group whose mail it processes on arrival (see {@link MailboxPoller.deliveredOnArrival}).
|
|
39
40
|
*/
|
|
40
41
|
export class MailboxPoller {
|
|
41
42
|
/**
|
|
@@ -45,9 +46,10 @@ export class MailboxPoller {
|
|
|
45
46
|
* @param mailboxDirector Borrowed provider/topic/cursor owner.
|
|
46
47
|
* @param http Borrowed HTTP implementation selected by composition.
|
|
47
48
|
* @param clock Borrowed clock with epoch-millisecond now and millisecond timer delays.
|
|
48
|
-
* @param
|
|
49
|
+
* @param relay The agent every subject is polled through; null selects authorized mailboxes and witnesses.
|
|
50
|
+
* @param options Optional timeout overrides and poll-report deck; finite positive timeouts are floored with a 1 ms minimum. Missing/nonpositive/nonfinite values use 5-second headers and 30-second poll windows.
|
|
49
51
|
*/
|
|
50
|
-
constructor(hby, mailboxDirector, http, clock, options = {}) {
|
|
52
|
+
constructor(hby, mailboxDirector, http, clock, relay, options = {}) {
|
|
51
53
|
/** Borrowed habitat collection used to select mailbox signers. */
|
|
52
54
|
Object.defineProperty(this, "hby", {
|
|
53
55
|
enumerable: true,
|
|
@@ -83,22 +85,15 @@ export class MailboxPoller {
|
|
|
83
85
|
writable: true,
|
|
84
86
|
value: void 0
|
|
85
87
|
});
|
|
86
|
-
/**
|
|
87
|
-
Object.defineProperty(this, "
|
|
88
|
+
/** Where served polls and 403s are pushed, for the KEL publisher; null when nothing consumes them. */
|
|
89
|
+
Object.defineProperty(this, "polls", {
|
|
88
90
|
enumerable: true,
|
|
89
91
|
configurable: true,
|
|
90
92
|
writable: true,
|
|
91
93
|
value: void 0
|
|
92
94
|
});
|
|
93
|
-
/**
|
|
94
|
-
Object.defineProperty(this, "
|
|
95
|
-
enumerable: true,
|
|
96
|
-
configurable: true,
|
|
97
|
-
writable: true,
|
|
98
|
-
value: void 0
|
|
99
|
-
});
|
|
100
|
-
/** Told when a mailbox answers 403. */
|
|
101
|
-
Object.defineProperty(this, "onUnauthorized", {
|
|
95
|
+
/** The agent this runtime polls through (an edge), its only provider; null polls mailboxes and witnesses. */
|
|
96
|
+
Object.defineProperty(this, "relay", {
|
|
102
97
|
enumerable: true,
|
|
103
98
|
configurable: true,
|
|
104
99
|
writable: true,
|
|
@@ -106,15 +101,14 @@ export class MailboxPoller {
|
|
|
106
101
|
});
|
|
107
102
|
/** Identifiers whose mailbox endpoints changed; the poller re-selects their workers on the next tock. */
|
|
108
103
|
_MailboxPoller_dirty.set(this, new Set());
|
|
109
|
-
const { timeouts,
|
|
110
|
-
this.
|
|
111
|
-
this.onPolled = onPolled;
|
|
112
|
-
this.onUnauthorized = onUnauthorized;
|
|
104
|
+
const { timeouts, polls } = options;
|
|
105
|
+
this.polls = polls ?? null;
|
|
113
106
|
this.hby = hby;
|
|
114
107
|
this.mailboxDirector = mailboxDirector;
|
|
115
108
|
this.timeoutPolicy = normalizeMailboxPollingTimeoutPolicy(timeouts);
|
|
116
109
|
this.http = http;
|
|
117
110
|
this.clock = clock;
|
|
111
|
+
this.relay = relay;
|
|
118
112
|
}
|
|
119
113
|
/**
|
|
120
114
|
* Register one mailbox topic that this poller should retrieve.
|
|
@@ -148,12 +142,12 @@ export class MailboxPoller {
|
|
|
148
142
|
shape = topics.join("|");
|
|
149
143
|
habs = self.hby.habs.size;
|
|
150
144
|
__classPrivateFieldGet(self, _MailboxPoller_dirty, "f").clear();
|
|
151
|
-
yield* self.syncRemoteWorkers(remoteWorkers, onBatch, topics);
|
|
145
|
+
yield* self.syncRemoteWorkers(remoteWorkers, onBatch, topics, pacing.tockMs);
|
|
152
146
|
}
|
|
153
147
|
else if (__classPrivateFieldGet(self, _MailboxPoller_dirty, "f").size) {
|
|
154
148
|
const only = new Set(__classPrivateFieldGet(self, _MailboxPoller_dirty, "f"));
|
|
155
149
|
__classPrivateFieldGet(self, _MailboxPoller_dirty, "f").clear();
|
|
156
|
-
yield* self.syncRemoteWorkers(remoteWorkers, onBatch, topics, only);
|
|
150
|
+
yield* self.syncRemoteWorkers(remoteWorkers, onBatch, topics, pacing.tockMs, only);
|
|
157
151
|
}
|
|
158
152
|
});
|
|
159
153
|
}
|
|
@@ -176,26 +170,20 @@ export class MailboxPoller {
|
|
|
176
170
|
* @param target Mailbox subject habitat plus the actual local signer (a member for groups).
|
|
177
171
|
* @param endpoint Selected remote endpoint AID and HTTP(S) URL.
|
|
178
172
|
* @param onBatch Awaited consumer; successful completion precedes durable cursor advancement.
|
|
179
|
-
* @returns How the poll ended; see {@link PollOutcome}.
|
|
173
|
+
* @returns How the poll ended; see {@link PollOutcome}.
|
|
180
174
|
*/
|
|
181
175
|
*pollRemoteEndpointOnce(target, endpoint, onBatch) {
|
|
182
176
|
const cursor = this.mailboxDirector.remoteQueryCursor(target.targetPre, endpoint.eid);
|
|
183
177
|
// deno-lint-ignore no-this-alias -- Generator callbacks have no lexical this.
|
|
184
178
|
const self = this;
|
|
185
|
-
// The concrete HTTP/SSE operation owns and joins this whole poll attempt.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
catch (error) {
|
|
195
|
-
if (error instanceof SignerNotHeldError)
|
|
196
|
-
return { outcome: "unsignable", missing: error.pub };
|
|
197
|
-
throw error;
|
|
198
|
-
}
|
|
179
|
+
// The concrete HTTP/SSE operation owns and joins this whole poll attempt.
|
|
180
|
+
return yield* call(() => fetchMailboxMessages(target.signerHab, target.targetPre, endpoint.eid, endpoint.url, cursor, mailboxFetchTimeoutPolicy(this.timeoutPolicy), this.http, this.clock, function* deliverThenAdvanceCursor(message) {
|
|
181
|
+
yield* call(() => onBatch({ pre: target.targetPre, eid: endpoint.eid, messages: [message.msg] }));
|
|
182
|
+
// A subject retired during this poll keeps no cursor: its worker halts on the next tock.
|
|
183
|
+
if (!self.hby.habs.has(target.targetPre))
|
|
184
|
+
return;
|
|
185
|
+
self.mailboxDirector.updateRemoteCursor(target.targetPre, endpoint.eid, message.topic, message.idx);
|
|
186
|
+
}));
|
|
199
187
|
}
|
|
200
188
|
/**
|
|
201
189
|
* Keep one long-lived worker running for each currently configured remote endpoint.
|
|
@@ -203,29 +191,32 @@ export class MailboxPoller {
|
|
|
203
191
|
* @param remoteWorkers Owned worker map, mutated to match current endpoint selection.
|
|
204
192
|
* @param onBatch Shared awaited consumer used independently by each endpoint worker.
|
|
205
193
|
* @param topics Current registered topics; no topics means no remote workers.
|
|
194
|
+
* @param tockMs How long a worker whose signer cannot sign yet waits before it looks again.
|
|
206
195
|
*/
|
|
207
|
-
*syncRemoteWorkers(remoteWorkers, onBatch, topics, only) {
|
|
208
|
-
const
|
|
196
|
+
*syncRemoteWorkers(remoteWorkers, onBatch, topics, tockMs, only) {
|
|
197
|
+
const wanted = new Map();
|
|
209
198
|
if (topics.length > 0) {
|
|
210
199
|
for (const target of this.remotePollTargets(only)) {
|
|
211
|
-
for (const endpoint of mailboxPollEndpoints(target.hab)) {
|
|
212
|
-
|
|
213
|
-
active.add(workerKey);
|
|
214
|
-
if (remoteWorkers.has(workerKey)) {
|
|
200
|
+
for (const endpoint of mailboxPollEndpoints(target.hab, this.relay)) {
|
|
201
|
+
if (this.deliveredOnArrival(target.targetPre, endpoint.eid))
|
|
215
202
|
continue;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
remoteWorkers.set(workerKey, task);
|
|
203
|
+
// The signer is part of the identity: a group whose local member was replaced gets a new worker.
|
|
204
|
+
wanted.set(`${target.targetPre}:${target.signerHab.pre}:${endpoint.key}`, { target, endpoint });
|
|
219
205
|
}
|
|
220
206
|
}
|
|
221
207
|
}
|
|
222
208
|
for (const [workerKey, task] of [...remoteWorkers.entries()]) {
|
|
223
|
-
if (
|
|
209
|
+
if (wanted.has(workerKey) || (only && !only.has(workerKey.slice(0, workerKey.indexOf(":"))))) {
|
|
224
210
|
continue;
|
|
225
211
|
}
|
|
226
212
|
yield* task.halt();
|
|
227
213
|
remoteWorkers.delete(workerKey);
|
|
228
214
|
}
|
|
215
|
+
for (const [workerKey, { target, endpoint }] of wanted) {
|
|
216
|
+
if (remoteWorkers.has(workerKey))
|
|
217
|
+
continue;
|
|
218
|
+
remoteWorkers.set(workerKey, yield* spawn(() => this.remoteEndpointWorker(target, endpoint, onBatch, tockMs)));
|
|
219
|
+
}
|
|
229
220
|
}
|
|
230
221
|
/**
|
|
231
222
|
* Poll one remote endpoint continuously in the long-lived runtime shape.
|
|
@@ -234,21 +225,26 @@ export class MailboxPoller {
|
|
|
234
225
|
* `keri-ts`'s split between mailbox transport and runtime parser ownership. Each poll ends in a
|
|
235
226
|
* {@link PollOutcome}; {@link nextPoll} maps it and the backoff state to the next action. A mailbox that is
|
|
236
227
|
* unreachable or refuses is retried on {@link MailboxPoller.RefusalBackoff}, never in a tight loop and never
|
|
237
|
-
* by failing the runtime. A 403
|
|
228
|
+
* by failing the runtime. A served poll and a 403 are pushed onto `polls` for the KEL publisher, which owns posting
|
|
229
|
+
* the KEL.
|
|
230
|
+
*
|
|
231
|
+
* A signer that cannot sign yet (a store opened without it) is not a remote failure: the worker neither polls
|
|
232
|
+
* nor backs off, and looks again each tock, so it polls within one tock of `Manager.hold`. Because it never polls
|
|
233
|
+
* for such a subject, the consumer's protocol work never runs for it either.
|
|
238
234
|
*
|
|
239
235
|
* @param target Mailbox subject habitat plus the actual local signer (a member for groups).
|
|
240
236
|
* @param endpoint Selected remote endpoint AID and HTTP(S) URL.
|
|
241
237
|
* @param onBatch Awaited consumer; successful completion precedes durable cursor advancement.
|
|
238
|
+
* @param tockMs How long the worker waits before it looks again at a signer that cannot sign.
|
|
242
239
|
*/
|
|
243
|
-
*remoteEndpointWorker(target, endpoint, onBatch) {
|
|
240
|
+
*remoteEndpointWorker(target, endpoint, onBatch, tockMs) {
|
|
244
241
|
let state = { backoffMs: 0 };
|
|
245
242
|
while (true) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
this.signerConditions?.clear(source);
|
|
243
|
+
if (!target.signerHab.canSign()) {
|
|
244
|
+
yield* waitOnClock(this.clock, tockMs);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const outcome = yield* this.pollRemoteEndpointOnce(target, endpoint, onBatch);
|
|
252
248
|
const step = nextPoll(state, outcome, {
|
|
253
249
|
initialMs: this.timeoutPolicy.refusalInitialMs,
|
|
254
250
|
maxMs: this.timeoutPolicy.refusalMaxMs,
|
|
@@ -256,23 +252,36 @@ export class MailboxPoller {
|
|
|
256
252
|
state = step.state;
|
|
257
253
|
switch (step.action.kind) {
|
|
258
254
|
case "poll":
|
|
259
|
-
this.
|
|
255
|
+
this.polls?.push({ kin: "polled", pre: target.targetPre, mailbox: endpoint.eid });
|
|
260
256
|
yield* runtimeTurn();
|
|
261
257
|
break;
|
|
262
258
|
case "prompt-publisher":
|
|
263
|
-
this.
|
|
259
|
+
this.polls?.push({ kin: "unauthorized", pre: target.targetPre, mailbox: endpoint.eid });
|
|
264
260
|
yield* waitOnClock(this.clock, step.action.ms);
|
|
265
261
|
break;
|
|
266
262
|
case "wait":
|
|
267
|
-
|
|
268
|
-
if (missing && this.signerConditions)
|
|
269
|
-
yield* this.signerConditions.waitForHold(this.clock, step.action.ms);
|
|
270
|
-
else
|
|
271
|
-
yield* waitOnClock(this.clock, step.action.ms);
|
|
263
|
+
yield* waitOnClock(this.clock, step.action.ms);
|
|
272
264
|
break;
|
|
273
265
|
}
|
|
274
266
|
}
|
|
275
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Whether `subject`'s mail at `eid` already reached local processing when it arrived, so polling `eid` for it would
|
|
270
|
+
* process every stored message a second time. That holds when this runtime is the provider (it owns the mailbox
|
|
271
|
+
* store), `eid` is one of its own habitats and it keeps `subject`'s mail for pickup ({@link keepsForPickup}: for a
|
|
272
|
+
* local subject, a group that authorized `eid` as mailbox or agent). Such a host ingests every `/fwd` for the group
|
|
273
|
+
* on arrival (`ForwardHandler`) and dispatches every raw arrival before keeping it; the copy it stores is for the
|
|
274
|
+
* group's remote members, who poll it from here. Any other local subject at a local endpoint (a single-sig habitat
|
|
275
|
+
* whose mailbox is this host) is still polled over loopback HTTP, which is its only local delivery of stored mail.
|
|
276
|
+
* Without a mailbox store this runtime is not the provider, so it polls as any remote client would.
|
|
277
|
+
*
|
|
278
|
+
* @param subject Local mailbox subject AID.
|
|
279
|
+
* @param eid Selected provider AID.
|
|
280
|
+
* @returns True when the endpoint must not be polled for this subject.
|
|
281
|
+
*/
|
|
282
|
+
deliveredOnArrival(subject, eid) {
|
|
283
|
+
return !!this.mailboxDirector.mailboxer && this.hby.habs.has(eid) && keepsForPickup(this.hby, subject, eid);
|
|
284
|
+
}
|
|
276
285
|
/**
|
|
277
286
|
* Select locally pollable subjects without substituting the group for its member signer.
|
|
278
287
|
*
|
|
@@ -374,7 +383,6 @@ export function nextPoll(state, outcome, backoff = MailboxPoller.RefusalBackoff)
|
|
|
374
383
|
}
|
|
375
384
|
case "no-response":
|
|
376
385
|
case "unreachable":
|
|
377
|
-
case "unsignable":
|
|
378
386
|
case "refused":
|
|
379
387
|
return backOff(state, backoff);
|
|
380
388
|
}
|
|
@@ -103,15 +103,31 @@ export function authorizedMailboxes(db, pre) {
|
|
|
103
103
|
* Both sets are polled, as in KERIpy MailboxDirector. A provider in both roles
|
|
104
104
|
* shares one history and cursor, so it gets one worker. Locally controlled AIDs
|
|
105
105
|
* remain eligible for HTTP polling; provider storage is never scanned directly.
|
|
106
|
+
* This lists every provider; the poller itself drops a local provider that
|
|
107
|
+
* processes the subject's mail on arrival (`MailboxPoller.deliveredOnArrival`).
|
|
106
108
|
* Missing HTTP(S) locations produce no poll target until a location is available.
|
|
107
109
|
*
|
|
110
|
+
* With a relay (an edge runtime) the relay is the only provider: witnesses and other mailboxes are not polled, and
|
|
111
|
+
* the relay is polled once the subject has authorized it as mailbox (for a group, the group's own row, since the
|
|
112
|
+
* relay stores the group's messages under that authorization). Until then the subject has no poll target; the
|
|
113
|
+
* runtime prompts the poller when the role is accepted.
|
|
114
|
+
*
|
|
108
115
|
* @param hab Mailbox subject habitat supplying accepted roles and current witnesses.
|
|
116
|
+
* @param relay The agent this runtime polls through; null polls authorized mailboxes and witnesses.
|
|
109
117
|
* @returns One HTTPS-preferred HTTP target per provider AID, with stable worker keys.
|
|
110
118
|
*/
|
|
111
|
-
export function mailboxPollEndpoints(hab) {
|
|
112
|
-
const eids = new Set(
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
export function mailboxPollEndpoints(hab, relay) {
|
|
120
|
+
const eids = new Set();
|
|
121
|
+
if (relay) {
|
|
122
|
+
if (authorizedMailboxes(hab.db, hab.pre).includes(relay.aid))
|
|
123
|
+
eids.add(relay.aid);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
for (const eid of authorizedMailboxes(hab.db, hab.pre))
|
|
127
|
+
eids.add(eid);
|
|
128
|
+
for (const eid of hab.kever?.wits ?? [])
|
|
129
|
+
eids.add(eid);
|
|
130
|
+
}
|
|
115
131
|
const endpoints = [];
|
|
116
132
|
for (const eid of eids) {
|
|
117
133
|
const urls = hab.fetchUrls(eid);
|
|
@@ -80,10 +80,14 @@ export class Counselor {
|
|
|
80
80
|
* @param clock Time source for fallback and retry; only `now` is used.
|
|
81
81
|
* @param changes Optional sink told `{kind:"group", id:"<pre>:<sn>"}` after commits.
|
|
82
82
|
* @param delegation Anchorer and query port for delegated events; omit for non-delegated hosts.
|
|
83
|
-
* @param
|
|
84
|
-
* the group's mailboxes the new key state
|
|
83
|
+
* @param rotated Deck that gets a record of each completed group rotation (`rot` or `drt`); the member runtime
|
|
84
|
+
* passes its KEL publisher's, which then tells the group's mailboxes the new key state. Several members doing
|
|
85
|
+
* so is harmless.
|
|
86
|
+
* @param groupChanges Deck that gets the prefix of a group whose establishment event completed or whose stored
|
|
87
|
+
* members were aligned late; the member runtime shares it with its root replicator, which re-derives the
|
|
88
|
+
* copies the group owes on its next pass instead of at its next sweep.
|
|
85
89
|
*/
|
|
86
|
-
constructor(hby, receipting, clock, changes, delegation,
|
|
90
|
+
constructor(hby, receipting, clock, changes, delegation, rotated, groupChanges, receiptRetry = DEFAULT_RECEIPT_RETRY, forwarding) {
|
|
87
91
|
Object.defineProperty(this, "hby", {
|
|
88
92
|
enumerable: true,
|
|
89
93
|
configurable: true,
|
|
@@ -114,11 +118,17 @@ export class Counselor {
|
|
|
114
118
|
writable: true,
|
|
115
119
|
value: delegation
|
|
116
120
|
});
|
|
117
|
-
Object.defineProperty(this, "
|
|
121
|
+
Object.defineProperty(this, "rotated", {
|
|
118
122
|
enumerable: true,
|
|
119
123
|
configurable: true,
|
|
120
124
|
writable: true,
|
|
121
|
-
value:
|
|
125
|
+
value: rotated
|
|
126
|
+
});
|
|
127
|
+
Object.defineProperty(this, "groupChanges", {
|
|
128
|
+
enumerable: true,
|
|
129
|
+
configurable: true,
|
|
130
|
+
writable: true,
|
|
131
|
+
value: groupChanges
|
|
122
132
|
});
|
|
123
133
|
Object.defineProperty(this, "receiptRetry", {
|
|
124
134
|
enumerable: true,
|
|
@@ -151,6 +161,13 @@ export class Counselor {
|
|
|
151
161
|
writable: true,
|
|
152
162
|
value: new Map()
|
|
153
163
|
});
|
|
164
|
+
/** Per local group, the latest establishment SAID its stored members were found to follow; memory only. */
|
|
165
|
+
Object.defineProperty(this, "aligned", {
|
|
166
|
+
enumerable: true,
|
|
167
|
+
configurable: true,
|
|
168
|
+
writable: true,
|
|
169
|
+
value: new Map()
|
|
170
|
+
});
|
|
154
171
|
Object.defineProperty(this, "anchorQuery", {
|
|
155
172
|
enumerable: true,
|
|
156
173
|
configurable: true,
|
|
@@ -282,8 +299,16 @@ export class Counselor {
|
|
|
282
299
|
for (const sn of removed)
|
|
283
300
|
this.changes?.push({ kind: "group", id: `${pre}:${sn}` });
|
|
284
301
|
}
|
|
285
|
-
/**
|
|
302
|
+
/** Whether completed events are held in memory for forwarding, which a pass retries without any store write. */
|
|
303
|
+
get holdsForwards() {
|
|
304
|
+
return this.forwards.size > 0;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* One pass over all stages, as KERIpy `processEscrows`, after aligning stored group members with accepted state
|
|
308
|
+
* so that a completion in this pass forwards to the members of the event that completed.
|
|
309
|
+
*/
|
|
286
310
|
*processOnce() {
|
|
311
|
+
this.alignMembers();
|
|
287
312
|
// A runtime calls this every turn; with nothing counseled, three row counts are the whole cost.
|
|
288
313
|
const db = this.hby.db;
|
|
289
314
|
if (db.gpse.cnt() === 0 && db.gdee.cnt() === 0 && db.gpwe.cnt() === 0 && this.forwards.size === 0)
|
|
@@ -303,23 +328,12 @@ export class Counselor {
|
|
|
303
328
|
return;
|
|
304
329
|
for (const [key, owed] of [...this.forwards]) {
|
|
305
330
|
const now = this.clock.now();
|
|
306
|
-
|
|
331
|
+
// The local member signs the forward; a group whose member cannot sign yet keeps it without spending an attempt.
|
|
332
|
+
if (now < owed.nextAt || !this.hby.habs.get(owed.event.pre)?.canSign())
|
|
307
333
|
continue;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
catch (error) {
|
|
313
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
314
|
-
outcomes = owed.members.map((recipient) => ({
|
|
315
|
-
recipient,
|
|
316
|
-
exnSaid: owed.event.said,
|
|
317
|
-
payloadSaid: owed.event.said,
|
|
318
|
-
outcome: "failed",
|
|
319
|
-
deliveries: [],
|
|
320
|
-
reason,
|
|
321
|
-
}));
|
|
322
|
-
}
|
|
334
|
+
// A member that could not be reached is its `failed` outcome; what `forward` throws is a fault (the accepted
|
|
335
|
+
// event or the local member missing from the store) and ends the loop instead of spending an attempt.
|
|
336
|
+
const outcomes = yield* this.forwarding.forward(owed.event, owed.members);
|
|
323
337
|
const failed = outcomes.filter((outcome) => outcome.outcome === "failed");
|
|
324
338
|
this.forwarded.set(key, [
|
|
325
339
|
...(this.forwarded.get(key)?.filter((prior) => !outcomes.some((o) => o.recipient === prior.recipient)) ?? []),
|
|
@@ -406,6 +420,34 @@ export class Counselor {
|
|
|
406
420
|
});
|
|
407
421
|
this.changes?.push({ kind: "group", id: `${event.pre}:${event.sn}` });
|
|
408
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Bring each local group's stored members in line with its latest accepted establishment event, whoever proposed
|
|
425
|
+
* it and however late its last signature arrived (`Habery.alignGroupMembers`). A group is re-read only when that
|
|
426
|
+
* event differs from the one its members were last found to follow; one that cannot be attributed yet (a member's
|
|
427
|
+
* KEL is missing) is left as it is and read again on a later pass, which the arriving KEL's write brings about.
|
|
428
|
+
*
|
|
429
|
+
* After a change, an elected member whose latest establishment event already completed owes it again to the
|
|
430
|
+
* members it now has, and the group goes on `groupChanges` so the root replicator re-derives its copies.
|
|
431
|
+
*/
|
|
432
|
+
alignMembers() {
|
|
433
|
+
const db = this.hby.db;
|
|
434
|
+
for (const pre of [...db.groups]) {
|
|
435
|
+
const est = db.getKever(pre)?.lastEst;
|
|
436
|
+
if (!est || this.aligned.get(pre) === est.d)
|
|
437
|
+
continue;
|
|
438
|
+
const alignment = this.hby.alignGroupMembers(pre);
|
|
439
|
+
if (alignment === "unattributed")
|
|
440
|
+
continue;
|
|
441
|
+
this.aligned.set(pre, est.d);
|
|
442
|
+
if (alignment === "unchanged")
|
|
443
|
+
continue;
|
|
444
|
+
this.changes?.push({ kind: "group", id: `${pre}:${est.s}` });
|
|
445
|
+
this.groupChanges?.push(pre);
|
|
446
|
+
const event = { pre, sn: est.s, said: est.d };
|
|
447
|
+
if (this.completed(pre, est.s) === est.d)
|
|
448
|
+
this.owe(event);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
409
451
|
/** The elected member owes the completed event to the members that did not sign it into existence. */
|
|
410
452
|
owe(event) {
|
|
411
453
|
if (!this.forwarding || !groupWitnesser(this.hby, event.pre, event))
|
|
@@ -414,7 +456,10 @@ export class Counselor {
|
|
|
414
456
|
if (members.length)
|
|
415
457
|
this.forwards.set(this.runKey(event), { event, members, attempts: 0, nextAt: 0 });
|
|
416
458
|
}
|
|
417
|
-
/**
|
|
459
|
+
/**
|
|
460
|
+
* Record completion of a counseled event and drop its row from `table`. A completed establishment event goes on
|
|
461
|
+
* `groupChanges`, and a completed rotation on `rotated` as well.
|
|
462
|
+
*/
|
|
418
463
|
finish(table, event, row) {
|
|
419
464
|
const db = this.hby.db;
|
|
420
465
|
db.transaction(() => {
|
|
@@ -424,9 +469,11 @@ export class Counselor {
|
|
|
424
469
|
this.runs.delete(this.runKey(event));
|
|
425
470
|
this.changes?.push({ kind: "group", id: `${event.pre}:${event.sn}` });
|
|
426
471
|
this.owe(event);
|
|
427
|
-
const
|
|
428
|
-
if (
|
|
429
|
-
this.
|
|
472
|
+
const serder = db.getEvtSerder(event.pre, event.said);
|
|
473
|
+
if (serder?.estive)
|
|
474
|
+
this.groupChanges?.push(event.pre);
|
|
475
|
+
if (serder?.ilk === Ilks.rot || serder?.ilk === Ilks.drt) {
|
|
476
|
+
this.rotated?.push({ kin: "rotated", pre: event.pre, sn: event.sn, said: event.said });
|
|
430
477
|
}
|
|
431
478
|
}
|
|
432
479
|
/** Complete events whose own witness threshold is met; otherwise submit or fetch receipts. */
|
|
@@ -456,8 +503,9 @@ export class Counselor {
|
|
|
456
503
|
return;
|
|
457
504
|
const submit = forceSubmit || groupWitnesser(this.hby, event.pre, event) || now - run.since >= run.fallbackAfterMs;
|
|
458
505
|
try {
|
|
506
|
+
// Group receipting reaches TCP-only witnesses, as `WitnessReceiptor` and KERIpy's Counselor do.
|
|
459
507
|
if (submit)
|
|
460
|
-
yield* this.receipting.receipt(event.pre, { sn: event.sn, auths: run.auths });
|
|
508
|
+
yield* this.receipting.receipt(event.pre, { sn: event.sn, auths: run.auths, allowTcp: true });
|
|
461
509
|
else
|
|
462
510
|
yield* this.receipting.get(event.pre, { sn: event.sn, said: event.said });
|
|
463
511
|
run.failure = null;
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import { TransIdxSigGroup } from "../../core/dispatch.js";
|
|
2
|
-
import { ValidationError } from "../../core/errors.js";
|
|
2
|
+
import { MissingDurableStateError, ValidationError } from "../../core/errors.js";
|
|
3
3
|
import { Kever } from "../../core/kel/kever.js";
|
|
4
4
|
import { dgKey } from "../../db/core/keys.js";
|
|
5
5
|
/** True when the selected habitat is a local group habitat. */
|
|
6
6
|
export function isLocalGroupHab(hby, hab) {
|
|
7
7
|
return !!hab.pre && !!hby.db.getHab(hab.pre)?.mid;
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Return the local member habitat for a persisted group identifier.
|
|
11
|
+
*
|
|
12
|
+
* @throws ValidationError when `groupPre` is not a local group (no habitat record names a local member).
|
|
13
|
+
* @throws MissingDurableStateError when the group's record names a local member the store does not hold: the
|
|
14
|
+
* store is damaged, which no remote message can cause (retirement refuses a local group's member).
|
|
15
|
+
*/
|
|
10
16
|
export function localGroupMember(hby, groupPre) {
|
|
11
|
-
const
|
|
12
|
-
|
|
17
|
+
const mid = hby.db.getHab(groupPre)?.mid;
|
|
18
|
+
if (!mid)
|
|
19
|
+
throw new ValidationError(`${groupPre} is not a local group.`);
|
|
20
|
+
const member = hby.habs.get(mid);
|
|
13
21
|
if (!member) {
|
|
14
|
-
throw new
|
|
22
|
+
throw new MissingDurableStateError(`Group ${groupPre} names local member ${mid}, which this store does not hold.`);
|
|
15
23
|
}
|
|
16
24
|
return member;
|
|
17
25
|
}
|
|
@@ -172,6 +180,34 @@ export function localIpexContribution(db, member, embedded, proofs) {
|
|
|
172
180
|
}
|
|
173
181
|
return null;
|
|
174
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Whether the group signatures a member received on the group-authored message `embedded`, from every copy it holds,
|
|
185
|
+
* together satisfy the threshold of the accepted group establishment event they name. Only signatures that verify
|
|
186
|
+
* against that event count, and copies naming different events are not pooled, so a message below the threshold, or
|
|
187
|
+
* one signed under keys this store has not accepted, reads false.
|
|
188
|
+
*
|
|
189
|
+
* This is the rule under which a member takes a group message it did not sign: accepting what already carries the
|
|
190
|
+
* group's threshold adds no signature, so it is not consent.
|
|
191
|
+
*/
|
|
192
|
+
export function groupThresholdSigned(db, embedded, proofs) {
|
|
193
|
+
const signed = new Map();
|
|
194
|
+
for (const proof of proofs) {
|
|
195
|
+
if (!embedded.pre || proof.pre !== embedded.pre || proof.sn > BigInt(Number.MAX_SAFE_INTEGER))
|
|
196
|
+
continue;
|
|
197
|
+
const said = db.kels.getLast(proof.pre, Number(proof.sn));
|
|
198
|
+
const event = said && said === proof.said ? db.getEvtSerder(proof.pre, said) : null;
|
|
199
|
+
if (!said || !event?.estive || !event.tholder)
|
|
200
|
+
continue;
|
|
201
|
+
const indices = signed.get(said) ?? new Set();
|
|
202
|
+
for (const index of Kever.verifyIndexedSignatures(embedded.raw, proof.sigers, event.verfers).indices) {
|
|
203
|
+
indices.add(index);
|
|
204
|
+
}
|
|
205
|
+
signed.set(said, indices);
|
|
206
|
+
if (event.tholder.satisfy([...indices]))
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
175
211
|
/**
|
|
176
212
|
* Derive the members of `group`'s latest accepted establishment event from
|
|
177
213
|
* local KELs, as KERIpy `GroupHab.acceptedMembers`.
|
|
@@ -14,11 +14,14 @@ import { exchange, isIpexVerb, isV2RegistryRoute, multisigChildField, nestedIpex
|
|
|
14
14
|
import { Kevery } from "../../core/kel/eventing.js";
|
|
15
15
|
import { Kever } from "../../core/kel/kever.js";
|
|
16
16
|
import { BasicReplyRouteHandler, Revery, Router } from "../../core/routing.js";
|
|
17
|
+
import { loadNests } from "../../db/exchange/exchange-nests.js";
|
|
17
18
|
import { Exchanger } from "../exchanges/exchanging.js";
|
|
19
|
+
import { isIssuanceOrigin } from "../ipex/ipex-node-authentication.js";
|
|
18
20
|
import { IpexV2Handler } from "../ipex/ipex-v2.js";
|
|
21
|
+
import { IPEX_GRANT_ROUTE } from "../ipex/ipexing.js";
|
|
19
22
|
import { dispatchEnvelope, envelopeFromFrame } from "../protocol/parsering.js";
|
|
20
23
|
import { abortable, rethrowCleanupFailure } from "../runtime/abortable.js";
|
|
21
|
-
import { groupSigningMembers, isLocalGroupHab, localIpexContribution } from "./group-members.js";
|
|
24
|
+
import { groupSigningMembers, groupThresholdSigned, isLocalGroupHab, localIpexContribution } from "./group-members.js";
|
|
22
25
|
import { multisigEmbeddedSection, multisigPayloadSaid } from "./multisig-messaging.js";
|
|
23
26
|
/** KERIpy multisig route constants. */
|
|
24
27
|
export const MULTISIG_ICP_ROUTE = "/multisig/icp";
|
|
@@ -529,7 +532,10 @@ export class Multiplexor {
|
|
|
529
532
|
&& handler instanceof IpexV2Handler && handler.hby === this.hby
|
|
530
533
|
&& handler.registry === this.registry.store;
|
|
531
534
|
}
|
|
532
|
-
/**
|
|
535
|
+
/**
|
|
536
|
+
* Index an authenticated proposal. Its child is accepted, with its own proofs, after local approval, or without one
|
|
537
|
+
* when the signatures received for it already complete it (see {@link alreadyComplete} and {@link groupIpexFollowable}).
|
|
538
|
+
*/
|
|
533
539
|
*add(serder, nests = []) {
|
|
534
540
|
const inspection = this.inspect(serder, nests);
|
|
535
541
|
if (!inspection.ok) {
|
|
@@ -588,6 +594,14 @@ export class Multiplexor {
|
|
|
588
594
|
else
|
|
589
595
|
yield* this.processApproved(nests);
|
|
590
596
|
}
|
|
597
|
+
else if (v2Ipex && groupIpexFollowable(this.hby, this.receivedCopies(nests, existing))) {
|
|
598
|
+
// The same holds for a group IPEX message: once the copies received for it sign it to the group's threshold,
|
|
599
|
+
// this member takes it on receipt without signing, so every member holds what the group completed before the
|
|
600
|
+
// counterparty can answer in its thread. Earlier copies go first so that their signatures join this one's; a
|
|
601
|
+
// message already accepted here only gains this copy's. What cannot be taken yet (an answer whose prior has
|
|
602
|
+
// not arrived) the group IPEX owner takes on a later pass.
|
|
603
|
+
yield* this.processApproved(this.exchanger.complete(embeddedSaid) ? nests : this.receivedCopies(nests, existing).reverse());
|
|
604
|
+
}
|
|
591
605
|
else if (serder.pvrsn.major === 2 || firstSubmission) {
|
|
592
606
|
// Legacy approval polling consumes its first notice; repeated v1 notices would re-propose it.
|
|
593
607
|
this.notifier?.add(firstSubmission ? { r: route, d: said } : { r: route, d: said, e: embeddedSaid });
|
|
@@ -660,6 +674,14 @@ export class Multiplexor {
|
|
|
660
674
|
return event.ilk === "ixn"
|
|
661
675
|
&& kever.tholder.satisfy(Kever.verifyIndexedSignatures(event.raw, envelope.sigers, kever.verfers).indices);
|
|
662
676
|
}
|
|
677
|
+
/** This wrapper's child first, then the children of the accepted wrappers already indexed for the same message. */
|
|
678
|
+
receivedCopies(nests, existing) {
|
|
679
|
+
const said = nests[0]?.body.said;
|
|
680
|
+
return [
|
|
681
|
+
...nests.slice(0, 1),
|
|
682
|
+
...existing.map((prior) => loadNests(this.hby.db, prior.qb64)[0]).filter((copy) => !!said && copy?.body.said === said),
|
|
683
|
+
];
|
|
684
|
+
}
|
|
663
685
|
/** Whether a rotation proposal names exactly the group's current signing members and rotation members. */
|
|
664
686
|
namesCurrentMembers(attrs) {
|
|
665
687
|
const group = typeof attrs.gid === "string" ? attrs.gid : "";
|
|
@@ -925,6 +947,32 @@ export class Multiplexor {
|
|
|
925
947
|
return pre.length > 0 && this.hby.habs.has(pre);
|
|
926
948
|
}
|
|
927
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* Whether a member takes, without consent, the v2 group IPEX message that `copies` carry: the children of the stored
|
|
952
|
+
* `/multisig/exn` wrappers it received for that message, the first naming it. It does when the message belongs to a
|
|
953
|
+
* local group, the copies sign it to the group's threshold ({@link groupThresholdSigned}), the exchange it answers
|
|
954
|
+
* (if any) is accepted here, and it is not a group issuer's DH Grant, which the issuance owner follows instead.
|
|
955
|
+
* Accepting such a message adds no signature, so it is not consent; one below the threshold waits for approval.
|
|
956
|
+
*
|
|
957
|
+
* KERIpy's `Multiplexor.add` replays a child only after local approval, so a KERIpy member that did not sign holds
|
|
958
|
+
* only the proposal. keri-ts delivers to, and completes at, every member regardless of weight.
|
|
959
|
+
*/
|
|
960
|
+
export function groupIpexFollowable(hby, copies) {
|
|
961
|
+
const first = copies[0], embedded = first?.body;
|
|
962
|
+
if (!(embedded instanceof SerderKERI) || embedded.pvrsn.major !== 2 || !embedded.pre || !embedded.said
|
|
963
|
+
|| !embedded.route?.startsWith("/ipex/"))
|
|
964
|
+
return false;
|
|
965
|
+
const hab = hby.habs.get(embedded.pre);
|
|
966
|
+
if (!hab || !isLocalGroupHab(hby, hab))
|
|
967
|
+
return false;
|
|
968
|
+
if (embedded.route === IPEX_GRANT_ROUTE && isIssuanceOrigin(embedded.pre, first.nests?.[0]))
|
|
969
|
+
return false;
|
|
970
|
+
const prior = embedded.sad?.p;
|
|
971
|
+
if (typeof prior === "string" && prior.length > 0 && !hby.db.exns.get([prior]))
|
|
972
|
+
return false;
|
|
973
|
+
const proofs = copies.filter((copy) => copy.body.said === embedded.said).flatMap((copy) => envelopeFromFrame(copy).tsgs);
|
|
974
|
+
return groupThresholdSigned(hby.db, embedded, proofs);
|
|
975
|
+
}
|
|
928
976
|
/** Extract the embedded business EXN SAD from a `/multisig/exn` wrapper. */
|
|
929
977
|
export function embeddedBusinessExnSAD(serder) {
|
|
930
978
|
const embed = serder.ked ? multisigEmbeddedSection(serder.ked) : null;
|