Skip to content
This repository was archived by the owner on Jul 9, 2024. It is now read-only.

449 update single beacon without multicall #471

Merged
merged 2 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions src/update-data-feeds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,146 @@ describe('updateDataFeedsInLoop', () => {
});
});

describe('updateBeacons', () => {
it('calls updateBeaconWithSignedData in Api3ServerV1 contract for single beacon', async () => {
state.updateState((currentState) => ({
...currentState,
beaconValues: {
'0x2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c': validSignedData,
'0xa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2': undefined as any,
'0x8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd': validSignedData,
},
}));

const txCountSpy = jest.spyOn(ethers.providers.StaticJsonRpcProvider.prototype, 'getTransactionCount');
txCountSpy.mockResolvedValueOnce(212);

const timestamp = 1649664085;

const updateBeaconWithSignedDataMock = jest
.fn()
.mockReturnValueOnce({ hash: ethers.utils.hexlify(ethers.utils.randomBytes(32)) });
const callStaticTryMulticallMock = jest.fn().mockReturnValueOnce({
successes: [true, true],
returndata: [
ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [ethers.BigNumber.from(41000000000), timestamp - 30]),
ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [ethers.BigNumber.from(40000000000), timestamp]),
],
});
jest.spyOn(Api3ServerV1Factory, 'connect').mockImplementation(
(_dapiServerAddress, _provider) =>
({
connect(_signerOrProvider: ethers.Signer | ethers.providers.Provider | string) {
return this;
},
updateBeaconWithSignedData: updateBeaconWithSignedDataMock,
interface: {
encodeFunctionData: (functionFragment: string, values: [any]): string => {
if (functionFragment === 'dataFeeds')
return '0x67a7cfb741c3d6e0ee82ae3d33356c4dceb84e98d1a0b361db0f51081fc5a2541ae51683';

if (functionFragment === 'readDataFeedWithId') {
switch (values[0]) {
case '0x2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c':
return '0xa5fc076f2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c';
case '0xa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2':
return '0xa5fc076fa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2';
case '0x8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd':
return '0xa5fc076f8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd';
}
}

if (functionFragment === 'updateBeaconWithSignedData')
return '0x1a0a0b3e0000000000000000000000005656d3a378b1aadfddcf4196ea364a9d786172909ec34b00a5019442dcd05a4860ff2bf015164b368cb83fcb756088fcabcdabcd000000000000000000000000000000000000000000000000000000006253e05500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000009dc41b78000000000000000000000000000000000000000000000000000000000000000418aace553ec28f53cc976c8a2469d50f16de121d248495117aca36feb4950957827570e0648f82bdbc0afa6cb69dd9fe37dc7f9d58ae3aa06450e627e06c1b8031b00000000000000000000000000000000000000000000000000000000000000';

if (functionFragment === 'updateBeaconSetWithBeacons')
return '0x00aae33f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002924b5d4cb3ec6366ae4302a1ca6aec035594ea3ea48a102d160b50b0c43ebfb5bf7ce55d109fd196de2a8bf1515d166c56c9decbe9cb473656bbca30d5743990';

return '';
},
},
callStatic: { tryMulticall: callStaticTryMulticallMock },
} as any)
);

const groups = api.groupDataFeedsByProviderSponsor();

await api.updateBeacons(groups[0], Date.now());

expect(callStaticTryMulticallMock).toHaveBeenCalledTimes(1);
expect(updateBeaconWithSignedDataMock).toHaveBeenCalledTimes(1);
});

