@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
105 lines • 4.2 kB
JavaScript
import worker from "node:worker_threads";
import { PublicKey } from "@chainsafe/blst";
import { expose } from "@chainsafe/threads/worker";
import { verifySignatureSetsMaybeBatch } from "../maybeBatch.js";
import { WorkResultCode } from "./types.js";
import { chunkifyMaximizeChunkSize } from "./utils.js";
/**
* Split batchable sets in chunks of minimum size 16.
* Batch verify 16 has an aprox cost of 16+1. For 32 it's 32+1. After ~16 the additional savings are not significant.
* However, if a sig is invalid the whole batch has to be re-verified. So it's important to keep this number low.
* In normal network conditions almost all signatures received by the node are correct.
* After observing metrics this number can be reviewed
*/
const BATCHABLE_MIN_PER_CHUNK = 16;
// Cloned data from instatiation
const workerData = worker.workerData;
if (!workerData)
throw Error("workerData must be defined");
const { workerId } = workerData || {};
expose({
async verifyManySignatureSets(workReqArr) {
return verifyManySignatureSets(workReqArr);
},
});
function verifyManySignatureSets(workReqArr) {
const [startSec, startNs] = process.hrtime();
const results = [];
let batchRetries = 0;
let batchSigsSuccess = 0;
// If there are multiple batchable sets attempt batch verification with them
const batchableSets = [];
const nonBatchableSets = [];
// Split sets between batchable and non-batchable preserving their original index in the req array
for (let i = 0; i < workReqArr.length; i++) {
const workReq = workReqArr[i];
const sets = workReq.sets.map(deserializeSet);
if (workReq.opts.batchable) {
batchableSets.push({ idx: i, sets });
}
else {
nonBatchableSets.push({ idx: i, sets });
}
}
if (batchableSets.length > 0) {
// Split batchable into chunks of max size ~ 32 to minimize cost if a sig is wrong
const batchableChunks = chunkifyMaximizeChunkSize(batchableSets, BATCHABLE_MIN_PER_CHUNK);
for (const batchableChunk of batchableChunks) {
const allSets = [];
for (const { sets } of batchableChunk) {
for (const set of sets) {
allSets.push(set);
}
}
try {
// Attempt to verify multiple sets at once
const isValid = verifySignatureSetsMaybeBatch(allSets);
if (isValid) {
// The entire batch is valid, return success to all
for (const { idx, sets } of batchableChunk) {
batchSigsSuccess += sets.length;
results[idx] = { code: WorkResultCode.success, result: isValid };
}
}
else {
batchRetries++;
// Re-verify all sigs
nonBatchableSets.push(...batchableChunk);
}
}
catch (_e) {
// TODO: Ignore this error expecting that the same error will happen when re-verifying the set individually
// It's not ideal but '@chainsafe/blst' may throw errors on some conditions
batchRetries++;
// Re-verify all sigs
nonBatchableSets.push(...batchableChunk);
}
}
}
for (const { idx, sets } of nonBatchableSets) {
try {
const isValid = verifySignatureSetsMaybeBatch(sets);
results[idx] = { code: WorkResultCode.success, result: isValid };
}
catch (e) {
results[idx] = { code: WorkResultCode.error, error: e };
}
}
const [workerEndSec, workerEndNs] = process.hrtime();
return {
workerId,
batchRetries,
batchSigsSuccess,
workerStartTime: [startSec, startNs],
workerEndTime: [workerEndSec, workerEndNs],
results,
};
}
function deserializeSet(set) {
return {
publicKey: PublicKey.fromBytes(set.publicKey),
message: set.message,
signature: set.signature,
};
}
//# sourceMappingURL=worker.js.map