diff --git a/substrate/core/finality-grandpa/primitives/src/lib.rs b/substrate/core/finality-grandpa/primitives/src/lib.rs
index f3721d5dcc7ea93f8f180511d99a4f9ac54a4c7c..77527820251837b49b996dfbf2928ee4ef7867da 100644
--- a/substrate/core/finality-grandpa/primitives/src/lib.rs
+++ b/substrate/core/finality-grandpa/primitives/src/lib.rs
@@ -93,6 +93,14 @@ pub enum ConsensusLog<N: Codec> {
 	/// Note that the authority with given index is disabled until the next change.
 	#[codec(index = "3")]
 	OnDisabled(AuthorityIndex),
+	/// A signal to pause the current authority set after the given delay.
+	/// After finalizing the block at _delay_ the authorities should stop voting.
+	#[codec(index = "4")]
+	Pause(N),
+	/// A signal to resume the current authority set after the given delay.
+	/// After authoring the block at _delay_ the authorities should resume voting.
+	#[codec(index = "5")]
+	Resume(N),
 }
 
 impl<N: Codec> ConsensusLog<N> {
@@ -100,7 +108,7 @@ impl<N: Codec> ConsensusLog<N> {
 	pub fn try_into_change(self) -> Option<ScheduledChange<N>> {
 		match self {
 			ConsensusLog::ScheduledChange(change) => Some(change),
-			ConsensusLog::ForcedChange(_, _) | ConsensusLog::OnDisabled(_) => None,
+			_ => None,
 		}
 	}
 
@@ -108,7 +116,23 @@ impl<N: Codec> ConsensusLog<N> {
 	pub fn try_into_forced_change(self) -> Option<(N, ScheduledChange<N>)> {
 		match self {
 			ConsensusLog::ForcedChange(median, change) => Some((median, change)),
-			ConsensusLog::ScheduledChange(_) | ConsensusLog::OnDisabled(_) => None,
+			_ => None,
+		}
+	}
+
+	/// Try to cast the log entry as a contained pause signal.
+	pub fn try_into_pause(self) -> Option<N> {
+		match self {
+			ConsensusLog::Pause(delay) => Some(delay),
+			_ => None,
+		}
+	}
+
+	/// Try to cast the log entry as a contained resume signal.
+	pub fn try_into_resume(self) -> Option<N> {
+		match self {
+			ConsensusLog::Resume(delay) => Some(delay),
+			_ => None,
 		}
 	}
 }
diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs
index 3bfb86c8cf4e7388f12266bbf88033d4e8f71a92..eab18591832db936a66ac5e6ae4686470531d801 100644
--- a/substrate/node/runtime/src/lib.rs
+++ b/substrate/node/runtime/src/lib.rs
@@ -69,7 +69,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
 	// and set impl_version to equal spec_version. If only runtime
 	// implementation changes and behavior does not, then leave spec_version as
 	// is and increment impl_version.
-	spec_version: 113,
+	spec_version: 114,
 	impl_version: 114,
 	apis: RUNTIME_API_VERSIONS,
 };
diff --git a/substrate/srml/grandpa/src/lib.rs b/substrate/srml/grandpa/src/lib.rs
index ba60128a897cf583112d8ee0bc0b0e70dd78608b..a62f5652d4e6707f7675ff9b47482b883111398c 100644
--- a/substrate/srml/grandpa/src/lib.rs
+++ b/substrate/srml/grandpa/src/lib.rs
@@ -91,10 +91,42 @@ impl<N: Decode> Decode for StoredPendingChange<N> {
 	}
 }
 
