Skip to content
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
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ The acurast.json file is generated by running acurast init. Here is an example c
"fileUrl": "dist/bundle.js",
"network": "mainnet",
"onlyAttestedDevices": true,
"enableDevtools": false,
"assignmentStrategy": {
"type": "Single"
},
Expand Down Expand Up @@ -120,6 +121,7 @@ ACURAST_MNEMONIC=abandon abandon about ...
- `fileUrl`: The path to the bundled file, including all dependencies (e.g., `dist/bundle.js`).
- `network`: The network on which the project will be deployed. (e.g. `mainnet`)
- `onlyAttestedDevices`: A boolean to specify if only attested devices are allowed to run the app.
- `enableDevtools`: A boolean to enable [DevTools](#devtools) for the deployment. When enabled, console logs from processor executions are forwarded to the DevTools dashboard. Defaults to `false`.
- `startAt`: The start time of the deployment.
- `msFromNow`: The deployment will start the specified number of milliseconds from now.
- `timestamp`: The deployment will start at the specified timestamp.
Expand All @@ -134,6 +136,7 @@ ACURAST_MNEMONIC=abandon abandon about ...
- `type`: 'interval'`: Multiple executions for the deployment.
- `intervalInMs`: Interval in milliseconds between each execution start.
- `numberOfExecutions`: The number of executions.
- `maxExecutionTimeInMs`: Maximum execution time for each execution in milliseconds. If not specified, the full duration of the interval will be used (minus a 10s buffer). It is recommended to set this to at least 10s less than `intervalInMs`.
- `maxAllowedStartDelayInMs`: Specifies the maximum allowed start delay (relative to the starting time) of the deployment in milliseconds.
- `usageLimit`: The usage limits for the deployment:
- `maxMemory`: Maximum memory usage in bytes.
Expand Down Expand Up @@ -277,10 +280,10 @@ Logs are only accessible with a valid view key. The key is scoped to the specifi

### Environment variables

| Variable | Default | Description |
|---|---|---|
| `ACURAST_DEVTOOLS_URL` | `https://devtools.acurast.com` | DevTools frontend URL |
| `ACURAST_DEVTOOLS_API_URL` | `https://api.devtools.acurast.com` | DevTools API URL |
| Variable | Default | Description |
| -------------------------- | ---------------------------------- | --------------------- |
| `ACURAST_DEVTOOLS_URL` | `https://devtools.acurast.com` | DevTools frontend URL |
| `ACURAST_DEVTOOLS_API_URL` | `https://api.devtools.acurast.com` | DevTools API URL |

## Deployment Management

Expand Down
2 changes: 1 addition & 1 deletion acurast.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
"maxNetworkRequests": 0,
"maxStorage": 0
},
"numberOfReplicas": 20,
"numberOfReplicas": 1,
"requiredModules": [],
"minProcessorReputation": 0,
"maxCostPerExecution": 3000000000,
Expand Down
44 changes: 44 additions & 0 deletions examples/devtools-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
console.log("Devtools test started", { jobId: _STD_.job.getId(), device: _STD_.device.getAddress() })
console.info("Processor info", { timestamp: Date.now() })

// Probe runtime for HTTP/upload primitives
console.log("Runtime probe", {
fetch: typeof fetch,
FormData: typeof FormData,
Blob: typeof Blob,
XMLHttpRequest: typeof XMLHttpRequest,
Request: typeof Request,
Response: typeof Response,
Headers: typeof Headers,
Buffer: typeof Buffer,
btoa: typeof btoa,
atob: typeof atob,
})

