372 lines
9.5 KiB
Rust
372 lines
9.5 KiB
Rust
//! Network message types for Synor.
|
|
|
|
use borsh::{BorshDeserialize, BorshSerialize};
|
|
use serde::{Deserialize, Serialize};
|
|
use synor_types::{BlockHeader, BlockId, Hash256, Transaction, TransactionId};
|
|
|
|
/// Message types for P2P communication.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
|
|
pub enum Message {
|
|
/// Block announcement.
|
|
BlockAnnouncement(BlockAnnouncement),
|
|
/// Transaction announcement.
|
|
TransactionAnnouncement(TransactionAnnouncement),
|
|
/// Header announcement.
|
|
HeaderAnnouncement(HeaderAnnouncement),
|
|
/// Ping message.
|
|
Ping(u64),
|
|
/// Pong response.
|
|
Pong(u64),
|
|
}
|
|
|
|
impl Message {
|
|
/// Returns the message type as a string.
|
|
pub fn message_type(&self) -> &'static str {
|
|
match self {
|
|
Message::BlockAnnouncement(_) => "block_announcement",
|
|
Message::TransactionAnnouncement(_) => "tx_announcement",
|
|
Message::HeaderAnnouncement(_) => "header_announcement",
|
|
Message::Ping(_) => "ping",
|
|
Message::Pong(_) => "pong",
|
|
}
|
|
}
|
|
|
|
/// Serializes the message to bytes.
|
|
pub fn to_bytes(&self) -> Result<Vec<u8>, borsh::io::Error> {
|
|
borsh::to_vec(self)
|
|
}
|
|
|
|
/// Deserializes a message from bytes.
|
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self, borsh::io::Error> {
|
|
borsh::from_slice(bytes)
|
|
}
|
|
}
|
|
|
|
/// Announcement of a new block.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
|
pub struct BlockAnnouncement {
|
|
/// Block hash.
|
|
pub hash: BlockId,
|
|
/// Block header.
|
|
pub header: BlockHeader,
|
|
/// Number of transactions in the block.
|
|
pub tx_count: u32,
|
|
/// Blue score of the block.
|
|
pub blue_score: u64,
|
|
}
|
|
|
|
impl BlockAnnouncement {
|
|
/// Creates a new block announcement.
|
|
pub fn new(header: BlockHeader, tx_count: u32, blue_score: u64) -> Self {
|
|
let hash = header.block_id();
|
|
BlockAnnouncement {
|
|
hash,
|
|
header,
|
|
tx_count,
|
|
blue_score,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Announcement of a new transaction.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
|
pub struct TransactionAnnouncement {
|
|
/// Transaction ID.
|
|
pub txid: TransactionId,
|
|
/// Transaction (optional, may be just the ID for inv messages).
|
|
pub transaction: Option<Transaction>,
|
|
/// Fee rate in sompi per byte.
|
|
pub fee_rate: Option<u64>,
|
|
}
|
|
|
|
impl TransactionAnnouncement {
|
|
/// Creates a new transaction announcement with full transaction.
|
|
pub fn with_transaction(tx: Transaction, fee_rate: u64) -> Self {
|
|
let txid = tx.txid();
|
|
TransactionAnnouncement {
|
|
txid,
|
|
transaction: Some(tx),
|
|
fee_rate: Some(fee_rate),
|
|
}
|
|
}
|
|
|
|
/// Creates a new transaction announcement with just the ID.
|
|
pub fn id_only(txid: TransactionId) -> Self {
|
|
TransactionAnnouncement {
|
|
txid,
|
|
transaction: None,
|
|
fee_rate: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Announcement of a new header.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
|
pub struct HeaderAnnouncement {
|
|
/// Block hash.
|
|
pub hash: BlockId,
|
|
/// Block header.
|
|
pub header: BlockHeader,
|
|
/// Blue score.
|
|
pub blue_score: u64,
|
|
/// DAA score.
|
|
pub daa_score: u64,
|
|
}
|
|
|
|
impl HeaderAnnouncement {
|
|
/// Creates a new header announcement.
|
|
pub fn new(header: BlockHeader, blue_score: u64, daa_score: u64) -> Self {
|
|
let hash = header.block_id();
|
|
HeaderAnnouncement {
|
|
hash,
|
|
header,
|
|
blue_score,
|
|
daa_score,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Inventory item types.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
|
pub enum InvType {
|
|
/// Block.
|
|
Block,
|
|
/// Transaction.
|
|
Transaction,
|
|
/// Block header.
|
|
Header,
|
|
}
|
|
|
|
/// Inventory item.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
|
|
pub struct InvItem {
|
|
/// Item type.
|
|
pub inv_type: InvType,
|
|
/// Item hash.
|
|
pub hash: Hash256,
|
|
}
|
|
|
|
impl InvItem {
|
|
/// Creates a block inventory item.
|
|
pub fn block(hash: Hash256) -> Self {
|
|
InvItem {
|
|
inv_type: InvType::Block,
|
|
hash,
|
|
}
|
|
}
|
|
|
|
/// Creates a transaction inventory item.
|
|
pub fn transaction(hash: Hash256) -> Self {
|
|
InvItem {
|
|
inv_type: InvType::Transaction,
|
|
hash,
|
|
}
|
|
}
|
|
|
|
/// Creates a header inventory item.
|
|
pub fn header(hash: Hash256) -> Self {
|
|
InvItem {
|
|
inv_type: InvType::Header,
|
|
hash,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Inventory message for announcing available data.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
|
|
pub struct Inventory {
|
|
/// List of inventory items.
|
|
pub items: Vec<InvItem>,
|
|
}
|
|
|
|
impl Inventory {
|
|
/// Creates a new inventory message.
|
|
pub fn new(items: Vec<InvItem>) -> Self {
|
|
Inventory { items }
|
|
}
|
|
|
|
/// Creates an empty inventory.
|
|
pub fn empty() -> Self {
|
|
Inventory { items: Vec::new() }
|
|
}
|
|
|
|
/// Adds an item to the inventory.
|
|
pub fn add(&mut self, item: InvItem) {
|
|
self.items.push(item);
|
|
}
|
|
|
|
/// Returns the number of items.
|
|
pub fn len(&self) -> usize {
|
|
self.items.len()
|
|
}
|
|
|
|
/// Returns true if empty.
|
|
pub fn is_empty(&self) -> bool {
|
|
self.items.is_empty()
|
|
}
|
|
}
|
|
|
|
/// Get data request message.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
|
|
pub struct GetData {
|
|
/// Requested items.
|
|
pub items: Vec<InvItem>,
|
|
}
|
|
|
|
impl GetData {
|
|
/// Creates a new get data request.
|
|
pub fn new(items: Vec<InvItem>) -> Self {
|
|
GetData { items }
|
|
}
|
|
|
|
/// Creates a request for blocks.
|
|
pub fn blocks(hashes: Vec<Hash256>) -> Self {
|
|
GetData {
|
|
items: hashes.into_iter().map(InvItem::block).collect(),
|
|
}
|
|
}
|
|
|
|
/// Creates a request for transactions.
|
|
pub fn transactions(hashes: Vec<Hash256>) -> Self {
|
|
GetData {
|
|
items: hashes.into_iter().map(InvItem::transaction).collect(),
|
|
}
|
|
}
|
|
|
|
/// Creates a request for headers.
|
|
pub fn headers(hashes: Vec<Hash256>) -> Self {
|
|
GetData {
|
|
items: hashes.into_iter().map(InvItem::header).collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get headers request.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
|
|
pub struct GetHeaders {
|
|
/// Start hash (get headers after this).
|
|
pub start_hash: Hash256,
|
|
/// Stop hash (optional, get up to this hash).
|
|
pub stop_hash: Option<Hash256>,
|
|
/// Maximum number of headers to return.
|
|
pub max_headers: u32,
|
|
}
|
|
|
|
impl GetHeaders {
|
|
/// Creates a new get headers request.
|
|
pub fn new(start_hash: Hash256, stop_hash: Option<Hash256>, max_headers: u32) -> Self {
|
|
GetHeaders {
|
|
start_hash,
|
|
stop_hash,
|
|
max_headers,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Headers response.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
|
|
pub struct Headers {
|
|
/// List of headers.
|
|
pub headers: Vec<BlockHeader>,
|
|
}
|
|
|
|
impl Headers {
|
|
/// Creates a new headers response.
|
|
pub fn new(headers: Vec<BlockHeader>) -> Self {
|
|
Headers { headers }
|
|
}
|
|
|
|
/// Creates an empty headers response.
|
|
pub fn empty() -> Self {
|
|
Headers {
|
|
headers: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Status message for handshake.
|
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
|
|
pub struct StatusMessage {
|
|
/// Protocol version.
|
|
pub version: u32,
|
|
/// Chain ID magic bytes.
|
|
pub chain_id: [u8; 4],
|
|
/// Genesis block hash.
|
|
pub genesis_hash: Hash256,
|
|
/// Best block hash (current tip).
|
|
pub best_hash: Hash256,
|
|
/// Best block blue score.
|
|
pub best_blue_score: u64,
|
|
/// Best block DAA score.
|
|
pub best_daa_score: u64,
|
|
/// Timestamp.
|
|
pub timestamp: u64,
|
|
/// User agent.
|
|
pub user_agent: String,
|
|
}
|
|
|
|
impl StatusMessage {
|
|
/// Protocol version constant.
|
|
pub const PROTOCOL_VERSION: u32 = 1;
|
|
|
|
/// Creates a new status message.
|
|
pub fn new(
|
|
chain_id: [u8; 4],
|
|
genesis_hash: Hash256,
|
|
best_hash: Hash256,
|
|
best_blue_score: u64,
|
|
best_daa_score: u64,
|
|
user_agent: String,
|
|
) -> Self {
|
|
StatusMessage {
|
|
version: Self::PROTOCOL_VERSION,
|
|
chain_id,
|
|
genesis_hash,
|
|
best_hash,
|
|
best_blue_score,
|
|
best_daa_score,
|
|
timestamp: chrono::Utc::now().timestamp() as u64,
|
|
user_agent,
|
|
}
|
|
}
|
|
|
|
/// Checks if this status is compatible with ours.
|
|
pub fn is_compatible(&self, our_chain_id: &[u8; 4], our_genesis: &Hash256) -> bool {
|
|
self.chain_id == *our_chain_id && self.genesis_hash == *our_genesis
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_message_serialization() {
|
|
let msg = Message::Ping(12345);
|
|
let bytes = msg.to_bytes().unwrap();
|
|
let decoded = Message::from_bytes(&bytes).unwrap();
|
|
|
|
match decoded {
|
|
Message::Ping(n) => assert_eq!(n, 12345),
|
|
_ => panic!("Wrong message type"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_inventory() {
|
|
let mut inv = Inventory::empty();
|
|
inv.add(InvItem::block(Hash256::from([1u8; 32])));
|
|
inv.add(InvItem::transaction(Hash256::from([2u8; 32])));
|
|
|
|
assert_eq!(inv.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_data() {
|
|
let hashes = vec![Hash256::from([1u8; 32]), Hash256::from([2u8; 32])];
|
|
let get_data = GetData::blocks(hashes);
|
|
|
|
assert_eq!(get_data.items.len(), 2);
|
|
assert_eq!(get_data.items[0].inv_type, InvType::Block);
|
|
}
|
|
}
|