From 9d1ad9ed3e91bfd771f484423601f30fdb2bcd82 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Sat, 19 Oct 2024 10:04:47 +0300 Subject: [PATCH 1/3] chore(price-feed): push prices and experiment with vat information Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/25 --- .../proposals/z:acceptance/priceFeed.test.js | 78 ++++++++++++++ .../z:acceptance/test-lib/priceFeed-lib.js | 56 ++++++++++ .../z:acceptance/test-lib/vat-helpers.js | 102 ++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 a3p-integration/proposals/z:acceptance/priceFeed.test.js create mode 100644 a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.js create mode 100644 a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js diff --git a/a3p-integration/proposals/z:acceptance/priceFeed.test.js b/a3p-integration/proposals/z:acceptance/priceFeed.test.js new file mode 100644 index 00000000000..ba1037c47ef --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/priceFeed.test.js @@ -0,0 +1,78 @@ +import test from 'ava'; +import { + getTranscriptItemsForVat, + getVatsWithSameName, + swingStore, +} from './test-lib/vat-helpers.js'; +import { + addPreexistingOracles, + generateOracleMap, + getPriceQuote, + GOV1ADDR, + GOV2ADDR, + GOV3ADDR, + pushPrices, + registerOraclesForBrand, + waitForBlock, +} from '@agoric/synthetic-chain'; +import { bankSend, pollRoundIdAndPushPrice } from './test-lib/priceFeed-lib.js'; + +const init = async oraclesByBrand => { + await registerOraclesForBrand('ATOM', oraclesByBrand); + await waitForBlock(3); + await registerOraclesForBrand('stATOM', oraclesByBrand); + await waitForBlock(3); + + await pushPrices(1, 'ATOM', oraclesByBrand, 1); + await waitForBlock(3); + await pushPrices(1, 'stATOM', oraclesByBrand, 1); +} + +/** + * @typedef {Map>} OraclesByBrand + */ + +test.before.skip(async t => { + // Fund each oracle members with 10IST incase we hit batch limit here https://github.com/Agoric/agoric-sdk/issues/6525 + await bankSend(GOV2ADDR, '10000000uist', GOV1ADDR); + await bankSend(GOV3ADDR, '10000000uist', GOV1ADDR); + + const oraclesByBrand = generateOracleMap('f-priceFeeds', ['ATOM', 'stATOM']); + t.log(oraclesByBrand); + + await init(oraclesByBrand); + t.context = { + oraclesByBrand, + }; +}); + +test.skip('push-price', async t => { + // @ts-expect-error casting + const { oraclesByBrand } = t.context; + + await pollRoundIdAndPushPrice('ATOM', 25, oraclesByBrand); + await pollRoundIdAndPushPrice('stATOM', 21, oraclesByBrand); + + const atomOut = await getPriceQuote('ATOM'); + t.is(atomOut, '+25000000'); + const stAtomOut = await getPriceQuote('stATOM'); + t.is(stAtomOut, '+21000000'); + t.pass(); +}); + +test.serial('dum', async t => { + // const stATOM = await getVatsWithSameName('-stATOM-USD_price_feed'); + // const scaledStATOM = await getVatsWithSameName( + // '-scaledPriceAuthority-stATOM', + // ); + // t.log(scaledStATOM); + const { findVatsAll, lookupVat, findVatsExact } = swingStore; + + // const items = findVatsAll('scaledPriceAuthority'); + // items.forEach(id => console.log(lookupVat(id).options(), id)); + + const stATOMScaledPAs = findVatsExact('-scaledPriceAuthority-stATOM'); + stATOMScaledPAs.forEach(id => console.log(getTranscriptItemsForVat(id, 1).map((element) =>( JSON.parse(element.item).d[0])))); + + t.pass(); +}); diff --git a/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.js b/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.js new file mode 100644 index 00000000000..c01518d08fe --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.js @@ -0,0 +1,56 @@ +import { + agd, + CHAINID, + VALIDATORADDR, + agoric as agoricAmbient, + pushPrices, +} from '@agoric/synthetic-chain'; + +/** + * Import from synthetic-chain once it is updated + * + * @param {string} addr + * @param {string} wanted + * @param {string} [from] + */ +export const bankSend = (addr, wanted, from = VALIDATORADDR) => { + const chain = ['--chain-id', CHAINID]; + const fromArg = ['--from', from]; + const testKeyring = ['--keyring-backend', 'test']; + const noise = [...fromArg, ...chain, ...testKeyring, '--yes']; + + return agd.tx('bank', 'send', from, addr, wanted, ...noise); +}; + +/** + * Import from synthetic-chain when https://github.com/Agoric/agoric-3-proposals/pull/183 is in + * + * @param {string} price + * @param {{ + * agoric?: { follow: () => Promise}; + * prefix?: string + * }} io + * @returns + */ +export const getRoundId = async (price, io = {}) => { + const { agoric = { follow: agoricAmbient.follow }, prefix = 'published.' } = + io; + const path = `:${prefix}priceFeed.${price}-USD_price_feed.latestRound`; + const round = await agoric.follow('-lF', path); + return parseInt(round.roundId); +}; + +/** + * + * @param {string} brandIn + * @param {number} price + * @param {import('../priceFeed.test.js').OraclesByBrand} oraclesByBrand + */ +export const pollRoundIdAndPushPrice = async ( + brandIn, + price, + oraclesByBrand, +) => { + const roundId = await getRoundId(brandIn); + await pushPrices(price, brandIn, oraclesByBrand, roundId + 1); +}; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js b/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js new file mode 100644 index 00000000000..a9d0d07878c --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js @@ -0,0 +1,102 @@ +import dbOpenAmbient from 'better-sqlite3'; +import { HOME, dbTool } from '@agoric/synthetic-chain'; + +/** + * @file look up vat incarnation from kernel DB + * @see {getIncarnation} + */ + +const swingstorePath = '~/.agoric/data/agoric/swingstore.sqlite'; + +/** + * @param {import('better-sqlite3').Database} db + */ +export const makeSwingstore = db => { + const sql = dbTool(db); + + /** @param {string} key */ + // @ts-expect-error sqlite typedefs + const kvGet = key => sql.get`select * from kvStore where key = ${key}`.value; + /** @param {string} key */ + const kvGetJSON = key => JSON.parse(kvGet(key)); + + /** @param {string} vatID */ + const lookupVat = vatID => { + return Object.freeze({ + source: () => kvGetJSON(`${vatID}.source`), + options: () => kvGetJSON(`${vatID}.options`), + currentSpan: () => + sql.get`select * from transcriptSpans where isCurrent = 1 and vatID = ${vatID}`, + }); + }; + + return Object.freeze({ + /** @param {string} vatName */ + findVat: vatName => { + /** @type {string[]} */ + const dynamicIDs = kvGetJSON('vat.dynamicIDs'); + const targetVat = dynamicIDs.find(vatID => + lookupVat(vatID).options().name.includes(vatName), + ); + if (!targetVat) throw Error(`vat not found: ${vatName}`); + return targetVat; + }, + /** @param {string} string a substring to search for within the vat name. */ + findVatsExact: string => { + /** @type {string[]} */ + const dynamicIDs = kvGetJSON('vat.dynamicIDs'); + return dynamicIDs.filter(vatID => + lookupVat(vatID).options().name.endsWith(string), + ); + }, + findVatsAll: string => { + /** @type {string[]} */ + const dynamicIDs = kvGetJSON('vat.dynamicIDs'); + return dynamicIDs.filter(vatID => + lookupVat(vatID).options().name.includes(string), + ); + }, + lookupVat, + kvGetJSON, + sql, + db, + }); +}; + +/** + * @param {string} vatName + */ +export const getVatsWithSameName = async vatName => { + const fullPath = swingstorePath.replace(/^~/, HOME); + const kStore = makeSwingstore(dbOpenAmbient(fullPath, { readonly: true })); + + const vatIDs = kStore.findVatsExact(vatName); + const vats = vatIDs.map(id => { + const vatLookup = kStore.lookupVat(id); + return { + options: vatLookup.options(), + currentSpan: vatLookup.currentSpan(), + }; + }); + return vats; +}; + +export const getTranscriptItemsForVat = (vatId, n = 10) => { + const fullPath = swingstorePath.replace(/^~/, HOME); + const { sql, db } = makeSwingstore( + dbOpenAmbient(fullPath, { readonly: true }), + ); + + // const items = sql.get`select * from transcriptItems where vatId = ${vatId} order by position desc limit ${n}`; + const items = db + .prepare( + 'select * from transcriptItems where vatId = ? order by position desc limit ?', + ) + .all(vatId, n); + // console.log(items); + return items; +}; + +export const swingStore = makeSwingstore( + dbOpenAmbient(swingstorePath.replace(/^~/, HOME), { readonly: true }), +); From d3393aadcc755db0a51dc23233412c56223be121 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 24 Oct 2024 12:32:06 +0100 Subject: [PATCH 2/3] chore(acceptance-price-feeds): make sure old vats are quiescent and vaults receive quotes Refs: https://github.com/Agoric/agoric-sdk/issues/9928 Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/25 fix: lint fixes Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/25 --- .../proposals/z:acceptance/package.json | 3 +- .../proposals/z:acceptance/priceFeed.test.js | 101 ++++++++++++----- .../z:acceptance/test-lib/priceFeed-lib.js | 89 ++++++++++++++- .../test-lib/priceFeed-lib.test.js | 103 ++++++++++++++++++ .../z:acceptance/test-lib/vat-helpers.js | 42 +++++-- 5 files changed, 296 insertions(+), 42 deletions(-) create mode 100644 a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.test.js diff --git a/a3p-integration/proposals/z:acceptance/package.json b/a3p-integration/proposals/z:acceptance/package.json index a5910e5ded6..e5941bd1a8d 100644 --- a/a3p-integration/proposals/z:acceptance/package.json +++ b/a3p-integration/proposals/z:acceptance/package.json @@ -16,7 +16,8 @@ "@endo/init": "^1.1.4", "ava": "^6.1.2", "execa": "^9.3.1", - "tsx": "^4.17.0" + "tsx": "^4.17.0", + "better-sqlite3": "11.5.0" }, "ava": { "concurrency": 1, diff --git a/a3p-integration/proposals/z:acceptance/priceFeed.test.js b/a3p-integration/proposals/z:acceptance/priceFeed.test.js index ba1037c47ef..94be7d731ac 100644 --- a/a3p-integration/proposals/z:acceptance/priceFeed.test.js +++ b/a3p-integration/proposals/z:acceptance/priceFeed.test.js @@ -1,11 +1,6 @@ import test from 'ava'; +import '@endo/init'; import { - getTranscriptItemsForVat, - getVatsWithSameName, - swingStore, -} from './test-lib/vat-helpers.js'; -import { - addPreexistingOracles, generateOracleMap, getPriceQuote, GOV1ADDR, @@ -15,8 +10,38 @@ import { registerOraclesForBrand, waitForBlock, } from '@agoric/synthetic-chain'; -import { bankSend, pollRoundIdAndPushPrice } from './test-lib/priceFeed-lib.js'; +import { snapshotVat } from './test-lib/vat-helpers.js'; +import { + bankSend, + ensureGCDeliveryOnly, + getQuoteFromVault, + pollRoundIdAndPushPrice, + scale6, +} from './test-lib/priceFeed-lib.js'; + +const config = { + vatNames: [ + '-scaledPriceAuthority-stATOM', + '-scaledPriceAuthority-ATOM', + '-stATOM-USD_price_feed', + '-ATOM-USD_price_feed', + ], + snapshots: { before: {}, after: {} }, // Will be filled in the runtime + priceFeeds: { + ATOM: { + price: 29, + managerIndex: 0, + name: 'ATOM', + }, + stATOM: { + price: 25, + managerIndex: 1, + name: 'stATOM', + }, + }, +}; +// Remove this one when #10296 goes in const init = async oraclesByBrand => { await registerOraclesForBrand('ATOM', oraclesByBrand); await waitForBlock(3); @@ -26,13 +51,13 @@ const init = async oraclesByBrand => { await pushPrices(1, 'ATOM', oraclesByBrand, 1); await waitForBlock(3); await pushPrices(1, 'stATOM', oraclesByBrand, 1); -} +}; /** * @typedef {Map>} OraclesByBrand */ -test.before.skip(async t => { +test.before(async t => { // Fund each oracle members with 10IST incase we hit batch limit here https://github.com/Agoric/agoric-sdk/issues/6525 await bankSend(GOV2ADDR, '10000000uist', GOV1ADDR); await bankSend(GOV3ADDR, '10000000uist', GOV1ADDR); @@ -46,33 +71,53 @@ test.before.skip(async t => { }; }); -test.skip('push-price', async t => { +test.serial('snapshot state', t => { + config.vatNames.forEach(name => { + config.snapshots.before[name] = snapshotVat(name); + }); + console.dir(config.snapshots, { depth: null }); + t.pass(); +}); + +test.serial('push-price', async t => { // @ts-expect-error casting const { oraclesByBrand } = t.context; + const { + priceFeeds: { ATOM, stATOM }, + } = config; - await pollRoundIdAndPushPrice('ATOM', 25, oraclesByBrand); - await pollRoundIdAndPushPrice('stATOM', 21, oraclesByBrand); + await pollRoundIdAndPushPrice(ATOM.name, ATOM.price, oraclesByBrand); + await pollRoundIdAndPushPrice(stATOM.name, stATOM.price, oraclesByBrand); - const atomOut = await getPriceQuote('ATOM'); - t.is(atomOut, '+25000000'); - const stAtomOut = await getPriceQuote('stATOM'); - t.is(stAtomOut, '+21000000'); + const atomOut = await getPriceQuote(ATOM.name); + t.is(atomOut, `+${scale6(ATOM.price)}`); + const stAtomOut = await getPriceQuote(stATOM.name); + t.is(stAtomOut, `+${scale6(stATOM.price)}`); t.pass(); }); -test.serial('dum', async t => { - // const stATOM = await getVatsWithSameName('-stATOM-USD_price_feed'); - // const scaledStATOM = await getVatsWithSameName( - // '-scaledPriceAuthority-stATOM', - // ); - // t.log(scaledStATOM); - const { findVatsAll, lookupVat, findVatsExact } = swingStore; +test.serial('snapshot state after price pushed', t => { + config.vatNames.forEach(name => { + config.snapshots.after[name] = snapshotVat(name); + }); + console.dir(config.snapshots, { depth: null }); + t.pass(); +}); - // const items = findVatsAll('scaledPriceAuthority'); - // items.forEach(id => console.log(lookupVat(id).options(), id)); +test.serial('ensure only gc', t => { + ensureGCDeliveryOnly(config.snapshots); + t.pass(); +}); - const stATOMScaledPAs = findVatsExact('-scaledPriceAuthority-stATOM'); - stATOMScaledPAs.forEach(id => console.log(getTranscriptItemsForVat(id, 1).map((element) =>( JSON.parse(element.item).d[0])))); +test.serial('make sure vaults got the prices', async t => { + const { + priceFeeds: { ATOM, stATOM }, + } = config; + const [atomVaultQuote, stAtomVaultQuote] = await Promise.all([ + getQuoteFromVault(ATOM.managerIndex), + getQuoteFromVault(stATOM.managerIndex), + ]); - t.pass(); + t.is(atomVaultQuote, scale6(ATOM.price).toString()); + t.is(stAtomVaultQuote, scale6(stATOM.price).toString()); }); diff --git a/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.js b/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.js index c01518d08fe..a7f98d26b8e 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.js @@ -5,6 +5,29 @@ import { agoric as agoricAmbient, pushPrices, } from '@agoric/synthetic-chain'; +import { Fail, q } from '@endo/errors'; +import { getTranscriptItemsForVat } from './vat-helpers.js'; + +/** + * By the time we push prices to the new price feed vat, the old one might receive + * some deliveries related to GC events. These delivery types might be; 'dropExports', + * 'retireExports', 'retireImports', 'bringOutYourDead'. + * + * Even though we don't expect to receive all these types of deliveries at once; + * choosing MAX_DELIVERIES_ALLOWED = 5 seems reasonable. + */ +const MAX_DELIVERIES_ALLOWED = 5; + +export const scale6 = x => BigInt(x * 1000000); + +/** + * @typedef {Record< + * string, + * Record + * >} SnapshotItem + * + * @typedef {Record} Snapshots + */ /** * Import from synthetic-chain once it is updated @@ -37,7 +60,7 @@ export const getRoundId = async (price, io = {}) => { io; const path = `:${prefix}priceFeed.${price}-USD_price_feed.latestRound`; const round = await agoric.follow('-lF', path); - return parseInt(round.roundId); + return parseInt(round.roundId, 10); }; /** @@ -54,3 +77,67 @@ export const pollRoundIdAndPushPrice = async ( const roundId = await getRoundId(brandIn); await pushPrices(price, brandIn, oraclesByBrand, roundId + 1); }; + +/** + * @param {SnapshotItem} snapShotItem + */ +export const getQuiescentVats = snapShotItem => { + const quiescentVats = {}; + [...Object.values(snapShotItem)].forEach(vats => { + const keyOne = Object.keys(vats)[0]; + const keyTwo = Object.keys(vats)[1]; + + return parseInt(keyOne.substring(1), 10) > parseInt(keyTwo.substring(1), 10) + ? (quiescentVats[keyTwo] = vats[keyTwo]) + : (quiescentVats[keyOne] = vats[keyOne]); + }); + + return quiescentVats; +}; + +/** + * + * @param {Snapshots} snapshots + * @param {{ getTranscriptItems?: () => Array}} io + */ +export const ensureGCDeliveryOnly = (snapshots, io = {}) => { + const { getTranscriptItems = getTranscriptItemsForVat } = io; + + const { after, before } = snapshots; + const quiescentVatsBefore = getQuiescentVats(before); + const quiescentVatsAfter = getQuiescentVats(after); + + console.dir(quiescentVatsBefore, { depth: null }); + console.dir(quiescentVatsAfter, { depth: null }); + + [...Object.entries(quiescentVatsBefore)].forEach(([vatId, position]) => { + const afterPosition = quiescentVatsAfter[vatId]; + const messageDiff = afterPosition - position; + console.log(vatId, messageDiff); + + if (messageDiff > MAX_DELIVERIES_ALLOWED) + Fail`${q(messageDiff)} deliveries is greater than maximum allowed: ${q(MAX_DELIVERIES_ALLOWED)}`; + else if (messageDiff === 0) return; + + const transcripts = getTranscriptItems(vatId, messageDiff); + console.log('TRANSCRIPTS', transcripts); + + transcripts.forEach(({ item }) => { + const deliveryType = JSON.parse(item).d[0]; + console.log('DELIVERY TYPE', deliveryType); + if (deliveryType === 'notify' || deliveryType === 'message') + Fail`DeliveryType ${q(deliveryType)} is not GC delivery`; + }); + }); +}; + +/** + * @param {number} managerIndex + */ +export const getQuoteFromVault = async managerIndex => { + const res = await agoricAmbient.follow( + '-lF', + `:published.vaultFactory.managers.manager${managerIndex}.quotes`, + ); + return res.quoteAmount.value[0].amountOut.value; +}; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.test.js b/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.test.js new file mode 100644 index 00000000000..5d2adad73ee --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.test.js @@ -0,0 +1,103 @@ +import test from 'ava'; +import '@endo/init'; +import { ensureGCDeliveryOnly } from './priceFeed-lib.js'; + +const testConfig = { + before: { + '-scaledPriceAuthority-stATOM': { v58: 13, v74: 119 }, + '-scaledPriceAuthority-ATOM': { v46: 77, v73: 178 }, + '-stATOM-USD_price_feed': { v57: 40, v72: 192 }, + '-ATOM-USD_price_feed': { v29: 100, v70: 247 }, + }, + after: { + '-scaledPriceAuthority-stATOM': { v58: 15, v74: 119 }, + '-scaledPriceAuthority-ATOM': { v46: 79, v73: 178 }, + '-stATOM-USD_price_feed': { v57: 42, v72: 192 }, + '-ATOM-USD_price_feed': { v29: 102, v70: 247 }, + }, +}; + +const makeFakeGetTranscriptItemsForVat = ( + deliveryType, + maximumAllowedDeliveries, +) => { + const fakeGetTranscriptItemsForVat = (_, number) => { + const fakeTranscriptItems = []; + for (let i = 0; i < number; i += 1) { + const item = { d: [deliveryType] }; + fakeTranscriptItems.push({ item: JSON.stringify(item) }); + } + return fakeTranscriptItems; + }; + + const tooManyTranscriptItemsForVat = () => { + const fakeTranscriptItems = []; + for (let i = 0; i <= maximumAllowedDeliveries; i += 1) { + const item = { d: [deliveryType] }; + fakeTranscriptItems.push({ item: JSON.stringify(item) }); + } + return fakeTranscriptItems; + }; + + return { fakeGetTranscriptItemsForVat, tooManyTranscriptItemsForVat }; +}; + +test('should not throw', t => { + const { fakeGetTranscriptItemsForVat } = + makeFakeGetTranscriptItemsForVat('dropExports'); + + t.notThrows(() => + ensureGCDeliveryOnly(testConfig, { + getTranscriptItems: fakeGetTranscriptItemsForVat, + }), + ); +}); + +test('should throw for "notify"', t => { + const { fakeGetTranscriptItemsForVat } = + makeFakeGetTranscriptItemsForVat('notify'); + + t.throws( + () => + ensureGCDeliveryOnly(testConfig, { + getTranscriptItems: fakeGetTranscriptItemsForVat, + }), + { message: 'DeliveryType "notify" is not GC delivery' }, + ); +}); + +test('should throw for "message"', t => { + const { fakeGetTranscriptItemsForVat } = + makeFakeGetTranscriptItemsForVat('message'); + + t.throws( + () => + ensureGCDeliveryOnly(testConfig, { + getTranscriptItems: fakeGetTranscriptItemsForVat, + }), + { message: 'DeliveryType "message" is not GC delivery' }, + ); +}); + +test('should throw too many deliveries', t => { + const { fakeGetTranscriptItemsForVat } = makeFakeGetTranscriptItemsForVat( + 'dropExports', + 5, + ); + + const config = { + ...testConfig, + after: { + ...testConfig.after, + '-scaledPriceAuthority-stATOM': { v58: 20, v74: 119 }, + }, + }; + + t.throws( + () => + ensureGCDeliveryOnly(config, { + getTranscriptItems: fakeGetTranscriptItemsForVat, + }), + { message: '7 deliveries is greater than maximum allowed: 5' }, + ); +}); diff --git a/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js b/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js index a9d0d07878c..f4e42d8a3c6 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js @@ -2,8 +2,7 @@ import dbOpenAmbient from 'better-sqlite3'; import { HOME, dbTool } from '@agoric/synthetic-chain'; /** - * @file look up vat incarnation from kernel DB - * @see {getIncarnation} + * @typedef {{position: number; item: string; vatID: string; incarnation: number}} TranscriptItem */ const swingstorePath = '~/.agoric/data/agoric/swingstore.sqlite'; @@ -57,18 +56,20 @@ export const makeSwingstore = db => { ); }, lookupVat, - kvGetJSON, - sql, db, }); }; +const initSwingstore = () => { + const fullPath = swingstorePath.replace(/^~/, HOME); + return makeSwingstore(dbOpenAmbient(fullPath, { readonly: true })); +}; + /** * @param {string} vatName */ export const getVatsWithSameName = async vatName => { - const fullPath = swingstorePath.replace(/^~/, HOME); - const kStore = makeSwingstore(dbOpenAmbient(fullPath, { readonly: true })); + const kStore = initSwingstore(); const vatIDs = kStore.findVatsExact(vatName); const vats = vatIDs.map(id => { @@ -81,22 +82,39 @@ export const getVatsWithSameName = async vatName => { return vats; }; +/** + * + * @param {string} vatId + * @param {number} n + * @returns {Array} + */ export const getTranscriptItemsForVat = (vatId, n = 10) => { - const fullPath = swingstorePath.replace(/^~/, HOME); - const { sql, db } = makeSwingstore( - dbOpenAmbient(fullPath, { readonly: true }), - ); + const { db } = initSwingstore(); - // const items = sql.get`select * from transcriptItems where vatId = ${vatId} order by position desc limit ${n}`; const items = db .prepare( 'select * from transcriptItems where vatId = ? order by position desc limit ?', ) .all(vatId, n); - // console.log(items); + + // @ts-expect-error casting problem when assigning values coming from db return items; }; +export const snapshotVat = vatName => { + const { findVatsExact } = initSwingstore(); + + const snapshots = {}; + const vatIdsWithExactName = findVatsExact(vatName); + vatIdsWithExactName.forEach(id => { + const element = getTranscriptItemsForVat(id, 1)[0]; + + snapshots[id] = element.position; + }); + + return snapshots; +}; + export const swingStore = makeSwingstore( dbOpenAmbient(swingstorePath.replace(/^~/, HOME), { readonly: true }), ); From cbee1a58c3ffcba87c34cdcfefc2a0d72f789e48 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 24 Oct 2024 14:36:22 +0100 Subject: [PATCH 3/3] chore(acceptance-price-feeds): apply sync tools, adjust `vaults.test.js` to the changes `priceFeed.test.js` introduces Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/25 Refs: https://github.com/Agoric/agoric-sdk/issues/9928 fix: clear unused code fix: prettier fix --- .../proposals/z:acceptance/package.json | 4 +- .../proposals/z:acceptance/priceFeed.test.js | 103 ++++++++++++++++-- .../z:acceptance/test-lib/vat-helpers.js | 26 +---- .../proposals/z:acceptance/test.sh | 3 + .../proposals/z:acceptance/vaults.test.js | 4 +- .../proposals/z:acceptance/yarn.lock | 12 ++ 6 files changed, 117 insertions(+), 35 deletions(-) diff --git a/a3p-integration/proposals/z:acceptance/package.json b/a3p-integration/proposals/z:acceptance/package.json index e5941bd1a8d..425de5fd527 100644 --- a/a3p-integration/proposals/z:acceptance/package.json +++ b/a3p-integration/proposals/z:acceptance/package.json @@ -15,9 +15,9 @@ "@endo/far": "^1.1.5", "@endo/init": "^1.1.4", "ava": "^6.1.2", + "better-sqlite3": "11.5.0", "execa": "^9.3.1", - "tsx": "^4.17.0", - "better-sqlite3": "11.5.0" + "tsx": "^4.17.0" }, "ava": { "concurrency": 1, diff --git a/a3p-integration/proposals/z:acceptance/priceFeed.test.js b/a3p-integration/proposals/z:acceptance/priceFeed.test.js index 94be7d731ac..37c5ddfef7b 100644 --- a/a3p-integration/proposals/z:acceptance/priceFeed.test.js +++ b/a3p-integration/proposals/z:acceptance/priceFeed.test.js @@ -1,6 +1,20 @@ +/* eslint-env node */ + +/** + * @file The purpose of this test is to make sure; + * - Old priceFeed and scaledPriceAuthority vats that are replaced with new ones are truly quiescent. + * The method we use for this is to check if those vats received any deliveries from swingset that + * are of type "message" or "notify" (We give delivery types related to GC a pass since GC cycles + * aren't in our control). + * - Make sure new price feeds can produce quotes + * - Make sure vaults receive quotes + */ + import test from 'ava'; import '@endo/init'; import { + agd, + agoric, generateOracleMap, getPriceQuote, GOV1ADDR, @@ -8,7 +22,6 @@ import { GOV3ADDR, pushPrices, registerOraclesForBrand, - waitForBlock, } from '@agoric/synthetic-chain'; import { snapshotVat } from './test-lib/vat-helpers.js'; import { @@ -18,6 +31,16 @@ import { pollRoundIdAndPushPrice, scale6, } from './test-lib/priceFeed-lib.js'; +import { + retryUntilCondition, + waitUntilOfferResult, +} from './test-lib/sync-tools.js'; + +const ambientAuthority = { + query: agd.query, + follow: agoric.follow, + setTimeout, +}; const config = { vatNames: [ @@ -41,16 +64,80 @@ const config = { }, }; -// Remove this one when #10296 goes in +/** + * https://github.com/Agoric/agoric-sdk/pull/10074 introduced new price feeds to the system. + * However, `f:replace-price-feeds` does not activate oracles for future layers of the build. + * Meaning, proposals running after `f:replace-price-feeds` will not have oracles that received + * invitationMakers for new price feeds and there will not be quotes published by new + * price feeds. There are conflicting work to fix this issue, see; + * - https://github.com/Agoric/agoric-sdk/pull/10296 + * - https://github.com/Agoric/agoric-sdk/pull/10317 + * - https://github.com/Agoric/agoric-sdk/pull/10296#pullrequestreview-2389390624 + * + * The purpose of init() is to unblock testing new price feeds from the situation above. We can remove + * this when it resolves. + * + * @param {Map>} oraclesByBrand + */ const init = async oraclesByBrand => { - await registerOraclesForBrand('ATOM', oraclesByBrand); - await waitForBlock(3); - await registerOraclesForBrand('stATOM', oraclesByBrand); - await waitForBlock(3); + const retryOptions = { + log: console.log, + maxRetries: 5, + retryIntervalMs: 3000, + }; + + const atomInviteOffers = []; + registerOraclesForBrand('ATOM', oraclesByBrand); + // @ts-expect-error we expect oraclesByBrand.get('ATOM') will not return undefined + for (const { address, offerId } of oraclesByBrand.get('ATOM')) { + const offerP = waitUntilOfferResult( + address, + offerId, + false, + ambientAuthority, + { + errorMessage: `ERROR: ${address} could not accept invite, offerID: ${offerId}`, + ...retryOptions, + }, + ); + atomInviteOffers.push(offerP); + } + await Promise.all(atomInviteOffers); + + const stAtomInviteOffers = []; + registerOraclesForBrand('stATOM', oraclesByBrand); + // @ts-expect-error we expect oraclesByBrand.get('ATOM') will not return undefined + for (const { address, offerId } of oraclesByBrand.get('stATOM')) { + const offerP = waitUntilOfferResult( + address, + offerId, + false, + ambientAuthority, + { + errorMessage: `ERROR: ${address} could not accept invite, offerID: ${offerId}`, + ...retryOptions, + }, + ); + + stAtomInviteOffers.push(offerP); + } + await Promise.all(stAtomInviteOffers); await pushPrices(1, 'ATOM', oraclesByBrand, 1); - await waitForBlock(3); + // await waitForBlock(3); + await retryUntilCondition( + () => getPriceQuote('ATOM'), + res => res === '+1000000', + 'ATOM quote not received', + { ...retryOptions, setTimeout }, + ); await pushPrices(1, 'stATOM', oraclesByBrand, 1); + await retryUntilCondition( + () => getPriceQuote('stATOM'), + res => res === '+1000000', + 'stATOM quote not received', + { ...retryOptions, setTimeout }, + ); }; /** @@ -62,7 +149,7 @@ test.before(async t => { await bankSend(GOV2ADDR, '10000000uist', GOV1ADDR); await bankSend(GOV3ADDR, '10000000uist', GOV1ADDR); - const oraclesByBrand = generateOracleMap('f-priceFeeds', ['ATOM', 'stATOM']); + const oraclesByBrand = generateOracleMap('z-acc', ['ATOM', 'stATOM']); t.log(oraclesByBrand); await init(oraclesByBrand); diff --git a/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js b/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js index f4e42d8a3c6..b37af097248 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/vat-helpers.js @@ -8,9 +8,12 @@ import { HOME, dbTool } from '@agoric/synthetic-chain'; const swingstorePath = '~/.agoric/data/agoric/swingstore.sqlite'; /** + * Initially from https://github.com/Agoric/agoric-3-proposals/blob/93bb953db209433499db08ae563942d1bf7eeb46/proposals/76%3Avaults-auctions/vatDetails.js#L36 + * but with small modifications + * * @param {import('better-sqlite3').Database} db */ -export const makeSwingstore = db => { +const makeSwingstore = db => { const sql = dbTool(db); /** @param {string} key */ @@ -65,23 +68,6 @@ const initSwingstore = () => { return makeSwingstore(dbOpenAmbient(fullPath, { readonly: true })); }; -/** - * @param {string} vatName - */ -export const getVatsWithSameName = async vatName => { - const kStore = initSwingstore(); - - const vatIDs = kStore.findVatsExact(vatName); - const vats = vatIDs.map(id => { - const vatLookup = kStore.lookupVat(id); - return { - options: vatLookup.options(), - currentSpan: vatLookup.currentSpan(), - }; - }); - return vats; -}; - /** * * @param {string} vatId @@ -114,7 +100,3 @@ export const snapshotVat = vatName => { return snapshots; }; - -export const swingStore = makeSwingstore( - dbOpenAmbient(swingstorePath.replace(/^~/, HOME), { readonly: true }), -); diff --git a/a3p-integration/proposals/z:acceptance/test.sh b/a3p-integration/proposals/z:acceptance/test.sh index f9982b31916..a83c2f51b53 100755 --- a/a3p-integration/proposals/z:acceptance/test.sh +++ b/a3p-integration/proposals/z:acceptance/test.sh @@ -26,5 +26,8 @@ echo ACCEPTANCE TESTING state sync echo ACCEPTANCE TESTING wallet yarn ava wallet.test.js +echo ACCEPTANCE TESTING replaced price feeds +yarn ava priceFeed.test.js + echo ACCEPTANCE TESTING vaults yarn ava vaults.test.js diff --git a/a3p-integration/proposals/z:acceptance/vaults.test.js b/a3p-integration/proposals/z:acceptance/vaults.test.js index 5545a9381be..1b21a049367 100644 --- a/a3p-integration/proposals/z:acceptance/vaults.test.js +++ b/a3p-integration/proposals/z:acceptance/vaults.test.js @@ -14,7 +14,6 @@ import { ATOM_DENOM, USER1ADDR, waitForBlock, - registerOraclesForBrand, generateOracleMap, } from '@agoric/synthetic-chain'; import { getBalances, agopsVaults } from './test-lib/utils.js'; @@ -30,13 +29,12 @@ test.before(async t => { retryIntervalMs: 5000, // in ms }; t.context = { - roundId: 1, + roundId: 3, retryOpts: { pushPriceRetryOpts, }, }; const oraclesByBrand = generateOracleMap('z-acc', ['ATOM']); - await registerOraclesForBrand('ATOM', oraclesByBrand); const price = 15.2; // @ts-expect-error t.context is fine diff --git a/a3p-integration/proposals/z:acceptance/yarn.lock b/a3p-integration/proposals/z:acceptance/yarn.lock index 72977f2a884..8741b14d421 100644 --- a/a3p-integration/proposals/z:acceptance/yarn.lock +++ b/a3p-integration/proposals/z:acceptance/yarn.lock @@ -784,6 +784,17 @@ __metadata: languageName: node linkType: hard +"better-sqlite3@npm:11.5.0": + version: 11.5.0 + resolution: "better-sqlite3@npm:11.5.0" + dependencies: + bindings: "npm:^1.5.0" + node-gyp: "npm:latest" + prebuild-install: "npm:^7.1.1" + checksum: 10c0/c24200972e11f6f99c4e6538122bd7ec8b31b92b2fa095f4b595cc39fedf924cb0a93fd326f0900415eccdf634367f7bba2ba4eaa4d164edd7352f4cfaaaec51 + languageName: node + linkType: hard + "better-sqlite3@npm:^9.6.0": version: 9.6.0 resolution: "better-sqlite3@npm:9.6.0" @@ -2517,6 +2528,7 @@ __metadata: "@endo/far": "npm:^1.1.5" "@endo/init": "npm:^1.1.4" ava: "npm:^6.1.2" + better-sqlite3: "npm:11.5.0" execa: "npm:^9.3.1" tsx: "npm:^4.17.0" typescript: "npm:^5.5.4"