diff --git a/Cargo.lock b/Cargo.lock index e935169e3c..4a38a49bed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3794,6 +3794,7 @@ dependencies = [ "sp-timestamp", "sp-transaction-pool", "sp-trie", + "sp-utils", "structopt", "substrate-browser-utils", "substrate-build-script-utils", @@ -3892,6 +3893,7 @@ dependencies = [ "sp-consensus-babe", "sp-runtime", "sp-transaction-pool", + "sp-utils", "substrate-frame-rpc-system", ] @@ -6968,6 +6970,7 @@ dependencies = [ "sc-finality-grandpa", "sc-network-test", "sc-rpc", + "sc-rpc-api", "serde", "serde_json", "sp-blockchain", @@ -6976,6 +6979,7 @@ dependencies = [ "sp-finality-grandpa", "sp-keyring", "sp-runtime", + "sp-utils", "substrate-test-runtime-client", ] diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 39df211707..71dd4e592b 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -57,6 +57,8 @@ sp-keyring = { version = "2.0.0", path = "../../../primitives/keyring" } sp-io = { version = "2.0.0", path = "../../../primitives/io" } sp-consensus = { version = "0.8.0", path = "../../../primitives/consensus/common" } sp-transaction-pool = { version = "2.0.0", path = "../../../primitives/transaction-pool" } +sp-utils = { version = "2.0.0", path = "../../../primitives/utils" } + # client dependencies sc-client-api = { version = "2.0.0", path = "../../../client/api" } diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index b15ace6181..66796ba945 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -81,6 +81,7 @@ pub fn new_partial(config: &Configuration) -> Result), select_chain.clone(), )?; let justification_import = grandpa_block_import.clone(); + let send_voter_commands = grandpa_block_import.send_voter_commands(); let (block_import, babe_link) = sc_consensus_babe::block_import( sc_consensus_babe::Config::get_or_compute(&*client)?, @@ -136,6 +137,7 @@ pub fn new_partial(config: &Configuration) -> Result { @@ -74,6 +78,8 @@ pub struct BabeDeps { /// Extra dependencies for GRANDPA pub struct GrandpaDeps { + /// send handle for grandpa voter worker + pub voter_worker_send_handle: TracingUnboundedSender::Hash, NumberFor>>, /// Voting round info. pub shared_voter_state: SharedVoterState, /// Authority set info. @@ -142,6 +148,7 @@ pub fn create_full( shared_epoch_changes, } = babe; let GrandpaDeps { + voter_worker_send_handle, shared_voter_state, shared_authority_set, justification_stream, @@ -177,10 +184,12 @@ pub fn create_full( sc_finality_grandpa_rpc::GrandpaApi::to_delegate( GrandpaRpcHandler::new( shared_authority_set, + voter_worker_send_handle, shared_voter_state, justification_stream, subscription_executor, finality_provider, + deny_unsafe, ) ) ); diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index c0c2ea8b27..2b05c3e78c 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -14,6 +14,7 @@ sc-rpc = { version = "2.0.0", path = "../../rpc" } sp-blockchain = { version = "2.0.0", path = "../../../primitives/blockchain" } sp-core = { version = "2.0.0", path = "../../../primitives/core" } sp-runtime = { version = "2.0.0", path = "../../../primitives/runtime" } +sp-utils = { version = "2.0.0", path = "../../../primitives/utils" } finality-grandpa = { version = "0.12.3", features = ["derive-codec"] } jsonrpc-core = "15.0.0" jsonrpc-core-client = "15.0.0" @@ -26,6 +27,7 @@ log = "0.4.8" derive_more = "0.99.2" parity-scale-codec = { version = "1.3.0", features = ["derive"] } sc-client-api = { version = "2.0.0", path = "../../api" } +sc-rpc-api = { version = "0.8.0", path = "../../rpc-api" } [dev-dependencies] sc-block-builder = { version = "0.8.0", path = "../../block-builder" } diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index 172473ad65..44168c4c6f 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -36,8 +36,10 @@ mod finality; mod notification; mod report; -use sc_finality_grandpa::GrandpaJustificationStream; -use sp_runtime::traits::Block as BlockT; +use sc_finality_grandpa::{GrandpaJustificationStream, VoterCommand}; +use sc_rpc_api::DenyUnsafe; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_utils::mpsc::TracingUnboundedSender; use finality::{EncodedFinalityProofs, RpcFinalityProofProvider}; use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates}; @@ -57,6 +59,10 @@ pub trait GrandpaApi { #[rpc(name = "grandpa_roundState")] fn round_state(&self) -> FutureResult; + /// Restarts the grandpa voter future + #[rpc(name = "grandpa_restartVoter")] + fn restart_voter(&self) -> Result<(), jsonrpc_core::Error>; + /// Returns the block most recently finalized by Grandpa, alongside /// side its justification. #[pubsub( @@ -95,11 +101,15 @@ pub trait GrandpaApi { /// Implements the GrandpaApi RPC trait for interacting with GRANDPA. pub struct GrandpaRpcHandler { + /// Handle to the local grandpa voter future + voter_worker_send_handle: TracingUnboundedSender>>, authority_set: AuthoritySet, voter_state: VoterState, justification_stream: GrandpaJustificationStream, manager: SubscriptionManager, finality_proof_provider: Arc, + /// Whether to deny unsafe calls + deny_unsafe: DenyUnsafe, } impl @@ -108,10 +118,12 @@ impl /// Creates a new GrandpaRpcHandler instance. pub fn new( authority_set: AuthoritySet, + voter_worker_send_handle: TracingUnboundedSender>>, voter_state: VoterState, justification_stream: GrandpaJustificationStream, executor: E, finality_proof_provider: Arc, + deny_unsafe: DenyUnsafe, ) -> Self where E: Executor01 + Send>> + Send + Sync + 'static, @@ -119,10 +131,12 @@ impl let manager = SubscriptionManager::new(Arc::new(executor)); Self { authority_set, + voter_worker_send_handle, voter_state, justification_stream, manager, finality_proof_provider, + deny_unsafe, } } } @@ -137,6 +151,12 @@ where { type Metadata = sc_rpc::Metadata; + fn restart_voter(&self) -> Result<(), jsonrpc_core::Error> { + self.deny_unsafe.check_if_safe()?; + let _ = self.voter_worker_send_handle.unbounded_send(VoterCommand::Restart); + Ok(()) + } + fn round_state(&self) -> FutureResult { let round_states = ReportedRoundStates::from(&self.authority_set, &self.voter_state); let future = async move { round_states }.boxed(); @@ -211,6 +231,7 @@ mod tests { use sp_core::crypto::Public; use sp_keyring::Ed25519Keyring; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; + use sp_utils::mpsc::tracing_unbounded; use substrate_test_runtime_client::{ runtime::{Block, Header, H256}, DefaultTestClientBuilderExt, @@ -302,18 +323,19 @@ mod tests { } } - fn setup_io_handler(voter_state: VoterState) -> ( + fn setup_io_handler(voter_state: VoterState, deny_unsafe: DenyUnsafe) -> ( jsonrpc_core::MetaIoHandler, GrandpaJustificationSender, ) where VoterState: ReportVoterState + Send + Sync + 'static, { - setup_io_handler_with_finality_proofs(voter_state, Default::default()) + setup_io_handler_with_finality_proofs(voter_state, Default::default(), deny_unsafe) } fn setup_io_handler_with_finality_proofs( voter_state: VoterState, finality_proofs: Vec>, + deny_unsafe: DenyUnsafe, ) -> ( jsonrpc_core::MetaIoHandler, GrandpaJustificationSender, @@ -322,13 +344,15 @@ mod tests { { let (justification_sender, justification_stream) = GrandpaJustificationStream::channel(); let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proofs }); - + let (voter_worker_send_handle, _) = tracing_unbounded("test_grandpa_voter_command"); let handler = GrandpaRpcHandler::new( TestAuthoritySet, + voter_worker_send_handle, voter_state, justification_stream, sc_rpc::testing::TaskExecutor, finality_proof_provider, + deny_unsafe, ); let mut io = jsonrpc_core::MetaIoHandler::default(); @@ -339,7 +363,7 @@ mod tests { #[test] fn uninitialized_rpc_handler() { - let (io, _) = setup_io_handler(EmptyVoterState); + let (io, _) = setup_io_handler(EmptyVoterState, DenyUnsafe::No); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#; let response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":1}"#; @@ -348,9 +372,31 @@ mod tests { assert_eq!(Some(response.into()), io.handle_request_sync(request, meta)); } + #[test] + fn restart_grandpa_voter() { + let (io, _) = setup_io_handler(TestVoterState, DenyUnsafe::No); + + let request = r#"{"jsonrpc":"2.0","method":"grandpa_restartVoter","params":[],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":null,"id":1}"#; + + let meta = sc_rpc::Metadata::default(); + assert_eq!(Some(response.into()), io.handle_request_sync(request, meta)); + } + + #[test] + fn restart_grandpa_voter_denied() { + let (io, _) = setup_io_handler(TestVoterState, DenyUnsafe::Yes); + + let request = r#"{"jsonrpc":"2.0","method":"grandpa_restartVoter","params":[],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":1}"#; + + let meta = sc_rpc::Metadata::default(); + assert_eq!(Some(response.into()), io.handle_request_sync(request, meta)); + } + #[test] fn working_rpc_handler() { - let (io, _) = setup_io_handler(TestVoterState); + let (io, _) = setup_io_handler(TestVoterState, DenyUnsafe::No); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#; let response = "{\"jsonrpc\":\"2.0\",\"result\":{\ @@ -379,7 +425,7 @@ mod tests { #[test] fn subscribe_and_unsubscribe_to_justifications() { - let (io, _) = setup_io_handler(TestVoterState); + let (io, _) = setup_io_handler(TestVoterState, DenyUnsafe::No); let (meta, _) = setup_session(); // Subscribe @@ -411,7 +457,7 @@ mod tests { #[test] fn subscribe_and_unsubscribe_with_wrong_id() { - let (io, _) = setup_io_handler(TestVoterState); + let (io, _) = setup_io_handler(TestVoterState, DenyUnsafe::No); let (meta, _) = setup_session(); // Subscribe @@ -483,7 +529,7 @@ mod tests { #[test] fn subscribe_and_listen_to_one_justification() { - let (io, justification_sender) = setup_io_handler(TestVoterState); + let (io, justification_sender) = setup_io_handler(TestVoterState, DenyUnsafe::No); let (meta, receiver) = setup_session(); // Subscribe @@ -529,6 +575,7 @@ mod tests { let (io, _) = setup_io_handler_with_finality_proofs( TestVoterState, finality_proofs.clone(), + DenyUnsafe::No, ); let request = "{\"jsonrpc\":\"2.0\",\"method\":\"grandpa_proveFinality\",\"params\":[\ diff --git a/client/finality-grandpa/src/import.rs b/client/finality-grandpa/src/import.rs index 04df95a318..ed131dc55e 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/finality-grandpa/src/import.rs @@ -67,6 +67,13 @@ pub struct GrandpaBlockImport { _phantom: PhantomData, } +impl GrandpaBlockImport { + /// Get the grandpa voter future command handle + pub fn send_voter_commands(&self) -> TracingUnboundedSender>> { + self.send_voter_commands.clone() + } +} + impl Clone for GrandpaBlockImport { diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index a15130942c..3321c4eaa5 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -375,20 +375,26 @@ impl BlockSyncRequester for NetworkBridge /// A new authority set along with the canonical block it changed at. #[derive(Debug)] -pub(crate) struct NewAuthoritySet { - pub(crate) canon_number: N, - pub(crate) canon_hash: H, - pub(crate) set_id: SetId, - pub(crate) authorities: AuthorityList, +pub struct NewAuthoritySet { + /// Canonical block number + pub canon_number: N, + /// Canonical block hash + pub canon_hash: H, + /// Authority set id + pub set_id: SetId, + /// The authority IDs + pub authorities: AuthorityList, } /// Commands issued to the voter. #[derive(Debug)] -pub(crate) enum VoterCommand { +pub enum VoterCommand { /// Pause the voter for given reason. Pause(String), /// New authorities. - ChangeAuthorities(NewAuthoritySet) + ChangeAuthorities(NewAuthoritySet), + /// Restart the local grandpa voter. + Restart, } impl fmt::Display for VoterCommand { @@ -396,6 +402,7 @@ impl fmt::Display for VoterCommand { match *self { VoterCommand::Pause(ref reason) => write!(f, "Pausing voter: {}", reason), VoterCommand::ChangeAuthorities(_) => write!(f, "Changing authorities"), + VoterCommand::Restart => write!(f, "Restart the local grandpa voter"), } } } @@ -1034,6 +1041,11 @@ where self.rebuild_voter(); Ok(()) } + VoterCommand::Restart => { + info!(target: "afg", "👴 restarting grandpa voter..."); + self.rebuild_voter(); + Ok(()) + } } } } diff --git a/client/finality-grandpa/src/observer.rs b/client/finality-grandpa/src/observer.rs index 6a9955aa86..70fee2e6df 100644 --- a/client/finality-grandpa/src/observer.rs +++ b/client/finality-grandpa/src/observer.rs @@ -334,6 +334,7 @@ where set_state }, + VoterCommand::Restart => return Ok(()), }.into(); self.rebuild_observer();