You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The logic in collectCycleData for selecting the "best" cycle marker and handling multiple markers is complex and may lead to inconsistent state if not all edge cases are handled (e.g., race conditions, marker selection when cache is empty, or marker scoring ties). This should be carefully validated for correctness and determinism.
exportfunctioncollectCycleData(cycleData: subscriptionCycleData[]|P2PTypes.CycleCreatorTypes.CycleData[],senderInfo: string,source: string,dataSenders?: Map<NodeList.ConsensusNodeInfo['publicKey'],DataSender>): void{conststartTime=Date.now()constoperationId=ArchiverLogging.generateOperationId()Logger.mainLogger.debug(`collectCycleData: Processing ${cycleData.length} cycles from ${senderInfo}, source: ${source}`)nestedCountersInstance.countEvent('collectCycleData','cycles_received',cycleData.length)nestedCountersInstance.countEvent('collectCycleData','source_'+source,1)ArchiverLogging.logDataSync({sourceArchiver: senderInfo,targetArchiver: config.ARCHIVER_IP,cycle: 0,dataType: 'CYCLE_RECORD',dataHash: '',status: 'STARTED',
operationId,metrics: {duration: 0,dataSize: StringUtils.safeStringify(cycleData).length,},})if(NodeList.activeListByIdSorted.length>0){const[ip,port]=senderInfo.split(':')constisInActiveNodes=NodeList.activeListByIdSorted.some((node)=>node.ip===ip&&node.port.toString()===port)constisInActiveArchivers=State.activeArchivers.some((archiver)=>archiver.ip===ip&&archiver.port.toString()===port)if(!isInActiveNodes&&!isInActiveArchivers){nestedCountersInstance.countEvent('collectCycleData','sender_not_active',1)Logger.mainLogger.warn(`collectCycleData: Ignoring cycle data from non-active node: ${senderInfo}`)ArchiverLogging.logDataSync({sourceArchiver: senderInfo,targetArchiver: config.ARCHIVER_IP,cycle: 0,dataType: 'CYCLE_RECORD',dataHash: '',status: 'ERROR',
operationId,metrics: {duration: Date.now()-startTime,dataSize: StringUtils.safeStringify(cycleData).length,},error: 'Sender not in active nodes or archivers',})return}}for(constcycleofcycleData){Logger.mainLogger.debug(`collectCycleData: Processing cycle ${cycle.counter}, marker: ${cycle.marker}`)if(receivedCycleTracker[cycle.counter]?.saved===true){nestedCountersInstance.countEvent('collectCycleData','cycle_already_saved_'+cycle.mode,1)Logger.mainLogger.debug(`collectCycleData: Cycle ${cycle.counter} already saved, skipping`)ArchiverLogging.logDataSync({sourceArchiver: senderInfo,targetArchiver: config.ARCHIVER_IP,cycle: cycle.counter,dataType: 'CYCLE_RECORD',dataHash: cycle.marker,status: 'COMPLETE',
operationId,metrics: {duration: Date.now()-startTime,dataSize: StringUtils.safeStringify(cycle).length,},})break}nestedCountersInstance.countEvent('collectCycleData','process_cycle_'+cycle.mode,1)if(source==='archiver'){nestedCountersInstance.countEvent('collectCycleData','direct_process_from_archiver',1)Logger.mainLogger.debug(`collectCycleData: Processing cycle ${cycle.counter} from archiver directly`)processCycles([cycleasP2PTypes.CycleCreatorTypes.CycleData])continue}letreceivedCertSigners=[]if(NodeList.activeListByIdSorted.length>0){constcertSigners=receivedCycleTracker[cycle.counter]?.[cycle.marker]?.['certSigners']??newSet()try{Logger.mainLogger.debug(`collectCycleData: Original cycle data: ${UtilsTypes.safeStringify(cycle)}`)constcycleCopy=getRecordWithoutPostQ3Changes(cycle)constcomputedMarker=Cycles.computeCycleMarker(cycleCopy)Logger.mainLogger.debug(`collectCycleData: cycle copy ${UtilsTypes.safeStringify(cycleCopy)}`)Logger.mainLogger.debug(`collectCycleData: Computed marker for cycle ${cycle.counter}: ${computedMarker}, original marker: ${cycle.marker}`)Logger.mainLogger.debug(`collectCycleData: Validating ${(cycleassubscriptionCycleData).certificates?.length||0} certificates for cycle ${cycle.counter}`)constvalidateCertsResult=validateCerts((cycleassubscriptionCycleData).certificates,certSigners,computedMarker,cycleCopyasP2PTypes.CycleCreatorTypes.CycleData)if(validateCertsResult===false){nestedCountersInstance.countEvent('collectCycleData','certificate_validation_failed_'+cycle.mode,1)Logger.mainLogger.warn(`collectCycleData: Certificate validation failed for cycle ${cycle.counter} from ${senderInfo} in ${cycle.mode} mode`)ArchiverLogging.logDataSync({sourceArchiver: senderInfo,targetArchiver: config.ARCHIVER_IP,cycle: cycle.counter,dataType: 'CYCLE_RECORD',dataHash: cycle.marker,status: 'ERROR',
operationId,metrics: {duration: Date.now()-startTime,dataSize: StringUtils.safeStringify(cycle).length,},error: 'Certificate validation failed',})break}nestedCountersInstance.countEvent('collectCycleData','certificate_validation_success_'+cycle.mode,1)Logger.mainLogger.debug(`collectCycleData: Certificate validation successful for cycle ${cycle.counter}`)}catch(error){nestedCountersInstance.countEvent('collectCycleData','certificate_validation_error_'+cycle.mode,1)Logger.mainLogger.error(`collectCycleData: Error during certificate validation for cycle ${cycle.counter}: ${error}`)ArchiverLogging.logDataSync({sourceArchiver: senderInfo,targetArchiver: config.ARCHIVER_IP,cycle: cycle.counter,dataType: 'CYCLE_RECORD',dataHash: cycle.marker,status: 'ERROR',
operationId,metrics: {duration: Date.now()-startTime,dataSize: StringUtils.safeStringify(cycle).length,},error: `Certificate validation error: ${error.message}`,})break}}receivedCertSigners=(cycleassubscriptionCycleData).certificates.map((cert)=>cert.sign.owner)Logger.mainLogger.debug(`collectCycleData: Received ${receivedCertSigners.length} certificate signers for cycle ${cycle.counter}`)delete(cycleassubscriptionCycleData).certificatesif(receivedCycleTracker[cycle.counter]){if(receivedCycleTracker[cycle.counter][cycle.marker]){nestedCountersInstance.countEvent('collectCycleData','add_signers_to_existing_marker_'+cycle.mode,1)Logger.mainLogger.debug(`collectCycleData: Adding signers to existing marker for cycle ${cycle.counter}`)for(constsignerofreceivedCertSigners)receivedCycleTracker[cycle.counter][cycle.marker]['certSigners'].add(signer)}else{if(!validateCycleData(cycle)){nestedCountersInstance.countEvent('collectCycleData','cycle_data_validation_failed_'+cycle.mode,1)Logger.mainLogger.warn(`collectCycleData: Cycle data validation failed for cycle ${cycle.counter} with marker ${cycle.marker}`)ArchiverLogging.logDataSync({sourceArchiver: senderInfo,targetArchiver: config.ARCHIVER_IP,cycle: cycle.counter,dataType: 'CYCLE_RECORD',dataHash: cycle.marker,status: 'ERROR',
operationId,metrics: {duration: Date.now()-startTime,dataSize: StringUtils.safeStringify(cycle).length,},error: 'Cycle data validation failed',})continue}nestedCountersInstance.countEvent('collectCycleData','create_new_marker_entry_'+cycle.mode,1)Logger.mainLogger.debug(`collectCycleData: Creating new marker entry for cycle ${cycle.counter} with marker ${cycle.marker}`)receivedCycleTracker[cycle.counter][cycle.marker]={cycleInfo: cycle,certSigners: newSet(receivedCertSigners),}Logger.mainLogger.debug('Different Cycle Record received',cycle.counter)}receivedCycleTracker[cycle.counter]['received']++Logger.mainLogger.debug(`collectCycleData: Cycle ${cycle.counter} received count: ${receivedCycleTracker[cycle.counter]['received']}`)}else{if(!validateCycleData(cycle)){nestedCountersInstance.countEvent('collectCycleData','cycle_data_validation_failed_'+cycle.mode,1)Logger.mainLogger.warn(`collectCycleData: Cycle data validation failed for cycle ${cycle.counter} with marker ${cycle.marker}`)ArchiverLogging.logDataSync({sourceArchiver: senderInfo,targetArchiver: config.ARCHIVER_IP,cycle: cycle.counter,dataType: 'CYCLE_RECORD',dataHash: cycle.marker,status: 'ERROR',
operationId,metrics: {duration: Date.now()-startTime,dataSize: StringUtils.safeStringify(cycle).length,},error: 'Cycle data validation failed',})continue}nestedCountersInstance.countEvent('collectCycleData','create_new_cycle_tracker_'+cycle.mode,1)Logger.mainLogger.debug(`collectCycleData: Creating new cycle tracker entry for cycle ${cycle.counter}`)constcycleEntry={}asReceivedCycleEntry&{received?: number;saved?: boolean}cycleEntry[cycle.marker]={cycleInfo: cycle,certSigners: newSet(receivedCertSigners),}cycleEntry.received=1cycleEntry.saved=falsereceivedCycleTracker[cycle.counter]=cycleEntry}if(config.VERBOSE)Logger.mainLogger.debug('Cycle received',cycle.counter,receivedCycleTracker[cycle.counter])if(NodeList.activeListByIdSorted.length===0){nestedCountersInstance.countEvent('collectCycleData','no_active_nodes_direct_process_'+cycle.mode,1)Logger.mainLogger.debug(`collectCycleData: No active nodes, processing cycle ${cycle.counter} directly`)processCycles([receivedCycleTracker[cycle.counter][cycle.marker].cycleInfoasP2PTypes.CycleCreatorTypes.CycleData])continue}constrequiredSenders=dataSenders&&dataSenders.size ? Math.ceil(dataSenders.size/2) : 1Logger.mainLogger.debug(`collectCycleData: Cycle ${cycle.counter} requires ${requiredSenders} senders, current count: ${receivedCycleTracker[cycle.counter]['received']}`)if(receivedCycleTracker[cycle.counter]['received']>=requiredSenders){nestedCountersInstance.countEvent('collectCycleData','enough_senders_process_'+cycle.mode,1)Logger.mainLogger.debug(`collectCycleData: Cycle ${cycle.counter} has enough senders, processing`)letbestScore=0letbestMarker=''letprevMarker=''if(cachedCycleRecords.length===0){updateCacheFromDB().then(()=>{if(cachedCycleRecords.length>0&&cycle.counter-cachedCycleRecords[0].counter>1){Logger.mainLogger.debug(`updateCacheFromDB: No previous marker found for cycle ${cycle.counter}`)}processCycleWithPrevMarker()}).catch((error)=>{Logger.mainLogger.error(`updateCacheFromDB: Error updating cache from db: ${error}`)})}else{processCycleWithPrevMarker()}functionprocessCycleWithPrevMarker(){if(cachedCycleRecords.length>0&&cycle.counter-cachedCycleRecords[0].counter===1){prevMarker=cachedCycleRecords[0].markerLogger.mainLogger.debug(`collectCycleData: Previous marker for scoring: ${prevMarker}`)}else{Logger.mainLogger.debug(`collectCycleData: No previous marker found for cycle ${cycle.counter}`)return}constmarkers=Object.entries(receivedCycleTracker[cycle.counter]).filter(([key])=>key!=='saved'&&key!=='received').map(([,value])=>value)Logger.mainLogger.debug(`collectCycleData: Found ${markers.length} different markers for cycle ${cycle.counter}`)for(constmarkerofmarkers){constscores=[]for(constsignerofmarker['certSigners']){constscore=scoreCert(signerasstring,prevMarker)scores.push(score)Logger.mainLogger.debug(`collectCycleData: Cert from ${signer} scored ${score}`)}constsum=scores.sort((a,b)=>b-a).slice(0,3).reduce((sum,score)=>(sum+=score),0)Logger.mainLogger.debug(`collectCycleData: Marker ${marker['cycleInfo'].marker} scored ${sum}`)if(sum>bestScore){bestScore=sumbestMarker=marker['cycleInfo'].markerLogger.mainLogger.debug(`collectCycleData: New best marker: ${bestMarker} with score ${bestScore}`)}}Logger.mainLogger.debug(`collectCycleData: Processing cycle ${cycle.counter} with best marker ${bestMarker}, score: ${bestScore}`)processCycles([receivedCycleTracker[cycle.counter][bestMarker].cycleInfoasP2PTypes.CycleCreatorTypes.CycleData])receivedCycleTracker[cycle.counter]['saved']=truenestedCountersInstance.countEvent('collectCycleData','cycle_processed_successfully_'+cycle.mode,1)ArchiverLogging.logDataSync({sourceArchiver: senderInfo,targetArchiver: config.ARCHIVER_IP,cycle: cycle.counter,dataType: 'CYCLE_RECORD',dataHash: bestMarker,status: 'COMPLETE',
operationId,metrics: {duration: Date.now()-startTime,dataSize: StringUtils.safeStringify(receivedCycleTracker[cycle.counter][bestMarker].cycleInfo).length,},})}}}if(Object.keys(receivedCycleTracker).length>maxCyclesInCycleTracker){nestedCountersInstance.countEvent('collectCycleData','cleanup_old_cycles',1)Logger.mainLogger.debug(`collectCycleData: Cleaning up old cycles, current count: ${Object.keys(receivedCycleTracker).length}`)for(constcounterofObject.keys(receivedCycleTracker)){if(parseInt(counter)<getCurrentCycleCounter()-maxCyclesInCycleTracker){lettotalTimes=receivedCycleTracker[counter]['received']letlogCycle=falseconstmarkers=Object.entries(receivedCycleTracker[counter]).filter(([key])=>key!=='saved'&&key!=='received').map(([,value])=>value)if(markers.length>1){logCycle=truenestedCountersInstance.countEvent('collectCycleData','multiple_markers_for_cycle',1)}for(constmarkerofmarkers){Logger.mainLogger.debug('Cycle',counter,marker,logCycle ? StringUtils.safeStringify([...receivedCycleTracker[counter][marker]['certSigners']]) : '',logCycle ? receivedCycleTracker[counter][marker] : '')}if(logCycle)Logger.mainLogger.debug(`Cycle ${counter} has ${markers.length} different markers!`)Logger.mainLogger.debug(`Received ${totalTimes} times for cycle counter ${counter}`)deletereceivedCycleTracker[counter]}}}}
Several sync and download functions (e.g., syncCyclesAndNodeList, syncReceiptsByCycle, downloadOldCycles) rely on throwing or logging errors but may not always propagate failures or handle partial data gracefully. Review error handling to ensure partial syncs do not leave the system in an inconsistent state.
exportasyncfunctionsyncCyclesAndNodeList(lastStoredCycleCount=0): Promise<void>{Logger.mainLogger.debug('Getting newest cycle...')constcycleToSyncTo=awaitgetNewestCycleFromArchivers()Logger.mainLogger.debug('cycleToSyncTo',cycleToSyncTo)Logger.mainLogger.debug(`Syncing till cycle ${cycleToSyncTo.counter}...`)constcyclesToGet=2*Math.floor(Math.sqrt(cycleToSyncTo.active))+2Logger.mainLogger.debug(`Cycles to get is ${cyclesToGet}`)constCycleChain=[]constsquasher=newChangeSquasher()CycleChain.unshift(cycleToSyncTo)squasher.addChange(parse(CycleChain[0]))do{letend: number=CycleChain[0].counter-1letstart: number=end-cyclesToGetif(start<0)start=0if(end<start)end=startLogger.mainLogger.debug(`Getting cycles ${start} - ${end}...`)constprevCycles=awaitfetchCycleRecords(start,end)if(prevCycles.length<1)thrownewError('Got empty previous cycles')prevCycles.sort((a,b)=>(a.counter>b.counter ? -1 : 1))letprepended=0for(constprevCycleofprevCycles){if(validateCycle(prevCycle,CycleChain[0])===false){Logger.mainLogger.error(`Record ${prevCycle.counter} failed validation`)break}CycleChain.unshift(prevCycle)squasher.addChange(parse(prevCycle))prepended++if(squasher.final.updated.length>=activeNodeCount(cycleToSyncTo)&&squasher.final.added.length>=totalNodeCount(cycleToSyncTo)){break}}Logger.mainLogger.debug(`Got ${squasher.final.updated.length} active nodes, need ${activeNodeCount(cycleToSyncTo)}`)Logger.mainLogger.debug(`Got ${squasher.final.added.length} total nodes, need ${totalNodeCount(cycleToSyncTo)}`)if(squasher.final.added.length<totalNodeCount(cycleToSyncTo))Logger.mainLogger.debug('Short on nodes. Need to get more cycles. Cycle:'+cycleToSyncTo.counter)if(prepended<1)thrownewError('Unable to prepend any previous cycles')}while(squasher.final.updated.length<activeNodeCount(cycleToSyncTo)||squasher.final.added.length<totalNodeCount(cycleToSyncTo))applyNodeListChange(squasher.final)Logger.mainLogger.debug('NodeList after sync',NodeList.getActiveList())for(leti=0;i<CycleChain.length;i++){constrecord=CycleChain[i]Cycles.CycleChain.set(record.counter,{ ...record})if(i===CycleChain.length-1)awaitstoreCycleData(CycleChain)Cycles.setCurrentCycleCounter(record.counter)Cycles.setCurrentCycleMarker(record.marker)}Logger.mainLogger.debug('Cycle chain is synced. Size of CycleChain',Cycles.CycleChain.size)letendCycle=CycleChain[0].counter-1Logger.mainLogger.debug('endCycle counter',endCycle,'lastStoredCycleCount',lastStoredCycleCount)if(endCycle>lastStoredCycleCount){Logger.mainLogger.debug(`Downloading old cycles from cycles ${lastStoredCycleCount} to cycle ${endCycle}!`)}letsavedCycleRecord=CycleChain[0]while(endCycle>lastStoredCycleCount){letnextEnd: number=endCycle-MAX_CYCLES_PER_REQUESTif(nextEnd<0)nextEnd=0Logger.mainLogger.debug(`Getting cycles ${nextEnd} - ${endCycle} ...`)constprevCycles=awaitfetchCycleRecords(nextEnd,endCycle)if(!prevCycles||prevCycles.length<1)thrownewError('Got empty previous cycles')prevCycles.sort((a,b)=>(a.counter>b.counter ? -1 : 1))constcombineCycles=[]for(constprevCycleofprevCycles){if(validateCycle(prevCycle,savedCycleRecord)===false){Logger.mainLogger.error(`Record ${prevCycle.counter} failed validation`)Logger.mainLogger.debug('fail',prevCycle,savedCycleRecord)break}savedCycleRecord=prevCyclecombineCycles.push(prevCycle)}awaitstoreCycleData(combineCycles)endCycle=nextEnd-1}}exportasyncfunctionsyncCyclesAndNodeListV2(activeArchivers: State.ArchiverNodeInfo[],lastStoredCycleCount=0): Promise<boolean>{Logger.mainLogger.debug('Syncing validators and latest cycle...')constsyncResult=awaitsyncV2(activeArchivers)letcycleToSyncTo: P2PTypes.CycleCreatorTypes.CycleDataif(syncResult.isOk()){cycleToSyncTo=syncResult.value}else{throwsyncResult.error}Logger.mainLogger.debug('cycleToSyncTo',cycleToSyncTo)Logger.mainLogger.debug(`Syncing till cycle ${cycleToSyncTo.counter}...`)awaitprocessCycles([cycleToSyncTo])awaitdownloadOldCycles(cycleToSyncTo,lastStoredCycleCount)returntrue}exportasyncfunctionsyncCyclesBetweenCycles(lastStoredCycle=0,cycleToSyncTo=0): Promise<boolean>{constMAX_RETRIES=3letretryCount=0letstartCycle=lastStoredCycleletendCycle=startCycle+MAX_CYCLES_PER_REQUESTwhile(cycleToSyncTo>startCycle){if(endCycle>cycleToSyncTo)endCycle=cycleToSyncToLogger.mainLogger.debug(`Downloading cycles from ${startCycle} to ${endCycle}`)letsuccess=falseretryCount=0while(!success&&retryCount<MAX_RETRIES){constres=(awaitqueryFromArchivers(RequestDataType.CYCLE,{start: startCycle,end: endCycle,},QUERY_TIMEOUT_MAX))asArchiverCycleResponseif(res&&res.cycleInfo){constcycles=res.cycleInfoasP2PTypes.CycleCreatorTypes.CycleData[]Logger.mainLogger.debug(`Downloaded cycles`,cycles.length)letvalidCyclesCount=0for(constcycleofcycles){if(!validateCycleData(cycle)){Logger.mainLogger.debug('Found invalid cycle data')continue}awaitprocessCycles([cycle])validCyclesCount++}success=trueif(cycles.length<MAX_CYCLES_PER_REQUEST||validCyclesCount===0){startCycle+=Math.max(cycles.length,1)endCycle=startCycle+MAX_CYCLES_PER_REQUESTif(startCycle>=cycleToSyncTo){Logger.mainLogger.debug('Sync cycles completed!')returntrue}break}}else{Logger.mainLogger.debug(`Invalid cycle download response, attempt ${retryCount+1} of ${MAX_RETRIES}`)retryCount++if(retryCount>=MAX_RETRIES){Logger.mainLogger.error('Max retries reached for cycle download')returnfalse}}}if(success){startCycle=endCycle+1endCycle+=MAX_CYCLES_PER_REQUEST}}returntrue}exportasyncfunctionsyncReceipts(): Promise<void>{constMAX_RETRIES=3letretryCount=0constlastUpdatedCycle=getLastUpdatedCycle()Logger.mainLogger.debug(`[syncReceipts] Last updated cycle from tracker: ${lastUpdatedCycle}`)letstartCycle=0if(lastUpdatedCycle>0){Logger.mainLogger.info(`[syncReceipts] Starting receipt sync from last updated cycle: ${lastUpdatedCycle}`)startCycle=Math.max(lastUpdatedCycle-config.checkpoint.syncCycleBuffer,0)awaitsyncReceiptsByCycle(startCycle)return}letresponse: ArchiverTotalDataResponse=awaitgetTotalDataFromArchivers()if(!response||response.totalReceipts<0){return}let{ totalReceipts }=responseif(totalReceipts<1)returnletcomplete=falseletstart=0letend=start+MAX_RECEIPTS_PER_REQUESTwhile(!complete){if(end>=totalReceipts){response=awaitgetTotalDataFromArchivers()if(response&&response.totalReceipts>0){if(response.totalReceipts>totalReceipts)totalReceipts=response.totalReceiptsLogger.mainLogger.debug('totalReceiptsToSync',totalReceipts)}}Logger.mainLogger.debug(`Downloading receipts from ${start} to ${end}`)letsuccess=falseretryCount=0while(!success&&retryCount<MAX_RETRIES){constres=(awaitqueryFromArchivers(RequestDataType.RECEIPT,{start: start,end: end,},QUERY_TIMEOUT_MAX))asArchiverReceiptResponseif(res&&res.receipts){constdownloadedReceipts=res.receiptsasReceiptDB.Receipt[]Logger.mainLogger.debug(`Downloaded receipts`,downloadedReceipts.length)awaitstoreReceiptData(downloadedReceipts,'',false,false,true)success=trueif(downloadedReceipts.length<MAX_RECEIPTS_PER_REQUEST){start+=downloadedReceipts.lengthend=start+MAX_RECEIPTS_PER_REQUESTresponse=awaitgetTotalDataFromArchivers()if(response&&response.totalReceipts>0){if(response.totalReceipts>totalReceipts)totalReceipts=response.totalReceiptsif(start>=totalReceipts){complete=trueLogger.mainLogger.debug('Download receipts completed')}}}}else{Logger.mainLogger.debug(`Invalid download response, attempt ${retryCount+1} of ${MAX_RETRIES}`)retryCount++if(retryCount>=MAX_RETRIES){Logger.mainLogger.error('Max retries reached for receipt download')start=end+1end+=MAX_RECEIPTS_PER_REQUESTif(start>=totalReceipts){complete=true}}}}if(success){start=end+1end+=MAX_RECEIPTS_PER_REQUEST}}Logger.mainLogger.debug('Sync receipts data completed!')}classArchiverSelector{privatearchivers: ArchiverWithRetries[]privatecurrentIndex: number=0privatereadonlymaxRetries: number=3constructor(){this.archivers=State.otherArchivers.map((archiver)=>({
archiver,retriesLeft: this.maxRetries,}))Utils.shuffleArray(this.archivers)}getCurrentArchiver(): State.ArchiverNodeInfo|null{if(this.currentIndex>=this.archivers.length){returnnull}returnthis.archivers[this.currentIndex].archiver}markCurrentArchiverFailed(): State.ArchiverNodeInfo|null{if(this.currentIndex>=this.archivers.length){returnnull}this.archivers[this.currentIndex].retriesLeft--if(this.archivers[this.currentIndex].retriesLeft<=0){this.currentIndex++}returnthis.getCurrentArchiver()}hasMoreArchivers(): boolean{returnthis.currentIndex<this.archivers.length}}exportasyncfunctionsyncReceiptsByCycle(lastStoredReceiptCycle=0,cycleToSyncTo=0): Promise<boolean>{if(lastStoredReceiptCycle===0){consttrackedCycle=getLastUpdatedCycle()if(trackedCycle>0){Logger.mainLogger.info(`[syncReceiptsByCycle] Using last updated cycle from tracker: ${trackedCycle}`)lastStoredReceiptCycle=Math.max(trackedCycle-config.checkpoint.syncCycleBuffer,0)}}lettotalCycles=cycleToSyncTolettotalReceipts=0if(cycleToSyncTo===0){constresponse: ArchiverTotalDataResponse=awaitgetTotalDataFromArchivers()if(!response||response.totalReceipts<0){returnfalse}totalCycles=response.totalCyclestotalReceipts=response.totalReceipts}letstartCycle=lastStoredReceiptCycleletendCycle=startCycle+MAX_BETWEEN_CYCLES_PER_REQUESTletreceiptsCountToSyncBetweenCycles=0letsavedReceiptsCountBetweenCycles=0lettotalSavedReceiptsCount=0letarchiverSelector=newArchiverSelector()while(true){if(endCycle>totalCycles){endCycle=totalCyclestotalSavedReceiptsCount=awaitReceiptDB.queryReceiptCount()}if(cycleToSyncTo>0){if(startCycle>cycleToSyncTo){Logger.mainLogger.debug(`Sync receipts data completed!`)returntrue}if(endCycle>cycleToSyncTo)endCycle=cycleToSyncTo}Logger.mainLogger.debug(`Downloading receipts between cycles ${startCycle} to ${endCycle}`)constcurrentArchiver=archiverSelector.getCurrentArchiver()if(!currentArchiver){Logger.mainLogger.error('No more archivers available for syncing receipts')returnfalse}constres=(awaitP2P.getJson(`http://${currentArchiver.ip}:${currentArchiver.port}/receipt?startCycle=${startCycle}&endCycle=${endCycle}&type=tally`,QUERY_TIMEOUT_MAX))asArchiverReceiptResponseif(res&&res.receipts){constdownloadedReceiptsByCycles=res.receiptsasReceiptDB.ReceiptCount[]for(constreceiptDataofdownloadedReceiptsByCycles){receiptsCountToSyncBetweenCycles+=receiptData.receiptCount}Logger.mainLogger.debug(`Total receipts to sync between cycles ${startCycle} to ${endCycle}`,receiptsCountToSyncBetweenCycles)startCycle=endCycle+1endCycle+=MAX_BETWEEN_CYCLES_PER_REQUEST}else{Logger.mainLogger.error(`Failed to download receipts tally between cycles ${startCycle} to ${endCycle} from archiver ${currentArchiver.ip}:${currentArchiver.port}`)archiverSelector.markCurrentArchiverFailed()if(!archiverSelector.hasMoreArchivers()){Logger.mainLogger.error('All archivers failed for syncing receipts')returnfalse}continue}Logger.mainLogger.debug(`Downloading receipts between cycles ${lastStoredReceiptCycle} to ${startCycle-1}`,receiptsCountToSyncBetweenCycles)constMAX_RETRIES=3letstart=0letend=start+MAX_RECEIPTS_PER_REQUESTlethasMoreReceiptsToDownload=receiptsCountToSyncBetweenCycles>0while(hasMoreReceiptsToDownload){letsuccess=falseletretryCount=0while(!success&&retryCount<MAX_RETRIES){Logger.mainLogger.debug(`Downloading receipts from index ${start} to ${end}`)constres2=(awaitP2P.getJson(`http://${currentArchiver.ip}:${currentArchiver.port}/receipt?startCycle=${lastStoredReceiptCycle}&endCycle=${startCycle-1}&start=${start}&end=${end}`,QUERY_TIMEOUT_MAX))asArchiverReceiptResponseif(res2&&res2.receipts){constdownloadedReceipts=res2.receiptsasReceiptDB.Receipt[]Logger.mainLogger.debug(`Downloaded receipts`,downloadedReceipts.length)conststorageResult=awaitstoreReceiptData(downloadedReceipts,'',false,false,true)asStoredReceiptObjectsavedReceiptsCountBetweenCycles+=storageResult.receipts.lengthsuccess=trueif(downloadedReceipts.length===0||downloadedReceipts.length<MAX_RECEIPTS_PER_REQUEST){updateLastUpdatedCycle(startCycle-1)Logger.mainLogger.debug(`[syncReceiptsByCycle] Updated cycle tracker to cycle ${startCycle-1}`)hasMoreReceiptsToDownload=falsereceiptsCountToSyncBetweenCycles=0savedReceiptsCountBetweenCycles=0break}}else{Logger.mainLogger.debug(`Invalid download response, attempt ${retryCount+1} of ${MAX_RETRIES}`)retryCount++if(retryCount>=MAX_RETRIES){Logger.mainLogger.error('Max retries reached for receipt download')archiverSelector.markCurrentArchiverFailed()if(!archiverSelector.hasMoreArchivers()){Logger.mainLogger.error('All archivers failed for syncing receipts')returnfalse}}}}if(success){start=end+1end+=MAX_RECEIPTS_PER_REQUEST}}if(cycleToSyncTo===0&&totalSavedReceiptsCount>=totalReceipts){Logger.mainLogger.debug('Sync receipts data by cycle completed!')returntrue}}}exportasyncfunctionsyncCyclesAndTxsData(lastStoredCycle=0,cycleToSyncTo=0): Promise<void>{letresponse: ArchiverTotalDataResponse=awaitgetTotalDataFromArchivers()if(!response||response.totalCycles<0){return}let{ totalCycles, totalReceipts }=responseif(totalCycles<1)returnletcompleteForCycle=falseletstartCycle=lastStoredCycleletendCycle=startCycle+MAX_CYCLES_PER_REQUESTconstMAX_RETRIES=3letretryCount=0while(!completeForCycle||startCycle<endCycle){if(endCycle>=totalCycles){endCycle=totalCyclescompleteForCycle=trueresponse=awaitgetTotalDataFromArchivers()if(response&&response.totalCycles>0){if(response.totalCycles>totalCycles)totalCycles=response.totalCyclesif(response.totalReceipts>totalReceipts)totalReceipts=response.totalReceiptsLogger.mainLogger.debug('totalCyclesToSync',totalCycles,'totalReceiptsToSync',totalReceipts)}}if(!completeForCycle){Logger.mainLogger.debug(`Downloading cycles from ${startCycle} to ${endCycle}`)letsuccess=falseretryCount=0while(!success&&retryCount<MAX_RETRIES){constres=(awaitqueryFromArchivers(RequestDataType.CYCLE,{start: startCycle,end: endCycle,},QUERY_TIMEOUT_MAX))asArchiverCycleResponseif(res&&res.cycleInfo){constcycles=res.cycleInfoLogger.mainLogger.debug(`Downloaded cycles`,cycles.length)for(constcycleofcycles){if(!validateCycleData(cycle)){Logger.mainLogger.debug('Found invalid cycle data')continue}awaitprocessCycles([cycle])}success=trueconsthighestCycle=cycles.reduce((max,cycle)=>Math.max(max,cycle.counter),0)if(highestCycle>0){updateLastUpdatedCycle(highestCycle)Logger.mainLogger.debug(`[syncCyclesAndTxsData] Updated cycle tracker to cycle ${highestCycle}`)}if(cycles.length<MAX_CYCLES_PER_REQUEST){startCycle+=cycles.length+1endCycle+=cycles.length+MAX_CYCLES_PER_REQUEST}}else{Logger.mainLogger.debug(`Invalid cycle download response, attempt ${retryCount+1} of ${MAX_RETRIES}`)retryCount++if(retryCount>=MAX_RETRIES){Logger.mainLogger.error('Max retries reached for cycle download')}}}if(success){startCycle=endCycle+1endCycle+=MAX_CYCLES_PER_REQUEST}}}Logger.mainLogger.debug('Sync Cycle, Receipt & Original-Tx data completed!')}exportconstsyncCyclesAndTxsDataBetweenCycles=async(lastStoredCycle=0,cycleToSyncTo=0): Promise<void>=>{Logger.mainLogger.debug(`Syncing cycles and txs data between cycles ${lastStoredCycle} and ${cycleToSyncTo}`)awaitsyncCyclesBetweenCycles(lastStoredCycle,cycleToSyncTo)awaitsyncReceiptsByCycle(lastStoredCycle,cycleToSyncTo)}asyncfunctiondownloadOldCycles(cycleToSyncTo: P2PTypes.CycleCreatorTypes.CycleData,lastStoredCycleCount: number): Promise<void>{letendCycle=cycleToSyncTo.counter-1Logger.mainLogger.debug('endCycle counter',endCycle,'lastStoredCycleCount',lastStoredCycleCount)if(endCycle>lastStoredCycleCount){Logger.mainLogger.debug(`Downloading old cycles from cycles ${lastStoredCycleCount} to cycle ${endCycle}!`)}letsavedCycleRecord=cycleToSyncToconstMAX_RETRY_COUNT=3letretryCount=0while(endCycle>lastStoredCycleCount){letstartCycle: number=endCycle-MAX_CYCLES_PER_REQUESTif(startCycle<0)startCycle=0if(startCycle<lastStoredCycleCount)startCycle=lastStoredCycleCountLogger.mainLogger.debug(`Getting cycles ${startCycle} - ${endCycle} ...`)constres=(awaitqueryFromArchivers(RequestDataType.CYCLE,{start: startCycle,end: endCycle,},QUERY_TIMEOUT_MAX))asArchiverCycleResponseif(!res||!res.cycleInfo||!Array.isArray(res.cycleInfo)||res.cycleInfo.length===0){Logger.mainLogger.error(`Can't fetch data from cycle ${startCycle} to cycle ${endCycle} from archivers`)if(retryCount<MAX_RETRY_COUNT){retryCount++continue}else{endCycle=startCycle-1retryCount=0}}constprevCycles=res.cycleInfoasP2PTypes.CycleCreatorTypes.CycleData[]if(prevCycles)prevCycles.sort((a,b)=>(a.counter>b.counter ? -1 : 1))constcombineCycles: P2PTypes.CycleCreatorTypes.CycleData[]=[]for(constprevCycleofprevCycles){if(validateCycle(prevCycle,savedCycleRecord)===false){Logger.mainLogger.error(`Record ${prevCycle.counter} failed validation`)Logger.mainLogger.debug('fail',prevCycle,savedCycleRecord)}savedCycleRecord=prevCyclecombineCycles.push(prevCycle)}awaitstoreCycleData(combineCycles)endCycle=startCycle-1}}
The new type definitions (e.g., subscriptionCycleData, DataSender, response interfaces) are critical for data integrity. Ensure that all usages across the refactored modules are consistent and that type mismatches or missing fields are caught by TypeScript.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Type
enhancement
Description
Refactored large Data.ts into modular files for maintainability
Introduced new modules: dataSync, cycleData, nodeSubscription, etc.
Updated imports and function calls throughout codebase
Improved data synchronization, validation, and subscription logic
Changes walkthrough 📝
14 files
Core data synchronization logic extracted from Data.tsCycle data collection and validation logic modularizedUtility and network sync functions separated from Data.tsNode subscription and data sender management refactoredSocket client logic for data transfer modularizedData comparison utilities for receipts, cycles, and txsNetwork configuration sync and consensus radius logicType definitions for modular Data layerData request sending utilities for node communicationCombined accounts data management extracted from Data.tsUpdated to use new modular account data managementUpdated to use modular subscription and validation logicUpdated API routes to use modular Data layer functionsUpdated server startup to use new subscription logic2 files