Compute to Data
Compute to Data runs an algorithm against a dataset without the algorithm's owner ever seeing the data. You order both, the node runs the job in an isolated environment, and you get back the results.
What is different in v2
C2D v2 changed the model substantially:
- Resources are requested explicitly —
[{ id: 'cpu', amount: 2 }]— instead of the old fixed CPU/GPU descriptors. Environments advertise what they have and what it costs per chain. - Payment runs through an escrow contract. The node quotes an amount, nautilus locks it.
- All datasets travel in one array. v1 had
datasetplusadditionalDatasetson the wire. - Free compute exists, with no order, no escrow and no payment token.
- Output is
{ remoteStorage, encryption }. ThepublishAlgorithmLogandpublishOutputflags are gone.
Pick an environment first
v1 silently used the node's first environment. Worth choosing deliberately: resources, duration limits, accepted tokens and free-tier availability all differ.
const = await .()
for (const of ) {
.(., ., ., .)
}fees is keyed by chain id, so a token that is not listed for your chain cannot pay for a
job there.
A free job
The cheapest way to get started, where the environment offers it.
const = await .()
const = .(() => .)
const = await .({
: { : 'did:ope:1234abcd...' },
: { : 'did:ope:5678wxyz...' },
: ?.
})
const = .[0].The jobId is <environmentHash>-<jobId>, the form the node gives a started job. Keep it
whole: every job method takes it, and a bare id is refused.
No datatoken is bought and no escrow is touched. An environment without a free section
throws, naming it — free jobs are opt-in for the node operator, and its access list may
restrict who can use them.
A paid job
const = await .()
const = [0]
const = .().
const = .[()][0].
const = await .({
: { : 'did:ope:1234abcd...' },
: { : 'did:ope:5678wxyz...' },
: .,
: [
{ : 'cpu', : 2 },
{ : 'ram', : 2 }
],
: 3600,
,
// the most this job may lock in escrow, in the token's smallest unit
: { : , : ('5', 18) }
})
const = .[0].
.('orders', .)What nautilus does, in order:
- Resolves every input and picks its compute service.
- Satisfies all credential policies. A failure here aborts with nothing spent — which is exactly why it comes before the orders.
- Asks the node to quote the job: provider fees, reusable orders, and the escrow amount.
- Checks the quote against what you allowed, before any transaction: the provider fees
against
maxProviderFee/confirmProviderFees, the escrow contract against the pinned one (the chain'sEnterpriseEscrowby default), and the escrow amount againstmaxEscrowPayment/confirmEscrowPayment. A zero escrow payment funds nothing and is not checked further. Then it plans every input's order, reading its pricing from chain, so an input that cannot be ordered is refused before escrow is funded. - Funds and authorises escrow for exactly the quoted amount: it approves and deposits only what escrow lacks, and authorises the environment to lock the job's amount.
- Orders each input as planned, reusing an existing order where the node says one is valid.
Unlike
access(),compute()does not look previous orders up on chain itself, and it never throws anOrderCheckError. It orders every input the node does not report as validly ordered, and sincecompute()sends the node notransferTxId, the node reports none: each paid input is bought on every job (see known limitations). - Starts the job.
Steps 5 to 7 run one job at a time per chain, payer, payment token and environment within
one Nautilus instance, so two jobs started together do not overwrite each other's escrow
authorisation. Jobs from another instance or process are not serialised; see
Concurrent jobs.
A non-empty resources list is sent exactly as given, and the node gives each resource it leaves out
that resource's minimum, often 0; a job with 0 RAM runs without a memory limit. Without
resources, or with an empty list, nautilus requests every resource the environment lists at its minimum, raised to
1 for cpu, ram and disk within the resource's maximum; other resources, such as GPUs,
at their minimum. paymentToken defaults to the first token the
environment accepts on your chain, and maxJobDuration is capped to the environment's limit
with a warning.
Several datasets
const = await .({
: { : 'did:ope:1234abcd...' },
: [
{ : 'did:ope:926098d069b017dcf...' },
{ : 'did:ope:475698d069b017dcf...' }
],
: { : 'did:ope:5678wxyz...' }
})nautilus assembles the single array the node expects. Every dataset is ordered.
Following the job
const = await .({ })
.(?., ?.)undefined means the node does not know the job. The job has finished once the node sets
its dateFinished, whether it succeeded or not, and the status says how it ended: failed
jobs end below 70, for example 41 when the algorithm failed or 11 when its image could
not be pulled. Poll on dateFinished rather than on a status. A finished job's results are
readable; at 71 (JobSettle) the node may still be writing outputs.tar, which is listed
as the output result once it is written.
The algorithm's logs are useful for debugging an algorithm you cannot otherwise observe.
While the job runs they stream live; once it has finished, getComputeLogs reads the job's
algorithmLog result instead, because the node serves live logs only while the algorithm
runs:
const = await .({ })
for await (const of ) ..() Getting the results
const = await .({ })
if () {
const = await ()
}undefined means the job has not finished or not listed its output yet, the node does not
know it, or it produced no output result (a failed job usually has none) — the log line
says which.
The output result is outputs.tar, a tar archive of everything the algorithm wrote to
its outputs folder, so unpack it rather than reading it as a file of its own. It is listed
only when the results stay on the node; with output storage or an outputBucketId they go
there instead. A finished job's results (on getComputeStatus)
list on ocean-node 4.2:
type | File |
|---|---|
imageLog | image.log, pulling or building the algorithm's image |
configurationLog | configuration.log, preparing the job's inputs and algorithm |
algorithmLog | algorithm.log, what the algorithm printed |
output | outputs.tar, the algorithm's outputs |
Pass a result's index as resultIndex to read another one.
For a large result, stream it instead of buffering a download:
const = await .({ })
for await (const of ) {
..()
} It reads the same result as getComputeResult: the output unless resultIndex says
otherwise. Where getComputeResult returns undefined, it throws.
Stopping a job
await .({ }) Publishing for compute
On the dataset side, a COMPUTE service decides which algorithms may run. Pinning specific
algorithms is the safe default — nautilus resolves their container and file checksums at
publish time, so a publisher cannot swap the container afterwards:
const = new <., .>({
: .
})
.('https://ocean-node.dev.pontus-x.eu')
.({ : 'url', : 'https://data.example/x.csv', : 'GET' })
.({ : 'free' })
.(false) // no unpublished code
.(false) // no exfiltration
.([{ : 'did:ope:926098d069b017dcf...' }])
.()allowAlgorithmNetworkAccess(false) is what makes the isolation meaningful: without network
access, an algorithm cannot send your data anywhere.
These settings belong to the dataset. ocean-node reads them from the dataset's service only,
so nautilus publishes the services of an algorithm asset without them.
See Publishing for the algorithm side.
Try it
The examples cover the whole job lifecycle:
npm start -- compute:envs # what a node offers, and at what price
npm start -- compute:free <datasetDid> <algoDid> # no order, no escrow
npm start -- compute:paid <datasetDid> <algoDid> # funds the escrow from the node's quote
npm start -- compute:full <datasetDid> <algoDid> # start → wait → logs → result