Skip to content

Commit af9b167

Browse files
committed
fix: correct DataLayer wallet type constant from 14 to 11
getDLWalletId() was searching for WalletType 14 but chia-blockchain defines DATA_LAYER as 11 (and has since 2022). This meant the entire transaction health and recovery system — rejected tx detection, auto-clearing, DL unconfirmed tx gating — was silently disabled on real nodes. The original integration test was tautological: it stubbed type 14 in the mock response then asserted CADT found type 14. It also ran under USE_SIMULATOR=true which short-circuits getDLWalletId() to return '2' without ever hitting the RPC path. - Extract findDLWalletInResponse() pure function with named constant CHIA_WALLET_TYPE_DATA_LAYER = 11, testable without simulator bypass - Add __test_resetDLWalletCache() to prevent cross-test cache leakage - Replace tautological test with findDLWalletInResponse unit tests that validate the constant value and reject adjacent type values - Add live-api test (wallet-health.live.spec.js) that calls the real Chia wallet RPC get_wallets and verifies CADT's constant matches - Run wallet-health live test in both v1 and v2 CI jobs as an early sanity check before the longer org-creation tests
1 parent a94ef07 commit af9b167

5 files changed

Lines changed: 194 additions & 16 deletions

File tree

.github/workflows/tests.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,10 @@ jobs:
406406
env:
407407
TEST_API_HOST: 127.0.0.1
408408
run: |
409+
echo "########################################################"
410+
echo "Running wallet health sanity check"
411+
echo "########################################################"
412+
npm run test:v2:live:wallet-health
409413
echo "########################################################"
410414
echo "Running organization creation tests"
411415
echo "########################################################"
@@ -774,6 +778,10 @@ jobs:
774778
env:
775779
TEST_API_HOST: 127.0.0.1
776780
run: |
781+
echo "########################################################"
782+
echo "Running wallet health sanity check"
783+
echo "########################################################"
784+
npm run test:v2:live:wallet-health
777785
echo "########################################################"
778786
echo "Running V1 organization creation tests"
779787
echo "########################################################"

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"test:v2:live:data:short": "node --import=extensionless/register tests/v2/live-api/data-short.js",
2626
"test:v2:live:cascade-delete": "npx cross-env NODE_ENV=production mocha --loader node_modules/extensionless/src/register.js 'tests/v2/live-api/cascade-delete.live.spec.js' --reporter spec --exit --timeout 1800000",
2727
"test:live:readonly:smoke": "npx cross-env NODE_ENV=production mocha --loader node_modules/extensionless/src/register.js 'tests/v2/live-api/read-only-smoke.live.spec.js' --reporter spec --exit --timeout 600000",
28+
"test:v2:live:wallet-health": "npx cross-env NODE_ENV=production mocha --loader node_modules/extensionless/src/register.js 'tests/v2/live-api/wallet-health.live.spec.js' --reporter spec --exit --timeout 60000",
2829
"test:v2:live:organization:delete": "npx cross-env NODE_ENV=production mocha --loader node_modules/extensionless/src/register.js 'tests/v2/live-api/organization/organization-delete.live.spec.js' --reporter spec --exit --timeout 600000",
2930
"test:locks": "node --import=extensionless/register tests/helpers/show-test-locks.js",
3031
"test:v1:live:data:short": "node --import=extensionless/register tests/v1/live-api/data-short.js",

