Newer
Older
initialize_substrate_bridge();
assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
RuntimeOrigin::root(),
BasicOperatingMode::Halted
));
assert_noop!(
submit_finality_proof(1),
Error::<TestRuntime>::BridgeModule(bp_runtime::OwnedBridgeModuleError::Halted)
);
assert_ok!(Pallet::<TestRuntime>::set_operating_mode(
RuntimeOrigin::root(),
BasicOperatingMode::Normal
));
assert_ok!(submit_finality_proof(1));
})
}
#[test]
fn pallet_rejects_header_if_not_initialized_yet() {
run_test(|| {
assert_noop!(submit_finality_proof(1), Error::<TestRuntime>::NotInitialized);
});
}
fn successfully_imports_header_with_valid_finality() {
run_test(|| {
initialize_substrate_bridge();
let header_number = 1;
let header = test_header(header_number.into());
let justification = make_default_justification(&header);
let pre_dispatch_weight = <TestRuntime as Config>::WeightInfo::submit_finality_proof(
justification.commit.precommits.len().try_into().unwrap_or(u32::MAX),
justification.votes_ancestries.len().try_into().unwrap_or(u32::MAX),
);
let result = submit_finality_proof(header_number);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
// our test config assumes 2048 max authorities and we are just using couple
let pre_dispatch_proof_size = pre_dispatch_weight.proof_size();
let actual_proof_size = result.unwrap().actual_weight.unwrap().proof_size();
assert!(actual_proof_size > 0);
assert!(
actual_proof_size < pre_dispatch_proof_size,
"Actual proof size {actual_proof_size} must be less than the pre-dispatch {pre_dispatch_proof_size}",
);
Svyatoslav Nikolsky
committed
assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
Svyatoslav Nikolsky
committed
assert_eq!(
System::events(),
vec![EventRecord {
phase: Phase::Initialization,
event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
number: *header.number(),
hash: header.hash(),
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification.clone(),
new_verification_context: None,
Svyatoslav Nikolsky
committed
}),
topics: vec![],
}],
);
assert_eq!(
Pallet::<TestRuntime>::synced_headers_grandpa_info(),
vec![StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: None
}]
#[test]
fn rejects_justification_that_skips_authority_set_transition() {
run_test(|| {
initialize_substrate_bridge();
let next_set_id = 2;
let params = JustificationGeneratorParams::<TestHeader> {
set_id: next_set_id,
..Default::default()
};
let justification = make_justification_for_header(params);
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header.clone()),
justification.clone(),
TEST_GRANDPA_SET_ID,
),
<Error<TestRuntime>>::InvalidJustification
);
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
next_set_id,
<Error<TestRuntime>>::InvalidAuthoritySetId
);
})
}
#[test]
fn does_not_import_header_with_invalid_finality_proof() {
run_test(|| {
initialize_substrate_bridge();
let mut justification = make_default_justification(&header);
justification.round = 42;
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
TEST_GRANDPA_SET_ID,
<Error<TestRuntime>>::InvalidJustification
);
})
}
#[test]
fn disallows_invalid_authority_set() {
run_test(|| {
let genesis = test_header(0);
let invalid_authority_list = vec![(ALICE.into(), u64::MAX), (BOB.into(), u64::MAX)];
let init_data = InitializationData {
header: Box::new(genesis),
authority_list: invalid_authority_list,
set_id: 1,
operating_mode: BasicOperatingMode::Normal,
assert_ok!(Pallet::<TestRuntime>::initialize(RuntimeOrigin::root(), init_data));
let justification = make_default_justification(&header);
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
TEST_GRANDPA_SET_ID,
<Error<TestRuntime>>::InvalidAuthoritySet
);
})
}
fn importing_header_ensures_that_chain_is_extended() {
run_test(|| {
initialize_substrate_bridge();
assert_ok!(submit_finality_proof(4));
assert_err!(submit_finality_proof(3), Error::<TestRuntime>::OldHeader);
assert_ok!(submit_finality_proof(5));
})
}
#[test]
fn importing_header_enacts_new_authority_set() {
run_test(|| {
initialize_substrate_bridge();
let next_set_id = 2;
let next_authorities = vec![(ALICE.into(), 1), (BOB.into(), 1)];
// Need to update the header digest to indicate that our header signals an authority set
// change. The change will be enacted when we import our header.
let mut header = test_header(2);
header.digest = change_log(0);
// Create a valid justification for the header
let justification = make_default_justification(&header);
// Let's import our test header
let result = Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header.clone()),
justification.clone(),
TEST_GRANDPA_SET_ID,
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No);
// Make sure that our header is the best finalized
Svyatoslav Nikolsky
committed
assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
// Make sure that the authority set actually changed upon importing our header
assert_eq!(
<CurrentAuthoritySet<TestRuntime>>::get(),
Svyatoslav Nikolsky
committed
StoredAuthoritySet::<TestRuntime, ()>::try_new(next_authorities, next_set_id)
.unwrap(),
// Here
assert_eq!(
System::events(),
vec![EventRecord {
phase: Phase::Initialization,
event: TestEvent::Grandpa(Event::UpdatedBestFinalizedHeader {
number: *header.number(),
hash: header.hash(),
grandpa_info: StoredHeaderGrandpaInfo {
finality_proof: justification.clone(),
new_verification_context: Some(
<CurrentAuthoritySet<TestRuntime>>::get().into()
),
},
}),
topics: vec![],
}],
);
assert_eq!(
Pallet::<TestRuntime>::synced_headers_grandpa_info(),
vec![StoredHeaderGrandpaInfo {
finality_proof: justification,
new_verification_context: Some(
<CurrentAuthoritySet<TestRuntime>>::get().into()
),
})
}
#[test]
fn relayer_pays_tx_fee_when_submitting_huge_mandatory_header() {
run_test(|| {
initialize_substrate_bridge();
// let's prepare a huge authorities change header, which is definitely above size limits
let mut header = test_header(2);
header.digest = change_log(0);
header.digest.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
let justification = make_default_justification(&header);
// without large digest item ^^^ the relayer would have paid zero transaction fee
// (`Pays::No`)
let result = Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header.clone()),
justification,
TEST_GRANDPA_SET_ID,
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
// Make sure that our header is the best finalized
assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
})
}
#[test]
fn relayer_pays_tx_fee_when_submitting_justification_with_long_ancestry_votes() {
run_test(|| {
initialize_substrate_bridge();
// let's prepare a huge authorities change header, which is definitely above weight
// limits
let mut header = test_header(2);
header.digest = change_log(0);
let justification = make_justification_for_header(JustificationGeneratorParams {
header: header.clone(),
ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY + 1,
..Default::default()
});
// without many headers in votes ancestries ^^^ the relayer would have paid zero
// transaction fee (`Pays::No`)
let result = Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Box::new(header.clone()),
justification,
TEST_GRANDPA_SET_ID,
);
assert_ok!(result);
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
// Make sure that our header is the best finalized
assert_eq!(<BestFinalized<TestRuntime>>::get().unwrap().1, header.hash());
assert!(<ImportedHeaders<TestRuntime>>::contains_key(header.hash()));
})
}
#[test]
fn importing_header_rejects_header_with_scheduled_change_delay() {
run_test(|| {
initialize_substrate_bridge();
// Need to update the header digest to indicate that our header signals an authority set
// change. However, the change doesn't happen until the next block.
let mut header = test_header(2);
header.digest = change_log(1);
// Create a valid justification for the header
let justification = make_default_justification(&header);
// Should not be allowed to import this header
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
justification,
TEST_GRANDPA_SET_ID,
<Error<TestRuntime>>::UnsupportedScheduledChange
);
})
}
#[test]
fn importing_header_rejects_header_with_forced_changes() {
run_test(|| {
initialize_substrate_bridge();
// Need to update the header digest to indicate that it signals a forced authority set
// change.
let mut header = test_header(2);
header.digest = forced_change_log(0);
// Create a valid justification for the header
let justification = make_default_justification(&header);
// Should not be allowed to import this header
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
justification,
TEST_GRANDPA_SET_ID,
<Error<TestRuntime>>::UnsupportedScheduledChange
);
})
}
Svyatoslav Nikolsky
committed
#[test]
fn importing_header_rejects_header_with_too_many_authorities() {
run_test(|| {
initialize_substrate_bridge();
// Need to update the header digest to indicate that our header signals an authority set
// change. However, the change doesn't happen until the next block.
let mut header = test_header(2);
header.digest = many_authorities_log();
// Create a valid justification for the header
let justification = make_default_justification(&header);
// Should not be allowed to import this header
assert_err!(
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
Svyatoslav Nikolsky
committed
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
Svyatoslav Nikolsky
committed
),
<Error<TestRuntime>>::TooManyAuthoritiesInSet
);
});
}
#[test]
fn parse_finalized_storage_proof_rejects_proof_on_unknown_header() {
run_test(|| {
assert_noop!(
Pallet::<TestRuntime>::storage_proof_checker(Default::default(), vec![],)
.map(|_| ()),
bp_header_chain::HeaderChainError::UnknownHeader,
);
});
}
#[test]
fn parse_finalized_storage_accepts_valid_proof() {
run_test(|| {
let (state_root, storage_proof) = bp_runtime::craft_valid_storage_proof();
let mut header = test_header(2);
header.set_state_root(state_root);
let hash = header.hash();
<BestFinalized<TestRuntime>>::put(HeaderId(2, hash));
<ImportedHeaders<TestRuntime>>::insert(hash, header.build());
Pallet::<TestRuntime>::storage_proof_checker(hash, storage_proof).map(|_| ())
);
});
}
Svyatoslav Nikolsky
committed
fn rate_limiter_disallows_free_imports_once_limit_is_hit_in_single_block() {
run_test(|| {
initialize_substrate_bridge();
Svyatoslav Nikolsky
committed
let result = submit_mandatory_finality_proof(1, 1);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(2, 2);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(3, 3);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
})
}
#[test]
fn rate_limiter_invalid_requests_do_not_count_towards_request_count() {
run_test(|| {
let submit_invalid_request = || {
Svyatoslav Nikolsky
committed
let mut header = test_header(1);
header.digest = change_log(0);
let mut invalid_justification = make_default_justification(&header);
invalid_justification.round = 42;
Pallet::<TestRuntime>::submit_finality_proof_ex(
RuntimeOrigin::signed(1),
TEST_GRANDPA_SET_ID,
};
initialize_substrate_bridge();
for _ in 0..<TestRuntime as Config>::MaxFreeHeadersPerBlock::get() + 1 {
assert_err!(submit_invalid_request(), <Error<TestRuntime>>::InvalidJustification);
}
Svyatoslav Nikolsky
committed
// Can still submit free mandatory headers afterwards
let result = submit_mandatory_finality_proof(1, 1);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(2, 2);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(3, 3);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
fn rate_limiter_allows_request_after_new_block_has_started() {
run_test(|| {
initialize_substrate_bridge();
Svyatoslav Nikolsky
committed
let result = submit_mandatory_finality_proof(1, 1);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
Svyatoslav Nikolsky
committed
let result = submit_mandatory_finality_proof(2, 2);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(3, 3);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
Svyatoslav Nikolsky
committed
let result = submit_mandatory_finality_proof(4, 4);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(5, 5);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(6, 6);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
Svyatoslav Nikolsky
committed
fn rate_limiter_ignores_non_mandatory_headers() {
run_test(|| {
initialize_substrate_bridge();
Svyatoslav Nikolsky
committed
let result = submit_finality_proof(1);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
Svyatoslav Nikolsky
committed
let result = submit_mandatory_finality_proof(2, 1);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_finality_proof_with_set_id(3, 2);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
let result = submit_mandatory_finality_proof(4, 2);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_finality_proof_with_set_id(5, 3);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
let result = submit_mandatory_finality_proof(6, 3);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
#[test]
fn may_import_non_mandatory_header_for_free() {
run_test(|| {
initialize_substrate_bridge();
// set best finalized to `100`
const BEST: u8 = 12;
fn reset_best() {
BestFinalized::<TestRuntime, ()>::set(Some(HeaderId(
BEST as _,
Default::default(),
)));
}
// non-mandatory header is imported with fee
reset_best();
let non_free_header_number = BEST + FreeHeadersInterval::get() as u8 - 1;
let result = submit_finality_proof(non_free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
// non-mandatory free header is imported without fee
reset_best();
let free_header_number = BEST + FreeHeadersInterval::get() as u8;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::No);
// another non-mandatory free header is imported without fee
let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 2;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::No);
// now the rate limiter starts charging fees even for free headers
let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 3;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
// check that we can import for free if `improved_by` is larger
// than the free interval
next_block();
reset_best();
let free_header_number = FreeHeadersInterval::get() as u8 + 42;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::No);
// check that the rate limiter shares the counter between mandatory
// and free non-mandatory headers
next_block();
reset_best();
let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 4;
let result = submit_finality_proof(free_header_number);
assert_eq!(result.unwrap().pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(free_header_number + 1, 1);
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
let result = submit_mandatory_finality_proof(free_header_number + 2, 2);
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
});
}
#[test]
fn should_prune_headers_over_headers_to_keep_parameter() {
run_test(|| {
initialize_substrate_bridge();
assert_ok!(submit_finality_proof(1));
let first_header_hash = Pallet::<TestRuntime>::best_finalized().unwrap().hash();
next_block();
assert_ok!(submit_finality_proof(2));
next_block();
assert_ok!(submit_finality_proof(3));
next_block();
assert_ok!(submit_finality_proof(4));
next_block();
assert_ok!(submit_finality_proof(5));
next_block();
assert_ok!(submit_finality_proof(6));
assert!(
!ImportedHeaders::<TestRuntime, ()>::contains_key(first_header_hash),
"First header should be pruned.",
Svyatoslav Nikolsky
committed
#[test]
fn storage_keys_computed_properly() {
Svyatoslav Nikolsky
committed
assert_eq!(
PalletOperatingMode::<TestRuntime>::storage_value_final_key().to_vec(),
bp_header_chain::storage_keys::pallet_operating_mode_key("Grandpa").0,
Svyatoslav Nikolsky
committed
);
assert_eq!(
CurrentAuthoritySet::<TestRuntime>::storage_value_final_key().to_vec(),
bp_header_chain::storage_keys::current_authority_set_key("Grandpa").0,
);
Svyatoslav Nikolsky
committed
assert_eq!(
BestFinalized::<TestRuntime>::storage_value_final_key().to_vec(),
Svyatoslav Nikolsky
committed
bp_header_chain::storage_keys::best_finalized_key("Grandpa").0,
Svyatoslav Nikolsky
committed
);
}
#[test]
fn test_bridge_grandpa_call_is_correctly_defined() {
let header = test_header(0);
let init_data = InitializationData {
header: Box::new(header.clone()),
authority_list: authority_list(),
set_id: 1,
operating_mode: BasicOperatingMode::Normal,
};
let justification = make_default_justification(&header);
let direct_initialize_call =
Call::<TestRuntime>::initialize { init_data: init_data.clone() };
let indirect_initialize_call = BridgeGrandpaCall::<TestHeader>::initialize { init_data };
assert_eq!(direct_initialize_call.encode(), indirect_initialize_call.encode());
let direct_submit_finality_proof_call = Call::<TestRuntime>::submit_finality_proof {
finality_target: Box::new(header.clone()),
justification: justification.clone(),
};
let indirect_submit_finality_proof_call =
BridgeGrandpaCall::<TestHeader>::submit_finality_proof {
finality_target: Box::new(header),
justification,
};
assert_eq!(
direct_submit_finality_proof_call.encode(),
indirect_submit_finality_proof_call.encode()
);
}
generate_owned_bridge_module_tests!(BasicOperatingMode::Normal, BasicOperatingMode::Halted);
#[test]
fn maybe_headers_to_keep_returns_correct_value() {
assert_eq!(MaybeHeadersToKeep::<TestRuntime, ()>::get(), Some(mock::HeadersToKeep::get()));
}
Svyatoslav Nikolsky
committed
#[test]
fn submit_finality_proof_requires_signed_origin() {
run_test(|| {
initialize_substrate_bridge();
let header = test_header(1);
let justification = make_default_justification(&header);
assert_noop!(
Pallet::<TestRuntime>::submit_finality_proof_ex(
Svyatoslav Nikolsky
committed
RuntimeOrigin::root(),
Box::new(header),
justification,
TEST_GRANDPA_SET_ID,
Svyatoslav Nikolsky
committed
),
DispatchError::BadOrigin,
);
})
}
#[test]
fn on_free_header_imported_never_sets_to_none() {
run_test(|| {
FreeHeadersRemaining::<TestRuntime, ()>::set(Some(2));
on_free_header_imported::<TestRuntime, ()>();
assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(1));
on_free_header_imported::<TestRuntime, ()>();
assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(0));
on_free_header_imported::<TestRuntime, ()>();
assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(0));
})
}