use crate::consensus::paxos::{AcceptResponse, Promise, ProposalNumber, ProposalValue};
use crate::error::{Result, TdbError};
use crate::transaction::two_phase_commit::Vote;
use async_trait::async_trait;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[async_trait]
pub trait NetworkTransport: Send + Sync {
async fn send_prepare(&self, node_id: &str, payload: &[u8]) -> Result<Vec<u8>>;
async fn send_accept(&self, node_id: &str, payload: &[u8]) -> Result<Vec<u8>>;
async fn request_prepare_vote(&self, node_id: &str, payload: &[u8]) -> Result<Vec<u8>>;
async fn send_commit(&self, node_id: &str, payload: &[u8]) -> Result<()>;
async fn send_abort(&self, node_id: &str, payload: &[u8]) -> Result<()>;
async fn send_replication_changes(&self, node_id: &str, payload: &[u8]) -> Result<()>;
fn name(&self) -> &str;
}
pub struct LoopbackSimulationTransport {
simulated_latency: std::time::Duration,
calls_made: AtomicU64,
}
impl LoopbackSimulationTransport {
pub fn new() -> Self {
Self {
simulated_latency: std::time::Duration::from_millis(5),
calls_made: AtomicU64::new(0),
}
}
pub fn with_latency(latency: std::time::Duration) -> Self {
Self {
simulated_latency: latency,
calls_made: AtomicU64::new(0),
}
}
pub fn arc() -> Arc<dyn NetworkTransport> {
Arc::new(Self::new())
}
pub fn calls_made(&self) -> u64 {
self.calls_made.load(Ordering::Relaxed)
}
async fn simulate(&self) {
self.calls_made.fetch_add(1, Ordering::Relaxed);
if !self.simulated_latency.is_zero() {
tokio::time::sleep(self.simulated_latency).await;
}
}
}
impl Default for LoopbackSimulationTransport {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl NetworkTransport for LoopbackSimulationTransport {
async fn send_prepare(&self, node_id: &str, payload: &[u8]) -> Result<Vec<u8>> {
self.simulate().await;
let (proposal_number, _): (ProposalNumber, usize) =
oxicode::serde::decode_from_slice(payload, oxicode::config::standard())
.map_err(|e| TdbError::Deserialization(e.to_string()))?;
let promise = Promise {
acceptor_id: node_id.to_string(),
promised_number: proposal_number,
accepted_proposal: None,
};
oxicode::serde::encode_to_vec(&promise, oxicode::config::standard())
.map_err(|e| TdbError::Serialization(e.to_string()))
}
async fn send_accept(&self, node_id: &str, payload: &[u8]) -> Result<Vec<u8>> {
self.simulate().await;
let ((proposal_number, _value), _): ((ProposalNumber, ProposalValue), usize) =
oxicode::serde::decode_from_slice(payload, oxicode::config::standard())
.map_err(|e| TdbError::Deserialization(e.to_string()))?;
let response = AcceptResponse {
acceptor_id: node_id.to_string(),
accepted_number: proposal_number,
accepted: true,
};
oxicode::serde::encode_to_vec(&response, oxicode::config::standard())
.map_err(|e| TdbError::Serialization(e.to_string()))
}
async fn request_prepare_vote(&self, _node_id: &str, _payload: &[u8]) -> Result<Vec<u8>> {
self.simulate().await;
oxicode::serde::encode_to_vec(&Vote::Yes, oxicode::config::standard())
.map_err(|e| TdbError::Serialization(e.to_string()))
}
async fn send_commit(&self, _node_id: &str, _payload: &[u8]) -> Result<()> {
self.simulate().await;
Ok(())
}
async fn send_abort(&self, _node_id: &str, _payload: &[u8]) -> Result<()> {
self.simulate().await;
Ok(())
}
async fn send_replication_changes(&self, _node_id: &str, _payload: &[u8]) -> Result<()> {
self.simulate().await;
Ok(())
}
fn name(&self) -> &str {
"LoopbackSimulationTransport (single-node simulation, NOT real networking)"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn loopback_transport_acknowledges_and_counts_calls() {
let transport = LoopbackSimulationTransport::with_latency(std::time::Duration::ZERO);
let proposal_number = ProposalNumber::new(1, 42);
let prepare_payload =
oxicode::serde::encode_to_vec(&proposal_number, oxicode::config::standard()).unwrap();
let response = transport
.send_prepare("node-1", &prepare_payload)
.await
.unwrap();
let (promise, _): (Promise, usize) =
oxicode::serde::decode_from_slice(&response, oxicode::config::standard()).unwrap();
assert_eq!(promise.acceptor_id, "node-1");
assert_eq!(promise.promised_number, proposal_number);
let accept_payload = oxicode::serde::encode_to_vec(
&(proposal_number, ProposalValue::Noop),
oxicode::config::standard(),
)
.unwrap();
let response = transport
.send_accept("node-1", &accept_payload)
.await
.unwrap();
let (accept_response, _): (AcceptResponse, usize) =
oxicode::serde::decode_from_slice(&response, oxicode::config::standard()).unwrap();
assert!(accept_response.accepted);
let vote_response = transport
.request_prepare_vote("node-1", b"txn-001")
.await
.unwrap();
let (vote, _): (Vote, usize) =
oxicode::serde::decode_from_slice(&vote_response, oxicode::config::standard()).unwrap();
assert_eq!(vote, Vote::Yes);
transport.send_commit("node-1", b"txn-001").await.unwrap();
transport.send_abort("node-1", b"txn-001").await.unwrap();
transport
.send_replication_changes("node-1", b"changes")
.await
.unwrap();
assert_eq!(transport.calls_made(), 6);
assert!(transport.name().contains("simulation"));
}
}