Skip to content

Commit

Permalink
Merge pull request #51 from nyonson/add-v2-serde
Browse files Browse the repository at this point in the history
Add v2 serde
  • Loading branch information
nyonson authored May 17, 2024
2 parents eef9d68 + a032273 commit 4d6d9bc
Show file tree
Hide file tree
Showing 6 changed files with 483 additions and 201 deletions.
6 changes: 4 additions & 2 deletions protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ extern crate std;
mod chacha20poly1305;
mod fschacha20poly1305;
mod hkdf;
#[cfg(feature = "std")]
pub mod serde;

use core::fmt;

Expand Down Expand Up @@ -226,7 +228,7 @@ impl PacketReader {
/// # Arguments
///
/// - `ciphertext` - The message from the peer.
/// - `aad` - Optional authentication for the peer, currently only used for the first round of messages.
/// - `aad` - Optional authentication for the peer, currently only used for the first round of messages.
///
/// # Errors
///
Expand Down Expand Up @@ -370,7 +372,7 @@ impl PacketHandler {
}

/// Split the handler into separate reader and a writer.
pub fn split(self) -> (PacketReader, PacketWriter) {
pub fn into_split(self) -> (PacketReader, PacketWriter) {
(self.packet_reader, self.packet_writer)
}

Expand Down
315 changes: 315 additions & 0 deletions protocol/src/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

//! A subset of commands are represented with a single byte in V2 instead of the 12-byte ASCII encoding like V1.
//!
//! ID mappings defined in [BIP324](https://github.com/bitcoin/bips/blob/master/bip-0324.mediawiki#user-content-v2_Bitcoin_P2P_message_structure).
use core::fmt;
use std::io;

use alloc::vec::Vec;
use bitcoin::{
block,
consensus::{encode, Decodable, Encodable},
p2p::message::{CommandString, NetworkMessage},
VarInt,
};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Error {
Serialize,
Deserialize,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Serialize => write!(f, "Unable to serialize"),
Error::Deserialize => write!(f, "Unable to deserialize"),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Serialize => None,
Error::Deserialize => None,
}
}
}

/// Serialize message in v2 format to buffer.
pub fn serialize(msg: NetworkMessage) -> Result<Vec<u8>, Error> {
let mut buffer = Vec::new();
match &msg {
NetworkMessage::Addr(_) => {
buffer.push(1u8);
}
NetworkMessage::Inv(_) => {
buffer.push(14u8);
}
NetworkMessage::GetData(_) => {
buffer.push(11u8);
}
NetworkMessage::NotFound(_) => {
buffer.push(17u8);
}
NetworkMessage::GetBlocks(_) => {
buffer.push(9u8);
}
NetworkMessage::GetHeaders(_) => {
buffer.push(12u8);
}
NetworkMessage::MemPool => {
buffer.push(15u8);
}
NetworkMessage::Tx(_) => {
buffer.push(21u8);
}
NetworkMessage::Block(_) => {
buffer.push(2u8);
}
NetworkMessage::Headers(_) => {
buffer.push(13u8);
}
NetworkMessage::Ping(_) => {
buffer.push(18u8);
}
NetworkMessage::Pong(_) => {
buffer.push(19u8);
}
NetworkMessage::MerkleBlock(_) => {
buffer.push(16u8);
}
NetworkMessage::FilterLoad(_) => {
buffer.push(8u8);
}
NetworkMessage::FilterAdd(_) => {
buffer.push(6u8);
}
NetworkMessage::FilterClear => {
buffer.push(7u8);
}
NetworkMessage::GetCFilters(_) => {
buffer.push(22u8);
}
NetworkMessage::CFilter(_) => {
buffer.push(23u8);
}
NetworkMessage::GetCFHeaders(_) => {
buffer.push(24u8);
}
NetworkMessage::CFHeaders(_) => {
buffer.push(25u8);
}
NetworkMessage::GetCFCheckpt(_) => {
buffer.push(26u8);
}
NetworkMessage::CFCheckpt(_) => {
buffer.push(27u8);
}
NetworkMessage::SendCmpct(_) => {
buffer.push(20u8);
}
NetworkMessage::CmpctBlock(_) => {
buffer.push(4u8);
}
NetworkMessage::GetBlockTxn(_) => {
buffer.push(10u8);
}
NetworkMessage::BlockTxn(_) => {
buffer.push(3u8);
}
NetworkMessage::FeeFilter(_) => {
buffer.push(5u8);
}
NetworkMessage::AddrV2(_) => {
buffer.push(28u8);
}
// Messages which are not optimized and use the zero-byte + 12 following bytes to encode command in ascii.
NetworkMessage::Version(_)
| NetworkMessage::Verack
| NetworkMessage::SendHeaders
| NetworkMessage::GetAddr
| NetworkMessage::WtxidRelay
| NetworkMessage::SendAddrV2
| NetworkMessage::Alert(_)
| NetworkMessage::Reject(_) => {
buffer.push(0u8);
msg.command()
.consensus_encode(&mut buffer)
.map_err(|_| Error::Serialize)?;
}
NetworkMessage::Unknown {
command,
payload: _,
} => {
buffer.push(0u8);
command
.consensus_encode(&mut buffer)
.map_err(|_| Error::Serialize)?;
}
}

msg.consensus_encode(&mut buffer)
.map_err(|_| Error::Serialize)?;

Ok(buffer)
}

