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