fixes
This commit is contained in:
@@ -94,6 +94,11 @@ async function handleConsensusRoutes(req, res) {
|
|||||||
const allEntries = await getAllEntries();
|
const allEntries = await getAllEntries();
|
||||||
await autoVoteForDomain(domain, allEntries);
|
await autoVoteForDomain(domain, allEntries);
|
||||||
|
|
||||||
|
// Notify other peers to recalculate consensus for this domain
|
||||||
|
if (state.sendConsensusRequest) {
|
||||||
|
state.sendConsensusRequest(domain);
|
||||||
|
}
|
||||||
|
|
||||||
// Broadcast update
|
// Broadcast update
|
||||||
broadcast({ type: 'update-database' });
|
broadcast({ type: 'update-database' });
|
||||||
broadcast({ type: 'update-stats' });
|
broadcast({ type: 'update-stats' });
|
||||||
|
|||||||
+73
-61
@@ -331,9 +331,12 @@ async function getConsensusState(domain) {
|
|||||||
|
|
||||||
if (Object.keys(claims).length === 0) {
|
if (Object.keys(claims).length === 0) {
|
||||||
status = 'no_claims';
|
status = 'no_claims';
|
||||||
} else if (!quorumMet && totalVotes === 0) {
|
} else if (!quorumMet) {
|
||||||
// No votes but have claims - check if single local claim
|
// IMPROVED: Allow resolution for a single local claim even if we've already auto-voted
|
||||||
if (Object.keys(claims).length === 1 && claims.hasOwnProperty(localWriter)) {
|
const isSingleLocalClaim = Object.keys(claims).length === 1 && claims.hasOwnProperty(localWriter);
|
||||||
|
const onlyWeVoted = totalVotes === 0 || (totalVotes === 1 && voters.has(localWriter));
|
||||||
|
|
||||||
|
if (isSingleLocalClaim && (totalVotes === 0 || onlyWeVoted)) {
|
||||||
status = 'resolved';
|
status = 'resolved';
|
||||||
resolvedClaimant = localWriter;
|
resolvedClaimant = localWriter;
|
||||||
hash = claims[localWriter];
|
hash = claims[localWriter];
|
||||||
@@ -350,17 +353,10 @@ async function getConsensusState(domain) {
|
|||||||
countedQuorumFailures.add(domain);
|
countedQuorumFailures.add(domain);
|
||||||
consensusMetrics.quorumFailures++;
|
consensusMetrics.quorumFailures++;
|
||||||
}
|
}
|
||||||
}
|
if (trackConsensusEvent && totalVotes > 0) {
|
||||||
} else if (!quorumMet) {
|
|
||||||
status = 'insufficient_quorum';
|
|
||||||
// Count quorum failure once per domain
|
|
||||||
if (!countedQuorumFailures.has(domain)) {
|
|
||||||
countedQuorumFailures.add(domain);
|
|
||||||
consensusMetrics.quorumFailures++;
|
|
||||||
}
|
|
||||||
if (trackConsensusEvent) {
|
|
||||||
trackConsensusEvent('quorum_failure', { domain, totalVotes, minVotes, activePeers });
|
trackConsensusEvent('quorum_failure', { domain, totalVotes, minVotes, activePeers });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const maxVotes = Math.max(...Object.values(voteCounts));
|
const maxVotes = Math.max(...Object.values(voteCounts));
|
||||||
const candidates = Object.keys(voteCounts).filter(claimant => voteCounts[claimant] === maxVotes);
|
const candidates = Object.keys(voteCounts).filter(claimant => voteCounts[claimant] === maxVotes);
|
||||||
@@ -498,29 +494,29 @@ async function doAutoVotes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autoVoteForDomain(domain, allEntries) {
|
async function voteForDomain(domain, claimant, allEntries = null) {
|
||||||
const pass = state.dnsPass;
|
const pass = state.dnsPass;
|
||||||
if (!pass) return;
|
if (!pass) return false;
|
||||||
|
|
||||||
logDebug('Core', `Auto-voting for domain: ${domain}`);
|
|
||||||
const config = getConsensusConfig();
|
|
||||||
const claims = {};
|
|
||||||
const claimTimestamps = {};
|
|
||||||
const myVotes = new Map(); // claimant -> voteKey
|
|
||||||
const localWriter = getPersistentPublicKey();
|
const localWriter = getPersistentPublicKey();
|
||||||
if (!localWriter) {
|
if (!localWriter) {
|
||||||
logWarn('Core', 'Cannot auto-vote: persistent public key not available');
|
logWarn('Core', 'Cannot vote: persistent public key not available');
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect claims and timestamps
|
if (!allEntries) {
|
||||||
|
allEntries = await getAllEntries(pass, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const claims = {};
|
||||||
|
const myVotes = new Map(); // claimant -> voteKey
|
||||||
|
|
||||||
|
// Collect claims and our current votes
|
||||||
for (const entry of allEntries) {
|
for (const entry of allEntries) {
|
||||||
if (entry.key.startsWith(`claim:${domain}:`)) {
|
if (entry.key.startsWith(`claim:${domain}:`)) {
|
||||||
const claimant = entry.key.slice(`claim:${domain}:`.length);
|
const entryClaimant = entry.key.slice(`claim:${domain}:`.length);
|
||||||
const parsed = parseClaimValue(entry.value);
|
const parsed = parseClaimValue(entry.value);
|
||||||
claims[claimant] = parsed.hash;
|
claims[entryClaimant] = parsed.hash;
|
||||||
claimTimestamps[claimant] = parsed.timestamp;
|
|
||||||
logDebug('Core', `Processing claim for ${domain}: claimant=${claimant}, hash=${parsed.hash}, timestamp=${parsed.timestamp}`);
|
|
||||||
}
|
}
|
||||||
if (entry.key.startsWith(`vote:${domain}:`)) {
|
if (entry.key.startsWith(`vote:${domain}:`)) {
|
||||||
const parts = entry.key.split(':');
|
const parts = entry.key.split(':');
|
||||||
@@ -530,52 +526,67 @@ async function autoVoteForDomain(domain, allEntries) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const claimsLength = Object.keys(claims).length;
|
if (!claims.hasOwnProperty(claimant)) {
|
||||||
logDebug('Core', `Found ${claimsLength} claims for ${domain}`);
|
logWarn('Core', `Cannot vote for claimant ${claimant}: no claim found for domain ${domain}`);
|
||||||
if (claimsLength === 0) return;
|
return false;
|
||||||
|
|
||||||
// Get consensus state to inform voting decision
|
|
||||||
const consensusState = await getConsensusState(domain);
|
|
||||||
|
|
||||||
// Determine intended claimant
|
|
||||||
const localHasClaim = claims.hasOwnProperty(localWriter);
|
|
||||||
let intendedClaimant;
|
|
||||||
|
|
||||||
if (localHasClaim) {
|
|
||||||
intendedClaimant = localWriter;
|
|
||||||
logDebug('Core', `Voting for own claim: ${intendedClaimant}`);
|
|
||||||
} else if (claimsLength === 1) {
|
|
||||||
intendedClaimant = Object.keys(claims)[0];
|
|
||||||
logDebug('Core', `Voting for single claim: ${intendedClaimant}`);
|
|
||||||
} else {
|
|
||||||
// Multiple claims - use tie-breaker strategy
|
|
||||||
const candidates = Object.keys(claims);
|
|
||||||
intendedClaimant = applyTieBreaker(candidates, claims, claimTimestamps, localWriter);
|
|
||||||
logDebug('Core', `Voting for claimant selected by tie-breaker: ${intendedClaimant}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate intended claimant exists
|
// Validate vote
|
||||||
if (!validateVote(intendedClaimant, claims)) {
|
if (!validateVote(claimant, claims)) {
|
||||||
logWarn('Core', `Cannot vote for invalid claimant: ${intendedClaimant}`);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove votes for other claimants
|
// Remove existing votes for other claimants
|
||||||
for (const [votedClaimant, voteKey] of myVotes) {
|
for (const [votedClaimant, voteKey] of myVotes) {
|
||||||
if (votedClaimant !== intendedClaimant) {
|
if (votedClaimant !== claimant) {
|
||||||
await pass.remove(voteKey);
|
await pass.remove(voteKey);
|
||||||
logInfo('Core', `Removed vote for ${domain} claimant ${votedClaimant}`);
|
logInfo('Core', `Removed vote for ${domain} claimant ${votedClaimant}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add vote if not already voting for intended claimant
|
// Add new vote if not already voting for this claimant
|
||||||
if (!myVotes.has(intendedClaimant)) {
|
if (!myVotes.has(claimant)) {
|
||||||
const voteKey = `vote:${domain}:${intendedClaimant}:${localWriter}`;
|
const voteKey = `vote:${domain}:${claimant}:${localWriter}`;
|
||||||
await pass.add(voteKey, claims[intendedClaimant]);
|
await pass.add(voteKey, claims[claimant]);
|
||||||
logInfo('Core', `Auto-voted for ${domain} claimant ${intendedClaimant} (reason: ${localHasClaim ? 'own_claim' : claimsLength === 1 ? 'single_claim' : 'tie_breaker'})`);
|
logInfo('Core', `Voted for ${domain} claimant ${claimant}`);
|
||||||
} else {
|
invalidateEntriesCache();
|
||||||
logDebug('Core', `Already voted for ${domain} claimant ${intendedClaimant}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function autoVoteForDomain(domain, allEntries) {
|
||||||
|
const localWriter = getPersistentPublicKey();
|
||||||
|
if (!localWriter) return;
|
||||||
|
|
||||||
|
const claims = {};
|
||||||
|
const claimTimestamps = {};
|
||||||
|
|
||||||
|
for (const entry of allEntries) {
|
||||||
|
if (entry.key.startsWith(`claim:${domain}:`)) {
|
||||||
|
const claimant = entry.key.slice(`claim:${domain}:`.length);
|
||||||
|
const parsed = parseClaimValue(entry.value);
|
||||||
|
claims[claimant] = parsed.hash;
|
||||||
|
claimTimestamps[claimant] = parsed.timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimsLength = Object.keys(claims).length;
|
||||||
|
if (claimsLength === 0) return;
|
||||||
|
|
||||||
|
const localHasClaim = claims.hasOwnProperty(localWriter);
|
||||||
|
let intendedClaimant;
|
||||||
|
|
||||||
|
if (localHasClaim) {
|
||||||
|
intendedClaimant = localWriter;
|
||||||
|
} else if (claimsLength === 1) {
|
||||||
|
intendedClaimant = Object.keys(claims)[0];
|
||||||
|
} else {
|
||||||
|
const candidates = Object.keys(claims);
|
||||||
|
intendedClaimant = applyTieBreaker(candidates, claims, claimTimestamps, localWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
await voteForDomain(domain, intendedClaimant, allEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeDomain(domain) {
|
async function removeDomain(domain) {
|
||||||
@@ -699,6 +710,7 @@ module.exports = {
|
|||||||
doAutoVotes,
|
doAutoVotes,
|
||||||
getAllEntries,
|
getAllEntries,
|
||||||
autoVoteForDomain,
|
autoVoteForDomain,
|
||||||
|
voteForDomain,
|
||||||
removeDomain,
|
removeDomain,
|
||||||
removeAllRecords,
|
removeAllRecords,
|
||||||
invalidateEntriesCache,
|
invalidateEntriesCache,
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ async function addDomain(domain, hash, ssl = false) {
|
|||||||
if (config.CONSENSUS_IMMEDIATE_UPDATE !== false) {
|
if (config.CONSENSUS_IMMEDIATE_UPDATE !== false) {
|
||||||
const allEntries = await getAllEntries();
|
const allEntries = await getAllEntries();
|
||||||
await autoVoteForDomain(domain, allEntries);
|
await autoVoteForDomain(domain, allEntries);
|
||||||
|
|
||||||
|
// Notify other peers to recalculate consensus for this domain
|
||||||
|
if (state.sendConsensusRequest) {
|
||||||
|
state.sendConsensusRequest(domain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function addInternalDomain(domain, ip = '127.0.0.1') {
|
function addInternalDomain(domain, ip = '127.0.0.1') {
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ module.exports = {
|
|||||||
localDnsRecords: [],
|
localDnsRecords: [],
|
||||||
isMaster: process.argv.includes('--master'),
|
isMaster: process.argv.includes('--master'),
|
||||||
sendRemovalRequest: null,
|
sendRemovalRequest: null,
|
||||||
|
sendConsensusRequest: null,
|
||||||
domainsWithBoth: new Set(),
|
domainsWithBoth: new Set(),
|
||||||
publicIpForDomain: {},
|
publicIpForDomain: {},
|
||||||
caForDomain: new Map(),
|
caForDomain: new Map(),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const {
|
|||||||
removeDomain,
|
removeDomain,
|
||||||
doAutoVotes,
|
doAutoVotes,
|
||||||
autoVoteForDomain,
|
autoVoteForDomain,
|
||||||
|
voteForDomain,
|
||||||
invalidateEntriesCache
|
invalidateEntriesCache
|
||||||
} = require('../core/core');
|
} = require('../core/core');
|
||||||
const { atomicDomainCleanup } = require('../core/domain_cleanup');
|
const { atomicDomainCleanup } = require('../core/domain_cleanup');
|
||||||
@@ -685,9 +686,14 @@ const sdk = {
|
|||||||
if (!state.dnsPass) {
|
if (!state.dnsPass) {
|
||||||
throw new Error('DNS service not initialized');
|
throw new Error('DNS service not initialized');
|
||||||
}
|
}
|
||||||
const allEntries = await getAllEntries();
|
|
||||||
await autoVoteForDomain(domain, allEntries);
|
const success = await voteForDomain(domain, claimant);
|
||||||
// Check if vote was successful
|
if (!success) return false;
|
||||||
|
|
||||||
|
// Invalidate cache to ensure subsequent state check is fresh
|
||||||
|
invalidateEntriesCache();
|
||||||
|
|
||||||
|
// Check if vote resulted in expected resolution
|
||||||
const consensusState = await getConsensusState(domain);
|
const consensusState = await getConsensusState(domain);
|
||||||
return consensusState.resolvedClaimant === claimant;
|
return consensusState.resolvedClaimant === claimant;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -804,6 +804,14 @@ async function main() {
|
|||||||
const domain = message.slice(14);
|
const domain = message.slice(14);
|
||||||
logDebug('Swarm', `Processing removal request for domain: ${domain}`);
|
logDebug('Swarm', `Processing removal request for domain: ${domain}`);
|
||||||
await removeDomain(domain);
|
await removeDomain(domain);
|
||||||
|
} else if (message.startsWith('recalculate_consensus:')) {
|
||||||
|
const domain = message.slice(22);
|
||||||
|
logDebug('Swarm', `Processing consensus recalculation request for domain: ${domain}`);
|
||||||
|
// Invalidate cache first to ensure we get the latest replicated data
|
||||||
|
const { invalidateEntriesCache, getAllEntries, autoVoteForDomain } = require('./includes/core/core');
|
||||||
|
invalidateEntriesCache();
|
||||||
|
const allEntries = await getAllEntries();
|
||||||
|
await autoVoteForDomain(domain, allEntries);
|
||||||
} else {
|
} else {
|
||||||
logWarn('Swarm', `Unknown message from ${peerId}: ${message}`);
|
logWarn('Swarm', `Unknown message from ${peerId}: ${message}`);
|
||||||
}
|
}
|
||||||
@@ -2031,6 +2039,14 @@ async function main() {
|
|||||||
}
|
}
|
||||||
state.sendRemovalRequest = sendRemovalRequest;
|
state.sendRemovalRequest = sendRemovalRequest;
|
||||||
|
|
||||||
|
// Function to send consensus recalculation request to all peers
|
||||||
|
function sendConsensusRequest(domain) {
|
||||||
|
// Use channel-manager broadcast to send to all peers
|
||||||
|
const count = channelManager.broadcastToPeers(CORE_DOMAIN, 'request', `recalculate_consensus:${domain}`);
|
||||||
|
logDebug('Main', `Sent consensus recalculation request for ${domain} to ${count} peers`);
|
||||||
|
}
|
||||||
|
state.sendConsensusRequest = sendConsensusRequest;
|
||||||
|
|
||||||
// Function to auto-subscribe to services on bootup
|
// Function to auto-subscribe to services on bootup
|
||||||
async function autoSubscribeToServices() {
|
async function autoSubscribeToServices() {
|
||||||
const disableAutoSubscription = process.env.DISABLE_AUTO_SUBSCRIPTION === 'true';
|
const disableAutoSubscription = process.env.DISABLE_AUTO_SUBSCRIPTION === 'true';
|
||||||
|
|||||||
Reference in New Issue
Block a user