Skip to content

Commit 0794ea7

Browse files
committed
bench | tx-centrifuge: Leios tx-generator
1 parent 8bd1d2d commit 0794ea7

30 files changed

Lines changed: 8037 additions & 0 deletions

File tree

bench/tx-centrifuge/README.md

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
# Tx Centrifuge & Pull-Fiction
2+
3+
`tx-centrifuge` is a high-performance load generator for Cardano, built on top of the protocol-agnostic **Pull-Fiction** library.
4+
5+
Unlike traditional load generators that "push" data at a fixed rate, this system is designed for **pull-based protocols**. It does not generate load by itself; instead, it acts as a **policer** that reacts to requests from downstream consumers, admitting or delaying them to enforce a configured rate ceiling.
6+
7+
### Minimal Configuration Example
8+
9+
A basic configuration defines how to load initial resources, how to build payloads, the desired rate, and where to send the results:
10+
11+
```json
12+
{
13+
"initial_inputs": {
14+
"type": "genesis_utxo_keys",
15+
"params": {
16+
"network_magic": 42,
17+
"signing_keys_file": "funds.json"
18+
}
19+
},
20+
"builder": { "type": "value", "params": { "fee": 1000000 } },
21+
"rate_limit": { "type": "token_bucket", "params": { "tps": 10 } },
22+
"workloads": {
23+
"group-A": {
24+
"targets": {
25+
"node-1": { "addr": "127.0.0.1", "port": 30000 },
26+
"node-2": { "addr": "127.0.0.1", "port": 30001 }
27+
}
28+
}
29+
},
30+
"nodeConfig": "config.json",
31+
"protocolParametersFile": "pp.json"
32+
}
33+
```
34+
35+
## Core Concepts: The Pull-Fiction Engine
36+
37+
The underlying `pull-fiction` library implements a reactive rate-limiting strategy. It only produces data when a consumer asks for it, and only as fast as the rate limiter allows.
38+
39+
### Reactive Rate Limiting
40+
- **Downstream Driven**: Load is only dispensed in response to an explicit pull from a target. If the target doesn't ask, the engine stays idle.
41+
- **Ceiling Enforcement**: The rate limiter enforces a tokens-per-second (TPS) ceiling. Even if a consumer pulls aggressively, the engine ensures the dispensed items never exceed the configured limit.
42+
- **Fairness**: Token slots are claimed in a single atomic STM transaction, providing FIFO-fair scheduling across multiple workers sharing the same limiter.
43+
44+
### Workloads and Targets
45+
The configuration is organized into a hierarchy that defines the concurrency model:
46+
47+
- **Target**: A single network endpoint (e.g., a Cardano node). Each target has a dedicated **Worker thread** that manages the network connection and handles requests.
48+
- **Workload**: A logical grouping of targets.
49+
- All targets within a workload share the same **Builder thread** and the same **Payload Queue**.
50+
- **Transaction Profiles**: Each workload can define its own `builder` configuration. This allows you to generate different "profiles" of transactions (e.g., different sizes, complexities, or fees) for different groups of nodes.
51+
- **Isolation**: By using multiple workloads, you can isolate different groups of targets. For example, one workload could simulate high-volume "small" transactions for one group of nodes, while another generates "heavy" transactions for another.
52+
53+
### Pipeline Architecture
54+
The engine operates as a decoupled production pipeline using generic `input` and `payload` types:
55+
1. **Initial Inputs**: Starting resources (of type `input`) are partitioned across workloads.
56+
2. **Input Queue (Unbounded)**: Holds available `input` items.
57+
3. **Builder (One per Workload)**: A dedicated thread that pulls `input`s, produces a `payload`, and pairs it with any `[input]`s to be recycled. It pushes the `(payload, [input])` pair to the payload queue.
58+
4. **Payload Queue (Bounded)**: The sole source of **backpressure**. The builder blocks here if consumers are slower than the production rate.
59+
5. **Workers (One per Target)**: Threads that manage the consumer connection. They pull from the payload queue via a rate-limited fetcher.
60+
61+
### Resource Recycling
62+
To enable indefinite-duration runs with finite resources, inputs must be returned to the `Input Queue`. There are two main patterns:
63+
64+
1. **Optimistic Recycling (Builder-level)**: The builder immediately returns resources to the `Input Queue` as soon as the payload is constructed. This is the highest-throughput mode but assumes the payload will be successfully processed downstream.
65+
2. **Standard Recycling (On-Fetch)**: The builder pairs the payload with the resources to be **recycled**. The library then automatically returns those resources to the `Input Queue` as soon as a worker **fetches** the payload from the internal queue to deliver it to a target.
66+
67+
## Configuration
68+
69+
### Initial Inputs (`initial_inputs`)
70+
The generator requires a set of initial UTxOs, configured in the `initial_inputs` section of the main configuration file.
71+
72+
- **`type`**: The input loader variant (e.g., `"genesis_utxo_keys"`).
73+
- **`params`**:
74+
- - **`network_magic`**: Required for deriving UTxO references from keys (e.g., `42` for testnet).
75+
- - **`signing_keys_file`**: Path to a JSON file (e.g., `funds.json`) containing the actual fund data.
76+
77+
#### `funds.json` entry types
78+
The file contains an array of fund objects. There are two distinct types:
79+
80+
1. **Genesis Funds** (Key-only): Identified only by their signing key. The `TxIn` is derived automatically.
81+
```json
82+
{ "signing_key": "genesis.skey", "value": 1500000000000 }
83+
```
84+
2. **Payment Funds** (Explicit UTxO): Requires a specific transaction reference.
85+
```json
86+
{ "signing_key": "payment.skey", "value": 1000000, "tx_in": "df6...#0" }
87+
```
88+
89+
**Design Note**: The `funds.json` format is designed to be compatible with the output of `cardano-cli conway create-testnet-data --utxo-keys`. This allows you to immediately use an arbitrary large set of Shelley genesis keys created during testnet bootstrapping as the initial fund pool for the generator, without needing to manually create UTxOs once the network is live.
90+
91+
### Rate Limiting Scopes
92+
The `rate_limit` scope determines the granularity of the TPS ceiling:
93+
- **`shared`**: A single global rate limiter is shared by all targets across all workloads.
94+
- **`per_workload`**: Each workload gets its own independent rate limiter (shared by its targets).
95+
- **`per_target`**: Every connection to a target gets its own independent rate limiter at the full configured TPS.
96+
97+
### Batching and Flow Control
98+
- **`max_batch_size`**: Limits the number of items (e.g., transactions) the generator will announce to a target in a single protocol request.
99+
- This acts as a safety cap: even if a target's protocol allows for 500 items, a `max_batch_size` of 100 ensures the generator doesn't commit too much capacity to a single connection at once.
100+
- This helps distribute the available "payload queue" more evenly across multiple targets and prevents a single aggressive node from starving others.
101+
- **`on_exhaustion`**:
102+
- `block`: The worker thread waits until the builder produces a new payload.
103+
- `error`: The generator fails immediately if the builder cannot keep up with the requested TPS.
104+
105+
## Cardano Implementation (`tx-centrifuge`)
106+
107+
### Value Builder Parameters
108+
These parameters define the **transaction profile** for a workload:
109+
- `inputs_per_tx` / `outputs_per_tx`: Controls the transaction structure (size and complexity).
110+
- `fee`: Fixed Lovelace fee per transaction.
111+
- `optimistic_recycle`:
112+
- `true`: Output UTxOs are recycled immediately by the builder.
113+
- `false`: Output UTxOs are recycled by the worker as soon as the transaction is announced to the node.
114+
115+
## Usage
116+
117+
```bash
118+
tx-centrifuge config.json
119+
```
120+
121+
## Detailed Examples
122+
123+
### 1. High-Throughput (Optimistic Recycling)
124+
Optimized for maximum TPS using simple 1-in/1-out transactions.
125+
126+
**`config.json` snippet:**
127+
```json
128+
{
129+
"initial_inputs": {
130+
"type": "genesis_utxo_keys",
131+
"params": {
132+
"network_magic": 42,
133+
"signing_keys_file": "funds.1.json"
134+
}
135+
},
136+
"builder": {
137+
"type": "value",
138+
"params": {
139+
"inputs_per_tx": 1,
140+
"outputs_per_tx": 1,
141+
"fee": 1000000,
142+
"optimistic_recycle": true
143+
}
144+
},
145+
"rate_limit": {
146+
"type": "token_bucket",
147+
"scope": "shared",
148+
"params": { "tps": 1000 }
149+
},
150+
"workloads": {
151+
"simulation": {
152+
"targets": {
153+
"node-0": { "addr": "127.0.0.1", "port": 30000 }
154+
}
155+
}
156+
},
157+
"nodeConfig": "config.json",
158+
"protocolParametersFile": "pp.json"
159+
}
160+
```
161+
162+
**`funds.1.json` snippet:**
163+
```json
164+
[
165+
{"signing_key": "utxo1.skey", "value": 1500000000000},
166+
{"signing_key": "utxo2.skey", "value": 1500000000000}
167+
]
168+
```
169+
170+
### 2. Large Transactions (Target-Specific Limits)
171+
Uses complex transactions with independent rate limits for each target connection.
172+
173+
**`config.json` snippet:**
174+
```json
175+
{
176+
"initial_inputs": {
177+
"type": "genesis_utxo_keys",
178+
"params": {
179+
"network_magic": 42,
180+
"signing_keys_file": "funds.2.json"
181+
}
182+
},
183+
"builder": {
184+
"type": "value",
185+
"params": {
186+
"inputs_per_tx": 5,
187+
"outputs_per_tx": 5,
188+
"fee": 2000000,
189+
"optimistic_recycle": false
190+
}
191+
},
192+
"rate_limit": {
193+
"type": "token_bucket",
194+
"scope": "per_target",
195+
"params": { "tps": 5 }
196+
},
197+
"max_batch_size": 50,
198+
"on_exhaustion": "block",
199+
"workloads": {
200+
"heavy-load": {
201+
"targets": {
202+
"edge-node": { "addr": "192.168.1.10", "port": 30001 }
203+
}
204+
}
205+
}
206+
}
207+
```
208+
209+
**`funds.2.json` snippet:**
210+
```json
211+
[
212+
{"signing_key": "utxo1.skey", "value": 1000000000},
213+
{"signing_key": "utxo2.skey", "value": 1000000000},
214+
{"signing_key": "utxo3.skey", "value": 1000000000}
215+
]
216+
```

0 commit comments

Comments
 (0)