+/// Current state of the GRANDPA authority set. State transitions must happen in
+/// the same order of states defined below, e.g. `Paused` implies a prior
+/// `PendingPause`.
+#[derive(Decode, Encode)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+pub enum StoredState<N> {
+	/// The current authority set is live, and GRANDPA is enabled.
+	Live,
+	/// There is a pending pause event which will be enacted at the given block
+	/// height.
+	PendingPause {
+		/// Block at which the intention to pause was scheduled.
+		scheduled_at: N,
+		/// Number of blocks after which the change will be enacted.
+		delay: N
+	},
+	/// The current GRANDPA authority set is paused.
+	Paused,
+	/// There is a pending resume event which will be enacted at the given block
+	/// height.
+	PendingResume {
+		/// Block at which the intention to resume was scheduled.
+		scheduled_at: N,
+		/// Number of blocks after which the change will be enacted.
+		delay: N,
+	},
+}
+
 decl_event!(
 	pub enum Event {
 		/// New authority set has been applied.
 		NewAuthorities(Vec<(AuthorityId, u64)>),
+		/// Current authority set has been paused.
+		Paused,
+		/// Current authority set has been resumed.
+		Resumed,
 	}
 );
 
@@ -103,6 +135,9 @@ decl_storage! {
 		/// The current authority set.
 		Authorities get(authorities) config(): Vec<(AuthorityId, AuthorityWeight)>;
 
+		/// State of the current authority set.
+		State get(state): StoredState<T::BlockNumber> = StoredState::Live;
+
 		/// Pending change: (signaled at, scheduled change).
 		PendingChange: Option<StoredPendingChange<T::BlockNumber>>;
 
@@ -125,12 +160,14 @@ decl_module! {
 		}
 
 		fn on_finalize(block_number: T::BlockNumber) {
+			// check for scheduled pending authority set changes
 			if let Some(pending_change) = <PendingChange<T>>::get() {
+				// emit signal if we're at the block that scheduled the change
 				if block_number == pending_change.scheduled_at {
 					if let Some(median) = pending_change.forced {
 						Self::deposit_log(ConsensusLog::ForcedChange(
 							median,
-							ScheduledChange{
+							ScheduledChange {
 								delay: pending_change.delay,
 								next_authorities: pending_change.next_authorities.clone(),
 							}
@@ -145,6 +182,7 @@ decl_module! {
 					}
 				}
 
+				// enact the change if we've reached the enacting block
 				if block_number == pending_change.scheduled_at + pending_change.delay {
 					Authorities::put(&pending_change.next_authorities);
 					Self::deposit_event(
@@ -153,6 +191,35 @@ decl_module! {
 					<PendingChange<T>>::kill();
 				}
 			}
+
+			// check for scheduled pending state changes
+			match <State<T>>::get() {
+				StoredState::PendingPause { scheduled_at, delay } => {
+					// signal change to pause
+					if block_number == scheduled_at {
+						Self::deposit_log(ConsensusLog::Pause(delay));
+					}
+
+					// enact change to paused state
+					if block_number == scheduled_at + delay {
+						<State<T>>::put(StoredState::Paused);
+						Self::deposit_event(Event::Paused);
+					}
+				},
+				StoredState::PendingResume { scheduled_at, delay } => {
+					// signal change to resume
+					if block_number == scheduled_at {
+						Self::deposit_log(ConsensusLog::Resume(delay));
+					}
+
+					// enact change to live state
+					if block_number == scheduled_at + delay {
+						<State<T>>::put(StoredState::Live);
+						Self::deposit_event(Event::Resumed);
+					}
+				},
+				_ => {},
+			}
 		}
 	}
 }
@@ -163,6 +230,36 @@ impl<T: Trait> Module<T> {
 		Authorities::get()
 	}
 
+	pub fn schedule_pause(in_blocks: T::BlockNumber) -> Result {
+		if let StoredState::Live = <State<T>>::get() {
+			let scheduled_at = system::ChainContext::<T>::default().current_height();
+			<State<T>>::put(StoredState::PendingPause {
+				delay: in_blocks,
+				scheduled_at,
+			});
+
+			Ok(())
+		} else {
+			Err("Attempt to signal GRANDPA pause when the authority set isn't live \
+				(either paused or already pending pause).")
+		}
+	}
+
+	pub fn schedule_resume(in_blocks: T::BlockNumber) -> Result {
+		if let StoredState::Paused = <State<T>>::get() {
+			let scheduled_at = system::ChainContext::<T>::default().current_height();
+			<State<T>>::put(StoredState::PendingResume {
+				delay: in_blocks,
+				scheduled_at,
+			});
+
+			Ok(())
+		} else {
+			Err("Attempt to signal GRANDPA resume when the authority set isn't paused \
+				(either live or already pending resume).")
+		}
+	}
+
 	/// Schedule a change in the authorities.
 	///
 	/// The change will be applied at the end of execution of the block
@@ -232,6 +329,18 @@ impl<T: Trait> Module<T> {
 	{
 		Self::grandpa_log(digest).and_then(|signal| signal.try_into_forced_change())
 	}
+
+	pub fn pending_pause(digest: &DigestOf<T>)
+		-> Option<T::BlockNumber>
+	{
+		Self::grandpa_log(digest).and_then(|signal| signal.try_into_pause())
+	}
+
+	pub fn pending_resume(digest: &DigestOf<T>)
+		-> Option<T::BlockNumber>
+	{
+		Self::grandpa_log(digest).and_then(|signal| signal.try_into_resume())
+	}
 }
 
 impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
@@ -254,6 +363,7 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
 			}
 		}
 	}
+
 	fn on_disabled(i: usize) {
 		Self::deposit_log(ConsensusLog::OnDisabled(i as u64))
 	}
diff --git a/substrate/srml/grandpa/src/tests.rs b/substrate/srml/grandpa/src/tests.rs
index 763c4fce89cca0b271f16cf92052e60ff4266dba..11700fa99fa78618cf27a189d4847e6301d45f10 100644
--- a/substrate/srml/grandpa/src/tests.rs
+++ b/substrate/srml/grandpa/src/tests.rs
@@ -202,3 +202,83 @@ fn dispatch_forced_change() {
 		let _ = header;
 	});
 }
