Skip to main content

fiber_types/
protocol.rs

1//! Layer 2: Protocol message types and supporting types.
2//!
3//! Contains gossip protocol types (ChannelAnnouncement, ChannelUpdate, NodeAnnouncement,
4//! BroadcastMessage) and their supporting types (EcdsaSignature, AnnouncedNodeName,
5//! FeatureVector, SchnorrSignature).
6
7use crate::channel::{ChannelUpdateChannelFlags, ChannelUpdateMessageFlags};
8use crate::gen::fiber as molecule_fiber;
9use crate::gen::gossip as molecule_gossip;
10use crate::primitives::u8_32_as_byte_32;
11use crate::serde_utils::EntityHex;
12use crate::UdtCfgInfos;
13use crate::{Hash256, Privkey, Pubkey};
14use ckb_types::packed::{BytesVec, OutPoint, Script};
15use ckb_types::prelude::Pack;
16use molecule::prelude::{Builder, Byte, Entity};
17use musig2::LiftedSignature;
18use serde::{Deserialize, Deserializer, Serialize, Serializer};
19use serde_with::serde_as;
20use std::cmp::Ordering;
21use std::time::Duration;
22
23/// The size of a serialized cursor in bytes.
24pub const CURSOR_SIZE: usize = 45;
25pub use feature_bits::*;
26
27type Secp256k1Signature = secp256k1::ecdsa::Signature;
28
29/// A wrapper around secp256k1 ECDSA signature with serde and molecule support.
30#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
31pub struct EcdsaSignature(pub Secp256k1Signature);
32
33impl EcdsaSignature {
34    pub fn verify(&self, pubkey: &Pubkey, message: &[u8; 32]) -> bool {
35        let message = secp256k1::Message::from_digest(*message);
36        let pk = secp256k1::PublicKey::from_slice(&pubkey.0)
37            .expect("Pubkey should always contain valid serialized public key");
38        secp256k1::SECP256K1
39            .verify_ecdsa(&message, &self.0, &pk)
40            .is_ok()
41    }
42}
43
44impl From<EcdsaSignature> for Secp256k1Signature {
45    fn from(sig: EcdsaSignature) -> Self {
46        sig.0
47    }
48}
49
50impl From<Secp256k1Signature> for EcdsaSignature {
51    fn from(sig: Secp256k1Signature) -> Self {
52        Self(sig)
53    }
54}
55
56// EcdsaSignature <-> molecule_fiber::EcdsaSignature
57
58impl From<EcdsaSignature> for molecule_fiber::EcdsaSignature {
59    fn from(signature: EcdsaSignature) -> molecule_fiber::EcdsaSignature {
60        molecule_fiber::EcdsaSignature::new_builder()
61            .set(
62                signature
63                    .0
64                    .serialize_compact()
65                    .into_iter()
66                    .map(Into::into)
67                    .collect::<Vec<Byte>>()
68                    .try_into()
69                    .expect("Signature serialized to correct length"),
70            )
71            .build()
72    }
73}
74
75impl TryFrom<molecule_fiber::EcdsaSignature> for EcdsaSignature {
76    type Error = secp256k1::Error;
77
78    fn try_from(signature: molecule_fiber::EcdsaSignature) -> Result<Self, Self::Error> {
79        let signature = signature.raw_data();
80        Secp256k1Signature::from_compact(&signature).map(Into::into)
81    }
82}
83
84/// A node's announced name (up to 32 bytes, UTF-8 encoded).
85/// If the length is less than 32 bytes, it will be padded with 0.
86/// If the length is more than 32 bytes, it should be truncated.
87#[derive(Eq, PartialEq, Copy, Clone, Default, Hash)]
88pub struct AnnouncedNodeName(pub [u8; 32]);
89
90impl AnnouncedNodeName {
91    pub fn as_bytes(&self) -> &[u8; 32] {
92        &self.0
93    }
94
95    pub fn from_slice(slice: &[u8]) -> Result<Self, String> {
96        if slice.len() > 32 {
97            return Err("Node Alias can not be longer than 32 bytes".to_string());
98        }
99        let end = slice.iter().position(|&b| b == 0).unwrap_or(slice.len());
100        let name = std::str::from_utf8(&slice[..end]).map_err(|err| err.to_string())?;
101        if name.chars().any(|c| c.is_control()) {
102            return Err("Node announce name can not contain control chars".to_string());
103        }
104
105        let mut bytes = [0; 32];
106        bytes[..slice.len()].copy_from_slice(slice);
107        Ok(Self(bytes))
108    }
109
110    pub fn from_string(value: &str) -> Result<Self, String> {
111        let str_bytes = value.as_bytes();
112        Self::from_slice(str_bytes)
113    }
114
115    fn try_as_str(&self) -> Result<&str, std::str::Utf8Error> {
116        let end = self.0.iter().position(|&b| b == 0).unwrap_or(self.0.len());
117        if end == 0 {
118            return Ok("");
119        }
120        std::str::from_utf8(&self.0[..end])
121    }
122
123    pub fn as_str(&self) -> &str {
124        self.try_as_str().unwrap_or_default()
125    }
126}
127
128impl std::fmt::Display for AnnouncedNodeName {
129    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
130        write!(f, "{}", self.as_str())
131    }
132}
133
134impl std::fmt::Debug for AnnouncedNodeName {
135    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
136        write!(f, "AnnouncedNodeName({})", self)
137    }
138}
139
140impl<'s> From<&'s str> for AnnouncedNodeName {
141    fn from(value: &'s str) -> Self {
142        Self::from_string(value).expect("Valid announced node name")
143    }
144}
145
146impl Serialize for AnnouncedNodeName {
147    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
148    where
149        S: Serializer,
150    {
151        serializer.serialize_str(self.try_as_str().map_err(serde::ser::Error::custom)?)
152    }
153}
154
155impl<'de> Deserialize<'de> for AnnouncedNodeName {
156    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
157    where
158        D: Deserializer<'de>,
159    {
160        let s = String::deserialize(deserializer)?;
161        Self::from_string(&s).map_err(serde::de::Error::custom)
162    }
163}
164
165/// Feature bit type alias.
166pub type FeatureBit = u16;
167
168/// Macro for declaring feature bits and generating methods on FeatureVector.
169///
170/// Each feature is declared with a name and an odd bit number. The macro generates:
171/// - `NAME_REQUIRED` (even bit) and `NAME_OPTIONAL` (odd bit) constants
172/// - `MAX_FEATURE_BIT` constant
173/// - `feature_bit_name()` function
174/// - Setter/unsetter/query methods on `FeatureVector`
175#[macro_export]
176macro_rules! declare_feature_bits_and_methods {
177    (
178        $( $name:ident, $odd:expr; )*
179    ) => {
180        paste::paste! {
181            $(
182                /// Even bit, the bit used to signify that the feature is required.
183                pub const [<$name _REQUIRED>]: u16 = $odd - 1;
184                /// Odd bit, the bit used to signify that the feature is optional.
185                pub const [<$name _OPTIONAL>]: u16 = $odd;
186            )*
187
188            pub const MAX_FEATURE_BIT: u16 = {
189                let mut max = 0;
190                $(
191                    if $odd % 2 == 0 || $odd <= max {
192                        panic!("feature base bit must be defined as increasing odd numbers");
193                    }
194                    max = $odd;
195                )*
196                max
197            };
198
199            pub fn feature_bit_name(bit: FeatureBit) -> &'static str {
200                match bit {
201                    $(
202                        [<$name _REQUIRED>] => stringify!([<$name _REQUIRED>]),
203                        [<$name _OPTIONAL>] => stringify!([<$name _OPTIONAL>]),
204                    )*
205                    _ => "Unknown Feature",
206                }
207            }
208
209            impl FeatureVector {
210                $(
211                    pub fn [<set_ $name:lower _required>](&mut self) {
212                        self.set([<$name _REQUIRED>], true);
213                    }
214                    pub fn [<set_ $name:lower _optional>](&mut self) {
215                        self.set([<$name _OPTIONAL>], true);
216                    }
217                    pub fn [<unset_ $name:lower _required>](&mut self) {
218                        self.set([<$name _REQUIRED>], false);
219                    }
220                    pub fn [<unset_ $name:lower _optional>](&mut self) {
221                        self.set([<$name _OPTIONAL>], false);
222                    }
223                    pub fn [<requires_ $name:lower>](&self) -> bool {
224                        self.requires_feature([<$name _REQUIRED>])
225                    }
226                    pub fn [<supports_ $name:lower>](&self) -> bool {
227                        self.supports_feature([<$name _OPTIONAL>])
228                    }
229                )*
230            }
231        }
232    };
233}
234
235/// Feature bits and methods for the Fiber protocol.
236/// Pair bits: a feature can be introduced as optional (odd bits)
237/// and later upgraded to be compulsory (even bits).
238///   - Even bits are used to signify that the feature is required.
239///   - Odd bits are used to signify that the feature is optional.
240pub mod feature_bits {
241    use super::*;
242    declare_feature_bits_and_methods! {
243        GOSSIP_QUERIES, 1;
244        BASIC_MPP, 3;
245        TRAMPOLINE_ROUTING, 5;
246        // more features, please note that base bit must be defined as increasing odd numbers
247    }
248}
249
250/// A compact bit-vector representing protocol feature flags.
251#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
252pub struct FeatureVector {
253    inner: Vec<u8>,
254}
255
256impl Default for FeatureVector {
257    fn default() -> Self {
258        let mut feature = Self::new();
259        feature.set_gossip_queries_required();
260        feature.set_basic_mpp_required();
261        feature.set_trampoline_routing_required();
262
263        // set other default features here
264        // ...
265
266        feature
267    }
268}
269
270impl FeatureVector {
271    pub fn new() -> Self {
272        let len = (feature_bits::MAX_FEATURE_BIT / 8) as usize + 1;
273        Self {
274            inner: vec![0; len],
275        }
276    }
277
278    pub fn from(bytes: Vec<u8>) -> Self {
279        Self { inner: bytes }
280    }
281
282    pub fn bytes(&self) -> Vec<u8> {
283        self.inner.clone()
284    }
285
286    fn is_set(&self, bit: FeatureBit) -> bool {
287        let idx = (bit / 8) as usize;
288        if idx >= self.inner.len() {
289            return false;
290        }
291        self.inner
292            .get(idx)
293            .map(|&byte| (byte >> (bit % 8)) & 1 == 1)
294            .unwrap_or(false)
295    }
296
297    fn set(&mut self, bit: FeatureBit, set: bool) {
298        let idx = (bit / 8) as usize;
299        if self.inner.len() <= idx {
300            self.inner.resize(idx + 1, 0);
301        }
302        let mask = 1 << (bit % 8);
303        if set {
304            self.inner[idx] |= mask;
305        } else {
306            self.inner[idx] &= !mask;
307        }
308    }
309
310    pub fn enabled_features(&self) -> Vec<FeatureBit> {
311        (0..(self.inner.len() * 8) as FeatureBit)
312            .filter(|&bit| self.is_set(bit))
313            .collect()
314    }
315
316    pub fn enabled_features_names(&self) -> Vec<String> {
317        self.enabled_features()
318            .into_iter()
319            .map(feature_bits::feature_bit_name)
320            .map(|name| name.to_string())
321            .collect()
322    }
323
324    pub fn is_empty(&self) -> bool {
325        self.inner.iter().all(|&b| b == 0)
326    }
327
328    pub fn set_feature(&mut self, bit: FeatureBit) {
329        self.set(bit, true);
330    }
331
332    pub fn unset_feature(&mut self, bit: FeatureBit) {
333        self.set(bit, false);
334    }
335
336    pub fn requires_feature(&self, bit: FeatureBit) -> bool {
337        self.is_set(bit) && bit.is_multiple_of(2)
338    }
339
340    pub fn supports_feature(&self, bit: FeatureBit) -> bool {
341        self.is_set(bit) || self.is_set(bit ^ 1)
342    }
343
344    pub fn compatible_with(&self, other: &Self) -> bool {
345        if self
346            .enabled_features()
347            .iter()
348            .any(|&bit| self.requires_feature(bit) && !other.supports_feature(bit))
349        {
350            return false;
351        }
352        true
353    }
354}
355
356impl std::fmt::Debug for FeatureVector {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        f.debug_struct("FeatureVector")
359            .field("features", &self.enabled_features_names())
360            .finish()
361    }
362}
363
364/// A wrapper around secp256k1 Schnorr signature with serde and molecule support.
365#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize, Debug)]
366pub struct SchnorrSignature(pub secp256k1::schnorr::Signature);
367
368impl SchnorrSignature {
369    /// Returns a reference to the inner secp256k1 Schnorr signature.
370    pub fn inner(&self) -> &secp256k1::schnorr::Signature {
371        &self.0
372    }
373
374    /// Serializes the signature to a 64-byte array.
375    pub fn to_byte_array(&self) -> [u8; 64] {
376        self.0.to_byte_array()
377    }
378
379    /// Creates a SchnorrSignature from a byte slice.
380    pub fn from_slice(data: &[u8]) -> Result<Self, secp256k1::Error> {
381        secp256k1::schnorr::Signature::from_slice(data).map(SchnorrSignature)
382    }
383}
384
385impl std::ops::Deref for SchnorrSignature {
386    type Target = secp256k1::schnorr::Signature;
387
388    fn deref(&self) -> &Self::Target {
389        &self.0
390    }
391}
392
393impl From<SchnorrSignature> for secp256k1::schnorr::Signature {
394    fn from(sig: SchnorrSignature) -> Self {
395        sig.0
396    }
397}
398
399impl From<secp256k1::schnorr::Signature> for SchnorrSignature {
400    fn from(sig: secp256k1::schnorr::Signature) -> Self {
401        Self(sig)
402    }
403}
404
405impl From<LiftedSignature> for SchnorrSignature {
406    fn from(sig: LiftedSignature) -> Self {
407        Self(secp256k1::schnorr::Signature::from(sig))
408    }
409}
410
411// SchnorrSignature <-> molecule_gossip::SchnorrSignature
412
413impl From<SchnorrSignature> for molecule_gossip::SchnorrSignature {
414    fn from(signature: SchnorrSignature) -> molecule_gossip::SchnorrSignature {
415        molecule_gossip::SchnorrSignature::new_builder()
416            .set(
417                signature
418                    .0
419                    .to_byte_array()
420                    .into_iter()
421                    .map(Into::into)
422                    .collect::<Vec<Byte>>()
423                    .try_into()
424                    .expect("Signature serialized to correct length"),
425            )
426            .build()
427    }
428}
429
430impl TryFrom<molecule_gossip::SchnorrSignature> for SchnorrSignature {
431    type Error = secp256k1::Error;
432
433    fn try_from(signature: molecule_gossip::SchnorrSignature) -> Result<Self, Self::Error> {
434        secp256k1::schnorr::Signature::from_slice(signature.as_slice()).map(Into::into)
435    }
436}
437
438/// Announcement of a new channel in the network.
439///
440/// This message is broadcast to all nodes to inform them about a new channel.
441/// It contains the channel's capacity, the nodes involved, and signatures.
442#[serde_as]
443#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
444pub struct ChannelAnnouncement {
445    /// Signature from node 1
446    pub node1_signature: Option<EcdsaSignature>,
447    /// Signature from node 2
448    pub node2_signature: Option<EcdsaSignature>,
449    /// Signature signed by the funding transaction output public key
450    pub ckb_signature: Option<SchnorrSignature>,
451    /// Feature flags for the channel
452    pub features: u64,
453    /// The chain hash this channel belongs to
454    pub chain_hash: Hash256,
455    /// The outpoint of the funding transaction
456    #[serde_as(as = "EntityHex")]
457    pub channel_outpoint: OutPoint,
458    /// Public key of node 1
459    pub node1_id: Pubkey,
460    /// Public key of node 2
461    pub node2_id: Pubkey,
462    /// The aggregated public key of the funding transaction output
463    pub ckb_key: secp256k1::XOnlyPublicKey,
464    /// The total capacity of the channel
465    pub capacity: u128,
466    /// UDT type script if this is a UDT channel
467    #[serde_as(as = "Option<EntityHex>")]
468    pub udt_type_script: Option<Script>,
469}
470
471impl ChannelAnnouncement {
472    /// Check if the announcement is fully signed.
473    pub fn is_signed(&self) -> bool {
474        self.node1_signature.is_some()
475            && self.node2_signature.is_some()
476            && self.ckb_signature.is_some()
477    }
478
479    /// Get the channel outpoint.
480    pub fn out_point(&self) -> &OutPoint {
481        &self.channel_outpoint
482    }
483
484    /// Create an unsigned channel announcement with the given parameters.
485    pub fn new_unsigned(
486        node1_pubkey: &Pubkey,
487        node2_pubkey: &Pubkey,
488        channel_outpoint: OutPoint,
489        chain_hash: Hash256,
490        ckb_pubkey: &secp256k1::XOnlyPublicKey,
491        capacity: u128,
492        udt_type_script: Option<Script>,
493    ) -> Self {
494        Self {
495            node1_signature: None,
496            node2_signature: None,
497            ckb_signature: None,
498            features: Default::default(),
499            chain_hash,
500            channel_outpoint,
501            node1_id: *node1_pubkey,
502            node2_id: *node2_pubkey,
503            ckb_key: *ckb_pubkey,
504            capacity,
505            udt_type_script,
506        }
507    }
508}
509
510/// Update to an existing channel's routing parameters.
511///
512/// This message is broadcast to update routing information for a channel,
513/// such as fees, timelock requirements, or disabled status.
514#[serde_as]
515#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
516pub struct ChannelUpdate {
517    /// Signature of the node that wants to update the channel information
518    pub signature: Option<EcdsaSignature>,
519    /// The chain hash this channel belongs to
520    pub chain_hash: Hash256,
521    /// The outpoint of the funding transaction
522    #[serde_as(as = "EntityHex")]
523    pub channel_outpoint: OutPoint,
524    /// Timestamp for this update
525    pub timestamp: u64,
526    /// Message flags (indicates which node this update is from)
527    pub message_flags: ChannelUpdateMessageFlags,
528    /// Channel flags (indicates if channel is disabled)
529    pub channel_flags: ChannelUpdateChannelFlags,
530    /// TLC expiry delta in blocks
531    pub tlc_expiry_delta: u64,
532    /// Minimum TLC value
533    pub tlc_minimum_value: u128,
534    /// Fee proportional millionths
535    pub tlc_fee_proportional_millionths: u128,
536}
537
538impl ChannelUpdate {
539    /// Check if this update is from node 1.
540    pub fn is_update_of_node_1(&self) -> bool {
541        !self.is_update_of_node_2()
542    }
543
544    /// Check if this update is from node 2.
545    pub fn is_update_of_node_2(&self) -> bool {
546        self.message_flags
547            .contains(ChannelUpdateMessageFlags::UPDATE_OF_NODE2)
548    }
549
550    /// Check if the channel is disabled.
551    pub fn is_disabled(&self) -> bool {
552        self.channel_flags
553            .contains(ChannelUpdateChannelFlags::DISABLED)
554    }
555
556    /// Get the message bytes to sign (hash of unsigned molecule serialization).
557    pub fn message_to_sign(&self) -> [u8; 32] {
558        let unsigned_update = ChannelUpdate {
559            signature: None,
560            chain_hash: self.chain_hash,
561            channel_outpoint: self.channel_outpoint.clone(),
562            timestamp: self.timestamp,
563            message_flags: self.message_flags,
564            channel_flags: self.channel_flags,
565            tlc_expiry_delta: self.tlc_expiry_delta,
566            tlc_minimum_value: self.tlc_minimum_value,
567            tlc_fee_proportional_millionths: self.tlc_fee_proportional_millionths,
568        };
569        deterministically_hash(&molecule_fiber::ChannelUpdate::from(unsigned_update))
570    }
571
572    /// Get the cursor for this channel update.
573    pub fn cursor(&self) -> Cursor {
574        Cursor::new(
575            self.timestamp,
576            BroadcastMessageID::ChannelUpdate(self.channel_outpoint.clone()),
577        )
578    }
579}
580
581impl ChannelAnnouncement {
582    /// Get the message bytes to sign (hash of unsigned molecule serialization).
583    pub fn message_to_sign(&self) -> [u8; 32] {
584        let unsigned_announcement = Self {
585            node1_signature: None,
586            node2_signature: None,
587            ckb_signature: None,
588            features: self.features,
589            chain_hash: self.chain_hash,
590            channel_outpoint: self.channel_outpoint.clone(),
591            node1_id: self.node1_id,
592            node2_id: self.node2_id,
593            ckb_key: self.ckb_key,
594            capacity: self.capacity,
595            udt_type_script: self.udt_type_script.clone(),
596        };
597        deterministically_hash(&molecule_gossip::ChannelAnnouncement::from(
598            unsigned_announcement,
599        ))
600    }
601}
602
603impl NodeAnnouncement {
604    /// Create an unsigned node announcement with the given parameters.
605    ///
606    /// The `udt_cfg_infos` and `version` are passed as parameters to avoid
607    /// depending on runtime/global state from the caller's crate.
608    #[allow(clippy::too_many_arguments)]
609    pub fn new_unsigned(
610        node_name: AnnouncedNodeName,
611        features: FeatureVector,
612        addresses: Vec<tentacle_multiaddr::Multiaddr>,
613        node_id: Pubkey,
614        chain_hash: Hash256,
615        timestamp: u64,
616        auto_accept_min_ckb_funding_amount: u64,
617        udt_cfg_infos: UdtCfgInfos,
618        version: String,
619    ) -> Self {
620        Self {
621            signature: None,
622            features,
623            timestamp,
624            node_id,
625            version,
626            node_name,
627            chain_hash,
628            addresses,
629            auto_accept_min_ckb_funding_amount,
630            udt_cfg_infos,
631        }
632    }
633
634    /// Create a signed node announcement.
635    ///
636    /// Builds an unsigned announcement, signs it with the given private key,
637    /// and returns the signed announcement.
638    #[allow(clippy::too_many_arguments)]
639    pub fn new_signed(
640        node_name: AnnouncedNodeName,
641        features: FeatureVector,
642        addresses: Vec<tentacle_multiaddr::Multiaddr>,
643        private_key: &Privkey,
644        chain_hash: Hash256,
645        timestamp: u64,
646        auto_accept_min_ckb_funding_amount: u64,
647        udt_cfg_infos: UdtCfgInfos,
648        version: String,
649    ) -> Self {
650        let mut unsigned = Self::new_unsigned(
651            node_name,
652            features,
653            addresses,
654            private_key.pubkey(),
655            chain_hash,
656            timestamp,
657            auto_accept_min_ckb_funding_amount,
658            udt_cfg_infos,
659            version,
660        );
661        unsigned.signature = Some(private_key.sign(unsigned.message_to_sign()));
662        unsigned
663    }
664
665    /// Get the message bytes to sign (hash of unsigned molecule serialization).
666    pub fn message_to_sign(&self) -> [u8; 32] {
667        let unsigned_announcement = NodeAnnouncement {
668            signature: None,
669            features: self.features.clone(),
670            timestamp: self.timestamp,
671            node_id: self.node_id,
672            version: self.version.clone(),
673            node_name: self.node_name,
674            chain_hash: self.chain_hash,
675            addresses: self.addresses.clone(),
676            auto_accept_min_ckb_funding_amount: self.auto_accept_min_ckb_funding_amount,
677            udt_cfg_infos: self.udt_cfg_infos.clone(),
678        };
679        deterministically_hash(&molecule_gossip::NodeAnnouncement::from(
680            unsigned_announcement,
681        ))
682    }
683
684    /// Get the cursor for this node announcement.
685    pub fn cursor(&self) -> Cursor {
686        Cursor::new(
687            self.timestamp,
688            BroadcastMessageID::NodeAnnouncement(self.node_id),
689        )
690    }
691
692    /// Verify the signature on this announcement.
693    pub fn verify(&self) -> bool {
694        let message = self.message_to_sign();
695        match self.signature {
696            Some(ref signature) => signature.verify(&self.node_id, &message),
697            _ => false,
698        }
699    }
700}
701
702/// Hash a molecule entity deterministically using blake2b-256.
703pub(crate) fn deterministically_hash<T: Entity>(v: &T) -> [u8; 32] {
704    ckb_hash::blake2b_256(v.as_slice())
705}
706
707/// A broadcast message in the gossip protocol.
708///
709/// This enum represents the different types of messages that can be broadcast
710/// to all nodes in the network.
711#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
712pub enum BroadcastMessage {
713    /// Node announcement message
714    NodeAnnouncement(NodeAnnouncement),
715    /// Channel announcement message
716    ChannelAnnouncement(ChannelAnnouncement),
717    /// Channel update message
718    ChannelUpdate(ChannelUpdate),
719}
720
721impl BroadcastMessage {
722    /// Returns the cursor for this broadcast message, if applicable.
723    pub fn cursor(&self) -> Option<Cursor> {
724        match self {
725            BroadcastMessage::ChannelAnnouncement(_) => None,
726            BroadcastMessage::ChannelUpdate(channel_update) => Some(channel_update.cursor()),
727            BroadcastMessage::NodeAnnouncement(node_announcement) => {
728                Some(node_announcement.cursor())
729            }
730        }
731    }
732
733    /// Returns the message ID for this broadcast message.
734    pub fn message_id(&self) -> BroadcastMessageID {
735        match self {
736            BroadcastMessage::NodeAnnouncement(node_announcement) => {
737                BroadcastMessageID::NodeAnnouncement(node_announcement.node_id)
738            }
739            BroadcastMessage::ChannelAnnouncement(channel_announcement) => {
740                BroadcastMessageID::ChannelAnnouncement(
741                    channel_announcement.channel_outpoint.clone(),
742                )
743            }
744            BroadcastMessage::ChannelUpdate(channel_update) => {
745                BroadcastMessageID::ChannelUpdate(channel_update.channel_outpoint.clone())
746            }
747        }
748    }
749
750    /// Returns the timestamp for this broadcast message, if applicable.
751    pub fn timestamp(&self) -> Option<u64> {
752        match self {
753            BroadcastMessage::NodeAnnouncement(node_announcement) => {
754                Some(node_announcement.timestamp)
755            }
756            BroadcastMessage::ChannelAnnouncement(_) => None,
757            BroadcastMessage::ChannelUpdate(channel_update) => Some(channel_update.timestamp),
758        }
759    }
760}
761
762/// Announcement of a node's presence and capabilities.
763///
764/// This message is broadcast to inform other nodes about this node's
765/// addresses, features, and other metadata.
766#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
767pub struct NodeAnnouncement {
768    /// Signature of this message
769    pub signature: Option<EcdsaSignature>,
770    /// Features supported by this node
771    pub features: FeatureVector,
772    /// Timestamp for this announcement
773    pub timestamp: u64,
774    /// The public key of the node
775    pub node_id: Pubkey,
776    /// Software version string
777    pub version: String,
778    /// Human-readable node name (up to 32 bytes)
779    pub node_name: AnnouncedNodeName,
780    /// Reachable addresses for this node
781    pub addresses: Vec<tentacle_multiaddr::Multiaddr>,
782    /// The chain hash this node is on
783    pub chain_hash: Hash256,
784    /// Minimum CKB funding amount for auto-accept
785    pub auto_accept_min_ckb_funding_amount: u64,
786    /// UDT configuration info
787    pub udt_cfg_infos: UdtCfgInfos,
788}
789
790/// The ID of a broadcast message.
791#[derive(Debug, Clone, Eq, PartialEq, Hash)]
792pub enum BroadcastMessageID {
793    ChannelAnnouncement(OutPoint),
794    ChannelUpdate(OutPoint),
795    NodeAnnouncement(Pubkey),
796}
797
798impl Default for BroadcastMessageID {
799    fn default() -> Self {
800        BroadcastMessageID::ChannelAnnouncement(OutPoint::default())
801    }
802}
803
804// We need to implement Ord for BroadcastMessageID to make sure that a ChannelUpdate message is always ordered after ChannelAnnouncement,
805// so that we can use it as the sorting key in fn prune_messages_to_be_saved to simplify the logic.
806// We need to implement Ord for BroadcastMessageID to make sure that a ChannelUpdate message is always ordered after ChannelAnnouncement,
807// so that we can use it as the sorting key in fn prune_messages_to_be_saved to simplify the logic.
808// Ordering: NodeAnnouncement < ChannelAnnouncement < ChannelUpdate
809impl Ord for BroadcastMessageID {
810    fn cmp(&self, other: &Self) -> Ordering {
811        match (self, other) {
812            (
813                BroadcastMessageID::ChannelAnnouncement(outpoint1),
814                BroadcastMessageID::ChannelAnnouncement(outpoint2),
815            ) => outpoint1.cmp(outpoint2),
816            (
817                BroadcastMessageID::ChannelUpdate(outpoint1),
818                BroadcastMessageID::ChannelUpdate(outpoint2),
819            ) => outpoint1.cmp(outpoint2),
820            (
821                BroadcastMessageID::NodeAnnouncement(node1),
822                BroadcastMessageID::NodeAnnouncement(node2),
823            ) => node1.cmp(node2),
824            (BroadcastMessageID::NodeAnnouncement(_), _) => Ordering::Less,
825            (BroadcastMessageID::ChannelUpdate(_), _) => Ordering::Greater,
826            (
827                BroadcastMessageID::ChannelAnnouncement(_),
828                BroadcastMessageID::NodeAnnouncement(_),
829            ) => Ordering::Greater,
830            (BroadcastMessageID::ChannelAnnouncement(_), BroadcastMessageID::ChannelUpdate(_)) => {
831                Ordering::Less
832            }
833        }
834    }
835}
836
837impl PartialOrd for BroadcastMessageID {
838    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
839        Some(self.cmp(other))
840    }
841}
842
843// 1 byte for message type, 36 bytes for message id
844const MESSAGE_ID_SIZE: usize = 1 + 36;
845
846impl BroadcastMessageID {
847    pub fn to_bytes(&self) -> [u8; MESSAGE_ID_SIZE] {
848        let mut result = [0u8; MESSAGE_ID_SIZE];
849        match self {
850            BroadcastMessageID::ChannelAnnouncement(channel_outpoint) => {
851                result[0] = 0;
852                result[1..].copy_from_slice(&channel_outpoint.as_bytes());
853            }
854            BroadcastMessageID::ChannelUpdate(channel_outpoint) => {
855                result[0] = 1;
856                result[1..].copy_from_slice(&channel_outpoint.as_bytes());
857            }
858            BroadcastMessageID::NodeAnnouncement(node_id) => {
859                result[0] = 2;
860                let node_id = node_id.serialize();
861                result[1..1 + node_id.len()].copy_from_slice(&node_id);
862            }
863        };
864        result
865    }
866
867    pub fn from_bytes(bytes: &[u8]) -> Result<Self, anyhow::Error> {
868        use molecule::prelude::Entity;
869        if bytes.len() != MESSAGE_ID_SIZE {
870            anyhow::bail!("Invalid message id size: {}", bytes.len());
871        }
872        match bytes[0] {
873            0 => Ok(BroadcastMessageID::ChannelAnnouncement(
874                OutPoint::from_slice(&bytes[1..])?,
875            )),
876            1 => Ok(BroadcastMessageID::ChannelUpdate(OutPoint::from_slice(
877                &bytes[1..],
878            )?)),
879            2 => Ok(BroadcastMessageID::NodeAnnouncement(Pubkey::from_slice(
880                &bytes[1..1 + Pubkey::serialization_len()],
881            )?)),
882            _ => anyhow::bail!("Invalid message id type: {}", bytes[0]),
883        }
884    }
885}
886
887/// A cursor for paginating broadcast messages.
888#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
889pub struct Cursor {
890    pub timestamp: u64,
891    pub message_id: BroadcastMessageID,
892}
893
894impl Cursor {
895    pub fn new(timestamp: u64, message_id: BroadcastMessageID) -> Self {
896        Self {
897            timestamp,
898            message_id,
899        }
900    }
901
902    /// Create a new cursor which is the same as the current cursor but with a smaller timestamp.
903    /// This is useful when we want to query messages back from this cursor for a certain duration.
904    pub fn go_back_for_some_time(&self, duration: Duration) -> Self {
905        let current_timestamp = self.timestamp;
906        let duration_millis = duration.as_millis() as u64;
907        if current_timestamp > duration_millis {
908            Self {
909                timestamp: current_timestamp - duration_millis,
910                message_id: self.message_id.clone(),
911            }
912        } else {
913            Default::default()
914        }
915    }
916
917    pub fn to_bytes(&self) -> [u8; 45] {
918        self.timestamp
919            .to_be_bytes()
920            .into_iter()
921            .chain(self.message_id.to_bytes())
922            .collect::<Vec<_>>()
923            .try_into()
924            .expect("Must serialize cursor to 45 bytes")
925    }
926
927    pub fn from_bytes(bytes: &[u8]) -> Result<Self, anyhow::Error> {
928        if bytes.len() != CURSOR_SIZE {
929            anyhow::bail!("Invalid cursor size: {}, want {}", bytes.len(), CURSOR_SIZE);
930        }
931        let timestamp = u64::from_be_bytes(bytes[..8].try_into().expect("Cursor timestamp to u64"));
932        let message_id = BroadcastMessageID::from_bytes(&bytes[8..])?;
933        Ok(Cursor {
934            timestamp,
935            message_id,
936        })
937    }
938
939    /// A dummy cursor with the maximum timestamp and a dummy message id.
940    /// This is useful when we want to create a cursor after which none of the messages should be included.
941    pub fn max() -> Self {
942        Self {
943            timestamp: u64::MAX,
944            message_id: BroadcastMessageID::ChannelAnnouncement(OutPoint::default()),
945        }
946    }
947
948    pub fn is_max(&self) -> bool {
949        self.timestamp == u64::MAX
950    }
951}
952
953// NodeAnnouncement molecule conversions
954
955impl From<NodeAnnouncement> for molecule_gossip::NodeAnnouncement {
956    fn from(node_announcement: NodeAnnouncement) -> Self {
957        let builder = molecule_gossip::NodeAnnouncement::new_builder()
958            .features(node_announcement.features.bytes().pack())
959            .timestamp(node_announcement.timestamp.pack())
960            .node_id(node_announcement.node_id.into())
961            .version(node_announcement.version.pack())
962            .node_name(u8_32_as_byte_32(&node_announcement.node_name.0))
963            .chain_hash(node_announcement.chain_hash.into())
964            .auto_accept_min_ckb_funding_amount(
965                node_announcement.auto_accept_min_ckb_funding_amount.pack(),
966            )
967            .udt_cfg_infos(node_announcement.udt_cfg_infos.into())
968            .address(
969                BytesVec::new_builder()
970                    .set(
971                        node_announcement
972                            .addresses
973                            .into_iter()
974                            .map(|address| address.to_vec().pack())
975                            .collect(),
976                    )
977                    .build(),
978            );
979
980        let builder = if let Some(signature) = node_announcement.signature {
981            builder.signature(signature.into())
982        } else {
983            builder
984        };
985
986        builder.build()
987    }
988}
989
990impl TryFrom<molecule_gossip::NodeAnnouncement> for NodeAnnouncement {
991    type Error = anyhow::Error;
992
993    fn try_from(node_announcement: molecule_gossip::NodeAnnouncement) -> Result<Self, Self::Error> {
994        use ckb_types::prelude::Unpack;
995        Ok(NodeAnnouncement {
996            signature: Some(
997                node_announcement
998                    .signature()
999                    .try_into()
1000                    .map_err(|e: secp256k1::Error| anyhow::anyhow!(e))?,
1001            ),
1002            features: FeatureVector::from(node_announcement.features().unpack()),
1003            timestamp: node_announcement.timestamp().unpack(),
1004            node_id: node_announcement
1005                .node_id()
1006                .try_into()
1007                .map_err(|e: secp256k1::Error| anyhow::anyhow!(e))?,
1008            version: String::from_utf8(node_announcement.version().unpack()).unwrap_or_default(),
1009            chain_hash: node_announcement.chain_hash().into(),
1010            auto_accept_min_ckb_funding_amount: node_announcement
1011                .auto_accept_min_ckb_funding_amount()
1012                .unpack(),
1013            node_name: AnnouncedNodeName::from_slice(node_announcement.node_name().as_slice())
1014                .map_err(|e| anyhow::anyhow!("Invalid node_name: {}", e))?,
1015            udt_cfg_infos: node_announcement.udt_cfg_infos().try_into()?,
1016            addresses: node_announcement
1017                .address()
1018                .into_iter()
1019                .map(|bytes| {
1020                    tentacle_multiaddr::Multiaddr::try_from(bytes.raw_data().to_vec())
1021                        .map_err(Into::into)
1022                })
1023                .collect::<Result<Vec<_>, anyhow::Error>>()?,
1024        })
1025    }
1026}
1027
1028impl From<ChannelAnnouncement> for molecule_gossip::ChannelAnnouncement {
1029    fn from(channel_announcement: ChannelAnnouncement) -> Self {
1030        let builder = molecule_gossip::ChannelAnnouncement::new_builder()
1031            .features(channel_announcement.features.pack())
1032            .chain_hash(channel_announcement.chain_hash.into())
1033            .channel_outpoint(channel_announcement.channel_outpoint)
1034            .node1_id(channel_announcement.node1_id.into())
1035            .node2_id(channel_announcement.node2_id.into())
1036            .capacity(channel_announcement.capacity.pack())
1037            .udt_type_script(channel_announcement.udt_type_script.pack())
1038            .ckb_key(channel_announcement.ckb_key.into());
1039
1040        let builder = if let Some(signature) = channel_announcement.node1_signature {
1041            builder.node1_signature(signature.into())
1042        } else {
1043            builder
1044        };
1045
1046        let builder = if let Some(signature) = channel_announcement.node2_signature {
1047            builder.node2_signature(signature.into())
1048        } else {
1049            builder
1050        };
1051
1052        let builder = if let Some(signature) = channel_announcement.ckb_signature {
1053            builder.ckb_signature(signature.into())
1054        } else {
1055            builder
1056        };
1057
1058        builder.build()
1059    }
1060}
1061
1062impl TryFrom<molecule_gossip::ChannelAnnouncement> for ChannelAnnouncement {
1063    type Error = anyhow::Error;
1064
1065    fn try_from(
1066        channel_announcement: molecule_gossip::ChannelAnnouncement,
1067    ) -> Result<Self, Self::Error> {
1068        use ckb_types::prelude::Unpack;
1069        Ok(ChannelAnnouncement {
1070            node1_signature: Some(channel_announcement.node1_signature().try_into()?),
1071            node2_signature: Some(channel_announcement.node2_signature().try_into()?),
1072            ckb_signature: Some(channel_announcement.ckb_signature().try_into()?),
1073            features: channel_announcement.features().unpack(),
1074            capacity: channel_announcement.capacity().unpack(),
1075            chain_hash: channel_announcement.chain_hash().into(),
1076            channel_outpoint: channel_announcement.channel_outpoint(),
1077            udt_type_script: channel_announcement.udt_type_script().to_opt(),
1078            node1_id: channel_announcement.node1_id().try_into()?,
1079            node2_id: channel_announcement.node2_id().try_into()?,
1080            ckb_key: channel_announcement.ckb_key().try_into()?,
1081        })
1082    }
1083}
1084
1085impl From<ChannelUpdate> for molecule_fiber::ChannelUpdate {
1086    fn from(channel_update: ChannelUpdate) -> Self {
1087        let builder = molecule_fiber::ChannelUpdate::new_builder()
1088            .chain_hash(channel_update.chain_hash.into())
1089            .channel_outpoint(channel_update.channel_outpoint)
1090            .timestamp(channel_update.timestamp.pack())
1091            .message_flags(channel_update.message_flags.bits().pack())
1092            .channel_flags(channel_update.channel_flags.bits().pack())
1093            .tlc_expiry_delta(channel_update.tlc_expiry_delta.pack())
1094            .tlc_minimum_value(channel_update.tlc_minimum_value.pack())
1095            .tlc_fee_proportional_millionths(channel_update.tlc_fee_proportional_millionths.pack());
1096
1097        let builder = if let Some(signature) = channel_update.signature {
1098            builder.signature(signature.into())
1099        } else {
1100            builder
1101        };
1102
1103        builder.build()
1104    }
1105}
1106
1107impl TryFrom<molecule_fiber::ChannelUpdate> for ChannelUpdate {
1108    type Error = anyhow::Error;
1109
1110    fn try_from(channel_update: molecule_fiber::ChannelUpdate) -> Result<Self, Self::Error> {
1111        use ckb_types::prelude::Unpack;
1112        Ok(ChannelUpdate {
1113            signature: Some(channel_update.signature().try_into()?),
1114            chain_hash: channel_update.chain_hash().into(),
1115            channel_outpoint: channel_update.channel_outpoint(),
1116            timestamp: channel_update.timestamp().unpack(),
1117            message_flags: ChannelUpdateMessageFlags::from_bits_truncate(
1118                channel_update.message_flags().unpack(),
1119            ),
1120            channel_flags: ChannelUpdateChannelFlags::from_bits_truncate(
1121                channel_update.channel_flags().unpack(),
1122            ),
1123            tlc_expiry_delta: channel_update.tlc_expiry_delta().unpack(),
1124            tlc_minimum_value: channel_update.tlc_minimum_value().unpack(),
1125            tlc_fee_proportional_millionths: channel_update
1126                .tlc_fee_proportional_millionths()
1127                .unpack(),
1128        })
1129    }
1130}
1131
1132impl Ord for BroadcastMessage {
1133    fn cmp(&self, other: &Self) -> Ordering {
1134        self.message_id()
1135            .cmp(&other.message_id())
1136            .then(self.timestamp().cmp(&other.timestamp()))
1137    }
1138}
1139
1140impl PartialOrd for BroadcastMessage {
1141    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1142        Some(self.cmp(other))
1143    }
1144}
1145
1146impl From<BroadcastMessage> for molecule_gossip::BroadcastMessageUnion {
1147    fn from(fiber_broadcast_message: BroadcastMessage) -> Self {
1148        match fiber_broadcast_message {
1149            BroadcastMessage::NodeAnnouncement(node_announcement) => {
1150                molecule_gossip::BroadcastMessageUnion::NodeAnnouncement(node_announcement.into())
1151            }
1152            BroadcastMessage::ChannelAnnouncement(channel_announcement) => {
1153                molecule_gossip::BroadcastMessageUnion::ChannelAnnouncement(
1154                    channel_announcement.into(),
1155                )
1156            }
1157            BroadcastMessage::ChannelUpdate(channel_update) => {
1158                molecule_gossip::BroadcastMessageUnion::ChannelUpdate(channel_update.into())
1159            }
1160        }
1161    }
1162}
1163
1164impl TryFrom<molecule_gossip::BroadcastMessageUnion> for BroadcastMessage {
1165    type Error = anyhow::Error;
1166
1167    fn try_from(
1168        fiber_broadcast_message: molecule_gossip::BroadcastMessageUnion,
1169    ) -> Result<Self, Self::Error> {
1170        match fiber_broadcast_message {
1171            molecule_gossip::BroadcastMessageUnion::NodeAnnouncement(node_announcement) => Ok(
1172                BroadcastMessage::NodeAnnouncement(node_announcement.try_into()?),
1173            ),
1174            molecule_gossip::BroadcastMessageUnion::ChannelAnnouncement(channel_announcement) => {
1175                Ok(BroadcastMessage::ChannelAnnouncement(
1176                    channel_announcement.try_into()?,
1177                ))
1178            }
1179            molecule_gossip::BroadcastMessageUnion::ChannelUpdate(channel_update) => {
1180                Ok(BroadcastMessage::ChannelUpdate(channel_update.try_into()?))
1181            }
1182        }
1183    }
1184}
1185
1186impl From<BroadcastMessage> for molecule_gossip::BroadcastMessage {
1187    fn from(fiber_broadcast_message: BroadcastMessage) -> Self {
1188        molecule_gossip::BroadcastMessage::new_builder()
1189            .set(fiber_broadcast_message)
1190            .build()
1191    }
1192}
1193
1194impl TryFrom<molecule_gossip::BroadcastMessage> for BroadcastMessage {
1195    type Error = anyhow::Error;
1196
1197    fn try_from(
1198        fiber_broadcast_message: molecule_gossip::BroadcastMessage,
1199    ) -> Result<Self, Self::Error> {
1200        fiber_broadcast_message.to_enum().try_into()
1201    }
1202}
1203
1204impl Ord for Cursor {
1205    fn cmp(&self, other: &Self) -> Ordering {
1206        self.to_bytes().cmp(&other.to_bytes())
1207    }
1208}
1209
1210impl PartialOrd for Cursor {
1211    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1212        Some(self.cmp(other))
1213    }
1214}
1215
1216impl From<Cursor> for molecule_gossip::Cursor {
1217    fn from(cursor: Cursor) -> Self {
1218        use molecule::prelude::{Builder, Byte};
1219        let serialized = cursor
1220            .timestamp
1221            .to_be_bytes()
1222            .into_iter()
1223            .chain(cursor.message_id.to_bytes())
1224            .map(Byte::new)
1225            .collect::<Vec<_>>()
1226            .try_into()
1227            .expect("Must serialize cursor to 45 bytes");
1228
1229        molecule_gossip::Cursor::new_builder()
1230            .set(serialized)
1231            .build()
1232    }
1233}
1234
1235impl TryFrom<molecule_gossip::Cursor> for Cursor {
1236    type Error = anyhow::Error;
1237
1238    fn try_from(cursor: molecule_gossip::Cursor) -> Result<Self, Self::Error> {
1239        use molecule::prelude::Entity;
1240        let slice = cursor.as_slice();
1241        if slice.len() != CURSOR_SIZE {
1242            anyhow::bail!("Invalid cursor size: {}, want {}", slice.len(), CURSOR_SIZE);
1243        }
1244        let timestamp = u64::from_be_bytes(slice[..8].try_into().expect("Cursor timestamp to u64"));
1245        let message_id = BroadcastMessageID::from_bytes(&slice[8..])?;
1246        Ok(Cursor {
1247            timestamp,
1248            message_id,
1249        })
1250    }
1251}
1252
1253#[cfg(test)]
1254mod tests {
1255    use super::*;
1256
1257    fn node_announcement_with_raw_name(raw_name: [u8; 32]) -> molecule_gossip::NodeAnnouncement {
1258        let private_key = Privkey::from_slice(&[42u8; 32]);
1259        let announcement = NodeAnnouncement::new_signed(
1260            AnnouncedNodeName::from_string("valid-node").expect("valid node name"),
1261            FeatureVector::default(),
1262            vec![],
1263            &private_key,
1264            Hash256::default(),
1265            1,
1266            0,
1267            UdtCfgInfos::default(),
1268            "test".to_string(),
1269        );
1270        let molecule_announcement = molecule_gossip::NodeAnnouncement::from(announcement);
1271
1272        molecule_gossip::NodeAnnouncement::new_builder()
1273            .signature(molecule_announcement.signature())
1274            .features(molecule_announcement.features())
1275            .timestamp(molecule_announcement.timestamp())
1276            .node_id(molecule_announcement.node_id())
1277            .version(molecule_announcement.version())
1278            .node_name(u8_32_as_byte_32(&raw_name))
1279            .address(molecule_announcement.address())
1280            .chain_hash(molecule_announcement.chain_hash())
1281            .auto_accept_min_ckb_funding_amount(
1282                molecule_announcement.auto_accept_min_ckb_funding_amount(),
1283            )
1284            .udt_cfg_infos(molecule_announcement.udt_cfg_infos())
1285            .build()
1286    }
1287
1288    fn raw_node_name(name: &[u8]) -> [u8; 32] {
1289        let mut raw_name = [0u8; 32];
1290        raw_name[..name.len()].copy_from_slice(name);
1291        raw_name
1292    }
1293
1294    #[test]
1295    fn node_announcement_from_molecule_rejects_non_utf8_node_name() {
1296        let mut raw_name = [0u8; 32];
1297        raw_name[0] = b'f';
1298        raw_name[1] = 0xff;
1299
1300        let err = NodeAnnouncement::try_from(node_announcement_with_raw_name(raw_name))
1301            .expect_err("invalid UTF-8 node_name must be rejected");
1302        assert!(err.to_string().contains("Invalid node_name"));
1303    }
1304
1305    #[test]
1306    fn node_announcement_from_molecule_rejects_control_chars_in_node_name() {
1307        for raw_name in [
1308            raw_node_name(b"\x1bc"),
1309            raw_node_name(b"bad\x07name"),
1310            raw_node_name("bad\u{85}name".as_bytes()),
1311        ] {
1312            let err = NodeAnnouncement::try_from(node_announcement_with_raw_name(raw_name))
1313                .expect_err("control chars in node_name must be rejected");
1314            assert!(err.to_string().contains("Invalid node_name"));
1315        }
1316    }
1317
1318    #[test]
1319    fn announced_node_name_allows_printable_utf8() {
1320        let name = "节点-café";
1321        let announced_name =
1322            AnnouncedNodeName::from_string(name).expect("printable UTF-8 node name is valid");
1323        assert_eq!(announced_name.as_str(), name);
1324
1325        let announcement = NodeAnnouncement::try_from(node_announcement_with_raw_name(
1326            raw_node_name(name.as_bytes()),
1327        ))
1328        .expect("printable UTF-8 node_name from molecule is valid");
1329        assert_eq!(announcement.node_name.as_str(), name);
1330    }
1331
1332    #[test]
1333    fn malformed_announced_node_name_debug_and_serialize_do_not_panic() {
1334        let mut raw_name = [0u8; 32];
1335        raw_name[0] = b'f';
1336        raw_name[1] = 0xff;
1337        let node_name = AnnouncedNodeName(raw_name);
1338
1339        let debug_result = std::panic::catch_unwind(|| format!("{node_name:?}"));
1340        assert!(debug_result.is_ok());
1341
1342        let serialize_result = std::panic::catch_unwind(|| serde_json::to_string(&node_name));
1343        assert!(serialize_result.is_ok());
1344        assert!(serialize_result.expect("panic checked").is_err());
1345    }
1346}