cfxcore/sync/state/
snapshot_chunk_sync.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use crate::sync::{
6    error::Error,
7    message::{
8        msgid, Context, SnapshotManifestRequest, SnapshotManifestResponse,
9        StateSyncCandidateRequest,
10    },
11    state::{
12        state_sync_candidate::state_sync_candidate_manager::StateSyncCandidateManager,
13        state_sync_chunk::snapshot_chunk_manager::{
14            SnapshotChunkConfig, SnapshotChunkManager,
15        },
16        state_sync_manifest::snapshot_manifest_manager::{
17            RelatedData, SnapshotManifestConfig, SnapshotManifestManager,
18        },
19        storage::{Chunk, ChunkKey, SnapshotSyncCandidate},
20    },
21    synchronization_state::PeerFilter,
22    SynchronizationProtocolHandler,
23};
24use cfx_parameters::consensus_internal::REWARD_EPOCH_COUNT;
25use cfx_storage::Result as StorageResult;
26use cfx_types::H256;
27use network::{node_table::NodeId, NetworkContext};
28use parking_lot::RwLock;
29use primitives::EpochId;
30use std::{
31    collections::HashSet,
32    fmt::{Debug, Formatter},
33    sync::Arc,
34    time::{Duration, Instant},
35};
36
37#[derive(Copy, Clone, PartialEq)]
38pub enum Status {
39    Inactive,
40    RequestingCandidates,
41    StartCandidateSync,
42    DownloadingManifest(Instant),
43    DownloadingChunks(Instant),
44    Completed,
45    Invalid,
46}
47
48impl Default for Status {
49    fn default() -> Self { Status::Inactive }
50}
51
52impl Debug for Status {
53    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
54        let status = match self {
55            Status::Inactive => "inactive".into(),
56            Status::RequestingCandidates => "requesting candidates".into(),
57            Status::StartCandidateSync => {
58                "about to request a candidate state".into()
59            }
60            Status::DownloadingManifest(t) => {
61                format!("downloading manifest ({:?})", t.elapsed())
62            }
63            Status::DownloadingChunks(t) => {
64                format!("downloading chunks ({:?})", t.elapsed())
65            }
66            Status::Completed => "completed".into(),
67            Status::Invalid => "invalid".into(),
68        };
69
70        write!(f, "{}", status)
71    }
72}
73
74// Only FullSync is supported. OneStepSync / IncSync were never implemented,
75// are deprecated, and rejected at decode (see `SnapshotSyncCandidate`).
76struct Inner {
77    status: Status,
78
79    sync_candidate_manager: StateSyncCandidateManager,
80    // Initialized after we receive a valid manifest.
81    chunk_manager: Option<SnapshotChunkManager>,
82    manifest_manager: Option<SnapshotManifestManager>,
83
84    related_data: Option<RelatedData>,
85    manifest_attempts: usize,
86}
87
88impl Default for Inner {
89    fn default() -> Self { Self::new() }
90}
91
92impl Inner {
93    fn new() -> Self {
94        Self {
95            sync_candidate_manager: Default::default(),
96            status: Status::Inactive,
97            related_data: None,
98            chunk_manager: None,
99            manifest_manager: None,
100            manifest_attempts: 0,
101        }
102    }
103
104    pub fn start_sync_for_candidate(
105        &mut self, sync_candidate: SnapshotSyncCandidate,
106        active_peers: HashSet<NodeId>, trusted_blame_block: H256,
107        io: &dyn NetworkContext, sync_handler: &SynchronizationProtocolHandler,
108        manifest_config: SnapshotManifestConfig,
109    ) {
110        if let Some(chunk_manager) = &mut self.chunk_manager {
111            if chunk_manager.snapshot_candidate == sync_candidate {
112                // TODO If the chunk manager does not make progress for a long
113                // time, we should also resync the manifest,
114                // because the manifest might be valid but also
115                // malicious. For example, the chunk size might be larger than
116                // MaxPacketSize so no one can return us that chunk.
117
118                // The new candidate is not changed, so we can resume our
119                // previous sync status with new `active_peers`.
120                self.status = Status::DownloadingChunks(Instant::now());
121                chunk_manager.set_active_peers(active_peers);
122                return;
123            }
124        }
125        info!(
126            "start to sync state, snapshot_to_sync = {:?}, trusted blame block = {:?}",
127            sync_candidate, trusted_blame_block);
128        let manifest_manager = SnapshotManifestManager::new_and_start(
129            sync_candidate,
130            trusted_blame_block,
131            active_peers,
132            manifest_config,
133            io,
134            sync_handler,
135        );
136        self.manifest_manager = Some(manifest_manager);
137        self.status = Status::DownloadingManifest(Instant::now());
138    }
139
140    pub fn start_sync(
141        &mut self, current_era_genesis: EpochId,
142        candidates: Vec<SnapshotSyncCandidate>, io: &dyn NetworkContext,
143        sync_handler: &SynchronizationProtocolHandler,
144    ) {
145        let peers = PeerFilter::new(msgid::STATE_SYNC_CANDIDATE_REQUEST)
146            .select_all(&sync_handler.syn);
147        if peers.is_empty() {
148            return;
149        }
150        self.status = Status::RequestingCandidates;
151        self.sync_candidate_manager.reset(
152            current_era_genesis,
153            candidates.clone(),
154            peers.clone(),
155        );
156        self.request_candidates(io, sync_handler, candidates, peers);
157    }
158
159    /// request state candidates from all peers
160    fn request_candidates(
161        &self, io: &dyn NetworkContext,
162        sync_handler: &SynchronizationProtocolHandler,
163        candidates: Vec<SnapshotSyncCandidate>, peers: Vec<NodeId>,
164    ) {
165        let request = StateSyncCandidateRequest {
166            request_id: 0,
167            candidates,
168        };
169        for peer in peers {
170            sync_handler.request_manager.request_with_delay(
171                io,
172                Box::new(request.clone()),
173                Some(peer),
174                None,
175            );
176        }
177    }
178}
179
180impl Debug for Inner {
181    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
182        write!(
183            f,
184            "(status = {:?}, pending_peers: {}, manifest: {:?}, chunks: {:?})",
185            self.status,
186            self.sync_candidate_manager.pending_peers().len(),
187            self.manifest_manager,
188            self.chunk_manager,
189        )
190    }
191}
192
193pub struct SnapshotChunkSync {
194    inner: Arc<RwLock<Inner>>,
195    config: StateSyncConfiguration,
196}
197
198impl SnapshotChunkSync {
199    pub fn new(config: StateSyncConfiguration) -> Self {
200        SnapshotChunkSync {
201            inner: Default::default(),
202            config,
203        }
204    }
205
206    pub fn status(&self) -> Status { self.inner.read().status }
207
208    pub fn handle_snapshot_manifest_response(
209        &self, ctx: &Context, response: SnapshotManifestResponse,
210        request: &SnapshotManifestRequest,
211    ) -> Result<(), Error> {
212        let inner = &mut *self.inner.write();
213
214        // status mismatch
215        if !matches!(inner.status, Status::DownloadingManifest(_)) {
216            info!("Snapshot manifest received, but mismatch with current status {:?}", inner.status);
217            return Ok(());
218        };
219        if let Some(manifest_manager) = &mut inner.manifest_manager {
220            let r = manifest_manager
221                .handle_snapshot_manifest_response(ctx, response, request)?;
222            if let Some(related_data) = r {
223                // Build into a local first: if `new_and_start` fails, setting
224                // status to `DownloadingChunks` before it would leave
225                // `chunk_manager == None` and panic the next `update_status`.
226                let chunk_manager = SnapshotChunkManager::new_and_start(
227                    ctx,
228                    manifest_manager.snapshot_candidate.clone(),
229                    related_data.snapshot_info.clone(),
230                    related_data.parent_snapshot_info.clone(),
231                    manifest_manager.chunk_boundaries.clone(),
232                    manifest_manager.chunk_boundary_proofs.clone(),
233                    manifest_manager.active_peers.clone(),
234                    self.config.chunk_config(),
235                    // This delta_root is the intermediate_delta_root of
236                    // the new snapshot, and this field will be used to
237                    // fill new state_root in
238                    // get_state_trees_for_next_epoch
239                    related_data
240                        .true_state_root_by_blame_info
241                        .state_root
242                        .delta_root,
243                )?;
244                inner.status = Status::DownloadingChunks(Instant::now());
245                inner.chunk_manager = Some(chunk_manager);
246                inner.related_data = Some(related_data);
247            }
248            debug!("sync state progress: {:?}", *inner);
249        } else {
250            error!("manifest manager is None in status {:?}", inner.status);
251        }
252        if matches!(inner.status, Status::DownloadingChunks(_)) {
253            inner.manifest_manager = None;
254        }
255        Ok(())
256    }
257
258    pub fn handle_snapshot_chunk_response(
259        &self, ctx: &Context, chunk_key: ChunkKey, chunk: Chunk,
260    ) -> StorageResult<()> {
261        let mut inner = self.inner.write();
262
263        if !matches!(inner.status, Status::DownloadingChunks(_)) {
264            info!("Snapshot chunk {:?} received, but mismatch with current status {:?}",
265                chunk_key, inner.status);
266            return Ok(());
267        }
268
269        if let Some(chunk_manager) = &mut inner.chunk_manager {
270            if chunk_manager.add_chunk(ctx, chunk_key, chunk)? {
271                // Once the status becomes Completed, it will never be changed
272                // to another status, and all the related fields
273                // (snapshot_id, trust_blame_block, receipts, e.t.c.)
274                // of Inner will not be modified, because we return early in
275                // `update_status`
276                // and `handle_snapshot_manifest_response`. Thus, we can rely on
277                // the phase changing thread
278                // to call `restore_execution_state` later safely.
279                inner.status = Status::Completed;
280            }
281        } else {
282            debug!(
283                "Chunk {:?} received in status {:?}",
284                chunk_key, inner.status
285            );
286        }
287        info!("sync state progress: {:?}", *inner);
288        Ok(())
289    }
290
291    pub fn restore_execution_state(
292        &self, sync_handler: &SynchronizationProtocolHandler,
293    ) {
294        let inner = self.inner.read();
295        let related_data = inner
296            .related_data
297            .as_ref()
298            .expect("Set after receving manifest");
299        let mut deferred_block_hash =
300            related_data.snapshot_info.get_snapshot_epoch_id().clone();
301        // FIXME: Because state_root_aux_info can't be computed for state block
302        // FIXME: before snapshot, for the reward epoch count, maybe
303        // FIXME: save it to a dedicated place for reward computation.
304        for i in related_data.blame_vec_offset
305            ..(related_data.blame_vec_offset + REWARD_EPOCH_COUNT as usize)
306        {
307            info!(
308                "insert_epoch_execution_commitment for block hash {:?}",
309                &deferred_block_hash
310            );
311            sync_handler
312                .graph
313                .data_man
314                .insert_epoch_execution_commitment(
315                    deferred_block_hash,
316                    // FIXME: the state root is wrong for epochs before sync
317                    // FIXME: point. but these information won't be used.
318                    related_data.true_state_root_by_blame_info.clone(),
319                    related_data.receipt_blame_vec[i],
320                    related_data.bloom_blame_vec[i],
321                );
322            let block = sync_handler
323                .graph
324                .data_man
325                .block_header_by_hash(&deferred_block_hash)
326                .unwrap();
327            deferred_block_hash = *block.parent_hash();
328        }
329        for (block_hash, epoch_hash, receipts) in &related_data.epoch_receipts {
330            sync_handler.graph.data_man.insert_block_execution_result(
331                *block_hash,
332                *epoch_hash,
333                receipts.clone(),
334                true, /* persistent */
335            );
336        }
337    }
338
339    /// TODO Handling manifest requesting separately
340    /// Return Some if a candidate is ready and we can start requesting
341    /// manifests
342    pub fn handle_snapshot_candidate_response(
343        &self, peer: &NodeId,
344        supported_candidates: &Vec<SnapshotSyncCandidate>,
345        requested_candidates: &Vec<SnapshotSyncCandidate>,
346    ) {
347        self.inner.write().sync_candidate_manager.on_peer_response(
348            peer,
349            supported_candidates,
350            requested_candidates,
351        )
352    }
353
354    pub fn on_peer_disconnected(&self, peer: &NodeId) {
355        let mut inner = self.inner.write();
356        inner.sync_candidate_manager.on_peer_disconnected(peer);
357        if let Some(manifest_manager) = &mut inner.manifest_manager {
358            manifest_manager.on_peer_disconnected(peer);
359        }
360        if let Some(chunk_manager) = &mut inner.chunk_manager {
361            chunk_manager.on_peer_disconnected(peer);
362        }
363    }
364
365    /// Reset status if we cannot make progress based on current peers and
366    /// candidates
367    pub fn update_status(
368        &self, current_era_genesis: EpochId, epoch_to_sync: EpochId,
369        io: &dyn NetworkContext, sync_handler: &SynchronizationProtocolHandler,
370    ) {
371        let mut inner = self.inner.write();
372        if inner.manifest_attempts
373            >= self.config.max_downloading_manifest_attempts
374        {
375            // Remote-triggerable (peers serving unusable manifests/chunks), so
376            // panicking here would be a remote crash; reset and fall through to
377            // restart candidate discovery instead.
378            error!(
379                "Exceeded max manifest download attempts ({}); resetting state \
380                 sync and restarting candidate discovery. This usually means \
381                 peers served unusable manifests or chunks.",
382                self.config.max_downloading_manifest_attempts
383            );
384            inner.manifest_attempts = 0;
385            inner.status = Status::Inactive;
386            inner.manifest_manager = None;
387            inner.chunk_manager = None;
388            inner.related_data = None;
389        }
390
391        debug!("sync state status before updating: {:?}", *inner);
392        self.check_timeout(
393            &mut *inner,
394            &Context {
395                // node_id is not used here
396                node_id: Default::default(),
397                io,
398                manager: sync_handler,
399            },
400        );
401
402        // If we moves into the next era, we should force state_sync to change
403        // the candidates to states with in the new stable era. If the
404        // era stays the same and a new snapshot becomes available, we
405        // only change candidates if old candidates cannot to be synced,
406        // so a state can be synced with one era time instead of only
407        // one snapshot time
408        if inner.sync_candidate_manager.current_era_genesis
409            == current_era_genesis
410        {
411            match inner.status {
412                Status::Completed => return,
413                Status::RequestingCandidates => {
414                    if inner.sync_candidate_manager.pending_peers().is_empty() {
415                        inner.status = Status::StartCandidateSync;
416                        inner.sync_candidate_manager.set_active_candidate();
417                    }
418                }
419                Status::DownloadingManifest(_) => {
420                    if inner
421                        .manifest_manager
422                        .as_ref()
423                        .expect("always set in DownloadingManifest")
424                        .is_inactive()
425                    {
426                        // The current candidate fails, so try to choose the
427                        // next one.
428                        inner.status = Status::StartCandidateSync;
429                        inner.sync_candidate_manager.set_active_candidate();
430                    }
431                }
432                Status::DownloadingChunks(_) => {
433                    if inner
434                        .chunk_manager
435                        .as_ref()
436                        .expect("always set in DownloadingChunks")
437                        .is_inactive()
438                    {
439                        // The current candidate fails, so try to choose the
440                        // next one.
441                        inner.status = Status::StartCandidateSync;
442                        inner.sync_candidate_manager.set_active_candidate();
443                    }
444                }
445                _ => {}
446            }
447            if inner.sync_candidate_manager.is_inactive()
448                && inner
449                    .chunk_manager
450                    .as_ref()
451                    .map_or(true, |m| m.is_inactive())
452                && inner
453                    .manifest_manager
454                    .as_ref()
455                    .map_or(true, |m| m.is_inactive())
456            {
457                // We are requesting candidates and all `pending_peers` timeout,
458                // or we are syncing states and all
459                // `active_peers` for all candidates timeout.
460                warn!("current sync candidate becomes inactive: {:?}", inner);
461                inner.status = Status::Inactive;
462                inner.manifest_manager = None;
463            }
464            // We need to start/restart syncing states for a candidate.
465            if inner.status == Status::StartCandidateSync {
466                if let Some((sync_candidate, active_peers)) = inner
467                    .sync_candidate_manager
468                    .get_active_candidate_and_peers()
469                {
470                    match sync_handler
471                        .graph
472                        .consensus
473                        .get_trusted_blame_block_for_snapshot(
474                            sync_candidate.get_snapshot_epoch_id(),
475                        ) {
476                        Some(trusted_blame_block) => {
477                            inner.start_sync_for_candidate(
478                                sync_candidate,
479                                active_peers,
480                                trusted_blame_block,
481                                io,
482                                sync_handler,
483                                self.config.manifest_config(),
484                            );
485                        }
486                        None => {
487                            error!("failed to start checkpoint sync, the trusted blame block is unavailable, epoch_to_sync={:?}", epoch_to_sync);
488                        }
489                    }
490                } else {
491                    inner.status = Status::Inactive;
492                }
493            }
494        } else {
495            inner.status = Status::Inactive;
496        }
497
498        if inner.status == Status::Inactive {
499            // New era started or all candidates fail, we should restart
500            // candidates sync
501            let height = sync_handler
502                .graph
503                .data_man
504                .block_header_by_hash(&epoch_to_sync)
505                .expect("Syncing checkpoint should have available header")
506                .height();
507            let candidates = vec![SnapshotSyncCandidate::FullSync {
508                height,
509                snapshot_epoch_id: epoch_to_sync,
510            }];
511            inner.start_sync(current_era_genesis, candidates, io, sync_handler)
512        }
513        debug!("sync state status after updating: {:?}", *inner);
514    }
515
516    fn check_timeout(&self, inner: &mut Inner, ctx: &Context) {
517        inner
518            .sync_candidate_manager
519            .check_timeout(&self.config.candidate_request_timeout);
520        if let Some(manifest_manager) = &mut inner.manifest_manager {
521            manifest_manager.check_timeout(ctx);
522        }
523        if let Some(chunk_manager) = &mut inner.chunk_manager {
524            if !chunk_manager.check_timeout(ctx) {
525                debug!("reset status to Inactive and redownload manifest");
526                inner.status = Status::Inactive;
527                inner.chunk_manager = None;
528                inner.manifest_attempts += 1;
529            }
530        }
531    }
532}
533
534pub struct StateSyncConfiguration {
535    pub max_downloading_chunks: usize,
536    pub candidate_request_timeout: Duration,
537    pub chunk_request_timeout: Duration,
538    pub manifest_request_timeout: Duration,
539    pub max_downloading_manifest_attempts: usize,
540}
541
542impl StateSyncConfiguration {
543    fn chunk_config(&self) -> SnapshotChunkConfig {
544        SnapshotChunkConfig {
545            max_downloading_chunks: self.max_downloading_chunks,
546            chunk_request_timeout: self.chunk_request_timeout,
547        }
548    }
549
550    fn manifest_config(&self) -> SnapshotManifestConfig {
551        SnapshotManifestConfig {
552            manifest_request_timeout: self.manifest_request_timeout,
553        }
554    }
555}