# How Rust NIFs Power the Amadeus Protocol Node: High-Performance Storage and Consensus in Elixir

> Discover how Rust NIFs enable the Amadeus Protocol node to achieve high-performance storage and consensus by bridging Elixir concurrency with Rust's speed and memory safety.

- Repository: [Amadeus Protocol/node](https://github.com/amadeusprotocol/node)
- Tags: deep-dive
- Published: 2026-08-20

---

**Rust NIFs serve as the critical bridge that lets the Amadeus Protocol node's Elixir/Erlang runtime execute performance-critical storage and consensus operations in native code, combining BEAM concurrency with Rust's speed and memory safety.**

The Amadeus Protocol node is architected primarily in Elixir, running on the Erlang VM (BEAM). Yet blockchain nodes demand extreme efficiency for on-disk storage and cryptographic verification. Rather than compromise the developer experience or rewrite the entire system, the Amadeus team uses **Rust Native Implemented Functions (NIFs)** — compiled Rust libraries that the BEAM can call directly. This article examines exactly how these NIFs function in the [amadeusprotocol/node](https://github.com/amadeusprotocol/node) repository based on the source code itself.

## What Are Rust NIFs and Why They Matter for Blockchain Nodes

Rust NIFs are dynamic libraries loaded into the BEAM that expose Rust functions as if they were native Erlang functions. For the Amadeus node, this architecture solves three hard problems:

- **Storage latency** — RocksDB operations must not block lightweight Erlang processes
- **Cryptographic throughput** — BLS12-381 signatures and proof-of-work verification need raw compute performance
- **Memory safety** — Native code handling financial data cannot tolerate use-after-free or data races

The tradeoff is worth it: Rust NIFs give the Amadeus node **native speed without sacrificing the fault-tolerance and distribution primitives** that make Elixir ideal for networked systems.

## Core Storage NIFs: RocksDB Operations in [`lib.rs`](https://github.com/amadeusprotocol/node/blob/main/lib.rs)

The heart of the Amadeus Protocol node's Rust NIF layer lives in [`ex/native/rdb/src/lib.rs`](https://github.com/amadeusprotocol/node/blob/main/ex/native/rdb/src/lib.rs). This file registers the NIF module **Elixir.RDB** and implements the complete RocksDB interface used by the node.

### NIF Registration and Function Exports

At lines 14-15 of [`lib.rs`](https://github.com/amadeusprotocol/node/blob/main/lib.rs), the module declares its exported functions using the `rustler` macro system:

```rust
#[rustler::nif]
fn open_transaction_db<'a>(env: Env<'a>, path: String, cf_names: Vec<String>) -> NifResult<Term<'a>> {
    // Implementation opens RocksDB with column families
}

```

The exposed functions include:

- `open_transaction_db/2` — Initialize database with named column families
- `put/3`, `get/2`, `delete/2` — Basic key-value operations
- `batch_write/2` — Atomic multi-key updates
- Iterator functions for range scans

Each function returns `NifResult<T>`, a Rustler type that automatically converts Rust `Result` types into Erlang `{:ok, term}` or `{:error, reason}` tuples.

### Dirty Scheduler Integration for Non-Blocking Execution

A blockchain node cannot afford scheduler starvation. The Amadeus NIFs declare execution constraints explicitly using `schedule` attributes. Throughout [`lib.rs`](https://github.com/amadeusprotocol/node/blob/main/lib.rs), you'll find patterns like these:

```rust
#[rustler::nif(schedule = "DirtyCpu")]
fn compute-intensive-op(...) { ... }

#[rustler::nif(schedule = "DirtyIo")]
fn disk-heavy-op(...) { ... }

```

Specific locations include:

- Lines 227-229 — DirtyCpu scheduling for cryptographic operations
- Lines 240-242 — DirtyIo scheduling for large batch writes
- Lines 365-367 — DirtyCpu for iterator-heavy range queries
- Lines 721-723 — DirtyIo for compaction-triggering deletes

**DirtyCpu** runs the NIF on a separate CPU-bound thread pool, while **DirtyIo** uses I/O-bound threads. This ensures long-running RocksDB compactions or signature verifications never pause the BEAM's main schedulers.

## Safe Resource Management with `ResourceArc`

Rust's ownership model integrates with the BEAM's garbage collector through the `rustler::resource!` macro. In [`lib.rs`](https://github.com/amadeusprotocol/node/blob/main/lib.rs) lines 99-102, the module registers resource types:

```rust
fn on_load(env: Env, _info: Term) -> bool {
    rustler::resource!(DbResource, env);
    rustler::resource!(CfResource, env);
    rustler::resource!(TxResource, env);
    rustler::resource!(ItResource, env);
    true
}

```

These correspond to:

| Resource | Rust Type | Purpose |
|----------|-----------|---------|
| `DbResource` | `Arc<DB>` | Open RocksDB database handle |
| `CfResource` | `Arc<ColumnFamily>` | Reference to column family |
| `TxResource` | `Transaction` | Active transaction guard |
| `ItResource` | `DBIterator` | Live iterator over key range |

When Elixir passes a database reference to `put/3`, it actually passes an opaque term containing a `ResourceArc<DbResource>`. The Rust side borrows the underlying `DB`, operates on it, and returns. If the Elixir process terminates or the reference goes out of scope, the `ResourceArc` drops to zero and Rust automatically closes handles — no manual cleanup, no leaks, no use-after-free.

## Consensus and Cryptography NIFs

Beyond storage, the Amadeus Protocol node delegates consensus-critical work to Rust. The `ex/native/rdb/src/consensus/` directory contains additional NIF-exposed modules:

- **[`bls12_381.rs`](https://github.com/amadeusprotocol/node/blob/main/bls12_381.rs)** — BLS signature aggregation and verification
- **`bic/*.rs`** — Proof-of-work verification and block validation helpers

These compile into the same NIF library as the storage layer. When the Elixir consensus engine processes a new block, it calls these functions like any other Erlang function, but execution happens in optimized Rust with constant-time cryptographic primitives.

The scheduling attributes apply here too: signature batch verification uses `schedule = "DirtyCpu"` to saturate available cores without blocking message passing.

## The Elixir API: Thin Wrappers Over Native Code

From the Elixir developer's perspective, Rust NIFs disappear behind idiomatic module boundaries. A typical wrapper looks like:

```elixir
defmodule AmadeusRDB do
  use Rustler, otp_app: :amadeusd, crate: "rdb"

  # NIF stubs — actual implementation is in Rust

  def open_transaction_db(_path, _cf_names), do: :erlang.nif_error(:nif_not_loaded)
  def put(_db, _key, _value), do: :erlang.nif_error(:nif_not_loaded)
  def get(_db, _key), do: :erlang.nif_error(:nif_not_loaded)
  def delete(_db, _key), do: :erlang.nif_error(:nif_not_loaded)
end

```

The `use Rustler` line configures:
- `otp_app: :amadeusd` — Application name for path resolution
- `crate: "rdb"` — Cargo crate name in [`ex/native/rdb/Cargo.toml`](https://github.com/amadeusprotocol/node/blob/main/ex/native/rdb/Cargo.toml)

At runtime, the compiled `.so` or `.dll` is loaded automatically. The `:nif_not_loaded` stubs are replaced with actual function pointers, and calls become direct jumps into Rust code.

### Complete Usage Example

```elixir

# Open database with column families

{:ok, db} = AmadeusRDB.open_transaction_db("/var/lib/amadeusd/db", ["default", "index"])

# Write operation (executes in Rust, scheduled on DirtyIo)

:ok = AmadeusRDB.put(db, "block:1000", serialized_block)

# Read with automatic deserialization

{:ok, block_data} = AmadeusRDB.get(db, "block:1000")

```

## Key Source Files for Understanding Rust NIFs in Amadeus

| File | Role |
|------|------|
| [`ex/native/rdb/src/lib.rs`](https://github.com/amadeusprotocol/node/blob/main/ex/native/rdb/src/lib.rs) | Core NIF library with RocksDB operations, resource registration, and scheduler hints |
| [`ex/native/rdb/src/atoms.rs`](https://github.com/amadeusprotocol/node/blob/main/ex/native/rdb/src/atoms.rs) | Erlang atom declarations (`:ok`, `:error`, `:not_found`) for NIF return values |
| [`ex/native/rdb/src/tx_filter.rs`](https://github.com/amadeusprotocol/node/blob/main/ex/native/rdb/src/tx_filter.rs) | Transaction filtering utilities before Elixir handoff |
| `ex/native/rdb/src/consensus/*` | Cryptographic and consensus NIFs (BLS, proof-of-work) |

These files are compiled together via Cargo and linked as a single NIF library loaded by the BEAM at application startup.

## Summary

Rust NIFs in the Amadeus Protocol node deliver:

- **Native RocksDB performance** through direct [`lib.rs`](https://github.com/amadeusprotocol/node/blob/main/lib.rs) implementations of `put`, `get`, `delete`, and iterator operations
- **Scheduler isolation** via `DirtyCpu` and `DirtyIo` attributes that prevent storage and crypto work from blocking lightweight processes
- **Memory safety guarantees** through `ResourceArc` wrappers around database handles, transactions, and iterators
- **Ecosystem consistency** — consensus cryptography and storage share the same Rust codebase and Elixir calling conventions

The result is a node that runs Erlang's battle-tested networking and supervision trees alongside Rust's zero-cost abstractions, without FFI complexity or process boundary overhead.

## Frequently Asked Questions

### What is a NIF in Erlang/Elixir?

A NIF (Native Implemented Function) is a function written in C, C++, or Rust that the BEAM virtual machine can call directly as if it were a built-in Erlang function. NIFs run in the same OS process as the VM but execute native machine code, making them ideal for performance-critical operations that would be too slow in pure Erlang.

### Why does the Amadeus node use Rust instead of C for NIFs?

Rust provides memory safety without garbage collection, which is critical for a blockchain node handling financial data. The `rustler` crate automates NIF boilerplate and resource management, making Rust NIFs less error-prone than manual C implementations while matching C performance. The Amadeus source uses `rustler::resource!` and `ResourceArc` to eliminate entire classes of memory bugs.

### How do DirtyCpu and DirtyIo schedulers prevent node freezing?

The BEVM has limited scheduler threads for running Erlang processes. If a NIF runs too long on these threads, it blocks all process execution on that scheduler. DirtyCpu and DirtyIo are separate thread pools: CPU-bound NIFs (cryptography, complex calculations) run on DirtyCpu threads, while I/O-bound NIFs (large disk writes) run on DirtyIo threads. The Amadeus node marks its RocksDB and consensus NIFs appropriately to maintain responsive message passing.

### Can Rust NIFs crash the entire Erlang VM?

Yes — NIFs share memory space with the BEAM, so a segfault or panic in Rust will terminate the entire node. The Amadeus codebase mitigates this through Rust's type system and by avoiding `unsafe` code in NIF implementations. All database handle access goes through `ResourceArc`, which guarantees valid pointers, and error handling uses `NifResult` to convert Rust errors into catchable Erlang exceptions rather than panics.