{
if &self.upward != &candidate.upward_messages {
return Err(Error::UpwardMessagesInvalid {
expected: candidate.upward_messages.clone(),
got: self.upward.clone(),
});
}
if self.fees_charged != candidate.fees {
return Err(Error::FeesChargedInvalid {
expected: candidate.fees.clone(),
got: self.fees_charged.clone(),
});
}
check_egress(
self.outgoing,
&candidate.egress_queue_roots[..],
)
}
}
/// Validate incoming messages against expected roots.
pub fn validate_incoming(
roots: &StructuredUnroutedIngress,
ingress: &ConsolidatedIngress,
) -> Result<(), Error> {
if roots.len() != ingress.0.len() {
return Err(Error::IngressCanonicalityMismatch {
expected: roots.0.len(),
got: ingress.0.len()
});
}
let all_iter = roots.iter().zip(&ingress.0);
for ((_, expected_from, root), (got_id, messages)) in all_iter {
if expected_from != got_id {
return Err(Error::IngressChainMismatch {
expected: *expected_from,
got: *got_id
});
}
let got_root = message_queue_root(messages.iter().map(|msg| &msg.0[..]));
if &got_root != root {
return Err(Error::IngressRootMismatch{
id: *expected_from,
expected: *root,
got: got_root
});
}
}
Ok(())
}
/// Check whether a given collation is valid. Returns `Ok` on success, error otherwise.
///
/// This assumes that basic validity checks have been done:
/// - Block data hash is the same as linked in candidate receipt.
pub fn validate_collation(
client: &P,
relay_parent: &BlockId,
collation: &Collation,
max_block_data_size: Option,
) -> Result where
P: ProvideRuntimeApi,
P::Api: ParachainHost,
{
use parachain::{IncomingMessage, ValidationParams};
if let Some(max_size) = max_block_data_size {
let block_data_size = collation.pov.block_data.0.len() as u64;
if block_data_size > max_size {
return Err(Error::BlockDataTooBig { size: block_data_size, max_size });
}
}
let api = client.runtime_api();
let para_id = collation.receipt.parachain_index;
let validation_code = api.parachain_code(relay_parent, para_id)?
.ok_or_else(|| Error::InactiveParachain(para_id))?;
let chain_status = api.parachain_status(relay_parent, para_id)?
.ok_or_else(|| Error::InactiveParachain(para_id))?;
let roots = api.ingress(relay_parent, para_id, None)?
.ok_or_else(|| Error::InactiveParachain(para_id))?;
validate_incoming(&roots, &collation.pov.ingress)?;
let params = ValidationParams {
parent_head: chain_status.head_data.0,
block_data: collation.pov.block_data.0.clone(),
ingress: collation.pov.ingress.0.iter()
.flat_map(|&(source, ref messages)| {
messages.iter().map(move |msg| IncomingMessage {
source,
data: msg.0.clone(),
})
})
.collect()
};
let mut ext = Externalities {
parachain_index: collation.receipt.parachain_index.clone(),
outgoing: Vec::new(),
upward: Vec::new(),
free_balance: chain_status.balance,
fee_schedule: chain_status.fee_schedule,
fees_charged: 0,
};
match wasm_executor::validate_candidate(&validation_code, params, &mut ext, ExecutionMode::Remote) {
Ok(result) => {
if result.head_data == collation.receipt.head_data.0 {
ext.final_checks(&collation.receipt)
} else {
Err(Error::WrongHeadData {
expected: collation.receipt.head_data.0.clone(),
got: result.head_data
})
}
}
Err(e) => Err(e.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use parachain::wasm_executor::Externalities as ExternalitiesTrait;
use parachain::ParachainDispatchOrigin;
use polkadot_primitives::parachain::{CandidateReceipt, HeadData};
#[test]
fn compute_and_check_egress() {
let messages = vec![
TargetedMessage { target: 3.into(), data: vec![1, 1, 1] },
TargetedMessage { target: 1.into(), data: vec![1, 2, 3] },
TargetedMessage { target: 2.into(), data: vec![4, 5, 6] },
TargetedMessage { target: 1.into(), data: vec![7, 8, 9] },
];
let root_1 = message_queue_root(&[vec![1, 2, 3], vec![7, 8, 9]]);
let root_2 = message_queue_root(&[vec![4, 5, 6]]);
let root_3 = message_queue_root(&[vec![1, 1, 1]]);
assert!(check_egress(
messages.clone(),
&[(1.into(), root_1), (2.into(), root_2), (3.into(), root_3)],
).is_ok());
let egress_roots = egress_roots(&mut messages.clone()[..]);
assert!(check_egress(
messages.clone(),
&egress_roots[..],
).is_ok());
// missing root.
assert!(check_egress(
messages.clone(),
&[(1.into(), root_1), (3.into(), root_3)],
).is_err());
// extra root.
assert!(check_egress(
messages.clone(),
&[(1.into(), root_1), (2.into(), root_2), (3.into(), root_3), (4.into(), Default::default())],
).is_err());
// root mismatch.
assert!(check_egress(
messages.clone(),
&[(1.into(), root_2), (2.into(), root_1), (3.into(), root_3)],
).is_err());
}
#[test]
fn ext_rejects_local_message() {
let mut ext = Externalities {
parachain_index: 5.into(),
outgoing: Vec::new(),
upward: Vec::new(),
fees_charged: 0,
free_balance: 1_000_000,
fee_schedule: FeeSchedule {
base: 1000,
per_byte: 10,
},
};
assert!(ext.post_message(MessageRef { target: 1.into(), data: &[] }).is_ok());
assert!(ext.post_message(MessageRef { target: 5.into(), data: &[] }).is_err());
}
#[test]
fn ext_checks_upward_messages() {
let ext = || Externalities {
parachain_index: 5.into(),
outgoing: Vec::new(),
upward: vec![
UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Parachain },
],
fees_charged: 0,
free_balance: 1_000_000,
fee_schedule: FeeSchedule {
base: 1000,
per_byte: 10,
},
};
let receipt = CandidateReceipt {
parachain_index: 5.into(),
collator: Default::default(),
signature: Default::default(),
head_data: HeadData(Vec::new()),
egress_queue_roots: Vec::new(),
fees: 0,
block_data_hash: Default::default(),
upward_messages: vec![
UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Signed },
UpwardMessage{ data: vec![69], origin: ParachainDispatchOrigin::Parachain },
],
};
assert!(ext().final_checks(&receipt).is_err());
let receipt = CandidateReceipt {
parachain_index: 5.into(),
collator: Default::default(),
signature: Default::default(),
head_data: HeadData(Vec::new()),
egress_queue_roots: Vec::new(),
fees: 0,
block_data_hash: Default::default(),
upward_messages: vec![
UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Signed },
],
};
assert!(ext().final_checks(&receipt).is_err());
let receipt = CandidateReceipt {
parachain_index: 5.into(),
collator: Default::default(),
signature: Default::default(),
head_data: HeadData(Vec::new()),
egress_queue_roots: Vec::new(),
fees: 0,
block_data_hash: Default::default(),
upward_messages: vec![
UpwardMessage{ data: vec![69], origin: ParachainDispatchOrigin::Parachain },
],
};
assert!(ext().final_checks(&receipt).is_err());
let receipt = CandidateReceipt {
parachain_index: 5.into(),
collator: Default::default(),
signature: Default::default(),
head_data: HeadData(Vec::new()),
egress_queue_roots: Vec::new(),
fees: 0,
block_data_hash: Default::default(),
upward_messages: vec![
UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Parachain },
],
};
assert!(ext().final_checks(&receipt).is_ok());
}
#[test]
fn ext_checks_fees_and_updates_correctly() {
let mut ext = Externalities {
parachain_index: 5.into(),
outgoing: Vec::new(),
upward: vec![
UpwardMessage{ data: vec![42], origin: ParachainDispatchOrigin::Parachain },
],
fees_charged: 0,
free_balance: 1_000_000,
fee_schedule: FeeSchedule {
base: 1000,
per_byte: 10,
},
};
ext.apply_message_fee(100).unwrap();
assert_eq!(ext.fees_charged, 2000);
ext.post_message(MessageRef {
target: 1.into(),
data: &[0u8; 100],
}).unwrap();
assert_eq!(ext.fees_charged, 4000);
ext.post_upward_message(UpwardMessageRef {
origin: ParachainDispatchOrigin::Signed,
data: &[0u8; 100],
}).unwrap();
assert_eq!(ext.fees_charged, 6000);
ext.apply_message_fee((1_000_000 - 6000 - 1000) / 10).unwrap();
assert_eq!(ext.fees_charged, 1_000_000);
// cannot pay fee.
assert!(ext.apply_message_fee(1).is_err());
}
}