1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use crate::{fragment::FragmentId, network::retrieve_local_ip};
use jormungandr_lib::{
    crypto::hash::Hash,
    interfaces::{BlockDate, FragmentLog, FragmentOrigin, FragmentStatus},
};
use lru::LruCache;
use std::collections::HashMap;

pub struct Logs {
    entries: LruCache<Hash, (FragmentLog, Option<BlockDate>)>,
}

impl Logs {
    pub fn new(max_entries: usize) -> Self {
        Logs {
            entries: LruCache::new(max_entries),
        }
    }

    pub fn exists(&self, fragment_id: FragmentId) -> bool {
        let fragment_id: Hash = fragment_id.into();
        self.entries.contains(&fragment_id)
    }

    pub fn exist_all(&self, fragment_ids: impl IntoIterator<Item = FragmentId>) -> Vec<bool> {
        fragment_ids
            .into_iter()
            .map(|fragment_id| self.exists(fragment_id))
            .collect()
    }

    /// Returns true if fragment was registered
    pub fn insert_pending(&mut self, log: FragmentLog) -> bool {
        assert!(log.is_pending());
        let fragment_id = *log.fragment_id();
        if self.entries.contains(&fragment_id) {
            false
        } else {
            self.entries.put(fragment_id, (log, None));
            true
        }
    }

    /// Returns number of registered fragments
    pub fn insert_all_pending(&mut self, logs: impl IntoIterator<Item = FragmentLog>) -> usize {
        logs.into_iter()
            .map(|log| self.insert_pending(log))
            .filter(|was_modified| *was_modified)
            .count()
    }

    pub fn modify(
        &mut self,
        fragment_id: FragmentId,
        status: FragmentStatus,
        ledger_date: BlockDate,
    ) {
        let fragment_id: Hash = fragment_id.into();
        match self.entries.get_mut(&fragment_id) {
            Some((entry, date)) => {
                if !entry.modify(status) {
                    tracing::debug!("the fragment log update was refused: cannot mark the fragment as invalid if it was already committed to a block");
                } else {
                    *date = Some(ledger_date);
                }
            }
            None => {
                // Possible reasons for entering this branch are:
                //
                // - Receiving a fragment with a network block.
                // - Having a fragment evicted from the log due to overflow.
                //
                // For both scenarios the code defaults to FragmentOrigin::Network, since there are
                // no means of knowing where the fragment came from.
                //
                // Also, in this scenario we accept any provided FragmentStatus, since we do not
                // actually know what the previous status was, and thus cannot execute the correct
                // state transition.
                let mut entry = FragmentLog::new(
                    fragment_id.into_hash(),
                    FragmentOrigin::Network {
                        addr: retrieve_local_ip(),
                    },
                );
                entry.modify(status);
                self.entries.put(fragment_id, (entry, Some(ledger_date)));
            }
        }
    }

    pub fn modify_all(
        &mut self,
        fragment_ids: impl IntoIterator<Item = FragmentId>,
        status: FragmentStatus,
        ledger_date: BlockDate,
    ) {
        for fragment_id in fragment_ids {
            self.modify(fragment_id, status.clone(), ledger_date);
        }
    }

    pub fn logs_by_ids(
        &self,
        fragment_ids: impl IntoIterator<Item = FragmentId>,
    ) -> HashMap<FragmentId, &FragmentLog> {
        let mut result = HashMap::new();
        fragment_ids
            .into_iter()
            .filter_map(|fragment_id| {
                let key: Hash = fragment_id.into();
                self.entries.peek(&key).map(|log| (fragment_id, log))
            })
            .for_each(|(k, (log, _date))| {
                result.insert(k, log);
            });
        result
    }

    pub fn logs(&self) -> impl Iterator<Item = &FragmentLog> {
        self.entries.iter().map(|(_, (log, _date))| log)
    }

    pub fn remove_logs_after_date(&mut self, target_date: BlockDate) {
        let mut to_remove = Vec::new();
        for (_, (log, date)) in self.entries.iter() {
            match log.status() {
                FragmentStatus::InABlock { .. } | FragmentStatus::Rejected { .. } => {
                    // date is always present for non pending statuses.
                    if date.unwrap() > target_date {
                        to_remove.push(*log.fragment_id());
                    } else {
                        // iterating in most-recently used order (i.e. most recently added to block)
                        break;
                    }
                }
                FragmentStatus::Pending => (),
            }
        }

        for fragment in to_remove {
            self.entries.pop(&fragment);
        }
    }
}