Merge rust-bitcoin/rust-bitcoin#3792: Add BIP324 V2 p2p network message support
e09bdb5f98
Add BIP324 V2 p2p network message support (Nick Johnson) Pull request description: Migrating over the BIP324's library's V2 network message encoding and decoding. As discussed over in https://github.com/rust-bitcoin/rust-bitcoin/discussions/2959, this is probably the natural home for it and also cleans up some gross copy/pasta of some of the encoding/decoding chain logic. This patch adds `V2NetworkMessage` which wraps a `NetworkMessage`, but handles the V2 encoding and decoding. It is a parallel of the existing `RawNetworkMessage` (which mentioned before, may be better described as `V1NetworkMessage` https://github.com/rust-bitcoin/rust-bitcoin/issues/3157). A priority of this patch was to not re-invent any wheels and try to use the existing patterns as much as possible. ACKs for top commit: tcharding: ACKe09bdb5f98
apoelstra: ACK e09bdb5f98ea516382a04283373ad97a41d57c2b; successfully ran local tests; nice! Tree-SHA512: a5078d4d3deb04c2e06ea513bbc8c97d0e6d5da5b029847a97b3f90bf55a263858dd16d88299f853aa3c468f7b9bceb3973c5652a49d3e96df3e91181b455f29
This commit is contained in:
commit
653ea702d6
|
@ -148,7 +148,7 @@ impl std::error::Error for CommandStringError {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Network message
|
/// A Network message using the v1 p2p protocol.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct RawNetworkMessage {
|
pub struct RawNetworkMessage {
|
||||||
magic: Magic,
|
magic: Magic,
|
||||||
|
@ -157,6 +157,12 @@ pub struct RawNetworkMessage {
|
||||||
checksum: [u8; 4],
|
checksum: [u8; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Network message using the v2 p2p protocol defined in BIP324.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct V2NetworkMessage {
|
||||||
|
payload: NetworkMessage,
|
||||||
|
}
|
||||||
|
|
||||||
/// A Network message payload. Proper documentation is available on at
|
/// A Network message payload. Proper documentation is available on at
|
||||||
/// [Bitcoin Wiki: Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification)
|
/// [Bitcoin Wiki: Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification)
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
@ -332,6 +338,27 @@ impl RawNetworkMessage {
|
||||||
pub fn command(&self) -> CommandString { self.payload.command() }
|
pub fn command(&self) -> CommandString { self.payload.command() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl V2NetworkMessage {
|
||||||
|
/// Constructs a new [V2NetworkMessage].
|
||||||
|
pub fn new(payload: NetworkMessage) -> Self { Self { payload } }
|
||||||
|
|
||||||
|
/// Consumes the [V2NetworkMessage] instance and returns the inner payload.
|
||||||
|
pub fn into_payload(self) -> NetworkMessage { self.payload }
|
||||||
|
|
||||||
|
/// The actual message data
|
||||||
|
pub fn payload(&self) -> &NetworkMessage { &self.payload }
|
||||||
|
|
||||||
|
/// Return the message command as a static string reference.
|
||||||
|
///
|
||||||
|
/// This returns `"unknown"` for [NetworkMessage::Unknown],
|
||||||
|
/// regardless of the actual command in the unknown message.
|
||||||
|
/// Use the [Self::command] method to get the command for unknown messages.
|
||||||
|
pub fn cmd(&self) -> &'static str { self.payload.cmd() }
|
||||||
|
|
||||||
|
/// Return the CommandString for the message command.
|
||||||
|
pub fn command(&self) -> CommandString { self.payload.command() }
|
||||||
|
}
|
||||||
|
|
||||||
struct HeaderSerializationWrapper<'a>(&'a Vec<block::Header>);
|
struct HeaderSerializationWrapper<'a>(&'a Vec<block::Header>);
|
||||||
|
|
||||||
impl Encodable for HeaderSerializationWrapper<'_> {
|
impl Encodable for HeaderSerializationWrapper<'_> {
|
||||||
|
@ -404,6 +431,62 @@ impl Encodable for RawNetworkMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Encodable for V2NetworkMessage {
|
||||||
|
fn consensus_encode<W: Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
||||||
|
// A subset of message types are optimized to only use one byte to encode the command.
|
||||||
|
// Non-optimized message types use the zero-byte flag and the following twelve bytes to encode the command.
|
||||||
|
let (command_byte, full_command) = match self.payload {
|
||||||
|
NetworkMessage::Addr(_) => (1u8, None),
|
||||||
|
NetworkMessage::Inv(_) => (14u8, None),
|
||||||
|
NetworkMessage::GetData(_) => (11u8, None),
|
||||||
|
NetworkMessage::NotFound(_) => (17u8, None),
|
||||||
|
NetworkMessage::GetBlocks(_) => (9u8, None),
|
||||||
|
NetworkMessage::GetHeaders(_) => (12u8, None),
|
||||||
|
NetworkMessage::MemPool => (15u8, None),
|
||||||
|
NetworkMessage::Tx(_) => (21u8, None),
|
||||||
|
NetworkMessage::Block(_) => (2u8, None),
|
||||||
|
NetworkMessage::Headers(_) => (13u8, None),
|
||||||
|
NetworkMessage::Ping(_) => (18u8, None),
|
||||||
|
NetworkMessage::Pong(_) => (19u8, None),
|
||||||
|
NetworkMessage::MerkleBlock(_) => (16u8, None),
|
||||||
|
NetworkMessage::FilterLoad(_) => (8u8, None),
|
||||||
|
NetworkMessage::FilterAdd(_) => (6u8, None),
|
||||||
|
NetworkMessage::FilterClear => (7u8, None),
|
||||||
|
NetworkMessage::GetCFilters(_) => (22u8, None),
|
||||||
|
NetworkMessage::CFilter(_) => (23u8, None),
|
||||||
|
NetworkMessage::GetCFHeaders(_) => (24u8, None),
|
||||||
|
NetworkMessage::CFHeaders(_) => (25u8, None),
|
||||||
|
NetworkMessage::GetCFCheckpt(_) => (26u8, None),
|
||||||
|
NetworkMessage::CFCheckpt(_) => (27u8, None),
|
||||||
|
NetworkMessage::SendCmpct(_) => (20u8, None),
|
||||||
|
NetworkMessage::CmpctBlock(_) => (4u8, None),
|
||||||
|
NetworkMessage::GetBlockTxn(_) => (10u8, None),
|
||||||
|
NetworkMessage::BlockTxn(_) => (3u8, None),
|
||||||
|
NetworkMessage::FeeFilter(_) => (5u8, None),
|
||||||
|
NetworkMessage::AddrV2(_) => (28u8, None),
|
||||||
|
NetworkMessage::Version(_)
|
||||||
|
| NetworkMessage::Verack
|
||||||
|
| NetworkMessage::SendHeaders
|
||||||
|
| NetworkMessage::GetAddr
|
||||||
|
| NetworkMessage::WtxidRelay
|
||||||
|
| NetworkMessage::SendAddrV2
|
||||||
|
| NetworkMessage::Alert(_)
|
||||||
|
| NetworkMessage::Reject(_)
|
||||||
|
| NetworkMessage::Unknown { .. } => (0u8, Some(self.payload.command())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut len = command_byte.consensus_encode(writer)?;
|
||||||
|
if let Some(cmd) = full_command {
|
||||||
|
len += cmd.consensus_encode(writer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the payload.
|
||||||
|
len += self.payload.consensus_encode(writer)?;
|
||||||
|
|
||||||
|
Ok(len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct HeaderDeserializationWrapper(Vec<block::Header>);
|
struct HeaderDeserializationWrapper(Vec<block::Header>);
|
||||||
|
|
||||||
impl Decodable for HeaderDeserializationWrapper {
|
impl Decodable for HeaderDeserializationWrapper {
|
||||||
|
@ -537,6 +620,79 @@ impl Decodable for RawNetworkMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decodable for V2NetworkMessage {
|
||||||
|
fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(
|
||||||
|
r: &mut R,
|
||||||
|
) -> Result<Self, encode::Error> {
|
||||||
|
let short_id: u8 = Decodable::consensus_decode_from_finite_reader(r)?;
|
||||||
|
let payload = match short_id {
|
||||||
|
0u8 => {
|
||||||
|
// Full command encoding.
|
||||||
|
let cmd = CommandString::consensus_decode_from_finite_reader(r)?;
|
||||||
|
match &cmd.0[..] {
|
||||||
|
"version" =>
|
||||||
|
NetworkMessage::Version(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
"verack" => NetworkMessage::Verack,
|
||||||
|
"sendheaders" => NetworkMessage::SendHeaders,
|
||||||
|
"getaddr" => NetworkMessage::GetAddr,
|
||||||
|
"wtxidrelay" => NetworkMessage::WtxidRelay,
|
||||||
|
"sendaddrv2" => NetworkMessage::SendAddrV2,
|
||||||
|
"alert" =>
|
||||||
|
NetworkMessage::Alert(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
"reject" =>
|
||||||
|
NetworkMessage::Reject(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
_ => NetworkMessage::Unknown {
|
||||||
|
command: cmd,
|
||||||
|
payload: Vec::consensus_decode_from_finite_reader(r)?,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1u8 => NetworkMessage::Addr(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
2u8 => NetworkMessage::Block(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
3u8 => NetworkMessage::BlockTxn(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
4u8 => NetworkMessage::CmpctBlock(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
5u8 => NetworkMessage::FeeFilter(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
6u8 => NetworkMessage::FilterAdd(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
7u8 => NetworkMessage::FilterClear,
|
||||||
|
8u8 => NetworkMessage::FilterLoad(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
9u8 => NetworkMessage::GetBlocks(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
10u8 => NetworkMessage::GetBlockTxn(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
11u8 => NetworkMessage::GetData(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
12u8 => NetworkMessage::GetHeaders(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
13u8 => NetworkMessage::Headers(
|
||||||
|
HeaderDeserializationWrapper::consensus_decode_from_finite_reader(r)?.0,
|
||||||
|
),
|
||||||
|
14u8 => NetworkMessage::Inv(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
15u8 => NetworkMessage::MemPool,
|
||||||
|
16u8 => NetworkMessage::MerkleBlock(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
17u8 => NetworkMessage::NotFound(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
18u8 => NetworkMessage::Ping(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
19u8 => NetworkMessage::Pong(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
20u8 => NetworkMessage::SendCmpct(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
21u8 => NetworkMessage::Tx(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
22u8 => NetworkMessage::GetCFilters(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
23u8 => NetworkMessage::CFilter(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
24u8 =>
|
||||||
|
NetworkMessage::GetCFHeaders(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
25u8 => NetworkMessage::CFHeaders(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
26u8 =>
|
||||||
|
NetworkMessage::GetCFCheckpt(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
27u8 => NetworkMessage::CFCheckpt(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
28u8 => NetworkMessage::AddrV2(Decodable::consensus_decode_from_finite_reader(r)?),
|
||||||
|
_ =>
|
||||||
|
return Err(encode::Error::Parse(encode::ParseError::ParseFailed(
|
||||||
|
"Unknown short ID",
|
||||||
|
))),
|
||||||
|
};
|
||||||
|
Ok(V2NetworkMessage { payload })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
|
||||||
|
Self::consensus_decode_from_finite_reader(&mut r.take(MAX_MSG_SIZE.to_u64()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
@ -669,9 +825,14 @@ mod test {
|
||||||
NetworkMessage::SendCmpct(SendCmpct { send_compact: true, version: 8333 }),
|
NetworkMessage::SendCmpct(SendCmpct { send_compact: true, version: 8333 }),
|
||||||
];
|
];
|
||||||
|
|
||||||
for msg in msgs {
|
for msg in &msgs {
|
||||||
let raw_msg = RawNetworkMessage::new(Magic::from_bytes([57, 0, 0, 0]), msg);
|
// V1 messages.
|
||||||
|
let raw_msg = RawNetworkMessage::new(Magic::from_bytes([57, 0, 0, 0]), msg.clone());
|
||||||
assert_eq!(deserialize::<RawNetworkMessage>(&serialize(&raw_msg)).unwrap(), raw_msg);
|
assert_eq!(deserialize::<RawNetworkMessage>(&serialize(&raw_msg)).unwrap(), raw_msg);
|
||||||
|
|
||||||
|
// V2 messages.
|
||||||
|
let v2_msg = V2NetworkMessage::new(msg.clone());
|
||||||
|
assert_eq!(deserialize::<V2NetworkMessage>(&serialize(&v2_msg)).unwrap(), v2_msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,6 +870,17 @@ mod test {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
|
0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_v2_verack() {
|
||||||
|
assert_eq!(
|
||||||
|
serialize(&V2NetworkMessage::new(NetworkMessage::Verack)),
|
||||||
|
[
|
||||||
|
0x00, // Full command encoding flag.
|
||||||
|
0x76, 0x65, 0x72, 0x61, 0x63, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn serialize_ping() {
|
fn serialize_ping() {
|
||||||
|
@ -719,6 +891,17 @@ mod test {
|
||||||
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_v2_ping() {
|
||||||
|
assert_eq!(
|
||||||
|
serialize(&V2NetworkMessage::new(NetworkMessage::Ping(100))),
|
||||||
|
[
|
||||||
|
0x12, // Ping command short ID
|
||||||
|
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn serialize_mempool() {
|
fn serialize_mempool() {
|
||||||
|
@ -728,6 +911,16 @@ mod test {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
|
0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_v2_mempool() {
|
||||||
|
assert_eq!(
|
||||||
|
serialize(&V2NetworkMessage::new(NetworkMessage::MemPool)),
|
||||||
|
[
|
||||||
|
0x0F, // MemPool command short ID
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn serialize_getaddr() {
|
fn serialize_getaddr() {
|
||||||
|
@ -737,6 +930,17 @@ mod test {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
|
0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_v2_getaddr() {
|
||||||
|
assert_eq!(
|
||||||
|
serialize(&V2NetworkMessage::new(NetworkMessage::GetAddr)),
|
||||||
|
[
|
||||||
|
0x00, // Full command encoding flag.
|
||||||
|
0x67, 0x65, 0x74, 0x61, 0x64, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deserialize_getaddr() {
|
fn deserialize_getaddr() {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -752,6 +956,19 @@ mod test {
|
||||||
assert_eq!(preimage.payload, msg.payload);
|
assert_eq!(preimage.payload, msg.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_v2_getaddr() {
|
||||||
|
let msg = deserialize(&[
|
||||||
|
0x00, // Full command encoding flag
|
||||||
|
0x67, 0x65, 0x74, 0x61, 0x64, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
]);
|
||||||
|
|
||||||
|
let preimage = V2NetworkMessage::new(NetworkMessage::GetAddr);
|
||||||
|
assert!(msg.is_ok());
|
||||||
|
let msg: V2NetworkMessage = msg.unwrap();
|
||||||
|
assert_eq!(preimage, msg);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deserialize_version() {
|
fn deserialize_version() {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -796,6 +1013,44 @@ mod test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_v2_version() {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let msg = deserialize::<V2NetworkMessage>(&[
|
||||||
|
0x00, // Full command encoding flag
|
||||||
|
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, // "version" command
|
||||||
|
0x7f, 0x11, 0x01, 0x00, // version: 70015
|
||||||
|
0x0d, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // services
|
||||||
|
0xf0, 0x0f, 0x4d, 0x5c, 0x00, 0x00, 0x00, 0x00, // timestamp: 1548554224
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // receiver services: NONE
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5b, 0xf0, 0x8c, 0x80, 0xb4, 0xbd, // addr_recv
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sender services: NONE
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // addr_from
|
||||||
|
0xfa, 0xa9, 0x95, 0x59, 0xcc, 0x68, 0xa1, 0xc1, // nonce
|
||||||
|
0x10, 0x2f, 0x53, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x3a, 0x30, 0x2e, 0x31, 0x37, 0x2e, 0x31, 0x2f, // user_agent: "/Satoshi:0.17.1/"
|
||||||
|
0x93, 0x8c, 0x08, 0x00, // start_height: 560275
|
||||||
|
0x01 // relay: true
|
||||||
|
]).unwrap();
|
||||||
|
|
||||||
|
if let NetworkMessage::Version(version_msg) = msg.payload {
|
||||||
|
assert_eq!(version_msg.version, 70015);
|
||||||
|
assert_eq!(
|
||||||
|
version_msg.services,
|
||||||
|
ServiceFlags::NETWORK
|
||||||
|
| ServiceFlags::BLOOM
|
||||||
|
| ServiceFlags::WITNESS
|
||||||
|
| ServiceFlags::NETWORK_LIMITED
|
||||||
|
);
|
||||||
|
assert_eq!(version_msg.timestamp, 1548554224);
|
||||||
|
assert_eq!(version_msg.nonce, 13952548347456104954);
|
||||||
|
assert_eq!(version_msg.user_agent, "/Satoshi:0.17.1/");
|
||||||
|
assert_eq!(version_msg.start_height, 560275);
|
||||||
|
assert!(version_msg.relay);
|
||||||
|
} else {
|
||||||
|
panic!("wrong message type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deserialize_partial_message() {
|
fn deserialize_partial_message() {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
Loading…
Reference in New Issue