@optimystic/db-p2p 0.22.0 → 0.24.1
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/dist/src/cluster/client.d.ts +10 -0
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +30 -1
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +39 -14
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +226 -119
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/service.d.ts +32 -1
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +43 -2
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cohort-topic/host.js +34 -11
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +37 -7
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +77 -19
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +9 -3
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +88 -2
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +134 -28
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +25 -1
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/logger.d.ts +17 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +19 -2
- package/dist/src/logger.js.map +1 -1
- package/dist/src/matchmaking/query-transport.js +3 -3
- package/dist/src/matchmaking/query-transport.js.map +1 -1
- package/dist/src/owned-block-seed.d.ts +6 -3
- package/dist/src/owned-block-seed.d.ts.map +1 -1
- package/dist/src/owned-block-seed.js +16 -3
- package/dist/src/owned-block-seed.js.map +1 -1
- package/dist/src/peer-address-book.d.ts +72 -0
- package/dist/src/peer-address-book.d.ts.map +1 -0
- package/dist/src/peer-address-book.js +123 -0
- package/dist/src/peer-address-book.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts +4 -4
- package/dist/src/reactivity/notify-transport.js +6 -6
- package/dist/src/reactivity/notify-transport.js.map +1 -1
- package/dist/src/reactivity/push-state-gossip.js +2 -2
- package/dist/src/reactivity/push-state-gossip.js.map +1 -1
- package/dist/src/reactivity/recover-transport.d.ts +6 -2
- package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
- package/dist/src/reactivity/recover-transport.js +7 -3
- package/dist/src/reactivity/recover-transport.js.map +1 -1
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +11 -2
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +30 -0
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +95 -3
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +62 -9
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +242 -73
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/rn.d.ts +3 -0
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +3 -0
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +83 -0
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
- package/dist/src/storage/cached-raw-storage.js +152 -0
- package/dist/src/storage/cached-raw-storage.js.map +1 -0
- package/dist/src/storage/cached-store-driver.d.ts +186 -0
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
- package/dist/src/storage/cached-store-driver.js +775 -0
- package/dist/src/storage/cached-store-driver.js.map +1 -0
- package/dist/src/storage/i-raw-storage.d.ts +12 -5
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +234 -0
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
- package/dist/src/storage/shared-cache-pool.js +354 -0
- package/dist/src/storage/shared-cache-pool.js.map +1 -0
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts +13 -6
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/cohort-topic-mesh-harness.js +15 -6
- package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +35 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +3 -3
- package/readme.md +668 -668
- package/src/cluster/block-transfer.ts +424 -424
- package/src/cluster/client.ts +119 -88
- package/src/cluster/cluster-error.ts +64 -64
- package/src/cluster/cluster-policy.ts +203 -203
- package/src/cluster/cluster-repo.ts +242 -122
- package/src/cluster/cluster-size-coupling.ts +45 -45
- package/src/cluster/commit-cert.ts +139 -139
- package/src/cluster/i-transaction-state-store.ts +43 -43
- package/src/cluster/memory-transaction-state-store.ts +56 -56
- package/src/cluster/peer-key-binding.ts +37 -37
- package/src/cluster/persistent-transaction-state-store.ts +92 -92
- package/src/cluster/quorum-restore.ts +223 -223
- package/src/cluster/reconcile-block.ts +203 -203
- package/src/cluster/service.ts +293 -241
- package/src/cluster/supermajority-coupling.ts +37 -37
- package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
- package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
- package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
- package/src/cohort-topic/change-bridge.ts +109 -109
- package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
- package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
- package/src/cohort-topic/fret-trust-anchor.ts +153 -153
- package/src/cohort-topic/host.ts +42 -11
- package/src/cohort-topic/index.ts +13 -13
- package/src/cohort-topic/membership-publish-sink.ts +20 -20
- package/src/cohort-topic/membership-source.ts +68 -68
- package/src/cohort-topic/peer-codec.ts +31 -31
- package/src/cohort-topic/peer-sig.ts +86 -86
- package/src/cohort-topic/protocols.ts +71 -71
- package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
- package/src/cohort-topic/size-estimator.ts +16 -16
- package/src/cohort-topic/stream-util.ts +79 -19
- package/src/cohort-topic/threshold-crypto.ts +239 -239
- package/src/cohort-topic/topic-router.ts +77 -77
- package/src/dispute/arbitrator-selection.ts +138 -138
- package/src/dispute/cascade.ts +524 -524
- package/src/dispute/dispute-service.ts +11 -5
- package/src/dispute/invalidation.ts +625 -625
- package/src/inbound-authorization.ts +190 -190
- package/src/index.ts +52 -49
- package/src/libp2p-key-network.ts +1120 -990
- package/src/libp2p-node-base.ts +1675 -1651
- package/src/libp2p-node-rn.ts +30 -30
- package/src/libp2p-node.ts +36 -36
- package/src/logger.ts +19 -2
- package/src/matchmaking/aggregate-counts.ts +104 -104
- package/src/matchmaking/index.ts +20 -20
- package/src/matchmaking/module.ts +363 -363
- package/src/matchmaking/protocols.ts +51 -51
- package/src/matchmaking/provider-manager.ts +95 -95
- package/src/matchmaking/query-handler.ts +88 -88
- package/src/matchmaking/query-transport.ts +3 -3
- package/src/matchmaking/seeker-manager.ts +64 -64
- package/src/matchmaking/seeker-walk-client.ts +293 -293
- package/src/matchmaking/traffic-validation.ts +195 -195
- package/src/optimystic-node.ts +36 -36
- package/src/owned-block-seed.ts +53 -40
- package/src/peer-address-book.ts +149 -0
- package/src/protocol-limits.ts +33 -33
- package/src/reactivity/forwarder-host.ts +438 -438
- package/src/reactivity/index.ts +19 -19
- package/src/reactivity/notify-transport.ts +144 -144
- package/src/reactivity/origination-manager.ts +192 -192
- package/src/reactivity/protocols.ts +61 -61
- package/src/reactivity/push-state-gossip.ts +291 -291
- package/src/reactivity/recover-transport.ts +7 -3
- package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
- package/src/reactivity/subscriber-registry.ts +96 -96
- package/src/reactivity/subscription-manager.ts +450 -450
- package/src/reactivity/topic-bytes.ts +37 -37
- package/src/repo/client.ts +12 -2
- package/src/repo/cluster-coordinator.ts +99 -3
- package/src/repo/coordinator-repo.ts +281 -74
- package/src/repo/types.ts +7 -7
- package/src/rn.ts +39 -36
- package/src/rpc-deadline.ts +45 -45
- package/src/storage/arachnode-partition.ts +74 -74
- package/src/storage/cached-raw-storage.ts +180 -0
- package/src/storage/cached-store-driver.ts +859 -0
- package/src/storage/i-kv-store.ts +8 -8
- package/src/storage/i-raw-storage.ts +12 -5
- package/src/storage/kv-raw-storage.ts +135 -135
- package/src/storage/memory-kv-store.ts +28 -28
- package/src/storage/memory-storage.ts +25 -25
- package/src/storage/memory-store-driver.ts +157 -157
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +80 -80
- package/src/storage/ring-selector.ts +317 -317
- package/src/storage/ring-shift-coordinator.ts +271 -271
- package/src/storage/shared-cache-pool.ts +452 -0
- package/src/storage/storage-repo.ts +1014 -1014
- package/src/testing/cohort-topic-mesh-harness.ts +673 -663
- package/src/testing/index.ts +8 -8
- package/src/testing/matchmaking-mesh-harness.ts +475 -475
- package/src/testing/raw-storage-conformance.ts +453 -417
- package/src/testing/reactivity-mesh-harness.ts +922 -922
- package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
- package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
- package/dist/src/storage/restoration-coordinator-v2.js +0 -172
- package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reactivity — node-level subscriber registry (the socket-delivery → subscription-manager seam).
|
|
3
|
-
*
|
|
4
|
-
* A {@link import("./subscription-manager.js").ReactivitySubscriptionManager} owns the verify/dedupe/deliver
|
|
5
|
-
* path for one subscription but is transport-agnostic: it exposes `onNotification(n)` and never touches a
|
|
6
|
-
* socket. This registry is the routing table that connects a socket-delivered {@link NotificationV1} to the
|
|
7
|
-
* right manager(s): the forwarder host's `deliverLocal(topicId, n)` looks topic up here and invokes every
|
|
8
|
-
* manager registered for it, and a constructed manager registers its `onNotification` keyed by the reactivity
|
|
9
|
-
* `topicId` it subscribed under.
|
|
10
|
-
*
|
|
11
|
-
* Keyed by topicId-base64url. A manager subscribes under `topicId = reactivityTopicId(reactivityTailBytes(
|
|
12
|
-
* tailId))` (the same anchor origination derives), so a notification's topic — derived by the forwarder host
|
|
13
|
-
* from the notification's tail anchor — routes to exactly the managers watching that tail's collection.
|
|
14
|
-
*
|
|
15
|
-
* **Scope.** This delivers socket-routed notifications into a manager that was *already constructed* and
|
|
16
|
-
* registered. Constructing the manager from a Quereus `Database.watch` (the application bridge) stays the
|
|
17
|
-
* backlog item `optimystic-network-reactive-watch-integration-test`; this registry is the plug it will use.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { bytesToB64url, type NotificationV1 } from "@optimystic/db-core";
|
|
21
|
-
import { createLogger } from "../logger.js";
|
|
22
|
-
|
|
23
|
-
const log = createLogger("reactivity-subscribers");
|
|
24
|
-
|
|
25
|
-
/** A subscriber's inbound seam — a manager's `onNotification` (which returns a delivery outcome promise). */
|
|
26
|
-
export type ReactivityNotificationHandler = (n: NotificationV1) => void | Promise<unknown>;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The node's `topicId → subscriber handlers` table. Exposed on the node as `reactivitySubscribers` so a
|
|
30
|
-
* subscribe factory can register a {@link import("./subscription-manager.js").ReactivitySubscriptionManager}
|
|
31
|
-
* and have socket-delivered notifications routed to it.
|
|
32
|
-
*/
|
|
33
|
-
export class ReactivitySubscriberRegistry {
|
|
34
|
-
private readonly byTopic = new Map<string, Set<ReactivityNotificationHandler>>();
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Register `onNotification` to receive every notification the node delivers for `topicId`. Multiple
|
|
38
|
-
* managers may register for one topic (each gets every notification). Returns an idempotent unregister.
|
|
39
|
-
*/
|
|
40
|
-
register(topicId: Uint8Array, onNotification: ReactivityNotificationHandler): () => void {
|
|
41
|
-
const key = bytesToB64url(topicId);
|
|
42
|
-
let handlers = this.byTopic.get(key);
|
|
43
|
-
if (handlers === undefined) {
|
|
44
|
-
handlers = new Set();
|
|
45
|
-
this.byTopic.set(key, handlers);
|
|
46
|
-
}
|
|
47
|
-
handlers.add(onNotification);
|
|
48
|
-
return (): void => this.unregister(topicId, onNotification);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Drop a previously-registered handler; idempotent (a no-op if it was never registered / already gone). */
|
|
52
|
-
unregister(topicId: Uint8Array, onNotification: ReactivityNotificationHandler): void {
|
|
53
|
-
const key = bytesToB64url(topicId);
|
|
54
|
-
const handlers = this.byTopic.get(key);
|
|
55
|
-
if (handlers === undefined) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
handlers.delete(onNotification);
|
|
59
|
-
if (handlers.size === 0) {
|
|
60
|
-
this.byTopic.delete(key);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Deliver `n` to every handler registered for `topicId` (the forwarder host's `deliverLocal` seam). Each
|
|
66
|
-
* handler is isolated: a synchronous throw or a rejected delivery promise is logged, never propagated —
|
|
67
|
-
* one faulty subscriber must not break delivery to the rest or the fan-out loop that called this.
|
|
68
|
-
*/
|
|
69
|
-
deliver(topicId: Uint8Array, n: NotificationV1): void {
|
|
70
|
-
const handlers = this.byTopic.get(bytesToB64url(topicId));
|
|
71
|
-
if (handlers === undefined) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// Snapshot: a handler that unregisters itself on delivery would otherwise mutate the live set.
|
|
75
|
-
for (const handler of [...handlers]) {
|
|
76
|
-
try {
|
|
77
|
-
const result = handler(n);
|
|
78
|
-
if (result instanceof Promise) {
|
|
79
|
-
result.catch((err: unknown) => log("subscriber delivery rejected (isolated) for rev=%d: %o", n.revision, err));
|
|
80
|
-
}
|
|
81
|
-
} catch (err) {
|
|
82
|
-
log("subscriber handler threw (isolated) for rev=%d: %o", n.revision, err);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Number of topics with at least one registered subscriber (diagnostic / test). */
|
|
88
|
-
get topicCount(): number {
|
|
89
|
-
return this.byTopic.size;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Whether any subscriber is registered for `topicId` (diagnostic / test). */
|
|
93
|
-
has(topicId: Uint8Array): boolean {
|
|
94
|
-
return this.byTopic.has(bytesToB64url(topicId));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Reactivity — node-level subscriber registry (the socket-delivery → subscription-manager seam).
|
|
3
|
+
*
|
|
4
|
+
* A {@link import("./subscription-manager.js").ReactivitySubscriptionManager} owns the verify/dedupe/deliver
|
|
5
|
+
* path for one subscription but is transport-agnostic: it exposes `onNotification(n)` and never touches a
|
|
6
|
+
* socket. This registry is the routing table that connects a socket-delivered {@link NotificationV1} to the
|
|
7
|
+
* right manager(s): the forwarder host's `deliverLocal(topicId, n)` looks topic up here and invokes every
|
|
8
|
+
* manager registered for it, and a constructed manager registers its `onNotification` keyed by the reactivity
|
|
9
|
+
* `topicId` it subscribed under.
|
|
10
|
+
*
|
|
11
|
+
* Keyed by topicId-base64url. A manager subscribes under `topicId = reactivityTopicId(reactivityTailBytes(
|
|
12
|
+
* tailId))` (the same anchor origination derives), so a notification's topic — derived by the forwarder host
|
|
13
|
+
* from the notification's tail anchor — routes to exactly the managers watching that tail's collection.
|
|
14
|
+
*
|
|
15
|
+
* **Scope.** This delivers socket-routed notifications into a manager that was *already constructed* and
|
|
16
|
+
* registered. Constructing the manager from a Quereus `Database.watch` (the application bridge) stays the
|
|
17
|
+
* backlog item `optimystic-network-reactive-watch-integration-test`; this registry is the plug it will use.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { bytesToB64url, type NotificationV1 } from "@optimystic/db-core";
|
|
21
|
+
import { createLogger } from "../logger.js";
|
|
22
|
+
|
|
23
|
+
const log = createLogger("reactivity-subscribers");
|
|
24
|
+
|
|
25
|
+
/** A subscriber's inbound seam — a manager's `onNotification` (which returns a delivery outcome promise). */
|
|
26
|
+
export type ReactivityNotificationHandler = (n: NotificationV1) => void | Promise<unknown>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The node's `topicId → subscriber handlers` table. Exposed on the node as `reactivitySubscribers` so a
|
|
30
|
+
* subscribe factory can register a {@link import("./subscription-manager.js").ReactivitySubscriptionManager}
|
|
31
|
+
* and have socket-delivered notifications routed to it.
|
|
32
|
+
*/
|
|
33
|
+
export class ReactivitySubscriberRegistry {
|
|
34
|
+
private readonly byTopic = new Map<string, Set<ReactivityNotificationHandler>>();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Register `onNotification` to receive every notification the node delivers for `topicId`. Multiple
|
|
38
|
+
* managers may register for one topic (each gets every notification). Returns an idempotent unregister.
|
|
39
|
+
*/
|
|
40
|
+
register(topicId: Uint8Array, onNotification: ReactivityNotificationHandler): () => void {
|
|
41
|
+
const key = bytesToB64url(topicId);
|
|
42
|
+
let handlers = this.byTopic.get(key);
|
|
43
|
+
if (handlers === undefined) {
|
|
44
|
+
handlers = new Set();
|
|
45
|
+
this.byTopic.set(key, handlers);
|
|
46
|
+
}
|
|
47
|
+
handlers.add(onNotification);
|
|
48
|
+
return (): void => this.unregister(topicId, onNotification);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Drop a previously-registered handler; idempotent (a no-op if it was never registered / already gone). */
|
|
52
|
+
unregister(topicId: Uint8Array, onNotification: ReactivityNotificationHandler): void {
|
|
53
|
+
const key = bytesToB64url(topicId);
|
|
54
|
+
const handlers = this.byTopic.get(key);
|
|
55
|
+
if (handlers === undefined) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
handlers.delete(onNotification);
|
|
59
|
+
if (handlers.size === 0) {
|
|
60
|
+
this.byTopic.delete(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Deliver `n` to every handler registered for `topicId` (the forwarder host's `deliverLocal` seam). Each
|
|
66
|
+
* handler is isolated: a synchronous throw or a rejected delivery promise is logged, never propagated —
|
|
67
|
+
* one faulty subscriber must not break delivery to the rest or the fan-out loop that called this.
|
|
68
|
+
*/
|
|
69
|
+
deliver(topicId: Uint8Array, n: NotificationV1): void {
|
|
70
|
+
const handlers = this.byTopic.get(bytesToB64url(topicId));
|
|
71
|
+
if (handlers === undefined) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Snapshot: a handler that unregisters itself on delivery would otherwise mutate the live set.
|
|
75
|
+
for (const handler of [...handlers]) {
|
|
76
|
+
try {
|
|
77
|
+
const result = handler(n);
|
|
78
|
+
if (result instanceof Promise) {
|
|
79
|
+
result.catch((err: unknown) => log("subscriber delivery rejected (isolated) for rev=%d: %o", n.revision, err));
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
log("subscriber handler threw (isolated) for rev=%d: %o", n.revision, err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Number of topics with at least one registered subscriber (diagnostic / test). */
|
|
88
|
+
get topicCount(): number {
|
|
89
|
+
return this.byTopic.size;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Whether any subscriber is registered for `topicId` (diagnostic / test). */
|
|
93
|
+
has(topicId: Uint8Array): boolean {
|
|
94
|
+
return this.byTopic.has(bytesToB64url(topicId));
|
|
95
|
+
}
|
|
96
|
+
}
|