diff --git a/substrate/client/beefy/src/aux_schema.rs b/substrate/client/beefy/src/aux_schema.rs
index fafa9948c54441cbf0ca565d4872be8f2f25d2c0..217ea4769d8b9269147ec79bd3b05c446ee48966 100644
--- a/substrate/client/beefy/src/aux_schema.rs
+++ b/substrate/client/beefy/src/aux_schema.rs
@@ -28,7 +28,7 @@ use sp_runtime::traits::Block as BlockT;
 const VERSION_KEY: &[u8] = b"beefy_auxschema_version";
 const WORKER_STATE: &[u8] = b"beefy_voter_state";
 
-const CURRENT_VERSION: u32 = 1;
+const CURRENT_VERSION: u32 = 2;
 
 pub(crate) fn write_current_version<B: AuxStore>(backend: &B) -> ClientResult<()> {
 	info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION);
@@ -63,7 +63,8 @@ where
 
 	match version {
 		None => (),
-		Some(1) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE),
+		Some(1) => (), // version 1 is totally obsolete and should be simply ignored
+		Some(2) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE),
 		other =>
 			return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))),
 	}
diff --git a/substrate/client/beefy/src/lib.rs b/substrate/client/beefy/src/lib.rs
index 185f3b1ad502e7d93d8527c22067a4942fca8aae..d7de7295a4a11df54fc005b3b5118e2f08121c4a 100644
--- a/substrate/client/beefy/src/lib.rs
+++ b/substrate/client/beefy/src/lib.rs
@@ -53,7 +53,7 @@ use sp_keystore::SyncCryptoStorePtr;
 use sp_mmr_primitives::MmrApi;
 use sp_runtime::{
 	generic::BlockId,
-	traits::{Block, One, Zero},
+	traits::{Block, Zero},
 };
 use std::{collections::VecDeque, marker::PhantomData, sync::Arc};
 
@@ -346,6 +346,12 @@ where
 	R: ProvideRuntimeApi<B>,
 	R::Api: BeefyApi<B>,
 {
+	let beefy_genesis = runtime
+		.runtime_api()
+		.beefy_genesis(&BlockId::hash(best_grandpa.hash()))
+		.ok()
+		.flatten()
+		.ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into()))?;
 	// Walk back the imported blocks and initialize voter either, at the last block with
 	// a BEEFY justification, or at pallet genesis block; voter will resume from there.
 	let blockchain = backend.blockchain();
@@ -378,20 +384,19 @@ where
 			break state
 		}
 
-		if *header.number() == One::one() {
-			// We've reached chain genesis, initialize voter here.
-			let genesis_num = *header.number();
+		if *header.number() == beefy_genesis {
+			// We've reached BEEFY genesis, initialize voter here.
 			let genesis_set = expect_validator_set(runtime, BlockId::hash(header.hash()))
 				.and_then(genesis_set_sanity_check)?;
 			info!(
 				target: LOG_TARGET,
 				"🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \
 				Starting voting rounds at block {:?}, genesis validator set {:?}.",
-				genesis_num,
+				beefy_genesis,
 				genesis_set,
 			);
 
-			sessions.push_front(Rounds::new(genesis_num, genesis_set));
+			sessions.push_front(Rounds::new(beefy_genesis, genesis_set));
 			break PersistedState::checked_new(best_grandpa, Zero::zero(), sessions, min_block_delta)
 				.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?
 		}
@@ -448,13 +453,16 @@ where
 					None => break
 				};
 				let at = BlockId::hash(notif.header.hash());
