Skip to content

Commit

Permalink
feat: StatusManager tracks seenTxs
Browse files Browse the repository at this point in the history
- use a composite key of `txHash+chainId` to track unique `EventFeed` submissions
  • Loading branch information
0xpatrickdev committed Nov 12, 2024
1 parent 0ed2be3 commit 9eac623
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 2 deletions.
34 changes: 32 additions & 2 deletions packages/fast-usdc/src/exos/status-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { CctpTxEvidenceShape, PendingTxShape } from '../typeGuards.js';
import { PendingTxStatus } from '../constants.js';

/**
* @import {MapStore} from '@agoric/store';
* @import {MapStore, SetStore} from '@agoric/store';
* @import {Zone} from '@agoric/zone';
* @import {CctpTxEvidence, NobleAddress, PendingTxKey, PendingTx} from '../types.js';
* @import {CctpTxEvidence, NobleAddress, SeenTxKey, PendingTxKey, PendingTx} from '../types.js';
*/

/**
Expand All @@ -35,6 +35,20 @@ const pendingTxKeyOf = evidence => {
return makePendingTxKey(forwardingAddress, amount);
};

/**
* Get the key for the seenTxs SetStore.
*
* The key is a composite of `NobleAddress` and transaction `amount` and not
* meant to be parsable.
*
* @param {CctpTxEvidence} evidence
* @returns {SeenTxKey}
*/
const seenTxKeyOf = evidence => {
const { txHash, chainId } = evidence;
return `seenTx:${JSON.stringify([txHash, chainId])}`;
};

/**
* The `StatusManager` keeps track of Pending and Seen Transactions
* via {@link PendingTxStatus} states, aiding in coordination between the `Advancer`
Expand All @@ -51,11 +65,27 @@ export const prepareStatusManager = zone => {
valueShape: M.arrayOf(PendingTxShape),
});

/** @type {SetStore<SeenTxKey>} */
const seenTxs = zone.setStore('SeenTxs', {
keyShape: M.string(),
});

/**
* Ensures that `txHash+chainId` has not been processed
* and adds entry to `seenTxs` set.
*
* Also records the CctpTxEvidence and status in `pendingTxs`.
*
* @param {CctpTxEvidence} evidence
* @param {PendingTxStatus} status
*/
const recordPendingTx = (evidence, status) => {
const seenKey = seenTxKeyOf(evidence);
if (seenTxs.has(seenKey)) {
throw makeError(`Transaction already seen: ${q(seenKey)}`);
}
seenTxs.add(seenKey);

appendToStoredArray(
pendingTxs,
pendingTxKeyOf(evidence),
Expand Down
3 changes: 3 additions & 0 deletions packages/fast-usdc/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,7 @@ export interface PendingTx extends CctpTxEvidence {
/** internal key for `StatusManager` exo */
export type PendingTxKey = `pendingTx:${string}`;

/** internal key for `StatusManager` exo */
export type SeenTxKey = `seenTx:${string}`;

export type * from './constants.js';
23 changes: 23 additions & 0 deletions packages/fast-usdc/test/exos/status-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,29 @@ test('observe creates new entry with OBSERVED status', t => {
t.is(entries[0]?.status, PendingTxStatus.Observed);
});

test('cannot process same tx twice', t => {
const zone = provideDurableZone('status-test');
const statusManager = prepareStatusManager(zone.subZone('status-manager'));

const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO();
statusManager.advance(evidence);

t.throws(() => statusManager.advance(evidence), {
message:
'Transaction already seen: "seenTx:[\\"0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702\\",1]"',
});

t.throws(() => statusManager.observe(evidence), {
message:
'Transaction already seen: "seenTx:[\\"0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702\\",1]"',
});

// new txHash should not throw
t.notThrows(() => statusManager.advance({ ...evidence, txHash: '0xtest2' }));
// new chainId with existing txHash should not throw
t.notThrows(() => statusManager.advance({ ...evidence, chainId: 9999 }));
});

test('settle removes entries from PendingTxs', t => {
const zone = provideDurableZone('status-test');
const statusManager = prepareStatusManager(zone.subZone('status-manager'));
Expand Down

0 comments on commit 9eac623

Please sign in to comment.