Skip to content

Commit cfc2613

Browse files
authored
Merge pull request #577 from NodeFactoryIo/develop
Metrics and multiclient
2 parents 5a746e7 + c6eeab1 commit cfc2613

File tree

254 files changed

+4942
-432
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

254 files changed

+4942
-432
lines changed

.env.dist

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ SENTRY_DSN=https://[email protected]/yyyyyyy
33
SENTRY_ORG=nodefactory
44
SENTRY_PROJECT=chainguardian
55
SENTRY_AUTH_TOKEN=xxx
6-
DOCKER_LIGHTHOUSE_IMAGE=sigp/lighthouse:v1.1.1
6+
DOCKER_LIGHTHOUSE_IMAGE=sigp/lighthouse:v1.3.0
7+
DOCKER_TEKU_IMAGE=consensys/teku:21.5
8+
DOCKER_PRYSM_IMAGE=gcr.io/prysmaticlabs/prysm/beacon-chain:v1.4.2
9+
DOCKER_PRYSM_VALIDATOR_IMAGE=gcr.io/prysmaticlabs/prysm/validator:v1.4.2
10+
DOCKER_NIMBUS_IMAGE=statusim/nimbus-eth2:amd64-v1.4.0

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,5 @@ coverage
99
*.log
1010
package-lock.json
1111
*.db
12-
/eth2_testnet/genesis.ssz
1312
.tmp
1413
*.sqlite*
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"crypto":{"kdf":{"function":"scrypt","params":{"dklen":32,"n":2,"r":8,"p":1,"salt":"0101010101010101010101010101010101010101010101010101010101010101"},"message":""},"checksum":{"function":"sha256","params":{},"message":"49d259b9bb69eb767a2600f8e47847f53a9fa7aac7831dd0484deebad23460a1"},"cipher":{"function":"aes-128-ctr","params":{"iv":"073025af5ec5337d071f51908da1fcae"},"message":"604e18583806140b5ee3926d3aefa0d7b018a2dc37d0b02d32318228f8b03dd3"}},"uuid":"de9d7b36-83f1-4cc4-a0c7-6ab207a84619","path":"","pubkey":"aec922bd7a9b7b1dc21993133b586b0c3041c1e2e04b513e862227b9d7aecaf9444222f7e78282a449622ffc6278915d","version":4,"description":"","name":null}
1+
{"crypto":{"kdf":{"function":"scrypt","params":{"dklen":32,"n":2,"r":8,"p":1,"salt":"0101010101010101010101010101010101010101010101010101010101010101"},"message":""},"checksum":{"function":"sha256","params":{},"message":"41bfec3aaf2f15424f8e5171c789a55e5a12148c250bdee8fcaa798a0ceda2ba"},"cipher":{"function":"aes-128-ctr","params":{"iv":"90791e5760637b9c60944ad159f65c3f"},"message":"7769a7d09b77bf8991e1c0dc0d1aa20f742fbe1520e12a7eee8d08d12f494cb1"}},"uuid":"2bcc4bc3-cbc6-49f4-ab86-ad8bc84f78bb","path":"","pubkey":"aec922bd7a9b7b1dc21993133b586b0c3041c1e2e04b513e862227b9d7aecaf9444222f7e78282a449622ffc6278915d","version":4,"description":"","name":null}

integration/lighthouse.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {SecretKey} from "@chainsafe/bls";
2+
import {Keystore} from "@chainsafe/bls-keystore";
3+
import {config} from "@chainsafe/lodestar-config/lib/presets/mainnet";
4+
import assert from "assert";
5+
6+
import {restValidation} from "./restValidation";
7+
import keystore from "./lighthouse-keystore.json";
8+
import {CgLighthouseEth2Api} from "../src/renderer/services/eth2/client/module";
9+
const keystorePassword = "222222222222222222222222222222222222222222222222222";
10+
11+
(async function (): Promise<void> {
12+
const {proposer, attestation} = await restValidation({
13+
baseUrl: "http://localhost:5052",
14+
getValidatorPrivateKey: async () =>
15+
SecretKey.fromBytes(await Keystore.fromObject(keystore).decrypt(keystorePassword)),
16+
limit: 2,
17+
config,
18+
ApiClient: CgLighthouseEth2Api,
19+
});
20+
21+
console.log("\n\n\n");
22+
let isFailed = false;
23+
try {
24+
assert.equal(proposer.proposed, proposer.delegated);
25+
console.info(`Successfully proposed all ${proposer.delegated} blocks`);
26+
} catch (e) {
27+
console.error("Proposals", e.message);
28+
isFailed = true;
29+
}
30+
try {
31+
assert.equal(attestation.attestations, attestation.delegated);
32+
console.info(`Successfully provide all ${proposer.delegated} attestations`);
33+
} catch (e) {
34+
console.error("Attestations", e.message);
35+
isFailed = true;
36+
}
37+
process.exit(Number(isFailed));
38+
})();

