-
Notifications
You must be signed in to change notification settings - Fork 212
Expand file tree
/
Copy pathlayering.rs
More file actions
215 lines (194 loc) · 7.37 KB
/
Copy pathlayering.rs
File metadata and controls
215 lines (194 loc) · 7.37 KB
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
use ethrex_common::H256;
use rustc_hash::FxHashMap;
use std::sync::Arc;
use ethrex_trie::{Nibbles, TrieDB, TrieError};
#[derive(Debug, Clone)]
struct TrieLayer {
nodes: FxHashMap<Vec<u8>, Vec<u8>>,
parent: H256,
id: usize,
}
#[derive(Clone, Debug)]
pub struct TrieLayerCache {
/// Monotonically increasing ID for layers, starting at 1.
/// TODO: this implementation panics on overflow
last_id: usize,
layers: FxHashMap<H256, Arc<TrieLayer>>,
/// Global bloom that accrues all layer blooms.
///
/// The bloom filter is used to avoid looking up all layers when the given path doesn't exist in any
/// layer, thus going directly to the database.
///
/// In case a bloom filter insert or merge fails, we need to mark the bloom filter as poisoned
/// so we never use it again, because if we don't we may be misled into believing a key is not present
/// on a diff layer when it is (i.e. a false negative), leading to wrong executions.
bloom: Option<qfilter::Filter>,
}
impl Default for TrieLayerCache {
fn default() -> Self {
// Try to create the bloom filter, if it fails use poison mode.
let bloom = Self::create_filter().ok();
Self {
bloom,
last_id: 0,
layers: Default::default(),
}
}
}
impl TrieLayerCache {
// TODO: tune this
fn create_filter() -> Result<qfilter::Filter, qfilter::Error> {
qfilter::Filter::new_resizeable(1_000_000, 100_000_000, 0.02)
.inspect_err(|e| tracing::warn!("could not create trie layering bloom filter {e}"))
}
pub fn get(&self, state_root: H256, key: &[u8]) -> Option<Vec<u8>> {
// Fast check to know if any layer may contains the given key.
// We can only be certain it doesn't exist, but if it returns true it may or not exist (false positive).
if let Some(filter) = &self.bloom
&& !filter.contains(key)
{
// TrieWrapper goes to db when returning None.
return None;
}
let mut current_state_root = state_root;
while let Some(layer) = self.layers.get(¤t_state_root) {
if let Some(value) = layer.nodes.get(key) {
return Some(value.clone());
}
current_state_root = layer.parent;
if current_state_root == state_root {
// TODO: check if this is possible in practice
// This can't happen in L1, due to system contracts irreversibly modifying state
// at each block.
// On L2, if no transactions are included in a block, the state root remains the same,
// but we handle that case in put_batch. It may happen, however, if someone modifies
// state with a privileged tx and later reverts it (since it doesn't update nonce).
panic!("State cycle found");
}
}
None
}
// TODO: use finalized hash to know when to commit
pub fn get_commitable(&self, mut state_root: H256, commit_threshold: usize) -> Option<H256> {
let mut counter = 0;
while let Some(layer) = self.layers.get(&state_root) {
state_root = layer.parent;
counter += 1;
if counter > commit_threshold {
return Some(state_root);
}
}
None
}
pub fn put_batch(
&mut self,
parent: H256,
state_root: H256,
key_values: Vec<(Nibbles, Vec<u8>)>,
) {
if parent == state_root && key_values.is_empty() {
return;
} else if parent == state_root {
tracing::error!("Inconsistent state: parent == state_root but key_values not empty");
return;
}
if self.layers.contains_key(&state_root) {
tracing::warn!("tried to insert a state_root that's already inserted");
return;
}
// add this new bloom to the global one.
if let Some(filter) = &mut self.bloom {
for (p, _) in &key_values {
if let Err(qfilter::Error::CapacityExceeded) = filter.insert(p.as_ref()) {
tracing::warn!("TrieLayerCache: put_batch capacity exceeded");
self.bloom = None;
break;
}
}
}
let nodes: FxHashMap<Vec<u8>, Vec<u8>> = key_values
.into_iter()
.map(|(path, value)| (path.into_vec(), value))
.collect();
self.last_id += 1;
let entry = TrieLayer {
nodes,
parent,
id: self.last_id,
};
self.layers.insert(state_root, Arc::new(entry));
}
/// Rebuilds the global bloom filter by inserting all keys from all layers.
pub fn rebuild_bloom(&mut self) {
let Ok(mut new_global_filter) = Self::create_filter() else {
tracing::warn!(
"TrieLayerCache: rebuild_bloom could not create new filter. Poisoning bloom."
);
self.bloom = None;
return;
};
for layer in self.layers.values() {
for path in layer.nodes.keys() {
if let Err(qfilter::Error::CapacityExceeded) = new_global_filter.insert(path) {
tracing::warn!(
"TrieLayerCache: rebuild_bloom capacity exceeded. Poisoning bloom."
);
self.bloom = None;
return;
}
}
}
self.bloom = Some(new_global_filter);
}
pub fn commit(&mut self, state_root: H256) -> Option<Vec<(Vec<u8>, Vec<u8>)>> {
let layer = match Arc::try_unwrap(self.layers.remove(&state_root)?) {
Ok(layer) => layer,
Err(layer) => TrieLayer::clone(&layer),
};
// ensure parents are commited
let parent_nodes = self.commit(layer.parent);
// older layers are useless
self.layers.retain(|_, item| item.id > layer.id);
self.rebuild_bloom(); // layers removed, rebuild global bloom filter.
Some(
parent_nodes
.unwrap_or_default()
.into_iter()
.chain(layer.nodes)
.collect(),
)
}
}
pub struct TrieWrapper {
pub state_root: H256,
pub inner: Arc<TrieLayerCache>,
pub db: Box<dyn TrieDB>,
pub prefix: Option<H256>,
}
pub fn apply_prefix(prefix: Option<H256>, path: Nibbles) -> Nibbles {
// Apply a prefix with an invalid nibble (17) as a separator, to
// differentiate between a state trie value and a storage trie root.
match prefix {
Some(prefix) => Nibbles::from_bytes(prefix.as_bytes())
.append_new(17)
.concat(&path),
None => path,
}
}
impl TrieDB for TrieWrapper {
fn flatkeyvalue_computed(&self, key: Nibbles) -> bool {
let key = apply_prefix(self.prefix, key);
self.db.flatkeyvalue_computed(key)
}
fn get(&self, key: Nibbles) -> Result<Option<Vec<u8>>, TrieError> {
let key = apply_prefix(self.prefix, key);
if let Some(value) = self.inner.get(self.state_root, key.as_ref()) {
return Ok(Some(value));
}
self.db.get(key)
}
fn put_batch(&self, _key_values: Vec<(Nibbles, Vec<u8>)>) -> Result<(), TrieError> {
// TODO: Get rid of this.
unimplemented!("This function should not be called");
}
}