1use std::io::{Error, Write};
2use std::str::FromStr;
3
4use anyhow::format_err;
5use bitcoin::address::NetworkUnchecked;
6use bitcoin::hashes::Hash as BitcoinHash;
7use hex::{FromHex, ToHex};
8use lightning::util::ser::{BigSize, Readable, Writeable};
9use miniscript::{Descriptor, MiniscriptKey};
10use serde::{Deserialize, Serialize};
11
12use crate::encoding::{Decodable, DecodeError, Encodable};
13use crate::get_network_for_address;
14use crate::module::registry::ModuleDecoderRegistry;
15
16macro_rules! impl_encode_decode_bridge {
17 ($btc_type:ty) => {
18 impl crate::encoding::Encodable for $btc_type {
19 fn consensus_encode<W: std::io::Write>(
20 &self,
21 writer: &mut W,
22 ) -> Result<usize, std::io::Error> {
23 Ok(bitcoin::consensus::Encodable::consensus_encode(
24 self,
25 &mut std::io::BufWriter::new(writer),
26 )?)
27 }
28 }
29
30 impl crate::encoding::Decodable for $btc_type {
31 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
32 d: &mut D,
33 _modules: &$crate::module::registry::ModuleDecoderRegistry,
34 ) -> Result<Self, crate::encoding::DecodeError> {
35 bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(
36 &mut SimpleBitcoinRead(d),
37 )
38 .map_err(crate::encoding::DecodeError::from_err)
39 }
40 }
41 };
42}
43
44impl_encode_decode_bridge!(bitcoin::block::Header);
45impl_encode_decode_bridge!(bitcoin::BlockHash);
46impl_encode_decode_bridge!(bitcoin::OutPoint);
47impl_encode_decode_bridge!(bitcoin::TxOut);
48impl_encode_decode_bridge!(bitcoin::ScriptBuf);
49impl_encode_decode_bridge!(bitcoin::Transaction);
50impl_encode_decode_bridge!(bitcoin::merkle_tree::PartialMerkleTree);
51
52impl crate::encoding::Encodable for bitcoin::psbt::Psbt {
53 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
54 Ok(self.serialize_to_writer(&mut CountWrite::from(writer))?)
55 }
56}
57
58impl crate::encoding::Decodable for bitcoin::psbt::Psbt {
59 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
60 d: &mut D,
61 _modules: &ModuleDecoderRegistry,
62 ) -> Result<Self, crate::encoding::DecodeError> {
63 Self::deserialize_from_reader(&mut BufBitcoinReader::new(d))
64 .map_err(crate::encoding::DecodeError::from_err)
65 }
66}
67
68impl crate::encoding::Encodable for bitcoin::Txid {
69 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
70 Ok(bitcoin::consensus::Encodable::consensus_encode(
71 self,
72 &mut std::io::BufWriter::new(writer),
73 )?)
74 }
75
76 fn consensus_encode_to_hex(&self) -> String {
77 let mut bytes = self.consensus_encode_to_vec();
78
79 bytes.reverse();
81
82 bytes.encode_hex()
84 }
85}
86
87impl crate::encoding::Decodable for bitcoin::Txid {
88 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
89 d: &mut D,
90 _modules: &::fedimint_core::module::registry::ModuleDecoderRegistry,
91 ) -> Result<Self, crate::encoding::DecodeError> {
92 bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(&mut SimpleBitcoinRead(
93 d,
94 ))
95 .map_err(crate::encoding::DecodeError::from_err)
96 }
97
98 fn consensus_decode_hex(
99 hex: &str,
100 modules: &ModuleDecoderRegistry,
101 ) -> Result<Self, DecodeError> {
102 let mut bytes = Vec::<u8>::from_hex(hex)
103 .map_err(anyhow::Error::from)
104 .map_err(DecodeError::new_custom)?;
105
106 bytes.reverse();
108
109 Decodable::consensus_decode_whole(&bytes, modules)
110 }
111}
112
113impl<K> Encodable for Descriptor<K>
114where
115 K: MiniscriptKey,
116{
117 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<usize, Error> {
118 let descriptor_str = self.to_string();
119 descriptor_str.consensus_encode(writer)
120 }
121}
122
123impl<K> Decodable for Descriptor<K>
124where
125 Self: FromStr,
126 <Self as FromStr>::Err: ToString + std::error::Error + Send + Sync + 'static,
127 K: MiniscriptKey,
128{
129 fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
130 d: &mut D,
131 modules: &ModuleDecoderRegistry,
132 ) -> Result<Self, DecodeError> {
133 let descriptor_str = String::consensus_decode_partial_from_finite_reader(d, modules)?;
134 Self::from_str(&descriptor_str).map_err(DecodeError::from_err)
135 }
136}
137
138#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
142pub struct NetworkLegacyEncodingWrapper(pub bitcoin::Network);
143
144impl std::fmt::Display for NetworkLegacyEncodingWrapper {
145 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
146 write!(f, "{}", self.0)
147 }
148}
149
150impl Encodable for NetworkLegacyEncodingWrapper {
151 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<usize, Error> {
152 u32::from_le_bytes(self.0.magic().to_bytes()).consensus_encode(writer)
153 }
154}
155
156impl Decodable for NetworkLegacyEncodingWrapper {
157 fn consensus_decode_partial<D: std::io::Read>(
158 d: &mut D,
159 modules: &ModuleDecoderRegistry,
160 ) -> Result<Self, DecodeError> {
161 let num = u32::consensus_decode_partial(d, modules)?;
162 let magic = bitcoin::p2p::Magic::from_bytes(num.to_le_bytes());
163 let network = bitcoin::Network::from_magic(magic).ok_or_else(|| {
164 DecodeError::new_custom(format_err!("Unknown network magic: {:x}", magic))
165 })?;
166 Ok(Self(network))
167 }
168}
169impl Encodable for bitcoin::Network {
170 fn consensus_encode<W: Write>(&self, writer: &mut W) -> Result<usize, Error> {
171 self.magic().to_bytes().consensus_encode(writer)
172 }
173}
174
175impl Decodable for bitcoin::Network {
176 fn consensus_decode_partial<D: std::io::Read>(
177 d: &mut D,
178 modules: &ModuleDecoderRegistry,
179 ) -> Result<Self, DecodeError> {
180 Self::from_magic(bitcoin::p2p::Magic::from_bytes(
181 Decodable::consensus_decode_partial(d, modules)?,
182 ))
183 .ok_or_else(|| DecodeError::new_custom(format_err!("Unknown network magic")))
184 }
185}
186
187impl Encodable for bitcoin::Amount {
188 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
189 self.to_sat().consensus_encode(writer)
190 }
191}
192
193impl Decodable for bitcoin::Amount {
194 fn consensus_decode_partial<D: std::io::Read>(
195 d: &mut D,
196 modules: &ModuleDecoderRegistry,
197 ) -> Result<Self, DecodeError> {
198 Ok(Self::from_sat(u64::consensus_decode_partial(d, modules)?))
199 }
200}
201
202impl Encodable for bitcoin::Address<NetworkUnchecked> {
203 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
204 let mut len = 0;
205 len +=
206 NetworkLegacyEncodingWrapper(get_network_for_address(self)).consensus_encode(writer)?;
207 len += self
208 .clone()
209 .assume_checked()
213 .script_pubkey()
214 .consensus_encode(writer)?;
215 Ok(len)
216 }
217}
218
219impl Decodable for bitcoin::Address<NetworkUnchecked> {
220 fn consensus_decode_partial<D: std::io::Read>(
221 mut d: &mut D,
222 modules: &ModuleDecoderRegistry,
223 ) -> Result<Self, DecodeError> {
224 let network = NetworkLegacyEncodingWrapper::consensus_decode_partial(&mut d, modules)?.0;
225 let script_pk = bitcoin::ScriptBuf::consensus_decode_partial(&mut d, modules)?;
226
227 let address = bitcoin::Address::from_script(&script_pk, network)
228 .map_err(|e| DecodeError::new_custom(e.into()))?;
229
230 Ok(address.into_unchecked())
231 }
232}
233
234impl Encodable for bitcoin::hashes::sha256::Hash {
235 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
236 self.to_byte_array().consensus_encode(writer)
237 }
238}
239
240impl Decodable for bitcoin::hashes::sha256::Hash {
241 fn consensus_decode_partial<D: std::io::Read>(
242 d: &mut D,
243 modules: &ModuleDecoderRegistry,
244 ) -> Result<Self, DecodeError> {
245 Ok(Self::from_byte_array(Decodable::consensus_decode_partial(
246 d, modules,
247 )?))
248 }
249}
250
251impl Encodable for lightning_invoice::Bolt11Invoice {
252 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
253 self.to_string().consensus_encode(writer)
254 }
255}
256
257impl Decodable for lightning_invoice::Bolt11Invoice {
258 fn consensus_decode_partial<D: std::io::Read>(
259 d: &mut D,
260 modules: &ModuleDecoderRegistry,
261 ) -> Result<Self, DecodeError> {
262 String::consensus_decode_partial(d, modules)?
263 .parse::<Self>()
264 .map_err(DecodeError::from_err)
265 }
266}
267
268impl Encodable for lightning_invoice::RoutingFees {
269 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, Error> {
270 let mut len = 0;
271 len += self.base_msat.consensus_encode(writer)?;
272 len += self.proportional_millionths.consensus_encode(writer)?;
273 Ok(len)
274 }
275}
276
277impl Decodable for lightning_invoice::RoutingFees {
278 fn consensus_decode_partial<D: std::io::Read>(
279 d: &mut D,
280 modules: &ModuleDecoderRegistry,
281 ) -> Result<Self, DecodeError> {
282 let base_msat = Decodable::consensus_decode_partial(d, modules)?;
283 let proportional_millionths = Decodable::consensus_decode_partial(d, modules)?;
284 Ok(Self {
285 base_msat,
286 proportional_millionths,
287 })
288 }
289}
290
291impl Encodable for BigSize {
292 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<usize, std::io::Error> {
293 let mut writer = CountWrite::from(writer);
294 self.write(&mut writer)?;
295 Ok(usize::try_from(writer.count()).expect("can't overflow"))
296 }
297}
298
299impl Decodable for BigSize {
300 fn consensus_decode_partial<R: std::io::Read>(
301 r: &mut R,
302 _modules: &ModuleDecoderRegistry,
303 ) -> Result<Self, DecodeError> {
304 Self::read(&mut SimpleBitcoinRead(r))
305 .map_err(|e| DecodeError::new_custom(anyhow::anyhow!("BigSize decoding error: {e:?}")))
306 }
307}
308
309struct SimpleBitcoinRead<R: std::io::Read>(R);
313
314impl<R: std::io::Read> bitcoin_io::Read for SimpleBitcoinRead<R> {
315 fn read(&mut self, buf: &mut [u8]) -> bitcoin_io::Result<usize> {
316 self.0.read(buf).map_err(bitcoin_io::Error::from)
317 }
318}
319
320struct BufBitcoinReader<'a, R: std::io::Read> {
331 inner: &'a mut R,
332 buf: [u8; 1],
333 is_consumed: bool,
334}
335
336impl<'a, R: std::io::Read> BufBitcoinReader<'a, R> {
337 fn new(inner: &'a mut R) -> Self {
339 BufBitcoinReader {
340 inner,
341 buf: [0; 1],
342 is_consumed: true,
343 }
344 }
345}
346
347impl<'a, R: std::io::Read> bitcoin_io::Read for BufBitcoinReader<'a, R> {
348 #[inline]
349 fn read(&mut self, output: &mut [u8]) -> bitcoin_io::Result<usize> {
350 if output.is_empty() {
351 return Ok(0);
352 }
353 #[allow(clippy::useless_let_if_seq)]
354 let mut offset = 0;
355 if !self.is_consumed {
356 output[0] = self.buf[0];
357 self.is_consumed = true;
358 offset = 1;
359 }
360 Ok(self
361 .inner
362 .read(&mut output[offset..])
363 .map(|len| len + offset)?)
364 }
365}
366
367impl<'a, R: std::io::Read> bitcoin_io::BufRead for BufBitcoinReader<'a, R> {
368 #[inline]
369 fn fill_buf(&mut self) -> bitcoin_io::Result<&[u8]> {
370 debug_assert!(false, "rust-bitcoin doesn't actually use this");
371 if self.is_consumed {
372 let count = self.inner.read(&mut self.buf[..])?;
373 debug_assert!(count <= 1, "read gave us a garbage length");
374
375 self.is_consumed = count == 0;
377 }
378
379 if self.is_consumed {
380 Ok(&[])
381 } else {
382 Ok(&self.buf[..])
383 }
384 }
385
386 #[inline]
387 fn consume(&mut self, amount: usize) {
388 debug_assert!(false, "rust-bitcoin doesn't actually use this");
389 if amount >= 1 {
390 debug_assert_eq!(amount, 1, "Can only consume one byte");
391 debug_assert!(!self.is_consumed, "Cannot consume more than had been read");
392 self.is_consumed = true;
393 }
394 }
395}
396
397struct CountWrite<W> {
403 inner: W,
404 count: u64,
405}
406
407impl<W> CountWrite<W> {
408 fn count(&self) -> u64 {
410 self.count
411 }
412}
413
414impl<W> From<W> for CountWrite<W> {
415 fn from(inner: W) -> Self {
416 Self { inner, count: 0 }
417 }
418}
419
420impl<W: Write> bitcoin_io::Write for CountWrite<W> {
421 fn write(&mut self, buf: &[u8]) -> bitcoin_io::Result<usize> {
422 let written = self.inner.write(buf)?;
423 self.count += written as u64;
424 Ok(written)
425 }
426
427 fn flush(&mut self) -> bitcoin_io::Result<()> {
428 self.inner.flush().map_err(bitcoin_io::Error::from)
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use std::str::FromStr;
435
436 use bitcoin::hashes::Hash as BitcoinHash;
437 use hex::FromHex;
438
439 use crate::db::DatabaseValue;
440 use crate::encoding::btc::NetworkLegacyEncodingWrapper;
441 use crate::encoding::tests::{test_roundtrip, test_roundtrip_expected};
442 use crate::encoding::{Decodable, Encodable};
443 use crate::ModuleDecoderRegistry;
444
445 #[test_log::test]
446 fn block_hash_roundtrip() {
447 let blockhash = bitcoin::BlockHash::from_str(
448 "0000000000000000000065bda8f8a88f2e1e00d9a6887a43d640e52a4c7660f2",
449 )
450 .unwrap();
451 test_roundtrip_expected(
452 &blockhash,
453 &[
454 242, 96, 118, 76, 42, 229, 64, 214, 67, 122, 136, 166, 217, 0, 30, 46, 143, 168,
455 248, 168, 189, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
456 ],
457 );
458 }
459
460 #[test_log::test]
461 fn tx_roundtrip() {
462 let transaction: Vec<u8> = FromHex::from_hex(
463 "02000000000101d35b66c54cf6c09b81a8d94cd5d179719cd7595c258449452a9305ab9b12df250200000000fdffffff020cd50a0000000000160014ae5d450b71c04218e6e81c86fcc225882d7b7caae695b22100000000160014f60834ef165253c571b11ce9fa74e46692fc5ec10248304502210092062c609f4c8dc74cd7d4596ecedc1093140d90b3fd94b4bdd9ad3e102ce3bc02206bb5a6afc68d583d77d5d9bcfb6252a364d11a307f3418be1af9f47f7b1b3d780121026e5628506ecd33242e5ceb5fdafe4d3066b5c0f159b3c05a621ef65f177ea28600000000"
464 ).unwrap();
465 let transaction =
466 bitcoin::Transaction::from_bytes(&transaction, &ModuleDecoderRegistry::default())
467 .unwrap();
468 test_roundtrip_expected(
469 &transaction,
470 &[
471 2, 0, 0, 0, 0, 1, 1, 211, 91, 102, 197, 76, 246, 192, 155, 129, 168, 217, 76, 213,
472 209, 121, 113, 156, 215, 89, 92, 37, 132, 73, 69, 42, 147, 5, 171, 155, 18, 223,
473 37, 2, 0, 0, 0, 0, 253, 255, 255, 255, 2, 12, 213, 10, 0, 0, 0, 0, 0, 22, 0, 20,
474 174, 93, 69, 11, 113, 192, 66, 24, 230, 232, 28, 134, 252, 194, 37, 136, 45, 123,
475 124, 170, 230, 149, 178, 33, 0, 0, 0, 0, 22, 0, 20, 246, 8, 52, 239, 22, 82, 83,
476 197, 113, 177, 28, 233, 250, 116, 228, 102, 146, 252, 94, 193, 2, 72, 48, 69, 2,
477 33, 0, 146, 6, 44, 96, 159, 76, 141, 199, 76, 215, 212, 89, 110, 206, 220, 16, 147,
478 20, 13, 144, 179, 253, 148, 180, 189, 217, 173, 62, 16, 44, 227, 188, 2, 32, 107,
479 181, 166, 175, 198, 141, 88, 61, 119, 213, 217, 188, 251, 98, 82, 163, 100, 209,
480 26, 48, 127, 52, 24, 190, 26, 249, 244, 127, 123, 27, 61, 120, 1, 33, 2, 110, 86,
481 40, 80, 110, 205, 51, 36, 46, 92, 235, 95, 218, 254, 77, 48, 102, 181, 192, 241,
482 89, 179, 192, 90, 98, 30, 246, 95, 23, 126, 162, 134, 0, 0, 0, 0,
483 ],
484 );
485 }
486
487 #[test_log::test]
488 fn txid_roundtrip() {
489 let txid = bitcoin::Txid::from_str(
490 "51f7ed2f23e58cc6e139e715e9ce304a1e858416edc9079dd7b74fa8d2efc09a",
491 )
492 .unwrap();
493 test_roundtrip_expected(
494 &txid,
495 &[
496 154, 192, 239, 210, 168, 79, 183, 215, 157, 7, 201, 237, 22, 132, 133, 30, 74, 48,
497 206, 233, 21, 231, 57, 225, 198, 140, 229, 35, 47, 237, 247, 81,
498 ],
499 );
500 }
501
502 #[test_log::test]
503 fn network_roundtrip() {
504 let networks: [(bitcoin::Network, [u8; 5], [u8; 4]); 5] = [
505 (
506 bitcoin::Network::Bitcoin,
507 [0xFE, 0xD9, 0xB4, 0xBE, 0xF9],
508 [0xF9, 0xBE, 0xB4, 0xD9],
509 ),
510 (
511 bitcoin::Network::Testnet,
512 [0xFE, 0x07, 0x09, 0x11, 0x0B],
513 [0x0B, 0x11, 0x09, 0x07],
514 ),
515 (
516 bitcoin::Network::Testnet4,
517 [0xFE, 0x28, 0x3F, 0x16, 0x1C],
518 [0x1C, 0x16, 0x3F, 0x28],
519 ),
520 (
521 bitcoin::Network::Signet,
522 [0xFE, 0x40, 0xCF, 0x03, 0x0A],
523 [0x0A, 0x03, 0xCF, 0x40],
524 ),
525 (
526 bitcoin::Network::Regtest,
527 [0xFE, 0xDA, 0xB5, 0xBF, 0xFA],
528 [0xFA, 0xBF, 0xB5, 0xDA],
529 ),
530 ];
531
532 for (network, magic_legacy_bytes, magic_bytes) in networks {
533 let network_legacy_encoded =
534 NetworkLegacyEncodingWrapper(network).consensus_encode_to_vec();
535
536 let network_encoded = network.consensus_encode_to_vec();
537
538 let network_legacy_decoded = NetworkLegacyEncodingWrapper::consensus_decode_whole(
539 &network_legacy_encoded,
540 &ModuleDecoderRegistry::default(),
541 )
542 .unwrap()
543 .0;
544
545 let network_decoded = bitcoin::Network::consensus_decode_whole(
546 &network_encoded,
547 &ModuleDecoderRegistry::default(),
548 )
549 .unwrap();
550
551 assert_eq!(magic_legacy_bytes, *network_legacy_encoded);
552 assert_eq!(magic_bytes, *network_encoded);
553 assert_eq!(network, network_legacy_decoded);
554 assert_eq!(network, network_decoded);
555 }
556 }
557
558 #[test_log::test]
559 fn address_roundtrip() {
560 let addresses = [
561 "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5",
562 "mxMYaq5yWinZ9AKjCDcBEbiEwPJD9n2uLU",
563 "1FK8o7mUxyd6QWJAUw7J4vW7eRxuyjj6Ne",
564 "3JSrSU7z7R1Yhh26pt1zzRjQz44qjcrXwb",
565 "tb1qunn0thpt8uk3yk2938ypjccn3urxprt78z9ccq",
566 "2MvUMRv2DRHZi3VshkP7RMEU84mVTfR9xjq",
567 ];
568
569 for address_str in addresses {
570 let address =
571 bitcoin::Address::from_str(address_str).expect("All tested addresses are valid");
572 let encoding = address.consensus_encode_to_vec();
573 let parsed_address = bitcoin::Address::consensus_decode_whole(
574 &encoding,
575 &ModuleDecoderRegistry::default(),
576 )
577 .expect("Decoding address failed");
578
579 assert_eq!(address, parsed_address);
580 }
581 }
582
583 #[test_log::test]
584 fn sha256_roundtrip() {
585 test_roundtrip_expected(
586 &bitcoin::hashes::sha256::Hash::hash(b"Hello world!"),
587 &[
588 192, 83, 94, 75, 226, 183, 159, 253, 147, 41, 19, 5, 67, 107, 248, 137, 49, 78, 74,
589 63, 174, 192, 94, 207, 252, 187, 125, 243, 26, 217, 229, 26,
590 ],
591 );
592 }
593
594 #[test_log::test]
595 fn bolt11_invoice_roundtrip() {
596 let invoice_str = "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcs\
597 h2pu5qc7lgq0xs578ngs6s0s68ua4h7cvspp5q6rmq35js88zp5dvwrv9m459tnk2zunwj5jalqtyxqulh0l\
598 5gflssp5nf55ny5gcrfl30xuhzj3nphgj27rstekmr9fw3ny5989s300gyus9qyysgqcqpcrzjqw2sxwe993\
599 h5pcm4dxzpvttgza8zhkqxpgffcrf5v25nwpr3cmfg7z54kuqq8rgqqqqqqqq2qqqqq9qq9qrzjqd0ylaqcl\
600 j9424x9m8h2vcukcgnm6s56xfgu3j78zyqzhgs4hlpzvznlugqq9vsqqqqqqqlgqqqqqeqq9qrzjqwldmj9d\
601 ha74df76zhx6l9we0vjdquygcdt3kssupehe64g6yyp5yz5rhuqqwccqqyqqqqlgqqqqjcqq9qrzjqf9e58a\
602 guqr0rcun0ajlvmzq3ek63cw2w282gv3z5uupmuwvgjtq2z55qsqqg6qqqyqqqrtnqqqzq3cqygrzjqvphms\
603 ywntrrhqjcraumvc4y6r8v4z5v593trte429v4hredj7ms5z52usqq9ngqqqqqqqlgqqqqqqgq9qrzjq2v0v\
604 p62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh3\
605 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
606 j5r6drg6k6zcqj0fcwg";
607 let invoice = invoice_str
608 .parse::<lightning_invoice::Bolt11Invoice>()
609 .unwrap();
610 test_roundtrip(&invoice);
611 }
612}