Fix Atomic state

This commit is contained in:
Raven Scott
2026-05-27 19:44:05 -04:00
parent 3ece7abe9f
commit c8334a8d63
5 changed files with 83 additions and 33 deletions
@@ -280,7 +280,8 @@ async function handleDomainsRoutes(req, res) {
let errorCount = 0;
for (const entry of entriesToRemove) {
try {
await pass.remove(entry.key);
const { dnsPassRemove } = require('../../../core/dns-pass-queue');
await dnsPassRemove(pass, entry.key);
removedCount++;
} catch (err) {
logError('Admin', `Error removing entry ${entry.key}: ${err.message}`);
+13 -11
View File
@@ -4,6 +4,7 @@ const { logDebug, logError, logInfo, logWarn } = require('../infrastructure/logg
const { trackDomainEvent, trackConsensusEvent } = require('../maintenance/metrics');
const { validateConfig } = require('../infrastructure/config');
const { secondsToMs, getPersistentPublicKey } = require('../infrastructure/utils');
const { dnsPassAdd, dnsPassRemove, dnsPassGet, whenDnsPassIdle } = require('./dns-pass-queue');
// Get consensus configuration
let consensusConfig = null;
@@ -94,6 +95,7 @@ async function getAllEntries(pass = state.dnsPass, useCache = true) {
const entries = [];
try {
await whenDnsPassIdle();
const stream = pass.list();
for await (const entry of stream) {
entries.push({
@@ -107,8 +109,8 @@ async function getAllEntries(pass = state.dnsPass, useCache = true) {
logDebug('Core', `Cached ${entries.length} entries`);
} catch (err) {
const msg = err.message || '';
if (msg.includes('SESSION_CLOSED') || msg.includes('closing core')) {
logWarn('Core', `dnsPass core is closing — stop the node and restart with --clean if this persists after upgrading Autopass: ${msg}`);
if (msg.includes('SESSION_CLOSED') || msg.includes('closing core') || msg.includes('Atomic state must flush')) {
logWarn('Core', `dnsPass core busy or closing — restart with --clean if this persists: ${msg}`);
} else {
logError('Core', `Error getting entries: ${msg}`);
}
@@ -216,7 +218,7 @@ async function updateClaimClients(domain, claimant, clients) {
// SAFE: Remove old claim and add updated one (ownership verified)
await safeRemoveClaim(claimKey, claimant);
await pass.add(claimKey, updatedValue);
await dnsPassAdd(pass, claimKey, updatedValue);
invalidateEntriesCache();
logInfo('Core', `Updated claim clients for ${domain} by ${claimant}`);
@@ -674,7 +676,7 @@ async function voteForDomain(domain, claimant, allEntries = null) {
// Remove existing votes for other claimants
for (const [votedClaimant, voteKey] of myVotes) {
if (votedClaimant !== claimant) {
await pass.remove(voteKey);
await dnsPassRemove(pass, voteKey);
logInfo('Core', `Removed vote for ${domain} claimant ${votedClaimant}`);
}
}
@@ -682,7 +684,7 @@ async function voteForDomain(domain, claimant, allEntries = null) {
// Add new vote if not already voting for this claimant
if (!myVotes.has(claimant)) {
const voteKey = `vote:${domain}:${claimant}:${localWriter}`;
await pass.add(voteKey, claims[claimant]);
await dnsPassAdd(pass, voteKey, claims[claimant]);
logInfo('Core', `Voted for ${domain} claimant ${claimant}`);
invalidateEntriesCache();
}
@@ -735,7 +737,7 @@ async function removeDomain(domain) {
for (const entry of allEntries) {
if (entry.key.startsWith(`claim:${domain}:`) ||
entry.key.startsWith(`vote:${domain}:`)) {
await pass.remove(entry.key);
await dnsPassRemove(pass, entry.key);
logDebug('Core', `Removed ${entry.key} for domain removal`);
}
}
@@ -769,7 +771,7 @@ async function removeOwnClaimAndVotes(domain, localWriter) {
// Remove ONLY the user's own claim record
// Format: claim:domain:claimant
if (entry.key === `claim:${domain}:${localWriter}`) {
await pass.remove(entry.key);
await dnsPassRemove(pass, entry.key);
logInfo('Core', `Removed own claim: ${entry.key}`);
removedClaims++;
}
@@ -780,7 +782,7 @@ async function removeOwnClaimAndVotes(domain, localWriter) {
const parts = entry.key.split(':');
if (parts.length === 4 && parts[3] === localWriter) {
// This is a vote cast BY the user (voter is localWriter)
await pass.remove(entry.key);
await dnsPassRemove(pass, entry.key);
logInfo('Core', `Removed vote cast by user: ${entry.key}`);
removedVotes++;
}
@@ -825,7 +827,7 @@ async function safeRemoveClaim(claimKey, expectedClaimant) {
}
// Safe to remove - ownership verified
await state.dnsPass.remove(claimKey);
await dnsPassRemove(state.dnsPass, claimKey);
logInfo('Core', `Safely removed claim ${claimKey} (owned by ${claimant})`);
return true;
}
@@ -847,7 +849,7 @@ async function removeAllRecords() {
for (const entry of allEntries) {
if (entry.key.startsWith('claim:') || entry.key.startsWith('vote:')) {
try {
await pass.remove(entry.key);
await dnsPassRemove(pass, entry.key);
removed++;
logDebug('Core', `Removed ${entry.key}`);
} catch (err) {
@@ -899,7 +901,7 @@ async function cleanupRedundantClientEntries() {
for (const entry of allEntries) {
if (entry.key.startsWith('clients:')) {
try {
await pass.remove(entry.key);
await dnsPassRemove(pass, entry.key);
removed++;
logDebug('Core', `Removed redundant client entry: ${entry.key}`);
} catch (err) {
+47
View File
@@ -0,0 +1,47 @@
/**
* Serializes Autopass / Autobase operations on the master corestore.
* Concurrent createInvite, list(), add(), and remove() calls cause
* "Atomic state must flush to parent" on Hypercore 11.
*/
let chain = Promise.resolve();
function enqueueDnsPass(operation) {
const run = chain.then(() => operation());
chain = run.then(
() => {},
() => {}
);
return run;
}
/** Wait until queued dnsPass work finishes (for reads). */
function whenDnsPassIdle() {
return chain;
}
async function createInvite(pass, opts) {
return enqueueDnsPass(() => pass.createInvite(opts));
}
async function dnsPassAdd(pass, key, value, file) {
return enqueueDnsPass(() => pass.add(key, value, file));
}
async function dnsPassRemove(pass, key) {
return enqueueDnsPass(() => pass.remove(key));
}
async function dnsPassGet(pass, key) {
await whenDnsPassIdle();
return pass.get(key);
}
module.exports = {
enqueueDnsPass,
whenDnsPassIdle,
createInvite,
dnsPassAdd,
dnsPassRemove,
dnsPassGet
};
+4 -3
View File
@@ -3,6 +3,7 @@ const { logDebug, logInfo, logError } = require('../infrastructure/logger');
const { getAllEntries, autoVoteForDomain, invalidateEntriesCache, parseClaimValue, safeRemoveClaim } = require('./core');
const { trackDomainEvent } = require('../maintenance/metrics');
const { getPersistentPublicKey } = require('../infrastructure/utils');
const { dnsPassAdd, dnsPassGet } = require('./dns-pass-queue');
// Initialize reserved IPs set if not already present
if (!state.reservedIPs) {
state.reservedIPs = new Set(['127.0.0.1']);
@@ -65,15 +66,15 @@ async function addDomain(domain, hash, ssl = false) {
// SAFE: Remove old claim and add updated one (ownership verified above)
// Use the safe remove helper for consistent validation
await safeRemoveClaim(claimKey, claimant);
await pass.add(claimKey, claimValue);
await dnsPassAdd(pass, claimKey, claimValue);
logInfo('Domains', `Updated claim ${claimKey} with new hash ${hash} and SSL=${sslValue}`);
} else {
// New claim - add it
logDebug('Domains', `Adding new domain ${domain} with hash ${hash} and SSL=${sslValue} as claim ${claimKey} at timestamp ${timestamp}`);
await pass.add(claimKey, claimValue);
await dnsPassAdd(pass, claimKey, claimValue);
const verified = await pass.get(claimKey);
const verified = await dnsPassGet(pass, claimKey);
logDebug('Domains', `Verified add for ${claimKey}: ${verified ? verified.toString('utf8') : 'null'}`);
logInfo('Domains', `Domain ${domain} added as claim ${claimKey} with timestamp ${timestamp}`);
}