keri-ts 2.2.4 → 2.2.6
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/dhking.js +29 -2
- package/esm/keri/npm/src/acdc/observing.js +10 -3
- package/esm/keri/npm/src/acdc/query-resend.js +74 -0
- package/esm/keri/npm/src/acdc/regengine.js +39 -9
- package/esm/keri/npm/src/acdc/regeventing.js +5 -3
- 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/acdc/unblinding.js +38 -3
- package/esm/keri/npm/src/app/bootstrap/authenticating.js +32 -10
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +363 -30
- 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 +205 -44
- 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 +137 -37
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +94 -31
- 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 +147 -44
- 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 +39 -11
- 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 +71 -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 +47 -21
- 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 +180 -34
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +42 -73
- package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
- package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +8 -1
- 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 +16 -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/missing-anchor-query.js +20 -0
- 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/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 +71 -28
- 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/dhking.d.ts +8 -0
- package/types/acdc/dhking.d.ts.map +1 -1
- package/types/acdc/observing.d.ts +5 -1
- package/types/acdc/observing.d.ts.map +1 -1
- package/types/acdc/query-resend.d.ts +24 -0
- package/types/acdc/query-resend.d.ts.map +1 -0
- package/types/acdc/regengine.d.ts +19 -1
- package/types/acdc/regengine.d.ts.map +1 -1
- package/types/acdc/regeventing.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/acdc/unblinding.d.ts +13 -1
- package/types/acdc/unblinding.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 +90 -5
- 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 +54 -8
- package/types/app/ipex/group-issuance.d.ts.map +1 -1
- package/types/app/ipex/ipex-discloser.d.ts +39 -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 +46 -16
- 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 +13 -2
- 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 +32 -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 +25 -5
- 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 +56 -5
- 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/ipex-auxiliaries.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 +12 -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/missing-anchor-query.d.ts +34 -0
- package/types/core/missing-anchor-query.d.ts.map +1 -0
- 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/indexeddb-persistence.d.ts +3 -2
- package/types/db/core/indexeddb-persistence.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 +32 -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
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* | holder: pending Grant, no root held | `awaitingExchangeConsent` |
|
|
23
23
|
* | pending Grant past its deadline | `expired` |
|
|
24
24
|
* | holder: root held, not committed otherwise; issuer: update accepted | `active`, `rootRetained` (adopted) |
|
|
25
|
+
* | group member: root copy received or the custodian's final proof stored | `active`, `awaitingAnchor` (followed) |
|
|
25
26
|
* | issuer: pending Grant, update not accepted | `interrupted` |
|
|
26
27
|
* | root held and the registry committed to another exchange | `interrupted` |
|
|
27
28
|
* | holder: Grant accepted, no accepted Admit | `active`, `awaitingAdmitConsent` |
|
|
@@ -54,9 +55,12 @@
|
|
|
54
55
|
* `{kind: "issuance", id: <grant>}` and the embedder records it.
|
|
55
56
|
*
|
|
56
57
|
* A local group issues and receives through the same table, status and wait: its consent calls and per-operation
|
|
57
|
-
* steps are in `group-issuance.ts` and run inside the same `processOnce`. A group exchange is not
|
|
58
|
-
* restart (its frozen coordinator and custodian are not stored): a group pending Grant with no root
|
|
59
|
-
* `awaitingExchangeConsent`, and a member consents again with the same call.
|
|
58
|
+
* steps are in `group-issuance.ts` and run inside the same `processOnce`. A group exchange in progress is not
|
|
59
|
+
* resumed after a restart (its frozen coordinator and custodian are not stored): a group pending Grant with no root
|
|
60
|
+
* reads `awaitingExchangeConsent`, and a member consents again with the same call. The custodian that retained the
|
|
61
|
+
* root is adopted once the stores allow it, and a member the group completed without is followed: the same sweep
|
|
62
|
+
* that finds resumable Grants begins the proof-only follow of each group Grant whose custodian's final proof is
|
|
63
|
+
* stored, signing nothing, so a restart begins it again.
|
|
60
64
|
*
|
|
61
65
|
* Collaborators are narrow concrete owners (ADR-0013); no `AgentRuntime`.
|
|
62
66
|
*/
|
|
@@ -65,27 +69,29 @@ import { call, race } from "effection";
|
|
|
65
69
|
import { RegistryAuthor } from "../../acdc/authoring.js";
|
|
66
70
|
import { RegistryCustody } from "../../acdc/custody.js";
|
|
67
71
|
import { grantTransactionId, registryUncommitted, resumableExchange } from "../../acdc/dhking.js";
|
|
68
|
-
import {
|
|
72
|
+
import { QUERY_RESEND_MS } from "../../acdc/query-resend.js";
|
|
73
|
+
import { KramRefusedError, ValidationError } from "../../core/errors.js";
|
|
69
74
|
import { CREDENTIAL_MAILBOX_TOPIC } from "../../core/mailbox/mailbox-topics.js";
|
|
70
75
|
import { timestampMicros } from "../../time/mod.js";
|
|
71
76
|
import { groupSigningMembers, isLocalGroupHab, localGroupMember } from "../multisig/group-members.js";
|
|
72
77
|
import { remoteGroupMembers } from "../multisig/multisig-messaging.js";
|
|
73
78
|
import { DeliveryOutbox, } from "../runtime/delivery-outbox.js";
|
|
74
79
|
import { observeUntil, waitOnClock } from "../runtime/workflow-contract.js";
|
|
75
|
-
import { deriveGroupCustody, GroupIssuance, GroupParty, registerGroupRoutes, stepGroup, } from "./group-issuance.js";
|
|
80
|
+
import { awaitsGroupProof, beginGroupProofFollow, deriveGroupCustody, GroupIssuance, GroupParty, registerGroupRoutes, stepGroup, storedGroupProof, } from "./group-issuance.js";
|
|
76
81
|
import { IpexAdmitter } from "./ipex-admitting.js";
|
|
77
|
-
import { exchangeWire, IpexDiscloser, storedExchangeWire } from "./ipex-discloser.js";
|
|
82
|
+
import { exchangeWire, IpexDiscloser, storedExchangeWire, } from "./ipex-discloser.js";
|
|
78
83
|
/**
|
|
79
|
-
* Milliseconds between resends of the issuer query
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
84
|
+
* Milliseconds between the first resends of the issuer query, which the holder may have lost before consenting (a
|
|
85
|
+
* process that ended, or a retained query that outlived its window). A single-signer issuer re-dates every resend
|
|
86
|
+
* (new date, SAID and signatures): the peer's KRAM caches every query it accepts, so an identical resend would be
|
|
87
|
+
* a replay it drops. A group cannot re-date (re-signing needs the members), so it resends the same bytes, doubling
|
|
88
|
+
* this interval up to a cap and stopping once the query is older than the multi-key acceptance window.
|
|
83
89
|
*/
|
|
84
|
-
export const ISSUANCE_QUERY_RESEND_MS =
|
|
90
|
+
export const ISSUANCE_QUERY_RESEND_MS = QUERY_RESEND_MS;
|
|
85
91
|
/**
|
|
86
|
-
* Milliseconds between scans of the pending exchange escrow for Grants a restarted process can finish
|
|
87
|
-
* repeats because what makes a Grant resumable can arrive after
|
|
88
|
-
* other members approve it).
|
|
92
|
+
* Milliseconds between scans of the pending exchange escrow for Grants a restarted process can finish and group
|
|
93
|
+
* Grants whose final proof a member follows. The scan repeats because what makes a Grant resumable can arrive after
|
|
94
|
+
* the restart (an issuer's update accepted once the other members approve it), and a custodian's proof arrives late.
|
|
89
95
|
*/
|
|
90
96
|
export const ISSUANCE_SWEEP_MS = 5_000;
|
|
91
97
|
/** The key of one party's operation on a Grant. */
|
|
@@ -219,23 +225,27 @@ export class IssuanceOperations {
|
|
|
219
225
|
remember(grant, ended) {
|
|
220
226
|
this.ended.set(partyKey(grant, ended.role), ended);
|
|
221
227
|
}
|
|
222
|
-
/** Whether a pass has work: a live operation, or
|
|
228
|
+
/** Whether a pass has work: a live operation, an adoption still owed, or a sweep of the pending Grants is due. */
|
|
223
229
|
active() {
|
|
224
230
|
return this.live.size > 0 || this.stale.length > 0 || this.deps.clock.now() >= this.nextSweep;
|
|
225
231
|
}
|
|
226
232
|
/**
|
|
227
233
|
* Advance every live operation by one pass. Each pass is bounded by the operation's Grant deadline.
|
|
228
|
-
*
|
|
229
|
-
*
|
|
234
|
+
* Every {@link ISSUANCE_SWEEP_MS} a pass also scans the pending exchange escrow once ({@link sweptGrants}): it
|
|
235
|
+
* adopts the Grants the stores prove resumable and begins the proof-only follow of the group Grants whose
|
|
236
|
+
* custodian's proof is stored. Neither signs nor sends anything.
|
|
230
237
|
*/
|
|
231
238
|
*processOnce() {
|
|
232
239
|
if (this.deps.clock.now() >= this.nextSweep) {
|
|
233
240
|
this.nextSweep = this.deps.clock.now() + ISSUANCE_SWEEP_MS;
|
|
234
241
|
const known = new Set(this.stale.map(({ grant, role }) => partyKey(grant, role)));
|
|
235
|
-
|
|
242
|
+
const swept = sweptGrants(this.deps, (grant, role) => {
|
|
236
243
|
const key = partyKey(grant, role);
|
|
237
|
-
return
|
|
238
|
-
})
|
|
244
|
+
return this.live.has(key) || known.has(key) || this.refused.has(key) || this.ended.has(key);
|
|
245
|
+
});
|
|
246
|
+
this.stale.push(...swept.resumable);
|
|
247
|
+
for (const found of swept.follows)
|
|
248
|
+
this.follow(found);
|
|
239
249
|
}
|
|
240
250
|
for (const found of [...this.stale]) {
|
|
241
251
|
if (yield* this.settle(found))
|
|
@@ -249,6 +259,10 @@ export class IssuanceOperations {
|
|
|
249
259
|
const left = op.expires === null ? null : op.expires - BigInt(this.deps.clock.now()) * 1000n;
|
|
250
260
|
if (left !== null && left <= 0n)
|
|
251
261
|
this.end(op, "expired", EXPIRED);
|
|
262
|
+
// A habitat that cannot sign yet waits for its signer; only its Grant deadline ends the operation meanwhile.
|
|
263
|
+
// A proof-only follow signs nothing, so it proceeds without one.
|
|
264
|
+
else if (!op.hab.canSign() && op.group?.mode !== "proofOnly")
|
|
265
|
+
continue;
|
|
252
266
|
else if ((yield* race([this.advance(op), ...(left === null ? [] : [this.timer(Number(left) / 1000)])])) === "expired")
|
|
253
267
|
this.end(op, "expired", EXPIRED);
|
|
254
268
|
}
|
|
@@ -263,7 +277,8 @@ export class IssuanceOperations {
|
|
|
263
277
|
/** End a live operation: release its DH approval, remember why until the process ends, tell the sink. */
|
|
264
278
|
end(op, state, reason) {
|
|
265
279
|
this.release(op);
|
|
266
|
-
|
|
280
|
+
const deadline = state === "expired" ? op.deadline : null;
|
|
281
|
+
this.ended.set(partyKey(op.grant, op.role), { role: op.role, state, reason, ...(deadline && { deadline }) });
|
|
267
282
|
this.deps.changes?.push({ kind: "issuance", id: op.grant });
|
|
268
283
|
}
|
|
269
284
|
/**
|
|
@@ -277,6 +292,14 @@ export class IssuanceOperations {
|
|
|
277
292
|
this.end(op, "abandoned", reason);
|
|
278
293
|
return bound.map(op => op.grant);
|
|
279
294
|
}
|
|
295
|
+
/** Grants of the live operations, and of those found resumable and not yet adopted, whose habitat is `pre`. */
|
|
296
|
+
grantsOf(pre) {
|
|
297
|
+
const grants = [...this.live.values()].filter(op => op.hab.pre === pre).map(op => op.grant);
|
|
298
|
+
for (const found of this.stale)
|
|
299
|
+
if (found.hab.pre === pre)
|
|
300
|
+
grants.push(found.grant);
|
|
301
|
+
return [...new Set(grants)];
|
|
302
|
+
}
|
|
280
303
|
/** Drop the live entry and the DH approval; a missing approval means the owner already closed. */
|
|
281
304
|
release(op) {
|
|
282
305
|
this.live.delete(partyKey(op.grant, op.role));
|
|
@@ -324,6 +347,24 @@ export class IssuanceOperations {
|
|
|
324
347
|
}
|
|
325
348
|
return true;
|
|
326
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Begin the proof-only follow a sweep found, under the stored proof's election; it signs nothing, so it needs no
|
|
352
|
+
* unlocked Keeper. A refusal means the stores no longer support it: the Grant then reads from them and is not tried
|
|
353
|
+
* again in this process. Any other failure is this Grant's alone and ends it `failed`, as for an adoption.
|
|
354
|
+
*/
|
|
355
|
+
follow(found) {
|
|
356
|
+
const key = partyKey(found.grant, found.role);
|
|
357
|
+
try {
|
|
358
|
+
beginGroupProofFollow(this, found.grant, found.role, found.election.coordinator.said);
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
this.refused.add(key);
|
|
362
|
+
if (!(error instanceof ValidationError)) {
|
|
363
|
+
this.ended.set(key, { role: found.role, state: "failed", reason: message(error) });
|
|
364
|
+
this.deps.changes?.push({ kind: "issuance", id: found.grant });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
327
368
|
/** Rebuild the live entry of a resumable Grant from the stored Grant and the held roots. */
|
|
328
369
|
*adopt({ hab, grant, role }) {
|
|
329
370
|
const group = isLocalGroupHab(this.deps.hby, hab);
|
|
@@ -345,7 +386,7 @@ export class IssuanceOperations {
|
|
|
345
386
|
peer: wires ? retained.serder.sad.ri : retained.serder.pre,
|
|
346
387
|
registry: credential.regid,
|
|
347
388
|
expires,
|
|
348
|
-
issuer: wires ? { credential, ...wires, query: null, sent: true, nextQuery: 0 } : undefined,
|
|
389
|
+
issuer: wires ? { credential, ...wires, query: null, sent: true, supportMark: null, nextQuery: 0 } : undefined,
|
|
349
390
|
phase: "rootRetained",
|
|
350
391
|
outbox: new DeliveryOutbox(this.deps.retry),
|
|
351
392
|
finished: false,
|
|
@@ -442,7 +483,8 @@ export class IssuanceOperations {
|
|
|
442
483
|
}
|
|
443
484
|
/**
|
|
444
485
|
* Record one group operation the user consented to ({@link approveGroupIssuance},
|
|
445
|
-
* {@link approveGroupIssuanceExchange},
|
|
486
|
+
* {@link approveGroupIssuanceExchange}), or a proof-only follow, which needs no consent ({@link beginGroupProofFollow},
|
|
487
|
+
* begun by the sweep or by {@link followGroupIssuanceProof}). A holder approves DH here, with its
|
|
446
488
|
* consent; an issuer approves once the group has authenticated the Grant.
|
|
447
489
|
*/
|
|
448
490
|
beginGroup(group) {
|
|
@@ -482,6 +524,7 @@ export class IssuanceOperations {
|
|
|
482
524
|
dest: op.peer,
|
|
483
525
|
msg: concatBytes(this.discloser.proofSupport(issuer.raw, op.hab), issuer.grantWire),
|
|
484
526
|
});
|
|
527
|
+
issuer.supportMark = this.discloser.proofSupportMark(issuer.raw, op.hab);
|
|
485
528
|
issuer.sent = true;
|
|
486
529
|
}
|
|
487
530
|
while (op.dh.outgoing.length) {
|
|
@@ -551,7 +594,11 @@ export class IssuanceOperations {
|
|
|
551
594
|
yield* op.dh.finish(op.xid);
|
|
552
595
|
if (!this.deps.exc.complete(op.grant))
|
|
553
596
|
throw new ValidationError("Issued Grant remains unaccepted");
|
|
554
|
-
|
|
597
|
+
// The Grant send already carried the support; proving added the anchoring event and the registry update.
|
|
598
|
+
const support = issuer.supportMark
|
|
599
|
+
? this.discloser.proofSupportSince(issuer.raw, op.hab, issuer.supportMark)
|
|
600
|
+
: this.discloser.proofSupport(issuer.raw, op.hab);
|
|
601
|
+
this.enqueue(op, { src: op.hab.pre, dest: op.peer, msg: support });
|
|
555
602
|
op.finished = true;
|
|
556
603
|
}
|
|
557
604
|
}
|
|
@@ -559,6 +606,20 @@ export class IssuanceOperations {
|
|
|
559
606
|
function reached(clock, expires) {
|
|
560
607
|
return expires !== null && BigInt(clock.now()) * 1000n >= expires;
|
|
561
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* Refuse a Grant whose exchange deadline has passed with the KRAM lifetime refusal that names that deadline,
|
|
611
|
+
* so a caller learns why the Grant can no longer be acted on rather than that it is unknown. The deadline
|
|
612
|
+
* outlives the Grant's KRAM cache row and its ejection from escrow ({@link Exchanger.exchangeExpiry}). Returns
|
|
613
|
+
* when the Grant has no known deadline or is still inside it; the caller then raises its own refusal.
|
|
614
|
+
*
|
|
615
|
+
* @throws KramRefusedError (reason `lifetime`) once the deadline has passed.
|
|
616
|
+
*/
|
|
617
|
+
function refuseLapsedGrant(deps, grant) {
|
|
618
|
+
const deadline = deps.exc.exchangeExpiry(grant);
|
|
619
|
+
if (deadline && reached(deps.clock, timestampMicros(deadline))) {
|
|
620
|
+
throw new KramRefusedError(grant, { kind: "drop", reason: "lifetime", deadline });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
562
623
|
/**
|
|
563
624
|
* Whether a restarted process can complete this Grant without a new key exchange: it is pending inside its
|
|
564
625
|
* deadline, `hab` is the party `role` names, and {@link resumableExchange} finds the roots. An issuer
|
|
@@ -585,23 +646,43 @@ function grantParties(hby, grant) {
|
|
|
585
646
|
return issuer || holder ? { serder, issuer, holder } : null;
|
|
586
647
|
}
|
|
587
648
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
649
|
+
* One scan of the pending exchange escrow, made every {@link ISSUANCE_SWEEP_MS}: each local party of a pending Grant
|
|
650
|
+
* that `skip` does not exclude (the owner skips what it holds live, ended, refused or already found) is either
|
|
651
|
+
* resumable, or a group member that follows the custodian's stored proof ({@link followable}). A pure read.
|
|
590
652
|
*/
|
|
591
|
-
function
|
|
592
|
-
const
|
|
653
|
+
function sweptGrants(deps, skip) {
|
|
654
|
+
const swept = { resumable: [], follows: [] };
|
|
593
655
|
for (const [[grant]] of [...deps.hby.db.epse.getTopItemIter()]) {
|
|
594
656
|
const parties = grantParties(deps.hby, grant);
|
|
595
657
|
if (!parties)
|
|
596
658
|
continue;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
659
|
+
for (const [hab, role] of [[parties.issuer, "issuer"], [parties.holder, "holder"]]) {
|
|
660
|
+
if (!hab || skip(grant, role))
|
|
661
|
+
continue;
|
|
662
|
+
if (resumable(deps, hab, grant, role)) {
|
|
663
|
+
swept.resumable.push({ hab, grant, role });
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
const election = followable(deps, hab, grant, role, parties.serder);
|
|
667
|
+
if (election)
|
|
668
|
+
swept.follows.push({ hab, grant, role, election });
|
|
602
669
|
}
|
|
603
670
|
}
|
|
604
|
-
return
|
|
671
|
+
return swept;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* The election under which the local member of group `hab` follows `grant` to its final proof, or null: the party's
|
|
675
|
+
* side is not complete, the Grant is inside its exchange deadline, and the custodian's proof is stored
|
|
676
|
+
* ({@link storedGroupProof}). Only the proof decides it, never the member's weight: a member that should sign is not
|
|
677
|
+
* followed, because the proof exists only once the key exchange no longer wants any signature.
|
|
678
|
+
*/
|
|
679
|
+
function followable(deps, hab, grant, role, serder) {
|
|
680
|
+
if (!isLocalGroupHab(deps.hby, hab) || partyComplete(deps, grant, role))
|
|
681
|
+
return null;
|
|
682
|
+
const deadline = deps.exc.exchangeDeadline(serder), expires = deadline ? timestampMicros(deadline) : null;
|
|
683
|
+
if (expires === null || reached(deps.clock, expires))
|
|
684
|
+
return null;
|
|
685
|
+
return storedGroupProof(deps, hab, grant);
|
|
605
686
|
}
|
|
606
687
|
/** The terminal reading of a pending Grant no process can complete: ended by a restart, or by another exchange. */
|
|
607
688
|
function orphaned(deps, hab, grant) {
|
|
@@ -641,7 +722,8 @@ function requireIndividual(deps, hab) {
|
|
|
641
722
|
}
|
|
642
723
|
/**
|
|
643
724
|
* The exact unanchored pending Grant a local party may consent to, with its deadline. Refuses a group
|
|
644
|
-
* identifier, an issuance already begun, a Grant that is not this party's, and an issuer without its registry
|
|
725
|
+
* identifier, an issuance already begun, a Grant that is not this party's, and an issuer without its registry;
|
|
726
|
+
* a Grant past its deadline is refused with `KramRefusedError`.
|
|
645
727
|
*/
|
|
646
728
|
function selectGrant(ops, hab, grant, role, group = false) {
|
|
647
729
|
const { deps } = ops;
|
|
@@ -655,6 +737,7 @@ function selectGrant(ops, hab, grant, role, group = false) {
|
|
|
655
737
|
if (!retained || !(credential instanceof SerderACDC) || !credential.regid
|
|
656
738
|
|| (role === "issuer" ? retained.serder.pre : retained.serder.sad.ri) !== hab.pre
|
|
657
739
|
|| retained.sourceSeals?.length || retained.ptds?.length || retained.essrs?.length) {
|
|
740
|
+
refuseLapsedGrant(deps, grant);
|
|
658
741
|
throw new ValidationError("Select the exact unanchored pending issuance Grant for this local party");
|
|
659
742
|
}
|
|
660
743
|
const reg = deps.rgy.regs.get(credential.regid);
|
|
@@ -689,12 +772,15 @@ function* grantWires(retained) {
|
|
|
689
772
|
*
|
|
690
773
|
* @throws ValidationError for a Grant that is not this identifier's exact
|
|
691
774
|
* unanchored pending issuance, an unmanaged registry, a group identifier, or
|
|
692
|
-
* an issuance already begun or completed
|
|
775
|
+
* an issuance already begun or completed; its subclass `KramRefusedError`
|
|
776
|
+
* (reason `lifetime`, with the deadline) for a Grant past its exchange deadline.
|
|
693
777
|
*/
|
|
694
778
|
export function* beginIssuance(ops, grant, auths) {
|
|
695
779
|
const { deps } = ops, hab = grantParties(deps.hby, grant)?.issuer;
|
|
696
|
-
if (!hab)
|
|
780
|
+
if (!hab) {
|
|
781
|
+
refuseLapsedGrant(deps, grant);
|
|
697
782
|
throw new ValidationError("Select the exact unanchored pending issuance Grant for this local party");
|
|
783
|
+
}
|
|
698
784
|
const { retained, credential, deadline, expires } = selectGrant(ops, hab, grant, "issuer");
|
|
699
785
|
const { raw, grantWire } = yield* grantWires(retained);
|
|
700
786
|
const dh = deps.dhkes.dhke(hab), xid = dh.approve(grant), query = dh.start(grant);
|
|
@@ -708,7 +794,7 @@ export function* beginIssuance(ops, grant, auths) {
|
|
|
708
794
|
peer: retained.serder.sad.ri,
|
|
709
795
|
registry: credential.regid,
|
|
710
796
|
expires,
|
|
711
|
-
issuer: { credential, raw, grantWire, query, auths, sent: false, nextQuery: 0 },
|
|
797
|
+
issuer: { credential, raw, grantWire, query, auths, sent: false, supportMark: null, nextQuery: 0 },
|
|
712
798
|
phase: "exchangingKeys",
|
|
713
799
|
outbox: new DeliveryOutbox(ops.deps.retry),
|
|
714
800
|
finished: false,
|
|
@@ -730,14 +816,18 @@ export function* proposeIssuance(ops, draft, auths) {
|
|
|
730
816
|
* operation holds reads `interrupted` (or `expired` once the Grant deadline has passed); that is a
|
|
731
817
|
* statement, not a write.
|
|
732
818
|
*
|
|
819
|
+
* @throws KramRefusedError (reason `lifetime`, with the deadline) when the Grant's deadline has passed and the
|
|
820
|
+
* stores no longer hold what a status needs: KRAM pruned its cache row, or the escrow sweep ejected the Grant.
|
|
733
821
|
* @throws ValidationError when `grant` is not a v2 Grant this runtime's identifiers are party to.
|
|
734
822
|
*/
|
|
735
823
|
export function issuanceStatus(ops, grant, party = {}) {
|
|
736
824
|
const { deps } = ops, parties = grantParties(deps.hby, grant);
|
|
737
825
|
const chosen = chooseParty(parties, party);
|
|
738
826
|
const ended = ops.endedOf(grant, chosen), live = ops.liveOf(grant, chosen);
|
|
739
|
-
if (ended)
|
|
740
|
-
|
|
827
|
+
if (ended) {
|
|
828
|
+
const { state, role, reason, deadline } = ended;
|
|
829
|
+
return { state, role, reason, ...(deadline && { deadline }) };
|
|
830
|
+
}
|
|
741
831
|
if (live && !reached(deps.clock, live.expires)) {
|
|
742
832
|
return {
|
|
743
833
|
state: "active",
|
|
@@ -747,8 +837,10 @@ export function issuanceStatus(ops, grant, party = {}) {
|
|
|
747
837
|
...(live.outbox.failure && { failure: live.outbox.failure }),
|
|
748
838
|
};
|
|
749
839
|
}
|
|
750
|
-
if (!parties)
|
|
840
|
+
if (!parties) {
|
|
841
|
+
refuseLapsedGrant(deps, grant);
|
|
751
842
|
throw new ValidationError("No issuance was begun for this Grant");
|
|
843
|
+
}
|
|
752
844
|
const { serder, issuer, holder } = parties, db = deps.hby.db;
|
|
753
845
|
if (partyComplete(deps, grant, chosen)) {
|
|
754
846
|
const admitted = !!db.erpy.get([grant]);
|
|
@@ -763,16 +855,22 @@ export function issuanceStatus(ops, grant, party = {}) {
|
|
|
763
855
|
// A group issuer's partial Grant has no accepted lifetime until its members' contributions authenticate it.
|
|
764
856
|
const lifetime = deps.exc.exchangeDeadline(serder) ?? undefined;
|
|
765
857
|
const deadline = lifetime, expires = lifetime ? timestampMicros(lifetime) : null;
|
|
766
|
-
if (!group && (!deadline || expires === null))
|
|
858
|
+
if (!group && (!deadline || expires === null)) {
|
|
859
|
+
// KRAM pruned the Grant's cache row at its deadline, before the escrow sweep ejected the Grant.
|
|
860
|
+
refuseLapsedGrant(deps, grant);
|
|
767
861
|
throw new ValidationError("No issuance was begun for this Grant");
|
|
862
|
+
}
|
|
768
863
|
if (reached(deps.clock, expires)) {
|
|
769
864
|
const credential = deps.exc.retainedInput(grant)?.nests?.[0]?.body;
|
|
770
865
|
const registry = credential instanceof SerderACDC ? credential.regid : undefined;
|
|
771
866
|
const late = role === "holder" && registry && deps.rgy.store.headEvent(registry)?.ilk === "bup";
|
|
772
|
-
return { state: "expired", role, reason: late ? EXPIRED_LATE : EXPIRED };
|
|
867
|
+
return { state: "expired", role, reason: late ? EXPIRED_LATE : EXPIRED, deadline };
|
|
773
868
|
}
|
|
774
869
|
if (resumable(deps, hab, grant, role))
|
|
775
870
|
return { state: "active", role, phase: "rootRetained", deadline };
|
|
871
|
+
// The group completed without this member: the owner follows the custodian's proof once it is stored.
|
|
872
|
+
if (group && awaitsGroupProof(deps, hab, grant))
|
|
873
|
+
return { state: "active", role, phase: "awaitingAnchor", deadline };
|
|
776
874
|
if ((role === "holder" || group) && !heldRoot(deps, hab, grant)) {
|
|
777
875
|
// A group issuer's Grant may still be a partial one that only its members' contributions authenticate.
|
|
778
876
|
if (!group && !deps.exc.retainedInput(grant, { pending: true })) {
|
|
@@ -892,11 +990,16 @@ export function* waitForIssuance(ops, grant, { timeoutMs, clock = ops.deps.clock
|
|
|
892
990
|
* The holder's consent to the DH exchange for one pending Grant addressed to a local identifier. Approves DH and records the
|
|
893
991
|
* consent; `processOnce` then answers the issuer's query, waits for its registry update and finishes the Grant.
|
|
894
992
|
* Admitting the credential is a separate consent ({@link admitIssuance}).
|
|
993
|
+
*
|
|
994
|
+
* @throws KramRefusedError (reason `lifetime`, with the deadline) for a Grant past its exchange deadline, and
|
|
995
|
+
* ValidationError for any other Grant this holder cannot consent to.
|
|
895
996
|
*/
|
|
896
997
|
export function approveIssuanceExchange(ops, grant) {
|
|
897
998
|
const { deps } = ops, hab = grantParties(deps.hby, grant)?.holder;
|
|
898
|
-
if (!hab)
|
|
999
|
+
if (!hab) {
|
|
1000
|
+
refuseLapsedGrant(deps, grant);
|
|
899
1001
|
throw new ValidationError("Select the exact unanchored pending issuance Grant for this local party");
|
|
1002
|
+
}
|
|
900
1003
|
const { retained, credential, deadline, expires } = selectGrant(ops, hab, grant, "holder");
|
|
901
1004
|
const dh = deps.dhkes.dhke(hab), xid = dh.approve(grant);
|
|
902
1005
|
ops.begin({
|
|
@@ -49,7 +49,9 @@ export function pendingPresentations(ops, { hab } = {}) {
|
|
|
49
49
|
/**
|
|
50
50
|
* The accepted v2 Applies addressed to a local identifier (to `hab` when given) that no exchange has answered
|
|
51
51
|
* (no `erpy` pointer) and whose deadline has not passed; with `includeAnswered`, the answered ones too. Mutates
|
|
52
|
-
* nothing. A group member lists the Applies of its group once the group has accepted them.
|
|
52
|
+
* nothing. A group member lists the Applies of its group once the group has accepted them. The deadline is the
|
|
53
|
+
* Apply's own stamp plus the exchange window once KRAM has pruned its cache row, so an expired Apply stays
|
|
54
|
+
* omitted after the prune.
|
|
53
55
|
*/
|
|
54
56
|
export function pendingApplies(deps, { hab, includeAnswered = false } = {}) {
|
|
55
57
|
const { hby, exc, clock } = deps, found = [];
|
|
@@ -65,7 +67,7 @@ export function pendingApplies(deps, { hab, includeAnswered = false } = {}) {
|
|
|
65
67
|
const answer = hby.db.erpy.get([apply])?.qb64 ?? null;
|
|
66
68
|
if (answer && !includeAnswered)
|
|
67
69
|
continue;
|
|
68
|
-
const deadline = exc.
|
|
70
|
+
const deadline = exc.exchangeExpiry(apply), expiry = deadline ? timestampMicros(deadline) : null;
|
|
69
71
|
if (!answer && expiry !== null && now >= expiry)
|
|
70
72
|
continue;
|
|
71
73
|
const q = serder.sad?.q;
|
|
@@ -47,7 +47,6 @@ import { DeliveryOutbox } from "../runtime/delivery-outbox.js";
|
|
|
47
47
|
import { observeUntil, waitOnClock } from "../runtime/workflow-contract.js";
|
|
48
48
|
import { proposeGroupIpex, proposeGroupIpexAfter as groupIpexAfter, } from "./group-ipex.js";
|
|
49
49
|
import { IpexDiscloser, storedExchangeWire, } from "./ipex-discloser.js";
|
|
50
|
-
import { prepareAcdcV2GroupIpex } from "./ipex-grouping.js";
|
|
51
50
|
/** How long delivery failures may last on an exchange with no deadline (an Apply, an Agree) before it reads `failed`. */
|
|
52
51
|
export const PRESENTATION_RETRY_BOUND_MS = 600_000;
|
|
53
52
|
const EXPIRED = "The presentation reached its original exchange deadline";
|
|
@@ -220,7 +219,7 @@ export function* proposeGroupPresentation(deps, groupIpex, { hab, selection, opt
|
|
|
220
219
|
const { presentationRegistry, ...body } = options;
|
|
221
220
|
const gvrsn = body.gvrsn ?? undefined;
|
|
222
221
|
const discloser = discloserFor(deps, hab, selection.derive);
|
|
223
|
-
|
|
222
|
+
const prepared = yield* discloser.prepareGrantBody(hab.pre, selection, body);
|
|
224
223
|
if (!presentationRegistry) {
|
|
225
224
|
return yield* proposeGroupIpex(groupIpex, { hab, exn: prepared.exn, nests: prepared.nests, gvrsn, deliver });
|
|
226
225
|
}
|
|
@@ -237,26 +236,12 @@ export function* proposeGroupPresentation(deps, groupIpex, { hab, selection, opt
|
|
|
237
236
|
// The Grant SAID is frozen before the update is anchored; only its nested proof is extended afterwards.
|
|
238
237
|
const [proof, update] = reg.present(prepared.exn, "presented", { stamp: body.stamp });
|
|
239
238
|
const proposed = yield* proposeRegistryEvent(proposals, hab, update, proof, counsel);
|
|
240
|
-
const exn = prepared.exn;
|
|
241
239
|
return groupIpexAfter(groupIpex, {
|
|
242
240
|
hab,
|
|
243
|
-
exn,
|
|
241
|
+
exn: prepared.exn,
|
|
244
242
|
gvrsn,
|
|
245
243
|
deliver,
|
|
246
|
-
prerequisite: {
|
|
247
|
-
anchor: registryEventRef(update, proposed.event),
|
|
248
|
-
*proceed() {
|
|
249
|
-
const seal = deps.rgy.store.anchor(update.said);
|
|
250
|
-
if (!seal)
|
|
251
|
-
throw new ValidationError("The presentation update is accepted but its anchor is unavailable");
|
|
252
|
-
prepared = yield* discloser.attachPresentationProof(prepared, proof, { s: seal[0], d: seal[1] });
|
|
253
|
-
const candidate = concatBytes(Counter.makeGVC(gvrsn ?? Vrsn_2_0), prepared.exn.raw, Counter.enclose({ qb64: prepared.nests, version: gvrsn ?? Vrsn_2_0 }));
|
|
254
|
-
const own = yield* prepareAcdcV2GroupIpex(deps.hby, groupIpex.deps.protocol, hab, candidate);
|
|
255
|
-
if (!own)
|
|
256
|
-
throw new ValidationError("ACDC v2 group disclosure awaits authenticating dependencies.");
|
|
257
|
-
return own;
|
|
258
|
-
},
|
|
259
|
-
},
|
|
244
|
+
prerequisite: { anchor: registryEventRef(update, proposed.event), grant: prepared, opening: proof },
|
|
260
245
|
});
|
|
261
246
|
}
|
|
262
247
|
/**
|
|
@@ -48,7 +48,8 @@ export function* admitAndDeliver(deps, input) {
|
|
|
48
48
|
* wait for the issuer to finish it, then admit and deliver ({@link admitAndDeliver}).
|
|
49
49
|
*
|
|
50
50
|
* @throws ValidationError for a Grant that is not addressed to a local identifier, one this runtime issued, or a
|
|
51
|
-
* group identifier
|
|
51
|
+
* group identifier; its subclass `KramRefusedError` (reason `lifetime`, with the deadline) for a Grant whose
|
|
52
|
+
* deadline passed before the stores could say more (its cache row pruned or the Grant ejected from escrow).
|
|
52
53
|
*/
|
|
53
54
|
export function* receiveIssuance(ops, input) {
|
|
54
55
|
const { grant, timeoutMs } = input, { deps } = ops;
|
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { bytesEqual, collectPayload, framePayload, parseBytes, SerderKERI, Vrsn_2_0 } from "cesr-ts";
|
|
30
30
|
import { call, race } from "effection";
|
|
31
|
+
import { QUERY_RESEND_MS, ThresholdQueryResend } from "../../acdc/query-resend.js";
|
|
31
32
|
import { Unblind } from "../../acdc/unblinding.js";
|
|
32
33
|
import { ValidationError } from "../../core/errors.js";
|
|
33
|
-
import { singleKeyQueryWindow } from "../../core/mailbox/kram-policy.js";
|
|
34
|
+
import { multiKeyQueryWindow, singleKeyQueryWindow } from "../../core/mailbox/kram-policy.js";
|
|
34
35
|
import { timestampMicros } from "../../time/mod.js";
|
|
35
36
|
import { groupSigningMembers, isLocalGroupHab, localGroupMember } from "../multisig/group-members.js";
|
|
36
37
|
import { MULTISIG_QRY_ROUTE, MULTISIG_RPY_ROUTE, multisigExnBody } from "../multisig/grouping.js";
|
|
@@ -38,8 +39,13 @@ import { remoteGroupMembers } from "../multisig/multisig-messaging.js";
|
|
|
38
39
|
import { envelopeFromFrame } from "../protocol/parsering.js";
|
|
39
40
|
import { DeliveryOutbox } from "../runtime/delivery-outbox.js";
|
|
40
41
|
import { observeUntil, waitOnClock } from "../runtime/workflow-contract.js";
|
|
41
|
-
/**
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Milliseconds between the first resends of the disclosee query, as `tufa ipex unblind` does. A single-signer
|
|
44
|
+
* requester sends the signed bytes unchanged while they are timely and re-dates them once older than the
|
|
45
|
+
* single-key window. A group requester cannot re-date (re-signing needs the members), so it resends the same
|
|
46
|
+
* bytes, doubling this interval up to a cap and stopping once the query is older than the multi-key window.
|
|
47
|
+
*/
|
|
48
|
+
export const UNBLIND_QUERY_RESEND_MS = QUERY_RESEND_MS;
|
|
43
49
|
/** The key of one party's operation on a Grant. */
|
|
44
50
|
export function unblindingKey(grant, role) {
|
|
45
51
|
return `${role}:${grant}`;
|
|
@@ -96,6 +102,13 @@ class GroupUnblind {
|
|
|
96
102
|
writable: true,
|
|
97
103
|
value: false
|
|
98
104
|
});
|
|
105
|
+
/** The frozen threshold query a requester resends unchanged, with its backoff schedule. */
|
|
106
|
+
Object.defineProperty(this, "resend", {
|
|
107
|
+
enumerable: true,
|
|
108
|
+
configurable: true,
|
|
109
|
+
writable: true,
|
|
110
|
+
value: new ThresholdQueryResend()
|
|
111
|
+
});
|
|
99
112
|
}
|
|
100
113
|
}
|
|
101
114
|
const EXPIRED = "The presentation reached its original exchange deadline";
|
|
@@ -186,14 +199,16 @@ export class UnblindingOperations {
|
|
|
186
199
|
}
|
|
187
200
|
/**
|
|
188
201
|
* Whether a pass has work: a live operation, or an owner holds an authenticated peer query whose reply is
|
|
189
|
-
* owed (an in-memory walk of the owners, no store read).
|
|
202
|
+
* owed (an in-memory walk of the owners, no store read). Finding what is owed reprocesses held queries, which
|
|
203
|
+
* answers them (signs), so an owner whose habitat cannot sign yet is skipped here as it is in `adopt`.
|
|
190
204
|
*/
|
|
191
205
|
active() {
|
|
192
206
|
if (this.live.size > 0)
|
|
193
207
|
return true;
|
|
194
|
-
for (const owner of this.deps.unblinds.unblindOwners())
|
|
195
|
-
if (owner.awaitingReply().length)
|
|
208
|
+
for (const owner of this.deps.unblinds.unblindOwners()) {
|
|
209
|
+
if (owner.hab.canSign() && owner.awaitingReply().length)
|
|
196
210
|
return true;
|
|
211
|
+
}
|
|
197
212
|
return false;
|
|
198
213
|
}
|
|
199
214
|
/** Advance every live operation by one pass, each bounded by its Grant deadline. */
|
|
@@ -208,6 +223,9 @@ export class UnblindingOperations {
|
|
|
208
223
|
const left = op.expires - BigInt(deps.clock.now()) * 1000n;
|
|
209
224
|
if (left <= 0n)
|
|
210
225
|
this.end(op, "expired", this.expiry(op));
|
|
226
|
+
// A habitat that cannot sign yet waits for its signer; only its Grant deadline ends the operation meanwhile.
|
|
227
|
+
else if (!op.hab.canSign())
|
|
228
|
+
continue;
|
|
211
229
|
else if ((yield* race([this.advance(op), this.timer(Number(left) / 1000)])) === "expired") {
|
|
212
230
|
this.end(op, "expired", this.expiry(op));
|
|
213
231
|
}
|
|
@@ -233,6 +251,10 @@ export class UnblindingOperations {
|
|
|
233
251
|
adopt() {
|
|
234
252
|
const { deps } = this;
|
|
235
253
|
for (const owner of deps.unblinds.unblindOwners()) {
|
|
254
|
+
// Finding what is owed reprocesses held queries, which answers them (signs); an owner whose habitat cannot
|
|
255
|
+
// sign yet is asked on a later pass.
|
|
256
|
+
if (!owner.hab.canSign())
|
|
257
|
+
continue;
|
|
236
258
|
for (const { xid, grant } of owner.awaitingReply()) {
|
|
237
259
|
if (this.live.has(unblindingKey(grant, "responder")))
|
|
238
260
|
continue;
|
|
@@ -297,6 +319,10 @@ export class UnblindingOperations {
|
|
|
297
319
|
this.end(op, "abandoned", reason);
|
|
298
320
|
return bound.map(op => op.grant);
|
|
299
321
|
}
|
|
322
|
+
/** Grants of the live Unblind operations whose habitat is `pre`. */
|
|
323
|
+
grantsOf(pre) {
|
|
324
|
+
return [...new Set([...this.live.values()].filter(op => op.hab.pre === pre).map(op => op.grant))];
|
|
325
|
+
}
|
|
300
326
|
/** Drop the live entry and the owner's approval and frozen messages; delivered bytes stay the peer's. */
|
|
301
327
|
release(op) {
|
|
302
328
|
this.live.delete(unblindingKey(op.grant, op.role));
|
|
@@ -396,7 +422,7 @@ export class UnblindingOperations {
|
|
|
396
422
|
yield* broadcast(message.msg);
|
|
397
423
|
}
|
|
398
424
|
else if (message.kin === "query")
|
|
399
|
-
|
|
425
|
+
g.resend.set(message.msg);
|
|
400
426
|
else if (!g.finished) {
|
|
401
427
|
yield* owner.finish(xid); // Validate exact evidence; no issuer-state admission policy here.
|
|
402
428
|
g.finished = true;
|
|
@@ -415,10 +441,12 @@ export class UnblindingOperations {
|
|
|
415
441
|
yield* owner.finish(xid);
|
|
416
442
|
return this.finishedStep(op);
|
|
417
443
|
}
|
|
418
|
-
// A threshold query cannot be re-dated by one member: it is resent unchanged
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
444
|
+
// A threshold query cannot be re-dated by one member: it is resent unchanged, with backoff, while the
|
|
445
|
+
// discloser can still accept it. The operation keeps waiting past that, to its own deadline.
|
|
446
|
+
const now = deps.clock.now();
|
|
447
|
+
if (g.resend.ready(now) && g.resend.fresh(now, multiKeyQueryWindow(deps.hby.db, Unblind.Route))) {
|
|
448
|
+
yield* send(op.peer, g.resend.query);
|
|
449
|
+
g.resend.sent(now, UNBLIND_QUERY_RESEND_MS);
|
|
422
450
|
}
|
|
423
451
|
return "stepped";
|
|
424
452
|
}
|