integration/nimbus-keystore.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"crypto":{"kdf":{"function":"pbkdf2","params":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"394aa7bf7b8543bec9328678fabbb82b1d88b651a1629992c596011c87035ee0"},"message":""},"checksum":{"function":"sha256","params":{},"message":"4e1093d856e9cfa77b2ef37e49d96e248aa2b88be51c542a1a5c295f97eb30c2"},"cipher":{"function":"aes-128-ctr","params":{"iv":"8fa9958ef656695f12e3999d869d291e"},"message":"5869a4b6f5bab27676d6212737afbdedf5823ecb522ea88a72322c50a2f4c392"}},"description":"","pubkey":"8309a5281dd43297a3eccc1e70553831ec4dbf6a0b5b38fc910b49fb4eecf37075c90269443fd5fc6b8cad701e3eec53","path":"m/12381/3600/29/0/0","uuid":"97ee5497-09d8-489b-af81-e060358f5d5c","version":4}

integration/nimbus.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import assert from "assert";
2+
import {restValidation} from "./restValidation";
3+
import {config} from "@chainsafe/lodestar-config/lib/presets/mainnet";
4+
import {CgNimbusEth2Api} from "../src/renderer/services/eth2/client/module";
5+
import {SecretKey} from "@chainsafe/bls";
6+
import {Keystore} from "@chainsafe/bls-keystore";
7+
8+
import keystore from "./nimbus-keystore.json";
9+
const keystorePassword = "4E015C5AF6C9610B0230DBC4FD9714B786F24A28414E49C52D85950E1ED23AD8";
10+
11+
(async function (): Promise<void> {
12+
const {proposer, attestation} = await restValidation({
13+
baseUrl: "http://localhost:5052",
14+
getValidatorPrivateKey: async () =>
15+
SecretKey.fromBytes(await Keystore.fromObject(keystore).decrypt(keystorePassword)),
16+
limit: 5,
17+
config,
18+
ApiClient: CgNimbusEth2Api,
19+
});
20+
21+
console.log("\n\n\n");
22+
let isFailed = false;
23+
try {
24+
assert.equal(proposer.proposed, proposer.delegated);
25+
console.info(`Successfully proposed all ${proposer.delegated} blocks`);
26+
} catch (e) {
27+
console.error("Proposals", e.message);
28+
isFailed = true;
29+
}
30+
try {
31+
assert.equal(attestation.attestations, attestation.delegated);
32+
console.info(`Successfully provide all ${proposer.delegated} attestations`);
33+
} catch (e) {
34+
console.error("Attestations", e.message);
35+
isFailed = true;
36+
}
37+
process.exit(Number(isFailed));
38+
})();

integration/prysm.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {getInteropKey} from "../src/renderer/services/validator/interop_keys";
2+
import assert from "assert";
3+
import {restValidation} from "./restValidation";
4+
import {config} from "@chainsafe/lodestar-config/lib/presets/mainnet";
5+
import {CgPrysmEth2Api} from "../src/renderer/services/eth2/client/module";
6+
7+
(async function (): Promise<void> {
8+
const {proposer, attestation} = await restValidation({
9+
baseUrl: "http://localhost:5050",
10+
getValidatorPrivateKey: async () => getInteropKey(15),
11+
limit: 2,
12+
config,
13+
ApiClient: CgPrysmEth2Api,
14+
});
15+
16+
console.log("\n\n\n");
17+
let isFailed = false;
18+
try {
19+
assert.equal(proposer.proposed, proposer.delegated);
20+
console.info(`Successfully proposed all ${proposer.delegated} blocks`);
21+
} catch (e) {
22+
console.error("Proposals", e.message);
23+
isFailed = true;
24+
}
25+
try {
26+
assert.equal(attestation.attestations, attestation.delegated);
27+
console.info(`Successfully provide all ${proposer.delegated} attestations`);
28+
} catch (e) {
29+
console.error("Attestations", e.message);
30+
isFailed = true;
31+
}
32+
process.exit(Number(isFailed));
33+
})();

