|
20 | 20 | //! This is suitable for a testing environment. |
21 | 21 |
|
22 | 22 | use futures::prelude::*; |
| 23 | +use futures_timer::Delay; |
23 | 24 | use prometheus_endpoint::Registry; |
24 | | -use sc_client_api::backend::{Backend as ClientBackend, Finalizer}; |
| 25 | +use sc_client_api::{ |
| 26 | + backend::{Backend as ClientBackend, Finalizer}, |
| 27 | + client::BlockchainEvents, |
| 28 | +}; |
25 | 29 | use sc_consensus::{ |
26 | 30 | block_import::{BlockImport, BlockImportParams, ForkChoiceStrategy}, |
27 | 31 | import_queue::{BasicQueue, BoxBlockImport, Verifier}, |
28 | 32 | }; |
29 | 33 | use sp_blockchain::HeaderBackend; |
30 | 34 | use sp_consensus::{Environment, Proposer, SelectChain}; |
| 35 | +use sp_core::traits::SpawnNamed; |
31 | 36 | use sp_inherents::CreateInherentDataProviders; |
32 | 37 | use sp_runtime::{traits::Block as BlockT, ConsensusEngineId}; |
33 | | -use std::{marker::PhantomData, sync::Arc}; |
| 38 | +use std::{marker::PhantomData, sync::Arc, time::Duration}; |
34 | 39 |
|
35 | 40 | mod error; |
36 | 41 | mod finalize_block; |
|
84 | 89 |
|
85 | 90 | /// Params required to start the instant sealing authorship task. |
86 | 91 | pub struct ManualSealParams<B: BlockT, BI, E, C: ProvideRuntimeApi<B>, TP, SC, CS, CIDP, P> { |
87 | | - /// Block import instance for well. importing blocks. |
| 92 | + /// Block import instance. |
88 | 93 | pub block_import: BI, |
89 | 94 |
|
90 | 95 | /// The environment we are producing blocks for. |
@@ -136,7 +141,19 @@ pub struct InstantSealParams<B: BlockT, BI, E, C: ProvideRuntimeApi<B>, TP, SC, |
136 | 141 | pub create_inherent_data_providers: CIDP, |
137 | 142 | } |
138 | 143 |
|
139 | | -/// Creates the background authorship task for the manual seal engine. |
| 144 | +/// Params required to start the delayed finalization task. |
| 145 | +pub struct DelayedFinalizeParams<C, S> { |
| 146 | + /// Block import instance. |
| 147 | + pub client: Arc<C>, |
| 148 | + |
| 149 | + /// Handle for spawning delayed finalization tasks. |
| 150 | + pub spawn_handle: S, |
| 151 | + |
| 152 | + /// The delay in seconds before a block is finalized. |
| 153 | + pub delay_sec: u64, |
| 154 | +} |
| 155 | + |
| 156 | +/// Creates the background authorship task for the manually seal engine. |
140 | 157 | pub async fn run_manual_seal<B, BI, CB, E, C, TP, SC, CS, CIDP, P>( |
141 | 158 | ManualSealParams { |
142 | 159 | mut block_import, |
@@ -303,6 +320,44 @@ pub async fn run_instant_seal_and_finalize<B, BI, CB, E, C, TP, SC, CIDP, P>( |
303 | 320 | .await |
304 | 321 | } |
305 | 322 |
|
| 323 | +/// Creates a future for delayed finalization of manual sealed blocks. |
| 324 | +/// |
| 325 | +/// The future needs to be spawned in the background alongside the |
| 326 | +/// [`run_manual_seal`]/[`run_instant_seal`] future. It is required that |
| 327 | +/// [`EngineCommand::SealNewBlock`] is send with `finalize = false` to not finalize blocks directly |
| 328 | +/// after building them. This also means that delayed finality can not be used with |
| 329 | +/// [`run_instant_seal_and_finalize`]. |
| 330 | +pub async fn run_delayed_finalize<B, CB, C, S>( |
| 331 | + DelayedFinalizeParams { client, spawn_handle, delay_sec }: DelayedFinalizeParams<C, S>, |
| 332 | +) where |
| 333 | + B: BlockT + 'static, |
| 334 | + CB: ClientBackend<B> + 'static, |
| 335 | + C: HeaderBackend<B> + Finalizer<B, CB> + ProvideRuntimeApi<B> + BlockchainEvents<B> + 'static, |
| 336 | + S: SpawnNamed, |
| 337 | +{ |
| 338 | + let mut block_import_stream = client.import_notification_stream(); |
| 339 | + |
| 340 | + while let Some(notification) = block_import_stream.next().await { |
| 341 | + let delay = Delay::new(Duration::from_secs(delay_sec)); |
| 342 | + let cloned_client = client.clone(); |
| 343 | + spawn_handle.spawn( |
| 344 | + "delayed-finalize", |
| 345 | + None, |
| 346 | + Box::pin(async move { |
| 347 | + delay.await; |
| 348 | + finalize_block(FinalizeBlockParams { |
| 349 | + hash: notification.hash, |
| 350 | + sender: None, |
| 351 | + justification: None, |
| 352 | + finalizer: cloned_client, |
| 353 | + _phantom: PhantomData, |
| 354 | + }) |
| 355 | + .await |
| 356 | + }), |
| 357 | + ); |
| 358 | + } |
| 359 | +} |
| 360 | + |
306 | 361 | #[cfg(test)] |
307 | 362 | mod tests { |
308 | 363 | use super::*; |
@@ -428,6 +483,101 @@ mod tests { |
428 | 483 | assert_eq!(client.header(created_block.hash).unwrap().unwrap().number, 1) |
429 | 484 | } |
430 | 485 |
|
| 486 | + #[tokio::test] |
| 487 | + async fn instant_seal_delayed_finalize() { |
| 488 | + let builder = TestClientBuilder::new(); |
| 489 | + let (client, select_chain) = builder.build_with_longest_chain(); |
| 490 | + let client = Arc::new(client); |
| 491 | + let spawner = sp_core::testing::TaskExecutor::new(); |
| 492 | + let genesis_hash = client.info().genesis_hash; |
| 493 | + let pool = Arc::new(BasicPool::with_revalidation_type( |
| 494 | + Options::default(), |
| 495 | + true.into(), |
| 496 | + api(), |
| 497 | + None, |
| 498 | + RevalidationType::Full, |
| 499 | + spawner.clone(), |
| 500 | + 0, |
| 501 | + genesis_hash, |
| 502 | + genesis_hash, |
| 503 | + )); |
| 504 | + let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None); |
| 505 | + // this test checks that blocks are created as soon as transactions are imported into the |
| 506 | + // pool. |
| 507 | + let (sender, receiver) = futures::channel::oneshot::channel(); |
| 508 | + let mut sender = Arc::new(Some(sender)); |
| 509 | + let commands_stream = |
| 510 | + pool.pool().validated_pool().import_notification_stream().map(move |_| { |
| 511 | + // we're only going to submit one tx so this fn will only be called once. |
| 512 | + let mut_sender = Arc::get_mut(&mut sender).unwrap(); |
| 513 | + let sender = std::mem::take(mut_sender); |
| 514 | + EngineCommand::SealNewBlock { |
| 515 | + create_empty: false, |
| 516 | + // set to `false`, expecting to be finalized by delayed finalize |
| 517 | + finalize: false, |
| 518 | + parent_hash: None, |
| 519 | + sender, |
| 520 | + } |
| 521 | + }); |
| 522 | + |
| 523 | + let future_instant_seal = run_manual_seal(ManualSealParams { |
| 524 | + block_import: client.clone(), |
| 525 | + commands_stream, |
| 526 | + env, |
| 527 | + client: client.clone(), |
| 528 | + pool: pool.clone(), |
| 529 | + select_chain, |
| 530 | + create_inherent_data_providers: |_, _| async { Ok(()) }, |
| 531 | + consensus_data_provider: None, |
| 532 | + }); |
| 533 | + std::thread::spawn(|| { |
| 534 | + let rt = tokio::runtime::Runtime::new().unwrap(); |
| 535 | + // spawn the background authorship task |
| 536 | + rt.block_on(future_instant_seal); |
| 537 | + }); |
| 538 | + |
| 539 | + let delay_sec = 5; |
| 540 | + let future_delayed_finalize = run_delayed_finalize(DelayedFinalizeParams { |
| 541 | + client: client.clone(), |
| 542 | + delay_sec, |
| 543 | + spawn_handle: spawner, |
| 544 | + }); |
| 545 | + std::thread::spawn(|| { |
| 546 | + let rt = tokio::runtime::Runtime::new().unwrap(); |
| 547 | + // spawn the background authorship task |
| 548 | + rt.block_on(future_delayed_finalize); |
| 549 | + }); |
| 550 | + |
| 551 | + let mut finality_stream = client.finality_notification_stream(); |
| 552 | + // submit a transaction to pool. |
| 553 | + let result = pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Alice, 0)).await; |
| 554 | + // assert that it was successfully imported |
| 555 | + assert!(result.is_ok()); |
| 556 | + // assert that the background task returns ok |
| 557 | + let created_block = receiver.await.unwrap().unwrap(); |
| 558 | + assert_eq!( |
| 559 | + created_block, |
| 560 | + CreatedBlock { |
| 561 | + hash: created_block.hash, |
| 562 | + aux: ImportedAux { |
| 563 | + header_only: false, |
| 564 | + clear_justification_requests: false, |
| 565 | + needs_justification: false, |
| 566 | + bad_justification: false, |
| 567 | + is_new_best: true, |
| 568 | + } |
| 569 | + } |
| 570 | + ); |
| 571 | + // assert that there's a new block in the db. |
| 572 | + assert!(client.header(created_block.hash).unwrap().is_some()); |
| 573 | + assert_eq!(client.header(created_block.hash).unwrap().unwrap().number, 1); |
| 574 | + |
| 575 | + assert_eq!(client.info().finalized_hash, client.info().genesis_hash); |
| 576 | + |
| 577 | + let finalized = finality_stream.select_next_some().await; |
| 578 | + assert_eq!(finalized.hash, created_block.hash); |
| 579 | + } |
| 580 | + |
431 | 581 | #[tokio::test] |
432 | 582 | async fn manual_seal_and_finalization() { |
433 | 583 | let builder = TestClientBuilder::new(); |
|
0 commit comments