Extract syncing protocol from sc-network (#12828)

* Move import queue out of `sc-network`

Add supplementary asynchronous API for the import queue which means
it can be run as an independent task and communicated with through
the `ImportQueueService`.

This commit removes removes block and justification imports from
`sc-network` and provides `ChainSync` with a handle to import queue so
it can import blocks and justifications. Polling of the import queue is
moved complete out of `sc-network` and `sc_consensus::Link` is
implemented for `ChainSyncInterfaceHandled` so the import queue
can still influence the syncing process.

* Move stuff to SyncingEngine

* Move `ChainSync` instanation to `SyncingEngine`

Some of the tests have to be rewritten

* Move peer hashmap to `SyncingEngine`

* Let `SyncingEngine` to implement `ChainSyncInterface`

* Introduce `SyncStatusProvider`

* Move `sync_peer_(connected|disconnected)` to `SyncingEngine`

* Implement `SyncEventStream`

Remove `SyncConnected`/`SyncDisconnected` events from
`NetworkEvenStream` and provide those events through
`ChainSyncInterface` instead.

Modify BEEFY/GRANDPA/transactions protocol and `NetworkGossip` to take
`SyncEventStream` object which they listen to for incoming sync peer
events.

* Introduce `ChainSyncInterface`

This interface provides a set of miscellaneous functions that other
subsystems can use to query, for example, the syncing status.

* Move event stream polling to `SyncingEngine`

Subscribe to `NetworkStreamEvent` and poll the incoming notifications
and substream events from `SyncingEngine`.

The code needs refactoring.

* Make `SyncingEngine` into an asynchronous runner

This commits removes the last hard dependency of syncing from
`sc-network` meaning the protocol now lives completely outside of
`sc-network`, ignoring the hardcoded peerset entry which will be
addressed in the future.

Code needs a lot of refactoring.

* Fix warnings

* Code refactoring

* Use `SyncingService` for BEEFY

* Use `SyncingService` for GRANDPA

* Remove call delegation from `NetworkService`

* Remove `ChainSyncService`

* Remove `ChainSync` service tests

They were written for the sole purpose of verifying that `NetworWorker`
continues to function while the calls are being dispatched to
`ChainSync`.

* Refactor code

* Refactor code

* Update client/finality-grandpa/src/communication/tests.rs

Co-authored-by: Anton <anton.kalyaev@gmail.com>

* Fix warnings

* Apply review comments

* Fix docs

* Fix test

* cargo-fmt

* Update client/network/sync/src/engine.rs

Co-authored-by: Anton <anton.kalyaev@gmail.com>

* Update client/network/sync/src/engine.rs

Co-authored-by: Anton <anton.kalyaev@gmail.com>

* Add missing docs

* Refactor code

---------

Co-authored-by: Anton <anton.kalyaev@gmail.com>
This commit is contained in:
Aaro Altonen
2023-03-06 18:33:38 +02:00
committed by GitHub
parent 8adde84330
commit 1a7f5be07f
57 changed files with 2904 additions and 2877 deletions
+127 -100
View File
@@ -46,24 +46,24 @@ use sc_consensus::{
ForkChoiceStrategy, ImportQueue, ImportResult, JustificationImport, JustificationSyncLink,
LongestChain, Verifier,
};
use sc_network::{
config::{NetworkConfiguration, RequestResponseConfig, Role, SyncMode},
Multiaddr, NetworkService, NetworkWorker,
};
use sc_network::{Multiaddr, NetworkService, NetworkWorker};
use sc_network_common::{
config::{
MultiaddrWithPeerId, NonDefaultSetConfig, NonReservedPeerMode, ProtocolId, TransportConfig,
MultiaddrWithPeerId, NetworkConfiguration, NonDefaultSetConfig, NonReservedPeerMode,
ProtocolId, RequestResponseConfig, Role, SyncMode, TransportConfig,
},
protocol::{role::Roles, ProtocolName},
service::{NetworkBlock, NetworkStateInfo, NetworkSyncForkRequest},
service::{NetworkBlock, NetworkEventStream, NetworkStateInfo, NetworkSyncForkRequest},
sync::warp::{
AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncParams, WarpSyncProvider,
},
};
use sc_network_light::light_client_requests::handler::LightClientRequestHandler;
use sc_network_sync::{
block_request_handler::BlockRequestHandler, service::network::NetworkServiceProvider,
state_request_handler::StateRequestHandler, warp_request_handler, ChainSync,
block_request_handler::BlockRequestHandler,
service::{chain_sync::SyncingService, network::NetworkServiceProvider},
state_request_handler::StateRequestHandler,
warp_request_handler,
};
use sc_service::client::Client;
use sp_blockchain::{
@@ -241,7 +241,8 @@ pub struct Peer<D, BlockImport> {
block_import: BlockImportAdapter<BlockImport>,
select_chain: Option<LongestChain<substrate_test_runtime_client::Backend, Block>>,
backend: Option<Arc<substrate_test_runtime_client::Backend>>,
network: NetworkWorker<Block, <Block as BlockT>::Hash, PeersFullClient>,
network: NetworkWorker<Block, <Block as BlockT>::Hash>,
sync_service: Arc<SyncingService<Block>>,
imported_blocks_stream: Pin<Box<dyn Stream<Item = BlockImportNotification<Block>> + Send>>,
finality_notification_stream: Pin<Box<dyn Stream<Item = FinalityNotification<Block>> + Send>>,
listen_addr: Multiaddr,
@@ -259,7 +260,7 @@ where
/// Returns true if we're major syncing.
pub fn is_major_syncing(&self) -> bool {
self.network.service().is_major_syncing()
self.sync_service.is_major_syncing()
}
// Returns a clone of the local SelectChain, only available on full nodes
@@ -275,23 +276,23 @@ where
}
/// Returns the number of downloaded blocks.
pub fn num_downloaded_blocks(&self) -> usize {
self.network.num_downloaded_blocks()
pub async fn num_downloaded_blocks(&self) -> usize {
self.sync_service.num_downloaded_blocks().await.unwrap()
}
/// Returns true if we have no peer.
pub fn is_offline(&self) -> bool {
self.num_peers() == 0
self.sync_service.is_offline()
}
/// Request a justification for the given block.
pub fn request_justification(&self, hash: &<Block as BlockT>::Hash, number: NumberFor<Block>) {
self.network.service().request_justification(hash, number);
self.sync_service.request_justification(hash, number);
}
/// Announces an important block on the network.
pub fn announce_block(&self, hash: <Block as BlockT>::Hash, data: Option<Vec<u8>>) {
self.network.service().announce_block(hash, data);
self.sync_service.announce_block(hash, data);
}
/// Request explicit fork sync.
@@ -301,7 +302,7 @@ where
hash: <Block as BlockT>::Hash,
number: NumberFor<Block>,
) {
self.network.service().set_sync_fork_request(peers, hash, number);
self.sync_service.set_sync_fork_request(peers, hash, number);
}
/// Add blocks to the peer -- edit the block before adding
@@ -402,14 +403,14 @@ where
futures::executor::block_on(self.block_import.import_block(import_block, cache))
.expect("block_import failed");
if announce_block {
self.network.service().announce_block(hash, None);
self.sync_service.announce_block(hash, None);
}
hashes.push(hash);
at = hash;
}
if inform_sync_about_new_best_block {
self.network.new_best_block_imported(
self.sync_service.new_best_block_imported(
at,
*full_client.header(at).ok().flatten().unwrap().number(),
);
@@ -525,8 +526,12 @@ where
self.network.service()
}
pub fn sync_service(&self) -> &Arc<SyncingService<Block>> {
&self.sync_service
}
/// Get a reference to the network worker.
pub fn network(&self) -> &NetworkWorker<Block, <Block as BlockT>::Hash, PeersFullClient> {
pub fn network(&self) -> &NetworkWorker<Block, <Block as BlockT>::Hash> {
&self.network
}
@@ -728,13 +733,13 @@ pub struct FullPeerConfig {
}
#[async_trait::async_trait]
pub trait TestNetFactory: Default + Sized
pub trait TestNetFactory: Default + Sized + Send
where
<Self::BlockImport as BlockImport<Block>>::Transaction: Send,
{
type Verifier: 'static + Verifier<Block>;
type BlockImport: BlockImport<Block, Error = ConsensusError> + Clone + Send + Sync + 'static;
type PeerData: Default;
type PeerData: Default + Send;
/// This one needs to be implemented!
fn make_verifier(&self, client: PeersClient, peer_data: &Self::PeerData) -> Self::Verifier;
@@ -742,6 +747,7 @@ where
/// Get reference to peer.
fn peer(&mut self, i: usize) -> &mut Peer<Self::PeerData, Self::BlockImport>;
fn peers(&self) -> &Vec<Peer<Self::PeerData, Self::BlockImport>>;
fn peers_mut(&mut self) -> &mut Vec<Peer<Self::PeerData, Self::BlockImport>>;
fn mut_peers<F: FnOnce(&mut Vec<Peer<Self::PeerData, Self::BlockImport>>)>(
&mut self,
closure: F,
@@ -900,31 +906,25 @@ where
let (chain_sync_network_provider, chain_sync_network_handle) =
NetworkServiceProvider::new();
let (chain_sync, chain_sync_service, block_announce_config) = ChainSync::new(
match network_config.sync_mode {
SyncMode::Full => sc_network_common::sync::SyncMode::Full,
SyncMode::Fast { skip_proofs, storage_chain_mode } =>
sc_network_common::sync::SyncMode::LightState {
skip_proofs,
storage_chain_mode,
},
SyncMode::Warp => sc_network_common::sync::SyncMode::Warp,
},
client.clone(),
protocol_id.clone(),
&fork_id,
Roles::from(if config.is_authority { &Role::Authority } else { &Role::Full }),
block_announce_validator,
network_config.max_parallel_downloads,
Some(warp_sync_params),
None,
chain_sync_network_handle,
import_queue.service(),
block_request_protocol_config.name.clone(),
state_request_protocol_config.name.clone(),
Some(warp_protocol_config.name.clone()),
)
.unwrap();
let (engine, sync_service, block_announce_config) =
sc_network_sync::engine::SyncingEngine::new(
Roles::from(if config.is_authority { &Role::Authority } else { &Role::Full }),
client.clone(),
None,
&network_config,
protocol_id.clone(),
&fork_id,
block_announce_validator,
Some(warp_sync_params),
chain_sync_network_handle,
import_queue.service(),
block_request_protocol_config.name.clone(),
state_request_protocol_config.name.clone(),
Some(warp_protocol_config.name.clone()),
)
.unwrap();
let sync_service_import_queue = Box::new(sync_service.clone());
let sync_service = Arc::new(sync_service.clone());
let network = NetworkWorker::new(sc_network::config::Params {
role: if config.is_authority { Role::Authority } else { Role::Full },
@@ -935,8 +935,6 @@ where
chain: client.clone(),
protocol_id,
fork_id,
chain_sync: Box::new(chain_sync),
chain_sync_service: Box::new(chain_sync_service.clone()),
metrics_registry: None,
block_announce_config,
request_response_protocol_configs: [
@@ -955,8 +953,14 @@ where
tokio::spawn(async move {
chain_sync_network_provider.run(service).await;
});
tokio::spawn(async move {
import_queue.run(Box::new(chain_sync_service)).await;
import_queue.run(sync_service_import_queue).await;
});
let service = network.service().clone();
tokio::spawn(async move {
engine.run(service.event_stream("syncing")).await;
});
self.mut_peers(move |peers| {
@@ -979,6 +983,7 @@ where
block_import,
verifier,
network,
sync_service,
listen_addr,
});
});
@@ -989,48 +994,6 @@ where
tokio::spawn(f);
}
/// Polls the testnet until all nodes are in sync.
///
/// Must be executed in a task context.
fn poll_until_sync(&mut self, cx: &mut FutureContext) -> Poll<()> {
self.poll(cx);
// Return `NotReady` if there's a mismatch in the highest block number.
let mut highest = None;
for peer in self.peers().iter() {
if peer.is_major_syncing() || peer.network.num_queued_blocks() != 0 {
return Poll::Pending
}
if peer.network.num_sync_requests() != 0 {
return Poll::Pending
}
match (highest, peer.client.info().best_hash) {
(None, b) => highest = Some(b),
(Some(ref a), ref b) if a == b => {},
(Some(_), _) => return Poll::Pending,
}
}
Poll::Ready(())
}
/// Polls the testnet until theres' no activiy of any kind.
///
/// Must be executed in a task context.
fn poll_until_idle(&mut self, cx: &mut FutureContext) -> Poll<()> {
self.poll(cx);
for peer in self.peers().iter() {
if peer.is_major_syncing() || peer.network.num_queued_blocks() != 0 {
return Poll::Pending
}
if peer.network.num_sync_requests() != 0 {
return Poll::Pending
}
}
Poll::Ready(())
}
/// Polls the testnet until all peers are connected to each other.
///
/// Must be executed in a task context.
@@ -1045,15 +1008,61 @@ where
Poll::Pending
}
/// Run the network until we are sync'ed.
async fn is_in_sync(&mut self) -> bool {
let mut highest = None;
let peers = self.peers_mut();
for peer in peers {
if peer.sync_service.is_major_syncing() ||
peer.sync_service.num_queued_blocks().await.unwrap() != 0
{
return false
}
if peer.sync_service.num_sync_requests().await.unwrap() != 0 {
return false
}
match (highest, peer.client.info().best_hash) {
(None, b) => highest = Some(b),
(Some(ref a), ref b) if a == b => {},
(Some(_), _) => return false,
}
}
true
}
async fn is_idle(&mut self) -> bool {
let peers = self.peers_mut();
for peer in peers {
if peer.sync_service.num_queued_blocks().await.unwrap() != 0 {
return false
}
if peer.sync_service.num_sync_requests().await.unwrap() != 0 {
return false
}
}
true
}
/// Blocks the current thread until we are sync'ed.
/// Wait until we are sync'ed.
///
/// Calls `poll_until_sync` repeatedly.
/// (If we've not synced within 10 mins then panic rather than hang.)
async fn run_until_sync(&mut self) {
timeout(
Duration::from_secs(10 * 60),
futures::future::poll_fn::<(), _>(|cx| self.poll_until_sync(cx)),
)
timeout(Duration::from_secs(10 * 60), async {
loop {
futures::future::poll_fn::<(), _>(|cx| {
self.poll(cx);
Poll::Ready(())
})
.await;
if self.is_in_sync().await {
break
}
}
})
.await
.expect("sync didn't happen within 10 mins");
}
@@ -1062,7 +1071,17 @@ where
///
/// Calls `poll_until_idle` repeatedly with the runtime passed as parameter.
async fn run_until_idle(&mut self) {
futures::future::poll_fn::<(), _>(|cx| self.poll_until_idle(cx)).await;
loop {
futures::future::poll_fn::<(), _>(|cx| {
self.poll(cx);
Poll::Ready(())
})
.await;
if self.is_idle().await {
break
}
}
}
/// Run the network until all peers are connected to each other.
@@ -1095,14 +1114,14 @@ where
while let Poll::Ready(Some(notification)) =
peer.imported_blocks_stream.as_mut().poll_next(cx)
{
peer.network.service().announce_block(notification.hash, None);
peer.sync_service.announce_block(notification.hash, None);
}
// We poll `finality_notification_stream`.
while let Poll::Ready(Some(notification)) =
peer.finality_notification_stream.as_mut().poll_next(cx)
{
peer.network.on_block_finalized(notification.hash, notification.header);
peer.sync_service.on_block_finalized(notification.hash, notification.header);
}
}
});
@@ -1142,6 +1161,10 @@ impl TestNetFactory for TestNet {
&self.peers
}
fn peers_mut(&mut self) -> &mut Vec<Peer<(), Self::BlockImport>> {
&mut self.peers
}
fn mut_peers<F: FnOnce(&mut Vec<Peer<(), Self::BlockImport>>)>(&mut self, closure: F) {
closure(&mut self.peers);
}
@@ -1189,6 +1212,10 @@ impl TestNetFactory for JustificationTestNet {
self.0.peers()
}
fn peers_mut(&mut self) -> &mut Vec<Peer<Self::PeerData, Self::BlockImport>> {
self.0.peers_mut()
}
fn mut_peers<F: FnOnce(&mut Vec<Peer<Self::PeerData, Self::BlockImport>>)>(
&mut self,
closure: F,
+15 -12
View File
@@ -652,12 +652,12 @@ async fn imports_stale_once() {
// check that NEW block is imported from announce message
let new_hash = net.peer(0).push_blocks(1, false).pop().unwrap();
import_with_announce(&mut net, new_hash).await;
assert_eq!(net.peer(1).num_downloaded_blocks(), 1);
assert_eq!(net.peer(1).num_downloaded_blocks().await, 1);
// check that KNOWN STALE block is imported from announce message
let known_stale_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 1, true).pop().unwrap();
import_with_announce(&mut net, known_stale_hash).await;
assert_eq!(net.peer(1).num_downloaded_blocks(), 2);
assert_eq!(net.peer(1).num_downloaded_blocks().await, 2);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -820,7 +820,7 @@ async fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block()
assert!(!net.peer(1).has_block(block_hash));
// Make sync protocol aware of the best block
net.peer(0).network_service().new_best_block_imported(block_hash, 3);
net.peer(0).sync_service().new_best_block_imported(block_hash, 3);
net.run_until_idle().await;
// Connect another node that should now sync to the tip
@@ -865,8 +865,8 @@ async fn sync_to_tip_when_we_sync_together_with_multiple_peers() {
assert!(!net.peer(2).has_block(block_hash));
net.peer(0).network_service().new_best_block_imported(block_hash, 10_000);
net.peer(0).network_service().announce_block(block_hash, None);
net.peer(0).sync_service().new_best_block_imported(block_hash, 10_000);
net.peer(0).sync_service().announce_block(block_hash, None);
while !net.peer(2).has_block(block_hash) && !net.peer(1).has_block(block_hash) {
net.run_until_idle().await;
@@ -1045,14 +1045,17 @@ async fn syncs_all_forks_from_single_peer() {
let branch1 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, true).pop().unwrap();
// Wait till peer 1 starts downloading
futures::future::poll_fn::<(), _>(|cx| {
net.poll(cx);
if net.peer(1).network().best_seen_block() != Some(12) {
return Poll::Pending
loop {
futures::future::poll_fn::<(), _>(|cx| {
net.poll(cx);
Poll::Ready(())
})
.await;
if net.peer(1).sync_service().best_seen_block().await.unwrap() == Some(12) {
break
}
Poll::Ready(())
})
.await;
}
// Peer 0 produces and announces another fork
let branch2 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, false).pop().unwrap();