-				if let Some(active) = runtime.runtime_api().validator_set(&at).ok().flatten() {
-					// Beefy pallet available, return best grandpa at the time.
-					info!(
-						target: LOG_TARGET, "🥩 BEEFY pallet available: block {:?} validator set {:?}",
-						notif.header.number(), active
-					);
-					return Ok(notif.header)
+				if let Some(start) = runtime.runtime_api().beefy_genesis(&at).ok().flatten() {
+					if *notif.header.number() >= start {
+						// Beefy pallet available, return header for best grandpa at the time.
+						info!(
+							target: LOG_TARGET,
+							"🥩 BEEFY pallet available: block {:?} beefy genesis {:?}",
+							notif.header.number(), start
+						);
+						return Ok(notif.header)
+					}
 				}
 			},
 			_ = gossip_engine => {
diff --git a/substrate/client/beefy/src/tests.rs b/substrate/client/beefy/src/tests.rs
index 2642035ba5dd552ea6984cf47ad191b05dd8c889..008137d9a9965a58ec06e90dad704fb01a6659da 100644
--- a/substrate/client/beefy/src/tests.rs
+++ b/substrate/client/beefy/src/tests.rs
@@ -77,8 +77,8 @@ const BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x42);
 type BeefyBlockImport = crate::BeefyBlockImport<
 	Block,
 	substrate_test_runtime_client::Backend,
-	two_validators::TestApi,
-	BlockImportAdapter<PeersClient, sp_api::TransactionFor<two_validators::TestApi, Block>>,
+	TestApi,
+	BlockImportAdapter<PeersClient, sp_api::TransactionFor<TestApi, Block>>,
 >;
 
 pub(crate) type BeefyValidatorSet = ValidatorSet<AuthorityId>;
@@ -198,12 +198,12 @@ impl TestNetFactory for BeefyTestNet {
 		Option<BoxJustificationImport<Block>>,
 		Self::PeerData,
 	) {
+		let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob];
+		let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
+		let api = Arc::new(TestApi::with_validator_set(&validator_set));
 		let inner = BlockImportAdapter::new(client.clone());
-		let (block_import, voter_links, rpc_links) = beefy_block_import_and_links(
-			inner,
-			client.as_backend(),
-			Arc::new(two_validators::TestApi {}),
-		);
+		let (block_import, voter_links, rpc_links) =
+			beefy_block_import_and_links(inner, client.as_backend(), api);
 		let peer_data = PeerData {
 			beefy_rpc_links: Mutex::new(Some(rpc_links)),
 			beefy_voter_links: Mutex::new(Some(voter_links)),
@@ -230,79 +230,79 @@ impl TestNetFactory for BeefyTestNet {
 	}
 }
 
