Note: In this project, Tx == Bundle
A bundle is a vector of transactions.
A single transaction is treated as a bundle of length1.
Arbiter is a block engine clone.
Arbiter is split into 5 components:
- Tx Receive / Tx Send
- Filtering Layer
- Auction Batch (50ms)
- gRPC Stream to Validator
- Bank Freeze Simulation (Optional)
Incoming transactions are always treated as bundles:
bundle = Vec<Tx>- if user sends one tx →
bundle = vec![tx]
POST /send
- Accepts
Vec<Tx> - Bundle size limit:
<= 5
Example internal shape:
Vec<Tx> // 1..=5Bundles go into a pending ring buffer.
Filtering rule: do heavy work last, and most filtering is deferred into auction stage.
Filter priority (light → heavy):
Blockhash Expiration <<<< Tip Filter <<< Signature Verify
✅ Blockhash expiration check first (cheap + fast)
✅ Tip-based filtering next
✅ Signature verify last (most expensive)
- A ring buffer stores incoming bundles until they are pulled by Auction.
Auction operates on a fixed internal time tick.
Implementation idea:
- internal clock loop (
std::thread) - every 50ms
- pull everything from pending ring buffer
- reset buffer counter
- sort bundles by decreasing TIP
- push sorted bundles into an outbound channel for validator stream
Auction produces a batch of prioritized bundles:
Vec<([Tx; 5], Tip)>Notes:
- internally bundle may be padded into
[Tx;5]form for transport - sorting key = highest tip first
This component handles:
- full protobuf implementation
- connect / reconnect / disconnect resilience
- stream outbound auction batches continuously
- Maintain a long-lived gRPC stream
- Send
Vec<Vec<Tx>>batches (final format TBD) - Track validator state
- slot tracking: determine whether the connected validator is leader
- confirm batch payload format:
Vec<Vec<Tx>>vsVec<Bundle>- whether to send only new batch or resend window
Optional simulation executes bundles against a frozen bank snapshot.
At time T:
validator bank != rpc bank- RPC bank state can be delayed / stale
But simulation needs the latest possible bank for meaningful results.
Find the best way to simulate against the freshest bank source:
- ideally validator-side bank state
- avoid laggy RPC state
High-level pipeline:
/send API
↓
Pending Ring Buffer
↓
Auction Tick (every 50ms)
↓ (sort by TIP desc)
Outbound Stream Channel
↓
gRPC Validator Stream
↓
(Optional) Bank Freeze Simulation
arbiter/
├── crates/
│ ├── api/ # HTTP API: /send
│ ├── buffer/ # ring buffer + pending bundles
│ ├── auction/ # 50ms tick + tip sorting + batching
│ ├── grpc-client/ # validator grpc stream + reconnect logic
│ ├── filters/ # blockhash/tip/sigverify modules
│ └── simulation/ # bank freeze + replay logic (optional)
├── proto/ # protobuf definitions
├── configs/ # config files (ports, limits, tick interval)
└── README.md| Setting | Default |
|---|---|
| Bundle size limit | 5 |
| Auction tick interval | 50ms |
| Sort order | Tip descending |
| Pending buffer | Ring buffer |
| Filter ordering | blockhash → tip → sigverify |
-
/sendAPI acceptsVec<Tx>bundles (1..=5) - ring buffer for pending bundles
- 50ms tick loop
- pull + reset pending buffer
- sort bundles by tip
- push to outbound stream channel
- full protobuf schema
- connect/reconnect handling
- stream batches to validator
- enforce filter ordering
- move heavy verification late (auction stage)
- freeze bank snapshot
- replay tx bundles
- handle validator bank vs rpc bank drift
- A bundle is the atomic unit (even 1 tx = bundle)
- heavy checks should not block ingestion
- prioritization is tip-based auction ordering
- validator streaming format is still evolving