Cut over domain resolution to an Autobase apply-based sidecar fed by claim/vote dual-writes from dnsPass. Remove the legacy full-KV scan getConsensusState path and wire all reads through consensus-view. - Add consensus-resolver, consensus-events, consensus-apply, consensus-autobase, and consensus-view modules - Dual-append consensus events on claim/vote dnsPassAdd/Remove - Bootstrap sidecar from existing KV entries; persist consensusAutobaseKey in network manifest - Expose sidecar health via GET /api/consensus/status and metrics - Add consensus-resolver and consensus-apply unit tests - Expand RFC 0001 to Implemented; update CONSENSUS.md Rollback: deploy prior release; KV data unchanged, sidecar rebuilds on next startup.
184 lines
5.4 KiB
JavaScript
184 lines
5.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Unit tests for consensus-resolver.js
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
const {
|
|
parseClaimValue,
|
|
resolveDomainConsensus,
|
|
applyTieBreaker,
|
|
LEGACY_CLAIM_TIMESTAMP
|
|
} = require('../includes/core/consensus-resolver');
|
|
|
|
const DEFAULT_CONFIG = {
|
|
CONSENSUS_QUORUM_THRESHOLD: 0.5,
|
|
CONSENSUS_MIN_VOTES: 2,
|
|
CONSENSUS_TIE_BREAKER: 'timestamp',
|
|
CONSENSUS_VOTE_VALIDATION: true
|
|
};
|
|
|
|
const LOCAL = 'local-peer-key';
|
|
const REMOTE_A = 'remote-a-key';
|
|
const REMOTE_B = 'remote-b-key';
|
|
|
|
function resolve(domain, claims, voterVotes, overrides = {}) {
|
|
return resolveDomainConsensus({
|
|
domain,
|
|
claims,
|
|
voterVotes,
|
|
config: { ...DEFAULT_CONFIG, ...overrides.config },
|
|
localWriter: overrides.localWriter ?? LOCAL,
|
|
activePeers: overrides.activePeers ?? 4
|
|
});
|
|
}
|
|
|
|
function testLegacyClaimTimestamp() {
|
|
const parsed = parseClaimValue('hs://legacy-hash');
|
|
assert.strictEqual(parsed.timestamp, LEGACY_CLAIM_TIMESTAMP);
|
|
assert.strictEqual(parsed.legacy, true);
|
|
}
|
|
|
|
function testNoClaims() {
|
|
const state = resolve('example.test', {}, {});
|
|
assert.strictEqual(state.status, 'no_claims');
|
|
assert.strictEqual(state.hash, null);
|
|
}
|
|
|
|
function testSingleLocalClaim() {
|
|
const state = resolve(
|
|
'solo.test',
|
|
{ [LOCAL]: { hash: 'hs://solo', timestamp: 1000 } },
|
|
{}
|
|
);
|
|
assert.strictEqual(state.status, 'resolved');
|
|
assert.strictEqual(state.resolvedClaimant, LOCAL);
|
|
assert.strictEqual(state.hash, 'hs://solo');
|
|
}
|
|
|
|
function testInsufficientQuorum() {
|
|
const state = resolve(
|
|
'quorum.test',
|
|
{
|
|
[LOCAL]: { hash: 'hs://local', timestamp: 1000 },
|
|
[REMOTE_A]: { hash: 'hs://remote', timestamp: 2000 }
|
|
},
|
|
{ [LOCAL]: LOCAL },
|
|
{ activePeers: 10 }
|
|
);
|
|
assert.strictEqual(state.status, 'insufficient_quorum');
|
|
assert.strictEqual(state.quorumMet, false);
|
|
}
|
|
|
|
function testQuorumResolved() {
|
|
const claims = {
|
|
[LOCAL]: { hash: 'hs://local', timestamp: 1000 },
|
|
[REMOTE_A]: { hash: 'hs://remote', timestamp: 2000 }
|
|
};
|
|
const votes = {
|
|
voter1: LOCAL,
|
|
voter2: LOCAL,
|
|
voter3: LOCAL
|
|
};
|
|
const state = resolve('winner.test', claims, votes, { activePeers: 4 });
|
|
assert.strictEqual(state.status, 'resolved');
|
|
assert.strictEqual(state.resolvedClaimant, LOCAL);
|
|
}
|
|
|
|
function testVoteValidationDisabled() {
|
|
const state = resolve(
|
|
'noval.test',
|
|
{ [LOCAL]: { hash: 'hs://local', timestamp: 1 } },
|
|
{ voter1: REMOTE_A, voter2: REMOTE_A, voter3: REMOTE_A },
|
|
{ config: { CONSENSUS_VOTE_VALIDATION: false }, activePeers: 4 }
|
|
);
|
|
assert.strictEqual(state.status, 'resolved');
|
|
assert.strictEqual(state.resolvedClaimant, REMOTE_A);
|
|
}
|
|
|
|
function testVoteValidationEnabled() {
|
|
const state = resolve(
|
|
'val.test',
|
|
{
|
|
[LOCAL]: { hash: 'hs://local', timestamp: 1 },
|
|
[REMOTE_A]: { hash: 'hs://remote', timestamp: 2 }
|
|
},
|
|
{ voter1: REMOTE_B, voter2: REMOTE_B, voter3: REMOTE_B },
|
|
{ activePeers: 4 }
|
|
);
|
|
assert.strictEqual(state.status, 'insufficient_quorum');
|
|
}
|
|
|
|
function testTieTimestamp() {
|
|
const claims = {
|
|
[REMOTE_A]: { hash: 'hs://a', timestamp: 5000 },
|
|
[REMOTE_B]: { hash: 'hs://b', timestamp: 1000 }
|
|
};
|
|
const votes = { v1: REMOTE_A, v2: REMOTE_B, v3: REMOTE_A, v4: REMOTE_B };
|
|
const state = resolve('tie.test', claims, votes, {
|
|
activePeers: 4,
|
|
localWriter: 'other-peer'
|
|
});
|
|
assert.strictEqual(state.status, 'tie');
|
|
assert.strictEqual(state.resolvedClaimant, REMOTE_B);
|
|
}
|
|
|
|
function testTieBreakerLexicographicPrefersLocal() {
|
|
const winner = applyTieBreaker([REMOTE_B, LOCAL], {}, LOCAL, 'lexicographic');
|
|
assert.strictEqual(winner, LOCAL);
|
|
}
|
|
|
|
function testDuplicateVoteLastWinsViaOrderedReplay() {
|
|
const { createEmptyViewState, applyEventToView } = require('../includes/core/consensus-apply');
|
|
const { EVENT_TYPES } = require('../includes/core/consensus-events');
|
|
const view = createEmptyViewState();
|
|
const domain = 'dup.test';
|
|
|
|
applyEventToView(view, {
|
|
type: EVENT_TYPES.CLAIM_UPSERT,
|
|
domain,
|
|
claimant: REMOTE_A,
|
|
hash: 'hs://a',
|
|
timestamp: 1,
|
|
ssl: false,
|
|
clients: []
|
|
});
|
|
applyEventToView(view, {
|
|
type: EVENT_TYPES.CLAIM_UPSERT,
|
|
domain,
|
|
claimant: REMOTE_B,
|
|
hash: 'hs://b',
|
|
timestamp: 2,
|
|
ssl: false,
|
|
clients: []
|
|
});
|
|
applyEventToView(view, { type: EVENT_TYPES.VOTE_UPSERT, domain, claimant: REMOTE_A, voter: 'voter1' });
|
|
applyEventToView(view, { type: EVENT_TYPES.VOTE_UPSERT, domain, claimant: REMOTE_B, voter: 'voter1' });
|
|
applyEventToView(view, { type: EVENT_TYPES.VOTE_UPSERT, domain, claimant: REMOTE_A, voter: 'voter2' });
|
|
applyEventToView(view, { type: EVENT_TYPES.VOTE_UPSERT, domain, claimant: REMOTE_A, voter: 'voter3' });
|
|
|
|
const domainState = view.domains.get(domain);
|
|
assert.strictEqual(domainState.voterVotes.get('voter1'), REMOTE_B);
|
|
|
|
const { buildDomainSnapshotFromView } = require('../includes/core/consensus-resolver');
|
|
const snapshot = buildDomainSnapshotFromView(view, domain);
|
|
const state = resolve(domain, snapshot.claims, snapshot.voterVotes, { activePeers: 4, localWriter: 'x' });
|
|
assert.strictEqual(state.resolvedClaimant, REMOTE_A);
|
|
}
|
|
|
|
function run() {
|
|
testLegacyClaimTimestamp();
|
|
testNoClaims();
|
|
testSingleLocalClaim();
|
|
testInsufficientQuorum();
|
|
testQuorumResolved();
|
|
testVoteValidationDisabled();
|
|
testVoteValidationEnabled();
|
|
testTieTimestamp();
|
|
testTieBreakerLexicographicPrefersLocal();
|
|
testDuplicateVoteLastWinsViaOrderedReplay();
|
|
console.log('consensus-resolver.test.js: all passed');
|
|
}
|
|
|
|
run();
|