Skip to content

Commit 91f9402

Browse files
feat: freeze registry version until summary
1 parent fb258f5 commit 91f9402

9 files changed

Lines changed: 486 additions & 49 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rs/consensus/src/consensus/block_maker.rs

Lines changed: 211 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use ic_consensus_utils::{
1313
get_subnet_record,
1414
membership::Membership,
1515
pool_reader::{PoolReader, UnexpectedChainLength},
16+
subnet_splitting,
1617
};
1718
use ic_interfaces::{
1819
consensus::PayloadBuilder, dkg::DkgPool, idkg::IDkgPool, time_source::TimeSource,
@@ -217,7 +218,16 @@ impl BlockMaker {
217218

218219
// The stable registry version to be agreed on in this block. If this is a summary
219220
// block, this version will be the new membership version of the next dkg interval.
220-
let stable_registry_version = self.get_stable_registry_version(parent.as_ref())?;
221+
let stable_registry_version = self.get_stable_registry_version(
222+
parent.as_ref(),
223+
last_summary_block.context.registry_version,
224+
last_summary_block
225+
.payload
226+
.as_ref()
227+
.as_summary()
228+
.dkg
229+
.get_next_start_height(),
230+
)?;
221231
// Get the subnet records that are relevant to making a block
222232
let subnet_records =
223233
subnet_records_for_registry_version(self, registry_version, stable_registry_version)?;
@@ -521,20 +531,59 @@ impl BlockMaker {
521531
}
522532
}
523533

524-
// Returns the registry version received from the NNS some specified amount of
525-
// time ago. If the parent's context references higher version which is already
526-
// available locally, we use that version.
527-
pub(crate) fn get_stable_registry_version(&self, parent: &Block) -> Option<RegistryVersion> {
534+
/// Returns the registry version received from the NNS some specified amount of
535+
/// time ago. If the parent's context references higher version which is already
536+
/// available locally, we use that version.
537+
pub(crate) fn get_stable_registry_version(
538+
&self,
539+
parent: &Block,
540+
last_summary_block_registry_version: RegistryVersion,
541+
next_summary_block_height: Height,
542+
) -> Option<RegistryVersion> {
528543
let parents_version = parent.context.registry_version;
544+
let parents_height = parent.height();
529545
let latest_version = self.registry_client.get_latest_version();
530546
// Check if there is a stable version that we can bump up to.
531547
for v in (parents_version.get()..=latest_version.get()).rev() {
532548
let version = RegistryVersion::from(v);
549+
550+
// Don't consider a registry version if it's too fresh.
533551
let version_timestamp = self.registry_client.get_version_timestamp(version)?;
534-
if version_timestamp + self.stable_registry_version_age <= current_time() {
535-
return Some(version);
552+
if version_timestamp + self.stable_registry_version_age > current_time() {
553+
continue;
554+
}
555+
556+
let subnet_splitting_status = subnet_splitting::get_status(
557+
self.registry_client.as_ref(),
558+
self.replica_config.subnet_id,
559+
last_summary_block_registry_version,
560+
version,
561+
)
562+
.inspect_err(|err| {
563+
warn!(
564+
self.log,
565+
"Failed to get subnet splitting status at registry version {version}: {err}"
566+
)
567+
})
568+
.ok()?;
569+
570+
match subnet_splitting_status {
571+
subnet_splitting::Status::NotScheduled => return Some(version),
572+
subnet_splitting::Status::Scheduled { scheduled_at, .. } => {
573+
info!(
574+
every_n_seconds => 30,
575+
self.log,
576+
"Subnet splitting scheduled at registry version {scheduled_at} \
577+
and height {next_summary_block_height}. Freezing registry version."
578+
);
579+
580+
if parents_height.increment() == next_summary_block_height {
581+
return Some(scheduled_at);
582+
}
583+
}
536584
}
537585
}
586+
538587
// If parent's version is locally available, return that.
539588
if parents_version <= latest_version {
540589
return Some(parents_version);
@@ -669,6 +718,10 @@ mod tests {
669718
use ic_interfaces::consensus_pool::ConsensusPool;
670719
use ic_logger::replica_logger::no_op_logger;
671720
use ic_metrics::MetricsRegistry;
721+
use ic_protobuf::registry::subnet::v1::{
722+
CatchUpPackageContents, SubnetSplittingArgs, catch_up_package_contents::CupType,
723+
};
724+
use ic_registry_keys::make_catch_up_package_contents_key;
672725
use ic_test_utilities_consensus::fake::FromParent;
673726
use ic_test_utilities_registry::{SubnetRecordBuilder, add_subnet_record};
674727
use ic_test_utilities_types::ids::{node_test_id, subnet_test_id};
@@ -680,6 +733,8 @@ mod tests {
680733
signature::ThresholdSignature,
681734
*,
682735
};
736+
use ic_types_test_utils::ids::NODE_1;
737+
use ic_types_test_utils::ids::{SUBNET_0, SUBNET_1};
683738
use rstest::rstest;
684739
use std::sync::Arc;
685740

@@ -1230,25 +1285,33 @@ mod tests {
12301285
block_maker.stable_registry_version_age =
12311286
current_time().saturating_duration_since(v3_timestamp);
12321287
assert_eq!(
1233-
block_maker.get_stable_registry_version(&parent).unwrap(),
1288+
block_maker
1289+
.get_stable_registry_version(&parent, RegistryVersion::new(1), Height::new(100))
1290+
.unwrap(),
12341291
RegistryVersion::from(3)
12351292
);
12361293
block_maker.stable_registry_version_age =
12371294
current_time().saturating_duration_since(v2_timestamp);
12381295
assert_eq!(
1239-
block_maker.get_stable_registry_version(&parent).unwrap(),
1296+
block_maker
1297+
.get_stable_registry_version(&parent, RegistryVersion::new(1), Height::new(100))
1298+
.unwrap(),
12401299
RegistryVersion::from(2)
12411300
);
12421301
block_maker.stable_registry_version_age =
12431302
current_time().saturating_duration_since(v1_timestamp);
12441303
assert_eq!(
1245-
block_maker.get_stable_registry_version(&parent).unwrap(),
1304+
block_maker
1305+
.get_stable_registry_version(&parent, RegistryVersion::new(1), Height::new(100))
1306+
.unwrap(),
12461307
RegistryVersion::from(1)
12471308
);
12481309
// Now let's test if parent's version is used
12491310
parent.context.registry_version = RegistryVersion::from(2);
12501311
assert_eq!(
1251-
block_maker.get_stable_registry_version(&parent).unwrap(),
1312+
block_maker
1313+
.get_stable_registry_version(&parent, RegistryVersion::new(1), Height::new(100))
1314+
.unwrap(),
12521315
RegistryVersion::from(2)
12531316
);
12541317
})
@@ -1366,4 +1429,141 @@ mod tests {
13661429
)
13671430
})
13681431
}
1432+
1433+
mod subnet_splitting {
1434+
use super::*;
1435+
1436+
const MAX_REGISTRY_VERSION: u64 = 4;
1437+
1438+
#[derive(Debug)]
1439+
struct TestCase {
1440+
splitting_registry_version: Option<RegistryVersion>,
1441+
last_summary_block_registry_version: RegistryVersion,
1442+
next_summary_block_height: Height,
1443+
parent_height: Height,
1444+
expected_stable_registry_version: RegistryVersion,
1445+
}
1446+
1447+
#[rstest]
1448+
#[case::no_splitting(TestCase {
1449+
splitting_registry_version: None,
1450+
last_summary_block_registry_version: RegistryVersion::new(1),
1451+
next_summary_block_height: Height::new(4),
1452+
parent_height: Height::new(1),
1453+
expected_stable_registry_version: RegistryVersion::new(MAX_REGISTRY_VERSION),
1454+
})]
1455+
#[case::version_frozen_before_splitting(TestCase {
1456+
splitting_registry_version: Some(RegistryVersion::new(MAX_REGISTRY_VERSION - 1)),
1457+
last_summary_block_registry_version: RegistryVersion::new(1),
1458+
next_summary_block_height: Height::new(4),
1459+
parent_height: Height::new(1),
1460+
expected_stable_registry_version: RegistryVersion::new(MAX_REGISTRY_VERSION - 2),
1461+
})]
1462+
#[case::version_frozen_before_splitting(TestCase {
1463+
splitting_registry_version: Some(RegistryVersion::new(MAX_REGISTRY_VERSION - 2)),
1464+
last_summary_block_registry_version: RegistryVersion::new(1),
1465+
next_summary_block_height: Height::new(4),
1466+
parent_height: Height::new(1),
1467+
expected_stable_registry_version: RegistryVersion::new(MAX_REGISTRY_VERSION - 3),
1468+
})]
1469+
#[case::exact_version_during_splitting(TestCase {
1470+
splitting_registry_version: Some(RegistryVersion::new(MAX_REGISTRY_VERSION - 1)),
1471+
last_summary_block_registry_version: RegistryVersion::new(1),
1472+
next_summary_block_height: Height::new(4),
1473+
parent_height: Height::new(3),
1474+
expected_stable_registry_version: RegistryVersion::new(MAX_REGISTRY_VERSION - 1),
1475+
})]
1476+
fn test_stable_registry_version_with_subnet_splitting(#[case] test_case: TestCase) {
1477+
const SOURCE_SUBNET_ID: SubnetId = SUBNET_0;
1478+
const DESTINATION_SUBNET_ID: SubnetId = SUBNET_1;
1479+
ic_test_utilities::artifact_pool_config::with_test_pool_config(|pool_config| {
1480+
let record = SubnetRecordBuilder::from(&[NODE_1])
1481+
.with_dkg_interval_length(4)
1482+
.build();
1483+
let Dependencies {
1484+
registry,
1485+
crypto,
1486+
pool,
1487+
time_source,
1488+
replica_config,
1489+
state_manager,
1490+
registry_data_provider,
1491+
dkg_pool,
1492+
idkg_pool,
1493+
..
1494+
} = dependencies_with_subnet_params(
1495+
pool_config,
1496+
SOURCE_SUBNET_ID,
1497+
vec![(1, record.clone())],
1498+
);
1499+
1500+
let mut payload_builder = MockPayloadBuilder::new();
1501+
payload_builder
1502+
.expect_get_payload()
1503+
.return_const(BatchPayload::default());
1504+
let membership = Arc::new(Membership::new(
1505+
pool.get_cache(),
1506+
registry.clone(),
1507+
replica_config.subnet_id,
1508+
));
1509+
1510+
let block_maker = BlockMaker::new(
1511+
Arc::clone(&time_source) as Arc<_>,
1512+
replica_config,
1513+
Arc::clone(&registry) as Arc<dyn RegistryClient>,
1514+
membership,
1515+
crypto,
1516+
Arc::new(payload_builder),
1517+
dkg_pool,
1518+
idkg_pool,
1519+
state_manager,
1520+
Duration::from_millis(0),
1521+
MetricsRegistry::new(),
1522+
no_op_logger(),
1523+
);
1524+
1525+
for version in 2..=MAX_REGISTRY_VERSION {
1526+
add_subnet_record(
1527+
&registry_data_provider,
1528+
version,
1529+
SOURCE_SUBNET_ID,
1530+
record.clone(),
1531+
);
1532+
}
1533+
if let Some(splitting_registry_version) = test_case.splitting_registry_version {
1534+
registry_data_provider
1535+
.add(
1536+
&make_catch_up_package_contents_key(SOURCE_SUBNET_ID),
1537+
splitting_registry_version,
1538+
Some(CatchUpPackageContents {
1539+
cup_type: Some(CupType::SubnetSplitting(SubnetSplittingArgs {
1540+
destination_subnet_id: Some(subnet_id_into_protobuf(
1541+
DESTINATION_SUBNET_ID,
1542+
)),
1543+
})),
1544+
..Default::default()
1545+
}),
1546+
)
1547+
.unwrap();
1548+
}
1549+
1550+
registry.update_to_latest_version();
1551+
let mut parent = pool.get_cache().finalized_block();
1552+
parent.height = test_case.parent_height;
1553+
parent.context.registry_version = RegistryVersion::from(1);
1554+
1555+
std::thread::sleep(Duration::from_secs(1));
1556+
assert_eq!(
1557+
block_maker
1558+
.get_stable_registry_version(
1559+
&parent,
1560+
test_case.last_summary_block_registry_version,
1561+
test_case.next_summary_block_height,
1562+
)
1563+
.unwrap(),
1564+
test_case.expected_stable_registry_version,
1565+
);
1566+
})
1567+
}
1568+
}
13691569
}

