synor/crates/synor-network/src/protocol.rs
2026-01-08 05:22:24 +05:30

328 lines
9.2 KiB
Rust

//! 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<str> 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<BlockId>),
/// 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<TransactionId>),
/// 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<String>),
}
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<u8> {
borsh::to_vec(self).expect("Serialization should not fail")
}
/// Deserializes from bytes.
pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
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<Block>),
/// Multiple blocks response.
Blocks(Vec<Block>),
/// Header response.
Header(Option<BlockHeader>),
/// Headers response.
Headers(Vec<BlockHeader>),
/// Transaction response.
Transaction(Option<Transaction>),
/// Multiple transactions response.
Transactions(Vec<Transaction>),
/// UTXOs response.
Utxos(Vec<UtxoInfo>),
/// Peer addresses response.
Peers(Vec<String>),
/// Status response.
Status(ChainStatus),
/// Pruning point response.
PruningPoint(Option<BlockId>),
/// Tips response.
Tips(Vec<BlockId>),
/// 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<u8> {
borsh::to_vec(self).expect("Serialization should not fail")
}
/// Deserializes from bytes.
pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
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<BlockId>,
/// 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<T>(&mut self, _: &Self::Protocol, io: &mut T) -> io::Result<Self::Request>
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<T>(
&mut self,
_: &Self::Protocol,
io: &mut T,
) -> io::Result<Self::Response>
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<T>(
&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<T>(
&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<SynorCodec> {
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);
}
}