Remove the last layer of soft centralization: a lone `--master` on empty storage could still implicitly genesis a separate Autopass and split the network on the same TOPIC_SEED. Genesis is now explicit (`--master --genesis`); secondary masters pair via invite into the same dnsPass as joiners, with a shared network manifest, split-brain diagnostics, and writer-aware quorum. - Genesis vs secondary master boot paths; auto-adopt manifest on upgrade - Masters without dnsPass accept invites; masters with pass ignore them - NETWORK_MANIFEST_FILE, P2NS_GENESIS, MASTER_LOAD_DOMAINS, MASTER_INVITE_ONLY - core.status networkId; admin diagnostics; docs and multi-master tests BREAKING: operators must run one genesis per network; additional masters use `node p2ns.js --master` (not `--genesis`) on empty storage.
235 lines
8.1 KiB
JavaScript
235 lines
8.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Unit tests for native core swarm RPC handlers.
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
const { createCoreSwarmHandlers } = require('../includes/core/core-swarm-handlers');
|
|
const { METHODS, INVITE_STATUS } = require('../includes/core/core-rpc-contract');
|
|
|
|
function testMasterPendingPassAcceptsInvite() {
|
|
let pairedPass = null;
|
|
const shared = { base: { key: Buffer.alloc(32, 1), writable: true, update: async () => {} }, ready: async () => {} };
|
|
const localState = { consecutiveInviteFailures: 0, masterPendingPass: true };
|
|
|
|
const handlers = createCoreSwarmHandlers({
|
|
Autopass: {
|
|
pair: () => ({ finished: async () => shared })
|
|
},
|
|
store: {},
|
|
core: { writable: true, update: async () => {} },
|
|
state: localState,
|
|
isMaster: true,
|
|
getDnsPass: () => pairedPass,
|
|
setDnsPass: (p) => { pairedPass = p; },
|
|
connectedPeers: new Set(['genesis']),
|
|
peerChannels: new Map(),
|
|
failedInvitePeers: new Set(),
|
|
pendingInviteAcks: new Map(),
|
|
pendingInviteRequestHandlers: new Set(),
|
|
isProcessingInvite: () => false,
|
|
acquireInviteLock: async () => {},
|
|
releaseInviteLock: () => {},
|
|
withTimeout: (p) => p,
|
|
getCurrentInvitePromise: () => null,
|
|
setCurrentInvitePromise: () => {},
|
|
getCurrentPairOperation: () => null,
|
|
setCurrentPairOperation: () => {},
|
|
setupDomainsWatcher: () => {},
|
|
shouldLoadDomains: () => false,
|
|
listDomains: async () => [],
|
|
doAutoVotes: () => {},
|
|
setupListeners: () => {},
|
|
coreRpc: { inviteAck: () => true },
|
|
channelManager: { getChannelInfo: () => null, waitForBidirectionalOpen: async () => true },
|
|
CORE_DOMAIN: 'p2ns.core'
|
|
});
|
|
|
|
return handlers.processInviteWire('genesis', 'wire').then(() => {
|
|
assert.strictEqual(pairedPass, shared);
|
|
assert.strictEqual(localState.masterPendingPass, false);
|
|
});
|
|
}
|
|
|
|
function testMasterWithPassIgnoresInvite() {
|
|
let setCalled = false;
|
|
const handlers = createCoreSwarmHandlers({
|
|
Autopass: { pair: () => { throw new Error('should not pair'); } },
|
|
store: {},
|
|
core: { writable: true, update: async () => {} },
|
|
state: { consecutiveInviteFailures: 0 },
|
|
isMaster: true,
|
|
getDnsPass: () => ({}),
|
|
setDnsPass: () => { setCalled = true; },
|
|
connectedPeers: new Set(),
|
|
peerChannels: new Map(),
|
|
failedInvitePeers: new Set(),
|
|
pendingInviteAcks: new Map(),
|
|
pendingInviteRequestHandlers: new Set(),
|
|
isProcessingInvite: () => false,
|
|
acquireInviteLock: async () => {},
|
|
releaseInviteLock: () => {},
|
|
withTimeout: (p) => p,
|
|
getCurrentInvitePromise: () => null,
|
|
setCurrentInvitePromise: () => {},
|
|
getCurrentPairOperation: () => null,
|
|
setCurrentPairOperation: () => {},
|
|
setupDomainsWatcher: () => {},
|
|
shouldLoadDomains: () => false,
|
|
listDomains: async () => [],
|
|
doAutoVotes: () => {},
|
|
setupListeners: () => {},
|
|
coreRpc: { inviteAck: () => true },
|
|
channelManager: { getChannelInfo: () => null, waitForBidirectionalOpen: async () => true },
|
|
CORE_DOMAIN: 'p2ns.core'
|
|
});
|
|
|
|
return handlers.processInviteWire('peer1', 'wire').then(() => {
|
|
assert.strictEqual(setCalled, false);
|
|
});
|
|
}
|
|
|
|
function testInviteAckClearsPending() {
|
|
const pendingInviteAcks = new Map();
|
|
pendingInviteAcks.set('peer1', { timeout: null, retryCount: 1, inviteId: 'abc123' });
|
|
|
|
const handlers = createCoreSwarmHandlers({
|
|
Autopass: {},
|
|
store: {},
|
|
core: { writable: true, update: async () => {} },
|
|
state: { consecutiveInviteFailures: 0 },
|
|
isMaster: true,
|
|
getDnsPass: () => ({}),
|
|
setDnsPass: () => {},
|
|
connectedPeers: new Set(['peer1']),
|
|
peerChannels: new Map(),
|
|
failedInvitePeers: new Set(),
|
|
pendingInviteAcks,
|
|
pendingInviteRequestHandlers: new Set(),
|
|
isProcessingInvite: () => false,
|
|
acquireInviteLock: async () => {},
|
|
releaseInviteLock: () => {},
|
|
withTimeout: (p) => p,
|
|
getCurrentInvitePromise: () => null,
|
|
setCurrentInvitePromise: () => {},
|
|
getCurrentPairOperation: () => null,
|
|
setCurrentPairOperation: () => {},
|
|
setupDomainsWatcher: () => {},
|
|
listDomains: async () => [],
|
|
doAutoVotes: () => {},
|
|
setupListeners: () => {},
|
|
coreRpc: { inviteAck: () => true },
|
|
channelManager: { getChannelInfo: () => null, waitForBidirectionalOpen: async () => true },
|
|
CORE_DOMAIN: 'p2ns.core'
|
|
});
|
|
|
|
handlers.onInviteAck({ inviteId: 'abc123' }, { peerId: 'peer1' });
|
|
assert.strictEqual(pendingInviteAcks.has('peer1'), false);
|
|
}
|
|
|
|
function testInviteAckIgnoresStaleId() {
|
|
const pendingInviteAcks = new Map();
|
|
pendingInviteAcks.set('peer1', { inviteId: 'expected' });
|
|
|
|
const handlers = createCoreSwarmHandlers({
|
|
Autopass: {},
|
|
store: {},
|
|
core: { writable: true, update: async () => {} },
|
|
state: { consecutiveInviteFailures: 0 },
|
|
isMaster: true,
|
|
getDnsPass: () => ({}),
|
|
setDnsPass: () => {},
|
|
connectedPeers: new Set(['peer1']),
|
|
peerChannels: new Map(),
|
|
failedInvitePeers: new Set(),
|
|
pendingInviteAcks,
|
|
pendingInviteRequestHandlers: new Set(),
|
|
isProcessingInvite: () => false,
|
|
acquireInviteLock: async () => {},
|
|
releaseInviteLock: () => {},
|
|
withTimeout: (p) => p,
|
|
getCurrentInvitePromise: () => null,
|
|
setCurrentInvitePromise: () => {},
|
|
getCurrentPairOperation: () => null,
|
|
setCurrentPairOperation: () => {},
|
|
setupDomainsWatcher: () => {},
|
|
listDomains: async () => [],
|
|
doAutoVotes: () => {},
|
|
setupListeners: () => {},
|
|
coreRpc: { inviteAck: () => true },
|
|
channelManager: { getChannelInfo: () => null, waitForBidirectionalOpen: async () => true },
|
|
CORE_DOMAIN: 'p2ns.core'
|
|
});
|
|
|
|
handlers.onInviteAck({ inviteId: 'wrong' }, { peerId: 'peer1' });
|
|
assert.strictEqual(pendingInviteAcks.has('peer1'), true);
|
|
}
|
|
|
|
function testNoLegacyAdapterInP2ns() {
|
|
const fs = require('fs');
|
|
const src = fs.readFileSync(require('path').join(__dirname, '../p2ns.js'), 'utf8');
|
|
assert.ok(!src.includes('processCoreRequestMessage'), 'p2ns.js must not contain processCoreRequestMessage');
|
|
assert.ok(!src.includes("registerPluginChannel(CORE_DOMAIN, 'invite'"), 'invite channel registration removed');
|
|
assert.ok(!src.includes('registerPluginChannel'), 'registerPluginChannel removed — use registerPluginProtocol');
|
|
}
|
|
|
|
function testMethodsIncludeDeliver() {
|
|
assert.strictEqual(METHODS.INVITE_DELIVER, 'invite.deliver');
|
|
assert.strictEqual(METHODS.CORE_STATUS, 'core.status');
|
|
assert.ok(INVITE_STATUS.OK);
|
|
}
|
|
|
|
function testCoreStatusMaster() {
|
|
const handlers = createCoreSwarmHandlers({
|
|
Autopass: {},
|
|
store: {},
|
|
core: { writable: true, update: async () => {} },
|
|
state: { consecutiveInviteFailures: 0, pendingInviteRequests: new Map() },
|
|
isMaster: true,
|
|
getDnsPass: () => ({}),
|
|
setDnsPass: () => {},
|
|
connectedPeers: new Set(['peer1', 'peer2']),
|
|
peerChannels: new Map(),
|
|
failedInvitePeers: new Set(),
|
|
pendingInviteAcks: new Map(),
|
|
pendingInviteRequestHandlers: new Set(),
|
|
isProcessingInvite: () => false,
|
|
acquireInviteLock: async () => {},
|
|
releaseInviteLock: () => {},
|
|
withTimeout: (p) => p,
|
|
getCurrentInvitePromise: () => null,
|
|
setCurrentInvitePromise: () => {},
|
|
getCurrentPairOperation: () => null,
|
|
setCurrentPairOperation: () => {},
|
|
setupDomainsWatcher: () => {},
|
|
listDomains: async () => [],
|
|
doAutoVotes: () => {},
|
|
setupListeners: () => {},
|
|
coreRpc: {},
|
|
channelManager: { getChannelInfo: () => null, waitForBidirectionalOpen: async () => true },
|
|
CORE_DOMAIN: 'p2ns.core'
|
|
});
|
|
|
|
return handlers.onCoreStatus().then((status) => {
|
|
assert.strictEqual(status.nodeType, 'master');
|
|
assert.strictEqual(status.dnsPassInitialized, true);
|
|
assert.strictEqual(status.canProvideInvite, true);
|
|
assert.strictEqual(status.connectedPeers, 2);
|
|
assert.ok('networkId' in status);
|
|
assert.ok('masterPendingPass' in status);
|
|
});
|
|
}
|
|
|
|
testMasterPendingPassAcceptsInvite();
|
|
testMasterWithPassIgnoresInvite();
|
|
testInviteAckClearsPending();
|
|
testInviteAckIgnoresStaleId();
|
|
testNoLegacyAdapterInP2ns();
|
|
testMethodsIncludeDeliver();
|
|
testCoreStatusMaster().then(() => {
|
|
console.log('core-swarm-handlers.test: ok');
|
|
}).catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|