keri-ts 2.1.0 → 2.2.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 +30 -2
- package/esm/keri/npm/src/acdc/authoring.js +195 -0
- package/esm/keri/npm/src/acdc/custody.js +164 -0
- package/esm/keri/npm/src/acdc/dhking.js +244 -32
- package/esm/keri/npm/src/acdc/regbasing.js +10 -0
- package/esm/keri/npm/src/acdc/salting.js +16 -1
- package/esm/keri/npm/src/acdc/scheming.js +93 -54
- package/esm/keri/npm/src/acdc/unblinding.js +118 -95
- package/esm/keri/npm/src/app/bootstrap/authenticating.js +4 -6
- package/esm/keri/npm/src/app/bootstrap/oobi-resolving.js +41 -0
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +4 -6
- package/esm/keri/npm/src/app/credentials/credential-revoking.js +1 -0
- package/esm/keri/npm/src/app/credentials/credential-workflows.js +2 -0
- package/esm/keri/npm/src/app/credentials/registry-settling.js +44 -0
- package/esm/keri/npm/src/app/credentials/root-replicating.js +68 -60
- package/esm/keri/npm/src/app/credentials/root-replicator.js +198 -0
- package/esm/keri/npm/src/app/credentials/tel-completion.js +36 -0
- package/esm/keri/npm/src/app/delivery/forwarding.js +16 -3
- package/esm/keri/npm/src/app/exchanges/exchanging.js +14 -4
- package/esm/keri/npm/src/app/identity/challenging.js +50 -0
- package/esm/keri/npm/src/app/identity/delegating.js +77 -77
- package/esm/keri/npm/src/app/identity/delegation-approval.js +333 -0
- package/esm/keri/npm/src/app/identity/event-completion.js +154 -0
- package/esm/keri/npm/src/app/identity/habbing.js +129 -6
- package/esm/keri/npm/src/app/identity/keeping.js +77 -4
- package/esm/keri/npm/src/app/ipex/group-ipex.js +327 -0
- package/esm/keri/npm/src/app/ipex/group-issuance.js +902 -0
- package/esm/keri/npm/src/app/ipex/ipex-admitting.js +120 -0
- package/esm/keri/npm/src/app/ipex/ipex-conversation.js +197 -0
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +591 -0
- package/esm/keri/npm/src/app/ipex/ipex-granting.js +93 -0
- package/esm/keri/npm/src/app/ipex/ipex-grouping.js +34 -11
- package/esm/keri/npm/src/app/ipex/issuance.js +888 -0
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +46 -0
- package/esm/keri/npm/src/app/ipex/presentation-files.js +200 -0
- package/esm/keri/npm/src/app/ipex/presenting.js +401 -0
- package/esm/keri/npm/src/app/ipex/receive-issuance.js +86 -0
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +660 -0
- package/esm/keri/npm/src/app/mailbox/ipex-arrival.js +103 -0
- package/esm/keri/npm/src/app/mailbox/kel-publisher.js +322 -0
- package/esm/keri/npm/src/app/mailbox/kel-publishing.js +65 -0
- package/esm/keri/npm/src/app/mailbox/kel-submission.js +24 -0
- package/esm/keri/npm/src/app/mailbox/mailbox-admin.js +208 -0
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +212 -47
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +10 -5
- package/esm/keri/npm/src/app/multisig/counseling.js +482 -0
- package/esm/keri/npm/src/app/multisig/group-members.js +18 -0
- package/esm/keri/npm/src/app/multisig/multisig-workflows.js +94 -139
- package/esm/keri/npm/src/app/multisig/proposals.js +281 -0
- package/esm/keri/npm/src/app/multisig/proposing.js +122 -0
- package/esm/keri/npm/src/app/notifications/signaling.js +5 -5
- package/esm/keri/npm/src/app/protocol/parsering.js +26 -1
- package/esm/keri/npm/src/app/protocol/protocol-processing.js +9 -3
- package/esm/keri/npm/src/app/queries/query-workflows.js +98 -0
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +106 -55
- package/esm/keri/npm/src/app/runtime/cue-runtime.js +3 -6
- package/esm/keri/npm/src/app/runtime/delivery-outbox.js +87 -0
- package/esm/keri/npm/src/app/runtime/doing.js +17 -0
- package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +207 -0
- package/esm/keri/npm/src/app/runtime/operation-owners.js +151 -0
- package/esm/keri/npm/src/app/runtime/retry-backoff.js +93 -0
- package/esm/keri/npm/src/app/runtime/workflow-contract.js +80 -0
- package/esm/keri/npm/src/app/transport/http-client.js +25 -6
- package/esm/keri/npm/src/app/witness/http-endpoints.js +4 -4
- package/esm/keri/npm/src/cli/common/event-completion.js +49 -0
- package/esm/keri/npm/src/cli/common/runtime-workers.js +23 -0
- package/esm/keri/npm/src/cli/credentials/ipex.js +157 -190
- package/esm/keri/npm/src/cli/credentials/vc.js +53 -72
- package/esm/keri/npm/src/cli/endpoints/did.js +12 -3
- package/esm/keri/npm/src/cli/endpoints/oobi.js +10 -13
- package/esm/keri/npm/src/cli/identity/challenge.js +8 -34
- package/esm/keri/npm/src/cli/identity/delegate.js +81 -258
- package/esm/keri/npm/src/cli/identity/incept.js +15 -32
- package/esm/keri/npm/src/cli/identity/interact.js +5 -17
- package/esm/keri/npm/src/cli/identity/rotate.js +8 -36
- package/esm/keri/npm/src/cli/index.js +3 -1
- package/esm/keri/npm/src/cli/messaging/group-join.js +79 -0
- package/esm/keri/npm/src/cli/messaging/group-wait.js +88 -0
- package/esm/keri/npm/src/cli/messaging/multisig.js +163 -284
- package/esm/keri/npm/src/cli/messaging/root-copy-lines.js +31 -0
- package/esm/keri/npm/src/core/kel/eventing.js +66 -10
- package/esm/keri/npm/src/core/mailbox/kram-policy.js +39 -6
- package/esm/keri/npm/src/core/mailbox/kraming.js +2 -0
- package/esm/keri/npm/src/core/records.js +23 -0
- package/esm/keri/npm/src/core/routing.js +13 -0
- package/esm/keri/npm/src/db/basing.js +34 -2
- package/esm/keri/npm/src/db/core/indexeddb-persistence.js +221 -0
- package/esm/keri/npm/src/db/indexeddb.js +98 -0
- package/esm/keri/npm/src/db/keeping.js +11 -2
- package/esm/keri/npm/src/db/store-lock.js +150 -0
- package/esm/keri/npm/src/npm/db-indexeddb.browser.js +2 -0
- package/esm/keri/npm/src/npm/db-indexeddb.js +3 -0
- package/esm/keri/npm/src/npm/db.browser.js +2 -0
- package/esm/keri/npm/src/npm/index.browser.js +9 -0
- package/esm/keri/npm/src/npm/runtime-web.browser.js +2 -0
- package/esm/keri/npm/src/npm/runtime-web.js +3 -0
- package/esm/keri/npm/src/npm/runtime.browser.js +2 -0
- package/esm/keri/npm/src/runtime/index.js +51 -4
- package/esm/keri/npm/src/runtime/web/output-barrier.js +47 -0
- package/esm/keri/npm/src/runtime/web/persistent-storage.js +37 -0
- package/esm/keri/npm/src/runtime/web.js +10 -0
- package/esm/keri/npm/src/vdr/tel-publication.js +16 -0
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +15 -2
- package/types/acdc/authoring.d.ts +54 -0
- package/types/acdc/authoring.d.ts.map +1 -0
- package/types/acdc/custody.d.ts +77 -0
- package/types/acdc/custody.d.ts.map +1 -0
- package/types/acdc/dhking.d.ts +94 -3
- package/types/acdc/dhking.d.ts.map +1 -1
- package/types/acdc/regbasing.d.ts +4 -0
- package/types/acdc/regbasing.d.ts.map +1 -1
- package/types/acdc/salting.d.ts +6 -1
- package/types/acdc/salting.d.ts.map +1 -1
- package/types/acdc/scheming.d.ts +2 -0
- package/types/acdc/scheming.d.ts.map +1 -1
- package/types/acdc/unblinding.d.ts +58 -19
- package/types/acdc/unblinding.d.ts.map +1 -1
- package/types/app/bootstrap/authenticating.d.ts +3 -1
- package/types/app/bootstrap/authenticating.d.ts.map +1 -1
- package/types/app/bootstrap/oobi-resolving.d.ts +92 -0
- package/types/app/bootstrap/oobi-resolving.d.ts.map +1 -0
- package/types/app/bootstrap/oobiery.d.ts +3 -1
- package/types/app/bootstrap/oobiery.d.ts.map +1 -1
- package/types/app/credentials/credential-revoking.d.ts +3 -0
- package/types/app/credentials/credential-revoking.d.ts.map +1 -1
- package/types/app/credentials/credential-workflows.d.ts +5 -0
- package/types/app/credentials/credential-workflows.d.ts.map +1 -1
- package/types/app/credentials/registry-settling.d.ts +85 -0
- package/types/app/credentials/registry-settling.d.ts.map +1 -0
- package/types/app/credentials/root-replicating.d.ts +56 -44
- package/types/app/credentials/root-replicating.d.ts.map +1 -1
- package/types/app/credentials/root-replicator.d.ts +105 -0
- package/types/app/credentials/root-replicator.d.ts.map +1 -0
- package/types/app/credentials/tel-completion.d.ts +55 -0
- package/types/app/credentials/tel-completion.d.ts.map +1 -0
- package/types/app/delivery/forwarding.d.ts +6 -0
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/exchanges/exchanging.d.ts.map +1 -1
- package/types/app/identity/challenging.d.ts +54 -0
- package/types/app/identity/challenging.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts +21 -4
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/delegation-approval.d.ts +228 -0
- package/types/app/identity/delegation-approval.d.ts.map +1 -0
- package/types/app/identity/event-completion.d.ts +151 -0
- package/types/app/identity/event-completion.d.ts.map +1 -0
- package/types/app/identity/habbing.d.ts +67 -2
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +49 -3
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/ipex/group-ipex.d.ts +247 -0
- package/types/app/ipex/group-ipex.d.ts.map +1 -0
- package/types/app/ipex/group-issuance.d.ts +281 -0
- package/types/app/ipex/group-issuance.d.ts.map +1 -0
- package/types/app/ipex/ipex-admitting.d.ts +47 -0
- package/types/app/ipex/ipex-admitting.d.ts.map +1 -0
- package/types/app/ipex/ipex-conversation.d.ts +195 -0
- package/types/app/ipex/ipex-conversation.d.ts.map +1 -0
- package/types/app/ipex/ipex-discloser.d.ts +156 -0
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -0
- package/types/app/ipex/ipex-granting.d.ts +145 -0
- package/types/app/ipex/ipex-granting.d.ts.map +1 -0
- package/types/app/ipex/ipex-grouping.d.ts +18 -0
- package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +443 -0
- package/types/app/ipex/issuance.d.ts.map +1 -0
- package/types/app/ipex/pending-presentations.d.ts +25 -0
- package/types/app/ipex/pending-presentations.d.ts.map +1 -0
- package/types/app/ipex/presentation-files.d.ts +47 -0
- package/types/app/ipex/presentation-files.d.ts.map +1 -0
- package/types/app/ipex/presenting.d.ts +298 -0
- package/types/app/ipex/presenting.d.ts.map +1 -0
- package/types/app/ipex/receive-issuance.d.ts +99 -0
- package/types/app/ipex/receive-issuance.d.ts.map +1 -0
- package/types/app/ipex/unblinding-workflow.d.ts +255 -0
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -0
- package/types/app/mailbox/ipex-arrival.d.ts +104 -0
- package/types/app/mailbox/ipex-arrival.d.ts.map +1 -0
- package/types/app/mailbox/kel-publisher.d.ts +141 -0
- package/types/app/mailbox/kel-publisher.d.ts.map +1 -0
- package/types/app/mailbox/kel-publishing.d.ts +70 -0
- package/types/app/mailbox/kel-publishing.d.ts.map +1 -0
- package/types/app/mailbox/kel-submission.d.ts +18 -0
- package/types/app/mailbox/kel-submission.d.ts.map +1 -0
- package/types/app/mailbox/mailbox-admin.d.ts +113 -0
- package/types/app/mailbox/mailbox-admin.d.ts.map +1 -0
- package/types/app/mailbox/mailbox-poller.d.ts +92 -3
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +3 -0
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +196 -0
- package/types/app/multisig/counseling.d.ts.map +1 -0
- package/types/app/multisig/group-members.d.ts +11 -0
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/grouping.d.ts +39 -20
- package/types/app/multisig/grouping.d.ts.map +1 -1
- package/types/app/multisig/multisig-workflows.d.ts +91 -22
- package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
- package/types/app/multisig/proposals.d.ts +129 -0
- package/types/app/multisig/proposals.d.ts.map +1 -0
- package/types/app/multisig/proposing.d.ts +125 -0
- package/types/app/multisig/proposing.d.ts.map +1 -0
- package/types/app/notifications/signaling.d.ts +3 -1
- package/types/app/notifications/signaling.d.ts.map +1 -1
- package/types/app/protocol/parsering.d.ts +13 -0
- package/types/app/protocol/parsering.d.ts.map +1 -1
- package/types/app/protocol/protocol-processing.d.ts +7 -1
- package/types/app/protocol/protocol-processing.d.ts.map +1 -1
- package/types/app/queries/query-workflows.d.ts +98 -0
- package/types/app/queries/query-workflows.d.ts.map +1 -0
- package/types/app/runtime/agent-runtime.d.ts +72 -25
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/cue-runtime.d.ts +2 -1
- package/types/app/runtime/cue-runtime.d.ts.map +1 -1
- package/types/app/runtime/delivery-outbox.d.ts +68 -0
- package/types/app/runtime/delivery-outbox.d.ts.map +1 -0
- package/types/app/runtime/doing.d.ts +27 -0
- package/types/app/runtime/doing.d.ts.map +1 -0
- package/types/app/runtime/ipex-auxiliaries.d.ts +74 -0
- package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -0
- package/types/app/runtime/operation-owners.d.ts +137 -0
- package/types/app/runtime/operation-owners.d.ts.map +1 -0
- package/types/app/runtime/retry-backoff.d.ts +49 -0
- package/types/app/runtime/retry-backoff.d.ts.map +1 -0
- package/types/app/runtime/workflow-contract.d.ts +79 -0
- package/types/app/runtime/workflow-contract.d.ts.map +1 -0
- package/types/app/transport/http-client.d.ts.map +1 -1
- package/types/cli/common/event-completion.d.ts +26 -0
- package/types/cli/common/event-completion.d.ts.map +1 -0
- package/types/cli/common/runtime-workers.d.ts +23 -0
- package/types/cli/common/runtime-workers.d.ts.map +1 -0
- package/types/cli/credentials/ipex.d.ts.map +1 -1
- package/types/cli/credentials/vc.d.ts +1 -1
- package/types/cli/credentials/vc.d.ts.map +1 -1
- package/types/cli/endpoints/did.d.ts.map +1 -1
- package/types/cli/endpoints/oobi.d.ts +3 -1
- package/types/cli/endpoints/oobi.d.ts.map +1 -1
- package/types/cli/identity/challenge.d.ts +1 -1
- package/types/cli/identity/challenge.d.ts.map +1 -1
- package/types/cli/identity/delegate.d.ts +14 -1
- package/types/cli/identity/delegate.d.ts.map +1 -1
- package/types/cli/identity/incept.d.ts.map +1 -1
- package/types/cli/identity/interact.d.ts.map +1 -1
- package/types/cli/identity/rotate.d.ts.map +1 -1
- package/types/cli/index.d.ts +3 -1
- package/types/cli/index.d.ts.map +1 -1
- package/types/cli/messaging/group-join.d.ts +53 -0
- package/types/cli/messaging/group-join.d.ts.map +1 -0
- package/types/cli/messaging/group-wait.d.ts +34 -0
- package/types/cli/messaging/group-wait.d.ts.map +1 -0
- package/types/cli/messaging/multisig.d.ts +28 -5
- package/types/cli/messaging/multisig.d.ts.map +1 -1
- package/types/cli/messaging/root-copy-lines.d.ts +39 -0
- package/types/cli/messaging/root-copy-lines.d.ts.map +1 -0
- package/types/core/cues.d.ts +11 -0
- package/types/core/cues.d.ts.map +1 -1
- package/types/core/kel/eventing.d.ts.map +1 -1
- package/types/core/mailbox/kram-policy.d.ts +14 -0
- package/types/core/mailbox/kram-policy.d.ts.map +1 -1
- package/types/core/mailbox/kraming.d.ts +1 -1
- package/types/core/mailbox/kraming.d.ts.map +1 -1
- package/types/core/records.d.ts +61 -0
- package/types/core/records.d.ts.map +1 -1
- package/types/core/routing.d.ts +6 -0
- package/types/core/routing.d.ts.map +1 -1
- package/types/db/basing.d.ts +8 -2
- package/types/db/basing.d.ts.map +1 -1
- package/types/db/core/indexeddb-persistence.d.ts +59 -0
- package/types/db/core/indexeddb-persistence.d.ts.map +1 -0
- package/types/db/indexeddb.d.ts +37 -0
- package/types/db/indexeddb.d.ts.map +1 -0
- package/types/db/keeping.d.ts +5 -2
- package/types/db/keeping.d.ts.map +1 -1
- package/types/db/store-lock.d.ts +72 -0
- package/types/db/store-lock.d.ts.map +1 -0
- package/types/npm/db-indexeddb.d.ts +4 -0
- package/types/npm/db-indexeddb.d.ts.map +1 -0
- package/types/npm/runtime-web.d.ts +4 -0
- package/types/npm/runtime-web.d.ts.map +1 -0
- package/types/runtime/index.d.ts +41 -6
- package/types/runtime/index.d.ts.map +1 -1
- package/types/runtime/web/output-barrier.d.ts +30 -0
- package/types/runtime/web/output-barrier.d.ts.map +1 -0
- package/types/runtime/web/persistent-storage.d.ts +36 -0
- package/types/runtime/web/persistent-storage.d.ts.map +1 -0
- package/types/runtime/web.d.ts +11 -0
- package/types/runtime/web.d.ts.map +1 -0
- package/types/vdr/tel-publication.d.ts +6 -0
- package/types/vdr/tel-publication.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
- package/esm/keri/npm/src/cli/common/tel-completion.js +0 -24
- package/types/cli/common/tel-completion.d.ts +0 -8
- package/types/cli/common/tel-completion.d.ts.map +0 -1
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Finite delivery of prepared `/registry/root` copies; transmission is never receipt.
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
6
|
-
* is no acknowledgement
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* `replicateRootCopies` is the KERIpy `rootreplicating.replicate` happy path: a
|
|
5
|
+
* command calls it once a send is possible and reports each typed outcome. There
|
|
6
|
+
* is no acknowledgement or retry here (the runtime's `RootReplicator` derives
|
|
7
|
+
* what is owed and retries); receiving is idempotent, so duplicate copies are
|
|
8
|
+
* harmless.
|
|
9
9
|
*/
|
|
10
10
|
import { parseBytes } from "cesr-ts";
|
|
11
11
|
import { action, race } from "effection";
|
|
12
|
+
import { RootUnverified } from "../../acdc/salting.js";
|
|
12
13
|
import { AppError, ValidationError } from "../../core/errors.js";
|
|
13
14
|
import { CREDENTIAL_MAILBOX_TOPIC } from "../../core/mailbox/mailbox-topics.js";
|
|
14
15
|
import { localGroupMember } from "../multisig/group-members.js";
|
|
15
|
-
/** The operator command that resends a copy automatic replication did not send. */
|
|
16
|
-
export const RootCopyRecovery = "tufa vc registry root send";
|
|
17
16
|
/**
|
|
18
17
|
* Send each copy from `sender` to its recipient's `credential` mailbox, in
|
|
19
18
|
* order, and return one outcome per copy. A failed copy does not stop later
|
|
@@ -42,71 +41,79 @@ function* within(clock, milliseconds, operation) {
|
|
|
42
41
|
]);
|
|
43
42
|
}
|
|
44
43
|
/**
|
|
45
|
-
*
|
|
46
|
-
* `
|
|
44
|
+
* Prepare and deliver one `/registry/root` copy for `gid` from its local member to
|
|
45
|
+
* `recipient`, before `deadline` (epoch ms of `clock`).
|
|
47
46
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* does. `timeoutMs` is this replication's own budget: a copy reached after it
|
|
53
|
-
* ends, or still sending when it ends, is reported unsent. Nothing is retried
|
|
54
|
-
* or recorded and an `AppError` never escapes: every outcome is returned for
|
|
55
|
-
* display. The caller runs `postman.deliverDo` for the duration.
|
|
47
|
+
* The copy is sealed with `RegistryRoot.prepare` and a per-registry derived hint
|
|
48
|
+
* (`hinted`, so a copy that could never verify is reported, not sent). Any
|
|
49
|
+
* `AppError` ends in the result; any other exception is an invariant failure and
|
|
50
|
+
* escapes. The caller runs `postman.deliverDo` for the duration.
|
|
56
51
|
*/
|
|
57
|
-
export function*
|
|
52
|
+
export function* replicateRootCopy(root, postman, clock, gid, registry, recipient, deadline) {
|
|
53
|
+
const sender = localGroupMember(root.hby, gid);
|
|
54
|
+
const result = { kin: "failed", group: gid, registry, sender: sender.pre, recipient };
|
|
55
|
+
try {
|
|
56
|
+
if (clock.now() >= deadline) {
|
|
57
|
+
throw new ValidationError("registry root copies did not complete within their budget");
|
|
58
|
+
}
|
|
59
|
+
let wire;
|
|
60
|
+
try {
|
|
61
|
+
wire = root.prepare(sender, recipient, gid, registry, { hinted: true });
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
result.kin = error instanceof RootUnverified ? "deferred" : "refused";
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
result.exchange = parseBytes(wire)[0].body.said;
|
|
68
|
+
const outcome = yield* within(clock, deadline - clock.now(), deliverRegistryRoots(postman, sender.pre, [{ recipient, wire }]));
|
|
69
|
+
if (!outcome)
|
|
70
|
+
throw new ValidationError("registry root copies did not complete within their budget");
|
|
71
|
+
if (outcome[0].kin === "failed") {
|
|
72
|
+
result.error = outcome[0].error ?? "delivery failed";
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
result.kin = "sent";
|
|
76
|
+
result.deliveries = outcome[0].deliveries;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (!(error instanceof AppError))
|
|
81
|
+
throw error;
|
|
82
|
+
result.error = error;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Send one copy of each of `registries`' held roots for group `ghab` to each
|
|
88
|
+
* recipient, signed by `ghab`'s local member, and return one result per copy.
|
|
89
|
+
*
|
|
90
|
+
* `recipients` defaults to every other current signing member. `timeoutMs` is this
|
|
91
|
+
* replication's own budget: a copy reached after it ends, or still sending when it
|
|
92
|
+
* ends, is `failed`. Nothing is retried or recorded and an `AppError` never
|
|
93
|
+
* escapes. The caller runs `postman.deliverDo` for the duration.
|
|
94
|
+
*/
|
|
95
|
+
export function* replicateRootCopies(root, postman, clock, ghab, registries, { recipients, timeoutMs }) {
|
|
58
96
|
const gid = ghab.pre, sender = localGroupMember(root.hby, gid), deadline = clock.now() + Math.max(timeoutMs, 0);
|
|
59
97
|
const targets = recipients ?? root.members(gid).filter(aid => aid !== sender.pre);
|
|
60
|
-
const
|
|
98
|
+
const results = [];
|
|
61
99
|
for (const registry of registries) {
|
|
62
100
|
for (const recipient of targets) {
|
|
63
|
-
|
|
64
|
-
phase: "root_copy",
|
|
65
|
-
gid,
|
|
66
|
-
registry,
|
|
67
|
-
sender: sender.pre,
|
|
68
|
-
recipient,
|
|
69
|
-
transmitted: false,
|
|
70
|
-
};
|
|
71
|
-
let failure = null;
|
|
72
|
-
try {
|
|
73
|
-
if (clock.now() >= deadline) {
|
|
74
|
-
throw new ValidationError("registry root copies did not complete within their budget");
|
|
75
|
-
}
|
|
76
|
-
const wire = root.prepare(sender, recipient, gid, registry, { hinted: true });
|
|
77
|
-
result.exchange = parseBytes(wire)[0].body.said;
|
|
78
|
-
const outcome = yield* within(clock, deadline - clock.now(), deliverRegistryRoots(postman, sender.pre, [{ recipient, wire }]));
|
|
79
|
-
if (!outcome)
|
|
80
|
-
failure = new ValidationError("registry root copies did not complete within their budget");
|
|
81
|
-
else if (outcome[0].kin === "failed")
|
|
82
|
-
failure = outcome[0].error ?? "delivery failed";
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
if (!(error instanceof AppError))
|
|
86
|
-
throw error;
|
|
87
|
-
failure = error;
|
|
88
|
-
}
|
|
89
|
-
reports.push(failure === null ? { ...result, transmitted: true } : {
|
|
90
|
-
...result,
|
|
91
|
-
error: failure instanceof Error ? failure.message : String(failure),
|
|
92
|
-
recovery: RootCopyRecovery,
|
|
93
|
-
});
|
|
101
|
+
results.push(yield* replicateRootCopy(root, postman, clock, gid, registry, recipient, deadline));
|
|
94
102
|
}
|
|
95
103
|
}
|
|
96
|
-
return
|
|
104
|
+
return results;
|
|
97
105
|
}
|
|
98
106
|
/** Budget for re-seal copies from a command without its own `--timeout`, as KERIpy `rootreplicating.Budget`. */
|
|
99
107
|
export const RootResealBudgetMs = 30_000;
|
|
100
108
|
/**
|
|
101
109
|
* After an accepted group rotation, send held roots to new or rekeyed members.
|
|
102
110
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* sends
|
|
106
|
-
*
|
|
107
|
-
* `postman.deliverDo` for the duration.
|
|
111
|
+
* Only a signer of the rotation that held a prior key sends (`RegistryRoot.resealed`),
|
|
112
|
+
* every such signer sends with no election, and a member holding no root for the
|
|
113
|
+
* group sends nothing. `examination` is set when the recipients could not be
|
|
114
|
+
* selected, and then nothing is sent. Fire and forget like `replicateRootCopies`.
|
|
108
115
|
*/
|
|
109
|
-
export function*
|
|
116
|
+
export function* resealRootCopies(root, postman, clock, ghab, rotation, { timeoutMs = RootResealBudgetMs } = {}) {
|
|
110
117
|
let recipients;
|
|
111
118
|
try {
|
|
112
119
|
recipients = root.resealed(ghab, rotation);
|
|
@@ -114,10 +121,11 @@ export function* resealRegistryRoots(root, postman, clock, ghab, rotation, { tim
|
|
|
114
121
|
catch (error) {
|
|
115
122
|
if (!(error instanceof AppError))
|
|
116
123
|
throw error;
|
|
117
|
-
return
|
|
124
|
+
return { examination: { group: ghab.pre, rotation, error }, copies: [] };
|
|
118
125
|
}
|
|
119
126
|
const registries = root.held(ghab.pre);
|
|
120
127
|
if (!recipients.length || !registries.length)
|
|
121
|
-
return [];
|
|
122
|
-
|
|
128
|
+
return { examination: null, copies: [] };
|
|
129
|
+
const copies = yield* replicateRootCopies(root, postman, clock, ghab, registries, { recipients, timeoutMs });
|
|
130
|
+
return { examination: null, copies };
|
|
123
131
|
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _RootReplicator_instances, _RootReplicator_deps, _RootReplicator_pending, _RootReplicator_inflight, _RootReplicator_seen, _RootReplicator_dirty, _RootReplicator_pruned, _RootReplicator_attempt, _RootReplicator_refreshDirty, _RootReplicator_derive;
|
|
13
|
+
import { isLocalGroupHab, localGroupMember } from "../multisig/group-members.js";
|
|
14
|
+
import { replicateRootCopy, RootResealBudgetMs } from "./root-replicating.js";
|
|
15
|
+
/** Most copies one `processOnce` attempts, so a first pass over a large store cannot stall the runtime. */
|
|
16
|
+
export const RootSendsPerPass = 4;
|
|
17
|
+
/** Process-local retry delay after a copy's first failed attempt; doubles per attempt up to {@link RootRetryMaxMs}. */
|
|
18
|
+
export const RootRetryBaseMs = 1_000;
|
|
19
|
+
export const RootRetryMaxMs = 300_000;
|
|
20
|
+
const idOf = (group, registry, recipient) => [group, registry, recipient].join("/");
|
|
21
|
+
/**
|
|
22
|
+
* Derives, sends and records registry root copies for a wallet's local groups.
|
|
23
|
+
*
|
|
24
|
+
* Holds no durable state of its own beyond `Baser.rrsn`; what is pending and the
|
|
25
|
+
* backoff are process-local and rebuilt from state. A host calls `prompt` (or
|
|
26
|
+
* `replicate`) for each Dhke cue and `processOnce` on its loop; both are safe to repeat,
|
|
27
|
+
* and overlapping `processOnce` calls never send one copy twice.
|
|
28
|
+
*/
|
|
29
|
+
export class RootReplicator {
|
|
30
|
+
constructor(deps) {
|
|
31
|
+
_RootReplicator_instances.add(this);
|
|
32
|
+
_RootReplicator_deps.set(this, void 0);
|
|
33
|
+
_RootReplicator_pending.set(this, new Map());
|
|
34
|
+
_RootReplicator_inflight.set(this, new Set());
|
|
35
|
+
_RootReplicator_seen.set(this, new Map());
|
|
36
|
+
_RootReplicator_dirty.set(this, new Set());
|
|
37
|
+
_RootReplicator_pruned.set(this, false);
|
|
38
|
+
__classPrivateFieldSet(this, _RootReplicator_deps, deps, "f");
|
|
39
|
+
}
|
|
40
|
+
/** Re-derive `group`'s owed copies on the next pass and retry its failed copies at once. */
|
|
41
|
+
prompt(group) {
|
|
42
|
+
__classPrivateFieldGet(this, _RootReplicator_dirty, "f").add(group);
|
|
43
|
+
for (const entry of __classPrivateFieldGet(this, _RootReplicator_pending, "f").values())
|
|
44
|
+
if (entry.group === group)
|
|
45
|
+
entry.retryAt = 0;
|
|
46
|
+
}
|
|
47
|
+
/** The Dhke completion cue: a prompt to run the pass now, not the source of truth. */
|
|
48
|
+
replicate(cue) {
|
|
49
|
+
this.prompt(cue.gid);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Derive what is owed, then send up to {@link RootSendsPerPass} due copies within
|
|
53
|
+
* `timeoutMs`. A copy is recorded when sent and kept for retry otherwise. Never throws
|
|
54
|
+
* on a remote failure; every attempt is returned. The host runs `postman.deliverDo`.
|
|
55
|
+
*/
|
|
56
|
+
*processOnce({ timeoutMs = RootResealBudgetMs } = {}) {
|
|
57
|
+
this.sweep();
|
|
58
|
+
return yield* this.processDue({ timeoutMs });
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The fixed-tock pass: re-derive only the groups that were prompted, then send up to {@link RootSendsPerPass}
|
|
62
|
+
* due copies. A rotation nobody prompted for waits for {@link RootReplicator.sweep}.
|
|
63
|
+
*/
|
|
64
|
+
*processDue({ timeoutMs = RootResealBudgetMs } = {}) {
|
|
65
|
+
const { clock } = __classPrivateFieldGet(this, _RootReplicator_deps, "f"), deadline = clock.now() + Math.max(timeoutMs, 0);
|
|
66
|
+
__classPrivateFieldGet(this, _RootReplicator_instances, "m", _RootReplicator_refreshDirty).call(this);
|
|
67
|
+
const results = [];
|
|
68
|
+
for (const entry of [...__classPrivateFieldGet(this, _RootReplicator_pending, "f").values()]) {
|
|
69
|
+
if (results.length >= RootSendsPerPass)
|
|
70
|
+
break;
|
|
71
|
+
if (entry.retryAt > clock.now())
|
|
72
|
+
continue;
|
|
73
|
+
const result = yield* __classPrivateFieldGet(this, _RootReplicator_instances, "m", _RootReplicator_attempt).call(this, entry.group, entry.registry, entry.recipient, deadline);
|
|
74
|
+
if (!result)
|
|
75
|
+
continue;
|
|
76
|
+
results.push(result);
|
|
77
|
+
if (result.kin === "sent") {
|
|
78
|
+
__classPrivateFieldGet(this, _RootReplicator_pending, "f").delete(idOf(entry.group, entry.registry, entry.recipient));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
entry.attempts += 1;
|
|
82
|
+
entry.lastFailure = result.error;
|
|
83
|
+
entry.retryAt = clock.now() + Math.min(RootRetryBaseMs * 2 ** (entry.attempts - 1), RootRetryMaxMs);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Send each registry's root to each recipient now, whatever was sent before, as an
|
|
90
|
+
* explicit command does; a sent copy is recorded like any other. `recipients` defaults
|
|
91
|
+
* to every other current signing member.
|
|
92
|
+
*/
|
|
93
|
+
*send(ghab, registries, { recipients, timeoutMs }) {
|
|
94
|
+
const { root, clock } = __classPrivateFieldGet(this, _RootReplicator_deps, "f"), gid = ghab.pre, deadline = clock.now() + Math.max(timeoutMs, 0);
|
|
95
|
+
const sender = localGroupMember(root.hby, gid).pre;
|
|
96
|
+
const targets = recipients ?? root.members(gid).filter(aid => aid !== sender);
|
|
97
|
+
const results = [];
|
|
98
|
+
for (const registry of registries) {
|
|
99
|
+
for (const recipient of targets) {
|
|
100
|
+
const result = yield* __classPrivateFieldGet(this, _RootReplicator_instances, "m", _RootReplicator_attempt).call(this, gid, registry, recipient, deadline);
|
|
101
|
+
if (result)
|
|
102
|
+
results.push(result);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
107
|
+
/** Whether a copy is owed or a group is waiting to be re-derived; in memory, reads no store. */
|
|
108
|
+
pending() {
|
|
109
|
+
return __classPrivateFieldGet(this, _RootReplicator_pending, "f").size > 0 || __classPrivateFieldGet(this, _RootReplicator_dirty, "f").size > 0;
|
|
110
|
+
}
|
|
111
|
+
/** Pure read: what is owed and why it has not gone yet. */
|
|
112
|
+
status() {
|
|
113
|
+
return {
|
|
114
|
+
owed: [...__classPrivateFieldGet(this, _RootReplicator_pending, "f").values()].map(({ retryAt: _retryAt, ...entry }) => ({ ...entry })),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The full check: drop markers of groups that are gone (once) and re-derive each local group that is new or
|
|
119
|
+
* whose establishment event changed. One keyed read per local identifier, so a host runs it every
|
|
120
|
+
* many tocks, not every tock.
|
|
121
|
+
*/
|
|
122
|
+
sweep() {
|
|
123
|
+
const { root } = __classPrivateFieldGet(this, _RootReplicator_deps, "f"), hby = root.hby;
|
|
124
|
+
if (!__classPrivateFieldGet(this, _RootReplicator_pruned, "f")) {
|
|
125
|
+
__classPrivateFieldSet(this, _RootReplicator_pruned, true, "f");
|
|
126
|
+
for (const [[group]] of [...hby.db.rrsn.getTopItemIter()]) {
|
|
127
|
+
if (!hby.db.getHab(group)?.mid)
|
|
128
|
+
hby.db.rrsn.trim(group, { topive: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const hab of hby.habs.values()) {
|
|
132
|
+
const established = hab.pre && isLocalGroupHab(hby, hab) ? hab.kever?.lastEst.d : null;
|
|
133
|
+
if (!established || (__classPrivateFieldGet(this, _RootReplicator_seen, "f").get(hab.pre) === established && !__classPrivateFieldGet(this, _RootReplicator_dirty, "f").has(hab.pre)))
|
|
134
|
+
continue;
|
|
135
|
+
__classPrivateFieldGet(this, _RootReplicator_seen, "f").set(hab.pre, established);
|
|
136
|
+
__classPrivateFieldGet(this, _RootReplicator_dirty, "f").delete(hab.pre);
|
|
137
|
+
__classPrivateFieldGet(this, _RootReplicator_instances, "m", _RootReplicator_derive).call(this, hab.pre);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
_RootReplicator_deps = new WeakMap(), _RootReplicator_pending = new WeakMap(), _RootReplicator_inflight = new WeakMap(), _RootReplicator_seen = new WeakMap(), _RootReplicator_dirty = new WeakMap(), _RootReplicator_pruned = new WeakMap(), _RootReplicator_instances = new WeakSet(), _RootReplicator_attempt = function
|
|
142
|
+
/** Deliver one copy unless an overlapping call already is; record it when sent. */
|
|
143
|
+
* _RootReplicator_attempt(group, registry, recipient, deadline) {
|
|
144
|
+
const { root, postman, clock, changes } = __classPrivateFieldGet(this, _RootReplicator_deps, "f"), id = idOf(group, registry, recipient);
|
|
145
|
+
if (__classPrivateFieldGet(this, _RootReplicator_inflight, "f").has(id))
|
|
146
|
+
return null;
|
|
147
|
+
__classPrivateFieldGet(this, _RootReplicator_inflight, "f").add(id);
|
|
148
|
+
try {
|
|
149
|
+
const key = root.contributor(group, recipient);
|
|
150
|
+
const result = yield* replicateRootCopy(root, postman, clock, group, registry, recipient, deadline);
|
|
151
|
+
if (result.kin === "sent" && key) {
|
|
152
|
+
root.hby.db.rrsn.pin([group, registry, recipient], { key, dt: new Date(clock.now()).toISOString() });
|
|
153
|
+
}
|
|
154
|
+
changes?.push({ kind: "group", id: group });
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
__classPrivateFieldGet(this, _RootReplicator_inflight, "f").delete(id);
|
|
159
|
+
}
|
|
160
|
+
}, _RootReplicator_refreshDirty = function _RootReplicator_refreshDirty() {
|
|
161
|
+
const hby = __classPrivateFieldGet(this, _RootReplicator_deps, "f").root.hby;
|
|
162
|
+
for (const group of [...__classPrivateFieldGet(this, _RootReplicator_dirty, "f")]) {
|
|
163
|
+
const hab = hby.habs.get(group);
|
|
164
|
+
const established = hab && isLocalGroupHab(hby, hab) ? hab.kever?.lastEst.d : null;
|
|
165
|
+
__classPrivateFieldGet(this, _RootReplicator_dirty, "f").delete(group);
|
|
166
|
+
if (!established)
|
|
167
|
+
continue;
|
|
168
|
+
__classPrivateFieldGet(this, _RootReplicator_seen, "f").set(group, established);
|
|
169
|
+
__classPrivateFieldGet(this, _RootReplicator_instances, "m", _RootReplicator_derive).call(this, group);
|
|
170
|
+
}
|
|
171
|
+
}, _RootReplicator_derive = function _RootReplicator_derive(group) {
|
|
172
|
+
const { root, changes } = __classPrivateFieldGet(this, _RootReplicator_deps, "f"), db = root.hby.db;
|
|
173
|
+
const sender = localGroupMember(root.hby, group).pre, registries = root.held(group);
|
|
174
|
+
const members = root.members(group).filter(aid => aid !== sender);
|
|
175
|
+
const wanted = new Set();
|
|
176
|
+
for (const registry of registries) {
|
|
177
|
+
for (const recipient of members) {
|
|
178
|
+
const id = idOf(group, registry, recipient);
|
|
179
|
+
wanted.add(id);
|
|
180
|
+
if (db.rrsn.get([group, registry, recipient])?.key === root.contributor(group, recipient)) {
|
|
181
|
+
__classPrivateFieldGet(this, _RootReplicator_pending, "f").delete(id);
|
|
182
|
+
}
|
|
183
|
+
else if (!__classPrivateFieldGet(this, _RootReplicator_pending, "f").has(id)) {
|
|
184
|
+
__classPrivateFieldGet(this, _RootReplicator_pending, "f").set(id, { group, registry, recipient, attempts: 0, lastFailure: null, retryAt: 0 });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const [id, entry] of [...__classPrivateFieldGet(this, _RootReplicator_pending, "f")]) {
|
|
189
|
+
if (entry.group === group && !wanted.has(id))
|
|
190
|
+
__classPrivateFieldGet(this, _RootReplicator_pending, "f").delete(id);
|
|
191
|
+
}
|
|
192
|
+
for (const [[, registry, recipient]] of [...db.rrsn.getTopItemIter(group, { topive: true })]) {
|
|
193
|
+
if (!wanted.has(idOf(group, registry, recipient)))
|
|
194
|
+
db.rrsn.rem([group, registry, recipient]);
|
|
195
|
+
}
|
|
196
|
+
if (wanted.size)
|
|
197
|
+
changes?.push({ kind: "group", id: group });
|
|
198
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { systemClock } from "../runtime/runtime-clock.js";
|
|
2
|
+
import { observeUntil } from "../runtime/workflow-contract.js";
|
|
3
|
+
/** Elapsed milliseconds between reads of a TEL wait. */
|
|
4
|
+
export const TEL_WAIT_POLL_MS = 50;
|
|
5
|
+
/** Read the status of the TEL event `pre` at `sn`; mutates nothing. */
|
|
6
|
+
export function telEventStatus(deps, pre, sn = 0) {
|
|
7
|
+
const progress = deps.publication.progress(pre, sn);
|
|
8
|
+
if (progress === null)
|
|
9
|
+
return { state: "unknown", reason: "not-tracked" };
|
|
10
|
+
return progress === "complete" ? { state: "complete" } : { state: "active", phase: progress };
|
|
11
|
+
}
|
|
12
|
+
/** Status of an issued credential: its TEL issuance event plus saved credential evidence. */
|
|
13
|
+
export function issuedCredentialStatus(deps, said) {
|
|
14
|
+
const status = telEventStatus(deps, said, 0);
|
|
15
|
+
if (status.state === "complete" && deps.publication.reger.saved.get(said) === null) {
|
|
16
|
+
return { state: "active", phase: "awaitingCredentialSave" };
|
|
17
|
+
}
|
|
18
|
+
return status;
|
|
19
|
+
}
|
|
20
|
+
function* observe(read, { deadline, clock = systemClock }) {
|
|
21
|
+
const done = yield* observeUntil(() => read().state === "complete", {
|
|
22
|
+
timeoutMs: Math.max(0, deadline - clock.now()),
|
|
23
|
+
clock,
|
|
24
|
+
pollMs: TEL_WAIT_POLL_MS,
|
|
25
|
+
});
|
|
26
|
+
const status = read();
|
|
27
|
+
return done ? { outcome: "settled", status } : { outcome: "timed-out", status, deadline };
|
|
28
|
+
}
|
|
29
|
+
/** Observe one exact TEL coordinate until complete or the deadline passes. Starts no worker. */
|
|
30
|
+
export function* waitForTelEvent(deps, pre, sn, options) {
|
|
31
|
+
return yield* observe(() => telEventStatus(deps, pre, sn), options);
|
|
32
|
+
}
|
|
33
|
+
/** Observe an issuance until its TEL event is complete and the credential is saved. Starts no worker. */
|
|
34
|
+
export function* waitForIssuedCredential(deps, said, options) {
|
|
35
|
+
return yield* observe(() => issuedCredentialStatus(deps, said), options);
|
|
36
|
+
}
|
|
@@ -7,6 +7,7 @@ import { exchange } from "../../core/exchange/protocol-exchanging.js";
|
|
|
7
7
|
import { Roles } from "../../core/roles.js";
|
|
8
8
|
import { mailboxTopicKey, preferredUrl } from "../mailbox/mailboxing.js";
|
|
9
9
|
import { transportMailboxTopic } from "../mailbox/topic-routing.js";
|
|
10
|
+
import { localGroupMember } from "../multisig/group-members.js";
|
|
10
11
|
import { abortable } from "../runtime/abortable.js";
|
|
11
12
|
import { runtimeTurn } from "../runtime/runtime-turn.js";
|
|
12
13
|
import { CesrHttpRefusal } from "../transport/cesr-egress.js";
|
|
@@ -505,6 +506,15 @@ export class Poster {
|
|
|
505
506
|
};
|
|
506
507
|
});
|
|
507
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* Whether `src` has any eligible role (controller, agent, mailbox or witness) from which to reach `dest`, as
|
|
511
|
+
* delivery would find it now. A pure read of accepted endpoint state: it says nothing about whether the
|
|
512
|
+
* endpoint answers. A proposer uses it to refuse before it creates anything it could not publish.
|
|
513
|
+
*/
|
|
514
|
+
canReach(src, dest, topic = "multisig") {
|
|
515
|
+
const hab = this.hby.habs.get(src);
|
|
516
|
+
return !!hab && this.findBytesRoutes(hab, dest, topic).length > 0;
|
|
517
|
+
}
|
|
508
518
|
/**
|
|
509
519
|
* Prepare a target using the supplied wire and selected endpoint.
|
|
510
520
|
*
|
|
@@ -544,6 +554,9 @@ export class Poster {
|
|
|
544
554
|
throw new Error("Mailbox publication did not complete");
|
|
545
555
|
return { kind: "stored" };
|
|
546
556
|
}
|
|
557
|
+
// A group cannot sign the wrapper alone: one member's signature does not meet a threshold above one, and the
|
|
558
|
+
// host would escrow the wrapper. The local member wraps; the payload keeps whatever signatures it carries.
|
|
559
|
+
const signer = this.hby.db.groups.has(hab.pre) ? localGroupMember(this.hby, hab.pre) : hab;
|
|
547
560
|
const msgs = [];
|
|
548
561
|
if (evt.kin === "stream") {
|
|
549
562
|
// Prepare every frame before opening the one selected relay attempt.
|
|
@@ -555,13 +568,13 @@ export class Poster {
|
|
|
555
568
|
msgs.push(evt.msg);
|
|
556
569
|
}
|
|
557
570
|
// The provider reads the envelope, so it must read v2 whenever the envelope or child is v2.
|
|
558
|
-
if ((
|
|
571
|
+
if ((signer.kelVersion.major >= 2 || msgs.some(wireNeedsV2)) && knownV1Peer(this.hby, endpoint.eid)) {
|
|
559
572
|
throw new PeerVersionError(`Refusing a v2 envelope for provider ${endpoint.eid}, whose latest accepted event is v1.`);
|
|
560
573
|
}
|
|
561
574
|
const forwarded = [];
|
|
562
575
|
for (const message of msgs)
|
|
563
|
-
forwarded.push(yield* buildForwardedDelivery(
|
|
564
|
-
const intro = introduce(
|
|
576
|
+
forwarded.push(yield* buildForwardedDelivery(signer, { recipient, topic, message }));
|
|
577
|
+
const intro = introduce(signer, endpoint.eid);
|
|
565
578
|
let message = concatBytes(intro, ...forwarded);
|
|
566
579
|
if (evt.kin === "stream") {
|
|
567
580
|
const frames = parseBytes(message);
|
|
@@ -13,6 +13,7 @@ import { hasNests, loadNests, prepareNests, publishNests } from "../../db/exchan
|
|
|
13
13
|
import { encodeDateTimeToDater, makeNowIso8601, timestampMicros } from "../../time/mod.js";
|
|
14
14
|
import { groupMemberHasKey } from "../multisig/group-members.js";
|
|
15
15
|
import { abortable } from "../runtime/abortable.js";
|
|
16
|
+
import { runtimeTurn } from "../runtime/runtime-turn.js";
|
|
16
17
|
const textDecoder = new TextDecoder();
|
|
17
18
|
const EXCHANGE_ESCROW_TIMEOUT_MS = 10_000;
|
|
18
19
|
/** CESR syntax failures identify malformed evidence; I/O, limits, and hashing failures do not. */
|
|
@@ -212,6 +213,7 @@ export class Exchanger {
|
|
|
212
213
|
const nests = args.nests?.length
|
|
213
214
|
? yield* abortable((signal) => prepareNests(args.nests, signal))
|
|
214
215
|
: undefined;
|
|
216
|
+
const written = this.hby.db.writeVersion;
|
|
215
217
|
const result = this.hby.db.transaction(() => {
|
|
216
218
|
if (selectedGrant) {
|
|
217
219
|
const db = this.hby.db;
|
|
@@ -270,8 +272,9 @@ export class Exchanger {
|
|
|
270
272
|
}
|
|
271
273
|
return admission;
|
|
272
274
|
});
|
|
273
|
-
// Durable completion joins the actual LMDB flush;
|
|
274
|
-
|
|
275
|
+
// Durable completion joins the actual LMDB flush; a transaction that wrote nothing has nothing to wait for.
|
|
276
|
+
if (this.hby.db.writeVersion !== written)
|
|
277
|
+
yield* this.hby.db.flushed();
|
|
275
278
|
if (!result)
|
|
276
279
|
return undefined;
|
|
277
280
|
for (const cue of result.cues)
|
|
@@ -406,11 +409,16 @@ export class Exchanger {
|
|
|
406
409
|
// False means an existing qualified member, not a failed write. Exceptions abort the writer.
|
|
407
410
|
for (const texter of args.essrs ?? [])
|
|
408
411
|
this.hby.db.essrs.add([said], texter);
|
|
409
|
-
|
|
412
|
+
const meta = {
|
|
410
413
|
mailboxAid,
|
|
411
414
|
...(receiverTime !== undefined ? { receiverTime } : {}),
|
|
412
415
|
gvrsn: previous?.gvrsn ?? args.gvrsn ?? args.serder.gvrsn ?? args.serder.pvrsn,
|
|
413
|
-
}
|
|
416
|
+
};
|
|
417
|
+
// A retry that learns nothing new leaves the stored record as it is rather than committing it again.
|
|
418
|
+
if (!previous || previous.mailboxAid !== meta.mailboxAid || previous.receiverTime !== meta.receiverTime
|
|
419
|
+
|| JSON.stringify(previous.gvrsn) !== JSON.stringify(meta.gvrsn)) {
|
|
420
|
+
this.hby.db.exchangeMeta.pin([said], meta);
|
|
421
|
+
}
|
|
414
422
|
if (pending) {
|
|
415
423
|
if (newEscrow) {
|
|
416
424
|
if (senderQuery)
|
|
@@ -492,6 +500,8 @@ export class Exchanger {
|
|
|
492
500
|
...attempt.ejection,
|
|
493
501
|
});
|
|
494
502
|
}
|
|
503
|
+
// Deliveries and I/O take a slot between escrowed messages; none is skipped.
|
|
504
|
+
yield* runtimeTurn();
|
|
495
505
|
}
|
|
496
506
|
}
|
|
497
507
|
/**
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { Diger } from "cesr-ts";
|
|
2
|
+
import { ValidationError } from "../../core/errors.js";
|
|
3
|
+
import { sendExchangeMessage } from "../exchanges/exchange-messaging.js";
|
|
4
|
+
import { systemClock } from "../runtime/runtime-clock.js";
|
|
5
|
+
import { observeUntil } from "../runtime/workflow-contract.js";
|
|
6
|
+
import { isLocalGroupHab } from "./endpoint-roleing.js";
|
|
2
7
|
const CHALLENGE_CONSONANTS = [
|
|
3
8
|
"b",
|
|
4
9
|
"c",
|
|
@@ -91,6 +96,51 @@ export function findVerifiedChallengeResponse(db, signer, expected) {
|
|
|
91
96
|
}
|
|
92
97
|
return null;
|
|
93
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Respond to a challenge: sign and send a `/challenge/response` exchange message
|
|
101
|
+
* carrying `words` to `recipient`. Each call authors a new message (the exchange
|
|
102
|
+
* carries a timestamp); the verifier's matching is idempotent over repeats.
|
|
103
|
+
*
|
|
104
|
+
* @returns The signed exchange message that was sent.
|
|
105
|
+
* @throws ValidationError for a group habitat or empty words; send failures propagate.
|
|
106
|
+
*/
|
|
107
|
+
export function* respondToChallenge(deps, hab, recipient, words) {
|
|
108
|
+
if (isLocalGroupHab(deps.hby, hab)) {
|
|
109
|
+
throw new ValidationError("Challenge respond does not coordinate group approval. Select the responding member alias.");
|
|
110
|
+
}
|
|
111
|
+
if (words.length === 0)
|
|
112
|
+
throw new ValidationError("Challenge words must not be empty.");
|
|
113
|
+
const payload = { words: [...words] };
|
|
114
|
+
const { serder } = yield* sendExchangeMessage(deps.hby, hab, deps.http, deps.clock, {
|
|
115
|
+
dest: recipient,
|
|
116
|
+
route: CHALLENGE_RESPONSE_ROUTE,
|
|
117
|
+
topic: "challenge",
|
|
118
|
+
payload: { i: hab.pre, ...payload },
|
|
119
|
+
});
|
|
120
|
+
return serder;
|
|
121
|
+
}
|
|
122
|
+
/** Read the status of `words` from `signer`; mutates nothing. */
|
|
123
|
+
export function challengeStatus(deps, signer, words) {
|
|
124
|
+
const response = findVerifiedChallengeResponse(deps.db, signer, words);
|
|
125
|
+
if (!response)
|
|
126
|
+
return { state: "waiting" };
|
|
127
|
+
const recorded = deps.db.chas.get([signer]).some((diger) => diger.qb64 === response.said);
|
|
128
|
+
return { state: recorded ? "verified" : "received", response };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Observe stored exchange messages until a response with the expected words from
|
|
132
|
+
* `signer` arrives or the budget expires. Starts no worker: the host's runtime
|
|
133
|
+
* (mailbox polling, exchange intake) is what stores the response.
|
|
134
|
+
*/
|
|
135
|
+
export function* waitForChallengeResponse(deps, signer, words, { timeoutMs, pollMs = 250, clock = systemClock }) {
|
|
136
|
+
const done = yield* observeUntil(() => challengeStatus(deps, signer, words).state !== "waiting", {
|
|
137
|
+
timeoutMs,
|
|
138
|
+
pollMs,
|
|
139
|
+
clock,
|
|
140
|
+
});
|
|
141
|
+
const status = challengeStatus(deps, signer, words);
|
|
142
|
+
return done ? { outcome: "settled", status } : { outcome: "timed-out", status, timeoutMs };
|
|
143
|
+
}
|
|
94
144
|
/** Persist one verifier-confirmed challenge response in `chas.`. */
|
|
95
145
|
export function markChallengeVerified(db, signer, serder) {
|
|
96
146
|
if (!serder.said) {
|