use libp2p::{
request_response::{self, ProtocolSupport},
StreamProtocol,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use crate::message::ConsensusMessage;
pub const CONSENSUS_DIRECT_PROTOCOL: &str = "/tenzro/consensus-direct/1.0.0";
pub const MAX_INFLIGHT_REQUESTS_PER_PEER: usize = 64;
pub const MAX_INBOUND_STREAMS_PER_PEER: usize = 16;
pub const REQUEST_TIMEOUT: Duration = Duration::from_secs(5);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConsensusDirectRequest {
Message(ConsensusMessage),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ConsensusDirectResponse {
Ack,
Error(ConsensusDirectError),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
pub enum ConsensusDirectError {
#[error("server busy — exceeded {limit} concurrent inbound streams from this peer")]
ServerBusy { limit: usize },
#[error("no consensus subscriber attached")]
NoSubscriber,
#[error("server error: {0}")]
Internal(String),
}
pub type ConsensusDirectBehaviour =
request_response::cbor::Behaviour<ConsensusDirectRequest, ConsensusDirectResponse>;
pub fn new_behaviour() -> ConsensusDirectBehaviour {
let protocol = StreamProtocol::new(CONSENSUS_DIRECT_PROTOCOL);
let cfg = request_response::Config::default()
.with_request_timeout(REQUEST_TIMEOUT);
request_response::cbor::Behaviour::new(
std::iter::once((protocol, ProtocolSupport::Full)),
cfg,
)
}
#[cfg(test)]
mod tests {
use super::*;
use tenzro_types::primitives::{Address, Hash};
#[test]
fn response_round_trip() {
let cases = vec![
ConsensusDirectResponse::Ack,
ConsensusDirectResponse::Error(ConsensusDirectError::ServerBusy {
limit: MAX_INBOUND_STREAMS_PER_PEER,
}),
ConsensusDirectResponse::Error(ConsensusDirectError::NoSubscriber),
ConsensusDirectResponse::Error(ConsensusDirectError::Internal(
"downstream queue full".to_string(),
)),
];
for resp in cases {
let bytes = bincode::serialize(&resp).expect("encode");
let decoded: ConsensusDirectResponse =
bincode::deserialize(&bytes).expect("decode");
assert_eq!(format!("{:?}", resp), format!("{:?}", decoded));
}
}
#[test]
fn request_round_trip_timeout() {
let req = ConsensusDirectRequest::Message(ConsensusMessage::Timeout {
format_version: 1,
view: 42,
high_qc_view: 41,
finalized_height: 40,
voter: Address::from_bytes(&[0u8; 32]).unwrap(),
signature: vec![0xab; 64],
public_key: vec![0xcd; 32],
});
let bytes = bincode::serialize(&req).expect("encode");
let decoded: ConsensusDirectRequest =
bincode::deserialize(&bytes).expect("decode");
assert_eq!(format!("{:?}", req), format!("{:?}", decoded));
}
#[test]
fn request_round_trip_vote() {
let req = ConsensusDirectRequest::Message(ConsensusMessage::Vote {
block_hash: Hash::from_bytes(&[7u8; 32]).unwrap(),
voter: "0123456789abcdef".to_string(),
vote_type: crate::message::VoteType::Prevote,
round: 5,
height: 100,
high_qc_view: 4,
signature: vec![0u8; 96],
public_key: vec![1u8; 1952],
bls_signature: vec![0u8; 96],
});
let bytes = bincode::serialize(&req).expect("encode");
let decoded: ConsensusDirectRequest =
bincode::deserialize(&bytes).expect("decode");
assert_eq!(format!("{:?}", req), format!("{:?}", decoded));
}
#[test]
fn behaviour_constructs() {
let _ = new_behaviour();
}
}