-macro_rules! create_test_api {
-    ( $api_name:ident, mmr_root: $mmr_root:expr, $($inits:expr),+ ) => {
-		pub(crate) mod $api_name {
-			use super::*;
-
-			#[derive(Clone, Default)]
-			pub(crate) struct TestApi {}
+#[derive(Clone)]
+pub(crate) struct TestApi {
+	pub beefy_genesis: u64,
+	pub validator_set: BeefyValidatorSet,
+	pub mmr_root_hash: MmrRootHash,
+}
 
-			// compiler gets confused and warns us about unused inner
-			#[allow(dead_code)]
-			pub(crate) struct RuntimeApi {
-				inner: TestApi,
-			}
+impl TestApi {
+	pub fn new(
+		beefy_genesis: u64,
+		validator_set: &BeefyValidatorSet,
+		mmr_root_hash: MmrRootHash,
+	) -> Self {
+		TestApi { beefy_genesis, validator_set: validator_set.clone(), mmr_root_hash }
+	}
 
-			impl ProvideRuntimeApi<Block> for TestApi {
-				type Api = RuntimeApi;
-				fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> {
-					RuntimeApi { inner: self.clone() }.into()
-				}
-			}
-			sp_api::mock_impl_runtime_apis! {
-				impl BeefyApi<Block> for RuntimeApi {
-					fn validator_set() -> Option<BeefyValidatorSet> {
-						BeefyValidatorSet::new(make_beefy_ids(&[$($inits),+]), 0)
-					}
-				}
-
-				impl MmrApi<Block, MmrRootHash, NumberFor<Block>> for RuntimeApi {
-					fn mmr_root() -> Result<MmrRootHash, MmrError> {
-						Ok($mmr_root)
-					}
-
-					fn generate_proof(
-						_block_numbers: Vec<u64>,
-						_best_known_block_number: Option<u64>
-					) -> Result<(Vec<EncodableOpaqueLeaf>, Proof<MmrRootHash>), MmrError> {
-						unimplemented!()
-					}
-
-					fn verify_proof(_leaves: Vec<EncodableOpaqueLeaf>, _proof: Proof<MmrRootHash>) -> Result<(), MmrError> {
-						unimplemented!()
-					}
-
-					fn verify_proof_stateless(
-						_root: MmrRootHash,
-						_leaves: Vec<EncodableOpaqueLeaf>,
-						_proof: Proof<MmrRootHash>
-					) -> Result<(), MmrError> {
-						unimplemented!()
-					}
-				}
-			}
+	pub fn with_validator_set(validator_set: &BeefyValidatorSet) -> Self {
+		TestApi {
+			beefy_genesis: 1,
+			validator_set: validator_set.clone(),
+			mmr_root_hash: GOOD_MMR_ROOT,
 		}
-	};
+	}
+}
+
+// compiler gets confused and warns us about unused inner
+#[allow(dead_code)]
+pub(crate) struct RuntimeApi {
+	inner: TestApi,
+}
+
+impl ProvideRuntimeApi<Block> for TestApi {
+	type Api = RuntimeApi;
+	fn runtime_api(&self) -> ApiRef<Self::Api> {
+		RuntimeApi { inner: self.clone() }.into()
+	}
 }
+sp_api::mock_impl_runtime_apis! {
+	impl BeefyApi<Block> for RuntimeApi {
+		fn beefy_genesis() -> Option<NumberFor<Block>> {
+			Some(self.inner.beefy_genesis)
+		}
+
+		fn validator_set() -> Option<BeefyValidatorSet> {
+			Some(self.inner.validator_set.clone())
+		}
+	}
+
+	impl MmrApi<Block, MmrRootHash, NumberFor<Block>> for RuntimeApi {
+		fn mmr_root() -> Result<MmrRootHash, MmrError> {
+			Ok(self.inner.mmr_root_hash)
+		}
+
+		fn generate_proof(
+			_block_numbers: Vec<u64>,
+			_best_known_block_number: Option<u64>
+		) -> Result<(Vec<EncodableOpaqueLeaf>, Proof<MmrRootHash>), MmrError> {
+			unimplemented!()
+		}
+
+		fn verify_proof(_leaves: Vec<EncodableOpaqueLeaf>, _proof: Proof<MmrRootHash>) -> Result<(), MmrError> {
+			unimplemented!()
+		}
 
-create_test_api!(two_validators, mmr_root: GOOD_MMR_ROOT, BeefyKeyring::Alice, BeefyKeyring::Bob);
-create_test_api!(
-	four_validators,
-	mmr_root: GOOD_MMR_ROOT,
-	BeefyKeyring::Alice,
-	BeefyKeyring::Bob,
-	BeefyKeyring::Charlie,
-	BeefyKeyring::Dave
-);
-create_test_api!(
-	bad_four_validators,
-	mmr_root: BAD_MMR_ROOT,
-	BeefyKeyring::Alice,
-	BeefyKeyring::Bob,
-	BeefyKeyring::Charlie,
-	BeefyKeyring::Dave
-);
+		fn verify_proof_stateless(
+			_root: MmrRootHash,
+			_leaves: Vec<EncodableOpaqueLeaf>,
+			_proof: Proof<MmrRootHash>
+		) -> Result<(), MmrError> {
+			unimplemented!()
+		}
+	}
+}
 
 fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) {
 	header.digest_mut().push(DigestItem::Consensus(
@@ -332,9 +332,9 @@ pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStoreP
 fn voter_init_setup(
 	net: &mut BeefyTestNet,
 	finality: &mut futures::stream::Fuse<FinalityNotifications<Block>>,
+	api: &TestApi,
 ) -> sp_blockchain::Result<PersistedState<Block>> {
 	let backend = net.peer(0).client().as_backend();
-	let api = Arc::new(crate::tests::two_validators::TestApi {});
 	let known_peers = Arc::new(Mutex::new(KnownPeers::new()));
 	let gossip_validator =
 		Arc::new(crate::communication::gossip::GossipValidator::new(known_peers));
@@ -345,9 +345,9 @@ fn voter_init_setup(
 		None,
 	);
 	let best_grandpa =
-		futures::executor::block_on(wait_for_runtime_pallet(&*api, &mut gossip_engine, finality))
+		futures::executor::block_on(wait_for_runtime_pallet(api, &mut gossip_engine, finality))
 			.unwrap();
-	load_or_init_voter_state(&*backend, &*api, best_grandpa, 1)
+	load_or_init_voter_state(&*backend, api, best_grandpa, 1)
 }
 
 // Spawns beefy voters. Returns a future to spawn on the runtime.
@@ -357,7 +357,7 @@ fn initialize_beefy<API>(
 	min_block_delta: u32,
 ) -> impl Future<Output = ()>
 where
-	API: ProvideRuntimeApi<Block> + Default + Sync + Send,
+	API: ProvideRuntimeApi<Block> + Sync + Send,
 	API::Api: BeefyApi<Block> + MmrApi<Block, MmrRootHash, NumberFor<Block>>,
 {
 	let tasks = FuturesUnordered::new();
@@ -544,7 +544,7 @@ async fn beefy_finalizing_blocks() {
 
 	let mut net = BeefyTestNet::new(2);
 
-	let api = Arc::new(two_validators::TestApi {});
+	let api = Arc::new(TestApi::with_validator_set(&validator_set));
 	let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
 	tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta));
 
@@ -582,7 +582,7 @@ async fn lagging_validators() {
 	let min_block_delta = 1;
 
 	let mut net = BeefyTestNet::new(2);
-	let api = Arc::new(two_validators::TestApi {});
+	let api = Arc::new(TestApi::with_validator_set(&validator_set));
 	let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
 	tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta));
 
@@ -660,7 +660,7 @@ async fn correct_beefy_payload() {
 	let mut net = BeefyTestNet::new(4);
 
 	// Alice, Bob, Charlie will vote on good payloads
-	let good_api = Arc::new(four_validators::TestApi {});
+	let good_api = Arc::new(TestApi::new(1, &validator_set, GOOD_MMR_ROOT));
 	let good_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]
 		.iter()
 		.enumerate()
@@ -669,7 +669,7 @@ async fn correct_beefy_payload() {
 	tokio::spawn(initialize_beefy(&mut net, good_peers, min_block_delta));
 
 	// Dave will vote on bad mmr roots
-	let bad_api = Arc::new(bad_four_validators::TestApi {});
+	let bad_api = Arc::new(TestApi::new(1, &validator_set, BAD_MMR_ROOT));
 	let bad_peers = vec![(3, &BeefyKeyring::Dave, bad_api)];
 	tokio::spawn(initialize_beefy(&mut net, bad_peers, min_block_delta));
 
@@ -714,6 +714,8 @@ async fn beefy_importing_blocks() {
 	sp_tracing::try_init_simple();
 
 	let mut net = BeefyTestNet::new(2);
+	let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob];
+	let good_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
 
 	let client = net.peer(0).client().clone();
 	let (mut block_import, _, peer_data) = net.make_block_import(client.clone());
@@ -769,9 +771,7 @@ async fn beefy_importing_blocks() {
 	// Import with valid justification.
 	let parent_id = BlockId::Number(1);
 	let block_num = 2;
-	let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob];
-	let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
-	let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys);
+	let proof = crate::justification::tests::new_finality_proof(block_num, &good_set, keys);
 	let versioned_proof: VersionedFinalityProof<NumberFor<Block>, Signature> = proof.into();
 	let encoded = versioned_proof.encode();
 	let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded)));
