- name: Pin tokio to 1.14 for Rust 1.45
if: "matrix.build-net-old-tokio"
run: cargo update -p tokio --precise "1.14.0" --verbose
+ env:
+ CARGO_NET_GIT_FETCH_WITH_CLI: "true"
- name: Build on Rust ${{ matrix.toolchain }} with net-tokio
if: "matrix.build-net-tokio && !matrix.coverage"
run: cargo build --verbose --color always
super::do_test(&::hex::decode(one_hop_om).unwrap(), &logger);
{
let log_entries = logger.lines.lock().unwrap();
- assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Received an onion message with path_id: None".to_string())), Some(&1));
+ assert_eq!(log_entries.get(&("lightning::onion_message::messenger".to_string(), "Received an onion message with path_id: None and no reply_path".to_string())), Some(&1));
}
let two_unblinded_hops_om = "020000000000000000000000000000000000000000000000000000000000000e01055600020000000000000000000000000000000000000000000000000000000000000e0135043304210200000000000000000000000000000000000000000000000000000000000000039500000000000000000000000000000058000000000000000000000000000000000000000000000000000000000000001204105e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b300000000000000000000000000000000000000000000000000000000000000";
use chain::keysinterface::{KeysInterface, Sign};
use super::utils;
+use ln::msgs::DecodeError;
use util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
-use util::ser::{VecWriter, Writeable, Writer};
+use util::ser::{Readable, VecWriter, Writeable, Writer};
-use core::iter::FromIterator;
use io;
use prelude::*;
writer.0
}
+impl Writeable for BlindedRoute {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ self.introduction_node_id.write(w)?;
+ self.blinding_point.write(w)?;
+ (self.blinded_hops.len() as u8).write(w)?;
+ for hop in &self.blinded_hops {
+ hop.write(w)?;
+ }
+ Ok(())
+ }
+}
+
+impl Readable for BlindedRoute {
+ fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
+ let introduction_node_id = Readable::read(r)?;
+ let blinding_point = Readable::read(r)?;
+ let num_hops: u8 = Readable::read(r)?;
+ if num_hops == 0 { return Err(DecodeError::InvalidValue) }
+ let mut blinded_hops: Vec<BlindedHop> = Vec::with_capacity(num_hops.into());
+ for _ in 0..num_hops {
+ blinded_hops.push(Readable::read(r)?);
+ }
+ Ok(BlindedRoute {
+ introduction_node_id,
+ blinding_point,
+ blinded_hops,
+ })
+ }
+}
+
+impl_writeable!(BlindedHop, {
+ blinded_node_id,
+ encrypted_payload
+});
+
/// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded
/// route, they are encoded into [`BlindedHop::encrypted_payload`].
pub(crate) struct ForwardTlvs {
res
}
-fn pass_along_path(mut path: Vec<MessengerNode>, expected_path_id: Option<[u8; 32]>) {
- let mut prev_node = path.remove(0);
+fn pass_along_path(path: &Vec<MessengerNode>, expected_path_id: Option<[u8; 32]>) {
+ let mut prev_node = &path[0];
let num_nodes = path.len();
- for (idx, node) in path.into_iter().enumerate() {
+ for (idx, node) in path.into_iter().skip(1).enumerate() {
let events = prev_node.messenger.release_pending_msgs();
assert_eq!(events.len(), 1);
let onion_msg = {
fn one_hop() {
let nodes = create_nodes(2);
- nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk())).unwrap();
- pass_along_path(nodes, None);
+ nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk()), None).unwrap();
+ pass_along_path(&nodes, None);
}
#[test]
fn two_unblinded_hops() {
let nodes = create_nodes(3);
- nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk()], Destination::Node(nodes[2].get_node_pk())).unwrap();
- pass_along_path(nodes, None);
+ nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk()], Destination::Node(nodes[2].get_node_pk()), None).unwrap();
+ pass_along_path(&nodes, None);
}
#[test]
let secp_ctx = Secp256k1::new();
let blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[nodes[3].get_node_pk(), nodes[4].get_node_pk()], &*nodes[4].keys_manager, &secp_ctx).unwrap();
- nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::BlindedRoute(blinded_route)).unwrap();
- pass_along_path(nodes, None);
+ nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::BlindedRoute(blinded_route), None).unwrap();
+ pass_along_path(&nodes, None);
}
#[test]
let secp_ctx = Secp256k1::new();
let blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx).unwrap();
- nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap();
- pass_along_path(nodes, None);
+ nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), None).unwrap();
+ pass_along_path(&nodes, None);
}
#[test]
let hop_node_id = PublicKey::from_secret_key(&secp_ctx, &hop_secret);
let hops = [hop_node_id; 400];
- let err = nodes[0].messenger.send_onion_message(&hops, Destination::Node(hop_node_id)).unwrap_err();
+ let err = nodes[0].messenger.send_onion_message(&hops, Destination::Node(hop_node_id), None).unwrap_err();
assert_eq!(err, SendError::TooBigPacket);
}
fn invalid_blinded_route_error() {
// Make sure we error as expected if a provided blinded route has 0 or 1 hops.
let mut nodes = create_nodes(3);
- let (node1, node2, node3) = (nodes.remove(0), nodes.remove(0), nodes.remove(0));
// 0 hops
let secp_ctx = Secp256k1::new();
- let mut blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[node2.get_node_pk(), node3.get_node_pk()], &*node3.keys_manager, &secp_ctx).unwrap();
+ let mut blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
blinded_route.blinded_hops.clear();
- let err = node1.messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap_err();
+ let err = nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), None).unwrap_err();
assert_eq!(err, SendError::TooFewBlindedHops);
// 1 hop
- let mut blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[node2.get_node_pk(), node3.get_node_pk()], &*node3.keys_manager, &secp_ctx).unwrap();
+ let mut blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
blinded_route.blinded_hops.remove(0);
assert_eq!(blinded_route.blinded_hops.len(), 1);
- let err = node1.messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap_err();
+ let err = nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), None).unwrap_err();
assert_eq!(err, SendError::TooFewBlindedHops);
}
+
+#[test]
+fn reply_path() {
+ let mut nodes = create_nodes(4);
+ let secp_ctx = Secp256k1::new();
+
+ // Destination::Node
+ let reply_path = BlindedRoute::new::<EnforcingSigner, _, _>(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();
+ nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::Node(nodes[3].get_node_pk()), Some(reply_path)).unwrap();
+ pass_along_path(&nodes, None);
+ // Make sure the last node successfully decoded the reply path.
+ nodes[3].logger.assert_log_contains(
+ "lightning::onion_message::messenger".to_string(),
+ format!("Received an onion message with path_id: None and reply_path").to_string(), 1);
+
+ // Destination::BlindedRoute
+ let blinded_route = BlindedRoute::new::<EnforcingSigner, _, _>(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx).unwrap();
+ let reply_path = BlindedRoute::new::<EnforcingSigner, _, _>(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();
+
+ nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), Some(reply_path)).unwrap();
+ pass_along_path(&nodes, None);
+ nodes[3].logger.assert_log_contains(
+ "lightning::onion_message::messenger".to_string(),
+ format!("Received an onion message with path_id: None and reply_path").to_string(), 2);
+}
/// Send an empty onion message to `destination`, routing it through `intermediate_nodes`.
/// See [`OnionMessenger`] for example usage.
- pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination) -> Result<(), SendError> {
+ pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination, reply_path: Option<BlindedRoute>) -> Result<(), SendError> {
if let Destination::BlindedRoute(BlindedRoute { ref blinded_hops, .. }) = destination {
if blinded_hops.len() < 2 {
return Err(SendError::TooFewBlindedHops);
}
};
let (packet_payloads, packet_keys) = packet_payloads_and_keys(
- &self.secp_ctx, intermediate_nodes, destination, &blinding_secret)
+ &self.secp_ctx, intermediate_nodes, destination, reply_path, &blinding_secret)
.map_err(|e| SendError::Secp256k1(e))?;
let prng_seed = self.keys_manager.get_secure_random_bytes();
msg.onion_routing_packet.hmac, control_tlvs_ss)
{
Ok((Payload::Receive {
- control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id })
+ control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path,
}, None)) => {
- log_info!(self.logger, "Received an onion message with path_id: {:02x?}", path_id);
+ log_info!(self.logger,
+ "Received an onion message with path_id: {:02x?} and {}reply_path",
+ path_id, if reply_path.is_some() { "" } else { "no " });
},
Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
next_node_id, next_blinding_override
/// Construct onion packet payloads and keys for sending an onion message along the given
/// `unblinded_path` to the given `destination`.
fn packet_payloads_and_keys<T: secp256k1::Signing + secp256k1::Verification>(
- secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Destination, session_priv: &SecretKey
+ secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Destination, mut reply_path:
+ Option<BlindedRoute>, session_priv: &SecretKey
) -> Result<(Vec<(Payload, [u8; 32])>, Vec<onion_utils::OnionKeys>), secp256k1::Error> {
let num_hops = unblinded_path.len() + destination.num_hops();
let mut payloads = Vec::with_capacity(num_hops);
} else if let Some(encrypted_payload) = enc_payload_opt {
payloads.push((Payload::Receive {
control_tlvs: ReceiveControlTlvs::Blinded(encrypted_payload),
+ reply_path: reply_path.take(),
}, control_tlvs_ss));
}
if let Some(control_tlvs_ss) = prev_control_tlvs_ss {
payloads.push((Payload::Receive {
- control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id: None, })
+ control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id: None, }),
+ reply_path: reply_path.take(),
}, control_tlvs_ss));
}
use ln::msgs::DecodeError;
use ln::onion_utils;
-use super::blinded_route::{ForwardTlvs, ReceiveTlvs};
+use super::blinded_route::{BlindedRoute, ForwardTlvs, ReceiveTlvs};
use util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
-use util::ser::{FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer};
+use util::ser::{BigSize, FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer};
use core::cmp;
use io::{self, Read};
/// This payload is for the final hop.
Receive {
control_tlvs: ReceiveControlTlvs,
+ reply_path: Option<BlindedRoute>,
// Coming soon:
- // reply_path: Option<BlindedRoute>,
// message: Message,
}
}
impl Writeable for (Payload, [u8; 32]) {
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
match &self.0 {
- Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) |
- Payload::Receive { control_tlvs: ReceiveControlTlvs::Blinded(encrypted_bytes)} => {
+ Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) => {
encode_varint_length_prefixed_tlv!(w, {
(4, encrypted_bytes, vec_type)
})
},
+ Payload::Receive {
+ control_tlvs: ReceiveControlTlvs::Blinded(encrypted_bytes), reply_path
+ } => {
+ encode_varint_length_prefixed_tlv!(w, {
+ (2, reply_path, option),
+ (4, encrypted_bytes, vec_type)
+ })
+ },
Payload::Forward(ForwardControlTlvs::Unblinded(control_tlvs)) => {
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
encode_varint_length_prefixed_tlv!(w, {
(4, write_adapter, required)
})
},
- Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs)} => {
+ Payload::Receive {
+ control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs), reply_path,
+ } => {
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
encode_varint_length_prefixed_tlv!(w, {
+ (2, reply_path, option),
(4, write_adapter, required)
})
},
// Uses the provided secret to simultaneously decode and decrypt the control TLVs.
impl ReadableArgs<SharedSecret> for Payload {
fn read<R: Read>(mut r: &mut R, encrypted_tlvs_ss: SharedSecret) -> Result<Self, DecodeError> {
- use bitcoin::consensus::encode::{Decodable, Error, VarInt};
- let v: VarInt = Decodable::consensus_decode(&mut r)
- .map_err(|e| match e {
- Error::Io(ioe) => DecodeError::from(ioe),
- _ => DecodeError::InvalidValue
- })?;
-
+ let v: BigSize = Readable::read(r)?;
let mut rd = FixedLengthReader::new(r, v.0);
- // TODO: support reply paths
- let mut _reply_path_bytes: Option<Vec<u8>> = Some(Vec::new());
+ let mut reply_path: Option<BlindedRoute> = None;
let mut read_adapter: Option<ChaChaPolyReadAdapter<ControlTlvs>> = None;
let rho = onion_utils::gen_rho_from_shared_secret(&encrypted_tlvs_ss.secret_bytes());
decode_tlv_stream!(&mut rd, {
- (2, _reply_path_bytes, vec_type),
+ (2, reply_path, option),
(4, read_adapter, (option: LengthReadableArgs, rho))
});
rd.eat_remaining().map_err(|_| DecodeError::ShortRead)?;
Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs)))
},
Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Receive(tlvs)}) => {
- Ok(Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(tlvs)})
+ Ok(Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(tlvs), reply_path })
},
}
}
return Err(LightningError{err: "Channel announcement node had a channel with itself".to_owned(), action: ErrorAction::IgnoreError});
}
+ {
+ let channels = self.channels.read().unwrap();
+
+ if let Some(chan) = channels.get(&msg.short_channel_id) {
+ if chan.capacity_sats.is_some() {
+ // If we'd previously looked up the channel on-chain and checked the script
+ // against what appears on-chain, ignore the duplicate announcement.
+ //
+ // Because a reorg could replace one channel with another at the same SCID, if
+ // the channel appears to be different, we re-validate. This doesn't expose us
+ // to any more DoS risk than not, as a peer can always flood us with
+ // randomly-generated SCID values anyway.
+ //
+ // We use the Node IDs rather than the bitcoin_keys to check for "equivalence"
+ // as we didn't (necessarily) store the bitcoin keys, and we only really care
+ // if the peers on the channel changed anyway.
+ if NodeId::from_pubkey(&msg.node_id_1) == chan.node_one && NodeId::from_pubkey(&msg.node_id_2) == chan.node_two {
+ return Err(LightningError {
+ err: "Already have chain-validated channel".to_owned(),
+ action: ErrorAction::IgnoreDuplicateGossip
+ });
+ }
+ } else if chain_access.is_none() {
+ // Similarly, if we can't check the chain right now anyway, ignore the
+ // duplicate announcement without bothering to take the channels write lock.
+ return Err(LightningError {
+ err: "Already have non-chain-validated channel".to_owned(),
+ action: ErrorAction::IgnoreDuplicateGossip
+ });
+ }
+ }
+ }
+
let utxo_value = match &chain_access {
&None => {
// Tentatively accept, potentially exposing us to DoS attacks
// drop new one on the floor, since we can't see any changes.
match gossip_sync.handle_channel_announcement(&valid_announcement) {
Ok(_) => panic!(),
- Err(e) => assert_eq!(e.err, "Already have knowledge of channel")
+ Err(e) => assert_eq!(e.err, "Already have non-chain-validated channel")
};
// Test if an associated transaction were not on-chain (or not confirmed).
};
}
- // If we receive announcement for the same channel (but TX is not confirmed),
- // drop new one on the floor, since we can't see any changes.
- *chain_source.utxo_ret.lock().unwrap() = Err(chain::AccessError::UnknownTx);
- match gossip_sync.handle_channel_announcement(&valid_announcement) {
- Ok(_) => panic!(),
- Err(e) => assert_eq!(e.err, "Channel announced without corresponding UTXO entry")
- };
-
- // But if it is confirmed, replace the channel
+ // If we receive announcement for the same channel, once we've validated it against the
+ // chain, we simply ignore all new (duplicate) announcements.
*chain_source.utxo_ret.lock().unwrap() = Ok(TxOut { value: 0, script_pubkey: good_script });
- let valid_announcement = get_signed_channel_announcement(|unsigned_announcement| {
- unsigned_announcement.features = ChannelFeatures::empty();
- unsigned_announcement.short_channel_id += 2;
- }, node_1_privkey, node_2_privkey, &secp_ctx);
match gossip_sync.handle_channel_announcement(&valid_announcement) {
- Ok(res) => assert!(res),
- _ => panic!()
+ Ok(_) => panic!(),
+ Err(e) => assert_eq!(e.err, "Already have chain-validated channel")
};
- {
- match network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id) {
- Some(channel_entry) => {
- assert_eq!(channel_entry.features, ChannelFeatures::empty());
- },
- _ => panic!()
- };
- }
// Don't relay valid channels with excess data
let valid_announcement = get_signed_channel_announcement(|unsigned_announcement| {