Skip to content
LogoLogo

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 dataset plus additionalDatasets on the wire.
  • Free compute exists, with no order, no escrow and no payment token.
  • Output is { remoteStorage, encryption }. The publishAlgorithmLog and publishOutput flags are gone.
Loading diagram...

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:

  1. Resolves every input and picks its compute service.
  2. Satisfies all credential policies. A failure here aborts with nothing spent — which is exactly why it comes before the orders.
  3. Asks the node to quote the job: provider fees, reusable orders, and the escrow amount.
  4. 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's EnterpriseEscrow by default), and the escrow amount against maxEscrowPayment / 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.
  5. 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.
  6. 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 an OrderCheckError. It orders every input the node does not report as validly ordered, and since compute() sends the node no transferTxId, the node reports none: each paid input is bought on every job (see known limitations).
  7. 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:

typeFile
imageLogimage.log, pulling or building the algorithm's image
configurationLogconfiguration.log, preparing the job's inputs and algorithm
algorithmLogalgorithm.log, what the algorithm printed
outputoutputs.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