use std::collections::BTreeSet;
use std::time::Duration;
use nodedb_cluster::calvin::types::TxClass;
use nodedb_cluster::calvin::{AttemptOutcome, SEQUENCER_GROUP_ID, TxnId};
use nodedb_cluster::{
RaftRpc, SubmitCalvinInboxRequest, SubmitCalvinInboxResponse, SubmitCalvinTxnRequest,
SubmitCalvinTxnResponse,
};
use crate::Error;
use crate::bridge::envelope::Response;
use crate::control::server::exchange::resolve::register_peers_from_topology;
use crate::control::state::{CalvinApplyResult, SharedState};
fn synthetic_returning_response(payload_bytes: Vec<u8>) -> Response {
use crate::bridge::envelope::{Payload, Status};
use crate::types::{Lsn, RequestId};
Response {
request_id: RequestId::new(0),
status: Status::Ok,
attempt: 1,
partial: false,
payload: Payload::from_vec(payload_bytes),
watermark_lsn: Lsn::ZERO,
error_code: None,
read_set_valid: None,
read_version_lsn: crate::types::Lsn::ZERO,
write_set: Vec::new(),
}
}
pub async fn submit_and_await_calvin(
state: &SharedState,
tx_class: TxClass,
) -> crate::Result<Option<Response>> {
let timeout = Duration::from_secs(state.tuning.network.default_deadline_secs);
submit_and_await_calvin_with_timeout(state, tx_class, timeout).await
}
pub async fn submit_and_await_calvin_with_timeout(
state: &SharedState,
tx_class: TxClass,
timeout: Duration,
) -> crate::Result<Option<Response>> {
let inbox = state
.sequencer_inbox
.get()
.ok_or(Error::SequencerUnavailable)?;
let registry = state
.calvin_completion_registry
.get()
.ok_or(Error::SequencerUnavailable)?;
let inbox_seq = inbox.submit(tx_class).map_err(|e| Error::BadRequest {
detail: format!("Calvin sequencer rejected transaction: {e}"),
})?;
let assignment_rx = registry.register_submission(inbox_seq);
let (epoch, position, participants) = tokio::time::timeout(timeout, assignment_rx)
.await
.map_err(|_| Error::Internal {
detail: "timed out waiting for Calvin sequencer assignment".to_owned(),
})?
.map_err(|_| Error::Internal {
detail: "Calvin sequencer assignment channel closed".to_owned(),
})?;
let completion_rx = registry.register_completion(TxnId::new(epoch, position), participants);
let outcome = tokio::time::timeout(timeout, completion_rx)
.await
.map_err(|_| Error::Internal {
detail: "timed out waiting for Calvin transaction completion".to_owned(),
})?
.map_err(|_| Error::Internal {
detail: "Calvin completion channel closed".to_owned(),
})?;
if let AttemptOutcome::Failed { detail } = &outcome {
return Err(Error::Internal {
detail: format!("calvin transaction routing failed: {detail}"),
});
}
if outcome == AttemptOutcome::Aborted {
return Err(Error::CalvinSerializationConflict);
}
if outcome == AttemptOutcome::Mismatch {
return Err(Error::Internal {
detail: "OLLP mismatch outcome on non-dependent Calvin path".to_owned(),
});
}
let drained = state
.calvin_apply_results
.lock()
.unwrap_or_else(|p| p.into_inner())
.remove(&TxnId::new(epoch, position));
match drained {
Some(CalvinApplyResult::Single { response, .. }) => Ok(Some(response)),
Some(CalvinApplyResult::Conflict) => Err(Error::Internal {
detail: "multi-participant cross-shard RETURNING not supported".to_owned(),
}),
None => Ok(None),
}
}
const SEQUENCER_LEADER_WAIT_BACKOFF_MS: &[u64] = &[50, 100, 200, 400, 800, 1000, 1000, 1000];
pub async fn submit_calvin_routed(
state: &SharedState,
tx_class: TxClass,
) -> crate::Result<Option<Response>> {
let (Some(transport), Some(_routing)) = (
state.cluster_transport.as_ref(),
state.cluster_routing.as_ref(),
) else {
return submit_and_await_calvin(state, tx_class).await;
};
let status_fn = state.raft_status_fn.get().ok_or_else(|| Error::Internal {
detail: "calvin-submit: raft status fn not installed (cluster not started)".to_owned(),
})?;
let mut leader = 0;
for (attempt, &backoff_ms) in SEQUENCER_LEADER_WAIT_BACKOFF_MS.iter().enumerate() {
leader = status_fn()
.into_iter()
.find(|g| g.group_id == SEQUENCER_GROUP_ID)
.map(|g| g.leader_id)
.unwrap_or(0);
if leader != 0 {
break;
}
if attempt + 1 < SEQUENCER_LEADER_WAIT_BACKOFF_MS.len() {
tokio::time::sleep(std::time::Duration::from_millis(backoff_ms)).await;
}
}
if leader == 0 {
return Err(Error::Internal {
detail: "calvin-submit: no sequencer leader elected yet; cannot submit cross-shard \
transaction"
.to_owned(),
});
}
if leader == state.node_id {
return submit_and_await_calvin(state, tx_class).await;
}
let mut targets = BTreeSet::new();
targets.insert(leader);
register_peers_from_topology(state, transport, &targets);
let tx_class_bytes = zerompk::to_msgpack_vec(&tx_class).map_err(|e| Error::Serialization {
format: "msgpack".to_owned(),
detail: format!("failed to encode TxClass for routed Calvin submit: {e}"),
})?;
let deadline_remaining_ms = state
.tuning
.network
.default_deadline_secs
.saturating_mul(1000)
.max(1);
let req = SubmitCalvinTxnRequest {
tx_class_bytes,
deadline_remaining_ms,
trace_id: [0u8; 16],
};
let read_timeout = Duration::from_millis(deadline_remaining_ms.saturating_add(2_000));
match transport
.send_rpc_with_read_timeout(leader, RaftRpc::SubmitCalvinTxnRequest(req), read_timeout)
.await
{
Ok(RaftRpc::SubmitCalvinTxnResponse(SubmitCalvinTxnResponse {
error: None,
payload_bytes,
})) => {
Ok(payload_bytes.map(synthetic_returning_response))
}
Ok(RaftRpc::SubmitCalvinTxnResponse(SubmitCalvinTxnResponse {
error: Some(e), ..
})) => Err(Error::Internal {
detail: format!("calvin-submit failed on sequencer leader node {leader}: {e:?}"),
}),
Ok(other) => Err(Error::Internal {
detail: format!("calvin-submit: unexpected reply from node {leader}: {other:?}"),
}),
Err(e) => Err(Error::Internal {
detail: format!("calvin-submit RPC to sequencer leader node {leader} failed: {e}"),
}),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RoutedAssignment {
pub inbox_seq: u64,
pub epoch: u64,
pub position: u32,
pub participants: usize,
}
pub(crate) async fn submit_local_assign(
state: &SharedState,
tx_class: TxClass,
timeout: Duration,
) -> crate::Result<RoutedAssignment> {
let inbox = state
.sequencer_inbox
.get()
.ok_or(Error::SequencerUnavailable)?;
let registry = state
.calvin_completion_registry
.get()
.ok_or(Error::SequencerUnavailable)?;
let inbox_seq = inbox.submit(tx_class).map_err(|e| Error::BadRequest {
detail: format!("Calvin sequencer rejected transaction: {e}"),
})?;
let assignment_rx = registry.register_submission(inbox_seq);
let (epoch, position, participants) = tokio::time::timeout(timeout, assignment_rx)
.await
.map_err(|_| Error::Internal {
detail: "timed out waiting for Calvin sequencer assignment".to_owned(),
})?
.map_err(|_| Error::Internal {
detail: "Calvin sequencer assignment channel closed".to_owned(),
})?;
Ok(RoutedAssignment {
inbox_seq,
epoch,
position,
participants,
})
}
pub async fn submit_calvin_routed_assign(
state: &SharedState,
tx_class: TxClass,
) -> crate::Result<RoutedAssignment> {
let local_timeout = Duration::from_secs(state.tuning.network.default_deadline_secs);
let (Some(transport), Some(_routing)) = (
state.cluster_transport.as_ref(),
state.cluster_routing.as_ref(),
) else {
return submit_local_assign(state, tx_class, local_timeout).await;
};
let status_fn = state.raft_status_fn.get().ok_or_else(|| Error::Internal {
detail: "calvin-inbox: raft status fn not installed (cluster not started)".to_owned(),
})?;
let leader = status_fn()
.into_iter()
.find(|g| g.group_id == SEQUENCER_GROUP_ID)
.map(|g| g.leader_id)
.unwrap_or(0);
if leader == 0 {
return Err(Error::Internal {
detail: "calvin-inbox: no sequencer leader elected yet; cannot submit cross-shard \
transaction"
.to_owned(),
});
}
if leader == state.node_id {
return submit_local_assign(state, tx_class, local_timeout).await;
}
let mut targets = BTreeSet::new();
targets.insert(leader);
register_peers_from_topology(state, transport, &targets);
let tx_class_bytes = zerompk::to_msgpack_vec(&tx_class).map_err(|e| Error::Serialization {
format: "msgpack".to_owned(),
detail: format!("failed to encode TxClass for routed Calvin inbox submit: {e}"),
})?;
let deadline_remaining_ms = state
.tuning
.network
.default_deadline_secs
.saturating_mul(1000)
.max(1);
let req = SubmitCalvinInboxRequest {
tx_class_bytes,
deadline_remaining_ms,
trace_id: [0u8; 16],
};
let read_timeout = Duration::from_millis(deadline_remaining_ms.saturating_add(2_000));
match transport
.send_rpc_with_read_timeout(leader, RaftRpc::SubmitCalvinInboxRequest(req), read_timeout)
.await
{
Ok(RaftRpc::SubmitCalvinInboxResponse(SubmitCalvinInboxResponse {
inbox_seq,
epoch,
position,
participants,
error: None,
})) => Ok(RoutedAssignment {
inbox_seq,
epoch,
position,
participants: participants as usize,
}),
Ok(RaftRpc::SubmitCalvinInboxResponse(SubmitCalvinInboxResponse {
error: Some(e),
..
})) => Err(Error::Internal {
detail: format!("calvin-inbox failed on sequencer leader node {leader}: {e:?}"),
}),
Ok(other) => Err(Error::Internal {
detail: format!("calvin-inbox: unexpected reply from node {leader}: {other:?}"),
}),
Err(e) => Err(Error::Internal {
detail: format!("calvin-inbox RPC to sequencer leader node {leader} failed: {e}"),
}),
}
}