1use super::{InnerNodes, LeafNodes};
2use serde::{Deserialize, Serialize};
3use sqlx::{
4 encode::IsNull,
5 error::BoxDynError,
6 sqlite::{SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef},
7 Decode, Encode, Sqlite, Type,
8};
9use std::fmt;
10use xxhash_rust::xxh3::Xxh3Default;
11
12#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
15pub struct Summary {
16 pub state: NodeState,
19 pub block_presence: MultiBlockPresence,
20}
21
22impl Summary {
23 pub const INCOMPLETE: Self = Self {
25 state: NodeState::Incomplete,
26 block_presence: MultiBlockPresence::None,
27 };
28
29 pub fn from_leaves(nodes: &LeafNodes) -> Self {
30 let mut block_presence_builder = MultiBlockPresenceBuilder::new();
31
32 for node in nodes {
33 match node.block_presence {
34 SingleBlockPresence::Missing => {
35 block_presence_builder.update(MultiBlockPresence::None)
36 }
37 SingleBlockPresence::Expired => {
38 block_presence_builder.update(MultiBlockPresence::Full)
45 }
46 SingleBlockPresence::Present => {
47 block_presence_builder.update(MultiBlockPresence::Full)
48 }
49 }
50 }
51
52 Self {
53 state: NodeState::Complete,
54 block_presence: block_presence_builder.build(),
55 }
56 }
57
58 pub fn from_inners(nodes: &InnerNodes) -> Self {
59 let mut block_presence_builder = MultiBlockPresenceBuilder::new();
60 let mut state = NodeState::Complete;
61
62 for (_, node) in nodes {
63 if node.is_empty() {
66 continue;
67 }
68
69 block_presence_builder.update(node.summary.block_presence);
70 state.update(node.summary.state);
71 }
72
73 Self {
74 state,
75 block_presence: block_presence_builder.build(),
76 }
77 }
78
79 pub fn is_outdated(&self, other: &Self) -> bool {
87 self.state == NodeState::Incomplete
88 || self.block_presence.is_outdated(&other.block_presence)
89 }
90
91 pub fn with_state(self, state: NodeState) -> Self {
92 Self { state, ..self }
93 }
94}
95
96#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize, sqlx::Type)]
97#[repr(u8)]
98pub enum NodeState {
99 Incomplete = 0, Complete = 1, Approved = 2, Rejected = 3, }
104
105impl NodeState {
106 pub fn is_approved(self) -> bool {
107 matches!(self, Self::Approved)
108 }
109
110 pub fn update(&mut self, other: Self) {
111 *self = match (*self, other) {
112 (Self::Incomplete, _) | (_, Self::Incomplete) => Self::Incomplete,
113 (Self::Complete, _) | (_, Self::Complete) => Self::Complete,
114 (Self::Approved, Self::Approved) => Self::Approved,
115 (Self::Rejected, Self::Rejected) => Self::Rejected,
116 (Self::Approved, Self::Rejected) | (Self::Rejected, Self::Approved) => unreachable!(),
117 }
118 }
119}
120
121#[cfg(test)]
122mod test_utils {
123 use super::NodeState;
124 use proptest::{
125 arbitrary::Arbitrary,
126 strategy::{Just, Union},
127 };
128
129 impl Arbitrary for NodeState {
130 type Parameters = ();
131 type Strategy = Union<Just<Self>>;
132
133 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
134 Union::new([
135 Just(NodeState::Incomplete),
136 Just(NodeState::Complete),
137 Just(NodeState::Approved),
138 Just(NodeState::Rejected),
139 ])
140 }
141 }
142}
143
144#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize, sqlx::Type)]
146#[repr(u8)]
147pub enum SingleBlockPresence {
148 Missing = 0,
149 Present = 1,
150 Expired = 2,
151}
152
153impl SingleBlockPresence {
154 pub fn is_missing(self) -> bool {
155 match self {
156 Self::Missing => true,
157 Self::Present => false,
158 Self::Expired => false,
159 }
160 }
161}
162
163impl fmt::Debug for SingleBlockPresence {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 match self {
166 Self::Missing => write!(f, "Missing"),
167 Self::Present => write!(f, "Present"),
168 Self::Expired => write!(f, "Expired"),
169 }
170 }
171}
172
173#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
175pub enum MultiBlockPresence {
176 None,
178 Some(Checksum),
181 Full,
183}
184
185type Checksum = [u8; 16];
186
187const NONE: Checksum = [
188 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
189];
190const FULL: Checksum = [
191 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
192];
193
194impl MultiBlockPresence {
195 pub fn is_outdated(&self, other: &Self) -> bool {
196 match (self, other) {
197 (Self::Some(lhs), Self::Some(rhs)) => lhs != rhs,
198 (Self::Full, _) | (_, Self::None) => false,
199 (Self::None, _) | (_, Self::Full) => true,
200 }
201 }
202
203 pub fn checksum(&self) -> &[u8] {
204 match self {
205 Self::None => NONE.as_slice(),
206 Self::Some(checksum) => checksum.as_slice(),
207 Self::Full => FULL.as_slice(),
208 }
209 }
210}
211
212impl Type<Sqlite> for MultiBlockPresence {
213 fn type_info() -> SqliteTypeInfo {
214 <&[u8] as Type<Sqlite>>::type_info()
215 }
216}
217
218impl<'q> Encode<'q, Sqlite> for &'q MultiBlockPresence {
219 fn encode_by_ref(
220 &self,
221 args: &mut Vec<SqliteArgumentValue<'q>>,
222 ) -> Result<IsNull, BoxDynError> {
223 Encode::<Sqlite>::encode(self.checksum(), args)
224 }
225}
226
227impl<'r> Decode<'r, Sqlite> for MultiBlockPresence {
228 fn decode(value: SqliteValueRef<'r>) -> Result<Self, BoxDynError> {
229 let slice = <&[u8] as Decode<Sqlite>>::decode(value)?;
230 let array = slice.try_into()?;
231
232 match array {
233 NONE => Ok(Self::None),
234 FULL => Ok(Self::Full),
235 _ => Ok(Self::Some(array)),
236 }
237 }
238}
239
240impl fmt::Debug for MultiBlockPresence {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 match self {
243 Self::None => write!(f, "None"),
244 Self::Some(checksum) => write!(f, "Some({:<8})", hex_fmt::HexFmt(checksum)),
245 Self::Full => write!(f, "Full"),
246 }
247 }
248}
249
250struct MultiBlockPresenceBuilder {
251 state: BuilderState,
252 hasher: Xxh3Default,
253}
254
255#[derive(Copy, Clone, Debug)]
256enum BuilderState {
257 Init,
258 None,
259 Some,
260 Full,
261}
262
263impl MultiBlockPresenceBuilder {
264 fn new() -> Self {
265 Self {
266 state: BuilderState::Init,
267 hasher: Xxh3Default::default(),
268 }
269 }
270
271 fn update(&mut self, p: MultiBlockPresence) {
272 self.hasher.update(p.checksum());
273
274 self.state = match (self.state, p) {
275 (BuilderState::Init, MultiBlockPresence::None) => BuilderState::None,
276 (BuilderState::Init, MultiBlockPresence::Some(_)) => BuilderState::Some,
277 (BuilderState::Init, MultiBlockPresence::Full) => BuilderState::Full,
278 (BuilderState::None, MultiBlockPresence::None) => BuilderState::None,
279 (BuilderState::None, MultiBlockPresence::Some(_))
280 | (BuilderState::None, MultiBlockPresence::Full)
281 | (BuilderState::Some, _)
282 | (BuilderState::Full, MultiBlockPresence::None)
283 | (BuilderState::Full, MultiBlockPresence::Some(_)) => BuilderState::Some,
284 (BuilderState::Full, MultiBlockPresence::Full) => BuilderState::Full,
285 }
286 }
287
288 fn build(self) -> MultiBlockPresence {
289 match self.state {
290 BuilderState::Init | BuilderState::None => MultiBlockPresence::None,
291 BuilderState::Some => {
292 MultiBlockPresence::Some(clamp(self.hasher.digest128()).to_le_bytes())
293 }
294 BuilderState::Full => MultiBlockPresence::Full,
295 }
296 }
297}
298
299const fn clamp(s: u128) -> u128 {
302 if s == 0 {
303 1
304 } else if s == u128::MAX {
305 u128::MAX - 1
306 } else {
307 s
308 }
309}