Skip to content

Commit 84f2d96

Browse files
committed
feat: add a transaction api
Signed-off-by: Eric Torreborre <etorreborre@yahoo.com>
1 parent 1de248b commit 84f2d96

File tree

9 files changed

+463
-4
lines changed

9 files changed

+463
-4
lines changed

Cargo.lock

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ acto = { version = "0.8.0", features = ["tokio"] }
2323
anyhow = "1.0.100"
2424
async-compression = { version = "0.4.32", features = ["tokio", "gzip"] }
2525
async-trait = "0.1.83"
26+
axum = "0.8"
2627
assert-json-diff = "2.0.2"
2728
bech32 = "0.11.0"
2829
binrw = "0.15.0"

crates/amaru/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ doc = false
3131
[dependencies]
3232
# External dependencies ───────────────────────────────────────────────────────┐
3333
anyhow.workspace = true
34+
axum.workspace = true
3435
async-compression.workspace = true
3536
async-trait.workspace = true
3637
clap.workspace = true

crates/amaru/src/bin/amaru/cmd/run.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ use amaru::{
2222
config::{Config, MaxExtraLedgerSnapshots, StoreType},
2323
},
2424
};
25-
use amaru_kernel::NetworkName;
25+
use amaru_kernel::{NetworkName, Transaction};
26+
use amaru_ouroboros::ResourceMempool;
2627
use amaru_stores::rocksdb::RocksDbConfig;
2728
use clap::{ArgAction, Parser};
2829
use opentelemetry_sdk::metrics::SdkMeterProvider;
@@ -105,6 +106,16 @@ pub struct Args {
105106
)]
106107
network: NetworkName,
107108

109+
/// Address for the HTTP transaction submit API.
110+
///
111+
/// When set, starts an HTTP server exposing POST /api/submit/tx (Cardano Submit API).
112+
#[arg(
113+
long,
114+
value_name = amaru::value_names::ENDPOINT,
115+
env = amaru::env_vars::SUBMIT_API_ADDRESS,
116+
)]
117+
submit_api_address: Option<String>,
118+
108119
/// Upstream peer addresses to synchronize from.
109120
///
110121
/// This option can be specified multiple times to connect to multiple peers.
@@ -139,17 +150,22 @@ impl Args {
139150
pub async fn run(args: Args, meter_provider: Option<SdkMeterProvider>) -> Result<(), Box<dyn std::error::Error>> {
140151
with_optional_pid_file(args.pid_file.clone(), async |_pid_file| {
141152
let config = parse_args(args)?;
153+
let submit_api_address = config.submit_api_address()?;
142154
pre_flight_checks()?;
143155

144156
let metrics = meter_provider.clone().map(track_system_metrics).transpose()?;
157+
let running = build_and_run_node(config, meter_provider)?;
145158

146159
let exit = amaru::exit::hook_exit_token();
147-
148-
let running = build_and_run_node(config, meter_provider)?;
160+
let submit_api_handle = start_submit_api(submit_api_address, &running, &exit).await?;
149161

150162
exit.cancelled().await;
151163
running.abort();
152164

165+
if let Some(handle) = submit_api_handle {
166+
let _ = handle.await; // Let graceful shutdown complete
167+
}
168+
153169
if let Some(handle) = metrics {
154170
handle.abort();
155171
}
@@ -159,6 +175,21 @@ pub async fn run(args: Args, meter_provider: Option<SdkMeterProvider>) -> Result
159175
.await
160176
}
161177

178+
/// Start an HTTP API endpoint to allow local users to post CBOR-serialized transactions.
179+
async fn start_submit_api(
180+
address: Option<std::net::SocketAddr>,
181+
running: &pure_stage::tokio::TokioRunning,
182+
exit: &tokio_util::sync::CancellationToken,
183+
) -> Result<Option<tokio::task::JoinHandle<()>>, Box<dyn std::error::Error>> {
184+
let Some(addr) = address else {
185+
return Ok(None);
186+
};
187+
let mempool: ResourceMempool<Transaction> = running.resources().get::<ResourceMempool<Transaction>>()?.clone();
188+
let shutdown = exit.child_token();
189+
let (handle, _) = amaru::submit_api::start(addr, mempool, shutdown).await?;
190+
Ok(Some(handle))
191+
}
192+
162193
fn parse_args(args: Args) -> Result<Config, Box<dyn std::error::Error>> {
163194
let network = args.network;
164195

@@ -177,6 +208,7 @@ fn parse_args(args: Args) -> Result<Config, Box<dyn std::error::Error>> {
177208
network = %args.network,
178209
peer_address = %args.peer_address.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(", "),
179210
pid_file = %args.pid_file.unwrap_or_default().to_string_lossy(),
211+
submit_api_address = %args.submit_api_address.as_deref().unwrap_or("disabled"),
180212
"running"
181213
);
182214

@@ -190,6 +222,7 @@ fn parse_args(args: Args) -> Result<Config, Box<dyn std::error::Error>> {
190222
max_downstream_peers: args.max_downstream_peers,
191223
max_extra_ledger_snapshots: args.max_extra_ledger_snapshots,
192224
migrate_chain_db: args.migrate_chain_db,
225+
submit_api_address: args.submit_api_address,
193226
..Config::default()
194227
})
195228
}

crates/amaru/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod tests;
3232
/// The sync pipeline is responsible for fetching blocks from the upstream node and
3333
/// applying them to the local chain.
3434
pub mod stages;
35+
pub mod submit_api;
3536

3637
pub const SNAPSHOTS_DIR: &str = "snapshots";
3738

@@ -167,6 +168,9 @@ pub mod env_vars {
167168
/// --snapshots-dir
168169
pub const SNAPSHOTS_DIR: &str = "AMARU_SNAPSHOTS_DIR";
169170

171+
/// --submit-api-address
172+
pub const SUBMIT_API_ADDRESS: &str = "AMARU_SUBMIT_API_ADDRESS";
173+
170174
/// --target-dir
171175
pub const TARGET_DIR: &str = "AMARU_TARGET_DIR";
172176
}

crates/amaru/src/stages/config.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub struct Config {
3030
pub max_downstream_peers: usize,
3131
pub max_extra_ledger_snapshots: MaxExtraLedgerSnapshots,
3232
pub migrate_chain_db: bool,
33+
pub submit_api_address: Option<String>,
3334

3435
// Number of allocation arenas to keep around for performing parallel evaluation of scripts in
3536
// the ledger.
@@ -46,6 +47,11 @@ impl Config {
4647
pub fn listen_address(&self) -> anyhow::Result<SocketAddr> {
4748
self.listen_address.parse().context("invalid listen address")
4849
}
50+
51+
/// Parse the optional submit API address into a `SocketAddr`.
52+
pub fn submit_api_address(&self) -> anyhow::Result<Option<SocketAddr>> {
53+
self.submit_api_address.as_deref().map(|addr| addr.parse().context("invalid submit API address")).transpose()
54+
}
4955
}
5056

5157
impl Default for Config {
@@ -60,6 +66,7 @@ impl Default for Config {
6066
max_downstream_peers: 10,
6167
max_extra_ledger_snapshots: MaxExtraLedgerSnapshots::default(),
6268
migrate_chain_db: false,
69+
submit_api_address: None,
6370
ledger_vm_alloc_arena_count: 1,
6471
ledger_vm_alloc_arena_size: 1_024_000,
6572
}

0 commit comments

Comments
 (0)