From 1958f0a793705edf925074578cd1b55c12e61a07 Mon Sep 17 00:00:00 2001 From: Shrutesh Pachineela Date: Thu, 27 Aug 2026 14:16:16 +0530 Subject: [PATCH 1/5] add approved pool urls --- tests/unit/handlers/actions.handlers.test.js | 145 ++++++++++++++++++ tests/unit/handlers/pools.handlers.test.js | 19 ++- tests/unit/routes/pools.routes.test.js | 12 ++ workers/lib/constants.js | 9 ++ .../lib/server/handlers/actions.handlers.js | 35 ++++- workers/lib/server/handlers/pools.handlers.js | 10 +- workers/lib/server/routes/pools.routes.js | 11 +- 7 files changed, 234 insertions(+), 7 deletions(-) diff --git a/tests/unit/handlers/actions.handlers.test.js b/tests/unit/handlers/actions.handlers.test.js index a0e9cd6..35a26cb 100644 --- a/tests/unit/handlers/actions.handlers.test.js +++ b/tests/unit/handlers/actions.handlers.test.js @@ -11,6 +11,7 @@ const { voteAction, cancelActionsBatch } = require('../../../workers/lib/server/handlers/actions.handlers') +const { APPROVED_POOL_CONFIGS } = require('../../../workers/lib/constants') const { createMockCtxWithOrks, createMockReq, withDataProxy } = require('../helpers/mockHelpers') test('queryActionsBatch - basic functionality', async (t) => { @@ -257,6 +258,150 @@ test('pushAction - with valid permissions', async (t) => { t.pass() }) +test('pushAction - REGISTER_CONFIG resolves poolUrlIds to approved pool urls', async (t) => { + let capturedPayload = null + const mockCtx = withDataProxy({ + conf: { + orks: [ + { rpcPublicKey: 'key1' } + ] + }, + authLib: { + getTokenPerms: async () => ({ + write: true, + permissions: ['actions:write'] + }) + }, + net_r0: { + jRequest: async (key, method, payload, opts) => { + capturedPayload = payload + return { id: 'new-action', success: true } + } + } + }) + + const approvedIds = APPROVED_POOL_CONFIGS.map((config) => config.id) + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action: 'REGISTER_CONFIG', + params: [{ name: 'my-config', poolUrlIds: approvedIds }] + } + } + + const result = await pushAction(mockCtx, mockReq) + + t.ok(Array.isArray(result), 'should return array') + t.ok(result[0].id === 'new-action', 'should return new action id') + + const [poolConfig] = capturedPayload.params + t.absent(poolConfig.poolUrlIds, 'poolUrlIds should be removed from the pool config') + t.alike(poolConfig.poolUrls, APPROVED_POOL_CONFIGS, 'poolUrls should be resolved from APPROVED_POOL_CONFIGS') + + t.pass() +}) + +test('pushAction - REGISTER_CONFIG throws for missing/invalid poolUrlIds', async (t) => { + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: [] }) + }, + net_r0: { + jRequest: async () => ({ id: 'new-action', success: true }) + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action: 'REGISTER_CONFIG', + params: [{ name: 'my-config' }] + } + } + + try { + await pushAction(mockCtx, mockReq) + t.fail('should throw error for missing poolUrlIds') + } catch (err) { + t.is(err.message, 'ERR_INVALID_POOL_URL_IDS', 'should throw ERR_INVALID_POOL_URL_IDS') + } + + t.pass() +}) + +test('pushAction - REGISTER_CONFIG throws for unknown poolUrlId', async (t) => { + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: [] }) + }, + net_r0: { + jRequest: async () => ({ id: 'new-action', success: true }) + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action: 'REGISTER_CONFIG', + params: [{ name: 'my-config', poolUrlIds: ['does-not-exist'] }] + } + } + + try { + await pushAction(mockCtx, mockReq) + t.fail('should throw error for unknown poolUrlId') + } catch (err) { + t.is(err.message, 'ERR_INVALID_POOL_URL', 'should throw ERR_INVALID_POOL_URL') + } + + t.pass() +}) + +test('pushAction - REGISTER_CONFIG with no pool config passes params through unchanged', async (t) => { + let capturedPayload = null + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: [] }) + }, + net_r0: { + jRequest: async (key, method, payload, opts) => { + capturedPayload = payload + return { id: 'new-action', success: true } + } + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action: 'REGISTER_CONFIG', + params: [] + } + } + + const result = await pushAction(mockCtx, mockReq) + + t.ok(Array.isArray(result), 'should return array') + t.alike(capturedPayload.params, [], 'params should pass through unchanged when no pool config present') + + t.pass() +}) + test('pushActionsBatch - requires write permission', async (t) => { const mockCtx = { authLib: { diff --git a/tests/unit/handlers/pools.handlers.test.js b/tests/unit/handlers/pools.handlers.test.js index bdcf819..f52bb75 100644 --- a/tests/unit/handlers/pools.handlers.test.js +++ b/tests/unit/handlers/pools.handlers.test.js @@ -1,7 +1,7 @@ 'use strict' const test = require('brittle') -const { RPC_METHODS, WORKER_TYPES, MINER_CATEGORIES } = require('../../../workers/lib/constants') +const { RPC_METHODS, WORKER_TYPES, MINER_CATEGORIES, APPROVED_POOL_CONFIGS } = require('../../../workers/lib/constants') const { getPools, flattenPoolStats, @@ -10,7 +10,8 @@ const { flattenTransactionResults, groupByBucket, getPoolThingConfig, - getPoolStatsContainers + getPoolStatsContainers, + getApprovedPoolUrls } = require('../../../workers/lib/server/handlers/pools.handlers') const { withDataProxy } = require('../helpers/mockHelpers') @@ -549,3 +550,17 @@ test('getPoolStatsContainers - handles empty containers from RPC', async (t) => t.is(result.length, 0, 'should be empty when no containers') t.pass() }) + +test('getApprovedPoolUrls - returns the approved pool configs constant', async (t) => { + const result = await getApprovedPoolUrls() + + t.is(result, APPROVED_POOL_CONFIGS, 'should return the APPROVED_POOL_CONFIGS constant') + t.ok(Array.isArray(result), 'should return an array') + result.forEach((config) => { + t.ok(typeof config.id === 'string', 'each config should have an id') + t.ok(typeof config.name === 'string', 'each config should have a name') + t.ok(typeof config.host === 'string', 'each config should have a host') + t.ok(typeof config.port === 'number', 'each config should have a port') + }) + t.pass() +}) diff --git a/tests/unit/routes/pools.routes.test.js b/tests/unit/routes/pools.routes.test.js index f91a771..d98b7ff 100644 --- a/tests/unit/routes/pools.routes.test.js +++ b/tests/unit/routes/pools.routes.test.js @@ -7,6 +7,7 @@ const { createRoutesForTest } = require('../helpers/mockHelpers') const ROUTES_PATH = '../../../workers/lib/server/routes/pools.routes.js' const POOLS_CONFIG_ROUTE_URL = '/auth/pools/config/:id' const POOLS_STATS_CONTAINERS_ROUTE_URL = '/auth/pools/stats/containers' +const POOLS_APPROVED_CONFIGS_ROUTE_URL = '/auth/pools/approved-urls' test('pools routes - module structure', (t) => { testModuleStructure(t, ROUTES_PATH, 'pools') @@ -20,6 +21,7 @@ test('pools routes - route definitions', (t) => { t.ok(routeUrls.includes('/auth/pools/:pool/balance-history'), 'should have balance-history route') t.ok(routeUrls.includes('/auth/pools/config/:id'), 'should have pools thing config route') t.ok(routeUrls.includes('/auth/pools/stats/containers'), 'should have pools stats containers route') + t.ok(routeUrls.includes('/auth/pools/approved-urls'), 'should have pools approved configs route') t.pass() }) @@ -62,3 +64,13 @@ test('pools routes - GET /auth/pools/stats/containers', (t) => { t.ok(typeof statsRoute.onRequest === 'function', 'pools stats containers route should have onRequest (auth)') t.pass() }) + +test('pools routes - GET /auth/pools/approved-configs', (t) => { + const routes = createRoutesForTest(ROUTES_PATH) + const approvedConfigsRoute = routes.find(r => r.url === POOLS_APPROVED_CONFIGS_ROUTE_URL) + t.ok(approvedConfigsRoute, 'should have pools approved configs route') + t.is(approvedConfigsRoute.method, 'GET', 'pools approved configs route should be GET') + t.ok(typeof approvedConfigsRoute.handler === 'function', 'pools approved configs route should have handler') + t.ok(typeof approvedConfigsRoute.onRequest === 'function', 'pools approved configs route should have onRequest (auth)') + t.pass() +}) diff --git a/workers/lib/constants.js b/workers/lib/constants.js index 4589352..d0658f9 100644 --- a/workers/lib/constants.js +++ b/workers/lib/constants.js @@ -166,6 +166,7 @@ const ENDPOINTS = { POOLS_BALANCE_HISTORY: '/auth/pools/:pool/balance-history', POOLS_THING_CONFIG: '/auth/pools/config/:id', POOLS_CONTAINERS_STATS: '/auth/pools/stats/containers', + POOLS_APPROVED_URLS: '/auth/pools/approved-urls', SITE_STATUS_LIVE: '/auth/site/status/live', SITE_POWER_CONSUMPTION: '/auth/site/power-consumption', @@ -873,6 +874,13 @@ const CONFIG_TYPES = { POOL: 'pool' } +// Pool configs that are pre-approved for use, served read-only via ENDPOINTS.POOLS_APPROVED_CONFIGS +const APPROVED_POOL_URLS = [ + { id: 'f2pool-btc-1', name: 'F2Pool', host: 'btc.f2pool.com', port: 1314 }, + { id: 'ocean-btc-2', name: 'Ocean', host: 'mine.ocean.xyz', port: 3334 }, + { id: 'antpool-btc-3', name: 'Antpool', host: 'ss.antpool.com', port: 3333 } +] + const MINER_FIELD_MAP = { status: 'last.snap.stats.status', hashrate: 'last.snap.stats.hashrate_mhs', @@ -1226,6 +1234,7 @@ module.exports = { BTC_SATS, RANGE_BUCKETS, CONFIG_TYPES, + APPROVED_POOL_CONFIGS: APPROVED_POOL_URLS, METRICS_TIME, METRICS_DEFAULTS, MINER_CATEGORIES, diff --git a/workers/lib/server/handlers/actions.handlers.js b/workers/lib/server/handlers/actions.handlers.js index 7556249..4328983 100644 --- a/workers/lib/server/handlers/actions.handlers.js +++ b/workers/lib/server/handlers/actions.handlers.js @@ -1,7 +1,7 @@ 'use strict' const { parseJsonQueryParam } = require('../../utils') -const { ACTIONS_MAX_QUERIES } = require('../../constants') +const { ACTIONS_MAX_QUERIES, APPROVED_POOL_CONFIGS } = require('../../constants') async function queryActionsBatch (ctx, req) { const payload = { @@ -77,6 +77,35 @@ async function pushActionsBatch (ctx, req, rep) { }) } +const transformPushActionPayload = (payload) => { + switch (payload.action) { + case 'REGISTER_CONFIG': { + const [poolConfig] = payload.params + if (!poolConfig) return payload + + const poolUrls = [] + const { poolUrlIds } = poolConfig + if (!poolUrlIds || !Array.isArray(poolUrlIds)) throw new Error('ERR_INVALID_POOL_URL_IDS') + + for (const poolUrlId of poolUrlIds) { + const poolUrl = APPROVED_POOL_CONFIGS.find(config => config.id === poolUrlId) + if (!poolUrl) { + throw new Error('ERR_INVALID_POOL_URL') + } + + poolUrls.push(poolUrl) + } + + delete poolConfig.poolUrlIds + poolConfig.poolUrls = poolUrls + return payload + } + + default: + return payload + } +} + async function pushAction (ctx, req) { const { write, permissions } = await ctx.authLib.getTokenPerms(req._info.authToken) if (!write) { @@ -91,7 +120,9 @@ async function pushAction (ctx, req) { authPerms: permissions } - return await ctx.dataProxy.requestData('pushAction', payload, (res, resultsArray) => { + const transformedPayload = transformPushActionPayload(structuredClone(payload)) + + return await ctx.dataProxy.requestData('pushAction', transformedPayload, (res, resultsArray) => { if (res.error) { resultsArray.push({ id: null, errors: [res.error] }) } else { diff --git a/workers/lib/server/handlers/pools.handlers.js b/workers/lib/server/handlers/pools.handlers.js index 0af8b32..4ec2df0 100644 --- a/workers/lib/server/handlers/pools.handlers.js +++ b/workers/lib/server/handlers/pools.handlers.js @@ -6,7 +6,8 @@ const { WORKER_TYPES, MINERPOOL_EXT_DATA_KEYS, RANGE_BUCKETS, - MINER_FIELD_MAP + MINER_FIELD_MAP, + APPROVED_POOL_CONFIGS } = require('../../constants') const { parseJsonQueryParam, @@ -229,6 +230,10 @@ const getPoolStatsContainers = async (ctx, req) => { }) } +async function getApprovedPoolUrls () { + return APPROVED_POOL_CONFIGS +} + module.exports = { getPools, flattenPoolStats, @@ -237,5 +242,6 @@ module.exports = { flattenTransactionResults, groupByBucket, getPoolThingConfig, - getPoolStatsContainers + getPoolStatsContainers, + getApprovedPoolUrls } diff --git a/workers/lib/server/routes/pools.routes.js b/workers/lib/server/routes/pools.routes.js index ef3279d..f1d0fdd 100644 --- a/workers/lib/server/routes/pools.routes.js +++ b/workers/lib/server/routes/pools.routes.js @@ -8,7 +8,8 @@ const { getPools, getPoolBalanceHistory, getPoolThingConfig, - getPoolStatsContainers + getPoolStatsContainers, + getApprovedPoolUrls } = require('../handlers/pools.handlers') const { createCachedAuthRoute, createAuthRoute } = require('../lib/routeHelpers') @@ -68,6 +69,14 @@ module.exports = (ctx) => { ctx, getPoolStatsContainers ) + }, + { + method: HTTP_METHODS.GET, + url: ENDPOINTS.POOLS_APPROVED_URLS, + ...createAuthRoute( + ctx, + getApprovedPoolUrls + ) } ] } From f293b1e71af6cca857e86956be04fb7514fe037d Mon Sep 17 00:00:00 2001 From: Shrutesh Pachineela Date: Thu, 27 Aug 2026 14:41:22 +0530 Subject: [PATCH 2/5] disregard the pool url from the client --- tests/unit/handlers/actions.handlers.test.js | 109 ++++++++++++++++-- .../lib/server/handlers/actions.handlers.js | 29 +++-- 2 files changed, 120 insertions(+), 18 deletions(-) diff --git a/tests/unit/handlers/actions.handlers.test.js b/tests/unit/handlers/actions.handlers.test.js index 35a26cb..e6d0229 100644 --- a/tests/unit/handlers/actions.handlers.test.js +++ b/tests/unit/handlers/actions.handlers.test.js @@ -258,7 +258,7 @@ test('pushAction - with valid permissions', async (t) => { t.pass() }) -test('pushAction - REGISTER_CONFIG resolves poolUrlIds to approved pool urls', async (t) => { +test('pushAction - REGISTER_CONFIG resolves poolUrls to approved pool url/worker settings', async (t) => { let capturedPayload = null const mockCtx = withDataProxy({ conf: { @@ -280,7 +280,6 @@ test('pushAction - REGISTER_CONFIG resolves poolUrlIds to approved pool urls', a } }) - const approvedIds = APPROVED_POOL_CONFIGS.map((config) => config.id) const mockReq = { _info: { authToken: 'token123', @@ -288,7 +287,14 @@ test('pushAction - REGISTER_CONFIG resolves poolUrlIds to approved pool urls', a }, body: { action: 'REGISTER_CONFIG', - params: [{ name: 'my-config', poolUrlIds: approvedIds }] + params: [{ + name: 'my-config', + poolUrls: APPROVED_POOL_CONFIGS.map((config) => ({ + poolUrlId: config.id, + workerName: `${config.id}-worker`, + workerPassword: 'x' + })) + }] } } @@ -298,13 +304,64 @@ test('pushAction - REGISTER_CONFIG resolves poolUrlIds to approved pool urls', a t.ok(result[0].id === 'new-action', 'should return new action id') const [poolConfig] = capturedPayload.params - t.absent(poolConfig.poolUrlIds, 'poolUrlIds should be removed from the pool config') - t.alike(poolConfig.poolUrls, APPROVED_POOL_CONFIGS, 'poolUrls should be resolved from APPROVED_POOL_CONFIGS') + t.is(poolConfig.poolUrls.length, APPROVED_POOL_CONFIGS.length, 'should resolve one entry per approved pool') + poolConfig.poolUrls.forEach((resolved, i) => { + const approved = APPROVED_POOL_CONFIGS[i] + t.is(resolved.url, `stratum+tcp://${approved.host}:${approved.port}`, 'url should be built from host and port') + t.is(resolved.pool, approved.name, 'pool should be the approved config name') + t.is(resolved.workerName, `${approved.id}-worker`, 'workerName should pass through from the request') + t.is(resolved.workerPassword, 'x', 'workerPassword should pass through from the request') + }) + + t.pass() +}) + +test('pushAction - REGISTER_CONFIG disregards a url sent from the client', async (t) => { + let capturedPayload = null + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: ['actions:write'] }) + }, + net_r0: { + jRequest: async (key, method, payload, opts) => { + capturedPayload = payload + return { id: 'new-action', success: true } + } + } + }) + + const approved = APPROVED_POOL_CONFIGS[0] + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action: 'REGISTER_CONFIG', + params: [{ + name: 'my-config', + poolUrls: [{ + poolUrlId: approved.id, + url: 'stratum+tcp://attacker-controlled.example.com:9999', + workerName: 'worker1', + workerPassword: 'secret' + }] + }] + } + } + + await pushAction(mockCtx, mockReq) + + const [poolConfig] = capturedPayload.params + const [resolved] = poolConfig.poolUrls + t.is(resolved.url, `stratum+tcp://${approved.host}:${approved.port}`, 'url should be rebuilt from the approved pool config, not the client-supplied url') + t.not(resolved.url, 'stratum+tcp://attacker-controlled.example.com:9999', 'client-supplied url should never reach the payload') t.pass() }) -test('pushAction - REGISTER_CONFIG throws for missing/invalid poolUrlIds', async (t) => { +test('pushAction - REGISTER_CONFIG throws for missing/invalid poolUrls', async (t) => { const mockCtx = withDataProxy({ conf: { orks: [{ rpcPublicKey: 'key1' }] }, authLib: { @@ -328,9 +385,41 @@ test('pushAction - REGISTER_CONFIG throws for missing/invalid poolUrlIds', async try { await pushAction(mockCtx, mockReq) - t.fail('should throw error for missing poolUrlIds') + t.fail('should throw error for missing poolUrls') + } catch (err) { + t.is(err.message, 'ERR_INVALID_POOL_URLS', 'should throw ERR_INVALID_POOL_URLS') + } + + t.pass() +}) + +test('pushAction - REGISTER_CONFIG throws when a poolUrl entry is missing poolUrlId', async (t) => { + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: [] }) + }, + net_r0: { + jRequest: async () => ({ id: 'new-action', success: true }) + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action: 'REGISTER_CONFIG', + params: [{ name: 'my-config', poolUrls: [{ workerName: 'worker1' }] }] + } + } + + try { + await pushAction(mockCtx, mockReq) + t.fail('should throw error for missing poolUrlId') } catch (err) { - t.is(err.message, 'ERR_INVALID_POOL_URL_IDS', 'should throw ERR_INVALID_POOL_URL_IDS') + t.is(err.message, 'ERR_INVALID_POOL_URL_ID_MISSING', 'should throw ERR_INVALID_POOL_URL_ID_MISSING') } t.pass() @@ -354,7 +443,7 @@ test('pushAction - REGISTER_CONFIG throws for unknown poolUrlId', async (t) => { }, body: { action: 'REGISTER_CONFIG', - params: [{ name: 'my-config', poolUrlIds: ['does-not-exist'] }] + params: [{ name: 'my-config', poolUrls: [{ poolUrlId: 'does-not-exist' }] }] } } @@ -362,7 +451,7 @@ test('pushAction - REGISTER_CONFIG throws for unknown poolUrlId', async (t) => { await pushAction(mockCtx, mockReq) t.fail('should throw error for unknown poolUrlId') } catch (err) { - t.is(err.message, 'ERR_INVALID_POOL_URL', 'should throw ERR_INVALID_POOL_URL') + t.is(err.message, 'ERR_INVALID_POOL_URL_ID_INVALID', 'should throw ERR_INVALID_POOL_URL_ID_INVALID') } t.pass() diff --git a/workers/lib/server/handlers/actions.handlers.js b/workers/lib/server/handlers/actions.handlers.js index 4328983..832207f 100644 --- a/workers/lib/server/handlers/actions.handlers.js +++ b/workers/lib/server/handlers/actions.handlers.js @@ -83,21 +83,34 @@ const transformPushActionPayload = (payload) => { const [poolConfig] = payload.params if (!poolConfig) return payload - const poolUrls = [] - const { poolUrlIds } = poolConfig - if (!poolUrlIds || !Array.isArray(poolUrlIds)) throw new Error('ERR_INVALID_POOL_URL_IDS') + const { poolUrls } = poolConfig + if (!poolUrls || !Array.isArray(poolUrls)) throw new Error('ERR_INVALID_POOL_URLS') + + const result = [] + for (const poolUrlSetting of poolUrls) { + const { + poolUrlId, workerName, workerPassword + } = poolUrlSetting + + if (!poolUrlId) { + throw new Error('ERR_INVALID_POOL_URL_ID_MISSING') + } - for (const poolUrlId of poolUrlIds) { const poolUrl = APPROVED_POOL_CONFIGS.find(config => config.id === poolUrlId) if (!poolUrl) { - throw new Error('ERR_INVALID_POOL_URL') + throw new Error('ERR_INVALID_POOL_URL_ID_INVALID') } - poolUrls.push(poolUrl) + const { host, port, name } = poolUrl + result.push({ + url: `stratum+tcp://${host}:${port}`, + workerName, + workerPassword, + pool: name + }) } - delete poolConfig.poolUrlIds - poolConfig.poolUrls = poolUrls + poolConfig.poolUrls = result return payload } From 5c9ee577269eeb88c053f983097373241af52ab5 Mon Sep 17 00:00:00 2001 From: Shrutesh Pachineela Date: Thu, 27 Aug 2026 17:11:21 +0530 Subject: [PATCH 3/5] handle updateConfig and fix transformPayload --- tests/unit/handlers/actions.handlers.test.js | 429 ++++++++++-------- tests/unit/handlers/pools.handlers.test.js | 4 +- workers/lib/constants.js | 4 +- .../lib/server/handlers/actions.handlers.js | 17 +- workers/lib/server/handlers/pools.handlers.js | 4 +- 5 files changed, 252 insertions(+), 206 deletions(-) diff --git a/tests/unit/handlers/actions.handlers.test.js b/tests/unit/handlers/actions.handlers.test.js index e6d0229..9d48430 100644 --- a/tests/unit/handlers/actions.handlers.test.js +++ b/tests/unit/handlers/actions.handlers.test.js @@ -11,7 +11,7 @@ const { voteAction, cancelActionsBatch } = require('../../../workers/lib/server/handlers/actions.handlers') -const { APPROVED_POOL_CONFIGS } = require('../../../workers/lib/constants') +const { APPROVED_POOL_URLS } = require('../../../workers/lib/constants') const { createMockCtxWithOrks, createMockReq, withDataProxy } = require('../helpers/mockHelpers') test('queryActionsBatch - basic functionality', async (t) => { @@ -258,238 +258,277 @@ test('pushAction - with valid permissions', async (t) => { t.pass() }) -test('pushAction - REGISTER_CONFIG resolves poolUrls to approved pool url/worker settings', async (t) => { - let capturedPayload = null - const mockCtx = withDataProxy({ - conf: { - orks: [ - { rpcPublicKey: 'key1' } - ] - }, - authLib: { - getTokenPerms: async () => ({ - write: true, - permissions: ['actions:write'] - }) - }, - net_r0: { - jRequest: async (key, method, payload, opts) => { - capturedPayload = payload - return { id: 'new-action', success: true } +for (const action of ['registerConfig', 'updateConfig']) { + test(`pushAction - ${action} resolves poolUrls to approved pool url/worker settings`, async (t) => { + let capturedPayload = null + const mockCtx = withDataProxy({ + conf: { + orks: [ + { rpcPublicKey: 'key1' } + ] + }, + authLib: { + getTokenPerms: async () => ({ + write: true, + permissions: ['actions:write'] + }) + }, + net_r0: { + jRequest: async (key, method, payload, opts) => { + capturedPayload = payload + return { id: 'new-action', success: true } + } + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: [{ + name: 'my-config', + data: { + poolUrls: APPROVED_POOL_URLS.map((config) => ({ + poolUrlId: config.id, + workerName: `${config.id}-worker`, + workerPassword: 'x' + })) + } + }] } } - }) - const mockReq = { - _info: { - authToken: 'token123', - user: { metadata: { email: 'test@example.com' } } - }, - body: { - action: 'REGISTER_CONFIG', - params: [{ - name: 'my-config', - poolUrls: APPROVED_POOL_CONFIGS.map((config) => ({ - poolUrlId: config.id, - workerName: `${config.id}-worker`, - workerPassword: 'x' - })) - }] - } - } + const result = await pushAction(mockCtx, mockReq) - const result = await pushAction(mockCtx, mockReq) + t.ok(Array.isArray(result), 'should return array') + t.ok(result[0].id === 'new-action', 'should return new action id') - t.ok(Array.isArray(result), 'should return array') - t.ok(result[0].id === 'new-action', 'should return new action id') + const [poolConfig] = capturedPayload.params + t.is(poolConfig.data.poolUrls.length, APPROVED_POOL_URLS.length, 'should resolve one entry per approved pool') + poolConfig.data.poolUrls.forEach((resolved, i) => { + const approved = APPROVED_POOL_URLS[i] + t.is(resolved.poolUrlId, approved.id, 'poolUrlId should be preserved on the resolved entry') + t.is(resolved.url, `stratum+tcp://${approved.host}:${approved.port}`, 'url should be built from host and port') + t.is(resolved.pool, approved.name, 'pool should be the approved config name') + t.is(resolved.workerName, `${approved.id}-worker`, 'workerName should pass through from the request') + t.is(resolved.workerPassword, 'x', 'workerPassword should pass through from the request') + }) - const [poolConfig] = capturedPayload.params - t.is(poolConfig.poolUrls.length, APPROVED_POOL_CONFIGS.length, 'should resolve one entry per approved pool') - poolConfig.poolUrls.forEach((resolved, i) => { - const approved = APPROVED_POOL_CONFIGS[i] - t.is(resolved.url, `stratum+tcp://${approved.host}:${approved.port}`, 'url should be built from host and port') - t.is(resolved.pool, approved.name, 'pool should be the approved config name') - t.is(resolved.workerName, `${approved.id}-worker`, 'workerName should pass through from the request') - t.is(resolved.workerPassword, 'x', 'workerPassword should pass through from the request') + t.pass() }) - t.pass() -}) - -test('pushAction - REGISTER_CONFIG disregards a url sent from the client', async (t) => { - let capturedPayload = null - const mockCtx = withDataProxy({ - conf: { orks: [{ rpcPublicKey: 'key1' }] }, - authLib: { - getTokenPerms: async () => ({ write: true, permissions: ['actions:write'] }) - }, - net_r0: { - jRequest: async (key, method, payload, opts) => { - capturedPayload = payload - return { id: 'new-action', success: true } + test(`pushAction - ${action} disregards a url sent from the client`, async (t) => { + let capturedPayload = null + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: ['actions:write'] }) + }, + net_r0: { + jRequest: async (key, method, payload, opts) => { + capturedPayload = payload + return { id: 'new-action', success: true } + } } - } - }) - - const approved = APPROVED_POOL_CONFIGS[0] - const mockReq = { - _info: { - authToken: 'token123', - user: { metadata: { email: 'test@example.com' } } - }, - body: { - action: 'REGISTER_CONFIG', - params: [{ - name: 'my-config', - poolUrls: [{ - poolUrlId: approved.id, - url: 'stratum+tcp://attacker-controlled.example.com:9999', - workerName: 'worker1', - workerPassword: 'secret' + }) + + const approved = APPROVED_POOL_URLS[0] + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: [{ + name: 'my-config', + data: { + poolUrls: [{ + poolUrlId: approved.id, + url: 'stratum+tcp://attacker-controlled.example.com:9999', + workerName: 'worker1', + workerPassword: 'secret' + }] + } }] - }] + } } - } - await pushAction(mockCtx, mockReq) + await pushAction(mockCtx, mockReq) - const [poolConfig] = capturedPayload.params - const [resolved] = poolConfig.poolUrls - t.is(resolved.url, `stratum+tcp://${approved.host}:${approved.port}`, 'url should be rebuilt from the approved pool config, not the client-supplied url') - t.not(resolved.url, 'stratum+tcp://attacker-controlled.example.com:9999', 'client-supplied url should never reach the payload') + const [poolConfig] = capturedPayload.params + const [resolved] = poolConfig.data.poolUrls + t.is(resolved.url, `stratum+tcp://${approved.host}:${approved.port}`, 'url should be rebuilt from the approved pool config, not the client-supplied url') + t.not(resolved.url, 'stratum+tcp://attacker-controlled.example.com:9999', 'client-supplied url should never reach the payload') - t.pass() -}) - -test('pushAction - REGISTER_CONFIG throws for missing/invalid poolUrls', async (t) => { - const mockCtx = withDataProxy({ - conf: { orks: [{ rpcPublicKey: 'key1' }] }, - authLib: { - getTokenPerms: async () => ({ write: true, permissions: [] }) - }, - net_r0: { - jRequest: async () => ({ id: 'new-action', success: true }) - } + t.pass() }) - const mockReq = { - _info: { - authToken: 'token123', - user: { metadata: { email: 'test@example.com' } } - }, - body: { - action: 'REGISTER_CONFIG', - params: [{ name: 'my-config' }] + test(`pushAction - ${action} throws for missing/invalid poolUrls`, async (t) => { + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: [] }) + }, + net_r0: { + jRequest: async () => ({ id: 'new-action', success: true }) + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: [{ name: 'my-config', data: {} }] + } } - } - try { - await pushAction(mockCtx, mockReq) - t.fail('should throw error for missing poolUrls') - } catch (err) { - t.is(err.message, 'ERR_INVALID_POOL_URLS', 'should throw ERR_INVALID_POOL_URLS') - } + try { + await pushAction(mockCtx, mockReq) + t.fail('should throw error for missing poolUrls') + } catch (err) { + t.is(err.message, 'ERR_INVALID_POOL_URLS', 'should throw ERR_INVALID_POOL_URLS') + } - t.pass() -}) + t.pass() + }) -test('pushAction - REGISTER_CONFIG throws when a poolUrl entry is missing poolUrlId', async (t) => { - const mockCtx = withDataProxy({ - conf: { orks: [{ rpcPublicKey: 'key1' }] }, - authLib: { - getTokenPerms: async () => ({ write: true, permissions: [] }) - }, - net_r0: { - jRequest: async () => ({ id: 'new-action', success: true }) + test(`pushAction - ${action} throws when a poolUrl entry is missing poolUrlId`, async (t) => { + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: [] }) + }, + net_r0: { + jRequest: async () => ({ id: 'new-action', success: true }) + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: [{ name: 'my-config', data: { poolUrls: [{ workerName: 'worker1' }] } }] + } } - }) - const mockReq = { - _info: { - authToken: 'token123', - user: { metadata: { email: 'test@example.com' } } - }, - body: { - action: 'REGISTER_CONFIG', - params: [{ name: 'my-config', poolUrls: [{ workerName: 'worker1' }] }] + try { + await pushAction(mockCtx, mockReq) + t.fail('should throw error for missing poolUrlId') + } catch (err) { + t.is(err.message, 'ERR_INVALID_POOL_URL_ID_MISSING', 'should throw ERR_INVALID_POOL_URL_ID_MISSING') } - } - try { - await pushAction(mockCtx, mockReq) - t.fail('should throw error for missing poolUrlId') - } catch (err) { - t.is(err.message, 'ERR_INVALID_POOL_URL_ID_MISSING', 'should throw ERR_INVALID_POOL_URL_ID_MISSING') - } + t.pass() + }) - t.pass() -}) + test(`pushAction - ${action} throws for unknown poolUrlId`, async (t) => { + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: [] }) + }, + net_r0: { + jRequest: async () => ({ id: 'new-action', success: true }) + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: [{ name: 'my-config', data: { poolUrls: [{ poolUrlId: 'does-not-exist' }] } }] + } + } -test('pushAction - REGISTER_CONFIG throws for unknown poolUrlId', async (t) => { - const mockCtx = withDataProxy({ - conf: { orks: [{ rpcPublicKey: 'key1' }] }, - authLib: { - getTokenPerms: async () => ({ write: true, permissions: [] }) - }, - net_r0: { - jRequest: async () => ({ id: 'new-action', success: true }) + try { + await pushAction(mockCtx, mockReq) + t.fail('should throw error for unknown poolUrlId') + } catch (err) { + t.is(err.message, 'ERR_INVALID_POOL_URL_ID_INVALID', 'should throw ERR_INVALID_POOL_URL_ID_INVALID') } + + t.pass() }) - const mockReq = { - _info: { - authToken: 'token123', - user: { metadata: { email: 'test@example.com' } } - }, - body: { - action: 'REGISTER_CONFIG', - params: [{ name: 'my-config', poolUrls: [{ poolUrlId: 'does-not-exist' }] }] + test(`pushAction - ${action} with no pool config passes params through unchanged`, async (t) => { + let capturedPayload = null + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: [] }) + }, + net_r0: { + jRequest: async (key, method, payload, opts) => { + capturedPayload = payload + return { id: 'new-action', success: true } + } + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: [] + } } - } - try { - await pushAction(mockCtx, mockReq) - t.fail('should throw error for unknown poolUrlId') - } catch (err) { - t.is(err.message, 'ERR_INVALID_POOL_URL_ID_INVALID', 'should throw ERR_INVALID_POOL_URL_ID_INVALID') - } + const result = await pushAction(mockCtx, mockReq) - t.pass() -}) + t.ok(Array.isArray(result), 'should return array') + t.alike(capturedPayload.params, [], 'params should pass through unchanged when no pool config present') -test('pushAction - REGISTER_CONFIG with no pool config passes params through unchanged', async (t) => { - let capturedPayload = null - const mockCtx = withDataProxy({ - conf: { orks: [{ rpcPublicKey: 'key1' }] }, - authLib: { - getTokenPerms: async () => ({ write: true, permissions: [] }) - }, - net_r0: { - jRequest: async (key, method, payload, opts) => { - capturedPayload = payload - return { id: 'new-action', success: true } - } - } + t.pass() }) - const mockReq = { - _info: { - authToken: 'token123', - user: { metadata: { email: 'test@example.com' } } - }, - body: { - action: 'REGISTER_CONFIG', - params: [] + test(`pushAction - ${action} throws ERR_INVALID_PAYLOAD when params is not an array`, async (t) => { + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: [] }) + }, + net_r0: { + jRequest: async () => ({ id: 'new-action', success: true }) + } + }) + + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: { name: 'my-config' } + } } - } - const result = await pushAction(mockCtx, mockReq) - - t.ok(Array.isArray(result), 'should return array') - t.alike(capturedPayload.params, [], 'params should pass through unchanged when no pool config present') + try { + await pushAction(mockCtx, mockReq) + t.fail('should throw error for non-array params') + } catch (err) { + t.is(err.message, 'ERR_INVALID_PAYLOAD', 'should throw ERR_INVALID_PAYLOAD') + } - t.pass() -}) + t.pass() + }) +} test('pushActionsBatch - requires write permission', async (t) => { const mockCtx = { diff --git a/tests/unit/handlers/pools.handlers.test.js b/tests/unit/handlers/pools.handlers.test.js index f52bb75..60a6d2e 100644 --- a/tests/unit/handlers/pools.handlers.test.js +++ b/tests/unit/handlers/pools.handlers.test.js @@ -1,7 +1,7 @@ 'use strict' const test = require('brittle') -const { RPC_METHODS, WORKER_TYPES, MINER_CATEGORIES, APPROVED_POOL_CONFIGS } = require('../../../workers/lib/constants') +const { RPC_METHODS, WORKER_TYPES, MINER_CATEGORIES, APPROVED_POOL_URLS } = require('../../../workers/lib/constants') const { getPools, flattenPoolStats, @@ -554,7 +554,7 @@ test('getPoolStatsContainers - handles empty containers from RPC', async (t) => test('getApprovedPoolUrls - returns the approved pool configs constant', async (t) => { const result = await getApprovedPoolUrls() - t.is(result, APPROVED_POOL_CONFIGS, 'should return the APPROVED_POOL_CONFIGS constant') + t.is(result, APPROVED_POOL_URLS, 'should return the APPROVED_POOL_URLS constant') t.ok(Array.isArray(result), 'should return an array') result.forEach((config) => { t.ok(typeof config.id === 'string', 'each config should have an id') diff --git a/workers/lib/constants.js b/workers/lib/constants.js index d0658f9..4f684dc 100644 --- a/workers/lib/constants.js +++ b/workers/lib/constants.js @@ -874,7 +874,7 @@ const CONFIG_TYPES = { POOL: 'pool' } -// Pool configs that are pre-approved for use, served read-only via ENDPOINTS.POOLS_APPROVED_CONFIGS +// Pool configs that are pre-approved for use const APPROVED_POOL_URLS = [ { id: 'f2pool-btc-1', name: 'F2Pool', host: 'btc.f2pool.com', port: 1314 }, { id: 'ocean-btc-2', name: 'Ocean', host: 'mine.ocean.xyz', port: 3334 }, @@ -1234,7 +1234,7 @@ module.exports = { BTC_SATS, RANGE_BUCKETS, CONFIG_TYPES, - APPROVED_POOL_CONFIGS: APPROVED_POOL_URLS, + APPROVED_POOL_URLS, METRICS_TIME, METRICS_DEFAULTS, MINER_CATEGORIES, diff --git a/workers/lib/server/handlers/actions.handlers.js b/workers/lib/server/handlers/actions.handlers.js index 832207f..e8b40b1 100644 --- a/workers/lib/server/handlers/actions.handlers.js +++ b/workers/lib/server/handlers/actions.handlers.js @@ -1,7 +1,7 @@ 'use strict' const { parseJsonQueryParam } = require('../../utils') -const { ACTIONS_MAX_QUERIES, APPROVED_POOL_CONFIGS } = require('../../constants') +const { ACTIONS_MAX_QUERIES, APPROVED_POOL_URLS } = require('../../constants') async function queryActionsBatch (ctx, req) { const payload = { @@ -79,12 +79,18 @@ async function pushActionsBatch (ctx, req, rep) { const transformPushActionPayload = (payload) => { switch (payload.action) { - case 'REGISTER_CONFIG': { + case 'registerConfig': + case 'updateConfig': { + if (!payload || !Array.isArray(payload.params)) { + throw new Error('ERR_INVALID_PAYLOAD') + } + const [poolConfig] = payload.params if (!poolConfig) return payload - const { poolUrls } = poolConfig + const { poolUrls } = poolConfig.data ?? {} if (!poolUrls || !Array.isArray(poolUrls)) throw new Error('ERR_INVALID_POOL_URLS') + delete poolConfig.data.poolUrls const result = [] for (const poolUrlSetting of poolUrls) { @@ -96,13 +102,14 @@ const transformPushActionPayload = (payload) => { throw new Error('ERR_INVALID_POOL_URL_ID_MISSING') } - const poolUrl = APPROVED_POOL_CONFIGS.find(config => config.id === poolUrlId) + const poolUrl = APPROVED_POOL_URLS.find(config => config.id === poolUrlId) if (!poolUrl) { throw new Error('ERR_INVALID_POOL_URL_ID_INVALID') } const { host, port, name } = poolUrl result.push({ + poolUrlId, url: `stratum+tcp://${host}:${port}`, workerName, workerPassword, @@ -110,7 +117,7 @@ const transformPushActionPayload = (payload) => { }) } - poolConfig.poolUrls = result + poolConfig.data.poolUrls = result return payload } diff --git a/workers/lib/server/handlers/pools.handlers.js b/workers/lib/server/handlers/pools.handlers.js index 4ec2df0..0979a1e 100644 --- a/workers/lib/server/handlers/pools.handlers.js +++ b/workers/lib/server/handlers/pools.handlers.js @@ -7,7 +7,7 @@ const { MINERPOOL_EXT_DATA_KEYS, RANGE_BUCKETS, MINER_FIELD_MAP, - APPROVED_POOL_CONFIGS + APPROVED_POOL_URLS } = require('../../constants') const { parseJsonQueryParam, @@ -231,7 +231,7 @@ const getPoolStatsContainers = async (ctx, req) => { } async function getApprovedPoolUrls () { - return APPROVED_POOL_CONFIGS + return APPROVED_POOL_URLS } module.exports = { From 54fb780b6eb14f41fb7ce852fed4bbd7a91513dc Mon Sep 17 00:00:00 2001 From: Shrutesh Pachineela Date: Fri, 28 Aug 2026 09:29:10 +0530 Subject: [PATCH 4/5] remote urls and fetch them from ork global config --- tests/unit/handlers/actions.handlers.test.js | 134 ++++++++++++++++++ tests/unit/handlers/pools.handlers.test.js | 19 +-- tests/unit/routes/pools.routes.test.js | 13 +- workers/lib/constants.js | 1 - .../lib/server/handlers/actions.handlers.js | 17 ++- workers/lib/server/handlers/pools.handlers.js | 10 +- workers/lib/server/routes/pools.routes.js | 11 +- 7 files changed, 153 insertions(+), 52 deletions(-) diff --git a/tests/unit/handlers/actions.handlers.test.js b/tests/unit/handlers/actions.handlers.test.js index 9d48430..45e4681 100644 --- a/tests/unit/handlers/actions.handlers.test.js +++ b/tests/unit/handlers/actions.handlers.test.js @@ -275,6 +275,9 @@ for (const action of ['registerConfig', 'updateConfig']) { }, net_r0: { jRequest: async (key, method, payload, opts) => { + if (method === 'getGlobalConfig') { + return { approvedPoolUrls: APPROVED_POOL_URLS } + } capturedPayload = payload return { id: 'new-action', success: true } } @@ -329,6 +332,9 @@ for (const action of ['registerConfig', 'updateConfig']) { }, net_r0: { jRequest: async (key, method, payload, opts) => { + if (method === 'getGlobalConfig') { + return { approvedPoolUrls: APPROVED_POOL_URLS } + } capturedPayload = payload return { id: 'new-action', success: true } } @@ -367,6 +373,134 @@ for (const action of ['registerConfig', 'updateConfig']) { t.pass() }) + test(`pushAction - ${action} fetches approved pool urls from ork global config`, async (t) => { + let getGlobalConfigCalls = 0 + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: ['actions:write'] }) + }, + net_r0: { + jRequest: async (key, method, payload, opts) => { + if (method === 'getGlobalConfig') { + getGlobalConfigCalls++ + return { approvedPoolUrls: APPROVED_POOL_URLS } + } + return { id: 'new-action', success: true } + } + } + }) + + const approved = APPROVED_POOL_URLS[0] + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: [{ + name: 'my-config', + data: { poolUrls: [{ poolUrlId: approved.id, workerName: 'worker1', workerPassword: 'x' }] } + }] + } + } + + await pushAction(mockCtx, mockReq) + + t.ok(getGlobalConfigCalls > 0, 'should fetch global config from the orks instead of a static constant') + + t.pass() + }) + + test(`pushAction - ${action} throws for unknown poolUrlId when no ork reports approvedPoolUrls`, async (t) => { + const mockCtx = withDataProxy({ + conf: { orks: [{ rpcPublicKey: 'key1' }] }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: ['actions:write'] }) + }, + net_r0: { + jRequest: async (key, method, payload, opts) => { + if (method === 'getGlobalConfig') { + return {} + } + return { id: 'new-action', success: true } + } + } + }) + + const approved = APPROVED_POOL_URLS[0] + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: [{ + name: 'my-config', + data: { poolUrls: [{ poolUrlId: approved.id, workerName: 'worker1', workerPassword: 'x' }] } + }] + } + } + + try { + await pushAction(mockCtx, mockReq) + t.fail('should throw error when no ork reports approved pool urls') + } catch (err) { + t.is(err.message, 'ERR_INVALID_POOL_URL_ID_INVALID', 'should throw ERR_INVALID_POOL_URL_ID_INVALID') + } + + t.pass() + }) + + test(`pushAction - ${action} resolves poolUrls when only one ork reports approvedPoolUrls`, async (t) => { + let capturedPayload = null + const mockCtx = withDataProxy({ + conf: { + orks: [ + { rpcPublicKey: 'key1' }, + { rpcPublicKey: 'key2' } + ] + }, + authLib: { + getTokenPerms: async () => ({ write: true, permissions: ['actions:write'] }) + }, + net_r0: { + jRequest: async (key, method, payload, opts) => { + if (method === 'getGlobalConfig') { + return key === 'key1' ? { approvedPoolUrls: APPROVED_POOL_URLS } : {} + } + capturedPayload = payload + return { id: 'new-action', success: true } + } + } + }) + + const approved = APPROVED_POOL_URLS[0] + const mockReq = { + _info: { + authToken: 'token123', + user: { metadata: { email: 'test@example.com' } } + }, + body: { + action, + params: [{ + name: 'my-config', + data: { poolUrls: [{ poolUrlId: approved.id, workerName: 'worker1', workerPassword: 'x' }] } + }] + } + } + + await pushAction(mockCtx, mockReq) + + const [poolConfig] = capturedPayload.params + const [resolved] = poolConfig.data.poolUrls + t.is(resolved.url, `stratum+tcp://${approved.host}:${approved.port}`, 'should resolve using the ork that reported approvedPoolUrls') + + t.pass() + }) + test(`pushAction - ${action} throws for missing/invalid poolUrls`, async (t) => { const mockCtx = withDataProxy({ conf: { orks: [{ rpcPublicKey: 'key1' }] }, diff --git a/tests/unit/handlers/pools.handlers.test.js b/tests/unit/handlers/pools.handlers.test.js index 60a6d2e..bdcf819 100644 --- a/tests/unit/handlers/pools.handlers.test.js +++ b/tests/unit/handlers/pools.handlers.test.js @@ -1,7 +1,7 @@ 'use strict' const test = require('brittle') -const { RPC_METHODS, WORKER_TYPES, MINER_CATEGORIES, APPROVED_POOL_URLS } = require('../../../workers/lib/constants') +const { RPC_METHODS, WORKER_TYPES, MINER_CATEGORIES } = require('../../../workers/lib/constants') const { getPools, flattenPoolStats, @@ -10,8 +10,7 @@ const { flattenTransactionResults, groupByBucket, getPoolThingConfig, - getPoolStatsContainers, - getApprovedPoolUrls + getPoolStatsContainers } = require('../../../workers/lib/server/handlers/pools.handlers') const { withDataProxy } = require('../helpers/mockHelpers') @@ -550,17 +549,3 @@ test('getPoolStatsContainers - handles empty containers from RPC', async (t) => t.is(result.length, 0, 'should be empty when no containers') t.pass() }) - -test('getApprovedPoolUrls - returns the approved pool configs constant', async (t) => { - const result = await getApprovedPoolUrls() - - t.is(result, APPROVED_POOL_URLS, 'should return the APPROVED_POOL_URLS constant') - t.ok(Array.isArray(result), 'should return an array') - result.forEach((config) => { - t.ok(typeof config.id === 'string', 'each config should have an id') - t.ok(typeof config.name === 'string', 'each config should have a name') - t.ok(typeof config.host === 'string', 'each config should have a host') - t.ok(typeof config.port === 'number', 'each config should have a port') - }) - t.pass() -}) diff --git a/tests/unit/routes/pools.routes.test.js b/tests/unit/routes/pools.routes.test.js index d98b7ff..88e8d16 100644 --- a/tests/unit/routes/pools.routes.test.js +++ b/tests/unit/routes/pools.routes.test.js @@ -7,7 +7,6 @@ const { createRoutesForTest } = require('../helpers/mockHelpers') const ROUTES_PATH = '../../../workers/lib/server/routes/pools.routes.js' const POOLS_CONFIG_ROUTE_URL = '/auth/pools/config/:id' const POOLS_STATS_CONTAINERS_ROUTE_URL = '/auth/pools/stats/containers' -const POOLS_APPROVED_CONFIGS_ROUTE_URL = '/auth/pools/approved-urls' test('pools routes - module structure', (t) => { testModuleStructure(t, ROUTES_PATH, 'pools') @@ -21,7 +20,7 @@ test('pools routes - route definitions', (t) => { t.ok(routeUrls.includes('/auth/pools/:pool/balance-history'), 'should have balance-history route') t.ok(routeUrls.includes('/auth/pools/config/:id'), 'should have pools thing config route') t.ok(routeUrls.includes('/auth/pools/stats/containers'), 'should have pools stats containers route') - t.ok(routeUrls.includes('/auth/pools/approved-urls'), 'should have pools approved configs route') + t.absent(routeUrls.includes('/auth/pools/approved-urls'), 'should not have the removed pools approved-urls route') t.pass() }) @@ -64,13 +63,3 @@ test('pools routes - GET /auth/pools/stats/containers', (t) => { t.ok(typeof statsRoute.onRequest === 'function', 'pools stats containers route should have onRequest (auth)') t.pass() }) - -test('pools routes - GET /auth/pools/approved-configs', (t) => { - const routes = createRoutesForTest(ROUTES_PATH) - const approvedConfigsRoute = routes.find(r => r.url === POOLS_APPROVED_CONFIGS_ROUTE_URL) - t.ok(approvedConfigsRoute, 'should have pools approved configs route') - t.is(approvedConfigsRoute.method, 'GET', 'pools approved configs route should be GET') - t.ok(typeof approvedConfigsRoute.handler === 'function', 'pools approved configs route should have handler') - t.ok(typeof approvedConfigsRoute.onRequest === 'function', 'pools approved configs route should have onRequest (auth)') - t.pass() -}) diff --git a/workers/lib/constants.js b/workers/lib/constants.js index 4f684dc..f965e90 100644 --- a/workers/lib/constants.js +++ b/workers/lib/constants.js @@ -166,7 +166,6 @@ const ENDPOINTS = { POOLS_BALANCE_HISTORY: '/auth/pools/:pool/balance-history', POOLS_THING_CONFIG: '/auth/pools/config/:id', POOLS_CONTAINERS_STATS: '/auth/pools/stats/containers', - POOLS_APPROVED_URLS: '/auth/pools/approved-urls', SITE_STATUS_LIVE: '/auth/site/status/live', SITE_POWER_CONSUMPTION: '/auth/site/power-consumption', diff --git a/workers/lib/server/handlers/actions.handlers.js b/workers/lib/server/handlers/actions.handlers.js index 8be5079..91fd763 100644 --- a/workers/lib/server/handlers/actions.handlers.js +++ b/workers/lib/server/handlers/actions.handlers.js @@ -1,7 +1,7 @@ 'use strict' const { parseJsonQueryParam } = require('../../utils') -const { ACTIONS_MAX_QUERIES, APPROVED_POOL_URLS } = require('../../constants') +const { ACTIONS_MAX_QUERIES } = require('../../constants') const { detectPayloadFormat, peekFirstChunk, prependChunk } = require('../lib/payloadFormat') async function queryActionsBatch (ctx, req) { @@ -78,7 +78,7 @@ async function pushActionsBatch (ctx, req, rep) { }) } -const transformPushActionPayload = (payload) => { +const transformPushActionPayload = async (ctx, payload) => { switch (payload.action) { case 'registerConfig': case 'updateConfig': { @@ -103,7 +103,16 @@ const transformPushActionPayload = (payload) => { throw new Error('ERR_INVALID_POOL_URL_ID_MISSING') } - const poolUrl = APPROVED_POOL_URLS.find(config => config.id === poolUrlId) + let approvedPoolUrls = [] + const orkGlobalConfigResults = await ctx.dataProxy.requestDataMap('getGlobalConfig', {}) + for (const orkResult of orkGlobalConfigResults) { + if (!orkResult || typeof orkResult !== 'object') continue + if (orkResult.approvedPoolUrls) { + approvedPoolUrls = orkResult.approvedPoolUrls + } + } + + const poolUrl = approvedPoolUrls.find(config => config.id === poolUrlId) if (!poolUrl) { throw new Error('ERR_INVALID_POOL_URL_ID_INVALID') } @@ -141,7 +150,7 @@ async function pushAction (ctx, req) { authPerms: permissions } - const transformedPayload = transformPushActionPayload(structuredClone(payload)) + const transformedPayload = await transformPushActionPayload(ctx, structuredClone(payload)) return await ctx.dataProxy.requestData('pushAction', transformedPayload, (res, resultsArray) => { if (res.error) { diff --git a/workers/lib/server/handlers/pools.handlers.js b/workers/lib/server/handlers/pools.handlers.js index 0979a1e..0af8b32 100644 --- a/workers/lib/server/handlers/pools.handlers.js +++ b/workers/lib/server/handlers/pools.handlers.js @@ -6,8 +6,7 @@ const { WORKER_TYPES, MINERPOOL_EXT_DATA_KEYS, RANGE_BUCKETS, - MINER_FIELD_MAP, - APPROVED_POOL_URLS + MINER_FIELD_MAP } = require('../../constants') const { parseJsonQueryParam, @@ -230,10 +229,6 @@ const getPoolStatsContainers = async (ctx, req) => { }) } -async function getApprovedPoolUrls () { - return APPROVED_POOL_URLS -} - module.exports = { getPools, flattenPoolStats, @@ -242,6 +237,5 @@ module.exports = { flattenTransactionResults, groupByBucket, getPoolThingConfig, - getPoolStatsContainers, - getApprovedPoolUrls + getPoolStatsContainers } diff --git a/workers/lib/server/routes/pools.routes.js b/workers/lib/server/routes/pools.routes.js index f1d0fdd..ef3279d 100644 --- a/workers/lib/server/routes/pools.routes.js +++ b/workers/lib/server/routes/pools.routes.js @@ -8,8 +8,7 @@ const { getPools, getPoolBalanceHistory, getPoolThingConfig, - getPoolStatsContainers, - getApprovedPoolUrls + getPoolStatsContainers } = require('../handlers/pools.handlers') const { createCachedAuthRoute, createAuthRoute } = require('../lib/routeHelpers') @@ -69,14 +68,6 @@ module.exports = (ctx) => { ctx, getPoolStatsContainers ) - }, - { - method: HTTP_METHODS.GET, - url: ENDPOINTS.POOLS_APPROVED_URLS, - ...createAuthRoute( - ctx, - getApprovedPoolUrls - ) } ] } From 9559b2567c13c1a34f03afb378ffa4a44a1afb4c Mon Sep 17 00:00:00 2001 From: Shrutesh Pachineela Date: Fri, 28 Aug 2026 14:55:01 +0530 Subject: [PATCH 5/5] remove unused APPROVED_POOL_URLS --- tests/unit/routes/pools.routes.test.js | 1 - workers/lib/constants.js | 8 -------- 2 files changed, 9 deletions(-) diff --git a/tests/unit/routes/pools.routes.test.js b/tests/unit/routes/pools.routes.test.js index 88e8d16..f91a771 100644 --- a/tests/unit/routes/pools.routes.test.js +++ b/tests/unit/routes/pools.routes.test.js @@ -20,7 +20,6 @@ test('pools routes - route definitions', (t) => { t.ok(routeUrls.includes('/auth/pools/:pool/balance-history'), 'should have balance-history route') t.ok(routeUrls.includes('/auth/pools/config/:id'), 'should have pools thing config route') t.ok(routeUrls.includes('/auth/pools/stats/containers'), 'should have pools stats containers route') - t.absent(routeUrls.includes('/auth/pools/approved-urls'), 'should not have the removed pools approved-urls route') t.pass() }) diff --git a/workers/lib/constants.js b/workers/lib/constants.js index f965e90..4589352 100644 --- a/workers/lib/constants.js +++ b/workers/lib/constants.js @@ -873,13 +873,6 @@ const CONFIG_TYPES = { POOL: 'pool' } -// Pool configs that are pre-approved for use -const APPROVED_POOL_URLS = [ - { id: 'f2pool-btc-1', name: 'F2Pool', host: 'btc.f2pool.com', port: 1314 }, - { id: 'ocean-btc-2', name: 'Ocean', host: 'mine.ocean.xyz', port: 3334 }, - { id: 'antpool-btc-3', name: 'Antpool', host: 'ss.antpool.com', port: 3333 } -] - const MINER_FIELD_MAP = { status: 'last.snap.stats.status', hashrate: 'last.snap.stats.hashrate_mhs', @@ -1233,7 +1226,6 @@ module.exports = { BTC_SATS, RANGE_BUCKETS, CONFIG_TYPES, - APPROVED_POOL_URLS, METRICS_TIME, METRICS_DEFAULTS, MINER_CATEGORIES,