@@ -814,8 +814,8 @@ async fn beefy_importing_blocks() {
 	let parent_id = BlockId::Number(2);
 	let block_num = 3;
 	let keys = &[BeefyKeyring::Alice];
-	let validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap();
-	let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys);
+	let bad_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap();
+	let proof = crate::justification::tests::new_finality_proof(block_num, &bad_set, keys);
 	let versioned_proof: VersionedFinalityProof<NumberFor<Block>, Signature> = proof.into();
 	let encoded = versioned_proof.encode();
 	let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded)));
@@ -865,7 +865,7 @@ async fn voter_initialization() {
 	let min_block_delta = 10;
 
 	let mut net = BeefyTestNet::new(2);
-	let api = Arc::new(two_validators::TestApi {});
+	let api = Arc::new(TestApi::with_validator_set(&validator_set));
 	let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
 	tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta));
 
@@ -898,7 +898,7 @@ async fn on_demand_beefy_justification_sync() {
 	let mut net = BeefyTestNet::new(4);
 
 	// Alice, Bob, Charlie start first and make progress through voting.
-	let api = Arc::new(four_validators::TestApi {});
+	let api = Arc::new(TestApi::with_validator_set(&validator_set));
 	let fast_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie];
 	let voting_peers =
 		fast_peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
