328 lines
9.2 KiB
Rust
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);
|
|
}
|
|
}
|