1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use std::collections::VecDeque;
use crate::{
cache::UpdateCapable, errors::ClientResult, BlockHeight, ClientError, HasBlockInfo, MoreInfo,
};
use super::Client;
mod structs;
use itertools::Itertools;
pub use structs::*;
pub use upd_with_cache::REORG_CACHE_SIZE;
mod get_block_info;
mod upd_with_cache;
// (╥﹏╥)
impl<T: HasBlockInfo> Client<T> {
/// preferred function to fetch updates if you want to handle state by yourself
/// if you don't want to handle it by yourself take a look to `fetch_updates` with cache
///
/// requirements to provided `last_blocks_smetas`:
/// - heights should be sorted in asc order
/// - heights should not containg gaps (e.g. 1,2,4,5,6 (here 3 is skipped))
/// - hash chain should be valid (prev hash shoul be equal hash of previous block)
///
/// if reorg occures (hashes of fetched and provided are not equal)
/// returns sequence of `Update::RemoveBlock` which should be handled
pub async fn fetch_updates(
&self,
last_blocks_metas: &[BlockMeta],
) -> ClientResult<Vec<Update<T>>> {
if !last_blocks_metas.is_sorted_by(|a, b| a.height + 1 == b.height) {
println!(
"you give: {:?}",
last_blocks_metas.iter().map(|v| v.height).collect_vec()
);
return Err(ClientError::NotSorted);
}
let last_block = last_blocks_metas
.last()
.cloned()
.unwrap_or(BlockMeta::default());
let start_height = last_block.height + 1;
let fetched_blocks = self
.fetch_range(start_height..=BlockHeight::MAX)
.await
.err_log()?;
// perform checks of fetched
if let Some(f) = fetched_blocks.first() {
fetched_blocks
.iter()
.skip(1)
.try_fold((f.block.height, f.block.hash), |s, v| {
let (p_height, p_hash) = s;
let (height, hash, prev) =
(v.block.height, v.block.hash, v.block.prev_block_hash);
if p_height + 1 != height {
println!("gap in {p_height} {height}",);
return Err(ClientError::ReceivedHeightContainsGaps);
}
if p_hash != prev {
return Err(ClientError::ReceivedHashChainInvalid);
}
Ok((height, hash))
})?;
};
let valid_prev_hash = match fetched_blocks.first() {
Some(v) => v.block.prev_block_hash,
None => return Ok(vec![]),
};
let mut result = vec![];
// if reorg occured
if valid_prev_hash != last_block.hash && last_block.height != 0 {
let reorgs_len = last_blocks_metas.len() as BlockHeight;
let fetched_blocks = self
.fetch_range(last_block.height + 1 - reorgs_len..=last_block.height)
.await
.err_log()?;
let mut add = VecDeque::new();
for (old, new) in last_blocks_metas.iter().zip(fetched_blocks).rev() {
if old.hash != new.block.hash {
result.push(Update::RemoveBlock::<T> {
height: new.block.height,
block: None,
});
add.push_front(Update::AddBlock(new))
} else {
break;
}
}
result.extend(add);
}
result.extend(fetched_blocks.into_iter().map(|x| Update::AddBlock(x)));
if result.is_empty() {
eprintln!("Take your meds shizo");
return Ok(result);
}
// integrity check for updates
{
let mut h = result.first().unwrap().get_height() - 1;
if result.first().unwrap().is_remove() {
h += 1;
}
for update in &result {
match update {
Update::AddBlock(UpdateCapable {
block: BlockMeta { height, .. },
..
}) => {
h += 1;
if h != *height {
println!(
"blocks provider return not valid block range: {:?}",
result
.iter()
.map(|x| {
format!(
"{}{}",
if x.is_remove() { "r" } else { "a" },
x.get_height()
)
})
.collect_vec()
);
return Err(ClientError::CacheWithGapsFromProvider(h));
}
}
Update::RemoveBlock { .. } => {
h -= 1;
}
}
}
}
Ok(result)
}
}