diff --git a/polkadot/.gitignore b/polkadot/.gitignore index 1b9aecc6b4..91f7df9514 100644 --- a/polkadot/.gitignore +++ b/polkadot/.gitignore @@ -6,5 +6,6 @@ polkadot/runtime/wasm/target/ **/._* .idea .vscode +.idea polkadot.* .DS_Store diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock index 95416920d4..0a2abcb1d8 100644 --- a/polkadot/Cargo.lock +++ b/polkadot/Cargo.lock @@ -2319,7 +2319,9 @@ dependencies = [ "substrate-keyring 0.1.0 (git+https://github.com/paritytech/substrate)", "substrate-primitives 0.1.0 (git+https://github.com/paritytech/substrate)", "substrate-serializer 0.1.0 (git+https://github.com/paritytech/substrate)", + "substrate-trie 0.4.0 (git+https://github.com/paritytech/substrate)", "tiny-keccak 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "trie-db 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/polkadot/runtime/Cargo.toml b/polkadot/runtime/Cargo.toml index df4f87d9aa..80a10a5e30 100644 --- a/polkadot/runtime/Cargo.toml +++ b/polkadot/runtime/Cargo.toml @@ -45,6 +45,8 @@ hex-literal = "0.1.0" libsecp256k1 = "0.2.1" tiny-keccak = "1.4.2" substrate-keyring = { git = "https://github.com/paritytech/substrate" } +substrate-trie = { git = "https://github.com/paritytech/substrate" } +trie-db = "0.11" [features] default = ["std"] diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs index 76466e1954..e5a483a887 100644 --- a/polkadot/runtime/src/lib.rs +++ b/polkadot/runtime/src/lib.rs @@ -70,6 +70,9 @@ extern crate polkadot_primitives as primitives; #[cfg(test)] extern crate substrate_keyring as keyring; +#[cfg(test)] +extern crate substrate_trie; + mod parachains; mod claims; diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs index 504109c3b3..8217b5d500 100644 --- a/polkadot/runtime/src/parachains.rs +++ b/polkadot/runtime/src/parachains.rs @@ -40,6 +40,12 @@ use system::ensure_inherent; pub trait Trait: session::Trait {} +// result of as trie_db::NodeCodec>::hashed_null_node() +const EMPTY_TRIE_ROOT: [u8; 32] = [ + 3, 23, 10, 46, 117, 151, 183, 183, 227, 216, 76, 5, 57, 29, 19, 154, + 98, 177, 87, 231, 135, 134, 216, 192, 130, 242, 157, 207, 76, 17, 19, 20 +]; + decl_storage! { trait Store for Module as Parachains { // Vector of all parachain IDs. @@ -110,13 +116,14 @@ decl_module! { "Submitted candidate for unregistered or out-of-order parachain {}" ); + Self::check_egress_queue_roots(&head, &active_parachains)?; + last_id = Some(head.parachain_index()); } } Self::check_attestations(&heads)?; - for head in heads { let id = head.parachain_index(); >::insert(id, head.candidate.head_data.0); @@ -248,6 +255,39 @@ impl Module { .collect()) } + fn check_egress_queue_roots(head: &AttestedCandidate, active_parachains: &[ParaId]) -> Result { + let mut last_egress_id = None; + let mut iter = active_parachains.iter(); + for (egress_para_id, root) in &head.candidate.egress_queue_roots { + // egress routes should be ascending order by parachain ID without duplicate. + ensure!( + last_egress_id.as_ref().map_or(true, |x| x < &egress_para_id), + "Egress routes out of order by ID" + ); + + // a parachain can't route to self + ensure!( + *egress_para_id != head.candidate.parachain_index, + "Parachain routing to self" + ); + + // no empty trie roots + ensure!( + *root != EMPTY_TRIE_ROOT.into(), + "Empty trie root included" + ); + + // can't route to a parachain which doesn't exist + ensure!( + iter.find(|x| x == &egress_para_id).is_some(), + "Routing to non-existent parachain" + ); + + last_egress_id = Some(egress_para_id) + } + Ok(()) + } + // check the attestations on these candidates. The candidates should have been checked // that each candidates' chain ID is valid. fn check_attestations(attested_candidates: &[AttestedCandidate]) -> Result { @@ -424,6 +464,7 @@ mod tests { use super::*; use sr_io::{TestExternalities, with_externalities}; use substrate_primitives::{H256, Blake2Hasher}; + use substrate_trie::NodeCodec; use sr_primitives::{generic, BuildStorage}; use sr_primitives::traits::{BlakeTwo256, IdentityLookup}; use primitives::{parachain::{CandidateReceipt, HeadData, ValidityAttestation}, SessionKey}; @@ -533,6 +574,22 @@ mod tests { } } + fn new_candidate_with_egress_roots(egress_queue_roots: Vec<(ParaId, H256)>) -> AttestedCandidate { + AttestedCandidate { + validity_votes: vec![], + candidate: CandidateReceipt { + parachain_index: 0.into(), + collator: Default::default(), + signature: Default::default(), + head_data: HeadData(vec![1, 2, 3]), + balance_uploads: vec![], + egress_queue_roots, + fees: 0, + block_data_hash: Default::default(), + } + } + } + #[test] fn active_parachains_should_work() { let parachains = vec![ @@ -789,4 +846,110 @@ mod tests { assert_eq!(Parachains::ingress(ParaId::from(99)), Some(Vec::new())); }); } + + #[test] + fn egress_routed_to_non_existent_parachain_is_rejected() { + // That no parachain is routed to which doesn't exist + let parachains = vec![ + (0u32.into(), vec![], vec![]), + (1u32.into(), vec![], vec![]), + ]; + + with_externalities(&mut new_test_ext(parachains), || { + system::Module::::set_random_seed([0u8; 32].into()); + // parachain 99 does not exist + let non_existent = vec![(99.into(), [1; 32].into())]; + let mut candidate = new_candidate_with_egress_roots(non_existent); + + make_attestations(&mut candidate); + + let result = Parachains::dispatch( + Call::set_heads(vec![candidate.clone()]), + Origin::INHERENT, + ); + + assert_eq!(Err("Routing to non-existent parachain"), result); + }); + } + + #[test] + fn egress_routed_to_self_is_rejected() { + // That the parachain doesn't route to self + let parachains = vec![ + (0u32.into(), vec![], vec![]), + (1u32.into(), vec![], vec![]), + ]; + + with_externalities(&mut new_test_ext(parachains), || { + system::Module::::set_random_seed([0u8; 32].into()); + // parachain 0 is self + let to_self = vec![(0.into(), [1; 32].into())]; + let mut candidate = new_candidate_with_egress_roots(to_self); + + make_attestations(&mut candidate); + + let result = Parachains::dispatch( + Call::set_heads(vec![candidate.clone()]), + Origin::INHERENT, + ); + + assert_eq!(Err("Parachain routing to self"), result); + }); + } + + #[test] + fn egress_queue_roots_out_of_order_rejected() { + // That the list of egress queue roots is in ascending order by `ParaId`. + let parachains = vec![ + (0u32.into(), vec![], vec![]), + (1u32.into(), vec![], vec![]), + ]; + + with_externalities(&mut new_test_ext(parachains), || { + system::Module::::set_random_seed([0u8; 32].into()); + // parachain 0 is self + let out_of_order = vec![(1.into(), [1; 32].into()), ((0.into(), [1; 32].into()))]; + let mut candidate = new_candidate_with_egress_roots(out_of_order); + + make_attestations(&mut candidate); + + let result = Parachains::dispatch( + Call::set_heads(vec![candidate.clone()]), + Origin::INHERENT, + ); + + assert_eq!(Err("Egress routes out of order by ID"), result); + }); + } + + #[test] + fn egress_queue_roots_empty_trie_roots_rejected() { + let parachains = vec![ + (0u32.into(), vec![], vec![]), + (1u32.into(), vec![], vec![]), + (2u32.into(), vec![], vec![]), + ]; + + with_externalities(&mut new_test_ext(parachains), || { + system::Module::::set_random_seed([0u8; 32].into()); + // parachain 0 is self + let contains_empty_trie_root = vec![(1.into(), [1; 32].into()), ((2.into(), EMPTY_TRIE_ROOT.into()))]; + let mut candidate = new_candidate_with_egress_roots(contains_empty_trie_root); + + make_attestations(&mut candidate); + + let result = Parachains::dispatch( + Call::set_heads(vec![candidate.clone()]), + Origin::INHERENT, + ); + + assert_eq!(Err("Empty trie root included"), result); + }); + } + + #[test] + fn empty_trie_root_const_is_blake2_hashed_null_node() { + let hashed_null_node = as trie_db::NodeCodec>::hashed_null_node(); + assert_eq!(hashed_null_node, EMPTY_TRIE_ROOT.into()) + } }