Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c1b9538

Browse files
committedNov 8, 2024·
feat: StatusManager tracks seenTxs
- use a composite key of `txHash+chainId` to track unique `EventFeed` submissions
1 parent a93de8a commit c1b9538

File tree

3 files changed

+53
-2
lines changed

3 files changed

+53
-2
lines changed
 

‎packages/fast-usdc/src/exos/status-manager.js

+27-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { CctpTxEvidenceShape, PendingTxShape } from '../typeGuards.js';
66
import { PendingTxStatus } from '../constants.js';
77

88
/**
9-
* @import {MapStore} from '@agoric/store';
9+
* @import {MapStore, SetStore} from '@agoric/store';
1010
* @import {Zone} from '@agoric/zone';
11-
* @import {CctpTxEvidence, NobleAddress, PendingTxKey, PendingTx} from '../types.js';
11+
* @import {CctpTxEvidence, NobleAddress, SeenTxKey, PendingTxKey, PendingTx} from '../types.js';
1212
*/
1313

1414
/**
@@ -29,6 +29,15 @@ const getPendingTxKey = evidence => {
2929
return toPendingTxKey(forwardingAddress, amount);
3030
};
3131

32+
/**
33+
* Get the key for the seenTxs SetStore
34+
* @param {CctpTxEvidence} evidence
35+
*/
36+
const getSeenKey = evidence => {
37+
const { txHash, chainId } = evidence;
38+
return /** @type {SeenTxKey} */ (JSON.stringify([txHash, chainId]));
39+
};
40+
3241
/**
3342
* The `StatusManager` keeps track of Pending and Seen Transactions
3443
* via {@link PendingTxStatus} states, aiding in coordination between the `Advancer`
@@ -45,11 +54,27 @@ export const prepareStatusManager = zone => {
4554
valueShape: M.arrayOf(PendingTxShape),
4655
});
4756

57+
/** @type {SetStore<SeenTxKey>} */
58+
const seenTxs = zone.setStore('SeenTxs', {
59+
keyShape: M.string(),
60+
});
61+
4862
/**
63+
* Ensures that `txHash+chainId` has not been processed
64+
* and adds entry to `seenTxs` set.
65+
*
66+
* Also records the CctpTxEvidence and status in `pendingTxs`.
67+
*
4968
* @param {CctpTxEvidence} evidence
5069
* @param {PendingTxStatus} status
5170
*/
5271
const recordPendingTx = (evidence, status) => {
72+
const seenKey = getSeenKey(evidence);
73+
if (seenTxs.has(seenKey)) {
74+
throw makeError(`Transaction already seen: ${q(seenKey)}`);
75+
}
76+
seenTxs.add(seenKey);
77+
5378
appendToStoredArray(
5479
pendingTxs,
5580
getPendingTxKey(evidence),

‎packages/fast-usdc/src/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,7 @@ export interface PendingTx extends CctpTxEvidence {
3232
/** composite of NobleAddress and transaction amount value */
3333
export type PendingTxKey = `"["${NobleAddress}",${bigint}]"`;
3434

35+
/** composite of EvmHash and chainId */
36+
export type SeenTxKey = `"["${EvmHash}",${number}]"`;
37+
3538
export type * from './constants.js';

‎packages/fast-usdc/test/exos/status-manager.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,29 @@ test('observe creates new entry with OBSERVED status', t => {
3535
t.is(entries[0]?.status, PendingTxStatus.Observed);
3636
});
3737

38+
test('cannot process same tx twice', t => {
39+
const zone = provideDurableZone('status-test');
40+
const statusManager = prepareStatusManager(zone.subZone('status-manager'));
41+
42+
const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO();
43+
statusManager.advance(evidence);
44+
45+
t.throws(() => statusManager.advance(evidence), {
46+
message:
47+
'Transaction already seen: "[[\\"0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702\\",1]]"',
48+
});
49+
50+
t.throws(() => statusManager.observe(evidence), {
51+
message:
52+
'Transaction already seen: "[[\\"0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702\\",1]]"',
53+
});
54+
55+
// new txHash should not throw
56+
t.notThrows(() => statusManager.advance({ ...evidence, txHash: '0xtest2' }));
57+
// new chainId with existing txHash should not throw
58+
t.notThrows(() => statusManager.advance({ ...evidence, chainId: 9999 }));
59+
});
60+
3861
test('settle removes entries from PendingTxs', t => {
3962
const zone = provideDurableZone('status-test');
4063
const statusManager = prepareStatusManager(zone.subZone('status-manager'));

0 commit comments

Comments
 (0)
Please sign in to comment.