1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
#![cfg(test)]
18

            
19
use {
20
    crate::{tests::common::*, EthereumBeaconClient},
21
    frame_support::{assert_noop, assert_ok},
22
    snowbridge_pallet_ethereum_client::{functions::*, mock::*},
23
    sp_core::H256,
24
    sp_std::vec,
25
};
26
#[test]
27
1
fn test_ethereum_force_checkpoint() {
28
1
    ExtBuilder::default()
29
1
        .with_balances(vec![
30
1
            // Alice gets 10k extra tokens for her mapping deposit
31
1
            (AccountId::from(ALICE), 210_000 * UNIT),
32
1
            (AccountId::from(BOB), 100_000 * UNIT),
33
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
34
1
            (AccountId::from(DAVE), 100_000 * UNIT),
35
1
        ])
36
1
        .build()
37
1
        .execute_with(|| {
38
1
            // This tests submits the initial checkpoint that contains the initial sync committee
39
1
            let checkpoint =
40
1
                Box::new(snowbridge_pallet_ethereum_client::mock::load_checkpoint_update_fixture());
41
1
            assert_ok!(EthereumBeaconClient::force_checkpoint(
42
1
                root_origin(),
43
1
                checkpoint.clone()
44
1
            ));
45
            // assert checkpoint has updated storages
46
1
            assert_eq!(
47
1
                EthereumBeaconClient::initial_checkpoint_root(),
48
1
                checkpoint.header.hash_tree_root().unwrap()
49
1
            );
50
            // sync committee is correct
51
1
            let unwrap_keys: Vec<snowbridge_beacon_primitives::PublicKey> =
52
1
                snowbridge_pallet_ethereum_client::CurrentSyncCommittee::<Runtime>::get()
53
1
                    .pubkeys
54
1
                    .iter()
55
512
                    .map(|key| {
56
512
                        let unwrapped = key.as_bytes();
57
512
                        let pubkey: snowbridge_beacon_primitives::PublicKey = unwrapped.into();
58
512
                        pubkey
59
512
                    })
60
1
                    .collect();
61
1
            assert_eq!(
62
1
                unwrap_keys,
63
1
                checkpoint.current_sync_committee.pubkeys.to_vec()
64
1
            );
65
1
        })
66
1
}
67

            
68
#[test]
69
1
fn test_invalid_initial_checkpoint() {
70
1
    ExtBuilder::default()
71
1
            .with_balances(vec![
72
1
                // Alice gets 10k extra tokens for her mapping deposit
73
1
                (AccountId::from(ALICE), 210_000 * UNIT),
74
1
                (AccountId::from(BOB), 100_000 * UNIT),
75
1
                (AccountId::from(CHARLIE), 100_000 * UNIT),
76
1
                (AccountId::from(DAVE), 100_000 * UNIT),
77
1
            ])
78
1
            .build()
79
1
            .execute_with(|| {
80
1
                let mut checkpoint_invalid_sync_committee_proof = Box::new(snowbridge_pallet_ethereum_client::mock::load_checkpoint_update_fixture());
81
1

            
82
1
                let mut checkpoint_invalid_blocks_root_proof = checkpoint_invalid_sync_committee_proof.clone();
83
1

            
84
1
                let mut check_invalid_sync_committee = checkpoint_invalid_sync_committee_proof.clone();
85
1

            
86
1
	            checkpoint_invalid_sync_committee_proof.current_sync_committee_branch[0] = H256::default();
87
1
	            checkpoint_invalid_blocks_root_proof.block_roots_branch[0] = H256::default();
88
512
                let new_random_keys: Vec<snowbridge_beacon_primitives::PublicKey> = generate_ethereum_pub_keys(snowbridge_pallet_ethereum_client::config::SYNC_COMMITTEE_SIZE as u32).iter().map(|key| {
89
512
                    let public: snowbridge_beacon_primitives::PublicKey =   key.pk.as_bytes().into();
90
512
                    public
91
512
                }).collect();
92
1
                check_invalid_sync_committee.current_sync_committee.pubkeys = new_random_keys.try_into().expect("cannot convert keys");
93
1
                assert_noop!(
94
1
                    EthereumBeaconClient::force_checkpoint(root_origin(), checkpoint_invalid_sync_committee_proof),
95
1
                    snowbridge_pallet_ethereum_client::Error::<Runtime>::InvalidSyncCommitteeMerkleProof
96
1
                );
97

            
98
1
                assert_noop!(
99
1
                    EthereumBeaconClient::force_checkpoint(root_origin(), checkpoint_invalid_blocks_root_proof),
100
1
                    snowbridge_pallet_ethereum_client::Error::<Runtime>::InvalidBlockRootsRootMerkleProof
101
1
                );
102

            
103
1
                assert_noop!(
104
1
                    EthereumBeaconClient::force_checkpoint(root_origin(), check_invalid_sync_committee),
105
1
                    snowbridge_pallet_ethereum_client::Error::<Runtime>::InvalidSyncCommitteeMerkleProof
106
1
                );
107
1
	});
108
1
}
109

            
110
#[test]
111
1
fn test_submit_update_using_same_committee_same_checkpoint() {
112
1
    ExtBuilder::default()
113
1
        .with_balances(vec![
114
1
            // Alice gets 10k extra tokens for her mapping deposit
115
1
            (AccountId::from(ALICE), 210_000 * UNIT),
116
1
            (AccountId::from(BOB), 100_000 * UNIT),
117
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
118
1
            (AccountId::from(DAVE), 100_000 * UNIT),
119
1
        ])
120
1
        .build()
121
1
        .execute_with(|| {
122
1
            // This tests submits a new header signed by the sync committee members within the same
123
1
            // period BUT without injecting the next sync committee
124
1
            let initial_checkpoint =
125
1
                Box::new(snowbridge_pallet_ethereum_client::mock::load_checkpoint_update_fixture());
126
1
            let update_header = Box::new(
127
1
                snowbridge_pallet_ethereum_client::mock::load_finalized_header_update_fixture(),
128
1
            );
129
1

            
130
1
            let initial_period = compute_period(initial_checkpoint.header.slot);
131
1
            let update_period = compute_period(update_header.finalized_header.slot);
132
1
            assert_eq!(initial_period, update_period);
133
1
            assert_ok!(EthereumBeaconClient::force_checkpoint(
134
1
                root_origin(),
135
1
                initial_checkpoint.clone()
136
1
            ));
137
1
            assert_ok!(EthereumBeaconClient::submit(
138
1
                origin_of(ALICE.into()),
139
1
                update_header.clone()
140
1
            ));
141
1
            let block_root: H256 = update_header.finalized_header.hash_tree_root().unwrap();
142
1
            assert!(snowbridge_pallet_ethereum_client::FinalizedBeaconState::<
143
1
                Runtime,
144
1
            >::contains_key(block_root));
145
1
        });
146
1
}
147

            
148
#[test]
149
1
fn test_submit_update_with_next_sync_committee_in_current_period() {
150
1
    ExtBuilder::default()
151
1
        .with_balances(vec![
152
1
            // Alice gets 10k extra tokens for her mapping deposit
153
1
            (AccountId::from(ALICE), 210_000 * UNIT),
154
1
            (AccountId::from(BOB), 100_000 * UNIT),
155
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
156
1
            (AccountId::from(DAVE), 100_000 * UNIT),
157
1
        ])
158
1
        .build()
159
1
        .execute_with(|| {
160
1
            // This tests submits a new header signed by the sync committee members within the same
161
1
            // period AND injecting the next sync committee
162
1
            let initial_checkpoint = Box::new(load_checkpoint_update_fixture());
163
1
            let update_header = Box::new(load_sync_committee_update_fixture());
164
1
            let initial_period = compute_period(initial_checkpoint.header.slot);
165
1
            let update_period = compute_period(update_header.finalized_header.slot);
166
1
            assert_eq!(initial_period, update_period);
167
1
            assert_ok!(EthereumBeaconClient::force_checkpoint(
168
1
                root_origin(),
169
1
                initial_checkpoint.clone()
170
1
            ));
171
1
            assert!(!snowbridge_pallet_ethereum_client::NextSyncCommittee::<
172
1
                Runtime,
173
1
            >::exists());
174
1
            assert_ok!(EthereumBeaconClient::submit(
175
1
                origin_of(ALICE.into()),
176
1
                update_header.clone()
177
1
            ));
178
1
            assert!(snowbridge_pallet_ethereum_client::NextSyncCommittee::<
179
1
                Runtime,
180
1
            >::exists());
181
1
        });
182
1
}
183

            
184
#[test]
185
1
fn test_submit_update_with_next_sync_committee_in_current_period_without_majority() {
186
1
    ExtBuilder::default()
187
1
            .with_balances(vec![
188
1
                // Alice gets 10k extra tokens for her mapping deposit
189
1
                (AccountId::from(ALICE), 210_000 * UNIT),
190
1
                (AccountId::from(BOB), 100_000 * UNIT),
191
1
                (AccountId::from(CHARLIE), 100_000 * UNIT),
192
1
                (AccountId::from(DAVE), 100_000 * UNIT),
193
1
            ])
194
1
            .build()
195
1
            .execute_with(|| {
196
1
                // This tests submits a new header signed by the sync committee members within the same
197
1
                // period BUT putting all signed bits to 0
198
1
	            let initial_checkpoint = Box::new(load_checkpoint_update_fixture());
199
1
	            let mut update_header = Box::new(load_sync_committee_update_fixture());
200
1
                update_header.sync_aggregate.sync_committee_bits = [0u8; snowbridge_pallet_ethereum_client::config::SYNC_COMMITTEE_BITS_SIZE];
201
1
                let initial_period = compute_period(initial_checkpoint.header.slot);
202
1
                let update_period = compute_period(update_header.finalized_header.slot);
203
1
	            assert_eq!(initial_period, update_period);
204
1
                assert_ok!(EthereumBeaconClient::force_checkpoint(
205
1
                    root_origin(),
206
1
                    initial_checkpoint.clone()
207
1
                ));
208
1
                assert_noop!(EthereumBeaconClient::submit(origin_of(ALICE.into()), update_header.clone()), snowbridge_pallet_ethereum_client::Error::<Runtime>::SyncCommitteeParticipantsNotSupermajority);
209
1
	        });
210
1
}
211

            
212
#[test]
213
1
fn test_submit_update_in_next_period() {
214
1
    ExtBuilder::default()
215
1
        .with_balances(vec![
216
1
            // Alice gets 10k extra tokens for her mapping deposit
217
1
            (AccountId::from(ALICE), 210_000 * UNIT),
218
1
            (AccountId::from(BOB), 100_000 * UNIT),
219
1
            (AccountId::from(CHARLIE), 100_000 * UNIT),
220
1
            (AccountId::from(DAVE), 100_000 * UNIT),
221
1
        ])
222
1
        .build()
223
1
        .execute_with(|| {
224
1
            // This test submits, assuming current period is n:
225
1
            // 1. A header in the next period n+1 but without having set the next sync committee, which fails
226
1
            // 2. Then submits a proper sync committee update for this period (n), indicating who the next sync committee will be
227
1
            // 3. Then submits an update on the next period (n+1), but without indicating who the next committee is going to be again (in period n+2), which fails
228
1
            // 4. Then submits a header on the next period(n+1), this time indicating who the next sync committee is going to be
229
1
            // 4. Then submits an update on the next period(n+1)
230
1

            
231
1
            // Generate fixtures using a separate thread with 50MB stack size because those structs are huge
232
1
            let (
233
1
                initial_checkpoint,
234
1
                sync_committee_update,
235
1
                next_sync_committee_update,
236
1
                next_update,
237
1
            ) = with_increased_stack_size(50, || {
238
1
                let initial_checkpoint = Box::new(load_checkpoint_update_fixture());
239
1
                let sync_committee_update = Box::new(load_sync_committee_update_fixture());
240
1
                let next_sync_committee_update =
241
1
                    Box::new(load_next_sync_committee_update_fixture());
242
1
                let next_update = Box::new(load_next_finalized_header_update_fixture());
243
1

            
244
1
                (
245
1
                    initial_checkpoint,
246
1
                    sync_committee_update,
247
1
                    next_sync_committee_update,
248
1
                    next_update,
249
1
                )
250
1
            });
251
1

            
252
1
            let initial_period = compute_period(initial_checkpoint.header.slot);
253
1

            
254
1
            assert_ok!(EthereumBeaconClient::force_checkpoint(
255
1
                root_origin(),
256
1
                initial_checkpoint.clone()
257
1
            ));
258

            
259
            // we need an update about the sync committee before we proceed
260
1
            assert_noop!(
261
1
                EthereumBeaconClient::submit(origin_of(ALICE.into()), next_update.clone()),
262
1
                snowbridge_pallet_ethereum_client::Error::<Runtime>::SkippedSyncCommitteePeriod
263
1
            );
264

            
265
1
            assert_ok!(EthereumBeaconClient::submit(
266
1
                origin_of(ALICE.into()),
267
1
                sync_committee_update.clone()
268
1
            ));
269

            
270
            // we need an update about the next sync committee
271
1
            assert_noop!(
272
1
                EthereumBeaconClient::submit(origin_of(ALICE.into()), next_update.clone()),
273
1
                snowbridge_pallet_ethereum_client::Error::<Runtime>::SyncCommitteeUpdateRequired
274
1
            );
275

            
276
1
            assert_ok!(EthereumBeaconClient::submit(
277
1
                origin_of(ALICE.into()),
278
1
                next_sync_committee_update.clone()
279
1
            ));
280

            
281
            // check we are now in period +1
282
1
            let latest_finalized_block_root =
283
1
                snowbridge_pallet_ethereum_client::LatestFinalizedBlockRoot::<Runtime>::get();
284
1
            let last_finalized_state = snowbridge_pallet_ethereum_client::FinalizedBeaconState::<
285
1
                Runtime,
286
1
            >::get(latest_finalized_block_root)
287
1
            .unwrap();
288
1
            let last_synced_period = compute_period(last_finalized_state.slot);
289
1
            assert_eq!(last_synced_period, initial_period + 1);
290

            
291
1
            assert_ok!(EthereumBeaconClient::submit(
292
1
                origin_of(ALICE.into()),
293
1
                next_update.clone()
294
1
            ));
295
1
        });
296
1
}
297

            
298
1
fn with_increased_stack_size<T, F>(stack_size_mb: usize, closure: F) -> T
299
1
where
300
1
    F: FnOnce() -> T + Send + 'static,
301
1
    T: Send + 'static,
302
1
{
303
1
    let builder = std::thread::Builder::new().stack_size(stack_size_mb * 1024 * 1024);
304
1
    let handle = builder.spawn(closure).unwrap();
305
1

            
306
1
    handle.join().unwrap()
307
1
}