diff --git a/packages/fast-usdc/src/constants.js b/packages/fast-usdc/src/constants.js index d341f6c650d..28ab7775db7 100644 --- a/packages/fast-usdc/src/constants.js +++ b/packages/fast-usdc/src/constants.js @@ -21,7 +21,12 @@ export const TxStatus = /** @type {const} */ ({ }); harden(TxStatus); -// TODO: define valid state transitions +// According to the state diagram +export const TerminalTxStatus = { + [TxStatus.Forwarded]: true, + [TxStatus.ForwardFailed]: true, + [TxStatus.Disbursed]: true, +}; /** * Status values for the StatusManager. diff --git a/packages/fast-usdc/src/exos/status-manager.js b/packages/fast-usdc/src/exos/status-manager.js index b67185b5a7a..9a3bfd7d8f3 100644 --- a/packages/fast-usdc/src/exos/status-manager.js +++ b/packages/fast-usdc/src/exos/status-manager.js @@ -8,7 +8,7 @@ import { EvmHashShape, PendingTxShape, } from '../type-guards.js'; -import { PendingTxStatus, TxStatus } from '../constants.js'; +import { PendingTxStatus, TerminalTxStatus, TxStatus } from '../constants.js'; /** * @import {MapStore, SetStore} from '@agoric/store'; @@ -78,11 +78,26 @@ export const prepareStatusManager = ( valueShape: M.arrayOf(PendingTxShape), }); - /** @type {SetStore} */ + /** + * Transactions seen *ever* by the contract. + * + * Note that these are stored in IAVL and grow monotonically. At some point in the future we may want to prune them. + * + * @type {SetStore} + */ const seenTxs = zone.setStore('SeenTxs', { keyShape: M.string(), }); + /** + * Transactions that have completed, but are still in vstorage. + * + * @type {SetStore} + */ + const storedCompletedTxs = zone.setStore('StoredCompletedTxs', { + keyShape: M.string(), + }); + /** * @param {CctpTxEvidence['txHash']} hash * @param {CctpTxEvidence} evidence @@ -101,6 +116,13 @@ export const prepareStatusManager = ( const txnNodeP = E(transactionsNode).makeChildNode(hash); // Don't await, just writing to vstorage. void E(txnNodeP).setValue(status); + if (TerminalTxStatus[status]) { + // UNTIL https://github.com/Agoric/agoric-sdk/issues/7405 + // Queue it for deletion later because if we deleted it now the earlier + // writes in this block would be wiped. For now we keep track of what to + // delete when we know it'll be another block. + storedCompletedTxs.add(hash); + } }; /** @@ -158,6 +180,7 @@ export const prepareStatusManager = ( advanceOutcome: M.call(M.string(), M.nat(), M.boolean()).returns(), observe: M.call(CctpTxEvidenceShape).returns(M.undefined()), hasBeenObserved: M.call(CctpTxEvidenceShape).returns(M.boolean()), + deleteCompletedTxs: M.call().returns(M.undefined()), dequeueStatus: M.call(M.string(), M.bigint()).returns( M.or( { @@ -223,6 +246,19 @@ export const prepareStatusManager = ( return seenTxs.has(evidence.txHash); }, + // UNTIL https://github.com/Agoric/agoric-sdk/issues/7405 + deleteCompletedTxs() { + for (const txHash of storedCompletedTxs.values()) { + // As of now, setValue('') on a non-sequence node will delete it + const txNode = E(transactionsNode).makeChildNode(txHash, { + sequence: false, + }); + void E(txNode) + .setValue('') + .then(() => storedCompletedTxs.delete(txHash)); + } + }, + /** * Remove and return an `ADVANCED` or `OBSERVED` tx waiting to be `SETTLED`. * diff --git a/packages/fast-usdc/test/exos/settler.test.ts b/packages/fast-usdc/test/exos/settler.test.ts index d82479479c9..9456fb5417a 100644 --- a/packages/fast-usdc/test/exos/settler.test.ts +++ b/packages/fast-usdc/test/exos/settler.test.ts @@ -242,6 +242,11 @@ test('happy path: disburse to LPs; StatusManager removes tx', async t => { 'ADVANCED', 'DISBURSED', ]); + + // Check deletion of DISBURSED transactions + statusManager.deleteCompletedTxs(); + await eventLoopIteration(); + t.is(storage.data.get(`fun.txns.${cctpTxEvidence.txHash}`), undefined); }); test('slow path: forward to EUD; remove pending tx', async t => { @@ -314,6 +319,11 @@ test('slow path: forward to EUD; remove pending tx', async t => { 'OBSERVED', 'FORWARDED', ]); + + // Check deletion of FORWARDED transactions + statusManager.deleteCompletedTxs(); + await eventLoopIteration(); + t.is(storage.data.get(`fun.txns.${cctpTxEvidence.txHash}`), undefined); }); test('Settlement for unknown transaction', async t => {