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
use {
18
    cumulus_primitives_core::{ParaId, PersistedValidationData},
19
    cumulus_primitives_parachain_inherent::ParachainInherentData,
20
    dancebox_runtime::{AuthorInherent, RuntimeOrigin},
21
    dp_consensus::runtime_decl_for_tanssi_authority_assignment_api::TanssiAuthorityAssignmentApi,
22
    frame_support::{
23
        assert_ok,
24
        traits::{OnFinalize, OnInitialize},
25
    },
26
    nimbus_primitives::{NimbusId, NIMBUS_ENGINE_ID},
27
    pallet_registrar_runtime_api::ContainerChainGenesisData,
28
    parity_scale_codec::{Decode, Encode, MaxEncodedLen},
29
    polkadot_parachain_primitives::primitives::HeadData,
30
    sp_consensus_aura::AURA_ENGINE_ID,
31
    sp_consensus_slots::Slot,
32
    sp_core::Get,
33
    sp_runtime::{traits::Dispatchable, Digest, DigestItem},
34
    sp_std::collections::btree_map::BTreeMap,
35
};
36

            
37
pub use dancebox_runtime::{
38
    AccountId, AssetRate, AuthorNoting, AuthorityAssignment, AuthorityMapping, Balance, Balances,
39
    CollatorAssignment, Configuration, DataPreservers, ForeignAssets, ForeignAssetsCreator,
40
    InflationRewards, Initializer, Invulnerables, MinimumSelfDelegation, ParachainInfo,
41
    PooledStaking, Proxy, ProxyType, Registrar, RewardsPortion, Runtime, RuntimeCall,
42
    ServicesPayment, Session, System, TransactionPayment,
43
};
44

            
45
// TODO: This module copy/pasted for now from dancebox/tests/common/mod.rs, should be extracted and re-used in both places
46

            
47
pub const UNIT: Balance = 1_000_000_000_000;
48

            
49
12
pub fn session_to_block(n: u32) -> u32 {
50
12
    let block_number = dancebox_runtime::Period::get() * n;
51
12

            
52
12
    // Add 1 because the block that emits the NewSession event cannot contain any extrinsics,
53
12
    // so this is the first block of the new session that can actually be used
54
12
    block_number + 1
55
12
}
56

            
57
#[derive(Debug, Clone, Eq, PartialEq)]
58
pub struct RunSummary {
59
    pub author_id: AccountId,
60
    pub inflation: Balance,
61
}
62

            
63
12
pub fn run_to_session(n: u32) {
64
12
    run_to_block(session_to_block(n));
65
12
}
66

            
67
/// Utility function that advances the chain to the desired block number.
68
///
69
/// After this function returns, the current block number will be `n`, and the block will be "open",
70
/// meaning that on_initialize has been executed, but on_finalize has not. To execute on_finalize as
71
/// well, for example to test a runtime api, manually call `end_block` after this, run the test, and
72
/// call `start_block` to ensure that this function keeps working as expected.
73
/// Extrinsics should always be executed before on_finalize.
74
12
pub fn run_to_block(n: u32) -> BTreeMap<u32, RunSummary> {
75
12
    let current_block_number = System::block_number();
76
12
    assert!(
77
12
        current_block_number < n,
78
        "run_to_block called with block {} when current block is {}",
79
        n,
80
        current_block_number
81
    );
82
12
    let mut summaries = BTreeMap::new();
83

            
84
202
    while System::block_number() < n {
85
190
        let summary = run_block();
86
190
        let block_number = System::block_number();
87
190
        summaries.insert(block_number, summary);
88
190
    }
89

            
90
12
    summaries
91
12
}
92

            
93
202
pub fn insert_authorities_and_slot_digests(slot: u64) {
94
202
    let authorities =
95
202
        Runtime::para_id_authorities(ParachainInfo::get()).expect("authorities should be set");
96
202

            
97
202
    let authority: NimbusId = authorities[slot as usize % authorities.len()].clone();
98
202

            
99
202
    let pre_digest = Digest {
100
202
        logs: vec![
101
202
            DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode()),
102
202
            DigestItem::PreRuntime(NIMBUS_ENGINE_ID, authority.encode()),
103
202
        ],
104
202
    };
105
202

            
106
202
    System::reset_events();
107
202
    System::initialize(
108
202
        &(System::block_number() + 1),
109
202
        &System::parent_hash(),
110
202
        &pre_digest,
111
202
    );
