Skip to content

Commit

Permalink
Extract reading chain info to a separate function
Browse files Browse the repository at this point in the history
  • Loading branch information
Siegrift committed Jan 8, 2024
1 parent d0c223e commit 9fb525d
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 55 deletions.
19 changes: 10 additions & 9 deletions src/update-feeds-loops/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ export const verifyMulticallResponse = (
return returndata;
};

export const decodeActiveDataFeedCountResponse = (
airseekerRegistry: AirseekerRegistry,
activeDataFeedCountReturndata: string
) => {
const activeDataFeedCount = airseekerRegistry.interface.decodeFunctionResult(
'activeDataFeedCount',
activeDataFeedCountReturndata
)[0] as Awaited<ReturnType<AirseekerRegistry['activeDataFeedCount']>>;
return activeDataFeedCount.toNumber();
export const decodeActiveDataFeedCountResponse = (activeDataFeedCountReturndata: string) => {
return ethers.BigNumber.from(activeDataFeedCountReturndata).toNumber();
};

export const decodeGetBlockNumberResponse = (getBlockNumberReturndata: string) => {
return ethers.BigNumber.from(getBlockNumberReturndata).toNumber();
};

export const decodeGetChainIdResponse = (getChainIdReturndata: string) => {
return ethers.BigNumber.from(getChainIdReturndata).toNumber();
};

export const decodeDataFeedDetails = (dataFeed: string): DecodedDataFeed | null => {
Expand Down
7 changes: 6 additions & 1 deletion src/update-feeds-loops/get-updatable-feeds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ describe(multicallBeaconValues.name, () => {

jest.spyOn(contractsModule, 'getApi3ServerV1').mockReturnValue(mockContract as any);

const callAndParseMulticallPromise = await multicallBeaconValues(feedIds as unknown as string[], provider, '31337');
const callAndParseMulticallPromise = await multicallBeaconValues(
feedIds as unknown as string[],
provider,
'31337',
1
);

expect(callAndParseMulticallPromise).toStrictEqual({
'0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6': {
Expand Down
3 changes: 2 additions & 1 deletion src/update-feeds-loops/get-updatable-feeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export const getUpdatableFeeds = async (
const uniqueBeaconIds = [
...new Set(batch.flatMap((item) => item.decodedDataFeed.beacons.flatMap((beacon) => beacon.beaconId))),
];
const goOnChainFeedValues = await go(async () => multicallBeaconValues(uniqueBeaconIds, provider, chainId));
// TODO:
const goOnChainFeedValues = await go(async () => multicallBeaconValues(uniqueBeaconIds, provider, chainId, 0));

Check failure on line 38 in src/update-feeds-loops/get-updatable-feeds.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test

Expected 3 arguments, but got 4.
if (!goOnChainFeedValues.success) {
logger.error(
`Multicalling on-chain data feed values has failed. Skipping update for all data feeds in a batch`,
Expand Down
3 changes: 2 additions & 1 deletion src/update-feeds-loops/submit-transactions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,8 @@ describe(submitTransactionsModule.submitTransaction.name, () => {
dataFeedId: '0xBeaconSetId',
},
},
})
}),
1
);

// Verify that the data feed was updated successfully.
Expand Down
3 changes: 2 additions & 1 deletion src/update-feeds-loops/submit-transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ export const submitTransactions = async (
) =>
Promise.all(
updatableDataFeeds.map(async (dataFeed) =>
submitTransaction(chainId, providerName, provider, api3ServerV1, dataFeed)
// TODO:
submitTransaction(chainId, providerName, provider, api3ServerV1, dataFeed, 0)

Check failure on line 145 in src/update-feeds-loops/submit-transactions.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test

Expected 5 arguments, but got 6.
)
);

Expand Down
111 changes: 69 additions & 42 deletions src/update-feeds-loops/update-feeds-loops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RPC_PROVIDER_TIMEOUT_MS } from '../constants';
import { clearSponsorLastUpdateTimestamp, initializeGasState } from '../gas-price';
import { logger } from '../logger';
import { getState, updateState } from '../state';
import type { AirseekerRegistry } from '../typechain-types';
import type { ChainId, ProviderName } from '../types';
import { deriveSponsorWallet, sleep } from '../utils';

Expand All @@ -17,6 +18,8 @@ import {
verifyMulticallResponse,
type DecodedActiveDataFeedResponse,
getApi3ServerV1,
decodeGetBlockNumberResponse,
decodeGetChainIdResponse,
} from './contracts';
import { getUpdatableFeeds } from './get-updatable-feeds';
import { hasSponsorPendingTransaction, submitTransactions } from './submit-transactions';
Expand Down Expand Up @@ -78,6 +81,61 @@ export const calculateStaggerTimeMs = (
return optimalStaggerTimeMs;
};

export const readActiveDataFeedBatch = async (
airseekerRegistry: AirseekerRegistry,
chainId: string,
fromIndex: number,
toIndex: number
) => {
const calldatas: string[] = [];
if (fromIndex === 0) calldatas.push(airseekerRegistry.interface.encodeFunctionData('activeDataFeedCount'));
calldatas.push(
airseekerRegistry.interface.encodeFunctionData('getBlockNumber'),
airseekerRegistry.interface.encodeFunctionData('getChainId'),
...range(fromIndex, toIndex).map((dataFeedIndex) =>
airseekerRegistry.interface.encodeFunctionData('activeDataFeed', [dataFeedIndex])
)
);

let returndatas = verifyMulticallResponse(await airseekerRegistry.callStatic.tryMulticall(calldatas));
let activeDataFeedCountReturndata: string | undefined;
if (fromIndex === 0) {
activeDataFeedCountReturndata = returndatas[0]!;
returndatas = returndatas.slice(1);
}
const [getBlockNumberReturndata, getChainIdReturndata, ...activeDataFeedReturndatas] = returndatas;

// Check that the chain ID is correct and throw an error if it's not because providers may switch chain ID. In case
// the chain ID is wrong, we want to skip all data feeds in the batch (or all of them in case this is the first
// batch). Another possibility is a wrong chain ID in the configuration (misconfiguration).
const contractChainId = decodeGetChainIdResponse(getChainIdReturndata!);
if (contractChainId !== Number(chainId)) {
throw new Error(`Chain ID mismatch. Expected ${chainId}, got ${contractChainId}.`);
}

// In the first batch we may have asked for a non-existent data feed (index out of bounds). We need to slice them off
// based on the active data feed count.
let activeDataFeedCount: number | undefined;
let batchReturndata = activeDataFeedReturndatas;
if (fromIndex === 0) {
activeDataFeedCount = decodeActiveDataFeedCountResponse(activeDataFeedCountReturndata!);
batchReturndata = activeDataFeedReturndatas.slice(0, activeDataFeedCount);
}
const batch = batchReturndata
.map((dataFeedReturndata) => decodeActiveDataFeedResponse(airseekerRegistry, dataFeedReturndata))
.filter((dataFeed, dataFeedIndex): dataFeed is DecodedActiveDataFeedResponse => {
logger.warn(`Data feed not registered.`, { dataFeedIndex });
return dataFeed !== null;
});

const blockNumber = decodeGetBlockNumberResponse(getBlockNumberReturndata!);
return {
batch,
blockNumber,
...(activeDataFeedCount ? { activeDataFeedCount } : {}),
};
};

export const runUpdateFeeds = async (providerName: ProviderName, chain: Chain, chainId: ChainId) => {
await logger.runWithContext({ chainId, providerName, updateFeedsCoordinatorId: Date.now().toString() }, async () => {
// We do not expect this function to throw, but its possible that some execution path is incorrectly handled and we
Expand All @@ -96,40 +154,15 @@ export const runUpdateFeeds = async (providerName: ProviderName, chain: Chain, c
logger.debug(`Fetching first batch of data feeds batches.`);
const firstBatchStartTimeMs = Date.now();
const goFirstBatch = await go(
async () => {
const activeDataFeedCountCalldata = airseekerRegistry.interface.encodeFunctionData('activeDataFeedCount');
const activeDataFeedCalldatas = range(0, dataFeedBatchSize).map((dataFeedIndex) =>
airseekerRegistry.interface.encodeFunctionData('activeDataFeed', [dataFeedIndex])
);
const [activeDataFeedCountReturndata, ...activeDataFeedCallsReturndata] = verifyMulticallResponse(
await airseekerRegistry.callStatic.tryMulticall([activeDataFeedCountCalldata, ...activeDataFeedCalldatas])
);

const activeDataFeedCount = decodeActiveDataFeedCountResponse(
airseekerRegistry,
activeDataFeedCountReturndata!
);
const firstBatch = activeDataFeedCallsReturndata
// Because the activeDataFeedCount is not known during the multicall, we may ask for non-existent data feeds. These should be filtered out.
.slice(0, activeDataFeedCount)
.map((dataFeedReturndata) => decodeActiveDataFeedResponse(airseekerRegistry, dataFeedReturndata))
.filter((dataFeed, dataFeedIndex): dataFeed is DecodedActiveDataFeedResponse => {
logger.warn(`Data feed not registered.`, { dataFeedIndex });
return dataFeed !== null;
});
return {
firstBatch,
activeDataFeedCount,
};
},
async () => readActiveDataFeedBatch(airseekerRegistry, chainId, 0, dataFeedBatchSize),
{ totalTimeoutMs: dataFeedUpdateIntervalMs }
);
if (!goFirstBatch.success) {
logger.error(`Failed to get first active data feeds batch.`, goFirstBatch.error);
return;
}

const { firstBatch, activeDataFeedCount } = goFirstBatch.data;
const { batch: firstBatch, activeDataFeedCount } = goFirstBatch.data;
if (activeDataFeedCount === 0) {
logger.warn(`No active data feeds found.`);
return;
Expand All @@ -145,7 +178,7 @@ export const runUpdateFeeds = async (providerName: ProviderName, chain: Chain, c
).catch((error) => error);

// Calculate the stagger time.
const batchesCount = Math.ceil(activeDataFeedCount / dataFeedBatchSize);
const batchesCount = Math.ceil(activeDataFeedCount! / dataFeedBatchSize);
const firstBatchDurationMs = Date.now() - firstBatchStartTimeMs;
const staggerTimeMs = calculateStaggerTimeMs(batchesCount, firstBatchDurationMs, dataFeedUpdateIntervalMs);

Expand All @@ -162,26 +195,20 @@ export const runUpdateFeeds = async (providerName: ProviderName, chain: Chain, c
const goBatch = await go(async () => {
logger.debug(`Fetching batch of active data feeds.`, { batchIndex });
const dataFeedBatchIndexStart = batchIndex * dataFeedBatchSize;
const dataFeedBatchIndexEnd = Math.min(activeDataFeedCount, dataFeedBatchIndexStart + dataFeedBatchSize);
const activeDataFeedCalldatas = range(dataFeedBatchIndexStart, dataFeedBatchIndexEnd).map((dataFeedIndex) =>
airseekerRegistry.interface.encodeFunctionData('activeDataFeed', [dataFeedIndex])
);
const returndata = verifyMulticallResponse(
await airseekerRegistry.callStatic.tryMulticall(activeDataFeedCalldatas)
const dataFeedBatchIndexEnd = Math.min(activeDataFeedCount!, dataFeedBatchIndexStart + dataFeedBatchSize);
const { batch, blockNumber } = await readActiveDataFeedBatch(
airseekerRegistry,
chainId,
dataFeedBatchIndexStart,
dataFeedBatchIndexEnd
);

return returndata
.map((returndata) => decodeActiveDataFeedResponse(airseekerRegistry, returndata))
.filter((dataFeed, index): dataFeed is DecodedActiveDataFeedResponse => {
logger.warn(`Data feed not registered.`, { dataFeedIndex: dataFeedBatchIndexStart + index });
return dataFeed !== null;
});
return { batch, blockNumber };
});
if (!goBatch.success) {
logger.error(`Failed to get active data feeds batch.`, goBatch.error);
return;
}
const batch = goBatch.data;
const { batch } = goBatch.data;

return processBatch(batch, providerName, provider, chainId);
});
Expand Down

0 comments on commit 9fb525d

Please sign in to comment.