Skip to content
lib.rs 48.1 KiB
Newer Older
				set_id: 1,
				operating_mode: BasicOperatingMode::Normal,
			assert_ok!(Pallet::<TestRuntime>::initialize(RuntimeOrigin::root(), init_data));

			let header = test_header(1);
			let justification = make_default_justification(&header);
hacpy's avatar
hacpy committed
				Pallet::<TestRuntime>::submit_finality_proof(
hacpy's avatar
hacpy committed
					Box::new(header),
					justification,
				),
				<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(
				RuntimeOrigin::signed(1),
				Box::new(header.clone()),
			assert_ok!(result);
			assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No);

			// 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()));

			// Make sure that the authority set actually changed upon importing our header
			assert_eq!(
				<CurrentAuthoritySet<TestRuntime>>::get(),
				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(
				RuntimeOrigin::signed(1),
				Box::new(header.clone()),
				justification,
			);
			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_JUSTIFICATON_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(
				RuntimeOrigin::signed(1),
				Box::new(header.clone()),
				justification,
			);
			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!(
hacpy's avatar
hacpy committed
				Pallet::<TestRuntime>::submit_finality_proof(
hacpy's avatar
hacpy committed
					Box::new(header),
					justification
				),
				<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!(
hacpy's avatar
hacpy committed
				Pallet::<TestRuntime>::submit_finality_proof(
hacpy's avatar
hacpy committed
					Box::new(header),
					justification
				),
				<Error<TestRuntime>>::UnsupportedScheduledChange
			);
		})
	}
	#[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(
					Box::new(header),
					justification
				),
				<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(|_| ())
	fn rate_limiter_disallows_free_imports_once_limit_is_hit_in_single_block() {
		run_test(|| {
			initialize_substrate_bridge();
			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_invalid_requests_do_not_count_towards_request_count() {
		run_test(|| {
			let submit_invalid_request = || {
				let mut header = test_header(1);
				header.digest = change_log(0);
				let mut invalid_justification = make_default_justification(&header);
				invalid_justification.round = 42;
hacpy's avatar
hacpy committed
				Pallet::<TestRuntime>::submit_finality_proof(
hacpy's avatar
hacpy committed
					Box::new(header),
					invalid_justification,
				)
			for _ in 0..<TestRuntime as Config>::MaxFreeMandatoryHeadersPerBlock::get() + 1 {
				assert_err!(submit_invalid_request(), <Error<TestRuntime>>::InvalidJustification);
			}

			// 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();

			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);

			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);
	fn rate_limiter_ignores_non_mandatory_headers() {
		run_test(|| {
			initialize_substrate_bridge();

			let result = submit_finality_proof(1);
			assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
			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);

	#[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.",
			PalletOperatingMode::<TestRuntime>::storage_value_final_key().to_vec(),
			bp_header_chain::storage_keys::pallet_operating_mode_key("Grandpa").0,
		assert_eq!(
			CurrentAuthoritySet::<TestRuntime>::storage_value_final_key().to_vec(),
			bp_header_chain::storage_keys::current_authority_set_key("Grandpa").0,
		);

		assert_eq!(
			BestFinalized::<TestRuntime>::storage_value_final_key().to_vec(),
			bp_header_chain::storage_keys::best_finalized_key("Grandpa").0,
	#[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()));
	}

	#[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(
					RuntimeOrigin::root(),
					Box::new(header),
					justification,
				),
				DispatchError::BadOrigin,
			);
		})
	}