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

Commit

Permalink
Merge pull request #471 from api3dao/449-update-single-beacon-without…
Browse files Browse the repository at this point in the history
…-multicall

449 update single beacon without multicall
  • Loading branch information
acenolaza authored Sep 18, 2023
2 parents 9f4278a + e1aa4af commit 480438c
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 26 deletions.
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)),
{
...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

0 comments on commit 480438c

Please sign in to comment.