@agoric/fast-usdc 0.1.1-dev-3cbd11f.0 → 0.1.1-dev-4b5dff2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,422 +0,0 @@
1
- import { makeTracer } from '@agoric/internal';
2
- import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js';
3
- import { AmountKeywordRecordShape } from '@agoric/zoe/src/typeGuards.js';
4
- import { Fail, makeError, q } from '@endo/errors';
5
- import { E } from '@endo/eventual-send';
6
- import { M } from '@endo/patterns';
7
- import { PendingTxStatus, TerminalTxStatus, TxStatus } from '../constants.js';
8
- import {
9
- CctpTxEvidenceShape,
10
- EvmHashShape,
11
- PendingTxShape,
12
- } from '../type-guards.js';
13
-
14
- /**
15
- * @import {NatValue} from '@agoric/ertp';
16
- * @import {MapStore, SetStore} from '@agoric/store';
17
- * @import {Zone} from '@agoric/zone';
18
- * @import {CctpTxEvidence, NobleAddress, PendingTx, EvmHash, LogFn, TransactionRecord, EvidenceWithRisk, RiskAssessment} from '../types.js';
19
- */
20
-
21
- /**
22
- * @typedef {`pendingTx:${bigint}:${NobleAddress}`} PendingTxKey
23
- * The string template is for developer visibility but not meant to ever be parsed.
24
- */
25
-
26
- /**
27
- * Create the key for the pendingTxs MapStore.
28
- *
29
- * The key is a composite but not meant to be parsable.
30
- *
31
- * @param {NobleAddress} nfa Noble Forwarding Account (implies EUD)
32
- * @param {bigint} amount
33
- * @returns {PendingTxKey}
34
- */
35
- const makePendingTxKey = (nfa, amount) =>
36
- // amount can't contain colon
37
- `pendingTx:${amount}:${nfa}`;
38
-
39
- /**
40
- * Get the key for the pendingTxs MapStore.
41
- *
42
- * @param {CctpTxEvidence} evidence
43
- * @returns {PendingTxKey}
44
- */
45
- const pendingTxKeyOf = evidence => {
46
- const { amount, forwardingAddress } = evidence.tx;
47
- return makePendingTxKey(forwardingAddress, amount);
48
- };
49
-
50
- /**
51
- * @typedef {{
52
- * log?: LogFn;
53
- * marshaller: ERef<Marshaller>;
54
- * }} StatusManagerPowers
55
- */
56
-
57
- export const stateShape = harden({
58
- pendingSettleTxs: M.remotable(),
59
- seenTxs: M.remotable(),
60
- storedCompletedTxs: M.remotable(),
61
- });
62
-
63
- /**
64
- * The `StatusManager` keeps track of Pending and Seen Transactions
65
- * via {@link PendingTxStatus} states, aiding in coordination between the `Advancer`
66
- * and `Settler`.
67
- *
68
- * XXX consider separate facets for `Advancing` and `Settling` capabilities.
69
- *
70
- * @param {Zone} zone
71
- * @param {ERef<StorageNode>} txnsNode
72
- * @param {StatusManagerPowers} caps
73
- */
74
- export const prepareStatusManager = (
75
- zone,
76
- txnsNode,
77
- {
78
- marshaller,
79
- // eslint-disable-next-line no-unused-vars
80
- log = makeTracer('StatusManager', true),
81
- } = /** @type {StatusManagerPowers} */ ({}),
82
- ) => {
83
- /**
84
- * Keyed by a tuple of the Noble Forwarding Account and amount.
85
- * @type {MapStore<PendingTxKey, PendingTx[]>}
86
- */
87
- const pendingSettleTxs = zone.mapStore('PendingSettleTxs', {
88
- keyShape: M.string(),
89
- valueShape: M.arrayOf(PendingTxShape),
90
- });
91
-
92
- /**
93
- * Transactions seen *ever* by the contract.
94
- *
95
- * Note that like all durable stores, this MapStore is kept in IAVL. It stores
96
- * the `blockTimestamp` so that later we can prune old transactions.
97
- *
98
- * Note that `blockTimestamp` can drift between chains. Fortunately all CCTP
99
- * chains use the same Unix epoch and won't drift more than minutes apart,
100
- * which is more than enough precision for pruning old transaction.
101
- *
102
- * @type {MapStore<EvmHash, NatValue>}
103
- */
104
- const seenTxs = zone.mapStore('SeenTxs', {
105
- keyShape: M.string(),
106
- valueShape: M.nat(),
107
- });
108
-
109
- /**
110
- * Transactions that have completed, but are still in vstorage.
111
- *
112
- * @type {SetStore<EvmHash>}
113
- */
114
- const storedCompletedTxs = zone.setStore('StoredCompletedTxs', {
115
- keyShape: M.string(),
116
- });
117
-
118
- /**
119
- * @param {EvmHash} txId
120
- * @param {TransactionRecord} record
121
- */
122
- const publishTxnRecord = (txId, record) => {
123
- const txNode = E(txnsNode).makeChildNode(txId, {
124
- sequence: true, // avoid overwriting other output in the block
125
- });
126
-
127
- // XXX awkward for publish* to update a store, but it's temporary
128
- if (record.status && TerminalTxStatus[record.status]) {
129
- // UNTIL https://github.com/Agoric/agoric-sdk/issues/7405
130
- // Queue it for deletion later because if we deleted it now the earlier
131
- // writes in this block would be wiped. For now we keep track of what to
132
- // delete when we know it'll be another block.
133
- storedCompletedTxs.add(txId);
134
- }
135
-
136
- // Don't await, just writing to vstorage.
137
- void E.when(E(marshaller).toCapData(record), capData =>
138
- E(txNode).setValue(JSON.stringify(capData)),
139
- );
140
- };
141
-
142
- /**
143
- * @param {CctpTxEvidence['txHash']} hash
144
- * @param {CctpTxEvidence} evidence
145
- */
146
- const publishEvidence = (hash, evidence) => {
147
- // Don't await, just writing to vstorage.
148
- publishTxnRecord(hash, harden({ evidence, status: TxStatus.Observed }));
149
- };
150
-
151
- /**
152
- * Ensures that `txHash+chainId` has not been processed
153
- * and adds entry to `seenTxs` set.
154
- *
155
- * Also records the CctpTxEvidence and status in `pendingTxs`.
156
- *
157
- * @param {CctpTxEvidence} evidence
158
- * @param {PendingTxStatus} status
159
- * @param {string[]} [risksIdentified]
160
- */
161
- const initPendingTx = (evidence, status, risksIdentified) => {
162
- const { txHash } = evidence;
163
- if (seenTxs.has(txHash)) {
164
- throw makeError(`Transaction already seen: ${q(txHash)}`);
165
- }
166
- seenTxs.init(txHash, evidence.blockTimestamp);
167
-
168
- appendToStoredArray(
169
- pendingSettleTxs,
170
- pendingTxKeyOf(evidence),
171
- harden({ ...evidence, status }),
172
- );
173
- publishEvidence(txHash, evidence);
174
- if (status === PendingTxStatus.AdvanceSkipped) {
175
- publishTxnRecord(txHash, harden({ status, risksIdentified }));
176
- } else if (status !== PendingTxStatus.Observed) {
177
- // publishEvidence publishes Observed
178
- publishTxnRecord(txHash, harden({ status }));
179
- }
180
- };
181
-
182
- /**
183
- * Update the pending transaction status.
184
- *
185
- * @param {{nfa: NobleAddress, amount: bigint}} keyParts
186
- * @param {PendingTxStatus} status
187
- */
188
- function setPendingTxStatus({ nfa, amount }, status) {
189
- const key = makePendingTxKey(nfa, amount);
190
- pendingSettleTxs.has(key) || Fail`no advancing tx with ${{ nfa, amount }}`;
191
- const pending = pendingSettleTxs.get(key);
192
- const ix = pending.findIndex(tx => tx.status === PendingTxStatus.Advancing);
193
- ix >= 0 || Fail`no advancing tx with ${{ nfa, amount }}`;
194
- const [prefix, tx, suffix] = [
195
- pending.slice(0, ix),
196
- pending[ix],
197
- pending.slice(ix + 1),
198
- ];
199
- const txpost = { ...tx, status };
200
- pendingSettleTxs.set(key, harden([...prefix, txpost, ...suffix]));
201
- publishTxnRecord(tx.txHash, harden({ status }));
202
- }
203
-
204
- return zone.exo(
205
- 'Fast USDC Status Manager',
206
- M.interface('StatusManagerI', {
207
- // TODO: naming scheme for transition events
208
- advance: M.call(CctpTxEvidenceShape).returns(),
209
- advanceOutcome: M.call(M.string(), M.nat(), M.boolean()).returns(),
210
- skipAdvance: M.call(CctpTxEvidenceShape, M.arrayOf(M.string())).returns(),
211
- advanceOutcomeForMintedEarly: M.call(EvmHashShape, M.boolean()).returns(),
212
- advanceOutcomeForUnknownMint: M.call(CctpTxEvidenceShape).returns(),
213
- observe: M.call(CctpTxEvidenceShape).returns(),
214
- hasBeenObserved: M.call(CctpTxEvidenceShape).returns(M.boolean()),
215
- deleteCompletedTxs: M.call().returns(M.undefined()),
216
- dequeueStatus: M.call(M.string(), M.bigint()).returns(
217
- M.or(
218
- {
219
- txHash: EvmHashShape,
220
- status: M.or(...Object.values(PendingTxStatus)),
221
- },
222
- M.undefined(),
223
- ),
224
- ),
225
- disbursed: M.call(EvmHashShape, AmountKeywordRecordShape).returns(
226
- M.undefined(),
227
- ),
228
- forwarded: M.call(EvmHashShape, M.boolean()).returns(),
229
- lookupPending: M.call(M.string(), M.bigint()).returns(
230
- M.arrayOf(PendingTxShape),
231
- ),
232
- }),
233
- {
234
- /**
235
- * Add a new transaction with ADVANCING status
236
- *
237
- * NB: this acts like observe() but subsequently records an ADVANCING
238
- * state
239
- *
240
- * @param {CctpTxEvidence} evidence
241
- */
242
- advance(evidence) {
243
- initPendingTx(evidence, PendingTxStatus.Advancing);
244
- },
245
-
246
- /**
247
- * Add a new transaction with ADVANCE_SKIPPED status
248
- *
249
- * NB: this acts like observe() but subsequently records an
250
- * ADVANCE_SKIPPED state along with risks identified
251
- *
252
- * @param {CctpTxEvidence} evidence
253
- * @param {string[]} risksIdentified
254
- */
255
- skipAdvance(evidence, risksIdentified) {
256
- initPendingTx(
257
- evidence,
258
- PendingTxStatus.AdvanceSkipped,
259
- risksIdentified,
260
- );
261
- },
262
-
263
- /**
264
- * Record result of an ADVANCING transaction
265
- *
266
- * @param {NobleAddress} nfa Noble Forwarding Account
267
- * @param {import('@agoric/ertp').NatValue} amount
268
- * @param {boolean} success - Advanced vs. AdvanceFailed
269
- * @throws {Error} if nothing to advance
270
- */
271
- advanceOutcome(nfa, amount, success) {
272
- setPendingTxStatus(
273
- { nfa, amount },
274
- success ? PendingTxStatus.Advanced : PendingTxStatus.AdvanceFailed,
275
- );
276
- },
277
-
278
- /**
279
- * If minted while advancing, publish a status update for the advance
280
- * to vstorage.
281
- *
282
- * Does not add or amend `pendingSettleTxs` as this has
283
- * already settled.
284
- *
285
- * @param {EvmHash} txHash
286
- * @param {boolean} success whether the Transfer succeeded
287
- */
288
- advanceOutcomeForMintedEarly(txHash, success) {
289
- publishTxnRecord(
290
- txHash,
291
- harden({
292
- status: success
293
- ? PendingTxStatus.Advanced
294
- : PendingTxStatus.AdvanceFailed,
295
- }),
296
- );
297
- },
298
-
299
- /**
300
- * If minted before observed and the evidence is eventually
301
- * reported, publish the evidence without adding to `pendingSettleTxs`
302
- *
303
- * @param {CctpTxEvidence} evidence
304
- */
305
- advanceOutcomeForUnknownMint(evidence) {
306
- const { txHash } = evidence;
307
- // unexpected path, since `hasBeenObserved` will be called before this
308
- if (seenTxs.has(txHash)) {
309
- throw makeError(`Transaction already seen: ${q(txHash)}`);
310
- }
311
- seenTxs.init(txHash, evidence.blockTimestamp);
312
- publishEvidence(txHash, evidence);
313
- },
314
-
315
- /**
316
- * Add a new transaction with OBSERVED status
317
- * @param {CctpTxEvidence} evidence
318
- */
319
- observe(evidence) {
320
- initPendingTx(evidence, PendingTxStatus.Observed);
321
- },
322
-
323
- /**
324
- * Note: ADVANCING state implies tx has been OBSERVED
325
- *
326
- * @param {CctpTxEvidence} evidence
327
- */
328
- hasBeenObserved(evidence) {
329
- return seenTxs.has(evidence.txHash);
330
- },
331
-
332
- // UNTIL https://github.com/Agoric/agoric-sdk/issues/7405
333
- deleteCompletedTxs() {
334
- for (const txHash of storedCompletedTxs.values()) {
335
- // As of now, setValue('') on a non-sequence node will delete it
336
- const txNode = E(txnsNode).makeChildNode(txHash, {
337
- sequence: false,
338
- });
339
- void E(txNode)
340
- .setValue('')
341
- .then(() => storedCompletedTxs.delete(txHash));
342
- }
343
- },
344
-
345
- /**
346
- * Remove and return the oldest pending settlement transaction that matches the given
347
- * forwarding account and amount. Since multiple pending transactions may exist with
348
- * identical (account, amount) pairs, we process them in FIFO order.
349
- *
350
- * @param {NobleAddress} nfa
351
- * @param {bigint} amount
352
- * @returns {Pick<PendingTx, 'status' | 'txHash'> | undefined} undefined if no pending
353
- * transactions exist for this address and amount combination.
354
- */
355
- dequeueStatus(nfa, amount) {
356
- const key = makePendingTxKey(nfa, amount);
357
- if (!pendingSettleTxs.has(key)) return undefined;
358
- const pending = pendingSettleTxs.get(key);
359
-
360
- if (pending.length === 0) {
361
- return undefined;
362
- }
363
- // extract first item
364
- const [{ status, txHash }, ...remaining] = pending;
365
-
366
- if (remaining.length) {
367
- pendingSettleTxs.set(key, harden(remaining));
368
- } else {
369
- pendingSettleTxs.delete(key);
370
- }
371
-
372
- return harden({ status, txHash });
373
- },
374
-
375
- /**
376
- * Mark a transaction as `DISBURSED`
377
- *
378
- * @param {EvmHash} txHash
379
- * @param {import('./liquidity-pool.js').RepayAmountKWR} split
380
- */
381
- disbursed(txHash, split) {
382
- publishTxnRecord(txHash, harden({ split, status: TxStatus.Disbursed }));
383
- },
384
-
385
- /**
386
- * Mark a transaction as `FORWARDED` or `FORWARD_FAILED`
387
- *
388
- * @param {EvmHash} txHash
389
- * @param {boolean} success
390
- */
391
- forwarded(txHash, success) {
392
- publishTxnRecord(
393
- txHash,
394
- harden({
395
- status: success ? TxStatus.Forwarded : TxStatus.ForwardFailed,
396
- }),
397
- );
398
- },
399
-
400
- /**
401
- * Lookup all pending entries for a given address and amount
402
- *
403
- * XXX only used in tests. should we remove?
404
- *
405
- * @param {NobleAddress} nfa
406
- * @param {bigint} amount
407
- * @returns {PendingTx[]}
408
- */
409
- lookupPending(nfa, amount) {
410
- const key = makePendingTxKey(nfa, amount);
411
- if (!pendingSettleTxs.has(key)) {
412
- return harden([]);
413
- }
414
- return pendingSettleTxs.get(key);
415
- },
416
- },
417
- { stateShape },
418
- );
419
- };
420
- harden(prepareStatusManager);
421
-
422
- /** @typedef {ReturnType<typeof prepareStatusManager>} StatusManager */
@@ -1,275 +0,0 @@
1
- /** @file Exo for @see {prepareTransactionFeedKit} */
2
- import { makeTracer } from '@agoric/internal';
3
- import { prepareDurablePublishKit } from '@agoric/notifier';
4
- import { Fail, quote } from '@endo/errors';
5
- import { keyEQ, M } from '@endo/patterns';
6
- import { CctpTxEvidenceShape, RiskAssessmentShape } from '../type-guards.js';
7
- import { defineInertInvitation } from '../utils/zoe.js';
8
- import { prepareOperatorKit } from './operator-kit.js';
9
-
10
- /**
11
- * @import {Zone} from '@agoric/zone';
12
- * @import {MapStore} from '@agoric/store';
13
- * @import {OperatorKit} from './operator-kit.js';
14
- * @import {CctpTxEvidence, EvidenceWithRisk, RiskAssessment} from '../types.js';
15
- */
16
-
17
- const trace = makeTracer('TxFeed', true);
18
-
19
- /**
20
- * @typedef {Pick<OperatorKit, 'invitationMakers' | 'operator'>} OperatorOfferResult
21
- */
22
-
23
- /** Name in the invitation purse (keyed also by this contract instance) */
24
- export const INVITATION_MAKERS_DESC = 'oracle operator invitation';
25
-
26
- const TransactionFeedKitI = harden({
27
- operatorPowers: M.interface('Transaction Feed Admin', {
28
- attest: M.call(
29
- CctpTxEvidenceShape,
30
- RiskAssessmentShape,
31
- M.string(),
32
- ).returns(),
33
- }),
34
- creator: M.interface('Transaction Feed Creator', {
35
- initOperator: M.call(M.string()).returns({
36
- invitationMakers: M.remotable(),
37
- operator: M.remotable(),
38
- }),
39
- makeOperatorInvitation: M.call(M.string()).returns(M.promise()),
40
- removeOperator: M.call(M.string()).returns(),
41
- }),
42
- public: M.interface('Transaction Feed Public', {
43
- getEvidenceSubscriber: M.call().returns(M.remotable()),
44
- }),
45
- });
46
-
47
- /**
48
- * @param {MapStore<string, RiskAssessment>[]} riskStores
49
- * @param {string} txHash
50
- */
51
- const allRisksIdentified = (riskStores, txHash) => {
52
- /** @type {Set<string>} */
53
- const setOfRisks = new Set();
54
- for (const store of riskStores) {
55
- const next = store.get(txHash);
56
- for (const risk of next.risksIdentified ?? []) {
57
- setOfRisks.add(risk);
58
- }
59
- }
60
- return [...setOfRisks.values()].sort();
61
- };
62
-
63
- export const stateShape = {
64
- operators: M.remotable(),
65
- pending: M.remotable(),
66
- risks: M.remotable(),
67
- };
68
- harden(stateShape);
69
-
70
- /**
71
- * A TransactionFeed is responsible for finding quorum among oracles.
72
- *
73
- * It receives attestations, records their evidence, and when enough oracles
74
- * agree, publishes the results for the advancer to act on.
75
- *
76
- * @param {Zone} zone
77
- * @param {ZCF} zcf
78
- */
79
- export const prepareTransactionFeedKit = (zone, zcf) => {
80
- const kinds = zone.mapStore('Kinds');
81
- const makeDurablePublishKit = prepareDurablePublishKit(
82
- kinds,
83
- 'Transaction Feed',
84
- );
85
- /** @type {PublishKit<EvidenceWithRisk>} */
86
- const { publisher, subscriber } = makeDurablePublishKit();
87
-
88
- const makeInertInvitation = defineInertInvitation(zcf, 'submitting evidence');
89
-
90
- const makeOperatorKit = prepareOperatorKit(zone, {
91
- makeInertInvitation,
92
- });
93
-
94
- return zone.exoClassKit(
95
- 'Fast USDC Feed',
96
- TransactionFeedKitI,
97
- () => {
98
- /** @type {MapStore<string, OperatorKit>} */
99
- const operators = zone.mapStore('operators');
100
- /** @type {MapStore<string, MapStore<string, CctpTxEvidence>>} */
101
- const pending = zone.mapStore('pending');
102
- /** @type {MapStore<string, MapStore<string, RiskAssessment>>} */
103
- const risks = zone.mapStore('risks');
104
- return { operators, pending, risks };
105
- },
106
- {
107
- creator: {
108
- /**
109
- * An "operator invitation" is an invitation to be an operator in the
110
- * oracle network, with the able to submit data to submit evidence of
111
- * CCTP transactions.
112
- *
113
- * @param {string} operatorId unique per contract instance
114
- * @returns {Promise<Invitation<OperatorOfferResult>>}
115
- */
116
- makeOperatorInvitation(operatorId) {
117
- const { creator } = this.facets;
118
- trace('makeOperatorInvitation', operatorId);
119
-
120
- return zcf.makeInvitation(
121
- /** @type {OfferHandler<OperatorOfferResult>} */
122
- seat => {
123
- seat.exit();
124
- return creator.initOperator(operatorId);
125
- },
126
- INVITATION_MAKERS_DESC,
127
- );
128
- },
129
- /**
130
- * @param {string} operatorId
131
- * @returns {OperatorOfferResult}
132
- */
133
- initOperator(operatorId) {
134
- const { operators, pending, risks } = this.state;
135
- trace('initOperator', operatorId);
136
-
137
- const operatorKit = makeOperatorKit(
138
- operatorId,
139
- this.facets.operatorPowers,
140
- );
141
- operators.init(operatorId, operatorKit);
142
- pending.init(
143
- operatorId,
144
- zone.detached().mapStore('pending evidence'),
145
- );
146
- risks.init(operatorId, zone.detached().mapStore('risk assessments'));
147
-
148
- // Subset facets to all the off-chain operator needs
149
- const { invitationMakers, operator } = operatorKit;
150
- return {
151
- invitationMakers,
152
- operator,
153
- };
154
- },
155
-
156
- /** @param {string} operatorId */
157
- removeOperator(operatorId) {
158
- const { operators, pending, risks } = this.state;
159
- trace('removeOperator', operatorId);
160
- const operatorKit = operators.get(operatorId);
161
- operatorKit.admin.disable();
162
- operators.delete(operatorId);
163
- pending.delete(operatorId);
164
- risks.delete(operatorId);
165
- },
166
- },
167
- operatorPowers: {
168
- /**
169
- * Add evidence from an operator.
170
- *
171
- * NB: the operatorKit is responsible for revoking access.
172
- *
173
- * @param {CctpTxEvidence} evidence
174
- * @param {RiskAssessment} riskAssessment
175
- * @param {string} operatorId
176
- */
177
- attest(evidence, riskAssessment, operatorId) {
178
- const { operators, pending, risks } = this.state;
179
- trace('attest', operatorId, evidence);
180
-
181
- const { txHash } = evidence;
182
-
183
- // accept the evidence
184
- {
185
- const pendingStore = pending.get(operatorId);
186
- if (pendingStore.has(txHash)) {
187
- trace(`operator ${operatorId} already reported ${txHash}`);
188
- } else {
189
- pendingStore.init(txHash, evidence);
190
- // accept the risk assessment as well
191
- const riskStore = risks.get(operatorId);
192
- riskStore.init(txHash, riskAssessment);
193
- }
194
- }
195
-
196
- // check agreement
197
- const found = [...pending.values()].filter(store =>
198
- store.has(txHash),
199
- );
200
-
201
- {
202
- let lastEvidence;
203
- for (const store of found) {
204
- const next = store.get(txHash);
205
- if (lastEvidence && !keyEQ(lastEvidence, next)) {
206
- // Ignore conflicting evidence, but treat it as an error
207
- // because it should never happen and needs to be prevented
208
- // from happening again.
209
- trace(
210
- '🚨 conflicting evidence for',
211
- txHash,
212
- ':',
213
- lastEvidence,
214
- '!=',
215
- next,
216
- );
217
- Fail`conflicting evidence for ${quote(txHash)}`;
218
- }
219
- lastEvidence = next;
220
- }
221
- }
222
-
223
- const minAttestations = Math.ceil(operators.getSize() / 2);
224
- trace(
225
- 'transaction',
226
- txHash,
227
- 'has',
228
- found.length,
229
- 'of',
230
- minAttestations,
231
- 'necessary attestations',
232
- );
233
-
234
- if (found.length < minAttestations) {
235
- // wait for more
236
- return;
237
- }
238
-
239
- const riskStores = [...risks.values()].filter(store =>
240
- store.has(txHash),
241
- );
242
-
243
- // Publish at the threshold of agreement
244
- if (found.length === minAttestations) {
245
- // take the union of risks identified from all operators
246
- const risksIdentified = allRisksIdentified(riskStores, txHash);
247
- trace('publishing evidence', evidence, risksIdentified);
248
- publisher.publish({
249
- evidence,
250
- risk: { risksIdentified },
251
- });
252
- return;
253
- }
254
-
255
- if (found.length === pending.getSize()) {
256
- // all have reported so clean up
257
- for (const store of found) {
258
- store.delete(txHash);
259
- }
260
- for (const store of riskStores) {
261
- store.delete(txHash);
262
- }
263
- }
264
- },
265
- },
266
- public: {
267
- getEvidenceSubscriber: () => subscriber,
268
- },
269
- },
270
- { stateShape },
271
- );
272
- };
273
- harden(prepareTransactionFeedKit);
274
-
275
- /** @typedef {ReturnType<ReturnType<typeof prepareTransactionFeedKit>>} TransactionFeedKit */