}
},
events::Event::PaymentSent { .. } => {},
- events::Event::PaymentFailed { .. } => {},
+ events::Event::PaymentPathFailed { .. } => {},
events::Event::PaymentForwarded { .. } if $node == 1 => {},
events::Event::PendingHTLCsForwardable { .. } => {
nodes[$node].process_pending_htlc_forwards();
-use crate::{BlockHeaderData, BlockSourceError};
use crate::http::{BinaryResponse, JsonResponse};
use crate::utils::hex_to_uint256;
+use crate::{BlockHeaderData, BlockSourceError};
use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::consensus::encode;
use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid};
-use bitcoin::hashes::hex::{ToHex, FromHex};
+use bitcoin::hashes::hex::{FromHex, ToHex};
+use bitcoin::Transaction;
use serde::Deserialize;
}
}
-
/// Converts a JSON value into a block. Assumes the block is hex-encoded in a JSON string.
impl TryInto<Block> for JsonResponse {
type Error = std::io::Error;
}
}
+/// Converts a JSON value into a transaction. WATCH OUT! this cannot be used for zero-input transactions
+/// (e.g. createrawtransaction). See https://github.com/rust-bitcoin/rust-bitcoincore-rpc/issues/197
+impl TryInto<Transaction> for JsonResponse {
+ type Error = std::io::Error;
+ fn try_into(self) -> std::io::Result<Transaction> {
+ let hex_tx = if self.0.is_object() {
+ // result is json encoded
+ match &self.0["hex"] {
+ // result has hex field
+ serde_json::Value::String(hex_data) => match self.0["complete"] {
+ // result may or may not be signed (e.g. signrawtransactionwithwallet)
+ serde_json::Value::Bool(x) => {
+ if x == false {
+ let reason = match &self.0["errors"][0]["error"] {
+ serde_json::Value::String(x) => x.as_str(),
+ _ => "Unknown error",
+ };
+
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ format!("transaction couldn't be signed. {}", reason),
+ ));
+ } else {
+ hex_data
+ }
+ }
+ // result is a complete transaction (e.g. getrawtranaction verbose)
+ _ => hex_data,
+ },
+ _ => return Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ "expected JSON string",
+ )),
+ }
+ } else {
+ // result is plain text (e.g. getrawtransaction no verbose)
+ match self.0.as_str() {
+ Some(hex_tx) => hex_tx,
+ None => {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ "expected JSON string",
+ ))
+ }
+ }
+ };
+
+ match Vec::<u8>::from_hex(hex_tx) {
+ Err(_) => Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ "invalid hex data",
+ )),
+ Ok(tx_data) => match encode::deserialize(&tx_data) {
+ Err(_) => Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ "invalid transaction",
+ )),
+ Ok(tx) => Ok(tx),
+ },
+ }
+ }
+}
+
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use bitcoin::blockdata::constants::genesis_block;
- use bitcoin::consensus::encode;
use bitcoin::hashes::Hash;
use bitcoin::network::constants::Network;
+ use serde_json::value::Number;
+ use serde_json::Value;
/// Converts from `BlockHeaderData` into a `GetHeaderResponse` JSON value.
impl From<BlockHeaderData> for serde_json::Value {
Ok(txid) => assert_eq!(txid, target_txid),
}
}
+
+ // TryInto<Transaction> can be used in two ways, first with plain hex response where data is
+ // the hex encoded transaction (e.g. as a result of getrawtransaction) or as a JSON object
+ // where the hex encoded transaction can be found in the hex field of the object (if present)
+ // (e.g. as a result of signrawtransactionwithwallet).
+
+ // plain hex transaction
+
+ #[test]
+ fn into_tx_from_json_response_with_invalid_hex_data() {
+ let response = JsonResponse(serde_json::json!("foobar"));
+ match TryInto::<Transaction>::try_into(response) {
+ Err(e) => {
+ assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
+ assert_eq!(e.get_ref().unwrap().to_string(), "invalid hex data");
+ }
+ Ok(_) => panic!("Expected error"),
+ }
+ }
+
+ #[test]
+ fn into_tx_from_json_response_with_invalid_data_type() {
+ let response = JsonResponse(Value::Number(Number::from_f64(1.0).unwrap()));
+ match TryInto::<Transaction>::try_into(response) {
+ Err(e) => {
+ assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
+ assert_eq!(e.get_ref().unwrap().to_string(), "expected JSON string");
+ }
+ Ok(_) => panic!("Expected error"),
+ }
+ }
+
+ #[test]
+ fn into_tx_from_json_response_with_invalid_tx_data() {
+ let response = JsonResponse(serde_json::json!("abcd"));
+ match TryInto::<Transaction>::try_into(response) {
+ Err(e) => {
+ assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
+ assert_eq!(e.get_ref().unwrap().to_string(), "invalid transaction");
+ }
+ Ok(_) => panic!("Expected error"),
+ }
+ }
+
+ #[test]
+ fn into_tx_from_json_response_with_valid_tx_data_plain() {
+ let genesis_block = genesis_block(Network::Bitcoin);
+ let target_tx = genesis_block.txdata.get(0).unwrap();
+ let response = JsonResponse(serde_json::json!(encode::serialize_hex(&target_tx)));
+ match TryInto::<Transaction>::try_into(response) {
+ Err(e) => panic!("Unexpected error: {:?}", e),
+ Ok(tx) => assert_eq!(&tx, target_tx),
+ }
+ }
+
+ #[test]
+ fn into_tx_from_json_response_with_valid_tx_data_hex_field() {
+ let genesis_block = genesis_block(Network::Bitcoin);
+ let target_tx = genesis_block.txdata.get(0).unwrap();
+ let response = JsonResponse(serde_json::json!({"hex": encode::serialize_hex(&target_tx)}));
+ match TryInto::<Transaction>::try_into(response) {
+ Err(e) => panic!("Unexpected error: {:?}", e),
+ Ok(tx) => assert_eq!(&tx, target_tx),
+ }
+ }
+
+ // transaction in hex field of JSON object
+
+ #[test]
+ fn into_tx_from_json_response_with_no_hex_field() {
+ let response = JsonResponse(serde_json::json!({ "error": "foo" }));
+ match TryInto::<Transaction>::try_into(response) {
+ Err(e) => {
+ assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
+ assert_eq!(
+ e.get_ref().unwrap().to_string(),
+ "expected JSON string"
+ );
+ }
+ Ok(_) => panic!("Expected error"),
+ }
+ }
+
+ #[test]
+ fn into_tx_from_json_response_not_signed() {
+ let response = JsonResponse(serde_json::json!({ "hex": "foo", "complete": false }));
+ match TryInto::<Transaction>::try_into(response) {
+ Err(e) => {
+ assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
+ assert!(
+ e.get_ref().unwrap().to_string().contains(
+ "transaction couldn't be signed")
+ );
+ }
+ Ok(_) => panic!("Expected error"),
+ }
+ }
}
/// Timeout for reading the first byte of a response. This is separate from the general read
/// timeout as it is not uncommon for Bitcoin Core to be blocked waiting on UTXO cache flushes for
-/// upwards of a minute or more. Note that we always retry once when we time out, so the maximum
-/// time we allow Bitcoin Core to block for is twice this value.
-const TCP_STREAM_RESPONSE_TIMEOUT: Duration = Duration::from_secs(120);
+/// upwards of 10 minutes on slow devices (e.g. RPis with SSDs over USB). Note that we always retry
+/// once when we time out, so the maximum time we allow Bitcoin Core to block for is twice this
+/// value.
+const TCP_STREAM_RESPONSE_TIMEOUT: Duration = Duration::from_secs(300);
/// Maximum HTTP message header size in bytes.
const MAX_HTTP_MESSAGE_HEADER_SIZE: usize = 8192;
};
// TODO: Once we hit the chain with the failure transaction we should check that we get a
- // PaymentFailed event
+ // PaymentPathFailed event
assert_eq!(nodes[0].node.list_channels().len(), 0);
check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: "ChannelMonitor storage failure".to_string() });
check_closed_broadcast!(nodes[0], true);
// TODO: Once we hit the chain with the failure transaction we should check that we get a
- // PaymentFailed event
+ // PaymentPathFailed event
assert_eq!(nodes[0].node.list_channels().len(), 0);
check_closed_event!(nodes[0], 1, ClosureReason::HolderForceClosed);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 2);
- if let Event::PaymentFailed { payment_hash, rejected_by_dest, .. } = events[0] {
+ if let Event::PaymentPathFailed { payment_hash, rejected_by_dest, .. } = events[0] {
assert_eq!(payment_hash, payment_hash_1);
assert!(rejected_by_dest);
} else { panic!("Unexpected event!"); }
/// The session_priv bytes of outbound payments which are pending resolution.
/// The authoritative state of these HTLCs resides either within Channels or ChannelMonitors
/// (if the channel has been force-closed), however we track them here to prevent duplicative
- /// PaymentSent/PaymentFailed events. Specifically, in the case of a duplicative
+ /// PaymentSent/PaymentPathFailed events. Specifically, in the case of a duplicative
/// update_fulfill_htlc message after a reconnect, we may "claim" a payment twice.
/// Additionally, because ChannelMonitors are often not re-serialized after connecting block(s)
/// which may generate a claim event, we may receive similar duplicate claim/fail MonitorEvents
self.fail_htlc_backwards_internal(channel_state,
htlc_src, &payment_hash, HTLCFailReason::Reason { failure_code, data: onion_failure_data});
},
- HTLCSource::OutboundRoute { session_priv, mpp_id, .. } => {
+ HTLCSource::OutboundRoute { session_priv, mpp_id, path, .. } => {
let mut session_priv_bytes = [0; 32];
session_priv_bytes.copy_from_slice(&session_priv[..]);
let mut outbounds = self.pending_outbound_payments.lock().unwrap();
if let hash_map::Entry::Occupied(mut sessions) = outbounds.entry(mpp_id) {
if sessions.get_mut().remove(&session_priv_bytes) {
self.pending_events.lock().unwrap().push(
- events::Event::PaymentFailed {
+ events::Event::PaymentPathFailed {
payment_hash,
rejected_by_dest: false,
network_update: None,
all_paths_failed: sessions.get().len() == 0,
+ path: path.clone(),
#[cfg(test)]
error_code: None,
#[cfg(test)]
// process_onion_failure we should close that channel as it implies our
// next-hop is needlessly blaming us!
self.pending_events.lock().unwrap().push(
- events::Event::PaymentFailed {
+ events::Event::PaymentPathFailed {
payment_hash: payment_hash.clone(),
rejected_by_dest: !payment_retryable,
network_update,
all_paths_failed,
+ path: path.clone(),
#[cfg(test)]
error_code: onion_error_code,
#[cfg(test)]
// TODO: For non-temporary failures, we really should be closing the
// channel here as we apparently can't relay through them anyway.
self.pending_events.lock().unwrap().push(
- events::Event::PaymentFailed {
+ events::Event::PaymentPathFailed {
payment_hash: payment_hash.clone(),
rejected_by_dest: path.len() == 1,
network_update: None,
all_paths_failed,
+ path: path.clone(),
#[cfg(test)]
error_code: Some(*failure_code),
#[cfg(test)]
let events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
- Event::PaymentFailed { ref payment_hash, rejected_by_dest, ref network_update, ref error_code, ref error_data, .. } => {
+ Event::PaymentPathFailed { ref payment_hash, rejected_by_dest, ref network_update, ref error_code, ref error_data, .. } => {
assert_eq!(*payment_hash, $expected_payment_hash, "unexpected payment_hash");
assert_eq!(rejected_by_dest, $rejected_by_dest, "unexpected rejected_by_dest value");
assert!(error_code.is_some(), "expected error_code.is_some() = true");
let events = $node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
- Event::PaymentFailed { ref payment_hash, rejected_by_dest, network_update: _, ref error_code, ref error_data, .. } => {
+ Event::PaymentPathFailed { ref payment_hash, rejected_by_dest, network_update: _, ref error_code, ref error_data, .. } => {
assert_eq!(*payment_hash, $expected_payment_hash, "unexpected payment_hash");
assert_eq!(rejected_by_dest, $rejected_by_dest, "unexpected rejected_by_dest value");
assert!(error_code.is_some(), "expected error_code.is_some() = true");
let events = origin_node.node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
- Event::PaymentFailed { payment_hash, rejected_by_dest, all_paths_failed, .. } => {
+ Event::PaymentPathFailed { payment_hash, rejected_by_dest, all_paths_failed, ref path, .. } => {
assert_eq!(payment_hash, our_payment_hash);
assert!(rejected_by_dest);
assert_eq!(all_paths_failed, i == expected_paths.len() - 1);
+ for (idx, hop) in expected_route.iter().enumerate() {
+ assert_eq!(hop.node.get_our_node_id(), path[idx].pubkey);
+ }
},
_ => panic!("Unexpected event"),
}
_ => panic!("Unexepected event"),
}
match events[1] {
- Event::PaymentFailed { ref payment_hash, .. } => {
+ Event::PaymentPathFailed { ref payment_hash, .. } => {
assert_eq!(*payment_hash, fourth_payment_hash);
},
_ => panic!("Unexpected event"),
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 3);
match events[0] {
- Event::PaymentFailed { ref payment_hash, rejected_by_dest: _, ref network_update, .. } => {
+ Event::PaymentPathFailed { ref payment_hash, rejected_by_dest: _, ref network_update, .. } => {
assert!(failed_htlcs.insert(payment_hash.0));
// If we delivered B's RAA we got an unknown preimage error, not something
// that we should update our routing table for.
_ => panic!("Unexpected event"),
}
match events[1] {
- Event::PaymentFailed { ref payment_hash, rejected_by_dest: _, ref network_update, .. } => {
+ Event::PaymentPathFailed { ref payment_hash, rejected_by_dest: _, ref network_update, .. } => {
assert!(failed_htlcs.insert(payment_hash.0));
assert!(network_update.is_some());
},
_ => panic!("Unexpected event"),
}
match events[2] {
- Event::PaymentFailed { ref payment_hash, rejected_by_dest: _, ref network_update, .. } => {
+ Event::PaymentPathFailed { ref payment_hash, rejected_by_dest: _, ref network_update, .. } => {
assert!(failed_htlcs.insert(payment_hash.0));
assert!(network_update.is_some());
},
assert_eq!(events.len(), 2);
// Check that Alice fails backward the pending HTLC from the second payment.
match events[0] {
- Event::PaymentFailed { payment_hash, .. } => {
+ Event::PaymentPathFailed { payment_hash, .. } => {
assert_eq!(payment_hash, failed_payment_hash);
},
_ => panic!("Unexpected event"),
_ => panic!("Unexpected event"),
}
match events[1] {
- Event::PaymentFailed { payment_hash, rejected_by_dest, .. } => {
+ Event::PaymentPathFailed { payment_hash, rejected_by_dest, .. } => {
assert_eq!(payment_hash, payment_hash_5);
assert!(rejected_by_dest);
},
//
// If, due to an on-chain event, an HTLC is failed/claimed, and then we serialize the
// ChannelManager, we generally expect there not to be a duplicate HTLC fail/claim (eg via a
- // PaymentFailed event appearing). However, because we may not serialize the relevant
+ // PaymentPathFailed event appearing). However, because we may not serialize the relevant
// ChannelMonitor at the same time, this isn't strictly guaranteed. In order to provide this
// consistency, the ChannelManager explicitly tracks pending-onchain-resolution outbound HTLCs
// and de-duplicates ChannelMonitor events.
let mut as_failds = HashSet::new();
let mut as_updates = 0;
for event in as_events.iter() {
- if let &Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, .. } = event {
+ if let &Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, .. } = event {
assert!(as_failds.insert(*payment_hash));
if *payment_hash != payment_hash_2 {
assert_eq!(*rejected_by_dest, deliver_last_raa);
let mut bs_failds = HashSet::new();
let mut bs_updates = 0;
for event in bs_events.iter() {
- if let &Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, .. } = event {
+ if let &Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, .. } = event {
assert!(bs_failds.insert(*payment_hash));
if *payment_hash != payment_hash_1 && *payment_hash != payment_hash_5 {
assert_eq!(*rejected_by_dest, deliver_last_raa);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match &events[0] {
- &Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data, ref all_paths_failed } => {
+ &Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data, ref all_paths_failed, path: _ } => {
assert_eq!(our_payment_hash.clone(), *payment_hash);
assert_eq!(*rejected_by_dest, false);
assert_eq!(*all_paths_failed, true);
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match &events[0] {
- &Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data, ref all_paths_failed } => {
+ &Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data, ref all_paths_failed, path: _ } => {
assert_eq!(payment_hash_2.clone(), *payment_hash);
assert_eq!(*rejected_by_dest, false);
assert_eq!(*all_paths_failed, true);
assert_eq!(nodes[0].node.get_and_clear_pending_events().len(), 0);
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
let events = nodes[0].node.get_and_clear_pending_events();
- // Only 2 PaymentFailed events should show up, over-dust HTLC has to be failed by timeout tx
+ // Only 2 PaymentPathFailed events should show up, over-dust HTLC has to be failed by timeout tx
assert_eq!(events.len(), 2);
let mut first_failed = false;
for event in events {
match event {
- Event::PaymentFailed { payment_hash, .. } => {
+ Event::PaymentPathFailed { payment_hash, .. } => {
if payment_hash == payment_hash_1 {
assert!(!first_failed);
first_failed = true;
assert_eq!(events.len(), 2);
let first;
match events[0] {
- Event::PaymentFailed { payment_hash, .. } => {
+ Event::PaymentPathFailed { payment_hash, .. } => {
if payment_hash == dust_hash { first = true; }
else { first = false; }
},
_ => panic!("Unexpected event"),
}
match events[1] {
- Event::PaymentFailed { payment_hash, .. } => {
+ Event::PaymentPathFailed { payment_hash, .. } => {
if first { assert_eq!(payment_hash, non_dust_hash); }
else { assert_eq!(payment_hash, dust_hash); }
},
let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
- if let &Event::PaymentFailed { payment_hash:_, ref rejected_by_dest, ref network_update, ref error_code, error_data: _, ref all_paths_failed } = &events[0] {
+ if let &Event::PaymentPathFailed { payment_hash:_, ref rejected_by_dest, ref network_update, ref error_code, error_data: _, ref all_paths_failed, path: _ } = &events[0] {
assert_eq!(*rejected_by_dest, !expected_retryable);
assert_eq!(*all_paths_failed, true);
assert_eq!(*error_code, expected_error_code);
impl<C: Deref, L: Deref> EventHandler for NetGraphMsgHandler<C, L>
where C::Target: chain::Access, L::Target: Logger {
fn handle_event(&self, event: &Event) {
- if let Event::PaymentFailed { payment_hash: _, rejected_by_dest: _, network_update, .. } = event {
+ if let Event::PaymentPathFailed { payment_hash: _, rejected_by_dest: _, network_update, .. } = event {
if let Some(network_update) = network_update {
self.handle_network_update(network_update);
}
/// Provides interface to help with initial routing sync by
/// serving historical announcements.
///
-/// Serves as an [`EventHandler`] for applying updates from [`Event::PaymentFailed`] to the
+/// Serves as an [`EventHandler`] for applying updates from [`Event::PaymentPathFailed`] to the
/// [`NetworkGraph`].
pub struct NetGraphMsgHandler<C: Deref, L: Deref>
where C::Target: chain::Access, L::Target: Logger
assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_none());
- net_graph_msg_handler.handle_event(&Event::PaymentFailed {
+ net_graph_msg_handler.handle_event(&Event::PaymentPathFailed {
payment_hash: PaymentHash([0; 32]),
rejected_by_dest: false,
all_paths_failed: true,
+ path: vec![],
network_update: Some(NetworkUpdate::ChannelUpdateMessage {
msg: valid_channel_update,
}),
}
};
- net_graph_msg_handler.handle_event(&Event::PaymentFailed {
+ net_graph_msg_handler.handle_event(&Event::PaymentPathFailed {
payment_hash: PaymentHash([0; 32]),
rejected_by_dest: false,
all_paths_failed: true,
+ path: vec![],
network_update: Some(NetworkUpdate::ChannelClosed {
short_channel_id,
is_permanent: false,
// Permanent closing deletes a channel
{
- net_graph_msg_handler.handle_event(&Event::PaymentFailed {
+ net_graph_msg_handler.handle_event(&Event::PaymentPathFailed {
payment_hash: PaymentHash([0; 32]),
rejected_by_dest: false,
all_paths_failed: true,
+ path: vec![],
network_update: Some(NetworkUpdate::ChannelClosed {
short_channel_id,
is_permanent: true,
use core::ops::Deref;
/// A hop in a route
-#[derive(Clone, Hash, PartialEq, Eq)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct RouteHop {
/// The node_id of the node at this hop.
pub pubkey: PublicKey,
}
impl Route {
- /// Returns the total amount of fees paid on this Route.
- /// This doesn't include any extra payment made to the recipient,
- /// which can happen in excess of the amount passed to `get_route`'s `final_value_msat`.
+ /// Returns the total amount of fees paid on this [`Route`].
+ ///
+ /// This doesn't include any extra payment made to the recipient, which can happen in excess of
+ /// the amount passed to [`get_route`]'s `final_value_msat`.
pub fn get_total_fees(&self) -> u64 {
// Do not count last hop of each path since that's the full value of the payment
return self.paths.iter()
- .flat_map(|path| path.split_last().unwrap().1)
+ .flat_map(|path| path.split_last().map(|(_, path_prefix)| path_prefix).unwrap_or(&[]))
.map(|hop| &hop.fee_msat)
.sum();
}
- /// Returns the total amount paid on this Route, excluding the fees.
+
+ /// Returns the total amount paid on this [`Route`], excluding the fees.
pub fn get_total_amount(&self) -> u64 {
return self.paths.iter()
- .map(|path| path.split_last().unwrap().0)
- .map(|hop| &hop.fee_msat)
+ .map(|path| path.split_last().map(|(hop, _)| hop.fee_msat).unwrap_or(0))
.sum();
}
}
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually
+ short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually
+ short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually
+ short_channel_id: 0, fee_msat: 225, cltv_expiry_delta: 0
},
]],
};
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually
+ short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually
+ short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
},
],vec![
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually
+ short_channel_id: 0, fee_msat: 100, cltv_expiry_delta: 0
},
RouteHop {
pubkey: PublicKey::from_slice(&hex::decode("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c").unwrap()[..]).unwrap(),
channel_features: ChannelFeatures::empty(), node_features: NodeFeatures::empty(),
- short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0 // Test vectors are garbage and not generateble from a RouteHop, we fill in payloads manually
+ short_channel_id: 0, fee_msat: 150, cltv_expiry_delta: 0
},
]],
};
assert_eq!(route.get_total_amount(), 300);
}
+ #[test]
+ fn total_empty_route_no_panic() {
+ // In an earlier version of `Route::get_total_fees` and `Route::get_total_amount`, they
+ // would both panic if the route was completely empty. We test to ensure they return 0
+ // here, even though its somewhat nonsensical as a route.
+ let route = Route { paths: Vec::new() };
+
+ assert_eq!(route.get_total_fees(), 0);
+ assert_eq!(route.get_total_amount(), 0);
+ }
+
#[cfg(not(feature = "no-std"))]
pub(super) fn random_init_seed() -> u64 {
// Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.
use ln::msgs::DecodeError;
use ln::{PaymentPreimage, PaymentHash, PaymentSecret};
use routing::network_graph::NetworkUpdate;
-use util::ser::{Writeable, Writer, MaybeReadable, Readable, VecReadWrapper, VecWriteWrapper};
+use util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, VecReadWrapper, VecWriteWrapper};
+use routing::router::RouteHop;
use bitcoin::blockdata::script::Script;
/// Indicates an outbound payment we made succeeded (i.e. it made it all the way to its target
/// and we got back the payment preimage for it).
///
- /// Note for MPP payments: in rare cases, this event may be preceded by a `PaymentFailed` event.
- /// In this situation, you SHOULD treat this payment as having succeeded.
+ /// Note for MPP payments: in rare cases, this event may be preceded by a `PaymentPathFailed`
+ /// event. In this situation, you SHOULD treat this payment as having succeeded.
PaymentSent {
/// The preimage to the hash given to ChannelManager::send_payment.
/// Note that this serves as a payment receipt, if you wish to have such a thing, you must
},
/// Indicates an outbound payment we made failed. Probably some intermediary node dropped
/// something. You may wish to retry with a different route.
- PaymentFailed {
+ PaymentPathFailed {
/// The hash which was given to ChannelManager::send_payment.
payment_hash: PaymentHash,
/// Indicates the payment was rejected for some reason by the recipient. This implies that
/// failed. This will be set to false if (1) this is an MPP payment and (2) other parts of the
/// larger MPP payment were still in flight when this event was generated.
all_paths_failed: bool,
+ /// The payment path that failed.
+ path: Vec<RouteHop>,
#[cfg(test)]
error_code: Option<u16>,
#[cfg(test)]
(0, payment_preimage, required),
});
},
- &Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref all_paths_failed,
+ &Event::PaymentPathFailed { ref payment_hash, ref rejected_by_dest, ref network_update,
+ ref all_paths_failed, ref path,
#[cfg(test)]
ref error_code,
#[cfg(test)]
(1, network_update, option),
(2, rejected_by_dest, required),
(3, all_paths_failed, required),
+ (5, path, vec_type),
});
},
&Event::PendingHTLCsForwardable { time_forwardable: _ } => {
(2, reason, required)
});
},
+ // Note that, going forward, all new events must only write data inside of
+ // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
+ // data via `write_tlv_fields`.
}
Ok(())
}
impl MaybeReadable for Event {
fn read<R: io::Read>(reader: &mut R) -> Result<Option<Self>, msgs::DecodeError> {
match Readable::read(reader)? {
+ // Note that we do not write a length-prefixed TLV for FundingGenerationReady events,
+ // unlike all other events, thus we return immediately here.
0u8 => Ok(None),
1u8 => {
let f = || {
let mut rejected_by_dest = false;
let mut network_update = None;
let mut all_paths_failed = Some(true);
+ let mut path: Option<Vec<RouteHop>> = Some(vec![]);
read_tlv_fields!(reader, {
(0, payment_hash, required),
(1, network_update, ignorable),
(2, rejected_by_dest, required),
(3, all_paths_failed, option),
+ (5, path, vec_type),
});
- Ok(Some(Event::PaymentFailed {
+ Ok(Some(Event::PaymentPathFailed {
payment_hash,
rejected_by_dest,
network_update,
all_paths_failed: all_paths_failed.unwrap(),
+ path: path.unwrap(),
#[cfg(test)]
error_code,
#[cfg(test)]
Ok(Some(Event::ChannelClosed { channel_id, reason: reason.unwrap() }))
},
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
- x if x % 2 == 1 => Ok(None),
+ // Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
+ // reads.
+ x if x % 2 == 1 => {
+ // If the event is of unknown type, assume it was written with `write_tlv_fields`,
+ // which prefixes the whole thing with a length BigSize. Because the event is
+ // odd-type unknown, we should treat it as `Ok(None)` even if it has some TLV
+ // fields that are even. Thus, we avoid using `read_tlv_fields` and simply read
+ // exactly the number of bytes specified, ignoring them entirely.
+ let tlv_len: BigSize = Readable::read(reader)?;
+ FixedLengthReader::new(reader, tlv_len.0)
+ .eat_remaining().map_err(|_| msgs::DecodeError::ShortRead)?;
+ Ok(None)
+ },
_ => Err(msgs::DecodeError::InvalidValue)
}
}