-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathsigned-data-state.ts
86 lines (72 loc) · 3 KB
/
signed-data-state.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import type { Hex } from '@api3/commons';
import { logger } from '../logger';
import { getState, updateState } from '../state';
import type { SignedData, SignedDataRecordEntry } from '../types';
import { getVerifier } from './signed-data-verifier-pool';
const verifyTimestamp = ([beaconId, signedData]: SignedDataRecordEntry) => {
const { airnode, templateId, timestamp } = signedData;
// Check that the signed data is fresher than the one stored in state.
const timestampMs = Number(timestamp) * 1000;
const storedValue = getState().signedDatas[beaconId];
if (storedValue && Number(storedValue.timestamp) * 1000 >= timestampMs) {
logger.debug('Skipping state update. The signed data value is not fresher than the stored value.');
return false;
}
// Verify the timestamp of the signed data.
const nowMs = Date.now();
if (timestampMs > nowMs + 60 * 60 * 1000) {
logger.error(`Refusing to store sample as timestamp is more than one hour in the future.`, {
airnode,
templateId,
timestampMs,
nowMs,
});
return false;
}
if (timestampMs > nowMs) {
logger.warn(`Sample is in the future, but by less than an hour, therefore storing anyway.`, {
airnode,
templateId,
timestampMs,
nowMs,
});
}
return true;
};
export const saveSignedData = async (signedDataBatch: SignedDataRecordEntry[], isTrustedApi: boolean) => {
// Filter out signed data with invalid timestamps or we already have a fresher signed data stored in state.
signedDataBatch = signedDataBatch.filter((signedDataEntry) => verifyTimestamp(signedDataEntry));
if (signedDataBatch.length === 0) return;
// Only verify the signed data if it is not coming from a trusted API.
if (!isTrustedApi) {
const verifier = await getVerifier();
// We are skipping the whole batch even if there is only one invalid signed data. This is consistent with the Signed
// API approach.
const verificationResult = await verifier.verifySignedData(signedDataBatch);
if (verificationResult !== true) {
logger.error('Failed to verify signed data.', verificationResult);
return;
}
}
updateState((draft) => {
for (const [beaconId, signedData] of signedDataBatch) {
draft.signedDatas[beaconId] = signedData;
}
});
return signedDataBatch.length;
};
export const getSignedData = (beaconId: Hex) => getState().signedDatas[beaconId];
export const isSignedDataFresh = (signedData: SignedData) =>
BigInt(signedData.timestamp) > BigInt(Math.ceil(Date.now() / 1000 - 24 * 60 * 60));
export const purgeOldSignedData = () => {
const state = getState();
const oldSignedData = Object.values(state.signedDatas).filter((signedData) => isSignedDataFresh(signedData!));
if (oldSignedData.length > 0) {
logger.debug(`Purging some old signed data.`, { oldSignedData });
}
updateState((draft) => {
draft.signedDatas = Object.fromEntries(
Object.entries(draft.signedDatas).filter(([_beaconId, signedData]) => isSignedDataFresh(signedData!))
);
});
};