Skip to content

Commit f2956e8

Browse files
perf: Replace RoutingTable::route by NetworkTopology::route (#6361)
This commit removes `RoutingTable::route`, which was used to route messages to the correct subnet. The difference to the still existing `RoutingTable::lookup_entry` was that `route` would additionally consider the case where the target is a subnet id instead of a canister id. However, previously this was implemented by scanning over all canister ranges and checking if the input occurs as the subnet id for any range. There are two issues with this, which this commit both solves: 1. As it's a linear scan, route was `O(n)`, where n is the number of ranges in the routing table. Currently `n` is small, but will grow with the upcoming canister migration feature. 2. There is a theoretical edge case of subnets with no ranges assigned to them. It was not possible to route to such a subnet. This commit solves both problems by instead implementing `route` in the `NetworkTopology` struct. `NetworkTopology` contains both a `RoutingTable` and a `BTreeMap` of subnets. Instead of a linear scan, we now only need to do a quick lookup in the `BTreeMap`. Furthermore, subnets without any ranges will also be found this way. This commit is somewhat large due to the necessary test changes. In production code, the switch is very simple.
1 parent 880a214 commit f2956e8

File tree

13 files changed

+169
-163
lines changed

13 files changed

+169
-163
lines changed

rs/embedders/src/wasmtime_embedder/system_api/routing.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ fn route_canister_id(
4545
network_topology: &NetworkTopology,
4646
) -> Result<PrincipalId, ResolveDestinationError> {
4747
network_topology
48-
.routing_table
4948
.route(canister_id.get())
5049
.map(|subnet_id| subnet_id.get())
5150
.ok_or(ResolveDestinationError::SubnetNotFound(canister_id, method))

rs/execution_environment/src/canister_manager.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,12 +1284,7 @@ impl CanisterManager {
12841284
return Err(CanisterManagerError::CanisterAlreadyExists(new_canister_id));
12851285
}
12861286

1287-
if state
1288-
.metadata
1289-
.network_topology
1290-
.routing_table
1291-
.route(specified_id)
1292-
== Some(state.metadata.own_subnet_id)
1287+
if state.metadata.network_topology.route(specified_id) == Some(state.metadata.own_subnet_id)
12931288
{
12941289
Ok(new_canister_id)
12951290
} else {

rs/execution_environment/src/execution_environment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3290,7 +3290,7 @@ impl ExecutionEnvironment {
32903290

32913291
let topology = &state.metadata.network_topology;
32923292
// If the request isn't from the NNS, then we need to charge for it.
3293-
let source_subnet = topology.routing_table.route(request.sender.get());
3293+
let source_subnet = topology.route(request.sender.get());
32943294
if source_subnet != Some(state.metadata.network_topology.nns_subnet_id) {
32953295
let cost_schedule = state.get_own_cost_schedule();
32963296
let signature_fee = self.calculate_signature_fee(&args, subnet_size, cost_schedule);

rs/messaging/src/routing/stream_builder.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,8 @@ impl StreamBuilderImpl {
305305
};
306306

307307
let mut streams = state.take_streams();
308-
let routing_table = state.routing_table();
309-
let subnet_types: BTreeMap<_, _> = state
310-
.metadata
311-
.network_topology
308+
let network_topology = state.metadata.network_topology.clone();
309+
let subnet_types: BTreeMap<_, _> = network_topology
312310
.subnets
313311
.iter()
314312
.map(|(subnet_id, topology)| (*subnet_id, topology.subnet_type))
@@ -342,7 +340,7 @@ impl StreamBuilderImpl {
342340
}
343341
last_output_size = output_size;
344342

345-
match routing_table.route(msg.receiver().get()) {
343+
match network_topology.route(msg.receiver().get()) {
346344
// Destination subnet found.
347345
Some(dst_subnet_id) => {
348346
let dst_stream_entry = streams.entry(dst_subnet_id);

rs/messaging/src/routing/stream_builder/tests.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,11 @@ fn build_streams_with_messages_targeted_to_other_subnets() {
606606
CanisterIdRange{ start: CanisterId::from(0), end: CanisterId::from(0xfff) } => REMOTE_SUBNET,
607607
},
608608
).unwrap());
609+
provided_state
610+
.metadata
611+
.network_topology
612+
.subnets
613+
.insert(REMOTE_SUBNET, Default::default());
609614

610615
// Set up the provided_canister_states.
611616
let provided_canister_states = canister_states_with_outputs(msgs.clone());

rs/messaging/src/routing/stream_handler.rs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,6 @@ impl StreamHandlerImpl {
570570
let new_destination = state
571571
.metadata
572572
.network_topology
573-
.routing_table
574573
.route(response.receiver().get())
575574
.expect("Canister disappeared from registry. Registry in an inconsistent state.");
576575
info!(
@@ -827,11 +826,7 @@ impl StreamHandlerImpl {
827826
available_guaranteed_response_memory: &mut i64,
828827
) -> InductionResult {
829828
// Subnet that should have received the message according to the routing table.
830-
let receiver_host_subnet = state
831-
.metadata
832-
.network_topology
833-
.routing_table
834-
.route(msg.receiver().get());
829+
let receiver_host_subnet = state.metadata.network_topology.route(msg.receiver().get());
835830

836831
let payload_size = msg.payload_size_bytes().get();
837832
let msg_cycles = msg.cycles();
@@ -966,11 +961,7 @@ impl StreamHandlerImpl {
966961
state: &ReplicatedState,
967962
) -> bool {
968963
// Remote subnet that should have sent the message according to the routing table.
969-
let expected_subnet_id = state
970-
.metadata
971-
.network_topology
972-
.routing_table
973-
.route(msg.sender().get());
964+
let expected_subnet_id = state.metadata.network_topology.route(msg.sender().get());
974965

975966
match expected_subnet_id {
976967
// The actual originating subnet and the routing table entry for the sender are in agreement.
@@ -1030,11 +1021,7 @@ impl StreamHandlerImpl {
10301021
) -> bool {
10311022
debug_assert_eq!(
10321023
Some(actual_receiver_subnet_id),
1033-
state
1034-
.metadata
1035-
.network_topology
1036-
.routing_table
1037-
.route(msg.receiver().get())
1024+
state.metadata.network_topology.route(msg.receiver().get())
10381025
);
10391026

10401027
// Reroute if `msg.receiver()` is being migrated from `self.subnet_id` to

rs/messaging/src/routing/stream_handler/tests.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4085,20 +4085,38 @@ fn with_test_setup_and_config(
40854085

40864086
// Ensure the routing table maps `LOCAL_CANISTER` to `LOCAL_SUBNET`,
40874087
// `REMOTE_CANISTER` to `REMOTE_SUBNET` and `UNKNOWN_CANISTER` to `None`.
4088-
let routing_table = Arc::new(RoutingTable::try_from(btreemap! {
4089-
CanisterIdRange{ start: CanisterId::from(0x0), end: CanisterId::from(0xff) } => LOCAL_SUBNET,
4090-
CanisterIdRange{ start: CanisterId::from(0x100), end: CanisterId::from(0x1ff) } => REMOTE_SUBNET,
4091-
}).unwrap());
4088+
let local_range = CanisterIdRange {
4089+
start: CanisterId::from(0x0),
4090+
end: CanisterId::from(0xff),
4091+
};
4092+
let remote_range = CanisterIdRange {
4093+
start: CanisterId::from(0x100),
4094+
end: CanisterId::from(0x1ff),
4095+
};
4096+
let routing_table = Arc::new(
4097+
RoutingTable::try_from(btreemap! {
4098+
local_range => LOCAL_SUBNET,
4099+
remote_range => REMOTE_SUBNET,
4100+
})
4101+
.unwrap(),
4102+
);
40924103
assert_eq!(
4093-
Some(LOCAL_SUBNET),
4094-
routing_table.route(LOCAL_CANISTER.get())
4104+
Some((local_range, LOCAL_SUBNET)),
4105+
routing_table.lookup_entry(*LOCAL_CANISTER)
40954106
);
40964107
assert_eq!(
4097-
Some(REMOTE_SUBNET),
4098-
routing_table.route(REMOTE_CANISTER.get())
4108+
Some((remote_range, REMOTE_SUBNET)),
4109+
routing_table.lookup_entry(*REMOTE_CANISTER)
40994110
);
4100-
assert!(routing_table.route(UNKNOWN_CANISTER.get()).is_none());
4111+
assert!(routing_table.lookup_entry(*UNKNOWN_CANISTER).is_none());
41014112
state.metadata.network_topology.routing_table = routing_table;
4113+
for subnet in [LOCAL_SUBNET, REMOTE_SUBNET] {
4114+
state
4115+
.metadata
4116+
.network_topology
4117+
.subnets
4118+
.insert(subnet, Default::default());
4119+
}
41024120

41034121
// Generate testing canister using `LOCAL_CANISTER` as the canister ID.
41044122
let mut canister_state = CanisterStateBuilder::new()

rs/pocket_ic_server/src/pocket_ic.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,8 +676,11 @@ impl PocketIcSubnets {
676676
}
677677

678678
fn route(&self, canister_id: CanisterId) -> Option<Arc<StateMachine>> {
679-
let subnet_id = self.routing_table.route(canister_id.get());
680-
subnet_id.map(|subnet_id| self.get(subnet_id).unwrap())
679+
self.get(SubnetId::from(canister_id.get())).or_else(|| {
680+
self.routing_table
681+
.lookup_entry(canister_id)
682+
.and_then(|(_, subnet_id)| self.get(subnet_id))
683+
})
681684
}
682685

683686
fn time(&self) -> SystemTime {

rs/registry/routing_table/src/lib.rs

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod proto;
22

33
use candid::CandidType;
4-
use ic_base_types::{CanisterId, CanisterIdError, PrincipalId, SubnetId};
4+
use ic_base_types::{CanisterId, CanisterIdError, SubnetId};
55
use ic_protobuf::proxy::ProxyDecodeError;
66
use serde::{Deserialize, Serialize};
77
use std::convert::TryInto;
@@ -527,30 +527,6 @@ impl RoutingTable {
527527
Ok(())
528528
}
529529

530-
/// Returns the `SubnetId` that the given `principal_id` is assigned to or
531-
/// `None` if an assignment cannot be found.
532-
pub fn route(&self, principal_id: PrincipalId) -> Option<SubnetId> {
533-
// Check if the given `principal_id` is a subnet.
534-
// Note that the following assumes that all known subnets are in the routing
535-
// table, even if they're empty (i.e. no canister exists on them). In the
536-
// future, if this assumption does not hold, the list of existing
537-
// subnets should be taken from the rest of the registry (which should
538-
// be the absolute source of truth).
539-
if let Some(subnet_id) = self.0.values().find(|x| x.get() == principal_id) {
540-
return Some(*subnet_id);
541-
}
542-
543-
// If the `principal_id` was not a subnet, it must be a `CanisterId` (otherwise
544-
// we can't route to it).
545-
match CanisterId::try_from(principal_id) {
546-
Ok(canister_id) => {
547-
lookup_in_ranges(&self.0, canister_id).map(|(_range, subnet_id)| subnet_id)
548-
}
549-
// Cannot route to any subnet as we couldn't convert to a `CanisterId`.
550-
Err(_) => None,
551-
}
552-
}
553-
554530
/// Returns the corresponding `CanisterIdRange` and `SubnetId` that the given `canister_id` is assigned to
555531
/// or `None` if an assignment cannot be found.
556532
pub fn lookup_entry(&self, canister_id: CanisterId) -> Option<(CanisterIdRange, SubnetId)> {

0 commit comments

Comments
 (0)