Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cu)!: validate proper tags on process #205 #220

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion servers/cu/src/domain/client/ao-su.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Transform, pipeline } from 'node:stream'
import { of } from 'hyper-async'
import { always, applySpec, evolve, filter, isNotNil, last, path, pathOr, pipe, prop } from 'ramda'

import { findRawTag, padBlockHeight } from '../lib/utils.js'
import { findRawTag, padBlockHeight } from '../utils.js'

export const loadMessagesWith = ({ fetch, SU_URL, logger: _logger, pageSize }) => {
const logger = _logger.child('ao-su:loadMessages')
Expand Down
4 changes: 2 additions & 2 deletions servers/cu/src/domain/lib/evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const ctxSchema = z.object({
*/

function addHandler (ctx) {
return of(ctx.src)
return of(ctx.module)
.chain(fromPromise(AoLoader))
.map((handle) => ({ handle, ...ctx }))
}
Expand Down Expand Up @@ -64,7 +64,7 @@ function doesMessageIdExistWith ({ findMessageId }) {
* @property {string} id - the contract id
* @property {Record<string, any>} state - the initial state
* @property {string} from - the initial state sortKey
* @property {ArrayBuffer} src - the contract wasm as an array buffer
* @property {ArrayBuffer} module - the contract wasm as an array buffer
* @property {Record<string, any>[]} action - an array of interactions to apply
*
* @callback Evaluate
Expand Down
16 changes: 8 additions & 8 deletions servers/cu/src/domain/lib/evaluate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('evaluate', () => {
ctx = {
id: 'ctr-1234',
from: 'sort-key-start',
src: readFileSync('./test/processes/happy/process.wasm'),
module: readFileSync('./test/processes/happy/process.wasm'),
buffer: null,
messages: toAsyncIterable([
{
Expand Down Expand Up @@ -142,7 +142,7 @@ describe('evaluate', () => {
const ctx = {
id: 'ctr-1234',
from: 'sort-key-start',
src: readFileSync('./test/processes/happy/process.wasm'),
module: readFileSync('./test/processes/happy/process.wasm'),
buffer: null,
messages: toAsyncIterable([
{
Expand Down Expand Up @@ -197,7 +197,7 @@ describe('evaluate', () => {
const ctx = {
id: 'ctr-1234',
from: 'sort-key-start',
src: readFileSync('./test/processes/happy/process.wasm'),
module: readFileSync('./test/processes/happy/process.wasm'),
buffer: null,
messages: toAsyncIterable([
{
Expand Down Expand Up @@ -255,7 +255,7 @@ describe('evaluate', () => {
const ctx = {
id: 'ctr-1234',
from: 'sort-key-start',
src: readFileSync('./test/processes/happy/process.wasm'),
module: readFileSync('./test/processes/happy/process.wasm'),
/**
* In reality this would be an illegible byte array, since it's format
* will be determined by whatever the underlying runtime is, in this case,
Expand Down Expand Up @@ -291,7 +291,7 @@ describe('evaluate', () => {
const ctx = {
id: 'ctr-1234',
from: 'sort-key-start',
src: readFileSync('./test/processes/sad/process.wasm'),
module: readFileSync('./test/processes/sad/process.wasm'),
buffer: Buffer.from('Hello', 'utf-8'),
messages: toAsyncIterable([
{
Expand Down Expand Up @@ -338,7 +338,7 @@ describe('evaluate', () => {
const ctx = {
id: 'ctr-1234',
from: 'sort-key-start',
src: readFileSync('./test/processes/sad/process.wasm'),
module: readFileSync('./test/processes/sad/process.wasm'),
buffer: Buffer.from('Hello', 'utf-8'),
messages: toAsyncIterable([
{
Expand Down Expand Up @@ -378,7 +378,7 @@ describe('evaluate', () => {
const ctx = {
id: 'ctr-1234',
from: 'sort-key-start',
src: readFileSync('./test/processes/sad/process.wasm'),
module: readFileSync('./test/processes/sad/process.wasm'),
buffer: Buffer.from('Hello', 'utf-8'),
messages: toAsyncIterable([
{
Expand Down Expand Up @@ -417,7 +417,7 @@ describe('evaluate', () => {
const ctx = {
id: 'ctr-1234',
from: 'sort-key-start',
src: readFileSync('./test/processes/sad/process.wasm'),
module: readFileSync('./test/processes/sad/process.wasm'),
buffer: null,
messages: toAsyncIterable([
{
Expand Down
2 changes: 1 addition & 1 deletion servers/cu/src/domain/lib/hydrateMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import WarpArBundles from 'warp-arbundles'

import { loadTransactionDataSchema, loadTransactionMetaSchema } from '../dal.js'
import { streamSchema } from '../model.js'
import { findRawTag } from './utils.js'
import { findRawTag } from '../utils.js'

const { createData } = WarpArBundles

Expand Down
2 changes: 1 addition & 1 deletion servers/cu/src/domain/lib/loadMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ms from 'ms'

import { messageSchema, streamSchema } from '../model.js'
import { loadBlocksMetaSchema, loadMessagesSchema, loadTimestampSchema } from '../dal.js'
import { padBlockHeight } from './utils.js'
import { padBlockHeight } from '../utils.js'

/**
* - { name: 'Cron-Interval', value: 'interval' }
Expand Down
3 changes: 2 additions & 1 deletion servers/cu/src/domain/lib/loadMessages.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import * as assert from 'node:assert'
import ms from 'ms'
import { countBy, prop, uniqBy } from 'ramda'

import { padBlockHeight } from '../utils.js'

import { CRON_INTERVAL, parseCrons, isBlockOnSchedule, isTimestampOnSchedule, scheduleMessagesBetweenWith } from './loadMessages.js'
import { padBlockHeight } from './utils.js'

describe('loadMessages', () => {
describe('parseCrons', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { mergeRight, prop } from 'ramda'
import { z } from 'zod'

import { loadTransactionDataSchema } from '../dal.js'
import { parseTags } from './utils.js'
import { parseTags } from '../utils.js'

/**
* The result that is produced from this step
Expand All @@ -13,26 +13,26 @@ import { parseTags } from './utils.js'
* is always added to context
*/
const ctxSchema = z.object({
src: z.any().refine((val) => !!val, {
message: 'process src must be attached to context'
module: z.any().refine((val) => !!val, {
message: 'process module must be attached to context'
}),
srcId: z.string().refine((val) => !!val, {
message: 'process srcId must be attached to context'
moduleId: z.string().refine((val) => !!val, {
message: 'process moduleId must be attached to context'
})
}).passthrough()

function getSourceBufferWith ({ loadTransactionData }) {
function getModuleBufferWith ({ loadTransactionData }) {
loadTransactionData = fromPromise(loadTransactionDataSchema.implement(loadTransactionData))

return (tags) => {
return of(tags)
.map(parseTags)
.map(prop('Contract-Src'))
.chain(srcId =>
of(srcId)
.map(prop('Module'))
.chain(moduleId =>
of(moduleId)
.chain(loadTransactionData)
.chain(fromPromise((res) => res.arrayBuffer()))
.map(src => ({ src, srcId }))
.map(module => ({ module, moduleId }))
)
}
}
Expand All @@ -42,25 +42,25 @@ function getSourceBufferWith ({ loadTransactionData }) {
* @property {string} id - the id of the process
*
* @typedef Result
* @property {string} srcId - the id of the process source
* @property {ArrayBuffer} src - an array buffer that contains the Contract Wasm Src
* @property {string} moduleId - the id of the process source
* @property {ArrayBuffer} module - an array buffer that contains the Contract Wasm Src
*
* @callback LoadSource
* @callback LoadModule
* @param {Args} args
* @returns {Async<Result & Args>}
*
* @param {any} env
* @returns {LoadSource}
* @returns {LoadModule}
*/
export function loadSourceWith (env) {
const logger = env.logger.child('loadSource')
export function loadModuleWith (env) {
const logger = env.logger.child('loadModule')
env = { ...env, logger }

const getSourceBuffer = getSourceBufferWith(env)
const getModuleBuffer = getModuleBufferWith(env)

return (ctx) => {
return of(ctx.tags)
.chain(getSourceBuffer)
.chain(getModuleBuffer)
.map(mergeRight(ctx))
.map(ctxSchema.parse)
.map(ctx => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ import { describe, test } from 'node:test'
import * as assert from 'node:assert'

import { createLogger } from '../logger.js'
import { loadSourceWith } from './loadSource.js'
import { loadModuleWith } from './loadModule.js'

const PROCESS = 'contract-123-9HdeqeuYQOgMgWucro'
const logger = createLogger('ao-cu:readState')

describe('loadSource', () => {
test('append process source and process source id', async () => {
const loadSource = loadSourceWith({
describe('loadModule', () => {
test('append module and module id', async () => {
const loadModule = loadModuleWith({
loadTransactionData: async (_id) =>
new Response(JSON.stringify({ hello: 'world' })),
logger
})

const result = await loadSource({ id: PROCESS, tags: [{ name: 'Contract-Src', value: 'foobar' }] }).toPromise()
assert.equal(result.src.byteLength, 17)
assert.equal(result.srcId, 'foobar')
const result = await loadModule({ id: PROCESS, tags: [{ name: 'Module', value: 'foobar' }] }).toPromise()
assert.equal(result.module.byteLength, 17)
assert.equal(result.moduleId, 'foobar')
assert.equal(result.id, PROCESS)
})
})
26 changes: 9 additions & 17 deletions servers/cu/src/domain/lib/loadProcess.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { Rejected, Resolved, fromPromise, of } from 'hyper-async'
import { F, T, always, cond, equals, includes, is, isNotNil, mergeRight, omit } from 'ramda'
import { always, isNotNil, mergeRight, omit } from 'ramda'
import { z } from 'zod'

import { findLatestEvaluationSchema, findProcessSchema, loadProcessSchema, saveProcessSchema } from '../dal.js'
import { rawBlockSchema, rawTagSchema } from '../model.js'
import { parseTags } from './utils.js'
import { eqOrIncludes, parseTags } from '../utils.js'

function getProcessMetaWith ({ loadProcess, findProcess, saveProcess, logger }) {
findProcess = fromPromise(findProcessSchema.implement(findProcess))
saveProcess = fromPromise(saveProcessSchema.implement(saveProcess))
loadProcess = fromPromise(loadProcessSchema.implement(loadProcess))

const checkTag = (name, pred) => (tags) => pred(tags[name])
const checkTag = (name, pred, err) => tags => pred(tags[name])
? Resolved(tags)
: Rejected(`Tag '${name}' of value '${tags[name]}' was not valid on transaction`)
: Rejected(`Tag '${name}': ${err}`)

/**
* Load the process from the SU, extracting the metadata,
Expand All @@ -27,17 +27,9 @@ function getProcessMetaWith ({ loadProcess, findProcess, saveProcess, logger })
.chain(ctx =>
of(ctx.tags)
.map(parseTags)
/**
* The process could implement multiple Data-Protocols,
* so check in the case of a single value or an array of values
*/
.chain(checkTag('Data-Protocol', cond([
[is(String), equals('ao')],
[is(Array), includes('ao')],
[T, F]
])))
.chain(checkTag('ao-type', equals('process')))
.chain(checkTag('Contract-Src', isNotNil))
.chain(checkTag('Data-Protocol', eqOrIncludes('ao'), 'value \'ao\' was not found on process'))
.chain(checkTag('Type', eqOrIncludes('Process'), 'value \'Process\' was not found on process'))
.chain(checkTag('Module', isNotNil, 'was not found on process'))
.map(always({ id: processId, ...ctx }))
.bimap(
logger.tap('Verifying process failed: %s'),
Expand Down Expand Up @@ -175,10 +167,10 @@ const ctxSchema = z.object({

/**
* @typedef Args
* @property {string} id - the id of the contract
* @property {string} id - the id of the process
*
* @typedef Result
* @property {string} id - the id of the contract
* @property {string} id - the id of the process
* @property {string} owner
* @property {any} tags
* @property {{ height: number, timestamp: number }} block
Expand Down
Loading