diff --git a/servers/cu/README.md b/servers/cu/README.md index ca272f30c..bd1a85c21 100644 --- a/servers/cu/README.md +++ b/servers/cu/README.md @@ -120,6 +120,8 @@ There are a few environment variables that you can set. Besides `Checkpoint` creation to the file system. (You must explicitly enable `Checkpoint` creation by setting - `DISABLE_PROCESS_FILE_CHECKPOINT_CREATION` to `'false'`) +- `DISABLE_NON_HASH_CHAIN_CHECKPOINTS`: Whether to disable non-hash chain + checkpoints. Set to `true` to disable non-hash chain checkpoints. - `EAGER_CHECKPOINT_ACCUMULATED_GAS_THRESHOLD`: If a process uses this amount of gas, then it will immediately create a Checkpoint at the end of the evaluation stream. diff --git a/servers/cu/src/bootstrap.js b/servers/cu/src/bootstrap.js index bd9b69547..4ff58ec1b 100644 --- a/servers/cu/src/bootstrap.js +++ b/servers/cu/src/bootstrap.js @@ -209,6 +209,7 @@ export const createApis = async (ctx) => { }) ctx.logger('Process Arweave Checkpoint creation is set to "%s"', !ctx.DISABLE_PROCESS_CHECKPOINT_CREATION) + ctx.logger('Disabling non-hash chain checkpoints is set to "%s"', ctx.DISABLE_NON_HASH_CHAIN_CHECKPOINTS) ctx.logger('Process File Checkpoint creation is set to "%s"', !ctx.DISABLE_PROCESS_FILE_CHECKPOINT_CREATION) ctx.logger('Ignoring Arweave Checkpoints for processes [ %s ]', ctx.PROCESS_IGNORE_ARWEAVE_CHECKPOINTS.join(', ')) ctx.logger('Ignoring Arweave Checkpoints [ %s ]', ctx.IGNORE_ARWEAVE_CHECKPOINTS.join(', ')) @@ -333,6 +334,7 @@ export const createApis = async (ctx) => { PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: ctx.PROCESS_IGNORE_ARWEAVE_CHECKPOINTS, IGNORE_ARWEAVE_CHECKPOINTS: ctx.IGNORE_ARWEAVE_CHECKPOINTS, PROCESS_CHECKPOINT_TRUSTED_OWNERS: ctx.PROCESS_CHECKPOINT_TRUSTED_OWNERS, + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: ctx.DISABLE_NON_HASH_CHAIN_CHECKPOINTS, logger }), saveLatestProcessMemory: AoProcessClient.saveLatestProcessMemoryWith({ diff --git a/servers/cu/src/config.js b/servers/cu/src/config.js index 70f668554..a80e51253 100644 --- a/servers/cu/src/config.js +++ b/servers/cu/src/config.js @@ -144,6 +144,7 @@ const CONFIG_ENVS = { DISABLE_PROCESS_CHECKPOINT_CREATION: process.env.DISABLE_PROCESS_CHECKPOINT_CREATION !== 'false', DISABLE_PROCESS_FILE_CHECKPOINT_CREATION: process.env.DISABLE_PROCESS_FILE_CHECKPOINT_CREATION !== 'false', DISABLE_PROCESS_EVALUATION_CACHE: process.env.DISABLE_PROCESS_EVALUATION_CACHE, + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: process.env.DISABLE_NON_HASH_CHAIN_CHECKPOINTS === 'true', /** * EAGER_CHECKPOINT_ACCUMULATED_GAS_THRESHOLD: Amount of gas for 2 hours of continuous compute (300_000_000_000_000) * This was calculated by creating a process built to do continuous compute. After 2 hours, this process used @@ -196,6 +197,7 @@ const CONFIG_ENVS = { DISABLE_PROCESS_CHECKPOINT_CREATION: process.env.DISABLE_PROCESS_CHECKPOINT_CREATION !== 'false', DISABLE_PROCESS_FILE_CHECKPOINT_CREATION: process.env.DISABLE_PROCESS_FILE_CHECKPOINT_CREATION !== 'false', DISABLE_PROCESS_EVALUATION_CACHE: process.env.DISABLE_PROCESS_EVALUATION_CACHE, + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: process.env.DISABLE_NON_HASH_CHAIN_CHECKPOINTS === 'true', /** * EAGER_CHECKPOINT_ACCUMULATED_GAS_THRESHOLD: Amount of gas for 2 hours of continuous compute (300_000_000_000_000) * This was calculated by creating a process built to do continuous compute by adding and clearing a table. diff --git a/servers/cu/src/domain/model.js b/servers/cu/src/domain/model.js index c8d77addf..5419019a9 100644 --- a/servers/cu/src/domain/model.js +++ b/servers/cu/src/domain/model.js @@ -108,6 +108,10 @@ export const domainConfigSchema = z.object({ * a RU */ DISABLE_PROCESS_EVALUATION_CACHE: z.preprocess((val) => !!val, z.boolean()), + /** + * Whether to disable non-hash chain checkpoints + */ + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: z.preprocess((val) => !!val, z.boolean()), /** * If a process uses this amount of * gas, then it will immediately create a Checkpoint at the end of the diff --git a/servers/cu/src/effects/ao-process.js b/servers/cu/src/effects/ao-process.js index 197417bf1..e27d3cf72 100644 --- a/servers/cu/src/effects/ao-process.js +++ b/servers/cu/src/effects/ao-process.js @@ -798,6 +798,7 @@ export function findLatestProcessMemoryWith ({ PROCESS_IGNORE_ARWEAVE_CHECKPOINTS, IGNORE_ARWEAVE_CHECKPOINTS, PROCESS_CHECKPOINT_TRUSTED_OWNERS, + DISABLE_NON_HASH_CHAIN_CHECKPOINTS, logger: _logger }) { const logger = _logger.child('ao-process:findLatestProcessMemory') @@ -852,6 +853,17 @@ export function findLatestProcessMemoryWith ({ return transduce( compose( map(prop('node')), + filter((node) => { + if (DISABLE_NON_HASH_CHAIN_CHECKPOINTS) { + const hasAssignment = node.tags.find((tag) => tag.name === 'Assignment') + const hasHashChain = node.tags.find((tag) => tag.name === 'Hash-Chain') + if (!hasAssignment || !hasHashChain) { + logger('Encountered Checkpoint "%s" from Arweave without Assignment or Hash-Chain. Skipping...', node.id) + return false + } + } + return true + }), filter((node) => { const isIgnored = isCheckpointIgnored(node.id) if (isIgnored) logger('Encountered Ignored Checkpoint "%s" from Arweave. Skipping...', node.id) diff --git a/servers/cu/src/effects/ao-process.test.js b/servers/cu/src/effects/ao-process.test.js index 16a4414b4..24983631d 100644 --- a/servers/cu/src/effects/ao-process.test.js +++ b/servers/cu/src/effects/ao-process.test.js @@ -348,6 +348,7 @@ describe('ao-process', () => { PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], IGNORE_ARWEAVE_CHECKPOINTS: [], PROCESS_CHECKPOINT_TRUSTED_OWNERS: [], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false, DIR: 'fake/directory/' } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -500,7 +501,8 @@ describe('ao-process', () => { logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], IGNORE_ARWEAVE_CHECKPOINTS: [], - PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -618,6 +620,7 @@ describe('ao-process', () => { PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], IGNORE_ARWEAVE_CHECKPOINTS: [], PROCESS_CHECKPOINT_TRUSTED_OWNERS: [], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false, DIR: 'fake/directory/' } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -759,7 +762,8 @@ describe('ao-process', () => { logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], IGNORE_ARWEAVE_CHECKPOINTS: [], - PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -859,6 +863,7 @@ describe('ao-process', () => { const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith({ ...deps, PROCESS_CHECKPOINT_TRUSTED_OWNERS: ['wallet-123', 'wallet-456'], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false, queryGateway: async ({ query, variables }) => { try { assert.deepStrictEqual(variables, { owners: ['wallet-123', 'wallet-456'], processId: 'process-123', limit: 50 }) @@ -993,6 +998,91 @@ describe('ao-process', () => { assert.deepStrictEqual(res.ordinate, '11') }) + test('should use the latest retrieved checkpoint that is NOT ignored and HAS an assignment and hash-chain', async () => { + const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith({ + ...deps, + queryGateway: async () => ({ + data: { + transactions: { + edges: [ + edges[0], // node to be used + { + // node to be ignored due to 'ignored' id + node: { + ...edges[0].node, + id: 'ignored', + tags: [ + { name: 'Module', value: `${cachedEval.moduleId}` }, + { name: 'Assignment', value: `${cachedEval.assignmentId}` }, + { name: 'Hash-Chain', value: `${cachedEval.hashChain}` }, + { name: 'Timestamp', value: `${cachedEval.timestamp + 1000}` }, + { name: 'Epoch', value: `${cachedEval.epoch}` }, + { name: 'Nonce', value: '12' }, + { name: 'Block-Height', value: `${cachedEval.blockHeight}` }, + { name: 'Content-Encoding', value: `${cachedEval.encoding}` } + ] + } + }, + { + // node to be ignored due to missing assignment and hash-chain + node: { + ...edges[0].node, + id: 'tx-456', + tags: [ + { name: 'Module', value: `${cachedEval.moduleId}` }, + { name: 'Timestamp', value: `${cachedEval.timestamp + 2000}` }, + { name: 'Epoch', value: `${cachedEval.epoch}` }, + { name: 'Nonce', value: '13' }, + { name: 'Block-Height', value: `${cachedEval.blockHeight}` }, + { name: 'Content-Encoding', value: `${cachedEval.encoding}` } + ] + } + }, + { + // node to be ignored due to missing assignment + node: { + ...edges[0].node, + id: 'tx-456', + tags: [ + { name: 'Module', value: `${cachedEval.moduleId}` }, + { name: 'Hash-Chain', value: `${cachedEval.hashChain}` }, + { name: 'Timestamp', value: `${cachedEval.timestamp + 3000}` }, + { name: 'Epoch', value: `${cachedEval.epoch}` }, + { name: 'Nonce', value: '14' }, + { name: 'Block-Height', value: `${cachedEval.blockHeight}` }, + { name: 'Content-Encoding', value: `${cachedEval.encoding}` } + ] + } + }, + { + // node to be ignored due to missing hash-chain + node: { + ...edges[0].node, + id: 'tx-456', + tags: [ + { name: 'Module', value: `${cachedEval.moduleId}` }, + { name: 'Assignment', value: `${cachedEval.assignmentId}` }, + { name: 'Timestamp', value: `${cachedEval.timestamp + 4000}` }, + { name: 'Epoch', value: `${cachedEval.epoch}` }, + { name: 'Nonce', value: '15' }, + { name: 'Block-Height', value: `${cachedEval.blockHeight}` }, + { name: 'Content-Encoding', value: `${cachedEval.encoding}` } + ] + } + } + ] + } + } + }), + IGNORE_ARWEAVE_CHECKPOINTS: ['ignored'], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: true + })) + + const res = await findLatestProcessMemory(target) + + assert.deepStrictEqual(res.ordinate, '11') + }) + test('should retry querying the gateway', async () => { let count = 1 const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith({ @@ -1094,7 +1184,8 @@ describe('ao-process', () => { logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], IGNORE_ARWEAVE_CHECKPOINTS: [], - PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false } const COLDSTART = { src: 'cold_start', @@ -1170,7 +1261,8 @@ describe('ao-process', () => { logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], IGNORE_ARWEAVE_CHECKPOINTS: [], - PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -1207,7 +1299,8 @@ describe('ao-process', () => { logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], IGNORE_ARWEAVE_CHECKPOINTS: [], - PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -1247,6 +1340,7 @@ describe('ao-process', () => { PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], IGNORE_ARWEAVE_CHECKPOINTS: [], PROCESS_CHECKPOINT_TRUSTED_OWNERS: [], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false, fileExists: () => true, DIR: 'fake/directory/' } @@ -1301,6 +1395,7 @@ describe('ao-process', () => { logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], PROCESS_CHECKPOINT_TRUSTED_OWNERS: [], + DISABLE_NON_HASH_CHAIN_CHECKPOINTS: false, fileExists: () => true, DIR: 'fake/directory/' }