bitcoincore_zmq/
sequence_message.rs

1use 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    /// Returns the length of this [`SequenceMessage`] when serialized.
15    #[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    /// Returns the label of this [`SequenceMessage`] as a [`char`].
24    #[inline]
25    pub const fn label_char(&self) -> char {
26        self.label() as char
27    }
28
29    /// Returns the label of this [`SequenceMessage`] as a [`u8`].
30    #[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    /// Returns the contained hash (block hash or txid) of this [`SequenceMessage`].
41    #[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    /// Returns the mempool sequence of this [`SequenceMessage`] if it is [`MempoolAcceptance`] or
56    /// [`MempoolRemoval`]. This is a number that starts at 1 and goes up every time Bitcoin Core
57    /// adds or removes a transaction to the mempool.
58    ///
59    /// Note that transactions that got removed from the mempool because they were included in a
60    /// block increment Bitcoin Core's mempool sequence, but they do not produce a
61    /// [`MempoolRemoval`] message.
62    ///
63    /// [`MempoolAcceptance`]: SequenceMessage::MempoolAcceptance
64    /// [`MempoolRemoval`]: SequenceMessage::MempoolRemoval
65    #[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    /// Deserializes bytes to a [`SequenceMessage`].
79    #[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                    _ /* b'D' */ => 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                    _ /* b'R' */ => Self::MempoolRemoval { txid, mempool_sequence },
115                }
116            }
117            _ => return Err(Error::InvalidSequenceMessageLabel(label)),
118        })
119    }
120
121    /// Serializes a [`SequenceMessage`] to bytes.
122    #[inline]
123    pub fn serialize_to_vec(&self) -> Vec<u8> {
124        let mut ret = Vec::with_capacity(self.raw_length());
125
126        // blockhash or txid
127        ret.extend_from_slice(&self.inner_hash_as_bytes());
128
129        // label
130        ret.push(self.label());
131
132        // optional mempool sequence
133        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}