Skip to content

Commit

Permalink
make SeenTxs prunable (#10993)
Browse files Browse the repository at this point in the history
closes: #10992

## Description

`SeenTxs` is currently all transactions ever seen by the contract, but we don't want it to grow without bound. Eventually we'll want to prune old transactions and will need a way of seeing how old each entry is. This turns the set of `txHash` keys into a map in which the value is the `blockTimestamp`. That provides the necessary data for a contract upgrade to add functionality to delete old entries.

The doc comment says, "*ever* seen" which is currently accurate. Later it will be a subset of what was ever seen. For this reason I think the store name, "SeenTxs", is still appropriate even if in the future it won't be all of them.

### Security Considerations
none

### Scaling Considerations
No real additional IAVL costs because the SetStore uses a sentinel value anyway.

Definite net win soon by making pruning possible.

### Documentation Considerations
none

### Testing Considerations
CI suffices

### Upgrade Considerations
Not yet deployed. If it were, this would fail an upgrade test.
  • Loading branch information
mergify[bot] authored Feb 12, 2025
2 parents a67bf28 + f9920a8 commit eb8538b
Show file tree
Hide file tree
Showing 11 changed files with 1,803 additions and 15 deletions.
7 changes: 4 additions & 3 deletions a3p-integration/proposals/b:beta-fast-usdc/test/test-cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ yarn agoric wallet send --offer accept.json --from agoric1ee9hr0jyrxhy999y755mp8
# UNTIL https://github.com/Agoric/agoric-sdk/issues/10891
# ACCEPT_OFFER_ID=$(agoric wallet extract-id --offer accept.json)

yarn fast-usdc operator attest --previousOfferId="$ACCEPT_OFFER_ID" --forwardingChannel=foo --recipientAddress=agoric1foo --blockHash=0xfoo --blockNumber=1 --chainId=3 --amount=123 --forwardingAddress=noble1foo --sender 0xfoo --txHash=0xtx >| attest.json
cat attest.json
yarn agoric wallet send --offer attest.json --from agoric1ee9hr0jyrxhy999y755mp862ljgycmwyp4pl7q --keyring-backend test
# FIXME restore this test once the latest fast-usdc package is in NPM
# yarn fast-usdc operator attest --previousOfferId="$ACCEPT_OFFER_ID" --forwardingChannel=foo --recipientAddress=agoric1foo --blockHash=0xfoo --blockNumber=1 --blockTimestamp=1632340000 --chainId=3 --amount=123 --forwardingAddress=noble1foo --sender 0xfoo --txHash=0xtx >| attest.json
# cat attest.json
# yarn agoric wallet send --offer attest.json --from agoric1ee9hr0jyrxhy999y755mp862ljgycmwyp4pl7q --keyring-backend test

# The data above are bogus and result in errors in the logs:
# SwingSet: vat: v72: ----- TxFeed.11 8 publishing evidence { aux: { forwardingChannel: 'foo', recipientAddress: 'agoric1foo' }, blockHash: '0xfoo', blockNumber: 1n, chainId: 3, tx: { amount: 123n, forwardingAddress: 'noble1foo', sender: '0xfoo' }, txHash: '0xtx' } []
Expand Down
1 change: 1 addition & 0 deletions multichain-testing/test/fast-usdc/fast-usdc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ const makeTestContext = async (t: ExecutionContext) => {
blockHash:
'0x90d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee665',
blockNumber: 21037663n,
blockTimestamp: 1632340000n,
txHash: `0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617${String(callCount++).padStart(2, '0')}`,
tx: {
amount: mintAmt,
Expand Down
5 changes: 4 additions & 1 deletion multichain-testing/test/fast-usdc/fu-actors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ export const makeUserAgent = (
t.log('got forwardingAddress', userForwardingAddr);

const senderDigits = 'FAKE_SENDER_ADDR' as string & { length: 40 };
const tx: Omit<CctpTxEvidence, 'blockHash' | 'blockNumber'> = harden({
const tx: Omit<
CctpTxEvidence,
'blockHash' | 'blockNumber' | 'blockTimestamp'
> = harden({
txHash: `0xFAKE_TX_HASH`,
tx: {
sender: `0x${senderDigits}`,
Expand Down
1 change: 1 addition & 0 deletions packages/fast-usdc/src/cli/operator-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export const addOperatorCommands = (
.requiredOption('--recipientAddress <string>', 'bech32 address', String)
.requiredOption('--blockHash <0xhex>', 'hex hash', parseHex)
.requiredOption('--blockNumber <number>', 'number', parseNat)
.requiredOption('--blockTimestamp <number>', 'number', parseNat)
.requiredOption('--chainId <string>', 'chain id', Number)
.requiredOption('--amount <number>', 'number', parseNat)
.requiredOption('--forwardingAddress <string>', 'bech32 address', String)
Expand Down
19 changes: 12 additions & 7 deletions packages/fast-usdc/src/exos/status-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,18 @@ export const prepareStatusManager = (
/**
* Transactions seen *ever* by the contract.
*
* Note that like all durable stores, this SetStore is stored in IAVL. It
* grows without bound (though the amount of growth per incoming message to
* the contract is bounded). At some point in the future we may want to prune.
* @type {SetStore<EvmHash>}
* Note that like all durable stores, this MapStore is kept in IAVL. It stores
* the `blockTimestamp` so that later we can prune old transactions.
*
* Note that `blockTimestamp` can drift between chains. Fortunately all CCTP
* chains use the same Unix epoch and won't drift more than minutes apart,
* which is more than enough precision for pruning old transaction.
*
* @type {MapStore<EvmHash, NatValue>}
*/
const seenTxs = zone.setStore('SeenTxs', {
const seenTxs = zone.mapStore('SeenTxs', {
keyShape: M.string(),
valueShape: M.nat(),
});

/**
Expand Down Expand Up @@ -160,7 +165,7 @@ export const prepareStatusManager = (
if (seenTxs.has(txHash)) {
throw makeError(`Transaction already seen: ${q(txHash)}`);
}
seenTxs.add(txHash);
seenTxs.init(txHash, evidence.blockTimestamp);

appendToStoredArray(
pendingSettleTxs,
Expand Down Expand Up @@ -305,7 +310,7 @@ export const prepareStatusManager = (
if (seenTxs.has(txHash)) {
throw makeError(`Transaction already seen: ${q(txHash)}`);
}
seenTxs.add(txHash);
seenTxs.init(txHash, evidence.blockTimestamp);
publishEvidence(txHash, evidence);
},

Expand Down
1 change: 1 addition & 0 deletions packages/fast-usdc/src/type-guards.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export const CctpTxEvidenceShape = {
},
blockHash: EvmHashShape,
blockNumber: M.nat(),
blockTimestamp: M.nat(),
chainId: M.number(),
tx: {
amount: M.nat(),
Expand Down
13 changes: 13 additions & 0 deletions packages/fast-usdc/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,21 @@ export interface CctpTxEvidence {
forwardingChannel: IBCChannelID;
recipientAddress: ChainAddress['value'];
};
/** on the source chain (e.g. L1 Ethereum and L2s Arbitrum, Base) */
blockHash: EvmHash;
/** height of blockHash on the source chain */
blockNumber: bigint;
/**
* Seconds since Unix epoch. Not all CCTP source chains update time the same
* way but they all use Unix epoch and thus are approximately equal. (Within
* minutes apart.)
*/
blockTimestamp: bigint;
//
/**
* [Domain of values](https://chainid.network/) per [EIP-155](https://eips.ethereum.org/EIPS/eip-155)
* (We don't have [CCTP `domain`](https://developers.circle.com/stablecoins/supported-domains) )
*/
chainId: number;
/** data covered by signature (aka txHash) */
tx: {
Expand Down
18 changes: 16 additions & 2 deletions packages/fast-usdc/test/fast-usdc.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ const startContract = async (
commonPrivateArgs,
} = common;

const { zoe, bundleAndInstall } = await setUpZoeForTest();
let contractBaggage;
const setJig = ({ baggage }) => {
contractBaggage = baggage;
};

const { zoe, bundleAndInstall } = await setUpZoeForTest({ setJig });
const installation: Installation<FastUsdcSF> =
await bundleAndInstall(contractFile);

Expand All @@ -97,6 +102,7 @@ const startContract = async (

return {
...startKit,
contractBaggage,
terms,
zoe,
metricsSub,
Expand Down Expand Up @@ -175,7 +181,8 @@ type FucContext = EReturn<typeof makeTestContext>;
const test = anyTest as TestFn<FucContext>;
test.before(async t => (t.context = await makeTestContext(t)));

test('baggage', async t => {
// baggage after a simple startInstance, without any other startup logic
test('initial baggage', async t => {
const {
brands: { usdc },
commonPrivateArgs,
Expand All @@ -201,6 +208,13 @@ test('baggage', async t => {
t.snapshot(tree, 'contract baggage after start');
});

test('used baggage', async t => {
const { startKit } = t.context;

const tree = inspectMapStore(startKit.contractBaggage);
t.snapshot(tree, 'contract baggage after start');
});

test('getStaticInfo', async t => {
const { startKit } = t.context;
const { publicFacet } = startKit;
Expand Down
7 changes: 7 additions & 0 deletions packages/fast-usdc/test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const Senders = {
default: '0xDefaultFakeEthereumAddress',
} as unknown as Record<string, EvmAddress>;

const blockTimestamp = 1632340000n;

export const MockCctpTxEvidences: Record<
MockScenario,
(receiverAddress?: string) => CctpTxEvidence
Expand All @@ -27,6 +29,7 @@ export const MockCctpTxEvidences: Record<
blockHash:
'0x90d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee665',
blockNumber: 21037663n,
blockTimestamp,
txHash:
'0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702',
tx: {
Expand All @@ -48,6 +51,7 @@ export const MockCctpTxEvidences: Record<
blockHash:
'0x80d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee699',
blockNumber: 21037669n,
blockTimestamp,
txHash:
'0xd81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799',
tx: {
Expand All @@ -69,6 +73,7 @@ export const MockCctpTxEvidences: Record<
blockHash:
'0x80d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee6z9',
blockNumber: 21037600n,
blockTimestamp,
txHash:
'0xd81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617z9',
tx: {
Expand All @@ -90,6 +95,7 @@ export const MockCctpTxEvidences: Record<
blockHash:
'0x70d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee699',
blockNumber: 21037669n,
blockTimestamp,
txHash:
'0xa81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799',
tx: {
Expand All @@ -107,6 +113,7 @@ export const MockCctpTxEvidences: Record<
blockHash:
'0x70d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee699',
blockNumber: 21037669n,
blockTimestamp,
txHash:
'0xa81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799',
tx: {
Expand Down
Loading

0 comments on commit eb8538b

Please sign in to comment.