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
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { b } from "cesr-ts";
|
|
3
3
|
import { removeKramFactors } from "../../db/mailbox/kram-factors.js";
|
|
4
4
|
import { timestampMicros } from "../../time/mod.js";
|
|
5
|
+
import { consoleLogger } from "../logger.js";
|
|
5
6
|
import { TxnMsgCacheRecord } from "../records.js";
|
|
6
7
|
import { kramCacheValues } from "./kram-time.js";
|
|
7
8
|
/** Prune stored windows, then prove orphan XIDs with restartable finite existence scans. */
|
|
8
9
|
export class KramPruner {
|
|
9
|
-
constructor(db, policy) {
|
|
10
|
+
constructor(db, policy, logger = consoleLogger) {
|
|
10
11
|
Object.defineProperty(this, "db", {
|
|
11
12
|
enumerable: true,
|
|
12
13
|
configurable: true,
|
|
@@ -19,6 +20,12 @@ export class KramPruner {
|
|
|
19
20
|
writable: true,
|
|
20
21
|
value: policy
|
|
21
22
|
});
|
|
23
|
+
Object.defineProperty(this, "logger", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: logger
|
|
28
|
+
});
|
|
22
29
|
Object.defineProperty(this, "bound", {
|
|
23
30
|
enumerable: true,
|
|
24
31
|
configurable: true,
|
|
@@ -95,7 +102,14 @@ export class KramPruner {
|
|
|
95
102
|
throw new Error("Invalid KRAM pruning receiver clock");
|
|
96
103
|
if (!Number.isSafeInteger(maxRows) || maxRows < 1)
|
|
97
104
|
throw new RangeError("Invalid KRAM pruning batch");
|
|
98
|
-
const result = {
|
|
105
|
+
const result = {
|
|
106
|
+
kind: "pruned",
|
|
107
|
+
visited: 0,
|
|
108
|
+
removed: 0,
|
|
109
|
+
openers: 0,
|
|
110
|
+
partials: 0,
|
|
111
|
+
passComplete: false,
|
|
112
|
+
};
|
|
99
113
|
if (!this.db.opened)
|
|
100
114
|
throw new Error("KRAM pruning database is closed");
|
|
101
115
|
if (this.bound !== this.db.msgc.sdb) {
|
|
@@ -114,15 +128,20 @@ export class KramPruner {
|
|
|
114
128
|
continue;
|
|
115
129
|
}
|
|
116
130
|
const keys = this.keys(physical, this.phase + 2), table = this.phase ? this.db.tmsc : this.db.msgc;
|
|
117
|
-
this.db.transaction(() => {
|
|
131
|
+
const partial = this.db.transaction(() => {
|
|
118
132
|
const record = table.get(keys);
|
|
119
133
|
if (!record || !this.expired(record, when))
|
|
120
|
-
return;
|
|
134
|
+
return null;
|
|
121
135
|
table.rem(keys);
|
|
122
|
-
const
|
|
123
|
-
removeKramFactors(this.db,
|
|
136
|
+
const owner = [keys[0], keys.at(-1)], incomplete = this.db.pmkm.get(owner) !== null;
|
|
137
|
+
removeKramFactors(this.db, owner);
|
|
124
138
|
result.removed++;
|
|
139
|
+
return incomplete ? owner : null;
|
|
125
140
|
});
|
|
141
|
+
if (partial) {
|
|
142
|
+
result.partials++;
|
|
143
|
+
this.logger.warn("KRAM expired incomplete multi-signature message", { sender: partial[0], said: partial[1] });
|
|
144
|
+
}
|
|
126
145
|
scan.start = this.after(physical);
|
|
127
146
|
result.visited++;
|
|
128
147
|
continue;
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import { timestampMicros } from "../../time/mod.js";
|
|
1
|
+
import { timestampFromMicros, timestampMicros } from "../../time/mod.js";
|
|
2
2
|
import { MsgCacheRecord, TxnMsgCacheRecord } from "../records.js";
|
|
3
|
+
import { readDater, readDiger } from "../wire-fields.js";
|
|
4
|
+
/**
|
|
5
|
+
* Whether a drop reason means an authenticated message arrived too late or outside a live exchange.
|
|
6
|
+
*
|
|
7
|
+
* `timestamp`, `exchangeId`, `replay` and `authentication` are reachable by anyone who can post bytes (see
|
|
8
|
+
* kram-staging.ts), so callers that report or signal a refusal to anyone beyond the log must restrict themselves to
|
|
9
|
+
* these.
|
|
10
|
+
*/
|
|
11
|
+
export function isKramLateReason(reason) {
|
|
12
|
+
return reason === "timeliness" || reason === "lifetime" || reason === "unknownThread";
|
|
13
|
+
}
|
|
3
14
|
/** Validate durable cache values without turning corruption into remote refusal. */
|
|
4
15
|
export function kramCacheValues(cache) {
|
|
5
16
|
const { mdt, d, ml, pml, xl, pxl } = cache;
|
|
@@ -10,9 +21,19 @@ export function kramCacheValues(cache) {
|
|
|
10
21
|
throw new Error("Corrupt KRAM timeliness cache");
|
|
11
22
|
return { mdt: mdt, d: d, ml: ml, pml: pml, xl: xl, pxl: pxl };
|
|
12
23
|
}
|
|
24
|
+
/** Exchange id KRAM threads a message by: the `xip` SAID or the v2 `exn` `x` field; empty when it has none. */
|
|
25
|
+
export function kramExchangeId(body) {
|
|
26
|
+
const said = body.said;
|
|
27
|
+
return body.ilk === "xip" ? said ?? "" : body.ilk === "exn" && body.pvrsn.major >= 2 ? String(body.ked?.x ?? "") : "";
|
|
28
|
+
}
|
|
13
29
|
/**
|
|
14
30
|
* Inspect frozen message/xid routing and strict completed-message replay rules.
|
|
15
31
|
* Only an incomplete multi-signature message retains partial state.
|
|
32
|
+
*
|
|
33
|
+
* The stamp and exchange id are remote-authored and become durable values: an exchange message's stamp can be stored
|
|
34
|
+
* as its thread opener (a Dater, which needs the fixed-width form), and the exchange id is a cache key part (a value
|
|
35
|
+
* that is not one qualified digest, such as one containing `.`, would make a key the pruner cannot split). Both are
|
|
36
|
+
* refused here, before any store write.
|
|
16
37
|
*/
|
|
17
38
|
export function inspectKramReplay(db, body) {
|
|
18
39
|
const mdt = timestampMicros(body.stamp);
|
|
@@ -22,7 +43,11 @@ export function inspectKramReplay(db, body) {
|
|
|
22
43
|
if (!sender || !said)
|
|
23
44
|
throw new Error("KRAM replay inspection requires sender and message SAID");
|
|
24
45
|
const key = [sender, said];
|
|
25
|
-
const xid =
|
|
46
|
+
const xid = kramExchangeId(body);
|
|
47
|
+
if (xid && readDiger(xid)?.qb64 !== xid)
|
|
48
|
+
return { kind: "drop", reason: "exchangeId" };
|
|
49
|
+
if (xid && !readDater(body.stamp))
|
|
50
|
+
return { kind: "drop", reason: "timestamp" };
|
|
26
51
|
const cacheKey = xid ? [sender, xid, said] : key;
|
|
27
52
|
const cache = xid ? db.tmsc.get(cacheKey) : db.msgc.get(cacheKey);
|
|
28
53
|
const partial = db.pmkm.get(key) !== null;
|
|
@@ -54,8 +79,9 @@ export function evaluateKramTime(db, policy, inspection, authentication, receive
|
|
|
54
79
|
else {
|
|
55
80
|
const type = policy.cacheType(body.ilk, body.route), multi = authentication.kind === "multi";
|
|
56
81
|
const ml = multi ? type.ll : type.sl, pml = multi ? type.pll : type.psl;
|
|
57
|
-
|
|
58
|
-
|
|
82
|
+
const reach = (BigInt(type.d) + BigInt(ml)) * 1000n;
|
|
83
|
+
if (!within(mdt, receipt - reach, receipt + BigInt(type.d) * 1000n)) {
|
|
84
|
+
return { kind: "drop", reason: "timeliness", deadline: timestampFromMicros(mdt + reach) };
|
|
59
85
|
}
|
|
60
86
|
values = { mdt: body.stamp, d: type.d, ml, pml, xl: type.xl, pxl: type.pxl };
|
|
61
87
|
}
|
|
@@ -79,8 +105,10 @@ export function evaluateKramTime(db, policy, inspection, authentication, receive
|
|
|
79
105
|
const opener = timestampMicros(xdt);
|
|
80
106
|
if (opener === null)
|
|
81
107
|
throw new Error("Corrupt KRAM transaction opener");
|
|
82
|
-
|
|
83
|
-
|
|
108
|
+
const closing = opener + BigInt(values.xl) * 1000n;
|
|
109
|
+
if (!cache && !within(mdt, opener, closing)) {
|
|
110
|
+
return { kind: "drop", reason: "lifetime", deadline: timestampFromMicros(closing) };
|
|
111
|
+
}
|
|
84
112
|
const complete = authentication.kind !== "multi" || authentication.complete;
|
|
85
113
|
const rdt = cache instanceof TxnMsgCacheRecord && cache.rdt ? cache.rdt : complete ? receiverTime : "";
|
|
86
114
|
return {
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import { makeNowIso8601, timestampMicros } from "../../time/mod.js";
|
|
2
2
|
import { Deck } from "../deck.js";
|
|
3
|
+
import { consoleLogger } from "../logger.js";
|
|
3
4
|
import { KramPolicy } from "./kram-policy.js";
|
|
4
5
|
import { KramPruner } from "./kram-pruning.js";
|
|
5
6
|
import { stageKram } from "./kram-staging.js";
|
|
7
|
+
import { isKramLateReason, kramExchangeId } from "./kram-time.js";
|
|
6
8
|
/** Explicit receiver-owned cache and exemption configuration. */
|
|
7
9
|
export { DEFAULT_KRAM_CACHES } from "./kram-policy.js";
|
|
8
10
|
/** Optional receiver intake returns ordinary decisions without owning parser input. */
|
|
9
11
|
export class Kramer {
|
|
10
|
-
constructor(db, { config = {}, cues = new Deck(), now = Date.now } = {}) {
|
|
12
|
+
constructor(db, { config = {}, cues = new Deck(), now = Date.now } = {}, logger = consoleLogger) {
|
|
11
13
|
Object.defineProperty(this, "db", {
|
|
12
14
|
enumerable: true,
|
|
13
15
|
configurable: true,
|
|
14
16
|
writable: true,
|
|
15
17
|
value: db
|
|
16
18
|
});
|
|
19
|
+
Object.defineProperty(this, "logger", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: logger
|
|
24
|
+
});
|
|
17
25
|
/** Shared runtime cues, or the standalone caller's drainable receiver-state notifications. */
|
|
18
26
|
Object.defineProperty(this, "cues", {
|
|
19
27
|
enumerable: true,
|
|
@@ -42,7 +50,7 @@ export class Kramer {
|
|
|
42
50
|
this.now = now;
|
|
43
51
|
this.policy = new KramPolicy(db, config, now);
|
|
44
52
|
this.cues = cues;
|
|
45
|
-
this.pruner = new KramPruner(db, this.policy);
|
|
53
|
+
this.pruner = new KramPruner(db, this.policy, logger);
|
|
46
54
|
}
|
|
47
55
|
/** Decide intake synchronously; protocol dispatch consumes the returned evidence. */
|
|
48
56
|
intake(envelope) {
|
|
@@ -64,8 +72,56 @@ export class Kramer {
|
|
|
64
72
|
q: result.sn === null ? {} : { s: result.sn.toString(16) },
|
|
65
73
|
});
|
|
66
74
|
}
|
|
75
|
+
if (result.kind === "drop") {
|
|
76
|
+
this.report(envelope, result);
|
|
77
|
+
const cue = this.refusedCue(envelope, result);
|
|
78
|
+
if (cue)
|
|
79
|
+
this.cues.push(cue);
|
|
80
|
+
}
|
|
67
81
|
return result;
|
|
68
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Describe a refusal the application may want to hear about, or null. Staging has already committed. A local
|
|
85
|
+
* caller receives its own refusal, and only the reasons KRAM decides after authentication are safe to signal:
|
|
86
|
+
* `timestamp`, `replay` and `authentication` are reachable by anyone who can post bytes.
|
|
87
|
+
*/
|
|
88
|
+
refusedCue(envelope, refusal) {
|
|
89
|
+
if (envelope.local || !isKramLateReason(refusal.reason))
|
|
90
|
+
return null;
|
|
91
|
+
const { serder } = envelope;
|
|
92
|
+
const xid = kramExchangeId(serder), deadline = "deadline" in refusal ? refusal.deadline : undefined;
|
|
93
|
+
return {
|
|
94
|
+
kin: "kramRefused",
|
|
95
|
+
said: serder.said,
|
|
96
|
+
sender: serder.pre,
|
|
97
|
+
ilk: serder.ilk,
|
|
98
|
+
route: serder.route,
|
|
99
|
+
reason: refusal.reason,
|
|
100
|
+
stamp: serder.stamp,
|
|
101
|
+
...(deadline === undefined ? {} : { deadline }),
|
|
102
|
+
...(xid ? { xid } : {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Report one refused message. Replay is routine (every duplicate delivery lands here), so it logs at debug;
|
|
107
|
+
* the other reasons mean a sender or clock is misbehaving and log at warn. Dispatch does not log again.
|
|
108
|
+
*/
|
|
109
|
+
report(envelope, refusal) {
|
|
110
|
+
const { serder } = envelope;
|
|
111
|
+
const fields = {
|
|
112
|
+
said: serder.said,
|
|
113
|
+
sender: serder.pre,
|
|
114
|
+
ilk: serder.ilk,
|
|
115
|
+
route: serder.route,
|
|
116
|
+
reason: refusal.reason,
|
|
117
|
+
stamp: serder.stamp,
|
|
118
|
+
deadline: "deadline" in refusal ? refusal.deadline : undefined,
|
|
119
|
+
};
|
|
120
|
+
if (refusal.reason === "replay")
|
|
121
|
+
this.logger.debug("KRAM refused message", fields);
|
|
122
|
+
else
|
|
123
|
+
this.logger.warn("KRAM refused message", fields);
|
|
124
|
+
}
|
|
69
125
|
/** Reconfigure cache windows through the durable policy transition facility. */
|
|
70
126
|
changeConfig(caches, delay) {
|
|
71
127
|
this.policy.change(caches, delay);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Diger, NumberPrimitive, Prefixer, Siger } from "cesr-ts";
|
|
2
|
+
import { timestampMicros } from "../time/mod.js";
|
|
3
3
|
import { Deck } from "./deck.js";
|
|
4
4
|
import { TransIdxSigGroup } from "./dispatch.js";
|
|
5
5
|
import { ValidationError } from "./errors.js";
|
|
6
6
|
import { acceptEscrow, dropEscrow, keepEscrow, logEscrowDecision, } from "./kel/kever-decisions.js";
|
|
7
7
|
import { EndpointRecord, LocationRecord } from "./records.js";
|
|
8
8
|
import { isRole } from "./roles.js";
|
|
9
|
+
import { readDater, readPrefixer, readUrl } from "./wire-fields.js";
|
|
9
10
|
/** Builder for one accepted live reply decision. */
|
|
10
11
|
export function acceptReplyDecision() {
|
|
11
12
|
return { kind: "accept" };
|
|
@@ -276,6 +277,7 @@ Object.defineProperty(Router, "defaultResourceFunc", {
|
|
|
276
277
|
* - persist accepted reply artifacts into `sdts.`, `rpys.`, `scgs.`, `ssgs.`
|
|
277
278
|
* - escrow partially verifiable transferable replies into `rpes.`
|
|
278
279
|
* - emit `query` cues when missing signer state blocks verification
|
|
280
|
+
* - emit an `endRoleSaved` cue after an accepted `/end/role` reply commits (TypeScript-only; KERIpy has no consumer)
|
|
279
281
|
*
|
|
280
282
|
* Current `keri-ts` differences:
|
|
281
283
|
* - only the Gate E bootstrap reply families are currently routed
|
|
@@ -321,17 +323,6 @@ export class Revery {
|
|
|
321
323
|
writable: true,
|
|
322
324
|
value: void 0
|
|
323
325
|
});
|
|
324
|
-
/**
|
|
325
|
-
* Told after an `/end/role` reply is accepted, with the controller, role, endpoint and whether the role is
|
|
326
|
-
* allowed. The runtime uses it to have the mailbox poller and KEL publisher look at a controller whose mailbox
|
|
327
|
-
* role just changed, rather than at their next sweep.
|
|
328
|
-
*/
|
|
329
|
-
Object.defineProperty(this, "onEndRole", {
|
|
330
|
-
enumerable: true,
|
|
331
|
-
configurable: true,
|
|
332
|
-
writable: true,
|
|
333
|
-
value: void 0
|
|
334
|
-
});
|
|
335
326
|
this.db = db;
|
|
336
327
|
this.rtr = rtr ?? new Router();
|
|
337
328
|
this.cues = cues ?? new Deck();
|
|
@@ -404,11 +395,10 @@ export class Revery {
|
|
|
404
395
|
// Same-call groups see the last staged pin exactly as they previously saw the durable write.
|
|
405
396
|
const staged = new Map();
|
|
406
397
|
const groupKey = (pre, snh, said) => `${pre}.${snh}.${said}`;
|
|
407
|
-
const
|
|
408
|
-
if (
|
|
409
|
-
throw new ValidationError("Reply message is missing 'dt'.");
|
|
398
|
+
const dater = readDater(args.serder.ked?.dt);
|
|
399
|
+
if (!dater) {
|
|
400
|
+
throw new ValidationError("Reply message is missing a valid 'dt'.");
|
|
410
401
|
}
|
|
411
|
-
const dater = new Dater({ qb64: encodeDateTimeToDater(daterText) });
|
|
412
402
|
const odater = args.osaider ? this.db.sdts.get([args.osaider.qb64]) : null;
|
|
413
403
|
let pending = false;
|
|
414
404
|
const escrow = (material) => {
|
|
@@ -802,7 +792,8 @@ export class BasicReplyRouteHandler {
|
|
|
802
792
|
* - derives the BADA route base `/end/role`
|
|
803
793
|
* - applies same-SAID idempotence before acceptance
|
|
804
794
|
* - evaluates proof and updates proof/`eans.`/`ends.` in its own transaction
|
|
805
|
-
* - publishes queries only after that commit; caller-owned
|
|
795
|
+
* - publishes queries, then an `endRoleSaved` cue for an accepted reply, only after that commit; caller-owned
|
|
796
|
+
* writers are refused
|
|
806
797
|
*/
|
|
807
798
|
processReplyEndRole(args) {
|
|
808
799
|
const allowed = args.route.startsWith("/end/role/add")
|
|
@@ -817,11 +808,11 @@ export class BasicReplyRouteHandler {
|
|
|
817
808
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
818
809
|
throw new ValidationError("Missing one of cid/role/eid in /end/role reply.");
|
|
819
810
|
}
|
|
820
|
-
const cid = "cid" in data
|
|
811
|
+
const cid = "cid" in data ? readPrefixer(data.cid)?.qb64 ?? null : null;
|
|
821
812
|
const role = "role" in data && typeof data.role === "string" ? data.role : null;
|
|
822
|
-
const eid = "eid" in data
|
|
813
|
+
const eid = "eid" in data ? readPrefixer(data.eid)?.qb64 ?? null : null;
|
|
823
814
|
if (!cid || !role || !eid) {
|
|
824
|
-
throw new ValidationError("Missing one of cid/role/eid in /end/role reply.");
|
|
815
|
+
throw new ValidationError("Missing or malformed one of cid/role/eid in /end/role reply.");
|
|
825
816
|
}
|
|
826
817
|
if (!isRole(role)) {
|
|
827
818
|
throw new ValidationError(`Invalid endpoint role ${role}.`);
|
|
@@ -855,7 +846,7 @@ export class BasicReplyRouteHandler {
|
|
|
855
846
|
if (queryCues.length)
|
|
856
847
|
this.rvy.cues.extend(queryCues);
|
|
857
848
|
if (decision.kind === "accept")
|
|
858
|
-
this.rvy.
|
|
849
|
+
this.rvy.cues.push({ kin: "endRoleSaved", cid, role, eid, allowed });
|
|
859
850
|
return decision;
|
|
860
851
|
}
|
|
861
852
|
/**
|
|
@@ -877,14 +868,16 @@ export class BasicReplyRouteHandler {
|
|
|
877
868
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
878
869
|
throw new ValidationError("Missing one of eid/scheme/url in /loc/scheme reply.");
|
|
879
870
|
}
|
|
880
|
-
const eid = "eid" in data
|
|
871
|
+
const eid = "eid" in data ? readPrefixer(data.eid)?.qb64 ?? null : null;
|
|
881
872
|
const scheme = "scheme" in data && typeof data.scheme === "string" ? data.scheme : null;
|
|
882
873
|
const url = "url" in data && typeof data.url === "string" ? data.url : null;
|
|
883
874
|
if (!eid || !scheme || url === null) {
|
|
884
|
-
throw new ValidationError("Missing one of eid/scheme/url in /loc/scheme reply.");
|
|
875
|
+
throw new ValidationError("Missing or malformed one of eid/scheme/url in /loc/scheme reply.");
|
|
885
876
|
}
|
|
886
877
|
if (url) {
|
|
887
|
-
const parsed =
|
|
878
|
+
const parsed = readUrl(url);
|
|
879
|
+
if (!parsed)
|
|
880
|
+
throw new ValidationError(`Unparseable URL ${url} in /loc/scheme reply.`);
|
|
888
881
|
if (parsed.protocol && parsed.protocol.slice(0, -1) !== scheme) {
|
|
889
882
|
throw new ValidationError(`URL ${url} does not match declared scheme ${scheme}.`);
|
|
890
883
|
}
|
|
@@ -129,14 +129,21 @@ function parseSchemaJson(raw) {
|
|
|
129
129
|
}
|
|
130
130
|
return parsed;
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Decode credential data for validation. The bytes are a remote credential's body, which need not be JSON (a CBOR or
|
|
134
|
+
* MGPK ACDC); data that does not decode fails validation as a `ValidationError`, like KERIpy's `json.loads` failure.
|
|
135
|
+
*/
|
|
132
136
|
function parseSchemaData(raw) {
|
|
133
|
-
if (raw instanceof Uint8Array)
|
|
134
|
-
return
|
|
137
|
+
if (!(raw instanceof Uint8Array) && typeof raw !== "string")
|
|
138
|
+
return raw;
|
|
139
|
+
try {
|
|
140
|
+
return JSON.parse(typeof raw === "string" ? raw : new TextDecoder().decode(raw));
|
|
135
141
|
}
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (!(error instanceof SyntaxError))
|
|
144
|
+
throw error;
|
|
145
|
+
throw new ValidationError(`Credential data is not JSON: ${error.message}`);
|
|
138
146
|
}
|
|
139
|
-
return raw;
|
|
140
147
|
}
|
|
141
148
|
function verifySchemaSaid(sed, kind) {
|
|
142
149
|
const id = sed.$id;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guarded readers for the field values of remote messages.
|
|
3
|
+
*
|
|
4
|
+
* A remote message can parse and verify its SAID while a field value is still not a valid value for its type: a
|
|
5
|
+
* non-hex or over-wide sequence number, a key that is not a verification key, a next-key digest that is not a digest,
|
|
6
|
+
* a prefix that is not a prefix. The cesr accessors and primitive constructors throw on such values, and a throw out
|
|
7
|
+
* of a processor ends its loop (the mailbox poller, the escrow doer, HTTP ingress). Processor boundaries therefore read
|
|
8
|
+
* remote field values through these readers and turn a null into their own typed refusal (ADR-0005) before any escrow
|
|
9
|
+
* decision or store write: `Kevery.decideEvent` and `processReceipt`, the reply route handlers and `Revery`'s
|
|
10
|
+
* datetime read, `Exchanger` admission and `queryEnvelopeFromDispatch`. Accepted KEL events are remote-authored too:
|
|
11
|
+
* their raw `a` seals are read through `readEventSeal` wherever an anchor is searched.
|
|
12
|
+
*
|
|
13
|
+
* Only the cesr codec's own malformed-input taxonomy (`ParserError` and its subclasses) is read as malformed input, and
|
|
14
|
+
* only while constructing a primitive from a string the message carried. That construction reads no store, so a
|
|
15
|
+
* `ParserError` there cannot be corrupt durable state or I/O; every other error propagates. Numbers are checked as
|
|
16
|
+
* text before any `BigInt` conversion, so cesr's `SyntaxError`/`RangeError` for bad hex is never relied on.
|
|
17
|
+
*/
|
|
18
|
+
import { Dater, Diger, Ilks, NumberPrimitive, ParserError, Prefixer, SealEvent, Verfer, } from "cesr-ts";
|
|
19
|
+
import { encodeDateTimeToDater } from "../time/mod.js";
|
|
20
|
+
/** Construct `Type` from qualified text, or null when the text is not a valid value of that type. */
|
|
21
|
+
function readQualified(Type, value) {
|
|
22
|
+
if (typeof value !== "string" || !value)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
return new Type({ qb64: value });
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof ParserError)
|
|
29
|
+
return null;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Read an identifier prefix (`i`, `di`, `cid`, `eid`, ...), or null when it is not one. */
|
|
34
|
+
export function readPrefixer(value) {
|
|
35
|
+
return readQualified(Prefixer, value);
|
|
36
|
+
}
|
|
37
|
+
/** Read a digest (`d` of a key state notice, a next-key digest), or null when it is not one. */
|
|
38
|
+
export function readDiger(value) {
|
|
39
|
+
return readQualified(Diger, value);
|
|
40
|
+
}
|
|
41
|
+
/** Read a verification key (a current key, a witness), or null when it is not one. */
|
|
42
|
+
export function readVerfer(value) {
|
|
43
|
+
return readQualified(Verfer, value);
|
|
44
|
+
}
|
|
45
|
+
/** Read a datetime (`dt`) as a Dater, or null when it does not encode as one. */
|
|
46
|
+
export function readDater(value) {
|
|
47
|
+
return typeof value === "string" ? readQualified(Dater, encodeDateTimeToDater(value)) : null;
|
|
48
|
+
}
|
|
49
|
+
/** Read a URL, or null when it does not parse. */
|
|
50
|
+
export function readUrl(value) {
|
|
51
|
+
return URL.canParse(value) ? new URL(value) : null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Read a KERI ordinal: lower- or upper-case hex text, or a non-negative integer number or bigint, as cesr's numeric
|
|
55
|
+
* ladder accepts them. Null when it is none of those or exceeds `Number.MAX_SAFE_INTEGER`, the largest value the KEL
|
|
56
|
+
* processors can compare exactly as a number.
|
|
57
|
+
*/
|
|
58
|
+
export function readOrdinal(value) {
|
|
59
|
+
let ordinal;
|
|
60
|
+
if (typeof value === "string") {
|
|
61
|
+
if (!/^[0-9a-f]+$/iu.test(value))
|
|
62
|
+
return null;
|
|
63
|
+
ordinal = BigInt(`0x${value}`);
|
|
64
|
+
}
|
|
65
|
+
else if (typeof value === "number") {
|
|
66
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
67
|
+
return null;
|
|
68
|
+
ordinal = BigInt(value);
|
|
69
|
+
}
|
|
70
|
+
else if (typeof value === "bigint") {
|
|
71
|
+
ordinal = value;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return ordinal >= 0n && ordinal <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(ordinal) : null;
|
|
77
|
+
}
|
|
78
|
+
/** Read a message's `s`, which cesr's `sner` reads only as hex text; null when absent or malformed. */
|
|
79
|
+
export function readSequenceNumber(serder) {
|
|
80
|
+
const sn = serder.ked?.s;
|
|
81
|
+
return typeof sn === "string" ? readOrdinal(sn) : null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Name the first field of a KEL event whose value the key-state machine would fail to decode, or null when all decode.
|
|
85
|
+
*
|
|
86
|
+
* Covers what `Kever` and escrow persistence decode beyond `i` and `s` (which `decideEvent` reads first): current keys
|
|
87
|
+
* `k` and next-key digests `n` of establishment events, witnesses `b` and witness changes `br`/`ba`, the witness
|
|
88
|
+
* threshold `bt`, and the delegator `di` of a delegated inception. Thresholds (`kt`, `nt`) already decode to null and are
|
|
89
|
+
* rejected by `Kever`; raw seals in `a` are not decoded here.
|
|
90
|
+
*/
|
|
91
|
+
export function kelEventFieldDefect(serder) {
|
|
92
|
+
const ked = serder.ked ?? {};
|
|
93
|
+
const ilk = serder.ilk;
|
|
94
|
+
const bad = (label, values, read) => {
|
|
95
|
+
const value = values.find((entry) => read(entry) === null);
|
|
96
|
+
return value === undefined ? null : `${label} ${JSON.stringify(value)}`;
|
|
97
|
+
};
|
|
98
|
+
return bad("key", serder.keys, readVerfer)
|
|
99
|
+
?? bad("next key digest", serder.ndigs, readDiger)
|
|
100
|
+
?? bad("witness", serder.backs, readVerfer)
|
|
101
|
+
?? bad("witness cut", serder.cuts, readVerfer)
|
|
102
|
+
?? bad("witness add", serder.adds, readVerfer)
|
|
103
|
+
?? (ked.bt !== undefined && ked.bt !== null && readOrdinal(ked.bt) === null
|
|
104
|
+
? `witness threshold ${JSON.stringify(ked.bt)}`
|
|
105
|
+
: null)
|
|
106
|
+
?? (ilk === Ilks.dip && serder.delpre !== null && !readPrefixer(serder.delpre)
|
|
107
|
+
? `delegator ${JSON.stringify(serder.delpre)}`
|
|
108
|
+
: null);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Read one raw `a` entry as an event seal `{i, s, d}`, or null when it is another seal shape or carries a value that
|
|
112
|
+
* does not decode.
|
|
113
|
+
*
|
|
114
|
+
* An accepted event's seals are not value-checked at acceptance (KERIpy keeps them as raw dicts), so any identifier can
|
|
115
|
+
* anchor junk in its own KEL. Anchor searches (delegation approval, sealing-event lookup) therefore read each entry
|
|
116
|
+
* through this reader and skip a null instead of throwing. KERIpy filters by the same field shape
|
|
117
|
+
* (`tuple(seal) == SealEvent._fields`) and compares raw strings, so a malformed value can never match a real event.
|
|
118
|
+
*/
|
|
119
|
+
export function readEventSeal(value) {
|
|
120
|
+
if (!SealEvent.isSad(value))
|
|
121
|
+
return null;
|
|
122
|
+
const pre = readPrefixer(value.i);
|
|
123
|
+
const sn = readOrdinal(value.s);
|
|
124
|
+
const said = readDiger(value.d);
|
|
125
|
+
return pre && sn !== null && said ? SealEvent.fromTuple([pre, NumberPrimitive.fromNum(BigInt(sn)), said]) : null;
|
|
126
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/** KERI event-log databaser built on `LMDBer` composition. */
|
|
2
|
-
import { b, Cigar, Dater, Diger, Ilks, Labeler, Noncer, NumberPrimitive, NumDex, Prefixer, Saider,
|
|
2
|
+
import { b, Cigar, Dater, Diger, Ilks, Labeler, Noncer, NumberPrimitive, NumDex, Prefixer, Saider, SerderKERI, Siger, t, Texter, Verfer, Verser, Vrsn_1_0, } from "cesr-ts";
|
|
3
3
|
import { action } from "effection";
|
|
4
4
|
import { attachmentCounterPayloadQb64b, attachmentCounterQb64b, attachmentOrdinal, resolveAttachmentGvrsn, } from "../core/attachment-countering.js";
|
|
5
5
|
import { DatabaseOperationError, ValidationError } from "../core/errors.js";
|
|
6
6
|
import { Kever } from "../core/kel/kever.js";
|
|
7
7
|
import { consoleLogger } from "../core/logger.js";
|
|
8
8
|
import { CacheTypeRecord, EndpointRecord, EventSourceRecord, HabitatRecord, KelPublishedRecord, KeyStateRecord, LocationRecord, MsgCacheRecord, ObservedRecord, OobiRecord, RootSentRecord, TopicsRecord, TxnMsgCacheRecord, WellKnownAuthN, } from "../core/records.js";
|
|
9
|
+
import { readEventSeal } from "../core/wire-fields.js";
|
|
9
10
|
import { encodeDateTimeToDater, makeNowIso8601 } from "../time/mod.js";
|
|
10
11
|
import { dgKey, splitOnKey } from "./core/keys.js";
|
|
11
12
|
import { StoreOwner } from "./core/store-owner.js";
|
|
@@ -29,19 +30,9 @@ function isEventSealRecord(value) {
|
|
|
29
30
|
&& value.s instanceof NumberPrimitive
|
|
30
31
|
&& value.d instanceof Diger;
|
|
31
32
|
}
|
|
33
|
+
/** Accept an already-typed event seal, or read a raw `{i, s, d}` entry; null for other shapes or bad values. */
|
|
32
34
|
function normalizeEventSeal(value) {
|
|
33
|
-
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
|
-
if (!SealEvent.isSad(value)) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
return SealEvent.fromSad(value);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
35
|
+
return isEventSealRecord(value) ? value : readEventSeal(value);
|
|
45
36
|
}
|
|
46
37
|
function eventSealsEqual(left, right) {
|
|
47
38
|
return left.i.qb64 === right.i.qb64
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
*
|
|
26
26
|
* Another tab's upgrade or delete fires `versionchange` here. The connection
|
|
27
27
|
* closes at once so that tab is never blocked, and this adapter is then lost:
|
|
28
|
-
* its next `load` or `write` rejects,
|
|
29
|
-
*
|
|
28
|
+
* its next `load` or `write` rejects, and so does every retry of it (a write never
|
|
29
|
+
* reconnects), which latches the queue, because writing on after another context
|
|
30
|
+
* deleted the database would break prefix durability.
|
|
30
31
|
* Recovery is a new open, whose `meta` reconnects. Single-writer exclusion is
|
|
31
32
|
* the composition's job, not this adapter's.
|
|
32
33
|
*
|
|
@@ -254,6 +254,13 @@ export class LMDBer extends BaseDBer {
|
|
|
254
254
|
writable: true,
|
|
255
255
|
value: false
|
|
256
256
|
});
|
|
257
|
+
/** Write attempts made through this handle; a process-local counter, so reading it touches no store. */
|
|
258
|
+
Object.defineProperty(this, "commits", {
|
|
259
|
+
enumerable: true,
|
|
260
|
+
configurable: true,
|
|
261
|
+
writable: true,
|
|
262
|
+
value: 0
|
|
263
|
+
});
|
|
257
264
|
this.defaults = { ...LMDBER_DEFAULTS, ...defaults };
|
|
258
265
|
this.logger = options.logger ?? consoleLogger;
|
|
259
266
|
this.configuredMapSize = options.mapSize;
|
|
@@ -482,7 +489,7 @@ export class LMDBer extends BaseDBer {
|
|
|
482
489
|
setVer(val) {
|
|
483
490
|
const env = this.requireEnv();
|
|
484
491
|
try {
|
|
485
|
-
env.transactionSync(() => {
|
|
492
|
+
this.writing(env).transactionSync(() => {
|
|
486
493
|
env.putSync(b("__version__"), b(val));
|
|
487
494
|
});
|
|
488
495
|
}
|
|
@@ -521,6 +528,7 @@ export class LMDBer extends BaseDBer {
|
|
|
521
528
|
}
|
|
522
529
|
/** Run `fn` in one native synchronous write transaction; nesting is a child transaction. */
|
|
523
530
|
transaction(fn) {
|
|
531
|
+
// A transaction is not itself a write: the puts and removes inside it count, so one that writes nothing does not.
|
|
524
532
|
return this.requireEnv().transactionSync(fn);
|
|
525
533
|
}
|
|
526
534
|
/**
|
|
@@ -538,6 +546,15 @@ export class LMDBer extends BaseDBer {
|
|
|
538
546
|
yield { key, value: copyDbBytes(entry.value) };
|
|
539
547
|
}
|
|
540
548
|
}
|
|
549
|
+
/** Count one write attempt through this handle and return the environment to write through. */
|
|
550
|
+
writing(env) {
|
|
551
|
+
this.commits++;
|
|
552
|
+
return env;
|
|
553
|
+
}
|
|
554
|
+
/** Counts this handle's own writes without reading the store; other processes' commits are not seen. */
|
|
555
|
+
get writeActivity() {
|
|
556
|
+
return this.commits;
|
|
557
|
+
}
|
|
541
558
|
/** LMDB's committed transaction ID, which also counts other processes' commits. */
|
|
542
559
|
get writeVersion() {
|
|
543
560
|
const version = this.requireEnv().getStats().lastTxnId;
|
|
@@ -610,9 +627,11 @@ export class LMDBer extends BaseDBer {
|
|
|
610
627
|
}
|
|
611
628
|
putRow(table, key, val) {
|
|
612
629
|
assertRowFits(table, key, val);
|
|
630
|
+
this.commits++;
|
|
613
631
|
lmdbTable(table).putSync(key, val);
|
|
614
632
|
}
|
|
615
633
|
removeRow(table, key, val) {
|
|
634
|
+
this.commits++;
|
|
616
635
|
return val === undefined ? lmdbTable(table).removeSync(key) : lmdbTable(table).removeSync(key, val);
|
|
617
636
|
}
|
|
618
637
|
/*
|
|
@@ -632,7 +651,7 @@ export class LMDBer extends BaseDBer {
|
|
|
632
651
|
const db = lmdbTable(table);
|
|
633
652
|
const env = this.requireEnv();
|
|
634
653
|
try {
|
|
635
|
-
const result = env.transactionSync(() => {
|
|
654
|
+
const result = this.writing(env).transactionSync(() => {
|
|
636
655
|
const existing = db.get(key);
|
|
637
656
|
if (existing !== null && existing !== undefined) {
|
|
638
657
|
return false;
|
|
@@ -657,7 +676,7 @@ export class LMDBer extends BaseDBer {
|
|
|
657
676
|
const db = lmdbTable(table);
|
|
658
677
|
const env = this.requireEnv();
|
|
659
678
|
try {
|
|
660
|
-
env.transactionSync(() => {
|
|
679
|
+
this.writing(env).transactionSync(() => {
|
|
661
680
|
assertRowFits(db, key, val);
|
|
662
681
|
db.put(key, val);
|
|
663
682
|
});
|
|
@@ -700,7 +719,7 @@ export class LMDBer extends BaseDBer {
|
|
|
700
719
|
const db = lmdbTable(table);
|
|
701
720
|
const env = this.requireEnv();
|
|
702
721
|
try {
|
|
703
|
-
const result = env.transactionSync(() => {
|
|
722
|
+
const result = this.writing(env).transactionSync(() => {
|
|
704
723
|
if (val !== undefined) {
|
|
705
724
|
// An empty key must still fail, but an over-long one can only be absent.
|
|
706
725
|
if (key.length > MAX_KEY_BYTES)
|
|
@@ -809,7 +828,7 @@ export class LMDBer extends BaseDBer {
|
|
|
809
828
|
if (keys.length === 0) {
|
|
810
829
|
return false;
|
|
811
830
|
}
|
|
812
|
-
env.transactionSync(() => {
|
|
831
|
+
this.writing(env).transactionSync(() => {
|
|
813
832
|
for (const key of keys) {
|
|
814
833
|
db.remove(key);
|
|
815
834
|
}
|
|
@@ -870,7 +889,7 @@ export class LMDBer extends BaseDBer {
|
|
|
870
889
|
let result = true;
|
|
871
890
|
const existing = new Set(this.getVals(db, key).map((val) => bytesHex(val)));
|
|
872
891
|
const env = this.requireEnv();
|
|
873
|
-
env.transactionSync(() => {
|
|
892
|
+
this.writing(env).transactionSync(() => {
|
|
874
893
|
for (const val of vals) {
|
|
875
894
|
const vh = bytesHex(val);
|
|
876
895
|
if (existing.has(vh)) {
|
|
@@ -173,6 +173,10 @@ export class MemStore extends BaseDBer {
|
|
|
173
173
|
this.assertOpen();
|
|
174
174
|
return __classPrivateFieldGet(this, _MemStore_writeVersion, "f");
|
|
175
175
|
}
|
|
176
|
+
/** Same count as {@link writeVersion}; in memory, so reading it is free. */
|
|
177
|
+
get writeActivity() {
|
|
178
|
+
return __classPrivateFieldGet(this, _MemStore_writeVersion, "f");
|
|
179
|
+
}
|
|
176
180
|
/** Open or reopen the contents. A readonly open of never-created contents reports `false`. */
|
|
177
181
|
*reopen(options = {}) {
|
|
178
182
|
this.readonly = options.readonly ?? this.readonly;
|