,
) -> Result<(OutgoingMessages, Balance), Error> {
if self.upward != upward_messages {
return Err(Error::UpwardMessagesInvalid {
expected: upward_messages.to_vec(),
got: self.upward.clone(),
});
}
if let Some(fees_charged) = fees_charged {
if self.fees_charged != fees_charged {
return Err(Error::FeesChargedInvalid {
expected: fees_charged.clone(),
got: self.fees_charged.clone(),
});
}
}
let messages = check_egress(
self.outgoing,
&egress_queue_roots[..],
)?;
Ok((messages, self.fees_charged))
}
}
/// Validate an erasure chunk against an expected root.
pub fn validate_chunk(
root: &Hash,
chunk: &ErasureChunk,
) -> Result<(), Error> {
let expected = erasure::branch_hash(root, &chunk.proof, chunk.index as usize)?;
let got = BlakeTwo256::hash(&chunk.chunk);
if expected != got {
return Err(Error::ErasureRootMismatch {
expected,
got,
})
}
Ok(())
}
/// 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(())
}
// A utility function that implements most of the collation validation logic.
//
// Reused by `validate_collation` and `validate_receipt`.
// Returns outgoing messages and fees charged for later reuse.
fn do_validation(
client: &P,
relay_parent: &BlockId,
pov_block: &PoVBlock,
para_id: ParaId,
max_block_data_size: Option,
fees_charged: Option,
head_data: &HeadData,
queue_roots: &Vec<(ParaId, Hash)>,
upward_messages: &Vec,
) -> Result<(OutgoingMessages, Balance), Error> where
P: ProvideRuntimeApi,
P::Api: ParachainHost,
{
use parachain::{IncomingMessage, ValidationParams};
if let Some(max_size) = max_block_data_size {
let block_data_size = pov_block.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 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, &pov_block.ingress)?;
let params = ValidationParams {
parent_head: chain_status.head_data.0,
block_data: pov_block.block_data.0.clone(),
ingress: pov_block.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: para_id.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 == head_data.0 {
let (messages, fees) = ext.final_checks(
upward_messages,
queue_roots,
fees_charged
)?;
Ok((messages, fees))
} else {
Err(Error::WrongHeadData {
expected: head_data.0.clone(),
got: result.head_data
})
}
}
Err(e) => Err(e.into())
}
}
/// Produce a `CandidateReceipt` and erasure encoding chunks with a given collation.
///
/// To produce a `CandidateReceipt` among other things the root of erasure encoding of
/// the block data and messages needs to be known. To avoid redundant re-computations
/// of erasure encoding this method creates an encoding and produces a candidate with
/// encoding's root returning both for re-use.
pub fn produce_receipt_and_chunks(
n_validators: usize,
pov: &PoVBlock,
messages: &OutgoingMessages,
fees: Balance,
info: &CollationInfo,
) -> Result<(CandidateReceipt, Vec), Error>
{
let erasure_chunks = erasure::obtain_chunks(
n_validators,
&pov.block_data,
Some(&messages.clone().into())
)?;
let branches = erasure::branches(erasure_chunks.as_ref());
let erasure_root = branches.root();
let chunks: Vec<_> = erasure_chunks
.iter()
.zip(branches.map(|(proof, _)| proof))
.enumerate()
.map(|(index, (chunk, proof))| ErasureChunk {
// branches borrows the original chunks, but this clone could probably be dodged.
chunk: chunk.clone(),
index: index as u32,
proof,
})
.collect();
let receipt = CandidateReceipt {
parachain_index: info.parachain_index,
collator: info.collator.clone(),
signature: info.signature.clone(),
head_data: info.head_data.clone(),
egress_queue_roots: info.egress_queue_roots.clone(),
fees,
block_data_hash: info.block_data_hash.clone(),
upward_messages: info.upward_messages.clone(),
erasure_root,
};
Ok((receipt, chunks))
}
/// Check if a given candidate receipt is valid with a given collation.
///
/// This assumes that basic validity checks have been done:
/// - Block data hash is the same as linked in collation info and a receipt.
pub fn validate_receipt(
client: &P,
relay_parent: &BlockId,
pov_block: &PoVBlock,
receipt: &CandidateReceipt,
max_block_data_size: Option,
) -> Result<(OutgoingMessages, Vec), Error> where
P: ProvideRuntimeApi,
P::Api: ParachainHost,
{
let (messages, _fees) = do_validation(
client,
relay_parent,
pov_block,
receipt.parachain_index,
max_block_data_size,
Some(receipt.fees),
&receipt.head_data,
&receipt.egress_queue_roots,
&receipt.upward_messages,
)?;
let api = client.runtime_api();
let validators = api.validators(&relay_parent)?;
let n_validators = validators.len();
let (validated_receipt, chunks) = produce_receipt_and_chunks(
n_validators,
pov_block,
&messages,
receipt.fees,
&receipt.clone().into(),
)?;
if validated_receipt.erasure_root != receipt.erasure_root {
return Err(Error::ErasureRootMismatch {
expected: validated_receipt.erasure_root,
got: receipt.erasure_root,
});
}
Ok((messages, chunks))
}
/// 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 collation info.
pub fn validate_collation(
client: &P,
relay_parent: &BlockId,
collation: &Collation,
max_block_data_size: Option,
) -> Result<(OutgoingMessages, Balance), Error> where
P: ProvideRuntimeApi,
P::Api: ParachainHost,
{
let para_id = collation.info.parachain_index;
do_validation(
client,
relay_parent,
&collation.pov,
para_id,
max_block_data_size,
None,
&collation.info.head_data,
&collation.info.egress_queue_roots,
&collation.info.upward_messages,
)
}
#[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 },
],
erasure_root: [1u8; 32].into(),
};
assert!(ext().final_checks(
&receipt.upward_messages,
&receipt.egress_queue_roots,
Some(receipt.fees),
).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 },
],
erasure_root: [1u8; 32].into(),
};
assert!(ext().final_checks(
&receipt.upward_messages,
&receipt.egress_queue_roots,
Some(receipt.fees),
).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 },
],
erasure_root: [1u8; 32].into(),
};
assert!(ext().final_checks(
&receipt.upward_messages,
&receipt.egress_queue_roots,
Some(receipt.fees),
).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 },
],
erasure_root: [1u8; 32].into(),
};
assert!(ext().final_checks(
&receipt.upward_messages,
&receipt.egress_queue_roots,
Some(receipt.fees),
).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());
}
}