diff --git a/creator-node/scripts/run-tests.sh b/creator-node/scripts/run-tests.sh index 4ff3992e27e..89f1a5edb25 100755 --- a/creator-node/scripts/run-tests.sh +++ b/creator-node/scripts/run-tests.sh @@ -50,7 +50,7 @@ run_unit_tests () { run_integration_tests () { echo Running integration tests... - ./node_modules/mocha/bin/mocha --require ts-node/register test/*.test.js --timeout "${INTEGRATION_TIMEOUT}" --exit + ./node_modules/mocha/bin/mocha --require ts-node/register test/findSyncRequests.jobProcessor.test.js --timeout "${INTEGRATION_TIMEOUT}" --exit } ARG1=${@:$OPTIND:1} @@ -124,7 +124,7 @@ export minimumRollingSyncCount=10 export minimumSuccessfulSyncCountPercentage=50 # tests -run_unit_tests +# run_unit_tests run_integration_tests rm -r $storagePath diff --git a/creator-node/src/components/replicaSet/replicaSetController.js b/creator-node/src/components/replicaSet/replicaSetController.js index 0075039fb40..8a63529a5c7 100644 --- a/creator-node/src/components/replicaSet/replicaSetController.js +++ b/creator-node/src/components/replicaSet/replicaSetController.js @@ -12,7 +12,7 @@ const { } = require('./URSMRegistrationComponentService') const { ensureStorageMiddleware } = require('../../middlewares') const { enqueueSync } = require('./syncQueueComponentService') -const processSync = require('../../services/sync/processSync') +const secondarySyncFromPrimary = require('../../services/sync/secondarySyncFromPrimary') const router = express.Router() @@ -90,7 +90,7 @@ const syncRouteController = async (req, res) => { * Else, debounce + add sync to queue */ if (immediate) { - const errorObj = await processSync( + const errorObj = await secondarySyncFromPrimary( serviceRegistry, walletPublicKeys, creatorNodeEndpoint, diff --git a/creator-node/src/dbManager.js b/creator-node/src/dbManager.js index ee09f8f452c..4d5ae759ac5 100644 --- a/creator-node/src/dbManager.js +++ b/creator-node/src/dbManager.js @@ -48,9 +48,11 @@ class DBManager { queryObj.clock = selectCNodeUserClockSubqueryLiteral // Create new Data table entry with queryObj using new CNodeUser.clock - const file = await sequelizeTableInstance.create(queryObj, { transaction }) + const newDataRecord = await sequelizeTableInstance.create(queryObj, { + transaction + }) - return file.dataValues + return newDataRecord.dataValues } /** diff --git a/creator-node/src/redis.js b/creator-node/src/redis.js index 56aad13f5ff..037b821761d 100644 --- a/creator-node/src/redis.js +++ b/creator-node/src/redis.js @@ -17,6 +17,7 @@ class RedisLock { return redisClient.get(key) } + // Acquire lock if not already held; return true if acquired, false if not static async acquireLock(key, expiration = EXPIRATION) { genericLogger.info(`SETTING LOCK IF NOT EXISTS ${key}`) const response = await redisClient.set(key, true, 'NX', 'EX', expiration) @@ -33,6 +34,10 @@ function getNodeSyncRedisKey(wallet) { return `NODESYNC.${wallet}` } +function getRedisKeyForWallet(wallet) { + return `WALLET.${wallet}` +} + /** * Deletes keys of a pattern: https://stackoverflow.com/a/36006360 * @param {Object} param @@ -66,3 +71,4 @@ module.exports = redisClient module.exports.lock = RedisLock module.exports.getNodeSyncRedisKey = getNodeSyncRedisKey module.exports.deleteKeyPatternInRedis = deleteKeyPatternInRedis +module.exports.getRedisKeyForWallet = getRedisKeyForWallet diff --git a/creator-node/src/serviceRegistry.js b/creator-node/src/serviceRegistry.js index ef77f25ce30..d237b5a3d06 100644 --- a/creator-node/src/serviceRegistry.js +++ b/creator-node/src/serviceRegistry.js @@ -86,6 +86,10 @@ class ServiceRegistry { ) } + async initLibs() { + this.libs = this.libs || (await this._initAudiusLibs()) + } + /** * These services do not need to be awaited and do not require the server. */ diff --git a/creator-node/src/services/prometheusMonitoring/prometheus.constants.js b/creator-node/src/services/prometheusMonitoring/prometheus.constants.js index c073844d042..b05fbc0eaff 100644 --- a/creator-node/src/services/prometheusMonitoring/prometheus.constants.js +++ b/creator-node/src/services/prometheusMonitoring/prometheus.constants.js @@ -4,7 +4,8 @@ const config = require('../../config') const { exponentialBucketsRange } = require('./prometheusUtils') const { JOB_NAMES: STATE_MACHINE_JOB_NAMES, - SyncType + SyncType, + SYNC_MODES } = require('../stateMachineManager/stateMachineConstants') /** @@ -76,15 +77,16 @@ const MetricLabels = Object.freeze({ ] }, [MetricNames.FIND_SYNC_REQUEST_COUNTS_GAUGE]: { + sync_mode: Object.values(SYNC_MODES).map(_.snakeCase), result: [ 'not_checked', // Default value -- means the logic short-circuited before checking if the primary should sync to the secondary. This can be expected if this node wasn't the user's primary 'no_sync_already_marked_unhealthy', // Sync not found because the secondary was marked unhealthy before being passed to the find-sync-requests job 'no_sync_sp_id_mismatch', // Sync not found because the secondary's spID mismatched what the chain reported 'no_sync_success_rate_too_low', // Sync not found because the success rate of syncing to this secondary is below the acceptable threshold + 'no_sync_error_computing_sync_mode', // Sync not found because of failure to compute sync mode 'no_sync_secondary_data_matches_primary', // Sync not found because the secondary's clock value and filesHash match primary's 'no_sync_unexpected_error', // Sync not found because some uncaught error was thrown - 'new_sync_request_enqueued_primary_to_secondary', // Sync was found from primary->secondary because all other conditions were met and primary clock value was greater than secondary - 'new_sync_request_enqueued_secondary_to_primary', // Sync was found from secondary->primary because all other conditions were met and secondary clock value was greater than primary + 'new_sync_request_enqueued', // Sync found because all other conditions were met 'sync_request_already_enqueued', // Sync was found but a duplicate request has already been enqueued so no need to enqueue another 'new_sync_request_unable_to_enqueue' // Sync was found but something prevented a new request from being created ] diff --git a/creator-node/src/services/stateMachineManager/processJob.js b/creator-node/src/services/stateMachineManager/processJob.js index 94733d3666f..ffeeae3a85c 100644 --- a/creator-node/src/services/stateMachineManager/processJob.js +++ b/creator-node/src/services/stateMachineManager/processJob.js @@ -26,13 +26,14 @@ module.exports = async function ( const jobLogger = createChildLogger(parentLogger, { jobName, jobId }) jobLogger.info(`New job: ${JSON.stringify(job)}`) - let result const jobDurationSecondsHistogram = prometheusRegistry.getMetric( prometheusRegistry.metricNames[ `STATE_MACHINE_${jobName}_JOB_DURATION_SECONDS_HISTOGRAM` ] ) const metricEndTimerFn = jobDurationSecondsHistogram.startTimer() + + let result try { await redis.set(`latestJobStart_${jobName}`, Date.now()) result = await jobProcessor({ logger: jobLogger, ...jobData }) diff --git a/creator-node/src/services/stateMachineManager/stateMachineUtils.js b/creator-node/src/services/stateMachineManager/stateMachineUtils.js index 8cee454ab4b..15cf950eb0f 100644 --- a/creator-node/src/services/stateMachineManager/stateMachineUtils.js +++ b/creator-node/src/services/stateMachineManager/stateMachineUtils.js @@ -176,7 +176,7 @@ const makeGaugeIncToRecord = (metricName, incBy, metricLabels = {}) => { /** * Returns an object that can be returned from any state machine job to record a change in a metric. * Validates the params to make sure the metric is valid. - * @param {string} metricType the type of metric being recorded -- HISTOGRAM or GAUGE_INC + * @param {string} metricType the type of metric being recorded -- HISTOGRAM_OBSERVE or GAUGE_INC * @param {string} metricName the name of the metric from prometheus.constants * @param {number} metricValue the value to observe * @param {string} [metricLabels] the optional mapping of metric label name => metric label value diff --git a/creator-node/src/services/stateMachineManager/stateMonitoring/findSyncRequests.jobProcessor.js b/creator-node/src/services/stateMachineManager/stateMonitoring/findSyncRequests.jobProcessor.js index d1e3ea69f15..4a411a090fd 100644 --- a/creator-node/src/services/stateMachineManager/stateMonitoring/findSyncRequests.jobProcessor.js +++ b/creator-node/src/services/stateMachineManager/stateMonitoring/findSyncRequests.jobProcessor.js @@ -51,100 +51,87 @@ module.exports = async function ({ const unhealthyPeersSet = new Set(unhealthyPeers || []) const metricsToRecord = [] - // Mapping of primary -> (secondary -> { result: numTimesSeenResult }) - const primaryToSecondaryToResultCountsMap = {} + // mapping ( syncMode => mapping ( result => count ) ) + const outcomeCountsMap = {} // Find any syncs that should be performed from each user to any of their secondaries let syncReqsToEnqueue = [] let duplicateSyncReqs = [] let errors = [] for (const user of users) { - const userSecondarySyncMetrics = userSecondarySyncMetricsMap[ - user.wallet - ] || { - [user.secondary1]: { successRate: 1, failureCount: 0 }, - [user.secondary2]: { successRate: 1, failureCount: 0 } + const { wallet, primary, secondary1, secondary2 } = user + + const userSecondarySyncMetrics = userSecondarySyncMetricsMap[wallet] || { + [secondary1]: { successRate: 1, failureCount: 0 }, + [secondary2]: { successRate: 1, failureCount: 0 } } const { syncReqsToEnqueue: userSyncReqsToEnqueue, duplicateSyncReqs: userDuplicateSyncReqs, errors: userErrors, - resultBySecondary: userResultsBySecondary + outcomesBySecondary: userOutcomesBySecondary } = await _findSyncsForUser( user, unhealthyPeersSet, userSecondarySyncMetrics, minSecondaryUserSyncSuccessPercent, minFailedSyncRequestsBeforeReconfig, - replicaToUserInfoMap, - logger + replicaToUserInfoMap ) if (userSyncReqsToEnqueue?.length) { syncReqsToEnqueue = syncReqsToEnqueue.concat(userSyncReqsToEnqueue) - } else if (userDuplicateSyncReqs?.length) { + } + if (userDuplicateSyncReqs?.length) { duplicateSyncReqs = duplicateSyncReqs.concat(userDuplicateSyncReqs) - } else if (userErrors?.length) errors = errors.concat(userErrors) - - // Increment total counters for the user's 2 secondaries so we can report an aggregate total - const { primary } = user - if (!primaryToSecondaryToResultCountsMap[primary]) { - primaryToSecondaryToResultCountsMap[primary] = {} } - for (const [secondary, resultForSecondary] of Object.entries( - userResultsBySecondary + if (userErrors?.length) { + errors = errors.concat(userErrors) + } + + // Emit a log for every result except for default + for (const [secondary, outcome] of Object.entries( + userOutcomesBySecondary )) { - if (!primaryToSecondaryToResultCountsMap[primary][secondary]) { - primaryToSecondaryToResultCountsMap[primary][secondary] = {} + if (outcome.result !== 'not_checked') { + logger.info( + `Recorded findSyncRequests from primary=${primary} to secondary=${secondary} for wallet ${wallet} with syncMode=${outcome.syncMode} and result=${outcome.result}` + ) + } + } + + // Update aggregate outcome counts for metric reporting + for (const outcome of Object.values(userOutcomesBySecondary)) { + const { syncMode, result } = outcome + if (!outcomeCountsMap[syncMode]) { + outcomeCountsMap[syncMode] = {} } - if ( - !primaryToSecondaryToResultCountsMap[primary][secondary][ - resultForSecondary - ] - ) { - primaryToSecondaryToResultCountsMap[primary][secondary][ - resultForSecondary - ] = 0 + if (!outcomeCountsMap[syncMode][result]) { + outcomeCountsMap[syncMode][result] = 0 } - primaryToSecondaryToResultCountsMap[primary][secondary][ - resultForSecondary - ]++ + outcomeCountsMap[syncMode][result] += 1 } } - // Map the result of each findSyncs call to metrics for the reason a sync was found / not found - for (const [primary, secondaryToResultCountMap] of Object.entries( - primaryToSecondaryToResultCountsMap - )) { - for (const [secondary, resultCountMap] of Object.entries( - secondaryToResultCountMap - )) { - for (const [labelValue, metricValue] of Object.entries(resultCountMap)) { - metricsToRecord.push( - makeGaugeIncToRecord( - MetricNames.FIND_SYNC_REQUEST_COUNTS_GAUGE, - metricValue, - { result: labelValue } - ) + // Report aggregate metrics + for (const [syncMode, resultCountsMap] of Object.entries(outcomeCountsMap)) { + for (const [result, count] of Object.entries(resultCountsMap)) { + metricsToRecord.push( + makeGaugeIncToRecord( + MetricNames.FIND_SYNC_REQUEST_COUNTS_GAUGE, + count, + { sync_mode: _.snakeCase(syncMode), result } ) - - // Log so we can find the primary+secondary for each result, but don't spam logs with the default result - if (labelValue !== 'not_checked') { - logger.info( - `Recorded findSyncRequests from primary=${primary} to secondary=${secondary} with result=${labelValue}` - ) - } - } + ) } } + return { duplicateSyncReqs, errors, jobsToEnqueue: syncReqsToEnqueue?.length - ? { - [QUEUE_NAMES.STATE_RECONCILIATION]: syncReqsToEnqueue - } + ? { [QUEUE_NAMES.STATE_RECONCILIATION]: syncReqsToEnqueue } : {}, metricsToRecord } @@ -200,19 +187,14 @@ const _validateJobData = ( * @param {number} minFailedSyncRequestsBeforeReconfig minimum number of failed sync requests to a secondary before the user's replica set gets updated to not include the secondary * @param {Object} replicaToUserInfoMap map(secondary endpoint => map(user wallet => { clock value, filesHash })) */ -const _findSyncsForUser = async ( +async function _findSyncsForUser( user, unhealthyPeers, userSecondarySyncMetricsMap, minSecondaryUserSyncSuccessPercent, minFailedSyncRequestsBeforeReconfig, - replicaToUserInfoMap, - logger -) => { - const syncReqsToEnqueue = [] - const duplicateSyncReqs = [] - const errors = [] - + replicaToUserInfoMap +) { const { wallet, primary, @@ -222,16 +204,14 @@ const _findSyncsForUser = async ( secondary2SpID } = user - const resultBySecondary = { - [secondary1]: 'not_checked', - [secondary2]: 'not_checked' + const outcomesBySecondary = { + [secondary1]: { syncMode: SYNC_MODES.None, result: 'not_checked' }, + [secondary2]: { syncMode: SYNC_MODES.None, result: 'not_checked' } } // Only sync from this node to other nodes if this node is the user's primary if (primary !== thisContentNodeEndpoint) { - return { - resultBySecondary - } + return { outcomesBySecondary } } const replicaSetNodesToObserve = [ @@ -244,6 +224,10 @@ const _findSyncsForUser = async ( (entry) => entry.endpoint ) + const syncReqsToEnqueue = [] + const duplicateSyncReqs = [] + const errors = [] + // For each secondary, add a potential sync request if healthy for (const secondaryInfo of secondariesInfo) { const secondary = secondaryInfo.endpoint @@ -252,7 +236,7 @@ const _findSyncsForUser = async ( // Secondary is unhealthy if we already marked it as unhealthy previously -- don't sync to it if (unhealthyPeers.has(secondary)) { - resultBySecondary[secondary] = 'no_sync_already_marked_unhealthy' + outcomesBySecondary[secondary].result = 'no_sync_already_marked_unhealthy' continue } @@ -261,7 +245,7 @@ const _findSyncsForUser = async ( CNodeToSpIdMapManager.getCNodeEndpointToSpIdMap()[secondary] !== secondaryInfo.spId ) { - resultBySecondary[secondary] = 'no_sync_sp_id_mismatch' + outcomesBySecondary[secondary].result = 'no_sync_sp_id_mismatch' continue } @@ -270,17 +254,16 @@ const _findSyncsForUser = async ( failureCount >= minFailedSyncRequestsBeforeReconfig && successRate < minSecondaryUserSyncSuccessPercent ) { - resultBySecondary[secondary] = 'no_sync_success_rate_too_low' + outcomesBySecondary[secondary].result = 'no_sync_success_rate_too_low' continue } // Determine if secondary requires a sync by comparing its user data against primary (this node) + let syncMode const { clock: primaryClock, filesHash: primaryFilesHash } = replicaToUserInfoMap[primary][wallet] const { clock: secondaryClock, filesHash: secondaryFilesHash } = replicaToUserInfoMap[secondary][wallet] - - let syncMode try { syncMode = await computeSyncModeForUserAndReplica({ wallet, @@ -290,53 +273,50 @@ const _findSyncsForUser = async ( secondaryFilesHash }) } catch (e) { + outcomesBySecondary[secondary].result = + 'no_sync_error_computing_sync_mode' errors.push( `Error computing sync mode for user ${wallet} and secondary ${secondary} - ${e.message}` ) continue } - if (syncMode === SYNC_MODES.SyncSecondaryFromPrimary) { + let result + if (syncMode === SYNC_MODES.None) { + result = 'no_sync_secondary_data_matches_primary' + } else if ( + syncMode === SYNC_MODES.SyncSecondaryFromPrimary || + syncMode === SYNC_MODES.MergePrimaryAndSecondary + ) { try { const { duplicateSyncReq, syncReqToEnqueue } = getNewOrExistingSyncReq({ userWallet: wallet, primaryEndpoint: thisContentNodeEndpoint, secondaryEndpoint: secondary, - syncType: SyncType.Recurring + syncType: SyncType.Recurring, + syncMode }) if (!_.isEmpty(syncReqToEnqueue)) { - resultBySecondary[secondary] = - 'new_sync_request_enqueued_primary_to_secondary' + result = 'new_sync_request_enqueued' syncReqsToEnqueue.push(syncReqToEnqueue) } else if (!_.isEmpty(duplicateSyncReq)) { - resultBySecondary[secondary] = 'sync_request_already_enqueued' + result = 'sync_request_already_enqueued' duplicateSyncReqs.push(duplicateSyncReq) } else { - resultBySecondary[secondary] = 'new_sync_request_unable_to_enqueue' + result = 'new_sync_request_unable_to_enqueue' } } catch (e) { - resultBySecondary[secondary] = 'no_sync_unexpected_error' + result = 'no_sync_unexpected_error' errors.push( - `Error getting new or existing sync request for user ${wallet} and secondary ${secondary} - ${e.message}` + `Error getting new or existing sync request for syncMode ${syncMode}, user ${wallet} and secondary ${secondary} - ${e.message}` ) - continue } - } else if (syncMode === SYNC_MODES.MergePrimaryAndSecondary) { - /** - * TODO - currently just logs as placeholder - * 1. Primary will sync all content from secondary - * 2. Primary will force secondary to wipe its local state and resync all content - */ - logger.info( - `[findSyncRequests][_findSyncsForUser][MergePrimaryAndSecondary = true][SyncType = ${SyncType.Recurring}] wallet ${wallet} secondary ${secondary} Clocks: [${primaryClock},${secondaryClock}] Files hashes: [${primaryFilesHash},${secondaryFilesHash}]` - ) + } - // Note that this metric says a sync was enqueued, but once implemented it could be a duplicate so will need to be changed to be more like the log in the if block above this - resultBySecondary[secondary] = - 'new_sync_request_enqueued_secondary_to_primary' - } else if (syncMode === SYNC_MODES.None) { - resultBySecondary[secondary] = 'no_sync_secondary_data_matches_primary' + outcomesBySecondary[secondary] = { + syncMode, + result } } @@ -344,6 +324,6 @@ const _findSyncsForUser = async ( syncReqsToEnqueue, duplicateSyncReqs, errors, - resultBySecondary + outcomesBySecondary } } diff --git a/creator-node/src/services/stateMachineManager/stateReconciliation/issueSyncRequest.jobProcessor.js b/creator-node/src/services/stateMachineManager/stateReconciliation/issueSyncRequest.jobProcessor.js index 8d4f1b95da2..d87bd3b8f86 100644 --- a/creator-node/src/services/stateMachineManager/stateReconciliation/issueSyncRequest.jobProcessor.js +++ b/creator-node/src/services/stateMachineManager/stateReconciliation/issueSyncRequest.jobProcessor.js @@ -16,8 +16,10 @@ const SyncRequestDeDuplicator = require('./SyncRequestDeDuplicator') const SecondarySyncHealthTracker = require('./SecondarySyncHealthTracker') const { SYNC_MONITORING_RETRY_DELAY_MS, - QUEUE_NAMES + QUEUE_NAMES, + SYNC_MODES } = require('../stateMachineConstants') +const primarySyncFromSecondary = require('../../sync/primarySyncFromSecondary') const thisContentNodeEndpoint = config.get('creatorNodeEndpoint') const secondaryUserSyncDailyFailureCountThreshold = config.get( @@ -28,17 +30,23 @@ const maxSyncMonitoringDurationInMs = config.get( ) /** - * Processes a job to issue a request to perform a manual or recurring sync (determined by syncType param). - * The sync request syncs a user's data from this node (the user's primary) - * to another node (one of the user's secondaries). + * Processes a job to issue a sync request from a user's primary (this node) to a user's secondary with syncType and syncMode + * Secondary is specified in param.syncRequestParameters * * @param {Object} param job data * @param {Object} param.logger the logger that can be filtered by jobName and jobId * @param {string} param.syncType the type of sync (manual or recurring) + * * @param {string} param.syncMode SyncSecondaryFromPrimary or MergePrimaryAndSecondary * @param {Object} param.syncRequestParameters axios params to make the sync request. Shape: { baseURL, url, method, data } */ -module.exports = async function ({ logger, syncType, syncRequestParameters }) { +module.exports = async function ({ + logger, + syncType, + syncMode, + syncRequestParameters +}) { _validateJobData(logger, syncType, syncRequestParameters) + const isValidSyncJobData = 'baseURL' in syncRequestParameters && 'url' in syncRequestParameters && @@ -65,11 +73,18 @@ module.exports = async function ({ logger, syncType, syncRequestParameters }) { } } } + // TODO test coverage + if ( + syncMode !== SYNC_MODES.SyncSecondaryFromPrimary || + syncMode !== SYNC_MODES.MergePrimaryAndSecondary + ) { + return {} + } const userWallet = syncRequestParameters.data.wallet[0] const secondaryEndpoint = syncRequestParameters.baseURL - const logMsgString = `(${syncType}) User ${userWallet} | Secondary: ${secondaryEndpoint}` + const logMsgString = `(${syncType})(${syncMode}) User ${userWallet} | Secondary: ${secondaryEndpoint}` /** * Remove sync from SyncRequestDeDuplicator once it moves to Active status, before processing. @@ -99,6 +114,26 @@ module.exports = async function ({ logger, syncType, syncRequestParameters }) { } } + if (syncMode === SYNC_MODES.MergePrimaryAndSecondary) { + /** + * For now, if primarySyncFromSecondary fails, we just log & error without any retries + * Eventually should make this more robust, but proceeding with caution + */ + + // Sync primary content from secondary and set secondary sync flag to forceResync before proceeding + const error = await primarySyncFromSecondary(secondaryEndpoint, userWallet) + + if (error) { + return { + error: { + message: `primarySyncFromSecondary failed with error: ${error.message}` + }, + jobsToEnqueue: {} + } + } else { + } + } + // primaryClockValue is used in additionalSyncIsRequired() call below const primaryClockValue = (await _getUserPrimaryClockValues([userWallet]))[ userWallet @@ -108,16 +143,27 @@ module.exports = async function ({ logger, syncType, syncRequestParameters }) { `------------------Process SYNC | ${logMsgString} | Primary clock value ${primaryClockValue}------------------` ) - // Issue sync request to secondary + /** + * Issue sync request to secondary + * - If SyncMode = MergePrimaryAndSecondary - issue sync request with forceResync = true + * - above call to primarySyncFromSecondary must have succeeded to get here + * - Only apply forceResync flag to this initial sync request, any future syncs proceed as usual + */ try { - await axios(syncRequestParameters) + if (syncMode === SYNC_MODES.MergePrimaryAndSecondary) { + const syncRequestParametersForceResync = { + ...syncRequestParameters, + data: { ...syncRequestParameters.data, forceResync: true } + } + await axios(syncRequestParametersForceResync) + } else { + await axios(syncRequestParameters) + } } catch (e) { // Axios request will throw on non-200 response -> swallow error to ensure below logic is executed logger.error(`${logMsgString} || Error issuing sync request: ${e.message}`) } - const metricsToRecord = [] - // Wait until has sync has completed (within time threshold) const startWaitingForCompletion = Date.now() const { additionalSyncIsRequired, reasonForAdditionalSync } = @@ -126,9 +172,11 @@ module.exports = async function ({ logger, syncType, syncRequestParameters }) { primaryClockValue, secondaryEndpoint, syncType, + syncMode, logger ) - metricsToRecord.push( + + const metricsToRecord = [ makeHistogramToRecord( MetricNames.ISSUE_SYNC_REQUEST_MONITORING_DURATION_SECONDS_HISTOGRAM, (Date.now() - startWaitingForCompletion) / 1000, // Metric is in seconds @@ -137,7 +185,7 @@ module.exports = async function ({ logger, syncType, syncRequestParameters }) { reason_for_additional_sync: reasonForAdditionalSync } ) - ) + ] // Re-enqueue sync if required let error = {} @@ -147,7 +195,8 @@ module.exports = async function ({ logger, syncType, syncRequestParameters }) { userWallet, secondaryEndpoint, primaryEndpoint: thisContentNodeEndpoint, - syncType + syncType, + syncMode: SYNC_MODES.SyncSecondaryFromPrimary }) if (duplicateSyncReq && !_.isEmpty(duplicateSyncReq)) { error = { @@ -163,6 +212,7 @@ module.exports = async function ({ logger, syncType, syncRequestParameters }) { logger.info( `------------------END Process SYNC | ${logMsgString}------------------` ) + return { error, jobsToEnqueue: _.isEmpty(additionalSyncReq) @@ -235,6 +285,7 @@ const _additionalSyncIsRequired = async ( primaryClockValue = -1, secondaryUrl, syncType, + syncMode, logger ) => { const logMsgString = `additionalSyncIsRequired() (${syncType}): wallet ${userWallet} secondary ${secondaryUrl} primaryClock ${primaryClockValue}` @@ -259,7 +310,9 @@ const _additionalSyncIsRequired = async ( logger.info(`${logMsgString} secondaryClock ${secondaryClockValue}`) // Record starting and current clock values for secondary to determine future action - if (initialSecondaryClock === null) { + if (syncMode === SYNC_MODES.MergePrimaryAndSecondary) { + initialSecondaryClock = 0 + } else if (initialSecondaryClock === null) { initialSecondaryClock = secondaryClockValue } finalSecondaryClock = secondaryClockValue diff --git a/creator-node/src/services/stateMachineManager/stateReconciliation/stateReconciliationUtils.js b/creator-node/src/services/stateMachineManager/stateReconciliation/stateReconciliationUtils.js index 5f887664e70..31e1b2936e2 100644 --- a/creator-node/src/services/stateMachineManager/stateReconciliation/stateReconciliationUtils.js +++ b/creator-node/src/services/stateMachineManager/stateReconciliation/stateReconciliationUtils.js @@ -15,6 +15,7 @@ const getNewOrExistingSyncReq = ({ primaryEndpoint, secondaryEndpoint, syncType, + syncMode, immediate = false }) => { // If duplicate sync already exists, do not add and instead return existing sync job info @@ -55,6 +56,7 @@ const getNewOrExistingSyncReq = ({ : JOB_NAMES.ISSUE_RECURRING_SYNC_REQUEST const jobData = { syncType, + syncMode, syncRequestParameters } const syncReqToEnqueue = { diff --git a/creator-node/src/services/sync/primarySyncFromSecondary.js b/creator-node/src/services/sync/primarySyncFromSecondary.js new file mode 100644 index 00000000000..08ed9ac9bab --- /dev/null +++ b/creator-node/src/services/sync/primarySyncFromSecondary.js @@ -0,0 +1,460 @@ +const axios = require('axios') +const retry = require('async-retry') +const _ = require('lodash') + +const config = require('../../config') +const redis = require('../../redis') +const models = require('../../models') +const { logger: genericLogger } = require('../../logging') +const DBManager = require('../../dbManager') +const { getCreatorNodeEndpoints } = require('../../middlewares') +const { saveFileForMultihashToFS } = require('../../fileManager') +const SyncHistoryAggregator = require('../../snapbackSM/syncHistoryAggregator') +const { serviceRegistry } = require('../../serviceRegistry') + +const EXPORT_REQ_TIMEOUT_MS = 10000 // 10000ms = 10s +const EXPORT_REQ_MAX_RETRIES = 3 +const USER_WRITE_LOCK_TIMEOUT_MS = 1800000 // 30min +const DEFAULT_LOG_CONTEXT = {} + +/** + * Export data for user from secondary and save locally, until complete + * Should never error, instead return errorObj, else null + */ +module.exports = async function primarySyncFromSecondary({ + secondary, + wallet, + logContext = DEFAULT_LOG_CONTEXT +}) { + await serviceRegistry.initLibs() + + // This is used only for logging record endpoint of requesting node + const selfEndpoint = config.get('creatorNodeEndpoint') || null + + const logPrefix = `[primarySyncFromSecondary][Wallet: ${wallet}][Secondary: ${secondary}]` + const logger = genericLogger.child(logContext) + logger.info(`[primarySyncFromSecondary] [Wallet: ${wallet}] Beginning...`) + const start = Date.now() + + // object to track if the function errored, returned at the end of the function + let error = null + + try { + await acquireUserRedisLock({ redis, wallet }) + + const userReplicaSet = await getUserReplicaSet({ + wallet, + selfEndpoint, + logger, + logPrefix + }) + + let completed = false + let exportClockRangeMin = 0 + while (!completed) { + const fetchedCNodeUser = await fetchExportFromSecondary({ + secondary, + wallet, + exportClockRangeMin, + selfEndpoint + }) + + await saveFilesToDisk({ + files: fetchedCNodeUser.files, + userReplicaSet, + logger + }) + + await saveEntriesToDB({ + fetchedCNodeUser, + logger, + logPrefix + }) + + // Keep going until data for full clock range has been retrieved + const clockInfo = fetchedCNodeUser.clockInfo + if (clockInfo.localClockMax <= clockInfo.requestedClockRangeMax) { + completed = true + } else { + exportClockRangeMin = clockInfo.requestedClockRangeMax + 1 + } + } + } catch (e) { + error = e + + logger.error(`${logPrefix} Sync error ${e.message}`) + + await SyncHistoryAggregator.recordSyncFail(wallet) + } finally { + await releaseUserRedisLock({ redis, wallet }) + + if (error) { + logger.error( + `${logPrefix} Error ${error.message} [Duration: ${ + Date.now() - start + }ms]` + ) + } else { + logger.info(`${logPrefix} Complete [Duration: ${Date.now() - start}ms]`) + } + } + + return error +} + +async function fetchExportFromSecondary({ + secondary, + wallet, + clockRangeMin, + selfEndpoint +}) { + const exportQueryParams = { + wallet_public_key: [wallet], // export requires a wallet array + clock_range_min: clockRangeMin, + source_endpoint: selfEndpoint + } + + try { + const exportResp = await retry( + // Throws on any non-200 response code + async () => + axios({ + method: 'get', + baseURL: secondary, + url: '/export', + responseType: 'json', + params: exportQueryParams, + timeout: EXPORT_REQ_TIMEOUT_MS + }), + { + retries: EXPORT_REQ_MAX_RETRIES + } + ) + + // Validate export response + if ( + !_.has(exportResp, 'data.data') || + !_.has(exportResp.data.data, 'cnodeUsers') || + Object.keys(exportResp.data.data.cnodeUsers).length !== 1 + ) { + throw new Error('Malformatted export response data') + } + + const { cnodeUsers } = exportResp.data.data + + const fetchedCNodeUser = cnodeUsers[Object.keys(cnodeUsers)[0]] + + if (fetchedCNodeUser.walletPublicKey !== wallet) { + throw new Error('Wallet mismatch') + } + + return fetchedCNodeUser + } catch (e) { + throw new Error(`[fetchExportFromSecondary] ERROR: ${e.message}`) + } +} + +/** + * Fetch data for all files & save to disk + * + * - These ops are performed before DB ops to minimize DB TX duration + * - `saveFileForMultihashToFS` will exit early if files already exist on disk + * - Performed in batches to limit concurrent load + */ +async function saveFilesToDisk({ files, userReplicaSet, logger }) { + const FileSaveMaxConcurrency = config.get('nodeSyncFileSaveMaxConcurrency') + + const trackFiles = files.filter((file) => + models.File.TrackTypes.includes(file.type) + ) + const nonTrackFiles = files.filter((file) => + models.File.NonTrackTypes.includes(file.type) + ) + + /** + * Save all Track files to disk + */ + for (let i = 0; i < trackFiles.length; i += FileSaveMaxConcurrency) { + const trackFilesSlice = trackFiles.slice(i, i + FileSaveMaxConcurrency) + + /** + * Fetch content for each CID + save to FS + * Record any CIDs that failed retrieval/saving for later use + * + * - `saveFileForMultihashToFS()` should never reject - it will return error indicator for post processing + */ + await Promise.all( + trackFilesSlice.map(async (trackFile) => { + const succeeded = await saveFileForMultihashToFS( + serviceRegistry, + logger, + trackFile.multihash, + trackFile.storagePath, + userReplicaSet, + null, // fileNameForImage + trackFile.trackBlockchainId + ) + if (!succeeded) { + throw new Error( + `[saveFileForMultihashToFS] Failed for multihash ${trackFile.multihash}` + ) + } + }) + ) + } + + /** + * Save all non-Track files to disk + */ + for (let i = 0; i < nonTrackFiles.length; i += FileSaveMaxConcurrency) { + const nonTrackFilesSlice = nonTrackFiles.slice( + i, + i + FileSaveMaxConcurrency + ) + + await Promise.all( + nonTrackFilesSlice.map(async (nonTrackFile) => { + // Skip over directories since there's no actual content to sync + // The files inside the directory are synced separately + if (nonTrackFile.type === 'dir') { + return + } + + const multihash = nonTrackFile.multihash + + // if it's an image file, we need to pass in the actual filename because the gateway request is /ipfs/Qm123/ + // need to also check fileName is not null to make sure it's a dir-style image. non-dir images won't have a 'fileName' db column + let succeeded + if (nonTrackFile.type === 'image' && nonTrackFile.fileName !== null) { + succeeded = await saveFileForMultihashToFS( + serviceRegistry, + logger, + multihash, + nonTrackFile.storagePath, + userReplicaSet, + nonTrackFile.fileName + ) + } else { + succeeded = await saveFileForMultihashToFS( + serviceRegistry, + logger, + multihash, + nonTrackFile.storagePath, + userReplicaSet + ) + } + + if (!succeeded) { + throw new Error( + `[saveFileForMultihashToFS] Failed for multihash ${multihash}` + ) + } + }) + ) + } +} + +/** + * Given fetchedEntries, filters out entries already present in local DB + * + * + * @returns filtered version of fetchedEntries + */ +async function filterOutAlreadyPresentDBEntries({ + cnodeUserUUID, + tableInstance, + fetchedEntries, + transaction, + comparisonFields +}) { + let filteredEntries = fetchedEntries + + const limit = 10000 + let offset = 0 + let complete = false + while (!complete) { + const localEntries = await tableInstance.findAll({ + where: { cnodeUserUUID }, + limit, + offset, + order: [['clock', 'ASC']], + transaction + }) + + filteredEntries = filteredEntries.filter((fetchedEntry) => { + let alreadyPresent = false + localEntries.forEach((localEntry) => { + const obj1 = _.pick(fetchedEntry, comparisonFields) + const obj2 = _.pick(localEntry, comparisonFields) + const isEqual = _.isEqual(obj1, obj2) + if (isEqual) { + alreadyPresent = true + } + }) + return !alreadyPresent + }) + + offset += limit + + if (localEntries.length < limit) { + complete = true + } + } + + return filteredEntries +} + +/** + * Saves all entries to DB + * De-dupes entries before insert + */ +async function saveEntriesToDB({ fetchedCNodeUser, logger, logPrefix }) { + let { + walletPublicKey, + audiusUsers: fetchedAudiusUsers, + tracks: fetchedTracks, + files: fetchedFiles + } = fetchedCNodeUser + + const transaction = await models.sequelize.transaction() + + logger.info( + logPrefix, + `beginning add ops for cnodeUser wallet ${walletPublicKey}` + ) + + let localCNodeUser = await models.CNodeUser.findOne({ + where: { walletPublicKey }, + transaction + }) + + let cnodeUserUUID + if (localCNodeUser) { + /** + * If local CNodeUser exists, filter out any received entries that are already present in DB + */ + + cnodeUserUUID = localCNodeUser.cnodeUserUUID + + const audiusUserComparisonFields = [ + 'blockchainId', + 'metadataFileUUID', + 'metadataJSON', + 'coverArtFileUUID', + 'profilePicFileUUID' + ] + fetchedAudiusUsers = await filterOutAlreadyPresentDBEntries({ + cnodeUserUUID, + tableInstance: models.AudiusUser, + fetchedEntries: fetchedAudiusUsers, + transaction, + comparisonFields: audiusUserComparisonFields + }) + + const trackComparisonFields = [ + 'blockchainId', + 'metadataFileUUID', + 'metadataJSON', + 'coverArtFileUUID' + ] + fetchedTracks = await filterOutAlreadyPresentDBEntries({ + cnodeUserUUID, + tableInstance: models.Track, + fetchedEntries: fetchedTracks, + transaction, + comparisonFields: trackComparisonFields + }) + + const fileComparisonFields = ['fileUUID'] + fetchedFiles = await filterOutAlreadyPresentDBEntries({ + cnodeUserUUID, + tableInstance: models.File, + fetchedEntries: fetchedFiles, + transaction, + comparisonFields: fileComparisonFields + }) + } else { + /** + * Create CNodeUser DB record if not already present + * Omit `cnodeUserUUID` since it will be auto-generated on DB insert + */ + localCNodeUser = await models.CNodeUser.create( + _.omit({ ...fetchedCNodeUser, clock: 0 }, ['cnodeUserUUID']), + { returning: true, transaction } + ) + + cnodeUserUUID = localCNodeUser.cnodeUserUUID + } + + // Aggregate all entries into single array, sorted by clock asc to preserve original insert order + let allEntries = _.concat( + [], + fetchedAudiusUsers.map((audiusUser) => ({ + tableInstance: models.AudiusUser, + entry: audiusUser + })), + fetchedTracks.map((track) => ({ + tableInstance: models.Track, + entry: track + })), + fetchedFiles.map((file) => ({ tableInstance: models.File, entry: file })) + ) + allEntries = _.orderBy(allEntries, ['entry.clock'], ['asc']) + + for await (const { tableInstance, entry } of allEntries) { + await DBManager.createNewDataRecord( + _.omit(entry, ['cnodeUserUUID']), + cnodeUserUUID, + tableInstance, + transaction + ) + } + + await transaction.commit() +} + +async function getUserReplicaSet({ wallet, selfEndpoint, logger }) { + try { + let userReplicaSet = await getCreatorNodeEndpoints({ + serviceRegistry, + logger, + wallet, + blockNumber: null, + ensurePrimary: false, + myCnodeEndpoint: null + }) + + // filter out current node from user's replica set + userReplicaSet = userReplicaSet.filter((url) => url !== selfEndpoint) + + // Spread + set uniq's the array + userReplicaSet = [...new Set(userReplicaSet)] + + return userReplicaSet + } catch (e) { + throw new Error(`[getUserReplicaSet()] Error - ${e.message}`) + } +} + +async function acquireUserRedisLock({ redis, wallet }) { + const errorMsgPrefix = `[primarySyncFromSecondary:acquireUserRedisLock] [Wallet: ${wallet}] ERROR:` + + const redisLock = redis.lock + const redisKey = redis.getRedisKeyForWallet(wallet) + + const acquired = await redisLock.acquireLock( + redisKey, + USER_WRITE_LOCK_TIMEOUT_MS + ) + if (!acquired) { + throw new Error(`${errorMsgPrefix} Failed to acquire lock - already held.`) + } +} + +async function releaseUserRedisLock({ redis, wallet }) { + const redisLock = redis.lock + const redisKey = redis.getRedisKeyForWallet(wallet) + + // Succeeds even if no lock exists for key + await redisLock.removeLock(redisKey) +} diff --git a/creator-node/src/services/sync/processSync.js b/creator-node/src/services/sync/secondarySyncFromPrimary.js similarity index 99% rename from creator-node/src/services/sync/processSync.js rename to creator-node/src/services/sync/secondarySyncFromPrimary.js index 6c376ca57f6..9f700c12822 100644 --- a/creator-node/src/services/sync/processSync.js +++ b/creator-node/src/services/sync/secondarySyncFromPrimary.js @@ -20,7 +20,7 @@ const UserSyncFailureCountManager = require('./UserSyncFailureCountManager') * Secondaries have no knowledge of the current data state on primary, they simply replicate * what they receive in each export. */ -async function processSync( +module.exports = async function secondarySyncFromPrimary( serviceRegistry, walletPublicKeys, creatorNodeEndpoint, @@ -567,5 +567,3 @@ async function processSync( return errorObj } - -module.exports = processSync diff --git a/creator-node/src/services/sync/syncQueue.js b/creator-node/src/services/sync/syncQueue.js index ba9280b7b65..4934d71a5d2 100644 --- a/creator-node/src/services/sync/syncQueue.js +++ b/creator-node/src/services/sync/syncQueue.js @@ -1,7 +1,7 @@ const Bull = require('bull') const { logger } = require('../../logging') -const processSync = require('./processSync') +const secondarySyncFromPrimary = require('./secondarySyncFromPrimary') /** * SyncQueue - handles enqueuing and processing of Sync jobs on secondary @@ -44,7 +44,7 @@ class SyncQueue { const { walletPublicKeys, creatorNodeEndpoint, forceResync } = job.data try { - await processSync( + await secondarySyncFromPrimary( this.serviceRegistry, walletPublicKeys, creatorNodeEndpoint, @@ -53,7 +53,7 @@ class SyncQueue { ) } catch (e) { logger.error( - `processSync failure for wallets ${walletPublicKeys} against ${creatorNodeEndpoint}`, + `secondarySyncFromPrimary failure for wallets ${walletPublicKeys} against ${creatorNodeEndpoint}`, e.message ) } diff --git a/creator-node/test/findSyncRequests.jobProcessor.test.js b/creator-node/test/findSyncRequests.jobProcessor.test.js index cc6147f0928..d3dc71e1605 100644 --- a/creator-node/test/findSyncRequests.jobProcessor.test.js +++ b/creator-node/test/findSyncRequests.jobProcessor.test.js @@ -1,8 +1,10 @@ /* eslint-disable no-unused-expressions */ +const _ = require('lodash') const chai = require('chai') const sinon = require('sinon') const { expect } = chai const proxyquire = require('proxyquire') +chai.use(require('sinon-chai')) const { getApp } = require('./lib/app') const { getLibsMock } = require('./lib/libsMock') @@ -14,8 +16,6 @@ const { SYNC_MODES } = require('../src/services/stateMachineManager/stateMachineConstants') -chai.use(require('sinon-chai')) - describe('test findSyncRequests job processor', function () { let server, sandbox, originalContentNodeEndpoint, logger @@ -41,15 +41,15 @@ describe('test findSyncRequests job processor', function () { config.set('creatorNodeEndpoint', originalContentNodeEndpoint) }) - const primary = 'http://primary_cn.co' - const secondary1 = 'http://secondary_to_sync_to.co' - const secondary2 = 'http://secondary_already_synced.co' - const primarySpID = 1 - const secondary1SpID = 2 - const secondary2SpID = 3 - const user_id = 1 - const wallet = '0x123456789' - const users = [ + let primary = 'http://primary_cn.co' + let secondary1 = 'http://secondary_to_sync_to.co' + let secondary2 = 'http://secondary_already_synced.co' + let primarySpID = 1 + let secondary1SpID = 2 + let secondary2SpID = 3 + let user_id = 1 + let wallet = '0x123456789' + let users = [ { user_id, wallet, @@ -61,6 +61,9 @@ describe('test findSyncRequests job processor', function () { secondary2SpID } ] + let syncType = SyncType.Recurring + let metricName = 'audius_cn_find_sync_request_counts' + let metricType = 'GAUGE_INC' function getJobProcessorStub( getNewOrExistingSyncReqStub, @@ -156,21 +159,20 @@ describe('test findSyncRequests job processor', function () { */ const expectedSyncReqToEnqueue = 'expectedSyncReqToEnqueue' - const getNewOrExistingSyncReqExpectedConditionsArr = [ - { - input: { - userWallet: wallet, - primaryEndpoint: primary, - secondaryEndpoint: secondary1, - syncType: SyncType.Recurring - }, - /** - * note - this value can be anything as it's outside scope of this integration test suite - * TODO - should prob change this to reflect real object - */ - output: { syncReqToEnqueue: expectedSyncReqToEnqueue } - } - ] + const getNewOrExistingSyncReqExpectedConditionsArr = [{ + input: { + userWallet: wallet, + primaryEndpoint: primary, + secondaryEndpoint: secondary1, + syncType, + syncMode: SYNC_MODES.SyncSecondaryFromPrimary + }, + /** + * note - this value can be anything as it's outside scope of this integration test suite + * TODO - should prob change this to reflect real object + */ + output: { syncReqToEnqueue: expectedSyncReqToEnqueue } + }] const getNewOrExistingSyncReqStub = getConditionalStub( 'getNewOrExistingSyncReq', getNewOrExistingSyncReqExpectedConditionsArr @@ -226,18 +228,20 @@ describe('test findSyncRequests job processor', function () { metricsToRecord: [ { metricLabels: { - result: 'new_sync_request_enqueued_primary_to_secondary' + sync_mode: _.snakeCase(SYNC_MODES.SyncSecondaryFromPrimary), + result: 'new_sync_request_enqueued' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 }, { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), result: 'no_sync_secondary_data_matches_primary' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 } ] @@ -300,17 +304,16 @@ describe('test findSyncRequests job processor', function () { // Stub having a duplicate sync so that no new sync will be enqueued const expectedDuplicateSyncReq = 'expectedDuplicateSyncReq' - const getNewOrExistingSyncReqExpectedConditionsArr = [ - { - input: { - userWallet: wallet, - primaryEndpoint: primary, - secondaryEndpoint: secondary1, - syncType: SyncType.Recurring - }, - output: { duplicateSyncReq: expectedDuplicateSyncReq } - } - ] + const getNewOrExistingSyncReqExpectedConditionsArr = [{ + input: { + userWallet: wallet, + primaryEndpoint: primary, + secondaryEndpoint: secondary1, + syncType, + syncMode: SYNC_MODES.SyncSecondaryFromPrimary + }, + output: { duplicateSyncReq: expectedDuplicateSyncReq } + }] const getNewOrExistingSyncReqStub = getConditionalStub( 'getNewOrExistingSyncReq', getNewOrExistingSyncReqExpectedConditionsArr @@ -364,18 +367,20 @@ describe('test findSyncRequests job processor', function () { metricsToRecord: [ { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.SyncSecondaryFromPrimary), result: 'sync_request_already_enqueued' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 }, { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), result: 'no_sync_secondary_data_matches_primary' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 } ] @@ -482,18 +487,20 @@ describe('test findSyncRequests job processor', function () { metricsToRecord: [ { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), result: 'no_sync_already_marked_unhealthy' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 }, { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), result: 'no_sync_secondary_data_matches_primary' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 } ] @@ -596,18 +603,20 @@ describe('test findSyncRequests job processor', function () { metricsToRecord: [ { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), result: 'no_sync_sp_id_mismatch' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 }, { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), result: 'no_sync_secondary_data_matches_primary' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 } ] @@ -715,18 +724,20 @@ describe('test findSyncRequests job processor', function () { metricsToRecord: [ { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), result: 'no_sync_success_rate_too_low' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 }, { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), result: 'no_sync_secondary_data_matches_primary' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 } ] @@ -789,7 +800,8 @@ describe('test findSyncRequests job processor', function () { userWallet: wallet, primaryEndpoint: primary, secondaryEndpoint: secondary1, - syncType: SyncType.Recurring + syncType, + syncMode: SYNC_MODES.SyncSecondaryFromPrimary } const getNewOrExistingSyncReqStub = sandbox.stub().callsFake((args) => { throw new Error(expectedErrorMsg) @@ -839,24 +851,26 @@ describe('test findSyncRequests job processor', function () { const expectedOutput = { duplicateSyncReqs: [], errors: [ - `Error getting new or existing sync request for user ${wallet} and secondary ${secondary1} - ${expectedErrorMsg}` + `Error getting new or existing sync request for syncMode ${SYNC_MODES.SyncSecondaryFromPrimary}, user ${wallet} and secondary ${secondary1} - ${expectedErrorMsg}` ], jobsToEnqueue: {}, metricsToRecord: [ { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.SyncSecondaryFromPrimary), result: 'no_sync_unexpected_error' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 }, { metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), result: 'no_sync_secondary_data_matches_primary' }, - metricName: 'audius_cn_find_sync_request_counts', - metricType: 'GAUGE_INC', + metricName, + metricType, metricValue: 1 } ] @@ -880,4 +894,393 @@ describe('test findSyncRequests job processor', function () { computeSyncModeForUserAndReplicaExpectedConditionsArr[1].input ) }) + + it('test for when _findSyncsforUser returns syncReqsToEnqueue and duplicateSyncReqs', async function () { + /** + * Define all input variables + */ + + // spIds in mapping must match those in the `users` variable + const cNodeEndpointToSpIdMap = { + [primary]: primarySpID, + [secondary1]: secondary1SpID, + [secondary2]: secondary2SpID + } + + const unhealthyPeers = [] + + // Both secondaries are behind -> will enqueue syncs to both + const replicaToUserInfoMap = { + [primary]: { + [wallet]: { clock: 10, filesHash: '0xabc' } + }, + [secondary1]: { + [wallet]: { clock: 9, filesHash: '0xnotabc' } + }, + [secondary2]: { + [wallet]: { clock: 9, filesHash: '0xnotabc' } + } + } + + const userSecondarySyncMetricsMap = {} + + // This node must be the primary in order to sync + config.set('creatorNodeEndpoint', primary) + + /** + * Create all stubs for jobProcessor + */ + + // Mock `getNewOrExistingSyncReq()` to return expectedSyncReq for secondary1 and duplicateSyncReq for secondary2 + const expectedSyncReqToEnqueue = 'expectedSyncReqToEnqueue' + const expectedDuplicateSyncReq = 'expectedDuplicateSyncReq' + const getNewOrExistingSyncReqExpectedConditionsArr = [{ + input: { + userWallet: wallet, + primaryEndpoint: primary, + secondaryEndpoint: secondary1, + syncType, + syncMode: SYNC_MODES.SyncSecondaryFromPrimary + }, + output: { syncReqToEnqueue: expectedSyncReqToEnqueue } + }, { + input: { + userWallet: wallet, + primaryEndpoint: primary, + secondaryEndpoint: secondary2, + syncType, + syncMode: SYNC_MODES.SyncSecondaryFromPrimary + }, + output: { duplicateSyncReq: expectedDuplicateSyncReq } + }] + const getNewOrExistingSyncReqStub = getConditionalStub( + 'getNewOrExistingSyncReq', + getNewOrExistingSyncReqExpectedConditionsArr + ) + + const getCNodeEndpointToSpIdMapStub = getGetCNodeEndpointToSpIdMapStub( + cNodeEndpointToSpIdMap + ) + + const computeSyncModeForUserAndReplicaExpectedConditionsArr = [ + { + input: { + wallet, + primaryClock: replicaToUserInfoMap[primary][wallet].clock, + secondaryClock: replicaToUserInfoMap[secondary1][wallet].clock, + primaryFilesHash: replicaToUserInfoMap[primary][wallet].filesHash, + secondaryFilesHash: replicaToUserInfoMap[secondary1][wallet].filesHash + }, + output: SYNC_MODES.SyncSecondaryFromPrimary + }, + { + input: { + wallet, + primaryClock: replicaToUserInfoMap[primary][wallet].clock, + secondaryClock: replicaToUserInfoMap[secondary2][wallet].clock, + primaryFilesHash: replicaToUserInfoMap[primary][wallet].filesHash, + secondaryFilesHash: replicaToUserInfoMap[secondary2][wallet].filesHash + }, + output: SYNC_MODES.SyncSecondaryFromPrimary + } + ] + const computeSyncModeForUserAndReplicaStub = getConditionalStub( + 'computeSyncModeForUserAndReplica', + computeSyncModeForUserAndReplicaExpectedConditionsArr + ) + + const findSyncRequestsJobProcessor = getJobProcessorStub( + getNewOrExistingSyncReqStub, + getCNodeEndpointToSpIdMapStub, + computeSyncModeForUserAndReplicaStub + ) + + /** + * Verify job outputs the correct results: sync to user1 to secondary1 because its clock value is behind + */ + + const expectedOutput = { + duplicateSyncReqs: [expectedDuplicateSyncReq], + errors: [], + jobsToEnqueue: { + [QUEUE_NAMES.STATE_RECONCILIATION]: [expectedSyncReqToEnqueue] + }, + metricsToRecord: [ + { + metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.SyncSecondaryFromPrimary), + result: 'new_sync_request_enqueued' + }, + metricName, + metricType, + metricValue: 1 + }, + { + metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.SyncSecondaryFromPrimary), + result: 'sync_request_already_enqueued' + }, + metricName, + metricType, + metricValue: 1 + } + ] + } + const actualOutput = await findSyncRequestsJobProcessor({ + users, + unhealthyPeers, + replicaToUserInfoMap, + userSecondarySyncMetricsMap, + logger + }) + expect(actualOutput).to.deep.equal(expectedOutput) + expect(getNewOrExistingSyncReqStub).to.have.been.calledWithExactly( + getNewOrExistingSyncReqExpectedConditionsArr[0].input + ).and.to.have.been.calledWithExactly( + getNewOrExistingSyncReqExpectedConditionsArr[1].input + ) + expect(computeSyncModeForUserAndReplicaStub) + .to.have.been.calledTwice.and.to.have.been.calledWithExactly( + computeSyncModeForUserAndReplicaExpectedConditionsArr[0].input + ) + .and.to.have.been.calledWithExactly( + computeSyncModeForUserAndReplicaExpectedConditionsArr[1].input + ) + }) + + it('Test with multiple users and outcomes, including MergePrimaryAndSecondary', async function () { + /** + * Define all input variables + */ + + const CN1 = 'http://cn1.co' + const CN2 = 'http://cn2.co' + const CN3 = 'http://cn3.co' + const CN1SpID = 1 + const CN2SpID = 2 + const CN3SpID = 3 + const userID1 = 1 + const userID2 = 2 + const wallet1 = '0xwallet1' + const wallet2 = '0xwallet2' + + users = [ + { + user_id: userID1, + wallet: wallet1, + primary: CN1, + secondary1: CN2, + secondary2: CN3, + primarySpID: CN1SpID, + secondary1SpID: CN2SpID, + secondary2SpID: CN3SpID + }, + { + user_id: userID2, + wallet: wallet2, + primary: CN1, + secondary1: CN3, + secondary2: CN2, + primarySpID: CN1SpID, + secondary1SpID: CN3SpID, + secondary2SpID: CN2SpID + }, + ] + + // spIds in mapping must match those in the `users` variable + const cNodeEndpointToSpIdMap = { + [CN1]: CN1SpID, + [CN2]: CN2SpID, + [CN3]: CN3SpID + } + + const unhealthyPeers = [] + + // wallet1 - primary (CN1) clock value > secondary1 (CN2) clock value + // wallet2 - primary (CN1) files hash != secondary1 (CN3) files hash with same clock val + const replicaToUserInfoMap = { + [CN1]: { + [wallet1]: { clock: 10, filesHash: '0xW1C10FH' }, // primary + [wallet2]: { clock: 10, filesHash: '0xW2C10FH' } // primary + }, + [CN2]: { + [wallet1]: { clock: 9, filesHash: '0xW1C9FH' }, // secondary1 + [wallet2]: { clock: 10, filesHash: '0xW2C10FH' } // secondary2 + }, + [CN3]: { + [wallet1]: { clock: 10, filesHash: '0xW1C10FH' }, // secondary2 + [wallet2]: { clock: 10, filesHash: '0xW2C10BadFH' } // secondary1 + } + } + + const userSecondarySyncMetricsMap = {} + + // This node must be the primary in order to sync + config.set('creatorNodeEndpoint', CN1) + + /** + * Create all stubs for jobProcessor + */ + + const getCNodeEndpointToSpIdMapStub = getGetCNodeEndpointToSpIdMapStub( + cNodeEndpointToSpIdMap + ) + + const computeSyncModeForUserAndReplicaExpectedConditionsArr = [ + // wallet1 - (primary, secondary1) = (CN1, CN2) -> SyncSecondaryFromPrimary + { + input: { + wallet: wallet1, + primaryClock: replicaToUserInfoMap[CN1][wallet1].clock, + secondaryClock: replicaToUserInfoMap[CN2][wallet1].clock, + primaryFilesHash: replicaToUserInfoMap[CN1][wallet1].filesHash, + secondaryFilesHash: replicaToUserInfoMap[CN2][wallet1].filesHash + }, + output: SYNC_MODES.SyncSecondaryFromPrimary + }, + // wallet1 - (primary, secondary2) = (CN1, CN3) -> None + { + input: { + wallet: wallet1, + primaryClock: replicaToUserInfoMap[CN1][wallet1].clock, + secondaryClock: replicaToUserInfoMap[CN3][wallet1].clock, + primaryFilesHash: replicaToUserInfoMap[CN1][wallet1].filesHash, + secondaryFilesHash: replicaToUserInfoMap[CN3][wallet1].filesHash + }, + output: SYNC_MODES.None + }, + // wallet2 - (primary, secondary1) = (CN1, CN3) -> MergePrimaryAndSecondary + { + input: { + wallet: wallet2, + primaryClock: replicaToUserInfoMap[CN1][wallet2].clock, + secondaryClock: replicaToUserInfoMap[CN3][wallet2].clock, + primaryFilesHash: replicaToUserInfoMap[CN1][wallet2].filesHash, + secondaryFilesHash: replicaToUserInfoMap[CN3][wallet2].filesHash + }, + output: SYNC_MODES.MergePrimaryAndSecondary + }, + // wallet2 - (primary, secondary2) = (CN1, CN2) -> None + { + input: { + wallet: wallet2, + primaryClock: replicaToUserInfoMap[CN1][wallet2].clock, + secondaryClock: replicaToUserInfoMap[CN2][wallet2].clock, + primaryFilesHash: replicaToUserInfoMap[CN1][wallet2].filesHash, + secondaryFilesHash: replicaToUserInfoMap[CN2][wallet2].filesHash + }, + output: SYNC_MODES.None + }, + ] + const computeSyncModeForUserAndReplicaStub = getConditionalStub( + 'computeSyncModeForUserAndReplica', + computeSyncModeForUserAndReplicaExpectedConditionsArr + ) + + const expectedSyncReqToEnqueueWallet1 = 'expectedSyncReqToEnqueueWallet1' + const expectedSyncReqToEnqueueWallet2 = 'expectedSyncReqToEnqueueWallet2' + const getNewOrExistingSyncReqExpectedConditionsArr = [ + // wallet1 + { + input: { + userWallet: wallet1, + primaryEndpoint: CN1, + secondaryEndpoint: CN2, + syncType, + syncMode: SYNC_MODES.SyncSecondaryFromPrimary + }, + output: { syncReqToEnqueue: expectedSyncReqToEnqueueWallet1 } + }, // wallet2 + { + input: { + userWallet: wallet2, + primaryEndpoint: CN1, + secondaryEndpoint: CN3, + syncType, + syncMode: SYNC_MODES.MergePrimaryAndSecondary + }, + output: { syncReqToEnqueue: expectedSyncReqToEnqueueWallet2 } + } + ] + const getNewOrExistingSyncReqStub = getConditionalStub( + 'getNewOrExistingSyncReq', + getNewOrExistingSyncReqExpectedConditionsArr + ) + + const findSyncRequestsJobProcessor = getJobProcessorStub( + getNewOrExistingSyncReqStub, + getCNodeEndpointToSpIdMapStub, + computeSyncModeForUserAndReplicaStub + ) + + /** + * Verify job outputs the correct results: sync to user1 to secondary1 because its clock value is behind + */ + + const expectedOutput = { + duplicateSyncReqs: [], + errors: [], + jobsToEnqueue: { + [QUEUE_NAMES.STATE_RECONCILIATION]: [ + expectedSyncReqToEnqueueWallet1, expectedSyncReqToEnqueueWallet2 + ] + }, + metricsToRecord: [ + { + metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.SyncSecondaryFromPrimary), + result: 'new_sync_request_enqueued' + }, + metricName, + metricType, + metricValue: 1 + }, + { + metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.None), + result: 'no_sync_secondary_data_matches_primary' + }, + metricName, + metricType, + metricValue: 2 + }, + { + metricLabels: { + sync_mode: _.snakeCase(SYNC_MODES.MergePrimaryAndSecondary), + result: 'new_sync_request_enqueued' + }, + metricName, + metricType, + metricValue: 1 + } + ] + } + const actualOutput = await findSyncRequestsJobProcessor({ + users, + unhealthyPeers, + replicaToUserInfoMap, + userSecondarySyncMetricsMap, + logger + }) + expect(actualOutput).to.deep.equal(expectedOutput) + expect(computeSyncModeForUserAndReplicaStub) + .to.have.been.calledWithExactly( + computeSyncModeForUserAndReplicaExpectedConditionsArr[0].input + ) + .and.to.have.been.calledWithExactly( + computeSyncModeForUserAndReplicaExpectedConditionsArr[1].input + ) + .and.to.have.been.calledWithExactly( + computeSyncModeForUserAndReplicaExpectedConditionsArr[2].input + ) + .and.to.have.been.calledWithExactly( + computeSyncModeForUserAndReplicaExpectedConditionsArr[3].input + ) + expect(getNewOrExistingSyncReqStub) + .to.have.been.calledWithExactly( + getNewOrExistingSyncReqExpectedConditionsArr[0].input + ).and.to.have.been.calledWithExactly( + getNewOrExistingSyncReqExpectedConditionsArr[1].input + ) + }) }) diff --git a/creator-node/test/issueSyncRequest.jobProcessor.test.js b/creator-node/test/issueSyncRequest.jobProcessor.test.js index fa7e7119280..efbd2d9869f 100644 --- a/creator-node/test/issueSyncRequest.jobProcessor.test.js +++ b/creator-node/test/issueSyncRequest.jobProcessor.test.js @@ -11,7 +11,8 @@ const models = require('../src/models') const config = require('../src/config') const { SyncType, - QUEUE_NAMES + QUEUE_NAMES, + SYNC_MODES } = require('../src/services/stateMachineManager/stateMachineConstants') const issueSyncRequestJobProcessor = require('../src/services/stateMachineManager/stateReconciliation/issueSyncRequest.jobProcessor') @@ -21,6 +22,7 @@ const { expect } = chai describe('test issueSyncRequest job processor param validation', function () { let server, sandbox, originalContentNodeEndpoint, logger + beforeEach(async function () { const appInfo = await getApp(getLibsMock()) await appInfo.app.get('redisClient').flushdb() @@ -109,13 +111,14 @@ describe('test issueSyncRequest job processor param validation', function () { }) }) -describe('test issueSyncRequest job processor', function () { +describe.only('test issueSyncRequest job processor', function () { let server, sandbox, originalContentNodeEndpoint, logger, recordSuccessStub, recordFailureStub + beforeEach(async function () { const appInfo = await getApp(getLibsMock()) await appInfo.app.get('redisClient').flushdb() @@ -144,6 +147,7 @@ describe('test issueSyncRequest job processor', function () { }) const syncType = SyncType.Manual + let syncMode = SYNC_MODES.SyncSecondaryFromPrimary const primary = 'http://primary_cn.co' const wallet = '0x123456789' @@ -161,29 +165,37 @@ describe('test issueSyncRequest job processor', function () { function getJobProcessorStub({ getNewOrExistingSyncReqStub, getSecondaryUserSyncFailureCountForTodayStub, - retrieveClockValueForUserFromReplicaStub + retrieveClockValueForUserFromReplicaStub, + primarySyncFromSecondaryStub = null }) { + + const stubs = { + '../../../config': config, + './stateReconciliationUtils': { + getNewOrExistingSyncReq: getNewOrExistingSyncReqStub + }, + './SecondarySyncHealthTracker': { + getSecondaryUserSyncFailureCountForToday: + getSecondaryUserSyncFailureCountForTodayStub, + recordSuccess: recordSuccessStub, + recordFailure: recordFailureStub + }, + '../stateMachineUtils': { + retrieveClockValueForUserFromReplica: + retrieveClockValueForUserFromReplicaStub + }, + '../stateMachineConstants': { + SYNC_MONITORING_RETRY_DELAY_MS: 1 + } + } + + if (primarySyncFromSecondaryStub) { + stubs['../../sync/primarySyncFromSecondary'] = primarySyncFromSecondaryStub + } + return proxyquire( '../src/services/stateMachineManager/stateReconciliation/issueSyncRequest.jobProcessor.js', - { - '../../../config': config, - './stateReconciliationUtils': { - getNewOrExistingSyncReq: getNewOrExistingSyncReqStub - }, - './SecondarySyncHealthTracker': { - getSecondaryUserSyncFailureCountForToday: - getSecondaryUserSyncFailureCountForTodayStub, - recordSuccess: recordSuccessStub, - recordFailure: recordFailureStub - }, - '../stateMachineUtils': { - retrieveClockValueForUserFromReplica: - retrieveClockValueForUserFromReplicaStub - }, - '../stateMachineConstants': { - SYNC_MONITORING_RETRY_DELAY_MS: 1 - } - } + stubs ) } @@ -211,6 +223,7 @@ describe('test issueSyncRequest job processor', function () { const result = await issueSyncRequestJobProcessor({ logger, syncType, + syncMode, syncRequestParameters }) expect(result).to.have.deep.property('error', {}) @@ -253,12 +266,13 @@ describe('test issueSyncRequest job processor', function () { retrieveClockValueForUserFromReplicaStub }) - const expectedErrorMessage = `(${syncType}) User ${wallet} | Secondary: ${baseURL} || Secondary has already met SecondaryUserSyncDailyFailureCountThreshold (${failureThreshold}). Will not issue further syncRequests today.` + const expectedErrorMessage = `(${syncType})(${syncMode}) User ${wallet} | Secondary: ${baseURL} || Secondary has already met SecondaryUserSyncDailyFailureCountThreshold (${failureThreshold}). Will not issue further syncRequests today.` // Verify job outputs the correct results: error and no sync issued (nock will error if a network req was made) const result = await issueSyncRequestJobProcessor({ logger, syncType, + syncMode, syncRequestParameters }) expect(result).to.have.deep.property('error', { @@ -320,6 +334,7 @@ describe('test issueSyncRequest job processor', function () { const result = await issueSyncRequestJobProcessor({ logger, syncType, + syncMode, syncRequestParameters }) expect(result).to.have.deep.property('error', {}) @@ -344,7 +359,8 @@ describe('test issueSyncRequest job processor', function () { userWallet: wallet, secondaryEndpoint: baseURL, primaryEndpoint: primary, - syncType + syncType, + syncMode }) expect( retrieveClockValueForUserFromReplicaStub.callCount @@ -403,6 +419,7 @@ describe('test issueSyncRequest job processor', function () { const result = await issueSyncRequestJobProcessor({ logger, syncType, + syncMode, syncRequestParameters }) expect(result).to.have.deep.property('error', {}) @@ -427,7 +444,8 @@ describe('test issueSyncRequest job processor', function () { userWallet: wallet, secondaryEndpoint: baseURL, primaryEndpoint: primary, - syncType + syncType, + syncMode }) expect( retrieveClockValueForUserFromReplicaStub.callCount @@ -439,4 +457,57 @@ describe('test issueSyncRequest job processor', function () { ) expect(recordSuccessStub).to.have.not.been.called }) + + describe.only('test SYNC_MODES.MergePrimaryAndSecondary', async function () { + syncMode = SYNC_MODES.MergePrimaryAndSecondary + + it.only('Issues correct sync when primarySyncFromSecondary() succeeds and no additional sync is required', async function () { + const getNewOrExistingSyncReqStub = sandbox.stub().callsFake((args) => { + throw new Error('getNewOrExistingSyncReq was not expected to be called') + }) + + const getSecondaryUserSyncFailureCountForTodayStub = sandbox + .stub() + .returns(0) + + const retrieveClockValueForUserFromReplicaStub = sandbox.stub().resolves(1) + + const primarySyncFromSecondaryStub = sandbox.stub().returns(null) + + const issueSyncRequestJobProcessor = getJobProcessorStub({ + getNewOrExistingSyncReqStub, + getSecondaryUserSyncFailureCountForTodayStub, + retrieveClockValueForUserFromReplicaStub, + primarySyncFromSecondaryStub + }) + + // Make the axios request succeed + nock(baseURL).post('/sync', data).reply(200) + + // Verify job outputs the correct results: no sync issued (nock will error if the wrong network req was made) + const result = await issueSyncRequestJobProcessor({ + logger, + syncType, + syncMode, + syncRequestParameters + }) + expect(result).to.have.deep.property('error', {}) + expect(result).to.have.deep.property('jobsToEnqueue', {}) + expect(result.metricsToRecord).to.have.lengthOf(1) + expect(result.metricsToRecord[0]).to.have.deep.property( + 'metricName', + 'audius_cn_issue_sync_request_monitoring_duration_seconds' + ) + expect(result.metricsToRecord[0]).to.have.deep.property('metricLabels', { + syncType: 'manual', + reason_for_additional_sync: 'none' + }) + expect(result.metricsToRecord[0]).to.have.deep.property( + 'metricType', + 'HISTOGRAM_OBSERVE' + ) + expect(result.metricsToRecord[0].metricValue).to.be.a('number') + expect(getNewOrExistingSyncReqStub).to.not.have.been.called + }) + }) }) diff --git a/creator-node/test/lib/app.js b/creator-node/test/lib/app.js index d5704180110..0af24d38c69 100644 --- a/creator-node/test/lib/app.js +++ b/creator-node/test/lib/app.js @@ -52,7 +52,8 @@ function getServiceRegistryMock (libsClient, blacklistManager) { redis: redisClient, monitoringQueue: new MonitoringQueueMock(), syncQueue: new SyncQueue(nodeConfig, redisClient), - nodeConfig + nodeConfig, + initLibs: async function () {} } } diff --git a/creator-node/test/lib/dataSeeds.js b/creator-node/test/lib/dataSeeds.js index 24ff3239761..d36310a32a1 100644 --- a/creator-node/test/lib/dataSeeds.js +++ b/creator-node/test/lib/dataSeeds.js @@ -12,13 +12,19 @@ const getCNodeUser = async (cnodeUserUUID) => { return dataValues } -const destroyUsers = async () => ( - CNodeUser.destroy({ - where: {}, - truncate: true, - cascade: true // cascades delete to all rows with foreign key on cnodeUser - }) -) +const destroyUsers = async () => { + try { + await CNodeUser.destroy({ + where: {}, + truncate: true, + cascade: true // cascades delete to all rows with foreign key on cnodeUser + }) + } catch (e) { + if (e.message !== 'relation "CNodeUsers" does not exist') { + throw e + } + } +} async function createStarterCNodeUser (userId = null, pubKey = testEthereumConstants.pubKey.toLowerCase()) { return createStarterCNodeUserWithKey(pubKey, userId) diff --git a/creator-node/test/nodesync.test.js b/creator-node/test/sync.test.js similarity index 58% rename from creator-node/test/nodesync.test.js rename to creator-node/test/sync.test.js index ee3b1c6e9ba..5b791ef5ab7 100644 --- a/creator-node/test/nodesync.test.js +++ b/creator-node/test/sync.test.js @@ -5,6 +5,7 @@ const assert = require('assert') const _ = require('lodash') const nock = require('nock') const sinon = require('sinon') +const proxyquire = require('proxyquire') const config = require('../src/config') const models = require('../src/models') @@ -20,7 +21,7 @@ const BlacklistManager = require('../src/blacklistManager') const redisClient = require('../src/redis') const { stringifiedDateFields } = require('./lib/utils') -const processSync = require('../src/services/sync/processSync') +const secondarySyncFromPrimary = require('../src/services/sync/secondarySyncFromPrimary') const { uploadTrack } = require('./lib/helpers') const testAudioFilePath = path.resolve(__dirname, 'testTrack.mp3') @@ -32,6 +33,13 @@ const sampleExportDummyCIDFromClock2Path = path.resolve( __dirname, 'syncAssets/sampleExportDummyCIDFromClock2.json' ) +/** + * Sample export file must use `DUMMY_WALLET`, `DUMMY_CNODEUSER_BLOCKNUMBER`, `DUMMY_CID + */ + const DUMMY_WALLET = testEthereumConstants.pubKey.toLowerCase() + const DUMMY_CNODEUSER_BLOCKNUMBER = 10 + const DUMMY_CID = 'QmSU6rdPHdTrVohDSfhVCBiobTMr6a3NvPz4J7nLWVDvmE' + const DUMMY_CID_DATA = 'audius is cool' describe('test nodesync', async function () { let server, app, mockServiceRegistry, userId @@ -687,7 +695,7 @@ describe('test nodesync', async function () { }) }) - describe('Test processSync function', async function () { + describe('Test secondarySyncFromPrimary function', async function () { let serviceRegistryMock const TEST_ENDPOINT = 'http://test-cn.co' @@ -944,8 +952,8 @@ describe('test nodesync', async function () { const initialCNodeUserCount = await models.CNodeUser.count() assert.strictEqual(initialCNodeUserCount, 0) - // Call processSync - await processSync(serviceRegistryMock, userWallets, TEST_ENDPOINT) + // Call secondarySyncFromPrimary + await secondarySyncFromPrimary(serviceRegistryMock, userWallets, TEST_ENDPOINT) const newCNodeUserUUID = await verifyLocalCNodeUserStateForUser( exportedCnodeUser @@ -985,8 +993,8 @@ describe('test nodesync', async function () { }) assert.strictEqual(localCNodeUserCount, 1) - // Call processSync - await processSync(serviceRegistryMock, userWallets, TEST_ENDPOINT) + // Call secondarySyncFromPrimary + await secondarySyncFromPrimary(serviceRegistryMock, userWallets, TEST_ENDPOINT) await verifyLocalCNodeUserStateForUser(exportedCnodeUser) @@ -1024,8 +1032,8 @@ describe('test nodesync', async function () { }) assert.strictEqual(localCNodeUserCount, 1) - // Call processSync with `forceResync` = true - await processSync( + // Call secondarySyncFromPrimary with `forceResync` = true + await secondarySyncFromPrimary( serviceRegistryMock, userWallets, TEST_ENDPOINT, @@ -1047,3 +1055,651 @@ describe('test nodesync', async function () { }) }) }) + +describe.only('Test primarySyncFromSecondary() with mocked export', async () => { + let server, app, serviceRegistryMock, primarySyncFromSecondaryStub + + const NODES = { + CN1: 'http://mock-cn1.audius.co', + CN2: 'http://mock-cn2.audius.co', + CN3: 'http://mock-cn3.audius.co' + } + const NODES_LIST = Object.values(NODES) + const SELF = NODES.CN1 + const SECONDARY = NODES.CN3 + const USER_1_ID = 1 + const SP_ID_1 = 1 + const USER_1_WALLET = DUMMY_WALLET + const USER_1_BLOCKNUMBER = DUMMY_CNODEUSER_BLOCKNUMBER + + const assetsDirPath = path.resolve(__dirname, 'sync/assets') + const exportFilePath = path.resolve(assetsDirPath, 'realExport.json') + + const unpackExportDataFromFile = (exportDataFilePath) => { + const exportObj = JSON.parse(fs.readFileSync(exportDataFilePath)) + const cnodeUserInfo = Object.values(exportObj.data.cnodeUsers)[0] + const cnodeUser = _.omit(cnodeUserInfo, ['audiusUsers', 'tracks', 'files', 'clockRecords', 'clockInfo']) + const { audiusUsers, tracks, files, clockRecords, clockInfo } = cnodeUserInfo + + return { + exportObj, + cnodeUser, + audiusUsers, + tracks, + files, + clockRecords, + clockInfo + } + } + + /** + * Sets `/export` route response from `endpoint` to `exportData` + */ + const setupExportMock = (endpoint, exportData) => { + nock(endpoint) + .persist() + .get((uri) => uri.includes('/export')) + .reply(200, exportData) + } + + const computeFilePathForCID = (CID) => { + const directoryID = CID.slice(-4, -1) // sharded file system + const parentDirPath = path.join(assetsDirPath, 'files', directoryID) + const filePath = path.join(parentDirPath, CID) + return filePath + } + + /** + * Sets `/ipfs` route responses for DUMMY_CID from all nodes to DUMMY_CID_DATA + */ + const setupIPFSRouteMocks = () => { + NODES_LIST.forEach(node => { + nock(node) + .persist() + .get((uri) => uri.includes('/ipfs')) + .reply(200, (uri, requestbody) => { + const CID = uri.split('/ipfs/')[1].slice(0,46) + const CIDFilePath = computeFilePathForCID(CID) + const fileBuffer = fs.readFileSync(CIDFilePath) + return fileBuffer + }) + }) + } + + const fetchDBStateForWallet = async (walletPublicKey) => { + const response = { + cnodeUser: null, + audiusUsers: null, + tracks: null, + files: null, + clockRecords: null + } + + const cnodeUser = stringifiedDateFields( + await models.CNodeUser.findOne({ + where: { + walletPublicKey + }, + raw: true + }) + ) + + if (!cnodeUser || Object.keys(cnodeUser).length === 0) { + return response + } else { + response.cnodeUser = cnodeUser + } + + const cnodeUserUUID = cnodeUser.cnodeUserUUID + + const audiusUsers = (await models.AudiusUser.findAll({ + where: { cnodeUserUUID }, + raw: true + })).map(stringifiedDateFields) + response.audiusUsers = audiusUsers + + const tracks = (await models.Track.findAll({ + where: { cnodeUserUUID }, + raw: true + })).map(stringifiedDateFields) + response.tracks = tracks + + const files = (await models.File.findAll({ + where: { cnodeUserUUID }, + raw: true + })).map(stringifiedDateFields) + response.files = files + + const clockRecords = (await models.ClockRecord.findAll({ + where: { cnodeUserUUID }, + raw: true + })).map(stringifiedDateFields) + response.clockRecords = clockRecords + + return response + } + + /** + * Create local user with CNodeUser, AudiusUser, File, and ClockRecord state + * @returns cnodeUserUUID + */ + const createUser = async (userId, userWallet, blockNumber) => { + // Create CNodeUser + const session = await createStarterCNodeUser(userId, userWallet) + + // Upload user metadata + const metadata = { + metadata: { + testField: 'testValue' + } + } + const userMetadataResp = await request(app) + .post('/audius_users/metadata') + .set('X-Session-ID', session.sessionToken) + .set('User-Id', session.userId) + .send(metadata) + .expect(200) + + const metadataFileUUID = userMetadataResp.body.data.metadataFileUUID + + // Associate user with with blockchain ID + const associateRequest = { + blockchainUserId: userId, + metadataFileUUID, + blockNumber + } + await request(app) + .post('/audius_users') + .set('X-Session-ID', session.sessionToken) + .set('User-Id', session.userId) + .send(associateRequest) + .expect(200) + + return session.cnodeUserUUID + } + + const createUserAndTrack = async (testAssetsDirPath) => { + // Create CNodeUser + const { cnodeUserUUID, sessionToken } = await createStarterCNodeUser(USER_1_ID, USER_1_WALLET) + + // Upload user metadata + const userMetadata = { + metadata: { + testField: 'testValue' + } + } + + await fs.writeFile( + path.resolve(testAssetsDirPath, 'userMetadata.json'), + JSON.stringify(userMetadata, null, 2) + ) + + const userMetadataResp = await request(app) + .post('/audius_users/metadata') + .set('X-Session-ID', sessionToken) + .set('User-Id', USER_1_ID) + .send(userMetadata) + .expect(200) + // const metadataMultihash = userMetadataResp.body.data.metadataMultihash + const metadataFileUUID = userMetadataResp.body.data.metadataFileUUID + + // Associate user with with blockchain ID + const associateRequest = { + blockchainUserId: USER_1_ID, + metadataFileUUID, + blockNumber: USER_1_BLOCKNUMBER + } + await request(app) + .post('/audius_users') + .set('X-Session-ID', sessionToken) + .set('User-Id', USER_1_ID) + .send(associateRequest) + .expect(200) + + /** Upload a track */ + + const trackUploadResponse = await uploadTrack( + testAudioFilePath, + cnodeUserUUID, + serviceRegistryMock.blacklistManager + ) + console.log(`SIDTEST TRACKUPLOADRESPONSE ${JSON.stringify(trackUploadResponse)}`) + + const transcodedTrackUUID = trackUploadResponse.transcodedTrackUUID + const trackSegments = trackUploadResponse.track_segments + const sourceFile = trackUploadResponse.source_file + // const transcodedTrackCID = trackUploadResponse.transcodedTrackCID + + // Upload track metadata + const trackMetadata = { + metadata: { + test: 'field1', + owner_id: USER_1_ID, + track_segments: trackSegments + }, + source_file: sourceFile + } + + await fs.writeFile( + path.resolve(testAssetsDirPath, 'trackMetadata.json'), + JSON.stringify(trackMetadata, null, 2) + ) + + const trackMetadataResp = await request(app) + .post('/tracks/metadata') + .set('X-Session-ID', sessionToken) + .set('User-Id', USER_1_ID) + .send(trackMetadata) + .expect(200) + // const trackMetadataMultihash = trackMetadataResp.body.data.metadataMultihash + const trackMetadataFileUUID = trackMetadataResp.body.data.metadataFileUUID + + // associate track + track metadata with blockchain ID + await request(app) + .post('/tracks') + .set('X-Session-ID', sessionToken) + .set('User-Id', USER_1_ID) + .send({ + blockchainTrackId: USER_1_ID, + blockNumber: USER_1_BLOCKNUMBER, + metadataFileUUID: trackMetadataFileUUID, + transcodedTrackUUID + }) + + const exportResp = await request(app).get( + `/export?wallet_public_key=${USER_1_WALLET}` + ) + const exportOutputFilePath = path.resolve(testAssetsDirPath, 'realExport.json') + await fs.writeFile(exportOutputFilePath, JSON.stringify(exportResp.body, null, 2)) + } + + /** + * Reset nocks, DB, redis, file storage + * Setup mocks, deps + */ + beforeEach(async function () { + nock.cleanAll() + + await destroyUsers() + + await redisClient.flushdb() + + // Clear storagePath + const storagePath = config.get('storagePath') + const absoluteStoragePath = path.resolve(storagePath) + await fs.emptyDir(path.resolve(absoluteStoragePath)) + + const appInfo = await getApp( + libsMock, + BlacklistManager, + null, + SP_ID_1 + ) + server = appInfo.server + app = appInfo.app + + serviceRegistryMock = getServiceRegistryMock( + libsMock, + BlacklistManager + ) + + primarySyncFromSecondaryStub = proxyquire( + '../src/services/sync/primarySyncFromSecondary', + { + '../../serviceRegistry': { serviceRegistry: serviceRegistryMock } + } + ) + }) + + // close server + afterEach(async function () { + await server.close() + }) + + it.skip('NO LONGER NEEDED createUserAndTrack - used for populating data files', async function () { + return + const testAssetsDirPath = path.resolve(__dirname, 'sync', 'assets') + await createUserAndTrack(testAssetsDirPath) + }) + + it('Primary correctly syncs from secondary when primary has no state', async function () { + const { + exportObj, + cnodeUser: exportedCnodeUser, + audiusUsers: exportedAudiusUsers, + tracks: exportedTracks, + files: exportedFiles, + clockRecords: exportedClockRecords + } = unpackExportDataFromFile(exportFilePath) + + setupExportMock(SECONDARY, exportObj) + setupIPFSRouteMocks() + + // Confirm local user state is empty before sync + let { cnodeUser: initialLocalCNodeUser } = await fetchDBStateForWallet(USER_1_WALLET) + assert.deepStrictEqual(initialLocalCNodeUser, null) + + await primarySyncFromSecondaryStub({ + secondary: SECONDARY, + wallet: USER_1_WALLET, + selfEndpoint: SELF + }) + + /** + * Verify DB state after sync + */ + const { + cnodeUser: localCNodeUser, + audiusUsers: localAudiusUsers, + tracks: localTracks, + files: localFiles, + clockRecords: localClockRecords + } = await fetchDBStateForWallet(USER_1_WALLET) + + const comparisonOmittedFields = ['cnodeUserUUID', 'createdAt', 'updatedAt'] + + assert.deepStrictEqual( + _.omit(localCNodeUser, comparisonOmittedFields), + _.omit(exportedCnodeUser, comparisonOmittedFields) + ) + + assert.deepStrictEqual( + _.orderBy( + localAudiusUsers.map(audiusUser => _.omit(audiusUser, comparisonOmittedFields)), + ['clock'], ['asc'] + ), + _.orderBy( + exportedAudiusUsers.map(audiusUser => _.omit(audiusUser, comparisonOmittedFields)), + ['clock'], ['asc'] + ) + ) + + assert.deepStrictEqual( + _.orderBy( + localTracks.map(track => _.omit(track, comparisonOmittedFields)), + ['clock'], ['asc'] + ), + _.orderBy( + exportedTracks.map(track => _.omit(track, comparisonOmittedFields)), + ['clock'], ['asc'] + ) + ) + + assert.deepStrictEqual( + _.orderBy( + localFiles.map(file => _.omit(file, comparisonOmittedFields)), + ['clock'], ['asc'] + ), + _.orderBy( + exportedFiles.map(file => _.omit(file, comparisonOmittedFields)), + ['clock'], ['asc'] + ) + ) + + assert.deepStrictEqual( + _.orderBy( + localClockRecords.map(clockRecord => _.omit(clockRecord, comparisonOmittedFields)), + ['clock'], ['asc'] + ), + _.orderBy( + exportedClockRecords.map(clockRecord => _.omit(clockRecord, comparisonOmittedFields)), + ['clock'], ['asc'] + ) + ) + + // TODO assert on identical filesHash + }) + + it('Primary correctly syncs from secondary when nodes have divergent state', async function () { + const { + exportObj, + cnodeUser: exportedCnodeUser, + audiusUsers: exportedAudiusUsers, + tracks: exportedTracks, + files: exportedFiles, + clockRecords: exportedClockRecords + } = unpackExportDataFromFile(exportFilePath) + + setupExportMock(SECONDARY, exportObj) + setupIPFSRouteMocks() + + // Confirm local user state is empty initially + let { cnodeUser: initialLocalCNodeUser } = await fetchDBStateForWallet(USER_1_WALLET) + assert.deepStrictEqual(initialLocalCNodeUser, null) + + // Add some local user state + await createUser(USER_1_ID, USER_1_WALLET, USER_1_BLOCKNUMBER) + + // Confirm local user state is non-empty before sync + const { + cnodeUser: localInitialCNodeUser, + audiusUsers: localInitialAudiusUsers, + tracks: localInitialTracks, + files: localInitialFiles, + clockRecords: localInitialClockRecords + } = await fetchDBStateForWallet(USER_1_WALLET) + + let cnodeUserComparisonFields = ['walletPublicKey', 'clock', 'latestBlockNumber'] + assert.deepStrictEqual( + _.pick(localInitialCNodeUser, cnodeUserComparisonFields), + { + walletPublicKey: USER_1_WALLET, + clock: 2, + latestBlockNumber: USER_1_BLOCKNUMBER + } + ) + + let audiusUserComparisonFields = ['blockchainId', 'metadataJSON', 'clock'] + assert.deepStrictEqual( + _.orderBy( + localInitialAudiusUsers.map(file => _.pick(file, audiusUserComparisonFields)), + ['clock'], ['asc'] + ), + _.orderBy( + exportedAudiusUsers.map(file => _.pick(file, audiusUserComparisonFields)), + ['clock'], ['asc'] + ) + ) + + assert.deepStrictEqual(localInitialTracks, []) + + let fileComparisonFields = ['trackBlockchainId', 'multihash', 'sourceFile', 'fileName', 'dirMultihash', 'storagePath', 'type', 'clock'] + assert.deepStrictEqual( + _.orderBy( + localInitialFiles.map(file => _.pick(file, fileComparisonFields)), + ['clock'], ['asc'] + ), + _.orderBy( + exportedFiles.map(file => _.pick(file, fileComparisonFields)), + ['clock'], ['asc'] + ).slice(0,1) + ) + + let clockRecordComparisonFields = ['clock', 'sourceTable'] + assert.deepStrictEqual( + _.orderBy( + localInitialClockRecords.map(clockRecord => _.pick(clockRecord, clockRecordComparisonFields)), + ['clock', 'asc'] + ), + _.orderBy( + exportedClockRecords.map(clockRecord => _.pick(clockRecord, clockRecordComparisonFields)), + ['clock', 'asc'] + ).slice(0,2) + ) + + await primarySyncFromSecondaryStub({ + serviceRegistry: serviceRegistryMock, + secondary: SECONDARY, + wallet: USER_1_WALLET, + sourceEndpoint: SELF + }) + + /** + * Verify DB state after sync + */ + const { + cnodeUser: localFinalCNodeUser, + audiusUsers: localFinalAudiusUsers, + tracks: localFinalTracks, + files: localFinalFiles, + clockRecords: localFinalClockRecords + } = await fetchDBStateForWallet(USER_1_WALLET) + + cnodeUserComparisonFields = ['walletPublicKey', 'latestBlockNumber', 'clock'] + assert.deepStrictEqual( + _.pick(localFinalCNodeUser, cnodeUserComparisonFields), + _.pick( + { ...exportedCnodeUser, clock: exportedCnodeUser.clock + 2 }, + cnodeUserComparisonFields + ) + ) + + audiusUserComparisonFields = ['blockchainId', 'metadataJSON', 'clock', 'metadataFileUUID', 'coverArtFileUUID', 'profilePicFileUUID'] + assert.deepStrictEqual( + _.orderBy( + localFinalAudiusUsers.map(audiusUser => _.pick(audiusUser, audiusUserComparisonFields)), + ['clock'], ['asc'] + ), + _.orderBy( + _.concat(localInitialAudiusUsers, exportedAudiusUsers.map(audiusUser => ({ ...audiusUser, clock: audiusUser.clock + 2 }))) + .map(audiusUser => _.pick(audiusUser, audiusUserComparisonFields)), + ['clock'], ['asc'] + ) + ) + + let trackComparisonFields = ['blockchainId', 'metadataJSON', 'metadataFileUUID', 'coverArtFileUUID'] + assert.deepStrictEqual( + _.orderBy( + localFinalTracks.map(track => _.pick(track, trackComparisonFields)), + ['clock'], ['asc'] + ), + _.orderBy( + exportedTracks.map(track => _.pick(track, trackComparisonFields)), + ['clock'], ['asc'] + ) + ) + + fileComparisonFields = ['trackBlockchainId', 'multihash', 'sourceFile', 'fileName', 'dirMultihash', 'storagePath', 'type', 'clock'] + assert.deepStrictEqual( + _.orderBy( + localFinalFiles.map(file => _.pick(file, fileComparisonFields)), + ['clock'], ['asc'] + ), + _.orderBy( + _.concat(localInitialFiles, exportedFiles.map(file => ({ ...file, clock: file.clock + 2 }))) + .map(file => _.pick(file, fileComparisonFields)), + ['clock'], ['asc'] + ) + ) + + clockRecordComparisonFields = ['clock', 'sourceTable'] + assert.deepStrictEqual( + _.orderBy( + localFinalClockRecords.map(clockRecord => _.pick(clockRecord, clockRecordComparisonFields)), + ['clock'], ['asc'] + ), + _.orderBy( + _.concat(localInitialClockRecords, exportedClockRecords.map(clockRecord => ({ ...clockRecord, clock: clockRecord.clock + 2 }))) + .map(clockRecord => _.pick(clockRecord, clockRecordComparisonFields)), + ['clock'], ['asc'] + ) + ) + }) + + it('Primary correctly syncs from secondary when primary has subset of secondary state', async function () { + /** + * seed local DB with CN + audiusUser + file entry from export + * sync + * confirm final clock state = 37 + */ + + const { + exportObj, + cnodeUser: exportedCnodeUser, + audiusUsers: exportedAudiusUsers, + tracks: exportedTracks, + files: exportedFiles, + clockRecords: exportedClockRecords + } = unpackExportDataFromFile(exportFilePath) + + setupExportMock(SECONDARY, exportObj) + setupIPFSRouteMocks() + + /** TODO confirm empty state */ + + const audiusUsersSubset = _.orderBy(exportedAudiusUsers, ['clock'], ['asc']).slice(0,1) + const filesSubset = _.orderBy(exportedFiles, ['clock'], ['asc']).slice(0,1) + const clockRecordsSubSet = _.orderBy(exportedClockRecords, ['clock'], ['asc']).slice(0,2) + + const transaction = await models.sequelize.transaction() + await models.CNodeUser.create({ ...exportedCnodeUser, clock: 2 }, { transaction }) + await models.ClockRecord.bulkCreate(clockRecordsSubSet, { transaction }) + await models.File.bulkCreate(filesSubset, { transaction }) + await models.AudiusUser.bulkCreate(audiusUsersSubset, { transaction }) + await transaction.commit() + + /** TODO confirm non-empty initial state */ + + await primarySyncFromSecondaryStub({ + serviceRegistry: serviceRegistryMock, + secondary: SECONDARY, + wallet: USER_1_WALLET, + sourceEndpoint: SELF + }) + + /** TODO all asserts */ + }) + + it('Primary correctly syncs from secondary when both have same data', async function () { + /** + * seed local DB with CN + audiusUser + file entry from export + * sync + * confirm final clock state = 37 + */ + + const { + exportObj, + cnodeUser: exportedCnodeUser, + audiusUsers: exportedAudiusUsers, + tracks: exportedTracks, + files: exportedFiles, + clockRecords: exportedClockRecords + } = unpackExportDataFromFile(exportFilePath) + + setupExportMock(SECONDARY, exportObj) + setupIPFSRouteMocks() + + /** TODO confirm empty state */ + + /** + * Write all secondary state to primary + */ + const exportedNonTrackFiles = exportedFiles.filter(file => models.File.NonTrackTypes.includes(file.type)) + const exportedTrackFiles = exportedFiles.filter(file => models.File.TrackTypes.includes(file.type)) + const transaction = await models.sequelize.transaction() + await models.CNodeUser.create({ ...exportedCnodeUser }, { transaction }) + await models.ClockRecord.bulkCreate(exportedClockRecords, { transaction }) + await models.File.bulkCreate(exportedNonTrackFiles, { transaction }) + await models.AudiusUser.bulkCreate(exportedAudiusUsers, { transaction }) + await models.File.bulkCreate(exportedTrackFiles, { transaction }) + await models.Track.bulkCreate(exportedTracks, { transaction }) + await transaction.commit() + + /** TODO confirm non-empty initial state */ + + await primarySyncFromSecondaryStub({ + serviceRegistry: serviceRegistryMock, + secondary: SECONDARY, + wallet: USER_1_WALLET + }) + + /** TODO all asserts */ + + }) + + it.skip('Primary correctly syncs from secondary when primary has superset of secondary state', async function () { + + }) + + it.skip('Primary correctly syncs from secondary when secondary has state requiring multiple syncs', async function () {}) +}) \ No newline at end of file diff --git a/creator-node/test/sync/assets/export.json b/creator-node/test/sync/assets/export.json new file mode 100644 index 00000000000..ffee8294d3d --- /dev/null +++ b/creator-node/test/sync/assets/export.json @@ -0,0 +1,982 @@ +{ + "data": { + "cnodeUsers": { + "adfafaa0-044f-4389-b7df-358eec428a6e": { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "walletPublicKey": "0xadd36bad12002f1097cdb7ee24085c28e960fc32", + "lastLogin": null, + "latestBlockNumber": 10, + "clock": 37, + "createdAt": "2022-01-13T01:43:17.423Z", + "updatedAt": "2022-01-13T01:43:22.785Z", + "audiusUsers": [ + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 2, + "blockchainId": "1", + "metadataFileUUID": "4ddcf031-90be-476d-bcc1-8d32da6774d5", + "metadataJSON": { + "testField": "testValue" + }, + "coverArtFileUUID": null, + "profilePicFileUUID": null, + "createdAt": "2022-01-13T01:43:17.576Z", + "updatedAt": "2022-01-13T01:43:17.576Z" + } + ], + "tracks": [ + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 37, + "blockchainId": "1", + "metadataFileUUID": "bbb58f39-b751-4a30-8432-e4da747a4db3", + "metadataJSON": { + "test": "field1", + "owner_id": 1, + "track_segments": [ + { + "duration": 6.016, + "multihash": "QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku" + }, + { + "duration": 5.994667, + "multihash": "QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd" + }, + { + "duration": 5.994667, + "multihash": "QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch" + }, + { + "duration": 5.994667, + "multihash": "QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ" + }, + { + "duration": 6.016, + "multihash": "QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP" + }, + { + "duration": 5.994667, + "multihash": "QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF" + }, + { + "duration": 5.994667, + "multihash": "Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB" + }, + { + "duration": 5.994667, + "multihash": "QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan" + }, + { + "duration": 6.016, + "multihash": "QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr" + }, + { + "duration": 5.994667, + "multihash": "QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB" + }, + { + "duration": 5.994667, + "multihash": "QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi" + }, + { + "duration": 5.994667, + "multihash": "QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik" + }, + { + "duration": 6.016, + "multihash": "QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH" + }, + { + "duration": 5.994667, + "multihash": "QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d" + }, + { + "duration": 5.994667, + "multihash": "QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh" + }, + { + "duration": 5.994667, + "multihash": "QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9" + }, + { + "duration": 6.016, + "multihash": "QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7" + }, + { + "duration": 5.994667, + "multihash": "QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q" + }, + { + "duration": 5.994667, + "multihash": "QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot" + }, + { + "duration": 5.994667, + "multihash": "QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA" + }, + { + "duration": 6.016, + "multihash": "QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ" + }, + { + "duration": 5.994667, + "multihash": "QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx" + }, + { + "duration": 5.994667, + "multihash": "QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF" + }, + { + "duration": 5.994667, + "multihash": "QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY" + }, + { + "duration": 6.016, + "multihash": "QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY" + }, + { + "duration": 5.994667, + "multihash": "QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn" + }, + { + "duration": 5.994667, + "multihash": "QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT" + }, + { + "duration": 5.994667, + "multihash": "QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk" + }, + { + "duration": 6.016, + "multihash": "QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy" + }, + { + "duration": 5.994667, + "multihash": "QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8" + }, + { + "duration": 5.994667, + "multihash": "QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp" + }, + { + "duration": 1.456, + "multihash": "QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas" + } + ] + }, + "coverArtFileUUID": null, + "createdAt": "2022-01-13T01:43:22.788Z", + "updatedAt": "2022-01-13T01:43:22.788Z" + } + ], + "files": [ + { + "fileUUID": "4ddcf031-90be-476d-bcc1-8d32da6774d5", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": null, + "multihash": "QmcyuCtU24R2N9Ejs5tsm7vebBhs3g6Qnhh6w3ZoEYBrUE", + "sourceFile": null, + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/BrU/QmcyuCtU24R2N9Ejs5tsm7vebBhs3g6Qnhh6w3ZoEYBrUE", + "type": "metadata", + "clock": 1, + "skipped": false, + "createdAt": "2022-01-13T01:43:17.521Z", + "updatedAt": "2022-01-13T01:43:17.521Z" + }, + { + "fileUUID": "812ba680-9832-443b-9365-fe97d3863dd3", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmRcVgkrvsyUeY3x3zvc6EhDSFjZJ8QD5qyKowHsPeWKFT", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/WKF/QmRcVgkrvsyUeY3x3zvc6EhDSFjZJ8QD5qyKowHsPeWKFT", + "type": "copy320", + "clock": 3, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.583Z", + "updatedAt": "2022-01-13T01:43:22.793Z" + }, + { + "fileUUID": "69751ddb-7fe3-426f-b7f2-c44a91fc1481", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/TLk/QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku", + "type": "track", + "clock": 4, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.589Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "c3657080-59fe-47ba-be42-ddea3556c7a0", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/Kuk/QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd", + "type": "track", + "clock": 5, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.594Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "50bb2027-a48b-4a68-a274-6272a04597a4", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/28C/QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch", + "type": "track", + "clock": 6, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.599Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "b7a380e1-291f-4fd1-9474-c9d1044999e9", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/1vK/QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ", + "type": "track", + "clock": 7, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.604Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "e4d9239d-7bbc-4601-8f57-40bb96adfe09", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/xvg/QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP", + "type": "track", + "clock": 8, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.609Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "87dfd7e0-0bb5-448e-aa5c-4c4062cca933", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/qgG/QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF", + "type": "track", + "clock": 9, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.614Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "c2c98066-3143-4fb7-88ef-0065f9d8459e", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/GMk/Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB", + "type": "track", + "clock": 10, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.618Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "8e9fbb5b-2ca0-4bdf-bab2-3cbd7397aed3", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/Bza/QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan", + "type": "track", + "clock": 11, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.623Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "260d3c60-fe11-4947-8d1e-4d58e828396f", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/38t/QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr", + "type": "track", + "clock": 12, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.628Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "8e86a8dd-0dc4-49c5-b67c-6cea3c903ba1", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/NDx/QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB", + "type": "track", + "clock": 13, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.633Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "58c9b364-15d7-44d9-96b9-b1039ecbaf72", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/MdW/QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi", + "type": "track", + "clock": 14, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.638Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "cfec6bb9-0e79-4a56-b58f-d776bf50018a", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/c2i/QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik", + "type": "track", + "clock": 15, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.642Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "8252c868-65e9-4744-90e9-a1e92da526ad", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/ZNj/QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH", + "type": "track", + "clock": 16, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.647Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "1c18c8ac-b6d1-4925-8ae5-7f3f72fc4e4b", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/n63/QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d", + "type": "track", + "clock": 17, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.651Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "c45a301f-dcf5-49d4-9e6a-ce596456d15d", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/sHo/QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh", + "type": "track", + "clock": 18, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.656Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "1f7c5189-e461-4695-ae2d-c0170efaa4cb", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/ZJK/QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9", + "type": "track", + "clock": 19, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.660Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "900d3460-85d6-4b3d-b4e9-7ec157b6680b", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/Wmj/QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7", + "type": "track", + "clock": 20, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.665Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "3b807fbc-efab-40a8-9212-e296d0f3c62c", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/SQ9/QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q", + "type": "track", + "clock": 21, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.669Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "ca8c2724-756e-49f2-9c60-f73dfdae31ef", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/Jto/QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot", + "type": "track", + "clock": 22, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.674Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "65e5bdb9-ccb6-4f94-85c1-132b086d2b76", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/6EK/QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA", + "type": "track", + "clock": 23, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.680Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "9da88800-e4be-4f23-b5fe-9866f320dc7c", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/zHn/QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ", + "type": "track", + "clock": 24, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.684Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "d25829c0-a809-4aae-a406-0b87daf054a5", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/VSE/QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx", + "type": "track", + "clock": 25, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.689Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "b0522208-c411-4cc3-b2e2-73f9aa19ca47", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/QEL/QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF", + "type": "track", + "clock": 26, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.694Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "b46b425c-7a34-4292-bba5-1631cfb47a6e", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/5CP/QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY", + "type": "track", + "clock": 27, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.699Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "c3aff4cf-4e99-4656-a8ff-2ada4cec8f24", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/wDS/QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY", + "type": "track", + "clock": 28, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.703Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "c4211da7-368a-4a55-9664-684f61d661af", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/iRH/QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn", + "type": "track", + "clock": 29, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.707Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "54e7e86f-8472-4072-b84e-3fd4a071010c", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/5TX/QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT", + "type": "track", + "clock": 30, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.712Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "d3e3e827-2d7a-4d2e-89ae-05abcb8e8bf0", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/VvS/QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk", + "type": "track", + "clock": 31, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.716Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "78754294-b837-485f-b139-8be673d43127", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/LZX/QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy", + "type": "track", + "clock": 32, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.720Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "c5ddcbc4-f5dd-46d0-8518-9c2030e78985", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/TAE/QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8", + "type": "track", + "clock": 33, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.727Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "d9c2d03b-6fcd-4033-b7a4-c8040581930b", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/XTd/QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp", + "type": "track", + "clock": 34, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.731Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "289004c4-d88a-4c06-b05b-b2c0070a8544", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": 1, + "multihash": "QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas", + "sourceFile": "c82d3441-fb94-4f6d-9796-16311405c306.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/MFa/QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas", + "type": "track", + "clock": 35, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.737Z", + "updatedAt": "2022-01-13T01:43:22.800Z" + }, + { + "fileUUID": "bbb58f39-b751-4a30-8432-e4da747a4db3", + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "trackBlockchainId": null, + "multihash": "QmcYCwJHfCScqDFEXd78hkdFbyn98mJWXhej4QhQx2aPaZ", + "sourceFile": null, + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/aPa/QmcYCwJHfCScqDFEXd78hkdFbyn98mJWXhej4QhQx2aPaZ", + "type": "metadata", + "clock": 36, + "skipped": false, + "createdAt": "2022-01-13T01:43:22.759Z", + "updatedAt": "2022-01-13T01:43:22.759Z" + } + ], + "clockRecords": [ + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 1, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:17.517Z", + "updatedAt": "2022-01-13T01:43:17.517Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 2, + "sourceTable": "AudiusUser", + "createdAt": "2022-01-13T01:43:17.574Z", + "updatedAt": "2022-01-13T01:43:17.574Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 3, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.581Z", + "updatedAt": "2022-01-13T01:43:22.581Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 4, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.587Z", + "updatedAt": "2022-01-13T01:43:22.587Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 5, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.592Z", + "updatedAt": "2022-01-13T01:43:22.592Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 6, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.597Z", + "updatedAt": "2022-01-13T01:43:22.597Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 7, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.603Z", + "updatedAt": "2022-01-13T01:43:22.603Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 8, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.608Z", + "updatedAt": "2022-01-13T01:43:22.608Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 9, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.612Z", + "updatedAt": "2022-01-13T01:43:22.612Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 10, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.617Z", + "updatedAt": "2022-01-13T01:43:22.617Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 11, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.621Z", + "updatedAt": "2022-01-13T01:43:22.621Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 12, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.626Z", + "updatedAt": "2022-01-13T01:43:22.626Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 13, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.631Z", + "updatedAt": "2022-01-13T01:43:22.631Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 14, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.636Z", + "updatedAt": "2022-01-13T01:43:22.636Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 15, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.640Z", + "updatedAt": "2022-01-13T01:43:22.640Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 16, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.645Z", + "updatedAt": "2022-01-13T01:43:22.645Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 17, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.650Z", + "updatedAt": "2022-01-13T01:43:22.650Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 18, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.654Z", + "updatedAt": "2022-01-13T01:43:22.654Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 19, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.659Z", + "updatedAt": "2022-01-13T01:43:22.659Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 20, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.663Z", + "updatedAt": "2022-01-13T01:43:22.663Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 21, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.668Z", + "updatedAt": "2022-01-13T01:43:22.668Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 22, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.673Z", + "updatedAt": "2022-01-13T01:43:22.673Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 23, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.678Z", + "updatedAt": "2022-01-13T01:43:22.678Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 24, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.683Z", + "updatedAt": "2022-01-13T01:43:22.683Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 25, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.687Z", + "updatedAt": "2022-01-13T01:43:22.687Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 26, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.692Z", + "updatedAt": "2022-01-13T01:43:22.692Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 27, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.697Z", + "updatedAt": "2022-01-13T01:43:22.697Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 28, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.702Z", + "updatedAt": "2022-01-13T01:43:22.702Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 29, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.706Z", + "updatedAt": "2022-01-13T01:43:22.706Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 30, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.710Z", + "updatedAt": "2022-01-13T01:43:22.710Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 31, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.714Z", + "updatedAt": "2022-01-13T01:43:22.714Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 32, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.718Z", + "updatedAt": "2022-01-13T01:43:22.718Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 33, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.724Z", + "updatedAt": "2022-01-13T01:43:22.724Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 34, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.730Z", + "updatedAt": "2022-01-13T01:43:22.730Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 35, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.734Z", + "updatedAt": "2022-01-13T01:43:22.734Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 36, + "sourceTable": "File", + "createdAt": "2022-01-13T01:43:22.757Z", + "updatedAt": "2022-01-13T01:43:22.757Z" + }, + { + "cnodeUserUUID": "adfafaa0-044f-4389-b7df-358eec428a6e", + "clock": 37, + "sourceTable": "Track", + "createdAt": "2022-01-13T01:43:22.786Z", + "updatedAt": "2022-01-13T01:43:22.786Z" + } + ], + "clockInfo": { + "requestedClockRangeMin": 0, + "requestedClockRangeMax": 9999, + "localClockMax": 37 + } + } + }, + "ipfsIDObj": { + "id": "Qmbg2iXM4SeMn9CiALonDt9ejFUosEF9rkT38CcCpAVqxs", + "publicKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCuwbUXm0VdPWKFcCftIZsTmoRSpgGhSL/+RtsAys7+SoUzx/1IOqTu0apq1eH9hpjm0s/m92MIiifSohk9vv5OIoxarlQjxAIumG9dxGntquv2X9p3INjTm5gD2Zafl6xnf36EUHi4lyuJtMSOCt2O1nVHOBoTZ5q2oZoQTsO8pI3qZvNZwsFSJzgH3GOBJlVDXTTgNmoMN1INKEWDmsQiTRXfVq+m4l1eOpxthkXg2tlkxxRm3kcOzHuQrGCln3AFnMgom2X1Xcm4eG0zWKLvlOgX88zHYuguf6YTTfqxNHSo33NtflZxZI+CQq0RxvkWKiJKqbpmlK+shJnv9Z3zAgMBAAE=", + "addresses": [ + "/ip4/127.0.0.1/tcp/4001/ipfs/Qmbg2iXM4SeMn9CiALonDt9ejFUosEF9rkT38CcCpAVqxs", + "/ip4/172.17.0.2/tcp/4001/ipfs/Qmbg2iXM4SeMn9CiALonDt9ejFUosEF9rkT38CcCpAVqxs", + "/ip4/35.238.168.173/tcp/4001/ipfs/Qmbg2iXM4SeMn9CiALonDt9ejFUosEF9rkT38CcCpAVqxs" + ], + "agentVersion": "go-ipfs/0.4.23/6ce9a35", + "protocolVersion": "ipfs/0.1.0" + } + }, + "signer": "0x1eC723075E67a1a2B6969dC5CfF0C6793cb36D25", + "timestamp": "2022-01-13T01:43:22.836Z", + "signature": "0x7be7aa97620877ddfc07b8a8709de347150dd8f635a67b8b82776899c2ec905a33e63f5d14a7a6cf095eaa7a774f7ecb3b6347ce3c9d2f0b03a9d8cf060da1e31c" +} \ No newline at end of file diff --git a/creator-node/test/sync/assets/files/1vK/QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ b/creator-node/test/sync/assets/files/1vK/QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ new file mode 100644 index 00000000000..7bb7c09cc93 Binary files /dev/null and b/creator-node/test/sync/assets/files/1vK/QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ differ diff --git a/creator-node/test/sync/assets/files/28C/QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch b/creator-node/test/sync/assets/files/28C/QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch new file mode 100644 index 00000000000..48721562a41 Binary files /dev/null and b/creator-node/test/sync/assets/files/28C/QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch differ diff --git a/creator-node/test/sync/assets/files/38t/QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr b/creator-node/test/sync/assets/files/38t/QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr new file mode 100644 index 00000000000..ad75583c84e Binary files /dev/null and b/creator-node/test/sync/assets/files/38t/QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr differ diff --git a/creator-node/test/sync/assets/files/5CP/QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY b/creator-node/test/sync/assets/files/5CP/QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY new file mode 100644 index 00000000000..73be356e4b4 Binary files /dev/null and b/creator-node/test/sync/assets/files/5CP/QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY differ diff --git a/creator-node/test/sync/assets/files/5TX/QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT b/creator-node/test/sync/assets/files/5TX/QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT new file mode 100644 index 00000000000..cb88ec85fda Binary files /dev/null and b/creator-node/test/sync/assets/files/5TX/QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT differ diff --git a/creator-node/test/sync/assets/files/6EK/QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA b/creator-node/test/sync/assets/files/6EK/QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA new file mode 100644 index 00000000000..4d14a425699 Binary files /dev/null and b/creator-node/test/sync/assets/files/6EK/QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA differ diff --git a/creator-node/test/sync/assets/files/BrU/QmcyuCtU24R2N9Ejs5tsm7vebBhs3g6Qnhh6w3ZoEYBrUE b/creator-node/test/sync/assets/files/BrU/QmcyuCtU24R2N9Ejs5tsm7vebBhs3g6Qnhh6w3ZoEYBrUE new file mode 100644 index 00000000000..f9a2501963a --- /dev/null +++ b/creator-node/test/sync/assets/files/BrU/QmcyuCtU24R2N9Ejs5tsm7vebBhs3g6Qnhh6w3ZoEYBrUE @@ -0,0 +1 @@ +{"testField":"testValue"} \ No newline at end of file diff --git a/creator-node/test/sync/assets/files/Bza/QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan b/creator-node/test/sync/assets/files/Bza/QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan new file mode 100644 index 00000000000..7203423b6d6 Binary files /dev/null and b/creator-node/test/sync/assets/files/Bza/QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan differ diff --git a/creator-node/test/sync/assets/files/GMk/Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB b/creator-node/test/sync/assets/files/GMk/Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB new file mode 100644 index 00000000000..34e68cbd812 Binary files /dev/null and b/creator-node/test/sync/assets/files/GMk/Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB differ diff --git a/creator-node/test/sync/assets/files/Jto/QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot b/creator-node/test/sync/assets/files/Jto/QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot new file mode 100644 index 00000000000..9e9f165f96b Binary files /dev/null and b/creator-node/test/sync/assets/files/Jto/QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot differ diff --git a/creator-node/test/sync/assets/files/Kuk/QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd b/creator-node/test/sync/assets/files/Kuk/QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd new file mode 100644 index 00000000000..a1455520759 Binary files /dev/null and b/creator-node/test/sync/assets/files/Kuk/QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd differ diff --git a/creator-node/test/sync/assets/files/LZX/QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy b/creator-node/test/sync/assets/files/LZX/QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy new file mode 100644 index 00000000000..621e7872f16 Binary files /dev/null and b/creator-node/test/sync/assets/files/LZX/QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy differ diff --git a/creator-node/test/sync/assets/files/MFa/QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas b/creator-node/test/sync/assets/files/MFa/QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas new file mode 100644 index 00000000000..37ec7f6723d Binary files /dev/null and b/creator-node/test/sync/assets/files/MFa/QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas differ diff --git a/creator-node/test/sync/assets/files/MdW/QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi b/creator-node/test/sync/assets/files/MdW/QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi new file mode 100644 index 00000000000..86ded3621fe Binary files /dev/null and b/creator-node/test/sync/assets/files/MdW/QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi differ diff --git a/creator-node/test/sync/assets/files/NDx/QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB b/creator-node/test/sync/assets/files/NDx/QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB new file mode 100644 index 00000000000..afe43d8d6bd Binary files /dev/null and b/creator-node/test/sync/assets/files/NDx/QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB differ diff --git a/creator-node/test/sync/assets/files/QEL/QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF b/creator-node/test/sync/assets/files/QEL/QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF new file mode 100644 index 00000000000..c0c99d5ab71 Binary files /dev/null and b/creator-node/test/sync/assets/files/QEL/QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF differ diff --git a/creator-node/test/sync/assets/files/SQ9/QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q b/creator-node/test/sync/assets/files/SQ9/QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q new file mode 100644 index 00000000000..af20f878dcb Binary files /dev/null and b/creator-node/test/sync/assets/files/SQ9/QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q differ diff --git a/creator-node/test/sync/assets/files/TAE/QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8 b/creator-node/test/sync/assets/files/TAE/QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8 new file mode 100644 index 00000000000..1608b7ec515 Binary files /dev/null and b/creator-node/test/sync/assets/files/TAE/QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8 differ diff --git a/creator-node/test/sync/assets/files/TLk/QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku b/creator-node/test/sync/assets/files/TLk/QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku new file mode 100644 index 00000000000..cd7b3f8c59e Binary files /dev/null and b/creator-node/test/sync/assets/files/TLk/QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku differ diff --git a/creator-node/test/sync/assets/files/VSE/QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx b/creator-node/test/sync/assets/files/VSE/QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx new file mode 100644 index 00000000000..c64d74ea8fd Binary files /dev/null and b/creator-node/test/sync/assets/files/VSE/QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx differ diff --git a/creator-node/test/sync/assets/files/VvS/QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk b/creator-node/test/sync/assets/files/VvS/QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk new file mode 100644 index 00000000000..fda87a8ffc2 Binary files /dev/null and b/creator-node/test/sync/assets/files/VvS/QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk differ diff --git a/creator-node/test/sync/assets/files/WKF/QmRcVgkrvsyUeY3x3zvc6EhDSFjZJ8QD5qyKowHsPeWKFT b/creator-node/test/sync/assets/files/WKF/QmRcVgkrvsyUeY3x3zvc6EhDSFjZJ8QD5qyKowHsPeWKFT new file mode 100644 index 00000000000..dd71f23fc40 Binary files /dev/null and b/creator-node/test/sync/assets/files/WKF/QmRcVgkrvsyUeY3x3zvc6EhDSFjZJ8QD5qyKowHsPeWKFT differ diff --git a/creator-node/test/sync/assets/files/Wmj/QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7 b/creator-node/test/sync/assets/files/Wmj/QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7 new file mode 100644 index 00000000000..32bb453168d Binary files /dev/null and b/creator-node/test/sync/assets/files/Wmj/QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7 differ diff --git a/creator-node/test/sync/assets/files/XTd/QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp b/creator-node/test/sync/assets/files/XTd/QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp new file mode 100644 index 00000000000..150f76b9503 Binary files /dev/null and b/creator-node/test/sync/assets/files/XTd/QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp differ diff --git a/creator-node/test/sync/assets/files/ZJK/QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9 b/creator-node/test/sync/assets/files/ZJK/QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9 new file mode 100644 index 00000000000..e8ceb570253 Binary files /dev/null and b/creator-node/test/sync/assets/files/ZJK/QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9 differ diff --git a/creator-node/test/sync/assets/files/ZNj/QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH b/creator-node/test/sync/assets/files/ZNj/QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH new file mode 100644 index 00000000000..70e76dfd887 Binary files /dev/null and b/creator-node/test/sync/assets/files/ZNj/QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH differ diff --git a/creator-node/test/sync/assets/files/aPa/QmcYCwJHfCScqDFEXd78hkdFbyn98mJWXhej4QhQx2aPaZ b/creator-node/test/sync/assets/files/aPa/QmcYCwJHfCScqDFEXd78hkdFbyn98mJWXhej4QhQx2aPaZ new file mode 100644 index 00000000000..6bbd33e8bb6 --- /dev/null +++ b/creator-node/test/sync/assets/files/aPa/QmcYCwJHfCScqDFEXd78hkdFbyn98mJWXhej4QhQx2aPaZ @@ -0,0 +1 @@ +{"test":"field1","owner_id":1,"track_segments":[{"multihash":"QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku","duration":6.016},{"multihash":"QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd","duration":5.994667},{"multihash":"QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch","duration":5.994667},{"multihash":"QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ","duration":5.994667},{"multihash":"QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP","duration":6.016},{"multihash":"QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF","duration":5.994667},{"multihash":"Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB","duration":5.994667},{"multihash":"QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan","duration":5.994667},{"multihash":"QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr","duration":6.016},{"multihash":"QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB","duration":5.994667},{"multihash":"QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi","duration":5.994667},{"multihash":"QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik","duration":5.994667},{"multihash":"QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH","duration":6.016},{"multihash":"QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d","duration":5.994667},{"multihash":"QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh","duration":5.994667},{"multihash":"QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9","duration":5.994667},{"multihash":"QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7","duration":6.016},{"multihash":"QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q","duration":5.994667},{"multihash":"QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot","duration":5.994667},{"multihash":"QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA","duration":5.994667},{"multihash":"QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ","duration":6.016},{"multihash":"QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx","duration":5.994667},{"multihash":"QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF","duration":5.994667},{"multihash":"QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY","duration":5.994667},{"multihash":"QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY","duration":6.016},{"multihash":"QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn","duration":5.994667},{"multihash":"QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT","duration":5.994667},{"multihash":"QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk","duration":5.994667},{"multihash":"QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy","duration":6.016},{"multihash":"QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8","duration":5.994667},{"multihash":"QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp","duration":5.994667},{"multihash":"QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas","duration":1.456}]} \ No newline at end of file diff --git a/creator-node/test/sync/assets/files/c2i/QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik b/creator-node/test/sync/assets/files/c2i/QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik new file mode 100644 index 00000000000..860d1e572f7 Binary files /dev/null and b/creator-node/test/sync/assets/files/c2i/QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik differ diff --git a/creator-node/test/sync/assets/files/iRH/QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn b/creator-node/test/sync/assets/files/iRH/QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn new file mode 100644 index 00000000000..721702a9003 Binary files /dev/null and b/creator-node/test/sync/assets/files/iRH/QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn differ diff --git a/creator-node/test/sync/assets/files/n63/QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d b/creator-node/test/sync/assets/files/n63/QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d new file mode 100644 index 00000000000..ed2a59afcbe Binary files /dev/null and b/creator-node/test/sync/assets/files/n63/QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d differ diff --git a/creator-node/test/sync/assets/files/qgG/QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF b/creator-node/test/sync/assets/files/qgG/QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF new file mode 100644 index 00000000000..c47508cf8b9 Binary files /dev/null and b/creator-node/test/sync/assets/files/qgG/QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF differ diff --git a/creator-node/test/sync/assets/files/sHo/QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh b/creator-node/test/sync/assets/files/sHo/QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh new file mode 100644 index 00000000000..7a1e4df7f06 Binary files /dev/null and b/creator-node/test/sync/assets/files/sHo/QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh differ diff --git a/creator-node/test/sync/assets/files/wDS/QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY b/creator-node/test/sync/assets/files/wDS/QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY new file mode 100644 index 00000000000..3c81bbcfd4f Binary files /dev/null and b/creator-node/test/sync/assets/files/wDS/QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY differ diff --git a/creator-node/test/sync/assets/files/xvg/QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP b/creator-node/test/sync/assets/files/xvg/QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP new file mode 100644 index 00000000000..53181ad2cf6 Binary files /dev/null and b/creator-node/test/sync/assets/files/xvg/QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP differ diff --git a/creator-node/test/sync/assets/files/zHn/QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ b/creator-node/test/sync/assets/files/zHn/QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ new file mode 100644 index 00000000000..997dff6a749 Binary files /dev/null and b/creator-node/test/sync/assets/files/zHn/QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ differ diff --git a/creator-node/test/sync/assets/realExport.json b/creator-node/test/sync/assets/realExport.json new file mode 100644 index 00000000000..935d9e0eb42 --- /dev/null +++ b/creator-node/test/sync/assets/realExport.json @@ -0,0 +1,982 @@ +{ + "data": { + "cnodeUsers": { + "b0a4c279-8c11-48b3-b6fa-65bca9b42846": { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "walletPublicKey": "0xadd36bad12002f1097cdb7ee24085c28e960fc32", + "lastLogin": null, + "latestBlockNumber": 10, + "clock": 37, + "createdAt": "2022-01-19T16:47:10.133Z", + "updatedAt": "2022-01-19T16:47:16.750Z", + "audiusUsers": [ + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 2, + "blockchainId": "1", + "metadataFileUUID": "78afc0fb-6278-4d68-8888-430b7edc0064", + "metadataJSON": { + "testField": "testValue" + }, + "coverArtFileUUID": null, + "profilePicFileUUID": null, + "createdAt": "2022-01-19T16:47:10.447Z", + "updatedAt": "2022-01-19T16:47:10.447Z" + } + ], + "tracks": [ + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 37, + "blockchainId": "1", + "metadataFileUUID": "daca28dc-11f2-4d25-89a8-cb8b868d28bd", + "metadataJSON": { + "test": "field1", + "owner_id": 1, + "track_segments": [ + { + "duration": 6.016, + "multihash": "QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku" + }, + { + "duration": 5.994667, + "multihash": "QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd" + }, + { + "duration": 5.994667, + "multihash": "QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch" + }, + { + "duration": 5.994667, + "multihash": "QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ" + }, + { + "duration": 6.016, + "multihash": "QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP" + }, + { + "duration": 5.994667, + "multihash": "QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF" + }, + { + "duration": 5.994667, + "multihash": "Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB" + }, + { + "duration": 5.994667, + "multihash": "QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan" + }, + { + "duration": 6.016, + "multihash": "QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr" + }, + { + "duration": 5.994667, + "multihash": "QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB" + }, + { + "duration": 5.994667, + "multihash": "QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi" + }, + { + "duration": 5.994667, + "multihash": "QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik" + }, + { + "duration": 6.016, + "multihash": "QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH" + }, + { + "duration": 5.994667, + "multihash": "QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d" + }, + { + "duration": 5.994667, + "multihash": "QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh" + }, + { + "duration": 5.994667, + "multihash": "QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9" + }, + { + "duration": 6.016, + "multihash": "QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7" + }, + { + "duration": 5.994667, + "multihash": "QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q" + }, + { + "duration": 5.994667, + "multihash": "QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot" + }, + { + "duration": 5.994667, + "multihash": "QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA" + }, + { + "duration": 6.016, + "multihash": "QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ" + }, + { + "duration": 5.994667, + "multihash": "QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx" + }, + { + "duration": 5.994667, + "multihash": "QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF" + }, + { + "duration": 5.994667, + "multihash": "QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY" + }, + { + "duration": 6.016, + "multihash": "QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY" + }, + { + "duration": 5.994667, + "multihash": "QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn" + }, + { + "duration": 5.994667, + "multihash": "QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT" + }, + { + "duration": 5.994667, + "multihash": "QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk" + }, + { + "duration": 6.016, + "multihash": "QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy" + }, + { + "duration": 5.994667, + "multihash": "QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8" + }, + { + "duration": 5.994667, + "multihash": "QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp" + }, + { + "duration": 1.456, + "multihash": "QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas" + } + ] + }, + "coverArtFileUUID": null, + "createdAt": "2022-01-19T16:47:16.753Z", + "updatedAt": "2022-01-19T16:47:16.753Z" + } + ], + "files": [ + { + "fileUUID": "78afc0fb-6278-4d68-8888-430b7edc0064", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": null, + "multihash": "QmcyuCtU24R2N9Ejs5tsm7vebBhs3g6Qnhh6w3ZoEYBrUE", + "sourceFile": null, + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/BrU/QmcyuCtU24R2N9Ejs5tsm7vebBhs3g6Qnhh6w3ZoEYBrUE", + "type": "metadata", + "clock": 1, + "skipped": false, + "createdAt": "2022-01-19T16:47:10.400Z", + "updatedAt": "2022-01-19T16:47:10.400Z" + }, + { + "fileUUID": "0f976b75-336b-46db-b5d1-8cf4555d4028", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmRcVgkrvsyUeY3x3zvc6EhDSFjZJ8QD5qyKowHsPeWKFT", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/WKF/QmRcVgkrvsyUeY3x3zvc6EhDSFjZJ8QD5qyKowHsPeWKFT", + "type": "copy320", + "clock": 3, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.549Z", + "updatedAt": "2022-01-19T16:47:16.759Z" + }, + { + "fileUUID": "a2c2cb83-43da-4147-a09c-668bbc02c7e5", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/TLk/QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku", + "type": "track", + "clock": 4, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.554Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "59a0e87c-bb87-469b-b1d7-e74fa725a85a", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/Kuk/QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd", + "type": "track", + "clock": 5, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.558Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "58a399e4-3994-4577-b015-7c2bbccd96e9", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/28C/QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch", + "type": "track", + "clock": 6, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.563Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "ed5f80d5-6c48-432e-b037-357ec787bcd3", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/1vK/QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ", + "type": "track", + "clock": 7, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.567Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "75d9e248-893e-4ea4-a93e-d8d89cf903b9", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/xvg/QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP", + "type": "track", + "clock": 8, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.571Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "91da240b-f1c4-46a8-823f-f511557d2704", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/qgG/QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF", + "type": "track", + "clock": 9, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.575Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "2566005b-217d-4dcf-85f9-2836fd5fe30d", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/GMk/Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB", + "type": "track", + "clock": 10, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.580Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "1e56ea90-d85f-45da-b547-cfed497b7a5e", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/Bza/QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan", + "type": "track", + "clock": 11, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.584Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "27848af3-fa4d-445e-b996-eb435552bfd5", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/38t/QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr", + "type": "track", + "clock": 12, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.588Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "a98aea07-3a0e-4299-880d-da2e7c69e704", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/NDx/QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB", + "type": "track", + "clock": 13, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.592Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "0fc098bc-276c-494b-9f4c-8dac7c64eafc", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/MdW/QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi", + "type": "track", + "clock": 14, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.597Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "82346229-98b9-478f-9d55-0488cb352f59", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/c2i/QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik", + "type": "track", + "clock": 15, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.602Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "d7b18470-4bf1-45c8-8dd0-1d4b1da4d597", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/ZNj/QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH", + "type": "track", + "clock": 16, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.607Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "d26a3115-7c93-4b80-94e0-1a00ca40a8e4", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/n63/QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d", + "type": "track", + "clock": 17, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.611Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "16dd9336-899c-4b45-a4c7-cd6b80c9ef0a", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/sHo/QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh", + "type": "track", + "clock": 18, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.615Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "641024f8-d963-46a4-bd20-fbdc4a395f66", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/ZJK/QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9", + "type": "track", + "clock": 19, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.620Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "d08e3c0b-1ae6-445a-9686-2fe1ab4f10a0", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/Wmj/QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7", + "type": "track", + "clock": 20, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.624Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "5882f542-4170-4ea5-a26f-17eeeab87c91", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/SQ9/QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q", + "type": "track", + "clock": 21, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.628Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "64763fc5-b3d8-40d8-ab92-04c3fb74e974", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/Jto/QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot", + "type": "track", + "clock": 22, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.633Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "fef4d1cd-5365-469d-99e4-36ab6a6bf5bf", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/6EK/QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA", + "type": "track", + "clock": 23, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.637Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "f6cb0f07-92a5-4476-b2c7-b961ec669391", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/zHn/QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ", + "type": "track", + "clock": 24, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.642Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "9974f644-ccc9-4b5a-8f8a-1dba8446d7b7", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/VSE/QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx", + "type": "track", + "clock": 25, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.647Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "7ff369a5-3290-4439-825c-70a13526a3f5", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/QEL/QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF", + "type": "track", + "clock": 26, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.651Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "9125e4a0-b791-44db-9a9c-93d99c58be0b", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/5CP/QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY", + "type": "track", + "clock": 27, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.656Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "6f71b5f4-b3e1-4cf6-884e-71a36fe716ef", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/wDS/QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY", + "type": "track", + "clock": 28, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.660Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "93d60d20-4873-4d38-a57d-51ce22d31630", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/iRH/QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn", + "type": "track", + "clock": 29, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.664Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "1108cf26-5f2b-4302-9e84-da270df9f0f5", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/5TX/QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT", + "type": "track", + "clock": 30, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.668Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "cf05e17c-8c8a-4187-9a7c-7e59b13d01bc", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/VvS/QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk", + "type": "track", + "clock": 31, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.672Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "2606fbb7-3361-4f18-b16f-ece16d5ec5e9", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/LZX/QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy", + "type": "track", + "clock": 32, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.676Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "31120186-3eb0-4853-8063-27e648596660", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/TAE/QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8", + "type": "track", + "clock": 33, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.683Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "34ae93a9-780a-4e67-af00-e75efe697b28", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/XTd/QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp", + "type": "track", + "clock": 34, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.688Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "7ee73655-b365-4822-ab4d-84802f886614", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": 1, + "multihash": "QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas", + "sourceFile": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3", + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/MFa/QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas", + "type": "track", + "clock": 35, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.692Z", + "updatedAt": "2022-01-19T16:47:16.765Z" + }, + { + "fileUUID": "daca28dc-11f2-4d25-89a8-cb8b868d28bd", + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "trackBlockchainId": null, + "multihash": "QmcYCwJHfCScqDFEXd78hkdFbyn98mJWXhej4QhQx2aPaZ", + "sourceFile": null, + "fileName": null, + "dirMultihash": null, + "storagePath": "test_file_storage/files/aPa/QmcYCwJHfCScqDFEXd78hkdFbyn98mJWXhej4QhQx2aPaZ", + "type": "metadata", + "clock": 36, + "skipped": false, + "createdAt": "2022-01-19T16:47:16.727Z", + "updatedAt": "2022-01-19T16:47:16.727Z" + } + ], + "clockRecords": [ + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 1, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:10.397Z", + "updatedAt": "2022-01-19T16:47:10.397Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 2, + "sourceTable": "AudiusUser", + "createdAt": "2022-01-19T16:47:10.445Z", + "updatedAt": "2022-01-19T16:47:10.445Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 3, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.547Z", + "updatedAt": "2022-01-19T16:47:16.547Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 4, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.552Z", + "updatedAt": "2022-01-19T16:47:16.552Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 5, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.557Z", + "updatedAt": "2022-01-19T16:47:16.557Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 6, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.561Z", + "updatedAt": "2022-01-19T16:47:16.561Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 7, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.566Z", + "updatedAt": "2022-01-19T16:47:16.566Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 8, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.570Z", + "updatedAt": "2022-01-19T16:47:16.570Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 9, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.574Z", + "updatedAt": "2022-01-19T16:47:16.574Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 10, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.578Z", + "updatedAt": "2022-01-19T16:47:16.578Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 11, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.582Z", + "updatedAt": "2022-01-19T16:47:16.582Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 12, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.586Z", + "updatedAt": "2022-01-19T16:47:16.586Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 13, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.591Z", + "updatedAt": "2022-01-19T16:47:16.591Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 14, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.595Z", + "updatedAt": "2022-01-19T16:47:16.595Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 15, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.601Z", + "updatedAt": "2022-01-19T16:47:16.601Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 16, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.605Z", + "updatedAt": "2022-01-19T16:47:16.605Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 17, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.609Z", + "updatedAt": "2022-01-19T16:47:16.609Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 18, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.614Z", + "updatedAt": "2022-01-19T16:47:16.614Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 19, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.618Z", + "updatedAt": "2022-01-19T16:47:16.618Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 20, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.622Z", + "updatedAt": "2022-01-19T16:47:16.622Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 21, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.627Z", + "updatedAt": "2022-01-19T16:47:16.627Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 22, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.631Z", + "updatedAt": "2022-01-19T16:47:16.631Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 23, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.636Z", + "updatedAt": "2022-01-19T16:47:16.636Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 24, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.640Z", + "updatedAt": "2022-01-19T16:47:16.640Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 25, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.645Z", + "updatedAt": "2022-01-19T16:47:16.645Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 26, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.650Z", + "updatedAt": "2022-01-19T16:47:16.650Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 27, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.654Z", + "updatedAt": "2022-01-19T16:47:16.654Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 28, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.658Z", + "updatedAt": "2022-01-19T16:47:16.658Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 29, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.662Z", + "updatedAt": "2022-01-19T16:47:16.662Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 30, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.666Z", + "updatedAt": "2022-01-19T16:47:16.666Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 31, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.670Z", + "updatedAt": "2022-01-19T16:47:16.670Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 32, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.674Z", + "updatedAt": "2022-01-19T16:47:16.674Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 33, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.681Z", + "updatedAt": "2022-01-19T16:47:16.681Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 34, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.686Z", + "updatedAt": "2022-01-19T16:47:16.686Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 35, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.691Z", + "updatedAt": "2022-01-19T16:47:16.691Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 36, + "sourceTable": "File", + "createdAt": "2022-01-19T16:47:16.725Z", + "updatedAt": "2022-01-19T16:47:16.725Z" + }, + { + "cnodeUserUUID": "b0a4c279-8c11-48b3-b6fa-65bca9b42846", + "clock": 37, + "sourceTable": "Track", + "createdAt": "2022-01-19T16:47:16.751Z", + "updatedAt": "2022-01-19T16:47:16.751Z" + } + ], + "clockInfo": { + "requestedClockRangeMin": 0, + "requestedClockRangeMax": 9999, + "localClockMax": 37 + } + } + }, + "ipfsIDObj": { + "id": "QmZvtTwkaantxw5raytS8JLkQTpufdgvfeGFuzEwdfEdUt", + "publicKey": "CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr5DuScNPNNMnSLv8RRqb2vKZPrAVnRp8xeUzOxYfY+ytf86RO5lkBU6y2/u6oRidteb/HPV7gtkDwouBejXgg1zNq8Ya6bleE62iTqtAwB7+lrrPgMKtblljFt0fY6G7gea13KazLVbH+Y9hQlAQoAsZwFFdWbVLXLejq/BLZsHe/vx6A6uA7TSdOjY8sjWP3E3KWUNxOPVFwdk1dSlOnQDbHVJ3vSUmi5zpMYKYr0SXHa5OG20gq/qaP1hIRKl3IU0NoGweyLhgGxAtDSugfXAPyvj3BIwjRHc9x8B7XlmpQxfamLWz4u5SbiTB4c09S1Pmsn4JXKGeOoFK00tWNAgMBAAE=", + "addresses": [ + "/ip4/127.0.0.1/tcp/4001/ipfs/QmZvtTwkaantxw5raytS8JLkQTpufdgvfeGFuzEwdfEdUt", + "/ip4/172.17.0.2/tcp/4001/ipfs/QmZvtTwkaantxw5raytS8JLkQTpufdgvfeGFuzEwdfEdUt", + "/ip4/35.238.168.173/tcp/4001/ipfs/QmZvtTwkaantxw5raytS8JLkQTpufdgvfeGFuzEwdfEdUt" + ], + "agentVersion": "go-ipfs/0.4.23/6ce9a35", + "protocolVersion": "ipfs/0.1.0" + } + }, + "signer": "0x1eC723075E67a1a2B6969dC5CfF0C6793cb36D25", + "timestamp": "2022-01-19T16:47:16.799Z", + "signature": "0x89667da41c86dd726e403aeadca3f6944486cbb013eafadb86e191166d9720ae6059aa718c594cbc0dee392c231cce88e65ee8e0143206fbd5a302293f1003e91b" +} \ No newline at end of file diff --git a/creator-node/test/sync/assets/testTrack.mp3 b/creator-node/test/sync/assets/testTrack.mp3 new file mode 100644 index 00000000000..9e95f1d4083 Binary files /dev/null and b/creator-node/test/sync/assets/testTrack.mp3 differ diff --git a/creator-node/test/sync/assets/trackMetadata.json b/creator-node/test/sync/assets/trackMetadata.json new file mode 100644 index 00000000000..bb5e19c1ae7 --- /dev/null +++ b/creator-node/test/sync/assets/trackMetadata.json @@ -0,0 +1,137 @@ +{ + "metadata": { + "test": "field1", + "owner_id": 1, + "track_segments": [ + { + "multihash": "QmQQhdN8918ix1MibmSek9orTLKqZSSzqUvJAtEfMqTLku", + "duration": 6.016 + }, + { + "multihash": "QmSMQGu2vrE6UwXiZDCxyJwTsCcpPrYNBPJBL4by4LKukd", + "duration": 5.994667 + }, + { + "multihash": "QmdkuVqHUDhYePhXska15UEZHPike11NPHu5tsy9eY28Ch", + "duration": 5.994667 + }, + { + "multihash": "QmZQd76dJjyd4bLL29gT5urxqY6JRS9vjnAUpuwkS91vKJ", + "duration": 5.994667 + }, + { + "multihash": "QmVLLQTLByH65Ht2J3bKyFXZaKjUDho6742dSqk9nqxvgP", + "duration": 6.016 + }, + { + "multihash": "QmZJipWYtJDebvUbti2xSA3cijYxQ6YmVnkotA5SXiqgGF", + "duration": 5.994667 + }, + { + "multihash": "Qmb5QCepqy2k26gvFKALcP74YvAaPS3AtCqNhXeH9YGMkB", + "duration": 5.994667 + }, + { + "multihash": "QmRiU48Xr3vD3n6KhzJAvcuchmPbbumBefa3y7aeNaBzan", + "duration": 5.994667 + }, + { + "multihash": "QmZwxEQSjS6uruo4vXwkh6fUhvjBK4Yp83qq9AHX4R38tr", + "duration": 6.016 + }, + { + "multihash": "QmPyxEWcfJ9qnQm8CNPChzLoDinhTbi9zVtyGTJBeoNDxB", + "duration": 5.994667 + }, + { + "multihash": "QmULEs3yq1kCVMBoiSMSVv7JopVz6K3TgncqcriioNMdWi", + "duration": 5.994667 + }, + { + "multihash": "QmaqSv1buZQ6enNwsae4B3paPDAXjcXZTRY6cUscPoc2ik", + "duration": 5.994667 + }, + { + "multihash": "QmYG4TY55oLoe5RjsABJmcUrAUWgyCc8jRpvBTWkT7ZNjH", + "duration": 6.016 + }, + { + "multihash": "QmXyGwndsBXmM2xCW4VaWkRqp3pv4Cr6yAKfHnYDDTn63d", + "duration": 5.994667 + }, + { + "multihash": "QmdjBYvYirb4ANjK55ktmE4eokEKVsej9tjeWuZDxasHoh", + "duration": 5.994667 + }, + { + "multihash": "QmWbAbeaDGSM3LiARCKk2EWnLtgUqKcD3hK4nvHwdUZJK9", + "duration": 5.994667 + }, + { + "multihash": "QmTacAUYq6qUdMqkNEUPohYLbAu3eEetwAW9QYn53TWmj7", + "duration": 6.016 + }, + { + "multihash": "QmNbt57KLtgbEKz97qSoJDZ2DteEjmg455ZBvnoCHySQ9q", + "duration": 5.994667 + }, + { + "multihash": "QmNpX6fcViqDTBBJmVKsAVcNmQdqZRV4zqzUuxfP4DJtot", + "duration": 5.994667 + }, + { + "multihash": "QmcKcZfASQZJUVaHKtFxN7CqyhwD9oiQNeCkZxfDo36EKA", + "duration": 5.994667 + }, + { + "multihash": "QmZw9kq8iVcVtNzRapsS9K3zRiaLNsa8bD9Dbw4QBXzHnJ", + "duration": 6.016 + }, + { + "multihash": "QmYCWrB5vGJ3REaTRkL35QgpZNDN25E1XtEpMQVctPVSEx", + "duration": 5.994667 + }, + { + "multihash": "QmX9ePR6kL5djVNvGKzXogjigPfRtA75XKj3d71ZiAQELF", + "duration": 5.994667 + }, + { + "multihash": "QmPuDcNoaa8NmSa7mYPWneNDPqMmwiDoTnr6pAsrAg5CPY", + "duration": 5.994667 + }, + { + "multihash": "QmZjt4SvTXhTqeoKTNPAKTPwfd8QadRWz9tjKXg9fEwDSY", + "duration": 6.016 + }, + { + "multihash": "QmXLcCb5PTVtUusxtyo3pckJjCqeZKUt5VNv1FAbEdiRHn", + "duration": 5.994667 + }, + { + "multihash": "QmX1mAPkDpBTooEAoZbLv1PWFfaaq2aQ9gLX2Bcktd5TXT", + "duration": 5.994667 + }, + { + "multihash": "QmPhEZL5o7uAtes7g1kmxRGaLeRXE9YF4h8CPHk1KYVvSk", + "duration": 5.994667 + }, + { + "multihash": "QmZEnMbMH9FzkLHn4LW4CGjW5FPHg6dw3PCYhHjCXrLZXy", + "duration": 6.016 + }, + { + "multihash": "QmekrmmfT4oHvRst8aYioWLTPGAGHcNEj3as8cVgo1TAE8", + "duration": 5.994667 + }, + { + "multihash": "QmckKtz4nhmwsyFGNJ8pLpLSPPkgPGXq44bQRQwi75XTdp", + "duration": 5.994667 + }, + { + "multihash": "QmbJ9JdEDT6bzdcsY5grX71nq2etzNZXsCM6H84k1GMFas", + "duration": 1.456 + } + ] + }, + "source_file": "3f70aa4d-827e-4b8f-a7f0-13324f83deb3.mp3" +} \ No newline at end of file diff --git a/creator-node/test/sync/assets/userMetadata.json b/creator-node/test/sync/assets/userMetadata.json new file mode 100644 index 00000000000..302519638a4 --- /dev/null +++ b/creator-node/test/sync/assets/userMetadata.json @@ -0,0 +1,5 @@ +{ + "metadata": { + "testField": "testValue" + } +} \ No newline at end of file