bitcoincore_rest/responses/
deployment_info.rs1use std::collections::HashMap;
6
7use bitcoin::BlockHash;
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
12pub struct GetDeploymentInfoResult {
13 pub hash: BlockHash,
15 pub height: u32,
17 pub deployments: HashMap<String, DeploymentInfo>,
19}
20
21#[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#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
32pub struct DeploymentInfo {
33 #[serde(flatten)]
34 pub r#type: DeploymentType,
35 #[serde(skip_serializing_if = "Option::is_none")]
39 pub height: Option<u32>,
40 pub active: bool,
41}
42
43#[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#[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)]
56pub enum Signalling {
57 #[serde(rename = "#")]
58 Signalling,
59 #[serde(rename = "-")]
60 NotSignalling,
61}
62
63#[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 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#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
86pub struct Statistics {
87 pub period: u32,
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub threshold: Option<u32>,
91 pub elapsed: u32,
92 pub count: u32,
93 #[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}