Skip to content

Commit 5bf1ea2

Browse files
committed
Read beacon data from the contract, remove multicalling
1 parent ee81948 commit 5bf1ea2

13 files changed

+220
-424
lines changed

local-test-configuration/monitoring/index.html

+13-8
Original file line numberDiff line numberDiff line change
@@ -744,14 +744,18 @@ <h2>Active data feeds</h2>
744744
}
745745

746746
const decodeDataFeedDetails = (dataFeed) => {
747-
if (dataFeed.length === 130) {
748-
// (64 [actual bytes] * 2[hex encoding] ) + 2 [for the '0x' preamble]
749-
// This is a hex encoded string, the contract works with bytes directly
747+
// The contract returns empty bytes if the data feed is not registered. See:
748+
// https://github.com/bbenligiray/api3-contracts/blob/d394581549e4d2f343e9910bc330b21266808851/contracts/AirseekerRegistry.sol#L346
749+
if (dataFeed === '0x') return null;
750+
751+
// This is a hex encoded string, the contract works with bytes directly
752+
// 2 characters for the '0x' preamble + 32 * 2 hexadecimals for 32 bytes + 32 * 2 hexadecimals for 32 bytes
753+
if (dataFeed.length === 2 + 32 * 2 + 32 * 2) {
750754
const [airnodeAddress, templateId] = ethers.AbiCoder.defaultAbiCoder().decode(['address', 'bytes32'], dataFeed);
751755

752756
const dataFeedId = deriveBeaconId(airnodeAddress, templateId);
753757

754-
return { beacons: [{ beaconId: dataFeedId, airnodeAddress, templateId }] };
758+
return [{ beaconId: dataFeedId, airnodeAddress, templateId }];
755759
}
756760

757761
const [airnodeAddresses, templateIds] = ethers.AbiCoder.defaultAbiCoder().decode(
@@ -766,7 +770,7 @@ <h2>Active data feeds</h2>
766770
return { beaconId, airnodeAddress, templateId };
767771
});
768772

769-
return { beacons };
773+
return beacons;
770774
};
771775

772776
const decodeUpdateParameters = (updateParameters) => {
@@ -883,17 +887,18 @@ <h2>Active data feeds</h2>
883887
},
884888
dataFeedValue: dataFeedValue.toString(),
885889
dataFeedTimestamp: dataFeedTimestamp.toString(),
886-
decodedDataFeed: decodeDataFeedDetails(dataFeedDetails),
890+
// This slightly differs from the main logic in Airseeker, but we only care about beacon IDs here.
891+
beacons,
887892
signedApiUrls,
888893
};
889894
console.info('Data feed', dataFeed); // For debugging purposes.
890895

891896
let signedDatas = [];
892897
for (let i = 0; i < signedApiUrls.length; i++) {
893898
const url = signedApiUrls[i].replace('host.docker.internal', 'localhost');
894-
const airnode = dataFeed.decodedDataFeed.beacons[i].airnodeAddress;
899+
const airnode = dataFeed.beacons[i].airnodeAddress;
895900
const signedApiResponse = await fetch(`${url}/${airnode}`).then((res) => res.json());
896-
const signedData = signedApiResponse.data[dataFeed.decodedDataFeed.beacons[i].beaconId];
901+
const signedData = signedApiResponse.data[dataFeed.beacons[i].beaconId];
897902
signedDatas.push({ ...signedData, value: decodeBeaconValue(signedData.encodedValue).toString() });
898903
}
899904
console.info('Signed datas', signedDatas); // For debugging purposes.

src/types.ts

-10
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,3 @@ export const signedApiResponseSchema = z.object({
2626
count: z.number().positive(),
2727
data: z.record(signedDataSchema),
2828
});
29-
30-
export interface Beacon {
31-
airnodeAddress: AirnodeAddress;
32-
templateId: TemplateId;
33-
beaconId: string;
34-
}
35-
36-
export interface DecodedDataFeed {
37-
beacons: Beacon[];
38-
}

src/update-feeds-loops/contracts.test.ts

