Skip to content

Commit

Permalink
Merge pull request #2 from novon-labs/temp/validator-remote
Browse files Browse the repository at this point in the history
Implementation of sim tests for remote signing
  • Loading branch information
abdulsamijay committed Dec 23, 2021
2 parents 1629750 + e6c5978 commit 3070623
Show file tree
Hide file tree
Showing 11 changed files with 484 additions and 417 deletions.
220 changes: 112 additions & 108 deletions packages/lodestar/test/e2e/chain/lightclient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,129 +37,133 @@ describe("chain / lightclient", function () {
// This is a rare event, with maxLcHeadTrackingDiffSlots = 4, SECONDS_PER_SLOT = 1
this.retries(2);

it("Lightclient track head on server configuration", async function () {
this.timeout("10 min");
const signersChoices = ["local", "remote"];
for (const signingMode of signersChoices) {
it(`Lightclient ${signingMode} track head on server configuration`, async function () {
this.timeout("10 min");

// delay a bit so regular sync sees it's up to date and sync is completed from the beginning
const genesisSlotsDelay = 2 / testParams.SECONDS_PER_SLOT;
const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT;

const testLoggerOpts: TestLoggerOpts = {
logLevel: LogLevel.info,
timestampFormat: {
format: TimestampFormatCode.EpochSlot,
genesisTime,
slotsPerEpoch: SLOTS_PER_EPOCH,
secondsPerSlot: testParams.SECONDS_PER_SLOT,
},
};

// delay a bit so regular sync sees it's up to date and sync is completed from the beginning
const genesisSlotsDelay = 2 / testParams.SECONDS_PER_SLOT;
const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT;
const loggerNodeA = testLogger("Node", testLoggerOpts);
const loggerLC = testLogger("LC", {...testLoggerOpts, logLevel: LogLevel.debug});

const testLoggerOpts: TestLoggerOpts = {
logLevel: LogLevel.info,
timestampFormat: {
format: TimestampFormatCode.EpochSlot,
const bn = await getDevBeaconNode({
params: testParams,
options: {
sync: {isSingleNode: true},
api: {rest: {enabled: true, api: ["lightclient"], port: restPort}},
},
validatorCount,
genesisTime,
slotsPerEpoch: SLOTS_PER_EPOCH,
secondsPerSlot: testParams.SECONDS_PER_SLOT,
},
};

const loggerNodeA = testLogger("Node", testLoggerOpts);
const loggerLC = testLogger("LC", {...testLoggerOpts, logLevel: LogLevel.debug});

const bn = await getDevBeaconNode({
params: testParams,
options: {
sync: {isSingleNode: true},
api: {rest: {enabled: true, api: ["lightclient"], port: restPort}},
},
validatorCount,
genesisTime,
logger: loggerNodeA,
});
const validators = await getAndInitDevValidators({
node: bn,
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts: {...testLoggerOpts, logLevel: LogLevel.error},
});

await Promise.all(validators.map((validator) => validator.start()));

// This promise chain does:
// 1. Wait for the beacon node to emit one head that has a snapshot associated to it
// 2. Initialize lightclient from that head block root
// 3. Start lightclient to track head
// 4. On every new beacon node head, check that the lightclient is following closely
// - If too far behind error the test
// - If beacon node reaches the finality slot, resolve test
const promiseUntilHead = new Promise<IProtoBlock>((resolve) => {
bn.chain.emitter.on(ChainEvent.forkChoiceHead, async (head) => {
// Wait for the second slot so syncCommitteeWitness is available
if (head.slot > 2) {
resolve(head);
}
logger: loggerNodeA,
});
}).then(async (head) => {
// Initialize lightclient
loggerLC.important("Initializing lightclient", {slot: head.slot});

const lightclient = await Lightclient.initializeFromCheckpointRoot({
config: bn.config,
logger: loggerLC,
beaconApiUrl: `http://localhost:${restPort}`,
genesisData: {
genesisTime: bn.chain.genesisTime,
genesisValidatorsRoot: bn.chain.genesisValidatorsRoot as Uint8Array,
},
checkpointRoot: fromHexString(head.blockRoot),
const validators = await getAndInitDevValidators({
node: bn,
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts: {...testLoggerOpts, logLevel: LogLevel.error},
signingMode,
});

loggerLC.important("Initialized lightclient", {headSlot: lightclient.getHead().slot});
lightclient.start();
await Promise.all(validators.map((validator) => validator.start()));

return new Promise<void>((resolve, reject) => {
// This promise chain does:
// 1. Wait for the beacon node to emit one head that has a snapshot associated to it
// 2. Initialize lightclient from that head block root
// 3. Start lightclient to track head
// 4. On every new beacon node head, check that the lightclient is following closely
// - If too far behind error the test
// - If beacon node reaches the finality slot, resolve test
const promiseUntilHead = new Promise<IProtoBlock>((resolve) => {
bn.chain.emitter.on(ChainEvent.forkChoiceHead, async (head) => {
try {
// Test fetching proofs
const {proof, header} = await lightclient.getHeadStateProof([["latestBlockHeader", "bodyRoot"]]);
const stateRootHex = toHexString(header.stateRoot);
const lcHeadState = bn.chain.stateCache.get(stateRootHex);
if (!lcHeadState) {
throw Error(`LC head state not in cache ${stateRootHex}`);
}
// Wait for the second slot so syncCommitteeWitness is available
if (head.slot > 2) {
resolve(head);
}
});
}).then(async (head) => {
// Initialize lightclient
loggerLC.important("Initializing lightclient", {slot: head.slot});

const lightclient = await Lightclient.initializeFromCheckpointRoot({
config: bn.config,
logger: loggerLC,
beaconApiUrl: `http://localhost:${restPort}`,
genesisData: {
genesisTime: bn.chain.genesisTime,
genesisValidatorsRoot: bn.chain.genesisValidatorsRoot as Uint8Array,
},
checkpointRoot: fromHexString(head.blockRoot),
});

const stateLcFromProof = ssz.altair.BeaconState.createTreeBackedFromProof(
header.stateRoot as Uint8Array,
proof
);
expect(toHexString(stateLcFromProof.latestBlockHeader.bodyRoot)).to.equal(
toHexString(lcHeadState.latestBlockHeader.bodyRoot),
`Recovered 'latestBlockHeader.bodyRoot' from state ${stateRootHex} not correct`
);

// Stop test if reached target head slot
const lcHeadSlot = lightclient.getHead().slot;
if (head.slot - lcHeadSlot > maxLcHeadTrackingDiffSlots) {
throw Error(`Lightclient head ${lcHeadSlot} is too far behind the beacon node ${head.slot}`);
} else if (head.slot > targetSlotToReach) {
resolve();
loggerLC.important("Initialized lightclient", {headSlot: lightclient.getHead().slot});
lightclient.start();

return new Promise<void>((resolve, reject) => {
bn.chain.emitter.on(ChainEvent.forkChoiceHead, async (head) => {
try {
// Test fetching proofs
const {proof, header} = await lightclient.getHeadStateProof([["latestBlockHeader", "bodyRoot"]]);
const stateRootHex = toHexString(header.stateRoot);
const lcHeadState = bn.chain.stateCache.get(stateRootHex);
if (!lcHeadState) {
throw Error(`LC head state not in cache ${stateRootHex}`);
}

const stateLcFromProof = ssz.altair.BeaconState.createTreeBackedFromProof(
header.stateRoot as Uint8Array,
proof
);
expect(toHexString(stateLcFromProof.latestBlockHeader.bodyRoot)).to.equal(
toHexString(lcHeadState.latestBlockHeader.bodyRoot),
`Recovered 'latestBlockHeader.bodyRoot' from state ${stateRootHex} not correct`
);

// Stop test if reached target head slot
const lcHeadSlot = lightclient.getHead().slot;
if (head.slot - lcHeadSlot > maxLcHeadTrackingDiffSlots) {
throw Error(`Lightclient head ${lcHeadSlot} is too far behind the beacon node ${head.slot}`);
} else if (head.slot > targetSlotToReach) {
resolve();
}
} catch (e) {
reject(e);
}
} catch (e) {
reject(e);
}
});
});
});
});

const promiseTillFinalization = new Promise<void>((resolve) => {
bn.chain.emitter.on(ChainEvent.finalized, (checkpoint) => {
loggerNodeA.important("Node A emitted finalized checkpoint event", {epoch: checkpoint.epoch});
if (checkpoint.epoch >= finalizedEpochToReach) {
resolve();
}
const promiseTillFinalization = new Promise<void>((resolve) => {
bn.chain.emitter.on(ChainEvent.finalized, (checkpoint) => {
loggerNodeA.important("Node A emitted finalized checkpoint event", {epoch: checkpoint.epoch});
if (checkpoint.epoch >= finalizedEpochToReach) {
resolve();
}
});
});
});

await Promise.all([promiseUntilHead, promiseTillFinalization]);
await Promise.all([promiseUntilHead, promiseTillFinalization]);

const headSummary = bn.chain.forkChoice.getHead();
const head = await bn.db.block.get(fromHexString(headSummary.blockRoot));
if (!head) throw Error("First beacon node has no head block");
const headSummary = bn.chain.forkChoice.getHead();
const head = await bn.db.block.get(fromHexString(headSummary.blockRoot));
if (!head) throw Error("First beacon node has no head block");

await Promise.all(validators.map((v) => v.stop()));
await bn.close();
});
await Promise.all(validators.map((v) => v.stop()));
await bn.close();
});
}
});
102 changes: 52 additions & 50 deletions packages/lodestar/test/e2e/sync/finalizedSync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,62 +17,64 @@ describe("sync / finalized sync", function () {
SECONDS_PER_SLOT: 2,
};

it("should do a finalized sync from another BN", async function () {
this.timeout("10 min");
const signersChoices = ["local", "remote"];
for (const signingMode of signersChoices) {
it(`should do a finalized sync from another BN: ${signingMode}}`, async function () {
this.timeout("10 min");

const testLoggerOpts: TestLoggerOpts = {logLevel: LogLevel.info};
const loggerNodeA = testLogger("Node-A", testLoggerOpts);
const loggerNodeB = testLogger("Node-B", testLoggerOpts);
const testLoggerOpts: TestLoggerOpts = {logLevel: LogLevel.info};
const loggerNodeA = testLogger("Node-A", testLoggerOpts);
const loggerNodeB = testLogger("Node-B", testLoggerOpts);

const bn = await getDevBeaconNode({
params: beaconParams,
options: {
sync: {isSingleNode: true},
network: {
requestCountPeerLimit: 1000,
},
},
validatorCount,
logger: loggerNodeA,
});
const validators = await getAndInitDevValidators({
node: bn,
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts,
});
const bn = await getDevBeaconNode({
params: beaconParams,
options: {sync: {isSingleNode: true}},
validatorCount,
logger: loggerNodeA,
});
const validators = await getAndInitDevValidators({
node: bn,
validatorsPerClient: validatorCount,
validatorClientCount: 1,
startIndex: 0,
useRestApi: false,
testLoggerOpts,
signingMode,
});

await Promise.all(validators.map((validator) => validator.start()));
await Promise.all(validators.map((validator) => validator.start()));

await waitForEvent<phase0.Checkpoint>(bn.chain.emitter, ChainEvent.finalized, 240000);
loggerNodeA.important("Node A emitted finalized checkpoint event");
await waitForEvent<phase0.Checkpoint>(bn.chain.emitter, ChainEvent.finalized, 240000);
loggerNodeA.important("Node A emitted finalized checkpoint event");

const bn2 = await getDevBeaconNode({
params: beaconParams,
options: {api: {rest: {enabled: false}}},
validatorCount,
genesisTime: bn.chain.getHeadState().genesisTime,
logger: loggerNodeB,
});
const bn2 = await getDevBeaconNode({
params: beaconParams,
options: {api: {rest: {enabled: false}}},
validatorCount,
genesisTime: bn.chain.getHeadState().genesisTime,
logger: loggerNodeB,
});

const headSummary = bn.chain.forkChoice.getHead();
const head = await bn.db.block.get(fromHexString(headSummary.blockRoot));
if (!head) throw Error("First beacon node has no head block");
const waitForSynced = waitForEvent<phase0.SignedBeaconBlock>(bn2.chain.emitter, ChainEvent.block, 100000, (block) =>
ssz.phase0.SignedBeaconBlock.equals(block, head)
);
const headSummary = bn.chain.forkChoice.getHead();
const head = await bn.db.block.get(fromHexString(headSummary.blockRoot));
if (!head) throw Error("First beacon node has no head block");
const waitForSynced = waitForEvent<phase0.SignedBeaconBlock>(
bn2.chain.emitter,
ChainEvent.block,
100000,
(block) => ssz.phase0.SignedBeaconBlock.equals(block, head)
);

await connect(bn2.network as Network, bn.network.peerId, bn.network.localMultiaddrs);
await connect(bn2.network as Network, bn.network.peerId, bn.network.localMultiaddrs);

try {
await waitForSynced;
} catch (e) {
assert.fail("Failed to sync to other node in time");
}
await bn2.close();
await Promise.all(validators.map((v) => v.stop()));
await bn.close();
});
try {
await waitForSynced;
} catch (e) {
assert.fail("Failed to sync to other node in time");
}
await bn2.close();
await Promise.all(validators.map((v) => v.stop()));
await bn.close();
});
}
});
Loading

0 comments on commit 3070623

Please sign in to comment.