it('calls tryMulticall in Api3ServerV1 contract for multiple beacon updates', async () => {
state.updateState((currentState) => ({
...currentState,
beaconValues: {
'0x2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c': validSignedData,
'0xa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2': validSignedData,
'0x8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd': validSignedData,
},
}));

const txCountSpy = jest.spyOn(ethers.providers.StaticJsonRpcProvider.prototype, 'getTransactionCount');
txCountSpy.mockResolvedValueOnce(212);

const timestamp = 1649664085;

const tryMulticallMock = jest
.fn()
.mockReturnValueOnce({ hash: ethers.utils.hexlify(ethers.utils.randomBytes(32)) });
const callStaticTryMulticallMock = jest.fn().mockReturnValueOnce({
successes: [true, true],
returndata: [
ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [ethers.BigNumber.from(41000000000), timestamp - 30]),
ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [ethers.BigNumber.from(39000000000), timestamp - 30]),
],
});
jest.spyOn(Api3ServerV1Factory, 'connect').mockImplementation(
(_dapiServerAddress, _provider) =>
({
connect(_signerOrProvider: ethers.Signer | ethers.providers.Provider | string) {
return this;
},
tryMulticall: tryMulticallMock,
interface: {
encodeFunctionData: (functionFragment: string, values: [any]): string => {
if (functionFragment === 'dataFeeds')
return '0x67a7cfb741c3d6e0ee82ae3d33356c4dceb84e98d1a0b361db0f51081fc5a2541ae51683';

if (functionFragment === 'readDataFeedWithId') {
switch (values[0]) {
case '0x2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c':
return '0xa5fc076f2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c';
case '0xa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2':
return '0xa5fc076fa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2';
case '0x8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd':
return '0xa5fc076f8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd';
}
}

if (functionFragment === 'updateBeaconWithSignedData')
return '0x1a0a0b3e0000000000000000000000005656d3a378b1aadfddcf4196ea364a9d786172909ec34b00a5019442dcd05a4860ff2bf015164b368cb83fcb756088fcabcdabcd000000000000000000000000000000000000000000000000000000006253e05500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000009dc41b78000000000000000000000000000000000000000000000000000000000000000418aace553ec28f53cc976c8a2469d50f16de121d248495117aca36feb4950957827570e0648f82bdbc0afa6cb69dd9fe37dc7f9d58ae3aa06450e627e06c1b8031b00000000000000000000000000000000000000000000000000000000000000';

if (functionFragment === 'updateBeaconSetWithBeacons')
return '0x00aae33f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002924b5d4cb3ec6366ae4302a1ca6aec035594ea3ea48a102d160b50b0c43ebfb5bf7ce55d109fd196de2a8bf1515d166c56c9decbe9cb473656bbca30d5743990';

return '';
},
},
callStatic: { tryMulticall: callStaticTryMulticallMock },
} as any)
);

const groups = api.groupDataFeedsByProviderSponsor();

await api.updateBeacons(groups[0], Date.now());

expect(callStaticTryMulticallMock).toHaveBeenCalledTimes(1);
expect(tryMulticallMock).toHaveBeenCalledTimes(1);
});
});

