From fd226f206fe38e68c21cfde5e01ed5f1ec423271 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 14 Jul 2026 22:55:36 -0400 Subject: [PATCH] Raise SV2 retained job limit --- .env.example | 2 +- README.md | 2 +- src/models/StratumV2Client.spec.ts | 35 ++++++++++++++++++++++++++++++ src/models/StratumV2Client.ts | 2 +- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 864906e..cb619e3 100644 --- a/.env.example +++ b/.env.example @@ -102,7 +102,7 @@ STRATUM_SUBMISSION_DEDUP_MAX_ENTRIES=10000 # Per-client lifecycle bounds. Obsolete queued jobs are coalesced; clients are # disconnected instead of dropping still-recoverable candidates or buffering forever. SV2_JOB_RETENTION_MS=300000 -SV2_MAX_RETAINED_JOBS_PER_CHANNEL=16 +SV2_MAX_RETAINED_JOBS_PER_CHANNEL=64 SV2_MAX_QUEUED_JOB_OPERATIONS=4 SV2_MAX_SOCKET_BUFFER_BYTES=262144 SV2_SOCKET_WRITE_TIMEOUT_MS=2000 diff --git a/README.md b/README.md index e491d79..f403ddb 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Standard and extended candidates retain exact header/body reconstruction, and late network-target candidates remain recoverable without crediting stale shares. Pending SV2 canonical jobs are coalesced per client, while a new-tip activation is moved ahead of any not-yet-started canonical work for that tip. The finite -defaults are 16 retained jobs per channel, four queued operations, 256 KiB of +defaults are 64 retained jobs per channel, four queued operations, 256 KiB of outstanding socket writes, and a two-second write-callback deadline. A client is disconnected if `SV2_MAX_RETAINED_JOBS_PER_CHANNEL`, `SV2_MAX_QUEUED_JOB_OPERATIONS`, `SV2_MAX_SOCKET_BUFFER_BYTES`, or diff --git a/src/models/StratumV2Client.spec.ts b/src/models/StratumV2Client.spec.ts index 5048960..a54e01f 100644 --- a/src/models/StratumV2Client.spec.ts +++ b/src/models/StratumV2Client.spec.ts @@ -927,6 +927,41 @@ describe('StratumV2Client extended channels', () => { )).toBe(true); }); + it('keeps enough retained SV2 jobs for repeated fast activation and full-template followup cycles', async () => { + const { client, jobTemplate } = await createClient(); + await (client as any).handleOpenExtendedMiningChannel(serializeOpenExtendedMiningChannel({ + requestId: 1, + userIdentity: 'tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4.worker', + nominalHashRate: 0, + maxTarget: Buffer.alloc(32, 0xff), + minExtranonceSize: 8, + })); + + let currentTemplate = jobTemplate; + for (let index = 0; index < 20; index++) { + const activation = { + ...createActivationTemplate(currentTemplate), + previousblockhash: (index + 1).toString(16).padStart(64, '0'), + mintime: parseInt(MockRecording1.TIME, 16) + index + 1, + curtime: parseInt(MockRecording1.TIME, 16) + index + 1, + }; + await client.enqueueWorkActivation(activation as any); + currentTemplate = { + ...createCanonicalFollowup(currentTemplate, activation), + blockData: { + ...createCanonicalFollowup(currentTemplate, activation).blockData, + id: `canonical-followup-${index}`, + }, + }; + await client.enqueueCanonicalJob(currentTemplate); + } + + expect((client as any).socket.destroyed).toBe(false); + const channel = (client as any).channels.get(1); + expect(channel.extendedJobs.size).toBeLessThanOrEqual((client as any).maxRetainedJobsPerChannel); + expect(channel.stagedFutureJobId).toBeDefined(); + }); + it('activates a staged standard future and rejects incompatible activation versions', async () => { const { client, sentFrames, jobTemplate } = await createClient(); await (client as any).handleOpenStandardMiningChannel(serializeOpenStandardMiningChannel({ diff --git a/src/models/StratumV2Client.ts b/src/models/StratumV2Client.ts index 75732ff..e3623a6 100644 --- a/src/models/StratumV2Client.ts +++ b/src/models/StratumV2Client.ts @@ -78,7 +78,7 @@ const DEFAULT_DIFFICULTY_CHECK_INTERVAL_MS = 60 * 1000; const DEFAULT_CLIENT_HASHRATE_PERSIST_INTERVAL_MS = 60 * 1000; const FIXED_STANDARD_EXTRANONCE2 = '0000000000000000'; const DEFAULT_SV2_JOB_RETENTION_MS = 5 * 60 * 1000; -const DEFAULT_SV2_MAX_RETAINED_JOBS_PER_CHANNEL = 16; +const DEFAULT_SV2_MAX_RETAINED_JOBS_PER_CHANNEL = 64; const DEFAULT_SV2_MAX_QUEUED_JOB_OPERATIONS = 4; const DEFAULT_SV2_MAX_SOCKET_BUFFER_BYTES = 256 * 1024; const DEFAULT_SV2_SOCKET_WRITE_TIMEOUT_MS = 2 * 1000;