Skip to content

ledger: serve RPC WIT on /rpc - #262

Merged
rvolosatovs merged 4 commits into
mainfrom
roman/ledger/utxo-wit
Oct 21, 2026
Merged

rvolosatovs merged 4 commits into
mainfrom
roman/ledger/utxo-wit

Conversation

@rvolosatovs

@rvolosatovs rvolosatovs commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Serve invocation WIT on GET /rpc

Clients can now fetch WIT describing what POST /rpc accepts and generate wRPC bindings from it, without having to decode contracts themselves.

  • GET /rpc returns the starstream:ledger package that describes the ledger queries.
  • GET /rpc?index=INDEX[&transaction=DIGEST] returns a starstream:ledger package with only a utxo interface for that unspent output. Its functions are the UTXO's methods. Each
    takes the output reference (transaction, index) followed by the method parameters, which get an arg- prefix so names can't clash. If transaction is omitted, it refers to
    the genesis block. Repeating a parameter returns 400, and an empty query (?, ?&) counts as no query.
  • Responses are WIT text by default, or Wasm with Accept: application/wasm. HEAD /rpc is supported. The GET/HEAD handlers for /contracts, /transactions and /rpc are
    merged into one handler each, generic over HEAD.
  • When a contract is published, the UTXO WIT for each exported instance with a utxo resource is built once and served from memory. It is converted from the component types that
    publish validation already computes, so the contract's WIT is never decoded with wit-parser. That decoder overflows the stack on valid components whose imported packages depend on
    each other, which would abort the node.
  • The client gains build_utxo_wit_request and Client::get_utxo_wit. TransactionInput gains genesis and new constructors.
  • The CLI gains ledger utxo wit [--transaction DIGEST] <INDEX>. ledger utxo call now gets the method type from this endpoint instead of downloading and decoding the whole
    contract.

Behaviour changes

  • Publishing now returns 400 for Wasm that isn't a component. It also returns 400 for contracts whose UTXO methods take or return anything other than booleans, integers, or
    options, results and tuples of those (e.g. a UTXO handle). The error names the offending parameter or result. These are the only shapes the compiler currently produces: structs and
    enums in UTXO method signatures don't componentize yet.
  • Contract::utxos now treats an instance as a UTXO instance only if its utxo export is a resource. This matches what get_utxo_typed already required.

Dependency pins (temporary)

Tests

  • The CLI e2e test starts the ledger with the outputs of the simulated example call as genesis, prints the UTXO's WIT and calls one of its methods.
  • The ledger tests cover GET /rpc and the UTXO WIT of a compiled contract using options, results and tuples, with parameters named index and transaction. They also check that
    a UTXO method taking a UTXO is rejected, and that a component whose imported packages form a cycle publishes.

Comment thread starstream-ledger/src/server/http/mod.rs Outdated
Comment thread starstream-ledger/src/server/http/mod.rs Outdated
Comment thread starstream-ledger/src/server/http/mod.rs Outdated
Comment thread starstream-ledger/tests/ledger.rs
Comment thread starstream-ledger/src/server/http/mod.rs Outdated
Comment thread starstream-ledger/src/server/http/mod.rs Outdated
@rvolosatovs
rvolosatovs force-pushed the roman/ledger/utxo-wit branch 13 times, most recently from 0b523f4 to 6a7e51b Compare October 20, 2026 11:43
@rvolosatovs
rvolosatovs marked this pull request as ready for review October 20, 2026 11:47
@rvolosatovs
rvolosatovs force-pushed the roman/ledger/utxo-wit branch from 6a7e51b to 245af13 Compare October 20, 2026 15:01
@rvolosatovs
rvolosatovs added this pull request to stack #272 October 20, 2026 16:54
#268 and #265 raced: `tower-http` still pointed at `bitflags` 2.9.4, which #265 dropped from the lock, breaking `cargo check --locked`.

Assisted-by: anthropic:claude-opus-5-5
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
`Contract::utxos` treated every exported instance exporting anything
named `utxo` as a UTXO instance, while `get_utxo_typed` then rejects it
unless that export is a resource, and the typed UTXO imports are
already matched only on a `utxo` resource. Require the export to be a
resource, so an instance exporting a `utxo` type of another kind, or a
`utxo` function, is not listed as a UTXO instance.

Assisted-by: anthropic:claude-opus-5-5
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
`headers-accept` is pinned to the head of
maxcountryman/headers-accept#24, on top of the unreleased upstream main,
whose media range matching ignores `q` when comparing parameters, so
`Accept: text/plain;q=0.9` matches the `text/plain; charset=UTF-8` the
ledger responds with. The PR additionally compares charset values
case-insensitively, so `charset=utf-8` matches it too.

The revision is fetched from the upstream repository, which holds the
PR head, rather than from the fork it was pushed to.