+
+#[test]
+fn schedule_pause_only_when_live() {
+	with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
+		// we schedule a pause at block 1 with delay of 1
+		System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
+		Grandpa::schedule_pause(1).unwrap();
+
+		// we've switched to the pending pause state
+		assert_eq!(
+			Grandpa::state(),
+			StoredState::PendingPause {
+				scheduled_at: 1u64,
+				delay: 1,
+			},
+		);
+
+		Grandpa::on_finalize(1);
+		let _ = System::finalize();
+
+		System::initialize(&2, &Default::default(), &Default::default(), &Default::default());
+
+		// signaling a pause now should fail
+		assert!(Grandpa::schedule_pause(1).is_err());
+
+		Grandpa::on_finalize(2);
+		let _ = System::finalize();
+
+		// after finalizing block 2 the set should have switched to paused state
+		assert_eq!(
+			Grandpa::state(),
+			StoredState::Paused,
+		);
+	});
+}
+
+#[test]
+fn schedule_resume_only_when_paused() {
+	with_externalities(&mut new_test_ext(vec![(1, 1), (2, 1), (3, 1)]), || {
+		System::initialize(&1, &Default::default(), &Default::default(), &Default::default());
+
+		// the set is currently live, resuming it is an error
+		assert!(Grandpa::schedule_resume(1).is_err());
+
+		assert_eq!(
+			Grandpa::state(),
+			StoredState::Live,
+		);
+
+		// we schedule a pause to be applied instantly
+		Grandpa::schedule_pause(0).unwrap();
+		Grandpa::on_finalize(1);
+		let _ = System::finalize();
+
+		assert_eq!(
+			Grandpa::state(),
+			StoredState::Paused,
+		);
+
+		// we schedule the set to go back live in 2 blocks
+		System::initialize(&2, &Default::default(), &Default::default(), &Default::default());
+		Grandpa::schedule_resume(2).unwrap();
+		Grandpa::on_finalize(2);
+		let _ = System::finalize();
+
+		System::initialize(&3, &Default::default(), &Default::default(), &Default::default());
+		Grandpa::on_finalize(3);
+		let _ = System::finalize();
+
+		System::initialize(&4, &Default::default(), &Default::default(), &Default::default());
+		Grandpa::on_finalize(4);
+		let _ = System::finalize();
+
+		// it should be live at block 4
+		assert_eq!(
+			Grandpa::state(),
+			StoredState::Live,
+		);
+	});
+}