1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use diem_types::{ledger_info::LedgerInfoWithSignatures, transaction::Version};
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
/// We're currently considering several types of chunk requests depending on the information
/// available on the requesting side.
pub enum TargetType {
    /// The response is built relative to the target (or end of epoch).
    /// **DEPRECATED**: `TargetLedgerInfo` is only required for backward compatibility. State sync
    /// avoids sending these target types and instead uses `HighestAvailable` below. This message
    /// will be removed on the next breaking release: https://github.com/diem/diem/issues/8013
    TargetLedgerInfo(LedgerInfoWithSignatures),
    /// The response is built relative to the highest available LedgerInfo (or end of epoch).
    /// The value specifies the timeout in ms to wait for an available response.
    /// This "long poll" approach allows a responding node to add the request to the list of its
    /// subscriptions for the duration of a timeout until some new information becomes available.
    ///
    /// `target_li`: While asking for the highest available ledger info, this request also provides
    /// the option to the sync requester to specify a target LI.
    /// This is to support the scenario where the sync requester is lagging too much behind the responding node
    /// in the sync process. If the highest ledger info version keeps advancing on the responding node,
    /// even though the sync requester continues to receive and sync txns, those txns will never be backed an LI,
    /// since a LI can only be committed once all the transactions up to the LI's version has been received.
    /// (It is important for a transaction to be backed by an LI, because transactions need to be backed by an LI
    /// to be shown as committed upon storage query)
    /// To prevent the above problem where the transactions are never backed by a LI during sync catch-up
    /// (or the difference between synced version and committed LI version keeps growing on sync requester),
    /// this `TargetType` can simultaneously (1) ask for the highest ledger info, and (2) specify a target
    /// to build the requested transactions w.r.t.. With (1), the sync requester can store the LI later to target-sync
    /// once it is ready for that LI after syncing to an earlier target LI via (2).
    ///
    /// If `target_li` is not specified, the responding node will build the responses against its highest LI
    HighestAvailable {
        target_li: Option<LedgerInfoWithSignatures>,
        timeout_ms: u64,
    },
    /// The response is built relative to a LedgerInfo at a given version.
    Waypoint(Version),
}

impl TargetType {
    pub fn epoch(&self) -> Option<u64> {
        match self {
            TargetType::TargetLedgerInfo(li) => Some(li.ledger_info().epoch()),
            TargetType::HighestAvailable { target_li, .. } => {
                target_li.as_ref().map(|li| li.ledger_info().epoch())
            }
            TargetType::Waypoint(_) => None,
        }
    }

    pub fn version(&self) -> Option<u64> {
        match self {
            TargetType::TargetLedgerInfo(li) => Some(li.ledger_info().version()),
            TargetType::HighestAvailable { target_li, .. } => {
                target_li.as_ref().map(|li| li.ledger_info().version())
            }
            TargetType::Waypoint(version) => Some(*version),
        }
    }
}

impl fmt::Debug for TargetType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self)
    }
}

impl fmt::Display for TargetType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            TargetType::TargetLedgerInfo(ledger_info) => {
                write!(f, "TargetLedgerInfo({})", ledger_info)
            }
            TargetType::HighestAvailable {
                target_li,
                timeout_ms,
            } => write!(
                f,
                "HighestAvailable(timeout:{}, target_li:{})",
                timeout_ms,
                target_li
                    .as_ref()
                    .map_or_else(|| String::from("None"), |li| li.to_string())
            ),
            TargetType::Waypoint(version) => write!(f, "Waypoint({})", version),
        }
    }
}

#[derive(Clone, Deserialize, Eq, PartialEq, Serialize)]
pub struct GetChunkRequest {
    /// The response should start with `known_version + 1`.
    pub known_version: Version,
    /// Epoch the chunk response is supposed to belong to (i.e., epoch of known_version + 1).
    pub current_epoch: u64,
    /// Max size of a chunk response.
    pub limit: u64,
    /// The target of the given request.
    pub target: TargetType,
}

impl GetChunkRequest {
    pub fn new(known_version: Version, current_epoch: u64, limit: u64, target: TargetType) -> Self {
        Self {
            known_version,
            current_epoch,
            limit,
            target,
        }
    }
}

impl fmt::Debug for GetChunkRequest {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self)
    }
}

impl fmt::Display for GetChunkRequest {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "[ChunkRequest: known version: {}, epoch: {}, limit: {}, target: {}]",
            self.known_version, self.current_epoch, self.limit, self.target,
        )
    }
}