1use crate::{SnapAttemptStore, SnapSyncError, SnapWrite};
7use alloy_primitives::{keccak256, Bytes, B256};
8use reth_db_api::{
9 tables,
10 transaction::{DbTx, DbTxMut},
11 RawKey, RawTable,
12};
13use reth_storage_api::{DBProvider, MetadataProvider, StateWriter};
14use revm::{bytecode::Bytecode, database::states::StateChangeset};
15
16pub trait SnapBytecodeStore {
21 fn missing_code(
24 &self,
25 write: SnapWrite,
26 hashes: &[B256],
27 limit: usize,
28 ) -> Result<Vec<B256>, SnapSyncError>
29 where
30 Self: DBProvider;
31
32 fn commit_bytecodes(
35 &self,
36 write: SnapWrite,
37 codes: Vec<(B256, Bytes)>,
38 ) -> Result<usize, SnapSyncError>
39 where
40 Self: StateWriter + DBProvider<Tx: DbTxMut>;
41}
42
43impl<T: MetadataProvider> SnapBytecodeStore for T {
44 fn missing_code(
45 &self,
46 write: SnapWrite,
47 hashes: &[B256],
48 limit: usize,
49 ) -> Result<Vec<B256>, SnapSyncError>
50 where
51 Self: DBProvider,
52 {
53 self.authorize_snap_write(write)?;
54 let mut missing = Vec::new();
55 for hash in hashes {
56 if missing.len() == limit {
57 break
58 }
59 if self.tx_ref().get::<RawTable<tables::Bytecodes>>(RawKey::new(*hash))?.is_none() {
61 missing.push(*hash);
62 }
63 }
64 Ok(missing)
65 }
66
67 fn commit_bytecodes(
69 &self,
70 write: SnapWrite,
71 codes: Vec<(B256, Bytes)>,
72 ) -> Result<usize, SnapSyncError>
73 where
74 Self: StateWriter + DBProvider<Tx: DbTxMut>,
75 {
76 self.authorize_snap_write(write)?;
77 let contracts = codes
78 .into_iter()
79 .map(|(hash, code)| {
80 let got = keccak256(&code);
81 if got != hash {
82 return Err(SnapSyncError::CodeMismatch { expected: hash, got })
83 }
84 let code = Bytecode::new_raw_checked(code.clone())
87 .unwrap_or_else(|_| Bytecode::new_legacy(code));
88 Ok((hash, code))
89 })
90 .collect::<Result<Vec<_>, SnapSyncError>>()?;
91
92 let written = contracts.len();
93 self.write_state_changes(StateChangeset { contracts, ..Default::default() })?;
94 Ok(written)
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::test_utils::{
102 account, generation, hashed_factory, insert_generation_headers, key, state_root,
103 verified_range,
104 };
105 use alloy_primitives::bytes;
106 use reth_provider::{
107 test_utils::MockNodeTypesWithDB, DatabaseProviderFactory, ProviderFactory,
108 };
109 use reth_trie_common::TrieAccount;
110
111 type Factory = ProviderFactory<MockNodeTypesWithDB>;
112
113 fn code(byte: u8) -> Bytes {
114 Bytes::from(vec![byte; 4])
115 }
116
117 fn contract(nonce: u64, code: &Bytes) -> TrieAccount {
119 let mut contract = account(nonce);
120 contract.code_hash = keccak256(code);
121 contract
122 }
123
124 fn started(accounts: &[(B256, TrieAccount)]) -> (Factory, SnapWrite) {
125 let factory = hashed_factory();
126 insert_generation_headers(&factory);
127 let provider = factory.database_provider_rw().unwrap();
128 let write = provider.start_snap_attempt(generation(1, state_root(accounts))).unwrap();
129 provider.commit().unwrap();
130 (factory, write)
131 }
132
133 fn is_stored(provider: &impl DBProvider, code: &Bytes) -> bool {
134 provider.tx_ref().get::<tables::Bytecodes>(keccak256(code)).unwrap().is_some()
135 }
136
137 #[test]
138 fn one_blob_answers_every_account_sharing_its_hash() {
139 let shared = code(1);
140 let accounts = vec![
141 (key(1), contract(1, &shared)),
142 (key(2), account(2)),
143 (key(3), contract(3, &shared)),
144 (key(4), contract(4, &code(2))),
145 ];
146 let (factory, write) = started(&accounts);
147 let provider = factory.database_provider_rw().unwrap();
148 let referenced =
149 verified_range(&accounts, 0..accounts.len(), B256::ZERO, &[]).code_hashes();
150
151 let missing = provider.missing_code(write, &referenced, usize::MAX).unwrap();
152
153 assert_eq!(missing, [keccak256(&shared), keccak256(code(2))]);
154 assert_eq!(
155 provider.commit_bytecodes(write, vec![(missing[0], shared.clone())]).unwrap(),
156 1
157 );
158 assert_eq!(
160 provider.missing_code(write, &referenced, usize::MAX).unwrap(),
161 [keccak256(code(2))]
162 );
163 assert!(is_stored(&provider, &shared));
164 }
165
166 #[test]
167 fn a_scan_stops_once_it_has_the_hashes_a_request_can_carry() {
168 let hashes: Vec<_> = (1..=4).map(|byte| keccak256(code(byte))).collect();
169 let (factory, write) = started(&[(key(1), contract(1, &code(1)))]);
170 let provider = factory.database_provider_ro().unwrap();
171
172 assert_eq!(provider.missing_code(write, &hashes, 2).unwrap(), hashes[..2]);
173 }
174
175 #[test]
176 fn code_already_stored_is_not_requested_again() {
177 let stored = code(1);
178 let accounts = vec![(key(1), contract(1, &stored))];
179 let (factory, write) = started(&accounts);
180 let provider = factory.database_provider_rw().unwrap();
181 provider.commit_bytecodes(write, vec![(keccak256(&stored), stored.clone())]).unwrap();
182 provider.commit().unwrap();
183
184 let provider = factory.database_provider_ro().unwrap();
185 assert!(provider
186 .missing_code(write, &[keccak256(&stored)], usize::MAX)
187 .unwrap()
188 .is_empty());
189 assert!(is_stored(&provider, &stored));
190 }
191
192 #[test]
193 fn code_that_does_not_hash_to_its_requested_hash_is_refused() {
194 let wanted = code(1);
195 let accounts = vec![(key(1), contract(1, &wanted))];
196 let (factory, write) = started(&accounts);
197 let provider = factory.database_provider_rw().unwrap();
198 let hash = keccak256(&wanted);
199
200 let refused = provider.commit_bytecodes(write, vec![(hash, code(2))]);
201
202 assert!(
203 matches!(refused, Err(SnapSyncError::CodeMismatch { expected, .. }) if expected == hash)
204 );
205 assert_eq!(provider.missing_code(write, &[hash], usize::MAX).unwrap(), [hash]);
206 assert!(!is_stored(&provider, &wanted));
207 }
208
209 #[test]
212 fn delegation_shaped_code_that_predates_eip_3541_is_kept_as_legacy_code() {
213 let historical = bytes!("ef0100");
214 let accounts = vec![(key(1), contract(1, &historical))];
215 let (factory, write) = started(&accounts);
216 let provider = factory.database_provider_rw().unwrap();
217
218 let hash = keccak256(&historical);
219 assert_eq!(provider.commit_bytecodes(write, vec![(hash, historical.clone())]).unwrap(), 1);
220
221 let stored = provider.tx_ref().get::<tables::Bytecodes>(hash).unwrap().unwrap();
222 assert_eq!(stored.original_bytes(), historical);
223 assert!(provider.missing_code(write, &[hash], usize::MAX).unwrap().is_empty());
224 }
225
226 #[test]
227 fn code_is_refused_once_the_attempt_no_longer_owns_the_state() {
228 let wanted = code(1);
229 let accounts = vec![(key(1), contract(1, &wanted))];
230 let (factory, write) = started(&accounts);
231 let provider = factory.database_provider_rw().unwrap();
232 provider.advance_snap_pivot(write, generation(2, B256::repeat_byte(0xcc))).unwrap();
233
234 let refused = provider.commit_bytecodes(write, vec![(keccak256(&wanted), wanted.clone())]);
235
236 assert!(matches!(refused, Err(SnapSyncError::StaleWrite { .. })));
237 assert!(matches!(
238 provider.missing_code(write, &[keccak256(&wanted)], usize::MAX),
239 Err(SnapSyncError::StaleWrite { .. })
240 ));
241 assert!(!is_stored(&provider, &wanted));
242 }
243}