@mainsail/evm 0.0.1-evm.52 → 0.0.1-evm.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +5922 -0
- package/Cargo.toml +34 -0
- package/bindings/Cargo.toml +33 -0
- package/bindings/build.rs +6 -0
- package/bindings/src/ctx.rs +667 -0
- package/bindings/src/lib.rs +2231 -0
- package/bindings/src/logger.rs +110 -0
- package/bindings/src/result.rs +542 -0
- package/bindings/src/utils.rs +71 -0
- package/core/Cargo.toml +36 -0
- package/core/src/account.rs +112 -0
- package/core/src/bytecode.rs +39 -0
- package/core/src/compression.rs +158 -0
- package/core/src/db.rs +3311 -0
- package/core/src/events.rs +9 -0
- package/core/src/historical.rs +544 -0
- package/core/src/legacy.rs +153 -0
- package/core/src/lib.rs +14 -0
- package/core/src/logger.rs +98 -0
- package/core/src/logs_bloom.rs +96 -0
- package/core/src/precompiles.rs +450 -0
- package/core/src/receipt.rs +153 -0
- package/core/src/state_changes.rs +122 -0
- package/core/src/state_commit.rs +615 -0
- package/core/src/state_root.rs +266 -0
- package/index.d.ts +1 -0
- package/index.js +52 -52
- package/package.json +5 -4
- package/scripts/postinstall.mjs +73 -0
package/core/src/db.rs
ADDED
|
@@ -0,0 +1,3311 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
borrow::Cow,
|
|
3
|
+
cell::RefCell,
|
|
4
|
+
cmp::Ordering,
|
|
5
|
+
collections::BTreeMap,
|
|
6
|
+
convert::Infallible,
|
|
7
|
+
path::PathBuf,
|
|
8
|
+
sync::{Arc, LazyLock, RwLock, RwLockReadGuard},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
use alloy_primitives::Bloom;
|
|
12
|
+
use heed::{Comparator, EnvFlags, EnvOpenOptions};
|
|
13
|
+
use revm::{
|
|
14
|
+
Database, DatabaseRef,
|
|
15
|
+
context::{DBErrorMarker, result::ExecutionResult},
|
|
16
|
+
database::{CacheState, TransitionAccount, TransitionState},
|
|
17
|
+
primitives::*,
|
|
18
|
+
state::{AccountInfo, Bytecode},
|
|
19
|
+
};
|
|
20
|
+
use serde::{Deserialize, Serialize};
|
|
21
|
+
|
|
22
|
+
use crate::{
|
|
23
|
+
account::{AccountInfoExtended, StoredAccountInfo},
|
|
24
|
+
bytecode::StoredBytecode,
|
|
25
|
+
compression::CompactBincode,
|
|
26
|
+
historical::{AccountHistory, HistoricalAccountData},
|
|
27
|
+
legacy::{LegacyAccountAttributes, LegacyAddress, LegacyColdWallet},
|
|
28
|
+
logger::{LogLevel, Logger},
|
|
29
|
+
receipt::{TxReceipt, map_execution_result},
|
|
30
|
+
state_changes,
|
|
31
|
+
state_commit::StateCommit,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
#[derive(Debug)]
|
|
35
|
+
pub(crate) struct AddressWrapper(Address);
|
|
36
|
+
impl heed::BytesEncode<'_> for AddressWrapper {
|
|
37
|
+
type EItem = AddressWrapper;
|
|
38
|
+
|
|
39
|
+
fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
|
|
40
|
+
Ok(Cow::Borrowed(item.0.as_slice()))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
impl heed::BytesDecode<'_> for AddressWrapper {
|
|
45
|
+
type DItem = AddressWrapper;
|
|
46
|
+
|
|
47
|
+
fn bytes_decode(bytes: &'_ [u8]) -> Result<Self::DItem, heed::BoxedError> {
|
|
48
|
+
Ok(AddressWrapper(Address::from_slice(bytes)))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[derive(Debug)]
|
|
53
|
+
pub(crate) struct LegacyAddressWrapper(LegacyAddress);
|
|
54
|
+
impl heed::BytesEncode<'_> for LegacyAddressWrapper {
|
|
55
|
+
type EItem = LegacyAddressWrapper;
|
|
56
|
+
|
|
57
|
+
fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
|
|
58
|
+
Ok(Cow::Borrowed(item.0.as_slice()))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl heed::BytesDecode<'_> for LegacyAddressWrapper {
|
|
63
|
+
type DItem = LegacyAddressWrapper;
|
|
64
|
+
|
|
65
|
+
fn bytes_decode(bytes: &'_ [u8]) -> Result<Self::DItem, heed::BoxedError> {
|
|
66
|
+
Ok(LegacyAddressWrapper(LegacyAddress::from_slice(bytes)))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[derive(Debug)]
|
|
71
|
+
pub(crate) struct HashWrapper(B256);
|
|
72
|
+
impl heed::BytesEncode<'_> for HashWrapper {
|
|
73
|
+
type EItem = HashWrapper;
|
|
74
|
+
|
|
75
|
+
fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
|
|
76
|
+
Ok(Cow::Borrowed(item.0.as_slice()))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
81
|
+
pub struct TransactionKey {
|
|
82
|
+
pub block_number: u64,
|
|
83
|
+
pub index: u16,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
impl TransactionKey {
|
|
87
|
+
pub fn new(block_number: u64, index: u16) -> Self {
|
|
88
|
+
Self {
|
|
89
|
+
block_number,
|
|
90
|
+
index,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Parses the "<block_number>-<index>" token exchanged across the napi boundary.
|
|
95
|
+
pub fn parse(token: &str) -> Option<Self> {
|
|
96
|
+
let (block_number, index) = token.split_once('-')?;
|
|
97
|
+
Some(Self {
|
|
98
|
+
block_number: block_number.parse().ok()?,
|
|
99
|
+
index: index.parse().ok()?,
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pub fn to_token(&self) -> String {
|
|
104
|
+
format!("{}-{}", self.block_number, self.index)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
impl heed::BytesEncode<'_> for TransactionKey {
|
|
109
|
+
type EItem = TransactionKey;
|
|
110
|
+
|
|
111
|
+
fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
|
|
112
|
+
let mut buffer = Vec::with_capacity(10);
|
|
113
|
+
buffer.extend_from_slice(&item.block_number.to_be_bytes());
|
|
114
|
+
buffer.extend_from_slice(&item.index.to_be_bytes());
|
|
115
|
+
Ok(Cow::Owned(buffer))
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
impl heed::BytesDecode<'_> for TransactionKey {
|
|
120
|
+
type DItem = TransactionKey;
|
|
121
|
+
|
|
122
|
+
fn bytes_decode(bytes: &'_ [u8]) -> Result<Self::DItem, heed::BoxedError> {
|
|
123
|
+
let Some((block_number, rest)) = bytes.split_first_chunk::<8>() else {
|
|
124
|
+
return Err("TransactionKey: truncated key".into());
|
|
125
|
+
};
|
|
126
|
+
let Some((index, _)) = rest.split_first_chunk::<2>() else {
|
|
127
|
+
return Err("TransactionKey: truncated key".into());
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
Ok(TransactionKey {
|
|
131
|
+
block_number: u64::from_be_bytes(*block_number),
|
|
132
|
+
index: u16::from_be_bytes(*index),
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[derive(Debug)]
|
|
138
|
+
pub(crate) struct StaticStringWrapper(&'static str);
|
|
139
|
+
impl heed::BytesEncode<'_> for StaticStringWrapper {
|
|
140
|
+
type EItem = StaticStringWrapper;
|
|
141
|
+
|
|
142
|
+
fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
|
|
143
|
+
Ok(Cow::Borrowed(item.0.as_bytes()))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type HeedBlockNumber = heed::types::U64<heed::byteorder::BigEndian>;
|
|
148
|
+
|
|
149
|
+
#[derive(Debug)]
|
|
150
|
+
pub(crate) struct StorageEntryWrapper(U256, U256);
|
|
151
|
+
impl heed::BytesEncode<'_> for StorageEntryWrapper {
|
|
152
|
+
type EItem = StorageEntryWrapper;
|
|
153
|
+
|
|
154
|
+
fn bytes_encode(item: &Self::EItem) -> Result<Cow<'_, [u8]>, heed::BoxedError> {
|
|
155
|
+
let a = item.0.as_le_bytes();
|
|
156
|
+
let b = item.1.as_le_bytes();
|
|
157
|
+
|
|
158
|
+
let mut combined = Vec::with_capacity(a.len() + b.len());
|
|
159
|
+
combined.extend_from_slice(a.as_ref());
|
|
160
|
+
combined.extend_from_slice(b.as_ref());
|
|
161
|
+
|
|
162
|
+
Ok(Cow::Owned(combined))
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
impl heed::BytesDecode<'_> for StorageEntryWrapper {
|
|
167
|
+
type DItem = StorageEntryWrapper;
|
|
168
|
+
|
|
169
|
+
fn bytes_decode(bytes: &'_ [u8]) -> Result<Self::DItem, heed::BoxedError> {
|
|
170
|
+
let a = U256::from_le_slice(&bytes[0..32]);
|
|
171
|
+
let b = U256::from_le_slice(&bytes[32..]);
|
|
172
|
+
Ok(StorageEntryWrapper(a, b))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
pub enum StorageEntryDupSortCmp {}
|
|
177
|
+
|
|
178
|
+
impl Comparator for StorageEntryDupSortCmp {
|
|
179
|
+
fn compare(a: &[u8], b: &[u8]) -> Ordering {
|
|
180
|
+
// The compared values are tuples of `StorageEntry` and sorted by the first tuple value (=32 byte)
|
|
181
|
+
// which corresponds to the storage slot location. The second half of the tuple is ignored.
|
|
182
|
+
a[..32].cmp(&b[..32])
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// txHash -> receipt
|
|
187
|
+
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
188
|
+
pub(crate) struct CommitReceipts {
|
|
189
|
+
tx_receipts: HashMap<B256, TxReceipt>,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
pub(crate) struct InnerStorage {
|
|
193
|
+
pub accounts: heed::Database<AddressWrapper, CompactBincode<StoredAccountInfo>>,
|
|
194
|
+
pub accounts_history: Option<
|
|
195
|
+
heed::Database<HeedBlockNumber, CompactBincode<BTreeMap<Address, HistoricalAccountData>>>,
|
|
196
|
+
>,
|
|
197
|
+
pub commits: heed::Database<HeedBlockNumber, CompactBincode<CommitReceipts>>,
|
|
198
|
+
pub contracts: heed::Database<HashWrapper, CompactBincode<StoredBytecode>>,
|
|
199
|
+
pub legacy_attributes: heed::Database<AddressWrapper, CompactBincode<LegacyAccountAttributes>>,
|
|
200
|
+
pub legacy_cold_wallets: heed::Database<LegacyAddressWrapper, CompactBincode<LegacyColdWallet>>,
|
|
201
|
+
pub storage: heed::Database<
|
|
202
|
+
AddressWrapper,
|
|
203
|
+
StorageEntryWrapper,
|
|
204
|
+
heed::DefaultComparator,
|
|
205
|
+
StorageEntryDupSortCmp,
|
|
206
|
+
>,
|
|
207
|
+
// Carried over from previous database-service.ts lmdb backend
|
|
208
|
+
pub state: heed::Database<StaticStringWrapper, heed::types::SerdeBincode<Bytes>>,
|
|
209
|
+
pub proofs: heed::Database<HeedBlockNumber, CompactBincode<ProofData>>,
|
|
210
|
+
pub blocks: heed::Database<HeedBlockNumber, CompactBincode<BlockHeaderData>>,
|
|
211
|
+
pub blocks_hash_number: heed::Database<HashWrapper, HeedBlockNumber>,
|
|
212
|
+
pub transactions: heed::Database<TransactionKey, CompactBincode<TransactionData>>,
|
|
213
|
+
pub transactions_hash_key: heed::Database<HashWrapper, TransactionKey>,
|
|
214
|
+
//
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// A key of (block_number, round, block_hash) used to associate state with a processable unit.
|
|
218
|
+
#[derive(Hash, PartialEq, Eq, Debug, Default, Clone, Copy)]
|
|
219
|
+
pub struct CommitKey(pub u64, pub u64, pub B256);
|
|
220
|
+
|
|
221
|
+
pub type BlsSig = revm::primitives::FixedBytes<96>;
|
|
222
|
+
|
|
223
|
+
#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
224
|
+
pub struct ProofData {
|
|
225
|
+
pub round: u32,
|
|
226
|
+
pub signature: BlsSig,
|
|
227
|
+
pub validator_set: u128,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
231
|
+
pub struct BlockHeaderData {
|
|
232
|
+
pub version: u8,
|
|
233
|
+
pub timestamp: u64,
|
|
234
|
+
pub number: u32,
|
|
235
|
+
pub round: u32,
|
|
236
|
+
pub hash: B256,
|
|
237
|
+
pub parent_hash: B256,
|
|
238
|
+
pub state_root: B256,
|
|
239
|
+
pub logs_bloom: Bloom,
|
|
240
|
+
pub transactions_root: B256,
|
|
241
|
+
pub transactions_count: u16,
|
|
242
|
+
pub gas_used: u32,
|
|
243
|
+
pub fee: U256,
|
|
244
|
+
pub reward: U256,
|
|
245
|
+
pub payload_size: u32,
|
|
246
|
+
pub proposer: Address,
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#[derive(Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
250
|
+
pub struct TransactionData {
|
|
251
|
+
pub from: Address,
|
|
252
|
+
pub sender_public_key: String,
|
|
253
|
+
pub legacy_address: Option<LegacyAddress>,
|
|
254
|
+
pub to: Option<Address>,
|
|
255
|
+
pub gas_limit: u64,
|
|
256
|
+
pub gas_price: u128,
|
|
257
|
+
pub value: U256,
|
|
258
|
+
pub nonce: u64,
|
|
259
|
+
pub data: Bytes,
|
|
260
|
+
pub v: u32,
|
|
261
|
+
pub r: U256,
|
|
262
|
+
pub s: U256,
|
|
263
|
+
pub legacy_second_signature: Option<String>,
|
|
264
|
+
pub tx_hash: B256,
|
|
265
|
+
pub block_number: u32,
|
|
266
|
+
pub index: u32,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#[derive(Default, PartialEq, Eq)]
|
|
270
|
+
pub struct CommitData {
|
|
271
|
+
pub proof: ProofData,
|
|
272
|
+
pub header: BlockHeaderData,
|
|
273
|
+
pub transactions: Vec<TransactionData>,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
#[derive(Clone, Debug, Default)]
|
|
277
|
+
pub struct PendingCommit {
|
|
278
|
+
pub key: CommitKey,
|
|
279
|
+
pub cache: CacheState,
|
|
280
|
+
pub results: BTreeMap<B256, (ExecutionResult, u64)>,
|
|
281
|
+
pub transitions: TransitionState,
|
|
282
|
+
|
|
283
|
+
pub cumulative_gas_used: u64,
|
|
284
|
+
|
|
285
|
+
// Map of legacy attributes
|
|
286
|
+
pub legacy_attributes: BTreeMap<Address, LegacyAccountAttributes>,
|
|
287
|
+
|
|
288
|
+
// Map of legacy cold wallets
|
|
289
|
+
pub legacy_cold_wallets: BTreeMap<LegacyAddress, LegacyColdWallet>,
|
|
290
|
+
|
|
291
|
+
// Keeps track of all merged legacy cold wallets in this commit;
|
|
292
|
+
// If an address is found in the map, then a lookup for presence of cold wallet has been performed.
|
|
293
|
+
// The option indicates whether a corresponding cold wallet has been found and merged. To avoid
|
|
294
|
+
// redundant lookups, any address present in the map is skipped when processing a transaction.
|
|
295
|
+
pub merged_legacy_cold_wallets: BTreeMap<Address, Option<(B256, LegacyAddress)>>,
|
|
296
|
+
|
|
297
|
+
// Optimization to avoid unnecessary (deep) clones of commit data.
|
|
298
|
+
pub built_commit: Option<StateCommit>,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
|
|
302
|
+
pub struct GenesisInfo {
|
|
303
|
+
pub account: Address,
|
|
304
|
+
pub deployer_account: Address,
|
|
305
|
+
pub validator_contract: Address,
|
|
306
|
+
pub username_contract: Address,
|
|
307
|
+
pub initial_block_number: u64,
|
|
308
|
+
pub initial_supply: U256,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
pub struct PersistentDB {
|
|
312
|
+
pub(crate) env: heed::Env,
|
|
313
|
+
pub(crate) inner: RefCell<InnerStorage>,
|
|
314
|
+
pub(crate) accounts_history: Option<AccountHistory>,
|
|
315
|
+
resize_lock: Arc<RwLock<()>>,
|
|
316
|
+
logger: Logger,
|
|
317
|
+
pub genesis_info: Option<GenesisInfo>,
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
#[derive(Default)]
|
|
321
|
+
pub struct PersistentDBOptions {
|
|
322
|
+
pub path: PathBuf,
|
|
323
|
+
pub logger: Option<Logger>,
|
|
324
|
+
pub history_size: Option<u64>,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
impl PersistentDBOptions {
|
|
328
|
+
pub fn new(path: PathBuf) -> Self {
|
|
329
|
+
Self {
|
|
330
|
+
path,
|
|
331
|
+
..Default::default()
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
pub fn with_logger(mut self, logger: Logger) -> Self {
|
|
336
|
+
self.logger.replace(logger);
|
|
337
|
+
self
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
pub fn with_history_size(mut self, history_size: u64) -> Self {
|
|
341
|
+
self.history_size.replace(history_size);
|
|
342
|
+
self
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
#[derive(thiserror::Error, Debug)]
|
|
347
|
+
pub enum Error {
|
|
348
|
+
#[error("IO error: {0}")]
|
|
349
|
+
IO(#[from] std::io::Error),
|
|
350
|
+
#[error("BytecodeDecode error: {0}")]
|
|
351
|
+
BytecodeDecode(#[from] revm::bytecode::BytecodeDecodeError),
|
|
352
|
+
#[error("heed error: {0}")]
|
|
353
|
+
Heed(#[from] heed::Error),
|
|
354
|
+
#[error("state error: {0}")]
|
|
355
|
+
State(String),
|
|
356
|
+
#[error("db full error")]
|
|
357
|
+
DbFull,
|
|
358
|
+
#[error("bincode error: {0}")]
|
|
359
|
+
Bincode(#[from] bincode::Error),
|
|
360
|
+
#[error("infallible error: {0}")]
|
|
361
|
+
Infallible(#[from] Infallible),
|
|
362
|
+
#[error("Lock error")]
|
|
363
|
+
Lock,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
impl DBErrorMarker for Error {}
|
|
367
|
+
|
|
368
|
+
static ENV: LazyLock<RwLock<HashMap<PathBuf, (heed::Env, Arc<RwLock<()>>)>>> =
|
|
369
|
+
LazyLock::new(RwLock::default);
|
|
370
|
+
|
|
371
|
+
impl PersistentDB {
|
|
372
|
+
const MAX_DBS: u32 = 12;
|
|
373
|
+
|
|
374
|
+
pub fn new(opts: PersistentDBOptions) -> Result<Self, Error> {
|
|
375
|
+
std::fs::create_dir_all(&opts.path)?;
|
|
376
|
+
|
|
377
|
+
let mut lock = ENV.write().map_err(|_| Error::Lock)?;
|
|
378
|
+
|
|
379
|
+
let (env, resize_lock) = match lock.get(&opts.path) {
|
|
380
|
+
Some((env, resize_lock)) => (env.clone(), resize_lock.clone()),
|
|
381
|
+
None => {
|
|
382
|
+
let mut env_builder = EnvOpenOptions::new();
|
|
383
|
+
|
|
384
|
+
let mut max_dbs = Self::MAX_DBS;
|
|
385
|
+
if opts.history_size.is_some() {
|
|
386
|
+
max_dbs += 1;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
env_builder.max_dbs(max_dbs);
|
|
390
|
+
env_builder.map_size(1 * MAP_SIZE_UNIT);
|
|
391
|
+
unsafe { env_builder.flags(EnvFlags::NO_SUB_DIR) };
|
|
392
|
+
|
|
393
|
+
let env = unsafe { env_builder.open(opts.path.join("evm.mdb")) }?;
|
|
394
|
+
// One resize gate per env, shared by every instance for this path.
|
|
395
|
+
let resize_lock = Arc::new(RwLock::new(()));
|
|
396
|
+
lock.insert(opts.path.clone(), (env.clone(), resize_lock.clone()));
|
|
397
|
+
|
|
398
|
+
(env, resize_lock)
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
Self::new_with_env(env, resize_lock, opts)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
pub fn new_with_env(
|
|
406
|
+
env: heed::Env,
|
|
407
|
+
resize_lock: Arc<RwLock<()>>,
|
|
408
|
+
opts: PersistentDBOptions,
|
|
409
|
+
) -> Result<Self, Error> {
|
|
410
|
+
let real_disk_size = env.real_disk_size()?;
|
|
411
|
+
if real_disk_size >= env.info().map_size as u64 {
|
|
412
|
+
// Ensure initial map size is always larger than disk size. Resize requires exclusive
|
|
413
|
+
// access to the (possibly shared) env, so take the write side of the gate.
|
|
414
|
+
let _resize_guard = resize_lock.write().map_err(|_| Error::Lock)?;
|
|
415
|
+
unsafe { env.resize(next_map_size(real_disk_size as usize))? };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Database creation is a write txn; hold the read side so a concurrent resize on the
|
|
419
|
+
// shared env cannot remap memory underneath it. Dropped right after commit, before
|
|
420
|
+
// `resize_lock` is moved into the struct.
|
|
421
|
+
let init_guard = resize_lock.read().map_err(|_| Error::Lock)?;
|
|
422
|
+
|
|
423
|
+
let tx_env = env.clone();
|
|
424
|
+
let mut wtxn = tx_env.write_txn()?;
|
|
425
|
+
|
|
426
|
+
let accounts = env.create_database::<AddressWrapper, CompactBincode<StoredAccountInfo>>(
|
|
427
|
+
&mut wtxn,
|
|
428
|
+
Some("accounts"),
|
|
429
|
+
)?;
|
|
430
|
+
|
|
431
|
+
let (accounts_history_db, accounts_history) = match opts.history_size {
|
|
432
|
+
Some(history_size) if history_size > 0 => {
|
|
433
|
+
let db = env.create_database::<HeedBlockNumber,CompactBincode<
|
|
434
|
+
BTreeMap<Address, HistoricalAccountData>>>(&mut wtxn, Some("accounts_history")) ?;
|
|
435
|
+
(Some(db), Some(AccountHistory::new(history_size)))
|
|
436
|
+
}
|
|
437
|
+
_ => (None, None),
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
let commits = env.create_database::<HeedBlockNumber, CompactBincode<CommitReceipts>>(
|
|
441
|
+
&mut wtxn,
|
|
442
|
+
Some("commits"),
|
|
443
|
+
)?;
|
|
444
|
+
let contracts = env.create_database::<HashWrapper, CompactBincode<StoredBytecode>>(
|
|
445
|
+
&mut wtxn,
|
|
446
|
+
Some("contracts"),
|
|
447
|
+
)?;
|
|
448
|
+
let legacy_attributes = env
|
|
449
|
+
.create_database::<AddressWrapper, CompactBincode<LegacyAccountAttributes>>(
|
|
450
|
+
&mut wtxn,
|
|
451
|
+
Some("legacy_attributes"),
|
|
452
|
+
)?;
|
|
453
|
+
let legacy_cold_wallets = env
|
|
454
|
+
.create_database::<LegacyAddressWrapper, CompactBincode<LegacyColdWallet>>(
|
|
455
|
+
&mut wtxn,
|
|
456
|
+
Some("legacy_cold_wallets"),
|
|
457
|
+
)?;
|
|
458
|
+
let storage = env
|
|
459
|
+
.database_options()
|
|
460
|
+
.types::<AddressWrapper, StorageEntryWrapper>()
|
|
461
|
+
.name("storage")
|
|
462
|
+
.flags(heed::DatabaseFlags::DUP_SORT)
|
|
463
|
+
.dup_sort_comparator::<StorageEntryDupSortCmp>()
|
|
464
|
+
.create(&mut wtxn)?;
|
|
465
|
+
|
|
466
|
+
// Carried over from previous database-service.ts lmdb backend
|
|
467
|
+
let state = env.create_database::<StaticStringWrapper, heed::types::SerdeBincode<Bytes>>(
|
|
468
|
+
&mut wtxn,
|
|
469
|
+
Some("state"),
|
|
470
|
+
)?;
|
|
471
|
+
let proofs = env.create_database::<HeedBlockNumber, CompactBincode<ProofData>>(
|
|
472
|
+
&mut wtxn,
|
|
473
|
+
Some("proofs"),
|
|
474
|
+
)?;
|
|
475
|
+
let blocks = env.create_database::<HeedBlockNumber, CompactBincode<BlockHeaderData>>(
|
|
476
|
+
&mut wtxn,
|
|
477
|
+
Some("blocks"),
|
|
478
|
+
)?;
|
|
479
|
+
let blocks_hash_number = env.create_database::<HashWrapper, HeedBlockNumber>(
|
|
480
|
+
&mut wtxn,
|
|
481
|
+
Some("blocks_hash_number"),
|
|
482
|
+
)?;
|
|
483
|
+
let transactions = env.create_database::<TransactionKey, CompactBincode<TransactionData>>(
|
|
484
|
+
&mut wtxn,
|
|
485
|
+
Some("transactions"),
|
|
486
|
+
)?;
|
|
487
|
+
let transactions_hash_key = env.create_database::<HashWrapper, TransactionKey>(
|
|
488
|
+
&mut wtxn,
|
|
489
|
+
Some("transactions_hash_key"),
|
|
490
|
+
)?;
|
|
491
|
+
|
|
492
|
+
wtxn.commit()?;
|
|
493
|
+
drop(init_guard);
|
|
494
|
+
|
|
495
|
+
Ok(Self {
|
|
496
|
+
env,
|
|
497
|
+
inner: RefCell::new(InnerStorage {
|
|
498
|
+
accounts,
|
|
499
|
+
accounts_history: accounts_history_db,
|
|
500
|
+
commits,
|
|
501
|
+
contracts,
|
|
502
|
+
legacy_attributes,
|
|
503
|
+
legacy_cold_wallets,
|
|
504
|
+
storage,
|
|
505
|
+
state,
|
|
506
|
+
blocks_hash_number,
|
|
507
|
+
blocks,
|
|
508
|
+
proofs,
|
|
509
|
+
transactions_hash_key,
|
|
510
|
+
transactions,
|
|
511
|
+
}),
|
|
512
|
+
accounts_history,
|
|
513
|
+
resize_lock,
|
|
514
|
+
logger: opts.logger.unwrap_or_default(),
|
|
515
|
+
genesis_info: None,
|
|
516
|
+
})
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
pub fn set_genesis_info(&mut self, genesis_info: GenesisInfo) -> Result<(), Error> {
|
|
520
|
+
self.with_write_txn(|wtxn| {
|
|
521
|
+
let inner = self.inner.borrow_mut();
|
|
522
|
+
|
|
523
|
+
if inner
|
|
524
|
+
.accounts
|
|
525
|
+
.get(wtxn, &AddressWrapper(genesis_info.account))?
|
|
526
|
+
.is_none()
|
|
527
|
+
{
|
|
528
|
+
inner.accounts.put(
|
|
529
|
+
wtxn,
|
|
530
|
+
&AddressWrapper(genesis_info.account),
|
|
531
|
+
&CompactBincode(&StoredAccountInfo::new(
|
|
532
|
+
genesis_info.initial_supply,
|
|
533
|
+
0,
|
|
534
|
+
KECCAK_EMPTY,
|
|
535
|
+
)),
|
|
536
|
+
)?;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
Ok(())
|
|
540
|
+
})?;
|
|
541
|
+
|
|
542
|
+
self.genesis_info.replace(genesis_info);
|
|
543
|
+
Ok(())
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
pub fn get_accounts(
|
|
547
|
+
&self,
|
|
548
|
+
offset: u64,
|
|
549
|
+
limit: u64,
|
|
550
|
+
) -> Result<(Option<u64>, Vec<AccountInfoExtended>), Error> {
|
|
551
|
+
self.with_read_txn(|tx_env| {
|
|
552
|
+
let iter = self
|
|
553
|
+
.inner
|
|
554
|
+
.borrow()
|
|
555
|
+
.accounts
|
|
556
|
+
.iter(tx_env)?
|
|
557
|
+
.skip(offset as usize);
|
|
558
|
+
|
|
559
|
+
let (cursor, mut accounts) = self.get_items(
|
|
560
|
+
iter,
|
|
561
|
+
|item| match item {
|
|
562
|
+
Some(item) => {
|
|
563
|
+
let (address, info) = item?;
|
|
564
|
+
Ok(Some(AccountInfoExtended {
|
|
565
|
+
address: address.0,
|
|
566
|
+
info: AccountInfo {
|
|
567
|
+
balance: info.balance,
|
|
568
|
+
nonce: info.nonce,
|
|
569
|
+
..Default::default()
|
|
570
|
+
},
|
|
571
|
+
..Default::default()
|
|
572
|
+
}))
|
|
573
|
+
}
|
|
574
|
+
None => Ok(None),
|
|
575
|
+
},
|
|
576
|
+
offset,
|
|
577
|
+
limit,
|
|
578
|
+
)?;
|
|
579
|
+
|
|
580
|
+
for account in accounts.iter_mut() {
|
|
581
|
+
if let Some(legacy_attributes) = self
|
|
582
|
+
.inner
|
|
583
|
+
.borrow()
|
|
584
|
+
.legacy_attributes
|
|
585
|
+
.get(tx_env, &AddressWrapper(account.address))?
|
|
586
|
+
{
|
|
587
|
+
account.legacy_attributes = legacy_attributes.0;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
Ok((cursor, accounts))
|
|
592
|
+
})
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
pub fn get_legacy_cold_wallets(
|
|
596
|
+
&self,
|
|
597
|
+
offset: u64,
|
|
598
|
+
limit: u64,
|
|
599
|
+
) -> Result<(Option<u64>, Vec<LegacyColdWallet>), Error> {
|
|
600
|
+
self.with_read_txn(|tx_env| {
|
|
601
|
+
let iter = self
|
|
602
|
+
.inner
|
|
603
|
+
.borrow()
|
|
604
|
+
.legacy_cold_wallets
|
|
605
|
+
.iter(tx_env)?
|
|
606
|
+
.skip(offset as usize);
|
|
607
|
+
|
|
608
|
+
self.get_items(
|
|
609
|
+
iter,
|
|
610
|
+
|item| match item {
|
|
611
|
+
Some(item) => {
|
|
612
|
+
let (_, legacy_cold_wallet) = item?;
|
|
613
|
+
Ok(Some(legacy_cold_wallet.0))
|
|
614
|
+
}
|
|
615
|
+
None => Ok(None),
|
|
616
|
+
},
|
|
617
|
+
offset,
|
|
618
|
+
limit,
|
|
619
|
+
)
|
|
620
|
+
})
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
pub fn get_receipts(
|
|
624
|
+
&self,
|
|
625
|
+
offset: u64,
|
|
626
|
+
limit: u64,
|
|
627
|
+
) -> Result<(Option<u64>, Vec<(u64, Vec<(B256, TxReceipt)>)>), Error> {
|
|
628
|
+
self.with_read_txn(|tx_env| {
|
|
629
|
+
let iter = self
|
|
630
|
+
.inner
|
|
631
|
+
.borrow()
|
|
632
|
+
.commits
|
|
633
|
+
.iter(tx_env)?
|
|
634
|
+
.skip(offset as usize);
|
|
635
|
+
|
|
636
|
+
self.get_items(
|
|
637
|
+
iter,
|
|
638
|
+
|item| match item {
|
|
639
|
+
Some(item) => {
|
|
640
|
+
let (block_number, commit) = item?;
|
|
641
|
+
Ok(Some((
|
|
642
|
+
block_number,
|
|
643
|
+
commit.0.tx_receipts.into_iter().collect(),
|
|
644
|
+
)))
|
|
645
|
+
}
|
|
646
|
+
None => Ok(None),
|
|
647
|
+
},
|
|
648
|
+
offset,
|
|
649
|
+
limit,
|
|
650
|
+
)
|
|
651
|
+
})
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
pub fn get_receipts_by_block_number(
|
|
655
|
+
&self,
|
|
656
|
+
block_number: u64,
|
|
657
|
+
) -> Result<HashMap<B256, TxReceipt>, Error> {
|
|
658
|
+
self.with_read_txn(|tx_env| {
|
|
659
|
+
let commit = self.inner.borrow().commits.get(tx_env, &block_number)?;
|
|
660
|
+
|
|
661
|
+
match commit {
|
|
662
|
+
Some(inner) => Ok(inner.0.tx_receipts),
|
|
663
|
+
None => Ok(Default::default()),
|
|
664
|
+
}
|
|
665
|
+
})
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
pub fn get_receipts_by_block_range(
|
|
669
|
+
&self,
|
|
670
|
+
from_block_number: u64,
|
|
671
|
+
to_block_number: u64,
|
|
672
|
+
) -> Result<Vec<(u64, Vec<(B256, TxReceipt)>)>, Error> {
|
|
673
|
+
assert!(
|
|
674
|
+
from_block_number <= to_block_number,
|
|
675
|
+
"from_block_number ({from_block_number}) must be <= to_block_number ({to_block_number})"
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
self.with_read_txn(|tx_env| {
|
|
679
|
+
let inner = self.inner.borrow();
|
|
680
|
+
let range = from_block_number..=to_block_number;
|
|
681
|
+
|
|
682
|
+
let capacity = to_block_number.saturating_sub(from_block_number).min(1024) as usize;
|
|
683
|
+
let mut receipts = Vec::with_capacity(capacity);
|
|
684
|
+
|
|
685
|
+
for item in inner.commits.range(&tx_env, &range)? {
|
|
686
|
+
let (block_number, commit) = item?;
|
|
687
|
+
receipts.push((block_number, commit.0.tx_receipts.into_iter().collect()));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
Ok(receipts)
|
|
691
|
+
})
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
pub fn get_commits_by_block_range(
|
|
695
|
+
&self,
|
|
696
|
+
from_block_number: u64,
|
|
697
|
+
to_block_number: u64,
|
|
698
|
+
max_bytes: u64,
|
|
699
|
+
) -> Result<Vec<(ProofData, BlockHeaderData, Vec<TransactionData>)>, Error> {
|
|
700
|
+
assert!(
|
|
701
|
+
from_block_number <= to_block_number,
|
|
702
|
+
"from_block_number ({from_block_number}) must be <= to_block_number ({to_block_number})"
|
|
703
|
+
);
|
|
704
|
+
assert!(max_bytes > 0, "max_bytes ({max_bytes}) must be > 0");
|
|
705
|
+
|
|
706
|
+
// Per-commit fixed cost charged against the budget on top of the block's transaction payload,
|
|
707
|
+
// so that a long run of (near-)empty blocks is still bounded by block count, not just bytes.
|
|
708
|
+
const PER_COMMIT_OVERHEAD_BYTES: u64 = 1024;
|
|
709
|
+
|
|
710
|
+
self.with_read_txn(|tx_env| {
|
|
711
|
+
let inner = self.inner.borrow();
|
|
712
|
+
|
|
713
|
+
let capacity = to_block_number.saturating_sub(from_block_number).min(512) as usize;
|
|
714
|
+
let mut commits = Vec::with_capacity(capacity);
|
|
715
|
+
let mut accumulated_bytes: u64 = 0;
|
|
716
|
+
|
|
717
|
+
for item in inner
|
|
718
|
+
.blocks
|
|
719
|
+
.range(tx_env, &(from_block_number..=to_block_number))?
|
|
720
|
+
{
|
|
721
|
+
let (block_number, header) = item?;
|
|
722
|
+
let estimated_bytes = header.0.payload_size as u64 + PER_COMMIT_OVERHEAD_BYTES;
|
|
723
|
+
accumulated_bytes += estimated_bytes;
|
|
724
|
+
if accumulated_bytes > max_bytes {
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Headers and proofs are written together per commit; a missing proof means the end of
|
|
729
|
+
// the available data has been reached.
|
|
730
|
+
let Some(proof) = inner.proofs.get(tx_env, &block_number)? else {
|
|
731
|
+
break;
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
// Collect this block's transactions via a single range scan over its key prefix; the
|
|
735
|
+
// keys sort by (block_number, index), so they arrive in index order.
|
|
736
|
+
let mut transactions = Vec::with_capacity(header.0.transactions_count as usize);
|
|
737
|
+
let tx_from = TransactionKey::new(block_number, 0);
|
|
738
|
+
let tx_to = TransactionKey::new(block_number, u16::MAX);
|
|
739
|
+
for tx_item in inner.transactions.range(tx_env, &(tx_from..=tx_to))? {
|
|
740
|
+
let (_, transaction) = tx_item?;
|
|
741
|
+
transactions.push(transaction.0);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
commits.push((proof.0, header.0, transactions));
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
Ok(commits)
|
|
748
|
+
})
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
pub fn get_historical_account_info(
|
|
752
|
+
&self,
|
|
753
|
+
block_number: u64,
|
|
754
|
+
address: Address,
|
|
755
|
+
) -> Result<(Option<AccountInfo>, bool), Error> {
|
|
756
|
+
match self.inner.borrow().accounts_history {
|
|
757
|
+
Some(db) => self.with_read_txn(|tx_env| match self.accounts_history.as_ref() {
|
|
758
|
+
Some(accounts_history) => {
|
|
759
|
+
let (data, missing_fallback) = accounts_history.get_by_block_and_address(
|
|
760
|
+
tx_env,
|
|
761
|
+
&db,
|
|
762
|
+
block_number,
|
|
763
|
+
&address,
|
|
764
|
+
)?;
|
|
765
|
+
|
|
766
|
+
match data {
|
|
767
|
+
Some(data) => Ok((
|
|
768
|
+
Some(AccountInfo {
|
|
769
|
+
balance: data.balance,
|
|
770
|
+
nonce: data.nonce,
|
|
771
|
+
code_hash: data.code_hash,
|
|
772
|
+
..Default::default()
|
|
773
|
+
}),
|
|
774
|
+
missing_fallback,
|
|
775
|
+
)),
|
|
776
|
+
None => Ok((None, missing_fallback)),
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
None => Ok((None, false)),
|
|
780
|
+
}),
|
|
781
|
+
None => Ok((None, false)),
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
pub fn get_legacy_attributes(
|
|
786
|
+
&self,
|
|
787
|
+
address: Address,
|
|
788
|
+
) -> Result<Option<LegacyAccountAttributes>, Error> {
|
|
789
|
+
self.with_read_txn(|tx_env| {
|
|
790
|
+
Ok(self
|
|
791
|
+
.inner
|
|
792
|
+
.borrow()
|
|
793
|
+
.legacy_attributes
|
|
794
|
+
.get(tx_env, &AddressWrapper(address))?
|
|
795
|
+
.map(|inner| inner.0))
|
|
796
|
+
})
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
pub fn get_legacy_cold_wallet(
|
|
800
|
+
&self,
|
|
801
|
+
address: LegacyAddress,
|
|
802
|
+
) -> Result<Option<LegacyColdWallet>, Error> {
|
|
803
|
+
self.with_read_txn(|tx_env| {
|
|
804
|
+
Ok(self
|
|
805
|
+
.inner
|
|
806
|
+
.borrow()
|
|
807
|
+
.legacy_cold_wallets
|
|
808
|
+
.get(tx_env, &LegacyAddressWrapper(address))?
|
|
809
|
+
.map(|inner| inner.0))
|
|
810
|
+
})
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
pub fn resize(&self) -> Result<(), Error> {
|
|
814
|
+
// Exclusive access: blocks until every in-flight transaction (across all instances sharing
|
|
815
|
+
// this env in the process) has released its read guard, and prevents new ones from starting
|
|
816
|
+
// until the remap completes. This is what makes the unsafe env.resize() sound.
|
|
817
|
+
let _resize_guard = self.resize_lock.write().map_err(|_| Error::Lock)?;
|
|
818
|
+
|
|
819
|
+
let info = self.env.info();
|
|
820
|
+
|
|
821
|
+
let current_map_size = info.map_size;
|
|
822
|
+
|
|
823
|
+
let next_map_size = next_map_size(current_map_size);
|
|
824
|
+
|
|
825
|
+
self.logger.log(
|
|
826
|
+
LogLevel::Info,
|
|
827
|
+
format!("resizing db {} -> {}", current_map_size, next_map_size),
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
unsafe { self.env.resize(next_map_size)? };
|
|
831
|
+
|
|
832
|
+
Ok(())
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
fn get_items<T, I, F>(
|
|
836
|
+
&self,
|
|
837
|
+
mut iter: impl Iterator<Item = I>,
|
|
838
|
+
map: F,
|
|
839
|
+
offset: u64,
|
|
840
|
+
limit: u64,
|
|
841
|
+
) -> Result<(Option<u64>, Vec<T>), Error>
|
|
842
|
+
where
|
|
843
|
+
F: Fn(Option<I>) -> Result<Option<T>, Error>,
|
|
844
|
+
{
|
|
845
|
+
let limit = limit as usize;
|
|
846
|
+
let mut items = Vec::with_capacity(limit);
|
|
847
|
+
|
|
848
|
+
loop {
|
|
849
|
+
let item = map(iter.next())?;
|
|
850
|
+
let Some(item) = item else {
|
|
851
|
+
break;
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
items.push(item);
|
|
855
|
+
|
|
856
|
+
if items.len() == limit {
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
let next = if items.len() == limit {
|
|
862
|
+
// return next offset as there might be more to read
|
|
863
|
+
Some(offset + items.len() as u64)
|
|
864
|
+
} else {
|
|
865
|
+
None
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
Ok((next, items))
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const MAP_SIZE_UNIT: usize = 1024 * 1024 * 1024; // 1 GB
|
|
873
|
+
fn next_map_size(map_size: usize) -> usize {
|
|
874
|
+
map_size / MAP_SIZE_UNIT * MAP_SIZE_UNIT + MAP_SIZE_UNIT
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
impl PersistentDB {
|
|
878
|
+
fn basic_ref_tx(
|
|
879
|
+
&self,
|
|
880
|
+
txn: &heed::RoTxn,
|
|
881
|
+
address: Address,
|
|
882
|
+
) -> Result<Option<AccountInfo>, Error> {
|
|
883
|
+
let inner = self.inner.borrow();
|
|
884
|
+
|
|
885
|
+
let basic = inner
|
|
886
|
+
.accounts
|
|
887
|
+
.get(txn, &AddressWrapper(address))?
|
|
888
|
+
.map(|a| a.0.into());
|
|
889
|
+
|
|
890
|
+
Ok(basic)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
fn code_by_hash_ref_tx(&self, txn: &heed::RoTxn, code_hash: B256) -> Result<Bytecode, Error> {
|
|
894
|
+
let inner = self.inner.borrow();
|
|
895
|
+
|
|
896
|
+
let contract = match inner.contracts.get(txn, &HashWrapper(code_hash))? {
|
|
897
|
+
Some(contract) => contract.0,
|
|
898
|
+
None => Default::default(),
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
Ok(contract.try_into()?)
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
fn storage_ref_tx(
|
|
905
|
+
&self,
|
|
906
|
+
txn: &heed::RoTxn,
|
|
907
|
+
address: Address,
|
|
908
|
+
index: U256,
|
|
909
|
+
) -> Result<U256, Error> {
|
|
910
|
+
let inner = self.inner.borrow_mut();
|
|
911
|
+
|
|
912
|
+
let mut iter = inner.storage.iter(txn)?;
|
|
913
|
+
let location = &StorageEntryWrapper(index, U256::ZERO);
|
|
914
|
+
|
|
915
|
+
match iter.move_on_key_dup(&AddressWrapper(address), &location)? {
|
|
916
|
+
Some((_, value)) if value.0 == location.0 => Ok(value.1),
|
|
917
|
+
_ => Ok(U256::ZERO),
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
fn block_hash_ref_tx(&self, txn: &heed::RoTxn, number: u64) -> Result<B256, Error> {
|
|
922
|
+
let inner = self.inner.borrow_mut();
|
|
923
|
+
|
|
924
|
+
let data = inner.blocks.get(txn, &number)?;
|
|
925
|
+
match data {
|
|
926
|
+
Some(data) => Ok(data.hash),
|
|
927
|
+
None => Ok(B256::ZERO),
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
impl Database for PersistentDB {
|
|
933
|
+
type Error = Error;
|
|
934
|
+
|
|
935
|
+
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
|
|
936
|
+
<Self as DatabaseRef>::basic_ref(self, address)
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
|
|
940
|
+
<Self as DatabaseRef>::code_by_hash_ref(self, code_hash)
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
|
|
944
|
+
<Self as DatabaseRef>::storage_ref(self, address, index)
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
|
|
948
|
+
<Self as DatabaseRef>::block_hash_ref(self, number)
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
impl DatabaseRef for PersistentDB {
|
|
953
|
+
type Error = Error;
|
|
954
|
+
|
|
955
|
+
fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
|
|
956
|
+
self.with_read_txn(|txn| self.basic_ref_tx(txn, address))
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
|
|
960
|
+
self.with_read_txn(|txn| self.code_by_hash_ref_tx(txn, code_hash))
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
|
|
964
|
+
self.with_read_txn(|txn| self.storage_ref_tx(txn, address, index))
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
|
|
968
|
+
self.with_read_txn(|txn| self.block_hash_ref_tx(txn, number))
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/// `DatabaseRef` view that serves all reads from one `RoTxn` instead of opening one per read.
|
|
973
|
+
/// Holds the resize gate for the txn's lifetime so the env can't be remapped while it's open.
|
|
974
|
+
pub struct TxnDatabaseReader<'a> {
|
|
975
|
+
db: &'a PersistentDB,
|
|
976
|
+
txn: heed::RoTxn<'a, heed::WithTls>,
|
|
977
|
+
_resize_guard: RwLockReadGuard<'a, ()>,
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
impl<'a> TxnDatabaseReader<'a> {
|
|
981
|
+
pub fn new(db: &'a PersistentDB) -> Result<Self, Error> {
|
|
982
|
+
let resize_guard = db.resize_lock.read().map_err(|_| Error::Lock)?;
|
|
983
|
+
let txn = db.env.read_txn()?;
|
|
984
|
+
Ok(Self {
|
|
985
|
+
db,
|
|
986
|
+
txn,
|
|
987
|
+
_resize_guard: resize_guard,
|
|
988
|
+
})
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
impl DatabaseRef for TxnDatabaseReader<'_> {
|
|
993
|
+
type Error = Error;
|
|
994
|
+
fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Error> {
|
|
995
|
+
self.db.basic_ref_tx(&self.txn, address)
|
|
996
|
+
}
|
|
997
|
+
fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Error> {
|
|
998
|
+
self.db.storage_ref_tx(&self.txn, address, index)
|
|
999
|
+
}
|
|
1000
|
+
fn code_by_hash_ref(&self, hash: B256) -> Result<Bytecode, Error> {
|
|
1001
|
+
self.db.code_by_hash_ref_tx(&self.txn, hash)
|
|
1002
|
+
}
|
|
1003
|
+
fn block_hash_ref(&self, number: u64) -> Result<B256, Error> {
|
|
1004
|
+
self.db.block_hash_ref_tx(&self.txn, number)
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
impl PersistentDB {
|
|
1009
|
+
pub fn commit(
|
|
1010
|
+
&self,
|
|
1011
|
+
state_commit: &mut StateCommit,
|
|
1012
|
+
commit_data: &Option<CommitData>,
|
|
1013
|
+
) -> Result<(), Error> {
|
|
1014
|
+
let StateCommit {
|
|
1015
|
+
key,
|
|
1016
|
+
change_set,
|
|
1017
|
+
results,
|
|
1018
|
+
} = state_commit;
|
|
1019
|
+
|
|
1020
|
+
match self.commit_to_db(key, change_set, commit_data, results) {
|
|
1021
|
+
Ok(_) => return Ok(()),
|
|
1022
|
+
Err(err) => match &err {
|
|
1023
|
+
Error::Heed(heed_err) => match heed_err {
|
|
1024
|
+
heed::Error::Mdb(mdb_err) => match mdb_err {
|
|
1025
|
+
heed::MdbError::MapFull => return Err(Error::DbFull),
|
|
1026
|
+
_ => return Err(err),
|
|
1027
|
+
},
|
|
1028
|
+
_ => return Err(err),
|
|
1029
|
+
},
|
|
1030
|
+
_ => return Err(err),
|
|
1031
|
+
},
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
fn commit_to_db(
|
|
1036
|
+
&self,
|
|
1037
|
+
key: &CommitKey,
|
|
1038
|
+
change_set: &mut state_changes::StateChangeset,
|
|
1039
|
+
commit_data: &Option<CommitData>,
|
|
1040
|
+
results: &BTreeMap<B256, (ExecutionResult, u64)>,
|
|
1041
|
+
) -> Result<(), Error> {
|
|
1042
|
+
self.with_write_txn(|rwtxn| {
|
|
1043
|
+
if self.is_block_committed(&rwtxn, key.0) {
|
|
1044
|
+
return Err(Error::State("block already committed".into()));
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
let inner = self.inner.borrow_mut();
|
|
1048
|
+
|
|
1049
|
+
let state_changes::StateChangeset {
|
|
1050
|
+
accounts,
|
|
1051
|
+
storage,
|
|
1052
|
+
contracts,
|
|
1053
|
+
legacy_attributes,
|
|
1054
|
+
legacy_cold_wallets,
|
|
1055
|
+
merged_legacy_cold_wallets,
|
|
1056
|
+
} = change_set;
|
|
1057
|
+
|
|
1058
|
+
// Update accounts
|
|
1059
|
+
for (address, account) in accounts.iter() {
|
|
1060
|
+
let address = AddressWrapper(*address);
|
|
1061
|
+
|
|
1062
|
+
if let Some(account) = account {
|
|
1063
|
+
inner.accounts.put(
|
|
1064
|
+
rwtxn,
|
|
1065
|
+
&address,
|
|
1066
|
+
&CompactBincode(&StoredAccountInfo::new(
|
|
1067
|
+
account.balance,
|
|
1068
|
+
account.nonce,
|
|
1069
|
+
account.code_hash,
|
|
1070
|
+
)),
|
|
1071
|
+
)?;
|
|
1072
|
+
} else {
|
|
1073
|
+
inner.accounts.delete(rwtxn, &address)?;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// Update account history
|
|
1078
|
+
if let Some(db) = &inner.accounts_history {
|
|
1079
|
+
self.accounts_history
|
|
1080
|
+
.as_ref()
|
|
1081
|
+
.expect("accounts history")
|
|
1082
|
+
.insert(
|
|
1083
|
+
rwtxn,
|
|
1084
|
+
db,
|
|
1085
|
+
key.0,
|
|
1086
|
+
accounts
|
|
1087
|
+
.iter()
|
|
1088
|
+
.map(|a| (a.0, a.1.clone().unwrap_or_default()))
|
|
1089
|
+
.collect(),
|
|
1090
|
+
)?;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Update legacy attributes
|
|
1094
|
+
for (address, legacy_attributes) in legacy_attributes.into_iter() {
|
|
1095
|
+
let address = AddressWrapper(*address);
|
|
1096
|
+
inner
|
|
1097
|
+
.legacy_attributes
|
|
1098
|
+
.put(rwtxn, &address, &CompactBincode(legacy_attributes))?;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Update legacy cold wallets
|
|
1102
|
+
for (address, legacy_cold_wallets) in legacy_cold_wallets.into_iter() {
|
|
1103
|
+
let address = LegacyAddressWrapper(*address);
|
|
1104
|
+
inner.legacy_cold_wallets.put(
|
|
1105
|
+
rwtxn,
|
|
1106
|
+
&address,
|
|
1107
|
+
&CompactBincode(legacy_cold_wallets),
|
|
1108
|
+
)?;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// Update contracts
|
|
1112
|
+
for (hash, bytecode) in contracts.into_iter() {
|
|
1113
|
+
inner.contracts.put(
|
|
1114
|
+
rwtxn,
|
|
1115
|
+
&HashWrapper(*hash),
|
|
1116
|
+
&CompactBincode(&bytecode.clone().into()),
|
|
1117
|
+
)?;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Update storage
|
|
1121
|
+
for state_changes::StorageChangeset {
|
|
1122
|
+
address,
|
|
1123
|
+
wipe_storage,
|
|
1124
|
+
storage,
|
|
1125
|
+
} in storage.into_iter()
|
|
1126
|
+
{
|
|
1127
|
+
let mut iter = inner.storage.iter_mut(rwtxn)?;
|
|
1128
|
+
let address = AddressWrapper(*address);
|
|
1129
|
+
|
|
1130
|
+
if iter.move_on_key(&address)? {
|
|
1131
|
+
if *wipe_storage {
|
|
1132
|
+
// wipe all existing storage for address
|
|
1133
|
+
unsafe { iter.del_current_with_flags(heed::DeleteFlags::NO_DUP_DATA)? };
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
for value in storage.into_iter() {
|
|
1138
|
+
let new_storage_value = &StorageEntryWrapper(value.0, value.1.present_value());
|
|
1139
|
+
|
|
1140
|
+
if let Some((_, iter_value)) =
|
|
1141
|
+
iter.move_on_key_dup(&address, &new_storage_value)?
|
|
1142
|
+
{
|
|
1143
|
+
// overwrite or delete if key matches
|
|
1144
|
+
if iter_value.0 == value.0 {
|
|
1145
|
+
if value.1.present_value().is_zero() {
|
|
1146
|
+
let success = unsafe { iter.del_current()? };
|
|
1147
|
+
assert!(success);
|
|
1148
|
+
} else if value.1.present_value() != iter_value.1 {
|
|
1149
|
+
unsafe {
|
|
1150
|
+
// overwrite current position of cursor
|
|
1151
|
+
let success = iter.put_current(&address, &new_storage_value)?;
|
|
1152
|
+
assert!(success);
|
|
1153
|
+
}
|
|
1154
|
+
} else {
|
|
1155
|
+
// skip unchanged storage
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// cursor matched existing entry, move on to next
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if value.1.present_value() != U256::ZERO {
|
|
1164
|
+
unsafe {
|
|
1165
|
+
iter.put_current_with_options(
|
|
1166
|
+
heed::PutFlags::NO_DUP_DATA,
|
|
1167
|
+
&address,
|
|
1168
|
+
&new_storage_value,
|
|
1169
|
+
)?;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Mark legacy cold wallets as merged in storage and migrate legacy attributes
|
|
1176
|
+
for (address, legacy) in merged_legacy_cold_wallets {
|
|
1177
|
+
self.logger.log(
|
|
1178
|
+
LogLevel::Info,
|
|
1179
|
+
format!(
|
|
1180
|
+
"Merging legacy cold wallet '{}' with '{}'",
|
|
1181
|
+
legacy.1, address
|
|
1182
|
+
),
|
|
1183
|
+
);
|
|
1184
|
+
|
|
1185
|
+
let key = &LegacyAddressWrapper(legacy.1);
|
|
1186
|
+
let mut legacy_cold_wallet = inner
|
|
1187
|
+
.legacy_cold_wallets
|
|
1188
|
+
.get(&rwtxn, key)?
|
|
1189
|
+
.expect("legacy cold wallet to be found")
|
|
1190
|
+
.0;
|
|
1191
|
+
|
|
1192
|
+
assert!(legacy_cold_wallet.merge_info.is_none());
|
|
1193
|
+
legacy_cold_wallet.merge_info.replace((legacy.0, *address));
|
|
1194
|
+
|
|
1195
|
+
inner
|
|
1196
|
+
.legacy_cold_wallets
|
|
1197
|
+
.put(rwtxn, key, &CompactBincode(&legacy_cold_wallet))?;
|
|
1198
|
+
|
|
1199
|
+
// The legacy balance has already been applied to the `PendingCommit`,
|
|
1200
|
+
// thus only the legacy attributes need to be moved to a different storage.
|
|
1201
|
+
inner.legacy_attributes.put(
|
|
1202
|
+
rwtxn,
|
|
1203
|
+
&AddressWrapper(*address),
|
|
1204
|
+
&CompactBincode(&legacy_cold_wallet.legacy_attributes),
|
|
1205
|
+
)?;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// ========================================
|
|
1209
|
+
//
|
|
1210
|
+
if let Some(commit_data) = commit_data {
|
|
1211
|
+
let CommitData {
|
|
1212
|
+
proof,
|
|
1213
|
+
header,
|
|
1214
|
+
transactions,
|
|
1215
|
+
} = commit_data;
|
|
1216
|
+
|
|
1217
|
+
// Update blocks
|
|
1218
|
+
inner.blocks.put(rwtxn, &key.0, &CompactBincode(header))?;
|
|
1219
|
+
inner
|
|
1220
|
+
.blocks_hash_number
|
|
1221
|
+
.put(rwtxn, &HashWrapper(header.hash), &key.0)?;
|
|
1222
|
+
|
|
1223
|
+
// Update proofs
|
|
1224
|
+
inner.proofs.put(rwtxn, &key.0, &CompactBincode(proof))?;
|
|
1225
|
+
|
|
1226
|
+
// Update transactions
|
|
1227
|
+
for (sequence, _) in transactions.iter().enumerate() {
|
|
1228
|
+
debug_assert!(sequence <= u16::MAX as usize);
|
|
1229
|
+
|
|
1230
|
+
let key = TransactionKey::new(key.0, sequence as u16);
|
|
1231
|
+
let transaction = &transactions[sequence];
|
|
1232
|
+
|
|
1233
|
+
inner.transactions_hash_key.put(
|
|
1234
|
+
rwtxn,
|
|
1235
|
+
&HashWrapper(transaction.tx_hash),
|
|
1236
|
+
&key,
|
|
1237
|
+
)?;
|
|
1238
|
+
|
|
1239
|
+
inner
|
|
1240
|
+
.transactions
|
|
1241
|
+
.put(rwtxn, &key, &CompactBincode(transaction))?;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// Update state
|
|
1245
|
+
let total_round_key = StaticStringWrapper("total_round");
|
|
1246
|
+
let current_total_round =
|
|
1247
|
+
read_total_round(inner.state.get(rwtxn, &total_round_key)?);
|
|
1248
|
+
|
|
1249
|
+
inner.state.put(
|
|
1250
|
+
rwtxn,
|
|
1251
|
+
&total_round_key,
|
|
1252
|
+
&Bytes::from_iter((current_total_round + proof.round as u64 + 1).to_le_bytes()),
|
|
1253
|
+
)?;
|
|
1254
|
+
}
|
|
1255
|
+
// ========================================
|
|
1256
|
+
|
|
1257
|
+
// Finalize commit
|
|
1258
|
+
let mut tx_receipts = HashMap::default();
|
|
1259
|
+
for (k, (result, cumulative_gas_used)) in results {
|
|
1260
|
+
let receipt = map_execution_result(result.clone(), *cumulative_gas_used);
|
|
1261
|
+
tx_receipts.insert(k.clone(), receipt);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
inner.commits.put(
|
|
1265
|
+
rwtxn,
|
|
1266
|
+
&key.0,
|
|
1267
|
+
&CompactBincode(&CommitReceipts { tx_receipts }),
|
|
1268
|
+
)?;
|
|
1269
|
+
|
|
1270
|
+
Ok(())
|
|
1271
|
+
})
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
pub fn is_block_committed(&self, rtxn: &heed::RoTxn, block_number: u64) -> bool {
|
|
1275
|
+
self.inner
|
|
1276
|
+
.borrow()
|
|
1277
|
+
.commits
|
|
1278
|
+
.get(rtxn, &block_number)
|
|
1279
|
+
.is_ok_and(|v| v.is_some())
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
pub fn get_receipt(
|
|
1283
|
+
&self,
|
|
1284
|
+
block_number: u64,
|
|
1285
|
+
tx_hash: B256,
|
|
1286
|
+
) -> Result<(bool, Option<TxReceipt>), Error> {
|
|
1287
|
+
self.with_read_txn(|rtxn| {
|
|
1288
|
+
let inner = self.inner.borrow();
|
|
1289
|
+
|
|
1290
|
+
match inner.commits.get(rtxn, &block_number)? {
|
|
1291
|
+
Some(receipts) => Ok((true, receipts.tx_receipts.get(&tx_hash).cloned())),
|
|
1292
|
+
None => Ok((false, None)),
|
|
1293
|
+
}
|
|
1294
|
+
})
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
pub fn is_empty(&self) -> Result<bool, Error> {
|
|
1298
|
+
self.with_read_txn(|rtxn| {
|
|
1299
|
+
let inner = self.inner.borrow();
|
|
1300
|
+
|
|
1301
|
+
Ok(inner.blocks.is_empty(rtxn)?)
|
|
1302
|
+
})
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
pub fn get_state(&self) -> Result<(u64, u64), Error> {
|
|
1306
|
+
self.with_read_txn(|rtxn| {
|
|
1307
|
+
let inner = self.inner.borrow();
|
|
1308
|
+
|
|
1309
|
+
let total_round =
|
|
1310
|
+
read_total_round(inner.state.get(rtxn, &StaticStringWrapper("total_round"))?);
|
|
1311
|
+
|
|
1312
|
+
let block_number = match inner.blocks.last(rtxn)? {
|
|
1313
|
+
Some((block_number, _)) => block_number,
|
|
1314
|
+
None => 0,
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
Ok((block_number, total_round))
|
|
1318
|
+
})
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
pub fn get_block_number_by_hash(&self, block_hash: B256) -> Result<Option<u64>, Error> {
|
|
1322
|
+
self.with_read_txn(|rtxn| {
|
|
1323
|
+
let inner = self.inner.borrow();
|
|
1324
|
+
|
|
1325
|
+
Ok(inner
|
|
1326
|
+
.blocks_hash_number
|
|
1327
|
+
.get(rtxn, &HashWrapper(block_hash))?)
|
|
1328
|
+
})
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
pub fn get_proof_data(&self, block_number: u64) -> Result<Option<ProofData>, Error> {
|
|
1332
|
+
self.with_read_txn(|rtxn| {
|
|
1333
|
+
let inner = self.inner.borrow();
|
|
1334
|
+
|
|
1335
|
+
Ok(inner.proofs.get(rtxn, &block_number)?.map(|data| data.0))
|
|
1336
|
+
})
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
pub fn get_block_header_data(
|
|
1340
|
+
&self,
|
|
1341
|
+
block_number: u64,
|
|
1342
|
+
) -> Result<Option<BlockHeaderData>, Error> {
|
|
1343
|
+
self.with_read_txn(|rtxn| {
|
|
1344
|
+
let inner = self.inner.borrow();
|
|
1345
|
+
|
|
1346
|
+
Ok(inner.blocks.get(rtxn, &block_number)?.map(|data| data.0))
|
|
1347
|
+
})
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
pub fn get_transaction(&self, key: TransactionKey) -> Result<Option<TransactionData>, Error> {
|
|
1351
|
+
self.with_read_txn(|rtxn| {
|
|
1352
|
+
let inner = self.inner.borrow();
|
|
1353
|
+
|
|
1354
|
+
Ok(inner.transactions.get(rtxn, &key)?.map(|data| data.0))
|
|
1355
|
+
})
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
pub fn get_transaction_data(&self, key: String) -> Result<Option<TransactionData>, Error> {
|
|
1359
|
+
match TransactionKey::parse(&key) {
|
|
1360
|
+
Some(key) => self.get_transaction(key),
|
|
1361
|
+
None => Ok(None),
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
pub fn get_transaction_key_by_hash(&self, tx_hash: B256) -> Result<Option<String>, Error> {
|
|
1366
|
+
self.with_read_txn(|rtxn| {
|
|
1367
|
+
let inner = self.inner.borrow();
|
|
1368
|
+
|
|
1369
|
+
Ok(inner
|
|
1370
|
+
.transactions_hash_key
|
|
1371
|
+
.get(rtxn, &HashWrapper(tx_hash))?
|
|
1372
|
+
.map(|key| key.to_token()))
|
|
1373
|
+
})
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/// Runs `f` inside a read txn while holding the shared resize guard, so the env can't be remapped
|
|
1377
|
+
/// (mdb_env_set_mapsize) while the txn is live.
|
|
1378
|
+
fn with_read_txn<T>(
|
|
1379
|
+
&self,
|
|
1380
|
+
f: impl FnOnce(&heed::RoTxn) -> Result<T, Error>,
|
|
1381
|
+
) -> Result<T, Error> {
|
|
1382
|
+
let _resize_guard = self.resize_lock.read().map_err(|_| Error::Lock)?;
|
|
1383
|
+
let txn = self.env.read_txn()?;
|
|
1384
|
+
f(&txn)
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/// Runs `f` inside a write txn while holding the shared resize guard, so the env can't be remapped
|
|
1388
|
+
/// (mdb_env_set_mapsize) while the txn is live.
|
|
1389
|
+
fn with_write_txn<T>(
|
|
1390
|
+
&self,
|
|
1391
|
+
f: impl FnOnce(&mut heed::RwTxn) -> Result<T, Error>,
|
|
1392
|
+
) -> Result<T, Error> {
|
|
1393
|
+
let _resize_guard = self.resize_lock.read().map_err(|_| Error::Lock)?;
|
|
1394
|
+
let mut txn = self.env.write_txn()?;
|
|
1395
|
+
let out = f(&mut txn)?;
|
|
1396
|
+
txn.commit()?;
|
|
1397
|
+
Ok(out)
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
fn read_total_round(item: Option<Bytes>) -> u64 {
|
|
1402
|
+
match item {
|
|
1403
|
+
Some(total_round) => {
|
|
1404
|
+
assert_eq!(total_round.len(), 8);
|
|
1405
|
+
let mut buffer = [0u8; 8];
|
|
1406
|
+
buffer[..8].copy_from_slice(&total_round[..8]);
|
|
1407
|
+
u64::from_le_bytes(buffer)
|
|
1408
|
+
}
|
|
1409
|
+
None => 0,
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
impl PendingCommit {
|
|
1414
|
+
pub fn new(key: CommitKey) -> Self {
|
|
1415
|
+
Self {
|
|
1416
|
+
key,
|
|
1417
|
+
cache: Default::default(),
|
|
1418
|
+
cumulative_gas_used: Default::default(),
|
|
1419
|
+
results: Default::default(),
|
|
1420
|
+
transitions: Default::default(),
|
|
1421
|
+
legacy_attributes: Default::default(),
|
|
1422
|
+
legacy_cold_wallets: Default::default(),
|
|
1423
|
+
merged_legacy_cold_wallets: Default::default(),
|
|
1424
|
+
built_commit: Default::default(),
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
pub fn import_account(
|
|
1429
|
+
&mut self,
|
|
1430
|
+
address: Address,
|
|
1431
|
+
info: AccountInfo,
|
|
1432
|
+
legacy_attributes: Option<LegacyAccountAttributes>,
|
|
1433
|
+
) {
|
|
1434
|
+
let mut state = revm::database::State::builder()
|
|
1435
|
+
.with_bundle_update()
|
|
1436
|
+
.with_cached_prestate(std::mem::take(&mut self.cache))
|
|
1437
|
+
.build();
|
|
1438
|
+
|
|
1439
|
+
let account = state
|
|
1440
|
+
.load_cache_account(address)
|
|
1441
|
+
.expect("load_cache_account");
|
|
1442
|
+
|
|
1443
|
+
let balance = info.balance.try_into().expect("fit u128");
|
|
1444
|
+
let transition_account = account
|
|
1445
|
+
.increment_balance(balance)
|
|
1446
|
+
.unwrap_or_else(|| TransitionAccount::new_empty_eip161(Default::default()));
|
|
1447
|
+
|
|
1448
|
+
let transitions = vec![(address, transition_account)];
|
|
1449
|
+
|
|
1450
|
+
self.transitions.add_transitions(transitions);
|
|
1451
|
+
|
|
1452
|
+
self.cache = std::mem::take(&mut state.cache);
|
|
1453
|
+
|
|
1454
|
+
if let Some(legacy_attributes) = legacy_attributes {
|
|
1455
|
+
self.legacy_attributes.insert(address, legacy_attributes);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
#[cfg(test)]
|
|
1461
|
+
mod tests {
|
|
1462
|
+
use std::collections::BTreeMap;
|
|
1463
|
+
|
|
1464
|
+
use crate::{
|
|
1465
|
+
account::StoredAccountInfo,
|
|
1466
|
+
compression::CompactBincode,
|
|
1467
|
+
db::{
|
|
1468
|
+
AddressWrapper, BlockHeaderData, CommitData, CommitKey, CommitReceipts, HashWrapper,
|
|
1469
|
+
LegacyAddressWrapper, MAP_SIZE_UNIT, PendingCommit, PersistentDB, PersistentDBOptions,
|
|
1470
|
+
ProofData, StaticStringWrapper, StorageEntryWrapper, TransactionData, TransactionKey,
|
|
1471
|
+
TxnDatabaseReader, next_map_size,
|
|
1472
|
+
},
|
|
1473
|
+
historical::HistoricalAccountData,
|
|
1474
|
+
legacy::{LegacyAccountAttributes, LegacyAddress, LegacyColdWallet},
|
|
1475
|
+
logger::Logger,
|
|
1476
|
+
receipt::TxReceipt,
|
|
1477
|
+
state_changes::{StateChangeset, StorageChangeset},
|
|
1478
|
+
state_commit::{StateCommit, build_commit},
|
|
1479
|
+
};
|
|
1480
|
+
use alloy_primitives::{Address, B256, Bytes, U256, address, b256};
|
|
1481
|
+
use revm::{
|
|
1482
|
+
Database, DatabaseRef,
|
|
1483
|
+
context::result::{ExecutionResult, ResultGas, SuccessReason},
|
|
1484
|
+
database::{TransitionState, states::StorageSlot},
|
|
1485
|
+
primitives::HashMap,
|
|
1486
|
+
state::{AccountInfo, Bytecode},
|
|
1487
|
+
};
|
|
1488
|
+
|
|
1489
|
+
use heed::{BytesDecode, BytesEncode, EnvFlags, EnvOpenOptions};
|
|
1490
|
+
|
|
1491
|
+
#[test]
|
|
1492
|
+
fn test_open_db() {
|
|
1493
|
+
let tmp = tempfile::Builder::new()
|
|
1494
|
+
.prefix("evm.mdb")
|
|
1495
|
+
.tempdir()
|
|
1496
|
+
.unwrap();
|
|
1497
|
+
|
|
1498
|
+
assert!(PersistentDB::new(PersistentDBOptions::new(tmp.path().to_path_buf())).is_ok());
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
#[test]
|
|
1502
|
+
fn test_open_db_with_logger() {
|
|
1503
|
+
let tmp = tempfile::Builder::new()
|
|
1504
|
+
.prefix("evm.mdb")
|
|
1505
|
+
.tempdir()
|
|
1506
|
+
.unwrap();
|
|
1507
|
+
|
|
1508
|
+
assert!(
|
|
1509
|
+
PersistentDB::new(
|
|
1510
|
+
PersistentDBOptions::new(tmp.path().to_path_buf()).with_logger(Logger::new(None))
|
|
1511
|
+
)
|
|
1512
|
+
.is_ok()
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
#[test]
|
|
1517
|
+
fn test_commit_changes() {
|
|
1518
|
+
let mut db = create_temp_database();
|
|
1519
|
+
|
|
1520
|
+
// 1) Lookup empty account
|
|
1521
|
+
let address = address!("bd6f65c58a46427af4b257cbe231d0ed69ed5508");
|
|
1522
|
+
let account = db.basic(address).expect("works");
|
|
1523
|
+
assert_eq!(account, None);
|
|
1524
|
+
|
|
1525
|
+
// 2) Update balance for account
|
|
1526
|
+
let mut state = HashMap::default();
|
|
1527
|
+
|
|
1528
|
+
let mut account = revm::state::Account::new_not_existing(revm::state::TransactionId::ZERO);
|
|
1529
|
+
account.info.balance = U256::from(100);
|
|
1530
|
+
account.status = revm::state::AccountStatus::Touched;
|
|
1531
|
+
|
|
1532
|
+
let code = Bytecode::new();
|
|
1533
|
+
account.info.code_hash = code.hash_slow();
|
|
1534
|
+
account.info.code = Some(code.clone());
|
|
1535
|
+
|
|
1536
|
+
let mut storage = HashMap::default();
|
|
1537
|
+
storage.insert(
|
|
1538
|
+
U256::from(1),
|
|
1539
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(1234)),
|
|
1540
|
+
);
|
|
1541
|
+
storage.insert(
|
|
1542
|
+
U256::from(2),
|
|
1543
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(5678)),
|
|
1544
|
+
);
|
|
1545
|
+
|
|
1546
|
+
state.insert(
|
|
1547
|
+
address,
|
|
1548
|
+
revm::database::TransitionAccount {
|
|
1549
|
+
status: revm::database::AccountStatus::InMemoryChange,
|
|
1550
|
+
info: Some(account.info.clone()),
|
|
1551
|
+
previous_status: revm::database::AccountStatus::Loaded,
|
|
1552
|
+
previous_info: None,
|
|
1553
|
+
storage,
|
|
1554
|
+
storage_was_destroyed: false,
|
|
1555
|
+
},
|
|
1556
|
+
);
|
|
1557
|
+
|
|
1558
|
+
crate::state_commit::commit_to_db(
|
|
1559
|
+
&mut db,
|
|
1560
|
+
PendingCommit {
|
|
1561
|
+
key: CommitKey::default(),
|
|
1562
|
+
transitions: TransitionState { transitions: state },
|
|
1563
|
+
..Default::default()
|
|
1564
|
+
},
|
|
1565
|
+
Default::default(),
|
|
1566
|
+
)
|
|
1567
|
+
.expect("ok");
|
|
1568
|
+
|
|
1569
|
+
// 3) Assert updated storage
|
|
1570
|
+
|
|
1571
|
+
// Balance
|
|
1572
|
+
let account = db.basic(address).expect("works").expect("account info");
|
|
1573
|
+
assert_eq!(account.balance, U256::from(100));
|
|
1574
|
+
|
|
1575
|
+
// Code
|
|
1576
|
+
assert_eq!(account.code_hash, code.hash_slow());
|
|
1577
|
+
let account_code = db.code_by_hash(code.hash_slow()).expect("code");
|
|
1578
|
+
assert_eq!(account_code, code);
|
|
1579
|
+
|
|
1580
|
+
// Storage
|
|
1581
|
+
let mut account_storage = db.storage(address, U256::from(1)).expect("storage");
|
|
1582
|
+
assert_eq!(account_storage, U256::from(1234));
|
|
1583
|
+
|
|
1584
|
+
account_storage = db.storage(address, U256::from(2)).expect("storage");
|
|
1585
|
+
assert_eq!(account_storage, U256::from(5678));
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
#[test]
|
|
1589
|
+
fn test_commit_built() {
|
|
1590
|
+
let mut db = create_temp_database();
|
|
1591
|
+
let mut pending_commit = PendingCommit::default();
|
|
1592
|
+
pending_commit.built_commit = Some(build_commit(&mut pending_commit).unwrap());
|
|
1593
|
+
|
|
1594
|
+
crate::state_commit::commit_to_db(&mut db, pending_commit, Default::default()).unwrap();
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
#[test]
|
|
1598
|
+
fn test_commit_built_without_precomputed_hashes() {
|
|
1599
|
+
let mut db = create_temp_database();
|
|
1600
|
+
let mut pending_commit = PendingCommit::default();
|
|
1601
|
+
pending_commit.built_commit = Some(build_commit(&mut pending_commit).unwrap());
|
|
1602
|
+
|
|
1603
|
+
crate::state_commit::commit_to_db(&mut db, pending_commit, Default::default()).unwrap();
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
#[test]
|
|
1607
|
+
fn test_storage() {
|
|
1608
|
+
let mut db = create_temp_database();
|
|
1609
|
+
|
|
1610
|
+
let address = address!("bd6f65c58a46427af4b257cbe231d0ed69ed5508");
|
|
1611
|
+
let mut state = HashMap::default();
|
|
1612
|
+
|
|
1613
|
+
let mut account = revm::state::Account::new_not_existing(revm::state::TransactionId::ZERO);
|
|
1614
|
+
account.status = revm::state::AccountStatus::Touched;
|
|
1615
|
+
|
|
1616
|
+
let mut storage = HashMap::default();
|
|
1617
|
+
|
|
1618
|
+
storage.insert(
|
|
1619
|
+
U256::from(99),
|
|
1620
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(99)),
|
|
1621
|
+
);
|
|
1622
|
+
storage.insert(
|
|
1623
|
+
U256::from(1),
|
|
1624
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(1)),
|
|
1625
|
+
);
|
|
1626
|
+
storage.insert(
|
|
1627
|
+
U256::from(101),
|
|
1628
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(101)),
|
|
1629
|
+
);
|
|
1630
|
+
storage.insert(
|
|
1631
|
+
U256::from(2),
|
|
1632
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(2)),
|
|
1633
|
+
);
|
|
1634
|
+
storage.insert(
|
|
1635
|
+
U256::from(4),
|
|
1636
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(4)),
|
|
1637
|
+
);
|
|
1638
|
+
|
|
1639
|
+
state.insert(
|
|
1640
|
+
address,
|
|
1641
|
+
revm::database::TransitionAccount {
|
|
1642
|
+
status: revm::database::AccountStatus::InMemoryChange,
|
|
1643
|
+
info: Some(account.info.clone()),
|
|
1644
|
+
previous_status: revm::database::AccountStatus::Loaded,
|
|
1645
|
+
previous_info: None,
|
|
1646
|
+
storage,
|
|
1647
|
+
storage_was_destroyed: false,
|
|
1648
|
+
},
|
|
1649
|
+
);
|
|
1650
|
+
|
|
1651
|
+
crate::state_commit::commit_to_db(
|
|
1652
|
+
&mut db,
|
|
1653
|
+
PendingCommit {
|
|
1654
|
+
key: CommitKey::default(),
|
|
1655
|
+
transitions: TransitionState { transitions: state },
|
|
1656
|
+
..Default::default()
|
|
1657
|
+
},
|
|
1658
|
+
Default::default(),
|
|
1659
|
+
)
|
|
1660
|
+
.expect("ok");
|
|
1661
|
+
|
|
1662
|
+
// Assert storage is sorted
|
|
1663
|
+
|
|
1664
|
+
let indexes = vec![1, 2, 4, 99, 101];
|
|
1665
|
+
|
|
1666
|
+
// Storage
|
|
1667
|
+
for index in indexes {
|
|
1668
|
+
let account_storage = db.storage(address, U256::from(index)).expect("storage");
|
|
1669
|
+
assert_eq!(account_storage, U256::from(index));
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
#[test]
|
|
1674
|
+
fn test_storage_overwrite() {
|
|
1675
|
+
let mut db = create_temp_database();
|
|
1676
|
+
|
|
1677
|
+
let address = address!("bd6f65c58a46427af4b257cbe231d0ed69ed5508");
|
|
1678
|
+
let mut state = HashMap::default();
|
|
1679
|
+
|
|
1680
|
+
let mut account = revm::state::Account::new_not_existing(revm::state::TransactionId::ZERO);
|
|
1681
|
+
account.status = revm::state::AccountStatus::Touched;
|
|
1682
|
+
|
|
1683
|
+
let mut storage = HashMap::default();
|
|
1684
|
+
|
|
1685
|
+
storage.insert(
|
|
1686
|
+
U256::from(1),
|
|
1687
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(1)),
|
|
1688
|
+
);
|
|
1689
|
+
storage.insert(
|
|
1690
|
+
U256::from(2),
|
|
1691
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(2)),
|
|
1692
|
+
);
|
|
1693
|
+
|
|
1694
|
+
state.insert(
|
|
1695
|
+
address,
|
|
1696
|
+
revm::database::TransitionAccount {
|
|
1697
|
+
status: revm::database::AccountStatus::InMemoryChange,
|
|
1698
|
+
info: Some(account.info.clone()),
|
|
1699
|
+
previous_status: revm::database::AccountStatus::Loaded,
|
|
1700
|
+
previous_info: None,
|
|
1701
|
+
storage,
|
|
1702
|
+
storage_was_destroyed: false,
|
|
1703
|
+
},
|
|
1704
|
+
);
|
|
1705
|
+
|
|
1706
|
+
crate::state_commit::commit_to_db(
|
|
1707
|
+
&mut db,
|
|
1708
|
+
PendingCommit {
|
|
1709
|
+
key: CommitKey::default(),
|
|
1710
|
+
transitions: TransitionState { transitions: state },
|
|
1711
|
+
..Default::default()
|
|
1712
|
+
},
|
|
1713
|
+
Default::default(),
|
|
1714
|
+
)
|
|
1715
|
+
.expect("ok");
|
|
1716
|
+
|
|
1717
|
+
// Assert storage
|
|
1718
|
+
let mut account_storage = db.storage(address, U256::from(1)).expect("storage");
|
|
1719
|
+
assert_eq!(account_storage, U256::from(1));
|
|
1720
|
+
account_storage = db.storage(address, U256::from(2)).expect("storage");
|
|
1721
|
+
assert_eq!(account_storage, U256::from(2));
|
|
1722
|
+
|
|
1723
|
+
// Now overwrite index 1
|
|
1724
|
+
let mut storage = HashMap::default();
|
|
1725
|
+
storage.insert(
|
|
1726
|
+
U256::from(1),
|
|
1727
|
+
revm::database::states::StorageSlot::new_changed(U256::from(1), U256::from(99)),
|
|
1728
|
+
);
|
|
1729
|
+
|
|
1730
|
+
let mut state = HashMap::default();
|
|
1731
|
+
state.insert(
|
|
1732
|
+
address,
|
|
1733
|
+
revm::database::TransitionAccount {
|
|
1734
|
+
status: revm::database::AccountStatus::Changed,
|
|
1735
|
+
info: Some(account.info.clone()),
|
|
1736
|
+
previous_status: revm::database::AccountStatus::Loaded,
|
|
1737
|
+
previous_info: None,
|
|
1738
|
+
storage,
|
|
1739
|
+
storage_was_destroyed: false,
|
|
1740
|
+
},
|
|
1741
|
+
);
|
|
1742
|
+
|
|
1743
|
+
crate::state_commit::commit_to_db(
|
|
1744
|
+
&mut db,
|
|
1745
|
+
PendingCommit {
|
|
1746
|
+
key: CommitKey(1, 0, B256::ZERO),
|
|
1747
|
+
transitions: TransitionState { transitions: state },
|
|
1748
|
+
..Default::default()
|
|
1749
|
+
},
|
|
1750
|
+
Default::default(),
|
|
1751
|
+
)
|
|
1752
|
+
.expect("ok");
|
|
1753
|
+
|
|
1754
|
+
// Assert storage again
|
|
1755
|
+
|
|
1756
|
+
// - index 1 was overwritte
|
|
1757
|
+
let mut account_storage = db.storage(address, U256::from(1)).expect("storage");
|
|
1758
|
+
assert_eq!(account_storage, U256::from(99));
|
|
1759
|
+
|
|
1760
|
+
// - index 2 remains unchanged
|
|
1761
|
+
account_storage = db.storage(address, U256::from(2)).expect("storage");
|
|
1762
|
+
assert_eq!(account_storage, U256::from(2));
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
#[test]
|
|
1766
|
+
fn test_next_map_size() {
|
|
1767
|
+
let input = vec![0, 1, 2, 3, 4];
|
|
1768
|
+
for i in input {
|
|
1769
|
+
let next = next_map_size(i * MAP_SIZE_UNIT);
|
|
1770
|
+
assert_eq!(next, (i + 1) * MAP_SIZE_UNIT);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
#[test]
|
|
1775
|
+
fn test_resize_on_commit() {
|
|
1776
|
+
let create_large_commit = |block_number: u64, n: usize| {
|
|
1777
|
+
let mut buf = vec![0; 32];
|
|
1778
|
+
buf[0..8].copy_from_slice(&block_number.to_le_bytes());
|
|
1779
|
+
let address = Address::from_word(ethers_core::utils::keccak256(buf).into());
|
|
1780
|
+
|
|
1781
|
+
let mut state = HashMap::default();
|
|
1782
|
+
|
|
1783
|
+
let mut account =
|
|
1784
|
+
revm::state::Account::new_not_existing(revm::state::TransactionId::ZERO);
|
|
1785
|
+
account.status = revm::state::AccountStatus::Touched;
|
|
1786
|
+
|
|
1787
|
+
let mut storage = HashMap::default();
|
|
1788
|
+
|
|
1789
|
+
for i in 0..n {
|
|
1790
|
+
storage.insert(
|
|
1791
|
+
U256::from(i + 1),
|
|
1792
|
+
revm::database::states::StorageSlot::new_changed(U256::ZERO, U256::from(1)),
|
|
1793
|
+
);
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
state.insert(
|
|
1797
|
+
address,
|
|
1798
|
+
revm::database::TransitionAccount {
|
|
1799
|
+
status: revm::database::AccountStatus::InMemoryChange,
|
|
1800
|
+
info: Some(account.info.clone()),
|
|
1801
|
+
previous_status: revm::database::AccountStatus::Loaded,
|
|
1802
|
+
previous_info: None,
|
|
1803
|
+
storage,
|
|
1804
|
+
storage_was_destroyed: false,
|
|
1805
|
+
},
|
|
1806
|
+
);
|
|
1807
|
+
|
|
1808
|
+
PendingCommit {
|
|
1809
|
+
key: CommitKey(block_number, 0, B256::ZERO),
|
|
1810
|
+
transitions: TransitionState { transitions: state },
|
|
1811
|
+
..Default::default()
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
|
|
1815
|
+
let path = tempfile::Builder::new()
|
|
1816
|
+
.prefix("evm.mdb")
|
|
1817
|
+
.tempdir()
|
|
1818
|
+
.unwrap();
|
|
1819
|
+
|
|
1820
|
+
let mut env_builder = EnvOpenOptions::new();
|
|
1821
|
+
env_builder.max_dbs(PersistentDB::MAX_DBS);
|
|
1822
|
+
env_builder.map_size(4096 * 10); // start with very small (few kB)
|
|
1823
|
+
|
|
1824
|
+
unsafe { env_builder.flags(EnvFlags::NO_SUB_DIR) };
|
|
1825
|
+
|
|
1826
|
+
let env = unsafe { env_builder.open(path.path().join("evm.mdb")) }.expect("ok");
|
|
1827
|
+
|
|
1828
|
+
let mut db = PersistentDB::new_with_env(
|
|
1829
|
+
env,
|
|
1830
|
+
std::sync::Arc::new(std::sync::RwLock::new(())),
|
|
1831
|
+
Default::default(),
|
|
1832
|
+
)
|
|
1833
|
+
.expect("open");
|
|
1834
|
+
assert_eq!(db.env.info().map_size, 4096 * 10);
|
|
1835
|
+
|
|
1836
|
+
// large commit to trigger a resize
|
|
1837
|
+
crate::state_commit::commit_to_db(
|
|
1838
|
+
&mut db,
|
|
1839
|
+
create_large_commit(0, 1024),
|
|
1840
|
+
Default::default(),
|
|
1841
|
+
)
|
|
1842
|
+
.expect("ok");
|
|
1843
|
+
|
|
1844
|
+
// increased to next MAP_SIZE_UNIT
|
|
1845
|
+
assert_eq!(db.env.info().map_size, MAP_SIZE_UNIT);
|
|
1846
|
+
|
|
1847
|
+
// add more commits without triggering another resize
|
|
1848
|
+
for i in 0..10 {
|
|
1849
|
+
crate::state_commit::commit_to_db(
|
|
1850
|
+
&mut db,
|
|
1851
|
+
create_large_commit(i + 1, 1024),
|
|
1852
|
+
Default::default(),
|
|
1853
|
+
)
|
|
1854
|
+
.expect("ok");
|
|
1855
|
+
assert_eq!(db.env.info().map_size, MAP_SIZE_UNIT);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// reopen db with initial env size should automatically resize
|
|
1859
|
+
drop(db);
|
|
1860
|
+
|
|
1861
|
+
let env = unsafe { env_builder.open(path.path().join("evm.mdb")) }.expect("ok");
|
|
1862
|
+
let db = PersistentDB::new_with_env(
|
|
1863
|
+
env,
|
|
1864
|
+
std::sync::Arc::new(std::sync::RwLock::new(())),
|
|
1865
|
+
Default::default(),
|
|
1866
|
+
)
|
|
1867
|
+
.expect("open");
|
|
1868
|
+
assert_eq!(db.env.info().map_size, MAP_SIZE_UNIT);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
#[test]
|
|
1872
|
+
fn test_read_accounts() {
|
|
1873
|
+
let db = create_temp_database();
|
|
1874
|
+
|
|
1875
|
+
let addresses = [
|
|
1876
|
+
address!("27b1fdb04752bbc536007a920d24acb045561c26"),
|
|
1877
|
+
address!("3599689E6292b81B2d85451025146515070129Bb"),
|
|
1878
|
+
address!("42712D45473476b98452f434e72461577D686318"),
|
|
1879
|
+
address!("52908400098527886E0F7030069857D2E4169EE7"),
|
|
1880
|
+
address!("5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"),
|
|
1881
|
+
address!("6549f4939460DE12611948b3f82b88C3C8975323"),
|
|
1882
|
+
address!("66f9664f97F2b50F62D13eA064982f936dE76657"),
|
|
1883
|
+
address!("8617E340B3D01FA5F11F306F4090FD50E238070D"),
|
|
1884
|
+
address!("88021160C5C792225E4E5452585947470010289D"),
|
|
1885
|
+
address!("D1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"),
|
|
1886
|
+
address!("dbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"),
|
|
1887
|
+
address!("de709f2102306220921060314715629080e2fb77"),
|
|
1888
|
+
address!("fB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"),
|
|
1889
|
+
];
|
|
1890
|
+
|
|
1891
|
+
{
|
|
1892
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
1893
|
+
|
|
1894
|
+
for (index, address) in addresses.iter().enumerate() {
|
|
1895
|
+
db.inner
|
|
1896
|
+
.borrow_mut()
|
|
1897
|
+
.accounts
|
|
1898
|
+
.put(
|
|
1899
|
+
&mut wtxn,
|
|
1900
|
+
&AddressWrapper(*address),
|
|
1901
|
+
&CompactBincode(&StoredAccountInfo {
|
|
1902
|
+
balance: U256::from(index),
|
|
1903
|
+
nonce: index as u64,
|
|
1904
|
+
..Default::default()
|
|
1905
|
+
}),
|
|
1906
|
+
)
|
|
1907
|
+
.unwrap();
|
|
1908
|
+
|
|
1909
|
+
db.inner
|
|
1910
|
+
.borrow_mut()
|
|
1911
|
+
.legacy_attributes
|
|
1912
|
+
.put(
|
|
1913
|
+
&mut wtxn,
|
|
1914
|
+
&AddressWrapper(*address),
|
|
1915
|
+
&CompactBincode(&LegacyAccountAttributes::default()),
|
|
1916
|
+
)
|
|
1917
|
+
.unwrap();
|
|
1918
|
+
}
|
|
1919
|
+
wtxn.commit().unwrap();
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
const LIMIT: u64 = 5;
|
|
1923
|
+
let mut offset = 0;
|
|
1924
|
+
|
|
1925
|
+
let mut read = 0;
|
|
1926
|
+
|
|
1927
|
+
loop {
|
|
1928
|
+
let (next, accounts) = db.get_accounts(offset, LIMIT).unwrap();
|
|
1929
|
+
for _ in accounts {
|
|
1930
|
+
read += 1;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
if next.is_none() {
|
|
1934
|
+
break;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
match next {
|
|
1938
|
+
Some(next) => {
|
|
1939
|
+
offset = next;
|
|
1940
|
+
}
|
|
1941
|
+
None => {
|
|
1942
|
+
break;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
assert_eq!(read, addresses.len());
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
#[test]
|
|
1951
|
+
fn test_get_cold_wallets() {
|
|
1952
|
+
let db = create_temp_database();
|
|
1953
|
+
|
|
1954
|
+
let legacy_addresses = [
|
|
1955
|
+
"DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x",
|
|
1956
|
+
"D5KU9KrMYXdkEsRbv4y8hvetGbsJwf9z3P",
|
|
1957
|
+
"DJA2sqCbnmR63sD8doGrXrK3fCiqcA4GUw",
|
|
1958
|
+
"DJmvhhiQFSrEQCq9FUxvcLcpcBjx7K3yLt",
|
|
1959
|
+
];
|
|
1960
|
+
|
|
1961
|
+
{
|
|
1962
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
1963
|
+
|
|
1964
|
+
for (index, legacy) in legacy_addresses.iter().enumerate() {
|
|
1965
|
+
let legacy_address: LegacyAddress = (*legacy).try_into().unwrap();
|
|
1966
|
+
db.inner
|
|
1967
|
+
.borrow_mut()
|
|
1968
|
+
.legacy_cold_wallets
|
|
1969
|
+
.put(
|
|
1970
|
+
&mut wtxn,
|
|
1971
|
+
&LegacyAddressWrapper(legacy_address),
|
|
1972
|
+
&CompactBincode(&LegacyColdWallet {
|
|
1973
|
+
address: legacy_address,
|
|
1974
|
+
balance: U256::from(index),
|
|
1975
|
+
legacy_attributes: Default::default(),
|
|
1976
|
+
merge_info: None,
|
|
1977
|
+
}),
|
|
1978
|
+
)
|
|
1979
|
+
.unwrap();
|
|
1980
|
+
}
|
|
1981
|
+
wtxn.commit().unwrap();
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
const LIMIT: u64 = 2;
|
|
1985
|
+
let mut offset = 0;
|
|
1986
|
+
|
|
1987
|
+
let mut read = 0;
|
|
1988
|
+
|
|
1989
|
+
loop {
|
|
1990
|
+
let (next, wallets) = db.get_legacy_cold_wallets(offset, LIMIT).unwrap();
|
|
1991
|
+
for wallet in wallets {
|
|
1992
|
+
read += 1;
|
|
1993
|
+
|
|
1994
|
+
let cold_wallet = db.get_legacy_cold_wallet(wallet.address).unwrap();
|
|
1995
|
+
assert_eq!(cold_wallet, Some(wallet));
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
if next.is_none() {
|
|
1999
|
+
break;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
match next {
|
|
2003
|
+
Some(next) => {
|
|
2004
|
+
offset = next;
|
|
2005
|
+
}
|
|
2006
|
+
None => {
|
|
2007
|
+
break;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
assert_eq!(read, legacy_addresses.len());
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
#[test]
|
|
2016
|
+
fn test_get_account_history() {
|
|
2017
|
+
let address1 = address!("0000000000000000000000000000000000000001");
|
|
2018
|
+
let address2 = address!("0000000000000000000000000000000000000002");
|
|
2019
|
+
|
|
2020
|
+
{
|
|
2021
|
+
let db = create_temp_database();
|
|
2022
|
+
let history = db.get_historical_account_info(1, address2).unwrap();
|
|
2023
|
+
assert_eq!(history, (None, false));
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
let db = create_temp_database_opts(|opts| {
|
|
2027
|
+
opts.history_size = Some(8);
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
assert!(db.accounts_history.is_some());
|
|
2031
|
+
|
|
2032
|
+
{
|
|
2033
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2034
|
+
|
|
2035
|
+
let mut entries = BTreeMap::default();
|
|
2036
|
+
entries.insert(
|
|
2037
|
+
address1,
|
|
2038
|
+
HistoricalAccountData {
|
|
2039
|
+
balance: U256::from(1),
|
|
2040
|
+
nonce: 0,
|
|
2041
|
+
code_hash: B256::ZERO,
|
|
2042
|
+
},
|
|
2043
|
+
);
|
|
2044
|
+
|
|
2045
|
+
db.inner
|
|
2046
|
+
.borrow_mut()
|
|
2047
|
+
.accounts_history
|
|
2048
|
+
.unwrap()
|
|
2049
|
+
.put(&mut wtxn, &1, &CompactBincode(&entries))
|
|
2050
|
+
.unwrap();
|
|
2051
|
+
|
|
2052
|
+
wtxn.commit().unwrap();
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
let history = db.get_historical_account_info(1, address1).unwrap();
|
|
2056
|
+
assert_eq!(
|
|
2057
|
+
history,
|
|
2058
|
+
(
|
|
2059
|
+
Some(AccountInfo {
|
|
2060
|
+
balance: U256::from(1),
|
|
2061
|
+
nonce: 0,
|
|
2062
|
+
code_hash: B256::ZERO,
|
|
2063
|
+
..Default::default()
|
|
2064
|
+
}),
|
|
2065
|
+
false
|
|
2066
|
+
)
|
|
2067
|
+
);
|
|
2068
|
+
|
|
2069
|
+
let history = db.get_historical_account_info(1, address2).unwrap();
|
|
2070
|
+
assert_eq!(history, (None, true));
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
#[test]
|
|
2074
|
+
fn test_legacy_address_wrapper() {
|
|
2075
|
+
let legacy_address: LegacyAddress =
|
|
2076
|
+
"DJmvhhiQFSrEQCq9FUxvcLcpcBjx7K3yLt".try_into().unwrap();
|
|
2077
|
+
|
|
2078
|
+
let wrapper = LegacyAddressWrapper(legacy_address);
|
|
2079
|
+
let serialized = <LegacyAddressWrapper as BytesEncode>::bytes_encode(&wrapper).expect("ok");
|
|
2080
|
+
|
|
2081
|
+
let deserialized =
|
|
2082
|
+
<LegacyAddressWrapper as BytesDecode>::bytes_decode(&serialized).expect("ok");
|
|
2083
|
+
assert_eq!(legacy_address, deserialized.0);
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
#[test]
|
|
2087
|
+
fn test_static_string_wrapper() {
|
|
2088
|
+
let string = "test";
|
|
2089
|
+
|
|
2090
|
+
let wrapper = StaticStringWrapper(string);
|
|
2091
|
+
let serialized = <StaticStringWrapper as BytesEncode>::bytes_encode(&wrapper).expect("ok");
|
|
2092
|
+
|
|
2093
|
+
assert_eq!(serialized, &b"test"[..]);
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
#[test]
|
|
2097
|
+
fn test_commit_key() {
|
|
2098
|
+
let key = CommitKey(0, 0, B256::ZERO);
|
|
2099
|
+
let mut pending = PendingCommit::new(key);
|
|
2100
|
+
|
|
2101
|
+
let info = AccountInfo {
|
|
2102
|
+
balance: U256::ONE,
|
|
2103
|
+
nonce: 1,
|
|
2104
|
+
code_hash: b256!("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
2105
|
+
account_id: None,
|
|
2106
|
+
code: None,
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
let attributes = LegacyAccountAttributes {
|
|
2110
|
+
legacy_nonce: Some(0),
|
|
2111
|
+
second_public_key: Some("key".into()),
|
|
2112
|
+
multi_signature: None,
|
|
2113
|
+
};
|
|
2114
|
+
|
|
2115
|
+
pending.import_account(
|
|
2116
|
+
address!("0000000000000000000000000000000000000001"),
|
|
2117
|
+
info,
|
|
2118
|
+
Some(attributes),
|
|
2119
|
+
);
|
|
2120
|
+
|
|
2121
|
+
let info = AccountInfo {
|
|
2122
|
+
balance: U256::ZERO,
|
|
2123
|
+
nonce: 0,
|
|
2124
|
+
code_hash: B256::ZERO,
|
|
2125
|
+
account_id: None,
|
|
2126
|
+
code: None,
|
|
2127
|
+
};
|
|
2128
|
+
pending.import_account(
|
|
2129
|
+
address!("0000000000000000000000000000000000000002"),
|
|
2130
|
+
info,
|
|
2131
|
+
None,
|
|
2132
|
+
);
|
|
2133
|
+
|
|
2134
|
+
assert_eq!(pending.transitions.transitions.len(), 2);
|
|
2135
|
+
assert_eq!(pending.legacy_attributes.len(), 1);
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
#[test]
|
|
2139
|
+
fn test_basic_ref() {
|
|
2140
|
+
let mut db = create_temp_database();
|
|
2141
|
+
|
|
2142
|
+
let genesis = address!("0000000000000000000000000000000000000000");
|
|
2143
|
+
let account = address!("0000000000000000000000000000000000000001");
|
|
2144
|
+
db.set_genesis_info(crate::db::GenesisInfo {
|
|
2145
|
+
account: genesis,
|
|
2146
|
+
initial_supply: U256::from(1_000_000),
|
|
2147
|
+
..Default::default()
|
|
2148
|
+
})
|
|
2149
|
+
.unwrap();
|
|
2150
|
+
|
|
2151
|
+
let info = db.basic(genesis).unwrap();
|
|
2152
|
+
assert_eq!(
|
|
2153
|
+
info,
|
|
2154
|
+
Some(AccountInfo {
|
|
2155
|
+
balance: U256::from(1_000_000),
|
|
2156
|
+
..Default::default()
|
|
2157
|
+
})
|
|
2158
|
+
);
|
|
2159
|
+
|
|
2160
|
+
let info = db.basic(account).unwrap();
|
|
2161
|
+
assert_eq!(info, None);
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
#[test]
|
|
2165
|
+
fn test_code_by_hash() {
|
|
2166
|
+
let mut db = create_temp_database();
|
|
2167
|
+
|
|
2168
|
+
let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
|
|
2169
|
+
|
|
2170
|
+
assert_eq!(db.code_by_hash(B256::ZERO).unwrap(), Default::default());
|
|
2171
|
+
|
|
2172
|
+
{
|
|
2173
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2174
|
+
let inner = db.inner.borrow_mut();
|
|
2175
|
+
inner
|
|
2176
|
+
.contracts
|
|
2177
|
+
.put(
|
|
2178
|
+
&mut wtxn,
|
|
2179
|
+
&HashWrapper(hash),
|
|
2180
|
+
&CompactBincode(&Bytecode::new_raw(Bytes::from_static(&[0, 1, 2, 3])).into()),
|
|
2181
|
+
)
|
|
2182
|
+
.unwrap();
|
|
2183
|
+
|
|
2184
|
+
wtxn.commit().unwrap();
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
assert_eq!(
|
|
2188
|
+
db.code_by_hash(hash).unwrap().original_byte_slice(),
|
|
2189
|
+
&[0, 1, 2, 3][..]
|
|
2190
|
+
);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
#[test]
|
|
2194
|
+
fn test_storage_refe() {
|
|
2195
|
+
let mut db = create_temp_database();
|
|
2196
|
+
|
|
2197
|
+
let account = address!("0000000000000000000000000000000000000001");
|
|
2198
|
+
|
|
2199
|
+
assert_eq!(db.storage(account, U256::ZERO).unwrap(), U256::ZERO);
|
|
2200
|
+
assert_eq!(db.storage(account, U256::from(1)).unwrap(), U256::ZERO);
|
|
2201
|
+
|
|
2202
|
+
{
|
|
2203
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2204
|
+
let inner = db.inner.borrow_mut();
|
|
2205
|
+
inner
|
|
2206
|
+
.storage
|
|
2207
|
+
.put(
|
|
2208
|
+
&mut wtxn,
|
|
2209
|
+
&AddressWrapper(account),
|
|
2210
|
+
&StorageEntryWrapper(U256::from(1), U256::from(2)),
|
|
2211
|
+
)
|
|
2212
|
+
.unwrap();
|
|
2213
|
+
|
|
2214
|
+
wtxn.commit().unwrap();
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
assert_eq!(db.storage(account, U256::from(1)).unwrap(), U256::from(2));
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
#[test]
|
|
2221
|
+
fn test_block_hash() {
|
|
2222
|
+
let mut db = create_temp_database();
|
|
2223
|
+
|
|
2224
|
+
let hash = db.block_hash(1).unwrap();
|
|
2225
|
+
assert_eq!(hash, B256::ZERO);
|
|
2226
|
+
|
|
2227
|
+
{
|
|
2228
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2229
|
+
let inner = db.inner.borrow_mut();
|
|
2230
|
+
|
|
2231
|
+
inner
|
|
2232
|
+
.blocks
|
|
2233
|
+
.put(
|
|
2234
|
+
&mut wtxn,
|
|
2235
|
+
&1,
|
|
2236
|
+
&CompactBincode(&BlockHeaderData {
|
|
2237
|
+
hash: b256!(
|
|
2238
|
+
"0000000000000000000000000000000000000000000000000000000000000001"
|
|
2239
|
+
),
|
|
2240
|
+
..Default::default()
|
|
2241
|
+
}),
|
|
2242
|
+
)
|
|
2243
|
+
.unwrap();
|
|
2244
|
+
|
|
2245
|
+
wtxn.commit().unwrap();
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
let hash = db.block_hash(1).unwrap();
|
|
2249
|
+
assert_eq!(
|
|
2250
|
+
hash,
|
|
2251
|
+
b256!("0000000000000000000000000000000000000000000000000000000000000001")
|
|
2252
|
+
);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
#[test]
|
|
2256
|
+
fn test_open_multiple_same_path() {
|
|
2257
|
+
let path = tempfile::Builder::new()
|
|
2258
|
+
.prefix("evm.mdb")
|
|
2259
|
+
.tempdir()
|
|
2260
|
+
.unwrap();
|
|
2261
|
+
|
|
2262
|
+
let db1 =
|
|
2263
|
+
PersistentDB::new(PersistentDBOptions::new(path.path().to_path_buf())).expect("db1");
|
|
2264
|
+
|
|
2265
|
+
let db2 =
|
|
2266
|
+
PersistentDB::new(PersistentDBOptions::new(path.path().to_path_buf())).expect("db2");
|
|
2267
|
+
|
|
2268
|
+
drop(db1);
|
|
2269
|
+
drop(db2);
|
|
2270
|
+
|
|
2271
|
+
assert!(true);
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
#[test]
|
|
2275
|
+
fn test_set_genesis_info() {
|
|
2276
|
+
let mut db = create_temp_database();
|
|
2277
|
+
|
|
2278
|
+
assert_eq!(db.genesis_info, None);
|
|
2279
|
+
|
|
2280
|
+
db.set_genesis_info(Default::default()).expect("ok");
|
|
2281
|
+
|
|
2282
|
+
assert_eq!(db.genesis_info, Some(Default::default()));
|
|
2283
|
+
}
|
|
2284
|
+
#[test]
|
|
2285
|
+
fn test_get_commits_by_block_range() {
|
|
2286
|
+
let db = create_temp_database();
|
|
2287
|
+
|
|
2288
|
+
// Empty range before anything is written.
|
|
2289
|
+
assert!(
|
|
2290
|
+
db.get_commits_by_block_range(1, 3, u64::MAX)
|
|
2291
|
+
.unwrap()
|
|
2292
|
+
.is_empty()
|
|
2293
|
+
);
|
|
2294
|
+
|
|
2295
|
+
// Write blocks 1..=3; block N has N transactions, inserted in reverse sequence order to
|
|
2296
|
+
// prove the reader returns them ordered by (block_number, sequence).
|
|
2297
|
+
{
|
|
2298
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2299
|
+
let inner = db.inner.borrow();
|
|
2300
|
+
|
|
2301
|
+
for block_number in 1u64..=3 {
|
|
2302
|
+
inner
|
|
2303
|
+
.blocks
|
|
2304
|
+
.put(
|
|
2305
|
+
&mut wtxn,
|
|
2306
|
+
&block_number,
|
|
2307
|
+
&CompactBincode(&BlockHeaderData {
|
|
2308
|
+
number: block_number as u32,
|
|
2309
|
+
transactions_count: block_number as u16,
|
|
2310
|
+
..Default::default()
|
|
2311
|
+
}),
|
|
2312
|
+
)
|
|
2313
|
+
.unwrap();
|
|
2314
|
+
|
|
2315
|
+
inner
|
|
2316
|
+
.proofs
|
|
2317
|
+
.put(
|
|
2318
|
+
&mut wtxn,
|
|
2319
|
+
&block_number,
|
|
2320
|
+
&CompactBincode(&ProofData {
|
|
2321
|
+
round: block_number as u32,
|
|
2322
|
+
..Default::default()
|
|
2323
|
+
}),
|
|
2324
|
+
)
|
|
2325
|
+
.unwrap();
|
|
2326
|
+
|
|
2327
|
+
for sequence in (0..block_number).rev() {
|
|
2328
|
+
inner
|
|
2329
|
+
.transactions
|
|
2330
|
+
.put(
|
|
2331
|
+
&mut wtxn,
|
|
2332
|
+
&TransactionKey::new(block_number, sequence as u16),
|
|
2333
|
+
&CompactBincode(&TransactionData {
|
|
2334
|
+
block_number: block_number as u32,
|
|
2335
|
+
index: sequence as u32,
|
|
2336
|
+
tx_hash: B256::from(U256::from(block_number * 100 + sequence)),
|
|
2337
|
+
..Default::default()
|
|
2338
|
+
}),
|
|
2339
|
+
)
|
|
2340
|
+
.unwrap();
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
wtxn.commit().unwrap();
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// Full range (unbounded budget): blocks ascending, transactions per block in sequence order.
|
|
2348
|
+
let commits = db.get_commits_by_block_range(1, 3, u64::MAX).unwrap();
|
|
2349
|
+
assert_eq!(commits.len(), 3);
|
|
2350
|
+
|
|
2351
|
+
for (index, (proof, header, transactions)) in commits.iter().enumerate() {
|
|
2352
|
+
let block_number = (index + 1) as u64;
|
|
2353
|
+
|
|
2354
|
+
assert_eq!(header.number, block_number as u32);
|
|
2355
|
+
assert_eq!(proof.round, block_number as u32);
|
|
2356
|
+
assert_eq!(transactions.len(), block_number as usize);
|
|
2357
|
+
|
|
2358
|
+
for (sequence, transaction) in transactions.iter().enumerate() {
|
|
2359
|
+
assert_eq!(transaction.index, sequence as u32);
|
|
2360
|
+
assert_eq!(transaction.block_number, block_number as u32);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// Sub-range returns only the requested block.
|
|
2365
|
+
let commits = db.get_commits_by_block_range(2, 2, u64::MAX).unwrap();
|
|
2366
|
+
assert_eq!(commits.len(), 1);
|
|
2367
|
+
assert_eq!(commits[0].1.number, 2);
|
|
2368
|
+
assert_eq!(commits[0].2.len(), 2);
|
|
2369
|
+
|
|
2370
|
+
// Range extending past the tip stops at the last available block.
|
|
2371
|
+
let commits = db.get_commits_by_block_range(2, 99, u64::MAX).unwrap();
|
|
2372
|
+
assert_eq!(commits.len(), 2);
|
|
2373
|
+
|
|
2374
|
+
// A too tiny byte budget stops early and does not make progress.
|
|
2375
|
+
let commits = db.get_commits_by_block_range(1, 3, 1).unwrap();
|
|
2376
|
+
assert!(commits.is_empty());
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
#[test]
|
|
2380
|
+
#[should_panic(expected = "must be <= to_block_number")]
|
|
2381
|
+
fn test_get_commits_by_block_range_panics_when_from_exceeds_to() {
|
|
2382
|
+
let db = create_temp_database();
|
|
2383
|
+
let _ = db.get_commits_by_block_range(3, 1, u64::MAX);
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
#[test]
|
|
2387
|
+
#[should_panic(expected = "must be > 0")]
|
|
2388
|
+
fn test_get_commits_by_block_range_panics_when_max_bytes_0() {
|
|
2389
|
+
let db = create_temp_database();
|
|
2390
|
+
let _ = db.get_commits_by_block_range(1, 3, 0);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
#[test]
|
|
2394
|
+
fn test_get_commits_by_block_range_respects_max_bytes() {
|
|
2395
|
+
let db = create_temp_database();
|
|
2396
|
+
|
|
2397
|
+
// A commit's budget cost is its payload_size plus a fixed per-commit overhead. Use a payload
|
|
2398
|
+
// large enough to dominate that overhead so the expected counts below are unambiguous without
|
|
2399
|
+
// coupling the test to the exact overhead constant.
|
|
2400
|
+
const PAYLOAD: u32 = 1_000_000;
|
|
2401
|
+
|
|
2402
|
+
{
|
|
2403
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2404
|
+
let inner = db.inner.borrow();
|
|
2405
|
+
|
|
2406
|
+
for block_number in 1u64..=3 {
|
|
2407
|
+
inner
|
|
2408
|
+
.blocks
|
|
2409
|
+
.put(
|
|
2410
|
+
&mut wtxn,
|
|
2411
|
+
&block_number,
|
|
2412
|
+
&CompactBincode(&BlockHeaderData {
|
|
2413
|
+
number: block_number as u32,
|
|
2414
|
+
payload_size: PAYLOAD,
|
|
2415
|
+
..Default::default()
|
|
2416
|
+
}),
|
|
2417
|
+
)
|
|
2418
|
+
.unwrap();
|
|
2419
|
+
|
|
2420
|
+
inner
|
|
2421
|
+
.proofs
|
|
2422
|
+
.put(
|
|
2423
|
+
&mut wtxn,
|
|
2424
|
+
&block_number,
|
|
2425
|
+
&CompactBincode(&ProofData::default()),
|
|
2426
|
+
)
|
|
2427
|
+
.unwrap();
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
wtxn.commit().unwrap();
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
let count = |max_bytes: u64| {
|
|
2434
|
+
db.get_commits_by_block_range(1, 3, max_bytes)
|
|
2435
|
+
.unwrap()
|
|
2436
|
+
.len()
|
|
2437
|
+
};
|
|
2438
|
+
|
|
2439
|
+
// The budget bounds how many commits come back: ~1 payload fits one, ~2 two, ~3 all three.
|
|
2440
|
+
const PER_COMMIT_OVERHEAD_BYTES: u64 = 1024;
|
|
2441
|
+
assert_eq!(count(PAYLOAD as u64 + PER_COMMIT_OVERHEAD_BYTES), 1);
|
|
2442
|
+
assert_eq!(count(2 * (PAYLOAD as u64 + PER_COMMIT_OVERHEAD_BYTES)), 2);
|
|
2443
|
+
assert_eq!(count(3 * (PAYLOAD as u64 + PER_COMMIT_OVERHEAD_BYTES)), 3);
|
|
2444
|
+
|
|
2445
|
+
// An unbounded budget returns the whole range.
|
|
2446
|
+
assert_eq!(count(u64::MAX), 3);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
#[test]
|
|
2450
|
+
fn test_txn_read_db_serves_all_reads() {
|
|
2451
|
+
// TxnReadDb answers every read kind through its single held txn, matching what the
|
|
2452
|
+
// transient DatabaseRef path returns (including the empties for unknown entries).
|
|
2453
|
+
let db = create_temp_database();
|
|
2454
|
+
|
|
2455
|
+
let account = address!("0000000000000000000000000000000000000001");
|
|
2456
|
+
let code = Bytecode::new_raw(Bytes::from_static(&[0, 1, 2, 3]));
|
|
2457
|
+
let code_hash = code.hash_slow();
|
|
2458
|
+
let block_hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
|
|
2459
|
+
|
|
2460
|
+
{
|
|
2461
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2462
|
+
let inner = db.inner.borrow_mut();
|
|
2463
|
+
|
|
2464
|
+
inner
|
|
2465
|
+
.accounts
|
|
2466
|
+
.put(
|
|
2467
|
+
&mut wtxn,
|
|
2468
|
+
&AddressWrapper(account),
|
|
2469
|
+
&CompactBincode(&StoredAccountInfo::new(U256::from(100), 7, code_hash)),
|
|
2470
|
+
)
|
|
2471
|
+
.unwrap();
|
|
2472
|
+
inner
|
|
2473
|
+
.contracts
|
|
2474
|
+
.put(
|
|
2475
|
+
&mut wtxn,
|
|
2476
|
+
&HashWrapper(code_hash),
|
|
2477
|
+
&CompactBincode(&code.clone().into()),
|
|
2478
|
+
)
|
|
2479
|
+
.unwrap();
|
|
2480
|
+
inner
|
|
2481
|
+
.storage
|
|
2482
|
+
.put(
|
|
2483
|
+
&mut wtxn,
|
|
2484
|
+
&AddressWrapper(account),
|
|
2485
|
+
&StorageEntryWrapper(U256::from(1), U256::from(42)),
|
|
2486
|
+
)
|
|
2487
|
+
.unwrap();
|
|
2488
|
+
inner
|
|
2489
|
+
.blocks
|
|
2490
|
+
.put(
|
|
2491
|
+
&mut wtxn,
|
|
2492
|
+
&1,
|
|
2493
|
+
&CompactBincode(&BlockHeaderData {
|
|
2494
|
+
hash: block_hash,
|
|
2495
|
+
..Default::default()
|
|
2496
|
+
}),
|
|
2497
|
+
)
|
|
2498
|
+
.unwrap();
|
|
2499
|
+
|
|
2500
|
+
wtxn.commit().unwrap();
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
let read_db = TxnDatabaseReader::new(&db).unwrap();
|
|
2504
|
+
|
|
2505
|
+
let info = read_db.basic_ref(account).unwrap().expect("account");
|
|
2506
|
+
assert_eq!(info.balance, U256::from(100));
|
|
2507
|
+
assert_eq!(info.nonce, 7);
|
|
2508
|
+
assert_eq!(info.code_hash, code_hash);
|
|
2509
|
+
|
|
2510
|
+
assert_eq!(
|
|
2511
|
+
read_db
|
|
2512
|
+
.code_by_hash_ref(code_hash)
|
|
2513
|
+
.unwrap()
|
|
2514
|
+
.original_byte_slice(),
|
|
2515
|
+
&[0, 1, 2, 3][..]
|
|
2516
|
+
);
|
|
2517
|
+
assert_eq!(
|
|
2518
|
+
read_db.storage_ref(account, U256::from(1)).unwrap(),
|
|
2519
|
+
U256::from(42)
|
|
2520
|
+
);
|
|
2521
|
+
assert_eq!(
|
|
2522
|
+
read_db.storage_ref(account, U256::from(2)).unwrap(),
|
|
2523
|
+
U256::ZERO
|
|
2524
|
+
);
|
|
2525
|
+
assert_eq!(read_db.block_hash_ref(1).unwrap(), block_hash);
|
|
2526
|
+
|
|
2527
|
+
// Unknown entries return the documented empties.
|
|
2528
|
+
let other = address!("0000000000000000000000000000000000000002");
|
|
2529
|
+
assert_eq!(read_db.basic_ref(other).unwrap(), None);
|
|
2530
|
+
assert_eq!(read_db.block_hash_ref(2).unwrap(), B256::ZERO);
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
#[test]
|
|
2534
|
+
fn test_commit_persists_transactions_for_range_read() {
|
|
2535
|
+
// Exercises the real write path (commit_to_db via db.commit) end to end, unlike
|
|
2536
|
+
// test_get_commits_by_block_range which writes the transactions DB directly. Guards against a
|
|
2537
|
+
// key mismatch between how commit_to_db writes transactions and how get_commits_by_block_range
|
|
2538
|
+
// scans them.
|
|
2539
|
+
let db = create_temp_database();
|
|
2540
|
+
|
|
2541
|
+
let block_number = 1u64;
|
|
2542
|
+
let transaction_count = 3u16;
|
|
2543
|
+
|
|
2544
|
+
let transactions: Vec<TransactionData> = (0..transaction_count)
|
|
2545
|
+
.map(|index| TransactionData {
|
|
2546
|
+
block_number: block_number as u32,
|
|
2547
|
+
index: index as u32,
|
|
2548
|
+
tx_hash: B256::from(U256::from(100 + index as u64)),
|
|
2549
|
+
..Default::default()
|
|
2550
|
+
})
|
|
2551
|
+
.collect();
|
|
2552
|
+
|
|
2553
|
+
let mut state_commit = StateCommit {
|
|
2554
|
+
key: CommitKey(block_number, 0, B256::ZERO),
|
|
2555
|
+
change_set: StateChangeset::default(),
|
|
2556
|
+
results: Default::default(),
|
|
2557
|
+
};
|
|
2558
|
+
|
|
2559
|
+
let commit_data = CommitData {
|
|
2560
|
+
proof: ProofData::default(),
|
|
2561
|
+
header: BlockHeaderData {
|
|
2562
|
+
number: block_number as u32,
|
|
2563
|
+
transactions_count: transaction_count,
|
|
2564
|
+
..Default::default()
|
|
2565
|
+
},
|
|
2566
|
+
transactions,
|
|
2567
|
+
};
|
|
2568
|
+
|
|
2569
|
+
db.commit(&mut state_commit, &Some(commit_data)).unwrap();
|
|
2570
|
+
|
|
2571
|
+
// Read back through the same path findBlocks/restore use.
|
|
2572
|
+
let commits = db
|
|
2573
|
+
.get_commits_by_block_range(block_number, block_number, u64::MAX)
|
|
2574
|
+
.unwrap();
|
|
2575
|
+
assert_eq!(commits.len(), 1);
|
|
2576
|
+
assert_eq!(
|
|
2577
|
+
commits[0].2.len(),
|
|
2578
|
+
transaction_count as usize,
|
|
2579
|
+
"transactions committed via commit_to_db must be read back by get_commits_by_block_range"
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
#[test]
|
|
2584
|
+
fn test_commit_rejects_already_committed_block() {
|
|
2585
|
+
let db = create_temp_database();
|
|
2586
|
+
let block_number = 1u64;
|
|
2587
|
+
|
|
2588
|
+
let make_commit = || {
|
|
2589
|
+
(
|
|
2590
|
+
StateCommit {
|
|
2591
|
+
key: CommitKey(block_number, 0, B256::ZERO),
|
|
2592
|
+
change_set: StateChangeset::default(),
|
|
2593
|
+
results: Default::default(),
|
|
2594
|
+
},
|
|
2595
|
+
CommitData {
|
|
2596
|
+
proof: ProofData::default(),
|
|
2597
|
+
header: BlockHeaderData {
|
|
2598
|
+
number: block_number as u32,
|
|
2599
|
+
..Default::default()
|
|
2600
|
+
},
|
|
2601
|
+
transactions: vec![],
|
|
2602
|
+
},
|
|
2603
|
+
)
|
|
2604
|
+
};
|
|
2605
|
+
|
|
2606
|
+
// First commit of the block succeeds.
|
|
2607
|
+
let (mut state_commit, commit_data) = make_commit();
|
|
2608
|
+
db.commit(&mut state_commit, &Some(commit_data)).unwrap();
|
|
2609
|
+
|
|
2610
|
+
// Committing the same block number again is rejected gracefully, not asserted.
|
|
2611
|
+
let (mut state_commit, commit_data) = make_commit();
|
|
2612
|
+
let result = db.commit(&mut state_commit, &Some(commit_data));
|
|
2613
|
+
assert!(
|
|
2614
|
+
matches!(result, Err(crate::db::Error::State(_))),
|
|
2615
|
+
"expected Err(State(block already committed)), got {result:?}"
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
#[test]
|
|
2620
|
+
fn test_get_receipts() {
|
|
2621
|
+
let db = create_temp_database();
|
|
2622
|
+
|
|
2623
|
+
let receipts = db.get_receipts_by_block_number(1).unwrap();
|
|
2624
|
+
assert!(receipts.is_empty());
|
|
2625
|
+
|
|
2626
|
+
let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
|
|
2627
|
+
|
|
2628
|
+
let (_, receipt) = db.get_receipt(1, hash).unwrap();
|
|
2629
|
+
assert_eq!(receipt, None);
|
|
2630
|
+
|
|
2631
|
+
{
|
|
2632
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2633
|
+
|
|
2634
|
+
let mut tx_receipts: HashMap<B256, TxReceipt> = Default::default();
|
|
2635
|
+
tx_receipts.insert(hash, Default::default());
|
|
2636
|
+
|
|
2637
|
+
db.inner
|
|
2638
|
+
.borrow_mut()
|
|
2639
|
+
.commits
|
|
2640
|
+
.put(
|
|
2641
|
+
&mut wtxn,
|
|
2642
|
+
&1,
|
|
2643
|
+
&CompactBincode(&CommitReceipts {
|
|
2644
|
+
tx_receipts,
|
|
2645
|
+
..Default::default()
|
|
2646
|
+
}),
|
|
2647
|
+
)
|
|
2648
|
+
.unwrap();
|
|
2649
|
+
|
|
2650
|
+
wtxn.commit().unwrap();
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
let receipts = db.get_receipts_by_block_number(1).unwrap();
|
|
2654
|
+
assert_eq!(receipts.len(), 1);
|
|
2655
|
+
assert_eq!(receipts.get(&hash), Some(&Default::default()));
|
|
2656
|
+
|
|
2657
|
+
let (_, receipt) = db.get_receipt(1, hash).unwrap();
|
|
2658
|
+
assert_eq!(receipt, Some(Default::default()));
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
#[test]
|
|
2662
|
+
fn test_read_receipts() {
|
|
2663
|
+
let db = create_temp_database();
|
|
2664
|
+
|
|
2665
|
+
let target_block = 100;
|
|
2666
|
+
let mut total_receipts = 0;
|
|
2667
|
+
|
|
2668
|
+
{
|
|
2669
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2670
|
+
|
|
2671
|
+
fn random_b256(seed: u64, offset: u64) -> B256 {
|
|
2672
|
+
use std::collections::hash_map::DefaultHasher;
|
|
2673
|
+
use std::hash::{Hash, Hasher};
|
|
2674
|
+
let mut hasher = DefaultHasher::new();
|
|
2675
|
+
seed.hash(&mut hasher);
|
|
2676
|
+
|
|
2677
|
+
B256::from(U256::from(hasher.finish() + offset))
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
for i in 0..target_block {
|
|
2681
|
+
let block_number = (i + 1) as u64;
|
|
2682
|
+
|
|
2683
|
+
let receipts: HashMap<B256, TxReceipt> = [
|
|
2684
|
+
(random_b256(block_number, 0), TxReceipt::default()),
|
|
2685
|
+
(random_b256(block_number, 1), TxReceipt::default()),
|
|
2686
|
+
(random_b256(block_number, 2), TxReceipt::default()),
|
|
2687
|
+
(random_b256(block_number, 3), TxReceipt::default()),
|
|
2688
|
+
]
|
|
2689
|
+
.into_iter()
|
|
2690
|
+
.collect();
|
|
2691
|
+
|
|
2692
|
+
total_receipts += receipts.len();
|
|
2693
|
+
|
|
2694
|
+
db.inner
|
|
2695
|
+
.borrow_mut()
|
|
2696
|
+
.commits
|
|
2697
|
+
.put(
|
|
2698
|
+
&mut wtxn,
|
|
2699
|
+
&block_number,
|
|
2700
|
+
&CompactBincode(&CommitReceipts {
|
|
2701
|
+
tx_receipts: receipts,
|
|
2702
|
+
..Default::default()
|
|
2703
|
+
}),
|
|
2704
|
+
)
|
|
2705
|
+
.unwrap();
|
|
2706
|
+
}
|
|
2707
|
+
wtxn.commit().unwrap();
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
const LIMIT: u64 = 7;
|
|
2711
|
+
let mut offset = 0;
|
|
2712
|
+
|
|
2713
|
+
let mut read_block_number = 0;
|
|
2714
|
+
let mut read_receipts = 0;
|
|
2715
|
+
|
|
2716
|
+
loop {
|
|
2717
|
+
let (next, items) = db.get_receipts(offset, LIMIT).unwrap();
|
|
2718
|
+
for (block_number, receipts) in items {
|
|
2719
|
+
read_block_number = block_number;
|
|
2720
|
+
read_receipts += receipts.len();
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
if next.is_none() {
|
|
2724
|
+
break;
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
match next {
|
|
2728
|
+
Some(next) => {
|
|
2729
|
+
offset = next;
|
|
2730
|
+
}
|
|
2731
|
+
None => {
|
|
2732
|
+
break;
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
assert_eq!(read_block_number, target_block);
|
|
2738
|
+
assert_eq!(read_receipts, total_receipts);
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
#[test]
|
|
2742
|
+
fn test_get_receipts_by_block_range() {
|
|
2743
|
+
let db = create_temp_database();
|
|
2744
|
+
|
|
2745
|
+
// Empty before anything is written.
|
|
2746
|
+
assert!(db.get_receipts_by_block_range(1, 3).unwrap().is_empty());
|
|
2747
|
+
|
|
2748
|
+
// Write blocks 1..=3; block N gets N receipts with distinct hashes.
|
|
2749
|
+
{
|
|
2750
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2751
|
+
let inner = db.inner.borrow();
|
|
2752
|
+
|
|
2753
|
+
for block_number in 1u64..=3 {
|
|
2754
|
+
let mut tx_receipts: HashMap<B256, TxReceipt> = Default::default();
|
|
2755
|
+
for index in 0..block_number {
|
|
2756
|
+
tx_receipts.insert(
|
|
2757
|
+
B256::from(U256::from(block_number * 100 + index)),
|
|
2758
|
+
Default::default(),
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
inner
|
|
2763
|
+
.commits
|
|
2764
|
+
.put(
|
|
2765
|
+
&mut wtxn,
|
|
2766
|
+
&block_number,
|
|
2767
|
+
&CompactBincode(&CommitReceipts {
|
|
2768
|
+
tx_receipts,
|
|
2769
|
+
..Default::default()
|
|
2770
|
+
}),
|
|
2771
|
+
)
|
|
2772
|
+
.unwrap();
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
wtxn.commit().unwrap();
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
// Full range: blocks ascending, receipt count per block matches what was written.
|
|
2779
|
+
let receipts = db.get_receipts_by_block_range(1, 3).unwrap();
|
|
2780
|
+
assert_eq!(receipts.len(), 3);
|
|
2781
|
+
for (index, (block_number, block_receipts)) in receipts.iter().enumerate() {
|
|
2782
|
+
assert_eq!(*block_number, (index + 1) as u64);
|
|
2783
|
+
assert_eq!(block_receipts.len(), *block_number as usize);
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
// Sub-range returns only the requested block.
|
|
2787
|
+
let receipts = db.get_receipts_by_block_range(2, 2).unwrap();
|
|
2788
|
+
assert_eq!(receipts.len(), 1);
|
|
2789
|
+
assert_eq!(receipts[0].0, 2);
|
|
2790
|
+
assert_eq!(receipts[0].1.len(), 2);
|
|
2791
|
+
|
|
2792
|
+
// Range extending past the tip stops at the last available block.
|
|
2793
|
+
let receipts = db.get_receipts_by_block_range(2, 99).unwrap();
|
|
2794
|
+
assert_eq!(receipts.len(), 2);
|
|
2795
|
+
assert_eq!(receipts[0].0, 2);
|
|
2796
|
+
assert_eq!(receipts[1].0, 3);
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
#[test]
|
|
2800
|
+
#[should_panic(expected = "must be <= to_block_number")]
|
|
2801
|
+
fn test_get_receipts_by_block_range_panics_when_from_exceeds_to() {
|
|
2802
|
+
let db = create_temp_database();
|
|
2803
|
+
let _ = db.get_receipts_by_block_range(3, 1);
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
#[test]
|
|
2807
|
+
fn test_get_legacy_attributes() {
|
|
2808
|
+
let db = create_temp_database();
|
|
2809
|
+
|
|
2810
|
+
let address = address!("0000000000000000000000000000000000000001");
|
|
2811
|
+
|
|
2812
|
+
assert_eq!(db.get_legacy_attributes(address).unwrap(), None);
|
|
2813
|
+
|
|
2814
|
+
{
|
|
2815
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2816
|
+
|
|
2817
|
+
db.inner
|
|
2818
|
+
.borrow_mut()
|
|
2819
|
+
.legacy_attributes
|
|
2820
|
+
.put(
|
|
2821
|
+
&mut wtxn,
|
|
2822
|
+
&AddressWrapper(address),
|
|
2823
|
+
&CompactBincode(&LegacyAccountAttributes {
|
|
2824
|
+
legacy_nonce: Some(1234),
|
|
2825
|
+
second_public_key: Some("key".into()),
|
|
2826
|
+
multi_signature: None,
|
|
2827
|
+
}),
|
|
2828
|
+
)
|
|
2829
|
+
.unwrap();
|
|
2830
|
+
|
|
2831
|
+
wtxn.commit().unwrap();
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
assert_eq!(
|
|
2835
|
+
db.get_legacy_attributes(address).unwrap(),
|
|
2836
|
+
Some(LegacyAccountAttributes {
|
|
2837
|
+
legacy_nonce: Some(1234),
|
|
2838
|
+
second_public_key: Some("key".into()),
|
|
2839
|
+
multi_signature: None,
|
|
2840
|
+
})
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
#[test]
|
|
2845
|
+
fn test_is_empty() {
|
|
2846
|
+
let db = create_temp_database();
|
|
2847
|
+
|
|
2848
|
+
assert_eq!(db.is_empty().unwrap(), true);
|
|
2849
|
+
|
|
2850
|
+
{
|
|
2851
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2852
|
+
|
|
2853
|
+
db.inner
|
|
2854
|
+
.borrow_mut()
|
|
2855
|
+
.blocks
|
|
2856
|
+
.put(
|
|
2857
|
+
&mut wtxn,
|
|
2858
|
+
&1,
|
|
2859
|
+
&CompactBincode(&BlockHeaderData {
|
|
2860
|
+
hash: b256!(
|
|
2861
|
+
"0000000000000000000000000000000000000000000000000000000000000001"
|
|
2862
|
+
),
|
|
2863
|
+
..Default::default()
|
|
2864
|
+
}),
|
|
2865
|
+
)
|
|
2866
|
+
.unwrap();
|
|
2867
|
+
|
|
2868
|
+
wtxn.commit().unwrap();
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
assert_eq!(db.is_empty().unwrap(), false);
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
#[test]
|
|
2875
|
+
fn test_get_state() {
|
|
2876
|
+
let db = create_temp_database();
|
|
2877
|
+
|
|
2878
|
+
assert_eq!(db.get_state().unwrap(), (0, 0));
|
|
2879
|
+
|
|
2880
|
+
{
|
|
2881
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2882
|
+
|
|
2883
|
+
db.inner
|
|
2884
|
+
.borrow_mut()
|
|
2885
|
+
.state
|
|
2886
|
+
.put(
|
|
2887
|
+
&mut wtxn,
|
|
2888
|
+
&StaticStringWrapper("total_round"),
|
|
2889
|
+
&Bytes::from_iter(999u64.to_le_bytes()),
|
|
2890
|
+
)
|
|
2891
|
+
.unwrap();
|
|
2892
|
+
|
|
2893
|
+
db.inner
|
|
2894
|
+
.borrow_mut()
|
|
2895
|
+
.blocks
|
|
2896
|
+
.put(
|
|
2897
|
+
&mut wtxn,
|
|
2898
|
+
&255,
|
|
2899
|
+
&CompactBincode(&BlockHeaderData {
|
|
2900
|
+
number: 255,
|
|
2901
|
+
hash: b256!(
|
|
2902
|
+
"0000000000000000000000000000000000000000000000000000000000000001"
|
|
2903
|
+
),
|
|
2904
|
+
..Default::default()
|
|
2905
|
+
}),
|
|
2906
|
+
)
|
|
2907
|
+
.unwrap();
|
|
2908
|
+
|
|
2909
|
+
wtxn.commit().unwrap();
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
assert_eq!(db.get_state().unwrap(), (255, 999));
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
#[test]
|
|
2916
|
+
fn test_get_block_number_by_hash() {
|
|
2917
|
+
let db = create_temp_database();
|
|
2918
|
+
|
|
2919
|
+
let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
|
|
2920
|
+
assert_eq!(db.get_block_number_by_hash(hash).unwrap(), None);
|
|
2921
|
+
|
|
2922
|
+
{
|
|
2923
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2924
|
+
|
|
2925
|
+
db.inner
|
|
2926
|
+
.borrow_mut()
|
|
2927
|
+
.blocks_hash_number
|
|
2928
|
+
.put(&mut wtxn, &HashWrapper(hash), &10)
|
|
2929
|
+
.unwrap();
|
|
2930
|
+
|
|
2931
|
+
wtxn.commit().unwrap();
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
assert_eq!(db.get_block_number_by_hash(hash).unwrap(), Some(10));
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
#[test]
|
|
2938
|
+
fn test_get_block_header_data() {
|
|
2939
|
+
let db = create_temp_database();
|
|
2940
|
+
|
|
2941
|
+
assert_eq!(db.get_block_header_data(1).unwrap(), None);
|
|
2942
|
+
|
|
2943
|
+
let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
|
|
2944
|
+
{
|
|
2945
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2946
|
+
|
|
2947
|
+
db.inner
|
|
2948
|
+
.borrow_mut()
|
|
2949
|
+
.blocks
|
|
2950
|
+
.put(
|
|
2951
|
+
&mut wtxn,
|
|
2952
|
+
&1,
|
|
2953
|
+
&CompactBincode(&BlockHeaderData {
|
|
2954
|
+
number: 1,
|
|
2955
|
+
hash,
|
|
2956
|
+
..Default::default()
|
|
2957
|
+
}),
|
|
2958
|
+
)
|
|
2959
|
+
.unwrap();
|
|
2960
|
+
|
|
2961
|
+
wtxn.commit().unwrap();
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
assert_eq!(
|
|
2965
|
+
db.get_block_header_data(1).unwrap(),
|
|
2966
|
+
Some(BlockHeaderData {
|
|
2967
|
+
hash,
|
|
2968
|
+
number: 1,
|
|
2969
|
+
..Default::default()
|
|
2970
|
+
})
|
|
2971
|
+
);
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
#[test]
|
|
2975
|
+
fn test_get_proof_data() {
|
|
2976
|
+
let db = create_temp_database();
|
|
2977
|
+
|
|
2978
|
+
assert_eq!(db.get_proof_data(1).unwrap(), None);
|
|
2979
|
+
|
|
2980
|
+
{
|
|
2981
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
2982
|
+
|
|
2983
|
+
db.inner
|
|
2984
|
+
.borrow_mut()
|
|
2985
|
+
.proofs
|
|
2986
|
+
.put(
|
|
2987
|
+
&mut wtxn,
|
|
2988
|
+
&1,
|
|
2989
|
+
&CompactBincode(&ProofData {
|
|
2990
|
+
round: 1,
|
|
2991
|
+
validator_set: 1234,
|
|
2992
|
+
..Default::default()
|
|
2993
|
+
}),
|
|
2994
|
+
)
|
|
2995
|
+
.unwrap();
|
|
2996
|
+
|
|
2997
|
+
wtxn.commit().unwrap();
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
assert_eq!(
|
|
3001
|
+
db.get_proof_data(1).unwrap(),
|
|
3002
|
+
Some(ProofData {
|
|
3003
|
+
round: 1,
|
|
3004
|
+
validator_set: 1234,
|
|
3005
|
+
..Default::default()
|
|
3006
|
+
})
|
|
3007
|
+
);
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
#[test]
|
|
3011
|
+
fn test_transaction_key_encode_decode_roundtrip() {
|
|
3012
|
+
for (block_number, index) in [(0u64, 0u16), (1, 2), (5, 9999), (u64::MAX, u16::MAX)] {
|
|
3013
|
+
let key = TransactionKey::new(block_number, index);
|
|
3014
|
+
let encoded = <TransactionKey as BytesEncode>::bytes_encode(&key).unwrap();
|
|
3015
|
+
assert_eq!(encoded.len(), 10, "key is 8-byte block + 2-byte index");
|
|
3016
|
+
|
|
3017
|
+
let decoded = <TransactionKey as BytesDecode>::bytes_decode(&encoded).unwrap();
|
|
3018
|
+
assert_eq!(decoded, key);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
#[test]
|
|
3023
|
+
fn test_transaction_key_orders_by_block_then_index() {
|
|
3024
|
+
// The transactions DB relies on byte (memcmp) key order matching numeric
|
|
3025
|
+
// (block_number, index) order, so get_commits_by_block_range can scan by block number.
|
|
3026
|
+
// Big-endian encoding is what guarantees this (e.g. block 2 sorts before block 10).
|
|
3027
|
+
let ascending = [
|
|
3028
|
+
TransactionKey::new(0, 0),
|
|
3029
|
+
TransactionKey::new(0, 1),
|
|
3030
|
+
TransactionKey::new(0, u16::MAX),
|
|
3031
|
+
TransactionKey::new(1, 0), // block 1 sorts after every transaction of block 0
|
|
3032
|
+
TransactionKey::new(2, 0),
|
|
3033
|
+
TransactionKey::new(10, 0), // numeric order, not lexicographic on decimal
|
|
3034
|
+
TransactionKey::new(u64::MAX, 0),
|
|
3035
|
+
TransactionKey::new(u64::MAX, u16::MAX),
|
|
3036
|
+
];
|
|
3037
|
+
|
|
3038
|
+
for window in ascending.windows(2) {
|
|
3039
|
+
let lo = <TransactionKey as BytesEncode>::bytes_encode(&window[0]).unwrap();
|
|
3040
|
+
let hi = <TransactionKey as BytesEncode>::bytes_encode(&window[1]).unwrap();
|
|
3041
|
+
assert!(lo < hi, "encoded keys must sort by (block, index)");
|
|
3042
|
+
// The derived Ord must agree with the on-disk byte order.
|
|
3043
|
+
assert!(window[0] < window[1]);
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
#[test]
|
|
3048
|
+
fn test_transaction_key_token_roundtrip_and_lenient_parse() {
|
|
3049
|
+
assert_eq!(TransactionKey::new(5, 2).to_token(), "5-2");
|
|
3050
|
+
|
|
3051
|
+
let key = TransactionKey::new(123, 45);
|
|
3052
|
+
assert_eq!(TransactionKey::parse(&key.to_token()), Some(key));
|
|
3053
|
+
|
|
3054
|
+
// Malformed or out-of-range tokens parse to None (treated as "no such transaction").
|
|
3055
|
+
assert_eq!(TransactionKey::parse("nope"), None);
|
|
3056
|
+
assert_eq!(TransactionKey::parse("-5"), None);
|
|
3057
|
+
assert_eq!(TransactionKey::parse("1-2-3"), None);
|
|
3058
|
+
assert_eq!(TransactionKey::parse("1-70000"), None); // index exceeds u16::MAX
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
#[test]
|
|
3062
|
+
fn test_transaction_key_range_bounds_capture_a_block_range() {
|
|
3063
|
+
// Mirrors the scan bounds get_commits_by_block_range builds for [from, to].
|
|
3064
|
+
let from = TransactionKey::new(5, 0);
|
|
3065
|
+
let to = TransactionKey::new(7, u16::MAX);
|
|
3066
|
+
|
|
3067
|
+
for block in 5..=7u64 {
|
|
3068
|
+
for index in [0u16, 1, 1000, u16::MAX] {
|
|
3069
|
+
let key = TransactionKey::new(block, index);
|
|
3070
|
+
assert!(
|
|
3071
|
+
key >= from && key <= to,
|
|
3072
|
+
"{block}-{index} should be within range"
|
|
3073
|
+
);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
// The neighbouring blocks fall outside the range on each side.
|
|
3078
|
+
assert!(TransactionKey::new(4, u16::MAX) < from);
|
|
3079
|
+
assert!(TransactionKey::new(8, 0) > to);
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
#[test]
|
|
3083
|
+
fn test_get_transaction_data() {
|
|
3084
|
+
let db = create_temp_database();
|
|
3085
|
+
|
|
3086
|
+
// Lookups go through the "<block>-<index>" token; before anything is written it is absent,
|
|
3087
|
+
// and malformed/out-of-range tokens resolve to None rather than erroring.
|
|
3088
|
+
assert_eq!(db.get_transaction_data("1-0".into()).unwrap(), None);
|
|
3089
|
+
assert_eq!(db.get_transaction_data("not-a-key".into()).unwrap(), None);
|
|
3090
|
+
assert_eq!(db.get_transaction_data("1-70000".into()).unwrap(), None);
|
|
3091
|
+
|
|
3092
|
+
let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
|
|
3093
|
+
|
|
3094
|
+
{
|
|
3095
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
3096
|
+
|
|
3097
|
+
db.inner
|
|
3098
|
+
.borrow_mut()
|
|
3099
|
+
.transactions
|
|
3100
|
+
.put(
|
|
3101
|
+
&mut wtxn,
|
|
3102
|
+
&TransactionKey::new(1, 0),
|
|
3103
|
+
&CompactBincode(&TransactionData {
|
|
3104
|
+
tx_hash: hash,
|
|
3105
|
+
..Default::default()
|
|
3106
|
+
}),
|
|
3107
|
+
)
|
|
3108
|
+
.unwrap();
|
|
3109
|
+
|
|
3110
|
+
wtxn.commit().unwrap();
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3113
|
+
assert_eq!(
|
|
3114
|
+
db.get_transaction_data("1-0".into()).unwrap(),
|
|
3115
|
+
Some(TransactionData {
|
|
3116
|
+
tx_hash: hash,
|
|
3117
|
+
..Default::default()
|
|
3118
|
+
})
|
|
3119
|
+
);
|
|
3120
|
+
}
|
|
3121
|
+
|
|
3122
|
+
#[test]
|
|
3123
|
+
fn test_get_transaction_hash_by_hash() {
|
|
3124
|
+
let db = create_temp_database();
|
|
3125
|
+
|
|
3126
|
+
let hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
|
|
3127
|
+
|
|
3128
|
+
assert_eq!(db.get_transaction_key_by_hash(hash).unwrap(), None);
|
|
3129
|
+
|
|
3130
|
+
{
|
|
3131
|
+
let mut wtxn = db.env.write_txn().unwrap();
|
|
3132
|
+
|
|
3133
|
+
db.inner
|
|
3134
|
+
.borrow_mut()
|
|
3135
|
+
.transactions_hash_key
|
|
3136
|
+
.put(&mut wtxn, &HashWrapper(hash), &TransactionKey::new(1, 0))
|
|
3137
|
+
.unwrap();
|
|
3138
|
+
|
|
3139
|
+
wtxn.commit().unwrap();
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
// The stored typed key is returned to the napi boundary as its "<block>-<index>" token.
|
|
3143
|
+
assert_eq!(
|
|
3144
|
+
db.get_transaction_key_by_hash(hash).unwrap(),
|
|
3145
|
+
Some("1-0".to_string())
|
|
3146
|
+
);
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
#[test]
|
|
3150
|
+
fn test_commit() {
|
|
3151
|
+
let db = create_temp_database_opts(|opts| {
|
|
3152
|
+
opts.history_size = Some(8);
|
|
3153
|
+
});
|
|
3154
|
+
|
|
3155
|
+
let block_hash = b256!("0000000000000000000000000000000000000000000000000000000000000001");
|
|
3156
|
+
let key = CommitKey(1, 0, block_hash);
|
|
3157
|
+
|
|
3158
|
+
let account1 = address!("0000000000000000000000000000000000000001");
|
|
3159
|
+
let account2 = address!("0000000000000000000000000000000000000002");
|
|
3160
|
+
|
|
3161
|
+
let mut legacy_attributes: BTreeMap<Address, LegacyAccountAttributes> = Default::default();
|
|
3162
|
+
legacy_attributes.insert(
|
|
3163
|
+
account1,
|
|
3164
|
+
LegacyAccountAttributes {
|
|
3165
|
+
legacy_nonce: Some(2),
|
|
3166
|
+
..Default::default()
|
|
3167
|
+
},
|
|
3168
|
+
);
|
|
3169
|
+
legacy_attributes.insert(
|
|
3170
|
+
account2,
|
|
3171
|
+
LegacyAccountAttributes {
|
|
3172
|
+
legacy_nonce: Some(9),
|
|
3173
|
+
..Default::default()
|
|
3174
|
+
},
|
|
3175
|
+
);
|
|
3176
|
+
|
|
3177
|
+
let mut legacy_cold_wallets: BTreeMap<LegacyAddress, LegacyColdWallet> = Default::default();
|
|
3178
|
+
let legacy_addresses = [
|
|
3179
|
+
"DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x",
|
|
3180
|
+
"D5KU9KrMYXdkEsRbv4y8hvetGbsJwf9z3P",
|
|
3181
|
+
"DJA2sqCbnmR63sD8doGrXrK3fCiqcA4GUw",
|
|
3182
|
+
"DJmvhhiQFSrEQCq9FUxvcLcpcBjx7K3yLt",
|
|
3183
|
+
];
|
|
3184
|
+
|
|
3185
|
+
for (index, legacy) in legacy_addresses.iter().enumerate() {
|
|
3186
|
+
let legacy_address = (*legacy).try_into().unwrap();
|
|
3187
|
+
|
|
3188
|
+
legacy_cold_wallets.insert(
|
|
3189
|
+
legacy_address,
|
|
3190
|
+
LegacyColdWallet {
|
|
3191
|
+
address: legacy_address,
|
|
3192
|
+
balance: U256::from(index as u64),
|
|
3193
|
+
..Default::default()
|
|
3194
|
+
},
|
|
3195
|
+
);
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
let mut merged_legacy_cold_wallets: BTreeMap<Address, (B256, LegacyAddress)> =
|
|
3199
|
+
Default::default();
|
|
3200
|
+
merged_legacy_cold_wallets.insert(
|
|
3201
|
+
account1,
|
|
3202
|
+
(
|
|
3203
|
+
b256!("0000000000000000000000000000000000000000000000000000000000000001"),
|
|
3204
|
+
"DBYyh2vXcigrJGUHfvmYxVxEqeH7vomw6x".try_into().unwrap(),
|
|
3205
|
+
),
|
|
3206
|
+
);
|
|
3207
|
+
|
|
3208
|
+
let mut results: BTreeMap<B256, (ExecutionResult, u64)> = Default::default();
|
|
3209
|
+
results.insert(
|
|
3210
|
+
b256!("1000000000000000000000000000000000000000000000000000000000000000"),
|
|
3211
|
+
(
|
|
3212
|
+
ExecutionResult::Success {
|
|
3213
|
+
reason: SuccessReason::Stop,
|
|
3214
|
+
gas: ResultGas::default(),
|
|
3215
|
+
logs: Default::default(),
|
|
3216
|
+
output: revm::context::result::Output::Call(Default::default()),
|
|
3217
|
+
},
|
|
3218
|
+
1234,
|
|
3219
|
+
),
|
|
3220
|
+
);
|
|
3221
|
+
|
|
3222
|
+
let mut state = StateCommit {
|
|
3223
|
+
key,
|
|
3224
|
+
change_set: StateChangeset {
|
|
3225
|
+
accounts: vec![
|
|
3226
|
+
(
|
|
3227
|
+
account1,
|
|
3228
|
+
Some(AccountInfo {
|
|
3229
|
+
balance: U256::from(1),
|
|
3230
|
+
nonce: 1,
|
|
3231
|
+
..Default::default()
|
|
3232
|
+
}),
|
|
3233
|
+
),
|
|
3234
|
+
(account2, None),
|
|
3235
|
+
],
|
|
3236
|
+
storage: vec![
|
|
3237
|
+
StorageChangeset {
|
|
3238
|
+
address: address!("0000000000000000000000000000000000000003"),
|
|
3239
|
+
storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
|
|
3240
|
+
..Default::default()
|
|
3241
|
+
},
|
|
3242
|
+
StorageChangeset {
|
|
3243
|
+
address: address!("0000000000000000000000000000000000000004"),
|
|
3244
|
+
storage: vec![],
|
|
3245
|
+
wipe_storage: true,
|
|
3246
|
+
},
|
|
3247
|
+
StorageChangeset {
|
|
3248
|
+
address: address!("0000000000000000000000000000000000000003"),
|
|
3249
|
+
storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
|
|
3250
|
+
wipe_storage: true,
|
|
3251
|
+
},
|
|
3252
|
+
StorageChangeset {
|
|
3253
|
+
address: address!("0000000000000000000000000000000000000004"),
|
|
3254
|
+
storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
|
|
3255
|
+
..Default::default()
|
|
3256
|
+
},
|
|
3257
|
+
StorageChangeset {
|
|
3258
|
+
address: address!("0000000000000000000000000000000000000004"),
|
|
3259
|
+
storage: vec![(U256::from(1), StorageSlot::new(U256::ZERO))],
|
|
3260
|
+
..Default::default()
|
|
3261
|
+
},
|
|
3262
|
+
StorageChangeset {
|
|
3263
|
+
address: address!("0000000000000000000000000000000000000005"),
|
|
3264
|
+
storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
|
|
3265
|
+
..Default::default()
|
|
3266
|
+
},
|
|
3267
|
+
StorageChangeset {
|
|
3268
|
+
address: address!("0000000000000000000000000000000000000005"),
|
|
3269
|
+
storage: vec![(U256::from(1), StorageSlot::new(U256::from(2)))],
|
|
3270
|
+
..Default::default()
|
|
3271
|
+
},
|
|
3272
|
+
],
|
|
3273
|
+
contracts: vec![(
|
|
3274
|
+
b256!("1000000000000000000000000000000000000000000000000000000000000000"),
|
|
3275
|
+
Bytecode::new_legacy(Bytes(Bytes::from_static(&[1, 2, 3, 4]).into())),
|
|
3276
|
+
)],
|
|
3277
|
+
legacy_attributes,
|
|
3278
|
+
legacy_cold_wallets,
|
|
3279
|
+
merged_legacy_cold_wallets,
|
|
3280
|
+
},
|
|
3281
|
+
results,
|
|
3282
|
+
};
|
|
3283
|
+
let data = CommitData {
|
|
3284
|
+
transactions: vec![TransactionData::default()],
|
|
3285
|
+
..Default::default()
|
|
3286
|
+
};
|
|
3287
|
+
|
|
3288
|
+
db.commit(&mut state, &Some(data)).unwrap();
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
fn create_temp_database() -> PersistentDB {
|
|
3292
|
+
let db = create_temp_database_opts(|_| {});
|
|
3293
|
+
db
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
fn create_temp_database_opts<F>(callback: F) -> PersistentDB
|
|
3297
|
+
where
|
|
3298
|
+
F: FnOnce(&mut PersistentDBOptions),
|
|
3299
|
+
{
|
|
3300
|
+
let path = tempfile::Builder::new()
|
|
3301
|
+
.prefix("evm.mdb")
|
|
3302
|
+
.tempdir()
|
|
3303
|
+
.unwrap();
|
|
3304
|
+
|
|
3305
|
+
let mut opts = PersistentDBOptions::new(path.path().to_path_buf());
|
|
3306
|
+
callback(&mut opts);
|
|
3307
|
+
|
|
3308
|
+
let db = PersistentDB::new(opts).expect("database");
|
|
3309
|
+
db
|
|
3310
|
+
}
|
|
3311
|
+
}
|