Newer
Older
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use frame_support::{assert_err, assert_ok, assert_storage_noop};
use polkadot_primitives::{BlockNumber, SchedulerParams, PARACHAIN_KEY_TYPE_ID};
use polkadot_primitives_test_helpers::{dummy_head_data, dummy_validation_code, validator_pubkeys};
use sp_keystore::{Keystore, KeystorePtr};
use std::sync::Arc;
use crate::{
configuration::HostConfiguration,
mock::{new_test_ext, MockGenesisConfig, Paras, ParasShared, RuntimeOrigin, System, Test},
paras,
};
static VALIDATORS: &[Sr25519Keyring] = &[
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Ferdie,
];
fn sign_and_include_pvf_check_statement(stmt: PvfCheckStatement) {
let validators = &[
Sr25519Keyring::Alice,
Sr25519Keyring::Bob,
Sr25519Keyring::Charlie,
Sr25519Keyring::Dave,
Sr25519Keyring::Ferdie,
];
let signature = validators[stmt.validator_index.0 as usize].sign(&stmt.signing_payload());
Paras::include_pvf_check_statement(None.into(), stmt, signature.into()).unwrap();
}
fn submit_super_majority_pvf_votes(
validation_code: &ValidationCode,
session_index: SessionIndex,
accept: bool,
) {
[0, 1, 2, 3]
.into_iter()
.map(|i| PvfCheckStatement {
accept,
subject: validation_code.hash(),
session_index,
validator_index: i.into(),
})
.for_each(sign_and_include_pvf_check_statement);
}
fn test_validation_code_1() -> ValidationCode {
let validation_code = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
ValidationCode(validation_code)
}
fn test_validation_code_2() -> ValidationCode {
let validation_code = vec![9, 8, 7, 6, 5, 4, 3, 2, 1];
ValidationCode(validation_code)
}
fn run_to_block(to: BlockNumber, new_session: Option<Vec<BlockNumber>>) {
let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory());
Keystore::sr25519_generate_new(
&*keystore,
PARACHAIN_KEY_TYPE_ID,
Some(&validator.to_seed()),
)
.unwrap();
}
let validator_pubkeys = validator_pubkeys(VALIDATORS);
while System::block_number() < to {
let b = System::block_number();
Paras::initializer_finalize(b);
ParasShared::initializer_finalize();
if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) {
let mut session_change_notification = SessionChangeNotification::default();
session_change_notification.session_index =
shared::CurrentSessionIndex::<Test>::get() + 1;
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
session_change_notification.validators = validator_pubkeys.clone();
ParasShared::initializer_on_new_session(
session_change_notification.session_index,
session_change_notification.random_seed,
&session_change_notification.new_config,
session_change_notification.validators.clone(),
);
ParasShared::set_active_validators_ascending(validator_pubkeys.clone());
Paras::initializer_on_new_session(&session_change_notification);
}
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
ParasShared::initializer_initialize(b + 1);
Paras::initializer_initialize(b + 1);
}
}
fn upgrade_at(
expected_at: BlockNumber,
activated_at: BlockNumber,
) -> ReplacementTimes<BlockNumber> {
ReplacementTimes { expected_at, activated_at }
}
fn check_code_is_stored(validation_code: &ValidationCode) {
assert!(CodeByHashRefs::<Test>::get(validation_code.hash()) != 0);
assert!(CodeByHash::<Test>::contains_key(validation_code.hash()));
}
fn check_code_is_not_stored(validation_code: &ValidationCode) {
assert!(!CodeByHashRefs::<Test>::contains_key(validation_code.hash()));
assert!(!CodeByHash::<Test>::contains_key(validation_code.hash()));
/// An utility for checking that certain events were deposited.
struct EventValidator {
frame_system::EventRecord<
<Test as frame_system::Config>::RuntimeEvent,
polkadot_primitives::Hash,
>,
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
}
impl EventValidator {
fn new() -> Self {
Self { events: Vec::new() }
}
fn started(&mut self, code: &ValidationCode, id: ParaId) -> &mut Self {
self.events.push(frame_system::EventRecord {
phase: frame_system::Phase::Initialization,
event: Event::PvfCheckStarted(code.hash(), id).into(),
topics: vec![],
});
self
}
fn rejected(&mut self, code: &ValidationCode, id: ParaId) -> &mut Self {
self.events.push(frame_system::EventRecord {
phase: frame_system::Phase::Initialization,
event: Event::PvfCheckRejected(code.hash(), id).into(),
topics: vec![],
});
self
}
fn accepted(&mut self, code: &ValidationCode, id: ParaId) -> &mut Self {
self.events.push(frame_system::EventRecord {
phase: frame_system::Phase::Initialization,
event: Event::PvfCheckAccepted(code.hash(), id).into(),
topics: vec![],
});
self
}
fn check(&self) {
assert_eq!(&frame_system::Pallet::<Test>::events(), &self.events);
}
}
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#[test]
fn para_past_code_pruning_works_correctly() {
let mut past_code = ParaPastCodeMeta::default();
past_code.note_replacement(10u32, 10);
past_code.note_replacement(20, 25);
past_code.note_replacement(30, 35);
let old = past_code.clone();
assert!(past_code.prune_up_to(9).collect::<Vec<_>>().is_empty());
assert_eq!(old, past_code);
assert_eq!(past_code.prune_up_to(10).collect::<Vec<_>>(), vec![10]);
assert_eq!(
past_code,
ParaPastCodeMeta {
upgrade_times: vec![upgrade_at(20, 25), upgrade_at(30, 35)],
last_pruned: Some(10),
}
);
assert!(past_code.prune_up_to(21).collect::<Vec<_>>().is_empty());
assert_eq!(past_code.prune_up_to(26).collect::<Vec<_>>(), vec![20]);
assert_eq!(
past_code,
ParaPastCodeMeta { upgrade_times: vec![upgrade_at(30, 35)], last_pruned: Some(25) }
);
past_code.note_replacement(40, 42);
past_code.note_replacement(50, 53);
past_code.note_replacement(60, 66);
assert_eq!(
past_code,
ParaPastCodeMeta {
upgrade_times: vec![
upgrade_at(30, 35),
upgrade_at(40, 42),
upgrade_at(50, 53),
upgrade_at(60, 66)
],
last_pruned: Some(25),
}
);
assert_eq!(past_code.prune_up_to(60).collect::<Vec<_>>(), vec![30, 40, 50]);
assert_eq!(
past_code,
ParaPastCodeMeta { upgrade_times: vec![upgrade_at(60, 66)], last_pruned: Some(53) }
);
assert_eq!(past_code.most_recent_change(), Some(60));
assert_eq!(past_code.prune_up_to(66).collect::<Vec<_>>(), vec![60]);
assert_eq!(past_code, ParaPastCodeMeta { upgrade_times: Vec::new(), last_pruned: Some(66) });
}
#[test]
fn schedule_para_init_rejects_empty_code() {
new_test_ext(MockGenesisConfig::default()).execute_with(|| {
assert_err!(
Paras::schedule_para_initialize(
1000.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parathread,
genesis_head: dummy_head_data(),
validation_code: ValidationCode(vec![]),
}
),
Error::<Test>::CannotOnboard,
);
assert_ok!(Paras::schedule_para_initialize(
1000.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parathread,
genesis_head: dummy_head_data(),
validation_code: ValidationCode(vec![1]),
}
));
});
}
#[test]
fn para_past_code_pruning_in_initialize() {
let code_retention_period = 10;
let paras = vec![
(
0u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: dummy_head_data(),
validation_code: dummy_validation_code(),
},
),
(
1u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parathread,
genesis_head: dummy_head_data(),
validation_code: dummy_validation_code(),
},
),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration { code_retention_period, ..Default::default() },
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let id = ParaId::from(0u32);
let at_block: BlockNumber = 10;
let included_block: BlockNumber = 12;
let validation_code = test_validation_code_2();
Paras::increase_code_ref(&validation_code.hash(), &validation_code);
PastCodeHash::<Test>::insert(&(id, at_block), &validation_code.hash());
PastCodePruning::<Test>::put(&vec![(id, included_block)]);
let mut code_meta = paras::PastCodeMeta::<Test>::get(&id);
code_meta.note_replacement(at_block, included_block);
PastCodeMeta::<Test>::insert(&id, &code_meta);
}
let pruned_at: BlockNumber = included_block + code_retention_period + 1;
assert_eq!(PastCodeHash::<Test>::get(&(id, at_block)), Some(validation_code.hash()));
check_code_is_stored(&validation_code);
run_to_block(pruned_at - 1, None);
assert_eq!(PastCodeHash::<Test>::get(&(id, at_block)), Some(validation_code.hash()));
assert_eq!(paras::PastCodeMeta::<Test>::get(&id).most_recent_change(), Some(at_block));
check_code_is_stored(&validation_code);
run_to_block(pruned_at, None);
assert!(PastCodeHash::<Test>::get(&(id, at_block)).is_none());
assert!(paras::PastCodeMeta::<Test>::get(&id).most_recent_change().is_none());
check_code_is_not_stored(&validation_code);
});
}
#[test]
fn note_new_head_sets_head() {
let code_retention_period = 10;
let paras = vec![(
0u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: dummy_head_data(),
validation_code: dummy_validation_code(),
},
)];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration { code_retention_period, ..Default::default() },
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let id_a = ParaId::from(0u32);
assert_eq!(paras::Heads::<Test>::get(&id_a), Some(dummy_head_data()));
Paras::note_new_head(id_a, vec![1, 2, 3].into(), 0);
assert_eq!(paras::Heads::<Test>::get(&id_a), Some(vec![1, 2, 3].into()));
});
}
#[test]
fn note_past_code_sets_up_pruning_correctly() {
let code_retention_period = 10;
let paras = vec![
(
0u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: dummy_head_data(),
validation_code: dummy_validation_code(),
},
),
(
1u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parathread,
genesis_head: dummy_head_data(),
validation_code: dummy_validation_code(),
},
),
];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration { code_retention_period, ..Default::default() },
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let id_a = ParaId::from(0u32);
let id_b = ParaId::from(1u32);
Paras::note_past_code(id_a, 10, 12, test_validation_code_1().hash());
Paras::note_past_code(id_b, 20, 23, test_validation_code_2().hash());
assert_eq!(PastCodePruning::<Test>::get(), vec![(id_a, 12), (id_b, 23)]);
paras::PastCodeMeta::<Test>::get(&id_a),
ParaPastCodeMeta { upgrade_times: vec![upgrade_at(10, 12)], last_pruned: None }
);
assert_eq!(
paras::PastCodeMeta::<Test>::get(&id_b),
ParaPastCodeMeta { upgrade_times: vec![upgrade_at(20, 23)], last_pruned: None }
);
});
}
#[test]
fn code_upgrade_applied_after_delay() {
let code_retention_period = 10;
let validation_upgrade_delay = 5;
let validation_upgrade_cooldown = 10;
let original_code = test_validation_code_1();
let paras = vec![(
0u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: dummy_head_data(),
validation_code: original_code.clone(),
},
)];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
code_retention_period,
validation_upgrade_delay,
validation_upgrade_cooldown,
..Default::default()
},
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
check_code_is_stored(&original_code);
let para_id = ParaId::from(0);
let new_code = test_validation_code_2();
// Wait for at least one session change to set active validators.
const EXPECTED_SESSION: SessionIndex = 1;
run_to_block(2, Some(vec![1]));
assert_eq!(Paras::current_code(¶_id), Some(original_code.clone()));
let (expected_at, next_possible_upgrade_at) = {
// this parablock is in the context of block 1.
let expected_at = 1 + validation_upgrade_delay;
let next_possible_upgrade_at = 1 + validation_upgrade_cooldown;
Daan van der Plas
committed
Paras::schedule_code_upgrade(
para_id,
new_code.clone(),
1,
&configuration::ActiveConfig::<Test>::get(),
UpgradeStrategy::SetGoAheadSignal,
Daan van der Plas
committed
);
// Include votes for super-majority.
submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);
Paras::note_new_head(para_id, Default::default(), 1);
assert!(paras::PastCodeMeta::<Test>::get(¶_id).most_recent_change().is_none());
assert_eq!(FutureCodeUpgrades::<Test>::get(¶_id), Some(expected_at));
assert_eq!(FutureCodeHash::<Test>::get(¶_id), Some(new_code.hash()));
assert_eq!(UpcomingUpgrades::<Test>::get(), vec![(para_id, expected_at)]);
assert_eq!(UpgradeCooldowns::<Test>::get(), vec![(para_id, next_possible_upgrade_at)]);
assert_eq!(Paras::current_code(¶_id), Some(original_code.clone()));
check_code_is_stored(&original_code);
check_code_is_stored(&new_code);
(expected_at, next_possible_upgrade_at)
};
run_to_block(expected_at, None);
// the candidate is in the context of the parent of `expected_at`,
// thus does not trigger the code upgrade.
{
Paras::note_new_head(para_id, Default::default(), expected_at - 1);
assert!(paras::PastCodeMeta::<Test>::get(¶_id).most_recent_change().is_none());
assert_eq!(FutureCodeUpgrades::<Test>::get(¶_id), Some(expected_at));
assert_eq!(FutureCodeHash::<Test>::get(¶_id), Some(new_code.hash()));
assert_eq!(UpgradeGoAheadSignal::<Test>::get(¶_id), Some(UpgradeGoAhead::GoAhead));
assert_eq!(Paras::current_code(¶_id), Some(original_code.clone()));
check_code_is_stored(&original_code);
check_code_is_stored(&new_code);
}
run_to_block(expected_at + 1, None);
// the candidate is in the context of `expected_at`, and triggers
// the upgrade.
{
Paras::note_new_head(para_id, Default::default(), expected_at);
assert_eq!(
paras::PastCodeMeta::<Test>::get(¶_id).most_recent_change(),
Some(expected_at)
);
PastCodeHash::<Test>::get(&(para_id, expected_at)),
assert!(FutureCodeUpgrades::<Test>::get(¶_id).is_none());
assert!(FutureCodeHash::<Test>::get(¶_id).is_none());
assert!(UpgradeGoAheadSignal::<Test>::get(¶_id).is_none());
assert_eq!(Paras::current_code(¶_id), Some(new_code.clone()));
assert_eq!(
UpgradeRestrictionSignal::<Test>::get(¶_id),
Some(UpgradeRestriction::Present),
);
assert_eq!(UpgradeCooldowns::<Test>::get(), vec![(para_id, next_possible_upgrade_at)]);
check_code_is_stored(&original_code);
check_code_is_stored(&new_code);
}
run_to_block(next_possible_upgrade_at + 1, None);
{
assert!(UpgradeRestrictionSignal::<Test>::get(¶_id).is_none());
assert!(UpgradeCooldowns::<Test>::get().is_empty());
}
Daan van der Plas
committed
#[test]
fn upgrade_strategy_apply_at_expected_block_works() {
Daan van der Plas
committed
let code_retention_period = 10;
let validation_upgrade_delay = 5;
let validation_upgrade_cooldown = 10;
let original_code = test_validation_code_1();
Daan van der Plas
committed
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
let paras = vec![(
0u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: dummy_head_data(),
validation_code: original_code.clone(),
},
)];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
code_retention_period,
validation_upgrade_delay,
validation_upgrade_cooldown,
..Default::default()
},
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
check_code_is_stored(&original_code);
let para_id = ParaId::from(0);
let new_code = test_validation_code_2();
Daan van der Plas
committed
// Wait for at least one session change to set active validators.
const EXPECTED_SESSION: SessionIndex = 1;
run_to_block(2, Some(vec![1]));
assert_eq!(Paras::current_code(¶_id), Some(original_code.clone()));
// this parablock is in the context of block 1.
let expected_at = 1 + validation_upgrade_delay;
let next_possible_upgrade_at = 1 + validation_upgrade_cooldown;
// `set_go_ahead` parameter set to `false` which prevents signaling the parachain
// with the `GoAhead` signal.
Paras::schedule_code_upgrade(
para_id,
new_code.clone(),
1,
&configuration::ActiveConfig::<Test>::get(),
UpgradeStrategy::ApplyAtExpectedBlock,
);
// Include votes for super-majority.
submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);
assert!(FutureCodeUpgradesAt::<Test>::get().iter().any(|(id, _)| *id == para_id));
Daan van der Plas
committed
// Going to the expected block triggers the upgrade directly.
Daan van der Plas
committed
run_to_block(expected_at, None);
// Reporting a head doesn't change anything.
Paras::note_new_head(para_id, Default::default(), expected_at - 1);
Daan van der Plas
committed
assert_eq!(
paras::PastCodeMeta::<Test>::get(¶_id).most_recent_change(),
Some(expected_at)
);
assert_eq!(PastCodeHash::<Test>::get(&(para_id, expected_at)), Some(original_code.hash()));
assert!(FutureCodeUpgrades::<Test>::get(¶_id).is_none());
assert!(FutureCodeUpgradesAt::<Test>::get().iter().all(|(id, _)| *id != para_id));
assert!(FutureCodeHash::<Test>::get(¶_id).is_none());
assert!(UpgradeGoAheadSignal::<Test>::get(¶_id).is_none());
assert_eq!(Paras::current_code(¶_id), Some(new_code.clone()));
assert_eq!(
UpgradeRestrictionSignal::<Test>::get(¶_id),
Some(UpgradeRestriction::Present),
);
assert_eq!(UpgradeCooldowns::<Test>::get(), vec![(para_id, next_possible_upgrade_at)]);
check_code_is_stored(&original_code);
check_code_is_stored(&new_code);
Daan van der Plas
committed
run_to_block(next_possible_upgrade_at + 1, None);
{
assert!(UpgradeRestrictionSignal::<Test>::get(¶_id).is_none());
assert!(UpgradeCooldowns::<Test>::get().is_empty());
}
});
}
#[test]
fn code_upgrade_applied_after_delay_even_when_late() {
let code_retention_period = 10;
let validation_upgrade_delay = 5;
let validation_upgrade_cooldown = 10;
let original_code = test_validation_code_1();
let paras = vec![(
0u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: dummy_head_data(),
validation_code: original_code.clone(),
},
)];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
code_retention_period,
validation_upgrade_delay,
validation_upgrade_cooldown,
..Default::default()
},
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let para_id = ParaId::from(0);
let new_code = test_validation_code_2();
// Wait for at least one session change to set active validators.
const EXPECTED_SESSION: SessionIndex = 1;
run_to_block(2, Some(vec![1]));
assert_eq!(Paras::current_code(¶_id), Some(original_code.clone()));
let expected_at = {
// this parablock is in the context of block 1.
let expected_at = 1 + validation_upgrade_delay;
let next_possible_upgrade_at = 1 + validation_upgrade_cooldown;
Daan van der Plas
committed
Paras::schedule_code_upgrade(
para_id,
new_code.clone(),
1,
&configuration::ActiveConfig::<Test>::get(),
UpgradeStrategy::SetGoAheadSignal,
Daan van der Plas
committed
);
// Include votes for super-majority.
submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);
Paras::note_new_head(para_id, Default::default(), 1);
assert!(paras::PastCodeMeta::<Test>::get(¶_id).most_recent_change().is_none());
assert_eq!(FutureCodeUpgrades::<Test>::get(¶_id), Some(expected_at));
assert_eq!(FutureCodeHash::<Test>::get(¶_id), Some(new_code.hash()));
assert_eq!(UpcomingUpgrades::<Test>::get(), vec![(para_id, expected_at)]);
assert_eq!(UpgradeCooldowns::<Test>::get(), vec![(para_id, next_possible_upgrade_at)]);
assert!(UpgradeGoAheadSignal::<Test>::get(¶_id).is_none());
assert_eq!(Paras::current_code(¶_id), Some(original_code.clone()));
expected_at
};
run_to_block(expected_at + 1 + 4, None);
// the candidate is in the context of the first descendant of `expected_at`, and triggers
// the upgrade.
{
// The signal should be set to go-ahead until the new head is actually processed.
assert_eq!(UpgradeGoAheadSignal::<Test>::get(¶_id), Some(UpgradeGoAhead::GoAhead));
Paras::note_new_head(para_id, Default::default(), expected_at + 4);
assert_eq!(
paras::PastCodeMeta::<Test>::get(¶_id).most_recent_change(),
Some(expected_at)
);
PastCodeHash::<Test>::get(&(para_id, expected_at)),
assert!(FutureCodeUpgrades::<Test>::get(¶_id).is_none());
assert!(FutureCodeHash::<Test>::get(¶_id).is_none());
assert!(UpgradeGoAheadSignal::<Test>::get(¶_id).is_none());
assert_eq!(Paras::current_code(¶_id), Some(new_code.clone()));
}
});
}
#[test]
fn submit_code_change_when_not_allowed_is_err() {
let code_retention_period = 10;
let validation_upgrade_delay = 7;
let validation_upgrade_cooldown = 100;
let paras = vec![(
0u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: dummy_head_data(),
validation_code: vec![1, 2, 3].into(),
},
)];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
code_retention_period,
validation_upgrade_delay,
validation_upgrade_cooldown,
..Default::default()
},
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let para_id = ParaId::from(0);
let new_code = test_validation_code_1();
let newer_code = test_validation_code_2();
// Wait for at least one session change to set active validators.
const EXPECTED_SESSION: SessionIndex = 1;
run_to_block(1, Some(vec![1]));
Daan van der Plas
committed
Paras::schedule_code_upgrade(
para_id,
new_code.clone(),
1,
&configuration::ActiveConfig::<Test>::get(),
UpgradeStrategy::SetGoAheadSignal,
Daan van der Plas
committed
);
// Include votes for super-majority.
submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);
assert_eq!(FutureCodeUpgrades::<Test>::get(¶_id), Some(1 + validation_upgrade_delay));
assert_eq!(FutureCodeHash::<Test>::get(¶_id), Some(new_code.hash()));
check_code_is_stored(&new_code);
// We expect that if an upgrade is signalled while there is already one pending we just
// ignore it. Note that this is only true from perspective of this module.
run_to_block(2, None);
assert!(!Paras::can_upgrade_validation_code(para_id));
Daan van der Plas
committed
Paras::schedule_code_upgrade(
para_id,
newer_code.clone(),
2,
&configuration::ActiveConfig::<Test>::get(),
UpgradeStrategy::SetGoAheadSignal,
Daan van der Plas
committed
);
FutureCodeUpgrades::<Test>::get(¶_id),
Some(1 + validation_upgrade_delay), /* did not change since the same assertion from
* the last time. */
assert_eq!(FutureCodeHash::<Test>::get(¶_id), Some(new_code.hash()));
check_code_is_not_stored(&newer_code);
});
}
#[test]
fn upgrade_restriction_elapsed_doesnt_mean_can_upgrade() {
// Situation: parachain scheduled upgrade but it doesn't produce any candidate after
// `expected_at`. When `validation_upgrade_cooldown` elapsed the parachain produces a
// candidate that tries to upgrade the code.
//
// In the current code this is not allowed: the upgrade should be consumed first. This is
// rather an artifact of the current implementation and not necessarily something we want
// to keep in the future.
//
// This test exists that this is not accidentally changed.
let code_retention_period = 10;
let validation_upgrade_delay = 7;
let validation_upgrade_cooldown = 30;
let paras = vec![(
0u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: dummy_head_data(),
validation_code: vec![1, 2, 3].into(),
},
)];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
code_retention_period,
validation_upgrade_delay,
validation_upgrade_cooldown,
..Default::default()
},
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
let para_id = 0u32.into();
let new_code = test_validation_code_1();
let newer_code = test_validation_code_2();
// Wait for at least one session change to set active validators.
const EXPECTED_SESSION: SessionIndex = 1;
run_to_block(1, Some(vec![1]));
Daan van der Plas
committed
Paras::schedule_code_upgrade(
para_id,
new_code.clone(),
0,
&configuration::ActiveConfig::<Test>::get(),
UpgradeStrategy::SetGoAheadSignal,
Daan van der Plas
committed
);
// Include votes for super-majority.
submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);
Paras::note_new_head(para_id, dummy_head_data(), 0);
assert_eq!(
UpgradeRestrictionSignal::<Test>::get(¶_id),
assert_eq!(FutureCodeUpgrades::<Test>::get(¶_id), Some(0 + validation_upgrade_delay));
assert!(!Paras::can_upgrade_validation_code(para_id));
run_to_block(31, None);
assert!(UpgradeRestrictionSignal::<Test>::get(¶_id).is_none());
// Note the para still cannot upgrade the validation code.
assert!(!Paras::can_upgrade_validation_code(para_id));
// And scheduling another upgrade does not do anything. `expected_at` is still the same.
Daan van der Plas
committed
Paras::schedule_code_upgrade(
para_id,
newer_code.clone(),
30,
&configuration::ActiveConfig::<Test>::get(),
UpgradeStrategy::SetGoAheadSignal,
Daan van der Plas
committed
);
assert_eq!(FutureCodeUpgrades::<Test>::get(¶_id), Some(0 + validation_upgrade_delay));
});
}
#[test]
fn full_parachain_cleanup_storage() {
let code_retention_period = 20;
let validation_upgrade_delay = 1 + 5;
let original_code = test_validation_code_1();
let paras = vec![(
0u32.into(),
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: dummy_head_data(),
validation_code: original_code.clone(),
},
)];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
configuration: crate::configuration::GenesisConfig {
config: HostConfiguration {
code_retention_period,
validation_upgrade_delay,
minimum_validation_upgrade_delay: 2,
// Those are not relevant to this test. However, HostConfiguration is still a
// subject for the consistency check.
Tsvetomir Dimitrov
committed
scheduler_params: SchedulerParams {
paras_availability_period: 1,
..Default::default()
},
..Default::default()
},
},
..Default::default()
};
new_test_ext(genesis_config).execute_with(|| {
check_code_is_stored(&original_code);
let para_id = ParaId::from(0);
let new_code = test_validation_code_2();
// Wait for at least one session change to set active validators.
const EXPECTED_SESSION: SessionIndex = 1;
run_to_block(2, Some(vec![1]));
assert_eq!(Paras::current_code(¶_id), Some(original_code.clone()));
check_code_is_stored(&original_code);
let expected_at = {
// this parablock is in the context of block 1.
let expected_at = 1 + validation_upgrade_delay;
Daan van der Plas
committed
Paras::schedule_code_upgrade(
para_id,
new_code.clone(),
1,
&configuration::ActiveConfig::<Test>::get(),
UpgradeStrategy::SetGoAheadSignal,
Daan van der Plas
committed
);
// Include votes for super-majority.
submit_super_majority_pvf_votes(&new_code, EXPECTED_SESSION, true);
Paras::note_new_head(para_id, Default::default(), 1);
assert!(paras::PastCodeMeta::<Test>::get(¶_id).most_recent_change().is_none());
assert_eq!(FutureCodeUpgrades::<Test>::get(¶_id), Some(expected_at));
assert_eq!(FutureCodeHash::<Test>::get(¶_id), Some(new_code.hash()));
assert_eq!(Paras::current_code(¶_id), Some(original_code.clone()));
check_code_is_stored(&original_code);
check_code_is_stored(&new_code);
expected_at
};
// Enact the upgrade.
//
// For that run to block #7 and submit a new head.
assert_eq!(expected_at, 7);
run_to_block(7, None);
assert_eq!(frame_system::Pallet::<Test>::block_number(), 7);
Paras::note_new_head(para_id, Default::default(), expected_at);
assert_ok!(Paras::schedule_para_cleanup(para_id));
// run to block #10, with a 2 session changes at the end of the block 7 & 8 (so 8 and 9
// observe the new sessions).
run_to_block(10, Some(vec![8, 9]));
// cleaning up the parachain should place the current parachain code
// into the past code buffer & schedule cleanup.
//
// Why 7 and 8? See above, the clean up scheduled above was processed at the block 8.
// The initial upgrade was enacted at the block 7.
assert_eq!(paras::PastCodeMeta::<Test>::get(¶_id).most_recent_change(), Some(8));
assert_eq!(PastCodeHash::<Test>::get(&(para_id, 8)), Some(new_code.hash()));
assert_eq!(PastCodePruning::<Test>::get(), vec![(para_id, 7), (para_id, 8)]);
check_code_is_stored(&original_code);
check_code_is_stored(&new_code);
// any future upgrades haven't been used to validate yet, so those
// are cleaned up immediately.
assert!(FutureCodeUpgrades::<Test>::get(¶_id).is_none());
assert!(FutureCodeHash::<Test>::get(¶_id).is_none());
assert!(Paras::current_code(¶_id).is_none());
// run to do the final cleanup
let cleaned_up_at = 8 + code_retention_period + 1;
run_to_block(cleaned_up_at, None);
// now the final cleanup: last past code cleaned up, and this triggers meta cleanup.
assert_eq!(paras::PastCodeMeta::<Test>::get(¶_id), Default::default());
assert!(PastCodeHash::<Test>::get(&(para_id, 7)).is_none());
assert!(PastCodeHash::<Test>::get(&(para_id, 8)).is_none());
assert!(PastCodePruning::<Test>::get().is_empty());
check_code_is_not_stored(&original_code);
check_code_is_not_stored(&new_code);
});
}
#[test]
fn cannot_offboard_ongoing_pvf_check() {
let para_id = ParaId::from(0);
let existing_code = test_validation_code_1();
let new_code = test_validation_code_2();
let paras = vec![(
para_id,
ParaGenesisArgs {
para_kind: ParaKind::Parachain,
genesis_head: Default::default(),
validation_code: existing_code,
},
)];
let genesis_config = MockGenesisConfig {
paras: GenesisConfig { paras, ..Default::default() },
..Default::default()
};