Skip to content

Commit 227c1d2

Browse files
committed
Handle partial-response UTXO impls or reorgs in chan_announcements
Mostly to add a big comment noting why we aren't "spec-compliant"
1 parent 3b4c1a3 commit 227c1d2

File tree

1 file changed

+59
-27
lines changed

1 file changed

+59
-27
lines changed

src/ln/router.rs

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,21 @@ struct NetworkMap {
102102
our_node_id: PublicKey,
103103
nodes: HashMap<PublicKey, NodeInfo>,
104104
}
105-
105+
struct MutNetworkMap<'a> {
106+
#[cfg(feature = "non_bitcoin_chain_hash_routing")]
107+
channels: &'a mut HashMap<(u64, Sha256dHash), ChannelInfo>,
108+
#[cfg(not(feature = "non_bitcoin_chain_hash_routing"))]
109+
channels: &'a mut HashMap<u64, ChannelInfo>,
110+
nodes: &'a mut HashMap<PublicKey, NodeInfo>,
111+
}
112+
impl NetworkMap {
113+
fn borrow_parts(&mut self) -> MutNetworkMap {
114+
MutNetworkMap {
115+
channels: &mut self.channels,
116+
nodes: &mut self.nodes,
117+
}
118+
}
119+
}
106120
impl std::fmt::Display for NetworkMap {
107121
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
108122
write!(f, "Node id {} network map\n[Channels]\n", log_pubkey!(self.our_node_id))?;
@@ -213,7 +227,7 @@ impl RoutingMessageHandler for Router {
213227
panic!("Unknown-required-features ChannelAnnouncements should never deserialize!");
214228
}
215229

216-
match self.chain_monitor.get_chain_utxo(msg.contents.chain_hash, msg.contents.short_channel_id) {
230+
let checked_utxo = match self.chain_monitor.get_chain_utxo(msg.contents.chain_hash, msg.contents.short_channel_id) {
217231
Ok((script_pubkey, _value)) => {
218232
let expected_script = Builder::new().push_opcode(opcodes::All::OP_PUSHNUM_2)
219233
.push_slice(&msg.contents.bitcoin_key_1.serialize())
@@ -224,49 +238,67 @@ impl RoutingMessageHandler for Router {
224238
}
225239
//TODO: Check if value is worth storing, use it to inform routing, and compare it
226240
//to the new HTLC max field in channel_update
241+
true
227242
},
228243
Err(ChainError::NotSupported) => {
229244
// Tentatively accept, potentially exposing us to DoS attacks
245+
false
230246
},
231247
Err(ChainError::NotWatched) => {
232248
return Err(HandleError{err: "Channel announced on an unknown chain", action: Some(ErrorAction::IgnoreError)});
233249
},
234250
Err(ChainError::UnknownTx) => {
235251
return Err(HandleError{err: "Channel announced without corresponding UTXO entry", action: Some(ErrorAction::IgnoreError)});
236252
},
237-
}
253+
};
238254

239-
let mut network = self.network_map.write().unwrap();
255+
let mut network_lock = self.network_map.write().unwrap();
256+
let network = network_lock.borrow_parts();
257+
258+
let chan_info = ChannelInfo {
259+
features: msg.contents.features.clone(),
260+
one_to_two: DirectionalChannelInfo {
261+
src_node_id: msg.contents.node_id_1.clone(),
262+
last_update: 0,
263+
enabled: false,
264+
cltv_expiry_delta: u16::max_value(),
265+
htlc_minimum_msat: u64::max_value(),
266+
fee_base_msat: u32::max_value(),
267+
fee_proportional_millionths: u32::max_value(),
268+
},
269+
two_to_one: DirectionalChannelInfo {
270+
src_node_id: msg.contents.node_id_2.clone(),
271+
last_update: 0,
272+
enabled: false,
273+
cltv_expiry_delta: u16::max_value(),
274+
htlc_minimum_msat: u64::max_value(),
275+
fee_base_msat: u32::max_value(),
276+
fee_proportional_millionths: u32::max_value(),
277+
}
278+
};
240279

241280
match network.channels.entry(NetworkMap::get_key(msg.contents.short_channel_id, msg.contents.chain_hash)) {
242-
Entry::Occupied(_) => {
281+
Entry::Occupied(mut entry) => {
243282
//TODO: because asking the blockchain if short_channel_id is valid is only optional
244283
//in the blockchain API, we need to handle it smartly here, though its unclear
245284
//exactly how...
246-
return Err(HandleError{err: "Already have knowledge of channel", action: Some(ErrorAction::IgnoreError)})
285+
if checked_utxo {
286+
// Either our UTXO provider is busted, there was a reorg, or the UTXO provider
287+
// only sometimes returns results. In any case remove the previous entry. Note
288+
// that the spec expects us to "blacklist" the node_ids involved, but we can't
289+
// do that because
290+
// a) we don't *require* a UTXO provider that always returns results.
291+
// b) we don't track UTXOs of channels we know about and remove them if they
292+
// get reorg'd out.
293+
// c) it's unclear how to do so without exposing ourselves to massive DoS risk.
294+
Self::remove_channel_in_nodes(network.nodes, &entry.get(), msg.contents.short_channel_id);
295+
*entry.get_mut() = chan_info;
296+
} else {
297+
return Err(HandleError{err: "Already have knowledge of channel", action: Some(ErrorAction::IgnoreError)})
298+
}
247299
},
248300
Entry::Vacant(entry) => {
249-
entry.insert(ChannelInfo {
250-
features: msg.contents.features.clone(),
251-
one_to_two: DirectionalChannelInfo {
252-
src_node_id: msg.contents.node_id_1.clone(),
253-
last_update: 0,
254-
enabled: false,
255-
cltv_expiry_delta: u16::max_value(),
256-
htlc_minimum_msat: u64::max_value(),
257-
fee_base_msat: u32::max_value(),
258-
fee_proportional_millionths: u32::max_value(),
259-
},
260-
two_to_one: DirectionalChannelInfo {
261-
src_node_id: msg.contents.node_id_2.clone(),
262-
last_update: 0,
263-
enabled: false,
264-
cltv_expiry_delta: u16::max_value(),
265-
htlc_minimum_msat: u64::max_value(),
266-
fee_base_msat: u32::max_value(),
267-
fee_proportional_millionths: u32::max_value(),
268-
}
269-
});
301+
entry.insert(chan_info);
270302
}
271303
};
272304

0 commit comments

Comments
 (0)