integration/restValidation.ts

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import {init as initBLS, SecretKey} from "@chainsafe/bls";
2+
import {LogLevel, WinstonLogger} from "@chainsafe/lodestar-utils";
3+
import {Validator} from "@chainsafe/lodestar-validator";
4+
import {CGSlashingProtection} from "../src/renderer/services/eth2/client/slashingProtection";
5+
import sinon from "sinon";
6+
import {computeEpochAtSlot} from "@chainsafe/lodestar-beacon-state-transition";
7+
import {BeaconCommitteeResponse, BLSPubkey, ProposerDuty} from "@chainsafe/lodestar-types";
8+
import {AttestationEvent, CGBeaconEventType, ICgEth2ApiClient} from "../src/renderer/services/eth2/client/interface";
9+
import {BeaconBlockEvent, BeaconEventType} from "@chainsafe/lodestar-validator/lib/api/interface/events";
10+
import {IBeaconConfig} from "@chainsafe/lodestar-config";
11+
import {CgEth2ApiClient} from "../src/renderer/services/eth2/client/module";
12+
13+
const getCommitteesFactory = (apiClient: ICgEth2ApiClient) => async (
14+
validatorIndex: number,
15+
blockSlot: number,
16+
ignoreBefore?: number,
17+
): Promise<BeaconCommitteeResponse[]> => {
18+
const response = await apiClient.beacon.state.getCommittees(blockSlot);
19+
return response.filter(({validators, slot}: BeaconCommitteeResponse) =>
20+
[...validators].some(
21+
(index) => index === validatorIndex && (slot > ignoreBefore || ignoreBefore === undefined),
22+
),
23+
);
24+
};
25+
26+
const getProposerFactory = (apiClient: ICgEth2ApiClient) => async (
27+
pubKey: BLSPubkey,
28+
epoch: number,
29+
index: number,
30+
ignoreBefore?: number,
31+
): Promise<ProposerDuty[]> => {
32+
const result = await apiClient.validator.getProposerDuties(epoch, [pubKey]);
33+
return [...result].filter(
34+
({validatorIndex, slot}) => validatorIndex === index && (slot > ignoreBefore || ignoreBefore === undefined),
35+
);
36+
};
37+
38+
const processAttestation = (
39+
{data}: AttestationEvent["message"],
40+
committees: BeaconCommitteeResponse[],
41+
attestations: Map<number, boolean>,
42+
): void => {
43+
const committee = committees.find(({slot, index}) => slot === data.slot && index === data.index);
44+
if (committee) {
45+
attestations.set(committee.slot, true);
46+
}
47+
};
48+
49+
const processBlock = async (
50+
{slot}: BeaconBlockEvent["message"],
51+
lastEpoch: number,
52+
firstSlot: number,
53+
validatorPublicKeyBytes: Uint8Array,
54+
validatorIndex: number,
55+
proposers: Map<number, boolean>,
56+
attestations: Map<number, boolean>,
57+
committees: BeaconCommitteeResponse[],
58+
getProposer: ReturnType<typeof getProposerFactory>,
59+
getCommittees: ReturnType<typeof getCommitteesFactory>,
60+
limit: number,
61+
config: IBeaconConfig,
62+
): Promise<{
63+
epoch: number;
64+
lastEpoch: number;
65+
committees: BeaconCommitteeResponse[];
66+
}> => {
67+
if (proposers.has(slot)) proposers.set(slot, true);
68+
69+
const epoch = computeEpochAtSlot(config, slot);
70+
if (lastEpoch !== epoch && limit !== epoch) {
71+
// eslint-disable-next-line no-param-reassign
72+
lastEpoch = epoch;
73+
74+
const proposerResponse = await getProposer(validatorPublicKeyBytes, epoch, validatorIndex, firstSlot);
75+
proposerResponse.forEach(({slot: s}) => {
76+
proposers.set(s, false);
77+
});
78+
79+
// eslint-disable-next-line no-param-reassign
80+
committees = await getCommittees(validatorIndex, slot, firstSlot);
81+
committees.forEach(({slot: s}) => {
82+
attestations.set(s, false);
83+
});
84+
}
85+
86+
return {epoch, lastEpoch, committees};
87+
};
88+
89+
export const restValidation = ({
90+
baseUrl,
91+
getValidatorPrivateKey,
92+
limit,
93+
config,
94+
ApiClient,
95+
}: {
96+
baseUrl: string;
97+
getValidatorPrivateKey: () => Promise<SecretKey>;
98+
limit: number;
99+
ApiClient: typeof CgEth2ApiClient;
100+
config: IBeaconConfig;
101+
}): Promise<{
102+
proposer: {
103+
proposed: number;
104+
delegated: number;
105+
};
106+
attestation: {
107+
attestations: number;
108+
delegated: number;
109+
};
110+
}> =>
111+
new Promise((resolve) => {
112+
(async (): Promise<void> => {
113+
process.env.NODE_ENV = "validator-test";
114+
await initBLS("blst-native");
115+
const validatorPrivateKey = await getValidatorPrivateKey();
116+
const validatorPublicKey = validatorPrivateKey.toPublicKey();
117+
const validatorPublicKeyBytes = validatorPublicKey.toBytes();
118+
console.log("Starting validator " + validatorPublicKey.toHex());
119+
120+
const logger = new WinstonLogger({module: "ChainGuardian", level: LogLevel.verbose});
121+
const slashingProtection = sinon.createStubInstance(CGSlashingProtection);
122+
123+
const eth2API = new ApiClient(config, baseUrl);
124+
const validatorService = new Validator({
125+
slashingProtection,
126+
api: eth2API,
127+
config,
128+
secretKeys: [validatorPrivateKey],
129+
logger,
130+
graffiti: "ChainGuardian",
131+
});
132+
const validator = await eth2API.beacon.state.getStateValidator("head", validatorPublicKeyBytes);
133+
await validatorService.start();
134+
135+
let firstSlot: number | undefined;
136+
137+
let startEpoch = 1;
138+
let lastEpoch = 1;
139+
140+
const onFirstBlock = ({slot}: BeaconBlockEvent["message"]): void => {
141+
const epoch = computeEpochAtSlot(config, slot);
142+
if (!firstSlot) {
143+
firstSlot = slot;
144+
startEpoch = epoch;
145+
}
146+
};
147+
148+
const getCommittees = getCommitteesFactory(eth2API);
149+
let committees: BeaconCommitteeResponse[] = [];
150+
const attestations = new Map<number, boolean>();
151+
152+
const getProposer = getProposerFactory(eth2API);
153+
const proposers = new Map<number, boolean>();
154+
155+
const stream = await eth2API.events.getEventStream(([
156+
CGBeaconEventType.BLOCK,
157+
CGBeaconEventType.ATTESTATION,
158+
] as unknown) as BeaconEventType[]);
159+
for await (const {type, message} of stream) {
160+
switch ((type as unknown) as CGBeaconEventType) {
161+
case CGBeaconEventType.ATTESTATION: {
162+
processAttestation(
163+
(message as unknown) as AttestationEvent["message"],
164+
committees,
165+
attestations,
166+
);
167+
break;
168+
}
169+
case CGBeaconEventType.BLOCK: {
170+
onFirstBlock((message as unknown) as BeaconBlockEvent["message"]);
171+
const {epoch, ...rest} = await processBlock(
172+
(message as unknown) as BeaconBlockEvent["message"],
173+
lastEpoch,
174+
firstSlot,
175+
validatorPublicKeyBytes,
176+
validator.index,
177+
proposers,
178+
attestations,
179+
committees,
180+
getProposer,
181+
getCommittees,
182+
startEpoch + limit,
183+
config,
184+
);
185+
186+
lastEpoch = rest.lastEpoch;
187+
committees = rest.committees;
188+
189+
if (startEpoch + limit === epoch) {
190+
await validatorService.stop();
191+
resolve({
192+
proposer: {
193+
proposed: [...proposers.values()].reduce((p, c) => p + Number(c), 0),
194+
delegated: proposers.size,
195+
},
196+
attestation: {
197+
attestations: [...attestations.values()].reduce((p, c) => p + Number(c), 0),
198+
delegated: attestations.size,
199+
},
200+
});
201+
}
202+
break;
203+
}
204+
}
205+
}
206+
})();
207+
});

