1use crate::{SnapAttemptStore, SnapSyncError, SnapWrite};
7use alloy_primitives::{keccak256, Bytes, B256};
8use evm2::bytecode::Bytecode;
9use reth_db_api::{
10 tables,
11 transaction::{DbTx, DbTxMut},
12 RawKey, RawTable,
13};
14use reth_storage_api::{DBProvider, MetadataProvider, StateWriter};
15use reth_trie_common::bundle_state::StateChangeset;
16
17pub trait SnapBytecodeStore {
22 fn missing_code(
25 &self,
26 write: SnapWrite,
27 hashes: &[B256],
28 limit: usize,
29 ) -> Result<Vec<B256>, SnapSyncError>
30 where
31 Self: DBProvider;
32
33 fn commit_bytecodes(
36 &self,
37 write: SnapWrite,
38 codes: Vec<(B256, Bytes)>,
39 ) -> Result<usize, SnapSyncError>
40 where
41 Self: StateWriter + DBProvider<Tx: DbTxMut>;
42}
43
44impl<T: MetadataProvider> SnapBytecodeStore for T {
45 fn missing_code(
46 &self,
47 write: SnapWrite,
48 hashes: &[B256],
49 limit: usize,
50 ) -> Result<Vec<B256>, SnapSyncError>
51 where
52 Self: DBProvider,
53 {
54 self.authorize_snap_write(write)?;
55 let mut missing = Vec::new();
56 for hash in hashes {
57 if missing.len() == limit {
58 break
59 }
60 if self.tx_ref().get::<RawTable<tables::Bytecodes>>(RawKey::new(*hash))?.is_none() {
62 missing.push(*hash);
63 }
64 }
65 Ok(missing)
66 }
67
68 fn commit_bytecodes(
70 &self,
71 write: SnapWrite,
72 codes: Vec<(B256, Bytes)>,
73 ) -> Result<usize, SnapSyncError>
74 where
75 Self: StateWriter + DBProvider<Tx: DbTxMut>,
76 {
77 self.authorize_snap_write(write)?;
78 let contracts = codes
79 .into_iter()
80 .map(|(hash, code)| {
81 let got = keccak256(&code);
82 if got != hash {
83 return Err(SnapSyncError::CodeMismatch { expected: hash, got })
84 }
85 let code = Bytecode::new_raw_checked(code.clone())
88 .unwrap_or_else(|_| Bytecode::new_legacy(code));
89 Ok((hash, code))
90 })
91 .collect::<Result<Vec<_>, SnapSyncError>>()?;
92
93 let written = contracts.len();
94 self.write_state_changes(StateChangeset { contracts, ..Default::default() })?;
95 Ok(written)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::test_utils::{
103 account, generation, hashed_factory_with_roots, key, state_root, 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_with_roots(&[state_root(accounts); 3]);
126 let provider = factory.database_provider_rw().unwrap();
127 let write = provider.start_snap_attempt(generation(1, state_root(accounts))).unwrap();
128 provider.commit().unwrap();
129 (factory, write)
130 }
131
132 fn is_stored(provider: &impl DBProvider, code: &Bytes) -> bool {
133 provider.tx_ref().get::<tables::Bytecodes>(keccak256(code)).unwrap().is_some()
134 }
135
136 #[test]
137 fn one_blob_answers_every_account_sharing_its_hash() {
138 let shared = code(1);
139 let accounts = vec![
140 (key(1), contract(1, &shared)),
141 (key(2), account(2)),
142 (key(3), contract(3, &shared)),
143 (key(4), contract(4, &code(2))),
144 ];
145 let (factory, write) = started(&accounts);
146 let provider = factory.database_provider_rw().unwrap();
147 let referenced =
148 verified_range(&accounts, 0..accounts.len(), B256::ZERO, &[]).code_hashes();
149
150 let missing = provider.missing_code(write, &referenced, usize::MAX).unwrap();
151
152 assert_eq!(missing, [keccak256(&shared), keccak256(code(2))]);
153 assert_eq!(
154 provider.commit_bytecodes(write, vec![(missing[0], shared.clone())]).unwrap(),
155 1
156 );
157 assert_eq!(
159 provider.missing_code(write, &referenced, usize::MAX).unwrap(),
160 [keccak256(code(2))]
161 );
162 assert!(is_stored(&provider, &shared));
163 }
164
165 #[test]
166 fn code_that_does_not_hash_to_its_requested_hash_is_refused() {
167 let wanted = code(1);
168 let accounts = vec![(key(1), contract(1, &wanted))];
169 let (factory, write) = started(&accounts);
170 let provider = factory.database_provider_rw().unwrap();
171 let hash = keccak256(&wanted);
172
173 let refused = provider.commit_bytecodes(write, vec![(hash, code(2))]);
174
175 assert!(
176 matches!(refused, Err(SnapSyncError::CodeMismatch { expected, .. }) if expected == hash)
177 );
178 assert_eq!(provider.missing_code(write, &[hash], usize::MAX).unwrap(), [hash]);
179 assert!(!is_stored(&provider, &wanted));
180 }
181
182 #[test]
185 fn delegation_shaped_code_that_predates_eip_3541_is_kept_as_legacy_code() {
186 let historical = bytes!("ef0100");
187 let accounts = vec![(key(1), contract(1, &historical))];
188 let (factory, write) = started(&accounts);
189 let provider = factory.database_provider_rw().unwrap();
190
191 let hash = keccak256(&historical);
192 assert_eq!(provider.commit_bytecodes(write, vec![(hash, historical.clone())]).unwrap(), 1);
193
194 let stored = provider.tx_ref().get::<tables::Bytecodes>(hash).unwrap().unwrap();
195 assert_eq!(stored.original_bytes(), historical);
196 assert!(provider.missing_code(write, &[hash], usize::MAX).unwrap().is_empty());
197 }
198
199 #[test]
200 fn code_is_refused_once_the_attempt_no_longer_owns_the_state() {
201 let wanted = code(1);
202 let accounts = vec![(key(1), contract(1, &wanted))];
203 let (factory, write) = started(&accounts);
204 let provider = factory.database_provider_rw().unwrap();
205 provider.advance_snap_pivot(write, generation(2, state_root(&accounts))).unwrap();
206
207 let refused = provider.commit_bytecodes(write, vec![(keccak256(&wanted), wanted.clone())]);
208
209 assert!(matches!(refused, Err(SnapSyncError::StaleWrite { .. })));
210 assert!(matches!(
211 provider.missing_code(write, &[keccak256(&wanted)], usize::MAX),
212 Err(SnapSyncError::StaleWrite { .. })
213 ));
214 assert!(!is_stored(&provider, &wanted));
215 }
216}