/// Deserialize v2 message into NetworkMessage.
pub fn deserialize(buffer: &[u8]) -> Result<NetworkMessage, Error> {
let short_id = buffer[0];
let mut payload_buffer = &buffer[1..];
match short_id {
// Zero-byte means the command is encoded in the next 12 bytes.
0u8 => {
// Next 12 bytes have encoded command.
let mut command_buffer = &buffer[1..13];
let command = CommandString::consensus_decode(&mut command_buffer)
.map_err(|_| Error::Deserialize)?;
// Rest of buffer is payload.
payload_buffer = &buffer[13..];
// There are a handful of "known" messages which don't use a short ID, otherwise Unknown.
match command.as_ref() {
"version" => Ok(NetworkMessage::Version(
Decodable::consensus_decode(&mut payload_buffer)
.map_err(|_| Error::Deserialize)?,
)),
"verack" => Ok(NetworkMessage::Verack),
"sendheaders" => Ok(NetworkMessage::SendHeaders),
"getaddr" => Ok(NetworkMessage::GetAddr),
"wtxidrelay" => Ok(NetworkMessage::WtxidRelay),
"sendaddrv2" => Ok(NetworkMessage::SendAddrV2),
"alert" => Ok(NetworkMessage::Alert(
Decodable::consensus_decode(&mut payload_buffer)
.map_err(|_| Error::Deserialize)?,
)),
"reject" => Ok(NetworkMessage::Reject(
Decodable::consensus_decode(&mut payload_buffer)
.map_err(|_| Error::Deserialize)?,
)),
_ => Ok(NetworkMessage::Unknown {
command,
payload: payload_buffer.to_vec(),
}),
}
}
// The following single byte IDs map to command short IDs.
1u8 => Ok(NetworkMessage::Addr(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
2u8 => Ok(NetworkMessage::Block(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
3u8 => Ok(NetworkMessage::BlockTxn(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
4u8 => Ok(NetworkMessage::CmpctBlock(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
5u8 => Ok(NetworkMessage::FeeFilter(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
6u8 => Ok(NetworkMessage::FilterAdd(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
7u8 => Ok(NetworkMessage::FilterClear),
8u8 => Ok(NetworkMessage::FilterLoad(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
9u8 => Ok(NetworkMessage::GetBlocks(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
10u8 => Ok(NetworkMessage::GetBlockTxn(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
11u8 => Ok(NetworkMessage::GetData(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
12u8 => Ok(NetworkMessage::GetHeaders(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
// This one gets a little weird and needs a bit of love in the future.
13u8 => Ok(NetworkMessage::Headers(
HeaderDeserializationWrapper::consensus_decode(&mut payload_buffer)
.map_err(|_| Error::Deserialize)?
.0,
)),
14u8 => Ok(NetworkMessage::Inv(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
15u8 => Ok(NetworkMessage::MemPool),
16u8 => Ok(NetworkMessage::MerkleBlock(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
17u8 => Ok(NetworkMessage::NotFound(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
18u8 => Ok(NetworkMessage::Ping(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
19u8 => Ok(NetworkMessage::Pong(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
20u8 => Ok(NetworkMessage::SendCmpct(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
21u8 => Ok(NetworkMessage::Tx(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
22u8 => Ok(NetworkMessage::GetCFilters(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
23u8 => Ok(NetworkMessage::CFilter(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
24u8 => Ok(NetworkMessage::GetCFHeaders(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
25u8 => Ok(NetworkMessage::CFHeaders(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
26u8 => Ok(NetworkMessage::GetCFCheckpt(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
27u8 => Ok(NetworkMessage::CFCheckpt(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),
28u8 => Ok(NetworkMessage::AddrV2(
Decodable::consensus_decode(&mut payload_buffer).map_err(|_| Error::Deserialize)?,
)),

// Unsupported short ID.
_ => Err(Error::Deserialize),
}
}

// Copied from rust-bitcoin internals.
//
// Only the deserialized side needs to be copied over since
// the serialize side is applied at the NetworkMessage level.
struct HeaderDeserializationWrapper(Vec<block::Header>);

impl Decodable for HeaderDeserializationWrapper {
#[inline]
fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(
r: &mut R,
) -> Result<Self, encode::Error> {
let len = VarInt::consensus_decode(r)?.0;
// should be above usual number of items to avoid
// allocation
let mut ret = Vec::with_capacity(core::cmp::min(1024 * 16, len as usize));
for _ in 0..len {
ret.push(Decodable::consensus_decode(r)?);
if u8::consensus_decode(r)? != 0u8 {
return Err(encode::Error::ParseFailed(
"Headers message should not contain transactions",
));
}
}
Ok(HeaderDeserializationWrapper(ret))
}
}
2 changes: 1 addition & 1 deletion proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ readme = "README.md"
rust-version = "1.56.1"

[dependencies]
bitcoin = { version = "0.31.2", default-features = false, features = ["no-std"] }
bitcoin = { version = "0.31.2" }
tokio = { version = "1.37.0", features = ["full"] }
bytes = "1.6.0"
hex = { package = "hex-conservative", version = "0.2.0" }
Expand Down
Loading

0 comments on commit 4d6d9bc

Please sign in to comment.