112
202
}
113

            
114
// Used to create the next block inherent data
115
#[derive(Clone, Encode, Decode, Default, PartialEq, Debug, scale_info::TypeInfo, MaxEncodedLen)]
116
pub struct MockInherentData {
117
    pub random_seed: Option<[u8; 32]>,
118
}
119

            
120
202
fn take_new_inherent_data() -> Option<MockInherentData> {
121
202
    let data: Option<MockInherentData> =
122
202
        frame_support::storage::unhashed::take(b"__mock_new_inherent_data");
123
202

            
124
202
    data
125
202
}
126

            
127
#[derive(Clone, Encode, Decode, PartialEq, Debug, scale_info::TypeInfo, MaxEncodedLen)]
128
enum RunBlockState {
129
    Start(u32),
130
    End(u32),
131
}
132

            
133
impl RunBlockState {
134
    fn assert_can_advance(&self, new_state: &RunBlockState) {
135
        match self {
136
            RunBlockState::Start(n) => {
137
                assert_eq!(
138
                    new_state,
139
                    &RunBlockState::End(*n),
140
                    "expected a call to end_block({}), but user called {:?}",
141
                    *n,
142
                    new_state
143
                );
144
            }
145
            RunBlockState::End(n) => {
146
                assert_eq!(
147
                    new_state,
148
                    &RunBlockState::Start(*n + 1),
149
                    "expected a call to start_block({}), but user called {:?}",
150
                    *n + 1,
151
                    new_state
152
                )
153
            }
154
        }
155
    }
156
}
157

            
158
392
fn advance_block_state_machine(new_state: RunBlockState) {
159
392
    if frame_support::storage::unhashed::exists(b"__mock_is_xcm_test") {
160
        // Disable this check in XCM tests, because the XCM emulator runs on_initialize and
161
        // on_finalize automatically
162
392
        return;
163
    }
164
    let old_state: RunBlockState =
165
        frame_support::storage::unhashed::get(b"__mock_debug_block_state").unwrap_or(
166
            // Initial state is expecting a call to start() with block number 1, so old state should be
167
            // end of block 0
168
            RunBlockState::End(0),
169
        );
170
    old_state.assert_can_advance(&new_state);
171
    frame_support::storage::unhashed::put(b"__mock_debug_block_state", &new_state);
172
392
}
173

            
174
202
pub fn start_block() -> RunSummary {
175
202
    let block_number = System::block_number();
176
202
    advance_block_state_machine(RunBlockState::Start(block_number + 1));
177
202
    let mut slot = current_slot() + 1;
178
202
    if block_number == 0 {
179
        // Hack to avoid breaking all tests. When the current block is 1, the slot number should be
180
        // 1. But all of our tests assume it will be 0. So use slot number = block_number - 1.
181
        slot = 0;
182
202
    }
183

            
184
202
    let maybe_mock_inherent = take_new_inherent_data();
185

            
186
202
    if let Some(mock_inherent_data) = maybe_mock_inherent {
187
        set_parachain_inherent_data(mock_inherent_data);
188
202
    }
189

            
190
202
    insert_authorities_and_slot_digests(slot);
191
202

            
192
202
    // Initialize the new block
193
202
    CollatorAssignment::on_initialize(System::block_number());
194
202
    Session::on_initialize(System::block_number());
195
202
    Initializer::on_initialize(System::block_number());
196
202
    AuthorInherent::on_initialize(System::block_number());
197
202

            
198
202
    // `Initializer::on_finalize` needs to run at least one to have
199
202
    // author mapping setup.
200
202
    let author_id = current_author();
201
202

            
202
202
    let current_issuance = Balances::total_issuance();
203
202
    InflationRewards::on_initialize(System::block_number());
204
202
    let new_issuance = Balances::total_issuance();
205
202

            
206
202
    frame_support::storage::unhashed::put(
207
202
        &frame_support::storage::storage_prefix(b"AsyncBacking", b"SlotInfo"),
208
202
        &(Slot::from(slot), 1),
209
202
    );
210
202

            
211
202
    pallet_author_inherent::Pallet::<Runtime>::kick_off_authorship_validation(None.into())
212
202
        .expect("author inherent to dispatch correctly");
213
202

            
214
202
    RunSummary {
215
202
        author_id,
216
202
        inflation: new_issuance - current_issuance,
217
202
    }
218
202
}
219

            
220
190
pub fn end_block() {
221
190
    let block_number = System::block_number();
222
190
    advance_block_state_machine(RunBlockState::End(block_number));
223
190
    // Finalize the block
224
190
    CollatorAssignment::on_finalize(System::block_number());
225
190
    Session::on_finalize(System::block_number());
226
190
    Initializer::on_finalize(System::block_number());
227
190
    AuthorInherent::on_finalize(System::block_number());
228
190
    TransactionPayment::on_finalize(System::block_number());
229
190
}
230

            
231
190
pub fn run_block() -> RunSummary {
232
190
    end_block();
233
190

            
234
190
    start_block()
235
190
}
236

            
237
/// Mock the inherent that sets validation data in ParachainSystem, which
238
/// contains the `relay_chain_block_number`, which is used in `collator-assignment` as a
239
/// source of randomness.
240
pub fn set_parachain_inherent_data(mock_inherent_data: MockInherentData) {
241
    use {
242
        cumulus_primitives_core::relay_chain::well_known_keys,
243
        cumulus_test_relay_sproof_builder::RelayStateSproofBuilder,
244
    };
245

            
246
    let relay_sproof = RelayStateSproofBuilder {
247
        para_id: 100u32.into(),
248
        included_para_head: Some(HeadData(vec![1, 2, 3])),
249
        current_slot: (current_slot()).into(),
250
        additional_key_values: if mock_inherent_data.random_seed.is_some() {
251
            vec![(
252
                well_known_keys::CURRENT_BLOCK_RANDOMNESS.to_vec(),
253
                Some(mock_inherent_data.random_seed).encode(),
254
            )]
255
        } else {
256
            vec![]
257
        },
258
        ..Default::default()
259
    };
260

            
261
    let (relay_parent_storage_root, relay_chain_state) = relay_sproof.into_state_root_and_proof();
262
    let vfp = PersistedValidationData {
263
        relay_parent_number: 1u32,
264
        relay_parent_storage_root,
265
        ..Default::default()
266
    };
267
    let parachain_inherent_data = ParachainInherentData {
268
        validation_data: vfp,
269
        relay_chain_state,
270
        downward_messages: Default::default(),
271
        horizontal_messages: Default::default(),
272
    };
273

            
274
    // Delete existing flag to avoid error
275
    // 'ValidationData must be updated only once in a block'
276
    // TODO: this is a hack
277
    frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix(
278
        b"ParachainSystem",
279
        b"ValidationData",
280
    ));
