1use crate::transaction::Event;
2use certified_vars::hashtree::{fork, fork_hash};
3use certified_vars::Paged;
4use certified_vars::{rbtree::RbTree, AsHashTree, Hash, HashTree};
5use ic_kit::candid::types::{Compound, Type};
6use ic_kit::candid::CandidType;
7use ic_kit::Principal;
8use serde::ser::{SerializeSeq, SerializeTuple};
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use std::alloc::{dealloc, Layout};
11use std::ptr;
12use std::ptr::NonNull;
13
14pub struct TransactionList {
35 event_hashes: RbTree<u32, Hash>,
37 contract: Principal,
39 global_offset: u64,
41 user_indexer: Paged<Principal, NonNull<Event>, 64>,
43 contract_indexer: Paged<Principal, NonNull<Event>, 64>,
45 token_indexer: Paged<u64, NonNull<Event>, 64>,
47 events: Vec<NonNull<Event>>,
52}
53
54impl TransactionList {
55 #[inline]
57 pub fn new(contract: Principal, offset: u64) -> Self {
58 TransactionList {
59 events: vec![],
60 contract,
61 event_hashes: RbTree::new(),
62 global_offset: offset,
63 user_indexer: Paged::new(),
64 contract_indexer: Paged::new(),
65 token_indexer: Paged::new(),
66 }
67 }
68
69 #[inline]
71 pub fn contract_id(&self) -> &Principal {
72 &self.contract
73 }
74
75 #[inline]
77 pub fn size(&self) -> u64 {
78 self.global_offset + (self.events.len() as u64)
79 }
80
81 #[inline]
83 pub fn len(&self) -> usize {
84 self.events.len()
85 }
86
87 #[inline]
89 pub fn is_empty(&self) -> bool {
90 self.events.is_empty()
91 }
92
93 pub fn insert(&mut self, event: Event) -> u64 {
95 let local_index = self.events.len() as u32;
96 let hash = event.hash();
97 let event: NonNull<Event> = Box::leak(Box::new(event)).into();
98 let eve = unsafe { event.as_ref() };
99
100 self.contract_indexer.insert(self.contract, event);
102 for user in eve.extract_principal_ids() {
103 self.user_indexer.insert(*user, event);
104 }
105 for token_id in eve.extract_token_ids() {
106 self.token_indexer.insert(token_id, event);
107 }
108
109 self.event_hashes.insert(local_index, hash);
111 self.events.push(event);
112
113 self.global_offset + (local_index as u64)
114 }
115
116 #[inline]
118 pub fn get_transactions_for_user(&self, principal: &Principal, page: u32) -> Vec<&Event> {
119 if let Some(data) = self.user_indexer.get(principal, page as usize) {
120 data.iter().map(|v| unsafe { v.as_ref() }).collect()
121 } else {
122 vec![]
123 }
124 }
125
126 #[inline]
128 pub fn last_page_for_user(&self, principal: &Principal) -> u32 {
129 self.user_indexer
130 .get_last_page_number(principal)
131 .unwrap_or(0) as u32
132 }
133
134 #[inline]
136 pub fn get_transactions_for_contract(&self, principal: &Principal, page: u32) -> Vec<&Event> {
137 if let Some(data) = self.contract_indexer.get(principal, page as usize) {
138 data.iter().map(|v| unsafe { v.as_ref() }).collect()
139 } else {
140 vec![]
141 }
142 }
143
144 #[inline]
146 pub fn last_page_for_contract(&self, principal: &Principal) -> u32 {
147 self.contract_indexer
148 .get_last_page_number(principal)
149 .unwrap_or(0) as u32
150 }
151
152 #[inline]
154 pub fn get_transactions_for_token(&self, token_id: &u64, page: u32) -> Vec<&Event> {
155 if let Some(data) = self.token_indexer.get(token_id, page as usize) {
156 data.iter().map(|v| unsafe { v.as_ref() }).collect()
157 } else {
158 vec![]
159 }
160 }
161
162 #[inline]
163 pub fn last_page_for_token(&self, token_id: &u64) -> u32 {
164 self.token_indexer
165 .get_last_page_number(token_id)
166 .unwrap_or(0) as u32
167 }
168
169 #[inline]
171 pub fn witness_transactions_for_user(&self, principal: &Principal, page: u32) -> HashTree {
172 fork(
173 HashTree::Pruned(fork_hash(
174 &self.event_hashes.root_hash(),
175 &self.global_offset.root_hash(),
176 )),
177 fork(
178 self.user_indexer.witness(principal, page as usize),
179 HashTree::Pruned(fork_hash(
180 &self.contract_indexer.root_hash(),
181 &self.token_indexer.root_hash(),
182 )),
183 ),
184 )
185 }
186
187 #[inline]
189 pub fn witness_transactions_for_contract(&self, principal: &Principal, page: u32) -> HashTree {
190 fork(
191 HashTree::Pruned(fork_hash(
192 &self.event_hashes.root_hash(),
193 &self.global_offset.root_hash(),
194 )),
195 fork(
196 HashTree::Pruned(self.user_indexer.root_hash()),
197 fork(
198 self.contract_indexer.witness(principal, page as usize),
199 HashTree::Pruned(self.token_indexer.root_hash()),
200 ),
201 ),
202 )
203 }
204
205 #[inline]
207 pub fn witness_transactions_for_token(&self, token_id: &u64, page: u32) -> HashTree {
208 fork(
209 HashTree::Pruned(fork_hash(
210 &self.event_hashes.root_hash(),
211 &self.global_offset.root_hash(),
212 )),
213 fork(
214 HashTree::Pruned(self.user_indexer.root_hash()),
215 fork(
216 HashTree::Pruned(self.contract_indexer.root_hash()),
217 self.token_indexer.witness(token_id, page as usize),
218 ),
219 ),
220 )
221 }
222
223 #[inline]
225 pub fn get_transaction(&self, id: u64) -> Option<&Event> {
226 if id < self.global_offset {
227 None
228 } else {
229 let local = (id - self.global_offset) as usize;
230 if local < self.events.len() {
231 Some(unsafe { self.events[local].as_ref() })
232 } else {
233 None
234 }
235 }
236 }
237
238 #[inline]
240 pub fn witness_transaction(&self, id: u64) -> HashTree {
241 let left = if id < self.global_offset {
242 fork(
243 HashTree::Pruned(self.event_hashes.root_hash()),
244 self.global_offset.as_hash_tree(),
245 )
246 } else {
247 let local = (id - self.global_offset) as u32;
248 fork(
249 self.event_hashes.witness(&local),
250 self.global_offset.as_hash_tree(),
251 )
252 };
253
254 fork(
255 left,
256 HashTree::Pruned(fork_hash(
257 &self.user_indexer.root_hash(),
258 &fork_hash(
259 &self.contract_indexer.root_hash(),
260 &self.token_indexer.root_hash(),
261 ),
262 )),
263 )
264 }
265}
266
267impl AsHashTree for TransactionList {
268 fn root_hash(&self) -> Hash {
269 fork_hash(
270 &fork_hash(
271 &self.event_hashes.root_hash(),
272 &self.global_offset.root_hash(),
273 ),
274 &fork_hash(
275 &self.user_indexer.root_hash(),
276 &fork_hash(
277 &self.contract_indexer.root_hash(),
278 &self.token_indexer.root_hash(),
279 ),
280 ),
281 )
282 }
283
284 fn as_hash_tree(&self) -> HashTree<'_> {
285 fork(
286 fork(
287 self.event_hashes.as_hash_tree(),
288 self.global_offset.as_hash_tree(),
289 ),
290 fork(
291 self.user_indexer.as_hash_tree(),
292 fork(
293 self.contract_indexer.as_hash_tree(),
294 self.token_indexer.as_hash_tree(),
295 ),
296 ),
297 )
298 }
299}
300
301impl Drop for TransactionList {
302 fn drop(&mut self) {
303 unsafe {
304 for event in &self.events {
305 let as_mut_ref = &mut (*event.as_ptr());
306 ptr::drop_in_place(as_mut_ref);
307 dealloc(event.cast().as_ptr(), Layout::for_value(event.as_ref()));
308 }
309 }
310 }
311}
312
313struct EventsWrapper<'a>(&'a Vec<NonNull<Event>>);
314
315impl<'a> Serialize for EventsWrapper<'a> {
316 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
317 where
318 S: Serializer,
319 {
320 let mut s = serializer.serialize_seq(Some(self.0.len()))?;
321 for ev in self.0 {
322 s.serialize_element(unsafe { ev.as_ref() })?;
323 }
324 s.end()
325 }
326}
327
328impl<'a> CandidType for EventsWrapper<'a> {
329 fn _ty() -> Type {
330 <Vec<Event>>::_ty()
331 }
332
333 fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
334 where
335 S: ic_kit::candid::types::Serializer,
336 {
337 let mut ser = serializer.serialize_vec(self.0.len())?;
338 for e in self.0.iter() {
339 Compound::serialize_element(&mut ser, unsafe { e.as_ref() })?;
340 }
341 Ok(())
342 }
343}
344
345impl Serialize for TransactionList {
346 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
347 where
348 S: Serializer,
349 {
350 let mut s = serializer.serialize_tuple(3)?;
351 s.serialize_element(&self.global_offset)?;
352 s.serialize_element(&self.contract)?;
353 s.serialize_element(&EventsWrapper(&self.events))?;
354 s.end()
355 }
356}
357
358impl<'de> Deserialize<'de> for TransactionList {
359 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
360 where
361 D: Deserializer<'de>,
362 {
363 #[derive(Deserialize)]
364 struct BucketDe(u64, Principal, Vec<Event>);
365
366 let data = BucketDe::deserialize(deserializer)?;
367 let mut list = TransactionList::new(data.1, data.0);
368
369 for event in data.2 {
370 list.insert(event);
371 }
372
373 Ok(list)
374 }
375}
376
377impl CandidType for TransactionList {
378 fn _ty() -> Type {
379 <(u64, Principal, EventsWrapper)>::_ty()
380 }
381
382 fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
383 where
384 S: ic_kit::candid::types::Serializer,
385 {
386 (
387 &self.global_offset,
388 &self.contract,
389 &EventsWrapper(&self.events),
390 )
391 .idl_serialize(serializer)
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use ic_kit::candid::{decode_one, encode_one};
399 use ic_kit::mock_principals;
400
401 fn e(time: u64, caller: Principal) -> Event {
402 Event {
403 time,
404 caller,
405 operation: "transfer".into(),
406 details: vec![],
407 }
408 }
409
410 #[test]
412 fn test_hash_tree() {
413 let mut list = TransactionList::new(mock_principals::xtc(), 0);
414 list.insert(e(0, mock_principals::alice()));
415 list.insert(e(1, mock_principals::alice()));
416 list.insert(e(2, mock_principals::alice()));
417 list.insert(e(3, mock_principals::alice()));
418 assert_eq!(list.as_hash_tree().reconstruct(), list.root_hash());
419 }
420
421 #[test]
424 fn test_witness_transaction() {
425 let mut list = TransactionList::new(mock_principals::xtc(), 0);
426 list.insert(e(0, mock_principals::alice()));
427 list.insert(e(1, mock_principals::alice()));
428 list.insert(e(2, mock_principals::alice()));
429 list.insert(e(3, mock_principals::alice()));
430
431 let event = list.get_transaction(1).unwrap();
432 let witness = list.witness_transaction(1);
433 assert_eq!(event.time, 1);
434 assert_eq!(witness.reconstruct(), list.root_hash());
435 }
436
437 #[test]
438 fn test_witness_transaction_large() {
439 let mut list = TransactionList::new(mock_principals::xtc(), 0);
440 list.insert(e(0, mock_principals::alice()));
441 list.insert(e(1, mock_principals::alice()));
442 list.insert(e(2, mock_principals::alice()));
443 list.insert(e(3, mock_principals::alice()));
444
445 assert_eq!(list.get_transaction(4).is_none(), true);
446
447 let witness = list.witness_transaction(4);
448 assert_eq!(witness.reconstruct(), list.root_hash());
449 }
450
451 #[test]
452 fn test_witness_transaction_below_offset() {
453 let mut list = TransactionList::new(mock_principals::xtc(), 10);
454 list.insert(e(10, mock_principals::alice()));
455 list.insert(e(11, mock_principals::alice()));
456 list.insert(e(12, mock_principals::alice()));
457 list.insert(e(13, mock_principals::alice()));
458
459 assert_eq!(list.get_transaction(5).is_none(), true);
460 let witness = list.witness_transaction(5);
461 assert_eq!(witness.reconstruct(), list.root_hash());
462 }
463
464 #[test]
465 fn test_witness_user_transactions() {
466 let mut list = TransactionList::new(mock_principals::xtc(), 0);
467
468 for i in 0..5000 {
469 if i % 27 == 0 {
470 list.insert(e(i, mock_principals::bob()));
471 } else {
472 list.insert(e(i, mock_principals::alice()));
473 }
474 }
475
476 let mut count = 0;
477
478 for page in 0.. {
479 let principal = mock_principals::bob();
480 let data = list.get_transactions_for_user(&principal, page);
481 let witness = list.witness_transactions_for_user(&principal, page);
482 let len = data.len();
483
484 assert_eq!(witness.reconstruct(), list.root_hash());
485
486 count += len;
487
488 if len == 0 {
489 break;
490 }
491 }
492
493 assert_eq!(count, 186);
495 }
496
497 #[test]
498 fn serde() {
499 let mut list = TransactionList::new(mock_principals::xtc(), 0);
500 list.insert(e(0, mock_principals::alice()));
501 list.insert(e(1, mock_principals::alice()));
502 list.insert(e(2, mock_principals::alice()));
503 list.insert(e(3, mock_principals::alice()));
504 let expected = list.root_hash();
505
506 let data: Vec<u8> = serde_cbor::to_vec(&list).unwrap();
507 let list: TransactionList = serde_cbor::from_slice(&data).unwrap();
508 assert_eq!(list.root_hash(), expected);
509 }
510
511 #[test]
512 fn candid() {
513 let mut list = TransactionList::new(mock_principals::xtc(), 0);
514 list.insert(e(0, mock_principals::alice()));
515 list.insert(e(1, mock_principals::alice()));
516 list.insert(e(2, mock_principals::alice()));
517 list.insert(e(3, mock_principals::alice()));
518 let expected = list.root_hash();
519
520 let encoded = encode_one(&list).unwrap();
521 let decoded: TransactionList = decode_one(&encoded).unwrap();
522 assert_eq!(decoded.root_hash(), expected);
523 }
524}