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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
use ckb_constant::sync::{BAD_MESSAGE_BAN_TIME, SYNC_USELESS_BAN_TIME};
use std::fmt::{self, Display, Formatter};
use std::time::Duration;

/// Similar to `?`, `attempt!` is used for propagating `Status`.
///
/// `attempt!` return early if it is not `Status::ok()`.
///
/// ```rust
/// use ckb_sync::{Status, StatusCode, attempt};
///
/// fn return_early(status: Status) -> Status {
///     attempt!(status);
///     StatusCode::OK.with_context("bar")
/// }
///
/// assert_eq!(return_early(StatusCode::OK.into()).to_string(), "OK(100): bar");
/// assert_eq!(return_early(StatusCode::Ignored.into()).to_string(), "Ignored(101)");
/// ```
#[macro_export]
macro_rules! attempt {
    ($code:expr) => {{
        let ret = $code;
        if !ret.is_ok() {
            return ret;
        }
        ret
    }};
}

/// StatusCodes indicate whether a specific operation has been successfully completed.
/// The StatusCode element is a 3-digit integer.
///
/// The first digest of the StatusCode defines the class of result:
///   - 1xx: Informational - Request received, continuing process
///   - 4xx: Malformed Error - The request contains malformed messages
///   - 5xx: Warning - The node warns about recoverable conditions
#[repr(u16)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StatusCode {
    ///////////////////////////////////
    //      Informational 1xx        //
    ///////////////////////////////////
    /// OK
    OK = 100,
    /// Ignored
    Ignored = 101,
    /// The node had already received and recorded this block as pending block
    CompactBlockIsAlreadyPending = 102,
    /// The node is requesting from other peers for this block, but no response yet
    CompactBlockIsAlreadyInFlight = 103,
    /// The node had already stored this block into database
    CompactBlockAlreadyStored = 104,
    /// The CompactBlock is older than what the node expects
    CompactBlockIsStaled = 105,
    /// The node cannot process the arrived CompactBlock successfully for lack
    /// of information of its parent
    CompactBlockRequiresParent = 106,
    /// The node cannot process the arrived CompactBlock successfully for lack
    /// of parts of its transactions
    CompactBlockRequiresFreshTransactions = 107,
    /// CompactBlock short-ids collision
    CompactBlockMeetsShortIdsCollision = 108,
    /// In-flight blocks limit exceeded
    BlocksInFlightReachLimit = 109,
    /// Generic rate limit error
    TooManyRequests = 110,

    ///////////////////////////////////
    //      Malformed Errors 4xx     //
    ///////////////////////////////////
    /// Malformed protocol message
    ProtocolMessageIsMalformed = 400,
    /// Block verified failed or the block is already marked as invalid
    BlockIsInvalid = 401,
    /// Header verified failed or the header is already marked as invalid
    CompactBlockHasInvalidHeader = 402,
    /// Duplicated short-ids within a same CompactBlock
    CompactBlockHasDuplicatedShortIds = 403,
    /// Missing cellbase as the first transaction within a CompactBlock
    CompactBlockHasNotPrefilledCellbase = 404,
    /// Duplicated prefilled transactions within a same CompactBlock
    CompactBlockHasDuplicatedPrefilledTransactions = 405,
    /// The prefilled transactions are out-of-order
    CompactBlockHasOutOfOrderPrefilledTransactions = 406,
    /// Some of the prefilled transactions are out-of-index
    CompactBlockHasOutOfIndexPrefilledTransactions = 407,
    /// Invalid uncle block
    CompactBlockHasInvalidUncle = 408,
    /// Unmatched Transaction Root
    CompactBlockHasUnmatchedTransactionRootWithReconstructedBlock = 409,
    /// The length of BlockTransactions is unmatched with in pending_compact_blocks
    BlockTransactionsLengthIsUnmatchedWithPendingCompactBlock = 410,
    /// The short-ids of BlockTransactions is unmatched with in pending_compact_blocks
    BlockTransactionsShortIdsAreUnmatchedWithPendingCompactBlock = 411,
    /// The length of BlockUncles is unmatched with in pending_compact_blocks
    BlockUnclesLengthIsUnmatchedWithPendingCompactBlock = 412,
    /// The hash of uncles is unmatched
    BlockUnclesAreUnmatchedWithPendingCompactBlock = 413,
    /// Cannot locate the common blocks based on the GetHeaders
    GetHeadersMissCommonAncestors = 414,
    /// Headers verified failed
    HeadersIsInvalid = 415,
    /// Too many unknown transactions
    TooManyUnknownTransactions = 416,
    /// Request Genesis
    RequestGenesis = 417,
    /// Request Duplicate data
    RequestDuplicate = 418,

    ///////////////////////////////////
    //      Warning 5xx              //
    ///////////////////////////////////
    /// Errors returned from the tx-pool
    TxPool = 501,
    /// Errors returned from the network layer
    Network = 502,
}

impl StatusCode {
    /// Code with context
    pub fn with_context<S: ToString>(self, context: S) -> Status {
        Status::new(self, Some(context))
    }

    /// StatusCode's name like `Ok(100)`
    pub fn name(self) -> String {
        format!("{:?}({})", self, self as u16)
    }
}

/// Process message status
#[derive(Clone, Debug, Eq)]
pub struct Status {
    code: StatusCode,
    context: Option<String>,
}

impl Status {
    /// New with code
    pub fn new<S: ToString>(code: StatusCode, context: Option<S>) -> Self {
        Self {
            code,
            context: context.map(|s| s.to_string()),
        }
    }

    /// Ok status
    pub fn ok() -> Self {
        Self::new::<&str>(StatusCode::OK, None)
    }

    /// Ignored status
    pub fn ignored() -> Self {
        Self::new::<&str>(StatusCode::Ignored, None)
    }

    /// Whether ok
    pub fn is_ok(&self) -> bool {
        self.code == StatusCode::OK
    }

    /// Whether should ban session
    pub fn should_ban(&self) -> Option<Duration> {
        if !(400..500).contains(&(self.code as u16)) {
            return None;
        }
        match self.code {
            StatusCode::GetHeadersMissCommonAncestors => Some(SYNC_USELESS_BAN_TIME),
            _ => Some(BAD_MESSAGE_BAN_TIME),
        }
    }

    /// Whether should output a warning log
    pub fn should_warn(&self) -> bool {
        self.code as u16 >= 500
    }

    /// Status code
    pub fn code(&self) -> StatusCode {
        self.code
    }
}

impl PartialEq for Status {
    fn eq(&self, other: &Self) -> bool {
        self.code == other.code
    }
}

impl Display for Status {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self.context {
            Some(ref context) => write!(f, "{:?}({}): {}", self.code, self.code as u16, context),
            None => write!(f, "{:?}({})", self.code, self.code as u16),
        }
    }
}

impl From<StatusCode> for Status {
    fn from(code: StatusCode) -> Self {
        Self::new::<&str>(code, None)
    }
}