From 8a0c5fac762d8f02c84d8fe6ef50b8bec73f48b3 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 3 Jun 2026 12:20:00 +0200 Subject: [PATCH 1/2] wip - first draft --- data-functions.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 data-functions.md diff --git a/data-functions.md b/data-functions.md new file mode 100644 index 0000000000..1c5d43718a --- /dev/null +++ b/data-functions.md @@ -0,0 +1,66 @@ +NIP-XX +====== + +Data Functions +--------------- + +`draft` `optional` + +## Abstract + +A **data function** is an addressable, reusable computation over Nostr events: code ([NIP-C0](C0.md) `kind:1337`) plus a fixed configuration (source relays, params, ttl). A [NIP-90](90.md) Data Vending Machine runs it on demand and publishes a **content-addressed cached result**, so clients read a cheap precomputed event instead of crawling and aggregating raw events themselves (e.g. comment/like counts, rolling averages). + +## Motivation + +Today every client or relay that needs derived data builds its own heavy, private cache. Data functions move caching into the network: anyone *defines* a computation once, *any* user's client can *trigger* it, and the cached result lands back on relays where it compounds for everyone — computed only when actually requested. Because a definition references reusable code, you can build a data function from someone else's snippet (and others from yours), forming an open market of composable, serverless-style functions. In effect, a decentralised CDN for derived Nostr data. + +## Kinds + +| kind | name | publisher | role | +|------|------|-----------|------| +| `1337` | code (NIP-C0) | author | the reusable logic; `async function main(inputs, nostr)` | +| `31337` | data function definition | author/user | addressable; references a `1337` + bundles config | +| `5910` | job request (NIP-90) | client | thin pointer at a `31337` (+ optional overrides) | +| `7000` | job feedback (NIP-90) | DVM | `processing` / `success` / `error` | +| `6910` | job result (NIP-90) | DVM | points at the cached result | +| `31338` | cached result | DVM | addressable; the payload, keyed by inputs | + +## Data Function Definition (`kind:31337`) + +Addressable. `.content` is empty. + +- `d` - identifier (required) +- `code` - reference to the `kind:1337` code, as `"1337::"` (required) +- `relays` - source relays the code reads from (repeatable values) +- `output` - relay the cached result is published to +- `param` - default input, `["param", , ]` (repeatable) +- `i` - subject the computation is about, `["i", , ]` +- `ttl` - seconds a cached result stays fresh +- `runtime_ms`, `memory_mb` - per-function execution limits (clamped to DVM ceilings) + +## Request (`kind:5910`) + +`a` tag points at a `31337` definition: `["a", "31337::", ]`. The same tags as the definition (`i`, `param`, `relays`, `ttl`) MAY be included to override per request. `["cache", "no"]` forces recompute; `["cache", "clear"]` deletes the cached result. + +## Cached Result (`kind:31338`) + +Addressable. `.content` is the JSON-serialized output. + +- `d` - `sha256(canonical_json({ script, subject, params, relays }))` — the content address +- `expiration` - [NIP-40](40.md), `created_at + ttl` +- `i` - subject, if any + +`script` is the definition address; identical inputs yield the same `d`, so a recompute replaces the prior result and many clients share one cache entry. + +## Flow + +1. Client computes the `d` cache key and reads `kind:31338`. Fresh (`now - created_at <= ttl`) → done. +2. On miss, publish a `5910`. The DVM resolves the `31337` → fetches the `1337` → runs `main` in a sandbox with a mediated `nostr.query(filters, relays?)` → publishes the `31338` cache and a `6910` result. + +## Execution + +Code MUST be run in an isolated sandbox with no ambient I/O; the only capability is `nostr.query`. Implementations enforce runtime, memory, output-size and event-count limits. Since a DVM executes arbitrary third-party code at its own cost, operators MAY restrict which authors' code they run and which pubkeys may trigger jobs — this is operator policy, not part of the protocol. + +## Reference Implementation + +[RelayKit](https://github.com/samthomson/relaykit) ships a `dvm-compute` worker that runs data functions in a [quickjs-emscripten](https://github.com/justjake/quickjs-emscripten) sandbox. From 9b6727230bc488676e1f5d5b088f3975ba2911c8 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 3 Jun 2026 12:52:48 +0200 Subject: [PATCH 2/2] todo --- data-functions.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/data-functions.md b/data-functions.md index 1c5d43718a..f92545a2a2 100644 --- a/data-functions.md +++ b/data-functions.md @@ -6,6 +6,8 @@ Data Functions `draft` `optional` +> **Status: work in progress.** Incomplete and subject to change; not yet submitted for review. Kind numbers are provisional. + ## Abstract A **data function** is an addressable, reusable computation over Nostr events: code ([NIP-C0](C0.md) `kind:1337`) plus a fixed configuration (source relays, params, ttl). A [NIP-90](90.md) Data Vending Machine runs it on demand and publishes a **content-addressed cached result**, so clients read a cheap precomputed event instead of crawling and aggregating raw events themselves (e.g. comment/like counts, rolling averages). @@ -61,6 +63,16 @@ Addressable. `.content` is the JSON-serialized output. Code MUST be run in an isolated sandbox with no ambient I/O; the only capability is `nostr.query`. Implementations enforce runtime, memory, output-size and event-count limits. Since a DVM executes arbitrary third-party code at its own cost, operators MAY restrict which authors' code they run and which pubkeys may trigger jobs — this is operator policy, not part of the protocol. +## Security & Abuse (TBD) + +A DVM performs work on request, so it can be griefed into wasted computation. Cache-first design already absorbs the common case: repeat requests for the same (definition + inputs) return the cached event without recomputing. The remaining vectors are forcing *distinct* computations (varying inputs) or forcing recompute (`cache:no`). Mitigations to specify later (placeholders): + +- relay-level gating of `5910` (e.g. [NIP-42](42.md) auth / allowlisted relay) +- requester allowlist (operator policy) +- per-pubkey rate limiting and a bounded job queue +- honoring `cache:no` only from the definition author +- payment per job ([NIP-90](90.md) bids) + ## Reference Implementation [RelayKit](https://github.com/samthomson/relaykit) ships a `dvm-compute` worker that runs data functions in a [quickjs-emscripten](https://github.com/justjake/quickjs-emscripten) sandbox.