Assisted-by: anthropic:claude-opus-5-5
Assisted-by: anthropic:claude-fable-5-1
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
`GET /rpc` returns the `starstream:ledger` package describing the
ledger queries `POST /rpc` accepts. `GET /rpc?index=INDEX`, optionally
with `&transaction=DIGEST`, instead returns a `starstream:ledger`
package holding only a `utxo` interface for that unspent output: its
UTXO's methods as the freestanding functions `/rpc` invokes. Each takes
the output reference (`transaction`, `index`) followed by the method
parameters, which are prefixed with `arg-` so the names never clash.
`index` is a `u32` and `transaction` a digest, defaulting to the genesis
block. Repeating either is rejected with 400, and an empty query
(`GET /rpc?`, `GET /rpc?&`) is treated as no query.

Both are served as WIT text by default, or encoded as Wasm on
`Accept: application/wasm`, so clients can generate wRPC bindings
without decoding the contract themselves. `HEAD /rpc` returns the
headers of the same response. The `GET` and `HEAD` handlers of
`/contracts`, `/transactions` and `/rpc` are merged into one handler
each, generic over `HEAD`, which omits the body but keeps
`Content-Length`.

The UTXO WIT of every exported instance that exports a `utxo` resource
is built once, at publish, and served from memory, keyed by export name
as `POST /rpc` resolves it. It is converted from the component types
the publish validation already computes into a fresh `Resolve` holding
only the ledger package. The contract's WIT is never decoded with
wit-parser, whose decoder overflows the stack, aborting the node, on
valid components whose imported packages depend on each other.

Only the shapes the compiler produces are handled: synchronous methods
whose parameters and results are booleans, integers, and options,
results and tuples of them. The `utxo` interface therefore defines no
named types and depends on no other package. Publishing now rejects
with 400 Wasm that is not a component and contracts with UTXO methods
taking or returning anything else, such as a UTXO handle, naming the
offending parameter or result.

The ledger package is encoded to Wasm by a build script, only with the
`server` feature, which is also the only one pulling in the script's
`anyhow` build dependency.

The ledger client gains `build_utxo_wit_request` and
`Client::get_utxo_wit`, which takes an optional `Accept` media type and
returns the raw body. GET request builders share a helper.
`TransactionInput` gains `genesis` and `new` constructors.

The CLI gains `ledger utxo wit [--transaction DIGEST] <INDEX>`, printing
a UTXO's WIT in the shape `ledger utxo call` invokes it.
`ledger utxo call` now resolves the method type from the Wasm-encoded
UTXO WIT instead of downloading and decoding the whole contract.

The CLI e2e test now starts the ledger with the outputs of the
simulated `example` call as genesis, prints the UTXO's WIT and calls
one of its methods. The ledger tests cover `GET /rpc`, the UTXO WIT of
a compiled contract using options, results and tuples with parameters
named `index` and `transaction`, rejecting a UTXO method taking a UTXO,
and publishing a component whose imported packages form a cycle. Their
`serve` helper no longer publishes the contract itself.

Assisted-by: anthropic:claude-opus-5-5
Assisted-by: anthropic:claude-fable-5-1
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
@rvolosatovs
rvolosatovs force-pushed the roman/ledger/utxo-wit branch from 245af13 to 57a9fd4 Compare October 21, 2026 15:07
@rvolosatovs

Copy link
Copy Markdown
Contributor Author

same as #268 (comment)

@rvolosatovs
rvolosatovs merged commit fb195fe into main Oct 21, 2026
5 checks passed
@rvolosatovs
rvolosatovs deleted the roman/ledger/utxo-wit branch October 21, 2026 15:28
rvolosatovs added a commit that referenced this pull request Oct 21, 2026
Add `docs/ledger.md`, a usage-focused page for the development ledger:

- a quick start that compiles the `require_hash_preimage` example, runs
a
node with a genesis output created by `create-hash 42`, and spends it
with
`consume-hash`, first with a wrong preimage (traps, nothing submitted)
and
    then with the right one;
  - `starstream node` flags and the genesis TOML format;
- the `starstream ledger` client commands, including the `account`,
`admin`,
    `block get` and `network` queries from #271;
  - the ledger model: digests, accounts, nonces, UTXOs and blocks;
  - the HTTP API: `POST /rpc` (COSE envelopes and wRPC queries against
`ledger.wit`, plus UTXO method calls), `GET /rpc` (ledger and per-UTXO
WIT,
from #262), `GET /contracts/<digest>`, `GET /transactions/<digest>`, the
    envelope format and the error status codes.

A warning near the top says this is not a secure ledger: only genesis
outputs
  need authorization, proofs are not checked, and state lives in memory.

`docs/cli.md` now has a short `starstream node` / `starstream ledger`
section
  that links to the new page.

Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant