use base64ct::{Base64, Encoding};
use cuckoofilter::ExportedCuckooFilter;
use ed25519_compact::PublicKey;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{serde_as, skip_serializing_none, DefaultOnNull, DeserializeAs, SerializeAs};
use crate::block::{Block, BlockHeader, BlockID};
use crate::error::{DataError, EncodingError};
use crate::transaction::{AsBase64, Transaction, TransactionID};
pub const PROTOCOL: &str = "cruzbit.1";
#[skip_serializing_none]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type", content = "body")]
pub enum Message {
Balance(BalanceMessage),
Balances(BalancesMessage),
BlockHeader(Option<BlockHeaderMessage>),
Block(Option<Box<BlockMessage>>),
FilterAdd(FilterAddMessage),
FilterBlockUndo(FilterBlockMessage),
FilterBlock(FilterBlockMessage),
FilterLoad(FilterLoadMessage),
FilterResult(Option<FilterResultMessage>),
FilterTransactionQueue(FilterTransactionQueueMessage),
FindCommonAncestor(FindCommonAncestorMessage),
GetBalance(GetBalanceMessage),
GetBalances(GetBalancesMessage),
GetBlockHeaderByHeight(GetBlockHeaderByHeightMessage),
GetBlockHeader(GetBlockHeaderMessage),
GetBlockByHeight(GetBlockByHeightMessage),
GetBlock(GetBlockMessage),
GetFilterTransactionQueue,
GetPeerAddresses,
GetPublicKeyTransactions(GetPublicKeyTransactionsMessage),
GetWork(GetWorkMessage),
GetTipHeader,
GetTransaction(GetTransactionMessage),
GetTransactionResult(PushTransactionResultMessage),
GetTransactionRelayPolicy,
InvBlock(InvBlockMessage),
PeerAddresses(PeerAddressesMessage),
PublicKeyTransactions(PublicKeyTransactionsMessage),
PushTransaction(PushTransactionMessage),
PushTransactionResult(PushTransactionResultMessage),
SubmitWork(SubmitWorkMessage),
SubmitWorkResult(SubmitWorkResultMessage),
TipHeader(Option<TipHeaderMessage>),
Transaction(TransactionMessage),
TransactionRelayPolicy(TransactionRelayPolicyMessage),
Work(WorkMessage),
}
impl std::fmt::Display for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", std::mem::discriminant(self))
}
}
#[derive(Deserialize, Serialize)]
pub struct InvBlockMessage {
pub block_ids: Vec<BlockID>,
}
#[derive(Deserialize, Serialize)]
pub struct GetBlockMessage {
pub block_id: BlockID,
}
#[derive(Serialize, Deserialize)]
pub struct GetBlockByHeightMessage {
pub height: u64,
}
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct BlockMessage {
pub block_id: BlockID,
pub block: Option<Block>,
}
#[derive(Deserialize, Serialize)]
pub struct GetBlockHeaderMessage {
pub block_id: BlockID,
}
#[derive(Deserialize, Serialize)]
pub struct GetBlockHeaderByHeightMessage {
pub height: u64,
}
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct BlockHeaderMessage {
pub block_id: BlockID,
#[serde(rename = "header")]
pub block_header: Option<BlockHeader>,
}
#[derive(Deserialize, Serialize)]
pub struct FindCommonAncestorMessage {
pub block_ids: Vec<BlockID>,
}
#[serde_as]
#[derive(Deserialize, Serialize)]
pub struct GetBalanceMessage {
#[serde_as(as = "PublicKeySerde")]
pub public_key: PublicKey,
}
#[serde_as]
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct BalanceMessage {
pub block_id: Option<BlockID>,
pub height: Option<u64>,
#[serde_as(as = "Option<PublicKeySerde>")]
pub public_key: Option<PublicKey>,
pub balance: Option<u64>,
pub error: Option<String>,
}
#[serde_as]
#[derive(Deserialize, Serialize)]
pub struct GetBalancesMessage {
#[serde_as(as = "Vec<PublicKeySerde>")]
pub public_keys: Vec<PublicKey>,
}
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct BalancesMessage {
pub block_id: Option<BlockID>,
pub height: Option<u64>,
pub balances: Option<Vec<PublicKeyBalance>>,
pub error: Option<String>,
}
#[serde_as]
#[derive(Deserialize, Serialize)]
pub struct PublicKeyBalance {
#[serde_as(as = "PublicKeySerde")]
pub public_key: PublicKey,
pub balance: u64,
}
#[derive(Deserialize, Serialize)]
pub struct GetTransactionMessage {
pub transaction_id: TransactionID,
}
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct TransactionMessage {
pub block_id: Option<BlockID>,
pub height: Option<u64>,
pub transaction_id: TransactionID,
pub transaction: Option<Transaction>,
}
#[derive(Deserialize, Serialize)]
pub struct TipHeaderMessage {
pub block_id: BlockID,
#[serde(rename = "header")]
pub block_header: BlockHeader,
pub time_seen: u64,
}
#[derive(Deserialize, Serialize)]
pub struct PushTransactionMessage {
pub transaction: Transaction,
}
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct PushTransactionResultMessage {
pub transaction_id: Option<TransactionID>,
pub error: Option<String>,
}
#[serde_as]
#[derive(Deserialize, Serialize)]
pub struct FilterLoadMessage {
pub r#type: String,
#[serde_as(as = "ExportedCuckooFilterSerde")]
pub filter: ExportedCuckooFilter,
}
#[serde_as]
#[derive(Deserialize, Serialize)]
pub struct FilterAddMessage {
#[serde_as(as = "Vec<PublicKeySerde>")]
pub public_keys: Vec<PublicKey>,
}
#[derive(Deserialize, Serialize)]
pub struct FilterResultMessage {
pub error: String,
}
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct FilterTransactionQueueMessage {
pub transactions: Option<Vec<Transaction>>,
pub error: Option<String>,
}
#[serde_as]
#[derive(Deserialize, Serialize)]
pub struct GetPublicKeyTransactionsMessage {
#[serde_as(as = "PublicKeySerde")]
pub public_key: PublicKey,
pub start_height: u64,
pub start_index: u32,
pub end_height: u64,
pub limit: usize,
}
#[serde_as]
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct PublicKeyTransactionsMessage {
#[serde_as(as = "Option<PublicKeySerde>")]
pub public_key: Option<PublicKey>,
pub start_height: Option<u64>,
pub stop_height: Option<u64>,
pub stop_index: Option<u32>,
#[serde_as(as = "DefaultOnNull")]
pub filter_blocks: Option<Vec<FilterBlockMessage>>,
pub error: Option<String>,
}
#[derive(Deserialize, Serialize)]
pub struct PeerAddressesMessage {
pub addresses: Vec<String>,
}
#[derive(Deserialize, Serialize)]
pub struct TransactionRelayPolicyMessage {
pub min_fee: u64,
pub min_amount: u64,
}
#[serde_as]
#[derive(Deserialize, Serialize)]
pub struct GetWorkMessage {
#[serde_as(as = "Vec<PublicKeySerde>")]
pub public_keys: Vec<PublicKey>,
pub memo: String,
}
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct WorkMessage {
pub work_id: Option<u32>,
pub header: Option<BlockHeader>,
pub min_time: Option<u64>,
pub error: Option<String>,
}
#[derive(Deserialize, Serialize)]
pub struct SubmitWorkMessage {
pub work_id: u32,
pub header: BlockHeader,
}
#[skip_serializing_none]
#[derive(Deserialize, Serialize)]
pub struct SubmitWorkResultMessage {
pub work_id: u32,
pub error: Option<String>,
}
#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct FilterBlockMessage {
pub block_id: BlockID,
pub header: BlockHeader,
#[serde_as(as = "DefaultOnNull")]
pub transactions: Vec<Transaction>,
}
pub struct PublicKeySerde;
impl SerializeAs<PublicKey> for PublicKeySerde {
fn serialize_as<S>(value: &PublicKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self::serialize_public_key(*value, serializer)
}
}
impl<'de> DeserializeAs<'de, PublicKey> for PublicKeySerde {
fn deserialize_as<D>(deserializer: D) -> Result<PublicKey, D::Error>
where
D: Deserializer<'de>,
{
self::deserialize_public_key(deserializer)
}
}
pub fn serialize_public_key<S>(pub_key: PublicKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&pub_key.as_base64())
}
pub fn deserialize_public_key<'de, D>(deserializer: D) -> Result<PublicKey, D::Error>
where
D: Deserializer<'de>,
{
let encoded: String = Deserialize::deserialize(deserializer)?;
let mut buf = [0u8; PublicKey::BYTES];
let decoded = Base64::decode(&encoded, &mut buf)
.map_err(EncodingError::Base64Decode)
.map_err(serde::de::Error::custom)?;
let pub_key = PublicKey::from_slice(decoded)
.map_err(DataError::Ed25519)
.map_err(serde::de::Error::custom)?;
Ok(pub_key)
}
pub struct ExportedCuckooFilterSerde;
impl SerializeAs<ExportedCuckooFilter> for ExportedCuckooFilterSerde {
fn serialize_as<S>(value: &ExportedCuckooFilter, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self::serialize_cuckoo_filter(value, serializer)
}
}
impl<'de> DeserializeAs<'de, ExportedCuckooFilter> for ExportedCuckooFilterSerde {
fn deserialize_as<D>(deserializer: D) -> Result<ExportedCuckooFilter, D::Error>
where
D: Deserializer<'de>,
{
self::deserialize_cuckoo_filter(deserializer)
}
}
pub fn serialize_cuckoo_filter<S>(
cuckoo_filter: &ExportedCuckooFilter,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&Base64::encode_string(&cuckoo_filter.values))
}
pub fn deserialize_cuckoo_filter<'de, D>(deserializer: D) -> Result<ExportedCuckooFilter, D::Error>
where
D: Deserializer<'de>,
{
let encoded: String = Deserialize::deserialize(deserializer)?;
let values = Base64::decode_vec(encoded.as_str())
.map_err(EncodingError::Base64Decode)
.map_err(serde::de::Error::custom)?;
let length = values.len();
Ok(ExportedCuckooFilter { values, length })
}
#[cfg(test)]
mod test {
use super::*;
use crate::block::test_utils::make_test_block;
#[test]
fn test_serialize_find_common_ancestor() {
let block = make_test_block(1);
let block_id = block.id().unwrap();
let block_ids = vec![block_id];
let message = FindCommonAncestorMessage { block_ids };
let serialized = serde_json::to_string(&message).unwrap();
let json = format!(r#"{{"block_ids":["{block_id}"]}}"#);
assert_eq!(serialized, json);
}
#[test]
fn test_deserialize_inv_block_message() {
let block = make_test_block(1);
let block_id = block.id().unwrap();
let block_ids = format!(r#"{{"block_ids":["{block_id}"]}}"#);
let inv = serde_json::from_str::<InvBlockMessage>(&block_ids).unwrap();
assert_eq!(inv.block_ids.len(), 1);
assert_eq!(block_id, inv.block_ids[0]);
}
#[test]
fn test_deserialize_block_message() {
let block = make_test_block(1);
let block_id = block.id().unwrap();
let block_json = serde_json::to_string(&block).unwrap();
let block_message_json = format!(r#"{{"block_id": "{block_id}", "block": {block_json} }}"#);
let block_message = serde_json::from_str::<BlockMessage>(&block_message_json).unwrap();
assert_eq!(block_id, block_message.block_id);
assert_eq!(
block.transactions[0].to,
block_message.block.unwrap().transactions[0].to
);
}
}