From 28f427762dd1cf7131a09f0ed4e62b12856e08b3 Mon Sep 17 00:00:00 2001 From: jairajdev Date: Wed, 22 Jul 2026 14:23:47 +0800 Subject: [PATCH 1/3] fix(sync): preserve valid consensus topology - Keep existing consensus topology when radius calculation is invalid. - Avoid reassigning consensus radius after validation. - Remove ring-buffer debug console output. --- src/Data/Data.ts | 13 +++++++------ src/statistics/index.ts | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Data/Data.ts b/src/Data/Data.ts index ed2fb36..9757a00 100644 --- a/src/Data/Data.ts +++ b/src/Data/Data.ts @@ -998,16 +998,18 @@ export async function getConsensusRadius(): Promise { } if (nodesPerConsensusGroup === nodesPerConsensusGroupFromConfig && nodesPerEdge === nodesPerEdgeFromConfig) return currentConsensusRadius - nodesPerConsensusGroup = nodesPerConsensusGroupFromConfig - nodesPerEdge = nodesPerEdgeFromConfig + let newNodesPerConsensusGroup = nodesPerConsensusGroupFromConfig // Upgrading consensus size to an odd number - if (nodesPerConsensusGroup % 2 === 0) nodesPerConsensusGroup++ - const consensusRadius = Math.floor((nodesPerConsensusGroup - 1) / 2) + if (newNodesPerConsensusGroup % 2 === 0) newNodesPerConsensusGroup++ + const consensusRadius = Math.floor((newNodesPerConsensusGroup - 1) / 2) // Validation: Ensure consensusRadius is a number and greater than zero if (typeof consensusRadius !== 'number' || isNaN(consensusRadius) || consensusRadius <= 0) { Logger.mainLogger.error('Invalid consensusRadius:', consensusRadius) return currentConsensusRadius // Return the existing currentConsensusRadius in case of invalid consensusRadius } + nodesPerConsensusGroup = newNodesPerConsensusGroup + nodesPerEdge = nodesPerEdgeFromConfig + currentConsensusRadius = consensusRadius Logger.mainLogger.debug( 'consensusRadius', consensusRadius, @@ -1056,7 +1058,6 @@ export async function createNodesGroupByConsensusRadius(): Promise { Logger.mainLogger.error('Consensus radius is 0, unable to create nodes group.') return // Early return to prevent further execution } - currentConsensusRadius = consensusRadius const activeList = [...NodeList.activeListByIdSorted] if (config.VERBOSE) Logger.mainLogger.debug('activeList', activeList.length, activeList) let totalNumberOfNodesToSubscribe = Math.ceil(activeList.length / consensusRadius) @@ -1788,7 +1789,7 @@ export async function syncCyclesAndNodeListV2( Logger.mainLogger.debug('cycleToSyncTo', cycleToSyncTo) Logger.mainLogger.debug(`Syncing till cycle ${cycleToSyncTo.counter}...`) - currentConsensusRadius = await getConsensusRadius() + await getConsensusRadius() await processCycles([cycleToSyncTo]) // Download old cycle Records diff --git a/src/statistics/index.ts b/src/statistics/index.ts index 1f16f0c..22d2afc 100644 --- a/src/statistics/index.ts +++ b/src/statistics/index.ts @@ -303,7 +303,7 @@ class Ring { average(): number { let sum = 0 let total = 0 - console.log('elements', this.elements) + // console.log('elements', this.elements) for (const element of this.elements) { if (_exists(element)) { sum += Number(element) From 187887958919bdded3f9d81ebd07874a49a44b2d Mon Sep 17 00:00:00 2001 From: jairajdev Date: Thu, 23 Jul 2026 15:37:18 +0800 Subject: [PATCH 2/3] feat(checkpoint): enable checkpoint processing by default - Enable checkpoint updates by default. - Enable checkpoint-backed storage by default. --- src/Config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Config.ts b/src/Config.ts index 6539b5e..1fb6a78 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -212,8 +212,8 @@ let config: Config = { GiveUpAge: 20 * 60, // 20 minutes lastFailedBucketDuration: 5 * 60 * 1000, // 5 minutes RadixDepth: 2, // 2 nibbles (1 hex char) - allowCheckpointUpdates: false, - allowCheckpointStorage: false, + allowCheckpointUpdates: true, + allowCheckpointStorage: true, }, batchSize: 100, updateInterval: 60 * 1000, // 1 minute in milliseconds in milliseconds From bf692e8566be5162447a969b78aa45f22525e84d Mon Sep 17 00:00:00 2001 From: jairajdev Date: Tue, 28 Jul 2026 17:46:31 +0800 Subject: [PATCH 3/3] feat(checkpoint): isolate recovery state and audit repairs - Store failed buckets and the cycle tracker with their archiver instance data. - Initialize the cycle tracker when the checkpoint database opens. - Record checkpoint-persisted receipts in the main and error logs. - Cover the tracker database path in unit tests. --- src/Data/Collector.ts | 5 +++++ src/checkpoint/CheckpointData.ts | 2 +- src/server.ts | 5 +++-- src/utils/cycleTracker.ts | 15 ++++++++++----- test/unit/src/utils/cycleTracker.test.ts | 3 ++- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Data/Collector.ts b/src/Data/Collector.ts index 82da3b1..388c0cf 100644 --- a/src/Data/Collector.ts +++ b/src/Data/Collector.ts @@ -764,6 +764,11 @@ export const storeReceiptData = async ( if (!txId || !timestamp) { continue } + if (senderInfo === 'checkpoint') { + Logger.mainLogger.error( + `[CHECKPOINT_RECEIPT_RECEIVED] receiptId=${txId} cycle=${receipt.cycle} timestamp=${timestamp}` + ) + } if ( checkpoint && ((processedReceiptsMap.has(txId) && processedReceiptsMap.get(txId) === timestamp) || diff --git a/src/checkpoint/CheckpointData.ts b/src/checkpoint/CheckpointData.ts index c401a88..d796b64 100644 --- a/src/checkpoint/CheckpointData.ts +++ b/src/checkpoint/CheckpointData.ts @@ -635,7 +635,7 @@ export class CheckpointBucket { radixEntries: Array.from(this.radixEntries.entries()), peerDigests: Array.from(this.peerRadixDigests.entries()), } - const filename = `${config.failedBucketsDir}/failed-bucket-${this.checkpointType}-${this.bucketID}-${this.startTime}.json` + const filename = `${config.failedBucketsDir}/${config.ARCHIVER_IP}_${config.ARCHIVER_PORT}/failed-bucket-${this.checkpointType}-${this.bucketID}-${this.startTime}.json` if (config.VERBOSE) { Logger.mainLogger.debug(`Writing bucket id ${this.bucketID} data to file ${filename}`) } diff --git a/src/server.ts b/src/server.ts index 3e6a566..1f5e6d8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -99,6 +99,7 @@ async function start(): Promise { // Initialize storage and checkpoints if (config.experimentalSnapshot) { await dbstore.initializeDB(config) + getLastUpdatedCycle() } else { await Storage.initStorage(config) } @@ -246,8 +247,8 @@ async function start(): Promise { scheduleMultiSigKeysSyncFromNetConfig() }, 60 * 1000) // Start after 60 seconds - // Create the failed buckets directory - createDirectories(config.failedBucketsDir) + // Create the failed buckets directory for this archiver instance + createDirectories(`${config.failedBucketsDir}/${config.ARCHIVER_IP}_${config.ARCHIVER_PORT}`) // Initialize checkpoint V2 system if enabled and checkpoint updates and storage are allowed if (config.checkpoint.bucketConfig.allowCheckpointUpdates) { diff --git a/src/utils/cycleTracker.ts b/src/utils/cycleTracker.ts index 865fa94..611e4dc 100644 --- a/src/utils/cycleTracker.ts +++ b/src/utils/cycleTracker.ts @@ -9,18 +9,22 @@ interface CycleTrackerData { lastUpdatedTimestamp: number } -const CYCLE_TRACKER_FILE = path.join(process.cwd(), 'cycle-tracker.json') +function getCycleTrackerFile(): string { + return path.join(config.ARCHIVER_DB, 'cycle-tracker.json') +} /** * Gets the last updated cycle from the tracker file * @returns The last updated cycle number, or 0 if not found */ export function getLastUpdatedCycle(): number { + const cycleTrackerFile = getCycleTrackerFile() + try { let data: string try { - data = fs.readFileSync(CYCLE_TRACKER_FILE, 'utf8') + data = fs.readFileSync(cycleTrackerFile, 'utf8') } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') { const trackerData: CycleTrackerData = { @@ -30,16 +34,17 @@ export function getLastUpdatedCycle(): number { try { const fd = fs.openSync( - CYCLE_TRACKER_FILE, + cycleTrackerFile, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o600 ) fs.writeFileSync(fd, JSON.stringify(trackerData, null, 2), 'utf8') fs.closeSync(fd) + Logger.mainLogger.debug(`Created cycle tracker file at ${cycleTrackerFile}`) return 0 } catch (createError) { if ((createError as NodeJS.ErrnoException).code === 'EEXIST') { - data = fs.readFileSync(CYCLE_TRACKER_FILE, 'utf8') + data = fs.readFileSync(cycleTrackerFile, 'utf8') } else { throw createError } @@ -70,7 +75,7 @@ export function updateLastUpdatedCycle(cycle: number): void { lastUpdatedTimestamp: Date.now(), } - fs.writeFileSync(CYCLE_TRACKER_FILE, JSON.stringify(trackerData, null, 2), 'utf8') + fs.writeFileSync(getCycleTrackerFile(), JSON.stringify(trackerData, null, 2), 'utf8') Logger.mainLogger.debug(`Updated cycle tracker to cycle ${cycle}`) } catch (error) { Logger.mainLogger.error('Error updating cycle tracker file:', error) diff --git a/test/unit/src/utils/cycleTracker.test.ts b/test/unit/src/utils/cycleTracker.test.ts index 590273e..d42ac60 100644 --- a/test/unit/src/utils/cycleTracker.test.ts +++ b/test/unit/src/utils/cycleTracker.test.ts @@ -15,6 +15,7 @@ jest.mock('../../../../src/dbstore/index', () => ({})) jest.mock('../../../../src/dbstore/checkpointStatus') jest.mock('../../../../src/Config', () => ({ config: { + ARCHIVER_DB: '/tmp/archiver-db', checkpoint: { bucketConfig: { GiveUpAge: 20, @@ -42,7 +43,7 @@ const mockGetCheckpointStatusesByUnifiedStatus = getCheckpointStatusesByUnifiedS > describe('cycleTracker', () => { - const CYCLE_TRACKER_FILE = path.join(process.cwd(), 'cycle-tracker.json') + const CYCLE_TRACKER_FILE = path.join(config.ARCHIVER_DB, 'cycle-tracker.json') const mockDate = new Date('2024-01-15T10:30:00Z').getTime() beforeEach(() => {