-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathdatabase.rs
More file actions
351 lines (317 loc) · 12.2 KB
/
Copy pathdatabase.rs
File metadata and controls
351 lines (317 loc) · 12.2 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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
//! REVM database adapters backed by stateless trie witnesses.
use std::sync::{Arc, Mutex};
use alloy_consensus::BlockHeader as _;
use alloy_eips::eip2935::{HISTORY_SERVE_WINDOW, HISTORY_STORAGE_ADDRESS};
use alloy_primitives::{Address, B256, Bytes, U256, keccak256};
use alloy_rlp::Decodable as _;
use revm::{
Database,
database::states::bundle_state::BundleState,
primitives::{AddressMap, B256Map, U256Map},
state::{AccountInfo, Bytecode},
};
use tempo_primitives::TempoHeader;
use zone_precompiles::{L1StateError, L1StorageReader};
use crate::{
Error, StatelessSparseTrieError, TempoStateWitness, ZoneStateWitness,
mpt::{StatelessSparseTrie, index_node_pool},
};
/// Errors emitted while resolving an execution read against a witness.
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum WitnessDatabaseError {
/// A state path could not be resolved from the Zone or Tempo node pool.
#[error(transparent)]
Mpt(#[from] StatelessSparseTrieError),
/// REVM requested bytecode not supplied by the Zone witness.
#[error("missing bytecode in witness: {code_hash:?}")]
MissingCode { code_hash: B256 },
/// The Zone witness supplied the same bytecode preimage more than once.
#[error("duplicate bytecode hash in Zone state witness: {code_hash:?}")]
DuplicateBytecodeHash { code_hash: B256 },
/// The execution inputs assigned two different hashes to one Zone block number.
#[error("conflicting block hash for {number}: expected {expected:?}, got {actual:?}")]
ConflictingBlockHash {
number: u64,
expected: B256,
actual: B256,
},
/// The initial Tempo header is not a complete RLP-encoded Tempo header.
#[error("invalid initial Tempo header in witness")]
InvalidTempoHeader,
}
impl revm::database_interface::DBErrorMarker for WitnessDatabaseError {}
/// REVM database backed by a root-bound, fully revealed Zone state trie.
#[derive(Debug)]
pub struct WitnessDatabase {
state: StatelessSparseTrie,
accounts: AddressMap<Option<AccountInfo>>,
storage: AddressMap<U256Map<U256>>,
code_by_hash: B256Map<Bytecode>,
}
impl WitnessDatabase {
/// Create a Zone execution database rooted at the parent Zone header's
/// state root.
///
/// The node pool is fully revealed and checked against `state_root` before
/// this returns. Bytecode is looked up by the code hash proven in an
/// account leaf.
pub fn from_zone_state_witness(
witness: ZoneStateWitness,
state_root: B256,
) -> Result<Self, Error> {
let ZoneStateWitness {
node_pool,
bytecodes,
} = witness;
let state = StatelessSparseTrie::new(state_root, &node_pool)?;
let mut code_by_hash = B256Map::default();
for code in bytecodes {
let code_hash = keccak256(&code);
if code_by_hash
.insert(code_hash, Bytecode::new_raw(code))
.is_some()
{
return Err(WitnessDatabaseError::DuplicateBytecodeHash { code_hash }.into());
}
}
Ok(Self {
state,
accounts: AddressMap::default(),
storage: AddressMap::default(),
code_by_hash,
})
}
/// Apply one block's execution changes to the current Zone state trie and
/// return the resulting post-state root.
pub(crate) fn state_root(
&mut self,
bundle_state: BundleState,
) -> Result<B256, StatelessSparseTrieError> {
// Advance the trie from the previous block's root using this block's changes.
let state = reth_trie_common::HashedPostState::from_bundle_state::<
reth_trie_common::KeccakKeyHasher,
>(bundle_state.state());
let state_root = self.state.calculate_state_root(state)?;
// Keep database read caches coherent with the newly advanced trie.
for (address, account) in bundle_state.state() {
self.accounts.insert(*address, account.info.clone());
if account.status.is_storage_known() {
self.storage.remove(address);
}
let storage_entry = self.storage.entry(*address).or_default();
for (slot, value) in account.storage.iter() {
storage_entry.insert(*slot, value.present_value);
}
}
Ok(state_root)
}
}
impl Database for WitnessDatabase {
type Error = WitnessDatabaseError;
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
if let Some(account) = self.accounts.get(&address) {
return Ok(account.clone());
}
let account = self.state.account(address)?.map(|account| AccountInfo {
balance: account.balance,
nonce: account.nonce,
code_hash: account.code_hash,
account_id: None,
code: None,
});
self.accounts.insert(address, account.clone());
Ok(account)
}
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
self.code_by_hash
.get(&code_hash)
.cloned()
.ok_or(WitnessDatabaseError::MissingCode { code_hash })
}
fn storage(&mut self, address: Address, slot: U256) -> Result<U256, Self::Error> {
if let Some(value) = self
.storage
.get(&address)
.and_then(|slots| slots.get(&slot))
{
return Ok(*value);
}
let value = self.state.storage(address, slot)?;
self.storage.entry(address).or_default().insert(slot, value);
Ok(value)
}
fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
// EIP-2935 makes historical block hashes part of the authenticated Zone state.
// Resolve BLOCKHASH through the history contract so the ordinary storage witness
// proves the returned value against the parent header's state root.
let slot = U256::from(number % HISTORY_SERVE_WINDOW as u64);
let value = self.storage(HISTORY_STORAGE_ADDRESS, slot)?;
Ok(B256::from(value.to_be_bytes::<32>()))
}
}
/// Tempo state reader for the checkpoint header supplied in the witness.
///
/// When the shared node pool includes that checkpoint's root, it owns a fully
/// revealed immutable sparse trie. Otherwise it remains inactive and rejects
/// any Tempo storage read as an incomplete witness.
#[derive(Clone, Debug)]
pub struct TempoWitnessDatabase {
state: Option<Arc<StatelessSparseTrie>>,
state_root: B256,
tempo_block_hash: B256,
tempo_block_number: u64,
nodes: Arc<B256Map<Bytes>>,
missing_read: Arc<Mutex<Option<MissingTempoStorageRead>>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct MissingTempoStorageRead {
pub(crate) account: Address,
pub(crate) slot: B256,
pub(crate) block_number: u64,
}
impl TempoWitnessDatabase {
/// Construct the reader for the initial Tempo checkpoint.
pub fn from_tempo_state_witness(witness: TempoStateWitness) -> Result<Self, Error> {
let header = decode_checkpoint_header(&witness.initial_tempo_header_rlp)?;
// Every checkpoint imported by this batch resolves against the same
// pool, so it is hashed and indexed once here rather than per Zone block.
let nodes = Arc::new(index_node_pool(&witness.node_pool)?);
let state_root = header.state_root();
Ok(Self {
state: checkpoint_state(state_root, &nodes)?,
state_root,
tempo_block_hash: keccak256(&witness.initial_tempo_header_rlp),
tempo_block_number: header.number(),
nodes,
missing_read: Arc::default(),
})
}
/// Return a reader rooted at a Tempo header imported by the current Zone
/// block. `ZoneInbox.advanceTempo` validates that the header is the next
/// checkpoint before any Tempo-dependent system work executes.
pub(crate) fn with_imported_checkpoint(
self,
header_rlp: &alloy_primitives::Bytes,
) -> Result<Self, Error> {
let header = decode_checkpoint_header(header_rlp)?;
let state_root = header.state_root();
// A checkpoint that carries the previous state root resolves every read
// against the trie already revealed for it.
let state = if state_root == self.state_root {
self.state
} else {
checkpoint_state(state_root, &self.nodes)?
};
Ok(Self {
state,
state_root,
tempo_block_hash: keccak256(header_rlp),
tempo_block_number: header.number(),
nodes: self.nodes,
missing_read: self.missing_read,
})
}
/// Returns the checkpoint committed by the decoded initial Tempo header.
pub(crate) fn checkpoint(&self) -> (u64, B256) {
(self.tempo_block_number, self.tempo_block_hash)
}
pub(crate) fn missing_read(&self) -> Option<MissingTempoStorageRead> {
*self
.missing_read
.lock()
.expect("missing Tempo storage read mutex poisoned")
}
fn record_missing_read(&self, account: Address, slot: B256, block_number: u64) {
let mut missing = self
.missing_read
.lock()
.expect("missing Tempo storage read mutex poisoned");
missing.get_or_insert(MissingTempoStorageRead {
account,
slot,
block_number,
});
}
}
fn decode_checkpoint_header(header_rlp: &[u8]) -> Result<TempoHeader, Error> {
let mut encoded_header = header_rlp;
let header = TempoHeader::decode(&mut encoded_header)
.map_err(|_| WitnessDatabaseError::InvalidTempoHeader)?;
if !encoded_header.is_empty() {
return Err(WitnessDatabaseError::InvalidTempoHeader.into());
}
Ok(header)
}
fn checkpoint_state(
state_root: B256,
nodes: &B256Map<Bytes>,
) -> Result<Option<Arc<StatelessSparseTrie>>, Error> {
match StatelessSparseTrie::from_indexed_nodes(state_root, nodes) {
Ok(state) => Ok(Some(Arc::new(state))),
Err(StatelessSparseTrieError::MissingStateRootNode { .. }) => Ok(None),
Err(error) => Err(error.into()),
}
}
impl L1StorageReader for TempoWitnessDatabase {
fn read_l1_storage(
&self,
account: Address,
slot: B256,
tempo_block_number: u64,
) -> Result<B256, L1StateError> {
if tempo_block_number != self.tempo_block_number {
return Err(storage_unavailable(
account,
slot,
tempo_block_number,
"witness has no root for the requested checkpoint",
));
}
let state = self.state.as_ref().ok_or_else(|| {
self.record_missing_read(account, slot, tempo_block_number);
storage_unavailable(
account,
slot,
tempo_block_number,
"witness does not include the checkpoint state root",
)
})?;
let value = match state.storage(account, U256::from_be_bytes(slot.0)) {
Ok(value) => value,
Err(
error @ (StatelessSparseTrieError::IncompleteAccountProof { .. }
| StatelessSparseTrieError::IncompleteStorageProof { .. }),
) => {
self.record_missing_read(account, slot, tempo_block_number);
return Err(L1StateError::StorageUnavailable {
account,
slot,
block_number: tempo_block_number,
reason: format!("incomplete Tempo witness: {error}"),
});
}
Err(error) => {
return Err(L1StateError::StorageUnavailable {
account,
slot,
block_number: tempo_block_number,
reason: format!("invalid Tempo witness: {error}"),
});
}
};
Ok(B256::from(value.to_be_bytes::<32>()))
}
}
fn storage_unavailable(
account: Address,
slot: B256,
block_number: u64,
reason: &'static str,
) -> L1StateError {
L1StateError::StorageUnavailable {
account,
slot,
block_number,
reason: reason.into(),
}
}