snarkos 4.9.0

A decentralized operating system
# Slipstream Plugins

Slipstream is a plugin system that lets operators stream canonical mapping updates and staking
rewards from snarkOS nodes to external services (databases, metrics pipelines, etc.) in
real time, without modifying node code.

---

## Overview

Slipstream plugins are dynamically loaded shared libraries (`.so` / `.dylib` / `.dll`) that
implement the `SlipstreamPlugin` trait from `snarkvm-slipstream-plugin-interface`. The plugin
manager inside `snarkVM`'s `FinalizeStore` calls plugin hooks every time canonical finalize runs.

Plugins can subscribe to:

- **Mapping updates** — every key/value write that occurs during canonical finalize.
- **Staking rewards** — per-staker reward notifications

Only **Validator** and **Client** nodes finalize blocks and therefore support plugins.
Prover nodes do not.

---

## Building a Plugin

Use `snarkvm-slipstream-plugin-interface` as a dependency and implement the `SlipstreamPlugin`
trait. Compile your crate as a `cdylib`:

```toml
# Cargo.toml
[lib]
crate-type = ["cdylib"]

[dependencies]
snarkvm-slipstream-plugin-interface = { git = "https://github.com/ProvableHQ/snarkVM.git", branch = "stream_plugin_testing" }
```

Export the constructor with the exact symbol name `_create_plugin`:

```rust
#[no_mangle]
pub extern "C" fn _create_plugin() -> *mut dyn SlipstreamPlugin {
    Box::into_raw(Box::new(MyPlugin::new()))
}
```

See `slipstream-plugin-postgres` in the snarkVM repository for a complete reference
implementation.

---

## Plugin Config File (JSON5)

Each plugin is configured via a JSON5 file. The required field is `libpath`, which can be
absolute or relative to the config file's directory.

```json5
{
  // Required: path to the compiled .so / .dylib
  libpath: "./libslipstream_postgres_example.so",

  // Optional: override the plugin name reported by name()
  name: "postgres",

  // Plugin-specific fields (passed verbatim to on_load)
  connection_string: "postgres://user:pass@localhost/aleo",
  batch_size: 100,
}
```

---

## Starting a Node with Plugins

Compile snarkOS with the `slipstream-plugins` feature

```bash
cargo build --features slipstream-plugins
```

Pass one or more `--slipstream-config` flags at startup:

```bash
# Single plugin
snarkos start --client \
  --slipstream-config ~/.aleo/plugins/postgres/plugin.json5

# Multiple plugins
snarkos start --validator \
  --slipstream-config ~/.aleo/plugins/postgres/plugin.json5 \
  --slipstream-config ~/.aleo/plugins/metrics/plugin.json5
```

Plugins are loaded synchronously before the REST server starts. If any plugin fails to load,
the node exits with an error.

---

## Runtime Management via REST API

> **Authentication required.** All slipstream management endpoints are protected by JWT
> authentication. Every request must include an `Authorization: Bearer <token>` header.
> The token is printed to stdout at node startup and written to
> `<node_data_dir>/jwt_secret_<address>.txt`. To disable auth entirely, start the node
> with `--nojwt` (not recommended in production).

### List loaded plugins

```
GET /{network}/slipstream/plugins
```

Response (200):
```json
["postgres", "metrics"]
```

### Load a plugin at runtime

```
POST /{network}/slipstream/plugins
Content-Type: application/json

{ "config_file": "/path/to/plugin.json5" }
```

Response (200):
```json
{ "loaded": "postgres" }
```

Returns **422 Unprocessable Entity** if a plugin with that name is already loaded.

### Unload a plugin

```
DELETE /{network}/slipstream/plugins/{name}
```

Response (200):
```json
{ "unloaded": true }
```

Returns **404 Not Found** if no plugin with that name is loaded.

### Reload a plugin (not yet implemented)

`PUT /{network}/slipstream/plugins/{name}` is not currently available. To update a plugin's
config during runtime, unload it with DELETE and reload it with POST. Otherwise, stop the snarkos service, update the config, and restart it, pointing at the new config.

---

## Example: curl Commands

```bash
BASE="http://localhost:3030/mainnet"
TOKEN="<your-jwt-token>"   # printed at startup or found in <data_dir>/jwt_secret_<address>.txt (e.g. `~/.aleo/storage/jwt_secrect_{address}.txt`)

# List
curl -H "Authorization: Bearer $TOKEN" "$BASE/slipstream/plugins"

# Load
curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"config_file":"/path/to/plugin.json5"}' \
  "$BASE/slipstream/plugins"

# Unload
curl -X DELETE -H "Authorization: Bearer $TOKEN" "$BASE/slipstream/plugins/postgres"
```

---

## Notes

- Plugins are loaded in startup order and unloaded in reverse order on shutdown.
- The `on_unload` method is called on every plugin during graceful shutdown.
- Plugin errors during `notify_mapping_update` / `notify_staking_reward` are logged as warnings
  and never propagated to the node — a misbehaving plugin cannot crash the node.
- The plugin manager uses a `std::sync::RwLock`; `notify_*` calls acquire a read lock, while
  load/unload/reload operations acquire the write lock. Avoid long-running operations inside
  plugin callbacks.