bitcoincore_zmq/
sequence_message.rs1use crate::error::{Error, Result};
2use bitcoin::{hashes::Hash, BlockHash, Txid};
3use core::fmt;
4
5#[derive(Debug, PartialEq, Eq, Clone, Copy)]
6pub enum SequenceMessage {
7 BlockConnect { blockhash: BlockHash },
8 BlockDisconnect { blockhash: BlockHash },
9 MempoolAcceptance { txid: Txid, mempool_sequence: u64 },
10 MempoolRemoval { txid: Txid, mempool_sequence: u64 },
11}
12
13impl SequenceMessage {
14 #[inline]
16 pub const fn raw_length(&self) -> usize {
17 match self {
18 Self::BlockConnect { .. } | Self::BlockDisconnect { .. } => 33,
19 Self::MempoolAcceptance { .. } | Self::MempoolRemoval { .. } => 41,
20 }
21 }
22
23 #[inline]
25 pub const fn label_char(&self) -> char {
26 self.label() as char
27 }
28
29 #[inline]
31 pub const fn label(&self) -> u8 {
32 match self {
33 Self::BlockConnect { .. } => b'C',
34 Self::BlockDisconnect { .. } => b'D',
35 Self::MempoolAcceptance { .. } => b'A',
36 Self::MempoolRemoval { .. } => b'R',
37 }
38 }
39
40 #[inline]
42 pub fn inner_hash_as_bytes(&self) -> [u8; 32] {
43 let mut arr = match self {
44 Self::BlockConnect { blockhash } | Self::BlockDisconnect { blockhash } => {
45 blockhash.to_byte_array()
46 }
47 Self::MempoolAcceptance { txid, .. } | Self::MempoolRemoval { txid, .. } => {
48 txid.to_byte_array()
49 }
50 };
51 arr.reverse();
52 arr
53 }
54
55 #[inline]
66 pub const fn mempool_sequence(&self) -> Option<u64> {
67 match self {
68 Self::BlockConnect { .. } | Self::BlockDisconnect { .. } => None,
69 Self::MempoolAcceptance {
70 mempool_sequence, ..
71 }
72 | Self::MempoolRemoval {
73 mempool_sequence, ..
74 } => Some(*mempool_sequence),
75 }
76 }
77
78 #[inline]
80 pub fn from_byte_slice<T: AsRef<[u8]>>(bytes: T) -> Result<Self> {
81 let bytes = bytes.as_ref();
82
83 if bytes.len() < 33 {
84 return Err(Error::InvalidSequenceMessageLength(bytes.len()));
85 }
86
87 let mut hash: [u8; 32] = bytes[0..32].try_into().unwrap();
88 hash.reverse();
89
90 let label = bytes[32];
91 Ok(match label {
92 b'C' | b'D' => {
93 if bytes.len() != 33 {
94 return Err(Error::InvalidSequenceMessageLength(bytes.len()));
95 }
96
97 let blockhash = BlockHash::from_byte_array(hash);
98
99 match label {
100 b'C' => Self::BlockConnect { blockhash },
101 _ => Self::BlockDisconnect { blockhash },
102 }
103 }
104 b'A' | b'R' => {
105 if bytes.len() != 41 {
106 return Err(Error::InvalidSequenceMessageLength(bytes.len()));
107 }
108
109 let txid = Txid::from_byte_array(hash);
110 let mempool_sequence = u64::from_le_bytes(bytes[33..41].try_into().unwrap());
111
112 match label {
113 b'A' => Self::MempoolAcceptance { txid, mempool_sequence },
114 _ => Self::MempoolRemoval { txid, mempool_sequence },
115 }
116 }
117 _ => return Err(Error::InvalidSequenceMessageLabel(label)),
118 })
119 }
120
121 #[inline]
123 pub fn serialize_to_vec(&self) -> Vec<u8> {
124 let mut ret = Vec::with_capacity(self.raw_length());
125
126 ret.extend_from_slice(&self.inner_hash_as_bytes());
128
129 ret.push(self.label());
131
132 if let Some(mempool_sequence) = self.mempool_sequence() {
134 ret.extend_from_slice(&mempool_sequence.to_le_bytes());
135 }
136
137 ret
138 }
139}
140
141impl TryFrom<Vec<u8>> for SequenceMessage {
142 type Error = Error;
143
144 #[inline]
145 fn try_from(value: Vec<u8>) -> Result<Self> {
146 Self::from_byte_slice(value)
147 }
148}
149
150impl TryFrom<&[u8]> for SequenceMessage {
151 type Error = Error;
152
153 #[inline]
154 fn try_from(value: &[u8]) -> Result<Self> {
155 Self::from_byte_slice(value)
156 }
157}
158
159impl From<SequenceMessage> for Vec<u8> {
160 #[inline]
161 fn from(sm: SequenceMessage) -> Self {
162 sm.serialize_to_vec()
163 }
164}
165
166impl fmt::Display for SequenceMessage {
167 #[inline]
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 match self {
170 Self::BlockConnect { blockhash } => write!(f, "BlockConnect({blockhash})"),
171 Self::BlockDisconnect { blockhash } => {
172 write!(f, "BlockDisconnect({blockhash})")
173 }
174 Self::MempoolAcceptance {
175 txid,
176 mempool_sequence,
177 } => write!(
178 f,
179 "MempoolAcceptance({txid}, mempool_sequence={mempool_sequence})"
180 ),
181 Self::MempoolRemoval {
182 txid,
183 mempool_sequence,
184 } => write!(
185 f,
186 "MempoolRemoval({txid}, mempool_sequence={mempool_sequence})"
187 ),
188 }
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use crate::SequenceMessage;
195 use bitcoin::{constants::genesis_block, hashes::Hash, Network};
196
197 #[test]
198 fn serialization() {
199 let genesis_block = genesis_block(Network::Bitcoin);
200
201 let blockhash = genesis_block.block_hash();
202 let mut blockhash_bytes = blockhash.to_byte_array();
203 blockhash_bytes.reverse();
204
205 let txid = genesis_block.txdata[0].compute_txid();
206 let mut txid_bytes = txid.to_byte_array();
207 txid_bytes.reverse();
208
209 let connect_message = SequenceMessage::BlockConnect { blockhash };
210 let connect_bytes = connect_message.serialize_to_vec();
211 assert_eq!(connect_message.raw_length(), connect_bytes.len());
212 assert_eq!(connect_message.raw_length(), 32 + 1);
213 assert_eq!(connect_message, connect_bytes.try_into().unwrap());
214
215 assert_eq!(connect_message.label_char(), 'C');
216 assert_eq!(connect_message.inner_hash_as_bytes(), blockhash_bytes);
217 assert_eq!(connect_message.mempool_sequence(), None);
218
219 let disconnect_message = SequenceMessage::BlockDisconnect { blockhash };
220 let disconnect_bytes = disconnect_message.serialize_to_vec();
221 assert_eq!(disconnect_message.raw_length(), disconnect_bytes.len());
222 assert_eq!(disconnect_message.raw_length(), 32 + 1);
223 assert_eq!(disconnect_message, disconnect_bytes.try_into().unwrap());
224
225 assert_eq!(disconnect_message.label_char(), 'D');
226 assert_eq!(disconnect_message.inner_hash_as_bytes(), blockhash_bytes);
227 assert_eq!(disconnect_message.mempool_sequence(), None);
228
229 let accept_message = SequenceMessage::MempoolAcceptance {
230 txid,
231 mempool_sequence: 1,
232 };
233 let accept_bytes = accept_message.serialize_to_vec();
234 assert_eq!(accept_message.raw_length(), accept_bytes.len());
235 assert_eq!(accept_message.raw_length(), 32 + 1 + 8);
236 assert_eq!(accept_message, accept_bytes.try_into().unwrap());
237
238 assert_eq!(accept_message.label_char(), 'A');
239 assert_eq!(accept_message.inner_hash_as_bytes(), txid_bytes);
240 assert_eq!(accept_message.mempool_sequence(), Some(1));
241
242 let remove_message = SequenceMessage::MempoolRemoval {
243 txid,
244 mempool_sequence: 2,
245 };
246 let remove_bytes = remove_message.serialize_to_vec();
247 assert_eq!(remove_message.raw_length(), remove_bytes.len());
248 assert_eq!(remove_message.raw_length(), 32 + 1 + 8);
249 assert_eq!(remove_message, remove_bytes.try_into().unwrap());
250
251 assert_eq!(remove_message.label_char(), 'R');
252 assert_eq!(remove_message.inner_hash_as_bytes(), txid_bytes);
253 assert_eq!(remove_message.mempool_sequence(), Some(2));
254 }
255}