Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions server/src/config-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { SetupData } from './types.js';
const BASE_DATA_30: SetupData = {
miningMode: 'pool',
mode: 'jd',
miner_telemetry_cidr: '192.168.1.0/24',
pool: {
name: 'Custom Pool',
address: 'pool.example.com',
Expand Down Expand Up @@ -48,6 +49,7 @@ const BASE_DATA_31_SOLO: SetupData = {
const NO_JD_DATA: SetupData = {
miningMode: 'pool',
mode: 'no-jd',
miner_telemetry_cidr: '192.168.1.0/24',
pool: {
name: 'Remote Pool',
address: 'remote.pool.com',
Expand All @@ -73,6 +75,8 @@ test('translator config uses advanced setup values', () => {
assert.match(config, /min_individual_miner_hashrate = 100000000000000\.0/);
assert.match(config, /downstream_extranonce2_size = 8/);
assert.match(config, /shares_per_minute = 12\.5/);
assert.match(config, /monitoring_cache_refresh_secs = 5/);
assert.match(config, /\[miner_telemetry\]\ncidrs = \["192\.168\.1\.0\/24"\]/);
});

test('translator config omits payout verification for pool mining', () => {
Expand Down Expand Up @@ -151,11 +155,28 @@ 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, /jdc_signature = "custom-miner-tag"/);
assert.match(config, /monitoring_cache_refresh_secs = 5/);
assert.match(config, /\[miner_telemetry\]\ncidrs = \["192\.168\.1\.0\/24"\]/);
});

test('miner telemetry config is omitted when LAN CIDR is blank', () => {
const data = {
...BASE_DATA_30,
miner_telemetry_cidr: '',
};

const translatorConfig = generateTranslatorConfig(data);
const jdcConfig = generateJdcConfig(data);

assert.doesNotMatch(translatorConfig, /\[miner_telemetry\]/);
assert.ok(jdcConfig);
assert.doesNotMatch(jdcConfig, /\[miner_telemetry\]/);
});

test('normalization backfills advanced defaults for old saved configs', () => {
const data = {
...BASE_DATA_30,
miner_telemetry_cidr: undefined,
translator: {
...BASE_DATA_30.translator,
min_hashrate: undefined,
Expand All @@ -168,6 +189,7 @@ test('normalization backfills advanced defaults for old saved configs', () => {

assert.ok(normalized.translator);
assert.equal(normalized.translator.min_hashrate, 100_000_000_000_000);
assert.equal(normalized.miner_telemetry_cidr, '');
assert.equal(normalized.translator.shares_per_minute, 6);
assert.equal(normalized.translator.downstream_extranonce2_size, 4);
assert.equal('verify_payout' in normalized.translator, false);
Expand Down Expand Up @@ -328,6 +350,19 @@ test('jdc in solo mode omits user_identity entirely', () => {
assert.match(config, /upstreams = \[\]/);
});

test('jdc in solo mode emits upstreams before miner telemetry table', () => {
const config = generateJdcConfig(BASE_DATA_31_SOLO);

assert.ok(config);

const upstreamsIndex = config.indexOf('upstreams = []');
const telemetryIndex = config.indexOf('[miner_telemetry]');

assert.notEqual(upstreamsIndex, -1);
assert.notEqual(telemetryIndex, -1);
assert.ok(upstreamsIndex < telemetryIndex);
});

test('no-jd mode: translator uses new format (user_identity inside [[upstreams]])', () => {
const config = generateTranslatorConfig(NO_JD_DATA);
const upstreamIdx = config.indexOf('[[upstreams]]');
Expand Down
22 changes: 19 additions & 3 deletions server/src/config-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import {
DEFAULT_MIN_HASHRATE,
bitcoinCoreVersionToIpcMajor,
formatSupportedVersions,
normalizeMinerTelemetryCidr,
} from '@sv2-ui/shared';
import type { JdcConfig, PoolConfig, SetupData, TranslatorConfig } from './types.js';

const MONITORING_CACHE_REFRESH_SECS = 5;

function positiveNumber(value: number | undefined, fallback: number): number {
return typeof value === 'number' && Number.isFinite(value) && value > 0
? value
Expand Down Expand Up @@ -96,6 +99,15 @@ function shouldVerifyPayout(data: SetupData): boolean {
&& pools.every((pool) => !isFullDonationIdentity(pool.user_identity));
}

function minerTelemetryConfig(cidr: string): string {
if (!cidr) return '';

return `# LAN subnet containing ASIC miner web/API addresses (for example, 192.168.1.0/24).
[miner_telemetry]
cidrs = ["${cidr}"]
`;
}

export function normalizeSetupData(data: SetupData): SetupData {
const fallbackIdentity = legacyIdentity(data);
const pool = normalizePool(data.pool, fallbackIdentity);
Expand All @@ -119,6 +131,7 @@ export function normalizeSetupData(data: SetupData): SetupData {
return {
miningMode: data.miningMode,
mode: data.mode,
miner_telemetry_cidr: normalizeMinerTelemetryCidr(data.miner_telemetry_cidr),
pool,
fallbackPools,
bitcoin: data.bitcoin,
Expand All @@ -130,6 +143,7 @@ export function normalizeSetupData(data: SetupData): SetupData {
return {
miningMode: data.miningMode,
mode: data.mode,
miner_telemetry_cidr: normalizeMinerTelemetryCidr(data.miner_telemetry_cidr),
pool,
fallbackPools,
bitcoin: data.bitcoin,
Expand Down Expand Up @@ -224,8 +238,9 @@ required_extensions = []

# Monitoring HTTP server address
monitoring_address = "0.0.0.0:9092"
monitoring_cache_refresh_secs = 15
monitoring_cache_refresh_secs = ${MONITORING_CACHE_REFRESH_SECS}

${minerTelemetryConfig(normalizedData.miner_telemetry_cidr)}
# Difficulty params
[downstream_difficulty_config]
min_individual_miner_hashrate = ${minHashrate}
Expand Down Expand Up @@ -320,9 +335,10 @@ required_extensions = []

# Monitoring HTTP server address
monitoring_address = "0.0.0.0:9091"
monitoring_cache_refresh_secs = 15
monitoring_cache_refresh_secs = ${MONITORING_CACHE_REFRESH_SECS}

${upstreamsConfig}# Bitcoin Core IPC config
${upstreamsConfig}${minerTelemetryConfig(normalizedData.miner_telemetry_cidr)}
# Bitcoin Core IPC config
[template_provider_type.BitcoinCoreIpc]
version = ${bitcoinCoreIpcVersion}
network = "${bitcoin.network}"
Expand Down
1 change: 1 addition & 0 deletions server/src/images.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { SetupData } from './types.js';
const BASE_SETUP_DATA: SetupData = {
miningMode: 'pool',
mode: 'jd',
miner_telemetry_cidr: '192.168.1.0/24',
pool: {
name: 'Custom Pool',
address: 'pool.example.com',
Expand Down
6 changes: 6 additions & 0 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
normalizeBitcoinCoreVersion,
TRANSLATOR_MONITORING_PORT,
JDC_MONITORING_PORT,
getMinerTelemetryCidrError,
} from '@sv2-ui/shared';
import { BITCOIN_ERROR_MESSAGES } from './messages.js';
import {
Expand Down Expand Up @@ -161,6 +162,11 @@ function getSetupValidationError(data: SetupData): string | null {
return `No more than ${MAX_FALLBACK_POOLS} fallback pools may be configured`;
}

const minerTelemetryCidrError = getMinerTelemetryCidrError(data.miner_telemetry_cidr);
if (minerTelemetryCidrError) {
return minerTelemetryCidrError;
}

const pools = configuredPools(data);
for (const [index, pool] of pools.entries()) {
const error = getPoolConfigError(pool, index === 0 ? 'Primary pool' : `Fallback pool ${index}`);
Expand Down
2 changes: 1 addition & 1 deletion shared/openapi.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './constants.js';
export * from './images.js';
export * from './pools.js';
export * from './format.js';
export * from './miner-telemetry.js';
45 changes: 45 additions & 0 deletions shared/src/miner-telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const MIN_IPV4_PREFIX = 24;

export function normalizeMinerTelemetryCidr(value: string | null | undefined): string {
return value?.trim() ?? '';
}

function isPrivateIpv4(parts: number[]): boolean {
const [a, b] = parts;
return a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168);
}

export function getMinerTelemetryCidrError(value: string | null | undefined): string | null {
const cidr = normalizeMinerTelemetryCidr(value);
if (!cidr) {
return null;
}

const [address, prefix, extra] = cidr.split('/');
if (!address || !prefix || extra !== undefined) {
return 'Enter a private IPv4 CIDR such as 192.168.1.0/24';
}

const octets = address.split('.').map(Number);
if (
octets.length !== 4 ||
octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)
) {
return 'Enter a valid IPv4 address';
}

const prefixNumber = Number(prefix);
if (!Number.isInteger(prefixNumber) || prefixNumber < MIN_IPV4_PREFIX || prefixNumber > 32) {
return `Use a /${MIN_IPV4_PREFIX} or narrower private IPv4 range`;
}

if (!isPrivateIpv4(octets)) {
return 'Use the private LAN subnet where your miners expose their web/API interface';
}

return null;
}

export function isValidMinerTelemetryCidr(value: string | null | undefined): boolean {
return getMinerTelemetryCidrError(value) === null;
}
1 change: 1 addition & 0 deletions shared/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface TranslatorConfig {
export interface SetupData {
miningMode: MiningMode | null;
mode: SetupMode | null;
miner_telemetry_cidr: string;
pool: PoolConfig | null;
fallbackPools: PoolConfig[];
bitcoin: BitcoinConfig | null;
Expand Down
Loading
Loading