Skip to content

Commit 65f9c03

Browse files
committed
sig verification pool test
1 parent ee42ec5 commit 65f9c03

File tree

3 files changed

+177
-0
lines changed

3 files changed

+177
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ecrecover } from '@ethereumjs/util'
2+
import { parentPort } from 'worker_threads'
3+
4+
interface SignatureTask {
5+
msgHash: Uint8Array
6+
v: bigint
7+
r: Uint8Array
8+
s: Uint8Array
9+
chainId?: bigint
10+
}
11+
12+
interface SignatureResult {
13+
publicKey: Uint8Array
14+
}
15+
16+
function processSignature(task: SignatureTask): SignatureResult {
17+
const publicKey = ecrecover(task.msgHash, task.v, task.r, task.s, task.chainId)
18+
return { publicKey }
19+
}
20+
21+
// Listen for messages from the main thread
22+
parentPort?.on('message', (tasks: SignatureTask[]) => {
23+
const results = tasks.map(processSignature)
24+
parentPort?.postMessage(results)
25+
})
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { dirname, join } from 'path'
2+
import { fileURLToPath } from 'url'
3+
import { Worker } from 'worker_threads'
4+
5+
interface SignatureTask {
6+
msgHash: Uint8Array
7+
v: bigint
8+
r: Uint8Array
9+
s: Uint8Array
10+
chainId?: bigint
11+
}
12+
13+
interface SignatureResult {
14+
publicKey: Uint8Array
15+
}
16+
17+
export class SignatureWorkerPool {
18+
private workers: Worker[] = []
19+
private taskQueue: SignatureTask[] = []
20+
private results: Map<number, SignatureResult> = new Map()
21+
private nextTaskId = 0
22+
23+
constructor(numWorkers: number = 4) {
24+
const __filename = fileURLToPath(import.meta.url)
25+
const __dirname = dirname(__filename)
26+
const workerPath = join(__dirname, 'signatureWorker.ts')
27+
28+
for (let i = 0; i < numWorkers; i++) {
29+
const worker = new Worker(workerPath, {
30+
// Use tsx to run TypeScript files
31+
execArgv: ['-r', 'tsx/register'],
32+
})
33+
34+
worker.on('message', (results: SignatureResult[]) => {
35+
results.forEach((result, index) => {
36+
this.results.set(this.nextTaskId - results.length + index, result)
37+
})
38+
})
39+
this.workers.push(worker)
40+
}
41+
}
42+
43+
async processBatch(tasks: SignatureTask[]): Promise<Map<number, SignatureResult>> {
44+
const batchSize = Math.ceil(tasks.length / this.workers.length)
45+
const promises: Promise<void>[] = []
46+
47+
for (let i = 0; i < this.workers.length; i++) {
48+
const start = i * batchSize
49+
const end = Math.min(start + batchSize, tasks.length)
50+
const batch = tasks.slice(start, end)
51+
52+
if (batch.length > 0) {
53+
promises.push(
54+
new Promise((resolve) => {
55+
this.workers[i].postMessage(batch)
56+
resolve()
57+
}),
58+
)
59+
}
60+
}
61+
62+
await Promise.all(promises)
63+
return this.results
64+
}
65+
66+
terminate() {
67+
this.workers.forEach((worker) => worker.terminate())
68+
}
69+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { LegacyTx } from '@ethereumjs/tx'
2+
import { bigIntToUnpaddedBytes, ecrecover, equalsBytes, randomBytes } from '@ethereumjs/util'
3+
import { SignatureWorkerPool } from '../../src/worker/signatureWorkerPool.ts'
4+
5+
async function runBenchmark() {
6+
// Create 10 test transactions
7+
const transactions = Array.from({ length: 1000 }, (_, i) => {
8+
return new LegacyTx({
9+
nonce: i,
10+
gasPrice: 1000000000n,
11+
gasLimit: 21000n,
12+
to: '0x0000000000000000000000000000000000000000',
13+
value: 1000000000000000000n,
14+
data: '0x',
15+
})
16+
})
17+
18+
// Sign all transactions
19+
const signedTxs = transactions.map((tx) => tx.sign(randomBytes(32)))
20+
21+
// Precompute all hashes and signature values
22+
const msgHashes = signedTxs.map((tx) => tx.getMessageToVerifySignature())
23+
const vs = signedTxs.map((tx) => tx.v!)
24+
const rs = signedTxs.map((tx) => bigIntToUnpaddedBytes(tx.r!))
25+
const ss = signedTxs.map((tx) => bigIntToUnpaddedBytes(tx.s!))
26+
const chainIds = signedTxs.map((tx) => tx.common.chainId())
27+
28+
console.log('Running benchmark...\n')
29+
30+
// Sequential verification
31+
console.log('Sequential verification:')
32+
const startSeq = performance.now()
33+
34+
// Store sequential results
35+
const sequentialResults = new Map()
36+
for (let i = 0; i < signedTxs.length; i++) {
37+
const publicKey = ecrecover(msgHashes[i], vs[i], rs[i], ss[i], 1n)
38+
sequentialResults.set(i, publicKey)
39+
}
40+
41+
const endSeq = performance.now()
42+
console.log(`Time taken: ${endSeq - startSeq}ms\n`)
43+
44+
// Parallel verification using worker pool
45+
console.log('Parallel verification using worker pool:')
46+
const startPar = performance.now()
47+
48+
const signatureTasks = msgHashes.map((msgHash, i) => {
49+
return {
50+
msgHash,
51+
v: vs[i],
52+
r: rs[i],
53+
s: ss[i],
54+
chainId: chainIds[i],
55+
}
56+
})
57+
58+
const workerPool = new SignatureWorkerPool(4) // Use 4 workers
59+
const parallelResults = await workerPool.processBatch(signatureTasks)
60+
await workerPool.terminate()
61+
62+
const endPar = performance.now()
63+
console.log(`Time taken: ${endPar - startPar}ms\n`)
64+
65+
// Print speedup
66+
const speedup = (endSeq - startSeq) / (endPar - startPar)
67+
console.log(`Speedup: ${speedup.toFixed(2)}x`)
68+
69+
// Verify results match by comparing the stored sequential results with parallel results
70+
console.log('\nVerifying results...')
71+
let allMatch = true
72+
for (let i = 0; i < signedTxs.length; i++) {
73+
const parallelResult = parallelResults.get(i)
74+
const sequentialResult = sequentialResults.get(i)
75+
if (parallelResult && !equalsBytes(parallelResult.publicKey, sequentialResult)) {
76+
console.log(`Mismatch at index ${i}`)
77+
allMatch = false
78+
}
79+
}
80+
console.log(`All results match: ${allMatch}`)
81+
}
82+
83+
runBenchmark().catch(console.error)

0 commit comments

Comments
 (0)