let tick = 0
const interval = setInterval(() => {
tick++
Expand All @@ -20,6 +34,36 @@ const interval = setInterval(() => {
}
}

if (tick === 2) {
const filename = "test-upload.json"
const content = JSON.stringify({
message: "hello from acurast processor",
jobId: _STD_.job.getId().id,
device: _STD_.device.getAddress(),
timestamp: Date.now(),
})

console.log("Uploading file: " + filename + " (" + content.length + " bytes)")

_DEVTOOLS_.uploadFile(
filename,
content,
"application/json",
(fileInfo) => {
console.log("Upload succeeded", {
id: fileInfo.id,
filename: fileInfo.filename,
mimeType: fileInfo.mimeType,
fileSize: fileInfo.fileSize,
createdAt: fileInfo.createdAt,
})
},
(error) => {
console.error("Upload failed:", error)
}
)
}

if (tick >= 12) {
clearInterval(interval)
console.log("Devtools test complete after " + tick + " ticks")
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@acurast/cli",
"version": "0.7.0",
"version": "0.7.1",
"description": "A cli to interact with the Acurast Cloud.",
"main": "dist/index.js",
"bin": {
Expand Down
8 changes: 6 additions & 2 deletions src/acurast/createJob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ export const createJob = async (

if (config.fileUrl.startsWith('ipfs://')) {
ipfsHash = config.fileUrl
filelogger.debug(`config.fileUrl is an IPFS hash, so we this: ${ipfsHash}`)
if (config.enableDevtools) {
filelogger.warn(
'enableDevtools is ignored when fileUrl is an IPFS hash — the devtools snippet can only be injected into local bundles.'
)
}
filelogger.debug(`config.fileUrl is an IPFS hash, so we use this: ${ipfsHash}`)
} else {
filelogger.debug(
`config.fileUrl is not an IPFS hash, so we zip it: ${config.fileUrl}`
Expand Down Expand Up @@ -84,7 +89,6 @@ export const createJob = async (
zipPath,
config.entrypoint ?? basename(config.fileUrl),
devtoolsApiUrl,
wallet.address,
snippetDir
)
filelogger.debug('Devtools snippet injected into bundle')
Expand Down
16 changes: 16 additions & 0 deletions src/devtools/acurast-processor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ declare function httpPOST(
onError: (error: string) => void
): void

declare const _DEVTOOLS_: {
uploadFile(
filename: string,
content: string,
mimeType: string,
onSuccess: (fileInfo: {
id: number
filename: string
mimeType: string
fileSize: number
createdAt: string
}) => void,
onError: (error: string) => void
): void
}

declare const _STD_: {
job: {
getId(): { origin: { kind: string; source: string }; id: string }
Expand Down
50 changes: 47 additions & 3 deletions src/devtools/devtools-snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
// of user scripts when enableDevtools is true. It overrides console methods to
// forward logs to the Acurast DevTools API.
//
// Placeholders __DEVTOOLS_API_URL__ and __DEVTOOLS_DEPLOYER__ are replaced at
// injection time by the CLI.
// Placeholder __DEVTOOLS_API_URL__ is replaced at injection time by the CLI.

;(() => {
const DEVTOOLS_API_URL = '__DEVTOOLS_API_URL__'
const DEVTOOLS_DEPLOYER = '__DEVTOOLS_DEPLOYER__'

const originalConsole = {
log: console.log,
Expand Down Expand Up @@ -133,6 +131,52 @@
}
}

// --- File upload via /v1/files ---
const uploadFile = (
filename: string,
content: string,
mimeType: string,
onSuccess: (fileInfo: { id: number; filename: string; mimeType: string; fileSize: number; createdAt: string }) => void,
onError: (error: string) => void
) => {
if (!apiKey) {
onError('[devtools] file upload failed: not authenticated yet')
return
}

// The processor's httpPOST JSON-parses the body regardless of Content-Type,
// so we cannot send multipart/form-data through it. Use fetch + FormData
// instead — fetch sets the Content-Type header (with boundary) automatically.
const formData = new FormData()
formData.append('file', new Blob([content], { type: mimeType }), filename)

fetch(`${DEVTOOLS_API_URL}/v1/files`, {
method: 'POST',
headers: { Authorization: 'Bearer ' + apiKey },
body: formData,
})
.then((res: Response) =>
res.text().then((text: string) => ({ ok: res.ok, status: res.status, text }))
)
.then(({ ok, status, text }: { ok: boolean; status: number; text: string }) => {
if (!ok) {
onError('[devtools] file upload failed: HTTP ' + status + ' ' + text)
return
}
try {
onSuccess(JSON.parse(text))
} catch (_e) {
onError('[devtools] failed to parse upload response: ' + text)
}
})
.catch((err: any) => {
onError('[devtools] file upload failed: ' + (err?.message ?? String(err)))
})
}

// Expose _DEVTOOLS_ global
;(globalThis as any)._DEVTOOLS_ = { uploadFile }

for (const level of Object.keys(originalConsole) as Array<
keyof typeof originalConsole
>) {
Expand Down
2 changes: 0 additions & 2 deletions src/devtools/injectDevtoolsSnippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export async function injectDevtoolsSnippet(
zipPath: string,
entrypoint: string,
devtoolsApiUrl: string,
deployerAddress: string,
snippetDir: string
): Promise<string> {
const snippetPath = join(snippetDir, 'devtools-snippet.js')
Expand All @@ -23,7 +22,6 @@ export async function injectDevtoolsSnippet(
.trim()

snippet = snippet.replace(/__DEVTOOLS_API_URL__/g, devtoolsApiUrl)
snippet = snippet.replace(/__DEVTOOLS_DEPLOYER__/g, deployerAddress)

const zip = new AdmZip(zipPath)
const entry = zip.getEntry(entrypoint)
Expand Down
13 changes: 13 additions & 0 deletions src/util/validateConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,19 @@ const acurastProjectConfigSchemaWithNotes =
}
}

if (
data.execution.type === 'interval' &&
data.execution.maxExecutionTimeInMs !== undefined &&
data.execution.maxExecutionTimeInMs > data.execution.intervalInMs - 10_000
) {
context.addIssue({
code: z.ZodIssueCode.custom,
message:
'Warning: maxExecutionTimeInMs should be at least 10s shorter than intervalInMs to allow enough time between executions.',
path: ['execution', 'maxExecutionTimeInMs'],
})
}

// TODO: Add check for competing strategy and intervals
})

Expand Down
25 changes: 0 additions & 25 deletions test/devtools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ describe('devtools snippet injection', () => {
zipPath,
'index.js',
'https://api.devtools.acurast.com',
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
SNIPPET_DIR
)

Expand All @@ -68,7 +67,6 @@ describe('devtools snippet injection', () => {
zipPath,
'index.js',
apiUrl,
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
SNIPPET_DIR
)

Expand All @@ -79,33 +77,13 @@ describe('devtools snippet injection', () => {
expect(content).not.toContain('__DEVTOOLS_API_URL__')
})

it('should replace the deployer placeholder', async () => {
const zipPath = createTestZip('index.js', '// user code')
const deployer = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'

await injectDevtoolsSnippet(
zipPath,
'index.js',
'https://api.devtools.acurast.com',
deployer,
SNIPPET_DIR
)

const zip = new AdmZip(zipPath)
const content = zip.getEntry('index.js')!.getData().toString('utf-8')

expect(content).toContain(deployer)
expect(content).not.toContain('__DEVTOOLS_DEPLOYER__')
})

it('should strip TSC artifacts (export {}, sourcemap comment)', async () => {
const zipPath = createTestZip('index.js', '// user code')

await injectDevtoolsSnippet(
zipPath,
'index.js',
'https://api.devtools.acurast.com',
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
SNIPPET_DIR
)

Expand All @@ -124,7 +102,6 @@ describe('devtools snippet injection', () => {
zipPath,
'nonexistent.js',
'https://api.devtools.acurast.com',
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
SNIPPET_DIR
)
).rejects.toThrow('Could not find entrypoint')
Expand All @@ -142,7 +119,6 @@ describe('devtools snippet injection', () => {
zipPath,
'index.js',
'https://api.devtools.acurast.com',
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
SNIPPET_DIR
)

Expand All @@ -163,7 +139,6 @@ describe('devtools snippet injection', () => {
zipPath,
'index.js',
'https://api.devtools.acurast.com',
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
SNIPPET_DIR
)

Expand Down
Loading