bitcoincore_rest/responses/
deployment_info.rs

1//! Response types for `get_deployment_info`.
2//!
3//! See <https://bitcoincore.org/en/doc/25.0.0/rpc/blockchain/getdeploymentinfo/>
4
5use std::collections::HashMap;
6
7use bitcoin::BlockHash;
8use serde::{Deserialize, Serialize};
9
10/// Response from `get_deployment_info`
11#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
12pub struct GetDeploymentInfoResult {
13    /// requested block hash or tip
14    pub hash: BlockHash,
15    /// requested block height or tip
16    pub height: u32,
17    /// deployment info keyed by name of the deployment
18    pub deployments: HashMap<String, DeploymentInfo>,
19}
20
21/// The deployment type indicates if the deployment is buried or is bip9 softfork
22#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
23#[serde(rename_all = "lowercase")]
24#[serde(tag = "type", content = "bip9")]
25pub enum DeploymentType {
26    Buried,
27    Bip9(Bip9),
28}
29
30/// The deployment information
31#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
32pub struct DeploymentInfo {
33    #[serde(flatten)]
34    pub r#type: DeploymentType,
35    /// height of the first block which the rules are or will be enforced
36    ///
37    /// `None` if `DeploymentType::Bip9` and `active` is `false`
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub height: Option<u32>,
40    pub active: bool,
41}
42
43/// The deployment status
44#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
45#[serde(rename_all = "snake_case")]
46pub enum Status {
47    Defined,
48    Started,
49    LockedIn,
50    Active,
51    Failed,
52}
53
54/// Whether the block is signalling
55#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
56pub enum Signalling {
57    #[serde(rename = "#")]
58    Signalling,
59    #[serde(rename = "-")]
60    NotSignalling,
61}
62
63/// Deployment information for bip9 softforks
64#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
65pub struct Bip9 {
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub bit: Option<u8>,
68    pub start_time: i64,
69    pub timeout: i64,
70    pub min_activation_height: i32,
71    pub status: Status,
72    pub since: i32,
73    pub status_next: Status,
74    /// `None` for `Defined`, `Active`, and `Failed` status
75    pub statistics: Option<Statistics>,
76    #[serde(
77        default,
78        skip_serializing_if = "Option::is_none",
79        with = "signalling_serde"
80    )]
81    pub signalling: Option<Vec<Signalling>>,
82}
83
84/// Numeric statistics about signalling for a softfork
85#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
86pub struct Statistics {
87    pub period: u32,
88    /// `Some` only for `Started` status
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub threshold: Option<u32>,
91    pub elapsed: u32,
92    pub count: u32,
93    /// `Some` only for `Started` status
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub possible: Option<bool>,
96}
97
98mod signalling_serde {
99    use super::Signalling;
100
101    pub fn serialize<S: serde::Serializer>(
102        val: &Option<Vec<Signalling>>,
103        ser: S,
104    ) -> Result<S::Ok, S::Error> {
105        if let Some(val) = val {
106            use super::Signalling::*;
107            let val = val
108                .iter()
109                .map(|s| match s {
110                    Signalling => '#',
111                    NotSignalling => '-',
112                })
113                .collect::<String>();
114
115            ser.serialize_some(val.as_str())
116        } else {
117            ser.serialize_none()
118        }
119    }
120
121    struct Visitor;
122
123    impl<'de> serde::de::Visitor<'de> for Visitor {
124        type Value = Option<Vec<Signalling>>;
125
126        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
127            formatter.write_str("sequence of '#' or '-' characters")
128        }
129
130        fn visit_none<E>(self) -> Result<Self::Value, E>
131        where
132            E: serde::de::Error,
133        {
134            Ok(None)
135        }
136
137        fn visit_unit<E>(self) -> Result<Self::Value, E>
138        where
139            E: serde::de::Error,
140        {
141            Ok(None)
142        }
143
144        fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
145        where
146            D: serde::Deserializer<'de>,
147        {
148            deserializer.deserialize_str(self)
149        }
150
151        fn visit_str<E: serde::de::Error>(self, val: &str) -> Result<Self::Value, E> {
152            use super::Signalling::*;
153            let mut vec = Vec::with_capacity(val.len());
154            for b in val.as_bytes() {
155                let s = match b {
156                    b'#' => Signalling,
157                    b'-' => NotSignalling,
158                    _ => {
159                        return Err(E::invalid_value(
160                            serde::de::Unexpected::Bytes(val.as_bytes()),
161                            &self,
162                        ));
163                    }
164                };
165                vec.push(s);
166            }
167            Ok(Some(vec))
168        }
169    }
170
171    pub fn deserialize<'de, D>(de: D) -> Result<Option<Vec<Signalling>>, D::Error>
172    where
173        D: serde::Deserializer<'de>,
174    {
175        de.deserialize_option(Visitor)
176    }
177}
178
179#[cfg(test)]
180mod test {
181
182    use super::{DeploymentType, GetDeploymentInfoResult, Signalling};
183
184    #[test]
185    fn test_deployment_info_roundtrip() {
186        let json = r##"
187        {"hash":"2fad8afdcb4c14f11987bc721fd61ab68d0c817a7585267b2eb57f7e11202eb6","height":218,"deployments":{"bip34":{"type":"buried","active":true,"height":1},"bip66":{"type":"buried","active":true,"height":1},"bip65":{"type":"buried","active":true,"height":1},"csv":{"type":"buried","active":true,"height":1},"segwit":{"type":"buried","active":true,"height":0},"testdummy":{"type":"bip9","active":false,"bip9":{"bit":28,"start_time":0,"timeout":9223372036854775807,"min_activation_height":0,"status":"started","since":144,"status_next":"started","statistics":{"period":144,"elapsed":75,"count":75,"threshold":108,"possible":true},"signalling":"#-#-#"}},"taproot":{"type":"bip9","height":0,"active":true,"bip9":{"start_time":-1,"timeout":9223372036854775807,"min_activation_height":0,"status":"active","since":0,"status_next":"active"}}}}
188        "##;
189        let deployment_info: GetDeploymentInfoResult =
190            serde_json::from_str(json).expect("deserialize deployment info");
191        let deployment_info =
192            serde_json::to_string(&deployment_info).expect("serialize deployment info");
193        let deployment_info: GetDeploymentInfoResult =
194            serde_json::from_str(&deployment_info).expect("deserialize deployment info again");
195        let DeploymentType::Bip9(ref bip9) =
196            deployment_info.deployments.get("testdummy").unwrap().r#type
197        else {
198            panic!("incorrect deployment type for testdummy");
199        };
200        let signalling = bip9.signalling.as_ref().unwrap();
201        assert_eq!(signalling.len(), 5);
202        assert_eq!(signalling[0], Signalling::Signalling);
203        assert_eq!(signalling[1], Signalling::NotSignalling);
204        assert_eq!(signalling[2], Signalling::Signalling);
205        assert_eq!(signalling[3], Signalling::NotSignalling);
206        assert_eq!(signalling[4], Signalling::Signalling);
207    }
208}