diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs index 7702e2efef..6dba0435ca 100644 --- a/polkadot/node/collation-generation/src/lib.rs +++ b/polkadot/node/collation-generation/src/lib.rs @@ -33,7 +33,7 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_util::{ request_availability_cores_ctx, request_persisted_validation_data_ctx, - request_validators_ctx, + request_validators_ctx, request_validation_code_ctx, metrics::{self, prometheus}, }; use polkadot_primitives::v1::{ @@ -244,9 +244,10 @@ async fn handle_new_activations( continue; } - // we get validation data synchronously for each core instead of + // we get validation data and validation code synchronously for each core instead of // within the subtask loop, because we have only a single mutable handle to the // context, so the work can't really be distributed + let validation_data = match request_persisted_validation_data_ctx( relay_parent, scheduled_core.para_id, @@ -270,6 +271,30 @@ async fn handle_new_activations( } }; + let validation_code = match request_validation_code_ctx( + relay_parent, + scheduled_core.para_id, + assumption, + ctx, + ) + .await? + .await?? + { + Some(v) => v, + None => { + tracing::trace!( + target: LOG_TARGET, + core_idx = %core_idx, + relay_parent = ?relay_parent, + our_para = %config.para_id, + their_para = %scheduled_core.para_id, + "validation code is not available", + ); + continue + } + }; + let validation_code_hash = validation_code.hash(); + let task_config = config.clone(); let mut task_sender = sender.clone(); let metrics = metrics.clone(); @@ -295,6 +320,7 @@ async fn handle_new_activations( &scheduled_core.para_id, &persisted_validation_data_hash, &pov_hash, + &validation_code_hash, ); let erasure_root = match erasure_root( @@ -334,6 +360,7 @@ async fn handle_new_activations( pov_hash, erasure_root, para_head: commitments.head_data.hash(), + validation_code_hash: validation_code_hash, }, }; @@ -474,7 +501,7 @@ mod tests { }; use polkadot_primitives::v1::{ BlockNumber, CollatorPair, Id as ParaId, - PersistedValidationData, ScheduledCore, + PersistedValidationData, ScheduledCore, ValidationCode, }; use std::pin::Pin; @@ -696,6 +723,16 @@ mod tests { ))) => { tx.send(Ok(vec![Default::default(); 3])).unwrap(); } + Some(AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _hash, + RuntimeApiRequest::ValidationCode( + _para_id, + OccupiedCoreAssumption::Free, + tx, + ), + ))) => { + tx.send(Ok(Some(ValidationCode(vec![1, 2, 3])))).unwrap(); + } Some(msg) => { panic!("didn't expect any other overseer requests; got {:?}", msg) } @@ -733,11 +770,13 @@ mod tests { let expect_validation_data_hash = PersistedValidationData::::default().hash(); let expect_relay_parent = Hash::repeat_byte(4); + let expect_validation_code_hash = ValidationCode(vec![1, 2, 3]).hash(); let expect_payload = collator_signature_payload( &expect_relay_parent, &config.para_id, &expect_validation_data_hash, &expect_pov_hash, + &expect_validation_code_hash, ); let expect_descriptor = CandidateDescriptor { signature: config.key.sign(&expect_payload), @@ -748,6 +787,7 @@ mod tests { pov_hash: expect_pov_hash, erasure_root: Default::default(), // this isn't something we're checking right now para_head: test_collation().head_data.hash(), + validation_code_hash: expect_validation_code_hash, }; assert_eq!(sent_messages.len(), 1); @@ -768,6 +808,7 @@ mod tests { &descriptor.para_id, &descriptor.persisted_validation_data_hash, &descriptor.pov_hash, + &descriptor.validation_code_hash, ) .as_ref(), &descriptor.collator, diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index 5f6d857b0c..b00e46a59a 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -361,21 +361,27 @@ async fn spawn_validate_exhaustive( /// Does basic checks of a candidate. Provide the encoded PoV-block. Returns `Ok` if basic checks /// are passed, `Err` otherwise. -#[tracing::instrument(level = "trace", skip(pov), fields(subsystem = LOG_TARGET))] +#[tracing::instrument(level = "trace", skip(pov, validation_code), fields(subsystem = LOG_TARGET))] fn perform_basic_checks( candidate: &CandidateDescriptor, max_pov_size: u32, pov: &PoV, + validation_code: &ValidationCode, ) -> Result<(), InvalidCandidate> { let encoded_pov = pov.encode(); - let hash = pov.hash(); + let pov_hash = pov.hash(); + let validation_code_hash = validation_code.hash(); if encoded_pov.len() > max_pov_size as usize { return Err(InvalidCandidate::ParamsTooLarge(encoded_pov.len() as u64)); } - if hash != candidate.pov_hash { - return Err(InvalidCandidate::HashMismatch); + if pov_hash != candidate.pov_hash { + return Err(InvalidCandidate::PoVHashMismatch); + } + + if validation_code_hash != candidate.validation_code_hash { + return Err(InvalidCandidate::CodeHashMismatch); } if let Err(()) = candidate.check_collator_signature() { @@ -431,7 +437,12 @@ fn validate_candidate_exhaustive( ) -> Result { let _timer = metrics.time_validate_candidate_exhaustive(); - if let Err(e) = perform_basic_checks(&descriptor, persisted_validation_data.max_pov_size, &*pov) { + if let Err(e) = perform_basic_checks( + &descriptor, + persisted_validation_data.max_pov_size, + &*pov, + &validation_code, + ) { return Ok(ValidationResult::Invalid(e)) } @@ -601,6 +612,7 @@ mod tests { &descriptor.para_id, &descriptor.persisted_validation_data_hash, &descriptor.pov_hash, + &descriptor.validation_code_hash, ); descriptor.signature = collator.sign(&payload[..]).into(); @@ -891,13 +903,21 @@ mod tests { let pov = PoV { block_data: BlockData(vec![1; 32]) }; let head_data = HeadData(vec![1, 1, 1]); + let validation_code = ValidationCode(vec![2; 16]); let mut descriptor = CandidateDescriptor::default(); descriptor.pov_hash = pov.hash(); descriptor.para_head = head_data.hash(); + descriptor.validation_code_hash = validation_code.hash(); collator_sign(&mut descriptor, Sr25519Keyring::Alice); - assert!(perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov).is_ok()); + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code, + ); + assert!(check.is_ok()); let validation_result = WasmValidationResult { head_data, @@ -911,7 +931,7 @@ mod tests { let v = validate_candidate_exhaustive::( MockValidationArg { result: Ok(validation_result) }, validation_data.clone(), - vec![1, 2, 3].into(), + validation_code, descriptor, Arc::new(pov), TaskExecutor::new(), @@ -933,12 +953,20 @@ mod tests { let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let validation_code = ValidationCode(vec![2; 16]); let mut descriptor = CandidateDescriptor::default(); descriptor.pov_hash = pov.hash(); + descriptor.validation_code_hash = validation_code.hash(); collator_sign(&mut descriptor, Sr25519Keyring::Alice); - assert!(perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov).is_ok()); + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code, + ); + assert!(check.is_ok()); let v = validate_candidate_exhaustive::( MockValidationArg { @@ -947,7 +975,7 @@ mod tests { )) }, validation_data, - vec![1, 2, 3].into(), + validation_code, descriptor, Arc::new(pov), TaskExecutor::new(), @@ -962,12 +990,20 @@ mod tests { let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let validation_code = ValidationCode(vec![2; 16]); let mut descriptor = CandidateDescriptor::default(); descriptor.pov_hash = pov.hash(); + descriptor.validation_code_hash = validation_code.hash(); collator_sign(&mut descriptor, Sr25519Keyring::Alice); - assert!(perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov).is_ok()); + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code, + ); + assert!(check.is_ok()); let v = validate_candidate_exhaustive::( MockValidationArg { @@ -976,7 +1012,7 @@ mod tests { )) }, validation_data, - vec![1, 2, 3].into(), + validation_code, descriptor, Arc::new(pov), TaskExecutor::new(), @@ -985,4 +1021,42 @@ mod tests { assert_matches!(v, Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))); } + + #[test] + fn candidate_validation_code_mismatch_is_invalid() { + let validation_data = PersistedValidationData { max_pov_size: 1024, ..Default::default() }; + + let pov = PoV { block_data: BlockData(vec![1; 32]) }; + let validation_code = ValidationCode(vec![2; 16]); + + let mut descriptor = CandidateDescriptor::default(); + descriptor.pov_hash = pov.hash(); + descriptor.validation_code_hash = ValidationCode(vec![1; 16]).hash(); + collator_sign(&mut descriptor, Sr25519Keyring::Alice); + + let check = perform_basic_checks( + &descriptor, + validation_data.max_pov_size, + &pov, + &validation_code, + ); + assert_matches!(check, Err(InvalidCandidate::CodeHashMismatch)); + + let v = validate_candidate_exhaustive::( + MockValidationArg { + result: Err(ValidationError::InvalidCandidate( + WasmInvalidCandidate::BadReturn + )) + }, + validation_data, + validation_code, + descriptor, + Arc::new(pov), + TaskExecutor::new(), + &Default::default(), + ).unwrap(); + + assert_matches!(v, ValidationResult::Invalid(InvalidCandidate::CodeHashMismatch)); + } + } diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index 0e10ac25b5..a2cd09a8f3 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -556,6 +556,13 @@ mod tests { ) -> BTreeMap> { self.hrmp_channels.get(&recipient).map(|q| q.clone()).unwrap_or_default() } + + fn validation_code_by_hash( + &self, + _hash: Hash, + ) -> Option { + unreachable!("not used in tests"); + } } impl BabeApi for MockRuntimeApi { diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index bbbddf5ab6..98a94c75f9 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -124,11 +124,13 @@ pub enum InvalidCandidate { /// Invalid relay chain parent. BadParent, /// POV hash does not match. - HashMismatch, + PoVHashMismatch, /// Bad collator signature. BadSignature, /// Para head hash does not match. ParaHeadHashMismatch, + /// Validation code hash does not match. + CodeHashMismatch, } /// Result of the validation of the candidate. diff --git a/polkadot/parachain/src/primitives.rs b/polkadot/parachain/src/primitives.rs index 3f21f76372..e4f68a9acc 100644 --- a/polkadot/parachain/src/primitives.rs +++ b/polkadot/parachain/src/primitives.rs @@ -21,6 +21,7 @@ use sp_std::vec::Vec; use parity_scale_codec::{Encode, Decode, CompactAs}; use sp_core::{RuntimeDebug, TypeId}; +use sp_runtime::traits::Hash as _; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; @@ -45,7 +46,6 @@ pub struct HeadData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec Hash { - use sp_runtime::traits::Hash; sp_runtime::traits::BlakeTwo256::hash(&self.0) } } @@ -55,6 +55,13 @@ impl HeadData { #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash, MallocSizeOf))] pub struct ValidationCode(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec); +impl ValidationCode { + /// Get the blake2-256 hash of the validation code bytes. + pub fn hash(&self) -> Hash { + sp_runtime::traits::BlakeTwo256::hash(&self.0[..]) + } +} + /// Parachain block data. /// /// Contains everything required to validate para-block, may contain block and witness data. diff --git a/polkadot/primitives/src/v1.rs b/polkadot/primitives/src/v1.rs index ab0d4357a2..866e6ff69b 100644 --- a/polkadot/primitives/src/v1.rs +++ b/polkadot/primitives/src/v1.rs @@ -211,14 +211,16 @@ pub fn collator_signature_payload>( para_id: &Id, persisted_validation_data_hash: &Hash, pov_hash: &Hash, -) -> [u8; 100] { + validation_code_hash: &Hash, +) -> [u8; 132] { // 32-byte hash length is protected in a test below. - let mut payload = [0u8; 100]; + let mut payload = [0u8; 132]; payload[0..32].copy_from_slice(relay_parent.as_ref()); u32::from(*para_id).using_encoded(|s| payload[32..32 + s.len()].copy_from_slice(s)); payload[36..68].copy_from_slice(persisted_validation_data_hash.as_ref()); payload[68..100].copy_from_slice(pov_hash.as_ref()); + payload[100..132].copy_from_slice(validation_code_hash.as_ref()); payload } @@ -228,6 +230,7 @@ fn check_collator_signature>( para_id: &Id, persisted_validation_data_hash: &Hash, pov_hash: &Hash, + validation_code_hash: &Hash, collator: &CollatorId, signature: &CollatorSignature, ) -> Result<(),()> { @@ -236,6 +239,7 @@ fn check_collator_signature>( para_id, persisted_validation_data_hash, pov_hash, + validation_code_hash, ); if signature.verify(&payload[..], collator) { @@ -268,6 +272,8 @@ pub struct CandidateDescriptor { pub signature: CollatorSignature, /// Hash of the para header that is being generated by this candidate. pub para_head: Hash, + /// The blake2-256 hash of the validation code bytes. + pub validation_code_hash: Hash, } impl> CandidateDescriptor { @@ -278,6 +284,7 @@ impl> CandidateDescriptor { &self.para_id, &self.persisted_validation_data_hash, &self.pov_hash, + &self.validation_code_hash, &self.collator, &self.signature, ) @@ -872,6 +879,10 @@ sp_api::decl_runtime_apis! { /// messages in them are also included. #[skip_initialize_block] fn inbound_hrmp_channels_contents(recipient: Id) -> BTreeMap>>; + + /// Get the validation code from its hash. + #[skip_initialize_block] + fn validation_code_by_hash(hash: Hash) -> Option; } } @@ -1107,6 +1118,7 @@ mod tests { &5u32.into(), &Hash::repeat_byte(2), &Hash::repeat_byte(3), + &Hash::repeat_byte(4), ); } } diff --git a/polkadot/roadmap/implementers-guide/src/runtime/paras.md b/polkadot/roadmap/implementers-guide/src/runtime/paras.md index 51bc754f40..1007a3c27c 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/paras.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/paras.md @@ -116,10 +116,10 @@ Parachains: Vec, ParaLifecycle: map ParaId => Option, /// The head-data of every registered para. Heads: map ParaId => Option; -/// The validation code of every live para. -ValidationCode: map ParaId => Option; -/// Actual past code, indicated by the para id as well as the block number at which it became outdated. -PastCode: map (ParaId, BlockNumber) => Option; +/// The validation code hash of every live para. +CurrentCodeHash: map ParaId => Option; +/// Actual past code hash, indicated by the para id as well as the block number at which it became outdated. +PastCodeHash: map (ParaId, BlockNumber) => Option; /// Past code of parachains. The parachains themselves may not be registered anymore, /// but we also keep their code on-chain for the same amount of time as outdated code /// to keep it available for secondary checkers. @@ -136,24 +136,28 @@ PastCodePruning: Vec<(ParaId, BlockNumber)>; /// in the context of a relay chain block with a number >= `expected_at`. FutureCodeUpgrades: map ParaId => Option; /// The actual future code of a para. -FutureCode: map ParaId => Option; +FutureCodeHash: map ParaId => Option; /// The actions to perform during the start of a specific session index. ActionsQueue: map SessionIndex => Vec; /// Upcoming paras instantiation arguments. UpcomingParasGenesis: map ParaId => Option; +/// The number of references on the validation code in `CodeByHash` storage. +CodeByHashRefs: map Hash => u32; +/// Validation code stored by its hash. +CoeByHash: map Hash => Option ``` ## Session Change 1. Execute all queued actions for paralifecycle changes: 1. Clean up outgoing paras. - 1. This means removing the entries under `Heads`, `ValidationCode`, `FutureCodeUpgrades`, and + 1. This means removing the entries under `Heads`, `CurrentCode`, `FutureCodeUpgrades`, and `FutureCode`. An according entry should be added to `PastCode`, `PastCodeMeta`, and - `PastCodePruning` using the outgoing `ParaId` and removed `ValidationCode` value. This is + `PastCodePruning` using the outgoing `ParaId` and removed `CurrentCode` value. This is because any outdated validation code must remain available on-chain for a determined amount of blocks, and validation code outdated by de-registering the para is still subject to that invariant. - 1. Apply all incoming paras by initializing the `Heads` and `ValidationCode` using the genesis + 1. Apply all incoming paras by initializing the `Heads` and `CurrentCode` using the genesis parameters. 1. Amend the `Parachains` list and `ParaLifecycle` to reflect changes in registered parachains. 1. Amend the `ParaLifecycle` set to reflect changes in registered parathreads. @@ -175,7 +179,7 @@ UpcomingParasGenesis: map ParaId => Option; * `schedule_para_cleanup(ParaId)`: Schedule a para to be cleaned up after the next full session. * `schedule_parathread_upgrade(ParaId)`: Schedule a parathread to be upgraded to a parachain. * `schedule_parachain_downgrade(ParaId)`: Schedule a parachain to be downgraded to a parathread. -* `schedule_code_upgrade(ParaId, ValidationCode, expected_at: BlockNumber)`: Schedule a future code +* `schedule_code_upgrade(ParaId, CurrentCode, expected_at: BlockNumber)`: Schedule a future code upgrade of the given parachain, to be applied after inclusion of a block of the same parachain executed in the context of a relay-chain block with number >= `expected_at`. * `note_new_head(ParaId, HeadData, BlockNumber)`: note that a para has progressed to a new head, @@ -187,6 +191,7 @@ UpcomingParasGenesis: map ParaId => Option; intermediate parablock has been included at the given relay-chain height. This may return past, current, or (with certain choices of `assume_intermediate`) future code. `assume_intermediate`, if provided, must be before `at`. If the validation code has been pruned, this will return `None`. +* `validation_code_hash_at(ParaId, at: BlockNumber, assume_intermediate: Option)`: Just like `validation_code_at`, but returns the code hash. * `lifecycle(ParaId) -> Option`: Return the `ParaLifecycle` of a para. * `is_parachain(ParaId) -> bool`: Returns true if the para ID references any live parachain, including those which may be transitioning to a parathread in the future. @@ -197,9 +202,6 @@ UpcomingParasGenesis: map ParaId => Option; * `last_code_upgrade(id: ParaId, include_future: bool) -> Option`: The block number of the last scheduled upgrade of the requested para. Includes future upgrades if the flag is set. This is the `expected_at` number, not the `activated_at` number. -* `persisted_validation_data(id: ParaId) -> Option`: Get the - PersistedValidationData of the given para, assuming the context is the parent block. Returns - `None` if the para is not known. ## Finalization diff --git a/polkadot/runtime/kusama/src/lib.rs b/polkadot/runtime/kusama/src/lib.rs index 3b2bdea6f1..325474f8b7 100644 --- a/polkadot/runtime/kusama/src/lib.rs +++ b/polkadot/runtime/kusama/src/lib.rs @@ -1218,6 +1218,10 @@ sp_api::impl_runtime_apis! { ) -> BTreeMap>> { BTreeMap::new() } + + fn validation_code_by_hash(_hash: Hash) -> Option { + None + } } impl beefy_primitives::BeefyApi for Runtime { diff --git a/polkadot/runtime/parachains/src/inclusion.rs b/polkadot/runtime/parachains/src/inclusion.rs index a1ae846a91..733e3bc138 100644 --- a/polkadot/runtime/parachains/src/inclusion.rs +++ b/polkadot/runtime/parachains/src/inclusion.rs @@ -185,6 +185,8 @@ decl_error! { HrmpWatermarkMishandling, /// The HRMP messages sent by the candidate is not valid. InvalidOutboundHrmp, + /// The validation code hash of the candidate is not valid. + InvalidValidationCodeHash, } } @@ -444,6 +446,15 @@ impl Module { Error::::NotCollatorSigned, ); + let validation_code_hash = + >::validation_code_hash_at(para_id, now, None) + // A candidate for a parachain without current validation code is not scheduled. + .ok_or_else(|| Error::::UnscheduledCandidate)?; + ensure!( + candidate.descriptor().validation_code_hash == validation_code_hash, + Error::::InvalidValidationCodeHash, + ); + if let Err(err) = check_cx .check_validation_outputs( para_id, @@ -954,6 +965,7 @@ mod tests { &candidate.descriptor.para_id, &candidate.descriptor.persisted_validation_data_hash, &candidate.descriptor.pov_hash, + &candidate.descriptor.validation_code_hash, ); candidate.descriptor.signature = collator.sign(&payload[..]).into(); @@ -1109,6 +1121,7 @@ mod tests { relay_parent: Hash, persisted_validation_data_hash: Hash, new_validation_code: Option, + validation_code: ValidationCode, hrmp_watermark: BlockNumber, } @@ -1120,6 +1133,7 @@ mod tests { pov_hash: self.pov_hash, relay_parent: self.relay_parent, persisted_validation_data_hash: self.persisted_validation_data_hash, + validation_code_hash: self.validation_code.hash(), ..Default::default() }, commitments: CandidateCommitments { @@ -2071,6 +2085,43 @@ mod tests { Err(Error::::ValidationDataHashMismatch.into()), ); } + + // bad validation code hash + { + let mut candidate = TestCandidateBuilder { + para_id: chain_a, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(1), + persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + validation_code: ValidationCode(vec![1]), + ..Default::default() + }.build(); + + collator_sign_candidate( + Sr25519Keyring::One, + &mut candidate, + ); + + let backed = block_on(back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(0)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + )); + + assert_eq!( + Inclusion::process_candidates( + Default::default(), + vec![backed], + vec![chain_a_assignment.clone()], + &group_validators, + ), + Err(Error::::InvalidValidationCodeHash.into()), + ); + } }); } diff --git a/polkadot/runtime/parachains/src/paras.rs b/polkadot/runtime/parachains/src/paras.rs index e74755d502..090629fc7a 100644 --- a/polkadot/runtime/parachains/src/paras.rs +++ b/polkadot/runtime/parachains/src/paras.rs @@ -28,9 +28,9 @@ use sp_std::result; #[cfg(feature = "std")] use sp_std::marker::PhantomData; use primitives::v1::{ - Id as ParaId, ValidationCode, HeadData, SessionIndex, + Id as ParaId, ValidationCode, HeadData, SessionIndex, Hash, ConsensusLog, }; -use sp_runtime::{traits::One, DispatchResult}; +use sp_runtime::{traits::One, DispatchResult, SaturatedConversion}; use frame_system::ensure_root; use frame_support::{ decl_storage, decl_module, decl_error, decl_event, ensure, @@ -82,7 +82,7 @@ pub struct ParaPastCodeMeta { /// was actually replaced, respectively. The first is used to do accurate lookups /// of historic code in historic contexts, whereas the second is used to do /// pruning on an accurate timeframe. These can be used as indices - /// into the `PastCode` map along with the `ParaId` to fetch the code itself. + /// into the `PastCodeHash` map along with the `ParaId` to fetch the code itself. upgrade_times: Vec>, /// Tracks the highest pruned code-replacement, if any. This is the `expected_at` value, /// not the `activated_at` value. @@ -261,10 +261,15 @@ decl_storage! { ParaLifecycles: map hasher(twox_64_concat) ParaId => Option; /// The head-data of every registered para. Heads get(fn para_head): map hasher(twox_64_concat) ParaId => Option; - /// The validation code of every live para. - CurrentCode get(fn current_code): map hasher(twox_64_concat) ParaId => Option; - /// Actual past code, indicated by the para id as well as the block number at which it became outdated. - PastCode: map hasher(twox_64_concat) (ParaId, T::BlockNumber) => Option; + /// The validation code hash of every live para. + /// + /// Corresponding code can be retrieved with [`CodeByHash`]. + CurrentCodeHash: map hasher(twox_64_concat) ParaId => Option; + /// Actual past code hash, indicated by the para id as well as the block number at which it + /// became outdated. + /// + /// Corresponding code can be retrieved with [`CodeByHash`]. + PastCodeHash: map hasher(twox_64_concat) (ParaId, T::BlockNumber) => Option; /// Past code of parachains. The parachains themselves may not be registered anymore, /// but we also keep their code on-chain for the same amount of time as outdated code /// to keep it available for secondary checkers. @@ -281,12 +286,21 @@ decl_storage! { /// The change will be applied after the first parablock for this ID included which executes /// in the context of a relay chain block with a number >= `expected_at`. FutureCodeUpgrades get(fn future_code_upgrade_at): map hasher(twox_64_concat) ParaId => Option; - /// The actual future code of a para. - FutureCode: map hasher(twox_64_concat) ParaId => Option; + /// The actual future code hash of a para. + /// + /// Corresponding code can be retrieved with [`CodeByHash`]. + FutureCodeHash: map hasher(twox_64_concat) ParaId => Option; /// The actions to perform during the start of a specific session index. ActionsQueue get(fn actions_queue): map hasher(twox_64_concat) SessionIndex => Vec; /// Upcoming paras instantiation arguments. UpcomingParasGenesis: map hasher(twox_64_concat) ParaId => Option; + /// The number of reference on the validation code in [`CodeByHash`] storage. + CodeByHashRefs: map hasher(identity) Hash => u32; + /// Validation code stored by its hash. + /// + /// This storage is consistent with [`FutureCodeHash`], [`CurrentCodeHash`] and + /// [`PastCodeHash`]. + CodeByHash get(fn code_by_hash): map hasher(identity) Hash => Option; } add_extra_genesis { config(paras): Vec<(ParaId, ParaGenesisArgs)>; @@ -310,7 +324,9 @@ fn build(config: &GenesisConfig) { Parachains::put(¶chains); for (id, genesis_args) in &config.paras { - as Store>::CurrentCode::insert(&id, &genesis_args.validation_code); + let code_hash = genesis_args.validation_code.hash(); + >::increase_code_ref(&code_hash, &genesis_args.validation_code); + as Store>::CurrentCodeHash::insert(&id, &code_hash); as Store>::Heads::insert(&id, &genesis_args.genesis_head); if genesis_args.parachain { ParaLifecycles::insert(&id, ParaLifecycle::Parachain); @@ -361,11 +377,13 @@ decl_module! { #[weight = 0] fn force_set_current_code(origin, para: ParaId, new_code: ValidationCode) { ensure_root(origin)?; - let prior_code = ::CurrentCode::get(¶).unwrap_or_default(); - ::CurrentCode::insert(¶, new_code); + let prior_code_hash = ::CurrentCodeHash::get(¶).unwrap_or_default(); + let new_code_hash = new_code.hash(); + Self::increase_code_ref(&new_code_hash, &new_code); + ::CurrentCodeHash::insert(¶, new_code_hash); let now = frame_system::Pallet::::block_number(); - Self::note_past_code(para, now, now, prior_code); + Self::note_past_code(para, now, now, prior_code_hash); Self::deposit_event(Event::CurrentCodeUpdated(para)); } @@ -428,6 +446,21 @@ impl Module { outgoing_paras } + /// The validation code of live para. + pub(crate) fn current_code(para_id: &ParaId) -> Option { + CurrentCodeHash::get(para_id).and_then(|code_hash| { + let code = CodeByHash::get(&code_hash); + if code.is_none() { + log::error!( + "Pallet paras storage is inconsistent, code not found for hash {}", + code_hash, + ); + debug_assert!(false, "inconsistent paras storages"); + } + code + }) + } + // Apply all para actions queued for the given session index. // // The actions to take are based on the lifecycle of of the paras. @@ -458,8 +491,10 @@ impl Module { ParaLifecycles::insert(¶, ParaLifecycle::Parathread); } + let code_hash = genesis_data.validation_code.hash(); ::Heads::insert(¶, genesis_data.genesis_head); - ::CurrentCode::insert(¶, genesis_data.validation_code); + Self::increase_code_ref(&code_hash, &genesis_data.validation_code); + ::CurrentCodeHash::insert(¶, code_hash); } }, // Upgrade a parathread to a parachain @@ -484,12 +519,15 @@ impl Module { ::Heads::remove(¶); ::FutureCodeUpgrades::remove(¶); - ::FutureCode::remove(¶); ParaLifecycles::remove(¶); + let removed_future_code_hash = ::FutureCodeHash::take(¶); + if let Some(removed_future_code_hash) = removed_future_code_hash { + Self::decrease_code_ref(&removed_future_code_hash); + } - let removed_code = ::CurrentCode::take(¶); - if let Some(removed_code) = removed_code { - Self::note_past_code(para, now, now, removed_code); + let removed_code_hash = ::CurrentCodeHash::take(¶); + if let Some(removed_code_hash) = removed_code_hash { + Self::note_past_code(para, now, now, removed_code_hash); } outgoing.push(para); @@ -513,14 +551,14 @@ impl Module { id: ParaId, at: T::BlockNumber, now: T::BlockNumber, - old_code: ValidationCode, + old_code_hash: Hash, ) -> Weight { ::PastCodeMeta::mutate(&id, |past_meta| { past_meta.note_replacement(at, now); }); - ::PastCode::insert(&(id, at), old_code); + ::PastCodeHash::insert(&(id, at), old_code_hash); // Schedule pruning for this past-code to be removed as soon as it // exits the slashing window. @@ -559,7 +597,18 @@ impl Module { for (para_id, _) in pruning_tasks_to_do { let full_deactivate = ::PastCodeMeta::mutate(¶_id, |meta| { for pruned_repl_at in meta.prune_up_to(pruning_height) { - ::PastCode::remove(&(para_id, pruned_repl_at)); + let removed_code_hash = + ::PastCodeHash::take(&(para_id, pruned_repl_at)); + + if let Some(removed_code_hash) = removed_code_hash { + Self::decrease_code_ref(&removed_code_hash); + } else { + log::warn!( + target: "runtime::paras", + "Missing code for removed hash {:?}", + removed_code_hash, + ); + } } meta.most_recent_change().is_none() && Self::para_head(¶_id).is_none() @@ -689,8 +738,15 @@ impl Module { T::DbWeight::get().reads_writes(1, 0) } else { *up = Some(expected_at); - FutureCode::insert(&id, new_code); - T::DbWeight::get().reads_writes(1, 2) + + let new_code_hash = new_code.hash(); + let expected_at_u32 = expected_at.saturated_into(); + let log = ConsensusLog::ParaScheduleUpgradeCode(id, new_code_hash, expected_at_u32); + >::deposit_log(log.into()); + + let (reads, writes) = Self::increase_code_ref(&new_code_hash, &new_code); + FutureCodeHash::insert(&id, new_code_hash); + T::DbWeight::get().reads_writes(1 + reads, 2 + writes) } }) } @@ -710,9 +766,12 @@ impl Module { ::FutureCodeUpgrades::remove(&id); // Both should always be `Some` in this case, since a code upgrade is scheduled. - let new_code = FutureCode::take(&id).unwrap_or_default(); - let prior_code = CurrentCode::get(&id).unwrap_or_default(); - CurrentCode::insert(&id, &new_code); + let new_code_hash = FutureCodeHash::take(&id).unwrap_or_default(); + let prior_code_hash = CurrentCodeHash::get(&id).unwrap_or_default(); + CurrentCodeHash::insert(&id, &new_code_hash); + + let log = ConsensusLog::ParaUpgradeCode(id, new_code_hash); + >::deposit_log(log.into()); // `now` is only used for registering pruning as part of `fn note_past_code` let now = >::block_number(); @@ -721,7 +780,7 @@ impl Module { id, expected_at, now, - prior_code, + prior_code_hash, ); // add 1 to writes due to heads update. @@ -734,19 +793,22 @@ impl Module { } } - /// Fetches the validation code to be used when validating a block in the context of the given - /// relay-chain height. A second block number parameter may be used to tell the lookup to proceed - /// as if an intermediate parablock has been with the given relay-chain height as its context. - /// This may return past, current, or (with certain choices of `assume_intermediate`) future code. + /// Fetches the validation code hash for the validation code to be used when validating a block + /// in the context of the given relay-chain height. A second block number parameter may be used + /// to tell the lookup to proceed as if an intermediate parablock has been with the given + /// relay-chain height as its context. This may return the hash for the past, current, or + /// (with certain choices of `assume_intermediate`) future code. /// /// `assume_intermediate`, if provided, must be before `at`. This will return `None` if the validation /// code has been pruned. + /// + /// To get associated code see [`Self::validation_code_at`]. #[allow(unused)] - pub(crate) fn validation_code_at( + pub(crate) fn validation_code_hash_at( id: ParaId, at: T::BlockNumber, assume_intermediate: Option, - ) -> Option { + ) -> Option { let now = >::block_number(); let config = >::config(); @@ -761,16 +823,36 @@ impl Module { }; if upgrade_applied_intermediate { - FutureCode::get(&id) + FutureCodeHash::get(&id) } else { match Self::past_code_meta(&id).code_at(at) { None => None, - Some(UseCodeAt::Current) => CurrentCode::get(&id), - Some(UseCodeAt::ReplacedAt(replaced)) => ::PastCode::get(&(id, replaced)) + Some(UseCodeAt::Current) => CurrentCodeHash::get(&id), + Some(UseCodeAt::ReplacedAt(replaced)) => ::PastCodeHash::get(&(id, replaced)), } } } + /// Fetch validation code of para in specific context, see [`Self::validation_code_hash_at`]. + #[allow(unused)] + pub(crate) fn validation_code_at( + id: ParaId, + at: T::BlockNumber, + assume_intermediate: Option, + ) -> Option { + Self::validation_code_hash_at(id, at, assume_intermediate).and_then(|code_hash| { + let code = CodeByHash::get(&code_hash); + if code.is_none() { + log::error!( + "Pallet paras storage is inconsistent, code not found for hash {}", + code_hash, + ); + debug_assert!(false, "inconsistent paras storages"); + } + code + }) + } + /// Returns the current lifecycle state of the para. pub fn lifecycle(id: ParaId) -> Option { ParaLifecycles::get(&id) @@ -826,6 +908,34 @@ impl Module { shared::Module::::scheduled_session() } + /// Store the validation code if not already stored, and increase the number of reference. + /// + /// Returns the number of storage reads and number of storage writes. + fn increase_code_ref(code_hash: &Hash, code: &ValidationCode) -> (u64, u64) { + let reads = 1; + let mut writes = 1; + ::CodeByHashRefs::mutate(code_hash, |refs| { + if *refs == 0 { + writes += 1; + ::CodeByHash::insert(code_hash, code); + } + *refs += 1; + }); + (reads, writes) + } + + /// Decrease the number of reference ofthe validation code and remove it from storage if zero + /// is reached. + fn decrease_code_ref(code_hash: &Hash) { + let refs = ::CodeByHashRefs::get(code_hash); + if refs <= 1 { + ::CodeByHash::remove(code_hash); + ::CodeByHashRefs::remove(code_hash); + } else { + ::CodeByHashRefs::insert(code_hash, refs - 1); + } + } + /// Test function for triggering a new session in this pallet. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] pub fn test_on_new_session() { @@ -878,6 +988,16 @@ mod tests { ReplacementTimes { expected_at, activated_at } } + fn check_code_is_stored(validation_code: &ValidationCode) { + assert!(::CodeByHashRefs::get(validation_code.hash()) != 0); + assert!(::CodeByHash::contains_key(validation_code.hash())); + } + + fn check_code_is_not_stored(validation_code: &ValidationCode) { + assert!(!::CodeByHashRefs::contains_key(validation_code.hash())); + assert!(!::CodeByHash::contains_key(validation_code.hash())); + } + #[test] fn para_past_code_meta_gives_right_code() { let mut past_code = ParaPastCodeMeta::default(); @@ -981,8 +1101,10 @@ mod tests { let id = ParaId::from(0u32); let at_block: BlockNumber = 10; let included_block: BlockNumber = 12; + let validation_code = ValidationCode(vec![1, 2, 3]); - ::PastCode::insert(&(id, at_block), &ValidationCode(vec![1, 2, 3])); + Paras::increase_code_ref(&validation_code.hash(), &validation_code); + ::PastCodeHash::insert(&(id, at_block), &validation_code.hash()); ::PastCodePruning::put(&vec![(id, included_block)]); { @@ -992,15 +1114,18 @@ mod tests { } let pruned_at: BlockNumber = included_block + acceptance_period + 1; - assert_eq!(::PastCode::get(&(id, at_block)), Some(vec![1, 2, 3].into())); + assert_eq!(::PastCodeHash::get(&(id, at_block)), Some(validation_code.hash())); + check_code_is_stored(&validation_code); run_to_block(pruned_at - 1, None); - assert_eq!(::PastCode::get(&(id, at_block)), Some(vec![1, 2, 3].into())); + assert_eq!(::PastCodeHash::get(&(id, at_block)), Some(validation_code.hash())); assert_eq!(Paras::past_code_meta(&id).most_recent_change(), Some(at_block)); + check_code_is_stored(&validation_code); run_to_block(pruned_at, None); - assert!(::PastCode::get(&(id, at_block)).is_none()); + assert!(::PastCodeHash::get(&(id, at_block)).is_none()); assert!(Paras::past_code_meta(&id).most_recent_change().is_none()); + check_code_is_not_stored(&validation_code); }); } @@ -1070,8 +1195,8 @@ mod tests { let id_a = ParaId::from(0u32); let id_b = ParaId::from(1u32); - Paras::note_past_code(id_a, 10, 12, vec![1, 2, 3].into()); - Paras::note_past_code(id_b, 20, 23, vec![4, 5, 6].into()); + Paras::note_past_code(id_a, 10, 12, ValidationCode(vec![1, 2, 3]).hash()); + Paras::note_past_code(id_b, 20, 23, ValidationCode(vec![4, 5, 6]).hash()); assert_eq!(::PastCodePruning::get(), vec![(id_a, 12), (id_b, 23)]); assert_eq!( @@ -1096,11 +1221,12 @@ mod tests { let acceptance_period = 10; let validation_upgrade_delay = 5; + let original_code = ValidationCode(vec![1, 2, 3]); let paras = vec![ (0u32.into(), ParaGenesisArgs { parachain: true, genesis_head: Default::default(), - validation_code: vec![1, 2, 3].into(), + validation_code: original_code.clone(), }), ]; @@ -1118,11 +1244,13 @@ mod tests { }; new_test_ext(genesis_config).execute_with(|| { + check_code_is_stored(&original_code); + let para_id = ParaId::from(0); let new_code = ValidationCode(vec![4, 5, 6]); run_to_block(2, None); - assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into())); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); let expected_at = { // this parablock is in the context of block 1. @@ -1132,8 +1260,10 @@ mod tests { assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); - assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone())); - assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into())); + assert_eq!(::FutureCodeHash::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 }; @@ -1147,8 +1277,10 @@ mod tests { assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); - assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone())); - assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into())); + assert_eq!(::FutureCodeHash::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); } run_to_block(expected_at + 1, None); @@ -1163,12 +1295,14 @@ mod tests { Some(expected_at), ); assert_eq!( - ::PastCode::get(&(para_id, expected_at)), - Some(vec![1, 2, 3,].into()), + ::PastCodeHash::get(&(para_id, expected_at)), + Some(original_code.hash()), ); assert!(::FutureCodeUpgrades::get(¶_id).is_none()); - assert!(::FutureCode::get(¶_id).is_none()); - assert_eq!(Paras::current_code(¶_id), Some(new_code)); + assert!(::FutureCodeHash::get(¶_id).is_none()); + assert_eq!(Paras::current_code(¶_id), Some(new_code.clone())); + check_code_is_stored(&original_code); + check_code_is_stored(&new_code); } }); } @@ -1178,11 +1312,12 @@ mod tests { let acceptance_period = 10; let validation_upgrade_delay = 5; + let original_code = ValidationCode(vec![1, 2, 3]); let paras = vec![ (0u32.into(), ParaGenesisArgs { parachain: true, genesis_head: Default::default(), - validation_code: vec![1, 2, 3].into(), + validation_code: original_code.clone(), }), ]; @@ -1204,7 +1339,7 @@ mod tests { let new_code = ValidationCode(vec![4, 5, 6]); run_to_block(2, None); - assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into())); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); let expected_at = { // this parablock is in the context of block 1. @@ -1214,8 +1349,8 @@ mod tests { assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); - assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone())); - assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into())); + assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); + assert_eq!(Paras::current_code(¶_id), Some(original_code.clone())); expected_at }; @@ -1232,12 +1367,12 @@ mod tests { Some(expected_at), ); assert_eq!( - ::PastCode::get(&(para_id, expected_at)), - Some(vec![1, 2, 3,].into()), + ::PastCodeHash::get(&(para_id, expected_at)), + Some(original_code.hash()), ); assert!(::FutureCodeUpgrades::get(¶_id).is_none()); - assert!(::FutureCode::get(¶_id).is_none()); - assert_eq!(Paras::current_code(¶_id), Some(new_code)); + assert!(::FutureCodeHash::get(¶_id).is_none()); + assert_eq!(Paras::current_code(¶_id), Some(new_code.clone())); } }); } @@ -1275,11 +1410,13 @@ mod tests { Paras::schedule_code_upgrade(para_id, new_code.clone(), 8); assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(8)); - assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone())); + assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); + check_code_is_stored(&new_code); Paras::schedule_code_upgrade(para_id, newer_code.clone(), 10); assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(8)); - assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone())); + assert_eq!(::FutureCodeHash::get(¶_id), Some(new_code.hash())); + check_code_is_not_stored(&newer_code); }); } @@ -1287,11 +1424,12 @@ mod tests { fn full_parachain_cleanup_storage() { let acceptance_period = 10; + let original_code = ValidationCode(vec![1, 2, 3]); let paras = vec![ (0u32.into(), ParaGenesisArgs { parachain: true, genesis_head: Default::default(), - validation_code: vec![1, 2, 3].into(), + validation_code: original_code.clone(), }), ]; @@ -1308,11 +1446,14 @@ mod tests { }; new_test_ext(genesis_config).execute_with(|| { + check_code_is_stored(&original_code); + let para_id = ParaId::from(0); let new_code = ValidationCode(vec![4, 5, 6]); run_to_block(2, None); - assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into())); + 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. @@ -1322,8 +1463,10 @@ mod tests { assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); - assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone())); - assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into())); + assert_eq!(::FutureCodeHash::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 }; @@ -1340,8 +1483,10 @@ mod tests { assert!(Paras::past_code_meta(¶_id).most_recent_change().is_none()); assert_eq!(::FutureCodeUpgrades::get(¶_id), Some(expected_at)); - assert_eq!(::FutureCode::get(¶_id), Some(new_code.clone())); - assert_eq!(Paras::current_code(¶_id), Some(vec![1, 2, 3].into())); + assert_eq!(::FutureCodeHash::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); assert_eq!(::Heads::get(¶_id), Some(Default::default())); } @@ -1352,14 +1497,16 @@ mod tests { // cleaning up the parachain should place the current parachain code // into the past code buffer & schedule cleanup. assert_eq!(Paras::past_code_meta(¶_id).most_recent_change(), Some(3)); - assert_eq!(::PastCode::get(&(para_id, 3)), Some(vec![1, 2, 3].into())); + assert_eq!(::PastCodeHash::get(&(para_id, 3)), Some(original_code.hash())); assert_eq!(::PastCodePruning::get(), vec![(para_id, 3)]); + check_code_is_stored(&original_code); // any future upgrades haven't been used to validate yet, so those // are cleaned up immediately. assert!(::FutureCodeUpgrades::get(¶_id).is_none()); - assert!(::FutureCode::get(¶_id).is_none()); + assert!(::FutureCodeHash::get(¶_id).is_none()); assert!(Paras::current_code(¶_id).is_none()); + check_code_is_not_stored(&new_code); // run to do the final cleanup let cleaned_up_at = 3 + acceptance_period + 1; @@ -1367,8 +1514,9 @@ mod tests { // now the final cleanup: last past code cleaned up, and this triggers meta cleanup. assert_eq!(Paras::past_code_meta(¶_id), Default::default()); - assert!(::PastCode::get(&(para_id, 3)).is_none()); + assert!(::PastCodeHash::get(&(para_id, 3)).is_none()); assert!(::PastCodePruning::get().is_empty()); + check_code_is_not_stored(&original_code); }); } @@ -1564,4 +1712,26 @@ mod tests { assert_eq!(Paras::validation_code_at(para_id, 3, None), Some(new_code.clone())); }); } + + #[test] + fn code_ref_is_cleaned_correctly() { + new_test_ext(Default::default()).execute_with(|| { + let code: ValidationCode = vec![1, 2, 3].into(); + Paras::increase_code_ref(&code.hash(), &code); + Paras::increase_code_ref(&code.hash(), &code); + + assert!(CodeByHash::contains_key(code.hash())); + assert_eq!(CodeByHashRefs::get(code.hash()), 2); + + Paras::decrease_code_ref(&code.hash()); + + assert!(CodeByHash::contains_key(code.hash())); + assert_eq!(CodeByHashRefs::get(code.hash()), 1); + + Paras::decrease_code_ref(&code.hash()); + + assert!(!CodeByHash::contains_key(code.hash())); + assert!(!CodeByHashRefs::contains_key(code.hash())); + }); + } } diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs index 2714be84b8..7c999f086e 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs @@ -25,7 +25,7 @@ use primitives::v1::{ Id as ParaId, OccupiedCoreAssumption, SessionIndex, ValidationCode, CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex, GroupIndex, CandidateEvent, PersistedValidationData, SessionInfo, - InboundDownwardMessage, InboundHrmpMessage, AuthorityDiscoveryId + InboundDownwardMessage, InboundHrmpMessage, AuthorityDiscoveryId, Hash }; use crate::{initializer, inclusion, scheduler, configuration, paras, session_info, dmp, hrmp, shared}; @@ -330,3 +330,10 @@ pub fn inbound_hrmp_channels_contents( ) -> BTreeMap>> { >::inbound_hrmp_channels_contents(recipient) } + +/// Implementation for the `validation_code_by_hash` function of the runtime API. +pub fn validation_code_by_hash( + hash: Hash, +) -> Option { + >::code_by_hash(hash) +} diff --git a/polkadot/runtime/polkadot/src/lib.rs b/polkadot/runtime/polkadot/src/lib.rs index 739e560f00..5f1ce04211 100644 --- a/polkadot/runtime/polkadot/src/lib.rs +++ b/polkadot/runtime/polkadot/src/lib.rs @@ -1244,6 +1244,9 @@ sp_api::impl_runtime_apis! { BTreeMap::new() } + fn validation_code_by_hash(_hash: Hash) -> Option { + None + } } impl beefy_primitives::BeefyApi for Runtime { diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 96937fdd62..c5abaf1b98 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -951,6 +951,10 @@ sp_api::impl_runtime_apis! { ) -> BTreeMap>> { runtime_api_impl::inbound_hrmp_channels_contents::(recipient) } + + fn validation_code_by_hash(hash: Hash) -> Option { + runtime_api_impl::validation_code_by_hash::(hash) + } } impl fg_primitives::GrandpaApi for Runtime { diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 57f5afd779..1aeda45ff8 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -702,6 +702,10 @@ sp_api::impl_runtime_apis! { ) -> BTreeMap>> { runtime_impl::inbound_hrmp_channels_contents::(recipient) } + + fn validation_code_by_hash(hash: Hash) -> Option { + runtime_impl::validation_code_by_hash::(hash) + } } impl beefy_primitives::BeefyApi for Runtime { diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index fc3afc6874..3d576e7333 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -983,6 +983,10 @@ sp_api::impl_runtime_apis! { ) -> BTreeMap>> { BTreeMap::new() } + + fn validation_code_by_hash(_hash: Hash) -> Option { + None + } } impl beefy_primitives::BeefyApi for Runtime {