Unverified Commit ae04dafc authored by asynchronous rob's avatar asynchronous rob Committed by GitHub
Browse files

Introduce a maximum code size and head data size (#835)

* add a maximum code size and head data size

* get existing tests passing

* add tests for slots logic

* test registrar behavior

* introduce maximums and bump versions

* address review grumbles

* work around publicizing derive

* remove unneeded and wrong doc
parent 07426539
Pipeline #78485 passed with stages
in 21 minutes and 31 seconds
......@@ -121,6 +121,15 @@ pub enum LastContribution<BlockNumber> {
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
struct DeployData<Hash> {
code_hash: Hash,
code_size: u32,
initial_head_data: Vec<u8>,
}
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
#[codec(dumb_trait_bound)]
pub struct FundInfo<AccountId, Balance, Hash, BlockNumber> {
/// The parachain that this fund has funded, if there is one. As long as this is `Some`, then
/// the funds may not be withdrawn and the fund cannot be dissolved.
......@@ -150,8 +159,8 @@ pub struct FundInfo<AccountId, Balance, Hash, BlockNumber> {
/// BlockNumber.
last_slot: BlockNumber,
/// The deployment data associated with this fund, if any. Once set it may not be reset. First
/// is the code hash, second is the initial head data.
deploy_data: Option<(Hash, Vec<u8>)>,
/// is the code hash, second is the code size, third is the initial head data.
deploy_data: Option<DeployData<Hash>>,
}
decl_storage! {
......@@ -346,6 +355,7 @@ decl_module! {
fn fix_deploy_data(origin,
#[compact] index: FundIndex,
code_hash: T::Hash,
code_size: u32,
initial_head_data: Vec<u8>
) {
let who = ensure_signed(origin)?;
......@@ -354,7 +364,7 @@ decl_module! {
ensure!(fund.owner == who, Error::<T>::InvalidOrigin); // must be fund owner
ensure!(fund.deploy_data.is_none(), Error::<T>::ExistingDeployData);
fund.deploy_data = Some((code_hash, initial_head_data));
fund.deploy_data = Some(DeployData { code_hash, code_size, initial_head_data });
<Funds<T>>::insert(index, &fund);
......@@ -374,12 +384,20 @@ decl_module! {
let _ = ensure_signed(origin)?;
let mut fund = Self::funds(index).ok_or(Error::<T>::InvalidFundIndex)?;
let (code_hash, initial_head_data) = fund.clone().deploy_data.ok_or(Error::<T>::UnsetDeployData)?;
let DeployData { code_hash, code_size, initial_head_data }
= fund.clone().deploy_data.ok_or(Error::<T>::UnsetDeployData)?;
ensure!(fund.parachain.is_none(), Error::<T>::AlreadyOnboard);
fund.parachain = Some(para_id);
let fund_origin = system::RawOrigin::Signed(Self::fund_account_id(index)).into();
<slots::Module<T>>::fix_deploy_data(fund_origin, index, para_id, code_hash, initial_head_data)?;
<slots::Module<T>>::fix_deploy_data(
fund_origin,
index,
para_id,
code_hash,
code_size,
initial_head_data,
)?;
<Funds<T>>::insert(index, &fund);
......@@ -650,6 +668,9 @@ mod tests {
RefCell<HashMap<u32, (Vec<u8>, Vec<u8>)>> = RefCell::new(HashMap::new());
}
const MAX_CODE_SIZE: u32 = 100;
const MAX_HEAD_DATA_SIZE: u32 = 10;
pub struct TestParachains;
impl Registrar<u64> for TestParachains {
fn new_id() -> ParaId {
......@@ -659,6 +680,14 @@ mod tests {
})
}
fn head_data_size_allowed(head_data_size: u32) -> bool {
head_data_size <= MAX_HEAD_DATA_SIZE
}
fn code_size_allowed(code_size: u32) -> bool {
code_size <= MAX_CODE_SIZE
}
fn register_para(
id: ParaId,
_info: ParaInfo,
......@@ -875,13 +904,21 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
let fund = Crowdfund::funds(0).unwrap();
// Confirm deploy data is stored correctly
assert_eq!(fund.deploy_data, Some((<Test as system::Trait>::Hash::default(), vec![0])));
assert_eq!(
fund.deploy_data,
Some(DeployData {
code_hash: <Test as system::Trait>::Hash::default(),
code_size: 0,
initial_head_data: vec![0],
}),
);
});
}
......@@ -897,6 +934,7 @@ mod tests {
Origin::signed(2),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]),
Error::<Test>::InvalidOrigin
);
......@@ -906,6 +944,7 @@ mod tests {
Origin::signed(1),
1,
<Test as system::Trait>::Hash::default(),
0,
vec![0]),
Error::<Test>::InvalidFundIndex
);
......@@ -915,6 +954,7 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
......@@ -922,6 +962,7 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![1]),
Error::<Test>::ExistingDeployData
);
......@@ -941,6 +982,7 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
......@@ -986,6 +1028,7 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
......@@ -1013,6 +1056,7 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
......@@ -1055,6 +1099,7 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
......@@ -1196,6 +1241,7 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
assert_ok!(Crowdfund::onboard(Origin::signed(1), 0, 0.into()));
......@@ -1224,6 +1270,7 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
// Move to the end of auction...
......@@ -1262,12 +1309,14 @@ mod tests {
Origin::signed(1),
0,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
assert_ok!(Crowdfund::fix_deploy_data(
Origin::signed(2),
1,
<Test as system::Trait>::Hash::default(),
0,
vec![0]
));
......
......@@ -129,6 +129,14 @@ pub trait Trait: attestations::Trait {
/// The way that we are able to register parachains.
type Registrar: Registrar<Self::AccountId>;
/// Maximum code size for parachains, in bytes. Note that this is not
/// the entire storage burden of the parachain, as old code is stored for
/// `SlashPeriod` blocks.
type MaxCodeSize: Get<u32>;
/// Max head data size.
type MaxHeadDataSize: Get<u32>;
}
/// Origin for the parachains module.
......@@ -233,6 +241,8 @@ decl_error! {
UntaggedVotes,
/// Wrong parent head for parachain receipt.
ParentMismatch,
/// Head data was too large.
HeadDataTooLarge,
}
}
......@@ -778,6 +788,8 @@ impl<T: Trait> Module<T> {
let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]);
let mut para_block_hashes = Vec::new();
let max_head_data_size = T::MaxHeadDataSize::get();
for candidate in attested_candidates {
let para_id = candidate.parachain_index();
let validator_group = validator_groups.group_for(para_id)
......@@ -801,6 +813,11 @@ impl<T: Trait> Module<T> {
Error::<T>::VotesExceedsAuthorities,
);
ensure!(
max_head_data_size >= candidate.candidate().head_data.0.len() as _,
Error::<T>::HeadDataTooLarge,
);
let fees = candidate.candidate().fees;
T::ParachainCurrency::deduct(para_id, fees)?;
......@@ -1152,6 +1169,11 @@ mod tests {
type MaxRetries = MaxRetries;
}
parameter_types! {
pub const MaxHeadDataSize: u32 = 100;
pub const MaxCodeSize: u32 = 100;
}
impl Trait for Test {
type Origin = Origin;
type Call = Call;
......@@ -1159,6 +1181,8 @@ mod tests {
type Randomness = RandomnessCollectiveFlip;
type ActiveParachains = registrar::Module<Test>;
type Registrar = registrar::Module<Test>;
type MaxCodeSize = MaxCodeSize;
type MaxHeadDataSize = MaxHeadDataSize;
}
type Parachains = Module<Test>;
......
......@@ -46,8 +46,19 @@ pub trait Registrar<AccountId> {
/// Create a new unique parachain identity for later registration.
fn new_id() -> ParaId;
/// Checks whether the given initial head data size falls within the limit.
fn head_data_size_allowed(head_data_size: u32) -> bool;
/// Checks whether the given validation code falls within the limit.
fn code_size_allowed(code_size: u32) -> bool;
/// Register a parachain with given `code` and `initial_head_data`. `id` must not yet be registered or it will
/// result in a error.
///
/// This does not enforce any code size or initial head data limits, as these
/// are governable and parameters for parachain initialization are often
/// determined long ahead-of-time. Not checking these values ensures that changes to limits
/// do not invalidate in-progress auction winners.
fn register_para(
id: ParaId,
info: ParaInfo,
......@@ -64,6 +75,14 @@ impl<T: Trait> Registrar<T::AccountId> for Module<T> {
<NextFreeId>::mutate(|n| { let r = *n; *n = ParaId::from(u32::from(*n) + 1); r })
}
fn head_data_size_allowed(head_data_size: u32) -> bool {
head_data_size <= <T as parachains::Trait>::MaxHeadDataSize::get()
}
fn code_size_allowed(code_size: u32) -> bool {
code_size <= <T as parachains::Trait>::MaxCodeSize::get()
}
fn register_para(
id: ParaId,
info: ParaInfo,
......@@ -71,6 +90,7 @@ impl<T: Trait> Registrar<T::AccountId> for Module<T> {
initial_head_data: Vec<u8>,
) -> DispatchResult {
ensure!(!Paras::exists(id), Error::<T>::ParaAlreadyExists);
if let Scheduling::Always = info.scheduling {
Parachains::mutate(|parachains|
match parachains.binary_search(&id) {
......@@ -222,6 +242,10 @@ decl_error! {
InvalidChainId,
/// Invalid parathread ID.
InvalidThreadId,
/// Invalid para code size.
CodeTooLarge,
/// Invalid para head data size.
HeadDataTooLarge,
}
}
......@@ -232,8 +256,11 @@ decl_module! {
fn deposit_event() = default;
/// Register a parachain with given code.
/// Register a parachain with given code. Must be called by root.
/// Fails if given ID is already used.
///
/// Unlike the `Registrar` trait function of the same name, this
/// checks the code and head data against size limits.
#[weight = SimpleDispatchInfo::FixedOperational(5_000_000)]
pub fn register_para(origin,
#[compact] id: ParaId,
......@@ -242,6 +269,18 @@ decl_module! {
initial_head_data: Vec<u8>,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(
<Self as Registrar<T::AccountId>>::code_size_allowed(code.len() as _),
Error::<T>::CodeTooLarge,
);
ensure!(
<Self as Registrar<T::AccountId>>::head_data_size_allowed(
initial_head_data.len() as _
),
Error::<T>::HeadDataTooLarge,
);
<Self as Registrar<T::AccountId>>::
register_para(id, info, code, initial_head_data)
}
......@@ -267,6 +306,10 @@ decl_module! {
///
/// Must be sent from a Signed origin that is able to have ParathreadDeposit reserved.
/// `code` and `initial_head_data` are used to initialize the parathread's state.
///
/// Unlike `register_para`, this function does check that the maximum code size
/// and head data size are respected, as parathread registration is an atomic
/// action.
fn register_parathread(origin,
code: Vec<u8>,
initial_head_data: Vec<u8>,
......@@ -278,6 +321,19 @@ decl_module! {
let info = ParaInfo {
scheduling: Scheduling::Dynamic,
};
ensure!(
<Self as Registrar<T::AccountId>>::code_size_allowed(code.len() as _),
Error::<T>::CodeTooLarge,
);
ensure!(
<Self as Registrar<T::AccountId>>::head_data_size_allowed(
initial_head_data.len() as _
),
Error::<T>::HeadDataTooLarge,
);
let id = <Self as Registrar<T::AccountId>>::new_id();
let _ = <Self as Registrar<T::AccountId>>::
......@@ -709,6 +765,11 @@ mod tests {
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
}
parameter_types! {
pub const MaxHeadDataSize: u32 = 100;
pub const MaxCodeSize: u32 = 100;
}
impl parachains::Trait for Test {
type Origin = Origin;
type Call = Call;
......@@ -716,6 +777,8 @@ mod tests {
type ActiveParachains = Registrar;
type Registrar = Registrar;
type Randomness = RandomnessCollectiveFlip;
type MaxCodeSize = MaxCodeSize;
type MaxHeadDataSize = MaxHeadDataSize;
}
parameter_types! {
......@@ -929,7 +992,7 @@ mod tests {
run_to_block(10);
let h = BlakeTwo256::hash(&[2u8; 3]);
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, user_id(1), h, vec![2; 3]));
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, user_id(1), h, 3, vec![2; 3]));
assert_ok!(Slots::elaborate_deploy_data(Origin::signed(0), user_id(1), vec![2; 3]));
assert_ok!(Slots::set_offboarding(Origin::signed(user_id(1).into_account()), 1));
......@@ -1380,4 +1443,26 @@ mod tests {
);
});
}
#[test]
fn register_does_not_enforce_limits_when_registering() {
new_test_ext(vec![]).execute_with(|| {
let bad_code_size = <Test as parachains::Trait>::MaxCodeSize::get() + 1;
let bad_head_size = <Test as parachains::Trait>::MaxHeadDataSize::get() + 1;
let code = vec![1u8; bad_code_size as _];
let head_data = vec![2u8; bad_head_size as _];
assert!(!<Registrar as super::Registrar<u64>>::code_size_allowed(bad_code_size));
assert!(!<Registrar as super::Registrar<u64>>::head_data_size_allowed(bad_head_size));
let id = <Registrar as super::Registrar<u64>>::new_id();
assert_ok!(<Registrar as super::Registrar<u64>>::register_para(
id,
ParaInfo { scheduling: Scheduling::Always },
code,
head_data,
));
});
}
}
......@@ -108,8 +108,12 @@ impl<AccountId: Clone + Default + Codec> Bidder<AccountId> {
pub enum IncomingParachain<AccountId, Hash> {
/// Deploy information not yet set; just the bidder identity.
Unset(NewBidder<AccountId>),
/// Deploy information set only by code hash; so we store the code hash and head data.
Fixed { code_hash: Hash, initial_head_data: Vec<u8> },
/// Deploy information set only by code hash; so we store the code hash, code size, and head data.
///
/// The code size must be included so that checks against a maximum code size
/// can be done. If the size of the preimage of the code hash does not match
/// the given code size, it will not be possible to register the parachain.
Fixed { code_hash: Hash, code_size: u32, initial_head_data: Vec<u8> },
/// Deploy information fully set; so we store the code and head data.
Deploy { code: Vec<u8>, initial_head_data: Vec<u8> },
}
......@@ -250,6 +254,10 @@ decl_error! {
NotCurrentAuction,
/// Not an auction.
NotAuction,
/// Given code size is too large.
CodeTooLarge,
/// Given initial head data is too large.
HeadDataTooLarge,
}
}
......@@ -402,6 +410,7 @@ decl_module! {
#[compact] sub: SubId,
#[compact] para_id: ParaId,
code_hash: T::Hash,
code_size: u32,
initial_head_data: Vec<u8>
) {
let who = ensure_signed(origin)?;
......@@ -412,7 +421,17 @@ decl_module! {
} else {
Err(Error::<T>::AlreadyRegistered)?
}
let item = (starts, IncomingParachain::Fixed{code_hash, initial_head_data});
ensure!(
T::Parachains::head_data_size_allowed(initial_head_data.len() as _),
Error::<T>::HeadDataTooLarge,
);
ensure!(
T::Parachains::code_size_allowed(code_size),
Error::<T>::CodeTooLarge,
);
let item = (starts, IncomingParachain::Fixed{code_hash, code_size, initial_head_data});
<Onboarding<T>>::insert(&para_id, item);
}
......@@ -432,8 +451,10 @@ decl_module! {
pub fn elaborate_deploy_data(_origin, #[compact] para_id: ParaId, code: Vec<u8>) -> DispatchResult {
let (starts, details) = <Onboarding<T>>::get(&para_id)
.ok_or(Error::<T>::ParaNotOnboarding)?;
if let IncomingParachain::Fixed{code_hash, initial_head_data} = details {
if let IncomingParachain::Fixed{code_hash, code_size, initial_head_data} = details {
ensure!(code.len() as u32 == code_size, Error::<T>::InvalidCode);
ensure!(<T as system::Trait>::Hashing::hash(&code) == code_hash, Error::<T>::InvalidCode);
if starts > Self::lease_period_index() {
// Hasn't yet begun. Replace the on-boarding entry with the new information.
let item = (starts, IncomingParachain::Deploy{code, initial_head_data});
......@@ -918,6 +939,9 @@ mod tests {
RefCell<HashMap<u32, (Vec<u8>, Vec<u8>)>> = RefCell::new(HashMap::new());
}
const MAX_CODE_SIZE: u32 = 100;
const MAX_HEAD_DATA_SIZE: u32 = 10;
pub struct TestParachains;
impl Registrar<u64> for TestParachains {
fn new_id() -> ParaId {
......@@ -926,6 +950,15 @@ mod tests {
(*p.borrow() - 1).into()
})
}
fn head_data_size_allowed(head_data_size: u32) -> bool {
head_data_size <= MAX_HEAD_DATA_SIZE
}
fn code_size_allowed(code_size: u32) -> bool {
code_size <= MAX_CODE_SIZE
}
fn register_para(
id: ParaId,
_info: ParaInfo,
......@@ -1144,7 +1177,7 @@ mod tests {
run_to_block(9);
let h = BlakeTwo256::hash(&[42u8][..]);
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, 0.into(), h, vec![69]));
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, 0.into(), h, 1, vec![69]));
assert_ok!(Slots::elaborate_deploy_data(Origin::signed(0), 0.into(), vec![42]));
run_to_block(10);
......@@ -1169,7 +1202,7 @@ mod tests {
run_to_block(11);
let h = BlakeTwo256::hash(&[42u8][..]);
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, 0.into(), h, vec![69]));
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, 0.into(), h, 1, vec![69]));
assert_ok!(Slots::elaborate_deploy_data(Origin::signed(0), 0.into(), vec![42]));
with_parachains(|p| {
assert_eq!(p.len(), 1);
......@@ -1279,7 +1312,7 @@ mod tests {
for &(para, sub, acc) in &[(0, 0, 1), (1, 0, 2), (2, 0, 3), (3, 1, 4), (4, 1, 5)] {
let h = BlakeTwo256::hash(&[acc][..]);
assert_ok!(Slots::fix_deploy_data(Origin::signed(acc as _), sub, para.into(), h, vec![acc]));
assert_ok!(Slots::fix_deploy_data(Origin::signed(acc as _), sub, para.into(), h, 1, vec![acc]));
assert_ok!(Slots::elaborate_deploy_data(Origin::signed(0), para.into(), vec![acc]));
}
......@@ -1326,7 +1359,7 @@ mod tests {
run_to_block(10);
let h = BlakeTwo256::hash(&[1u8][..]);
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, 0.into(), h, vec![1]));
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, 0.into(), h, 1, vec![1]));
assert_ok!(Slots::elaborate_deploy_data(Origin::signed(0), 0.into(), vec![1]));
assert_ok!(Slots::new_auction(Origin::ROOT, 5, 2));
......@@ -1371,7 +1404,7 @@ mod tests {
run_to_block(10);
let h = BlakeTwo256::hash(&[1u8][..]);
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, 0.into(), h, vec![1]));
assert_ok!(Slots::fix_deploy_data(Origin::signed(1), 0, 0.into(), h, 1, vec![1]));
assert_ok!(Slots::elaborate_deploy_data(Origin::signed(0), 0.into(), vec![1]));
assert_ok!(Slots::new_auction(Origin::ROOT, 5, 2));
......@@ -1560,4 +1593,94 @@ mod tests {
];
assert_eq!(Slots::calculate_winners(winning.clone(), TestParachains::new_id), winners);
}
#[test]
fn deploy_code_too_large() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Slots::new_auction(Origin::ROOT, 5, 1));
assert_ok!(Slots::bid(Origin::signed(1), 0, 1, 1, 1, 5));
run_to_block(9);
assert_eq!(Slots::onboard_queue(1), vec![0.into()]);
run_to_block(10);
let code = vec![0u8; (MAX_CODE_SIZE + 1) as _];
let h = BlakeTwo256::hash(&code[..]);
assert_eq!(
Slots::fix_deploy_data(
Origin::signed(1), 0, 0.into(), h, code.len() as _, vec![1],
),
Err(Error::<Test>::CodeTooLarge.into()),
);
});
}
#[test]
fn deploy_maximum_ok() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Slots::new_auction(Origin::ROOT, 5, 1));
assert_ok!(Slots::bid(Origin::signed(1), 0, 1, 1, 1, 5));
run_to_block(9);
assert_eq!(Slots::onboard_queue(1), vec![0.into()]);
run_to_block(10);
let code = vec![0u8; MAX_CODE_SIZE as _];
let head_data = vec![1u8; MAX_HEAD_DATA_SIZE as _];
let h = BlakeTwo256::hash(&code[..]);
assert_ok!(Slots::fix_deploy_data(
Origin::signed(1), 0, 0.into(), h, code.len() as _, head_data,
));
});
}