keri-ts 2.2.3 → 2.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/keri/npm/src/_dnt.polyfills.js +14 -14
- package/esm/keri/npm/src/acdc/registering.js +19 -0
- package/esm/keri/npm/src/acdc/salting.js +4 -0
- package/esm/keri/npm/src/app/bootstrap/authenticating.js +34 -12
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +365 -32
- package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
- package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
- package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
- package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
- package/esm/keri/npm/src/app/delivery/forwarding.js +188 -47
- package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
- package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
- package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
- package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
- package/esm/keri/npm/src/app/identity/delegating.js +66 -9
- package/esm/keri/npm/src/app/identity/habbing.js +190 -71
- package/esm/keri/npm/src/app/identity/keeping.js +111 -39
- package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
- package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
- package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
- package/esm/keri/npm/src/app/ipex/group-issuance.js +109 -23
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +36 -22
- package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
- package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
- package/esm/keri/npm/src/app/ipex/issuance.js +131 -35
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
- package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
- package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +16 -3
- package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
- package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
- package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
- package/esm/keri/npm/src/app/multisig/counseling.js +75 -27
- package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
- package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
- package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
- package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
- package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
- package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
- package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
- package/esm/keri/npm/src/app/protocol/protocol-processing.js +62 -27
- package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
- package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
- package/esm/keri/npm/src/app/queries/witness-inquisitor.js +190 -40
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +46 -73
- package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
- package/esm/keri/npm/src/app/runtime/doing.js +25 -0
- package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
- package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
- package/esm/keri/npm/src/app/transport/http-client.js +20 -2
- package/esm/keri/npm/src/app/transport/message-source.js +2 -1
- package/esm/keri/npm/src/app/transport/messengering.js +39 -8
- package/esm/keri/npm/src/app/witness/ingress.js +7 -0
- package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
- package/esm/keri/npm/src/app/witness/receipting.js +62 -20
- package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
- package/esm/keri/npm/src/core/errors.js +73 -0
- package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
- package/esm/keri/npm/src/core/kel/eventing.js +25 -28
- package/esm/keri/npm/src/core/kel/kever.js +15 -9
- package/esm/keri/npm/src/core/mailbox/kram-policy.js +8 -0
- package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
- package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
- package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
- package/esm/keri/npm/src/core/routing.js +18 -25
- package/esm/keri/npm/src/core/scheming.js +12 -5
- package/esm/keri/npm/src/core/wire-fields.js +126 -0
- package/esm/keri/npm/src/db/basing.js +4 -13
- package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
- package/esm/keri/npm/src/db/core/lmdber.js +25 -6
- package/esm/keri/npm/src/db/core/memstore.js +4 -0
- package/esm/keri/npm/src/db/core/store-owner.js +4 -0
- package/esm/keri/npm/src/db/core/webdber.js +85 -27
- package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
- package/esm/keri/npm/src/db/subing.js +4 -0
- package/esm/keri/npm/src/runtime/index.js +11 -5
- package/esm/keri/npm/src/time/mod.js +5 -0
- package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
- package/esm/keri/npm/src/vdr/eventing.js +28 -19
- package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +1 -1
- package/types/_dnt.polyfills.d.ts +9 -9
- package/types/_dnt.polyfills.d.ts.map +1 -1
- package/types/acdc/registering.d.ts +6 -0
- package/types/acdc/registering.d.ts.map +1 -1
- package/types/acdc/salting.d.ts.map +1 -1
- package/types/app/bootstrap/authenticating.d.ts +12 -2
- package/types/app/bootstrap/authenticating.d.ts.map +1 -1
- package/types/app/bootstrap/oobiery.d.ts +144 -7
- package/types/app/bootstrap/oobiery.d.ts.map +1 -1
- package/types/app/credentials/credential-importing.d.ts +8 -0
- package/types/app/credentials/credential-importing.d.ts.map +1 -1
- package/types/app/credentials/root-replicator.d.ts +19 -8
- package/types/app/credentials/root-replicator.d.ts.map +1 -1
- package/types/app/credentials/schema-resolving.d.ts.map +1 -1
- package/types/app/credentials/tel-publishing.d.ts +4 -2
- package/types/app/credentials/tel-publishing.d.ts.map +1 -1
- package/types/app/delivery/forwarding.d.ts +75 -7
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/delivery/relaying.d.ts +144 -0
- package/types/app/delivery/relaying.d.ts.map +1 -0
- package/types/app/delivery/respondant.d.ts +9 -2
- package/types/app/delivery/respondant.d.ts.map +1 -1
- package/types/app/exchanges/exchanging.d.ts +36 -0
- package/types/app/exchanges/exchanging.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts +6 -0
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/habbing.d.ts +106 -19
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +54 -19
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/identity/retire-identifier.d.ts +24 -0
- package/types/app/identity/retire-identifier.d.ts.map +1 -0
- package/types/app/ipex/disclosure-replaying.d.ts +61 -0
- package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
- package/types/app/ipex/group-ipex.d.ts +60 -14
- package/types/app/ipex/group-ipex.d.ts.map +1 -1
- package/types/app/ipex/group-issuance.d.ts +48 -6
- package/types/app/ipex/group-issuance.d.ts.map +1 -1
- package/types/app/ipex/ipex-discloser.d.ts +14 -2
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
- package/types/app/ipex/ipex-grouping.d.ts +23 -2
- package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
- package/types/app/ipex/ipex-v2.d.ts +5 -2
- package/types/app/ipex/ipex-v2.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +35 -11
- package/types/app/ipex/issuance.d.ts.map +1 -1
- package/types/app/ipex/pending-presentations.d.ts +4 -2
- package/types/app/ipex/pending-presentations.d.ts.map +1 -1
- package/types/app/ipex/presenting.d.ts.map +1 -1
- package/types/app/ipex/receive-issuance.d.ts +2 -1
- package/types/app/ipex/receive-issuance.d.ts.map +1 -1
- package/types/app/ipex/unblinding-workflow.d.ts +4 -1
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
- package/types/app/mailbox/agent-inbox.d.ts +126 -0
- package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
- package/types/app/mailbox/forward-handler.d.ts +114 -14
- package/types/app/mailbox/forward-handler.d.ts.map +1 -1
- package/types/app/mailbox/kel-publisher.d.ts +12 -22
- package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
- package/types/app/mailbox/mailbox-poller.d.ts +40 -24
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +19 -5
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +34 -11
- package/types/app/multisig/counseling.d.ts.map +1 -1
- package/types/app/multisig/group-members.d.ts +17 -1
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/grouping.d.ts +17 -1
- package/types/app/multisig/grouping.d.ts.map +1 -1
- package/types/app/multisig/multisig-workflows.d.ts +0 -8
- package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
- package/types/app/multisig/proposing.d.ts +4 -19
- package/types/app/multisig/proposing.d.ts.map +1 -1
- package/types/app/multisig/rejoin-group.d.ts +119 -0
- package/types/app/multisig/rejoin-group.d.ts.map +1 -0
- package/types/app/multisig/replace-member.d.ts +15 -1
- package/types/app/multisig/replace-member.d.ts.map +1 -1
- package/types/app/protocol/parsering.d.ts +21 -2
- package/types/app/protocol/parsering.d.ts.map +1 -1
- package/types/app/protocol/protocol-processing.d.ts +31 -6
- package/types/app/protocol/protocol-processing.d.ts.map +1 -1
- package/types/app/queries/query-replies.d.ts +4 -8
- package/types/app/queries/query-replies.d.ts.map +1 -1
- package/types/app/queries/relay-refresh.d.ts +26 -0
- package/types/app/queries/relay-refresh.d.ts.map +1 -0
- package/types/app/queries/witness-inquisitor.d.ts +57 -6
- package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
- package/types/app/runtime/agent-runtime.d.ts +34 -3
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/cue-runtime.d.ts +15 -3
- package/types/app/runtime/cue-runtime.d.ts.map +1 -1
- package/types/app/runtime/doing.d.ts +17 -0
- package/types/app/runtime/doing.d.ts.map +1 -1
- package/types/app/runtime/operation-owners.d.ts +16 -26
- package/types/app/runtime/operation-owners.d.ts.map +1 -1
- package/types/app/runtime/workflow-contract.d.ts +21 -6
- package/types/app/runtime/workflow-contract.d.ts.map +1 -1
- package/types/app/transport/http-client.d.ts +9 -0
- package/types/app/transport/http-client.d.ts.map +1 -1
- package/types/app/transport/message-source.d.ts +6 -3
- package/types/app/transport/message-source.d.ts.map +1 -1
- package/types/app/transport/messengering.d.ts +10 -1
- package/types/app/transport/messengering.d.ts.map +1 -1
- package/types/app/witness/ingress.d.ts +1 -0
- package/types/app/witness/ingress.d.ts.map +1 -1
- package/types/app/witness/receipt-client.d.ts +19 -1
- package/types/app/witness/receipt-client.d.ts.map +1 -1
- package/types/app/witness/receipting.d.ts +24 -1
- package/types/app/witness/receipting.d.ts.map +1 -1
- package/types/cli/messaging/multisig.d.ts.map +1 -1
- package/types/core/cues.d.ts +53 -1
- package/types/core/cues.d.ts.map +1 -1
- package/types/core/errors.d.ts +32 -0
- package/types/core/errors.d.ts.map +1 -1
- package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
- package/types/core/kel/eventing.d.ts.map +1 -1
- package/types/core/kel/kever-decisions.d.ts +3 -1
- package/types/core/kel/kever-decisions.d.ts.map +1 -1
- package/types/core/kel/kever.d.ts +7 -1
- package/types/core/kel/kever.d.ts.map +1 -1
- package/types/core/mailbox/kram-policy.d.ts +6 -0
- package/types/core/mailbox/kram-policy.d.ts.map +1 -1
- package/types/core/mailbox/kram-pruning.d.ts +5 -1
- package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
- package/types/core/mailbox/kram-time.d.ts +26 -2
- package/types/core/mailbox/kram-time.d.ts.map +1 -1
- package/types/core/mailbox/kraming.d.ts +14 -1
- package/types/core/mailbox/kraming.d.ts.map +1 -1
- package/types/core/routing.d.ts +3 -7
- package/types/core/routing.d.ts.map +1 -1
- package/types/core/scheming.d.ts.map +1 -1
- package/types/core/wire-fields.d.ts +56 -0
- package/types/core/wire-fields.d.ts.map +1 -0
- package/types/db/basing.d.ts.map +1 -1
- package/types/db/core/dber.d.ts +5 -0
- package/types/db/core/dber.d.ts.map +1 -1
- package/types/db/core/indexeddb-persistence.d.ts +3 -2
- package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
- package/types/db/core/lmdber.d.ts +6 -0
- package/types/db/core/lmdber.d.ts.map +1 -1
- package/types/db/core/memstore.d.ts +2 -0
- package/types/db/core/memstore.d.ts.map +1 -1
- package/types/db/core/store-owner.d.ts +2 -0
- package/types/db/core/store-owner.d.ts.map +1 -1
- package/types/db/core/webdber.d.ts +32 -6
- package/types/db/core/webdber.d.ts.map +1 -1
- package/types/db/exchange/exchange-records.d.ts +4 -0
- package/types/db/exchange/exchange-records.d.ts.map +1 -1
- package/types/db/mailbox/mailboxing.d.ts +10 -1
- package/types/db/mailbox/mailboxing.d.ts.map +1 -1
- package/types/db/subing.d.ts +2 -0
- package/types/db/subing.d.ts.map +1 -1
- package/types/runtime/index.d.ts +20 -14
- package/types/runtime/index.d.ts.map +1 -1
- package/types/time/mod.d.ts +2 -0
- package/types/time/mod.d.ts.map +1 -1
- package/types/vdr/credential-tsn-record.d.ts.map +1 -1
- package/types/vdr/eventing.d.ts.map +1 -1
- package/types/vdr/registry-tsn-record.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
- package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
- package/types/app/runtime/signer-conditions.d.ts +0 -32
- package/types/app/runtime/signer-conditions.d.ts.map +0 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent side of relay mode: refresh the agent's own view of a subject when a served edge asks about it.
|
|
3
|
+
*
|
|
4
|
+
* An edge's only view of third-party key state is its agent's: every edge query is addressed to the agent, which
|
|
5
|
+
* answers from its own store. That store may be stale for an identifier the agent has no reason of its own to
|
|
6
|
+
* follow. So when a relaying agent receives a `logs` or `ksn` query from a sender it serves about a subject that is
|
|
7
|
+
* not one of its own, it also issues its own ordinary `logs` query for that subject, as an ordinary `query` cue its
|
|
8
|
+
* own querier consumes. The agent is a forwarder, so that query goes directly to the subject's controller, agent or
|
|
9
|
+
* witness endpoint. This is on demand only, as KERIA queries for its Signify clients: no watching, no subscription
|
|
10
|
+
* list, no new store.
|
|
11
|
+
*
|
|
12
|
+
* What the edge sees: the answer to the query that caused the refresh comes from what the agent held then, unless
|
|
13
|
+
* the query names a sequence number or anchor the agent lacks, in which case the agent escrows it (query-not-found)
|
|
14
|
+
* and answers once the refresh lands. A later edge query returns the refreshed state.
|
|
15
|
+
*/
|
|
16
|
+
import { Ilks } from "cesr-ts";
|
|
17
|
+
import { Kever } from "../../core/kel/kever.js";
|
|
18
|
+
import { servingHost } from "../mailbox/forward-handler.js";
|
|
19
|
+
import { queryEnvelopeFromDispatch } from "../protocol/parsering.js";
|
|
20
|
+
import { addressableAttesters } from "./witness-inquisitor.js";
|
|
21
|
+
/**
|
|
22
|
+
* The requester of a query whose attached signatures satisfy its current signing threshold, or null.
|
|
23
|
+
*
|
|
24
|
+
* `logs` and `ksn` answers are public, so the KEL query path does not authenticate the requester (nor does KERIpy).
|
|
25
|
+
* A refresh spends the agent's own traffic on the requester's behalf, so it needs the requester proven: a
|
|
26
|
+
* transferable source whose KEL the agent holds and whose current keys signed this query to threshold.
|
|
27
|
+
*
|
|
28
|
+
* @param hby Borrowed accepted-state store holding the requester's KEL.
|
|
29
|
+
* @param query Normalized query envelope with its requester source and indexed signatures.
|
|
30
|
+
* @returns The authenticated requester AID, or null.
|
|
31
|
+
*/
|
|
32
|
+
function authenticatedRequester(hby, query) {
|
|
33
|
+
const source = query.source?.qb64;
|
|
34
|
+
if (!source)
|
|
35
|
+
return null;
|
|
36
|
+
const kever = hby.db.getKever(source);
|
|
37
|
+
if (!kever?.tholder)
|
|
38
|
+
return null;
|
|
39
|
+
const verified = Kever.verifyIndexedSignatures(query.serder.raw, query.sigers ?? [], kever.verfers);
|
|
40
|
+
return kever.tholder.satisfy(verified.indices) ? source : null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Decide whether one processed query makes a relaying host refresh its view of the subject, and return the query cue
|
|
44
|
+
* that does it.
|
|
45
|
+
*
|
|
46
|
+
* Every condition must hold:
|
|
47
|
+
* - the message is a remote (not local) `logs` or `ksn` query the KEL owner accepted or escrowed;
|
|
48
|
+
* - its subject `q.i` is not a prefix of this Habery;
|
|
49
|
+
* - its requester is authenticated (see {@link authenticatedRequester}) and served by a local host under the shared
|
|
50
|
+
* `servingHost` rule (an accepted mailbox or agent end role naming the host);
|
|
51
|
+
* - the host can address an attester for the subject now ({@link addressableAttesters}: one with an accepted
|
|
52
|
+
* location), so the request is sent on the querier's next pass instead of waiting in its queue.
|
|
53
|
+
*
|
|
54
|
+
* The cue is `{ route: "logs", q: {} }` for the subject, signed by the serving host: one full replay from the
|
|
55
|
+
* attester covers both routes, since `ksn` is answered from the same key state. A `tels` query is not refreshed:
|
|
56
|
+
* its wire body names the registry and credential but not the issuer the querier selects attesters by.
|
|
57
|
+
*
|
|
58
|
+
* @param hby Borrowed habitats and accepted-state store of the relaying host.
|
|
59
|
+
* @param result Processed envelope and the decision its owner made.
|
|
60
|
+
* @param mailboxAid Receiving hosted endpoint AID, or null when the request named none.
|
|
61
|
+
* @returns The refresh query cue, or null when this query refreshes nothing.
|
|
62
|
+
*/
|
|
63
|
+
export function relayRefreshCue(hby, result, mailboxAid) {
|
|
64
|
+
const { envelope, decision } = result;
|
|
65
|
+
const serder = envelope.serder;
|
|
66
|
+
if (envelope.local || serder.ilk !== Ilks.qry || (serder.route !== "logs" && serder.route !== "ksn"))
|
|
67
|
+
return null;
|
|
68
|
+
if (!decision || (decision.kind !== "accept" && decision.kind !== "escrow"))
|
|
69
|
+
return null;
|
|
70
|
+
const subject = serder.ked?.q?.i;
|
|
71
|
+
if (typeof subject !== "string" || hby.db.prefixes.has(subject))
|
|
72
|
+
return null;
|
|
73
|
+
const requester = authenticatedRequester(hby, queryEnvelopeFromDispatch(envelope));
|
|
74
|
+
if (!requester)
|
|
75
|
+
return null;
|
|
76
|
+
const host = servingHost(hby, { sender: requester, origin: null }, mailboxAid);
|
|
77
|
+
const hab = host ? hby.habs.get(host) : undefined;
|
|
78
|
+
if (!hab || addressableAttesters(hab, subject).length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
return { kin: "query", pre: subject, route: "logs", q: {}, signer: hab.pre };
|
|
81
|
+
}
|
|
@@ -7,22 +7,36 @@
|
|
|
7
7
|
* is an explicitly selected remote attester. Resolve/sign just before sending,
|
|
8
8
|
* using accepted endpoint evidence. One worker owns the unsigned request Deck
|
|
9
9
|
* and current network attempt; there is no retained signed retry backlog.
|
|
10
|
+
*
|
|
11
|
+
* With a relay (an edge runtime) every query is addressed and sent to the relay,
|
|
12
|
+
* which is the attester: it answers from its own store through its Respondant
|
|
13
|
+
* and Poster into the edge's mailbox, which it hosts. No query goes to a witness
|
|
14
|
+
* or controller, whatever `src` or `wits` the request names.
|
|
10
15
|
*/
|
|
11
16
|
import { concatBytes } from "cesr-ts";
|
|
12
17
|
import { call, resource } from "effection";
|
|
13
18
|
import { Deck } from "../../core/deck.js";
|
|
14
|
-
import { ValidationError } from "../../core/errors.js";
|
|
15
19
|
import { consoleLogger } from "../../core/logger.js";
|
|
16
20
|
import { Roles } from "../../core/roles.js";
|
|
17
21
|
import { Schemes } from "../../core/schemes.js";
|
|
18
22
|
import { introduce } from "../delivery/introducing.js";
|
|
19
|
-
import {
|
|
23
|
+
import { relayEndpoint, relayRefusal } from "../delivery/relaying.js";
|
|
24
|
+
import { PROTOCOL_TOCK_MS } from "../runtime/doing.js";
|
|
25
|
+
import { waitOnClock } from "../runtime/workflow-contract.js";
|
|
20
26
|
import { CesrHttpRefusal } from "../transport/cesr-egress.js";
|
|
27
|
+
import { isRequestableHttpUrl } from "../transport/http-client.js";
|
|
21
28
|
import { HTTPMessenger } from "../transport/http-messenger.js";
|
|
29
|
+
import { messengerUrl } from "../transport/messengering.js";
|
|
22
30
|
import { requireTcpDialer } from "../transport/tcp-dialer.js";
|
|
23
31
|
import { TransportError } from "../transport/transport-error.js";
|
|
24
32
|
/** First eligible role used by KERIpy WitnessInquisitor before random endpoint selection. */
|
|
25
33
|
const QUERY_ROLE_PRIORITY = [Roles.controller, Roles.agent, Roles.witness];
|
|
34
|
+
/**
|
|
35
|
+
* Delivery passes an ordinary request may rotate with a ready signer but no reachable attester before it is dropped:
|
|
36
|
+
* about five minutes at the protocol tock. Any remote party can cause such requests (every unverified reply signed by
|
|
37
|
+
* an unknown prefix yields a `query` cue), so without a bound they would rotate in the queue forever.
|
|
38
|
+
*/
|
|
39
|
+
export const UNADDRESSED_QUERY_PASS_LIMIT = 9_600;
|
|
26
40
|
/**
|
|
27
41
|
* Copy only the owned query-body level without erasing its route/payload relationship.
|
|
28
42
|
*
|
|
@@ -40,6 +54,22 @@ function copyQuery(instruction) {
|
|
|
40
54
|
return { route: instruction.route, q: { ...instruction.q } };
|
|
41
55
|
}
|
|
42
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Identity of one unsigned request for duplicate detection: every field that changes what is signed or where it goes.
|
|
59
|
+
*
|
|
60
|
+
* @param request Queued or candidate request; query filters are strings and string-valued seal SADs.
|
|
61
|
+
* @returns A canonical string equal for two requests exactly when they would produce the same query.
|
|
62
|
+
*/
|
|
63
|
+
function requestKey(request) {
|
|
64
|
+
return JSON.stringify([
|
|
65
|
+
request.route,
|
|
66
|
+
request.pre,
|
|
67
|
+
request.signer ?? null,
|
|
68
|
+
request.src ?? null,
|
|
69
|
+
request.wits ?? null,
|
|
70
|
+
request.q,
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
43
73
|
/**
|
|
44
74
|
* Return the sole local habitat when exactly one exists.
|
|
45
75
|
*
|
|
@@ -55,12 +85,47 @@ function soleHab(hby) {
|
|
|
55
85
|
const first = habitats.next();
|
|
56
86
|
return first.done || !habitats.next().done ? null : first.value;
|
|
57
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* The attesters an ordinary (non-relay) query for `pre` may go to: the AIDs of the first role among controller,
|
|
90
|
+
* agent and witness that has any accepted endpoint for the subject. Mailbox providers are not query attesters.
|
|
91
|
+
*
|
|
92
|
+
* @param hab Local habitat whose accepted-state store is read.
|
|
93
|
+
* @param pre Subject AID whose accepted endpoint roles are consulted.
|
|
94
|
+
* @returns Eligible attester AIDs, or an empty array for an unknown subject or a subject with no such endpoint.
|
|
95
|
+
*/
|
|
96
|
+
function queryAttesters(hab, pre) {
|
|
97
|
+
if (!hab.db.getKever(pre))
|
|
98
|
+
return [];
|
|
99
|
+
const ends = hab.endsFor(pre);
|
|
100
|
+
for (const role of QUERY_ROLE_PRIORITY) {
|
|
101
|
+
const eids = Object.keys(ends[role] ?? {});
|
|
102
|
+
if (eids.length > 0)
|
|
103
|
+
return eids;
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The location a query can be sent to, preferring HTTPS, then HTTP, then TCP. An accepted location comes from a remote
|
|
109
|
+
* `/loc/scheme` reply, so one the HTTP client refuses by construction (embedded credentials) is no location: sending
|
|
110
|
+
* to it would throw a refusal that is not a send outcome.
|
|
111
|
+
*
|
|
112
|
+
* @param urls Accepted locations of one endpoint, by scheme.
|
|
113
|
+
* @returns The preferred usable location, or null when none is usable.
|
|
114
|
+
*/
|
|
115
|
+
function preferredLocation(urls) {
|
|
116
|
+
for (const url of [urls[Schemes.https], urls[Schemes.http]])
|
|
117
|
+
if (url && isRequestableHttpUrl(url))
|
|
118
|
+
return url;
|
|
119
|
+
// A TCP location without a valid port is refused by the TCP messenger; it is no location here.
|
|
120
|
+
const tcp = urls[Schemes.tcp];
|
|
121
|
+
return tcp && messengerUrl({ tcp }) ? tcp : null;
|
|
122
|
+
}
|
|
58
123
|
/**
|
|
59
124
|
* Resolve the actual transport URL for one query destination AID.
|
|
60
125
|
*
|
|
61
126
|
* An explicit witness uses its own location records. Ordinary queries use only
|
|
62
127
|
* the selected controller, agent or witness endpoint; mailbox providers are not
|
|
63
|
-
* query attesters. Missing
|
|
128
|
+
* query attesters. Missing, unsupported or unusable locations do not promote another endpoint.
|
|
64
129
|
*
|
|
65
130
|
* @param hab Local habitat used to construct/sign this output; stores remain borrowed.
|
|
66
131
|
* @param queriedPre Subject AID whose state is requested.
|
|
@@ -69,18 +134,27 @@ function soleHab(hby) {
|
|
|
69
134
|
* @returns Preferred endpoint URL, or null if no permitted route exists.
|
|
70
135
|
*/
|
|
71
136
|
function resolveQueryDestinationUrl(hab, queriedPre, destination, { explicitWitness = false, } = {}) {
|
|
72
|
-
if (explicitWitness)
|
|
73
|
-
|
|
74
|
-
return urls[Schemes.https] ?? urls[Schemes.http] ?? urls[Schemes.tcp] ?? null;
|
|
75
|
-
}
|
|
137
|
+
if (explicitWitness)
|
|
138
|
+
return preferredLocation(hab.fetchUrls(destination));
|
|
76
139
|
const ends = hab.endsFor(queriedPre);
|
|
77
140
|
for (const role of [Roles.controller, Roles.agent, Roles.witness]) {
|
|
78
141
|
const urls = ends[role]?.[destination];
|
|
79
142
|
if (urls)
|
|
80
|
-
return urls
|
|
143
|
+
return preferredLocation(urls);
|
|
81
144
|
}
|
|
82
145
|
return null;
|
|
83
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* The attesters of an ordinary query for `pre` that can be sent to now: those {@link queryAttesters} selects that also
|
|
149
|
+
* have an accepted location.
|
|
150
|
+
*
|
|
151
|
+
* @param hab Local habitat whose accepted-state store is read.
|
|
152
|
+
* @param pre Subject AID whose accepted endpoint roles and locations are consulted.
|
|
153
|
+
* @returns Addressable attester AIDs of the first eligible role; empty when none can be reached now.
|
|
154
|
+
*/
|
|
155
|
+
export function addressableAttesters(hab, pre) {
|
|
156
|
+
return queryAttesters(hab, pre).filter((aid) => resolveQueryDestinationUrl(hab, pre, aid) !== null);
|
|
157
|
+
}
|
|
84
158
|
/**
|
|
85
159
|
* KERIpy-shaped query sender, independent from semantic query completion.
|
|
86
160
|
*
|
|
@@ -108,9 +182,10 @@ export class WitnessInquisitor {
|
|
|
108
182
|
* @param protocol Borrowed same-database processor for incoming query replies.
|
|
109
183
|
* @param http Borrowed HTTP implementation selected by composition.
|
|
110
184
|
* @param clock Borrowed clock for transport deadlines; times are milliseconds.
|
|
185
|
+
* @param relay The agent every query goes to; null selects attesters from accepted endpoint roles.
|
|
111
186
|
* @param hab Optional default local signer; null requires an unambiguous sole habitat.
|
|
112
187
|
*/
|
|
113
|
-
constructor(hby, protocol, http, clock, hab = null) {
|
|
188
|
+
constructor(hby, protocol, http, clock, relay, hab = null) {
|
|
114
189
|
/** Borrowed local signers and accepted-state stores. */
|
|
115
190
|
Object.defineProperty(this, "hby", {
|
|
116
191
|
enumerable: true,
|
|
@@ -181,12 +256,20 @@ export class WitnessInquisitor {
|
|
|
181
256
|
writable: true,
|
|
182
257
|
value: void 0
|
|
183
258
|
});
|
|
259
|
+
/** The agent every query is addressed and sent to (an edge); null selects ordinary attesters. */
|
|
260
|
+
Object.defineProperty(this, "relay", {
|
|
261
|
+
enumerable: true,
|
|
262
|
+
configurable: true,
|
|
263
|
+
writable: true,
|
|
264
|
+
value: void 0
|
|
265
|
+
});
|
|
184
266
|
if (protocol.kvy.db !== hby.db)
|
|
185
267
|
throw new RangeError("Query protocol must use the same database");
|
|
186
268
|
this.hby = hby;
|
|
187
269
|
this.protocol = protocol;
|
|
188
270
|
this.http = http;
|
|
189
271
|
this.clock = clock;
|
|
272
|
+
this.relay = relay;
|
|
190
273
|
this.hab = hab;
|
|
191
274
|
}
|
|
192
275
|
/**
|
|
@@ -197,6 +280,17 @@ export class WitnessInquisitor {
|
|
|
197
280
|
configure({ hab = null } = {}) {
|
|
198
281
|
this.hab = hab;
|
|
199
282
|
}
|
|
283
|
+
/** The explicitly configured default signer, or null when the sole local habitat is used. */
|
|
284
|
+
get defaultSigner() {
|
|
285
|
+
return this.hab?.pre ?? null;
|
|
286
|
+
}
|
|
287
|
+
/** Whether a queued request names `signer` as its explicit signer. */
|
|
288
|
+
hasRequestsSignedBy(signer) {
|
|
289
|
+
for (const request of this.requests)
|
|
290
|
+
if (request.signer === signer)
|
|
291
|
+
return true;
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
200
294
|
/** Deferred instructions and active sends are this sender's only pending work. */
|
|
201
295
|
hasPendingWork() {
|
|
202
296
|
return !this.requests.empty || this.hasPendingDelivery;
|
|
@@ -214,6 +308,7 @@ export class WitnessInquisitor {
|
|
|
214
308
|
...request,
|
|
215
309
|
...copyQuery(request),
|
|
216
310
|
wits: request.wits ? [...request.wits] : undefined,
|
|
311
|
+
unaddressedPasses: 0,
|
|
217
312
|
});
|
|
218
313
|
}
|
|
219
314
|
/**
|
|
@@ -231,6 +326,27 @@ export class WitnessInquisitor {
|
|
|
231
326
|
const selected = { ...request, signer: request.signer ?? hab?.pre };
|
|
232
327
|
this.enqueue(selected);
|
|
233
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Admit a routed query cue unless an identical request is already queued: same subject, route, filters, signer,
|
|
331
|
+
* attester and witness overrides, with the signer resolved as {@link enqueueCue} resolves it. A relaying host's cue
|
|
332
|
+
* router uses this, so the queries of the many edges it serves about one subject leave one request queued; a request
|
|
333
|
+
* already taken for delivery is no longer queued, so a later cue queues the next one.
|
|
334
|
+
*
|
|
335
|
+
* @param cue Typed query cue; missing route/subject produces no request.
|
|
336
|
+
* @param hab Optional local signer hint when the cue has no explicit signer.
|
|
337
|
+
*/
|
|
338
|
+
enqueueCueOnce(cue, hab) {
|
|
339
|
+
if (this.closed)
|
|
340
|
+
throw new Error("Query sender is closed");
|
|
341
|
+
if (!cue.route || !cue.pre)
|
|
342
|
+
return;
|
|
343
|
+
const { kin: _, ...request } = cue;
|
|
344
|
+
const key = requestKey({ ...request, signer: request.signer ?? hab?.pre });
|
|
345
|
+
for (const queued of this.requests)
|
|
346
|
+
if (requestKey(queued) === key)
|
|
347
|
+
return;
|
|
348
|
+
this.enqueueCue(cue, hab);
|
|
349
|
+
}
|
|
234
350
|
/** Include actual query I/O in convergence; pending instructions have their own queue. */
|
|
235
351
|
get hasPendingDelivery() {
|
|
236
352
|
return this.delivering;
|
|
@@ -281,7 +397,16 @@ export class WitnessInquisitor {
|
|
|
281
397
|
const packet = self.prepareRequest(request);
|
|
282
398
|
self.requests.pull();
|
|
283
399
|
if (!packet) {
|
|
284
|
-
|
|
400
|
+
// A signer that cannot sign yet, or a relay not yet usable, is local state that waits; a ready signer with no
|
|
401
|
+
// reachable attester counts toward the bound.
|
|
402
|
+
if (!self.relay && self.configuredHab(request.signer)?.canSign())
|
|
403
|
+
request.unaddressedPasses++;
|
|
404
|
+
if (request.unaddressedPasses >= UNADDRESSED_QUERY_PASS_LIMIT) {
|
|
405
|
+
consoleLogger.warn(`Dropping ${request.route} query for ${request.pre}: no reachable attester after ${request.unaddressedPasses} passes.`);
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
self.requests.append(request);
|
|
409
|
+
}
|
|
285
410
|
continue;
|
|
286
411
|
}
|
|
287
412
|
active.signal.throwIfAborted();
|
|
@@ -339,12 +464,15 @@ export class WitnessInquisitor {
|
|
|
339
464
|
* preparation/cleanup faults terminate the worker. Caller cancellation joins
|
|
340
465
|
* the current attempt; it does not close the borrowed state or HTTP service.
|
|
341
466
|
*/
|
|
342
|
-
*deliverDo() {
|
|
467
|
+
*deliverDo(tockMs = PROTOCOL_TOCK_MS) {
|
|
343
468
|
while (true) {
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
|
|
469
|
+
// Fixed tock: a pass only when a request is queued; a request waits at most `tockMs` to start.
|
|
470
|
+
if (!this.requests.empty) {
|
|
471
|
+
const failure = yield* this.deliveryPass();
|
|
472
|
+
if (failure)
|
|
473
|
+
consoleLogger.warn("Query delivery failed", failure);
|
|
474
|
+
}
|
|
475
|
+
yield* waitOnClock(this.clock, tockMs);
|
|
348
476
|
}
|
|
349
477
|
}
|
|
350
478
|
/**
|
|
@@ -365,51 +493,73 @@ export class WitnessInquisitor {
|
|
|
365
493
|
* Resolve/sign one selected query and introduction immediately before its attempt.
|
|
366
494
|
*
|
|
367
495
|
* @param request Deferred unsigned request; nested query values must remain immutable.
|
|
368
|
-
*
|
|
369
|
-
*
|
|
496
|
+
* The attester is chosen at random among the eligible ones (the explicit `src`, the `wits` overrides, or the first
|
|
497
|
+
* eligible endpoint role) that have an accepted location. An attester without one is never promoted to another
|
|
498
|
+
* role; when none of the eligible attesters can be reached the request is unresolved, like a signer that cannot sign
|
|
499
|
+
* yet, so no endpoint condition ends the delivery loop or fails a caller's pass.
|
|
500
|
+
*
|
|
501
|
+
* @param request Deferred unsigned request; nested query values must remain immutable.
|
|
502
|
+
* @returns Prepared packet, or null when signer/attester evidence is unresolved, no eligible attester has an
|
|
503
|
+
* accepted location, or the signer cannot sign yet.
|
|
370
504
|
*/
|
|
371
505
|
prepareRequest(request) {
|
|
372
506
|
const hab = this.configuredHab(request.signer);
|
|
373
|
-
|
|
507
|
+
// A signer that cannot sign yet leaves the request unresolved: it goes to the tail, and the next pass tries again.
|
|
508
|
+
if (!hab || !hab.canSign())
|
|
374
509
|
return null;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
510
|
+
if (this.relay)
|
|
511
|
+
return this.prepareRelayRequest(hab, request, this.relay);
|
|
512
|
+
const eligible = request.src
|
|
513
|
+
? [request.src]
|
|
514
|
+
: this.attestersFor(request.pre, { signer: hab.pre, wits: request.wits });
|
|
515
|
+
const reachable = eligible.flatMap((destination) => {
|
|
516
|
+
const url = resolveQueryDestinationUrl(hab, request.pre, destination, {
|
|
517
|
+
explicitWitness: request.wits?.includes(destination) ?? false,
|
|
518
|
+
});
|
|
519
|
+
// A TCP location on a host without a TCP dialer cannot be sent to: it is unreachable, not a fault.
|
|
520
|
+
return url && (this.http.tcp || !url.startsWith("tcp:")) ? [{ destination, url }] : [];
|
|
381
521
|
});
|
|
382
|
-
|
|
383
|
-
|
|
522
|
+
const selected = reachable[Math.floor(Math.random() * reachable.length)];
|
|
523
|
+
if (!selected)
|
|
524
|
+
return null;
|
|
525
|
+
const { destination, url } = selected;
|
|
384
526
|
const message = hab.query(request.pre, destination, copyQuery(request));
|
|
385
527
|
const intro = introduce(hab, destination);
|
|
386
528
|
return { url, body: concatBytes(intro, message), destination };
|
|
387
529
|
}
|
|
388
530
|
/**
|
|
389
|
-
*
|
|
531
|
+
* Address one query to the relay and sign it for the relay's location. The relay must have an accepted location
|
|
532
|
+
* and the signer must have authorized it as mailbox (where the answer is stored); until then the request stays
|
|
533
|
+
* unresolved and is tried again on a later pass, as for a signer that cannot sign yet.
|
|
534
|
+
*
|
|
535
|
+
* @param hab Local signing habitat.
|
|
536
|
+
* @param request Deferred unsigned request; its `src` and `wits` are not consulted.
|
|
537
|
+
* @param relay The configured relay agent, which is the attester.
|
|
538
|
+
* @returns Prepared packet for the relay, or null while {@link relayRefusal} refuses the relay for this signer.
|
|
539
|
+
*/
|
|
540
|
+
prepareRelayRequest(hab, request, relay) {
|
|
541
|
+
if (relayRefusal(this.hby, relay, hab.pre))
|
|
542
|
+
return null;
|
|
543
|
+
const endpoint = relayEndpoint(this.hby, relay);
|
|
544
|
+
const message = hab.query(request.pre, relay.aid, copyQuery(request));
|
|
545
|
+
return { url: endpoint.url, body: concatBytes(introduce(hab, relay.aid), message), destination: relay.aid };
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Return the first eligible role’s attester IDs; preparation chooses one randomly. With a relay, the relay is the
|
|
549
|
+
* only attester.
|
|
390
550
|
*
|
|
391
551
|
* @param pre Subject AID whose accepted endpoint roles are consulted.
|
|
392
552
|
* @param options Optional local signer AID and remote witness/attester overrides; neither selects a transport implementation.
|
|
393
553
|
* @returns Eligible attester AIDs, or an empty array for an unknown subject or unresolved signer/role.
|
|
394
554
|
*/
|
|
395
555
|
attestersFor(pre, { signer, wits } = {}) {
|
|
556
|
+
if (this.relay)
|
|
557
|
+
return [this.relay.aid];
|
|
396
558
|
if (wits && wits.length > 0) {
|
|
397
559
|
// Explicit watcher/witness overrides outrank endpoint-role lookup.
|
|
398
560
|
return [...wits];
|
|
399
561
|
}
|
|
400
562
|
const hab = this.configuredHab(signer);
|
|
401
|
-
|
|
402
|
-
return [];
|
|
403
|
-
const ends = hab.endsFor(pre);
|
|
404
|
-
for (const role of QUERY_ROLE_PRIORITY) {
|
|
405
|
-
const roleEnds = ends[role];
|
|
406
|
-
if (!roleEnds) {
|
|
407
|
-
continue;
|
|
408
|
-
}
|
|
409
|
-
const eids = Object.keys(roleEnds);
|
|
410
|
-
if (eids.length > 0)
|
|
411
|
-
return eids;
|
|
412
|
-
}
|
|
413
|
-
return [];
|
|
563
|
+
return hab ? queryAttesters(hab, pre) : [];
|
|
414
564
|
}
|
|
415
565
|
}
|