integration/teku.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {getInteropKey} from "../src/renderer/services/validator/interop_keys";
2+
import assert from "assert";
3+
import {restValidation} from "./restValidation";
4+
import {config} from "@chainsafe/lodestar-config/lib/presets/minimal";
5+
import {CgTekuEth2Api} from "../src/renderer/services/eth2/client/module";
6+
7+
(async function (): Promise<void> {
8+
const {proposer, attestation} = await restValidation({
9+
baseUrl: "http://localhost:5051",
10+
getValidatorPrivateKey: async () => getInteropKey(7),
11+
limit: 5,
12+
config,
13+
ApiClient: CgTekuEth2Api,
14+
});
15+
16+
console.log("\n\n\n");
17+
let isFailed = false;
18+
try {
19+
assert.equal(proposer.proposed, proposer.delegated);
20+
console.info(`Successfully proposed all ${proposer.delegated} blocks`);
21+
} catch (e) {
22+
console.error("Proposals", e.message);
23+
isFailed = true;
24+
}
25+
try {
26+
assert.equal(attestation.attestations, attestation.delegated);
27+
console.info(`Successfully provide all ${proposer.delegated} attestations`);
28+
} catch (e) {
29+
console.error("Attestations", e.message);
30+
isFailed = true;
31+
}
32+
process.exit(Number(isFailed));
33+
})();

0 commit comments

Comments
 (0)