referrerpolicy=no-referrer-when-downgrade

sc_mixnet/
sync_with_runtime.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! [`sync_with_runtime`] synchronises the session status and mixnode sets from the blockchain
20//! runtime to the core mixnet state. It is called every time a block is finalised.
21
22use super::peer_id::from_core_peer_id;
23use log::{debug, info};
24use mixnet::core::{
25	Mixnet, Mixnode as CoreMixnode, MixnodesErr as CoreMixnodesErr, RelSessionIndex,
26	SessionPhase as CoreSessionPhase, SessionStatus as CoreSessionStatus,
27};
28use sc_network_types::{
29	multiaddr::{multiaddr, Multiaddr, Protocol},
30	PeerId,
31};
32use sp_api::{ApiError, ApiRef};
33use sp_mixnet::{
34	runtime_api::MixnetApi,
35	types::{
36		Mixnode as RuntimeMixnode, MixnodesErr as RuntimeMixnodesErr,
37		SessionPhase as RuntimeSessionPhase, SessionStatus as RuntimeSessionStatus,
38	},
39};
40use sp_runtime::traits::Block;
41
42const LOG_TARGET: &str = "mixnet";
43
44/// Convert a [`RuntimeSessionStatus`] to a [`CoreSessionStatus`].
45///
46/// The [`RuntimeSessionStatus`] and [`CoreSessionStatus`] types are effectively the same.
47/// [`RuntimeSessionStatus`] is used in the runtime to avoid depending on the [`mixnet`] crate
48/// there.
49fn to_core_session_status(status: RuntimeSessionStatus) -> CoreSessionStatus {
50	CoreSessionStatus {
51		current_index: status.current_index,
52		phase: match status.phase {
53			RuntimeSessionPhase::CoverToCurrent => CoreSessionPhase::CoverToCurrent,
54			RuntimeSessionPhase::RequestsToCurrent => CoreSessionPhase::RequestsToCurrent,
55			RuntimeSessionPhase::CoverToPrev => CoreSessionPhase::CoverToPrev,
56			RuntimeSessionPhase::DisconnectFromPrev => CoreSessionPhase::DisconnectFromPrev,
57		},
58	}
59}
60
61fn parse_external_addresses(external_addresses: Vec<Vec<u8>>) -> Vec<Multiaddr> {
62	external_addresses
63		.into_iter()
64		.flat_map(|addr| {
65			let addr = match String::from_utf8(addr) {
66				Ok(addr) => addr,
67				Err(addr) => {
68					debug!(
69						target: LOG_TARGET,
70						"Mixnode external address {:x?} is not valid UTF-8",
71						addr.into_bytes(),
72					);
73					return None
74				},
75			};
76			match addr.parse() {
77				Ok(addr) => Some(addr),
78				Err(err) => {
79					debug!(
80						target: LOG_TARGET,
81						"Could not parse mixnode address {addr}: {err}",
82					);
83					None
84				},
85			}
86		})
87		.collect()
88}
89
90/// Modify `external_addresses` such that there is at least one address and the final component of
91/// each address matches `peer_id`.
92fn fixup_external_addresses(external_addresses: &mut Vec<Multiaddr>, peer_id: &PeerId) {
93	// Ensure the final component of each address matches peer_id
94	external_addresses.retain_mut(|addr| match PeerId::try_from_multiaddr(addr) {
95		Some(addr_peer_id) if addr_peer_id == *peer_id => true,
96		Some(_) => {
97			debug!(
98				target: LOG_TARGET,
99				"Mixnode address {} does not match mixnode peer ID {}, ignoring",
100				addr,
101				peer_id
102			);
103			false
104		},
105		None if matches!(addr.iter().last(), Some(Protocol::P2p(_))) => {
106			debug!(
107				target: LOG_TARGET,
108				"Mixnode address {} has unrecognised P2P protocol, ignoring",
109				addr
110			);
111			false
112		},
113		None => {
114			addr.push(Protocol::P2p(*peer_id.as_ref()));
115			true
116		},
117	});
118
119	// If there are no addresses, insert one consisting of just the peer ID
120	if external_addresses.is_empty() {
121		external_addresses.push(multiaddr!(P2p(*peer_id.as_ref())));
122	}
123}
124
125/// Convert a [`RuntimeMixnode`] to a [`CoreMixnode`]. If the conversion fails, an error message is
126/// logged, but a [`CoreMixnode`] is still returned.
127///
128/// It would be possible to handle conversion failure in a better way, but this would complicate
129/// things for what should be a rare case. Note that even if the conversion here succeeds, there is
130/// no guarantee that we will be able to connect to the mixnode or send packets to it. The most
131/// common failure case is expected to be that a mixnode is simply unreachable over the network.
132fn into_core_mixnode(mixnode: RuntimeMixnode) -> CoreMixnode<Vec<Multiaddr>> {
133	let external_addresses = if let Some(peer_id) = from_core_peer_id(&mixnode.peer_id) {
134		let mut external_addresses = parse_external_addresses(mixnode.external_addresses);
135		fixup_external_addresses(&mut external_addresses, &peer_id);
136		external_addresses
137	} else {
138		debug!(
139			target: LOG_TARGET,
140			"Failed to convert mixnet peer ID {:x?} to libp2p peer ID",
141			mixnode.peer_id,
142		);
143		Vec::new()
144	};
145
146	CoreMixnode {
147		kx_public: mixnode.kx_public,
148		peer_id: mixnode.peer_id,
149		extra: external_addresses,
150	}
151}
152
153fn maybe_set_mixnodes(
154	mixnet: &mut Mixnet<Vec<Multiaddr>>,
155	rel_session_index: RelSessionIndex,
156	mixnodes: &dyn Fn() -> Result<Result<Vec<RuntimeMixnode>, RuntimeMixnodesErr>, ApiError>,
157) {
158	let current_session_index = mixnet.session_status().current_index;
159	mixnet.maybe_set_mixnodes(rel_session_index, &mut || {
160		// Note that RelSessionIndex::Prev + 0 would panic, but this closure will not get called in
161		// that case so we are fine. Do not move this out of the closure!
162		let session_index = rel_session_index + current_session_index;
163		match mixnodes() {
164			Ok(Ok(mixnodes)) => Ok(mixnodes.into_iter().map(into_core_mixnode).collect()),
165			Ok(Err(err)) => {
166				info!(target: LOG_TARGET, "Session {session_index}: Mixnet disabled: {err}");
167				Err(CoreMixnodesErr::Permanent) // Disable the session slot
168			},
169			Err(err) => {
170				debug!(
171					target: LOG_TARGET,
172					"Session {session_index}: Error getting mixnodes from runtime: {err}"
173				);
174				Err(CoreMixnodesErr::Transient) // Just leave the session slot empty; try again next block
175			},
176		}
177	});
178}
179
180pub fn sync_with_runtime<B, A>(mixnet: &mut Mixnet<Vec<Multiaddr>>, api: ApiRef<A>, hash: B::Hash)
181where
182	B: Block,
183	A: MixnetApi<B>,
184{
185	let session_status = match api.session_status(hash) {
186		Ok(session_status) => session_status,
187		Err(err) => {
188			debug!(target: LOG_TARGET, "Error getting session status from runtime: {err}");
189			return
190		},
191	};
192	mixnet.set_session_status(to_core_session_status(session_status));
193
194	maybe_set_mixnodes(mixnet, RelSessionIndex::Prev, &|| api.prev_mixnodes(hash));
195	maybe_set_mixnodes(mixnet, RelSessionIndex::Current, &|| api.current_mixnodes(hash));
196}
197
198#[cfg(test)]
199mod tests {
200	use super::*;
201
202	#[test]
203	fn fixup_empty_external_addresses() {
204		let peer_id = PeerId::random();
205		let mut external_addresses = Vec::new();
206		fixup_external_addresses(&mut external_addresses, &peer_id);
207		assert_eq!(external_addresses, vec![multiaddr!(P2p(peer_id))]);
208	}
209
210	#[test]
211	fn fixup_misc_external_addresses() {
212		let peer_id = PeerId::random();
213		let other_peer_id = PeerId::random();
214		let mut external_addresses = vec![
215			multiaddr!(Tcp(0u16), P2p(peer_id)),
216			multiaddr!(Tcp(1u16), P2p(other_peer_id)),
217			multiaddr!(Tcp(2u16)),
218			Multiaddr::empty(),
219		];
220		fixup_external_addresses(&mut external_addresses, &peer_id);
221		assert_eq!(
222			external_addresses,
223			vec![
224				multiaddr!(Tcp(0u16), P2p(peer_id)),
225				multiaddr!(Tcp(2u16), P2p(peer_id)),
226				multiaddr!(P2p(peer_id)),
227			]
228		);
229	}
230}