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
use crate::logs::sentry::{Error, SentryFragmentLog};
use crate::recovery::tally::{deconstruct_account_transaction, ValidationError};

use chain_core::property::Fragment as _;
use chain_impl_mockchain::fragment::Fragment;
use chain_impl_mockchain::vote::Payload;
use jormungandr_lib::interfaces::PersistentFragmentLog;

use std::collections::HashSet;

#[derive(Debug, Eq, PartialEq)]
pub struct LogCmpFields {
    pub public_key: String,
    pub chain_proposal_index: u8,
    pub voteplan_id: String,
    pub choice: u8,
    pub fragment_id: String,
}

pub fn persistent_fragment_log_to_log_cmp_fields(
    fragment: &PersistentFragmentLog,
) -> Result<LogCmpFields, Error> {
    if let Fragment::VoteCast(ref transaction) = fragment.fragment.clone() {
        let (vote_cast, identifier, choice) = deconstruct_account_transaction(
            &transaction.as_slice(),
        )
        .and_then(|(vote_cast, identifier, _)| {
            if let Payload::Public { choice } = vote_cast.payload().clone() {
                Ok((vote_cast, identifier, choice))
            } else {
                Err(ValidationError::UnsupportedPrivateVotes)
            }
        })?;
        Ok(LogCmpFields {
            fragment_id: fragment.fragment.id().to_string(),
            public_key: identifier.to_string(),
            chain_proposal_index: vote_cast.proposal_index(),
            choice: choice.as_byte(),
            voteplan_id: vote_cast.vote_plan().to_string(),
        })
    } else {
        Err(Error::NotVoteCastTransaction {
            fragment_id: fragment.fragment.id().to_string(),
        })
    }
}

pub struct LogCmpStats {
    pub sentry_logs_size: usize,
    pub fragment_logs_size: usize,
    pub duplicated_sentry_logs: usize,
    pub duplicated_fragment_logs: usize,
    pub fragment_ids_differ: HashSet<String>,
    pub unhandled_fragment_logs: Vec<(Fragment, Error)>,
}

pub fn compare_logs(
    sentry_logs: &[SentryFragmentLog],
    fragment_logs: &[PersistentFragmentLog],
) -> LogCmpStats {
    let sentry_logs_size = sentry_logs.len();
    let fragment_logs_size = fragment_logs.len();
    let sentry_cmp: Vec<LogCmpFields> = sentry_logs.iter().cloned().map(Into::into).collect();

    let (fragments_cmp, unhandled_fragment_logs): (Vec<LogCmpFields>, Vec<(Fragment, Error)>) =
        fragment_logs.iter().fold(
            (Vec::new(), Vec::new()),
            |(mut success, mut errored), log| {
                match persistent_fragment_log_to_log_cmp_fields(log) {
                    Ok(log) => {
                        success.push(log);
                    }
                    Err(e) => errored.push((log.fragment.clone(), e)),
                };
                (success, errored)
            },
        );

    let sentry_fragments_ids: HashSet<String> = sentry_cmp
        .iter()
        .map(|e| e.fragment_id.to_string())
        .collect();
    let fragment_logs_ids: HashSet<String> = fragments_cmp
        .iter()
        .map(|e| e.fragment_id.to_string())
        .collect();
    let fragment_ids_differ: HashSet<String> = sentry_fragments_ids
        .difference(&fragment_logs_ids)
        .cloned()
        .collect();
    let duplicated_sentry_logs = sentry_logs_size - sentry_fragments_ids.len();
    let duplicated_fragment_logs = fragment_logs_size - fragment_logs_ids.len();

    LogCmpStats {
        sentry_logs_size,
        fragment_logs_size,
        duplicated_sentry_logs,
        duplicated_fragment_logs,
        fragment_ids_differ,
        unhandled_fragment_logs,
    }
}