Skip to content

Commit

Permalink
feat(sdk)!: verify presence of scheduler tag on spawn. remove id suff…
Browse files Browse the repository at this point in the history
…ix from inputs #210 #212
  • Loading branch information
TillaTheHun0 committed Dec 12, 2023
1 parent 8b6b329 commit fc9f22e
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 46 deletions.
11 changes: 6 additions & 5 deletions sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This sdk will run in a browser or server environment.

- Read the result of an `ao` Message evaluation from a `ao` Compute Unit `cu`
- Send a Message targeting an `ao` Process to an `ao` Message Unit `mu`
- Spawn an `ao` Process on an `ao` Sequencer Unit `su`
- Spawn an `ao` Process, assigning it to an `ao` Scheduler Unit `su`

<!-- toc -->

Expand Down Expand Up @@ -66,7 +66,7 @@ send a message to an `ao` Message Unit `mu` targeting an ao `process`.
import { createDataItemSigner, message } from "@permaweb/ao-sdk";

const messageId = await message({
processId,
process,
signer: createDataItemSigner(wallet),
anchor,
tags,
Expand All @@ -78,13 +78,14 @@ const messageId = await message({
#### `spawn`

Spawn an `ao` process
Spawn an `ao` process, assigning the `ao` Scheduler to schedule its messages

```js
import { createDataItemSigner, spawn } from "@permaweb/ao-sdk";

const processId = await spawn({
moduleId,
module,
scheduler,
signer: createDataItemSigner(wallet),
tags,
});
Expand All @@ -98,7 +99,7 @@ specify those components by providing their urls to `connect`. You can currently
- The GATEWAY_URL
- The Messenger Unit URL
- The Compute Unit URL
- The Sequencer Unit URL
- The Scheduler Unit URL

```js
import { connect } from "@permaweb/ao-sdk";
Expand Down
6 changes: 3 additions & 3 deletions sdk/src/lib/message/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { uploadMessageWith } from './upload-message.js'
* @typedef Env1
*
* @typedef SendMessageArgs
* @property {string} processId
* @property {string} process
* @property {string} [anchor]
* @property {{ name: string, value: string }[]} [tags]
* @property {any} signer
Expand All @@ -25,8 +25,8 @@ export function messageWith (env) {
const verifyProcess = verifyProcessWith(env)
const uploadMessage = uploadMessageWith(env)

return ({ processId, tags, anchor, signer }) => {
return of({ id: processId, tags, anchor, signer })
return ({ process, tags, anchor, signer }) => {
return of({ id: process, tags, anchor, signer })
.chain(verifyProcess)
.chain(uploadMessage)
.map((ctx) => ctx.messageId) // the id of the data item
Expand Down
7 changes: 4 additions & 3 deletions sdk/src/lib/spawn/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { uploadProcessWith } from './upload-process.js'
* @typedef Env1
*
* @typedef SpawnProcessArgs
* @property {string} moduleId
* @property {string} module
* @property {string} scheduler
* @property {string} signer
* @property {{ name: string, value: string }[]} [tags]
* @property {string | ArrayBuffer} [data]
Expand All @@ -25,8 +26,8 @@ export function spawnWith (env) {
const verifyInputs = verifyInputsWith(env)
const uploadProcess = uploadProcessWith(env)

return ({ moduleId, signer, tags, data }) => {
return of({ moduleId, signer, tags, data })
return ({ module, scheduler, signer, tags, data }) => {
return of({ module, scheduler, signer, tags, data })
.chain(verifyInputs)
.chain(uploadProcess)
.map((ctx) => ctx.processId)
Expand Down
27 changes: 22 additions & 5 deletions sdk/src/lib/spawn/upload-process.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fromPromise, of, Resolved } from 'hyper-async'
import { z } from 'zod'
import { __, always, assoc, concat, defaultTo, ifElse } from 'ramda'
import { __, always, append, assoc, concat, defaultTo, ifElse, pipe, prop, propEq, reject } from 'ramda'

import { deployProcessSchema, signerSchema } from '../../dal.js'

Expand All @@ -15,7 +15,7 @@ const tagSchema = z.array(z.object({
* @property {any} value
*
* @typedef Context3
* @property {string} moduleId - the id of the transactions that contains the xontract source
* @property {string} module - the id of the transactions that contains the xontract source
* @property {any} initialState -the initialState of the contract
* @property {Tag[]} tags
* @property {string | ArrayBuffer} [data]
Expand All @@ -26,13 +26,14 @@ const tagSchema = z.array(z.object({

function buildTagsWith () {
return (ctx) => {
return of(ctx.tags)
return of(ctx)
.map(prop('tags'))
.map(defaultTo([]))
.map(concat(__, [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Process' },
{ name: 'Module', value: ctx.moduleId },
{ name: 'Content-Type', value: 'text/plain' },
{ name: 'Module', value: ctx.module },
{ name: 'Scheduler', value: ctx.scheduler },
{ name: 'SDK', value: 'ao' }
]))
.map(tagSchema.parse)
Expand All @@ -41,6 +42,10 @@ function buildTagsWith () {
}

function buildDataWith ({ logger }) {
function removeTagsByName (name) {
return (tags) => reject(propEq(name, 'name'), tags)
}

return (ctx) => {
return of(ctx)
.chain(ifElse(
Expand All @@ -54,6 +59,18 @@ function buildDataWith ({ logger }) {
*/
() => Resolved(Math.random().toString().slice(-4))
.map(assoc('data', __, ctx))
/**
* Since we generate the data value, we know it's Content-Type,
* so set it on the tags
*/
.map(
(ctx) => pipe(
prop('tags'),
removeTagsByName('Content-Type'),
append({ name: 'Content-Type', value: 'text/plain' }),
assoc('tags', __, ctx)
)(ctx)
)
.map(logger.tap('added pseudo-random data as payload for contract at "data"'))
))
}
Expand Down
30 changes: 23 additions & 7 deletions sdk/src/lib/spawn/upload-process.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ describe('upload-process', () => {
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Process' },
{ name: 'Module', value: 'module-id-123' },
{ name: 'Content-Type', value: 'text/plain' },
{ name: 'SDK', value: 'ao' }
{ name: 'Scheduler', value: 'zVkjFCALjk4xxuCilddKS8ShZ-9HdeqeuYQOgMgWucro' },
{ name: 'SDK', value: 'ao' },
{ name: 'Content-Type', value: 'text/plain' }
])
assert.ok(signer)

Expand All @@ -35,7 +36,8 @@ describe('upload-process', () => {
})

await uploadProcess({
moduleId: 'module-id-123',
module: 'module-id-123',
scheduler: 'zVkjFCALjk4xxuCilddKS8ShZ-9HdeqeuYQOgMgWucro',
tags: [
{ name: 'foo', value: 'bar' }
],
Expand All @@ -51,8 +53,9 @@ describe('upload-process', () => {
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Process' },
{ name: 'Module', value: 'module-id-123' },
{ name: 'Content-Type', value: 'text/plain' },
{ name: 'SDK', value: 'ao' }
{ name: 'Scheduler', value: 'zVkjFCALjk4xxuCilddKS8ShZ-9HdeqeuYQOgMgWucro' },
{ name: 'SDK', value: 'ao' },
{ name: 'Content-Type', value: 'text/plain' }
])

return { res: 'foobar', processId: 'process-id-123' }
Expand All @@ -62,7 +65,8 @@ describe('upload-process', () => {
})

await uploadProcess({
moduleId: 'module-id-123',
module: 'module-id-123',
scheduler: 'zVkjFCALjk4xxuCilddKS8ShZ-9HdeqeuYQOgMgWucro',
signer: async () => ({ id: 'process-id-123', raw: 'raw-buffer' })
}).toPromise()
})
Expand All @@ -71,6 +75,17 @@ describe('upload-process', () => {
const uploadProcess = uploadProcessWith({
deployProcess: async ({ data, tags, signer }) => {
assert.equal(data, 'foobar')
/**
* Assert no Content-Type tag is added
*/
assert.deepStrictEqual(tags, [
{ name: 'foo', value: 'bar' },
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Process' },
{ name: 'Module', value: 'module-id-123' },
{ name: 'Scheduler', value: 'zVkjFCALjk4xxuCilddKS8ShZ-9HdeqeuYQOgMgWucro' },
{ name: 'SDK', value: 'ao' }
])
return {
res: 'foobar',
processId: 'process-id-123',
Expand All @@ -84,7 +99,8 @@ describe('upload-process', () => {
})

await uploadProcess({
moduleId: 'module-id-123',
module: 'module-id-123',
scheduler: 'zVkjFCALjk4xxuCilddKS8ShZ-9HdeqeuYQOgMgWucro',
tags: [
{ name: 'foo', value: 'bar' }
],
Expand Down
39 changes: 30 additions & 9 deletions sdk/src/lib/spawn/verify-inputs.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Rejected, Resolved, fromPromise, of } from 'hyper-async'
import { isNotNil, prop } from 'ramda'
import { ifElse, isNotNil, prop } from 'ramda'

import { loadTransactionMetaSchema } from '../../dal.js'
import { eqOrIncludes, parseTags } from '../utils.js'

const checkTag = (name, pred, err) => tags => pred(tags[name])
? Resolved(tags)
: Rejected(`Tag '${name}': ${err}`)

/**
* @typedef Tag
* @property {string} name
Expand All @@ -19,16 +23,12 @@ import { eqOrIncludes, parseTags } from '../utils.js'
*/

function verifyModuleWith ({ loadTransactionMeta, logger }) {
const checkTag = (name, pred, err) => tags => pred(tags[name])
? Resolved(tags)
: Rejected(`Tag '${name}': ${err}`)

return (moduleId) => of(moduleId)
return (module) => of(module)
.chain(fromPromise(loadTransactionMetaSchema.implement(loadTransactionMeta)))
.map(prop('tags'))
.map(parseTags)
/**
* Ensure all tags required by the specification are set
* Ensure all Module tags required by the specification are set
*/
.chain(checkTag('Data-Protocol', eqOrIncludes('ao'), 'value \'ao\' was not found on module'))
.chain(checkTag('Type', eqOrIncludes('Module'), 'value \'Module\' was not found on module'))
Expand All @@ -41,6 +41,25 @@ function verifyModuleWith ({ loadTransactionMeta, logger }) {
)
}

function verifySchedulerWith ({ logger }) {
return (scheduler) => of(scheduler)
/**
* TODO: actually fetch Schedule-Location record
* by owner and confirm that it is valid
*/
.chain(
ifElse(
isNotNil,
Resolved,
() => Rejected('scheduler not found')
)
)
.bimap(
logger.tap('Verifying scheduler failed: %s'),
logger.tap('Verified scheduler')
)
}

function verifySignerWith ({ logger }) {
return (signer) => of(signer)
.map(logger.tap('Checking for signer'))
Expand All @@ -49,7 +68,7 @@ function verifySignerWith ({ logger }) {

/**
* @typedef Context
* @property {string} moduleId - the id of the module source
* @property {string} module - the id of the module source
* @property {Function} sign - the signer used to sign the process
* @property {Tag[]} tags - the additional tags to add to the process
*
Expand All @@ -70,11 +89,13 @@ export function verifyInputsWith (env) {
env = { ...env, logger }

const verifyModule = verifyModuleWith(env)
const verifyScheduler = verifySchedulerWith(env)
const verifySigner = verifySignerWith(env)

return (ctx) => {
return of(ctx)
.chain(ctx => verifyModule(ctx.moduleId).map(() => ctx))
.chain(ctx => verifyModule(ctx.module).map(() => ctx))
.chain(ctx => verifyScheduler(ctx.scheduler)).map(() => ctx)
.chain(ctx => verifySigner(ctx.signer).map(() => ctx))
.bimap(
logger.tap('Error when verify input: %s'),
Expand Down
Loading

0 comments on commit fc9f22e

Please sign in to comment.