From af400987aee5249e8944c6aac3c35bb2b9701ad7 Mon Sep 17 00:00:00 2001 From: GitGab19 Date: Mon, 27 Jul 2026 19:26:13 +0200 Subject: [PATCH 1/3] Allow custom pools to specify a JDS port, defaulting to 3334. --- server/src/config-generator.test.ts | 27 +++++++++++++ server/src/config-generator.ts | 8 +++- server/src/pool-validation.test.ts | 11 ++++++ server/src/pool-validation.ts | 5 +++ shared/src/types.ts | 1 + src/components/pools/PoolPriorityEditor.tsx | 38 +++++++++++++++++-- src/components/settings/ConfigurationTab.tsx | 1 + src/components/setup/steps/PoolConfigStep.tsx | 1 + src/lib/poolValidation.ts | 3 ++ 9 files changed, 90 insertions(+), 5 deletions(-) diff --git a/server/src/config-generator.test.ts b/server/src/config-generator.test.ts index dbfce0c0..792d8c42 100644 --- a/server/src/config-generator.test.ts +++ b/server/src/config-generator.test.ts @@ -154,6 +154,7 @@ test('jdc config uses shared shares-per-minute and miner signature', () => { assert.ok(config); assert.match(config, /shares_per_minute = 12\.5/); + assert.match(config, /reserved_downstream_rollable_extranonce_size = 8/); assert.match(config, /jdc_signature = "custom-miner-tag"/); assert.match(config, /monitoring_cache_refresh_secs = 5/); assert.match(config, /\[miner_telemetry\]\ncidrs = \["192\.168\.1\.0\/24"\]/); @@ -333,6 +334,32 @@ test('jdc in pool mode puts user_identity inside [[upstreams]]', () => { assert.match(config, /\[\[upstreams\]\][\s\S]*user_identity = "miner\.worker1"/); }); +test('jdc config uses a custom JD port and defaults fallbacks to 3334', () => { + const config = generateJdcConfig({ + ...BASE_DATA_30, + pool: { + ...BASE_DATA_30.pool!, + jds_port: 3337, + }, + fallbackPools: [ + { + name: 'Fallback Pool', + address: 'fallback.pool.com', + port: 4444, + authority_public_key: 'fallback-key', + user_identity: 'miner.fallback', + }, + ], + }); + + assert.ok(config); + const fallbackIndex = config.indexOf('pool_address = "fallback.pool.com"'); + + assert.ok(fallbackIndex > 0); + assert.match(config.slice(0, fallbackIndex), /jds_port = 3337/); + assert.match(config.slice(fallbackIndex), /jds_port = 3334/); +}); + test('jdc config writes Bitcoin Core IPC version', () => { const config30 = generateJdcConfig(BASE_DATA_30); const config31 = generateJdcConfig(BASE_DATA_31); diff --git a/server/src/config-generator.ts b/server/src/config-generator.ts index ec44b201..12969b99 100644 --- a/server/src/config-generator.ts +++ b/server/src/config-generator.ts @@ -60,6 +60,7 @@ function normalizePool(pool: PoolConfig | null | undefined, fallbackIdentity: st name: legacyPool.name ?? 'Custom Pool', address: legacyPool.address ?? '', port: legacyPool.port ?? 0, + jds_port: legacyPool.jds_port, authority_public_key: legacyPool.authority_public_key ?? '', user_identity: legacyPool.user_identity || fallbackIdentity, }; @@ -287,6 +288,9 @@ export function generateJdcConfig(data: SetupData): string | null { DEFAULT_SHARES_PER_MINUTE, ).toFixed(1); const shareBatchSize = '5'; + const reservedDownstreamRollableExtranonceSize = downstreamExtranonce2Size( + normalizedData.translator?.downstream_extranonce2_size, + ); // Fee threshold and min interval for template provider const feeThreshold = '1000'; const minInterval = '5'; @@ -300,7 +304,7 @@ authority_pubkey = "${upstream.authority_public_key}" pool_address = "${upstream.address}" pool_port = ${upstream.port} jds_address = "${upstream.address}" -jds_port = 3334 +jds_port = ${upstream.jds_port ?? 3334} user_identity = "${upstream.user_identity}" `).join('\n')} ` @@ -326,6 +330,8 @@ cert_validity_sec = 3600 # Shares configuration shares_per_minute = ${sharesPerMinute} share_batch_size = ${shareBatchSize} +# Extranonce bytes reserved for downstream miners +reserved_downstream_rollable_extranonce_size = ${reservedDownstreamRollableExtranonceSize} # JDC mode: FULLTEMPLATE, COINBASEONLY, or SOLOMINING mode = "${isSovereignSolo ? 'SOLOMINING' : 'FULLTEMPLATE'}" diff --git a/server/src/pool-validation.test.ts b/server/src/pool-validation.test.ts index 5e088d4d..d82f7bb5 100644 --- a/server/src/pool-validation.test.ts +++ b/server/src/pool-validation.test.ts @@ -57,3 +57,14 @@ test('rejects invalid ports and oversized identities', () => { /username/i, ); }); + +test('accepts an omitted JD port and rejects invalid specified JD ports', () => { + assert.equal(getPoolConfigError(VALID_POOL, 'Primary pool'), null); + + for (const jds_port of [0, 65536, 3334.5]) { + assert.match( + getPoolConfigError({ ...VALID_POOL, jds_port }, 'Primary pool') ?? '', + /JD port must be between/i, + ); + } +}); diff --git a/server/src/pool-validation.ts b/server/src/pool-validation.ts index accf3a7a..6cee7816 100644 --- a/server/src/pool-validation.ts +++ b/server/src/pool-validation.ts @@ -40,6 +40,11 @@ export function getPoolConfigError(pool: PoolConfig, label: string): string | nu if (!Number.isInteger(pool.port) || pool.port <= 0 || pool.port > 65535) { return `${label} port must be between 1 and 65535`; } + if (pool.jds_port !== undefined && ( + !Number.isInteger(pool.jds_port) || pool.jds_port <= 0 || pool.jds_port > 65535 + )) { + return `${label} JD port must be between 1 and 65535`; + } if (!isValidAuthorityPublicKey(pool.authority_public_key)) { return `${label} authority public key is invalid`; } diff --git a/shared/src/types.ts b/shared/src/types.ts index d2d55a7b..97c7adda 100644 --- a/shared/src/types.ts +++ b/shared/src/types.ts @@ -11,6 +11,7 @@ export interface PoolConfig { name: string; address: string; port: number; + jds_port?: number; authority_public_key: string; user_identity: string; } diff --git a/src/components/pools/PoolPriorityEditor.tsx b/src/components/pools/PoolPriorityEditor.tsx index f274d1fc..f64275fb 100644 --- a/src/components/pools/PoolPriorityEditor.tsx +++ b/src/components/pools/PoolPriorityEditor.tsx @@ -15,6 +15,7 @@ interface PoolPriorityEditorProps { presets: KnownPool[]; pools: PoolConfig[]; miningMode: MiningMode | null; + isJdMode: boolean; onChange: (pools: PoolConfig[]) => void; } @@ -26,6 +27,7 @@ export function PoolPriorityEditor({ presets, pools, miningMode, + isJdMode, onChange, }: PoolPriorityEditorProps) { const [draggedIndex, setDraggedIndex] = useState(null); @@ -210,6 +212,7 @@ export function PoolPriorityEditor({ updatePool(index, nextPool)} /> )} @@ -284,10 +287,12 @@ export function PoolPriorityEditor({ function CustomPoolFields({ pool, idPrefix, + isJdMode, onChange, }: { pool: PoolConfig; idPrefix: string; + isJdMode: boolean; onChange: (pool: PoolConfig) => void; }) { const updateField = (field: keyof PoolConfig, value: string | number) => { @@ -301,10 +306,14 @@ function CustomPoolFields({ return (
-
+
-
+ {isJdMode && ( +
+ + onChange({ + ...pool, + jds_port: event.target.value === '' ? undefined : Number(event.target.value), + })} + placeholder="3334" + className="h-9 w-full rounded-lg border border-input bg-background px-3 text-sm outline-none transition-all focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-primary/15" + /> +
+ )} + +
diff --git a/src/components/settings/ConfigurationTab.tsx b/src/components/settings/ConfigurationTab.tsx index 8262ced8..6f22317f 100644 --- a/src/components/settings/ConfigurationTab.tsx +++ b/src/components/settings/ConfigurationTab.tsx @@ -636,6 +636,7 @@ export function ConfigurationTab() { presets={pools} pools={editPools ?? []} miningMode={activeMiningMode} + isJdMode={isJdMode} onChange={(nextPools) => { setEditPools((currentPools) => normalizePoolPriorityIdentities( nextPools, diff --git a/src/components/setup/steps/PoolConfigStep.tsx b/src/components/setup/steps/PoolConfigStep.tsx index 988ba4fb..c7ee8868 100644 --- a/src/components/setup/steps/PoolConfigStep.tsx +++ b/src/components/setup/steps/PoolConfigStep.tsx @@ -157,6 +157,7 @@ export function PoolConfigStep({ data, updateData, onNext }: PoolConfigStepProps presets={pools} pools={selectedPools} miningMode={data.miningMode} + isJdMode={data.mode === 'jd'} onChange={updateSelectedPools} />
diff --git a/src/lib/poolValidation.ts b/src/lib/poolValidation.ts index 0b8e8a4e..4ca12478 100644 --- a/src/lib/poolValidation.ts +++ b/src/lib/poolValidation.ts @@ -8,6 +8,9 @@ export function isPoolConnectionComplete(pool: PoolConfig | null | undefined): b Number.isInteger(pool.port) && pool.port > 0 && pool.port <= 65535 && + (pool.jds_port === undefined || ( + Number.isInteger(pool.jds_port) && pool.jds_port > 0 && pool.jds_port <= 65535 + )) && isValidPoolAuthorityPubkey(pool.authority_public_key), ); } From 9ddefd890f731f040d9f3152a1ee0da07365940d Mon Sep 17 00:00:00 2001 From: GitGab19 Date: Tue, 28 Jul 2026 13:37:17 +0200 Subject: [PATCH 2/3] update CK pool url --- src/lib/pools.test.ts | 4 ++-- src/lib/pools.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/pools.test.ts b/src/lib/pools.test.ts index 58fb342a..4c552668 100644 --- a/src/lib/pools.test.ts +++ b/src/lib/pools.test.ts @@ -16,14 +16,14 @@ test('CKPool preset matches its SV2 solo endpoint', () => { assert.ok(ckPool); assert.deepEqual(knownPoolToConfig(ckPool), { name: 'CKPool', - address: 'sv2solo.ckpool.org', + address: 'stratum.ckpool.org', port: 3336, authority_public_key: '9anrRNhBh7869XtNnFcCuGBRZP51E635qGbu457J5kHdszhfRc3', user_identity: '', }); assert.equal( `stratum2+tcp://${ckPool.address}:${ckPool.port}/${ckPool.authority_public_key}`, - 'stratum2+tcp://sv2solo.ckpool.org:3336/9anrRNhBh7869XtNnFcCuGBRZP51E635qGbu457J5kHdszhfRc3', + 'stratum2+tcp://stratum.ckpool.org:3336/9anrRNhBh7869XtNnFcCuGBRZP51E635qGbu457J5kHdszhfRc3', ); assert.equal(isValidPoolAuthorityPubkey(ckPool.authority_public_key), true); assert.equal(ckPool.monogram, 'CK'); diff --git a/src/lib/pools.ts b/src/lib/pools.ts index 7c781e55..fbad94ac 100644 --- a/src/lib/pools.ts +++ b/src/lib/pools.ts @@ -58,7 +58,7 @@ export const SOLO_POOLS: KnownPool[] = [ { id: 'ckpool', name: 'CKPool', - address: 'sv2solo.ckpool.org', + address: 'stratum.ckpool.org', port: 3336, authority_public_key: '9anrRNhBh7869XtNnFcCuGBRZP51E635qGbu457J5kHdszhfRc3', description: 'CKPool', From b90be32665763a29eca60b8eebb174f9f0d5e0ab Mon Sep 17 00:00:00 2001 From: GitGab19 Date: Tue, 28 Jul 2026 14:07:05 +0200 Subject: [PATCH 3/3] fix(dashboard): restore share acceptance details --- src/pages/UnifiedDashboard.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pages/UnifiedDashboard.tsx b/src/pages/UnifiedDashboard.tsx index d6fb0882..943c2a65 100644 --- a/src/pages/UnifiedDashboard.tsx +++ b/src/pages/UnifiedDashboard.tsx @@ -679,6 +679,15 @@ export function UnifiedDashboard() { return {label}; })()} + subtitle={(() => { + const { submitted, rejected } = shareStats; + if (submitted === 0) return undefined; + const rejectionRate = (rejected / submitted) * 100; + const rejRateLabel = rejected === 0 + ? '0%' + : `${Math.max(rejectionRate, 0.01).toFixed(2)}%`; + return `${submitted.toLocaleString()} submitted ยท ${rejected.toLocaleString()} rejected (${rejRateLabel})`; + })()} /> )}