rs/consensus/src/consensus/malicious_consensus.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,16 @@ impl ConsensusImpl {
146146
let last_summary_block = pool.dkg_summary_block(parent.as_ref())?;
147147

148148
// Get the subnet records that are relevant to making a block
149-
let stable_registry_version = self
150-
.block_maker
151-
.get_stable_registry_version(parent.as_ref())?;
149+
let stable_registry_version = self.block_maker.get_stable_registry_version(
150+
parent.as_ref(),
151+
last_summary_block.context.registry_version,
152+
last_summary_block
153+
.payload
154+
.as_ref()
155+
.as_summary()
156+
.dkg
157+
.get_next_start_height(),
158+
)?;
152159
let subnet_records = block_maker::subnet_records_for_registry_version(
153160
&self.block_maker,
154161
registry_version,

rs/consensus/src/consensus/validator.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use ic_consensus_utils::{
1515
get_oldest_state_registry_version,
1616
membership::{Membership, MembershipError},
1717
pool_reader::{PoolReader, UnexpectedChainLength},
18+
subnet_splitting,
1819
};
1920
use ic_interfaces::{
2021
batch_payload::ProposalContext,
@@ -90,6 +91,7 @@ enum ValidationFailure {
9091
ValidationContextNotReached(ValidationContext, ValidationContext),
9192
CatchUpHeightNegligible,
9293
MissingPastPayloads,
94+
SubnetSplittingStatusError(subnet_splitting::StatusError),
9395
}
9496

9597
/// Possible reasons for invalid artifacts.
@@ -119,6 +121,9 @@ enum InvalidArtifactReason {
119121
RepeatedSigner,
120122
ReplicaVersionMismatch,
121123
NotABlockmaker,
124+
RegistryVersionNotFrozenDuringSubnetSplitting {
125+
context_registry_version: RegistryVersion,
126+
},
122127
}
123128

124129
impl From<CryptoError> for ValidationFailure {
@@ -1271,6 +1276,29 @@ impl Validator {
12711276
.into());
12721277
}
12731278

1279+
// If it's not a summary block, make sure that the registry version is 'frozen' during
1280+
// subnet splitting
1281+
if !proposal.payload.is_summary() {
1282+
match subnet_splitting::get_status(
1283+
self.registry_client.as_ref(),
1284+
self.replica_config.subnet_id,
1285+
last_summary_block.context.registry_version,
1286+
proposal.context.registry_version,
1287+
)
1288+
.map_err(ValidationFailure::SubnetSplittingStatusError)?
1289+
{
1290+
subnet_splitting::Status::NotScheduled => {}
1291+
subnet_splitting::Status::Scheduled { .. } => {
1292+
return Err(
1293+
InvalidArtifactReason::RegistryVersionNotFrozenDuringSubnetSplitting {
1294+
context_registry_version: proposal.context.registry_version,
1295+
}
1296+
.into(),
1297+
);
1298+
}
1299+
}
1300+
}
1301+
12741302
// While halting or halted, data blocks must have an empty payload: skip the rest of the
12751303
// validation if they do, and reject them otherwise. Summary blocks always carry a payload,
12761304
// so they are validated normally.

rs/consensus/utils/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ rust_library(
2929
"@crate_index//:rand",
3030
"@crate_index//:rayon",
3131
"@crate_index//:slog",
32+
"@crate_index//:thiserror",
3233
],
3334
)
3435

@@ -64,6 +65,7 @@ rust_test(
6465
"@crate_index//:prometheus",
6566
"@crate_index//:rand",
6667
"@crate_index//:rayon",
68+
"@crate_index//:rstest",
6769
"@crate_index//:slog",
6870
],
6971
)

0 commit comments

Comments
 (0)