Skip to content

Commit

Permalink
feat(sdk)!: verify tags on module #212
Browse files Browse the repository at this point in the history
  • Loading branch information
TillaTheHun0 committed Dec 12, 2023
1 parent 4d11262 commit 8b6b329
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 109 deletions.
12 changes: 1 addition & 11 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, propEq, reject } from 'ramda'
import { __, always, assoc, concat, defaultTo, ifElse } from 'ramda'

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

Expand All @@ -25,19 +25,9 @@ const tagSchema = z.array(z.object({
*/

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

return (ctx) => {
return of(ctx.tags)
.map(defaultTo([]))
/**
* Remove any reserved tags, so that the sdk
* can properly set them
*/
.map(removeTagsByName('Type'))
.map(removeTagsByName('Module'))
.map(concat(__, [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Process' },
Expand Down
28 changes: 0 additions & 28 deletions sdk/src/lib/spawn/upload-process.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,34 +67,6 @@ describe('upload-process', () => {
}).toPromise()
})

test('deduplicates identifying tags', async () => {
const uploadProcess = uploadProcessWith({
deployProcess: async ({ tags }) => {
assert.deepStrictEqual(tags, [
{ 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' }
])

return { res: 'foobar', processId: 'process-id-123' }
},
registerProcess: async () => ({ foo: 'bar' }),
logger
})

await uploadProcess({
moduleId: 'module-id-123',
tags: [
{ name: 'Type', value: 'Process' },
{ name: 'Type', value: 'Process' },
{ name: 'Module', value: 'oops-duplicate' }
],
signer: async () => ({ id: 'process-id-123', raw: 'raw-buffer' })
}).toPromise()
})

test('does not overwrite data', async () => {
const uploadProcess = uploadProcessWith({
deployProcess: async ({ data, tags, signer }) => {
Expand Down
32 changes: 19 additions & 13 deletions sdk/src/lib/spawn/verify-inputs.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Rejected, Resolved, fromPromise, of } from 'hyper-async'
import { equals, prop } from 'ramda'
import { isNotNil, prop } from 'ramda'

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

/**
* @typedef Tag
Expand All @@ -18,20 +18,26 @@ import { parseTags } from '../utils.js'
* @property {any} logger
*/

function verifySourceWith ({ loadTransactionMeta, logger }) {
const checkTag = (name, pred) => tags => pred(tags[name])
function verifyModuleWith ({ loadTransactionMeta, logger }) {
const checkTag = (name, pred, err) => tags => pred(tags[name])
? Resolved(tags)
: Rejected(`Tag '${name}' of value '${tags[name]}' was not valid on contract source`)
: Rejected(`Tag '${name}': ${err}`)

return (srcId) => of(srcId)
return (moduleId) => of(moduleId)
.chain(fromPromise(loadTransactionMetaSchema.implement(loadTransactionMeta)))
.map(prop('tags'))
.map(parseTags)
.chain(checkTag('Content-Type', equals('application/wasm')))
.chain(checkTag('Contract-Type', equals('ao')))
/**
* Ensure all 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'))
.chain(checkTag('Module-Format', isNotNil, 'was not found on module'))
.chain(checkTag('Input-Encoding', isNotNil, 'was not found on module'))
.chain(checkTag('Output-Encoding', isNotNil, 'was not found on module'))
.bimap(
logger.tap('Verifying contract source failed: %s'),
logger.tap('Verified contract source')
logger.tap('Verifying module source failed: %s'),
logger.tap('Verified module source')
)
}

Expand All @@ -43,7 +49,7 @@ function verifySignerWith ({ logger }) {

/**
* @typedef Context
* @property {string} srcId - the id of the contract source
* @property {string} moduleId - 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 @@ -63,12 +69,12 @@ export function verifyInputsWith (env) {
const logger = env.logger.child('verifyInput')
env = { ...env, logger }

const verifySource = verifySourceWith(env)
const verifyModule = verifyModuleWith(env)
const verifySigner = verifySignerWith(env)

return (ctx) => {
return of(ctx)
.chain(ctx => verifySource(ctx.srcId).map(() => ctx))
.chain(ctx => verifyModule(ctx.moduleId).map(() => ctx))
.chain(ctx => verifySigner(ctx.signer).map(() => ctx))
.bimap(
logger.tap('Error when verify input: %s'),
Expand Down
212 changes: 156 additions & 56 deletions sdk/src/lib/spawn/verify-inputs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as assert from 'node:assert'
import { createLogger } from '../../logger.js'
import { verifyInputsWith } from './verify-inputs.js'

const CONTRACT = 'zVkjFCALjk4xxuCilddKS8ShZ-9HdeqeuYQOgMgWucro'
const MODULE = 'zVkjFCALjk4xxuCilddKS8ShZ-9HdeqeuYQOgMgWucro'

const logger = createLogger('createContract')

Expand All @@ -14,15 +14,19 @@ describe('verify-input', () => {
loadTransactionMeta: async (_id) =>
({
tags: [
{ name: 'Content-Type', value: 'application/wasm' },
{ name: 'Contract-Type', value: 'ao' }
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Data-Protocol', value: 'Data-Protocol' },
{ name: 'Type', value: 'Module' },
{ name: 'Module-Format', value: 'emscripten' },
{ name: 'Input-Encoding', value: 'JSON-1' },
{ name: 'Output-Encoding', value: 'JSON-1' }
]
}),
logger
})

await verifyInput({
srcId: CONTRACT,
moduleId: MODULE,
initialState: { balances: { foo: 1 } },
signer: () => {},
tags: [
Expand All @@ -31,78 +35,174 @@ describe('verify-input', () => {
}).toPromise().then(assert.ok)
})

test('throw if missing Content-Type', async () => {
const verifyInput = verifyInputsWith({
loadTransactionMeta: async (_id) =>
({
tags: [
{ name: 'No-Content-Type', value: 'application/wasm' },
{ name: 'Contract-Type', value: 'ao' }
]
}),
logger
describe('throw if required tag is invalid on source', () => {
test('Data-Protocol', async () => {
const verifyInput = verifyInputsWith({
loadTransactionMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'not_ao' },
{ name: 'Data-Protocol', value: 'still_not_ao' }
]
}),
logger
})

await verifyInput({
moduleId: MODULE,
initialState: { balances: { foo: 1 } },
signer: () => {},
tags: [
{ name: 'foo', value: 'bar' }
]
}).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Data-Protocol': value 'ao' was not found on module"
)
})
})

await verifyInput({
srcId: CONTRACT,
initialState: { balances: { foo: 1 } },
signer: () => {},
tags: [
{ name: 'foo', value: 'bar' }
]
}).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Content-Type' of value 'undefined' was not valid on contract source"
)
test('Type', async () => {
const verifyInput = verifyInputsWith({
loadTransactionMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Foobar' }
]
}),
logger
})
})

test('throw if missing Content-Type', async () => {
const verifyInput = verifyInputsWith({
loadTransactionMeta: async (_id) =>
({
tags: [
{ name: 'Content-Type', value: 'application/wasm' },
{ name: 'Contract-Type', value: 'something else' }
]
}),
logger
await verifyInput({
moduleId: MODULE,
initialState: { balances: { foo: 1 } },
signer: () => {},
tags: [
{ name: 'foo', value: 'bar' }
]
}).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Type': value 'Module' was not found on module"
)
})
})

await verifyInput({
srcId: CONTRACT,
initialState: { balances: { foo: 1 } },
signer: () => {},
tags: [
{ name: 'foo', value: 'bar' }
]
}).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Contract-Type' of value 'something else' was not valid on contract source"
)
test('Module-Format', async () => {
const verifyInput = verifyInputsWith({
loadTransactionMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Module' }
]
}),
logger
})

await verifyInput({
moduleId: MODULE,
initialState: { balances: { foo: 1 } },
signer: () => {},
tags: [
{ name: 'foo', value: 'bar' }
]
}).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Module-Format': was not found on module"
)
})
})

test('Input-Encoding', async () => {
const verifyInput = verifyInputsWith({
loadTransactionMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Module' },
{ name: 'Module-Format', value: 'emscripten' }
]
}),
logger
})

await verifyInput({
moduleId: MODULE,
initialState: { balances: { foo: 1 } },
signer: () => {},
tags: [
{ name: 'foo', value: 'bar' }
]
}).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Input-Encoding': was not found on module"
)
})
})

test('Output-Encoding', async () => {
const verifyInput = verifyInputsWith({
loadTransactionMeta: async (_id) =>
({
tags: [
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Type', value: 'Module' },
{ name: 'Module-Format', value: 'emscripten' },
{ name: 'Input-Encoding', value: 'JSON-1' }
]
}),
logger
})

await verifyInput({
moduleId: MODULE,
initialState: { balances: { foo: 1 } },
signer: () => {},
tags: [
{ name: 'foo', value: 'bar' }
]
}).toPromise()
.then(assert.fail)
.catch(err => {
assert.equal(
err,
"Tag 'Output-Encoding': was not found on module"
)
})
})
})

test('throw if signer is not found', async () => {
const verifyInput = verifyInputsWith({
loadTransactionMeta: async (_id) =>
({
tags: [
{ name: 'Content-Type', value: 'application/wasm' },
{ name: 'Contract-Type', value: 'ao' }
{ name: 'Data-Protocol', value: 'ao' },
{ name: 'Data-Protocol', value: 'Data-Protocol' },
{ name: 'Type', value: 'Module' },
{ name: 'Module-Format', value: 'emscripten' },
{ name: 'Input-Encoding', value: 'JSON-1' },
{ name: 'Output-Encoding', value: 'JSON-1' }
]
}),
logger
})

await verifyInput({
srcId: CONTRACT,
moduleId: MODULE,
initialState: { balances: { foo: 1 } },
signer: undefined,
tags: [
Expand Down
Loading

0 comments on commit 8b6b329

Please sign in to comment.