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