@@ -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 ;
2627use amaru_stores:: rocksdb:: RocksDbConfig ;
2728use clap:: { ArgAction , Parser } ;
2829use 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 {
139150pub 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+
162193fn 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}
0 commit comments