describe('updateBeaconSets', () => {
it('calls updateBeaconSetWithBeacons in Api3ServerV1 contract', async () => {
state.updateState((currentState) => ({
Expand Down
87 changes: 61 additions & 26 deletions src/update-data-feeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,25 +238,25 @@ export const updateBeacons = async (providerSponsorDataFeeds: ProviderSponsorDat
for (const readBatch of chunk(beaconUpdates, DATAFEED_READ_BATCH_SIZE)) {
// Read beacon batch onchain values
const goDatafeedsTryMulticall = await go(
() => {
const calldatas = readBatch.map((beaconUpdate) => beaconUpdate.dataFeedsCalldata);
return contract.connect(voidSigner).callStatic.tryMulticall(calldatas);
},
() =>
contract
.connect(voidSigner)
.callStatic.tryMulticall(readBatch.map((beaconUpdate) => beaconUpdate.dataFeedsCalldata)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's nit. However it'd be better to not to use multicall while reading if batch only includes 1 beacon. To merge this PR quick, I created the issue #475.

{
...prepareGoOptions(startTime, totalTimeout),
onAttemptError: (goError) =>
logger.warn(`Failed attempt to read beacon data using multicall. Error ${goError.error}`, logOptions),
logger.warn(`Attempt to read beacon data using tryMulticall has failed. Error ${goError.error}`, logOptions),
}
);
if (!goDatafeedsTryMulticall.success) {
logger.warn(`Unable to read beacon data using multicall. Error: ${goDatafeedsTryMulticall.error}`, logOptions);
logger.warn(`Unable to read beacon data using tryMulticall. Error: ${goDatafeedsTryMulticall.error}`, logOptions);
continue;
}

const { successes, returndata } = goDatafeedsTryMulticall.data;

// Process beacon update calldatas
let beaconUpdateCalldatas: string[] = [];
let beaconUpdates: BeaconUpdate[] = [];

for (let i = 0; i < readBatch.length; i++) {
const beaconReturndata = returndata[i];
Expand Down Expand Up @@ -286,40 +286,75 @@ export const updateBeacons = async (providerSponsorDataFeeds: ProviderSponsorDat
continue;
}

beaconUpdateCalldatas = [
...beaconUpdateCalldatas,
contract.interface.encodeFunctionData('updateBeaconWithSignedData', [
beaconUpdateData.beacon.airnode,
beaconUpdateData.beacon.templateId,
beaconUpdateData.newBeaconResponse.timestamp,
beaconUpdateData.newBeaconResponse.encodedValue,
beaconUpdateData.newBeaconResponse.signature,
]),
];
beaconUpdates = [...beaconUpdates, beaconUpdateData];
}

let nonce = transactionCount;
for (const updateBatch of chunk(beaconUpdateCalldatas, DATAFEED_UPDATE_BATCH_SIZE)) {
for (const updateBatch of chunk(beaconUpdates, DATAFEED_UPDATE_BATCH_SIZE)) {
// Get the latest gas price
const getGasFn = () => getGasPrice(provider.rpcProvider.getProvider(), config.chains[chainId].options);
// We have to grab the limiter from the custom provider as the getGasPrice function contains its own timeouts
const [logs, gasTarget] = await provider.rpcProvider.getLimiter().schedule({ expiration: 30_000 }, getGasFn);
logger.logPending(logs, logOptions);

// Update beacon batch onchain values
const tx = await go(() => contract.connect(sponsorWallet).tryMulticall(updateBatch, { nonce, ...gasTarget }), {
...prepareGoOptions(startTime, totalTimeout),
onAttemptError: (goError) =>
logger.warn(`Failed attempt to update beacon batch. Error ${goError.error}`, logOptions),
});
// Update beacon onchain values
const updateBatchBeaconIds = updateBatch.map((beaconUpdate) => beaconUpdate.beaconTrigger.beaconId);
logger.debug(
`About to update ${updateBatch.length} beacon(s) with nonce ${nonce}. Beacon id(s): ${updateBatchBeaconIds.join(
', '
)}`,
logOptions
);

const tx = await go(
updateBatch.length === 1
? () =>
contract
.connect(sponsorWallet)
.updateBeaconWithSignedData(
beaconUpdates[0].beacon.airnode,
beaconUpdates[0].beacon.templateId,
beaconUpdates[0].newBeaconResponse.timestamp,
beaconUpdates[0].newBeaconResponse.encodedValue,
beaconUpdates[0].newBeaconResponse.signature,
{ nonce, ...gasTarget }
)
: () => {
return contract.connect(sponsorWallet).tryMulticall(
updateBatch.map((beaconUpdateData) =>
contract.interface.encodeFunctionData('updateBeaconWithSignedData', [
beaconUpdateData.beacon.airnode,
beaconUpdateData.beacon.templateId,
beaconUpdateData.newBeaconResponse.timestamp,
beaconUpdateData.newBeaconResponse.encodedValue,
beaconUpdateData.newBeaconResponse.signature,
])
),
{ nonce, ...gasTarget }
);
},
{
...prepareGoOptions(startTime, totalTimeout),
onAttemptError: (goError) =>
logger.warn(
`Attempt to send transaction to update ${updateBatch.length} beacon(s) has failed. Error ${goError.error}`,
logOptions
),
}
);
if (!tx.success) {
logger.warn(`Unable send beacon batch update transaction with nonce ${nonce}. Error: ${tx.error}`, logOptions);
logger.warn(
`Unable send transaction to update ${updateBatch.length} beacon(s) with nonce ${nonce}. Error: ${tx.error}`,
logOptions
);
logger.debug(`Beacon id(s) that failed to be updated: ${updateBatchBeaconIds.join(', ')}`, logOptions);
return;
}
logger.info(
`Beacon batch update transaction was successfully sent with nonce ${nonce}. Tx hash ${tx.data.hash}.`,
`Transaction to update ${updateBatch.length} beacon(s) was successfully sent with nonce ${nonce}. Tx hash ${tx.data.hash}`,
logOptions
);

nonce++;
}
}
Expand Down