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_electra::*},
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 = Box::new(
40
1
                snowbridge_pallet_ethereum_client::mock_electra::load_checkpoint_update_fixture(),
41
1
            );
42
1
            assert_ok!(EthereumBeaconClient::force_checkpoint(
43
1
                root_origin(),
44
1
                checkpoint.clone()
45
1
            ));
46
            // assert checkpoint has updated storages
47
1
            assert_eq!(
48
1
                EthereumBeaconClient::initial_checkpoint_root(),
49
1
                checkpoint.header.hash_tree_root().unwrap()
50
1
            );
51
            // sync committee is correct
52
1
            let unwrap_keys: Vec<snowbridge_beacon_primitives::PublicKey> =
53
1
                snowbridge_pallet_ethereum_client::CurrentSyncCommittee::<Runtime>::get()
54
1
                    .pubkeys
55
1
                    .iter()
56
512
                    .map(|key| {
57
512
                        let unwrapped = key.as_bytes();
58
512
                        let pubkey: snowbridge_beacon_primitives::PublicKey = unwrapped.into();
59
512
                        pubkey
60
512
                    })
61
1
                    .collect();
62
1
            assert_eq!(
63
1
                unwrap_keys,
64
1
                checkpoint.current_sync_committee.pubkeys.to_vec()
65
1
            );
66
1
        })
67
1
}
68

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

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

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

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

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

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

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

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

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

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

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

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

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

            
254
1
            let initial_period = compute_period(initial_checkpoint.header.slot);
255
1

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

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

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

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

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

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

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

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

            
308
1
    handle.join().unwrap()
309
1
}