# How Amadeus Protocol Handles High Transaction Throughput: 6 Architectural Techniques Explained

> Discover how Amadeus Protocol achieves high transaction throughput with 6 key architectural techniques including UDP gossip, replica clustering, and optimized socket handling.

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

---

**Amadeus Protocol achieves high transaction throughput by combining a UDP-based gossip layer, replica clustering, an ETS-backed transaction pool, batch broadcasting, asynchronous background timers, and optimized socket handling.**

The Amadeus Protocol (`amadeusprotocol/node`) is an Elixir-based blockchain node designed for massive on-chain activity. Its architecture eliminates bottlenecks at every layer—from network communication to consensus signing. Below is a detailed breakdown of how the protocol sustains thousands of transactions per second.

---

## UDP-Based Gossip Layer for Low-Latency Communication

Amadeus Protocol replaces TCP with **UDP sockets** for inter-node communication. This eliminates connection setup overhead and reduces latency for time-critical messages.

The socket is opened in `NodeGenSocketGen` using `:gen_udp.open/2` with binary mode and active receiving:

```elixir

# ex/lib/node/node_gen_socket_gen.ex

{:ok, sock} = :gen_udp.open(port, [:binary, {:active, true}, {:ip, {0,0,0,0}}])

```

All outbound packets use `:gen_udp.send/4` for connectionless transmission:

```elixir

# ex/lib/node/node_gen_socket_gen.ex – lines 59-62

:gen_udp.send(socket, ip, port, packet)

```

This design choice trades TCP's reliability guarantees for raw speed, which is appropriate for a gossip protocol where message redundancy provides natural fault tolerance.

---

## Replica Clustering with BFT Quorum Logic

Multiple **validator replicas** run in parallel to distribute consensus work. Each replica only signs a block once a quorum of replicas have locked the height, preventing duplicate signatures and wasted computation.

The coordination happens in `ReplicaGen`, which enforces the BFT quorum before any signing occurs:

```elixir

# ex/lib/node/replica_gen.ex

# Replica state management and lock-step signing logic

```

This approach parallelizes consensus without sacrificing safety—the quorum requirement ensures that a supermajority of honest replicas must agree before any block is finalized.

---

## ETS-Backed Transaction Pool with Asynchronous Cleanup

Incoming transactions are stored in an **ETS table** for fast, concurrent access. The transaction pool (`TXPool`) remains compact through periodic purging of stale entries.

The purge runs every 6 seconds via an asynchronous task:

```elixir

# Called from NodeGen.handle_info(:tick_purge_txpool)

Task.async(fn -> TXPool.purge_stale() end)

```

The purge logic lives in [`ex/lib/node/txpool.ex`](https://github.com/amadeusprotocol/node/blob/main/ex/lib/node/txpool.ex). Using ETS instead of a process dictionary or GenServer state minimizes contention and enables true parallel reads.

---

## Batch Broadcasting to Reduce Per-Message Overhead

The node batches validator and peer messages to amortize network overhead. Default limits are **1000 validators** and **10 peers** per batch, all sent in a single UDP packet.

The `broadcast/2` function in `NodeGen` implements this:

```elixir

# ex/lib/node/node_gen.ex

NodeGen.broadcast(my_msg, %{validators: 500, peers: 20})

```

Batching reduces system call frequency and leverages UDP's ability to send large datagrams efficiently. The function signature accepts an override map for fine-tuned control in high-contention scenarios.

---

## Non-Blocking Background Ticks with Lightweight Processes

All periodic work—heartbeats, ANR cleanup, and transaction pool purging—is driven by `:erlang.send_after/3` timers that spawn **separate lightweight processes**. This keeps the main event loop responsive.

The tick handlers in `NodeGen` follow a consistent pattern:

```elixir

# ex/lib/node/node_gen.ex – tick_* handlers

def handle_info(:tick_purge_txpool, state) do
  # Schedule next tick and spawn async work

  :erlang.send_after(6_000, self(), :tick_purge_txpool)
  Task.async(&TXPool.purge_stale/0)
  {:noreply, state}
end

```

This architecture prevents any single maintenance task from blocking consensus or network operations.

---

## Optimized Socket Handling for Deployment Flexibility

The protocol adapts its socket behavior based on deployment mode:

- **Solo testnet mode**: Skips socket creation entirely, saving resources
- **Replica cluster mode**: Binds the socket once at startup, then reuses it

The `skip_socket` logic in `NodeGenSocketGen` enables this optimization:

```elixir

# ex/lib/node/node_gen_socket_gen.ex

# Conditional socket creation based on configuration

```

This conditional approach eliminates unnecessary overhead for developers running local tests while maintaining full networking capabilities for production deployments.

---

## Summary

Amadeus Protocol handles high transaction throughput through deliberate architectural choices at every layer:

- **UDP gossip** eliminates TCP handshake latency
- **Replica clustering** parallelizes consensus with BFT safety
- **ETS-based TXPool** provides lock-free concurrent access
- **Batch broadcasting** reduces network overhead
- **Asynchronous ticks** prevent event loop blocking
- **Conditional socket creation** optimizes resource usage

These techniques combine to enable sustained high throughput without sacrificing the safety properties required of a Byzantine-fault-tolerant consensus system.

---

## Frequently Asked Questions

### What transport protocol does Amadeus Protocol use for node communication?

Amadeus Protocol uses **UDP** for all inter-node gossip. The switch from TCP eliminates connection setup overhead and enables true fire-and-forget messaging. Source code in [`ex/lib/node/node_gen_socket_gen.ex`](https://github.com/amadeusprotocol/node/blob/main/ex/lib/node/node_gen_socket_gen.ex) handles socket creation with `:gen_udp.open/2` and transmission with `:gen_udp.send/4`.

### How does the transaction pool prevent unbounded growth?

The `TXPool` module purges stale entries **every 6 seconds** via `TXPool.purge_stale/0`, called asynchronously from `NodeGen`'s `:tick_purge_txpool` handler. This runs in a separate Task process to avoid blocking the main node. The pool is backed by ETS for O(1) access patterns.

### Why batch validator messages instead of sending individually?

Batching reduces **system call frequency** and amortizes packet header overhead across multiple messages. The `NodeGen.broadcast/2` function defaults to 1000 validators and 10 peers per batch, tunable per call. This is critical for UDP performance at scale, where per-datagram overhead dominates small payloads.

### What happens in solo testnet mode?

In solo testnet configuration, the node **skips UDP socket creation entirely** via the `skip_socket` logic in `NodeGenSocketGen`. This eliminates unnecessary network resource usage when running without replica peers, making local development and testing more efficient.