281

            
282
    assert_ok!(RuntimeCall::ParachainSystem(
283
        cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data {
284
            data: parachain_inherent_data
285
        }
286
    )
287
    .dispatch(inherent_origin()));
288
}
289

            
290
pub fn inherent_origin() -> <Runtime as frame_system::Config>::RuntimeOrigin {
291
    <Runtime as frame_system::Config>::RuntimeOrigin::none()
292
}
293

            
294
12
pub fn empty_genesis_data() -> ContainerChainGenesisData {
295
12
    ContainerChainGenesisData {
296
12
        storage: Default::default(),
297
12
        name: Default::default(),
298
12
        id: Default::default(),
299
12
        fork_id: Default::default(),
300
12
        extensions: Default::default(),
301
12
        properties: Default::default(),
302
12
    }
303
12
}
304

            
305
202
pub fn current_slot() -> u64 {
306
202
    u64::from(
307
202
        pallet_async_backing::SlotInfo::<Runtime>::get()
308
202
            .unwrap_or_default()
309
202
            .0,
310
202
    )
311
202
}
312

            
313
202
pub fn current_author() -> AccountId {
314
202
    let current_session = Session::current_index();
315
202
    let mapping =
316
202
        pallet_authority_mapping::Pallet::<Runtime>::authority_id_mapping(current_session)
317
202
            .expect("there is a mapping for the current session");
318
202

            
319
202
    let author = pallet_author_inherent::Author::<Runtime>::get()
320
202
        .expect("there should be a registered author");
321
202

            
322
202
    mapping
323
202
        .get(&author)
324
202
        .expect("there is a mapping for the current author")
325
202
        .clone()
326
202
}
327

            
328
12
pub fn set_dummy_boot_node(para_manager: RuntimeOrigin, para_id: ParaId) {
329
    use {
330
        pallet_data_preservers::{ParaIdsFilter, Profile, ProfileMode},
331
        tp_data_preservers_common::{AssignerExtra, ProviderRequest},
332
    };
333

            
334
12
    let profile = Profile {
335
12
        url:
336
12
            b"/ip4/127.0.0.1/tcp/33049/ws/p2p/12D3KooWHVMhQDHBpj9vQmssgyfspYecgV6e3hH1dQVDUkUbCYC9"
337
12
                .to_vec()
338
12
                .try_into()
339
12
                .expect("to fit in BoundedVec"),
340
12
        para_ids: ParaIdsFilter::AnyParaId,
341
12
        mode: ProfileMode::Bootnode,
342
12
        assignment_request: ProviderRequest::Free,
343
12
    };
344
12

            
345
12
    let profile_id = pallet_data_preservers::NextProfileId::<Runtime>::get();
346
12
    let profile_owner = AccountId::new([1u8; 32]);
347
12
    DataPreservers::force_create_profile(RuntimeOrigin::root(), profile, profile_owner)
348
12
        .expect("profile create to succeed");
349
12

            
350
12
    DataPreservers::start_assignment(para_manager, profile_id, para_id, AssignerExtra::Free)
351
12
        .expect("assignement to work");
352
12

            
353
12
    assert!(
354
12
        pallet_data_preservers::Assignments::<Runtime>::get(para_id).contains(&profile_id),
355
        "profile should be correctly assigned"
356
    );
357
12
}