1use aztec_core::error::Error;
8use aztec_core::tx::TxHash;
9use aztec_core::types::{AztecAddress, Fr};
10use aztec_node_client::AztecNode;
11
12use crate::stores::private_event_store::{PrivateEventQueryFilter, StoredPrivateEvent};
13use crate::stores::{AnchorBlockStore, PrivateEventStore};
14
15use aztec_core::abi::EventSelector;
16
17pub struct EventService<'a, N: AztecNode> {
23 node: &'a N,
24 private_event_store: &'a PrivateEventStore,
25 anchor_block_store: &'a AnchorBlockStore,
26}
27
28impl<'a, N: AztecNode> EventService<'a, N> {
29 pub fn new(
30 node: &'a N,
31 private_event_store: &'a PrivateEventStore,
32 anchor_block_store: &'a AnchorBlockStore,
33 ) -> Self {
34 Self {
35 node,
36 private_event_store,
37 anchor_block_store,
38 }
39 }
40
41 pub async fn validate_and_store_event(
50 &self,
51 contract_address: &AztecAddress,
52 selector: &EventSelector,
53 randomness: Fr,
54 content: Vec<Fr>,
55 event_commitment: Fr,
56 tx_hash: TxHash,
57 scope: &AztecAddress,
58 ) -> Result<(), Error> {
59 let siloed_event_commitment = aztec_core::hash::poseidon2_hash_with_separator(
61 &[Fr::from(*contract_address), event_commitment],
62 0,
63 );
64
65 let anchor_block_number = self.anchor_block_store.get_block_number().await?;
67
68 let tx_effect =
72 self.node.get_tx_effect(&tx_hash).await?.ok_or_else(|| {
73 Error::InvalidData(format!("tx effect not found for tx {tx_hash}"))
74 })?;
75
76 let block_number = tx_effect
78 .pointer("/l2BlockNumber")
79 .or_else(|| tx_effect.get("l2BlockNumber"))
80 .and_then(|v| v.as_u64())
81 .unwrap_or(0);
82
83 if block_number > anchor_block_number && anchor_block_number > 0 {
84 return Err(Error::InvalidData(format!(
85 "tx {} is in block {block_number} which is after anchor block {anchor_block_number}",
86 tx_hash
87 )));
88 }
89
90 let l2_block_hash = tx_effect
92 .pointer("/l2BlockHash")
93 .or_else(|| tx_effect.get("l2BlockHash"))
94 .and_then(|v| v.as_str())
95 .unwrap_or("0x0")
96 .to_owned();
97
98 let tx_index_in_block = tx_effect
100 .pointer("/txIndexInBlock")
101 .or_else(|| tx_effect.get("txIndexInBlock"))
102 .and_then(|v| v.as_u64());
103
104 let nullifiers = tx_effect
106 .pointer("/nullifiers")
107 .or_else(|| tx_effect.get("nullifiers"))
108 .and_then(|v| v.as_array());
109
110 if let Some(nullifiers) = nullifiers {
111 let commitment_hex = format!("{siloed_event_commitment}");
112 let found = nullifiers
113 .iter()
114 .any(|n| n.as_str().map_or(false, |s| s == commitment_hex));
115 if !found {
116 return Err(Error::InvalidData(format!(
117 "siloed event commitment {commitment_hex} not found in tx {tx_hash} nullifiers"
118 )));
119 }
120 }
121
122 let event_index_in_tx = nullifiers.and_then(|nullifiers| {
124 let commitment_hex = format!("{siloed_event_commitment}");
125 nullifiers
126 .iter()
127 .position(|n| n.as_str().map_or(false, |s| s == commitment_hex))
128 .map(|i| i as u64)
129 });
130
131 let event = StoredPrivateEvent {
133 event_selector: *selector,
134 randomness,
135 msg_content: content,
136 siloed_event_commitment,
137 contract_address: *contract_address,
138 scopes: vec![],
139 tx_hash,
140 l2_block_number: block_number,
141 l2_block_hash,
142 tx_index_in_block,
143 event_index_in_tx,
144 };
145
146 self.private_event_store
147 .store_private_event_log(&event, scope)
148 .await?;
149
150 tracing::debug!(
151 contract = %contract_address,
152 event_selector = %selector.0,
153 block = block_number,
154 "stored private event"
155 );
156
157 Ok(())
158 }
159
160 pub async fn get_private_events(
162 &self,
163 event_selector: &EventSelector,
164 filter: &PrivateEventQueryFilter,
165 ) -> Result<Vec<StoredPrivateEvent>, Error> {
166 self.private_event_store
167 .get_private_events(event_selector, filter)
168 .await
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::stores::InMemoryKvStore;
176 use std::sync::Arc;
177
178 struct MockNode;
180
181 #[async_trait::async_trait]
182 impl AztecNode for MockNode {
183 async fn get_node_info(&self) -> Result<aztec_node_client::NodeInfo, Error> {
184 Ok(aztec_node_client::NodeInfo {
185 node_version: "mock".into(),
186 l1_chain_id: 1,
187 rollup_version: 1,
188 enr: None,
189 l1_contract_addresses: serde_json::Value::Null,
190 protocol_contract_addresses: serde_json::Value::Null,
191 real_proofs: false,
192 l2_circuits_vk_tree_root: None,
193 l2_protocol_contracts_hash: None,
194 })
195 }
196 async fn get_block_number(&self) -> Result<u64, Error> {
197 Ok(5)
198 }
199 async fn get_proven_block_number(&self) -> Result<u64, Error> {
200 Ok(5)
201 }
202 async fn get_tx_receipt(
203 &self,
204 _tx_hash: &TxHash,
205 ) -> Result<aztec_core::tx::TxReceipt, Error> {
206 Ok(aztec_core::tx::TxReceipt {
207 tx_hash: TxHash::zero(),
208 status: aztec_core::tx::TxStatus::Proposed,
209 execution_result: Some(aztec_core::tx::TxExecutionResult::Success),
210 error: None,
211 transaction_fee: None,
212 block_hash: None,
213 block_number: Some(3),
214 epoch_number: None,
215 })
216 }
217 async fn get_tx_effect(
218 &self,
219 _tx_hash: &TxHash,
220 ) -> Result<Option<serde_json::Value>, Error> {
221 Ok(Some(serde_json::json!({
225 "l2BlockNumber": 3,
226 "l2BlockHash": "0x0000000000000000000000000000000000000000000000000000000000000003",
227 "txIndexInBlock": 0
228 })))
229 }
230 async fn get_tx_by_hash(
231 &self,
232 _tx_hash: &TxHash,
233 ) -> Result<Option<serde_json::Value>, Error> {
234 Ok(None)
235 }
236 async fn get_public_logs(
237 &self,
238 _: aztec_node_client::PublicLogFilter,
239 ) -> Result<aztec_node_client::PublicLogsResponse, Error> {
240 Ok(aztec_node_client::PublicLogsResponse {
241 logs: vec![],
242 max_logs_hit: false,
243 })
244 }
245 async fn send_tx(&self, _: &serde_json::Value) -> Result<(), Error> {
246 Ok(())
247 }
248 async fn get_contract(
249 &self,
250 _: &AztecAddress,
251 ) -> Result<Option<aztec_core::types::ContractInstanceWithAddress>, Error> {
252 Ok(None)
253 }
254 async fn get_contract_class(&self, _: &Fr) -> Result<Option<serde_json::Value>, Error> {
255 Ok(None)
256 }
257 async fn get_block_header(&self, _: u64) -> Result<serde_json::Value, Error> {
258 Ok(serde_json::json!({}))
259 }
260 async fn get_block(&self, _: u64) -> Result<Option<serde_json::Value>, Error> {
261 Ok(None)
262 }
263 async fn get_note_hash_membership_witness(
264 &self,
265 _: u64,
266 _: &Fr,
267 ) -> Result<Option<serde_json::Value>, Error> {
268 Ok(None)
269 }
270 async fn get_nullifier_membership_witness(
271 &self,
272 _: u64,
273 _: &Fr,
274 ) -> Result<Option<serde_json::Value>, Error> {
275 Ok(None)
276 }
277 async fn get_low_nullifier_membership_witness(
278 &self,
279 _: u64,
280 _: &Fr,
281 ) -> Result<Option<serde_json::Value>, Error> {
282 Ok(None)
283 }
284 async fn get_public_storage_at(
285 &self,
286 _: u64,
287 _: &AztecAddress,
288 _: &Fr,
289 ) -> Result<Fr, Error> {
290 Ok(Fr::zero())
291 }
292 async fn get_public_data_witness(
293 &self,
294 _: u64,
295 _: &Fr,
296 ) -> Result<Option<serde_json::Value>, Error> {
297 Ok(None)
298 }
299 async fn get_l1_to_l2_message_membership_witness(
300 &self,
301 _: u64,
302 _: &Fr,
303 ) -> Result<Option<serde_json::Value>, Error> {
304 Ok(None)
305 }
306 async fn simulate_public_calls(
307 &self,
308 _: &serde_json::Value,
309 _: bool,
310 ) -> Result<serde_json::Value, Error> {
311 Ok(serde_json::Value::Null)
312 }
313 async fn is_valid_tx(
314 &self,
315 _: &serde_json::Value,
316 ) -> Result<aztec_node_client::TxValidationResult, Error> {
317 Ok(aztec_node_client::TxValidationResult::Valid)
318 }
319 async fn get_private_logs_by_tags(&self, _: &[Fr]) -> Result<serde_json::Value, Error> {
320 Ok(serde_json::json!([]))
321 }
322 async fn get_public_logs_by_tags_from_contract(
323 &self,
324 _: &AztecAddress,
325 _: &[Fr],
326 ) -> Result<serde_json::Value, Error> {
327 Ok(serde_json::json!([]))
328 }
329 async fn register_contract_function_signatures(&self, _: &[String]) -> Result<(), Error> {
330 Ok(())
331 }
332 async fn get_block_hash_membership_witness(
333 &self,
334 _: u64,
335 _: &Fr,
336 ) -> Result<Option<serde_json::Value>, Error> {
337 Ok(None)
338 }
339 async fn find_leaves_indexes(
340 &self,
341 _: u64,
342 _: &str,
343 _: &[Fr],
344 ) -> Result<Vec<Option<u64>>, Error> {
345 Ok(vec![])
346 }
347 }
348
349 #[tokio::test]
350 async fn validate_and_store_event_stores_correctly() {
351 let kv: Arc<dyn crate::stores::kv::KvStore> = Arc::new(InMemoryKvStore::new());
352 let event_store = PrivateEventStore::new(Arc::clone(&kv));
353 let anchor_store = AnchorBlockStore::new(Arc::clone(&kv));
354
355 let anchor = crate::stores::anchor_block_store::AnchorBlockHeader::from_header_json(
357 serde_json::json!({"globalVariables": {"blockNumber": 5}}),
358 );
359 anchor_store.set_header(&anchor).await.unwrap();
360
361 let node = MockNode;
362 let service = EventService::new(&node, &event_store, &anchor_store);
363
364 let contract = AztecAddress::from(1u64);
365 let selector = EventSelector(Fr::from(0x12345678u64));
366 let scope = AztecAddress::from(99u64);
367 let tx_hash = TxHash::zero();
368
369 service
370 .validate_and_store_event(
371 &contract,
372 &selector,
373 Fr::from(1u64),
374 vec![Fr::from(10u64)],
375 Fr::from(100u64),
376 tx_hash,
377 &scope,
378 )
379 .await
380 .unwrap();
381
382 let events = event_store
383 .get_private_events(
384 &selector,
385 &PrivateEventQueryFilter {
386 contract_address: contract,
387 from_block: None,
388 to_block: None,
389 scopes: vec![scope],
390 tx_hash: None,
391 },
392 )
393 .await
394 .unwrap();
395 assert_eq!(events.len(), 1);
396 assert_eq!(events[0].l2_block_number, 3);
397 }
398}