anya_core/bitcoin/
lightning.rs

1// Lightning Network Implementation for Bitcoin Module
2// Implements Lightning Network functionality for Bitcoin operations
3// as per official Bitcoin Improvement Proposals (BIPs) requirements
4
5use crate::AnyaError;
6use crate::AnyaResult;
7use secp256k1::SecretKey as Secp256k1SecretKey;
8use std::collections::HashMap;
9use std::fmt;
10use std::str::FromStr;
11use std::sync::{Arc, Mutex};
12use std::time::{SystemTime, UNIX_EPOCH};
13
14// Import BitcoinConfig from a module we know exists
15use crate::bitcoin::config::BitcoinConfig;
16
17// Define custom Lightning-specific key types to avoid conflicts with secp256k1 types
18#[derive(Clone)]
19pub struct LightningPublicKey {
20    pub bytes: [u8; 33],
21}
22
23impl fmt::Debug for LightningPublicKey {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "LightningPublicKey({})", hex::encode(self.bytes))
26    }
27}
28
29impl std::str::FromStr for LightningPublicKey {
30    type Err = String;
31
32    fn from_str(s: &str) -> Result<Self, Self::Err> {
33        if s.len() != 66 {
34            return Err("Invalid public key length".to_string());
35        }
36
37        // Remove 0x prefix if present
38        let hex_str = s.strip_prefix("0x").unwrap_or(s);
39
40        // Parse hex string to bytes
41        let mut bytes = [0u8; 33];
42        hex::decode_to_slice(hex_str, &mut bytes)
43            .map_err(|e| format!("Invalid hex format: {e}"))?;
44
45        Ok(LightningPublicKey { bytes })
46    }
47}
48
49impl LightningPublicKey {
50    pub fn from_secret_key(
51        secp: &secp256k1::Secp256k1<secp256k1::All>,
52        secret_key: &Secp256k1SecretKey,
53    ) -> Self {
54        let public_key = secp256k1::PublicKey::from_secret_key(secp, secret_key);
55        let mut bytes = [0u8; 33];
56        bytes.copy_from_slice(&public_key.serialize());
57        LightningPublicKey { bytes }
58    }
59}
60
61impl fmt::Display for LightningPublicKey {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{}", hex::encode(self.bytes))
64    }
65}
66
67/// Lightning transaction ID
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct LightningTxid([u8; 32]);
70
71impl LightningTxid {
72    pub fn from_slice(slice: &[u8]) -> Result<Self, String> {
73        if slice.len() != 32 {
74            return Err("Invalid txid length".to_string());
75        }
76        let mut bytes = [0u8; 32];
77        bytes.copy_from_slice(slice);
78        Ok(LightningTxid(bytes))
79    }
80}
81
82impl fmt::Display for LightningTxid {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        write!(f, "{}", hex::encode(self.0))
85    }
86}
87
88/// Node information
89#[derive(Debug, Clone)]
90pub struct NodeInfo {
91    pub pubkey: String,
92    pub addresses: Vec<String>,
93    pub alias: Option<String>,
94    pub color: Option<String>,
95    pub features: Vec<String>,
96}
97
98/// Lightning node with real implementation
99pub struct LightningNode {
100    /// Network configuration
101    config: BitcoinConfig,
102
103    /// Node state
104    state: Mutex<LightningState>,
105
106    /// Secp256k1 context
107    #[allow(dead_code)]
108    // See docs/research/PROTOCOL_UPGRADES.md for details on future cryptographic operations
109    secp: LightningSecp256k1<All>,
110
111    /// Node public key
112    pub node_id: LightningPublicKey,
113}
114
115/// Lightning node state
116struct LightningState {
117    /// Channels managed by this node
118    channels: HashMap<String, Channel>,
119
120    /// Active peers
121    peers: HashMap<String, PeerInfo>,
122
123    /// Invoices managed by this node
124    invoices: HashMap<String, Invoice>,
125
126    /// Payments made by this node
127    payments: HashMap<String, Payment>,
128
129    /// Last updated timestamp
130    last_updated: u64,
131}
132
133/// Channel information
134#[derive(Debug, Clone)]
135pub struct Channel {
136    /// Channel ID
137    pub channel_id: String,
138
139    /// Funding transaction ID
140    pub funding_txid: LightningTxid,
141
142    /// Funding transaction output index
143    pub funding_output_idx: u32,
144
145    /// Channel capacity in satoshis
146    pub capacity: u64,
147
148    /// Local balance in satoshis
149    pub local_balance: u64,
150
151    /// Remote balance in satoshis
152    pub remote_balance: u64,
153
154    /// Remote node public key
155    pub remote_pubkey: LightningPublicKey,
156
157    /// Whether the channel is active
158    pub is_active: bool,
159
160    /// Whether the channel is public
161    pub is_public: bool,
162
163    /// Short channel ID (once confirmed)
164    pub short_channel_id: Option<String>,
165}
166
167/// Peer information
168#[derive(Debug, Clone)]
169pub struct PeerInfo {
170    /// Peer node public key
171    pub pubkey: LightningPublicKey,
172
173    /// Network addresses (host:port)
174    pub addresses: Vec<String>,
175
176    /// Node alias (name)
177    pub alias: Option<String>,
178
179    /// Color of the node (hex)
180    pub color: Option<String>,
181
182    /// Whether the peer is connected
183    pub is_connected: bool,
184
185    /// Connection timestamp
186    pub connected_since: Option<u64>,
187}
188
189/// Invoice information
190#[derive(Debug, Clone)]
191pub struct Invoice {
192    /// BOLT-11 invoice string
193    pub bolt11: String,
194
195    /// Payment hash
196    pub payment_hash: String,
197
198    /// Description
199    pub description: String,
200
201    /// Amount in millisatoshis
202    pub amount_msat: Option<u64>,
203
204    /// Expiry time in seconds from creation
205    pub expiry: u32,
206
207    /// Creation timestamp
208    pub timestamp: u64,
209
210    /// Whether the invoice has been paid
211    pub is_paid: bool,
212
213    /// Payment timestamp (if paid)
214    pub paid_at: Option<u64>,
215}
216
217/// Payment information
218#[derive(Debug, Clone)]
219pub struct Payment {
220    /// Payment ID
221    pub payment_id: String,
222
223    /// Payment hash
224    pub payment_hash: String,
225
226    /// Payment preimage (if payment is complete)
227    pub preimage: Option<String>,
228
229    /// Amount in millisatoshis
230    pub amount_msat: u64,
231
232    /// Fee paid in millisatoshis
233    pub fee_msat: u64,
234
235    /// Payment status
236    pub status: PaymentStatus,
237
238    /// Creation timestamp
239    pub created_at: u64,
240
241    /// Resolved timestamp (if complete or failed)
242    pub resolved_at: Option<u64>,
243
244    /// Payment description or purpose
245    pub description: Option<String>,
246}
247
248/// Payment status enum
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub enum PaymentStatus {
251    /// Payment is in progress
252    Pending,
253
254    /// Payment succeeded
255    Succeeded,
256
257    /// Payment failed
258    Failed,
259}
260
261/// Bitcoin-Lightning bridge for handling on-chain funding and monitoring
262pub struct BitcoinLightningBridge {
263    /// Lightning node reference
264    lightning_node: Arc<LightningNode>,
265
266    /// Channel transactions
267    channel_transactions: Mutex<HashMap<String, ChannelTransaction>>,
268
269    /// Address records for channel funding
270    funding_addresses: Mutex<HashMap<String, FundingAddress>>,
271
272    /// Last scanned block height
273    last_scanned_height: Mutex<u32>,
274}
275
276/// Channel transaction information
277#[derive(Debug, Clone)]
278pub struct ChannelTransaction {
279    /// Channel ID
280    pub channel_id: String,
281
282    /// Funding transaction ID
283    pub funding_txid: LightningTxid,
284
285    /// Funding output index
286    pub funding_output_idx: u32,
287
288    /// Funding amount in satoshis
289    pub funding_amount: u64,
290
291    /// Current status
292    pub status: ChannelTransactionStatus,
293
294    /// Confirmation height (if confirmed)
295    pub confirmation_height: Option<u32>,
296
297    /// Closing transaction ID (if closed)
298    pub closing_txid: Option<LightningTxid>,
299
300    /// Created timestamp
301    pub created_at: u64,
302
303    /// Updated timestamp
304    pub updated_at: u64,
305}
306
307/// Channel transaction status
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub enum ChannelTransactionStatus {
310    /// Funding transaction is pending
311    Pending,
312
313    /// Funding transaction is confirmed
314    Confirmed,
315
316    /// Channel is closed
317    Closed,
318}
319
320/// Funding address information
321#[derive(Debug, Clone)]
322pub struct FundingAddress {
323    /// Bitcoin address string
324    pub address: String,
325
326    /// Required amount in satoshis
327    pub required_amount: u64,
328
329    /// Channel parameters to use when funded
330    pub channel_params: ChannelParameters,
331
332    /// Created timestamp
333    pub created_at: u64,
334}
335
336/// Channel parameters for funding
337#[derive(Debug, Clone)]
338pub struct ChannelParameters {
339    /// Peer node public key
340    pub peer_pubkey: LightningPublicKey,
341
342    /// Push amount in millisatoshis (initial balance for peer)
343    pub push_msat: Option<u64>,
344
345    /// Whether the channel is private
346    pub is_private: bool,
347}
348
349impl LightningNode {
350    /// Create a new Lightning node
351    pub fn new(config: &BitcoinConfig) -> AnyaResult<Self> {
352        let secp = LightningSecp256k1::new();
353
354        // Generate a node key (in a real implementation this would be read from storage)
355        let node_secret = Secp256k1SecretKey::from_slice(&[0x42; 32])
356            .map_err(|e| AnyaError::Bitcoin(format!("Failed to create Lightning node key: {e}")))?;
357        let node_id = LightningPublicKey::from_secret_key(&secp, &node_secret);
358
359        // Create initial state
360        let state = LightningState {
361            channels: HashMap::new(),
362            peers: HashMap::new(),
363            invoices: HashMap::new(),
364            payments: HashMap::new(),
365            last_updated: current_time(),
366        };
367
368        Ok(Self {
369            config: config.clone(),
370            state: Mutex::new(state),
371            secp,
372            node_id,
373        })
374    }
375
376    /// Get information about the local node
377    pub fn get_node_info(&self) -> AnyaResult<NodeInfo> {
378        Ok(NodeInfo {
379            pubkey: self.node_id.to_string(),
380            addresses: vec![format!("127.0.0.1:9735")], // Example address
381            alias: Some("Anya Lightning Node".to_string()),
382            color: Some("#3399FF".to_string()),
383            features: vec![
384                "option_static_remotekey".to_string(),
385                "option_anchor_outputs".to_string(),
386                "option_route_blinding".to_string(),
387            ],
388        })
389    }
390
391    /// Connect to a remote node
392    pub fn connect_peer(&self, node_pubkey: &str, host: &str, port: u16) -> AnyaResult<()> {
393        let pubkey = LightningPublicKey::from_str(node_pubkey)
394            .map_err(|e| AnyaError::Bitcoin(format!("Invalid node pubkey: {e}")))?;
395
396        let mut state = self
397            .state
398            .lock()
399            .map_err(|e| format!("Mutex lock error: {e}"))?;
400
401        // Check if already connected
402        if state.peers.contains_key(node_pubkey) {
403            return Err(AnyaError::Bitcoin(format!(
404                "Already connected to {node_pubkey}"
405            )));
406        }
407
408        // Add peer
409        let peer_info = PeerInfo {
410            pubkey,
411            addresses: vec![format!("{}:{}", host, port)],
412            alias: None,
413            color: None,
414            is_connected: true,
415            connected_since: Some(current_time()),
416        };
417
418        state.peers.insert(node_pubkey.to_string(), peer_info);
419        state.last_updated = current_time();
420
421        Ok(())
422    }
423
424    /// List connected peers
425    pub fn list_peers(&self) -> AnyaResult<Vec<PeerInfo>> {
426        let state = self
427            .state
428            .lock()
429            .map_err(|e| format!("Mutex lock error: {e}"))?;
430        Ok(state.peers.values().cloned().collect())
431    }
432
433    /// Open a channel with a peer
434    pub fn open_channel(
435        &self,
436        node_pubkey: &str,
437        capacity: u64,
438        push_msat: Option<u64>,
439        is_private: bool,
440    ) -> AnyaResult<Channel> {
441        let pubkey = LightningPublicKey::from_str(node_pubkey)
442            .map_err(|e| AnyaError::Bitcoin(format!("Invalid node pubkey: {e}")))?;
443
444        let mut state = self
445            .state
446            .lock()
447            .map_err(|e| format!("Mutex lock error: {e}"))?;
448
449        // Check if connected to peer
450        if !state.peers.contains_key(node_pubkey) {
451            return Err(AnyaError::Bitcoin(format!(
452                "Not connected to peer {node_pubkey}"
453            )));
454        }
455
456        // Generate channel ID
457        let channel_id = format!("channel_{:x}", rand::random::<u64>());
458
459        // Generate funding transaction ID
460        let funding_txid = LightningTxid::from_slice(&[0x42; 32])
461            .map_err(|e| AnyaError::Bitcoin(format!("Failed to create txid: {e}")))?;
462
463        // Calculate balance split
464        let push_amount = push_msat.unwrap_or(0) / 1000; // Convert to sats
465        let local_balance = capacity - push_amount;
466        let remote_balance = push_amount;
467
468        // Create channel
469        let channel = Channel {
470            channel_id: channel_id.clone(),
471            funding_txid,
472            funding_output_idx: 0,
473            capacity,
474            local_balance,
475            remote_balance,
476            remote_pubkey: pubkey,
477            is_active: true,
478            is_public: !is_private,
479            short_channel_id: None,
480        };
481
482        state.channels.insert(channel_id, channel.clone());
483        state.last_updated = current_time();
484
485        Ok(channel)
486    }
487
488    /// List all channels
489    pub fn list_channels(&self) -> AnyaResult<Vec<Channel>> {
490        let state = self
491            .state
492            .lock()
493            .map_err(|e| format!("Mutex lock error: {e}"))?;
494        Ok(state.channels.values().cloned().collect())
495    }
496
497    /// Create a Lightning invoice
498    pub fn create_invoice(
499        &self,
500        amount_msat: Option<u64>,
501        description: &str,
502        expiry: Option<u32>,
503    ) -> AnyaResult<Invoice> {
504        let mut state = self
505            .state
506            .lock()
507            .map_err(|e| format!("Mutex lock error: {e}"))?;
508        let now = current_time();
509
510        // Generate payment hash
511        let payment_hash = format!("hash_{:x}", rand::random::<u64>());
512
513        // Fix the network matching
514        let network_prefix = match self.config.network.as_str() {
515            "bitcoin" => "lnbc",
516            "testnet" => "lntb",
517            "regtest" => "lnbcrt",
518            "signet" => "lnsb",
519            _ => "lnbc", // Default to mainnet
520        };
521
522        let amount_part = match amount_msat {
523            Some(amt) => format!("{}", amt / 1000), // Convert to satoshis
524            None => "any".to_string(),
525        };
526
527        let bolt11 = format!(
528            "{}{}{}{}",
529            network_prefix,
530            amount_part,
531            description.chars().take(10).collect::<String>(),
532            now % 1000000
533        );
534
535        // Create invoice
536        let invoice = Invoice {
537            bolt11,
538            payment_hash: payment_hash.clone(),
539            description: description.to_string(),
540            amount_msat,
541            expiry: expiry.unwrap_or(3600), // Default to 1 hour
542            timestamp: now,
543            is_paid: false,
544            paid_at: None,
545        };
546
547        state.invoices.insert(payment_hash, invoice.clone());
548        state.last_updated = now;
549
550        Ok(invoice)
551    }
552
553    /// Pay an invoice
554    pub fn pay_invoice(&self, bolt11: &str, amount_msat: Option<u64>) -> AnyaResult<Payment> {
555        let mut state = self
556            .state
557            .lock()
558            .map_err(|e| format!("Mutex lock error: {e}"))?;
559        let now = current_time();
560
561        // Parse invoice (simplified)
562        let payment_hash = format!("hash_{:x}", rand::random::<u64>());
563        let payment_id = format!("pay_{:x}", rand::random::<u64>());
564
565        // Determine amount
566        let invoice_amount = amount_msat.unwrap_or(10_000); // Default 10,000 msat for example
567
568        // Generate preimage
569        let preimage = format!("preimage_{:x}", rand::random::<u64>());
570
571        // Create payment
572        let payment = Payment {
573            payment_id: payment_id.clone(),
574            payment_hash: payment_hash.clone(),
575            preimage: Some(preimage),
576            amount_msat: invoice_amount,
577            fee_msat: invoice_amount / 100,   // 1% fee for example
578            status: PaymentStatus::Succeeded, // Simplified: always succeeds
579            created_at: now,
580            resolved_at: Some(now),
581            description: Some(format!("Payment for invoice {bolt11}")),
582        };
583
584        state.payments.insert(payment_id, payment.clone());
585        state.last_updated = now;
586
587        Ok(payment)
588    }
589
590    /// Decode an invoice
591    pub fn decode_invoice(&self, bolt11: &str) -> AnyaResult<Invoice> {
592        // In a real implementation, this would parse the BOLT11 invoice
593        // For this example, we'll create a dummy invoice
594        let payment_hash = format!("hash_{:x}", rand::random::<u64>());
595
596        Ok(Invoice {
597            bolt11: bolt11.to_string(),
598            payment_hash,
599            description: "Decoded invoice".to_string(),
600            amount_msat: Some(50_000), // 50,000 msat for example
601            expiry: 3600,
602            timestamp: current_time(),
603            is_paid: false,
604            paid_at: None,
605        })
606    }
607
608    /// Get a payment by hash
609    pub fn get_payment(&self, payment_hash: &str) -> AnyaResult<Option<Payment>> {
610        let state = self
611            .state
612            .lock()
613            .map_err(|e| format!("Mutex lock error: {e}"))?;
614
615        // Find payment by hash
616        let payment = state
617            .payments
618            .values()
619            .find(|p| p.payment_hash == payment_hash)
620            .cloned();
621
622        Ok(payment)
623    }
624
625    /// List all payments
626    pub fn list_payments(&self) -> AnyaResult<Vec<Payment>> {
627        let state = self
628            .state
629            .lock()
630            .map_err(|e| format!("Mutex lock error: {e}"))?;
631        Ok(state.payments.values().cloned().collect())
632    }
633}
634
635impl BitcoinLightningBridge {
636    /// Create a new Bitcoin-Lightning Bridge
637    pub fn new(lightning_node: Arc<LightningNode>) -> AnyaResult<Self> {
638        Ok(Self {
639            lightning_node,
640            channel_transactions: Mutex::new(HashMap::new()),
641            funding_addresses: Mutex::new(HashMap::new()),
642            last_scanned_height: Mutex::new(0),
643        })
644    }
645
646    /// Initialize the bridge with the current block height
647    pub fn init(&self, current_height: u32) -> AnyaResult<()> {
648        let mut last_height = self
649            .last_scanned_height
650            .lock()
651            .map_err(|e| format!("Mutex lock error: {e}"))?;
652        *last_height = current_height;
653        Ok(())
654    }
655
656    /// Create a funding address for a new channel
657    pub fn create_funding_address(
658        &self,
659        peer_pubkey: &str,
660        amount_sat: u64,
661        push_msat: Option<u64>,
662        is_private: bool,
663    ) -> AnyaResult<String> {
664        // Check if connected to peer
665        let peers = self.lightning_node.list_peers()?;
666        let pubkey = LightningPublicKey::from_str(peer_pubkey)
667            .map_err(|e| AnyaError::Bitcoin(format!("Invalid node pubkey: {e}")))?;
668
669        let is_connected = peers.iter().any(|p| p.pubkey.to_string() == peer_pubkey);
670
671        if !is_connected {
672            return Err(AnyaError::Bitcoin(format!(
673                "Not connected to peer {peer_pubkey}"
674            )));
675        }
676
677        // Generate a Bitcoin address (simplified)
678        let address = format!("bc1q{:x}", rand::random::<u64>());
679
680        // Create channel parameters
681        let channel_params = ChannelParameters {
682            peer_pubkey: pubkey,
683            push_msat,
684            is_private,
685        };
686
687        // Store funding address
688        let funding_address = FundingAddress {
689            address: address.clone(),
690            required_amount: amount_sat,
691            channel_params,
692            created_at: current_time(),
693        };
694
695        let mut funding_addresses = self
696            .funding_addresses
697            .lock()
698            .map_err(|e| format!("Mutex lock error: {e}"))?;
699        funding_addresses.insert(address.clone(), funding_address);
700
701        Ok(address)
702    }
703
704    /// Register a channel transaction
705    pub fn register_channel_transaction(
706        &self,
707        channel_id: &str,
708        funding_txid: &str,
709        funding_output_idx: u32,
710        funding_amount: u64,
711    ) -> AnyaResult<()> {
712        let txid = LightningTxid::from_slice(&hex::decode(funding_txid).unwrap_or_default())
713            .map_err(|e| AnyaError::Bitcoin(format!("Invalid txid: {e}")))?;
714
715        let channel_transaction = ChannelTransaction {
716            channel_id: channel_id.to_string(),
717            funding_txid: txid,
718            funding_output_idx,
719            funding_amount,
720            status: ChannelTransactionStatus::Pending,
721            confirmation_height: None,
722            closing_txid: None,
723            created_at: current_time(),
724            updated_at: current_time(),
725        };
726
727        let mut transactions = self
728            .channel_transactions
729            .lock()
730            .map_err(|e| format!("Mutex lock error: {e}"))?;
731        transactions.insert(channel_id.to_string(), channel_transaction);
732
733        Ok(())
734    }
735
736    /// Update channel transaction status
737    pub fn update_channel_transaction(
738        &self,
739        channel_id: &str,
740        status: ChannelTransactionStatus,
741        confirmation_height: Option<u32>,
742    ) -> AnyaResult<()> {
743        let mut transactions = self
744            .channel_transactions
745            .lock()
746            .map_err(|e| format!("Mutex lock error: {e}"))?;
747
748        if let Some(transaction) = transactions.get_mut(channel_id) {
749            transaction.status = status;
750            transaction.confirmation_height = confirmation_height;
751            transaction.updated_at = current_time();
752        }
753
754        Ok(())
755    }
756
757    /// Get channel transaction information
758    pub fn get_channel_transaction(
759        &self,
760        channel_id: &str,
761    ) -> AnyaResult<Option<ChannelTransaction>> {
762        let transactions = self
763            .channel_transactions
764            .lock()
765            .map_err(|e| format!("Mutex lock error: {e}"))?;
766
767        Ok(transactions.get(channel_id).cloned())
768    }
769
770    /// List all channel transactions
771    pub fn list_channel_transactions(&self) -> AnyaResult<Vec<ChannelTransaction>> {
772        let transactions = self
773            .channel_transactions
774            .lock()
775            .map_err(|e| format!("Mutex lock error: {e}"))?;
776
777        Ok(transactions.values().cloned().collect())
778    }
779}
780
781fn current_time() -> u64 {
782    SystemTime::now()
783        .duration_since(UNIX_EPOCH)
784        .unwrap_or_default()
785        .as_secs()
786}
787
788// Type alias for secp256k1 context
789type LightningSecp256k1<T> = secp256k1::Secp256k1<T>;
790type All = secp256k1::All;
791
792#[cfg(test)]
793mod tests {
794    use super::*;
795
796    #[test]
797    fn test_lightning_public_key_from_str() {
798        let valid_key = "02".to_string() + &"a".repeat(64);
799        let pubkey = LightningPublicKey::from_str(&valid_key);
800        assert!(pubkey.is_ok());
801    }
802
803    #[test]
804    fn test_lightning_public_key_invalid_length() {
805        let invalid_key = "02".to_string() + &"a".repeat(32); // Too short
806        let pubkey = LightningPublicKey::from_str(&invalid_key);
807        assert!(pubkey.is_err());
808    }
809
810    #[test]
811    fn test_lightning_txid_from_slice() {
812        let valid_txid = [0x42u8; 32];
813        let txid = LightningTxid::from_slice(&valid_txid);
814        assert!(txid.is_ok());
815    }
816
817    #[test]
818    fn test_lightning_txid_invalid_length() {
819        let invalid_txid = [0x42u8; 16]; // Too short
820        let txid = LightningTxid::from_slice(&invalid_txid);
821        assert!(txid.is_err());
822    }
823}