+19-23
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,24 @@ describe('helper functions', () => {
2626
const decodedResultSingle = decodeDataFeedDetails(single);
2727
const decodedResultMultiple = decodeDataFeedDetails(multiple);
2828

29-
expect(decodedResultSingle).toStrictEqual({
30-
beacons: [
31-
{
32-
airnodeAddress: '0xc52EeA00154B4fF1EbbF8Ba39FDe37F1AC3B9Fd4',
33-
beaconId: '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6',
34-
templateId: '0x457a3b3da67e394a895ea49e534a4d91b2d009477bef15eab8cbed313925b010',
35-
},
36-
],
37-
});
38-
expect(decodedResultMultiple).toStrictEqual({
39-
beacons: [
40-
{
41-
airnodeAddress: '0xc52EeA00154B4fF1EbbF8Ba39FDe37F1AC3B9Fd4',
42-
beaconId: '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6',
43-
templateId: '0x457a3b3da67e394a895ea49e534a4d91b2d009477bef15eab8cbed313925b010',
44-
},
45-
{
46-
airnodeAddress: '0xc52EeA00154B4fF1EbbF8Ba39FDe37F1AC3B9Fd4',
47-
beaconId: '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6',
48-
templateId: '0x457a3b3da67e394a895ea49e534a4d91b2d009477bef15eab8cbed313925b010',
49-
},
50-
],
51-
});
29+
expect(decodedResultSingle).toStrictEqual([
30+
{
31+
airnodeAddress: '0xc52EeA00154B4fF1EbbF8Ba39FDe37F1AC3B9Fd4',
32+
beaconId: '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6',
33+
templateId: '0x457a3b3da67e394a895ea49e534a4d91b2d009477bef15eab8cbed313925b010',
34+
},
35+
]);
36+
expect(decodedResultMultiple).toStrictEqual([
37+
{
38+
airnodeAddress: '0xc52EeA00154B4fF1EbbF8Ba39FDe37F1AC3B9Fd4',
39+
beaconId: '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6',
40+
templateId: '0x457a3b3da67e394a895ea49e534a4d91b2d009477bef15eab8cbed313925b010',
41+
},
42+
{
43+
airnodeAddress: '0xc52EeA00154B4fF1EbbF8Ba39FDe37F1AC3B9Fd4',
44+
beaconId: '0xf5c140bcb4814dfec311d38f6293e86c02d32ba1b7da027fe5b5202cae35dbc6',
45+
templateId: '0x457a3b3da67e394a895ea49e534a4d91b2d009477bef15eab8cbed313925b010',
46+
},
47+
]);
5248
});
5349
});

src/update-feeds-loops/contracts.ts

+47-16
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { ethers } from 'ethers';
2+
import { zip } from 'lodash';
23

34
import {
4-
Api3ServerV1__factory as Api3ServerV1Factory,
55
type AirseekerRegistry,
66
AirseekerRegistry__factory as AirseekerRegistryFactory,
7+
Api3ServerV1__factory as Api3ServerV1Factory,
78
} from '../typechain-types';
8-
import type { DecodedDataFeed } from '../types';
9+
import type { AirnodeAddress, TemplateId } from '../types';
910
import { decodeDapiName, deriveBeaconId } from '../utils';
1011

1112
export const getApi3ServerV1 = (address: string, provider: ethers.JsonRpcProvider) =>
@@ -29,9 +30,15 @@ export const decodeGetBlockNumberResponse = Number;
2930

3031
export const decodeGetChainIdResponse = Number;
3132

32-
export const decodeDataFeedDetails = (dataFeed: string): DecodedDataFeed | null => {
33+
export interface Beacon {
34+
airnodeAddress: AirnodeAddress;
35+
templateId: TemplateId;
36+
beaconId: string;
37+
}
38+
39+
export const decodeDataFeedDetails = (dataFeed: string): Beacon[] | null => {
3340
// The contract returns empty bytes if the data feed is not registered. See:
34-
// https://github.com/api3dao/dapi-management/blob/f3d39e4707c33c075a8f07aa8f8369f8dc07736f/contracts/AirseekerRegistry.sol#L209
41+
// https://github.com/bbenligiray/api3-contracts/blob/d394581549e4d2f343e9910bc330b21266808851/contracts/AirseekerRegistry.sol#L346
3542
if (dataFeed === '0x') return null;
3643

3744
// This is a hex encoded string, the contract works with bytes directly
@@ -41,7 +48,7 @@ export const decodeDataFeedDetails = (dataFeed: string): DecodedDataFeed | null
4148

4249
const dataFeedId = deriveBeaconId(airnodeAddress, templateId)!;
4350

44-
return { beacons: [{ beaconId: dataFeedId, airnodeAddress, templateId }] };
51+
return [{ beaconId: dataFeedId, airnodeAddress, templateId }];
4552
}
4653

4754
const [airnodeAddresses, templateIds] = ethers.AbiCoder.defaultAbiCoder().decode(
@@ -56,7 +63,7 @@ export const decodeDataFeedDetails = (dataFeed: string): DecodedDataFeed | null
5663
return { beaconId, airnodeAddress, templateId };
5764
});
5865

