Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
//! Substrate Finality Verifier Pallet
//!
//! The goal of this pallet is to provide a safe interface for writing finalized headers to an
//! external pallet which tracks headers and finality proofs. By safe, we mean that only headers
//! whose finality has been verified will be written to the underlying pallet.
//!
//! By verifying the finality of headers before writing them to storage we prevent DoS vectors in
//! which unfinalized headers get written to storage even if they don't have a chance of being
//! finalized in the future (such as in the case where a different fork gets finalized).
//!
//! The underlying pallet used for storage is assumed to be a pallet which tracks headers and
//! GRANDPA authority set changes. This information is used during the verification of GRANDPA
//! finality proofs.
#![cfg_attr(not(feature = "std"), no_std)]
// Runtime-generated enums
#![allow(clippy::large_enum_variant)]
use bp_header_chain::{justification::verify_justification, AncestryChecker, HeaderChain};
use bp_runtime::{Chain, HeaderOf};
use finality_grandpa::voter_set::VoterSet;
use frame_support::{ensure, traits::Get};
use frame_system::ensure_signed;
use sp_runtime::traits::Header as HeaderT;
#[cfg(test)]
mod mock;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
/// Header of the bridged chain.
pub(crate) type BridgedHeader<T> = HeaderOf<<T as Config>::BridgedChain>;
#[pallet::config]
pub trait Config: frame_system::Config {
/// The chain we are bridging to here.
type BridgedChain: Chain;
/// The pallet which we will use as our underlying storage mechanism.
type HeaderChain: HeaderChain<<Self::BridgedChain as Chain>::Header>;
/// The type through which we will verify that a given header is related to the last
/// finalized header in our storage pallet.
type AncestryChecker: AncestryChecker<
<Self::BridgedChain as Chain>::Header,
Vec<<Self::BridgedChain as Chain>::Header>,
>;
/// The maximum length of headers we can have in a single ancestry proof. This prevents
/// unbounded iteration when verifying proofs.
#[pallet::constant]
type MaxHeadersInSingleProof: Get<u8>;
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Verify a header is finalized according to the given finality proof.
///
/// Will use the underlying storage pallet to fetch information about the current
/// authorities and best finalized header in order to verify that the header is finalized.
///
/// If successful in verification, it will write the headers to the underlying storage
/// pallet as well as import the valid finality proof.
#[pallet::weight(0)]
origin: OriginFor<T>,
finality_target: BridgedHeader<T>,
justification: Vec<u8>,
ancestry_proof: Vec<BridgedHeader<T>>,
) -> DispatchResultWithPostInfo {
let _ = ensure_signed(origin)?;
ensure!(
ancestry_proof.len() <= T::MaxHeadersInSingleProof::get() as usize,
<Error<T>>::OversizedAncestryProof
);
let authority_set = T::HeaderChain::authority_set();
let voter_set = VoterSet::new(authority_set.authorities).ok_or(<Error<T>>::InvalidAuthoritySet)?;
let set_id = authority_set.set_id;
verify_justification::<BridgedHeader<T>>(
(finality_target.hash(), *finality_target.number()),
set_id,
voter_set,
)
.map_err(|_| <Error<T>>::InvalidJustification)?;
let best_finalized = T::HeaderChain::best_finalized();
ensure!(
T::AncestryChecker::are_ancestors(&best_finalized, &finality_target, &ancestry_proof),
<Error<T>>::InvalidAncestryProof
);
// If for whatever reason we are unable to fully import headers and the corresponding
// finality proof we want to avoid writing to the base pallet storage
use frame_support::storage::{with_transaction, TransactionOutcome};
with_transaction(|| {
for header in ancestry_proof {
if T::HeaderChain::import_header(header).is_err() {
return TransactionOutcome::Rollback(Err(<Error<T>>::FailedToWriteHeader));
}
}
if T::HeaderChain::import_finality_proof(finality_target, justification).is_err() {
return TransactionOutcome::Rollback(Err(<Error<T>>::FailedToWriteFinalityProof));
}
TransactionOutcome::Commit(Ok(()))
})?;
#[pallet::error]
pub enum Error<T> {
/// The given justification is invalid for the given header.
InvalidJustification,
/// The given ancestry proof is unable to verify that the child and ancestor headers are
/// related.
InvalidAncestryProof,
/// The authority set from the underlying header chain is invalid.
InvalidAuthoritySet,
/// Failed to write a header to the underlying header chain.
FailedToWriteHeader,
/// Failed to write finality proof to the underlying header chain.
FailedToWriteFinalityProof,
/// The given ancestry proof is too large to be verified in a single transaction.
OversizedAncestryProof,
}
}
#[cfg(test)]
mod tests {
use super::pallet::*;
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
use super::*;
use crate::mock::{run_test, test_header, Origin, TestRuntime};
use bp_test_utils::{authority_list, make_justification_for_header};
use codec::Encode;
use frame_support::{assert_err, assert_ok};
fn initialize_substrate_bridge() {
let genesis = test_header(0);
let init_data = pallet_substrate_bridge::InitializationData {
header: genesis,
authority_list: authority_list(),
set_id: 1,
scheduled_change: None,
is_halted: false,
};
assert_ok!(pallet_substrate_bridge::Module::<TestRuntime>::initialize(
Origin::root(),
init_data
));
}
#[test]
fn succesfully_imports_header_with_valid_finality_and_ancestry_proofs() {
run_test(|| {
initialize_substrate_bridge();
let child = test_header(1);
let header = test_header(2);
let set_id = 1;
let grandpa_round = 1;
let justification =
make_justification_for_header(&header, grandpa_round, set_id, &authority_list()).encode();
let ancestry_proof = vec![child, header.clone()];
assert_ok!(Module::<TestRuntime>::submit_finality_proof(
Origin::signed(1),
header.clone(),
justification,
ancestry_proof,
));
assert_eq!(
pallet_substrate_bridge::Module::<TestRuntime>::best_headers(),
vec![(*header.number(), header.hash())]
);
assert_eq!(pallet_substrate_bridge::Module::<TestRuntime>::best_finalized(), header);
})
}
#[test]
fn does_not_import_header_with_invalid_finality_proof() {
run_test(|| {
initialize_substrate_bridge();
let child = test_header(1);
let header = test_header(2);
let justification = [1u8; 32].encode();
let ancestry_proof = vec![child, header.clone()];
assert_err!(
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,),
<Error<TestRuntime>>::InvalidJustification
);
})
}
#[test]
fn does_not_import_header_with_invalid_ancestry_proof() {
run_test(|| {
initialize_substrate_bridge();
let header = test_header(2);
let set_id = 1;
let grandpa_round = 1;
let justification =
make_justification_for_header(&header, grandpa_round, set_id, &authority_list()).encode();
// For testing, we've made it so that an empty ancestry proof is invalid
let ancestry_proof = vec![];
assert_err!(
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,),
<Error<TestRuntime>>::InvalidAncestryProof
);
})
}
#[test]
fn disallows_ancestry_proofs_which_are_too_large() {
run_test(|| {
initialize_substrate_bridge();
let header = test_header(1);
let justification = [1u8; 32].encode();
let mut ancestry_proof = vec![];
let max_len = <TestRuntime as Config>::MaxHeadersInSingleProof::get();
for i in 1..=max_len + 1 {
ancestry_proof.push(test_header(i as u64));
}
assert_err!(
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,),
<Error<TestRuntime>>::OversizedAncestryProof
);
})
}
#[test]
fn disallows_invalid_authority_set() {
run_test(|| {
use bp_test_utils::{alice, bob};
let genesis = test_header(0);
let invalid_authority_list = vec![(alice(), u64::MAX), (bob(), u64::MAX)];
let init_data = pallet_substrate_bridge::InitializationData {
header: genesis,
authority_list: invalid_authority_list,
set_id: 1,
scheduled_change: None,
is_halted: false,
};
assert_ok!(pallet_substrate_bridge::Module::<TestRuntime>::initialize(
Origin::root(),
init_data
));
let header = test_header(1);
let justification = [1u8; 32].encode();
let ancestry_proof = vec![];
assert_err!(
Module::<TestRuntime>::submit_finality_proof(Origin::signed(1), header, justification, ancestry_proof,),
<Error<TestRuntime>>::InvalidAuthoritySet
);
})
}
}