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
    crate::{
19
        collators as collator_util, collators::ClaimMode,
20
        consensus_orchestrator::RetrieveAuthoritiesFromOrchestrator, OrchestratorAuraWorkerAuxData,
21
    },
22
    cumulus_client_collator::{
23
        relay_chain_driven::CollationRequest, service::ServiceInterface as CollatorServiceInterface,
24
    },
25
    cumulus_client_consensus_proposer::ProposerInterface,
26
    cumulus_primitives_core::{
27
        relay_chain::{BlockId as RBlockId, Hash as PHash, OccupiedCoreAssumption},
28
        PersistedValidationData,
29
    },
30
    cumulus_relay_chain_interface::RelayChainInterface,
31
    futures::{channel::mpsc::Receiver, prelude::*},
32
    parity_scale_codec::{Codec, Decode},
33
    polkadot_node_primitives::CollationResult,
34
    polkadot_overseer::Handle as OverseerHandle,
35
    polkadot_primitives::{CollatorPair, Id as ParaId},
36
    sc_client_api::{backend::AuxStore, BlockBackend, BlockOf},
37
    sc_consensus::BlockImport,
38
    sc_consensus_slots::InherentDataProviderExt,
39
    sp_api::ProvideRuntimeApi,
40
    sp_application_crypto::AppPublic,
41
    sp_blockchain::HeaderBackend,
42
    sp_consensus::SyncOracle,
43
    sp_consensus_aura::SlotDuration,
44
    sp_core::crypto::Pair,
45
    sp_inherents::CreateInherentDataProviders,
46
    sp_keystore::KeystorePtr,
47
    sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member},
48
    std::{convert::TryFrom, sync::Arc, time::Duration},
49
};
50

            
51
/// Parameters for [`run`].
52
pub struct Params<BI, CIDP, Client, RClient, SO, Proposer, CS, GOH> {
53
    pub create_inherent_data_providers: CIDP,
54
    pub get_orchestrator_aux_data: GOH,
55
    pub block_import: BI,
56
    pub para_client: Arc<Client>,
57
    pub relay_client: RClient,
58
    pub sync_oracle: SO,
59
    pub keystore: KeystorePtr,
60
    pub collator_key: CollatorPair,
61
    pub para_id: ParaId,
62
    pub overseer_handle: OverseerHandle,
63
    pub slot_duration: SlotDuration,
64
    pub relay_chain_slot_duration: Duration,
65
    pub proposer: Proposer,
66
    pub collator_service: CS,
67
    pub authoring_duration: Duration,
68
    pub force_authoring: bool,
69
    pub collation_request_receiver: Option<Receiver<CollationRequest>>,
70
}
71

            
72
/// Run tanssi Aura consensus as a relay-chain-driven collator.
73
pub async fn run<Block, P, BI, CIDP, Client, RClient, SO, Proposer, CS, GOH>(
74
    params: Params<BI, CIDP, Client, RClient, SO, Proposer, CS, GOH>,
75
) where
76
    Block: BlockT + Send,
77
    Client: ProvideRuntimeApi<Block>
78
        + BlockOf
79
        + AuxStore
80
        + HeaderBackend<Block>
81
        + BlockBackend<Block>
82
        + Send
83
        + Sync
84
        + 'static,
85
    RClient: RelayChainInterface + Send + Clone + 'static,
86
    CIDP: CreateInherentDataProviders<Block, (PHash, PersistedValidationData)>
87
        + Send
88
        + 'static
89
        + Clone,
90
    CIDP::InherentDataProviders: Send + InherentDataProviderExt,
91
    BI: BlockImport<Block> + Send + Sync + 'static,
92
    SO: SyncOracle + Send + Sync + Clone + 'static,
93
    Proposer: ProposerInterface<Block> + Send + Sync + 'static,
94
    CS: CollatorServiceInterface<Block> + Send + Sync + 'static,
95
    P: Pair + Sync + Send + 'static,
96
    P::Public: AppPublic + Member + Codec,
97
    P::Signature: TryFrom<Vec<u8>> + Member + Codec,
98
    GOH: RetrieveAuthoritiesFromOrchestrator<
99
            Block,
100
            (PHash, PersistedValidationData),
101
            OrchestratorAuraWorkerAuxData<P>,
102
        >
103
        + 'static
104
        + Sync
105
        + Send,
