diff --git a/servers/cu/README.md b/servers/cu/README.md index 58c1a3875..b56905f3b 100644 --- a/servers/cu/README.md +++ b/servers/cu/README.md @@ -121,9 +121,10 @@ There are a few environment variables that you can set. Besides - `RESTRICT_PROCESSES`: A list of process ids that the CU should restrict aka. a `blacklist` (defaults to none) - `ALLOW_PROCESSES`: The counterpart to RESTRICT_PROCESSES. When configured the - CU will only execute these processes aka. a `whitelist` (defaults to allow all processes) + CU will only execute these processes aka. a `whitelist` (defaults to allow all processes) - `ALLOW_OWNERS`: A list of process owners, whose processes are allowed to execute on the CU aka. an owner `whitelist` (defaults to allow all owners) +- `PROCESS_CHECKPOINT_TRUSTED_OWNERS`: A list of wallets whose checkpoints are trusted and the CU can start from ## Tests diff --git a/servers/cu/src/config.js b/servers/cu/src/config.js index 86c832ea5..cd17fd24b 100644 --- a/servers/cu/src/config.js +++ b/servers/cu/src/config.js @@ -124,6 +124,7 @@ const CONFIG_ENVS = { WASM_BINARY_FILE_DIRECTORY: process.env.WASM_BINARY_FILE_DIRECTORY || tmpdir(), PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: process.env.PROCESS_IGNORE_ARWEAVE_CHECKPOINTS || [], IGNORE_ARWEAVE_CHECKPOINTS: process.env.IGNORE_ARWEAVE_CHECKPOINTS || [], + PROCESS_CHECKPOINT_TRUSTED_OWNERS: process.env.IGNORE_ARWEAVE_CHECKPOINTS || [], PROCESS_CHECKPOINT_FILE_DIRECTORY: process.env.PROCESS_CHECKPOINT_FILE_DIRECTORY || tmpdir(), PROCESS_MEMORY_CACHE_MAX_SIZE: process.env.PROCESS_MEMORY_CACHE_MAX_SIZE || bytes('500mb'), PROCESS_MEMORY_CACHE_TTL: process.env.PROCESS_MEMORY_CACHE_TTL || ms('24h'), @@ -170,6 +171,7 @@ const CONFIG_ENVS = { WASM_BINARY_FILE_DIRECTORY: process.env.WASM_BINARY_FILE_DIRECTORY || tmpdir(), PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: process.env.PROCESS_IGNORE_ARWEAVE_CHECKPOINTS || [], IGNORE_ARWEAVE_CHECKPOINTS: process.env.IGNORE_ARWEAVE_CHECKPOINTS || [], + PROCESS_CHECKPOINT_TRUSTED_OWNERS: process.env.IGNORE_ARWEAVE_CHECKPOINTS || [], PROCESS_CHECKPOINT_FILE_DIRECTORY: process.env.PROCESS_CHECKPOINT_FILE_DIRECTORY || tmpdir(), PROCESS_MEMORY_CACHE_MAX_SIZE: process.env.PROCESS_MEMORY_CACHE_MAX_SIZE || bytes('500mb'), PROCESS_MEMORY_CACHE_TTL: process.env.PROCESS_MEMORY_CACHE_TTL || ms('24h'), diff --git a/servers/cu/src/domain/client/ao-process.js b/servers/cu/src/domain/client/ao-process.js index 774e93cb3..a10381490 100644 --- a/servers/cu/src/domain/client/ao-process.js +++ b/servers/cu/src/domain/client/ao-process.js @@ -4,7 +4,7 @@ import { Readable } from 'node:stream' import { basename, join } from 'node:path' import { fromPromise, of, Rejected, Resolved } from 'hyper-async' -import { add, always, applySpec, compose, defaultTo, evolve, filter, head, identity, map, omit, path, pathOr, pipe, prop, transduce } from 'ramda' +import { add, always, applySpec, compose, defaultTo, evolve, filter, head, identity, ifElse, isEmpty, map, omit, path, pathOr, pipe, prop, transduce } from 'ramda' import { z } from 'zod' import { LRUCache } from 'lru-cache' import AsyncLock from 'async-lock' @@ -476,6 +476,7 @@ export function findLatestProcessMemoryWith ({ loadTransactionData, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS, IGNORE_ARWEAVE_CHECKPOINTS, + PROCESS_CHECKPOINT_TRUSTED_OWNERS, logger: _logger }) { const logger = _logger.child('ao-process:findLatestProcessMemory') @@ -492,7 +493,7 @@ export function findLatestProcessMemoryWith ({ const GET_AO_PROCESS_CHECKPOINTS = ` query GetAoProcessCheckpoints( - $owner: String! + $owners: [String!]! $processId: String! $limit: Int! ) { @@ -502,7 +503,7 @@ export function findLatestProcessMemoryWith ({ { name: "Type", values: ["Checkpoint"] } { name: "Data-Protocol", values: ["ao"] } ], - owners: [$owner] + owners: $owners, first: $limit, sort: HEIGHT_DESC ) { @@ -731,13 +732,20 @@ export function findLatestProcessMemoryWith ({ return Rejected(args) } - return address() - .chain((owner) => queryCheckpoints({ - query: GET_AO_PROCESS_CHECKPOINTS, - variables: { owner, processId, limit: 50 }, - processId, - before: LATEST - })) + return of({ PROCESS_CHECKPOINT_TRUSTED_OWNERS }) + .chain(({ PROCESS_CHECKPOINT_TRUSTED_OWNERS }) => { + if (isEmpty(PROCESS_CHECKPOINT_TRUSTED_OWNERS)) return address() + return Resolved(PROCESS_CHECKPOINT_TRUSTED_OWNERS) + }) + .map((owners) => ifElse(Array.isArray, always(owners), always([owners]))(owners)) + .chain((owners) => { + return queryCheckpoints({ + query: GET_AO_PROCESS_CHECKPOINTS, + variables: { owners, processId, limit: 50 }, + processId, + before: LATEST + }) + }) .map(path(['data', 'transactions', 'edges'])) .map(determineLatestCheckpoint) .chain((latestCheckpoint) => { diff --git a/servers/cu/src/domain/client/ao-process.test.js b/servers/cu/src/domain/client/ao-process.test.js index 05684edc1..0244d0308 100644 --- a/servers/cu/src/domain/client/ao-process.test.js +++ b/servers/cu/src/domain/client/ao-process.test.js @@ -354,7 +354,8 @@ describe('ao-process', () => { loadTransactionData: async () => assert.fail('should not call if found in cache'), logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], - IGNORE_ARWEAVE_CHECKPOINTS: [] + IGNORE_ARWEAVE_CHECKPOINTS: [], + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -503,7 +504,8 @@ describe('ao-process', () => { }, logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], - IGNORE_ARWEAVE_CHECKPOINTS: [] + IGNORE_ARWEAVE_CHECKPOINTS: [], + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -596,7 +598,7 @@ describe('ao-process', () => { queryGateway: async ({ query, variables }) => { assert.ok(query) assert.deepStrictEqual(variables, { - owner: 'address-123', + owners: ['address-123'], processId: PROCESS, limit: 50 }) @@ -610,7 +612,8 @@ describe('ao-process', () => { }, logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], - IGNORE_ARWEAVE_CHECKPOINTS: [] + IGNORE_ARWEAVE_CHECKPOINTS: [], + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -695,6 +698,29 @@ describe('ao-process', () => { ordinate: cachedEval.ordinate }) }) + + test('should query all trusted owners if not empty', async () => { + let assertionFailed = false + let addressCalled = false + const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith({ + ...deps, + PROCESS_CHECKPOINT_TRUSTED_OWNERS: ['wallet-123', 'wallet-456'], + queryGateway: async ({ query, variables }) => { + try { + assert.deepStrictEqual(variables, { owners: ['wallet-123', 'wallet-456'], processId: 'process-123', limit: 50 }) + assert.ok(query) + } catch (e) { + assertionFailed = true + } + return { data: { transactions: { edges: [] } } } + }, + address: async () => { addressCalled = true } + })) + await findLatestProcessMemory(target) + + assert.ok(!assertionFailed) + assert.ok(!addressCalled) + }) }) test('should decode if needed', async () => { @@ -850,7 +876,7 @@ describe('ao-process', () => { queryCheckpointGateway: async ({ query, variables }) => { assert.ok(query) assert.deepStrictEqual(variables, { - owner: 'address-123', + owners: ['address-123'], processId: PROCESS, limit: 50 }) @@ -904,7 +930,8 @@ describe('ao-process', () => { }, logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], - IGNORE_ARWEAVE_CHECKPOINTS: [] + IGNORE_ARWEAVE_CHECKPOINTS: [], + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] } const COLDSTART = { src: 'cold_start', @@ -977,7 +1004,8 @@ describe('ao-process', () => { }, logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], - IGNORE_ARWEAVE_CHECKPOINTS: [] + IGNORE_ARWEAVE_CHECKPOINTS: [], + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -1014,7 +1042,8 @@ describe('ao-process', () => { }, logger, PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], - IGNORE_ARWEAVE_CHECKPOINTS: [] + IGNORE_ARWEAVE_CHECKPOINTS: [], + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) @@ -1063,7 +1092,8 @@ describe('ao-process', () => { return new Response(Readable.toWeb(Readable.from(zipped))) }, logger, - PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [] + PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: [], + PROCESS_CHECKPOINT_TRUSTED_OWNERS: [] } const findLatestProcessMemory = findLatestProcessMemorySchema.implement(findLatestProcessMemoryWith(deps)) diff --git a/servers/cu/src/domain/index.js b/servers/cu/src/domain/index.js index 1596f44bd..b0644bfae 100644 --- a/servers/cu/src/domain/index.js +++ b/servers/cu/src/domain/index.js @@ -209,6 +209,7 @@ export const createApis = async (ctx) => { queryCheckpointGateway: ArweaveClient.queryGatewayWith({ fetch: ctx.fetch, GRAPHQL_URL: ctx.CHECKPOINT_GRAPHQL_URL, logger }), 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, logger }), saveLatestProcessMemory: AoProcessClient.saveLatestProcessMemoryWith({ diff --git a/servers/cu/src/domain/model.js b/servers/cu/src/domain/model.js index 4f6bb7aef..197f871ce 100644 --- a/servers/cu/src/domain/model.js +++ b/servers/cu/src/domain/model.js @@ -133,6 +133,10 @@ export const domainConfigSchema = z.object({ * on Arweave. */ PROCESS_IGNORE_ARWEAVE_CHECKPOINTS: commaDelimitedArraySchema, + /** + * An array of trusted owner wallets that Arweave checkpoints can be queried from. + */ + PROCESS_CHECKPOINT_TRUSTED_OWNERS: commaDelimitedArraySchema, /** * An array of checkpoint ids that should not be used */