Skip to content

Commit 4e1d963

Browse files
Accept only one block in validate_block when upgrading a runtime (#10280)
As the validation is running the entire time using the same validation code, we can not accept any other blocks after a runtime upgrade was applied. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 357984b commit 4e1d963

File tree

5 files changed

+105
-5
lines changed

5 files changed

+105
-5
lines changed

cumulus/pallets/parachain-system/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ pub mod pallet {
585585
// TODO: This is more than zero, but will need benchmarking to figure out what.
586586
let mut total_weight = Weight::zero();
587587

588-
// NOTE: the inherent data is expected to be unique, even if this block is built
588+
// NOTE: the inherent data is expected to be unique, even if this block is build
589589
// in the context of the same relay parent as the previous one. In particular,
590590
// the inherent shouldn't contain messages that were already processed by any of the
591591
// ancestors.
@@ -643,7 +643,7 @@ pub mod pallet {
643643
),
644644
);
645645

646-
// initialization logic: we know that this runs exactly once every block,
646+
// Initialization logic: we know that this runs exactly once every block,
647647
// which means we can put the initialization logic here to remove the
648648
// sequencing problem.
649649
let upgrade_go_ahead_signal = relay_state_proof

cumulus/pallets/parachain-system/src/validate_block/implementation.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use frame_support::{
2828
BoundedVec,
2929
};
3030
use polkadot_parachain_primitives::primitives::{HeadData, ValidationResult};
31-
use sp_core::storage::{ChildInfo, StateVersion};
31+
use sp_core::storage::{well_known_keys, ChildInfo, StateVersion};
3232
use sp_externalities::{set_and_run_with_externalities, Externalities};
3333
use sp_io::{hashing::blake2_128, KillStorageResult};
3434
use sp_runtime::traits::{
@@ -218,6 +218,10 @@ where
218218
},
219219
);
220220

221+
if overlay.storage(well_known_keys::CODE).is_some() && num_blocks > 1 {
222+
panic!("When applying a runtime upgrade, only one block per PoV is allowed. Received {num_blocks}.")
223+
}
224+
221225
run_with_externalities_and_recorder::<B, _, _>(
222226
&backend,
223227
&mut Default::default(),

cumulus/pallets/parachain-system/src/validate_block/tests.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616

1717
use crate::{validate_block::MemoryOptimizedValidationParams, *};
1818
use codec::{Decode, DecodeAll, Encode};
19-
use cumulus_primitives_core::{ParachainBlockData, PersistedValidationData};
19+
use cumulus_primitives_core::{relay_chain, ParachainBlockData, PersistedValidationData};
2020
use cumulus_test_client::{
2121
generate_extrinsic, generate_extrinsic_with_pair,
2222
runtime::{
23-
self as test_runtime, Block, Hash, Header, TestPalletCall, UncheckedExtrinsic, WASM_BINARY,
23+
self as test_runtime, Block, Hash, Header, SudoCall, SystemCall, TestPalletCall,
24+
UncheckedExtrinsic, WASM_BINARY,
2425
},
2526
seal_block, transfer, BlockData, BlockOrigin, BuildParachainBlockData, Client,
2627
DefaultTestClientBuilderExt, HeadData, InitBlockBuilder,
@@ -654,3 +655,89 @@ fn ensure_we_only_like_blockchains() {
654655
.contains("Not a valid chain of blocks :("));
655656
}
656657
}
658+
659+
#[test]
660+
fn rejects_multiple_blocks_per_pov_when_applying_runtime_upgrade() {
661+
sp_tracing::try_init_simple();
662+
663+
if env::var("RUN_TEST").is_ok() {
664+
let (client, genesis_head) = create_elastic_scaling_test_client();
665+
666+
let code = test_runtime::elastic_scaling_500ms::WASM_BINARY
667+
.expect("You need to build the WASM binaries to run the tests!")
668+
.to_vec();
669+
let code_len = code.len() as u32;
670+
671+
let mut proof_builder =
672+
RelayStateSproofBuilder { current_slot: 1.into(), ..Default::default() };
673+
proof_builder.host_config.max_code_size = code_len * 2;
674+
675+
// Build the block that send the runtime upgrade.
676+
let TestBlockData { block: initial_block_data, .. } = build_block_with_witness(
677+
&client,
678+
vec![generate_extrinsic_with_pair(
679+
&client,
680+
Alice.into(),
681+
SudoCall::sudo {
682+
call: Box::new(SystemCall::set_code_without_checks { code }.into()),
683+
},
684+
Some(0),
685+
)],
686+
genesis_head.clone(),
687+
proof_builder,
688+
Vec::new(),
689+
);
690+
691+
let initial_block = initial_block_data.blocks()[0].clone();
692+
let (mut header, extrinsics) = initial_block.clone().deconstruct();
693+
let seal = header.digest.pop().unwrap();
694+
695+
let mut import = BlockImportParams::new(BlockOrigin::Own, header.clone());
696+
import.body = Some(extrinsics);
697+
import.post_digests.push(seal);
698+
import.fork_choice = Some(ForkChoiceStrategy::Custom(true));
699+
700+
futures::executor::block_on(BlockImport::import_block(&client, import)).unwrap();
701+
let initial_block_header = initial_block.header().clone();
702+
703+
let mut proof_builder = RelayStateSproofBuilder {
704+
current_slot: 2.into(),
705+
upgrade_go_ahead: Some(relay_chain::UpgradeGoAhead::GoAhead),
706+
..Default::default()
707+
};
708+
proof_builder.host_config.max_code_size = code_len * 2;
709+
710+
// 2. Build a PoV that consists of multiple blocks.
711+
let TestBlockData { block: pov_block_data, validation_data: pov_validation_data } =
712+
build_multiple_blocks_with_witness(
713+
&client,
714+
initial_block_header.clone(), // Start building PoV from the initial block's header
715+
proof_builder,
716+
4,
717+
|_| Vec::new(),
718+
);
719+
720+
// 3. Validate the PoV.
721+
call_validate_block_elastic_scaling(
722+
initial_block_header, // The parent is the head of the initial block before the PoV
723+
pov_block_data,
724+
pov_validation_data.relay_parent_storage_root,
725+
)
726+
.unwrap_err();
727+
} else {
728+
let output = Command::new(env::current_exe().unwrap())
729+
.args([
730+
"rejects_multiple_blocks_per_pov_when_applying_runtime_upgrade",
731+
"--",
732+
"--nocapture",
733+
])
734+
.env("RUN_TEST", "1")
735+
.output()
736+
.expect("Runs the test");
737+
738+
assert!(output.status.success());
739+
740+
assert!(dbg!(String::from_utf8(output.stderr).unwrap())
741+
.contains("only one block per PoV is allowed"));
742+
}
743+
}

cumulus/test/runtime/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ pub use frame_support::{
9999
},
100100
StorageValue,
101101
};
102+
pub use frame_system::Call as SystemCall;
102103
use frame_system::{
103104
limits::{BlockLength, BlockWeights},
104105
EnsureRoot,

prdoc/pr_10280.prdoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
title: Accept only one block in `validate_block` when upgrading a runtime
2+
doc:
3+
- audience: Node Dev
4+
description: |-
5+
As the validation is running the entire time using the same validation code, we can not accept any other blocks after a runtime upgrade was applied.
6+
crates:
7+
- name: cumulus-pallet-parachain-system
8+
bump: patch

0 commit comments

Comments
 (0)