|
1 | 1 | use crate::{providers::StaticFileProviderRWRefMut, DatabaseProviderRW}; |
| 2 | +use itertools::Itertools; |
2 | 3 | use reth_db::{ |
3 | | - cursor::DbCursorRO, |
| 4 | + cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW}, |
4 | 5 | tables, |
5 | 6 | transaction::{DbTx, DbTxMut}, |
6 | 7 | Database, |
7 | 8 | }; |
8 | 9 | use reth_errors::{ProviderError, ProviderResult}; |
9 | | -use reth_primitives::BlockNumber; |
| 10 | +use reth_primitives::{BlockNumber, StorageEntry, U256}; |
10 | 11 | use reth_storage_api::ReceiptWriter; |
11 | 12 | use reth_storage_errors::writer::StorageWriterError; |
| 13 | +use reth_trie::HashedPostStateSorted; |
12 | 14 | use static_file::StaticFileWriter; |
13 | 15 |
|
14 | 16 | mod database; |
@@ -93,6 +95,49 @@ impl<'a, 'b, DB: Database> StorageWriter<'a, 'b, DB> { |
93 | 95 | Ok(()) |
94 | 96 | } |
95 | 97 |
|
| 98 | + /// Writes the hashed state changes to the database |
| 99 | + pub fn write_hashed_state(&self, hashed_state: &HashedPostStateSorted) -> ProviderResult<()> { |
| 100 | + self.ensure_database_writer()?; |
| 101 | + |
| 102 | + // Write hashed account updates. |
| 103 | + let mut hashed_accounts_cursor = |
| 104 | + self.database_writer().tx_ref().cursor_write::<tables::HashedAccounts>()?; |
| 105 | + for (hashed_address, account) in hashed_state.accounts().accounts_sorted() { |
| 106 | + if let Some(account) = account { |
| 107 | + hashed_accounts_cursor.upsert(hashed_address, account)?; |
| 108 | + } else if hashed_accounts_cursor.seek_exact(hashed_address)?.is_some() { |
| 109 | + hashed_accounts_cursor.delete_current()?; |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + // Write hashed storage changes. |
| 114 | + let sorted_storages = hashed_state.account_storages().iter().sorted_by_key(|(key, _)| *key); |
| 115 | + let mut hashed_storage_cursor = |
| 116 | + self.database_writer().tx_ref().cursor_dup_write::<tables::HashedStorages>()?; |
| 117 | + for (hashed_address, storage) in sorted_storages { |
| 118 | + if storage.is_wiped() && hashed_storage_cursor.seek_exact(*hashed_address)?.is_some() { |
| 119 | + hashed_storage_cursor.delete_current_duplicates()?; |
| 120 | + } |
| 121 | + |
| 122 | + for (hashed_slot, value) in storage.storage_slots_sorted() { |
| 123 | + let entry = StorageEntry { key: hashed_slot, value }; |
| 124 | + if let Some(db_entry) = |
| 125 | + hashed_storage_cursor.seek_by_key_subkey(*hashed_address, entry.key)? |
| 126 | + { |
| 127 | + if db_entry.key == entry.key { |
| 128 | + hashed_storage_cursor.delete_current()?; |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + if entry.value != U256::ZERO { |
| 133 | + hashed_storage_cursor.upsert(*hashed_address, entry)?; |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + Ok(()) |
| 139 | + } |
| 140 | + |
96 | 141 | /// Appends receipts block by block. |
97 | 142 | /// |
98 | 143 | /// ATTENTION: If called from [`StorageWriter`] without a static file producer, it will always |
@@ -155,3 +200,64 @@ impl<'a, 'b, DB: Database> StorageWriter<'a, 'b, DB> { |
155 | 200 | Ok(()) |
156 | 201 | } |
157 | 202 | } |
| 203 | + |
| 204 | +#[cfg(test)] |
| 205 | +mod tests { |
| 206 | + use super::*; |
| 207 | + use crate::test_utils::create_test_provider_factory; |
| 208 | + use reth_db_api::transaction::DbTx; |
| 209 | + use reth_primitives::{keccak256, Account, Address, B256}; |
| 210 | + use reth_trie::{HashedPostState, HashedStorage}; |
| 211 | + |
| 212 | + #[test] |
| 213 | + fn wiped_entries_are_removed() { |
| 214 | + let provider_factory = create_test_provider_factory(); |
| 215 | + |
| 216 | + let addresses = (0..10).map(|_| Address::random()).collect::<Vec<_>>(); |
| 217 | + let destroyed_address = *addresses.first().unwrap(); |
| 218 | + let destroyed_address_hashed = keccak256(destroyed_address); |
| 219 | + let slot = B256::with_last_byte(1); |
| 220 | + let hashed_slot = keccak256(slot); |
| 221 | + { |
| 222 | + let provider_rw = provider_factory.provider_rw().unwrap(); |
| 223 | + let mut accounts_cursor = |
| 224 | + provider_rw.tx_ref().cursor_write::<tables::HashedAccounts>().unwrap(); |
| 225 | + let mut storage_cursor = |
| 226 | + provider_rw.tx_ref().cursor_write::<tables::HashedStorages>().unwrap(); |
| 227 | + |
| 228 | + for address in addresses { |
| 229 | + let hashed_address = keccak256(address); |
| 230 | + accounts_cursor |
| 231 | + .insert(hashed_address, Account { nonce: 1, ..Default::default() }) |
| 232 | + .unwrap(); |
| 233 | + storage_cursor |
| 234 | + .insert(hashed_address, StorageEntry { key: hashed_slot, value: U256::from(1) }) |
| 235 | + .unwrap(); |
| 236 | + } |
| 237 | + provider_rw.commit().unwrap(); |
| 238 | + } |
| 239 | + |
| 240 | + let mut hashed_state = HashedPostState::default(); |
| 241 | + hashed_state.accounts.insert(destroyed_address_hashed, None); |
| 242 | + hashed_state.storages.insert(destroyed_address_hashed, HashedStorage::new(true)); |
| 243 | + |
| 244 | + let provider_rw = provider_factory.provider_rw().unwrap(); |
| 245 | + let storage_writer = StorageWriter::new(Some(&provider_rw), None); |
| 246 | + assert_eq!(storage_writer.write_hashed_state(&hashed_state.into_sorted()), Ok(())); |
| 247 | + provider_rw.commit().unwrap(); |
| 248 | + |
| 249 | + let provider = provider_factory.provider().unwrap(); |
| 250 | + assert_eq!( |
| 251 | + provider.tx_ref().get::<tables::HashedAccounts>(destroyed_address_hashed), |
| 252 | + Ok(None) |
| 253 | + ); |
| 254 | + assert_eq!( |
| 255 | + provider |
| 256 | + .tx_ref() |
| 257 | + .cursor_read::<tables::HashedStorages>() |
| 258 | + .unwrap() |
| 259 | + .seek_by_key_subkey(destroyed_address_hashed, hashed_slot), |
| 260 | + Ok(None) |
| 261 | + ); |
| 262 | + } |
| 263 | +} |
0 commit comments