X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fmsgs.rs;h=05a336c18eff5634e0fee7f2cec8b86a66e02a89;hb=28c9b56113ff1ebb1b505a2c979c55c1626aa06b;hp=7295c40d5442dd87639d08bbe6a6a3b2c1d5a873;hpb=99073c74ddce0d7b7a09eca1a4d938704309eb19;p=rust-lightning diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 7295c40d..05a336c1 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -24,13 +24,15 @@ //! raw socket events into your non-internet-facing system and then send routing events back to //! track the network on the less-secure system. -use bitcoin::secp256k1::key::PublicKey; -use bitcoin::secp256k1::Signature; +use bitcoin::secp256k1::PublicKey; +use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1; use bitcoin::blockdata::script::Script; use bitcoin::hash_types::{Txid, BlockHash}; use ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; +use ln::onion_utils; +use onion_message; use prelude::*; use core::fmt; @@ -40,7 +42,7 @@ use io_extras::read_to_end; use util::events::MessageSendEventsProvider; use util::logger; -use util::ser::{Readable, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt}; +use util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname}; use ln::{PaymentPreimage, PaymentHash, PaymentSecret}; @@ -75,6 +77,11 @@ pub enum DecodeError { pub struct Init { /// The relevant features which the sender supports pub features: InitFeatures, + /// The receipient's network address. This adds the option to report a remote IP address + /// back to a connecting peer using the init message. A node can decide to use that information + /// to discover a potential update to its public IPv4 address (NAT) and use + /// that for a node_announcement update message containing the new address. + pub remote_network_address: Option, } /// An error message to be sent or received from a peer @@ -234,13 +241,16 @@ pub struct FundingSigned { pub signature: Signature, } -/// A funding_locked message to be sent or received from a peer +/// A channel_ready message to be sent or received from a peer #[derive(Clone, Debug, PartialEq)] -pub struct FundingLocked { +pub struct ChannelReady { /// The channel ID pub channel_id: [u8; 32], /// The per-commitment point of the second commitment transaction pub next_per_commitment_point: PublicKey, + /// If set, provides a short_channel_id alias for this channel. The sender will accept payments + /// to be forwarded over this SCID and forward them to this messages' recipient. + pub short_channel_id_alias: Option, } /// A shutdown message to be sent or received from a peer @@ -296,6 +306,14 @@ pub struct UpdateAddHTLC { pub(crate) onion_routing_packet: OnionPacket, } + /// An onion message to be sent or received from a peer +#[derive(Clone, Debug, PartialEq)] +pub struct OnionMessage { + /// Used in decrypting the onion packet's payload. + pub blinding_point: PublicKey, + pub(crate) onion_routing_packet: onion_message::Packet, +} + /// An update_fulfill_htlc message to be sent or received from a peer #[derive(Clone, Debug, PartialEq)] pub struct UpdateFulfillHTLC { @@ -434,6 +452,13 @@ pub enum NetAddress { /// The port on which the node is listening port: u16, }, + /// A hostname/port on which the peer is listening. + Hostname { + /// The hostname on which the node is listening. + hostname: Hostname, + /// The port on which the node is listening. + port: u16, + }, } impl NetAddress { /// Gets the ID of this address type. Addresses in node_announcement messages should be sorted @@ -444,6 +469,7 @@ impl NetAddress { &NetAddress::IPv6 {..} => { 2 }, &NetAddress::OnionV2(_) => { 3 }, &NetAddress::OnionV3 {..} => { 4 }, + &NetAddress::Hostname {..} => { 5 }, } } @@ -454,11 +480,15 @@ impl NetAddress { &NetAddress::IPv6 { .. } => { 18 }, &NetAddress::OnionV2(_) => { 12 }, &NetAddress::OnionV3 { .. } => { 37 }, + // Consists of 1-byte hostname length, hostname bytes, and 2-byte port. + &NetAddress::Hostname { ref hostname, .. } => { u16::from(hostname.len()) + 3 }, } } - /// The maximum length of any address descriptor, not including the 1-byte type - pub(crate) const MAX_LEN: u16 = 37; + /// The maximum length of any address descriptor, not including the 1-byte type. + /// This maximum length is reached by a hostname address descriptor: + /// a hostname with a maximum length of 255, its 1-byte length and a 2-byte port. + pub(crate) const MAX_LEN: u16 = 258; } impl Writeable for NetAddress { @@ -484,7 +514,12 @@ impl Writeable for NetAddress { checksum.write(writer)?; version.write(writer)?; port.write(writer)?; - } + }, + &NetAddress::Hostname { ref hostname, ref port } => { + 5u8.write(writer)?; + hostname.write(writer)?; + port.write(writer)?; + }, } Ok(()) } @@ -515,6 +550,12 @@ impl Readable for Result { port: Readable::read(reader)?, })) }, + 5 => { + Ok(Ok(NetAddress::Hostname { + hostname: Readable::read(reader)?, + port: Readable::read(reader)?, + })) + }, _ => return Ok(Err(byte)), } } @@ -616,13 +657,16 @@ pub struct UnsignedChannelUpdate { pub cltv_expiry_delta: u16, /// The minimum HTLC size incoming to sender, in milli-satoshi pub htlc_minimum_msat: u64, - /// Optionally, the maximum HTLC value incoming to sender, in milli-satoshi - pub htlc_maximum_msat: OptionalField, + /// The maximum HTLC value incoming to sender, in milli-satoshi. Used to be optional. + pub htlc_maximum_msat: u64, /// The base HTLC fee charged by sender, in milli-satoshi pub fee_base_msat: u32, /// The amount to fee multiplier, in micro-satoshi pub fee_proportional_millionths: u32, - pub(crate) excess_data: Vec, + /// Excess data which was signed as a part of the message which we do not (yet) understand how + /// to decode. This is stored to ensure forward-compatibility as new fields are added to the + /// lightning gossip + pub excess_data: Vec, } /// A channel_update message to be sent or received from a peer #[derive(Clone, Debug, PartialEq)] @@ -804,8 +848,8 @@ pub trait ChannelMessageHandler : MessageSendEventsProvider { fn handle_funding_created(&self, their_node_id: &PublicKey, msg: &FundingCreated); /// Handle an incoming funding_signed message from the given peer. fn handle_funding_signed(&self, their_node_id: &PublicKey, msg: &FundingSigned); - /// Handle an incoming funding_locked message from the given peer. - fn handle_funding_locked(&self, their_node_id: &PublicKey, msg: &FundingLocked); + /// Handle an incoming channel_ready message from the given peer. + fn handle_channel_ready(&self, their_node_id: &PublicKey, msg: &ChannelReady); // Channl close: /// Handle an incoming shutdown message from the given peer. @@ -883,7 +927,7 @@ pub trait RoutingMessageHandler : MessageSendEventsProvider { /// Called when a connection is established with a peer. This can be used to /// perform routing table synchronization using a strategy defined by the /// implementor. - fn sync_routing_table(&self, their_node_id: &PublicKey, init: &Init); + fn peer_connected(&self, their_node_id: &PublicKey, init: &Init); /// Handles the reply of a query we initiated to learn about channels /// for a given range of blocks. We can expect to receive one or more /// replies to a single query. @@ -959,6 +1003,18 @@ pub(crate) struct OnionPacket { pub(crate) hmac: [u8; 32], } +impl onion_utils::Packet for OnionPacket { + type Data = onion_utils::FixedSizeOnionPacket; + fn new(pubkey: PublicKey, hop_data: onion_utils::FixedSizeOnionPacket, hmac: [u8; 32]) -> Self { + Self { + version: 0, + public_key: Ok(pubkey), + hop_data: hop_data.0, + hmac, + } + } +} + impl PartialEq for OnionPacket { fn eq(&self, other: &OnionPacket) -> bool { for (i, j) in self.hop_data.iter().zip(other.hop_data.iter()) { @@ -1152,17 +1208,23 @@ impl_writeable_msg!(FundingSigned, { signature }, {}); -impl_writeable_msg!(FundingLocked, { +impl_writeable_msg!(ChannelReady, { channel_id, next_per_commitment_point, -}, {}); +}, { + (1, short_channel_id_alias, option), +}); impl Writeable for Init { fn write(&self, w: &mut W) -> Result<(), io::Error> { // global_features gets the bottom 13 bits of our features, and local_features gets all of // our relevant feature bits. This keeps us compatible with old nodes. self.features.write_up_to_13(w)?; - self.features.write(w) + self.features.write(w)?; + encode_tlv_stream!(w, { + (3, self.remote_network_address, option) + }); + Ok(()) } } @@ -1170,8 +1232,13 @@ impl Readable for Init { fn read(r: &mut R) -> Result { let global_features: InitFeatures = Readable::read(r)?; let features: InitFeatures = Readable::read(r)?; + let mut remote_network_address: Option = None; + decode_tlv_stream!(r, { + (3, remote_network_address, option) + }); Ok(Init { features: features.or(global_features), + remote_network_address, }) } } @@ -1282,6 +1349,29 @@ impl_writeable_msg!(UpdateAddHTLC, { onion_routing_packet }, {}); +impl Readable for OnionMessage { + fn read(r: &mut R) -> Result { + let blinding_point: PublicKey = Readable::read(r)?; + let len: u16 = Readable::read(r)?; + let mut packet_reader = FixedLengthReader::new(r, len as u64); + let onion_routing_packet: onion_message::Packet = ::read(&mut packet_reader)?; + Ok(Self { + blinding_point, + onion_routing_packet, + }) + } +} + +impl Writeable for OnionMessage { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.blinding_point.write(w)?; + let onion_packet_len = self.onion_routing_packet.serialized_length(); + (onion_packet_len as u16).write(w)?; + self.onion_routing_packet.write(w)?; + Ok(()) + } +} + impl Writeable for FinalOnionHopData { fn write(&self, w: &mut W) -> Result<(), io::Error> { self.payment_secret.0.write(w)?; @@ -1327,6 +1417,14 @@ impl Writeable for OnionHopData { } } +// ReadableArgs because we need onion_utils::decode_next_hop to accommodate payment packets and +// onion message packets. +impl ReadableArgs<()> for OnionHopData { + fn read(r: &mut R, _arg: ()) -> Result { + ::read(r) + } +} + impl Readable for OnionHopData { fn read(mut r: &mut R) -> Result { use bitcoin::consensus::encode::{Decodable, Error, VarInt}; @@ -1469,14 +1567,12 @@ impl_writeable!(ChannelAnnouncement, { impl Writeable for UnsignedChannelUpdate { fn write(&self, w: &mut W) -> Result<(), io::Error> { - let mut message_flags: u8 = 0; - if let OptionalField::Present(_) = self.htlc_maximum_msat { - message_flags = 1; - } + // `message_flags` used to indicate presence of `htlc_maximum_msat`, but was deprecated in the spec. + const MESSAGE_FLAGS: u8 = 1; self.chain_hash.write(w)?; self.short_channel_id.write(w)?; self.timestamp.write(w)?; - let all_flags = self.flags as u16 | ((message_flags as u16) << 8); + let all_flags = self.flags as u16 | ((MESSAGE_FLAGS as u16) << 8); all_flags.write(w)?; self.cltv_expiry_delta.write(w)?; self.htlc_minimum_msat.write(w)?; @@ -1490,22 +1586,20 @@ impl Writeable for UnsignedChannelUpdate { impl Readable for UnsignedChannelUpdate { fn read(r: &mut R) -> Result { - let has_htlc_maximum_msat; Ok(Self { chain_hash: Readable::read(r)?, short_channel_id: Readable::read(r)?, timestamp: Readable::read(r)?, flags: { let flags: u16 = Readable::read(r)?; - let message_flags = flags >> 8; - has_htlc_maximum_msat = (message_flags as i32 & 1) == 1; + // Note: we ignore the `message_flags` for now, since it was deprecated by the spec. flags as u8 }, cltv_expiry_delta: Readable::read(r)?, htlc_minimum_msat: Readable::read(r)?, fee_base_msat: Readable::read(r)?, fee_proportional_millionths: Readable::read(r)?, - htlc_maximum_msat: if has_htlc_maximum_msat { Readable::read(r)? } else { OptionalField::Absent }, + htlc_maximum_msat: Readable::read(r)?, excess_data: read_to_end(r)?, }) } @@ -1807,7 +1901,7 @@ mod tests { use ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; use ln::msgs; use ln::msgs::{FinalOnionHopData, OptionalField, OnionErrorPacket, OnionHopDataFormat}; - use util::ser::{Writeable, Readable}; + use util::ser::{Writeable, Readable, Hostname}; use bitcoin::hashes::hex::FromHex; use bitcoin::util::address::Address; @@ -1816,11 +1910,12 @@ mod tests { use bitcoin::blockdata::opcodes; use bitcoin::hash_types::{Txid, BlockHash}; - use bitcoin::secp256k1::key::{PublicKey,SecretKey}; + use bitcoin::secp256k1::{PublicKey,SecretKey}; use bitcoin::secp256k1::{Secp256k1, Message}; use io::Cursor; use prelude::*; + use core::convert::TryFrom; #[test] fn encoding_channel_reestablish_no_secret() { @@ -1873,7 +1968,7 @@ mod tests { ($privkey: expr, $ctx: expr, $string: expr) => { { let sighash = Message::from_slice(&$string.into_bytes()[..]).unwrap(); - $ctx.sign(&sighash, &$privkey) + $ctx.sign_ecdsa(&sighash, &$privkey) } } } @@ -1949,7 +2044,7 @@ mod tests { do_encoding_channel_announcement(true, true); } - fn do_encoding_node_announcement(unknown_features_bits: bool, ipv4: bool, ipv6: bool, onionv2: bool, onionv3: bool, excess_address_data: bool, excess_data: bool) { + fn do_encoding_node_announcement(unknown_features_bits: bool, ipv4: bool, ipv6: bool, onionv2: bool, onionv3: bool, hostname: bool, excess_address_data: bool, excess_data: bool) { let secp_ctx = Secp256k1::new(); let (privkey_1, pubkey_1) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx); let sig_1 = get_sig_on!(privkey_1, secp_ctx, String::from("01010101010101010101010101010101")); @@ -1985,6 +2080,12 @@ mod tests { port: 9735 }); } + if hostname { + addresses.push(msgs::NetAddress::Hostname { + hostname: Hostname::try_from(String::from("host")).unwrap(), + port: 9735, + }); + } let mut addr_len = 0; for addr in &addresses { addr_len += addr.len() + 1; @@ -2025,6 +2126,9 @@ mod tests { if onionv3 { target_value.append(&mut hex::decode("04fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e00020102607").unwrap()); } + if hostname { + target_value.append(&mut hex::decode("0504686f73742607").unwrap()); + } if excess_address_data { target_value.append(&mut hex::decode("216c280b5395a2546e7e4b2663e04f811622f15a4f92e83aa2e92ba2a573c139142c54ae63072a1ec1ee7dc0c04bde5c847806172aa05c92c22ae8e308d1d269").unwrap()); } @@ -2036,18 +2140,19 @@ mod tests { #[test] fn encoding_node_announcement() { - do_encoding_node_announcement(true, true, true, true, true, true, true); - do_encoding_node_announcement(false, false, false, false, false, false, false); - do_encoding_node_announcement(false, true, false, false, false, false, false); - do_encoding_node_announcement(false, false, true, false, false, false, false); - do_encoding_node_announcement(false, false, false, true, false, false, false); - do_encoding_node_announcement(false, false, false, false, true, false, false); - do_encoding_node_announcement(false, false, false, false, false, true, false); - do_encoding_node_announcement(false, true, false, true, false, true, false); - do_encoding_node_announcement(false, false, true, false, true, false, false); - } - - fn do_encoding_channel_update(direction: bool, disable: bool, htlc_maximum_msat: bool, excess_data: bool) { + do_encoding_node_announcement(true, true, true, true, true, true, true, true); + do_encoding_node_announcement(false, false, false, false, false, false, false, false); + do_encoding_node_announcement(false, true, false, false, false, false, false, false); + do_encoding_node_announcement(false, false, true, false, false, false, false, false); + do_encoding_node_announcement(false, false, false, true, false, false, false, false); + do_encoding_node_announcement(false, false, false, false, true, false, false, false); + do_encoding_node_announcement(false, false, false, false, false, true, false, false); + do_encoding_node_announcement(false, false, false, false, false, false, true, false); + do_encoding_node_announcement(false, true, false, true, false, false, true, false); + do_encoding_node_announcement(false, false, true, false, true, false, false, false); + } + + fn do_encoding_channel_update(direction: bool, disable: bool, excess_data: bool) { let secp_ctx = Secp256k1::new(); let (privkey_1, _) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx); let sig_1 = get_sig_on!(privkey_1, secp_ctx, String::from("01010101010101010101010101010101")); @@ -2058,7 +2163,7 @@ mod tests { flags: if direction { 1 } else { 0 } | if disable { 1 << 1 } else { 0 }, cltv_expiry_delta: 144, htlc_minimum_msat: 1000000, - htlc_maximum_msat: if htlc_maximum_msat { OptionalField::Present(131355275467161) } else { OptionalField::Absent }, + htlc_maximum_msat: 131355275467161, fee_base_msat: 10000, fee_proportional_millionths: 20, excess_data: if excess_data { vec![0, 0, 0, 0, 59, 154, 202, 0] } else { Vec::new() } @@ -2071,11 +2176,7 @@ mod tests { let mut target_value = hex::decode("d977cb9b53d93a6ff64bb5f1e158b4094b66e798fb12911168a3ccdf80a83096340a6a95da0ae8d9f776528eecdbb747eb6b545495a4319ed5378e35b21e073a").unwrap(); target_value.append(&mut hex::decode("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f").unwrap()); target_value.append(&mut hex::decode("00083a840000034d013413a7").unwrap()); - if htlc_maximum_msat { - target_value.append(&mut hex::decode("01").unwrap()); - } else { - target_value.append(&mut hex::decode("00").unwrap()); - } + target_value.append(&mut hex::decode("01").unwrap()); target_value.append(&mut hex::decode("00").unwrap()); if direction { let flag = target_value.last_mut().unwrap(); @@ -2086,9 +2187,7 @@ mod tests { *flag = *flag | 1 << 1; } target_value.append(&mut hex::decode("009000000000000f42400000271000000014").unwrap()); - if htlc_maximum_msat { - target_value.append(&mut hex::decode("0000777788889999").unwrap()); - } + target_value.append(&mut hex::decode("0000777788889999").unwrap()); if excess_data { target_value.append(&mut hex::decode("000000003b9aca00").unwrap()); } @@ -2097,16 +2196,14 @@ mod tests { #[test] fn encoding_channel_update() { - do_encoding_channel_update(false, false, false, false); - do_encoding_channel_update(false, false, false, true); - do_encoding_channel_update(true, false, false, false); - do_encoding_channel_update(true, false, false, true); - do_encoding_channel_update(false, true, false, false); - do_encoding_channel_update(false, true, false, true); - do_encoding_channel_update(false, false, true, false); - do_encoding_channel_update(false, false, true, true); - do_encoding_channel_update(true, true, true, false); - do_encoding_channel_update(true, true, true, true); + do_encoding_channel_update(false, false, false); + do_encoding_channel_update(false, false, true); + do_encoding_channel_update(true, false, false); + do_encoding_channel_update(true, false, true); + do_encoding_channel_update(false, true, false); + do_encoding_channel_update(false, true, true); + do_encoding_channel_update(true, true, false); + do_encoding_channel_update(true, true, true); } fn do_encoding_open_channel(random_bit: bool, shutdown: bool, incl_chan_type: bool) { @@ -2136,7 +2233,7 @@ mod tests { htlc_basepoint: pubkey_5, first_per_commitment_point: pubkey_6, channel_flags: if random_bit { 1 << 5 } else { 0 }, - shutdown_scriptpubkey: if shutdown { OptionalField::Present(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, key: pubkey_1}, Network::Testnet).script_pubkey()) } else { OptionalField::Absent }, + shutdown_scriptpubkey: if shutdown { OptionalField::Present(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { OptionalField::Absent }, channel_type: if incl_chan_type { Some(ChannelTypeFeatures::empty()) } else { None }, }; let encoded_value = open_channel.encode(); @@ -2192,7 +2289,7 @@ mod tests { delayed_payment_basepoint: pubkey_4, htlc_basepoint: pubkey_5, first_per_commitment_point: pubkey_6, - shutdown_scriptpubkey: if shutdown { OptionalField::Present(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, key: pubkey_1}, Network::Testnet).script_pubkey()) } else { OptionalField::Absent }, + shutdown_scriptpubkey: if shutdown { OptionalField::Present(Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey()) } else { OptionalField::Absent }, channel_type: None, }; let encoded_value = accept_channel.encode(); @@ -2240,14 +2337,15 @@ mod tests { } #[test] - fn encoding_funding_locked() { + fn encoding_channel_ready() { let secp_ctx = Secp256k1::new(); let (_, pubkey_1,) = get_keys_from!("0101010101010101010101010101010101010101010101010101010101010101", secp_ctx); - let funding_locked = msgs::FundingLocked { + let channel_ready = msgs::ChannelReady { channel_id: [2; 32], next_per_commitment_point: pubkey_1, + short_channel_id_alias: None, }; - let encoded_value = funding_locked.encode(); + let encoded_value = channel_ready.encode(); let target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f").unwrap(); assert_eq!(encoded_value, target_value); } @@ -2259,9 +2357,9 @@ mod tests { let shutdown = msgs::Shutdown { channel_id: [2; 32], scriptpubkey: - if script_type == 1 { Address::p2pkh(&::bitcoin::PublicKey{compressed: true, key: pubkey_1}, Network::Testnet).script_pubkey() } - else if script_type == 2 { Address::p2sh(&script, Network::Testnet).script_pubkey() } - else if script_type == 3 { Address::p2wpkh(&::bitcoin::PublicKey{compressed: true, key: pubkey_1}, Network::Testnet).unwrap().script_pubkey() } + if script_type == 1 { Address::p2pkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).script_pubkey() } + else if script_type == 2 { Address::p2sh(&script, Network::Testnet).unwrap().script_pubkey() } + else if script_type == 3 { Address::p2wpkh(&::bitcoin::PublicKey{compressed: true, inner: pubkey_1}, Network::Testnet).unwrap().script_pubkey() } else { Address::p2wsh(&script, Network::Testnet).script_pubkey() }, }; let encoded_value = shutdown.encode(); @@ -2441,13 +2539,27 @@ mod tests { fn encoding_init() { assert_eq!(msgs::Init { features: InitFeatures::from_le_bytes(vec![0xFF, 0xFF, 0xFF]), + remote_network_address: None, }.encode(), hex::decode("00023fff0003ffffff").unwrap()); assert_eq!(msgs::Init { features: InitFeatures::from_le_bytes(vec![0xFF]), + remote_network_address: None, }.encode(), hex::decode("0001ff0001ff").unwrap()); assert_eq!(msgs::Init { features: InitFeatures::from_le_bytes(vec![]), + remote_network_address: None, }.encode(), hex::decode("00000000").unwrap()); + + let init_msg = msgs::Init { features: InitFeatures::from_le_bytes(vec![]), + remote_network_address: Some(msgs::NetAddress::IPv4 { + addr: [127, 0, 0, 1], + port: 1000, + }), + }; + let encoded_value = init_msg.encode(); + let target_value = hex::decode("000000000307017f00000103e8").unwrap(); + assert_eq!(encoded_value, target_value); + assert_eq!(msgs::Init::read(&mut Cursor::new(&target_value)).unwrap(), init_msg); } #[test]