//! Request-response protocol for Synor. use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; use futures::prelude::*; use libp2p::request_response::{self, Codec, ProtocolSupport}; use libp2p::StreamProtocol; use std::io; use synor_types::{Block, BlockHeader, BlockId, Hash256, Transaction, TransactionId}; /// Protocol name for Synor request-response. pub const SYNOR_PROTOCOL: &str = "/synor/req/1.0.0"; /// Synor protocol identifier. #[derive(Clone, Debug)] pub struct SynorProtocol; impl AsRef for SynorProtocol { fn as_ref(&self) -> &str { SYNOR_PROTOCOL } } /// Request types for the Synor protocol. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub enum SynorRequest { /// Request block by hash. GetBlock(BlockId), /// Request multiple blocks by hash. GetBlocks(Vec), /// Request block header by hash. GetHeader(BlockId), /// Request multiple headers. GetHeaders { /// Starting block hash. start: BlockId, /// Maximum number of headers. max_count: u32, }, /// Request headers by blue score range. GetHeadersByBlueScore { /// Start blue score. start_blue_score: u64, /// End blue score (exclusive). end_blue_score: u64, }, /// Request transaction by ID. GetTransaction(TransactionId), /// Request multiple transactions. GetTransactions(Vec), /// Request UTXO data for an address. GetUtxos(Hash256), /// Request peer addresses for discovery. GetPeers, /// Request chain status. GetStatus, /// Request pruning point. GetPruningPoint, /// Request tips. GetTips, /// Relay addresses to peer. RelayAddresses(Vec), } impl SynorRequest { /// Returns the request type name. pub fn request_type(&self) -> &'static str { match self { SynorRequest::GetBlock(_) => "get_block", SynorRequest::GetBlocks(_) => "get_blocks", SynorRequest::GetHeader(_) => "get_header", SynorRequest::GetHeaders { .. } => "get_headers", SynorRequest::GetHeadersByBlueScore { .. } => "get_headers_by_score", SynorRequest::GetTransaction(_) => "get_transaction", SynorRequest::GetTransactions(_) => "get_transactions", SynorRequest::GetUtxos(_) => "get_utxos", SynorRequest::GetPeers => "get_peers", SynorRequest::GetStatus => "get_status", SynorRequest::GetPruningPoint => "get_pruning_point", SynorRequest::GetTips => "get_tips", SynorRequest::RelayAddresses(_) => "relay_addresses", } } /// Serializes to bytes. pub fn to_bytes(&self) -> Vec { borsh::to_vec(self).expect("Serialization should not fail") } /// Deserializes from bytes. pub fn from_bytes(bytes: &[u8]) -> io::Result { borsh::from_slice(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } } /// Response types for the Synor protocol. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub enum SynorResponse { /// Block response. Block(Option), /// Multiple blocks response. Blocks(Vec), /// Header response. Header(Option), /// Headers response. Headers(Vec), /// Transaction response. Transaction(Option), /// Multiple transactions response. Transactions(Vec), /// UTXOs response. Utxos(Vec), /// Peer addresses response. Peers(Vec), /// Status response. Status(ChainStatus), /// Pruning point response. PruningPoint(Option), /// Tips response. Tips(Vec), /// Acknowledgement (for relay messages). Ack, /// Error response. Error(String), } impl SynorResponse { /// Checks if this is an error response. pub fn is_error(&self) -> bool { matches!(self, SynorResponse::Error(_)) } /// Serializes to bytes. pub fn to_bytes(&self) -> Vec { borsh::to_vec(self).expect("Serialization should not fail") } /// Deserializes from bytes. pub fn from_bytes(bytes: &[u8]) -> io::Result { borsh::from_slice(bytes).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) } } /// UTXO information for responses. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct UtxoInfo { /// Transaction ID. pub txid: TransactionId, /// Output index. pub index: u32, /// Amount in sompi. pub amount: u64, /// Block DAA score when created. pub daa_score: u64, /// Whether this is from a coinbase. pub is_coinbase: bool, } /// Chain status information. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct ChainStatus { /// Genesis block hash. pub genesis_hash: Hash256, /// Number of tips. pub tip_count: u32, /// Highest blue score. pub best_blue_score: u64, /// Current DAA score. pub daa_score: u64, /// Pruning point hash. pub pruning_point: Option, /// Current difficulty bits. pub difficulty_bits: u32, /// Total blocks. pub total_blocks: u64, /// Median time past. pub median_time: u64, } /// Codec for the Synor protocol. #[derive(Clone, Default)] pub struct SynorCodec; #[async_trait] impl Codec for SynorCodec { type Protocol = StreamProtocol; type Request = SynorRequest; type Response = SynorResponse; async fn read_request(&mut self, _: &Self::Protocol, io: &mut T) -> io::Result where T: AsyncRead + Unpin + Send, { let mut len_buf = [0u8; 4]; io.read_exact(&mut len_buf).await?; let len = u32::from_be_bytes(len_buf) as usize; if len > crate::MAX_MESSAGE_SIZE { return Err(io::Error::new( io::ErrorKind::InvalidData, "Request too large", )); } let mut buf = vec![0u8; len]; io.read_exact(&mut buf).await?; SynorRequest::from_bytes(&buf) } async fn read_response( &mut self, _: &Self::Protocol, io: &mut T, ) -> io::Result where T: AsyncRead + Unpin + Send, { let mut len_buf = [0u8; 4]; io.read_exact(&mut len_buf).await?; let len = u32::from_be_bytes(len_buf) as usize; if len > crate::MAX_MESSAGE_SIZE { return Err(io::Error::new( io::ErrorKind::InvalidData, "Response too large", )); } let mut buf = vec![0u8; len]; io.read_exact(&mut buf).await?; SynorResponse::from_bytes(&buf) } async fn write_request( &mut self, _: &Self::Protocol, io: &mut T, req: Self::Request, ) -> io::Result<()> where T: AsyncWrite + Unpin + Send, { let buf = req.to_bytes(); let len = (buf.len() as u32).to_be_bytes(); io.write_all(&len).await?; io.write_all(&buf).await?; io.flush().await?; Ok(()) } async fn write_response( &mut self, _: &Self::Protocol, io: &mut T, res: Self::Response, ) -> io::Result<()> where T: AsyncWrite + Unpin + Send, { let buf = res.to_bytes(); let len = (buf.len() as u32).to_be_bytes(); io.write_all(&len).await?; io.write_all(&buf).await?; io.flush().await?; Ok(()) } } /// Creates a request-response behaviour for the Synor protocol. pub fn create_request_response_behaviour() -> request_response::Behaviour { request_response::Behaviour::new( [(StreamProtocol::new(SYNOR_PROTOCOL), ProtocolSupport::Full)], request_response::Config::default(), ) } #[cfg(test)] mod tests { use super::*; #[test] fn test_request_serialization() { let req = SynorRequest::GetBlock(BlockId::from_bytes([1u8; 32])); let bytes = req.to_bytes(); let decoded = SynorRequest::from_bytes(&bytes).unwrap(); match decoded { SynorRequest::GetBlock(id) => assert_eq!(id.as_bytes(), &[1u8; 32]), _ => panic!("Wrong request type"), } } #[test] fn test_response_serialization() { let resp = SynorResponse::Tips(vec![ BlockId::from_bytes([1u8; 32]), BlockId::from_bytes([2u8; 32]), ]); let bytes = resp.to_bytes(); let decoded = SynorResponse::from_bytes(&bytes).unwrap(); match decoded { SynorResponse::Tips(tips) => assert_eq!(tips.len(), 2), _ => panic!("Wrong response type"), } } #[test] fn test_chain_status() { let status = ChainStatus { genesis_hash: Hash256::from([0u8; 32]), tip_count: 3, best_blue_score: 1000, daa_score: 1000, pruning_point: None, difficulty_bits: 0x1d00ffff, total_blocks: 5000, median_time: 1700000000, }; let bytes = borsh::to_vec(&status).unwrap(); let decoded: ChainStatus = borsh::from_slice(&bytes).unwrap(); assert_eq!(decoded.best_blue_score, 1000); } }