@@ -977,8 +977,9 @@ async fn should_initialize_voter_at_genesis() {
 	// finalize 13 without justifications
 	net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap();
 
-	// load persistent state - nothing in DB, should init at session boundary
-	let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap();
+	let api = TestApi::with_validator_set(&validator_set);
+	// load persistent state - nothing in DB, should init at genesis
+	let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap();
 
 	// Test initialization at session boundary.
 	// verify voter initialized with two sessions starting at blocks 1 and 10
@@ -1006,6 +1007,54 @@ async fn should_initialize_voter_at_genesis() {
 	assert_eq!(state, persisted_state);
 }
 
+#[tokio::test]
+async fn should_initialize_voter_at_custom_genesis() {
+	let keys = &[BeefyKeyring::Alice];
+	let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
+	let mut net = BeefyTestNet::new(1);
+	let backend = net.peer(0).client().as_backend();
+	// custom pallet genesis is block number 7
+	let custom_pallet_genesis = 7;
+	let api = TestApi::new(custom_pallet_genesis, &validator_set, GOOD_MMR_ROOT);
+
+	// push 15 blocks with `AuthorityChange` digests every 10 blocks
+	let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await;
+
+	let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
+
+	// finalize 3, 5, 8 without justifications
+	net.peer(0).client().as_client().finalize_block(hashes[3], None).unwrap();
+	net.peer(0).client().as_client().finalize_block(hashes[5], None).unwrap();
+	net.peer(0).client().as_client().finalize_block(hashes[8], None).unwrap();
+
+	// load persistent state - nothing in DB, should init at genesis
+	let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap();
+
+	// Test initialization at session boundary.
+	// verify voter initialized with single session starting at block `custom_pallet_genesis` (7)
+	let sessions = persisted_state.voting_oracle().sessions();
+	assert_eq!(sessions.len(), 1);
+	assert_eq!(sessions[0].session_start(), custom_pallet_genesis);
+	let rounds = persisted_state.active_round().unwrap();
+	assert_eq!(rounds.session_start(), custom_pallet_genesis);
+	assert_eq!(rounds.validator_set_id(), validator_set.id());
+
+	// verify next vote target is mandatory block 7
+	assert_eq!(persisted_state.best_beefy_block(), 0);
+	assert_eq!(persisted_state.best_grandpa_block(), 8);
+	assert_eq!(
+		persisted_state
+			.voting_oracle()
+			.voting_target(persisted_state.best_beefy_block(), 13),
+		Some(custom_pallet_genesis)
+	);
+
+	// verify state also saved to db
+	assert!(verify_persisted_version(&*backend));
+	let state = load_persistent(&*backend).unwrap().unwrap();
+	assert_eq!(state, persisted_state);
+}
+
 #[tokio::test]
 async fn should_initialize_voter_when_last_final_is_session_boundary() {
 	let keys = &[BeefyKeyring::Alice];
@@ -1038,8 +1087,9 @@ async fn should_initialize_voter_when_last_final_is_session_boundary() {
 	// Test corner-case where session boundary == last beefy finalized,
 	// expect rounds initialized at last beefy finalized 10.
 
+	let api = TestApi::with_validator_set(&validator_set);
 	// load persistent state - nothing in DB, should init at session boundary
-	let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap();
+	let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap();
 
 	// verify voter initialized with single session starting at block 10
 	assert_eq!(persisted_state.voting_oracle().sessions().len(), 1);
@@ -1095,8 +1145,9 @@ async fn should_initialize_voter_at_latest_finalized() {
 
 	// Test initialization at last BEEFY finalized.
 
+	let api = TestApi::with_validator_set(&validator_set);
 	// load persistent state - nothing in DB, should init at last BEEFY finalized
-	let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap();
+	let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap();
 
 	// verify voter initialized with single session starting at block 12
 	assert_eq!(persisted_state.voting_oracle().sessions().len(), 1);
@@ -1119,3 +1170,37 @@ async fn should_initialize_voter_at_latest_finalized() {
 	let state = load_persistent(&*backend).unwrap().unwrap();
 	assert_eq!(state, persisted_state);
 }
+
+#[tokio::test]
+async fn beefy_finalizing_after_pallet_genesis() {
+	sp_tracing::try_init_simple();
+
+	let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob];
+	let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap();
+	let session_len = 10;
+	let min_block_delta = 1;
+	let pallet_genesis = 15;
+
+	let mut net = BeefyTestNet::new(2);
+
+	let api = Arc::new(TestApi::new(pallet_genesis, &validator_set, GOOD_MMR_ROOT));
+	let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
+	tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta));
+
+	// push 42 blocks including `AuthorityChange` digests every 10 blocks.
+	let hashes = net.generate_blocks_and_sync(42, session_len, &validator_set, true).await;
+
+	let net = Arc::new(Mutex::new(net));
+	let peers = peers.into_iter().enumerate();
+
+	// Minimum BEEFY block delta is 1.
+
+	// GRANDPA finalize blocks leading up to BEEFY pallet genesis -> BEEFY should finalize nothing.
+	finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[1..14], &[]).await;
+
+	// GRANDPA finalize block #16 -> BEEFY should finalize #15 (genesis mandatory) and #16.
+	finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[16]], &[15, 16]).await;
+
+	// GRANDPA finalize #21 -> BEEFY finalize #20 (mandatory) and #21
+	finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[21]], &[20, 21]).await;
+}
diff --git a/substrate/client/beefy/src/worker.rs b/substrate/client/beefy/src/worker.rs
index 19ab52f520225103589b2e5e3b6d7afc350394e8..4d057bffc0cd98947b708ed27039d6dd9f5bb2f1 100644
--- a/substrate/client/beefy/src/worker.rs
+++ b/substrate/client/beefy/src/worker.rs
@@ -994,8 +994,8 @@ pub(crate) mod tests {
 		communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream},
 		keystore::tests::Keyring,
 		tests::{
-			create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi,
-			BeefyPeer, BeefyTestNet,
+			create_beefy_keystore, get_beefy_streams, make_beefy_ids, BeefyPeer, BeefyTestNet,
+			TestApi,
 		},
 		BeefyRPCLinks, KnownPeers,
 	};