106
{
107
    let mut collation_requests = match params.collation_request_receiver {
108
        Some(receiver) => receiver,
109
        None => {
110
            cumulus_client_collator::relay_chain_driven::init(
111
                params.collator_key,
112
                params.para_id,
113
                params.overseer_handle,
114
            )
115
            .await
116
        }
117
    };
118

            
119
    let mut collator = {
120
        let params = collator_util::Params {
121
            create_inherent_data_providers: params.create_inherent_data_providers.clone(),
122
            block_import: params.block_import,
123
            relay_client: params.relay_client.clone(),
124
            keystore: params.keystore.clone(),
125
            para_id: params.para_id,
126
            proposer: params.proposer,
127
            collator_service: params.collator_service,
128
        };
129

            
130
        collator_util::Collator::<Block, P, _, _, _, _, _>::new(params)
131
    };
132

            
133
    let mut last_processed_slot = 0;
134

            
135
    while let Some(request) = collation_requests.next().await {
136
        macro_rules! reject_with_error {
137
				($err:expr) => {{
138
					request.complete(None);
139
					tracing::error!(target: crate::LOG_TARGET, err = ?{ $err });
140
					continue;
141
				}};
142
			}
143

            
144
        macro_rules! try_request {
145
            ($x:expr) => {{
146
                match $x {
147
                    Ok(x) => x,
148
                    Err(e) => reject_with_error!(e),
149
                }
150
            }};
151
        }
152

            
153
        let validation_data = request.persisted_validation_data();
154

            
155
        let parent_header = try_request!(Block::Header::decode(
156
            &mut &validation_data.parent_head.0[..]
157
        ));
158

            
159
        let parent_hash = parent_header.hash();
160

            
161
        // Evaluate whether we can build on top
162
        // The requirement is that the parent_hash is the last included block in the relay
163
        let can_build = can_build_upon_included::<Block, _>(
164
            parent_hash,
165
            &collator.relay_client,
166
            params.para_id,
167
            *request.relay_parent(),
168
        )
169
        .await;
170
        if !can_build {
171
            continue;
172
        }
173

            
174
        // Check whether we can build upon this block
175
        if !collator
176
            .collator_service()
177
            .check_block_status(parent_hash, &parent_header)
178
        {
179
            continue;
180
        }
181

            
182
        let relay_parent_header = match params
183
            .relay_client
184
            .header(RBlockId::hash(*request.relay_parent()))
185
            .await
186
        {
187
            Err(e) => reject_with_error!(e),
188
            Ok(None) => continue, // sanity: would be inconsistent to get `None` here
189
            Ok(Some(h)) => h,
190
        };
191

            
192
        // Retrieve authorities that are able to produce the block
193
        let authorities = match params
194
            .get_orchestrator_aux_data
195
            .retrieve_authorities_from_orchestrator(
196
                parent_hash,
197
                (relay_parent_header.hash(), validation_data.clone()),
198
            )
199
            .await
200
        {
201
            Err(e) => reject_with_error!(e),
202
            Ok(h) => h,
203
        };
204

            
205
        let inherent_providers = match params
206
            .create_inherent_data_providers
207
            .create_inherent_data_providers(
208
                parent_hash,
209
                (*request.relay_parent(), validation_data.clone()),
210
            )
211
            .await
212
        {
213
            Err(e) => reject_with_error!(e),
214
            Ok(h) => h,
215
        };
216

            
217
        let claim_mode = if params.force_authoring {
218
            ClaimMode::ForceAuthoring
219
        } else {
220
            ClaimMode::NormalAuthoring
221
        };
222

            
223
        let mut claim = match collator_util::tanssi_claim_slot::<P, Block>(
224
            authorities,
225
            &parent_header,
226
            inherent_providers.slot(),
227
            claim_mode,
228
            &params.keystore,
229
        ) {
230
            None => continue,
231
            Some(h) => h,
232
        };
233

            
234
        // With async backing this function will be called every relay chain block.
235
        //
236
        // Most parachains currently run with 12 seconds slots and thus, they would try to
237
        // produce multiple blocks per slot which very likely would fail on chain. Thus, we have
238
        // this "hack" to only produce on block per slot.
239
        //
240
        // With https://github.com/paritytech/polkadot-sdk/issues/3168 this implementation will be
241
        // obsolete and also the underlying issue will be fixed.
242
        if last_processed_slot >= *claim.slot() {
243
            continue;
244
        }
245

            
246
        let (parachain_inherent_data, other_inherent_data) = try_request!(
247
            collator
248
                .create_inherent_data(*request.relay_parent(), validation_data, parent_hash, None,)
249
                .await
250
        );
251

            
252
        let maybe_collation = try_request!(
253
            collator
254
                .collate(
255
                    &parent_header,
256
                    &mut claim,
257
                    None,
258
                    (parachain_inherent_data, other_inherent_data),
259
                    params.authoring_duration,
260
                    // Set the block limit to 50% of the maximum PoV size.
261
                    //
262
                    // TODO: If we got benchmarking that includes the proof size,
263
                    // we should be able to use the maximum pov size.
264
                    (validation_data.max_pov_size / 2) as usize,
265
                )
266
                .await
267
        );
268

            
269
        if let Some((collation, _, post_hash)) = maybe_collation {
270
            let result_sender = Some(collator.collator_service().announce_with_barrier(post_hash));
271
            request.complete(Some(CollationResult {
272
                collation,
273
                result_sender,
274
            }));
275
        } else {
276
            request.complete(None);
277
            tracing::debug!(target: crate::LOG_TARGET, "No block proposal");
278
        }
279
        last_processed_slot = *claim.slot();
280
    }
281
}
282

            
283
// Checks whether we can build upon the last included block
284
// Essentially checks that the latest head we are trying to build
285
// is the one included in the relay
286
async fn can_build_upon_included<Block: BlockT, RClient>(
287
    parent_hash: <Block as BlockT>::Hash,
288
    relay_client: &RClient,
289
    para_id: ParaId,
290
    relay_parent: PHash,
291
) -> bool
292
where
293
    RClient: RelayChainInterface + Send + Clone + 'static,
294
{
295
    let included_header = relay_client
296
        .persisted_validation_data(relay_parent, para_id, OccupiedCoreAssumption::TimedOut)
297
        .await;
298

            
299
    if let Ok(Some(included_header)) = included_header {
300
        let decoded = Block::Header::decode(&mut &included_header.parent_head.0[..]).ok();
301
        if let Some(decoded_header) = decoded {
302
            let included_hash = decoded_header.hash();
303
            if parent_hash == included_hash {
304
                return true;
305
            }
306
        }
307
    }
308
    false
309
}