X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fln%2Fmsgs.rs;h=01a4378456498d7c20706784da60ea13790e3d26;hb=32f6205848806a3b2876a2ae36b1db7d5fa22f7d;hp=d77a39e59772ec9239d271ab55a988bd4b9fbc8a;hpb=b3b4f434a2c384b4d37d7e7977ebd190377f2891;p=rust-lightning diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index d77a39e5..01a43784 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -33,9 +33,10 @@ use bitcoin::hash_types::{Txid, BlockHash}; use ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use std::{cmp, fmt}; +use std::fmt::Debug; use std::io::Read; -use util::events; +use util::events::MessageSendEventsProvider; use util::ser::{Readable, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt}; use ln::channelmanager::{PaymentPreimage, PaymentHash, PaymentSecret}; @@ -44,7 +45,7 @@ use ln::channelmanager::{PaymentPreimage, PaymentHash, PaymentSecret}; pub(crate) const MAX_VALUE_MSAT: u64 = 21_000_000_0000_0000_000; /// An error in decoding a message or struct. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum DecodeError { /// A version byte specified something we don't know how to handle. /// Includes unknown realm byte in an OnionHopData packet @@ -60,19 +61,19 @@ pub enum DecodeError { /// A length descriptor in the packet didn't describe the later data correctly BadLengthDescriptor, /// Error from std::io - Io(::std::io::Error), + Io(/// (C-not exported) as ErrorKind doesn't have a reasonable mapping + ::std::io::ErrorKind), } /// An init message to be sent or received from a peer +#[derive(Clone, Debug, PartialEq)] pub struct Init { - #[cfg(not(feature = "fuzztarget"))] - pub(crate) features: InitFeatures, - #[cfg(feature = "fuzztarget")] + /// The relevant features which the sender supports pub features: InitFeatures, } /// An error message to be sent or received from a peer -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct ErrorMessage { /// The channel ID involved in the error pub channel_id: [u8; 32], @@ -84,6 +85,7 @@ pub struct ErrorMessage { } /// A ping message to be sent or received from a peer +#[derive(Clone, Debug, PartialEq)] pub struct Ping { /// The desired response length pub ponglen: u16, @@ -93,6 +95,7 @@ pub struct Ping { } /// A pong message to be sent or received from a peer +#[derive(Clone, Debug, PartialEq)] pub struct Pong { /// The pong packet size. /// This field is not sent on the wire. byteslen zeros are sent. @@ -100,7 +103,7 @@ pub struct Pong { } /// An open_channel message to be sent or received from a peer -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct OpenChannel { /// The genesis hash of the blockchain where the channel is to be opened pub chain_hash: BlockHash, @@ -143,7 +146,7 @@ pub struct OpenChannel { } /// An accept_channel message to be sent or received from a peer -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct AcceptChannel { /// A temporary channel ID, until the funding outpoint is announced pub temporary_channel_id: [u8; 32], @@ -178,7 +181,7 @@ pub struct AcceptChannel { } /// A funding_created message to be sent or received from a peer -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct FundingCreated { /// A temporary channel ID, until the funding is established pub temporary_channel_id: [u8; 32], @@ -191,7 +194,7 @@ pub struct FundingCreated { } /// A funding_signed message to be sent or received from a peer -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct FundingSigned { /// The channel ID pub channel_id: [u8; 32], @@ -200,7 +203,7 @@ pub struct FundingSigned { } /// A funding_locked message to be sent or received from a peer -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct FundingLocked { /// The channel ID pub channel_id: [u8; 32], @@ -209,7 +212,7 @@ pub struct FundingLocked { } /// A shutdown message to be sent or received from a peer -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Shutdown { /// The channel ID pub channel_id: [u8; 32], @@ -219,7 +222,7 @@ pub struct Shutdown { } /// A closing_signed message to be sent or received from a peer -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct ClosingSigned { /// The channel ID pub channel_id: [u8; 32], @@ -230,7 +233,7 @@ pub struct ClosingSigned { } /// An update_add_htlc message to be sent or received from a peer -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct UpdateAddHTLC { /// The channel ID pub channel_id: [u8; 32], @@ -246,7 +249,7 @@ pub struct UpdateAddHTLC { } /// An update_fulfill_htlc message to be sent or received from a peer -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct UpdateFulfillHTLC { /// The channel ID pub channel_id: [u8; 32], @@ -257,7 +260,7 @@ pub struct UpdateFulfillHTLC { } /// An update_fail_htlc message to be sent or received from a peer -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct UpdateFailHTLC { /// The channel ID pub channel_id: [u8; 32], @@ -267,7 +270,7 @@ pub struct UpdateFailHTLC { } /// An update_fail_malformed_htlc message to be sent or received from a peer -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct UpdateFailMalformedHTLC { /// The channel ID pub channel_id: [u8; 32], @@ -279,7 +282,7 @@ pub struct UpdateFailMalformedHTLC { } /// A commitment_signed message to be sent or received from a peer -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct CommitmentSigned { /// The channel ID pub channel_id: [u8; 32], @@ -290,7 +293,7 @@ pub struct CommitmentSigned { } /// A revoke_and_ack message to be sent or received from a peer -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct RevokeAndACK { /// The channel ID pub channel_id: [u8; 32], @@ -301,7 +304,7 @@ pub struct RevokeAndACK { } /// An update_fee message to be sent or received from a peer -#[derive(PartialEq, Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct UpdateFee { /// The channel ID pub channel_id: [u8; 32], @@ -309,7 +312,7 @@ pub struct UpdateFee { pub feerate_per_kw: u32, } -#[derive(PartialEq, Clone)] +#[derive(Clone, Debug, PartialEq)] /// Proof that the sender knows the per-commitment secret of the previous commitment transaction. /// This is used to convince the recipient that the channel is at a certain commitment /// number even if they lost that data due to a local failure. Of course, the peer may lie @@ -323,7 +326,7 @@ pub struct DataLossProtect { } /// A channel_reestablish message to be sent or received from a peer -#[derive(PartialEq, Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct ChannelReestablish { /// The channel ID pub channel_id: [u8; 32], @@ -336,7 +339,7 @@ pub struct ChannelReestablish { } /// An announcement_signatures message to be sent or received from a peer -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct AnnouncementSignatures { /// The channel ID pub channel_id: [u8; 32], @@ -349,7 +352,7 @@ pub struct AnnouncementSignatures { } /// An address which can be used to connect to a remote peer -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum NetAddress { /// An IPv4 address/port on which the peer is listening. IPv4 { @@ -387,15 +390,6 @@ pub enum NetAddress { }, } impl NetAddress { - fn get_id(&self) -> u8 { - match self { - &NetAddress::IPv4 {..} => { 1 }, - &NetAddress::IPv6 {..} => { 2 }, - &NetAddress::OnionV2 {..} => { 3 }, - &NetAddress::OnionV3 {..} => { 4 }, - } - } - /// Strict byte-length of address descriptor, 1-byte type not recorded fn len(&self) -> u16 { match self { @@ -476,7 +470,7 @@ impl Readable for Result { } /// The unsigned part of a node_announcement -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct UnsignedNodeAnnouncement { /// The advertised features pub features: NodeFeatures, @@ -495,7 +489,7 @@ pub struct UnsignedNodeAnnouncement { pub(crate) excess_address_data: Vec, pub(crate) excess_data: Vec, } -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] /// A node_announcement message to be sent or received from a peer pub struct NodeAnnouncement { /// The signature by the node key @@ -505,7 +499,7 @@ pub struct NodeAnnouncement { } /// The unsigned part of a channel_announcement -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct UnsignedChannelAnnouncement { /// The advertised channel features pub features: ChannelFeatures, @@ -524,7 +518,7 @@ pub struct UnsignedChannelAnnouncement { pub(crate) excess_data: Vec, } /// A channel_announcement message to be sent or received from a peer -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ChannelAnnouncement { /// Authentication of the announcement by the first public node pub node_signature_1: Signature, @@ -539,7 +533,7 @@ pub struct ChannelAnnouncement { } /// The unsigned part of a channel_update -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct UnsignedChannelUpdate { /// The genesis hash of the blockchain where the channel is to be opened pub chain_hash: BlockHash, @@ -549,7 +543,14 @@ pub struct UnsignedChannelUpdate { pub timestamp: u32, /// Channel flags pub flags: u8, - /// The number of blocks to subtract from incoming HTLC cltv_expiry values + /// The number of blocks such that if: + /// `incoming_htlc.cltv_expiry < outgoing_htlc.cltv_expiry + cltv_expiry_delta` + /// then we need to fail the HTLC backwards. When forwarding an HTLC, cltv_expiry_delta determines + /// the outgoing HTLC's minimum cltv_expiry value -- so, if an incoming HTLC comes in with a + /// cltv_expiry of 100000, and the node we're forwarding to has a cltv_expiry_delta value of 10, + /// then we'll check that the outgoing HTLC's cltv_expiry value is at least 100010 before + /// forwarding. Note that the HTLC sender is the one who originally sets this value when + /// constructing the route. pub cltv_expiry_delta: u16, /// The minimum HTLC size incoming to sender, in milli-satoshi pub htlc_minimum_msat: u64, @@ -562,7 +563,7 @@ pub struct UnsignedChannelUpdate { pub(crate) excess_data: Vec, } /// A channel_update message to be sent or received from a peer -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ChannelUpdate { /// A signature of the channel update pub signature: Signature, @@ -570,8 +571,91 @@ pub struct ChannelUpdate { pub contents: UnsignedChannelUpdate, } +/// A query_channel_range message is used to query a peer for channel +/// UTXOs in a range of blocks. The recipient of a query makes a best +/// effort to reply to the query using one or more reply_channel_range +/// messages. +#[derive(Clone, Debug, PartialEq)] +pub struct QueryChannelRange { + /// The genesis hash of the blockchain being queried + pub chain_hash: BlockHash, + /// The height of the first block for the channel UTXOs being queried + pub first_blocknum: u32, + /// The number of blocks to include in the query results + pub number_of_blocks: u32, +} + +/// A reply_channel_range message is a reply to a query_channel_range +/// message. Multiple reply_channel_range messages can be sent in reply +/// to a single query_channel_range message. The query recipient makes a +/// best effort to respond based on their local network view which may +/// not be a perfect view of the network. The short_channel_ids in the +/// reply are encoded. We only support encoding_type=0 uncompressed +/// serialization and do not support encoding_type=1 zlib serialization. +#[derive(Clone, Debug, PartialEq)] +pub struct ReplyChannelRange { + /// The genesis hash of the blockchain being queried + pub chain_hash: BlockHash, + /// The height of the first block in the range of the reply + pub first_blocknum: u32, + /// The number of blocks included in the range of the reply + pub number_of_blocks: u32, + /// True when this is the final reply for a query + pub sync_complete: bool, + /// The short_channel_ids in the channel range + pub short_channel_ids: Vec, +} + +/// A query_short_channel_ids message is used to query a peer for +/// routing gossip messages related to one or more short_channel_ids. +/// The query recipient will reply with the latest, if available, +/// channel_announcement, channel_update and node_announcement messages +/// it maintains for the requested short_channel_ids followed by a +/// reply_short_channel_ids_end message. The short_channel_ids sent in +/// this query are encoded. We only support encoding_type=0 uncompressed +/// serialization and do not support encoding_type=1 zlib serialization. +#[derive(Clone, Debug, PartialEq)] +pub struct QueryShortChannelIds { + /// The genesis hash of the blockchain being queried + pub chain_hash: BlockHash, + /// The short_channel_ids that are being queried + pub short_channel_ids: Vec, +} + +/// A reply_short_channel_ids_end message is sent as a reply to a +/// query_short_channel_ids message. The query recipient makes a best +/// effort to respond based on their local network view which may not be +/// a perfect view of the network. +#[derive(Clone, Debug, PartialEq)] +pub struct ReplyShortChannelIdsEnd { + /// The genesis hash of the blockchain that was queried + pub chain_hash: BlockHash, + /// Indicates if the query recipient maintains up-to-date channel + /// information for the chain_hash + pub full_information: bool, +} + +/// A gossip_timestamp_filter message is used by a node to request +/// gossip relay for messages in the requested time range when the +/// gossip_queries feature has been negotiated. +#[derive(Clone, Debug, PartialEq)] +pub struct GossipTimestampFilter { + /// The genesis hash of the blockchain for channel and node information + pub chain_hash: BlockHash, + /// The starting unix timestamp + pub first_timestamp: u32, + /// The range of information in seconds + pub timestamp_range: u32, +} + +/// Encoding type for data compression of collections in gossip queries. +/// We do not support encoding_type=1 zlib serialization defined in BOLT #7. +enum EncodingType { + Uncompressed = 0x00, +} + /// Used to put an error message in a LightningError -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum ErrorAction { /// The peer took some action which made us think they were useless. Disconnect them. DisconnectPeer { @@ -588,6 +672,7 @@ pub enum ErrorAction { } /// An Err type for failure to process messages. +#[derive(Clone, Debug)] pub struct LightningError { /// A human-readable message describing the error pub err: String, @@ -597,7 +682,7 @@ pub struct LightningError { /// Struct used to return values from revoke_and_ack messages, containing a bunch of commitment /// transaction updates if they were pending. -#[derive(PartialEq, Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct CommitmentUpdate { /// update_add_htlc messages which should be sent pub update_add_htlcs: Vec, @@ -616,7 +701,7 @@ pub struct CommitmentUpdate { /// The information we received from a peer along the route of a payment we originated. This is /// returned by ChannelMessageHandler::handle_update_fail_htlc to be passed into /// RoutingMessageHandler::handle_htlc_fail_channel_update to update our network map. -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub enum HTLCFailChannelUpdate { /// We received an error which included a full ChannelUpdate message. ChannelUpdateMessage { @@ -646,7 +731,8 @@ pub enum HTLCFailChannelUpdate { /// As we wish to serialize these differently from Options (Options get a tag byte, but /// OptionalFeild simply gets Present if there are enough bytes to read into it), we have a /// separate enum type for them. -#[derive(Clone, PartialEq, Debug)] +/// (C-not exported) due to a free generic in T +#[derive(Clone, Debug, PartialEq)] pub enum OptionalField { /// Optional field is included in message Present(T), @@ -658,7 +744,7 @@ pub enum OptionalField { /// /// Messages MAY be called in parallel when they originate from different their_node_ids, however /// they MUST NOT be called in parallel when the two calls have the same their_node_id. -pub trait ChannelMessageHandler : events::MessageSendEventsProvider + Send + Sync { +pub trait ChannelMessageHandler : MessageSendEventsProvider + Send + Sync { //Channel init: /// Handle an incoming open_channel message from the given peer. fn handle_open_channel(&self, their_node_id: &PublicKey, their_features: InitFeatures, msg: &OpenChannel); @@ -673,7 +759,7 @@ pub trait ChannelMessageHandler : events::MessageSendEventsProvider + Send + Syn // Channl close: /// Handle an incoming shutdown message from the given peer. - fn handle_shutdown(&self, their_node_id: &PublicKey, msg: &Shutdown); + fn handle_shutdown(&self, their_node_id: &PublicKey, their_features: &InitFeatures, msg: &Shutdown); /// Handle an incoming closing_signed message from the given peer. fn handle_closing_signed(&self, their_node_id: &PublicKey, msg: &ClosingSigned); @@ -710,13 +796,22 @@ pub trait ChannelMessageHandler : events::MessageSendEventsProvider + Send + Syn /// Handle an incoming channel_reestablish message from the given peer. fn handle_channel_reestablish(&self, their_node_id: &PublicKey, msg: &ChannelReestablish); + /// Handle an incoming channel update from the given peer. + fn handle_channel_update(&self, their_node_id: &PublicKey, msg: &ChannelUpdate); + // Error: /// Handle an incoming error message from the given peer. fn handle_error(&self, their_node_id: &PublicKey, msg: &ErrorMessage); } /// A trait to describe an object which can receive routing messages. -pub trait RoutingMessageHandler : Send + Sync { +/// +/// # Implementor DoS Warnings +/// +/// For `gossip_queries` messages there are potential DoS vectors when handling +/// inbound queries. Implementors using an on-disk network graph should be aware of +/// repeated disk I/O for queries accessing different parts of the network graph. +pub trait RoutingMessageHandler : Send + Sync + MessageSendEventsProvider { /// Handle an incoming node_announcement message, returning true if it should be forwarded on, /// false or returning an Err otherwise. fn handle_node_announcement(&self, msg: &NodeAnnouncement) -> Result; @@ -737,8 +832,25 @@ pub trait RoutingMessageHandler : Send + Sync { /// immediately higher (as defined by ::cmp) than starting_point. /// If None is provided for starting_point, we start at the first node. fn get_next_node_announcements(&self, starting_point: Option<&PublicKey>, batch_amount: u8) -> Vec; - /// Returns whether a full sync should be requested from a peer. - fn should_request_full_sync(&self, node_id: &PublicKey) -> bool; + /// 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); + /// 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. + fn handle_reply_channel_range(&self, their_node_id: &PublicKey, msg: ReplyChannelRange) -> Result<(), LightningError>; + /// Handles the reply of a query we initiated asking for routing gossip + /// messages for a list of channels. We should receive this message when + /// a node has completed its best effort to send us the pertaining routing + /// gossip messages. + fn handle_reply_short_channel_ids_end(&self, their_node_id: &PublicKey, msg: ReplyShortChannelIdsEnd) -> Result<(), LightningError>; + /// Handles when a peer asks us to send a list of short_channel_ids + /// for the requested range of blocks. + fn handle_query_channel_range(&self, their_node_id: &PublicKey, msg: QueryChannelRange) -> Result<(), LightningError>; + /// Handles when a peer asks us to send routing gossip messages for a + /// list of short_channel_ids. + fn handle_query_short_channel_ids(&self, their_node_id: &PublicKey, msg: QueryShortChannelIds) -> Result<(), LightningError>; } mod fuzzy_internal_msgs { @@ -808,7 +920,13 @@ impl PartialEq for OnionPacket { } } -#[derive(Clone, PartialEq)] +impl fmt::Debug for OnionPacket { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("OnionPacket version {} with hmac {:?}", self.version, &self.hmac[..])) + } +} + +#[derive(Clone, Debug, PartialEq)] pub(crate) struct OnionErrorPacket { // This really should be a constant size slice, but the spec lets these things be up to 128KB? // (TODO) We limit it in decode to much lower... @@ -828,18 +946,12 @@ impl fmt::Display for DecodeError { } } -impl fmt::Debug for LightningError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.err.as_str()) - } -} - impl From<::std::io::Error> for DecodeError { fn from(e: ::std::io::Error) -> Self { if e.kind() == ::std::io::ErrorKind::UnexpectedEof { DecodeError::ShortRead } else { - DecodeError::Io(e) + DecodeError::Io(e.kind()) } } } @@ -1423,14 +1535,12 @@ impl Writeable for UnsignedNodeAnnouncement { w.write_all(&self.rgb)?; self.alias.write(w)?; - let mut addrs_to_encode = self.addresses.clone(); - addrs_to_encode.sort_by(|a, b| { a.get_id().cmp(&b.get_id()) }); let mut addr_len = 0; - for addr in &addrs_to_encode { + for addr in self.addresses.iter() { addr_len += 1 + addr.len(); } (addr_len + self.excess_address_data.len() as u16).write(w)?; - for addr in addrs_to_encode { + for addr in self.addresses.iter() { addr.write(w)?; } w.write_all(&self.excess_address_data[..])?; @@ -1450,7 +1560,6 @@ impl Readable for UnsignedNodeAnnouncement { let addr_len: u16 = Readable::read(r)?; let mut addresses: Vec = Vec::new(); - let mut highest_addr_type = 0; let mut addr_readpos = 0; let mut excess = false; let mut excess_byte = 0; @@ -1458,11 +1567,6 @@ impl Readable for UnsignedNodeAnnouncement { if addr_len <= addr_readpos { break; } match Readable::read(r) { Ok(Ok(addr)) => { - if addr.get_id() < highest_addr_type { - // Addresses must be sorted in increasing order - return Err(DecodeError::InvalidValue); - } - highest_addr_type = addr.get_id(); if addr_len < addr_readpos + 1 + addr.len() { return Err(DecodeError::BadLengthDescriptor); } @@ -1515,6 +1619,197 @@ impl_writeable_len_match!(NodeAnnouncement, { contents }); +impl Readable for QueryShortChannelIds { + fn read(r: &mut R) -> Result { + let chain_hash: BlockHash = Readable::read(r)?; + + // We expect the encoding_len to always includes the 1-byte + // encoding_type and that short_channel_ids are 8-bytes each + let encoding_len: u16 = Readable::read(r)?; + if encoding_len == 0 || (encoding_len - 1) % 8 != 0 { + return Err(DecodeError::InvalidValue); + } + + // Must be encoding_type=0 uncompressed serialization. We do not + // support encoding_type=1 zlib serialization. + let encoding_type: u8 = Readable::read(r)?; + if encoding_type != EncodingType::Uncompressed as u8 { + return Err(DecodeError::InvalidValue); + } + + // Read short_channel_ids (8-bytes each), for the u16 encoding_len + // less the 1-byte encoding_type + let short_channel_id_count: u16 = (encoding_len - 1)/8; + let mut short_channel_ids = Vec::with_capacity(short_channel_id_count as usize); + for _ in 0..short_channel_id_count { + short_channel_ids.push(Readable::read(r)?); + } + + Ok(QueryShortChannelIds { + chain_hash, + short_channel_ids, + }) + } +} + +impl Writeable for QueryShortChannelIds { + fn write(&self, w: &mut W) -> Result<(), ::std::io::Error> { + // Calculated from 1-byte encoding_type plus 8-bytes per short_channel_id + let encoding_len: u16 = 1 + self.short_channel_ids.len() as u16 * 8; + + w.size_hint(32 + 2 + encoding_len as usize); + self.chain_hash.write(w)?; + encoding_len.write(w)?; + + // We only support type=0 uncompressed serialization + (EncodingType::Uncompressed as u8).write(w)?; + + for scid in self.short_channel_ids.iter() { + scid.write(w)?; + } + + Ok(()) + } +} + +impl Readable for ReplyShortChannelIdsEnd { + fn read(r: &mut R) -> Result { + let chain_hash: BlockHash = Readable::read(r)?; + let full_information: bool = Readable::read(r)?; + Ok(ReplyShortChannelIdsEnd { + chain_hash, + full_information, + }) + } +} + +impl Writeable for ReplyShortChannelIdsEnd { + fn write(&self, w: &mut W) -> Result<(), ::std::io::Error> { + w.size_hint(32 + 1); + self.chain_hash.write(w)?; + self.full_information.write(w)?; + Ok(()) + } +} + +impl QueryChannelRange { + /** + * Calculates the overflow safe ending block height for the query. + * Overflow returns `0xffffffff`, otherwise returns `first_blocknum + number_of_blocks` + */ + pub fn end_blocknum(&self) -> u32 { + match self.first_blocknum.checked_add(self.number_of_blocks) { + Some(block) => block, + None => u32::max_value(), + } + } +} + +impl Readable for QueryChannelRange { + fn read(r: &mut R) -> Result { + let chain_hash: BlockHash = Readable::read(r)?; + let first_blocknum: u32 = Readable::read(r)?; + let number_of_blocks: u32 = Readable::read(r)?; + Ok(QueryChannelRange { + chain_hash, + first_blocknum, + number_of_blocks + }) + } +} + +impl Writeable for QueryChannelRange { + fn write(&self, w: &mut W) -> Result<(), ::std::io::Error> { + w.size_hint(32 + 4 + 4); + self.chain_hash.write(w)?; + self.first_blocknum.write(w)?; + self.number_of_blocks.write(w)?; + Ok(()) + } +} + +impl Readable for ReplyChannelRange { + fn read(r: &mut R) -> Result { + let chain_hash: BlockHash = Readable::read(r)?; + let first_blocknum: u32 = Readable::read(r)?; + let number_of_blocks: u32 = Readable::read(r)?; + let sync_complete: bool = Readable::read(r)?; + + // We expect the encoding_len to always includes the 1-byte + // encoding_type and that short_channel_ids are 8-bytes each + let encoding_len: u16 = Readable::read(r)?; + if encoding_len == 0 || (encoding_len - 1) % 8 != 0 { + return Err(DecodeError::InvalidValue); + } + + // Must be encoding_type=0 uncompressed serialization. We do not + // support encoding_type=1 zlib serialization. + let encoding_type: u8 = Readable::read(r)?; + if encoding_type != EncodingType::Uncompressed as u8 { + return Err(DecodeError::InvalidValue); + } + + // Read short_channel_ids (8-bytes each), for the u16 encoding_len + // less the 1-byte encoding_type + let short_channel_id_count: u16 = (encoding_len - 1)/8; + let mut short_channel_ids = Vec::with_capacity(short_channel_id_count as usize); + for _ in 0..short_channel_id_count { + short_channel_ids.push(Readable::read(r)?); + } + + Ok(ReplyChannelRange { + chain_hash, + first_blocknum, + number_of_blocks, + sync_complete, + short_channel_ids + }) + } +} + +impl Writeable for ReplyChannelRange { + fn write(&self, w: &mut W) -> Result<(), ::std::io::Error> { + let encoding_len: u16 = 1 + self.short_channel_ids.len() as u16 * 8; + w.size_hint(32 + 4 + 4 + 1 + 2 + encoding_len as usize); + self.chain_hash.write(w)?; + self.first_blocknum.write(w)?; + self.number_of_blocks.write(w)?; + self.sync_complete.write(w)?; + + encoding_len.write(w)?; + (EncodingType::Uncompressed as u8).write(w)?; + for scid in self.short_channel_ids.iter() { + scid.write(w)?; + } + + Ok(()) + } +} + +impl Readable for GossipTimestampFilter { + fn read(r: &mut R) -> Result { + let chain_hash: BlockHash = Readable::read(r)?; + let first_timestamp: u32 = Readable::read(r)?; + let timestamp_range: u32 = Readable::read(r)?; + Ok(GossipTimestampFilter { + chain_hash, + first_timestamp, + timestamp_range, + }) + } +} + +impl Writeable for GossipTimestampFilter { + fn write(&self, w: &mut W) -> Result<(), ::std::io::Error> { + w.size_hint(32 + 4 + 4); + self.chain_hash.write(w)?; + self.first_timestamp.write(w)?; + self.timestamp_range.write(w)?; + Ok(()) + } +} + + #[cfg(test)] mod tests { use hex; @@ -1963,7 +2258,11 @@ mod tests { let script = Builder::new().push_opcode(opcodes::OP_TRUE).into_script(); 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).script_pubkey() } else { Address::p2wsh(&script, Network::Testnet).script_pubkey() }, + 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() } + else { Address::p2wsh(&script, Network::Testnet).script_pubkey() }, }; let encoded_value = shutdown.encode(); let mut target_value = hex::decode("0202020202020202020202020202020202020202020202020202020202020202").unwrap(); @@ -2246,4 +2545,140 @@ mod tests { assert_eq!(msg.amt_to_forward, 0x0badf00d01020304); assert_eq!(msg.outgoing_cltv_value, 0xffffffff); } + + #[test] + fn query_channel_range_end_blocknum() { + let tests: Vec<(u32, u32, u32)> = vec![ + (10000, 1500, 11500), + (0, 0xffffffff, 0xffffffff), + (1, 0xffffffff, 0xffffffff), + ]; + + for (first_blocknum, number_of_blocks, expected) in tests.into_iter() { + let sut = msgs::QueryChannelRange { + chain_hash: BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap(), + first_blocknum, + number_of_blocks, + }; + assert_eq!(sut.end_blocknum(), expected); + } + } + + #[test] + fn encoding_query_channel_range() { + let mut query_channel_range = msgs::QueryChannelRange { + chain_hash: BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap(), + first_blocknum: 100000, + number_of_blocks: 1500, + }; + let encoded_value = query_channel_range.encode(); + let target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000186a0000005dc").unwrap(); + assert_eq!(encoded_value, target_value); + + query_channel_range = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); + assert_eq!(query_channel_range.first_blocknum, 100000); + assert_eq!(query_channel_range.number_of_blocks, 1500); + } + + #[test] + fn encoding_reply_channel_range() { + do_encoding_reply_channel_range(0); + do_encoding_reply_channel_range(1); + } + + fn do_encoding_reply_channel_range(encoding_type: u8) { + let mut target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000b8a06000005dc01").unwrap(); + let expected_chain_hash = BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap(); + let mut reply_channel_range = msgs::ReplyChannelRange { + chain_hash: expected_chain_hash, + first_blocknum: 756230, + number_of_blocks: 1500, + sync_complete: true, + short_channel_ids: vec![0x000000000000008e, 0x0000000000003c69, 0x000000000045a6c4], + }; + + if encoding_type == 0 { + target_value.append(&mut hex::decode("001900000000000000008e0000000000003c69000000000045a6c4").unwrap()); + let encoded_value = reply_channel_range.encode(); + assert_eq!(encoded_value, target_value); + + reply_channel_range = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); + assert_eq!(reply_channel_range.chain_hash, expected_chain_hash); + assert_eq!(reply_channel_range.first_blocknum, 756230); + assert_eq!(reply_channel_range.number_of_blocks, 1500); + assert_eq!(reply_channel_range.sync_complete, true); + assert_eq!(reply_channel_range.short_channel_ids[0], 0x000000000000008e); + assert_eq!(reply_channel_range.short_channel_ids[1], 0x0000000000003c69); + assert_eq!(reply_channel_range.short_channel_ids[2], 0x000000000045a6c4); + } else { + target_value.append(&mut hex::decode("001601789c636000833e08659309a65878be010010a9023a").unwrap()); + let result: Result = Readable::read(&mut Cursor::new(&target_value[..])); + assert!(result.is_err(), "Expected decode failure with unsupported zlib encoding"); + } + } + + #[test] + fn encoding_query_short_channel_ids() { + do_encoding_query_short_channel_ids(0); + do_encoding_query_short_channel_ids(1); + } + + fn do_encoding_query_short_channel_ids(encoding_type: u8) { + let mut target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206").unwrap(); + let expected_chain_hash = BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap(); + let mut query_short_channel_ids = msgs::QueryShortChannelIds { + chain_hash: expected_chain_hash, + short_channel_ids: vec![0x0000000000008e, 0x0000000000003c69, 0x000000000045a6c4], + }; + + if encoding_type == 0 { + target_value.append(&mut hex::decode("001900000000000000008e0000000000003c69000000000045a6c4").unwrap()); + let encoded_value = query_short_channel_ids.encode(); + assert_eq!(encoded_value, target_value); + + query_short_channel_ids = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); + assert_eq!(query_short_channel_ids.chain_hash, expected_chain_hash); + assert_eq!(query_short_channel_ids.short_channel_ids[0], 0x000000000000008e); + assert_eq!(query_short_channel_ids.short_channel_ids[1], 0x0000000000003c69); + assert_eq!(query_short_channel_ids.short_channel_ids[2], 0x000000000045a6c4); + } else { + target_value.append(&mut hex::decode("001601789c636000833e08659309a65878be010010a9023a").unwrap()); + let result: Result = Readable::read(&mut Cursor::new(&target_value[..])); + assert!(result.is_err(), "Expected decode failure with unsupported zlib encoding"); + } + } + + #[test] + fn encoding_reply_short_channel_ids_end() { + let expected_chain_hash = BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap(); + let mut reply_short_channel_ids_end = msgs::ReplyShortChannelIdsEnd { + chain_hash: expected_chain_hash, + full_information: true, + }; + let encoded_value = reply_short_channel_ids_end.encode(); + let target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e220601").unwrap(); + assert_eq!(encoded_value, target_value); + + reply_short_channel_ids_end = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); + assert_eq!(reply_short_channel_ids_end.chain_hash, expected_chain_hash); + assert_eq!(reply_short_channel_ids_end.full_information, true); + } + + #[test] + fn encoding_gossip_timestamp_filter(){ + let expected_chain_hash = BlockHash::from_hex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f").unwrap(); + let mut gossip_timestamp_filter = msgs::GossipTimestampFilter { + chain_hash: expected_chain_hash, + first_timestamp: 1590000000, + timestamp_range: 0xffff_ffff, + }; + let encoded_value = gossip_timestamp_filter.encode(); + let target_value = hex::decode("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e22065ec57980ffffffff").unwrap(); + assert_eq!(encoded_value, target_value); + + gossip_timestamp_filter = Readable::read(&mut Cursor::new(&target_value[..])).unwrap(); + assert_eq!(gossip_timestamp_filter.chain_hash, expected_chain_hash); + assert_eq!(gossip_timestamp_filter.first_timestamp, 1590000000); + assert_eq!(gossip_timestamp_filter.timestamp_range, 0xffff_ffff); + } }