Verified incoming transfers against Monero Wallet PRC
This commit is contained in:
@@ -2,7 +2,7 @@ import { Pool } from 'pg';
|
||||
import { open, unlink, mkdir } from 'node:fs/promises';
|
||||
import { resolve, join } from 'node:path';
|
||||
import { postgresConfig } from '../server/market/postgres-config.ts';
|
||||
import { migrateCustody, grantCustodyRuntime } from '../server/market/postgres-schema.ts';
|
||||
import { migrateCustody, grantCustodyRuntime, grantMoneroDepositCreditRuntime } from '../server/market/postgres-schema.ts';
|
||||
import { importSqliteCustody } from '../server/market/custody-import.ts';
|
||||
import { PostgresCustody } from '../server/market/postgres-custody.ts';
|
||||
import { IS_MAINNET } from '../shared/network.ts';
|
||||
@@ -11,7 +11,7 @@ import { resolveCustodyIncident } from '../server/market/custody-incident.ts';
|
||||
// Explicit operator task, never invoked by node startup. No passwords or database URLs printed.
|
||||
if (IS_MAINNET) throw new Error('Only sandbox financial schema operations are implemented');
|
||||
const [action, argument] = process.argv.slice(2);
|
||||
if (!['migrate', 'grant-runtime', 'import-sqlite', 'audit', 'resolve-integrity'].includes(action)) throw new Error('Expected migrate, grant-runtime ROLE, import-sqlite DATA_DIR, audit, or resolve-integrity EVIDENCE_UUID');
|
||||
if (!['migrate', 'grant-runtime', 'grant-monero-credit-runtime', 'import-sqlite', 'audit', 'resolve-integrity'].includes(action)) throw new Error('Expected migrate, grant-runtime ROLE, grant-monero-credit-runtime ROLE, import-sqlite DATA_DIR, audit, or resolve-integrity EVIDENCE_UUID');
|
||||
try {
|
||||
const config = await postgresConfig();
|
||||
if (action === 'audit') {
|
||||
@@ -22,6 +22,10 @@ try {
|
||||
try {
|
||||
if (action === 'migrate') { await migrateCustody(pool); console.log('Custody PostgreSQL schema verified/migrated. No funds activated.'); }
|
||||
if (action === 'grant-runtime') { await grantCustodyRuntime(pool, argument ?? ''); console.log('Runtime grants applied; no credentials printed.'); }
|
||||
if (action === 'grant-monero-credit-runtime') {
|
||||
await grantMoneroDepositCreditRuntime(pool, argument ?? '');
|
||||
console.log('Separate reviewed pending-credit coordinator grants applied; no credentials printed and no live deposits enabled.');
|
||||
}
|
||||
if (action === 'resolve-integrity') console.log(JSON.stringify(await resolveCustodyIncident(pool, argument ?? '')));
|
||||
if (action === 'import-sqlite') {
|
||||
if (!argument) throw new Error('Stopped source node DATA_DIR required');
|
||||
|
||||
@@ -67,7 +67,7 @@ function startWallet(walletFile) {
|
||||
walletRpc = spawn(binary,args,{shell:false,windowsHide:true,stdio:'ignore',cwd:temp}); walletRpc.on('error',()=>{});
|
||||
}
|
||||
async function waitWallet() {
|
||||
const deadline=Date.now()+15_000;
|
||||
const deadline=Date.now()+60_000;
|
||||
while(Date.now()<deadline) {
|
||||
if(walletRpc.exitCode!==null) throw new Error(`Wallet RPC exited during startup (${walletRpc.exitCode})`);
|
||||
try { await rawRpc('get_version'); return; } catch { await new Promise(r=>setTimeout(r,100)); }
|
||||
@@ -128,9 +128,11 @@ try {
|
||||
console.log('stage: authorized mTLS subaddress creation');
|
||||
assert.equal(sub.status,200); assert.equal(sub.data.result.address_index,1);
|
||||
const outputs=await gatewayCall(validTransport,'incoming_transfers',{transfer_type:'all',account_index:0,subaddr_indices:[1]});
|
||||
assert.equal(outputs.status,200); assert.deepEqual(outputs.data.result.transfers,[],
|
||||
assert.equal(outputs.status,200);
|
||||
assert.equal(outputs.data.error,undefined,'incoming_transfers must be a supported JSON-RPC method');
|
||||
assert.deepEqual(outputs.data.result?.transfers ?? [],[],
|
||||
'a fresh watch-only Stagenet address must return an empty owned-output list');
|
||||
console.log('stage: authorized bounded incoming-output read');
|
||||
console.log('stage: incoming_transfers allowed; pinned RPC omits default empty result fields');
|
||||
const forbiddenStatus=await rawGatewayStatus('transfer',{destinations:[]},certs['client.crt'],certs['client.key'],certs['ca.crt']);
|
||||
assert.equal(forbiddenStatus,400,'gateway must reject transfer before Wallet RPC');
|
||||
console.log('stage: forbidden transfer blocked');
|
||||
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
const containerArgs = ['create', '--network', 'none', '--entrypoint', 'node', 'wikideck-monero-mtls-test',
|
||||
'--experimental-strip-types', 'scripts/monero-mtls-stagenet-rehearsal.mjs', '/opt/monero/monero-wallet-rpc'];
|
||||
const containerId = execFileSync('docker', containerArgs, { encoding: 'utf8' }).trim();
|
||||
if (!/^[a-f0-9]{64}$/.test(containerId)) throw new Error('Docker returned an invalid rehearsal container identity');
|
||||
|
||||
let exitCode = 1;
|
||||
try {
|
||||
execFileSync('docker', ['cp', 'Z:\\Repositories\\wikideck\\server', `${containerId}:/app/`], { stdio: 'inherit' });
|
||||
execFileSync('docker', ['cp', 'Z:\\Repositories\\wikideck\\shared', `${containerId}:/app/`], { stdio: 'inherit' });
|
||||
execFileSync('docker', ['cp', 'Z:\\Repositories\\wikideck\\scripts\\monero-mtls-stagenet-rehearsal.mjs',
|
||||
`${containerId}:/app/scripts/monero-mtls-stagenet-rehearsal.mjs`], { stdio: 'inherit' });
|
||||
try {
|
||||
execFileSync('docker', ['start', '--attach', containerId], { stdio: 'inherit' });
|
||||
exitCode = 0;
|
||||
} catch (error) {
|
||||
exitCode = typeof error.status === 'number' ? error.status : 1;
|
||||
}
|
||||
} finally {
|
||||
execFileSync('docker', ['rm', '--force', containerId], { stdio: 'ignore' });
|
||||
}
|
||||
process.exitCode = exitCode;
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$containerId = & docker create --network none --entrypoint node wikideck-monero-mtls-test `
|
||||
--experimental-strip-types scripts/monero-mtls-stagenet-rehearsal.mjs /opt/monero/monero-wallet-rpc
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
$exitCode = 1
|
||||
try {
|
||||
& docker cp 'Z:\Repositories\wikideck\server' "${containerId}:/app/"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Copying current server source into the isolated rehearsal container failed.' }
|
||||
& docker cp 'Z:\Repositories\wikideck\shared' "${containerId}:/app/"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Copying current shared source into the isolated rehearsal container failed.' }
|
||||
& docker cp 'Z:\Repositories\wikideck\scripts\monero-mtls-stagenet-rehearsal.mjs' `
|
||||
"${containerId}:/app/scripts/monero-mtls-stagenet-rehearsal.mjs"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Copying the current rehearsal script failed.' }
|
||||
& docker start --attach $containerId
|
||||
$exitCode = $LASTEXITCODE
|
||||
} finally {
|
||||
& docker rm --force $containerId | Out-Null
|
||||
}
|
||||
exit $exitCode
|
||||
Reference in New Issue
Block a user