44/// 1. Loads undelivered entries from `st_outbound_msg` on startup, resolving delivery data from outbox tables.
55/// 2. Accepts immediate notifications via an mpsc channel when new outbox rows are inserted.
66/// 3. Delivers each message in msg_id order via HTTP POST to
7- /// `http://localhost:80 /v1/database/{target_db}/call-from-database/{reducer}?sender_identity=<hex>&msg_id=<n>`
7+ /// `http://localhost:{http_port} /v1/database/{target_db}/call-from-database/{reducer}?sender_identity=<hex>&msg_id=<n>`
88/// 4. On transport errors (network, 5xx, 4xx except 422/402): retries infinitely with exponential
99/// backoff, blocking only the affected target database (other targets continue unaffected).
1010/// 5. On reducer errors (HTTP 422) or budget exceeded (HTTP 402): calls the configured
@@ -16,14 +16,13 @@ use crate::host::FunctionArgs;
1616use spacetimedb_datastore:: execution_context:: Workload ;
1717use spacetimedb_datastore:: system_tables:: { StOutboundMsgRow , ST_OUTBOUND_MSG_ID } ;
1818use spacetimedb_datastore:: traits:: IsolationLevel ;
19- use spacetimedb_lib:: { AlgebraicValue , Identity } ;
19+ use spacetimedb_lib:: { AlgebraicValue , Identity , ProductValue } ;
2020use spacetimedb_primitives:: { ColId , TableId } ;
2121use std:: collections:: { HashMap , VecDeque } ;
2222use std:: sync:: Arc ;
2323use std:: time:: { Duration , Instant } ;
2424use tokio:: sync:: mpsc;
2525
26- const IDC_HTTP_PORT : u16 = 80 ;
2726const INITIAL_BACKOFF : Duration = Duration :: from_millis ( 100 ) ;
2827const MAX_BACKOFF : Duration = Duration :: from_secs ( 30 ) ;
2928/// How long to wait before polling again when there is no work.
@@ -38,6 +37,7 @@ pub type IdcActorSender = mpsc::UnboundedSender<()>;
3837/// The identity of this (sender) database, set when the IDC actor is started.
3938pub struct IdcActorConfig {
4039 pub sender_identity : Identity ,
40+ pub http_port : u16 ,
4141}
4242
4343/// A handle that, when dropped, stops the IDC actor background task.
@@ -86,6 +86,7 @@ struct PendingMessage {
8686 target_db_identity : Identity ,
8787 target_reducer : String ,
8888 args_bsatn : Vec < u8 > ,
89+ request_row : ProductValue ,
8990 /// From the outbox table's `TableSchema::on_result_reducer`.
9091 on_result_reducer : Option < String > ,
9192}
@@ -171,7 +172,7 @@ async fn run_idc_loop(
171172 log:: warn!(
172173 "idc_actor: transport error delivering msg_id={} to {}: {reason}" ,
173174 msg. msg_id,
174- hex :: encode ( msg. target_db_identity. to_byte_array ( ) ) ,
175+ msg. target_db_identity. to_hex ( ) ,
175176 ) ;
176177 state. record_transport_error ( ) ;
177178 // Do NOT pop the front — keep retrying this message for this target.
@@ -246,19 +247,23 @@ async fn finalize_message(
246247 return ;
247248 } ;
248249
249- // Encode (result_payload: String) as BSATN args.
250- // The on_result reducer is expected to accept a single String argument.
251- let args_bytes = match spacetimedb_sats:: bsatn:: to_vec ( & result_payload) {
252- Ok ( b) => b,
253- Err ( e) => {
254- log:: error!(
255- "idc_actor: failed to encode on_result args for msg_id={}: {e}" ,
256- msg. msg_id
257- ) ;
258- delete_message ( db, msg. msg_id ) ;
259- return ;
260- }
250+ // Encode `(request_row: OutboxRow, result: Result<(), String>)` as BSATN args.
251+ let result = if result_payload. is_empty ( ) {
252+ Ok :: < ( ) , String > ( ( ) )
253+ } else {
254+ Err :: < ( ) , String > ( result_payload)
261255 } ;
256+ let mut args_bytes = Vec :: new ( ) ;
257+ if let Err ( e) = spacetimedb_sats:: bsatn:: to_writer ( & mut args_bytes, & msg. request_row )
258+ . and_then ( |_| spacetimedb_sats:: bsatn:: to_writer ( & mut args_bytes, & result) )
259+ {
260+ log:: error!(
261+ "idc_actor: failed to encode on_result args for msg_id={}: {e}" ,
262+ msg. msg_id
263+ ) ;
264+ delete_message ( db, msg. msg_id ) ;
265+ return ;
266+ }
262267
263268 let caller_identity = Identity :: ZERO ; // system call
264269 let result = host
@@ -366,12 +371,24 @@ fn load_pending_into_targets(db: &RelationalDB, targets: &mut HashMap<Identity,
366371
367372 let pv = outbox_row_ref. to_product_value ( ) ;
368373
369- // Col 1: target_db_identity (Identity stored as U256).
370- let target_db_identity = match pv. elements . get ( 1 ) {
371- Some ( AlgebraicValue :: U256 ( u) ) => Identity :: from_u256 ( * * u) ,
374+ // Col 1: target_db_identity stored as SATS `Identity`,
375+ // i.e. the product wrapper `(__identity__: U256)`.
376+ let target_db_identity: Identity = match pv. elements . get ( 1 ) {
377+ Some ( AlgebraicValue :: Product ( identity_pv) ) if identity_pv. elements . len ( ) == 1 => {
378+ match & identity_pv. elements [ 0 ] {
379+ AlgebraicValue :: U256 ( u) => Identity :: from_u256 ( * * u) ,
380+ other => {
381+ log:: error!(
382+ "idc_actor: outbox row col 1 expected Identity inner U256, got {other:?} (msg_id={})" ,
383+ st_row. msg_id,
384+ ) ;
385+ continue ;
386+ }
387+ }
388+ }
372389 other => {
373390 log:: error!(
374- "idc_actor: outbox row col 1 expected U256 ( Identity) , got {other:?} (msg_id={})" ,
391+ "idc_actor: outbox row col 1 expected Identity wrapper , got {other:?} (msg_id={})" ,
375392 st_row. msg_id,
376393 ) ;
377394 continue ;
@@ -392,6 +409,7 @@ fn load_pending_into_targets(db: &RelationalDB, targets: &mut HashMap<Identity,
392409 target_db_identity,
393410 target_reducer,
394411 args_bsatn,
412+ request_row : pv,
395413 on_result_reducer,
396414 } ) ;
397415 }
@@ -412,17 +430,13 @@ fn load_pending_into_targets(db: &RelationalDB, targets: &mut HashMap<Identity,
412430}
413431
414432/// Attempt a single HTTP delivery of a message.
415- async fn attempt_delivery (
416- client : & reqwest:: Client ,
417- config : & IdcActorConfig ,
418- msg : & PendingMessage ,
419- ) -> DeliveryOutcome {
420- let target_db_hex = hex:: encode ( msg. target_db_identity . to_byte_array ( ) ) ;
421- let sender_hex = hex:: encode ( config. sender_identity . to_byte_array ( ) ) ;
433+ async fn attempt_delivery ( client : & reqwest:: Client , config : & IdcActorConfig , msg : & PendingMessage ) -> DeliveryOutcome {
434+ let target_db_hex = msg. target_db_identity . to_hex ( ) ;
435+ let sender_hex = config. sender_identity . to_hex ( ) ;
422436
423437 let url = format ! (
424- "http://localhost:{IDC_HTTP_PORT }/v1/database/{target_db_hex}/call-from-database/{}?sender_identity={sender_hex}&msg_id={}" ,
425- msg. target_reducer, msg. msg_id,
438+ "http://localhost:{}/v1/database/{target_db_hex}/call-from-database/{}?sender_identity={sender_hex}&msg_id={}" ,
439+ config . http_port , msg. target_reducer, msg. msg_id,
426440 ) ;
427441
428442 let result = client
0 commit comments