Merge pull request #1923 from valentinewallace/2022-12-outbound-payment-mod
authorvalentinewallace <valentinewallace@users.noreply.github.com>
Tue, 20 Dec 2022 20:40:48 +0000 (15:40 -0500)
committerGitHub <noreply@github.com>
Tue, 20 Dec 2022 20:40:48 +0000 (15:40 -0500)
Abstract `ChannelManager` outbound payment logic

lightning/src/chain/channelmonitor.rs
lightning/src/ln/functional_tests.rs
lightning/src/ln/monitor_tests.rs
lightning/src/ln/payment_tests.rs
lightning/src/ln/reorg_tests.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/mod.rs
lightning/src/offers/offer.rs
lightning/src/offers/parse.rs
lightning/src/offers/refund.rs [new file with mode: 0644]
lightning/src/util/ser_macros.rs

index a41d853311ce924f388eaa5efbbb2ceb79489a64..db17810599d66e4d472d96ec96af76f1ced407f3 100644 (file)
@@ -2325,6 +2325,17 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
                                        log_trace!(logger, "Updating ChannelMonitor: channel force closed, should broadcast: {}", should_broadcast);
                                        self.lockdown_from_offchain = true;
                                        if *should_broadcast {
+                                               // There's no need to broadcast our commitment transaction if we've seen one
+                                               // confirmed (even with 1 confirmation) as it'll be rejected as
+                                               // duplicate/conflicting.
+                                               let detected_funding_spend = self.funding_spend_confirmed.is_some() ||
+                                                       self.onchain_events_awaiting_threshold_conf.iter().find(|event| match event.event {
+                                                               OnchainEvent::FundingSpendConfirmation { .. } => true,
+                                                               _ => false,
+                                                       }).is_some();
+                                               if detected_funding_spend {
+                                                       continue;
+                                               }
                                                self.broadcast_latest_holder_commitment_txn(broadcaster, logger);
                                                // If the channel supports anchor outputs, we'll need to emit an external
                                                // event to be consumed such that a child transaction is broadcast with a
index 28719f2d61ecb41c153894ee5aa90e91f8d1dd9c..4b1f95155fa636145b39156583d40603c807a5bf 100644 (file)
@@ -1268,19 +1268,16 @@ fn test_duplicate_htlc_different_direction_onchain() {
        connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
 
        let claim_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-       assert_eq!(claim_txn.len(), 5);
+       assert_eq!(claim_txn.len(), 3);
 
        check_spends!(claim_txn[0], remote_txn[0]); // Immediate HTLC claim with preimage
-       check_spends!(claim_txn[1], chan_1.3); // Alternative commitment tx
-       check_spends!(claim_txn[2], claim_txn[1]); // HTLC spend in alternative commitment tx
-
-       check_spends!(claim_txn[3], remote_txn[0]);
-       check_spends!(claim_txn[4], remote_txn[0]);
+       check_spends!(claim_txn[1], remote_txn[0]);
+       check_spends!(claim_txn[2], remote_txn[0]);
        let preimage_tx = &claim_txn[0];
-       let (preimage_bump_tx, timeout_tx) = if claim_txn[3].input[0].previous_output == preimage_tx.input[0].previous_output {
-               (&claim_txn[3], &claim_txn[4])
+       let (preimage_bump_tx, timeout_tx) = if claim_txn[1].input[0].previous_output == preimage_tx.input[0].previous_output {
+               (&claim_txn[1], &claim_txn[2])
        } else {
-               (&claim_txn[4], &claim_txn[3])
+               (&claim_txn[2], &claim_txn[1])
        };
 
        assert_eq!(preimage_tx.input.len(), 1);
@@ -2196,7 +2193,7 @@ fn channel_monitor_network_test() {
                assert_eq!(node_txn.len(), 1);
                mine_transaction(&nodes[0], &node_txn[0]);
                check_added_monitors!(nodes[0], 1);
-               test_txn_broadcast(&nodes[0], &chan_1, None, HTLCType::NONE);
+               test_txn_broadcast(&nodes[0], &chan_1, Some(node_txn[0].clone()), HTLCType::NONE);
        }
        check_closed_broadcast!(nodes[0], true);
        assert_eq!(nodes[0].node.list_channels().len(), 0);
@@ -2218,7 +2215,7 @@ fn channel_monitor_network_test() {
                test_txn_broadcast(&nodes[1], &chan_2, None, HTLCType::TIMEOUT);
                mine_transaction(&nodes[2], &node_txn[0]);
                check_added_monitors!(nodes[2], 1);
-               test_txn_broadcast(&nodes[2], &chan_2, None, HTLCType::NONE);
+               test_txn_broadcast(&nodes[2], &chan_2, Some(node_txn[0].clone()), HTLCType::NONE);
        }
        check_closed_broadcast!(nodes[2], true);
        assert_eq!(nodes[1].node.list_channels().len(), 0);
@@ -2386,16 +2383,15 @@ fn test_justice_tx() {
                mine_transaction(&nodes[1], &revoked_local_txn[0]);
                {
                        let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
-                       assert_eq!(node_txn.len(), 2); // ChannelMonitor: penalty tx, ChannelManager: local commitment tx
+                       assert_eq!(node_txn.len(), 1); // ChannelMonitor: penalty tx
                        assert_eq!(node_txn[0].input.len(), 2); // We should claim the revoked output and the HTLC output
 
                        check_spends!(node_txn[0], revoked_local_txn[0]);
                        node_txn.swap_remove(0);
-                       node_txn.truncate(1);
                }
                check_added_monitors!(nodes[1], 1);
                check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
-               test_txn_broadcast(&nodes[1], &chan_5, None, HTLCType::NONE);
+               test_txn_broadcast(&nodes[1], &chan_5, Some(revoked_local_txn[0].clone()), HTLCType::NONE);
 
                mine_transaction(&nodes[0], &revoked_local_txn[0]);
                connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
@@ -2434,14 +2430,14 @@ fn test_justice_tx() {
                mine_transaction(&nodes[0], &revoked_local_txn[0]);
                {
                        let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
-                       assert_eq!(node_txn.len(), 2); //ChannelMonitor: penalty tx, ChannelManager: local commitment tx
+                       assert_eq!(node_txn.len(), 1); // ChannelMonitor: penalty tx
                        assert_eq!(node_txn[0].input.len(), 1); // We claim the received HTLC output
 
                        check_spends!(node_txn[0], revoked_local_txn[0]);
                        node_txn.swap_remove(0);
                }
                check_added_monitors!(nodes[0], 1);
-               test_txn_broadcast(&nodes[0], &chan_6, None, HTLCType::NONE);
+               test_txn_broadcast(&nodes[0], &chan_6, Some(revoked_local_txn[0].clone()), HTLCType::NONE);
 
                mine_transaction(&nodes[1], &revoked_local_txn[0]);
                check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
@@ -2478,10 +2474,9 @@ fn revoked_output_claim() {
        check_added_monitors!(nodes[1], 1);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
        let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-       assert_eq!(node_txn.len(), 2); // ChannelMonitor: justice tx against revoked to_local output, ChannelManager: local commitment tx
+       assert_eq!(node_txn.len(), 1); // ChannelMonitor: justice tx against revoked to_local output
 
        check_spends!(node_txn[0], revoked_local_txn[0]);
-       check_spends!(node_txn[1], chan_1.3);
 
        // Inform nodes[0] that a watchtower cheated on its behalf, so it will force-close the chan
        mine_transaction(&nodes[0], &revoked_local_txn[0]);
@@ -2532,7 +2527,7 @@ fn claim_htlc_outputs_shared_tx() {
                assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
 
                let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-               assert_eq!(node_txn.len(), 2); // ChannelMonitor: penalty tx, ChannelManager: local commitment
+               assert_eq!(node_txn.len(), 1); // ChannelMonitor: penalty tx
 
                assert_eq!(node_txn[0].input.len(), 3); // Claim the revoked output + both revoked HTLC outputs
                check_spends!(node_txn[0], revoked_local_txn[0]);
@@ -2546,10 +2541,6 @@ fn claim_htlc_outputs_shared_tx() {
                assert_eq!(*witness_lens.iter().skip(1).next().unwrap(), OFFERED_HTLC_SCRIPT_WEIGHT); // revoked offered HTLC
                assert_eq!(*witness_lens.iter().skip(2).next().unwrap(), ACCEPTED_HTLC_SCRIPT_WEIGHT); // revoked received HTLC
 
-               // Next nodes[1] broadcasts its current local tx state:
-               assert_eq!(node_txn[1].input.len(), 1);
-               check_spends!(node_txn[1], chan_1.3);
-
                // Finally, mine the penalty transaction and check that we get an HTLC failure after
                // ANTI_REORG_DELAY confirmations.
                mine_transaction(&nodes[1], &node_txn[0]);
@@ -2602,7 +2593,7 @@ fn claim_htlc_outputs_single_tx() {
                assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
 
                let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-               assert!(node_txn.len() == 9 || node_txn.len() == 10);
+               assert_eq!(node_txn.len(), 7);
 
                // Check the pair local commitment and HTLC-timeout broadcast due to HTLC expiration
                assert_eq!(node_txn[0].input.len(), 1);
@@ -2612,7 +2603,7 @@ fn claim_htlc_outputs_single_tx() {
                assert_eq!(witness_script.len(), OFFERED_HTLC_SCRIPT_WEIGHT); //Spending an offered htlc output
                check_spends!(node_txn[1], node_txn[0]);
 
-               // Justice transactions are indices 1-2-4
+               // Justice transactions are indices 2-3-4
                assert_eq!(node_txn[2].input.len(), 1);
                assert_eq!(node_txn[3].input.len(), 1);
                assert_eq!(node_txn[4].input.len(), 1);
@@ -2701,11 +2692,8 @@ fn test_htlc_on_chain_success() {
        check_closed_broadcast!(nodes[2], true);
        check_added_monitors!(nodes[2], 1);
        check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
-       let node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelManager : 3 (commitment tx, 2*htlc-success tx), ChannelMonitor : 2 (2 * HTLC-Success tx)
-       assert_eq!(node_txn.len(), 5);
-       assert_eq!(node_txn[0], node_txn[3]);
-       assert_eq!(node_txn[1], node_txn[4]);
-       assert_eq!(node_txn[2], commitment_tx[0]);
+       let node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelMonitor: 2 (2 * HTLC-Success tx)
+       assert_eq!(node_txn.len(), 2);
        check_spends!(node_txn[0], commitment_tx[0]);
        check_spends!(node_txn[1], commitment_tx[0]);
        assert_eq!(node_txn[0].input[0].witness.clone().last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
@@ -2717,7 +2705,7 @@ fn test_htlc_on_chain_success() {
 
        // Verify that B's ChannelManager is able to extract preimage from HTLC Success tx and pass it backward
        let header = BlockHeader { version: 0x20000000, prev_blockhash: nodes[1].best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42};
-       connect_block(&nodes[1], &Block { header, txdata: node_txn});
+       connect_block(&nodes[1], &Block { header, txdata: vec![commitment_tx[0].clone(), node_txn[0].clone(), node_txn[1].clone()]});
        connect_blocks(&nodes[1], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
        {
                let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
@@ -2779,35 +2767,31 @@ fn test_htlc_on_chain_success() {
                _ => panic!("Unexpected event"),
        };
        macro_rules! check_tx_local_broadcast {
-               ($node: expr, $htlc_offered: expr, $commitment_tx: expr, $chan_tx: expr) => { {
+               ($node: expr, $htlc_offered: expr, $commitment_tx: expr) => { {
                        let mut node_txn = $node.tx_broadcaster.txn_broadcasted.lock().unwrap();
-                       assert_eq!(node_txn.len(), 3);
-                       // Node[1]: ChannelManager: 3 (commitment tx, 2*HTLC-Timeout tx), ChannelMonitor: 2 (timeout tx)
-                       // Node[0]: ChannelManager: 3 (commtiemtn tx, 2*HTLC-Timeout tx), ChannelMonitor: 2 HTLC-timeout
+                       assert_eq!(node_txn.len(), 2);
+                       // Node[1]: 2 * HTLC-timeout tx
+                       // Node[0]: 2 * HTLC-timeout tx
+                       check_spends!(node_txn[0], $commitment_tx);
                        check_spends!(node_txn[1], $commitment_tx);
-                       check_spends!(node_txn[2], $commitment_tx);
+                       assert_ne!(node_txn[0].lock_time.0, 0);
                        assert_ne!(node_txn[1].lock_time.0, 0);
-                       assert_ne!(node_txn[2].lock_time.0, 0);
                        if $htlc_offered {
+                               assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
                                assert_eq!(node_txn[1].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
-                               assert_eq!(node_txn[2].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
+                               assert!(node_txn[0].output[0].script_pubkey.is_v0_p2wsh()); // revokeable output
                                assert!(node_txn[1].output[0].script_pubkey.is_v0_p2wsh()); // revokeable output
-                               assert!(node_txn[2].output[0].script_pubkey.is_v0_p2wsh()); // revokeable output
                        } else {
+                               assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
                                assert_eq!(node_txn[1].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
-                               assert_eq!(node_txn[2].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
+                               assert!(node_txn[0].output[0].script_pubkey.is_v0_p2wpkh()); // direct payment
                                assert!(node_txn[1].output[0].script_pubkey.is_v0_p2wpkh()); // direct payment
-                               assert!(node_txn[2].output[0].script_pubkey.is_v0_p2wpkh()); // direct payment
                        }
-                       check_spends!(node_txn[0], $chan_tx);
-                       assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), 71);
                        node_txn.clear();
                } }
        }
-       // nodes[1] now broadcasts its own local state as a fallback, suggesting an alternate
-       // commitment transaction with a corresponding HTLC-Timeout transactions, as well as a
-       // timeout-claim of the output that nodes[2] just claimed via success.
-       check_tx_local_broadcast!(nodes[1], false, commitment_tx[0], chan_2.3);
+       // nodes[1] now broadcasts its own timeout-claim of the output that nodes[2] just claimed via success.
+       check_tx_local_broadcast!(nodes[1], false, commitment_tx[0]);
 
        // Broadcast legit commitment tx from A on B's chain
        // Broadcast preimage tx by B on offered output from A commitment tx  on A's chain
@@ -2818,23 +2802,24 @@ fn test_htlc_on_chain_success() {
        check_added_monitors!(nodes[1], 1);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
        let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-       assert!(node_txn.len() == 4 || node_txn.len() == 6); // ChannelManager : 3 (commitment tx + HTLC-Sucess * 2), ChannelMonitor : 3 (HTLC-Success, 2* RBF bumps of above HTLC txn)
+       assert!(node_txn.len() == 1 || node_txn.len() == 3); // HTLC-Success, 2* RBF bumps of above HTLC txn
        let commitment_spend =
-               if node_txn[0].input[0].previous_output.txid == node_a_commitment_tx[0].txid() {
-                       if node_txn.len() == 6 {
-                               // In some block `ConnectionStyle`s we may avoid broadcasting the double-spending
-                               // transactions spending the HTLC outputs of C's commitment transaction. Otherwise,
-                               // check that the extra broadcasts (double-)spend those here.
+               if node_txn.len() == 1 {
+                       &node_txn[0]
+               } else {
+                       // Certain `ConnectStyle`s will cause RBF bumps of the previous HTLC transaction to be broadcast.
+                       // FullBlockViaListen
+                       if node_txn[0].input[0].previous_output.txid == node_a_commitment_tx[0].txid() {
                                check_spends!(node_txn[1], commitment_tx[0]);
                                check_spends!(node_txn[2], commitment_tx[0]);
                                assert_ne!(node_txn[1].input[0].previous_output.vout, node_txn[2].input[0].previous_output.vout);
+                               &node_txn[0]
+                       } else {
+                               check_spends!(node_txn[0], commitment_tx[0]);
+                               check_spends!(node_txn[1], commitment_tx[0]);
+                               assert_ne!(node_txn[0].input[0].previous_output.vout, node_txn[1].input[0].previous_output.vout);
+                               &node_txn[2]
                        }
-                       &node_txn[0]
-               } else {
-                       check_spends!(node_txn[0], commitment_tx[0]);
-                       check_spends!(node_txn[1], commitment_tx[0]);
-                       assert_ne!(node_txn[0].input[0].previous_output.vout, node_txn[1].input[0].previous_output.vout);
-                       &node_txn[2]
                };
 
        check_spends!(commitment_spend, node_a_commitment_tx[0]);
@@ -2843,11 +2828,6 @@ fn test_htlc_on_chain_success() {
        assert_eq!(commitment_spend.input[1].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
        assert_eq!(commitment_spend.lock_time.0, 0);
        assert!(commitment_spend.output[0].script_pubkey.is_v0_p2wpkh()); // direct payment
-       let funding_spend_offset = if node_txn.len() == 6 { 3 } else { 1 };
-       check_spends!(node_txn[funding_spend_offset], chan_1.3);
-       assert_eq!(node_txn[funding_spend_offset].input[0].witness.clone().last().unwrap().len(), 71);
-       check_spends!(node_txn[funding_spend_offset + 1], node_txn[funding_spend_offset]);
-       check_spends!(node_txn[funding_spend_offset + 2], node_txn[funding_spend_offset]);
        // We don't bother to check that B can claim the HTLC output on its commitment tx here as
        // we already checked the same situation with A.
 
@@ -2876,7 +2856,7 @@ fn test_htlc_on_chain_success() {
                        _ => panic!("Unexpected event"),
                }
        }
-       check_tx_local_broadcast!(nodes[0], true, node_a_commitment_tx[0], chan_1.3);
+       check_tx_local_broadcast!(nodes[0], true, node_a_commitment_tx[0]);
 }
 
 fn do_test_htlc_on_chain_timeout(connect_style: ConnectStyle) {
@@ -2930,10 +2910,8 @@ fn do_test_htlc_on_chain_timeout(connect_style: ConnectStyle) {
        check_closed_broadcast!(nodes[2], true);
        check_added_monitors!(nodes[2], 1);
        check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
-       let node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelManager : 1 (commitment tx)
-       assert_eq!(node_txn.len(), 1);
-       check_spends!(node_txn[0], chan_2.3);
-       assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), 71);
+       let node_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
+       assert_eq!(node_txn.len(), 0);
 
        // Broadcast timeout transaction by B on received output from C's commitment tx on B's chain
        // Verify that B's ChannelManager is able to detect that HTLC is timeout by its own tx and react backward in consequence
@@ -2943,9 +2921,7 @@ fn do_test_htlc_on_chain_timeout(connect_style: ConnectStyle) {
        let timeout_tx;
        {
                let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               assert_eq!(node_txn.len(), 5); // ChannelManager : 2 (commitment tx, HTLC-Timeout tx), ChannelMonitor : 2 (local commitment tx + HTLC-timeout), 1 timeout tx
-               assert_eq!(node_txn[0], node_txn[3]);
-               assert_eq!(node_txn[1], node_txn[4]);
+               assert_eq!(node_txn.len(), 3); // 2 (local commitment tx + HTLC-timeout), 1 timeout tx
 
                check_spends!(node_txn[2], commitment_tx[0]);
                assert_eq!(node_txn[2].clone().input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
@@ -2990,12 +2966,10 @@ fn do_test_htlc_on_chain_timeout(connect_style: ConnectStyle) {
        check_closed_broadcast!(nodes[0], true);
        check_added_monitors!(nodes[0], 1);
        check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
-       let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelManager : 1 commitment tx, ChannelMonitor : 1 timeout tx
-       assert_eq!(node_txn.len(), 2);
-       check_spends!(node_txn[0], chan_1.3);
-       assert_eq!(node_txn[0].clone().input[0].witness.last().unwrap().len(), 71);
-       check_spends!(node_txn[1], commitment_tx[0]);
-       assert_eq!(node_txn[1].clone().input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
+       let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // 1 timeout tx
+       assert_eq!(node_txn.len(), 1);
+       check_spends!(node_txn[0], commitment_tx[0]);
+       assert_eq!(node_txn[0].clone().input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
 }
 
 #[test]
@@ -4328,12 +4302,10 @@ fn test_static_spendable_outputs_preimage_tx() {
        }
 
        // Check B's monitor was able to send back output descriptor event for preimage tx on A's commitment tx
-       let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelManager : 2 (local commitment tx + HTLC-Success), ChannelMonitor: preimage tx
-       assert_eq!(node_txn.len(), 3);
+       let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelMonitor: preimage tx
+       assert_eq!(node_txn.len(), 1);
        check_spends!(node_txn[0], commitment_tx[0]);
        assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
-       check_spends!(node_txn[1], chan_1.3);
-       check_spends!(node_txn[2], node_txn[1]);
 
        mine_transaction(&nodes[1], &node_txn[0]);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
@@ -4375,12 +4347,11 @@ fn test_static_spendable_outputs_timeout_tx() {
 
        // Check B's monitor was able to send back output descriptor event for timeout tx on A's commitment tx
        let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-       assert_eq!(node_txn.len(), 2); // ChannelManager : 1 local commitent tx, ChannelMonitor: timeout tx
-       check_spends!(node_txn[0], chan_1.3.clone());
-       check_spends!(node_txn[1],  commitment_tx[0].clone());
-       assert_eq!(node_txn[1].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
+       assert_eq!(node_txn.len(), 1); // ChannelMonitor: timeout tx
+       check_spends!(node_txn[0],  commitment_tx[0].clone());
+       assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
 
-       mine_transaction(&nodes[1], &node_txn[1]);
+       mine_transaction(&nodes[1], &node_txn[0]);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
        connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
        expect_payment_failed!(nodes[1], our_payment_hash, false);
@@ -4388,8 +4359,8 @@ fn test_static_spendable_outputs_timeout_tx() {
        let spend_txn = check_spendable_outputs!(nodes[1], node_cfgs[1].keys_manager);
        assert_eq!(spend_txn.len(), 3); // SpendableOutput: remote_commitment_tx.to_remote, timeout_tx.output
        check_spends!(spend_txn[0], commitment_tx[0]);
-       check_spends!(spend_txn[1], node_txn[1]);
-       check_spends!(spend_txn[2], node_txn[1], commitment_tx[0]); // All outputs
+       check_spends!(spend_txn[1], node_txn[0]);
+       check_spends!(spend_txn[2], node_txn[0], commitment_tx[0]); // All outputs
 }
 
 #[test]
@@ -4415,7 +4386,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_commitment_tx() {
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
 
        let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-       assert_eq!(node_txn.len(), 2);
+       assert_eq!(node_txn.len(), 1);
        assert_eq!(node_txn[0].input.len(), 2);
        check_spends!(node_txn[0], revoked_local_txn[0]);
 
@@ -4453,40 +4424,36 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_timeout_tx() {
        connect_blocks(&nodes[0], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
 
        let revoked_htlc_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-       assert_eq!(revoked_htlc_txn.len(), 2);
-       check_spends!(revoked_htlc_txn[0], chan_1.3);
-       assert_eq!(revoked_htlc_txn[1].input.len(), 1);
-       assert_eq!(revoked_htlc_txn[1].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
-       check_spends!(revoked_htlc_txn[1], revoked_local_txn[0]);
-       assert_ne!(revoked_htlc_txn[1].lock_time.0, 0); // HTLC-Timeout
+       assert_eq!(revoked_htlc_txn.len(), 1);
+       assert_eq!(revoked_htlc_txn[0].input.len(), 1);
+       assert_eq!(revoked_htlc_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
+       check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]);
+       assert_ne!(revoked_htlc_txn[0].lock_time.0, 0); // HTLC-Timeout
 
        // B will generate justice tx from A's revoked commitment/HTLC tx
        let header = BlockHeader { version: 0x20000000, prev_blockhash: nodes[1].best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 };
-       connect_block(&nodes[1], &Block { header, txdata: vec![revoked_local_txn[0].clone(), revoked_htlc_txn[1].clone()] });
+       connect_block(&nodes[1], &Block { header, txdata: vec![revoked_local_txn[0].clone(), revoked_htlc_txn[0].clone()] });
        check_closed_broadcast!(nodes[1], true);
        check_added_monitors!(nodes[1], 1);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
 
        let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-       assert_eq!(node_txn.len(), 3); // ChannelMonitor: bogus justice tx, justice tx on revoked outputs, ChannelManager: local commitment tx
+       assert_eq!(node_txn.len(), 2); // ChannelMonitor: bogus justice tx, justice tx on revoked outputs
        // The first transaction generated is bogus - it spends both outputs of revoked_local_txn[0]
        // including the one already spent by revoked_htlc_txn[1]. That's OK, we'll spend with valid
        // transactions next...
        assert_eq!(node_txn[0].input.len(), 3);
-       check_spends!(node_txn[0], revoked_local_txn[0], revoked_htlc_txn[1]);
+       check_spends!(node_txn[0], revoked_local_txn[0], revoked_htlc_txn[0]);
 
        assert_eq!(node_txn[1].input.len(), 2);
-       check_spends!(node_txn[1], revoked_local_txn[0], revoked_htlc_txn[1]);
-       if node_txn[1].input[1].previous_output.txid == revoked_htlc_txn[1].txid() {
-               assert_ne!(node_txn[1].input[0].previous_output, revoked_htlc_txn[1].input[0].previous_output);
+       check_spends!(node_txn[1], revoked_local_txn[0], revoked_htlc_txn[0]);
+       if node_txn[1].input[1].previous_output.txid == revoked_htlc_txn[0].txid() {
+               assert_ne!(node_txn[1].input[0].previous_output, revoked_htlc_txn[0].input[0].previous_output);
        } else {
-               assert_eq!(node_txn[1].input[0].previous_output.txid, revoked_htlc_txn[1].txid());
-               assert_ne!(node_txn[1].input[1].previous_output, revoked_htlc_txn[1].input[0].previous_output);
+               assert_eq!(node_txn[1].input[0].previous_output.txid, revoked_htlc_txn[0].txid());
+               assert_ne!(node_txn[1].input[1].previous_output, revoked_htlc_txn[0].input[0].previous_output);
        }
 
-       assert_eq!(node_txn[2].input.len(), 1);
-       check_spends!(node_txn[2], chan_1.3);
-
        mine_transaction(&nodes[1], &node_txn[1]);
        connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
 
@@ -4525,7 +4492,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_success_tx() {
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
        let revoked_htlc_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
 
-       assert_eq!(revoked_htlc_txn.len(), 2);
+       assert_eq!(revoked_htlc_txn.len(), 1);
        assert_eq!(revoked_htlc_txn[0].input.len(), 1);
        assert_eq!(revoked_htlc_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
        check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]);
@@ -4542,7 +4509,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_success_tx() {
        check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
 
        let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-       assert_eq!(node_txn.len(), 3); // ChannelMonitor: justice tx on revoked commitment, justice tx on revoked HTLC-success, ChannelManager: local commitment tx
+       assert_eq!(node_txn.len(), 2); // ChannelMonitor: justice tx on revoked commitment, justice tx on revoked HTLC-success
 
        // The first transaction generated is bogus - it spends both outputs of revoked_local_txn[0]
        // including the one already spent by revoked_htlc_txn[0]. That's OK, we'll spend with valid
@@ -4559,8 +4526,6 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_success_tx() {
        assert_eq!(node_txn[1].input.len(), 1);
        check_spends!(node_txn[1], revoked_htlc_txn[0]);
 
-       check_spends!(node_txn[2], chan_1.3);
-
        mine_transaction(&nodes[0], &node_txn[1]);
        connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
 
@@ -4623,20 +4588,16 @@ fn test_onchain_to_onchain_claim() {
        check_added_monitors!(nodes[2], 1);
        check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
 
-       let c_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelManager : 2 (commitment tx, HTLC-Success tx), ChannelMonitor : 1 (HTLC-Success tx)
-       assert_eq!(c_txn.len(), 3);
-       assert_eq!(c_txn[0], c_txn[2]);
-       assert_eq!(commitment_tx[0], c_txn[1]);
-       check_spends!(c_txn[1], chan_2.3);
-       check_spends!(c_txn[2], c_txn[1]);
-       assert_eq!(c_txn[1].input[0].witness.clone().last().unwrap().len(), 71);
-       assert_eq!(c_txn[2].input[0].witness.clone().last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
+       let c_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelMonitor: 1 (HTLC-Success tx)
+       assert_eq!(c_txn.len(), 1);
+       check_spends!(c_txn[0], commitment_tx[0]);
+       assert_eq!(c_txn[0].input[0].witness.clone().last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
        assert!(c_txn[0].output[0].script_pubkey.is_v0_p2wsh()); // revokeable output
        assert_eq!(c_txn[0].lock_time.0, 0); // Success tx
 
        // So we broadcast C's commitment tx and HTLC-Success on B's chain, we should successfully be able to extract preimage and update downstream monitor
        let header = BlockHeader { version: 0x20000000, prev_blockhash: nodes[1].best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42};
-       connect_block(&nodes[1], &Block { header, txdata: vec![c_txn[1].clone(), c_txn[2].clone()]});
+       connect_block(&nodes[1], &Block { header, txdata: vec![commitment_tx[0].clone(), c_txn[0].clone()]});
        check_added_monitors!(nodes[1], 1);
        let events = nodes[1].node.get_and_clear_pending_events();
        assert_eq!(events.len(), 2);
@@ -4653,13 +4614,6 @@ fn test_onchain_to_onchain_claim() {
                },
                _ => panic!("Unexpected event"),
        }
-       {
-               let mut b_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               // ChannelMonitor: claim tx
-               assert_eq!(b_txn.len(), 1);
-               check_spends!(b_txn[0], chan_2.3); // B local commitment tx, issued by ChannelManager
-               b_txn.clear();
-       }
        check_added_monitors!(nodes[1], 1);
        let msg_events = nodes[1].node.get_and_clear_pending_msg_events();
        assert_eq!(msg_events.len(), 3);
@@ -4686,10 +4640,8 @@ fn test_onchain_to_onchain_claim() {
        mine_transaction(&nodes[1], &commitment_tx[0]);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
        let b_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-       // ChannelMonitor: HTLC-Success tx, ChannelManager: local commitment tx + HTLC-Success tx
-       assert_eq!(b_txn.len(), 3);
-       check_spends!(b_txn[1], chan_1.3);
-       check_spends!(b_txn[2], b_txn[1]);
+       // ChannelMonitor: HTLC-Success tx
+       assert_eq!(b_txn.len(), 1);
        check_spends!(b_txn[0], commitment_tx[0]);
        assert_eq!(b_txn[0].input[0].witness.clone().last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
        assert!(b_txn[0].output[0].script_pubkey.is_v0_p2wpkh()); // direct payment
@@ -4749,31 +4701,30 @@ fn test_duplicate_payment_hash_one_failure_one_success() {
        let htlc_timeout_tx;
        { // Extract one of the two HTLC-Timeout transaction
                let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               // ChannelMonitor: timeout tx * 2-or-3, ChannelManager: local commitment tx
-               assert!(node_txn.len() == 4 || node_txn.len() == 3);
-               check_spends!(node_txn[0], chan_2.3);
+               // ChannelMonitor: timeout tx * 2-or-3
+               assert!(node_txn.len() == 2 || node_txn.len() == 3);
 
-               check_spends!(node_txn[1], commitment_txn[0]);
-               assert_eq!(node_txn[1].input.len(), 1);
+               check_spends!(node_txn[0], commitment_txn[0]);
+               assert_eq!(node_txn[0].input.len(), 1);
 
-               if node_txn.len() > 3 {
-                       check_spends!(node_txn[2], commitment_txn[0]);
-                       assert_eq!(node_txn[2].input.len(), 1);
-                       assert_eq!(node_txn[1].input[0].previous_output, node_txn[2].input[0].previous_output);
+               if node_txn.len() > 2 {
+                       check_spends!(node_txn[1], commitment_txn[0]);
+                       assert_eq!(node_txn[1].input.len(), 1);
+                       assert_eq!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
 
-                       check_spends!(node_txn[3], commitment_txn[0]);
-                       assert_ne!(node_txn[1].input[0].previous_output, node_txn[3].input[0].previous_output);
-               } else {
                        check_spends!(node_txn[2], commitment_txn[0]);
-                       assert_ne!(node_txn[1].input[0].previous_output, node_txn[2].input[0].previous_output);
+                       assert_ne!(node_txn[0].input[0].previous_output, node_txn[2].input[0].previous_output);
+               } else {
+                       check_spends!(node_txn[1], commitment_txn[0]);
+                       assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
                }
 
+               assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
                assert_eq!(node_txn[1].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
-               assert_eq!(node_txn[2].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
-               if node_txn.len() > 3 {
-                       assert_eq!(node_txn[3].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
+               if node_txn.len() > 2 {
+                       assert_eq!(node_txn[2].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
                }
-               htlc_timeout_tx = node_txn[1].clone();
+               htlc_timeout_tx = node_txn[0].clone();
        }
 
        nodes[2].node.claim_funds(our_payment_preimage);
@@ -4792,7 +4743,7 @@ fn test_duplicate_payment_hash_one_failure_one_success() {
                _ => panic!("Unexepected event"),
        }
        let htlc_success_txn: Vec<_> = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-       assert_eq!(htlc_success_txn.len(), 5); // ChannelMonitor: HTLC-Success txn (*2 due to 2-HTLC outputs), ChannelManager: local commitment tx + HTLC-Success txn (*2 due to 2-HTLC outputs)
+       assert_eq!(htlc_success_txn.len(), 2); // ChannelMonitor: HTLC-Success txn (*2 due to 2-HTLC outputs)
        check_spends!(htlc_success_txn[0], commitment_txn[0]);
        check_spends!(htlc_success_txn[1], commitment_txn[0]);
        assert_eq!(htlc_success_txn[0].input.len(), 1);
@@ -4800,9 +4751,6 @@ fn test_duplicate_payment_hash_one_failure_one_success() {
        assert_eq!(htlc_success_txn[1].input.len(), 1);
        assert_eq!(htlc_success_txn[1].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
        assert_ne!(htlc_success_txn[0].input[0].previous_output, htlc_success_txn[1].input[0].previous_output);
-       assert_eq!(htlc_success_txn[2], commitment_txn[0]);
-       assert_eq!(htlc_success_txn[3], htlc_success_txn[0]);
-       assert_eq!(htlc_success_txn[4], htlc_success_txn[1]);
        assert_ne!(htlc_success_txn[1].input[0].previous_output, htlc_timeout_tx.input[0].previous_output);
 
        mine_transaction(&nodes[1], &htlc_timeout_tx);
@@ -4884,9 +4832,7 @@ fn test_dynamic_spendable_outputs_local_htlc_success_tx() {
        }
        let node_tx = {
                let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               assert_eq!(node_txn.len(), 3);
-               assert_eq!(node_txn[0], node_txn[2]);
-               assert_eq!(node_txn[1], local_txn[0]);
+               assert_eq!(node_txn.len(), 1);
                assert_eq!(node_txn[0].input.len(), 1);
                assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
                check_spends!(node_txn[0], local_txn[0]);
@@ -5231,12 +5177,11 @@ fn test_dynamic_spendable_outputs_local_htlc_timeout_tx() {
 
        let htlc_timeout = {
                let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               assert_eq!(node_txn.len(), 2);
-               check_spends!(node_txn[0], chan_1.3);
-               assert_eq!(node_txn[1].input.len(), 1);
-               assert_eq!(node_txn[1].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
-               check_spends!(node_txn[1], local_txn[0]);
-               node_txn[1].clone()
+               assert_eq!(node_txn.len(), 1);
+               assert_eq!(node_txn[0].input.len(), 1);
+               assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
+               check_spends!(node_txn[0], local_txn[0]);
+               node_txn[0].clone()
        };
 
        mine_transaction(&nodes[0], &htlc_timeout);
@@ -5316,10 +5261,11 @@ fn test_key_derivation_params() {
 
        let htlc_timeout = {
                let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               assert_eq!(node_txn[1].input.len(), 1);
-               assert_eq!(node_txn[1].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
-               check_spends!(node_txn[1], local_txn_1[0]);
-               node_txn[1].clone()
+               assert_eq!(node_txn.len(), 1);
+               assert_eq!(node_txn[0].input.len(), 1);
+               assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
+               check_spends!(node_txn[0], local_txn_1[0]);
+               node_txn[0].clone()
        };
 
        mine_transaction(&nodes[0], &htlc_timeout);
@@ -6803,7 +6749,7 @@ fn do_test_sweep_outbound_htlc_failure_update(revoked: bool, local: bool) {
                check_closed_broadcast!(nodes[0], true);
                check_added_monitors!(nodes[0], 1);
                assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
-               timeout_tx.push(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap()[1].clone());
+               timeout_tx.push(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap()[0].clone());
                assert_eq!(timeout_tx[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
                // We fail non-dust-HTLC 2 by broadcast of local HTLC-timeout tx on local commitment tx
                assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
@@ -7108,7 +7054,7 @@ fn test_bump_penalty_txn_on_revoked_commitment() {
        let feerate_1;
        {
                let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               assert_eq!(node_txn.len(), 2); // justice tx (broadcasted from ChannelMonitor) + local commitment tx
+               assert_eq!(node_txn.len(), 1); // justice tx (broadcasted from ChannelMonitor)
                assert_eq!(node_txn[0].input.len(), 3); // Penalty txn claims to_local, offered_htlc and received_htlc outputs
                assert_eq!(node_txn[0].output.len(), 1);
                check_spends!(node_txn[0], revoked_txn[0]);
@@ -7208,24 +7154,23 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
        connect_blocks(&nodes[1], 49); // Confirm blocks until the HTLC expires (note CLTV was explicitly 50 above)
 
        let revoked_htlc_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-       assert_eq!(revoked_htlc_txn.len(), 3);
-       check_spends!(revoked_htlc_txn[1], chan.3);
+       assert_eq!(revoked_htlc_txn.len(), 2);
 
        assert_eq!(revoked_htlc_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
        assert_eq!(revoked_htlc_txn[0].input.len(), 1);
        check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]);
 
-       assert_eq!(revoked_htlc_txn[2].input.len(), 1);
-       assert_eq!(revoked_htlc_txn[2].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
-       assert_eq!(revoked_htlc_txn[2].output.len(), 1);
-       check_spends!(revoked_htlc_txn[2], revoked_local_txn[0]);
+       assert_eq!(revoked_htlc_txn[1].input.len(), 1);
+       assert_eq!(revoked_htlc_txn[1].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
+       assert_eq!(revoked_htlc_txn[1].output.len(), 1);
+       check_spends!(revoked_htlc_txn[1], revoked_local_txn[0]);
 
        // Broadcast set of revoked txn on A
        let hash_128 = connect_blocks(&nodes[0], 40);
        let header_11 = BlockHeader { version: 0x20000000, prev_blockhash: hash_128, merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 };
        connect_block(&nodes[0], &Block { header: header_11, txdata: vec![revoked_local_txn[0].clone()] });
        let header_129 = BlockHeader { version: 0x20000000, prev_blockhash: header_11.block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 };
-       connect_block(&nodes[0], &Block { header: header_129, txdata: vec![revoked_htlc_txn[0].clone(), revoked_htlc_txn[2].clone()] });
+       connect_block(&nodes[0], &Block { header: header_129, txdata: vec![revoked_htlc_txn[0].clone(), revoked_htlc_txn[1].clone()] });
        let events = nodes[0].node.get_and_clear_pending_events();
        expect_pending_htlcs_forwardable_from_events!(nodes[0], events[0..1], true);
        match events.last().unwrap() {
@@ -7237,7 +7182,7 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
        let penalty_txn;
        {
                let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               assert_eq!(node_txn.len(), 5); // 3 penalty txn on revoked commitment tx + A commitment tx + 1 penalty tnx on revoked HTLC txn
+               assert_eq!(node_txn.len(), 4); // 3 penalty txn on revoked commitment tx + 1 penalty tnx on revoked HTLC txn
                // Verify claim tx are spending revoked HTLC txn
 
                // node_txn 0-2 each spend a separate revoked output from revoked_local_txn[0]
@@ -7259,23 +7204,18 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
                assert_ne!(node_txn[1].input[0].previous_output, node_txn[2].input[0].previous_output);
 
                assert_eq!(node_txn[0].input[0].previous_output, revoked_htlc_txn[0].input[0].previous_output);
-               assert_eq!(node_txn[1].input[0].previous_output, revoked_htlc_txn[2].input[0].previous_output);
+               assert_eq!(node_txn[1].input[0].previous_output, revoked_htlc_txn[1].input[0].previous_output);
 
-               // node_txn[3] is the local commitment tx broadcast just because (and somewhat in case of
-               // reorgs, though its not clear its ever worth broadcasting conflicting txn like this when
-               // a remote commitment tx has already been confirmed).
-               check_spends!(node_txn[3], chan.3);
-
-               // node_txn[4] spends the revoked outputs from the revoked_htlc_txn (which only have one
+               // node_txn[3] spends the revoked outputs from the revoked_htlc_txn (which only have one
                // output, checked above).
-               assert_eq!(node_txn[4].input.len(), 2);
-               assert_eq!(node_txn[4].output.len(), 1);
-               check_spends!(node_txn[4], revoked_htlc_txn[0], revoked_htlc_txn[2]);
+               assert_eq!(node_txn[3].input.len(), 2);
+               assert_eq!(node_txn[3].output.len(), 1);
+               check_spends!(node_txn[3], revoked_htlc_txn[0], revoked_htlc_txn[1]);
 
-               first = node_txn[4].txid();
+               first = node_txn[3].txid();
                // Store both feerates for later comparison
-               let fee_1 = revoked_htlc_txn[0].output[0].value + revoked_htlc_txn[2].output[0].value - node_txn[4].output[0].value;
-               feerate_1 = fee_1 * 1000 / node_txn[4].weight() as u64;
+               let fee_1 = revoked_htlc_txn[0].output[0].value + revoked_htlc_txn[1].output[0].value - node_txn[3].output[0].value;
+               feerate_1 = fee_1 * 1000 / node_txn[3].weight() as u64;
                penalty_txn = vec![node_txn[2].clone()];
                node_txn.clear();
        }
@@ -7295,10 +7235,10 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
                assert_eq!(node_txn.len(), 1);
 
                assert_eq!(node_txn[0].input.len(), 2);
-               check_spends!(node_txn[0], revoked_htlc_txn[0], revoked_htlc_txn[2]);
+               check_spends!(node_txn[0], revoked_htlc_txn[0], revoked_htlc_txn[1]);
                // Verify bumped tx is different and 25% bump heuristic
                assert_ne!(first, node_txn[0].txid());
-               let fee_2 = revoked_htlc_txn[0].output[0].value + revoked_htlc_txn[2].output[0].value - node_txn[0].output[0].value;
+               let fee_2 = revoked_htlc_txn[0].output[0].value + revoked_htlc_txn[1].output[0].value - node_txn[0].output[0].value;
                let feerate_2 = fee_2 * 1000 / node_txn[0].weight() as u64;
                assert!(feerate_2 * 100 > feerate_1 * 125);
                let txn = vec![node_txn[0].clone()];
@@ -7363,29 +7303,25 @@ fn test_bump_penalty_txn_on_remote_commitment() {
        let feerate_preimage;
        {
                let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               // 5 transactions including:
-               //   local commitment + HTLC-Success
+               // 3 transactions including:
                //   preimage and timeout sweeps from remote commitment + preimage sweep bump
-               assert_eq!(node_txn.len(), 5);
+               assert_eq!(node_txn.len(), 3);
                assert_eq!(node_txn[0].input.len(), 1);
-               assert_eq!(node_txn[3].input.len(), 1);
-               assert_eq!(node_txn[4].input.len(), 1);
+               assert_eq!(node_txn[1].input.len(), 1);
+               assert_eq!(node_txn[2].input.len(), 1);
                check_spends!(node_txn[0], remote_txn[0]);
-               check_spends!(node_txn[3], remote_txn[0]);
-               check_spends!(node_txn[4], remote_txn[0]);
-
-               check_spends!(node_txn[1], chan.3); // local commitment
-               check_spends!(node_txn[2], node_txn[1]); // local HTLC-Success
+               check_spends!(node_txn[1], remote_txn[0]);
+               check_spends!(node_txn[2], remote_txn[0]);
 
                preimage = node_txn[0].txid();
                let index = node_txn[0].input[0].previous_output.vout;
                let fee = remote_txn[0].output[index as usize].value - node_txn[0].output[0].value;
                feerate_preimage = fee * 1000 / node_txn[0].weight() as u64;
 
-               let (preimage_bump_tx, timeout_tx) = if node_txn[3].input[0].previous_output == node_txn[0].input[0].previous_output {
-                       (node_txn[3].clone(), node_txn[4].clone())
+               let (preimage_bump_tx, timeout_tx) = if node_txn[2].input[0].previous_output == node_txn[0].input[0].previous_output {
+                       (node_txn[2].clone(), node_txn[1].clone())
                } else {
-                       (node_txn[4].clone(), node_txn[3].clone())
+                       (node_txn[1].clone(), node_txn[2].clone())
                };
 
                preimage_bump = preimage_bump_tx;
@@ -7508,7 +7444,7 @@ fn test_bump_txn_sanitize_tracking_maps() {
        check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed);
        let penalty_txn = {
                let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               assert_eq!(node_txn.len(), 4); //ChannelMonitor: justice txn * 3, ChannelManager: local commitment tx
+               assert_eq!(node_txn.len(), 3); //ChannelMonitor: justice txn * 3
                check_spends!(node_txn[0], revoked_local_txn[0]);
                check_spends!(node_txn[1], revoked_local_txn[0]);
                check_spends!(node_txn[2], revoked_local_txn[0]);
@@ -8353,10 +8289,11 @@ fn test_htlc_no_detection() {
 
        let htlc_timeout = {
                let node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               assert_eq!(node_txn[1].input.len(), 1);
-               assert_eq!(node_txn[1].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
-               check_spends!(node_txn[1], local_txn[0]);
-               node_txn[1].clone()
+               assert_eq!(node_txn.len(), 1);
+               assert_eq!(node_txn[0].input.len(), 1);
+               assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
+               check_spends!(node_txn[0], local_txn[0]);
+               node_txn[0].clone()
        };
 
        let header_201 = BlockHeader { version: 0x20000000, prev_blockhash: nodes[0].best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42 };
@@ -8422,14 +8359,11 @@ fn do_test_onchain_htlc_settlement_after_close(broadcast_alice: bool, go_onchain
                };
                let header = BlockHeader { version: 0x20000000, prev_blockhash: nodes[1].best_block_hash(), merkle_root: TxMerkleNode::all_zeros(), time: 42, bits: 42, nonce: 42};
                connect_block(&nodes[1], &Block { header, txdata: vec![txn_to_broadcast[0].clone()]});
-               let mut bob_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
                if broadcast_alice {
                        check_closed_broadcast!(nodes[1], true);
                        check_added_monitors!(nodes[1], 1);
                        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
                }
-               assert_eq!(bob_txn.len(), 1);
-               check_spends!(bob_txn[0], chan_ab.3);
        }
 
        // Step (5):
@@ -8513,13 +8447,8 @@ fn do_test_onchain_htlc_settlement_after_close(broadcast_alice: bool, go_onchain
                }
                let mut bob_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
                if broadcast_alice {
-                       // In `connect_block()`, the ChainMonitor and ChannelManager are separately notified about a
-                       // new block being connected. The ChannelManager being notified triggers a monitor update,
-                       // which triggers broadcasting our commitment tx and an HTLC-claiming tx. The ChainMonitor
-                       // being notified triggers the HTLC-claiming tx redundantly, resulting in 3 total txs being
-                       // broadcasted.
-                       assert_eq!(bob_txn.len(), 3);
-                       check_spends!(bob_txn[1], chan_ab.3);
+                       assert_eq!(bob_txn.len(), 1);
+                       check_spends!(bob_txn[0], txn_to_broadcast[0]);
                } else {
                        assert_eq!(bob_txn.len(), 2);
                        check_spends!(bob_txn[0], chan_ab.3);
@@ -8530,23 +8459,20 @@ fn do_test_onchain_htlc_settlement_after_close(broadcast_alice: bool, go_onchain
        // Finally, check that Bob broadcasted a preimage-claiming transaction for the HTLC output on the
        // broadcasted commitment transaction.
        {
-               let bob_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-               if go_onchain_before_fulfill {
-                       // Bob should now have an extra broadcasted tx, for the preimage-claiming transaction.
-                       assert_eq!(bob_txn.len(), 2);
-               }
                let script_weight = match broadcast_alice {
                        true => OFFERED_HTLC_SCRIPT_WEIGHT,
                        false => ACCEPTED_HTLC_SCRIPT_WEIGHT
                };
-               // If Alice force-closed and Bob didn't receive her commitment transaction until after he
-               // received Carol's fulfill, he broadcasts the HTLC-output-claiming transaction first. Else if
-               // Bob force closed or if he found out about Alice's commitment tx before receiving Carol's
-               // fulfill, then he broadcasts the HTLC-output-claiming transaction second.
-               if broadcast_alice && !go_onchain_before_fulfill {
+               // If Alice force-closed, Bob only broadcasts a HTLC-output-claiming transaction. Otherwise,
+               // Bob force-closed and broadcasts the commitment transaction along with a
+               // HTLC-output-claiming transaction.
+               let bob_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
+               if broadcast_alice {
+                       assert_eq!(bob_txn.len(), 1);
                        check_spends!(bob_txn[0], txn_to_broadcast[0]);
                        assert_eq!(bob_txn[0].input[0].witness.last().unwrap().len(), script_weight);
                } else {
+                       assert_eq!(bob_txn.len(), 2);
                        check_spends!(bob_txn[1], txn_to_broadcast[0]);
                        assert_eq!(bob_txn[1].input[0].witness.last().unwrap().len(), script_weight);
                }
index 8726c6a996204b99e0fa653c3240a348c2752fcf..1cb32bed08dc42e2cbb7371856343d14d8d21e35 100644 (file)
@@ -124,9 +124,8 @@ fn revoked_output_htlc_resolution_timing() {
        check_closed_broadcast!(nodes[1], true);
 
        let bs_spend_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-       assert_eq!(bs_spend_txn.len(), 2);
+       assert_eq!(bs_spend_txn.len(), 1);
        check_spends!(bs_spend_txn[0], revoked_local_txn[0]);
-       check_spends!(bs_spend_txn[1], chan.3);
 
        // After the commitment transaction confirms, we should still wait on the HTLC spend
        // transaction to confirm before resolving the HTLC.
@@ -364,21 +363,14 @@ fn do_test_claim_value_force_close(prev_commitment_tx: bool) {
        mine_transaction(&nodes[1], &remote_txn[0]);
 
        let b_broadcast_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-       assert_eq!(b_broadcast_txn.len(), if prev_commitment_tx { 4 } else { 5 });
-       if prev_commitment_tx {
-               check_spends!(b_broadcast_txn[3], b_broadcast_txn[2]);
-       } else {
-               assert_eq!(b_broadcast_txn[0], b_broadcast_txn[3]);
-               assert_eq!(b_broadcast_txn[1], b_broadcast_txn[4]);
-       }
-       // b_broadcast_txn[0] should spend the HTLC output of the commitment tx for 3_000 sats
+       assert_eq!(b_broadcast_txn.len(), 2);
+       // b_broadcast_txn should spend the HTLCs output of the commitment tx for 3_000 and 4_000 sats
        check_spends!(b_broadcast_txn[0], remote_txn[0]);
        check_spends!(b_broadcast_txn[1], remote_txn[0]);
        assert_eq!(b_broadcast_txn[0].input.len(), 1);
        assert_eq!(b_broadcast_txn[1].input.len(), 1);
        assert_eq!(remote_txn[0].output[b_broadcast_txn[0].input[0].previous_output.vout as usize].value, 3_000);
        assert_eq!(remote_txn[0].output[b_broadcast_txn[1].input[0].previous_output.vout as usize].value, 4_000);
-       check_spends!(b_broadcast_txn[2], funding_tx);
 
        assert!(nodes[0].node.list_channels().is_empty());
        check_closed_broadcast!(nodes[0], true);
@@ -482,21 +474,20 @@ fn do_test_claim_value_force_close(prev_commitment_tx: bool) {
        // When the HTLC timeout output is spendable in the next block, A should broadcast it
        connect_blocks(&nodes[0], htlc_cltv_timeout - nodes[0].best_block_info().1 - 1);
        let a_broadcast_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-       assert_eq!(a_broadcast_txn.len(), 3);
-       check_spends!(a_broadcast_txn[0], funding_tx);
+       assert_eq!(a_broadcast_txn.len(), 2);
+       assert_eq!(a_broadcast_txn[0].input.len(), 1);
+       check_spends!(a_broadcast_txn[0], remote_txn[0]);
        assert_eq!(a_broadcast_txn[1].input.len(), 1);
        check_spends!(a_broadcast_txn[1], remote_txn[0]);
-       assert_eq!(a_broadcast_txn[2].input.len(), 1);
-       check_spends!(a_broadcast_txn[2], remote_txn[0]);
-       assert_ne!(a_broadcast_txn[1].input[0].previous_output.vout,
-                  a_broadcast_txn[2].input[0].previous_output.vout);
-       // a_broadcast_txn [1] and [2] should spend the HTLC outputs of the commitment tx
-       assert_eq!(remote_txn[0].output[a_broadcast_txn[1].input[0].previous_output.vout as usize].value, 3_000);
-       assert_eq!(remote_txn[0].output[a_broadcast_txn[2].input[0].previous_output.vout as usize].value, 4_000);
+       assert_ne!(a_broadcast_txn[0].input[0].previous_output.vout,
+                  a_broadcast_txn[1].input[0].previous_output.vout);
+       // a_broadcast_txn [0] and [1] should spend the HTLC outputs of the commitment tx
+       assert_eq!(remote_txn[0].output[a_broadcast_txn[0].input[0].previous_output.vout as usize].value, 3_000);
+       assert_eq!(remote_txn[0].output[a_broadcast_txn[1].input[0].previous_output.vout as usize].value, 4_000);
 
        // Once the HTLC-Timeout transaction confirms, A will no longer consider the HTLC
        // "MaybeClaimable", but instead move it to "AwaitingConfirmations".
-       mine_transaction(&nodes[0], &a_broadcast_txn[2]);
+       mine_transaction(&nodes[0], &a_broadcast_txn[1]);
        assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
        assert_eq!(vec![Balance::ClaimableAwaitingConfirmations {
                        claimable_amount_satoshis: 4_000,
@@ -510,7 +501,7 @@ fn do_test_claim_value_force_close(prev_commitment_tx: bool) {
                nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances());
        expect_payment_failed!(nodes[0], timeout_payment_hash, false);
 
-       test_spendable_output(&nodes[0], &a_broadcast_txn[2]);
+       test_spendable_output(&nodes[0], &a_broadcast_txn[1]);
 
        // Node B will no longer consider the HTLC "contentious" after the HTLC claim transaction
        // confirms, and consider it simply "awaiting confirmations". Note that it has to wait for the
@@ -558,7 +549,7 @@ fn do_test_claim_value_force_close(prev_commitment_tx: bool) {
        // Finally, mine the HTLC timeout transaction that A broadcasted (even though B should be able
        // to claim this HTLC with the preimage it knows!). It will remain listed as a claimable HTLC
        // until ANTI_REORG_DELAY confirmations on the spend.
-       mine_transaction(&nodes[1], &a_broadcast_txn[2]);
+       mine_transaction(&nodes[1], &a_broadcast_txn[1]);
        assert_eq!(vec![Balance::ContentiousClaimable {
                        claimable_amount_satoshis: 4_000,
                        timeout_height: htlc_cltv_timeout,
@@ -670,10 +661,8 @@ fn test_balances_on_local_commitment_htlcs() {
        check_closed_broadcast!(nodes[1], true);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
        let bs_htlc_claim_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-       assert_eq!(bs_htlc_claim_txn.len(), 3);
+       assert_eq!(bs_htlc_claim_txn.len(), 1);
        check_spends!(bs_htlc_claim_txn[0], as_txn[0]);
-       check_spends!(bs_htlc_claim_txn[1], funding_tx);
-       check_spends!(bs_htlc_claim_txn[2], bs_htlc_claim_txn[1]);
 
        // Connect blocks until the HTLCs expire, allowing us to (validly) broadcast the HTLC-Timeout
        // transaction.
@@ -1309,11 +1298,10 @@ fn test_revoked_counterparty_htlc_tx_balances() {
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
        let revoked_htlc_success_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
 
-       assert_eq!(revoked_htlc_success_txn.len(), 2);
+       assert_eq!(revoked_htlc_success_txn.len(), 1);
        assert_eq!(revoked_htlc_success_txn[0].input.len(), 1);
        assert_eq!(revoked_htlc_success_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
        check_spends!(revoked_htlc_success_txn[0], revoked_local_txn[0]);
-       check_spends!(revoked_htlc_success_txn[1], funding_tx);
 
        connect_blocks(&nodes[1], TEST_FINAL_CLTV);
        let revoked_htlc_timeout_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
@@ -1331,9 +1319,8 @@ fn test_revoked_counterparty_htlc_tx_balances() {
        let to_remote_conf_height = nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1;
 
        let as_commitment_claim_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-       assert_eq!(as_commitment_claim_txn.len(), 2);
+       assert_eq!(as_commitment_claim_txn.len(), 1);
        check_spends!(as_commitment_claim_txn[0], revoked_local_txn[0]);
-       check_spends!(as_commitment_claim_txn[1], funding_tx);
 
        // The next two checks have the same balance set for A - even though we confirm a revoked HTLC
        // transaction our balance tracking doesn't use the on-chain value so the
index ff65bac4bc8b9e7e18832ae067ab5bbf1769b57c..20f535755698615be7ab490df49fa1b3f7075ff5 100644 (file)
@@ -751,10 +751,8 @@ fn do_test_dup_htlc_onchain_fails_on_reload(persist_manager_post_event: bool, co
        check_added_monitors!(nodes[1], 1);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
        let claim_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-       assert_eq!(claim_txn.len(), 3);
+       assert_eq!(claim_txn.len(), 1);
        check_spends!(claim_txn[0], node_txn[1]);
-       check_spends!(claim_txn[1], funding_tx);
-       check_spends!(claim_txn[2], claim_txn[1]);
 
        header.prev_blockhash = nodes[0].best_block_hash();
        connect_block(&nodes[0], &Block { header, txdata: vec![node_txn[1].clone()]});
index 91d5b3070610579474b39737b518c94932e9200d..d4dbaaa8da070e230844d3df6320c422a8a8d159 100644 (file)
@@ -82,10 +82,7 @@ fn do_test_onchain_htlc_reorg(local_commitment: bool, claim: bool) {
                check_closed_broadcast!(nodes[2], true); // We should get a BroadcastChannelUpdate (and *only* a BroadcstChannelUpdate)
                check_closed_event!(nodes[2], 1, ClosureReason::CommitmentTxConfirmed);
                let node_2_commitment_txn = nodes[2].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
-               assert_eq!(node_2_commitment_txn.len(), 3); // ChannelMonitor: 1 offered HTLC-Claim, ChannelManger: 1 local commitment tx, 1 Received HTLC-Claim
-               assert_eq!(node_2_commitment_txn[1].output.len(), 2); // to-remote and Received HTLC (to-self is dust)
-               check_spends!(node_2_commitment_txn[1], chan_2.3);
-               check_spends!(node_2_commitment_txn[2], node_2_commitment_txn[1]);
+               assert_eq!(node_2_commitment_txn.len(), 1); // ChannelMonitor: 1 offered HTLC-Claim
                check_spends!(node_2_commitment_txn[0], node_1_commitment_txn[0]);
 
                // Make sure node 1's height is the same as the !local_commitment case
@@ -108,13 +105,11 @@ fn do_test_onchain_htlc_reorg(local_commitment: bool, claim: bool) {
                mine_transaction(&nodes[1], &node_2_commitment_txn[0]);
                connect_blocks(&nodes[1], TEST_FINAL_CLTV - 1); // Confirm blocks until the HTLC expires
                let node_1_commitment_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
-               assert_eq!(node_1_commitment_txn.len(), 2); // ChannelMonitor: 1 offered HTLC-Timeout, ChannelManger: 1 local commitment tx
-               assert_eq!(node_1_commitment_txn[0].output.len(), 2); // to-local and Offered HTLC (to-remote is dust)
-               check_spends!(node_1_commitment_txn[0], chan_2.3);
-               check_spends!(node_1_commitment_txn[1], node_2_commitment_txn[0]);
+               assert_eq!(node_1_commitment_txn.len(), 1); // ChannelMonitor: 1 offered HTLC-Timeout
+               check_spends!(node_1_commitment_txn[0], node_2_commitment_txn[0]);
 
                // Confirm node 1's HTLC-Timeout on node 1
-               mine_transaction(&nodes[1], &node_1_commitment_txn[1]);
+               mine_transaction(&nodes[1], &node_1_commitment_txn[0]);
                // ...but return node 2's commitment tx (and claim) in case claim is set and we're preparing to reorg
                vec![node_2_commitment_txn.pop().unwrap()]
        };
@@ -459,12 +454,9 @@ fn test_set_outpoints_partial_claiming() {
        // Verify node A broadcast tx claiming both HTLCs
        {
                let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
-               // ChannelMonitor: claim tx, ChannelManager: local commitment tx + HTLC-Success*2
-               assert_eq!(node_txn.len(), 4);
+               // ChannelMonitor: claim tx
+               assert_eq!(node_txn.len(), 1);
                check_spends!(node_txn[0], remote_txn[0]);
-               check_spends!(node_txn[1], chan.3);
-               check_spends!(node_txn[2], node_txn[1]);
-               check_spends!(node_txn[3], node_txn[1]);
                assert_eq!(node_txn[0].input.len(), 2);
                node_txn.clear();
        }
@@ -553,11 +545,6 @@ fn do_test_to_remote_after_local_detection(style: ConnectStyle) {
        check_added_monitors!(nodes[1], 1);
        check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed);
 
-       // Drop transactions broadcasted in response to the first commitment transaction (we have good
-       // test coverage of these things already elsewhere).
-       assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0).len(), 1);
-       assert_eq!(nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0).len(), 1);
-
        assert!(nodes[0].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
        assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
 
index 90f6c183c0ee23ba1b1b6cf5a0e3b45b10d5f6af..e3fe112112eb151e5964fec8bad81c5dd0c9f8fa 100644 (file)
@@ -9,13 +9,14 @@
 
 //! Data structures and encoding for `invoice_request` messages.
 //!
-//! An [`InvoiceRequest`] can be either built from a parsed [`Offer`] as an "offer to be paid" or
-//! built directly as an "offer for money" (e.g., refund, ATM withdrawal). In the former case, it is
+//! An [`InvoiceRequest`] can be built from a parsed [`Offer`] as an "offer to be paid". It is
 //! typically constructed by a customer and sent to the merchant who had published the corresponding
-//! offer. In the latter case, an offer doesn't exist as a precursor to the request. Rather the
-//! merchant would typically construct the invoice request and present it to the customer.
+//! offer. The recipient of the request responds with an `Invoice`.
 //!
-//! The recipient of the request responds with an `Invoice`.
+//! For an "offer for money" (e.g., refund, ATM withdrawal), where an offer doesn't exist as a
+//! precursor, see [`Refund`].
+//!
+//! [`Refund`]: crate::offers::refund::Refund
 //!
 //! ```ignore
 //! extern crate bitcoin;
@@ -34,7 +35,6 @@
 //! let pubkey = PublicKey::from(keys);
 //! let mut buffer = Vec::new();
 //!
-//! // "offer to be paid" flow
 //! "lno1qcp4256ypq"
 //!     .parse::<Offer>()?
 //!     .request_invoice(vec![42; 64], pubkey)?
@@ -287,7 +287,7 @@ impl InvoiceRequest {
                self.contents.amount_msats
        }
 
-       /// Features for paying the invoice.
+       /// Features pertaining to requesting an invoice.
        pub fn features(&self) -> &InvoiceRequestFeatures {
                &self.contents.features
        }
@@ -471,7 +471,7 @@ impl TryFrom<PartialInvoiceRequestTlvStream> for InvoiceRequestContents {
 
 #[cfg(test)]
 mod tests {
-       use super::InvoiceRequest;
+       use super::{InvoiceRequest, InvoiceRequestTlvStreamRef};
 
        use bitcoin::blockdata::constants::ChainHash;
        use bitcoin::network::constants::Network;
@@ -483,9 +483,10 @@ mod tests {
        use core::time::Duration;
        use crate::ln::features::InvoiceRequestFeatures;
        use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
-       use crate::offers::merkle::SignError;
-       use crate::offers::offer::{Amount, OfferBuilder, Quantity};
+       use crate::offers::merkle::{SignError, SignatureTlvStreamRef};
+       use crate::offers::offer::{Amount, OfferBuilder, OfferTlvStreamRef, Quantity};
        use crate::offers::parse::{ParseError, SemanticError};
+       use crate::offers::payer::PayerTlvStreamRef;
        use crate::util::ser::{BigSize, Writeable};
        use crate::util::string::PrintableString;
 
@@ -517,14 +518,13 @@ mod tests {
 
        #[test]
        fn builds_invoice_request_with_defaults() {
-               let offer = OfferBuilder::new("foo".into(), recipient_pubkey())
+               let invoice_request = OfferBuilder::new("foo".into(), recipient_pubkey())
                        .amount_msats(1000)
-                       .build().unwrap();
-               let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
-                       .build().unwrap().sign(payer_sign).unwrap();
+                       .build().unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
 
-               let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, signature_tlv_stream) =
-                       invoice_request.as_tlv_stream();
                let mut buffer = Vec::new();
                invoice_request.write(&mut buffer).unwrap();
 
@@ -538,25 +538,34 @@ mod tests {
                assert_eq!(invoice_request.payer_note(), None);
                assert!(invoice_request.signature().is_some());
 
-               assert_eq!(payer_tlv_stream.metadata, Some(&vec![1; 32]));
-               assert_eq!(offer_tlv_stream.chains, None);
-               assert_eq!(offer_tlv_stream.metadata, None);
-               assert_eq!(offer_tlv_stream.currency, None);
-               assert_eq!(offer_tlv_stream.amount, Some(1000));
-               assert_eq!(offer_tlv_stream.description, Some(&String::from("foo")));
-               assert_eq!(offer_tlv_stream.features, None);
-               assert_eq!(offer_tlv_stream.absolute_expiry, None);
-               assert_eq!(offer_tlv_stream.paths, None);
-               assert_eq!(offer_tlv_stream.issuer, None);
-               assert_eq!(offer_tlv_stream.quantity_max, None);
-               assert_eq!(offer_tlv_stream.node_id, Some(&recipient_pubkey()));
-               assert_eq!(invoice_request_tlv_stream.chain, None);
-               assert_eq!(invoice_request_tlv_stream.amount, None);
-               assert_eq!(invoice_request_tlv_stream.features, None);
-               assert_eq!(invoice_request_tlv_stream.quantity, None);
-               assert_eq!(invoice_request_tlv_stream.payer_id, Some(&payer_pubkey()));
-               assert_eq!(invoice_request_tlv_stream.payer_note, None);
-               assert!(signature_tlv_stream.signature.is_some());
+               assert_eq!(
+                       invoice_request.as_tlv_stream(),
+                       (
+                               PayerTlvStreamRef { metadata: Some(&vec![1; 32]) },
+                               OfferTlvStreamRef {
+                                       chains: None,
+                                       metadata: None,
+                                       currency: None,
+                                       amount: Some(1000),
+                                       description: Some(&String::from("foo")),
+                                       features: None,
+                                       absolute_expiry: None,
+                                       paths: None,
+                                       issuer: None,
+                                       quantity_max: None,
+                                       node_id: Some(&recipient_pubkey()),
+                               },
+                               InvoiceRequestTlvStreamRef {
+                                       chain: None,
+                                       amount: None,
+                                       features: None,
+                                       quantity: None,
+                                       payer_id: Some(&payer_pubkey()),
+                                       payer_note: None,
+                               },
+                               SignatureTlvStreamRef { signature: invoice_request.signature().as_ref() },
+                       ),
+               );
 
                if let Err(e) = InvoiceRequest::try_from(buffer) {
                        panic!("error parsing invoice request: {:?}", e);
index be0eb2da522c3f260bec2db2b370ed6a3bf2711c..11df5ca1f8a108457fe1df9864ec8c0a916fdbe8 100644 (file)
@@ -17,3 +17,4 @@ mod merkle;
 pub mod offer;
 pub mod parse;
 mod payer;
+pub mod refund;
index 680f4094162ff49021bd47f1a705ff6f154cb375..6451d9431a188f3b15440c1d687435e7068f7240 100644 (file)
@@ -106,7 +106,7 @@ impl OfferBuilder {
                let offer = OfferContents {
                        chains: None, metadata: None, amount: None, description,
                        features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
-                       supported_quantity: Quantity::one(), signing_pubkey: Some(signing_pubkey),
+                       supported_quantity: Quantity::one(), signing_pubkey,
                };
                OfferBuilder { offer }
        }
@@ -263,7 +263,7 @@ pub(super) struct OfferContents {
        issuer: Option<String>,
        paths: Option<Vec<BlindedPath>>,
        supported_quantity: Quantity,
-       signing_pubkey: Option<PublicKey>,
+       signing_pubkey: PublicKey,
 }
 
 impl Offer {
@@ -359,7 +359,7 @@ impl Offer {
 
        /// The public key used by the recipient to sign invoices.
        pub fn signing_pubkey(&self) -> PublicKey {
-               self.contents.signing_pubkey.unwrap()
+               self.contents.signing_pubkey
        }
 
        /// Creates an [`InvoiceRequest`] for the offer with the given `metadata` and `payer_id`, which
@@ -497,7 +497,7 @@ impl OfferContents {
                        paths: self.paths.as_ref(),
                        issuer: self.issuer.as_ref(),
                        quantity_max: self.supported_quantity.to_tlv_record(),
-                       node_id: self.signing_pubkey.as_ref(),
+                       node_id: Some(&self.signing_pubkey),
                }
        }
 }
@@ -634,13 +634,14 @@ impl TryFrom<OfferTlvStream> for OfferContents {
                        Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
                };
 
-               if node_id.is_none() {
-                       return Err(SemanticError::MissingSigningPubkey);
-               }
+               let signing_pubkey = match node_id {
+                       None => return Err(SemanticError::MissingSigningPubkey),
+                       Some(node_id) => node_id,
+               };
 
                Ok(OfferContents {
                        chains, metadata, amount, description, features, absolute_expiry, issuer, paths,
-                       supported_quantity, signing_pubkey: node_id,
+                       supported_quantity, signing_pubkey,
                })
        }
 }
@@ -653,7 +654,7 @@ impl core::fmt::Display for Offer {
 
 #[cfg(test)]
 mod tests {
-       use super::{Amount, Offer, OfferBuilder, Quantity};
+       use super::{Amount, Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
 
        use bitcoin::blockdata::constants::ChainHash;
        use bitcoin::network::constants::Network;
@@ -680,7 +681,7 @@ mod tests {
        #[test]
        fn builds_offer_with_defaults() {
                let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
-               let tlv_stream = offer.as_tlv_stream();
+
                let mut buffer = Vec::new();
                offer.write(&mut buffer).unwrap();
 
@@ -699,17 +700,22 @@ mod tests {
                assert_eq!(offer.supported_quantity(), Quantity::one());
                assert_eq!(offer.signing_pubkey(), pubkey(42));
 
-               assert_eq!(tlv_stream.chains, None);
-               assert_eq!(tlv_stream.metadata, None);
-               assert_eq!(tlv_stream.currency, None);
-               assert_eq!(tlv_stream.amount, None);
-               assert_eq!(tlv_stream.description, Some(&String::from("foo")));
-               assert_eq!(tlv_stream.features, None);
-               assert_eq!(tlv_stream.absolute_expiry, None);
-               assert_eq!(tlv_stream.paths, None);
-               assert_eq!(tlv_stream.issuer, None);
-               assert_eq!(tlv_stream.quantity_max, None);
-               assert_eq!(tlv_stream.node_id, Some(&pubkey(42)));
+               assert_eq!(
+                       offer.as_tlv_stream(),
+                       OfferTlvStreamRef {
+                               chains: None,
+                               metadata: None,
+                               currency: None,
+                               amount: None,
+                               description: Some(&String::from("foo")),
+                               features: None,
+                               absolute_expiry: None,
+                               paths: None,
+                               issuer: None,
+                               quantity_max: None,
+                               node_id: Some(&pubkey(42)),
+                       },
+               );
 
                if let Err(e) = Offer::try_from(buffer) {
                        panic!("error parsing offer: {:?}", e);
@@ -1121,11 +1127,13 @@ mod tests {
                        panic!("error parsing offer: {:?}", e);
                }
 
-               let mut builder = OfferBuilder::new("foo".into(), pubkey(42));
-               builder.offer.signing_pubkey = None;
+               let mut tlv_stream = offer.as_tlv_stream();
+               tlv_stream.node_id = None;
 
-               let offer = builder.build().unwrap();
-               match offer.to_string().parse::<Offer>() {
+               let mut encoded_offer = Vec::new();
+               tlv_stream.write(&mut encoded_offer).unwrap();
+
+               match Offer::try_from(encoded_offer) {
                        Ok(_) => panic!("expected error"),
                        Err(e) => {
                                assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
index 0b3dda7928593871f8007c5ff50d2d081366dac8..b462e686910a2ed9d26f44c74c75cec3f00b5187 100644 (file)
@@ -127,20 +127,28 @@ pub enum SemanticError {
        AlreadyExpired,
        /// The provided chain hash does not correspond to a supported chain.
        UnsupportedChain,
+       /// A chain was provided but was not expected.
+       UnexpectedChain,
        /// An amount was expected but was missing.
        MissingAmount,
        /// The amount exceeded the total bitcoin supply.
        InvalidAmount,
        /// An amount was provided but was not sufficient in value.
        InsufficientAmount,
+       /// An amount was provided but was not expected.
+       UnexpectedAmount,
        /// A currency was provided that is not supported.
        UnsupportedCurrency,
        /// A feature was required but is unknown.
        UnknownRequiredFeatures,
+       /// Features were provided but were not expected.
+       UnexpectedFeatures,
        /// A required description was not provided.
        MissingDescription,
        /// A signing pubkey was not provided.
        MissingSigningPubkey,
+       /// A signing pubkey was provided but was not expected.
+       UnexpectedSigningPubkey,
        /// A quantity was expected but was missing.
        MissingQuantity,
        /// An unsupported quantity was provided.
diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs
new file mode 100644 (file)
index 0000000..4e553cb
--- /dev/null
@@ -0,0 +1,948 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Data structures and encoding for refunds.
+//!
+//! A [`Refund`] is an "offer for money" and is typically constructed by a merchant and presented
+//! directly to the customer. The recipient responds with an `Invoice` to be paid.
+//!
+//! This is an [`InvoiceRequest`] produced *not* in response to an [`Offer`].
+//!
+//! [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+//! [`Offer`]: crate::offers::offer::Offer
+//!
+//! ```ignore
+//! extern crate bitcoin;
+//! extern crate core;
+//! extern crate lightning;
+//!
+//! use core::convert::TryFrom;
+//! use core::time::Duration;
+//!
+//! use bitcoin::network::constants::Network;
+//! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
+//! use lightning::offers::parse::ParseError;
+//! use lightning::offers::refund::{Refund, RefundBuilder};
+//! use lightning::util::ser::{Readable, Writeable};
+//!
+//! # use lightning::onion_message::BlindedPath;
+//! # #[cfg(feature = "std")]
+//! # use std::time::SystemTime;
+//! #
+//! # fn create_blinded_path() -> BlindedPath { unimplemented!() }
+//! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
+//! #
+//! # #[cfg(feature = "std")]
+//! # fn build() -> Result<(), ParseError> {
+//! let secp_ctx = Secp256k1::new();
+//! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+//! let pubkey = PublicKey::from(keys);
+//!
+//! let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60);
+//! let refund = RefundBuilder::new("coffee, large".to_string(), vec![1; 32], pubkey, 20_000)?
+//!     .absolute_expiry(expiration.duration_since(SystemTime::UNIX_EPOCH).unwrap())
+//!     .issuer("Foo Bar".to_string())
+//!     .path(create_blinded_path())
+//!     .path(create_another_blinded_path())
+//!     .chain(Network::Bitcoin)
+//!     .payer_note("refund for order #12345".to_string())
+//!     .build()?;
+//!
+//! // Encode as a bech32 string for use in a QR code.
+//! let encoded_refund = refund.to_string();
+//!
+//! // Parse from a bech32 string after scanning from a QR code.
+//! let refund = encoded_refund.parse::<Refund>()?;
+//!
+//! // Encode refund as raw bytes.
+//! let mut bytes = Vec::new();
+//! refund.write(&mut bytes).unwrap();
+//!
+//! // Decode raw bytes into an refund.
+//! let refund = Refund::try_from(bytes)?;
+//! # Ok(())
+//! # }
+//! ```
+
+use bitcoin::blockdata::constants::ChainHash;
+use bitcoin::network::constants::Network;
+use bitcoin::secp256k1::PublicKey;
+use core::convert::TryFrom;
+use core::str::FromStr;
+use core::time::Duration;
+use crate::io;
+use crate::ln::features::InvoiceRequestFeatures;
+use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
+use crate::offers::invoice_request::{InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
+use crate::offers::offer::{OfferTlvStream, OfferTlvStreamRef};
+use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
+use crate::offers::payer::{PayerContents, PayerTlvStream, PayerTlvStreamRef};
+use crate::onion_message::BlindedPath;
+use crate::util::ser::{SeekReadable, WithoutLength, Writeable, Writer};
+use crate::util::string::PrintableString;
+
+use crate::prelude::*;
+
+#[cfg(feature = "std")]
+use std::time::SystemTime;
+
+/// Builds a [`Refund`] for the "offer for money" flow.
+///
+/// See [module-level documentation] for usage.
+///
+/// [module-level documentation]: self
+pub struct RefundBuilder {
+       refund: RefundContents,
+}
+
+impl RefundBuilder {
+       /// Creates a new builder for a refund using the [`Refund::payer_id`] for signing invoices. Use
+       /// a different pubkey per refund to avoid correlating refunds.
+       ///
+       /// Additionally, sets the required [`Refund::description`], [`Refund::metadata`], and
+       /// [`Refund::amount_msats`].
+       pub fn new(
+               description: String, metadata: Vec<u8>, payer_id: PublicKey, amount_msats: u64
+       ) -> Result<Self, SemanticError> {
+               if amount_msats > MAX_VALUE_MSAT {
+                       return Err(SemanticError::InvalidAmount);
+               }
+
+               let refund = RefundContents {
+                       payer: PayerContents(metadata), metadata: None, description, absolute_expiry: None,
+                       issuer: None, paths: None, chain: None, amount_msats,
+                       features: InvoiceRequestFeatures::empty(), payer_id, payer_note: None,
+               };
+
+               Ok(RefundBuilder { refund })
+       }
+
+       /// Sets the [`Refund::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has
+       /// already passed is valid and can be checked for using [`Refund::is_expired`].
+       ///
+       /// Successive calls to this method will override the previous setting.
+       pub fn absolute_expiry(mut self, absolute_expiry: Duration) -> Self {
+               self.refund.absolute_expiry = Some(absolute_expiry);
+               self
+       }
+
+       /// Sets the [`Refund::issuer`].
+       ///
+       /// Successive calls to this method will override the previous setting.
+       pub fn issuer(mut self, issuer: String) -> Self {
+               self.refund.issuer = Some(issuer);
+               self
+       }
+
+       /// Adds a blinded path to [`Refund::paths`]. Must include at least one path if only connected
+       /// by private channels or if [`Refund::payer_id`] is not a public node id.
+       ///
+       /// Successive calls to this method will add another blinded path. Caller is responsible for not
+       /// adding duplicate paths.
+       pub fn path(mut self, path: BlindedPath) -> Self {
+               self.refund.paths.get_or_insert_with(Vec::new).push(path);
+               self
+       }
+
+       /// Sets the [`Refund::chain`] of the given [`Network`] for paying an invoice. If not
+       /// called, [`Network::Bitcoin`] is assumed.
+       ///
+       /// Successive calls to this method will override the previous setting.
+       pub fn chain(mut self, network: Network) -> Self {
+               self.refund.chain = Some(ChainHash::using_genesis_block(network));
+               self
+       }
+
+       /// Sets the [`Refund::payer_note`].
+       ///
+       /// Successive calls to this method will override the previous setting.
+       pub fn payer_note(mut self, payer_note: String) -> Self {
+               self.refund.payer_note = Some(payer_note);
+               self
+       }
+
+       /// Builds a [`Refund`] after checking for valid semantics.
+       pub fn build(mut self) -> Result<Refund, SemanticError> {
+               if self.refund.chain() == self.refund.implied_chain() {
+                       self.refund.chain = None;
+               }
+
+               let mut bytes = Vec::new();
+               self.refund.write(&mut bytes).unwrap();
+
+               Ok(Refund {
+                       bytes,
+                       contents: self.refund,
+               })
+       }
+}
+
+#[cfg(test)]
+impl RefundBuilder {
+       fn features_unchecked(mut self, features: InvoiceRequestFeatures) -> Self {
+               self.refund.features = features;
+               self
+       }
+}
+
+/// A `Refund` is a request to send an `Invoice` without a preceding [`Offer`].
+///
+/// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to
+/// recoup their funds. A refund may be used more generally as an "offer for money", such as with a
+/// bitcoin ATM.
+///
+/// [`Offer`]: crate::offers::offer::Offer
+#[derive(Clone, Debug)]
+pub struct Refund {
+       bytes: Vec<u8>,
+       contents: RefundContents,
+}
+
+/// The contents of a [`Refund`], which may be shared with an `Invoice`.
+#[derive(Clone, Debug)]
+struct RefundContents {
+       payer: PayerContents,
+       // offer fields
+       metadata: Option<Vec<u8>>,
+       description: String,
+       absolute_expiry: Option<Duration>,
+       issuer: Option<String>,
+       paths: Option<Vec<BlindedPath>>,
+       // invoice_request fields
+       chain: Option<ChainHash>,
+       amount_msats: u64,
+       features: InvoiceRequestFeatures,
+       payer_id: PublicKey,
+       payer_note: Option<String>,
+}
+
+impl Refund {
+       /// A complete description of the purpose of the refund. Intended to be displayed to the user
+       /// but with the caveat that it has not been verified in any way.
+       pub fn description(&self) -> PrintableString {
+               PrintableString(&self.contents.description)
+       }
+
+       /// Duration since the Unix epoch when an invoice should no longer be sent.
+       ///
+       /// If `None`, the refund does not expire.
+       pub fn absolute_expiry(&self) -> Option<Duration> {
+               self.contents.absolute_expiry
+       }
+
+       /// Whether the refund has expired.
+       #[cfg(feature = "std")]
+       pub fn is_expired(&self) -> bool {
+               match self.absolute_expiry() {
+                       Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
+                               Ok(elapsed) => elapsed > seconds_from_epoch,
+                               Err(_) => false,
+                       },
+                       None => false,
+               }
+       }
+
+       /// The issuer of the refund, possibly beginning with `user@domain` or `domain`. Intended to be
+       /// displayed to the user but with the caveat that it has not been verified in any way.
+       pub fn issuer(&self) -> Option<PrintableString> {
+               self.contents.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
+       }
+
+       /// Paths to the sender originating from publicly reachable nodes. Blinded paths provide sender
+       /// privacy by obfuscating its node id.
+       pub fn paths(&self) -> &[BlindedPath] {
+               self.contents.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
+       }
+
+       /// An unpredictable series of bytes, typically containing information about the derivation of
+       /// [`payer_id`].
+       ///
+       /// [`payer_id`]: Self::payer_id
+       pub fn metadata(&self) -> &[u8] {
+               &self.contents.payer.0
+       }
+
+       /// A chain that the refund is valid for.
+       pub fn chain(&self) -> ChainHash {
+               self.contents.chain.unwrap_or_else(|| self.contents.implied_chain())
+       }
+
+       /// The amount to refund in msats (i.e., the minimum lightning-payable unit for [`chain`]).
+       ///
+       /// [`chain`]: Self::chain
+       pub fn amount_msats(&self) -> u64 {
+               self.contents.amount_msats
+       }
+
+       /// Features pertaining to requesting an invoice.
+       pub fn features(&self) -> &InvoiceRequestFeatures {
+               &self.contents.features
+       }
+
+       /// A possibly transient pubkey used to sign the refund.
+       pub fn payer_id(&self) -> PublicKey {
+               self.contents.payer_id
+       }
+
+       /// Payer provided note to include in the invoice.
+       pub fn payer_note(&self) -> Option<PrintableString> {
+               self.contents.payer_note.as_ref().map(|payer_note| PrintableString(payer_note.as_str()))
+       }
+
+       #[cfg(test)]
+       fn as_tlv_stream(&self) -> RefundTlvStreamRef {
+               self.contents.as_tlv_stream()
+       }
+}
+
+impl AsRef<[u8]> for Refund {
+       fn as_ref(&self) -> &[u8] {
+               &self.bytes
+       }
+}
+
+impl RefundContents {
+       fn chain(&self) -> ChainHash {
+               self.chain.unwrap_or_else(|| self.implied_chain())
+       }
+
+       pub fn implied_chain(&self) -> ChainHash {
+               ChainHash::using_genesis_block(Network::Bitcoin)
+       }
+
+       pub(super) fn as_tlv_stream(&self) -> RefundTlvStreamRef {
+               let payer = PayerTlvStreamRef {
+                       metadata: Some(&self.payer.0),
+               };
+
+               let offer = OfferTlvStreamRef {
+                       chains: None,
+                       metadata: self.metadata.as_ref(),
+                       currency: None,
+                       amount: None,
+                       description: Some(&self.description),
+                       features: None,
+                       absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()),
+                       paths: self.paths.as_ref(),
+                       issuer: self.issuer.as_ref(),
+                       quantity_max: None,
+                       node_id: None,
+               };
+
+               let features = {
+                       if self.features == InvoiceRequestFeatures::empty() { None }
+                       else { Some(&self.features) }
+               };
+
+               let invoice_request = InvoiceRequestTlvStreamRef {
+                       chain: self.chain.as_ref(),
+                       amount: Some(self.amount_msats),
+                       features,
+                       quantity: None,
+                       payer_id: Some(&self.payer_id),
+                       payer_note: self.payer_note.as_ref(),
+               };
+
+               (payer, offer, invoice_request)
+       }
+}
+
+impl Writeable for Refund {
+       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               WithoutLength(&self.bytes).write(writer)
+       }
+}
+
+impl Writeable for RefundContents {
+       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               self.as_tlv_stream().write(writer)
+       }
+}
+
+type RefundTlvStream = (PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream);
+
+type RefundTlvStreamRef<'a> = (
+       PayerTlvStreamRef<'a>,
+       OfferTlvStreamRef<'a>,
+       InvoiceRequestTlvStreamRef<'a>,
+);
+
+impl SeekReadable for RefundTlvStream {
+       fn read<R: io::Read + io::Seek>(r: &mut R) -> Result<Self, DecodeError> {
+               let payer = SeekReadable::read(r)?;
+               let offer = SeekReadable::read(r)?;
+               let invoice_request = SeekReadable::read(r)?;
+
+               Ok((payer, offer, invoice_request))
+       }
+}
+
+impl Bech32Encode for Refund {
+       const BECH32_HRP: &'static str = "lnr";
+}
+
+impl FromStr for Refund {
+       type Err = ParseError;
+
+       fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
+               Refund::from_bech32_str(s)
+       }
+}
+
+impl TryFrom<Vec<u8>> for Refund {
+       type Error = ParseError;
+
+       fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
+               let refund = ParsedMessage::<RefundTlvStream>::try_from(bytes)?;
+               let ParsedMessage { bytes, tlv_stream } = refund;
+               let contents = RefundContents::try_from(tlv_stream)?;
+
+               Ok(Refund { bytes, contents })
+       }
+}
+
+impl TryFrom<RefundTlvStream> for RefundContents {
+       type Error = SemanticError;
+
+       fn try_from(tlv_stream: RefundTlvStream) -> Result<Self, Self::Error> {
+               let (
+                       PayerTlvStream { metadata: payer_metadata },
+                       OfferTlvStream {
+                               chains, metadata, currency, amount: offer_amount, description,
+                               features: offer_features, absolute_expiry, paths, issuer, quantity_max, node_id,
+                       },
+                       InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note },
+               ) = tlv_stream;
+
+               let payer = match payer_metadata {
+                       None => return Err(SemanticError::MissingPayerMetadata),
+                       Some(metadata) => PayerContents(metadata),
+               };
+
+               if chains.is_some() {
+                       return Err(SemanticError::UnexpectedChain);
+               }
+
+               if currency.is_some() || offer_amount.is_some() {
+                       return Err(SemanticError::UnexpectedAmount);
+               }
+
+               let description = match description {
+                       None => return Err(SemanticError::MissingDescription),
+                       Some(description) => description,
+               };
+
+               if offer_features.is_some() {
+                       return Err(SemanticError::UnexpectedFeatures);
+               }
+
+               let absolute_expiry = absolute_expiry.map(Duration::from_secs);
+
+               if quantity_max.is_some() {
+                       return Err(SemanticError::UnexpectedQuantity);
+               }
+
+               if node_id.is_some() {
+                       return Err(SemanticError::UnexpectedSigningPubkey);
+               }
+
+               let amount_msats = match amount {
+                       None => return Err(SemanticError::MissingAmount),
+                       Some(amount_msats) if amount_msats > MAX_VALUE_MSAT => {
+                               return Err(SemanticError::InvalidAmount);
+                       },
+                       Some(amount_msats) => amount_msats,
+               };
+
+               let features = features.unwrap_or_else(InvoiceRequestFeatures::empty);
+
+               // TODO: Check why this isn't in the spec.
+               if quantity.is_some() {
+                       return Err(SemanticError::UnexpectedQuantity);
+               }
+
+               let payer_id = match payer_id {
+                       None => return Err(SemanticError::MissingPayerId),
+                       Some(payer_id) => payer_id,
+               };
+
+               // TODO: Should metadata be included?
+               Ok(RefundContents {
+                       payer, metadata, description, absolute_expiry, issuer, paths, chain, amount_msats,
+                       features, payer_id, payer_note,
+               })
+       }
+}
+
+impl core::fmt::Display for Refund {
+       fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
+               self.fmt_bech32_str(f)
+       }
+}
+
+#[cfg(test)]
+mod tests {
+       use super::{Refund, RefundBuilder, RefundTlvStreamRef};
+
+       use bitcoin::blockdata::constants::ChainHash;
+       use bitcoin::network::constants::Network;
+       use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
+       use core::convert::TryFrom;
+       use core::time::Duration;
+       use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
+       use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
+       use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
+       use crate::offers::offer::OfferTlvStreamRef;
+       use crate::offers::parse::{ParseError, SemanticError};
+       use crate::offers::payer::PayerTlvStreamRef;
+       use crate::onion_message::{BlindedHop, BlindedPath};
+       use crate::util::ser::{BigSize, Writeable};
+       use crate::util::string::PrintableString;
+
+       fn payer_pubkey() -> PublicKey {
+               let secp_ctx = Secp256k1::new();
+               KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()).public_key()
+       }
+
+       fn pubkey(byte: u8) -> PublicKey {
+               let secp_ctx = Secp256k1::new();
+               PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
+       }
+
+       fn privkey(byte: u8) -> SecretKey {
+               SecretKey::from_slice(&[byte; 32]).unwrap()
+       }
+
+       trait ToBytes {
+               fn to_bytes(&self) -> Vec<u8>;
+       }
+
+       impl<'a> ToBytes for RefundTlvStreamRef<'a> {
+               fn to_bytes(&self) -> Vec<u8> {
+                       let mut buffer = Vec::new();
+                       self.write(&mut buffer).unwrap();
+                       buffer
+               }
+       }
+
+       #[test]
+       fn builds_refund_with_defaults() {
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .build().unwrap();
+
+               let mut buffer = Vec::new();
+               refund.write(&mut buffer).unwrap();
+
+               assert_eq!(refund.bytes, buffer.as_slice());
+               assert_eq!(refund.metadata(), &[1; 32]);
+               assert_eq!(refund.description(), PrintableString("foo"));
+               assert_eq!(refund.absolute_expiry(), None);
+               #[cfg(feature = "std")]
+               assert!(!refund.is_expired());
+               assert_eq!(refund.paths(), &[]);
+               assert_eq!(refund.issuer(), None);
+               assert_eq!(refund.chain(), ChainHash::using_genesis_block(Network::Bitcoin));
+               assert_eq!(refund.amount_msats(), 1000);
+               assert_eq!(refund.features(), &InvoiceRequestFeatures::empty());
+               assert_eq!(refund.payer_id(), payer_pubkey());
+               assert_eq!(refund.payer_note(), None);
+
+               assert_eq!(
+                       refund.as_tlv_stream(),
+                       (
+                               PayerTlvStreamRef { metadata: Some(&vec![1; 32]) },
+                               OfferTlvStreamRef {
+                                       chains: None,
+                                       metadata: None,
+                                       currency: None,
+                                       amount: None,
+                                       description: Some(&String::from("foo")),
+                                       features: None,
+                                       absolute_expiry: None,
+                                       paths: None,
+                                       issuer: None,
+                                       quantity_max: None,
+                                       node_id: None,
+                               },
+                               InvoiceRequestTlvStreamRef {
+                                       chain: None,
+                                       amount: Some(1000),
+                                       features: None,
+                                       quantity: None,
+                                       payer_id: Some(&payer_pubkey()),
+                                       payer_note: None,
+                               },
+                       ),
+               );
+
+               if let Err(e) = Refund::try_from(buffer) {
+                       panic!("error parsing refund: {:?}", e);
+               }
+       }
+
+       #[test]
+       fn fails_building_refund_with_invalid_amount() {
+               match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), MAX_VALUE_MSAT + 1) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+               }
+       }
+
+       #[test]
+       fn builds_refund_with_absolute_expiry() {
+               let future_expiry = Duration::from_secs(u64::max_value());
+               let past_expiry = Duration::from_secs(0);
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .absolute_expiry(future_expiry)
+                       .build()
+                       .unwrap();
+               let (_, tlv_stream, _) = refund.as_tlv_stream();
+               #[cfg(feature = "std")]
+               assert!(!refund.is_expired());
+               assert_eq!(refund.absolute_expiry(), Some(future_expiry));
+               assert_eq!(tlv_stream.absolute_expiry, Some(future_expiry.as_secs()));
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .absolute_expiry(future_expiry)
+                       .absolute_expiry(past_expiry)
+                       .build()
+                       .unwrap();
+               let (_, tlv_stream, _) = refund.as_tlv_stream();
+               #[cfg(feature = "std")]
+               assert!(refund.is_expired());
+               assert_eq!(refund.absolute_expiry(), Some(past_expiry));
+               assert_eq!(tlv_stream.absolute_expiry, Some(past_expiry.as_secs()));
+       }
+
+       #[test]
+       fn builds_refund_with_paths() {
+               let paths = vec![
+                       BlindedPath {
+                               introduction_node_id: pubkey(40),
+                               blinding_point: pubkey(41),
+                               blinded_hops: vec![
+                                       BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
+                                       BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
+                               ],
+                       },
+                       BlindedPath {
+                               introduction_node_id: pubkey(40),
+                               blinding_point: pubkey(41),
+                               blinded_hops: vec![
+                                       BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
+                                       BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
+                               ],
+                       },
+               ];
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .path(paths[0].clone())
+                       .path(paths[1].clone())
+                       .build()
+                       .unwrap();
+               let (_, offer_tlv_stream, invoice_request_tlv_stream) = refund.as_tlv_stream();
+               assert_eq!(refund.paths(), paths.as_slice());
+               assert_eq!(refund.payer_id(), pubkey(42));
+               assert_ne!(pubkey(42), pubkey(44));
+               assert_eq!(offer_tlv_stream.paths, Some(&paths));
+               assert_eq!(invoice_request_tlv_stream.payer_id, Some(&pubkey(42)));
+       }
+
+       #[test]
+       fn builds_refund_with_issuer() {
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .issuer("bar".into())
+                       .build()
+                       .unwrap();
+               let (_, tlv_stream, _) = refund.as_tlv_stream();
+               assert_eq!(refund.issuer(), Some(PrintableString("bar")));
+               assert_eq!(tlv_stream.issuer, Some(&String::from("bar")));
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .issuer("bar".into())
+                       .issuer("baz".into())
+                       .build()
+                       .unwrap();
+               let (_, tlv_stream, _) = refund.as_tlv_stream();
+               assert_eq!(refund.issuer(), Some(PrintableString("baz")));
+               assert_eq!(tlv_stream.issuer, Some(&String::from("baz")));
+       }
+
+       #[test]
+       fn builds_refund_with_chain() {
+               let mainnet = ChainHash::using_genesis_block(Network::Bitcoin);
+               let testnet = ChainHash::using_genesis_block(Network::Testnet);
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .chain(Network::Bitcoin)
+                       .build().unwrap();
+               let (_, _, tlv_stream) = refund.as_tlv_stream();
+               assert_eq!(refund.chain(), mainnet);
+               assert_eq!(tlv_stream.chain, None);
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .chain(Network::Testnet)
+                       .build().unwrap();
+               let (_, _, tlv_stream) = refund.as_tlv_stream();
+               assert_eq!(refund.chain(), testnet);
+               assert_eq!(tlv_stream.chain, Some(&testnet));
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .chain(Network::Regtest)
+                       .chain(Network::Testnet)
+                       .build().unwrap();
+               let (_, _, tlv_stream) = refund.as_tlv_stream();
+               assert_eq!(refund.chain(), testnet);
+               assert_eq!(tlv_stream.chain, Some(&testnet));
+       }
+
+       #[test]
+       fn builds_refund_with_payer_note() {
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .payer_note("bar".into())
+                       .build().unwrap();
+               let (_, _, tlv_stream) = refund.as_tlv_stream();
+               assert_eq!(refund.payer_note(), Some(PrintableString("bar")));
+               assert_eq!(tlv_stream.payer_note, Some(&String::from("bar")));
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .payer_note("bar".into())
+                       .payer_note("baz".into())
+                       .build().unwrap();
+               let (_, _, tlv_stream) = refund.as_tlv_stream();
+               assert_eq!(refund.payer_note(), Some(PrintableString("baz")));
+               assert_eq!(tlv_stream.payer_note, Some(&String::from("baz")));
+       }
+
+       #[test]
+       fn parses_refund_with_metadata() {
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .build().unwrap();
+               if let Err(e) = refund.to_string().parse::<Refund>() {
+                       panic!("error parsing refund: {:?}", e);
+               }
+
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.0.metadata = None;
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerMetadata));
+                       },
+               }
+       }
+
+       #[test]
+       fn parses_refund_with_description() {
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .build().unwrap();
+               if let Err(e) = refund.to_string().parse::<Refund>() {
+                       panic!("error parsing refund: {:?}", e);
+               }
+
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.1.description = None;
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingDescription));
+                       },
+               }
+       }
+
+       #[test]
+       fn parses_refund_with_amount() {
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .build().unwrap();
+               if let Err(e) = refund.to_string().parse::<Refund>() {
+                       panic!("error parsing refund: {:?}", e);
+               }
+
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.2.amount = None;
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount));
+                       },
+               }
+
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.2.amount = Some(MAX_VALUE_MSAT + 1);
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount));
+                       },
+               }
+       }
+
+       #[test]
+       fn parses_refund_with_payer_id() {
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .build().unwrap();
+               if let Err(e) = refund.to_string().parse::<Refund>() {
+                       panic!("error parsing refund: {:?}", e);
+               }
+
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.2.payer_id = None;
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPayerId));
+                       },
+               }
+       }
+
+       #[test]
+       fn parses_refund_with_optional_fields() {
+               let past_expiry = Duration::from_secs(0);
+               let paths = vec![
+                       BlindedPath {
+                               introduction_node_id: pubkey(40),
+                               blinding_point: pubkey(41),
+                               blinded_hops: vec![
+                                       BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
+                                       BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
+                               ],
+                       },
+                       BlindedPath {
+                               introduction_node_id: pubkey(40),
+                               blinding_point: pubkey(41),
+                               blinded_hops: vec![
+                                       BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
+                                       BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
+                               ],
+                       },
+               ];
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .absolute_expiry(past_expiry)
+                       .issuer("bar".into())
+                       .path(paths[0].clone())
+                       .path(paths[1].clone())
+                       .chain(Network::Testnet)
+                       .features_unchecked(InvoiceRequestFeatures::unknown())
+                       .payer_note("baz".into())
+                       .build()
+                       .unwrap();
+               match refund.to_string().parse::<Refund>() {
+                       Ok(refund) => {
+                               assert_eq!(refund.absolute_expiry(), Some(past_expiry));
+                               #[cfg(feature = "std")]
+                               assert!(refund.is_expired());
+                               assert_eq!(refund.paths(), &paths[..]);
+                               assert_eq!(refund.issuer(), Some(PrintableString("bar")));
+                               assert_eq!(refund.chain(), ChainHash::using_genesis_block(Network::Testnet));
+                               assert_eq!(refund.features(), &InvoiceRequestFeatures::unknown());
+                               assert_eq!(refund.payer_note(), Some(PrintableString("baz")));
+                       },
+                       Err(e) => panic!("error parsing refund: {:?}", e),
+               }
+       }
+
+       #[test]
+       fn fails_parsing_refund_with_unexpected_fields() {
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .build().unwrap();
+               if let Err(e) = refund.to_string().parse::<Refund>() {
+                       panic!("error parsing refund: {:?}", e);
+               }
+
+               let chains = vec![ChainHash::using_genesis_block(Network::Testnet)];
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.1.chains = Some(&chains);
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedChain));
+                       },
+               }
+
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.1.currency = Some(&b"USD");
+               tlv_stream.1.amount = Some(1000);
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedAmount));
+                       },
+               }
+
+               let features = OfferFeatures::unknown();
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.1.features = Some(&features);
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedFeatures));
+                       },
+               }
+
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.1.quantity_max = Some(10);
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
+                       },
+               }
+
+               let node_id = payer_pubkey();
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.1.node_id = Some(&node_id);
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedSigningPubkey));
+                       },
+               }
+
+               let mut tlv_stream = refund.as_tlv_stream();
+               tlv_stream.2.quantity = Some(10);
+
+               match Refund::try_from(tlv_stream.to_bytes()) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => {
+                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::UnexpectedQuantity));
+                       },
+               }
+       }
+
+       #[test]
+       fn fails_parsing_refund_with_extra_tlv_records() {
+               let secp_ctx = Secp256k1::new();
+               let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], keys.public_key(), 1000).unwrap()
+                       .build().unwrap();
+
+               let mut encoded_refund = Vec::new();
+               refund.write(&mut encoded_refund).unwrap();
+               BigSize(1002).write(&mut encoded_refund).unwrap();
+               BigSize(32).write(&mut encoded_refund).unwrap();
+               [42u8; 32].write(&mut encoded_refund).unwrap();
+
+               match Refund::try_from(encoded_refund) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+               }
+       }
+}
index 73c4015726191f5ae5ec8b95eadcf82101bc3af8..91a52c538983a5dc1c65778488245545729c39ff 100644 (file)
@@ -510,6 +510,7 @@ macro_rules! tlv_stream {
                        )*
                }
 
+               #[derive(Debug, PartialEq)]
                pub(super) struct $nameref<'a> {
                        $(
                                pub(super) $field: Option<tlv_record_ref_type!($fieldty)>,