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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
use super::wallet_lane_iter::SplitLaneIter;
use chain_impl_mockchain::{fee::LinearFee, fragment::Fragment};
use jormungandr_automation::{
    jormungandr::{JormungandrProcess, RemoteJormungandr, RestError},
    testing::{settings::SettingsDtoExtension, SyncNode},
};
use jormungandr_lib::crypto::hash::Hash;
use jortestkit::load::{Request, RequestFailure, RequestGenerator};
use rand_core::OsRng;
use std::time::Instant;
use thor::{BlockDateGenerator, FragmentBuilder, FragmentSender, FragmentSenderSetup, Wallet};

pub struct BatchFragmentGenerator<'a, S: SyncNode + Send> {
    wallets: Vec<Wallet>,
    jormungandr: RemoteJormungandr,
    fragment_sender: FragmentSender<'a, S>,
    rand: OsRng,
    split_lane: SplitLaneIter,
    batch_size: u8,
}

impl<'a, S: SyncNode + Send> BatchFragmentGenerator<'a, S> {
    pub fn from_node_with_setup(
        setup: FragmentSenderSetup<'a, S>,
        jormungandr: &JormungandrProcess,
        block_date: BlockDateGenerator,
        batch_size: u8,
    ) -> Result<Self, RestError> {
        let settings = jormungandr.rest().settings()?;
        Ok(Self::new(
            setup,
            jormungandr.to_remote(),
            settings.genesis_block_hash(),
            settings.fees,
            block_date,
            batch_size,
        ))
    }
}

impl<'a, S: SyncNode + Send> BatchFragmentGenerator<'a, S> {
    pub fn new(
        fragment_sender_setup: FragmentSenderSetup<'a, S>,
        jormungandr: RemoteJormungandr,
        block_hash: Hash,
        fees: LinearFee,
        expiry_generator: BlockDateGenerator,
        batch_size: u8,
    ) -> Self {
        Self {
            wallets: Vec::new(),
            fragment_sender: FragmentSender::new(
                block_hash,
                fees,
                expiry_generator,
                fragment_sender_setup,
            ),
            rand: OsRng,
            jormungandr,
            split_lane: SplitLaneIter::new(),
            batch_size,
        }
    }

    pub fn fill_from_faucet(&mut self, faucet: &mut Wallet) {
        let discrimination = self.jormungandr.rest().settings().unwrap().discrimination;

        let mut wallets: Vec<Wallet> =
            std::iter::from_fn(|| Some(Wallet::new_account(&mut self.rand, discrimination)))
                .take(90)
                .collect();

        let fragment_sender = self
            .fragment_sender
            .clone_with_setup(FragmentSenderSetup::resend_3_times());
        fragment_sender
            .send_transaction_to_many(faucet, &wallets, &self.jormungandr, 1_000_000.into())
            .unwrap();

        let mut additional_wallets = Vec::new();

        for wallet in wallets.iter_mut().take(10) {
            let mut pack_of_wallets: Vec<Wallet> =
                std::iter::from_fn(|| Some(Wallet::new_account(&mut self.rand, discrimination)))
                    .take(90)
                    .collect();
            fragment_sender
                .send_transaction_to_many(wallet, &pack_of_wallets, &self.jormungandr, 1000.into())
                .unwrap();
            additional_wallets.append(&mut pack_of_wallets);
        }
        self.wallets.append(&mut additional_wallets);
        self.wallets.append(&mut wallets);
    }

    pub fn generate_transaction(&mut self) -> Result<Fragment, RequestFailure> {
        let (split_marker, witness_mode) = self.split_lane.next(self.wallets.len());
        let (senders, recievers) = self.wallets.split_at_mut(split_marker);
        let sender = senders.get_mut(senders.len() - 1).unwrap();
        let reciever = recievers.get(0).unwrap();

        let fragment = FragmentBuilder::new(
            &self.fragment_sender.block0_hash(),
            &self.fragment_sender.fees(),
            self.fragment_sender.date(),
        )
        .witness_mode(witness_mode)
        .transaction(sender, reciever.address(), 1.into())
        .map_err(|e| RequestFailure::General(format!("{:?}", e)));
        sender.confirm_transaction();
        fragment
    }

    pub fn batch_size(&self) -> u8 {
        self.batch_size
    }

    pub fn generate_batch_transaction(&mut self) -> Result<Vec<Fragment>, RequestFailure> {
        let mut transactions = vec![];

        for _ in 0..self.batch_size {
            transactions.push(self.generate_transaction()?);
        }
        Ok(transactions)
    }

    pub fn send_batch(&mut self) -> Result<Request, RequestFailure> {
        let transactions = self.generate_batch_transaction()?;
        let start = Instant::now();
        self.fragment_sender
            .send_batch_fragments(transactions, false, &self.jormungandr)
            .map(|summary| Request {
                ids: summary
                    .fragment_ids()
                    .iter()
                    .map(|x| Some(x.to_string()))
                    .collect(),
                duration: start.elapsed(),
            })
            .map_err(|e| RequestFailure::General(format!("{:?}", e)))
    }
}

impl<S: SyncNode + Send + Sync + Clone> RequestGenerator for BatchFragmentGenerator<'_, S> {
    fn next(&mut self) -> Result<Request, RequestFailure> {
        self.send_batch()
    }

    fn split(mut self) -> (Self, Option<Self>) {
        let wallets_len = self.wallets.len();
        // there needs to be at least one receiver and one sender in each half
        if wallets_len <= 4 {
            return (self, None);
        }
        let wallets = self.wallets.split_off(wallets_len / 2);
        let other = Self {
            wallets,
            jormungandr: self.jormungandr.clone_with_rest(),
            fragment_sender: self.fragment_sender.clone(),
            rand: OsRng,
            split_lane: SplitLaneIter::new(),
            batch_size: self.batch_size(),
        };
        (self, Some(other))
    }
}