@@ -1068,7 +1068,7 @@ pub(crate) mod tests {
 		};
 
 		let backend = peer.client().as_backend();
-		let api = Arc::new(TestApi {});
+		let api = Arc::new(TestApi::with_validator_set(&genesis_validator_set));
 		let network = peer.network_service().clone();
 		let known_peers = Arc::new(Mutex::new(KnownPeers::new()));
 		let gossip_validator = Arc::new(GossipValidator::new(known_peers.clone()));
diff --git a/substrate/frame/beefy/src/lib.rs b/substrate/frame/beefy/src/lib.rs
index 4cb23107e78439d22bb0065c3ddb3ced2cc2de08..86c8763a51dd31b1a6e1501a1398fa3b153831b1 100644
--- a/substrate/frame/beefy/src/lib.rs
+++ b/substrate/frame/beefy/src/lib.rs
@@ -49,6 +49,7 @@ pub use pallet::*;
 pub mod pallet {
 	use super::*;
 	use frame_support::pallet_prelude::*;
+	use frame_system::pallet_prelude::BlockNumberFor;
 
 	#[pallet::config]
 	pub trait Config: frame_system::Config {
@@ -91,15 +92,32 @@ pub mod pallet {
 	pub(super) type NextAuthorities<T: Config> =
 		StorageValue<_, BoundedVec<T::BeefyId, T::MaxAuthorities>, ValueQuery>;
 
+	/// Block number where BEEFY consensus is enabled/started.
+	/// If changing this, make sure `Self::ValidatorSetId` is also reset to
+	/// `GENESIS_AUTHORITY_SET_ID` in the state of the new block number configured here.
+	#[pallet::storage]
+	#[pallet::getter(fn genesis_block)]
+	pub(super) type GenesisBlock<T: Config> =
+		StorageValue<_, Option<BlockNumberFor<T>>, ValueQuery>;
+
 	#[pallet::genesis_config]
 	pub struct GenesisConfig<T: Config> {
+		/// Initial set of BEEFY authorities.
 		pub authorities: Vec<T::BeefyId>,
+		/// Block number where BEEFY consensus should start.
+		/// Should match the session where initial authorities are active.
+		/// *Note:* Ideally use block number where GRANDPA authorities are changed,
+		/// to guarantee the client gets a finality notification for exactly this block.
+		pub genesis_block: Option<BlockNumberFor<T>>,
 	}
 
 	#[cfg(feature = "std")]
 	impl<T: Config> Default for GenesisConfig<T> {
 		fn default() -> Self {
-			Self { authorities: Vec::new() }
+			// BEEFY genesis will be first BEEFY-MANDATORY block,
+			// use block number one instead of chain-genesis.
+			let genesis_block = Some(sp_runtime::traits::One::one());
+			Self { authorities: Vec::new(), genesis_block }
 		}
 	}
 
@@ -110,6 +128,7 @@ pub mod pallet {
 				// we panic here as runtime maintainers can simply reconfigure genesis and restart
 				// the chain easily
 				.expect("Authorities vec too big");
+			<GenesisBlock<T>>::put(&self.genesis_block);
 		}
 	}
 }
diff --git a/substrate/primitives/beefy/src/lib.rs b/substrate/primitives/beefy/src/lib.rs
index eb7b8db89b2145cb8d5f880623b6b521a68d57ee..8c219040b35238f06af5e9be5d0f5f20b60e522e 100644
--- a/substrate/primitives/beefy/src/lib.rs
+++ b/substrate/primitives/beefy/src/lib.rs
@@ -43,7 +43,7 @@ use codec::{Codec, Decode, Encode};
 use scale_info::TypeInfo;
 use sp_application_crypto::RuntimeAppPublic;
 use sp_core::H256;
-use sp_runtime::traits::Hash;
+use sp_runtime::traits::{Hash, NumberFor};
 use sp_std::prelude::*;
 
 /// Key type for BEEFY module.
@@ -201,6 +201,9 @@ sp_api::decl_runtime_apis! {
 	/// API necessary for BEEFY voters.
 	pub trait BeefyApi
 	{
+		/// Return the block number where BEEFY consensus is enabled/started
+		fn beefy_genesis() -> Option<NumberFor<Block>>;
+
 		/// Return the current active BEEFY validator set
 		fn validator_set() -> Option<ValidatorSet<crypto::AuthorityId>>;
 	}
diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs
index 1c2d6ec8a0c81402b2465692c09bfd0986aaec9e..c1a66eb6acb5cdb52f3da607c7cb49db0d55cf3b 100644
--- a/substrate/test-utils/runtime/src/lib.rs
+++ b/substrate/test-utils/runtime/src/lib.rs
@@ -973,6 +973,10 @@ cfg_if! {
 			}
 
 			impl beefy_primitives::BeefyApi<Block> for Runtime {
+				fn beefy_genesis() -> Option<BlockNumber> {
+					None
+				}
+
 				fn validator_set() -> Option<beefy_primitives::ValidatorSet<beefy_primitives::crypto::AuthorityId>> {
 					None
 				}