src/datalayer/wallet.js

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,25 @@ const MempoolInclusionStatus = {
582582
// Cached DL wallet ID (discovered once, reused thereafter)
583583
let cachedDLWalletId = null;
584584

585+
// chia-blockchain WalletType enum value for DATA_LAYER (see wallet_types.py)
586+
const CHIA_WALLET_TYPE_DATA_LAYER = 11;
587+
588+
/**
589+
* Extract the DataLayer wallet_id from a get_wallets RPC response.
590+
* Pure function — no I/O, no caching, no simulator awareness.
591+
* @param {Object} data - Parsed response body from get_wallets
592+
* @returns {string|null} wallet_id as a string, or null if not found
593+
*/
594+
const findDLWalletInResponse = (data) => {
595+
if (data.success && Array.isArray(data.wallets)) {
596+
const dlWallet = data.wallets.find((w) => w.type === CHIA_WALLET_TYPE_DATA_LAYER);
597+
if (dlWallet) {
598+
return String(dlWallet.id);
599+
}
600+
}
601+
return null;
602+
};
603+
585604
/**
586605
* Discover the DataLayer wallet's wallet_id dynamically via get_wallets RPC.
587606
* Caches the result so subsequent calls avoid the RPC round-trip.
@@ -608,15 +627,12 @@ const getDLWalletId = async () => {
608627
.timeout(timeout);
609628

610629
const data = response.body || JSON.parse(response.text);
630+
const walletId = findDLWalletInResponse(data);
611631

612-
if (data.success && Array.isArray(data.wallets)) {
613-
// WalletType.DATA_LAYER = 14 in chia-blockchain
614-
const dlWallet = data.wallets.find((w) => w.type === 14);
615-
if (dlWallet) {
616-
cachedDLWalletId = String(dlWallet.id);
617-
logger.info(`Discovered DataLayer wallet_id: ${cachedDLWalletId}`);
618-
return cachedDLWalletId;
619-
}
632+
if (walletId) {
633+
cachedDLWalletId = walletId;
634+
logger.info(`Discovered DataLayer wallet_id: ${cachedDLWalletId}`);
635+
return cachedDLWalletId;
620636
}
621637

622638
logger.warn('DataLayer wallet not found via get_wallets RPC');
@@ -853,6 +869,10 @@ const TRANSIENT_WALLET_ERRORS = [
853869
const isTransientWalletError = (error) =>
854870
TRANSIENT_WALLET_ERRORS.some((msg) => error.message?.includes(msg));
855871

872+
const __test_resetDLWalletCache = () => {
873+
cachedDLWalletId = null;
874+
};
875+
856876
export default {
857877
hasUnconfirmedTransactions,
858878
hasAnyUnconfirmedTransactions,
@@ -873,4 +893,7 @@ export default {
873893
getDLWalletId,
874894
clearRejectedTransactions,
875895
formatDuration,
896+
findDLWalletInResponse,
897+
CHIA_WALLET_TYPE_DATA_LAYER,
898+
__test_resetDLWalletCache,
876899
};

tests/v2/integration/transaction-health.spec.js

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('Transaction Health Monitoring', function () {
2626
let fsReadFileSyncStub;
2727

2828
beforeEach(function () {
29+
wallet.__test_resetDLWalletCache();
2930
fsReadFileSyncStub = sinon.stub(fs, 'readFileSync').returns(Buffer.from('fake-cert'));
3031
superagentPostStub = sinon.stub(superagent, 'post');
3132
});
@@ -340,20 +341,64 @@ describe('Transaction Health Monitoring', function () {
340341
});
341342

342343
describe('getDLWalletId', function () {
343-
it('should discover DL wallet by type 14', async function () {
344-
superagentPostStub.returns(createSuperagentMock({
344+
it('should return "2" in simulator mode', async function () {
345+
const walletId = await wallet.getDLWalletId();
346+
expect(walletId).to.equal('2');
347+
});
348+
});
349+
350+
describe('findDLWalletInResponse', function () {
351+
it('should match type 11 (WalletType.DATA_LAYER from chia-blockchain)', function () {
352+
const result = wallet.findDLWalletInResponse({
345353
success: true,
346354
wallets: [
347355
{ id: 1, type: 0, name: 'Chia Wallet' },
348-
{ id: 2, type: 14, name: 'DataLayer Wallet' },
356+
{ id: 2, type: 11, name: 'DataLayer Wallet' },
349357
],
350-
}));
358+
});
359+
expect(result).to.equal('2');
360+
});
351361

352-
// Reset cached value
353-
wallet.__test_resetDLWalletCache?.();
362+
it('should use the CHIA_WALLET_TYPE_DATA_LAYER constant equal to 11', function () {
363+
expect(wallet.CHIA_WALLET_TYPE_DATA_LAYER).to.equal(11);
364+
});
354365

355-
const walletId = await wallet.getDLWalletId();
356-
expect(walletId).to.equal('2');
366+
it('should return null when no wallet has DATA_LAYER type', function () {
367+
const result = wallet.findDLWalletInResponse({
368+
success: true,
369+
wallets: [
370+
{ id: 1, type: 0, name: 'Chia Wallet' },
371+
{ id: 3, type: 6, name: 'CAT Wallet' },
372+
{ id: 4, type: 10, name: 'NFT Wallet' },
373+
],
374+
});
375+
expect(result).to.be.null;
376+
});
377+
378+
it('should not match adjacent WalletType values (type 10, 12, 13)', function () {
379+
const result = wallet.findDLWalletInResponse({
380+
success: true,
381+
wallets: [
382+
{ id: 1, type: 0, name: 'Chia Wallet' },
383+
{ id: 2, type: 10, name: 'NFT Wallet' },
384+
{ id: 3, type: 12, name: 'DataLayer Offer Wallet' },
385+
{ id: 4, type: 13, name: 'VC Wallet' },
386+
],
387+
});
388+
expect(result).to.be.null;
389+
});
390+
391+
it('should return null on unsuccessful response', function () {
392+
const result = wallet.findDLWalletInResponse({
393+
success: false,
394+
wallets: [{ id: 2, type: 11, name: 'DataLayer Wallet' }],
395+
});
396+
expect(result).to.be.null;
397+
});
398+
399+
it('should return null when wallets array is missing', function () {
400+
const result = wallet.findDLWalletInResponse({ success: true });
401+
expect(result).to.be.null;
357402
});
358403
});
359404

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { expect } from 'chai';
2+
import fs from 'fs';
3+
import path from 'path';
4+
import superagent from 'superagent';
5+
import { getLiveApiRequest, getLiveApiConfig } from './helpers/live-api-helpers.js';
6+
import { getChiaRoot } from '../../../src/utils/chia-root.js';
7+
import wallet from '../../../src/datalayer/wallet.js';
8+
9+
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;
10+
11+
const getWalletRpcUrl = () => {
12+
const { config } = getLiveApiConfig();
13+
return config?.APP?.WALLET_URL || 'https://localhost:9256';
14+
};
15+
16+
const getWalletBaseOptions = () => {
17+
const chiaRoot = getChiaRoot();
18+
const certPath = path.resolve(`${chiaRoot}/config/ssl/wallet/private_wallet.crt`);
19+
const keyPath = path.resolve(`${chiaRoot}/config/ssl/wallet/private_wallet.key`);
20+
return {
21+
cert: fs.readFileSync(certPath),
22+
key: fs.readFileSync(keyPath),
23+
timeout: 30000,
24+
};
25+
};
26+
27+
describe('Wallet Health - Live', function () {
28+
this.timeout(60000);
29+
30+
let request;
31+
32+
before(async function () {
33+
request = await getLiveApiRequest({ apiVersion: 'any' });
34+
});
35+
36+
describe('CADT /health/wallet endpoint', function () {
37+
it('should report DataLayer wallet as available', async function () {
38+
// Try v2 first, fall back to v1
39+
let res = await request.get('/v2/health/wallet');
40+
if (res.status === 404) {
41+
res = await request.get('/v1/health/wallet');
42+
}
43+
expect(res.status).to.equal(200);
44+
45+
if (res.body.readOnly) {
46+
this.skip();
47+
return;
48+
}
49+
50+
expect(res.body.dataLayerWallet, 'dataLayerWallet missing from response').to.exist;
51+
expect(
52+
res.body.dataLayerWallet.available,
53+
'CADT failed to discover the DataLayer wallet. ' +
54+
'This likely means the WalletType constant in getDLWalletId() ' +
55+
'does not match chia-blockchain\'s WalletType.DATA_LAYER enum.',
56+
).to.equal(true);
57+
expect(res.body.dataLayerWallet.walletId).to.be.a('number').and.to.be.greaterThan(0);
58+
});
59+
});
60+
61+
describe('Chia wallet RPC get_wallets', function () {
62+
it('should contain a DATA_LAYER wallet matching CADT constant', async function () {
63+
const rpcUrl = getWalletRpcUrl();
64+
const { cert, key, timeout } = getWalletBaseOptions();
65+
66+
let data;
67+
try {
68+
const response = await superagent
69+
.post(`${rpcUrl}/get_wallets`)
70+
.send({})
71+
.key(key)
72+
.cert(cert)
73+
.timeout(timeout);
74+
data = response.body || JSON.parse(response.text);
75+
} catch (error) {
76+
console.log(` Wallet RPC at ${rpcUrl} unreachable: ${error.message}`);
77+
this.skip();
78+
return;
79+
}
80+
81+
expect(data.success, 'get_wallets RPC failed').to.be.true;
82+
83+
const dlWallet = data.wallets.find(
84+
(w) => w.type === wallet.CHIA_WALLET_TYPE_DATA_LAYER,
85+
);
86+
expect(
87+
dlWallet,
88+
`No wallet with type=${wallet.CHIA_WALLET_TYPE_DATA_LAYER} (DATA_LAYER) ` +
89+
`found in get_wallets response. Types present: ` +
90+
`[${data.wallets.map((w) => `${w.type} (${w.name})`).join(', ')}]. ` +
91+
`If chia-blockchain changed the WalletType enum, update ` +
92+
`CHIA_WALLET_TYPE_DATA_LAYER in src/datalayer/wallet.js.`,
93+
).to.exist;
94+
95+
const parsedId = wallet.findDLWalletInResponse(data);
96+
expect(parsedId, 'findDLWalletInResponse should return the DL wallet id').to.equal(
97+
String(dlWallet.id),
98+
);
99+
});
100+
});
101+
});

0 commit comments

Comments
 (0)