Skip to content

Commit

Permalink
chore(sync-tools): implement waitUntilElectionResults (#10385)
Browse files Browse the repository at this point in the history
closes: #10378



## Description

This PR adds a general purpose method that waits for the latest governance question's result based on a desired outcome. Could be used for operations like waiting for a parameter change to complete in `a3p-integration`.  

### Security Considerations

None.

### Scaling Considerations

None.

### Documentation Considerations

None.

### Testing Considerations

Current unit tests check that;
* questionHandle's match between `latestQuestion` and `latestOutcome`. This makes sure outcome belongs the latest question.
* `outcome` matches the desired result
* `deadline` of the question has to be known beforehand in order to make sure the latest question is indeed the one we just asked during our parameter change operation.

### Upgrade Considerations

None.
  • Loading branch information
mergify[bot] authored Nov 15, 2024
2 parents fb2d05c + d560d72 commit 0fa2d27
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 18 deletions.
101 changes: 99 additions & 2 deletions packages/client-utils/src/sync-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* - condition: dest account has a balance >= sent token
* - Making sure an offer resulted successfully
* - Making sure an offer was exited successfully
*
* - Make sure an election held by a given committee (see @agoric/governance) turned out as expected
*/

/**
Expand Down Expand Up @@ -98,7 +98,7 @@ export const retryUntilCondition = async (
}
} catch (error) {
if (error instanceof Error) {
log(`Error: ${error.message}`);
log(`Error: ${error.message}: ${error.stack}`);
} else {
log(`Unknown error: ${String(error)}`);
}
Expand Down Expand Up @@ -325,3 +325,100 @@ export const waitUntilOfferExited = async (addr, offerId, io, options) => {
{ setTimeout, ...resolvedOptions },
);
};

/// ////////// Make sure an election held by a given committee //////////
/// ////////// (see @agoric/governance) turned out as expected //////////

/**
* @typedef {{
* latestOutcome: {
* outcome: string;
* question: import('@endo/marshal').RemotableObject
* },
* latestQuestion: {
* closingRule: { deadline: bigint },
* questionHandle: import('@endo/marshal').RemotableObject
* }
* }} ElectionResult
*/

/**
* @param {string} basePath
* @param {import('./vstorage-kit').VstorageKit} vstorage
* @returns {Promise<ElectionResult>}
*/
const fetchLatestEcQuestion = async (basePath, vstorage) => {
const pathOutcome = `${basePath}.latestOutcome`;
const pathQuestion = `${basePath}.latestQuestion`;

const [latestOutcome, latestQuestion] = await Promise.all([
/** @type {Promise<ElectionResult["latestOutcome"]>} */ (
vstorage.readLatestHead(pathOutcome)
),
/** @type {Promise<ElectionResult["latestQuestion"]>} */ (
vstorage.readLatestHead(pathQuestion)
),
]);

return { latestOutcome, latestQuestion };
};

/**
*
* @param {ElectionResult} electionResult
* @param {{ outcome: string; deadline: bigint }} expectedResult
* @returns {boolean}
*/
const checkCommitteeElectionResult = (electionResult, expectedResult) => {
const {
latestOutcome: { outcome, question },
latestQuestion: {
closingRule: { deadline },
questionHandle,
},
} = electionResult;
const { outcome: expectedOutcome, deadline: expectedDeadline } =
expectedResult;

return (
expectedOutcome === outcome &&
deadline === expectedDeadline &&
question === questionHandle
);
};

/**
* Depends on "@agoric/governance" package's committee implementation where for a given committee
* there's two child nodes in vstorage named "latestOutcome" and "latestQuestion" respectively.
*
* @param {string} committeePathBase
* @param {{
* outcome: string;
* deadline: bigint;
* }} expectedResult
* @param {{
* vstorage: import('./vstorage-kit').VstorageKit;
* log: typeof console.log,
* setTimeout: typeof global.setTimeout
* }} io
* @param {WaitUntilOptions} options
*/
export const waitUntilElectionResult = (
committeePathBase,
expectedResult,
io,
options,
) => {
const { vstorage, log, setTimeout } = io;

const { maxRetries, retryIntervalMs, errorMessage } =
overrideDefaultOptions(options);

return retryUntilCondition(
() => fetchLatestEcQuestion(committeePathBase, vstorage),
electionResult =>
checkCommitteeElectionResult(electionResult, expectedResult),
errorMessage,
{ maxRetries, retryIntervalMs, log, setTimeout },
);
};
1 change: 1 addition & 0 deletions packages/client-utils/test/snapshots/exports.test.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Generated by [AVA](https://avajs.dev).
'storageHelper',
'waitUntilAccountFunded',
'waitUntilContractDeployed',
'waitUntilElectionResult',
'waitUntilInvitationReceived',
'waitUntilOfferExited',
'waitUntilOfferResult',
Expand Down
Binary file modified packages/client-utils/test/snapshots/exports.test.js.snap
Binary file not shown.
Loading

0 comments on commit 0fa2d27

Please sign in to comment.