59-
return { beacons };
66+
return beacons;
6067
};
6168

6269
export interface DecodedUpdateParameters {
@@ -81,29 +88,53 @@ export const decodeUpdateParameters = (updateParameters: string): DecodedUpdateP
8188
};
8289
};
8390

91+
export interface BeaconWithData extends Beacon {
92+
value: bigint;
93+
timestamp: bigint;
94+
}
95+
8496
export interface DecodedActiveDataFeedResponse {
8597
dapiName: string | null;
8698
dataFeedId: string;
8799
decodedDapiName: string | null;
88100
decodedUpdateParameters: DecodedUpdateParameters;
89101
dataFeedValue: bigint;
90102
dataFeedTimestamp: bigint;
91-
decodedDataFeed: DecodedDataFeed;
103+
beaconsWithData: BeaconWithData[];
92104
signedApiUrls: string[];
93105
}
94106

107+
export const createBeaconsWithData = (beacons: Beacon[], beaconValues: bigint[], beaconTimestamps: bigint[]) => {
108+
return zip(beacons, beaconValues, beaconTimestamps).map(([beacon, value, timestamp]) => ({
109+
...beacon!,
110+
value: value!,
111+
timestamp: BigInt(timestamp!),
112+
}));
113+
};
114+
95115
export const decodeActiveDataFeedResponse = (
96116
airseekerRegistry: AirseekerRegistry,
97117
activeDataFeedReturndata: string
98118
): DecodedActiveDataFeedResponse | null => {
99-
const { dapiName, dataFeedId, updateParameters, dataFeedValue, dataFeedTimestamp, dataFeedDetails, signedApiUrls } =
100-
airseekerRegistry.interface.decodeFunctionResult('activeDataFeed', activeDataFeedReturndata) as unknown as Awaited<
101-
ReturnType<AirseekerRegistry['activeDataFeed']['staticCall']>
102-
>;
103-
104-
// https://github.com/api3dao/dapi-management/blob/f3d39e4707c33c075a8f07aa8f8369f8dc07736f/contracts/AirseekerRegistry.sol#L162
105-
const decodedDataFeed = decodeDataFeedDetails(dataFeedDetails);
106-
if (!decodedDataFeed) return null;
119+
const {
120+
dataFeedId,
121+
dapiName,
122+
updateParameters,
123+
dataFeedValue,
124+
dataFeedTimestamp,
125+
dataFeedDetails,
126+
signedApiUrls,
127+
beaconValues,
128+
beaconTimestamps,
129+
} = airseekerRegistry.interface.decodeFunctionResult(
130+
'activeDataFeed',
131+
activeDataFeedReturndata
132+
) as unknown as Awaited<ReturnType<AirseekerRegistry['activeDataFeed']['staticCall']>>;
133+
134+
// https://github.com/bbenligiray/api3-contracts/blob/d394581549e4d2f343e9910bc330b21266808851/contracts/AirseekerRegistry.sol#L295
135+
const beacons = decodeDataFeedDetails(dataFeedDetails);
136+
if (!beacons) return null;
137+
const beaconsWithData = createBeaconsWithData(beacons, beaconValues, beaconTimestamps);
107138

108139
// The dAPI name will be set to zero (in bytes32) in case the data feed is not a dAPI and is identified by a data feed
109140
// ID.
@@ -116,7 +147,7 @@ export const decodeActiveDataFeedResponse = (
116147
decodedUpdateParameters: decodeUpdateParameters(updateParameters),
117148
dataFeedValue,
118149
dataFeedTimestamp,
119-
decodedDataFeed,
150+
beaconsWithData,
120151
signedApiUrls,
121152
};
122153
};

0 commit comments

Comments
 (0)