1use std::fmt;
2
3use crate::{Hash, Height};
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase", deny_unknown_fields)]
12pub struct EventHeader {
13 pub version: u32,
14 pub previous_hash: Hash,
15 pub merkle_root: Hash,
16 pub time: u32,
17 pub bits: u32,
18 pub nonce: u32,
19 pub height: Height,
20 pub hash: Hash,
21 pub chain_work: String,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(deny_unknown_fields)]
28pub struct Outpoint {
29 pub txid: Hash,
30 pub vout: u32,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
37pub enum ChainEvent {
38 Tip {
40 height: Height,
41 hash: Hash,
42 time: u32,
43 header: EventHeader,
44 },
45 Fork {
47 height: Height,
48 competing_tips: [EventHeader; 2],
49 depth: u32,
50 },
51 Reorg {
53 fork_height: Height,
54 depth: u32,
55 deactivated_headers: Vec<EventHeader>,
56 new_tip: EventHeader,
57 },
58 Invalidated { block_hash: Hash },
60 Frozen { outpoint: Outpoint },
62 TipAge { seconds: u64, tip: EventHeader },
64}
65
66#[derive(Serialize, Deserialize)]
69#[serde(
70 remote = "ChainEvent",
71 tag = "kind",
72 rename_all = "camelCase",
73 deny_unknown_fields
74)]
75enum EventFields {
76 Tip {
77 height: Height,
78 hash: Hash,
79 time: u32,
80 header: EventHeader,
81 },
82 Fork {
83 height: Height,
84 #[serde(rename = "competingTips")]
85 competing_tips: [EventHeader; 2],
86 depth: u32,
87 },
88 Reorg {
89 #[serde(rename = "forkHeight")]
90 fork_height: Height,
91 depth: u32,
92 #[serde(rename = "deactivatedHeaders")]
93 deactivated_headers: Vec<EventHeader>,
94 #[serde(rename = "newTip")]
95 new_tip: EventHeader,
96 },
97 Invalidated {
98 #[serde(rename = "blockHash")]
99 block_hash: Hash,
100 },
101 Frozen {
102 outpoint: Outpoint,
103 },
104 TipAge {
105 seconds: u64,
106 tip: EventHeader,
107 },
108}
109
110impl Serialize for ChainEvent {
111 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
112 #[derive(Serialize)]
113 struct EnvelopeRef<'a> {
114 v: u64,
115 #[serde(flatten, with = "EventFields")]
116 event: &'a ChainEvent,
117 }
118 EnvelopeRef { v: 1, event: self }.serialize(serializer)
119 }
120}
121
122impl<'de> Deserialize<'de> for ChainEvent {
123 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
124 let value = serde_json::Value::deserialize(deserializer)?;
125 decode_value(value).map_err(serde::de::Error::custom)
126 }
127}
128
129#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
131pub struct ChainEnvelope {
132 v: u64,
134 #[serde(flatten, with = "EventFields")]
136 event: ChainEvent,
137}
138
139impl ChainEnvelope {
140 pub fn new(event: ChainEvent) -> Self {
142 Self { v: 1, event }
143 }
144 pub fn version(&self) -> u64 {
146 self.v
147 }
148 pub fn event(&self) -> &ChainEvent {
150 &self.event
151 }
152 pub fn into_event(self) -> ChainEvent {
154 self.event
155 }
156}
157
158impl<'de> Deserialize<'de> for ChainEnvelope {
159 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
160 ChainEvent::deserialize(deserializer).map(Self::new)
161 }
162}
163
164#[derive(Clone, Debug, PartialEq, Eq)]
166pub enum DecodeError {
167 UnknownShape {
169 version: Option<u64>,
170 detail: String,
171 },
172 UnknownVersion(u64),
174 UnknownKind(String),
176}
177impl fmt::Display for DecodeError {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 write!(f, "{self:?}")
180 }
181}
182impl std::error::Error for DecodeError {}
183
184pub fn decode_envelope(bytes: &[u8]) -> Result<ChainEvent, DecodeError> {
187 let value: serde_json::Value =
188 serde_json::from_slice(bytes).map_err(|e| DecodeError::UnknownShape {
189 version: None,
190 detail: e.to_string(),
191 })?;
192 decode_value(value)
193}
194
195fn decode_value(mut value: serde_json::Value) -> Result<ChainEvent, DecodeError> {
196 let version = value.get("v").and_then(serde_json::Value::as_u64);
197 let Some(v) = version else {
198 return Err(DecodeError::UnknownShape {
199 version,
200 detail: "missing or malformed v".into(),
201 });
202 };
203 if v != 1 {
204 return Err(DecodeError::UnknownVersion(v));
205 }
206 value
207 .as_object_mut()
208 .expect("v was read from an object")
209 .remove("v");
210 let kind = value
211 .get("kind")
212 .and_then(serde_json::Value::as_str)
213 .ok_or_else(|| DecodeError::UnknownShape {
214 version: Some(v),
215 detail: "missing or malformed kind".into(),
216 })?;
217 if !matches!(
218 kind,
219 "tip" | "fork" | "reorg" | "invalidated" | "frozen" | "tipAge"
220 ) {
221 return Err(DecodeError::UnknownKind(kind.into()));
222 }
223 let event = EventFields::deserialize(value).map_err(|e| DecodeError::UnknownShape {
224 version: Some(v),
225 detail: e.to_string(),
226 })?;
227 if !valid_event(&event) {
228 return Err(DecodeError::UnknownShape {
229 version: Some(v),
230 detail: "inconsistent chain event".into(),
231 });
232 }
233 Ok(event)
234}
235
236fn valid_event(event: &ChainEvent) -> bool {
240 match event {
241 ChainEvent::Tip {
242 height,
243 hash,
244 time,
245 header,
246 } => {
247 valid_header(header)
248 && *height == header.height
249 && *hash == header.hash
250 && *time == header.time
251 }
252 ChainEvent::Fork {
253 height,
254 competing_tips,
255 depth,
256 } => {
257 *depth > 0
258 && competing_tips.iter().all(valid_header)
259 && competing_tips[0].hash != competing_tips[1].hash
260 && competing_tips[0]
261 .height
262 .checked_sub(*depth)
263 .and_then(|h| h.checked_add(1))
264 == Some(*height)
265 }
266 ChainEvent::Reorg {
267 fork_height,
268 depth,
269 deactivated_headers,
270 new_tip,
271 } => {
272 *depth > 0
273 && *depth <= 400
274 && deactivated_headers.len() == *depth as usize
275 && valid_header(new_tip)
276 && deactivated_headers.iter().all(valid_header)
277 && deactivated_headers
278 .last()
279 .is_some_and(|h| h.height == *fork_height)
280 && deactivated_headers
281 .windows(2)
282 .all(|p| p[0].height == p[1].height + 1 && p[0].previous_hash == p[1].hash)
283 }
284 ChainEvent::Invalidated { block_hash } => valid_hash(block_hash),
285 ChainEvent::Frozen { outpoint } => valid_hash(&outpoint.txid),
286 ChainEvent::TipAge { tip, .. } => valid_header(tip),
287 }
288}
289
290fn valid_header(header: &EventHeader) -> bool {
291 header.height <= i32::MAX as u32
292 && valid_hash(&header.hash)
293 && valid_hash(&header.previous_hash)
294 && valid_hash(&header.merkle_root)
295 && valid_hash(&header.chain_work)
296}
297
298fn valid_hash(hash: &str) -> bool {
299 hash.len() == 64 && hash.bytes().all(|b| b.is_ascii_hexdigit())
300}