Skip to content

Commit

Permalink
feat: use napi-rs blst bindings (#6894)
Browse files Browse the repository at this point in the history
  • Loading branch information
wemeetagain authored Jul 25, 2024
1 parent acbedaf commit 2b62754
Show file tree
Hide file tree
Showing 88 changed files with 485 additions and 386 deletions.
4 changes: 2 additions & 2 deletions dashboards/lodestar_block_processor.json
Original file line number Diff line number Diff line change
Expand Up @@ -5101,7 +5101,7 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_bucket[$rate_interval])",
"expr": "rate(lodestar_gossip_block_gossip_validate_time_bucket[$rate_interval])",
"format": "heatmap",
"instant": false,
"legendFormat": "time",
Expand Down Expand Up @@ -5273,7 +5273,7 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_sum[$rate_interval]) / rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_count[$rate_interval])",
"expr": "rate(lodestar_gossip_block_gossip_validate_time_sum[$rate_interval]) / rate(lodestar_gossip_block_gossip_validate_time_count[$rate_interval])",
"format": "heatmap",
"instant": false,
"legendFormat": "time",
Expand Down
8 changes: 4 additions & 4 deletions dashboards/lodestar_bls_thread_pool.json
Original file line number Diff line number Diff line change
Expand Up @@ -1174,9 +1174,9 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_sum[$rate_interval]) * 384",
"expr": "rate(lodestar_bls_thread_pool_aggregate_with_randomness_main_thread_time_seconds_sum[$rate_interval]) * 384",
"instant": false,
"legendFormat": "signature_deserialization",
"legendFormat": "aggregate_with_randomness",
"range": true,
"refId": "A"
},
Expand Down Expand Up @@ -1270,7 +1270,7 @@
"disableTextWrap": false,
"editorMode": "code",
"exemplar": false,
"expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_bucket[$rate_interval])",
"expr": "rate(lodestar_bls_thread_pool_aggregate_with_randomness_main_thread_time_seconds_bucket[$rate_interval])",
"format": "heatmap",
"fullMetaSearch": false,
"includeNullMetadata": true,
Expand All @@ -1281,7 +1281,7 @@
"useBackend": false
}
],
"title": "Main Thread Signature Aggregation Time",
"title": "Main Thread AggregateWithRandomness Time",
"type": "heatmap"
},
{
Expand Down
3 changes: 1 addition & 2 deletions packages/beacon-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@
},
"dependencies": {
"@chainsafe/as-sha256": "^0.4.1",
"@chainsafe/bls": "7.1.3",
"@chainsafe/blst": "^0.2.11",
"@chainsafe/blst": "^2.0.1",
"@chainsafe/discv5": "^9.0.0",
"@chainsafe/enr": "^3.0.0",
"@chainsafe/libp2p-gossipsub": "^13.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/chain/bls/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {PublicKey} from "@chainsafe/bls/types";
import {PublicKey} from "@chainsafe/blst";
import {ISignatureSet} from "@lodestar/state-transition";

export type VerifySignatureOpts = {
Expand Down
15 changes: 7 additions & 8 deletions packages/beacon-node/src/chain/bls/maybeBatch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {CoordType, PublicKey} from "@chainsafe/bls/types";
import bls from "@chainsafe/bls";
import {PublicKey, Signature, verify, verifyMultipleAggregateSignatures} from "@chainsafe/blst";

const MIN_SET_COUNT_TO_BATCH = 2;

Expand All @@ -16,12 +15,12 @@ export type SignatureSetDeserialized = {
export function verifySignatureSetsMaybeBatch(sets: SignatureSetDeserialized[]): boolean {
try {
if (sets.length >= MIN_SET_COUNT_TO_BATCH) {
return bls.Signature.verifyMultipleSignatures(
return verifyMultipleAggregateSignatures(
sets.map((s) => ({
publicKey: s.publicKey,
message: s.message,
pk: s.publicKey,
msg: s.message,
// true = validate signature
signature: bls.Signature.fromBytes(s.signature, CoordType.affine, true),
sig: Signature.fromBytes(s.signature, true),
}))
);
}
Expand All @@ -34,8 +33,8 @@ export function verifySignatureSetsMaybeBatch(sets: SignatureSetDeserialized[]):
// If too few signature sets verify them without batching
return sets.every((set) => {
// true = validate signature
const sig = bls.Signature.fromBytes(set.signature, CoordType.affine, true);
return sig.verify(set.publicKey, set.message);
const sig = Signature.fromBytes(set.signature, true);
return verify(set.message, set.publicKey, sig);
});
} catch (_) {
// A signature could be malformed, in that case fromBytes throws error
Expand Down
16 changes: 5 additions & 11 deletions packages/beacon-node/src/chain/bls/multithread/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import {spawn, Worker} from "@chainsafe/threads";
// @ts-ignore
// eslint-disable-next-line
self = undefined;
import bls from "@chainsafe/bls";
import {Implementation, PointFormat, PublicKey} from "@chainsafe/bls/types";
import {PublicKey} from "@chainsafe/blst";
import {Logger} from "@lodestar/utils";
import {ISignatureSet} from "@lodestar/state-transition";
import {QueueError, QueueErrorCode} from "../../../util/queue/index.js";
Expand Down Expand Up @@ -116,7 +115,6 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {
private readonly logger: Logger;
private readonly metrics: Metrics | null;

private readonly format: PointFormat;
private readonly workers: WorkerDescriptor[];
private readonly jobs = new LinkedList<JobQueueItem>();
private bufferedJobs: {
Expand All @@ -136,14 +134,10 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {
this.metrics = metrics;
this.blsVerifyAllMultiThread = options.blsVerifyAllMultiThread ?? false;

// TODO: Allow to customize implementation
const implementation = bls.implementation;

// Use compressed for herumi for now.
// THe worker is not able to deserialize from uncompressed
// `Error: err _wrapDeserialize`
this.format = implementation === "blst-native" ? PointFormat.uncompressed : PointFormat.compressed;
this.workers = this.createWorkers(implementation, blsPoolSize);
this.workers = this.createWorkers(blsPoolSize);

if (metrics) {
metrics.blsThreadPool.queueLength.addCollect(() => {
Expand Down Expand Up @@ -265,11 +259,11 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {
);
}

private createWorkers(implementation: Implementation, poolSize: number): WorkerDescriptor[] {
private createWorkers(poolSize: number): WorkerDescriptor[] {
const workers: WorkerDescriptor[] = [];

for (let i = 0; i < poolSize; i++) {
const workerData: WorkerData = {implementation, workerId: i};
const workerData: WorkerData = {workerId: i};
const worker = new Worker(path.join(workerDir, "worker.js"), {
workerData,
} as ConstructorParameters<typeof Worker>[1]);
Expand Down Expand Up @@ -400,7 +394,7 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {
try {
// Note: This can throw, must be handled per-job.
// Pubkey and signature aggregation is defered here
workReq = jobItemWorkReq(job, this.format, this.metrics);
workReq = jobItemWorkReq(job, this.metrics);
} catch (e) {
this.metrics?.blsThreadPool.errorAggregateSignatureSetsCount.inc({type: job.type});

Expand Down
24 changes: 12 additions & 12 deletions packages/beacon-node/src/chain/bls/multithread/jobItem.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import bls from "@chainsafe/bls";
import {CoordType, PointFormat, PublicKey} from "@chainsafe/bls/types";
import {PublicKey, aggregateWithRandomness} from "@chainsafe/blst";
import {ISignatureSet, SignatureSetType} from "@lodestar/state-transition";
import {VerifySignatureOpts} from "../interface.js";
import {getAggregatedPubkey} from "../utils.js";
Expand Down Expand Up @@ -49,36 +48,37 @@ export function jobItemSigSets(job: JobQueueItem): number {
* Prepare BlsWorkReq from JobQueueItem
* WARNING: May throw with untrusted user input
*/
export function jobItemWorkReq(job: JobQueueItem, format: PointFormat, metrics: Metrics | null): BlsWorkReq {
export function jobItemWorkReq(job: JobQueueItem, metrics: Metrics | null): BlsWorkReq {
switch (job.type) {
case JobQueueItemType.default:
return {
opts: job.opts,
sets: job.sets.map((set) => ({
// this can throw, handled in the consumer code
publicKey: getAggregatedPubkey(set, metrics).toBytes(format),
publicKey: getAggregatedPubkey(set, metrics).toBytes(),
signature: set.signature,
message: set.signingRoot,
})),
};
case JobQueueItemType.sameMessage: {
// validate signature = true, this is slow code on main thread so should only run with network thread mode (useWorker=true)
// For a node subscribing to all subnets, with 1 signature per validator per epoch it takes around 80s
// to deserialize 750_000 signatures per epoch
// This is slow code on main thread (mainly signature deserialization + group check).
// Ideally it can be taken off-thread, but in the mean time, keep track of total time spent here.
// As of July 2024, for a node subscribing to all subnets, with 1 signature per validator per epoch,
// it takes around 2.02 min to perform this operation for a single epoch.
// cpu profile on main thread has 250s idle so this only works until we reach 3M validators
// However, for normal node with only 2 to 7 subnet subscriptions per epoch this works until 27M validators
// and not a problem in the near future
// this is monitored on v1.11.0 https://github.com/ChainSafe/lodestar/pull/5912#issuecomment-1700320307
const timer = metrics?.blsThreadPool.signatureDeserializationMainThreadDuration.startTimer();
const signatures = job.sets.map((set) => bls.Signature.fromBytes(set.signature, CoordType.affine, true));
// this is monitored on v1.21.0 https://github.com/ChainSafe/lodestar/pull/6894/files#r1687359225
const timer = metrics?.blsThreadPool.aggregateWithRandomnessMainThreadDuration.startTimer();
const {pk, sig} = aggregateWithRandomness(job.sets.map((set) => ({pk: set.publicKey, sig: set.signature})));
timer?.();

return {
opts: job.opts,
sets: [
{
publicKey: bls.PublicKey.aggregate(job.sets.map((set) => set.publicKey)).toBytes(format),
signature: bls.Signature.aggregate(signatures).toBytes(format),
publicKey: pk.toBytes(),
signature: sig.toBytes(),
message: job.message,
},
],
Expand Down
1 change: 0 additions & 1 deletion packages/beacon-node/src/chain/bls/multithread/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {VerifySignatureOpts} from "../interface.js";

export type WorkerData = {
implementation: "herumi" | "blst-native";
workerId: number;
};

Expand Down
5 changes: 2 additions & 3 deletions packages/beacon-node/src/chain/bls/multithread/worker.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import worker from "node:worker_threads";
import {expose} from "@chainsafe/threads/worker";
import bls from "@chainsafe/bls";
import {CoordType} from "@chainsafe/bls/types";
import {PublicKey} from "@chainsafe/blst";
import {verifySignatureSetsMaybeBatch, SignatureSetDeserialized} from "../maybeBatch.js";
import {WorkerData, BlsWorkReq, WorkResult, WorkResultCode, SerializedSet, BlsWorkResult} from "./types.js";
import {chunkifyMaximizeChunkSize} from "./utils.js";
Expand Down Expand Up @@ -109,7 +108,7 @@ function verifyManySignatureSets(workReqArr: BlsWorkReq[]): BlsWorkResult {

function deserializeSet(set: SerializedSet): SignatureSetDeserialized {
return {
publicKey: bls.PublicKey.fromBytes(set.publicKey, CoordType.affine),
publicKey: PublicKey.fromBytes(set.publicKey),
message: set.message,
signature: set.signature,
};
Expand Down
14 changes: 6 additions & 8 deletions packages/beacon-node/src/chain/bls/singleThread.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {PublicKey, Signature} from "@chainsafe/bls/types";
import bls from "@chainsafe/bls";
import {CoordType} from "@chainsafe/blst";
import {PublicKey, Signature, aggregatePublicKeys, aggregateSignatures, verify} from "@chainsafe/blst";
import {ISignatureSet} from "@lodestar/state-transition";
import {Metrics} from "../../metrics/index.js";
import {IBlsVerifier} from "./interface.js";
Expand Down Expand Up @@ -40,12 +38,12 @@ export class BlsSingleThreadVerifier implements IBlsVerifier {
message: Uint8Array
): Promise<boolean[]> {
const timer = this.metrics?.blsThreadPool.mainThreadDurationInThreadPool.startTimer();
const pubkey = bls.PublicKey.aggregate(sets.map((set) => set.publicKey));
const pubkey = aggregatePublicKeys(sets.map((set) => set.publicKey));
let isAllValid = true;
// validate signature = true
const signatures = sets.map((set) => {
try {
return bls.Signature.fromBytes(set.signature, CoordType.affine, true);
return Signature.fromBytes(set.signature, true);
} catch (_) {
// at least one set has malformed signature
isAllValid = false;
Expand All @@ -54,8 +52,8 @@ export class BlsSingleThreadVerifier implements IBlsVerifier {
});

if (isAllValid) {
const signature = bls.Signature.aggregate(signatures as Signature[]);
isAllValid = signature.verify(pubkey, message);
const signature = aggregateSignatures(signatures as Signature[]);
isAllValid = verify(message, pubkey, signature);
}

let result: boolean[];
Expand All @@ -67,7 +65,7 @@ export class BlsSingleThreadVerifier implements IBlsVerifier {
if (sig === null) {
return false;
}
return sig.verify(set.publicKey, message);
return verify(message, set.publicKey, sig);
});
}

Expand Down
5 changes: 2 additions & 3 deletions packages/beacon-node/src/chain/bls/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type {PublicKey} from "@chainsafe/bls/types";
import bls from "@chainsafe/bls";
import {PublicKey, aggregatePublicKeys} from "@chainsafe/blst";
import {ISignatureSet, SignatureSetType} from "@lodestar/state-transition";
import {Metrics} from "../../metrics/metrics.js";

Expand All @@ -10,7 +9,7 @@ export function getAggregatedPubkey(signatureSet: ISignatureSet, metrics: Metric

case SignatureSetType.aggregate: {
const timer = metrics?.blsThreadPool.pubkeysAggregationMainThreadDuration.startTimer();
const pubkeys = bls.PublicKey.aggregate(signatureSet.pubkeys);
const pubkeys = aggregatePublicKeys(signatureSet.pubkeys);
timer?.();
return pubkeys;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import bls from "@chainsafe/bls";
import {toHexString} from "@chainsafe/ssz";
import {aggregateSignatures} from "@chainsafe/blst";
import {ForkName, ForkSeq, MAX_ATTESTATIONS, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params";
import {phase0, Epoch, Slot, ssz, ValidatorIndex, RootHex} from "@lodestar/types";
import {
Expand Down Expand Up @@ -383,7 +383,7 @@ export function aggregateInto(attestation1: AttestationWithIndex, attestation2:

const signature1 = signatureFromBytesNoCheck(attestation1.attestation.signature);
const signature2 = signatureFromBytesNoCheck(attestation2.attestation.signature);
attestation1.attestation.signature = bls.Signature.aggregate([signature1, signature2]).toBytes();
attestation1.attestation.signature = aggregateSignatures([signature1, signature2]).toBytes();
}

/**
Expand Down
10 changes: 3 additions & 7 deletions packages/beacon-node/src/chain/opPools/attestationPool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {PointFormat, Signature} from "@chainsafe/bls/types";
import bls from "@chainsafe/bls";
import {BitArray} from "@chainsafe/ssz";
import {Signature, aggregateSignatures} from "@chainsafe/blst";
import {phase0, Slot, RootHex} from "@lodestar/types";
import {MapDef} from "@lodestar/utils";
import {IClock} from "../../util/clock.js";
Expand Down Expand Up @@ -191,10 +190,7 @@ function aggregateAttestationInto(aggregate: AggregateFast, attestation: phase0.
}

aggregate.aggregationBits.set(bitIndex, true);
aggregate.signature = bls.Signature.aggregate([
aggregate.signature,
signatureFromBytesNoCheck(attestation.signature),
]);
aggregate.signature = aggregateSignatures([aggregate.signature, signatureFromBytesNoCheck(attestation.signature)]);
return InsertOutcome.Aggregated;
}

Expand All @@ -217,6 +213,6 @@ function fastToAttestation(aggFast: AggregateFast): phase0.Attestation {
return {
data: aggFast.data,
aggregationBits: aggFast.aggregationBits,
signature: aggFast.signature.toBytes(PointFormat.compressed),
signature: aggFast.signature.toBytes(),
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {PointFormat, Signature} from "@chainsafe/bls/types";
import bls from "@chainsafe/bls";
import {BitArray, toHexString} from "@chainsafe/ssz";
import {Signature, aggregateSignatures} from "@chainsafe/blst";
import {SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
import {altair, Root, Slot, SubcommitteeIndex} from "@lodestar/types";
import {MapDef} from "@lodestar/utils";
Expand Down Expand Up @@ -108,7 +107,7 @@ export class SyncCommitteeMessagePool {
return {
...contribution,
aggregationBits: contribution.aggregationBits,
signature: contribution.signature.toBytes(PointFormat.compressed),
signature: contribution.signature.toBytes(),
};
}

Expand Down Expand Up @@ -136,7 +135,7 @@ function aggregateSignatureInto(
}

contribution.aggregationBits.set(indexInSubcommittee, true);
contribution.signature = bls.Signature.aggregate([
contribution.signature = aggregateSignatures([
contribution.signature,
signatureFromBytesNoCheck(signature.signature),
]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type {Signature} from "@chainsafe/bls/types";
import bls from "@chainsafe/bls";
import {BitArray, toHexString} from "@chainsafe/ssz";
import {Signature, aggregateSignatures} from "@chainsafe/blst";
import {SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params";
import {altair, Slot, Root, ssz} from "@lodestar/types";
import {G2_POINT_AT_INFINITY} from "@lodestar/state-transition";
Expand Down Expand Up @@ -182,6 +181,6 @@ export function aggregate(bestContributionBySubnet: Map<number, SyncContribution
}
return {
syncCommitteeBits,
syncCommitteeSignature: bls.Signature.aggregate(signatures).toBytes(),
syncCommitteeSignature: aggregateSignatures(signatures).toBytes(),
};
}
5 changes: 2 additions & 3 deletions packages/beacon-node/src/chain/opPools/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import bls from "@chainsafe/bls";
import {CoordType, Signature} from "@chainsafe/bls/types";
import {Signature} from "@chainsafe/blst";
import {BLS_WITHDRAWAL_PREFIX} from "@lodestar/params";
import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
import {Slot, capella} from "@lodestar/types";
Expand Down Expand Up @@ -30,7 +29,7 @@ export function pruneBySlot(map: Map<Slot, unknown>, slot: Slot, slotsRetained:
* No need to verify Signature is valid, already run sig-verify = false
*/
export function signatureFromBytesNoCheck(signature: Uint8Array): Signature {
return bls.Signature.fromBytes(signature, CoordType.affine, false);
return Signature.fromBytes(signature);
}

/**
Expand Down
Loading

0 comments on commit 2b62754

Please sign in to comment.