diff --git a/dash/src/network/message_qrinfo.rs b/dash/src/network/message_qrinfo.rs index 2e4678a1b..846c66bbd 100644 --- a/dash/src/network/message_qrinfo.rs +++ b/dash/src/network/message_qrinfo.rs @@ -302,9 +302,12 @@ impl Decodable for MNSkipListMode { #[cfg(test)] mod tests { - use super::{MNSkipListMode, QuorumSnapshot}; + use hashes::{Hash, sha256}; + + use super::{MNSkipListMode, QRInfo, QuorumSnapshot}; use crate::consensus::{deserialize, serialize}; use crate::network::message::{NetworkMessage, RawNetworkMessage}; + use crate::sml::masternode_list_entry::{EntryMasternodeType, MasternodeNetInfo}; #[test] fn quorum_snapshot_encode_decode_roundtrip() { @@ -337,4 +340,65 @@ mod tests { }; assert_eq!(magic, 3177909439); } + + fn load_v3_qrinfo_bytes() -> Vec { + let hex = include_str!("../../tests/data/test_DML_diffs/qrinfo_core231_paloma.hex"); + let data = hex::decode(hex.trim()).expect("decode hex"); + assert_eq!(data.len(), 20064); + assert_eq!( + sha256::Hash::hash(&data).to_string(), + "2811689d4bca44b17a881fc799a563d9ac78c3b7a4b8395a67bb0d857eb61eec" + ); + data + } + + #[test] + fn deserialize_v3_qrinfo_extnetinfo() { + let data = load_v3_qrinfo_bytes(); + let _qr_info: QRInfo = deserialize(&data).expect("deserialize v3 QRInfo"); + } + + #[test] + fn v3_qrinfo_wire_roundtrip() { + let data = load_v3_qrinfo_bytes(); + let qr_info: QRInfo = deserialize(&data).expect("deserialize v3 QRInfo"); + let reencoded = serialize(&qr_info); + assert_eq!(reencoded, data, "v3 QRInfo did not round-trip byte-exact"); + } + + #[test] + fn v3_evo_entry_sources_platform_port_from_extnetinfo() { + let data = load_v3_qrinfo_bytes(); + let qr_info: QRInfo = deserialize(&data).expect("deserialize v3 QRInfo"); + + let mut saw_v3_evo = false; + let extra = qr_info.quorum_snapshot_and_mn_list_diff_at_h_minus_4c.as_ref().map(|(_, d)| d); + let mut all_diffs = vec![ + &qr_info.mn_list_diff_tip, + &qr_info.mn_list_diff_h, + &qr_info.mn_list_diff_at_h_minus_c, + &qr_info.mn_list_diff_at_h_minus_2c, + &qr_info.mn_list_diff_at_h_minus_3c, + ]; + all_diffs.extend(extra); + for diff in all_diffs { + for entry in &diff.new_masternodes { + if entry.version < 3 { + continue; + } + let MasternodeNetInfo::Extended(info) = &entry.service_address else { + panic!("v3 entry must carry an extended service address"); + }; + if let EntryMasternodeType::HighPerformance { + platform_http_port, + .. + } = &entry.mn_type + { + saw_v3_evo = true; + assert_eq!(Some(*platform_http_port), info.platform_https_port()); + } + } + } + assert!(saw_v3_evo, "fixture should contain at least one v3 Evo entry"); + } } diff --git a/dash/src/sml/masternode_list/masternode_helpers.rs b/dash/src/sml/masternode_list/masternode_helpers.rs index f296be38a..4b55a3291 100644 --- a/dash/src/sml/masternode_list/masternode_helpers.rs +++ b/dash/src/sml/masternode_list/masternode_helpers.rs @@ -12,16 +12,19 @@ impl MasternodeList { pub fn has_masternode_at_location(&self, address: [u8; 16], port: u16) -> bool { self.masternodes.values().any(|node| { - match node.masternode_list_entry.service_address.ip() { + let Some(service_address) = + node.masternode_list_entry.service_address.primary_service_address() + else { + return false; + }; + match service_address.ip() { IpAddr::V4(ipv4) => { let ipv4_bytes = ipv4.octets(); - address[..4] == ipv4_bytes - && node.masternode_list_entry.service_address.port() == port + address[..4] == ipv4_bytes && service_address.port() == port } IpAddr::V6(ipv6) => { let ipv6_bytes = ipv6.octets(); - address == ipv6_bytes - && node.masternode_list_entry.service_address.port() == port + address == ipv6_bytes && service_address.port() == port } } }) diff --git a/dash/src/sml/masternode_list/peer_addresses.rs b/dash/src/sml/masternode_list/peer_addresses.rs index 15f290dab..86dd7a6cd 100644 --- a/dash/src/sml/masternode_list/peer_addresses.rs +++ b/dash/src/sml/masternode_list/peer_addresses.rs @@ -53,10 +53,10 @@ impl MasternodeList { .take(max_count.min(self.masternodes.len())) .filter_map(|hash| { self.masternodes.get(&hash).and_then(|entry| { - entry - .masternode_list_entry - .is_valid - .then_some(entry.masternode_list_entry.service_address) + if !entry.masternode_list_entry.is_valid { + return None; + } + entry.masternode_list_entry.service_address.primary_service_address() }) }) .collect() diff --git a/dash/src/sml/masternode_list_entry/mod.rs b/dash/src/sml/masternode_list_entry/mod.rs index 72ca128f0..494205230 100644 --- a/dash/src/sml/masternode_list_entry/mod.rs +++ b/dash/src/sml/masternode_list_entry/mod.rs @@ -1,16 +1,25 @@ mod hash; mod helpers; +pub mod net_info; pub mod qualified_masternode_list_entry; mod score; +use std::cmp::Ordering; use std::io::{Read, Write}; -use std::net::SocketAddr; +use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; + +#[cfg(feature = "bincode")] +use bincode::{Decode, Encode}; +use hashes::Hash; use crate::bls_sig_utils::BLSPublicKey; use crate::consensus::encode::Error; use crate::consensus::{Decodable, Encodable}; use crate::hash_types::ConfirmedHash; use crate::internal_macros::impl_consensus_encoding; +use crate::sml::masternode_list_entry::net_info::{ + Bip155Network, ExtNetInfo, NetInfoEntry, NetInfoPurpose, +}; use crate::{ProTxHash, PubkeyHash}; #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] @@ -24,60 +33,99 @@ pub enum EntryMasternodeType { }, } -impl Encodable for EntryMasternodeType { - fn consensus_encode(&self, writer: &mut W) -> Result { - let mut len = 0; +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] +pub struct OperatorPublicKey { + // TODO: We are using two different public keys here + pub data: BLSPublicKey, + pub version: u16, +} + +impl_consensus_encoding!(OperatorPublicKey, data, version); + +/// Service address of a masternode entry. +/// +/// Pre-v3 entries carry a single fixed-length legacy `SocketAddr`; v3 entries carry a +/// variable-length `ExtNetInfo` map. +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum MasternodeNetInfo { + Legacy(SocketAddr), + Extended(ExtNetInfo), +} + +// `Legacy` reuses bincode's bare `SocketAddr` layout (leading u32 variant 0/1) so existing +// pre-v3 persisted snapshots keep decoding; `Extended` claims the next free variant tag. +#[cfg(feature = "bincode")] +impl bincode::Encode for MasternodeNetInfo { + fn encode( + &self, + encoder: &mut E, + ) -> Result<(), bincode::error::EncodeError> { match self { - EntryMasternodeType::Regular => { - // Write variant tag 0 for Regular - len += 0u16.consensus_encode(writer)?; - } - EntryMasternodeType::HighPerformance { - platform_http_port, - platform_node_id, - } => { - // Write variant tag 1 for HighPerformance, - // then the u16 port and the PubkeyHash - len += 1u16.consensus_encode(writer)?; - len += platform_http_port.consensus_encode(writer)?; - len += platform_node_id.consensus_encode(writer)?; + MasternodeNetInfo::Legacy(addr) => addr.encode(encoder), + MasternodeNetInfo::Extended(info) => { + 2u32.encode(encoder)?; + info.encode(encoder) } } - Ok(len) } } -impl Decodable for EntryMasternodeType { - fn consensus_decode(reader: &mut R) -> Result { - // First decode the variant tag. - let variant: u16 = Decodable::consensus_decode(reader)?; - match variant { - 0 => Ok(EntryMasternodeType::Regular), - 1 => { - let platform_http_port = Decodable::consensus_decode(reader)?; - let platform_node_id = Decodable::consensus_decode(reader)?; - Ok(EntryMasternodeType::HighPerformance { - platform_http_port, - platform_node_id, - }) - } - received => Err(Error::InvalidEnumValue { - max: 1, - received, - msg: "Invalid MasternodeType variant".to_string(), +#[cfg(feature = "bincode")] +impl bincode::Decode for MasternodeNetInfo { + fn decode>( + decoder: &mut D, + ) -> Result { + match u32::decode(decoder)? { + 0 => Ok(MasternodeNetInfo::Legacy(SocketAddr::V4(SocketAddrV4::decode(decoder)?))), + 1 => Ok(MasternodeNetInfo::Legacy(SocketAddr::V6(SocketAddrV6::decode(decoder)?))), + 2 => Ok(MasternodeNetInfo::Extended(ExtNetInfo::decode(decoder)?)), + found => Err(bincode::error::DecodeError::UnexpectedVariant { + allowed: &bincode::error::AllowedEnumVariants::Range { + min: 0, + max: 2, + }, + found, + type_name: "MasternodeNetInfo", }), } } } -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] -pub struct OperatorPublicKey { - // TODO: We are using two different public keys here - pub data: BLSPublicKey, - pub version: u16, -} +#[cfg(feature = "bincode")] +bincode::impl_borrow_decode!(MasternodeNetInfo); -impl_consensus_encoding!(OperatorPublicKey, data, version); +impl MasternodeNetInfo { + /// Returns the primary routable IPv4/IPv6 service address, if one is present. + /// + /// Tor, I2P, CJDNS and domain entries have no `SocketAddr` representation and yield `None`. + pub fn primary_service_address(&self) -> Option { + match self { + MasternodeNetInfo::Legacy(addr) => Some(*addr), + MasternodeNetInfo::Extended(info) => { + info.entries_for(NetInfoPurpose::CoreP2P).iter().find_map(|entry| match entry { + NetInfoEntry::Service { + network: Bip155Network::Ipv4, + addr, + port, + } => { + let octets: [u8; 4] = addr.as_slice().try_into().ok()?; + Some(SocketAddr::V4(SocketAddrV4::new(octets.into(), *port))) + } + NetInfoEntry::Service { + network: Bip155Network::Ipv6, + addr, + port, + } => { + let octets: [u8; 16] = addr.as_slice().try_into().ok()?; + Some(SocketAddr::V6(SocketAddrV6::new(octets.into(), *port, 0, 0))) + } + _ => None, + }) + } + } + } +} #[derive(Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "bincode", derive(Encode, Decode))] @@ -86,19 +134,13 @@ pub struct MasternodeListEntry { pub version: u16, pub pro_reg_tx_hash: ProTxHash, pub confirmed_hash: Option, - pub service_address: SocketAddr, + pub service_address: MasternodeNetInfo, pub operator_public_key: BLSPublicKey, pub key_id_voting: PubkeyHash, pub is_valid: bool, pub mn_type: EntryMasternodeType, } -use std::cmp::Ordering; - -#[cfg(feature = "bincode")] -use bincode::{Decode, Encode}; -use hashes::Hash; - impl Ord for MasternodeListEntry { fn cmp(&self, other: &Self) -> Ordering { self.pro_reg_tx_hash.cmp(&other.pro_reg_tx_hash) @@ -113,6 +155,11 @@ impl PartialOrd for MasternodeListEntry { impl Encodable for MasternodeListEntry { fn consensus_encode(&self, writer: &mut W) -> Result { + debug_assert_eq!( + matches!(self.service_address, MasternodeNetInfo::Legacy(_)), + self.version < 3, + "Legacy service address must be used iff version < 3" + ); let mut len = 0; len += self.version.consensus_encode(writer)?; len += self.pro_reg_tx_hash.consensus_encode(writer)?; @@ -121,12 +168,29 @@ impl Encodable for MasternodeListEntry { } else { len += [0; 32].consensus_encode(writer)?; } - len += self.service_address.consensus_encode(writer)?; + match &self.service_address { + MasternodeNetInfo::Legacy(addr) => len += addr.consensus_encode(writer)?, + MasternodeNetInfo::Extended(info) => len += info.consensus_encode_ext(writer)?, + } len += self.operator_public_key.consensus_encode(writer)?; len += self.key_id_voting.consensus_encode(writer)?; len += self.is_valid.consensus_encode(writer)?; if self.version >= 2 { - len += self.mn_type.consensus_encode(writer)?; + match &self.mn_type { + EntryMasternodeType::Regular => { + len += 0u16.consensus_encode(writer)?; + } + EntryMasternodeType::HighPerformance { + platform_http_port, + platform_node_id, + } => { + len += 1u16.consensus_encode(writer)?; + if self.version < 3 { + len += platform_http_port.consensus_encode(writer)?; + } + len += platform_node_id.consensus_encode(writer)?; + } + } } Ok(len) } @@ -142,12 +206,48 @@ impl Decodable for MasternodeListEntry { } else { Some(confirmed_hash) }; - let service_address: SocketAddr = Decodable::consensus_decode(reader)?; + let service_address = if version >= 3 { + MasternodeNetInfo::Extended(ExtNetInfo::consensus_decode_ext(reader)?) + } else { + MasternodeNetInfo::Legacy(SocketAddr::consensus_decode(reader)?) + }; let operator_public_key: BLSPublicKey = Decodable::consensus_decode(reader)?; let key_id_voting: PubkeyHash = Decodable::consensus_decode(reader)?; let is_valid: bool = Decodable::consensus_decode(reader)?; - let mn_type: EntryMasternodeType = if version >= 2 { - Decodable::consensus_decode(reader)? + let mn_type = if version >= 2 { + let variant: u16 = Decodable::consensus_decode(reader)?; + match variant { + 0 => EntryMasternodeType::Regular, + 1 => { + if version >= 3 { + let platform_node_id = Decodable::consensus_decode(reader)?; + let platform_http_port = match &service_address { + MasternodeNetInfo::Extended(info) => { + info.platform_https_port().unwrap_or(0) + } + MasternodeNetInfo::Legacy(_) => 0, + }; + EntryMasternodeType::HighPerformance { + platform_http_port, + platform_node_id, + } + } else { + let platform_http_port = Decodable::consensus_decode(reader)?; + let platform_node_id = Decodable::consensus_decode(reader)?; + EntryMasternodeType::HighPerformance { + platform_http_port, + platform_node_id, + } + } + } + received => { + return Err(Error::InvalidEnumValue { + max: 1, + received, + msg: "Invalid MasternodeType variant".to_string(), + }); + } + } } else { EntryMasternodeType::Regular }; diff --git a/dash/src/sml/masternode_list_entry/net_info.rs b/dash/src/sml/masternode_list_entry/net_info.rs new file mode 100644 index 000000000..e1362bfac --- /dev/null +++ b/dash/src/sml/masternode_list_entry/net_info.rs @@ -0,0 +1,424 @@ +use std::io; +use std::io::{Read, Write}; +use std::mem; + +#[cfg(feature = "bincode")] +use bincode::{Decode, Encode}; + +use crate::consensus::encode::{Error, MAX_VEC_SIZE, VarInt}; +use crate::consensus::{Decodable, Encodable}; + +const EXTNETINFO_CURRENT_VERSION: u8 = 1; + +const NET_TYPE_SERVICE: u8 = 0x01; +const NET_TYPE_DOMAIN: u8 = 0x02; +const NET_TYPE_INVALID: u8 = 0xff; + +/// BIP155 network identifiers as used inside an ADDRV2-form `CService`. +/// +/// Tor v2 (id 3) and unknown ids are never emitted by Core and are rejected on decode. +#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "bincode", derive(Encode, Decode))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Bip155Network { + Ipv4, + Ipv6, + TorV3, + I2p, + Cjdns, +} + +impl Bip155Network { + fn from_u8(value: u8) -> Result { + match value { + 1 => Ok(Bip155Network::Ipv4), + 2 => Ok(Bip155Network::Ipv6), + 4 => Ok(Bip155Network::TorV3), + 5 => Ok(Bip155Network::I2p), + 6 => Ok(Bip155Network::Cjdns), + received => Err(Error::InvalidEnumValue { + max: 6, + received: received as u16, + msg: "Invalid BIP155 network id".to_string(), + }), + } + } + + fn to_u8(self) -> u8 { + match self { + Bip155Network::Ipv4 => 1, + Bip155Network::Ipv6 => 2, + Bip155Network::TorV3 => 4, + Bip155Network::I2p => 5, + Bip155Network::Cjdns => 6, + } + } + + fn address_len(self) -> usize { + match self { + Bip155Network::Ipv4 => 4, + Bip155Network::Ipv6 => 16, + Bip155Network::TorV3 => 32, + Bip155Network::I2p => 32, + Bip155Network::Cjdns => 16, + } + } +} + +/// A single network info entry within an `ExtNetInfo` purpose list. +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "bincode", derive(Encode, Decode))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum NetInfoEntry { + Service { + network: Bip155Network, + addr: Vec, + port: u16, + }, + Domain { + host: String, + port: u16, + }, + Invalid, +} + +impl NetInfoEntry { + fn consensus_encode_ext(&self, writer: &mut W) -> Result { + let mut len = 0; + match self { + NetInfoEntry::Service { + network, + addr, + port, + } => { + len += NET_TYPE_SERVICE.consensus_encode(writer)?; + len += network.to_u8().consensus_encode(writer)?; + len += VarInt(addr.len() as u64).consensus_encode(writer)?; + writer.write_all(addr)?; + len += addr.len(); + writer.write_all(&port.to_be_bytes())?; + len += 2; + } + NetInfoEntry::Domain { + host, + port, + } => { + len += NET_TYPE_DOMAIN.consensus_encode(writer)?; + len += host.consensus_encode(writer)?; + writer.write_all(&port.to_be_bytes())?; + len += 2; + } + NetInfoEntry::Invalid => { + len += NET_TYPE_INVALID.consensus_encode(writer)?; + } + } + Ok(len) + } + + fn consensus_decode_ext(reader: &mut R) -> Result { + let net_type: u8 = Decodable::consensus_decode(reader)?; + match net_type { + NET_TYPE_SERVICE => { + let network = Bip155Network::from_u8(Decodable::consensus_decode(reader)?)?; + let addr_len = VarInt::consensus_decode(reader)?.0 as usize; + if addr_len != network.address_len() { + return Err(Error::ParseFailed("BIP155 address length does not match network")); + } + let mut addr = vec![0u8; addr_len]; + reader.read_exact(&mut addr)?; + let mut port_bytes = [0u8; 2]; + reader.read_exact(&mut port_bytes)?; + let port = u16::from_be_bytes(port_bytes); + Ok(NetInfoEntry::Service { + network, + addr, + port, + }) + } + NET_TYPE_DOMAIN => { + let host = String::consensus_decode(reader)?; + let mut port_bytes = [0u8; 2]; + reader.read_exact(&mut port_bytes)?; + let port = u16::from_be_bytes(port_bytes); + Ok(NetInfoEntry::Domain { + host, + port, + }) + } + NET_TYPE_INVALID => Ok(NetInfoEntry::Invalid), + received => Err(Error::InvalidEnumValue { + max: 0xff, + received: received as u16, + msg: "Invalid NetInfoEntry type".to_string(), + }), + } + } +} + +/// Purpose codes keyed in an `ExtNetInfo` map. +#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "bincode", derive(Encode, Decode))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum NetInfoPurpose { + CoreP2P, + PlatformP2P, + PlatformHttps, +} + +impl NetInfoPurpose { + fn from_u8(value: u8) -> Result { + match value { + 0 => Ok(NetInfoPurpose::CoreP2P), + 1 => Ok(NetInfoPurpose::PlatformP2P), + 2 => Ok(NetInfoPurpose::PlatformHttps), + received => Err(Error::InvalidEnumValue { + max: 2, + received: received as u16, + msg: "Invalid NetInfoPurpose".to_string(), + }), + } + } + + fn to_u8(self) -> u8 { + match self { + NetInfoPurpose::CoreP2P => 0, + NetInfoPurpose::PlatformP2P => 1, + NetInfoPurpose::PlatformHttps => 2, + } + } +} + +/// Variable-length extended network info introduced for ProTx version 3. +/// +/// `purposes` preserves the exact on-wire pair order so that a decode followed by an encode +/// reproduces the original bytes. +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "bincode", derive(Encode, Decode))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct ExtNetInfo { + pub version: u8, + pub purposes: Vec<(NetInfoPurpose, Vec)>, +} + +impl ExtNetInfo { + pub(super) fn consensus_encode_ext( + &self, + writer: &mut W, + ) -> Result { + let mut len = self.version.consensus_encode(writer)?; + // Unknown versions carry no further payload, mirroring Core's short-circuit. + if self.version == 0 || self.version > EXTNETINFO_CURRENT_VERSION { + return Ok(len); + } + len += VarInt(self.purposes.len() as u64).consensus_encode(writer)?; + for (purpose, entries) in &self.purposes { + len += purpose.to_u8().consensus_encode(writer)?; + len += VarInt(entries.len() as u64).consensus_encode(writer)?; + for entry in entries { + len += entry.consensus_encode_ext(writer)?; + } + } + Ok(len) + } + + pub(super) fn consensus_decode_ext(reader: &mut R) -> Result { + let version: u8 = Decodable::consensus_decode(reader)?; + if version == 0 || version > EXTNETINFO_CURRENT_VERSION { + return Ok(ExtNetInfo { + version, + purposes: Vec::new(), + }); + } + let n_purposes = VarInt::consensus_decode(reader)?.0; + // Cap speculative allocation so a malicious count cannot trigger an unbounded reserve. + let max_purposes = MAX_VEC_SIZE / 4 / mem::size_of::<(NetInfoPurpose, Vec)>(); + let mut purposes = Vec::with_capacity(core::cmp::min(n_purposes as usize, max_purposes)); + for _ in 0..n_purposes { + let purpose = NetInfoPurpose::from_u8(Decodable::consensus_decode(reader)?)?; + let n_entries = VarInt::consensus_decode(reader)?.0; + let max_entries = MAX_VEC_SIZE / 4 / mem::size_of::(); + let mut entries = Vec::with_capacity(core::cmp::min(n_entries as usize, max_entries)); + for _ in 0..n_entries { + entries.push(NetInfoEntry::consensus_decode_ext(reader)?); + } + purposes.push((purpose, entries)); + } + Ok(ExtNetInfo { + version, + purposes, + }) + } + + pub fn platform_https_port(&self) -> Option { + self.entries_for(NetInfoPurpose::PlatformHttps).iter().find_map(|entry| match entry { + NetInfoEntry::Service { + port, + .. + } + | NetInfoEntry::Domain { + port, + .. + } => Some(*port), + NetInfoEntry::Invalid => None, + }) + } + + pub(super) fn entries_for(&self, purpose: NetInfoPurpose) -> &[NetInfoEntry] { + self.purposes + .iter() + .find(|(p, _)| *p == purpose) + .map(|(_, entries)| entries.as_slice()) + .unwrap_or(&[]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn roundtrip(entry: &NetInfoEntry) -> Vec { + let mut buf = Vec::new(); + entry.consensus_encode_ext(&mut buf).unwrap(); + let decoded = NetInfoEntry::consensus_decode_ext(&mut buf.as_slice()).unwrap(); + assert_eq!(*entry, decoded); + buf + } + + fn roundtrip_ext(info: &ExtNetInfo) -> Vec { + let mut buf = Vec::new(); + info.consensus_encode_ext(&mut buf).unwrap(); + let decoded = ExtNetInfo::consensus_decode_ext(&mut buf.as_slice()).unwrap(); + assert_eq!(*info, decoded); + buf + } + + #[test] + fn service_ipv4() { + let entry = NetInfoEntry::Service { + network: Bip155Network::Ipv4, + addr: vec![1, 2, 3, 4], + port: 9999, + }; + let bytes = roundtrip(&entry); + assert_eq!(bytes, vec![0x01, 0x01, 0x04, 1, 2, 3, 4, 0x27, 0x0f]); + } + + #[test] + fn service_ipv6() { + let entry = NetInfoEntry::Service { + network: Bip155Network::Ipv6, + addr: (0..16).collect(), + port: 1, + }; + roundtrip(&entry); + } + + #[test] + fn domain_entry() { + let entry = NetInfoEntry::Domain { + host: "example.org".to_string(), + port: 443, + }; + let bytes = roundtrip(&entry); + assert_eq!(bytes[0], 0x02); + assert_eq!(&bytes[bytes.len() - 2..], &443u16.to_be_bytes()); + } + + #[test] + fn invalid_entry() { + let entry = NetInfoEntry::Invalid; + let bytes = roundtrip(&entry); + assert_eq!(bytes, vec![0xff]); + } + + #[test] + fn multi_purpose_map() { + let info = ExtNetInfo { + version: 1, + purposes: vec![ + ( + NetInfoPurpose::CoreP2P, + vec![NetInfoEntry::Service { + network: Bip155Network::Ipv4, + addr: vec![10, 0, 0, 1], + port: 9999, + }], + ), + ( + NetInfoPurpose::PlatformP2P, + vec![NetInfoEntry::Service { + network: Bip155Network::Ipv4, + addr: vec![10, 0, 0, 1], + port: 26656, + }], + ), + ( + NetInfoPurpose::PlatformHttps, + vec![NetInfoEntry::Service { + network: Bip155Network::Ipv4, + addr: vec![10, 0, 0, 1], + port: 443, + }], + ), + ], + }; + roundtrip_ext(&info); + assert_eq!(info.platform_https_port(), Some(443)); + } + + #[test] + fn multiple_entries_one_purpose() { + let info = ExtNetInfo { + version: 1, + purposes: vec![( + NetInfoPurpose::CoreP2P, + vec![ + NetInfoEntry::Service { + network: Bip155Network::Ipv4, + addr: vec![1, 1, 1, 1], + port: 1, + }, + NetInfoEntry::Service { + network: Bip155Network::Ipv6, + addr: (0..16).collect(), + port: 2, + }, + NetInfoEntry::Domain { + host: "node.example".to_string(), + port: 3, + }, + NetInfoEntry::Invalid, + ], + )], + }; + roundtrip_ext(&info); + } + + #[test] + fn empty_map() { + let info = ExtNetInfo { + version: 1, + purposes: Vec::new(), + }; + let bytes = roundtrip_ext(&info); + assert_eq!(bytes, vec![0x01, 0x00]); + } + + #[test] + fn unknown_version_short_circuit() { + for version in [0u8, 2, 255] { + let info = ExtNetInfo { + version, + purposes: Vec::new(), + }; + let bytes = roundtrip_ext(&info); + assert_eq!(bytes, vec![version]); + } + } + + #[test] + fn unknown_network_rejected() { + let bytes = [NET_TYPE_SERVICE, 3u8, 0x04, 0, 0, 0, 0, 0, 0]; + assert!(NetInfoEntry::consensus_decode_ext(&mut bytes.as_slice()).is_err()); + } +} diff --git a/dash/tests/data/test_DML_diffs/qrinfo_core231_paloma.hex b/dash/tests/data/test_DML_diffs/qrinfo_core231_paloma.hex new file mode 100644 index 000000000..acf216781 --- /dev/null +++ b/dash/tests/data/test_DML_diffs/qrinfo_core231_paloma.hex @@ -0,0 +1 @@ +010000000dfe1703020000000200000003000000010000000df91d020300000001000000010000000d7d1e02030000000100000001000d9a488d9d839643d6b6db89a05e1ca80f8594a06dd4ec37347f779e24040000bf0ecd76613af1bd5b02d924cced56d2dd6120a451b7f213a950ae0e9c00000003000000039196a09f8b87e9d040ea8e213249871b4f8da1dadb6f7f5ed4fdcc9078842b2d48b1342c562cd14b2625a9a7b0fc7b9b3a0ed7a49aa35c8f92063a2cc7aa564e68768e1c8386f52b503824f02a1517b1aaca5ffc5a65fa5fece16d088613e25a010703000500010000000000000000000000000000000000000000000000000000000000000000ffffffff016affffffff0300e1f505000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac209db40600000000016ae0052d0b000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac00000000af0300503b000017ec1975822e6df0634548233e259bb9426804b11ccf7a451d1eb94492024bf3142bfab98ad9a1d6e256d91ee8976c3229a946d6dbf3384b2c199ab21c537b8801a5ae827c95dd430f1d683f39b8a77d9d3e0d76799b7d34dd3db17ec2d6f79df7726f549f2b94b79cdbe08ed094cea74d03fb27ac4f32fc80f40b5996cef6e5c38be4315be8d3994c6fee2622db6d766aadf41465b636426c69c50ecb2394e0c648fadec3cc07000000000465932177145985099299fb26f303cdbe01058b9f0d4a5b4f5b06c5c5a2930100006960f95052365bb80df886e3b0789e65309bdf83bb7671c31d23ee99bb93010000697d1bc70194b21533dcbb6dc8ce97d81f9841bc85c0001ac98fb0a16b1c0200006b932177145985099299fb26f303cdbe01058b9f0d4a5b4f5b06c5c5a293010000040300654c3ee7975038af4938cf5a69ea6a2171ab1d9fb99afd8f4bf3115768ca0400000cff0f0cff0fb1af4db68127d5b29069a19676c3e3cb088ff7d4312a19d4ddd13cc364f5df9e2c3fe3a5333ceab9698fc075ba51a4bfeca789807561ed357a56dc693f7a5b1699c38a8d037fc19affa3fccb3667a32b96b18d8822cf15462667f113b23f9cacb9417ef317616dd830a9e3a2a09e910f1028701f0935bbf53c6da46243bdc3cc11ecaec96660e543ca82167dc40f3fbed295b5a3148ed3372b0fbb9a88dfb5c6f933447aea246c7ffe21b07832b47c5e8eee1866c29c782ac6feedd0f8d6da1fb4f966e70573215fe935cb1b3029d75280d8802241e4a56cf1ead24507803d8d0e550af187c07cac5232051df382d2c352e270cd74743e435ed0947571e2439136f8aa4034351b1f9a5927227eef77ea0400694c3ee7975038af4938cf5a69ea6a2171ab1d9fb99afd8f4bf3115768ca040000000008ff08ff89bc0fa5a797560e2acdacd45d1d61b82bd03c511205bb8356b1b502c322caf3ca882c71bdcea4fa2077058498685500abce0e8183ef7e02532930c948f202e8e355eebc996549220dd45dcf458e3fef980a45301accccb4064250124722ec40e3921efdfba7991140846ddd07cb008ab1c77f9d10c28a2bcb40bf8a901ac6ca0434bc9fb761f5198a5acb37a749d1863fcd258028a466d6f449e956237ba4b96deb71e8d9b266593d71fb5fe3aebb9eb0bead370575f3ac4f9dd8feae7993ce74563a8e539c1f48c1bc86b36ca4c927997e26d5441362b8e366aa6b112688e80354a32af9b4da2d0e5de1b11a5102b8fd9d3d63065cda5b850a32ab754ff110df97c8b337f0db5d528790e7b8dfe7db040069effec9e3c7855b3e34c95ee6eaa2ac502a79d17286e586a1c0d95174fc000000010008ff08ffb765196941549edffed3750810bb1806d6f6f8b8929c0d14625bb9fc81398652d704345d3a6e34f8593e5761a4d74067f0118103fd211dbe687b3956c78d64d42789c9eefd17451a0a33998bf4dfb8cf85b7c002f16b1bc5f536dcb970682d38c15cf5c7fe1e99ccd1209d7fef79fb7b2c75921234a304c5de441482c7426bc212d80e3fe321901bb941be7c8bb59094189ded875fe68ac59174fbdbdc97bddafd4e00fc09688c585bfe9f8355b9570e95a056a6d6075ce5c481dde8d602e6b9a7c1105efad0f251d7a6c083e7a93b4dd14c38a57b0eb86336c0bc1f4577417a0c009738ca42da7db03b67377c55c406ad6b040df9ae97410772b8c8cf592f0b7c54f52801a1761282f9b9d9af6f2ddc03006b4c3ee7975038af4938cf5a69ea6a2171ab1d9fb99afd8f4bf3115768ca0400000cff0f0cff0fb7bb8c1254dd7aa78f44702453fe989aa145ddfe78ce0958114f85764a24b54835dd3cccf1fbd8696731a199a2e190ad240491a656aa328d6145f0721bfcf182cc0a56a1af7d3374fead27e579989c1d8e983668c47a09485a8be921670f9a713b184f56cf451d32a5a78f9c1fbd16ad248ed80baebe99a4e6951820985ca3bf13dc31f0344fb1134d0917bae35b8d36a2868b738ae2a233b3940331d398a149d4215bb1c8383fee08a879284b4aa70fabb56f1238dee98823f84c362d24e0b8447d41c850a3425c370d82045e1c6ace61eb1faa53bba6e007af797b81fb63ea164ff4622060187322710efa00781d11097caf22566f82cfa7789bc48ba4e04ab7b59ca0c4926a74d391fa9c315b75af0196567999d129b294e6e50c7963775877a4b1d10b5dbd46d75d2aa930422f9fdead58b4998805fd954b33b16e87b31a2c19b0912cd7b638e449ddb100662063c39a7aea84fbcb18832dedd05f0ec1fb1edf5dd30c87177006d9da60d09abb6abf040000010002000300010075ed8ff8899a87abb96320410b1bc1a91e98a19f9c304d32e09a7ec5940000000d9a488d9d839643d6b6db89a05e1ca80f8594a06dd4ec37347f779e2404000003000000030378f2fe965fbf99f8e7912289de52ceac7389b36d1f752e64b94e68ed552b3412f9c4649026714c4e11b8b83391b6a5de4dd0dcb9c33c430425633ede2a509f25985cd72f33abc64f83029a2d1e492514e281d405aa082a4a26a84fecb1055f010703000500010000000000000000000000000000000000000000000000000000000000000000ffffffff016affffffff0300e1f505000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac209db40600000000016ae0052d0b000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac00000000af0300383b000017ec1975822e6df0634548233e259bb9426804b11ccf7a451d1eb94492024bf323d55f401ccd944015004ece6b8838e1353508aed6c47a4c71c98f4ecaf4470d0096567999d129b294e6e50c7963775877a4b1d10b5dbd46d75d2aa930422f9fdead58b4998805fd954b33b16e87b31a2c19b0912cd7b638e449ddb100662063c39a7aea84fbcb18832dedd05f0ec1fb1edf5dd30c87177006d9da60d09abb6abf483ff022cc070000000006650310d248be594459720e593883af1b76a2f83d97ad9c07950eb83c544103000065a7555de589f408753491157e49ceba86a2a4a0354aa3719f637c2ebb6701000069932177145985099299fb26f303cdbe01058b9f0d4a5b4f5b06c5c5a29301000069baf24c3e0e1bbf6a2e16c4787667f006698ef4de217e689d9764b5e3620400006b0310d248be594459720e593883af1b76a2f83d97ad9c07950eb83c54410300006ba7555de589f408753491157e49ceba86a2a4a0354aa3719f637c2ebb6701000006030065373ef479ef7fb0671157d211f34738e39a7917e7640ccb41dfc6248c3b0500000cff0f0cff0fa6e401089fc1aaac3b48cd8e86e1eaff87058117ffd26f5bf4bf38d253532d081340f73f218d7224e82af4ee181b3a88aa77e20238ea8418f74d654e8feb414370814337bf38cb6f0528335a1181e1f68bc65b6c5cd6073d744e0448c46a9eca344dedfba32c1a8cae6d6e9207be33afc006068c8e7fd081801ab69e63fd2e7f07cf682042cbdd2d14bbb19e29c435cf8feebf39deb763ef7d8df219e4a10b2152abd411aa548b0262befbca31ee10c0b5ecc2bb1552e0a4988c469cb84867f9b8987a0c36109d88970b0cfcf02dcb0ca0b9d2fe53e9c28e05bc9b6adb67c92f0ac5fa4131452cf7612b799f760564ff382e9fe453a3adbb743dca633d78f3031a79498ca1f2d63dddb7a1df42413b5a0300657d1bc70194b21533dcbb6dc8ce97d81f9841bc85c0001ac98fb0a16b1c0200000cff0f0cff0fb658ac8025f7c593287349c19d40694a774d90ed22cc872072222dd86228e3f45d54351ae0ee6b465ec6426facb28ff0459c5ffc9f55dad2491063ed70e4512ab2274c59369da1392c1ffa6851a3c425a5ebce69e492d829786e34c6d49416c61e5550622613397eb8bf11a42e912104d5cad451b46561a1b33aa8e0d4d54e7f0939d4e2ab6549d57ab5f2f50d70201334e3ab76b13190afaf88dcb38ff31b02da4f1ab7d04ba3d6a1eeab210b319b31a3148a2b7d1c1e682451c3ff0d33ae5c156af6b85934e70601b7a4326f4feca94b972bcf2f13ba0fc3ec941dbd0934110a4007ecfd1acfbad1dd89a1c68be4a89186551f9cc5d4ffab4311cb252b3f2c3bd4dfaba38e6840ddfb6fccb4a3bce504006960f95052365bb80df886e3b0789e65309bdf83bb7671c31d23ee99bb93010000010008ff08ffa0d406c71fb8aa8bcced1fc6481c0ed83cd034658955cd69bb23c461d60b7d35b64a947412ad6f850d598df7c95531780ff2ab87699158cb3f108e1b98173534fc202cc90ec315967587b7a11dd65afe8967909c380dfa76cc878dfc3bce3c1bf0a1cabbfc6daf40302d35f48f1ae04c83e37f7d258f405c4ee844cd7d26aac0107a880cdd923ee1b515cdb1e78a351051cf0a46864882617902e3f8ff368e337bdf6893dc38ff793c466327169ca2faad4d951d0d0a375e4dd4f50a372bec1a7052f3602e6840e43232bf47cd9c5b6abf5679098b5ed7e9fec51cb902dee99f030e5db8b6939449d257c45c770d11062eaa24b347271984d998cf7a95990f49286bfc4b07dad9f3799ff55ab56d74cc0400697d1bc70194b21533dcbb6dc8ce97d81f9841bc85c0001ac98fb0a16b1c020000000008ff08ffa18ff5683579c1f77f33acb8773ad27a8d1cf5628b3f835da700b060d9a12cf3746a19033139f9ed5dc540e6a85161a81ad80e71cc9ee258c1d4cbaec967d20d05ed477321d894b0a9bf4e90222a8ca28dab5ba510fab089681e92ddf82464ab4e490c9cf82c0217bab37fc979b546bf12f534eec3711f6370ba7ca00f68baa11703fa660cfffab300090207438538a459f0a24562dbdde7520feb1371b16760631482be38058f91eb582bc6fbd4b0f48570e189265cc4fb657566f6c3deb28330863a4933a63277800139ee106260088cb08d30903529dff78533491d5308f1123d124c61db2ba616ec6bbd739f3db1dd099b6ecbaf6f06ff8116a8de8103ddc7d8a0319c9cf3a5d6fb50827bc0162603006b373ef479ef7fb0671157d211f34738e39a7917e7640ccb41dfc6248c3b0500000cff0f0cff0fb40476887500dbd7a5257a2010204717e3a7f4946764aabb77adf2c087b88ca322bab11a85bd18341e2e72ad93f734ddac838f27996345e506d33e9b6c5e61154535f618d87bc9bc46a04a3da678845d866e7a8d710d1c87cc9ea880ee9408690b691490f74ec587e8af72eb0b515e9331a3e38219b20665b55537edc260867e043b334a7ef0a701607f259a35f7b9693a958c9edef9e044cf94e15f1ab7c26bfe4b1a9971d3817fb0d03a81a2955324afd822bdba7eade3d9ed4a341453734e9ce86b39ce91b2d15405701f6a368b071a84e132611c3cfb07d27ad1591fa3ee076a49da99b3d11463b7864e98cc32047db8f1a6ac4d1a0d5d8c9fd8f0188bfd72d0cf3a47baeef6aa06e591ddceba8003006b7d1bc70194b21533dcbb6dc8ce97d81f9841bc85c0001ac98fb0a16b1c0200000cff0f0cff0fae3cd1c788acde8ac49bff2af4e725966e3eae013beae1018884e9a74789f02d6015d8db019aa3529c069ca9c90369e3509a993be1af493c15b8f91b485465831816c652ad4fd6de86aef52cb801399693b593225e2604d08528566b336d4c76629199f9e91c3f14ef9bef111bf82adbcd057844f59cb701d6f3c04a6b8d6aa501299950daaeaa506b65c7bfa8d21491341a70306df8e7dc843b51cc5598b77f011a96621940a07c1aa2e8879046e34da2e54f9d0f8311fb8e17e2891fc95f832d0a8a4acce02628c53b37007188866734b6588082dd025e9987472e8b8c5fb1117fa191c7461e8e4e333c5cf7fded26eeb0c4f1f73848c90b04cb6bc5dc3c2841e9a493dfc76a1ab22b5c9698f2aa0b029973c9b6e2c70312af787d0948aad83501c8db683484d2fe7d6f3984211c6384b42f2a57a05813ab838337c99dd695920ea1a40471c0e60226c4f4e9b9457f37e2825cc3a81ab4fbed90a3c47e504a65f40c8b6daaa1a1c22cfc7f4b760e82a50200000400a6769398890e30d7b41591ee13d41d02fc809c490c94600a52f82b57d3b400e57208a136b6a2394824b5714fbe912aa20f9c6f3219272e07568b26d75c6fc26f1038d9c18386a129bc9da2dd96399f252cbbba0c8e02d1ce40b98302330cd4700401000200030005000100d0f1115da8bc9731b68c4e0c122c8a18c99aa558b3595b6d83084d689a01000075ed8ff8899a87abb96320410b1bc1a91e98a19f9c304d32e09a7ec59400000003000000035bd383bc5858d059810ba680936a87a6576e1c9a74ffcfe3ee82622572d09b9c7e1f385f046e747e01ceb5564b1d4c8bd54db0aaf8521f0be41e76dcf29480f39503acab07b0364650cdbb760fdb0d98d51ea79785e4ea8e111908353912094d010703000500010000000000000000000000000000000000000000000000000000000000000000ffffffff016affffffff0300e1f505000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac209db40600000000016ae0052d0b000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac00000000af0300083b000017ec1975822e6df0634548233e259bb9426804b11ccf7a451d1eb94492024bf320f22c33e7b52c362a0230b505771e69bf812830195d0315a71069fb6cb0355d01a6769398890e30d7b41591ee13d41d02fc809c490c94600a52f82b57d3b400e57208a136b6a2394824b5714fbe912aa20f9c6f3219272e07568b26d75c6fc26f1038d9c18386a129bc9da2dd96399f252cbbba0c8e02d1ce40b98302330cd47048c912e1ca07000000000665122b4db043e0b508b30961968fdf5b1a9aa97f2c71819935c6b56633920500006535fb6c8e59cee7494d0ec8c43e0c632148b40502a13860f2c024e2e6ee19000069080971c74ff8fbbb99ce0a010848bed36ea2857d8ea922218b983741aa01000069a7555de589f408753491157e49ceba86a2a4a0354aa3719f637c2ebb670100006b122b4db043e0b508b30961968fdf5b1a9aa97f2c71819935c6b56633920500006b35fb6c8e59cee7494d0ec8c43e0c632148b40502a13860f2c024e2e6ee190000060300657ff8328f6429a9f308dcf8b1ff4fe5bb5f7eea6f1ba82d860143248b730300000cff0f0cff0f9582cdbacc3cc04126bb6229982c85f691bca75fa96c276bd0397e288f99a4a35675ccf59893f5c93114e3a03dd426fecf47076cf4b22e0f480d61272661846b7ecefd1894b17e07f00f3b9738b1ecceabe058a8eb958545a9b6df90b4eaa5b257a80f3aedd4fed6da78e466993e439adee8903ff1473b89ba1fc8ea812581b91234431527e5bbee8a9ddb47565a5c484e874388872acf329b229a40b006cd1d1e25b80c835a9d64a8d8d34e0ccdaf28890ad85c12af0561c8594c64c4d4b5266a676a51f5b5da64d814a4dfae5d94881dbb0b587897e1df467343420caed6bd013050ff334d5b31e0ba621877b2c87436231368997aef17ece738bcee3e41f0a19e40ce06eed5cdb94e55042ab828f5030065932177145985099299fb26f303cdbe01058b9f0d4a5b4f5b06c5c5a2930100000cff0f0cff0f8dcdf91bde0bd46df72928d69a98457c0b81c818a14d7508b9a4c6cfd55c606c2c8739bf32adecec6c95af0016e0b3dd64f780e9312ab634f17aaed274d7bf68a87854a56244c95ddc70593d35adb2168e47386e47165c2dc07005e92cc40b222ce19356f4cdb223bc39375cb34298f829aa8e34b903097ba79f103c4c9c8f1a0dce16b7a03dfd4a294ee943808cea1b05a59d9eeaed8de82aefdc194b5bd54136c7aca3218bad32a6b32b0dad0905ec96dbedcd5d851bac56cccad13c93479d869dfa5eecff57d964e202235a506a24e62ebe72b7e6aaee7f6118aad5ba7f0d1993cb7244d193d50bc2b0b25ca0196d1186550fa12eaa7adf2080c0f70b24f487c63bc774f3f65da184f56f50b8006d040069932177145985099299fb26f303cdbe01058b9f0d4a5b4f5b06c5c5a293010000000008ff08ff99559223d0f26e21eec8b08f833a4c554d8f5866859c2e88ef7fdcbc46c385042cf967dd36c05f45967bd704925a35460cd4463302aaef13c114c88779af887dc6da1434c640ef39a4c89fdce67497c79069fb0d58bae0f5388f677c0bc8ac585910ff2fa0223d377da8da1ac3c23ee45b7616bb01b1d87307a1ee63eaf16b9409edb415d2c0f62c5bb32b236209ed07215ade0f6c94f2f6537ed3062ae85534f6e505cb30a606a7a5034809059a4effa98d51e7f19babbf93e3910d1309d6a401f71042ef88f41271247009c5aa8a2bf6699df53bc3d08d8ed26188862d79fd0a757ede313fe9974679c39ceeabdecc49302c508b5f9d8ec4c965a10460ea7b83528686bfd1437c6a5021bf3c31dfa1040069baf24c3e0e1bbf6a2e16c4787667f006698ef4de217e689d9764b5e362040000010008ff08ff834ed79efd40cbc38595b0221f3609756a5dca5d1207dcd5c5d56fc41b06cdb4b913f8d0b27c50d80e5a72473af39da058e05a02d903176e76290393823bae2d185462d679771cb16052c69f28d933989324ccf191e743a6ebdd487ca128be281064e0b8c23129c56307d9fb142f936ff51ef93f951dffbd09f3747178bfbfdb18c527aa50a971991bf372ba140f7f87f8e052a9055d34c20ffbabd9cae58eecd9bb3d22b2bc76a278086735d1357498b6476e9d77efca869413cff08821327bf160860bc5d170c892a1ce94e0878fd653a6e0c55c2c38d77d80f700c7042a8f066038777aa5e9bd6279da7884cb141c19e25bb067c620c794516802826aef3b553488ea0503b5c3bd306d371745e55803006b7ff8328f6429a9f308dcf8b1ff4fe5bb5f7eea6f1ba82d860143248b730300000cff0f0cff0fac1052f3f81238f64a6b1270c7d0d4d34879401e547e14e0219ac728a0a8b07e4b65817009ff0b566e0dc71219dc4517a8dd28829d265ca8bd608ba65febc9f5c221f3ef271de929a8990bc1d47d0f7fac8181531368caa5b6396640c0ff095cad13c9027e57cd6e492da2386d3fd9a4ac1c5c3c425dd381dd15c41345813dd9160402ce5f8b6f0f5bc85eba05afba70b64944c8687a69654600e37df8a93b1f2a1466ec3995af3c1c08d56a41298119b5ae42dc2d24e0e8fb76aa88bcc9e2490a4b6ecc00024a52c2864856a6226f5afbbd04d073126027a3fc854f0d9e6616111a448fde5fbb2905f231b88fe32f167e47d9ac1f6edf4dee8c2c175fcaeb38ee63a5e328863645672eba9fb4cffc5803006b932177145985099299fb26f303cdbe01058b9f0d4a5b4f5b06c5c5a2930100000cff0f0cff0f9180ef2882da8d679bc0570660320450bee36b65abe36b7939fbb40e097099589ea806de7887b5c0434736982cb588591d0bced314465fed65f1c0eeec5274137f485e7062bb9bb32a58be7da7d500978301bc64cafaf5f981f5093b557250d7e5095e75da53d39d4315ec6d5c69c821f993ac34beda5e8650faa517d00f749002c01cac1b3553a78c0686c3d8c8fa7f5c6d7e3072085ee6dda8f6ca5b9895d1134a94bfd6f5c3a4f82239a3bd34ce44a600957fbb103f3b085b6093fdf32bdea45fbbd83f5df4cf40915758baff09a50bcadd6e4f6f9702cabb56246a70eebc09cf9472003f890542fc9302f884c563525e8b2ce64db5cd20c8799aad85c5804ad5971200094e3abbcac8188dedd92402b3405768476f8bf1582ad0732613dce59d2535d7b647b5cc02dd6d3bf3534ff1d3be298090dec497009c5298871cc0de0f87d15ba807c87564e83f331167a08c8d8c50ec14f01f7feca7217d23c810ed64cbf993d7f6555462497ad54009a25d0200000400948ea9029dd86420179858601679ca6069090be7c18ca4548cc531e4e7293b149f0ca3cd7fb4897c91efcd297826c40c0077b3e7302f8bdd131b51f139ec921eaf9511f8302a058912cf2c9b0ee38129cb7907a09a8f8846795227d3028d70430401000200030005000100d845b857ac5a86947ade6c9de61a262c4a6766a12de0583174f0ab4631020000d0f1115da8bc9731b68c4e0c122c8a18c99aa558b3595b6d83084d689a0100000300000003c3d5d80678bf09ad174c663b63168b0eab63fe67e08beb14985a760ee889c4de606434779c561a0f5869206a2f595d506ae571beb761b5c0b4d97b875d6657c1364261341e7946188fa3d86a01dd2ef976b2ae55209f145c307a57817e4bf77b010703000500010000000000000000000000000000000000000000000000000000000000000000ffffffff016affffffff0300e1f505000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac209db40600000000016ae0052d0b000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac00000000af0300d83a000017ec1975822e6df0634548233e259bb9426804b11ccf7a451d1eb94492024bf3962b2a36c62ac4e4849b7d2fc39b2b69a35eb0c6c402615deb393150782ef05901948ea9029dd86420179858601679ca6069090be7c18ca4548cc531e4e7293b149f0ca3cd7fb4897c91efcd297826c40c0077b3e7302f8bdd131b51f139ec921eaf9511f8302a058912cf2c9b0ee38129cb7907a09a8f8846795227d3028d70434853359fc907000000000665170967209f1a2f864241ab7bde777df4da6581b23fa26c9e24bae464a400000065d528a90de22be70ca2dcfb01545ddf6cfb9721da0be927ec7556cc5723020000692f203eaed9cf2bd9841cc4a9f2942abbeacacb16999e1595467f4592850500006935fb6c8e59cee7494d0ec8c43e0c632148b40502a13860f2c024e2e6ee1900006b170967209f1a2f864241ab7bde777df4da6581b23fa26c9e24bae464a40000006bd528a90de22be70ca2dcfb01545ddf6cfb9721da0be927ec7556cc5723020000060300650310d248be594459720e593883af1b76a2f83d97ad9c07950eb83c54410300000cff0f0cff0f90f4836935d1e65373e6e76cce68aeebd8bd65a6e193a6af19a459a454b83a5764fcb8ff92080b71551718a9800f25add06238e59d9fb056edf189a656e5dde2923b04974c6be3e01e1e4c48d7d904068aaf373d3de7bf0f8d3cc65d62fa170363a299ec9678911e9b8c5ee07b1ced4bb8ef74f27149b2ba8b01bc06b20afa0413617287fbf9954c76a44ec23858f539f57739db0e3fc98156a992d88e16d5a9041ccab946a8c4be49d9c1c64584956bb00170cb8240ac52b9796e45bde0196b8a50eca357b74b69a2eca8f8f2ca7942a5d8599048c3d23e84633f9e8eff59620b1e568f62e9be5d35a78aca31576c15a5a107ddf9e688556a716e582e6afdfba9fcd0ac373db29bf796a61c24718a6e030065a7555de589f408753491157e49ceba86a2a4a0354aa3719f637c2ebb670100000cff0f0cff0f8a9db7baf74cb5851820d1126fb371ada572b8540992fa4280009c818bdac6e8dacbdff410abe86c7e229074535fdd9e35c5f8d480ab73f5730b798ef9b79cc4534f29d7633cf789e46112f0f33251b3ad16ae52542cee06136cf30faa83f8b7969ee47247808d097c76bd0309ac1805bfd2d9f798e41d331e23469050506f3004f08394d3e29b47ae34effa08669f36452ec67e1e6a2690f6ec90103172b9e6fb2642cd7b946515e32eb32a7521fe4e91c195b81d31f4f324849bf760de418e170ae30a6027cdee15907cf172f6d187154b1f264d8af999c047bd72102e53f5092eba90ebe32dfd161f89bf5be143e303ba1b0435bd363527038d5f7d2b097756b89edc6649ec5feaaf84a6b9e85b81040069080971c74ff8fbbb99ce0a010848bed36ea2857d8ea922218b983741aa010000010008ff08ffa88bfe41048d38bc2fb667ba22b74648c62eb042927c0a21679d1991f1fb5347e5c0ca51e57af14a4126ed592cc5b30347cafc018cfbdfafbf04b398b559ca2c082ad0e5f4c8a8df50b39ec29d5c160b8594bf7409ba63e60ce4bd9df0e33917b3dd3b1ee99b4666b65a0188fc0425633fee59e07600e7431e2d509567f003060abea24502ece5f02a47353d400eb49e6da91582514d836a044aecb7e66538cc36febc07fa82d7cd244643ebfd06e6cdaf05c0f414a93864771095f99f89b5aab8a72a33737ddc7ccc8065639211e9c75e2bb8ec40b20bf2c1f345743fbefb6317aca5db9270a4c3480de460d882c267e9bc44c9cda0e04464cd98dd1aa6789bb6f8140f9488aed79af1a5ee0577e773040069a7555de589f408753491157e49ceba86a2a4a0354aa3719f637c2ebb67010000000008ff08ffaf2c36362240b34b467b23836e373f039bb0026dae70f54ee504af2d5716d6fd2f45c5fb670de7ed6634a127a9963a40b8d650969c9767d6fe2a1d8e31145e6e67dea74b12222f941217008ebd6b9f5a91bf72adc7c7149e3f17ba6b0c8580054bb867d9b2cdfa44739783e27207489dbd84bdaaf2ca4f318166bafa8d1956ac0255b5b5e9352d732d80e789b130776c44a998351bdf5e5d77e02a1683907f06a3d2d9155a2575d50551ce586daf9227807d9a62951d258014cb7c3b264edc1cd8e0438eece8432817298baf18ec49ba21632e3f777e101b0bd28728c56f310c0419f42331e0575dd7c0355780eeed3fb70f8e83619551b3fee162cba1be161e41e539117dbfedae21dd6919c67fa25e03006b0310d248be594459720e593883af1b76a2f83d97ad9c07950eb83c54410300000cff0f0cff0f8e5966db68a2e644a8e2dc6c8f867834fdb0f66f2e95e049a662b924da84748bcf89ae3d309ca1ef125f9ec877dbd3dc32969347a3eb4bb98dc5bb7930c79bf6fa8f5883b92b3661b746c416194fda208b28cd4e829d3a6d61f0863603500f7a061df37e727108c8257103a49bafd9f5045761a8e20d829f592eed6c6ea5978308029fda09e80d3485d8dcc78df62f0c5616aa018910d8ecbccc5ab53af893983f0720eb7cba6f1a4e5e877d5852a8f986ae6ac09763153ccf24536b451ccfcf8e7f72a2679d6e5d8041ba22c074f69a083a90d93d2c4dac5b927e006208c2fb0661f88b7218f592f2f5626ebe8e5a4f9b7e17a2c7d5cb10a6cd36aaff1731b232d3deb171ebcaa0b783e14bdc5300b403006ba7555de589f408753491157e49ceba86a2a4a0354aa3719f637c2ebb670100000cff0f0cff0fb76b68c72ca6f033417795534133326b8cf90ae942256bbb15037a7104a2a4781e5d2f494393e62ebaf34972ac67af311049450c1a8ba936cad5b625dd16b9100b1b6f605ca5d3b65ce4ec5b514f760a88f2ef906d58550a231aa613e079604b10c3d5776fc03194347127686715931858ebc6268133289765ebf2a58f230a9f186f95316d59aca8a10530e4b2f6ab9b6f95d0ee3332965e62c49b156abaae30920b792cb04624ff8c7ae6d11f2067fda77ed56096ff2c7e8128d97d1d14923f2dd88b15db4d1a3e76925336567fcc5a848cef5efc841ea9bb7281c1419f8e020235b2323bda5246d023d041f10d01ffaea81c6157b41a83b3e850f7682e005ea611c81eac3c110faa2ddd025f086d97028f6d916a54e68452fbded9ce8cd093cb32b189c4716412ff0f8cd83501d8a693d9e54f5cc15bbd1fc9dd97bc715a47da158db538c6a8d886698fbf013396f5ee4ab46ad45fe2e0cc02594d3b78a83336b755bfbea014164455137f996b8f2ca9040100020003000500861676ae54ff9756dcd81c69340f50c2416e09c07bb622e9e73a3d32b7baffe15a56f1618b5766abbcf86cd6be87a7770e23cd059f5c5c28af6807d904e2248912d3755617960d4cf8c7330811e74f78f2ae60e852c250813c47d718c2c67b42020000040001002beb0fee25c3ecf21903d72fd138ce3ced285be61a8abc91434979fe64020000d845b857ac5a86947ade6c9de61a262c4a6766a12de0583174f0ab46310200000400000003511c6bfc662769be072061f1a400277167e954c4737e68ee244f514a9b013b4d79534b64ec7d2e2e7d1f52aa75fc70741560fabde724ff54f9bb27a48ecb31d5f6833b08a6882e62a194cdf2478bc7b4057cadfa4a8e37d6b35cbe0ce56b598a010703000500010000000000000000000000000000000000000000000000000000000000000000ffffffff016affffffff0300e1f505000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac209db40600000000016ae0052d0b000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac00000000af0300a83a000017ec1975822e6df0634548233e259bb9426804b11ccf7a451d1eb94492024bf3fc42737a1d070a4ddb93fadf15ecb674e41427efc9f9158172adb7a2db24cec4008f6d916a54e68452fbded9ce8cd093cb32b189c4716412ff0f8cd83501d8a693d9e54f5cc15bbd1fc9dd97bc715a47da158db538c6a8d886698fbf013396f5ee4ab46ad45fe2e0cc02594d3b78a83336b755bfbea014164455137f996b8f2ca948dd575dc80700000000066591ac84a9e7da8c16013a566392328add23f5c079cbb958fc5a12eef80720000065c163b459dabe70f9182d152d925f0e110b3a8da66d3832248e5d55153802000069d528a90de22be70ca2dcfb01545ddf6cfb9721da0be927ec7556cc572302000069e6516933639d964af037ef537e21b56b692ae0310ba29f4957c993fe581300006b91ac84a9e7da8c16013a566392328add23f5c079cbb958fc5a12eef8072000006bc163b459dabe70f9182d152d925f0e110b3a8da66d3832248e5d55153802000006030065122b4db043e0b508b30961968fdf5b1a9aa97f2c71819935c6b56633920500000cff0f0cff0fb8bcb5ee544de23d00f1a5a4f9bf43c23bd74269a19421a6f7eb99187681aaa0fd6cd02c420b9fcc00c26cd8a1134c1071e3dc96746c0b5f5e3b214d87abe4d5929d7ba06c44c378b29a5351a0e1fa8d9799ae24cd085f42606cefc9c3722dbc0ad32450e70d06b65526e3cd29b53de6c393283c6b26a4a73d15c8b6b4425f5005cafd7269f0571aa7495e4f09f8b3fa006ecb42b3843ec665f27ce196924ee3881ce3a264f2021cc87a1b8ea5d6ec168beb798016b728918f44aa5fa96a3e447d56e4f80db23202deaab4fc7cbf69d38934d4e7bc8c45a8ab4a073779ccdbb014a244915571c7b59b55517d71f67b6ca22402361639e93635fd29519168af587508798ab3a600d80f77a9341ab8cd4003006535fb6c8e59cee7494d0ec8c43e0c632148b40502a13860f2c024e2e6ee1900000cff0f0cff0f87b97ad0fda1fa84388407855ff560bbe5e3e7fff9f49ec5360d5c4a7ebbcf5f3ed896a224d9fd86dbd6e21f030b3e4d57e7923b58fda2c0337193e0753662fb81e59b091ca97157bf01ea0fd261b6dfaf8a1b778d9ec883e406bb41ef01f2a263df6f60e1c36d36b80c87d8df3bc5ca022bdf98794f2d10f8bac20806cdf58c07b4a350444a0728d9b30f1ab48520e24bfd00d7f48adc2d712b163002439acd6a74f9779f1a3de3a8c2f8c6d16d0d5c950fb679ddec173024b5b87af8caebf89ab0ef847cd8b10152daa736875abbb8984434bfc63f904043ec999ce14ac93707e576c3db43f84eb02262075d91e3b50c6d809d4b266349f33cab7fc80d60f623541313a195430fc095f77b129ab3050400692f203eaed9cf2bd9841cc4a9f2942abbeacacb16999e1595467f459285050000010008ff08ff979a30516f4178a844609ce84fa1b27169724957bc60a22e74eae6313a5740ea33c0315b5a768ec70d647d07ed8cebc48d87845b0951ac016a0532b04a72863ad96be86928f28c2299e9471310b57aaea62b4f086fe70a3031beccee5e3897c1af9a7138db6aab4c5a090b4349fe2dc4577a225c7bade4be877e66a48db3b217188b77d7f715ec636753bba94b8d84d81f5ba291e11183163bd72e97d58148a86c34ca44c22a2435917e441c6b003088ade4ad81a4d2414daa8252569f305ee6839f344ea3410229d011b3c1f3960e3265b0f06dbeadba43974a3c2a2ca12fac14a99318cbff71d8d6bb8885abc671466b82f0f86154f6eed5c784706f45cb4810102653f667866d047bb52b717facd904006935fb6c8e59cee7494d0ec8c43e0c632148b40502a13860f2c024e2e6ee190000000008ff08ffa54767f1205375cf327f4bb2cc8b84dd1a703e5c7089445f95992c8ce3a3e90a5eb3267ef02e6ad6beae503d3df05d630c11773d8be9cc34afc8df5f020ea36b0e8ec39d6948c26d3496c92592361603a13ca8481797ae9359311f1ba9e5144742db34c0926f7cb519c04d1ae6dc54fe1172d07553f65f702a39f1f3a1cfc1a40b6fd86b4f46cc7d7cf0fe9be583bdafa510ade6730a15942b7d407f44efa584f285fcae5a5940fc0b37cedab60efb6b86bbed7349f754a30e422e6e66a9085bf37d1b259168658c73df72ce8faa52cff343a4fe570e030728bea6f6e5a7c27515dcdf2297c6fd7df32d7b6ff9daaea127489efe56381ddf2844ba7a42bb7b1bb674aaaa9e1e9a78684f4571f079f1dd03006b122b4db043e0b508b30961968fdf5b1a9aa97f2c71819935c6b56633920500000cff0f0cff0fa3b19a9497e9d0864c0baf811349b17d5c4944fa3f8ce1acb2d9365aa15dfff11233bb44dd49d4d6452c26c4abf8a46f6034cfb51289bade7d1bb244ff6748a4387918a26cc5d5e649fa538bf07aa79493632aaf51c69c3b81973927dcadb9f786e54899e52eec27aeb20e5387a61ac925566648e5024b099675371ee6f3ad520855a634e9592777dd92522a733d716c17c0db3d9a187ba549ec34bbfa4bfc2a440e5371a3211abd9f5d0848b8e09833827c70e271e691d7c40e722f096405d8b48822fb8f574323b99df5f38fbdb6226cf328244fd0eab2c3fee821a1221e320dd5588d8e54f1a512919bbc032db2f2385fc31c903ddb2f0ad3aa92fda8ef29d41ab8fd8582ae7e69de815901f1541803006b35fb6c8e59cee7494d0ec8c43e0c632148b40502a13860f2c024e2e6ee1900000cff0f0cff0fb330001213808732b8f66ab95721b0b52cf9a5e1baf8c67f91cfab955737b24b3871b66089efab73c07ebfe33d7ace78e72c7a2aba4ebaa6901a8682da2fee8039fd2237c0a4b23af50ae7265cbcf028a38f45997176e48b97476cfac49041f99ad2a9c2986513f0f38515811bca01aa4a3fcb5ef41db148838c8dc6e79498350cf3bd57ce24e949e4951635f20b1df0236a8ee4789b492ac486e54908c9c00fbd3edbe0deb5d099cca922bab4a2d50c84eb97f4d69a778cc2253090cb6bfe8ffbb8920ecc6127ff7dba6aacd2e627f32c298c23b5cc8947d978c64bc477522b0827ad81d56fc91116853bafc9edd17112c48567c16f922210ae1ac7a7687e92c81ae3bc046bc250373fce12ef4fe5870290cfc13468eaa07f423adbb5f95b3b80dc4231df95f14b1df4384f8496d693340f4c2ecb334e5a5d58a9713445612754015cf1738169a3d147af6ad53bd2c06f31077d8ec92c2063157b6d17c9c6e2490620b05d342e6a1447d7aaa5fdf52c3e020000040096c955684e842d16ba794d8a57114fc2c7cacc1a954b55f7a0d7725bf129f836740e918d2da609d0f9a55809e552e94c1414840fea028aeb7c28cb43fcb0163c61be70c90c4f968749ccf7d610bee6760d470ac4e4b41574807d6b925702ce7c04010002000300050001010000000d7f0f02020000000200000001002e3df23eec5cd6a86edd509539028e2c3a3dc05315eb28f2baa43218ca0800002beb0fee25c3ecf21903d72fd138ce3ced285be61a8abc91434979fe6402000003000000032ceffaf495f596ff501c54447273d0a6e12a8f48f821f3b9bd2c55abfb92c6e4494c32aca0e6c0ab4038771dc0893b08f8652d277498ed0d04f0c8ad791edac7b686db3f9c285fbc3810b8be77eb1976e654b80dea33ed90a279a4dc14d8c762010703000500010000000000000000000000000000000000000000000000000000000000000000ffffffff016affffffff0300e1f505000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac209db40600000000016ae0052d0b000000001976a914e6224d97f439b9d68e9455afd8b57da8c9ae3b2888ac00000000af0300783a000017ec1975822e6df0634548233e259bb9426804b11ccf7a451d1eb94492024bf3f64fca8d91067e0e7359577f63bbe941c2d04fb1bf5a5502f5059970a235c8ee0196c955684e842d16ba794d8a57114fc2c7cacc1a954b55f7a0d7725bf129f836740e918d2da609d0f9a55809e552e94c1414840fea028aeb7c28cb43fcb0163c61be70c90c4f968749ccf7d610bee6760d470ac4e4b41574807d6b925702ce7c48677a1bc7070000000d0200a32e53687f4f5f86f5caaba1313254b7f268467a14d32d5b54a0f368d1de097e58ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437ac64e2197c17bd485c79144e1cb93249c7cfd0f089ebd5fdf1ec10d4b1a7eaf927ac27d202db08da1b95cc171185fe9e6ec4350fcb158a1b7c75173e596ec8281720028a6598625010100a305bdd200961d8fb778a34a1437dc6965917f95123202008dd0ff2387ec8d3c046d884938364e8ebff5594fad45ba04b2824c480cd1507b58ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437ac74e21b14da4356a01ff5b84de2aba00b92115aef8cc209133689beb04d832ea4fa658f3676b71ea0e0a2579bc7807c55a27e0924c0aa06410fdb174fb31f7a4a6e990fd210ebc010100a3059c87916c467ba32b3bb922dbf537cfd4fe67220c0200fbb6204dab59f8f0a704e0ff7467442f91086bd72850c4f118dfad38bfa57b9358ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437a554e21acb433166abc037c07d59d0a72ec92e6573e2bc8dc5ca8050e8955cc721d2bd57e8abd7127a9714445923739457389ed3c5e67b2b88d4577eab022967d3c30ad4c83414c010100a3057c0f03367c5387b91ddce1000181ab0e3b4c8cc703007c75052e5e1903d38c9481a20c709a6bf513e9741fa944e89c22e1cb6f0daa6358ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd1901600000103000101010444437a574e21010101010444437a578f30020101010444437a5705a3824cf9115d08f11baaf8b30a18cf68ea6346ed21c113f8c89871d6332cee437aae0e8daa7efec0c9f6b307afb93b2251d66244646f1e629b1c0363147090ce1bbd799a930101000ee415f074dfc2e772ddf429ae7bbefaab86f56a0200ff4e114469a2e1012186175d7486f63ee72db8052ef84533d9f79d1b79061c8158ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437ac34e2183105666ecf30efb6a7de06cf6de26e5d30a46e4c99017abbec4b78f00b7f548388c4e88873933339c4a6fde6082acb7bf6595e5c5ef245632b42cc9466dc865dd07b30e010100a30520cb34a5220fb2d6ff413c1ece98b9e07a3ea3500200095d3ac9b3f8fad9e7dfd09459ddb6f9b522c7ec6fe603c087c57ead5efe55d658ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437a564e2186b272be41917a82fd7b62e3b8abcb7f597be82d92384f11fcc3e3a330c380f9f3e901f7adcb3794fa0bcd1f724d6b140417a35c6fda9bb1a253e9bdd90c3efe633fface010100a305ed56e33175c3c51fc4decadf328a667ec64bf1550200c96ad28409617f3ec0203e188838a9ab8288321abbc6d0152111f3cb58dc92ce58ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437ac14e21a7e4044942ca14e4d6cd239e5512a0e1f20bbeecd675c5f69f00f2c777b5597636768f14479d6b91f168fbb537baa4fbffba6b1369ade1878a2ea341fcbee084155c4b8c010100a305a65d6f697408628c301792fe8f4c6208d7bd7e7202004b1da95be3153c1235d1212839c23b11421ad997afc9fea3b6efe679c163aff958ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437ac44e218ed8d3f3b3ed36395bfa002799945e7256086eac6879fe75c88ea5dcc7a150f0291e5392f40d04d6ac593ac8e5bc5a32e28c7a36dc0414892f61462fd1beb9fb17b6d1c0010100a305f4c52128f8738c8cd2433b644a7c86da2c22aa0703000b1acf76ae38870101b3ab55a430086141ec2d11c565465857d183e85fbc076d58ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd1901600000103000101010444437a584e21010101010444437a588f30020101010444437a5805a38614715b76b060755ca0a739f93e106cf29dc0ee571108aef48ad4592bd54eb1d8206d810e038f6fc22a6da9a2178e1b5e601f9b82cd7bbe1d060fa5a6d3f726b0e788f50101001d4d34a4d0c71860bcd3bdd9d3400717c84638390200cb2afa75d1318a93a96d4a45c1261378f7dfff043acd96c0ecb243fac566a22c58ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437acf4e2187e7765b4970189674ea2386c8afe86f018ba5a71b3b21ae3b5c2e785de89a1b93deb37f60438efb0a84cd6275c86ae32f6681b49a26f341fedea112cb836610192f8e33010100a305673b8bf25d7a5c9fe4686c296524bab0a806656402006b87125985504d651e8075207f25d1ae1c24fcda33d7fb15bcc20ad82037998f58ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437ac54e218777ee10e55950e47ed222d4b0f84a666b2757eba8006e19108f3762e3f984ebb0aea43b0a814d29e58564c9141252fb52c08f5f623a88bb011b0064c0b8591e7e8a7990010100a305118d9e2dfb1d16a43c53b4c665803098f6b91d6902001dc99231b2b596c2590c2b4816ac7cb93e4238cdfad5742ce512f78bf964539858ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437ac04e21a722d7a615313a8f25f42eb0210c33235cfd5610641159a23ff026114b4c857c1779189f80c1761f595336f9ec76bfeca51b1523c20b88a0bd93ea12bc3c03edc5524023010100a3052a5d1bcc31167de459f07174bd51f7ca21d6615502001dd3437f29ef8da5a629a505b03f6ee61b6a3cc8e100537f61b62ab143a2cb5158ff47d94d6e51a68436bbcd85b712ff140fd83f561653467aab3cd19016000000000000000000000000ffff44437ace4e21819a804d27c71be313195ed98a03fb883b6ac2cd71540088989614973527f9b1f7251f53397d3bb8f8d0589c3ca1d1fb9151765735702ad18aa0f90a529a92b8c6d3c4fe010100a305f2da79d8b78a0c2653e764d7fdff66b1c74a4414000a030065170967209f1a2f864241ab7bde777df4da6581b23fa26c9e24bae464a40000000cff0f0cff0f998814c3c169cb2c0fcc4d0c025e2b6c46fe9b9544f42c887ba4b69aa037d4267820b2a806ba15316d68e2846b498a0b9a9d524593ca3c8dba9980745c58cc1b52ab29f557deddb4af14f247289f5784a866777436ce63c3b98b6e211799ebbc294f5c7eb6c7774e5f167ea20d828f180e45eba9927dac9e7e255123492cc5cb009ed6c410c375087d9ba1e33a158340d88c0b830d9e26f849268ecf00edd9ebbce16423aaacc40760678249eeb1487cab747cf59bfc9062da4915188d3908107bcdfc473ef871ff76834ae3943421268233cbe98245543d31370d17d179325c0902b694e9b1ac3a7eff0404ea3ed8eae150ddb4ab8382de7061a93551ee955657596db9c38e6e5810ca2943694692ef03006591ac84a9e7da8c16013a566392328add23f5c079cbb958fc5a12eef8072000000cff0f0cff0f80d454d8941333e2407c9a1384db972bb15598fba312126bb94d82e98890739465af0a30d836c8cf3161dfcfaa990241a9095a71b432772f8c16d0ab4fa6e7c1470045198db949c5edf077423f401249889ce8e35797ad2ef91ab01cc1aa508849322a1670146c1db19beda72311e9172a22769fae518c43d3edd757fdec1ab30278e3fce0fcbe3ab0e52fb318ae00b5575833054c7226bb55c50dbc5f48f8f2e96a6ca66dcc805216b348cc2f28b9f2b7c14b8568ac503dcc4a3d5473ded4d163896d0222254fd953d8b5cf2b92c4a977d987385d749edb613ddb92e09854c0198596937ef35e8679df2d6bffba1114dfcc5d905039f7c5b74676ce19df0396155e8edff5b25aa5f7e49e2742deaebd030065c163b459dabe70f9182d152d925f0e110b3a8da66d3832248e5d5515380200000cff0f0cff0fa05baffce169a525a257742457e561d102b1bbe78eac41f4db73551a596bab670c61ee76cf34e35f8177534eaa08cfb9bd24dc89c69c1ce5fc8ea5c87bbfecb4158b909b4a065fe4579e6a131b8af6f88e4b719fa863201a656bf44f9e80dddb1c85343efa2a18ba5283b3261103715c080d84def7555bbce67a70f4d7fbded119172b651ed64e02ac49b48aaa03c044d274afd2fa0ff8750bdc1b847a5b7e106175bb898c354fd227bbd093d3c0619e95fd166e1074b22526b68b5859c041b32307d793eda4e83a02de80757495cf15b5424c12cb3e5c4a0a52c05ca83fdf8108e280ab3ac4bdc0ca030308ce69ebc9966e5def73aa53d3a83d844326704cc795a1c2b564c3c97a17aaa40129082589030065d528a90de22be70ca2dcfb01545ddf6cfb9721da0be927ec7556cc57230200000cff0f0cff0fb13843b654b0ccb5d9a6112adac2a6cd0d9095053fd19aaa3a008a7f705cfc2535951edc0f500b4af5d911225545378188af5f3211ca970a914f4cb54200507d8ed15318d3f2a009f7d6ad51b7a34a6c8e19208d8f597871ae3bb7d293feb1db4592405dbb4d19f5fac66e5f2fe3986e9270bb4b267a73707098b38cce578beb17c4e54a3c73b54ec5d3096d952a6278b9ec9ea02101ec6524a09b4569fa85ec5585b156e517636b0aa87e6780994c36b9c16f9e67526a25d10162854c11f87407bf0198681443b0807726cd6361e418416ac5934d78ad3c3ade9f1e8be2cfcb12eab54c1738a7b18286e97ef6a4c9e8997f17cf630229ba5f499277b9c678cfc0c21455d4ad3ba1a873c78dc7c5be56040069d528a90de22be70ca2dcfb01545ddf6cfb9721da0be927ec7556cc5723020000000008ff08ffb25523843231d2db7f47739782d2a63a950eb44ffee75af2271903fad983264e41e318d1b983156e18bb4bfecdc70abff3520e7e9669d70b21b18c7ea6a2e2045e18a7d7280a6489c5b63bff340b1b8d959384084569fba9253f3c52afd71b95d36737835eb9467a57c5a76b9c7d45d9245cece7abaf1c8433caf0f3ed7e318712b62dfa4f11a5feb2b09cb61a588033dcad5c5a3461ca7d5119e8e57658474b95a3e7ea5b53c64aba6476107b911dfd82b6d007a07cfc078765d47899d6ba70a081d4665e32d2389a10ef28d3f264c4df60af0e75e00d677a5055d4b2006571070cf5b0cafe353cfd1c5f76c859f63864ffb266258bd13cdd68d626e1a77d068ed0e8c8e46fd95e1b074df7d20789b7040069e6516933639d964af037ef537e21b56b692ae0310ba29f4957c993fe58130000010008ff08ff8d44ff8c32b104cf4dded4789fbc2aa26aa1aca77f10a549bc5982ff986447a07a5518c0f884d7fe08dcb2c1af556e8c04e2a4626273765dbda1eae087f20762c43a4d1a37d324f54d49267187390e59890a115dbe60e699a5decc4c531a46019fe43ed80bd3192dd725a3427de5ae3d546dcece636e4ea1a349bccd27f7dd49110b8529ebc27202371d5b8d0d9826faa6cd07a35418d80ccbbc139a947c5aedf6325225f8e84df28c38007236b303b697624fdd934513830f37c5ed1a1613d6722ea7e86be184cb574cd8242b1a2e6b636983bc29e2f1096f2a5f67f2952ee10f516cbe5b4f30fd30d526f34bb60e750953b0581e84963990503622610d81a69b43e02beb660c19cf957cf6c2af9dcf03006b170967209f1a2f864241ab7bde777df4da6581b23fa26c9e24bae464a40000000cff0f0cff0fac2b90fc24d785e6e22f90179565817239a993723688e124071cd3ea33b52ac5234bad842bc763b2cdffe52e89d2805409519f52c77b109a8ad15d87bdc4b9d24f41076076ec5aeef69e6f758bef2ad98af729e9d575dab77b5c774f712840b2f0a237de79e834747455e38d78be930b21567a377d40ffb3aa056d45585b3a6d0125e4e707d85e8de084b956d0a50bcd312b2dec7317e018bcc55553d8282c2f1be47ef6163fb5e7d3de326d7ddeba5695bdce0f0d1a79e78a046115e27c90085773b85c02416dc7a1312f1b05f5ade85da0f2571bcaeb57e3b376af1408b7a00acded34c4be8bfd50562167bded82e82a8ccfe4782239749e12347b34bc69b249bb4a8d20293a392c1591cbf55f7b3f03006b91ac84a9e7da8c16013a566392328add23f5c079cbb958fc5a12eef8072000000cff0f0cff0f90a02bb6c9e62422e4a8142cbd661f70b5155a74515c309454703357d62a7a344b15a82cca4a4790cf33cde752b81791241d49731024093a0d8105ba6572f84f5c6ec285819b2a82e298aac4bcf07bbda557e6bdc28d34211428f3d5a0657300d961555cec69700db4b6f0d49c8c36f2c8c9653b2229c7b04c54c8564a4fd2d60f094beab9888e01e0c29fe85a37728847f279a98a14ec3edede89593cbabfffeaee5ab38b2bb87d3e8d5508aab7d890a0dab071e8df83a55220a3c1edd32d9d852b869f74e124e7f547cbcd8d91efa6a2539b8253683027063bea674a5b29900f281c54045b646375d1e0f64c479e4631f27ef0888271e099c7df6620da158c47397cc7319db18230c67e4241c2aeb103006bc163b459dabe70f9182d152d925f0e110b3a8da66d3832248e5d5515380200000cff0f0cff0fb97a39f483b7935680f4eaba7cb848dac53e77018820876bcd9335c0664e08dbe91721418e81cf5ff8be9ac347bbe98ff1367a10bf7ab58fd693091f549976a0c4ac6507f8ae489412336737f1d875d88d281080ce28284129dda303e32b5e4d5a83f1bb30b4341dd894b094f0577aec4df7a878c71bd448fa544dad354318d808c6c4399a282840d78572129a6a4bf1d451ecc7826e0134aacd46e695f0356891592b2601b0ab2239ce058d8fce3cbf90650d59db349c12763e3ef59a9d6176e8a645d125caf6d72c52890668b97905e9140629d350f3cd1db16e67c418649f15b9f4fff5e9ec93a4e0e15aee90b5993a9fd90ba392459131b429db8d658548626d943cc9d50dbead87f47db909d79203006bd528a90de22be70ca2dcfb01545ddf6cfb9721da0be927ec7556cc57230200000cff0f0cff0f87b92c7061c4fce9c0e26f2e72c2ed63b4bb25a9d346fdb3c6f6af7c984dd0e2e407190629dfb8578ce25bb6abc197f7ac7653f227c6706b0da5ad232a7dba7769e9f5322940c2088046f3355b5c44908b183cd9e1d2f870389975db8f04afbe1e73bdcdfbdc8fcfc0f9c862156a0c7e0180c8db5b6ce0f06df031fb9d3a86a910728025df1087dd6c50d069f5404b8352db2bb24bef28fcd1929ac99a99a68a34ecd594bcd5cfa308e7f4268cf0c860b203713fe611911c815196a8d0716f36ac1f154dbd015a8b559f2950ee34d2e2f92980e6f364ec59f6dec9546d83a956101a1d6d15999b6ddf77f9bec1f503b3eb50c014e69aba04e470663945056ac9e448caba7ea3c221b5db3e7f8239065704a2e338a737439074d83a6843f4ccdfaa8598c6c3fbec9d2f7ae625ffef46c05ad107faa885b52b93c06999660742fa3a02974d8428167ff5c9d222e9c3c645006f366526a90d2ac3cfb2ce6d0d138480a006b0cfe2d0af33e8d63e76c20e60ac020200080093e46a7007c0189e6bb22598bec520565103dc82fe62f24b8d813cb85263b5d4b5b03a5369414348abd38bd9266faa6111e0a78ee82a82c751bbfb586235f3016df51e583d6de708c36fab5abfdf48c4f80d39139cfa7717678642847c4167dc0403000400050009008ff90de1d4fbd8ce1cae09411174b420a14edc951cddaad0712b1a95844893fceb75a973b4e245b59b250979a2ca7f73112b70d1d29caaff5dadac351aafd35493d9878305a38882d5d453ba6a6d6442b13da67ad24e293ea265c386b2c7c3c002010007009566f536c1a8e09e304435ffd3bb0e3854c66b9a2b2ea2f529546e6a3e9ace891a53a59632fb6a931674c1c10479b0cf1569836b17db4bb356b8e2e45b901226349548e3f8a23848a1c36d0939c91a3451df229343c48c8617ee3097cec8628c0200000600020400694c3ee7975038af4938cf5a69ea6a2171ab1d9fb99afd8f4bf3115768ca040000000008ff08ff89bc0fa5a797560e2acdacd45d1d61b82bd03c511205bb8356b1b502c322caf3ca882c71bdcea4fa2077058498685500abce0e8183ef7e02532930c948f202e8e355eebc996549220dd45dcf458e3fef980a45301accccb4064250124722ec40e3921efdfba7991140846ddd07cb008ab1c77f9d10c28a2bcb40bf8a901ac6ca0434bc9fb761f5198a5acb37a749d1863fcd258028a466d6f449e956237ba4b96deb71e8d9b266593d71fb5fe3aebb9eb0bead370575f3ac4f9dd8feae7993ce74563a8e539c1f48c1bc86b36ca4c927997e26d5441362b8e366aa6b112688e80354a32af9b4da2d0e5de1b11a5102b8fd9d3d63065cda5b850a32ab754ff110df97c8b337f0db5d528790e7b8dfe7db040069effec9e3c7855b3e34c95ee6eaa2ac502a79d17286e586a1c0d95174fc000000010008ff08ffb765196941549edffed3750810bb1806d6f6f8b8929c0d14625bb9fc81398652d704345d3a6e34f8593e5761a4d74067f0118103fd211dbe687b3956c78d64d42789c9eefd17451a0a33998bf4dfb8cf85b7c002f16b1bc5f536dcb970682d38c15cf5c7fe1e99ccd1209d7fef79fb7b2c75921234a304c5de441482c7426bc212d80e3fe321901bb941be7c8bb59094189ded875fe68ac59174fbdbdc97bddafd4e00fc09688c585bfe9f8355b9570e95a056a6d6075ce5c481dde8d602e6b9a7c1105efad0f251d7a6c083e7a93b4dd14c38a57b0eb86336c0bc1f4577417a0c009738ca42da7db03b67377c55c406ad6b040df9ae97410772b8c8cf592f0b7c54f52801a1761282f9b9d9af6f2ddc0000 \ No newline at end of file diff --git a/masternode-seeds-fetcher/src/main.rs b/masternode-seeds-fetcher/src/main.rs index 00a282881..6b1f68613 100644 --- a/masternode-seeds-fetcher/src/main.rs +++ b/masternode-seeds-fetcher/src/main.rs @@ -415,7 +415,9 @@ fn collect_seeds(diff: &MnListDiff, args: &Args) -> CollectedSeeds { if !args.include_invalid && !mn.is_valid { continue; } - let addr = mn.service_address; + let Some(addr) = mn.service_address.primary_service_address() else { + continue; + }; if addr.port() == 0 { continue; }