Skip to content

Commit dd2ed54

Browse files
authored
Merge pull request #29 from use-ink/daan/fix-instant_seal
fix: instant seal
2 parents 5b8e310 + 0a1fbfd commit dd2ed54

11 files changed

Lines changed: 358 additions & 37 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ cumulus-client-consensus-proposer ={ git = "https://github.com/paritytech/polkad
122122
cumulus-client-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1" }
123123
cumulus-client-consensus-common ={ git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1" }
124124
cumulus-client-service = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1" }
125+
cumulus-client-parachain-inherent = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1", default-features = false }
125126
cumulus-pallet-aura-ext = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1", default-features = false }
126127
cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1", default-features = false }
127128
cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1", default-features = false }
@@ -136,4 +137,4 @@ cumulus-primitives-utility = { git = "https://github.com/paritytech/polkadot-sdk
136137
cumulus-relay-chain-interface = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1" }
137138
pallet-collator-selection = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1", default-features = false }
138139
parachain-info = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1", package = "staging-parachain-info", default-features = false }
139-
parachains-common = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1", default-features = false }
140+
parachains-common = { git = "https://github.com/paritytech/polkadot-sdk", rev = "9136565addc23a552f6960a7581f13c8dfc651f1", default-features = false }

node/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ cumulus-client-collator = { workspace = true }
8181
cumulus-client-consensus-aura = { workspace = true }
8282
cumulus-client-consensus-common = { workspace = true }
8383
cumulus-client-consensus-proposer = { workspace = true }
84+
cumulus-client-parachain-inherent = { workspace = true }
8485
cumulus-client-service = { workspace = true }
8586
cumulus-primitives-core = { workspace = true }
8687
cumulus-primitives-parachain-inherent = { workspace = true }

node/src/cli.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,23 @@ pub struct Cli {
8080
/// The number of seconds to delay before finalizing blocks.
8181
#[arg(long, default_value_t = 1)]
8282
pub finalize_delay_sec: u8,
83+
84+
/// Start a dev node with instant seal.
85+
///
86+
/// This is a dev option that enables instant sealing, meaning blocks are produced
87+
/// immediately when transactions are received, rather than at fixed intervals.
88+
/// Using this option won't result in starting or connecting to a parachain network.
89+
/// The resulting node will work on its own, running the wasm blob and producing blocks
90+
/// instantly upon receiving transactions.
91+
#[arg(long)]
92+
pub instant_seal: bool,
93+
}
94+
95+
impl Cli {
96+
/// Returns true if instant seal mode is enabled.
97+
pub fn is_instant_seal(&self) -> bool {
98+
self.instant_seal
99+
}
83100
}
84101

85102
#[derive(Debug)]

node/src/command.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ pub fn run() -> Result<()> {
222222
let collator_options = cli.run.collator_options();
223223

224224
runner.run_node_until_exit(|config| async move {
225+
// Check for solochain dev mode first (chain spec "Development")
226+
// The solochain runtime doesn't have Aura, so it needs separate handling
225227
if config.chain_spec.name() == "Development" {
226228
return dev::new_full::<sc_network::NetworkWorker<_, _>>(
227229
config,
@@ -231,6 +233,16 @@ pub fn run() -> Result<()> {
231233
.map_err(sc_cli::Error::Service);
232234
}
233235

236+
// Check for instant seal mode for parachain (requires parachain runtime with Aura)
237+
if cli.is_instant_seal() {
238+
return crate::service::start_dev_parachain_node(
239+
config,
240+
cli.finalize_delay_sec.into(),
241+
)
242+
.await
243+
.map_err(sc_cli::Error::Service);
244+
}
245+
234246
let hwbench = (!cli.no_hardware_benchmarks)
235247
.then_some(config.database.path().map(|database_path| {
236248
let _ = std::fs::create_dir_all(database_path);

node/src/service.rs

Lines changed: 271 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use cumulus_client_cli::CollatorOptions;
1515
use cumulus_client_collator::service::CollatorService;
1616
use cumulus_client_consensus_aura::collators::lookahead::{self as aura, Params as AuraParams};
1717
use cumulus_client_consensus_common::ParachainBlockImport as TParachainBlockImport;
18+
use cumulus_client_parachain_inherent::MockValidationDataInherentDataProvider;
1819
use cumulus_client_service::{
1920
build_network, build_relay_chain_interface, prepare_node_config, start_relay_chain_tasks,
2021
BuildNetworkParams, CollatorSybilResistance, DARecoveryProfile, ParachainHostFunctions,
@@ -25,18 +26,24 @@ use cumulus_primitives_core::{
2526
ParaId,
2627
};
2728
use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface};
29+
use polkadot_primitives::{HeadData, UpgradeGoAhead};
2830

2931
// Substrate Imports
32+
use codec::Encode;
33+
use cumulus_primitives_core::CollectCollationInfo;
3034
use frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE;
3135
use prometheus_endpoint::Registry;
3236
use sc_client_api::Backend;
33-
use sc_consensus::ImportQueue;
37+
use sc_consensus::{ImportQueue, LongestChain};
38+
use sc_consensus_manual_seal::consensus::aura::AuraConsensusDataProvider;
3439
use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY};
3540
use sc_network::{NetworkBackend, NetworkBlock};
3641
use sc_service::{Configuration, PartialComponents, TFullBackend, TFullClient, TaskManager};
3742
use sc_telemetry::{Telemetry, TelemetryHandle, TelemetryWorker, TelemetryWorkerHandle};
3843
use sc_transaction_pool_api::OffchainTransactionPoolFactory;
44+
use sp_api::ProvideRuntimeApi;
3945
use sp_keystore::KeystorePtr;
46+
use sp_runtime::traits::UniqueSaturatedInto;
4047

4148
type ParachainExecutor = WasmExecutor<ParachainHostFunctions>;
4249

@@ -406,3 +413,266 @@ pub async fn start_parachain_node(
406413

407414
Ok((task_manager, client))
408415
}
416+
417+
/// Creates the inherent data providers for dev mode (instant/manual seal).
418+
///
419+
/// This function sets up the timestamp and parachain validation data providers
420+
/// required for dev seal block production in a parachain environment without a relay chain.
421+
fn create_dev_inherent_data_providers(
422+
client: Arc<ParachainClient>,
423+
para_id: ParaId,
424+
slot_duration: sp_consensus_aura::SlotDuration,
425+
) -> impl Fn(
426+
Hash,
427+
(),
428+
) -> std::future::Ready<
429+
Result<
430+
(sp_timestamp::InherentDataProvider, MockValidationDataInherentDataProvider<()>),
431+
Box<dyn std::error::Error + Send + Sync>,
432+
>,
433+
> + Send
434+
+ Sync {
435+
move |parent_hash: Hash, ()| {
436+
let current_para_head = client
437+
.header(parent_hash)
438+
.expect("Header lookup should succeed")
439+
.expect("Header passed in as parent should be present in backend.");
440+
441+
// Check if there's pending validation code that needs upgrade signal
442+
let should_send_go_ahead = client
443+
.runtime_api()
444+
.collect_collation_info(parent_hash, &current_para_head)
445+
.map(|info| info.new_validation_code.is_some())
446+
.unwrap_or_default();
447+
448+
let current_para_block_head = Some(HeadData(current_para_head.encode()));
449+
let current_block_number =
450+
UniqueSaturatedInto::<u32>::unique_saturated_into(current_para_head.number) + 1;
451+
452+
// Create mock parachain validation data
453+
let mocked_parachain = MockValidationDataInherentDataProvider::<()> {
454+
current_para_block: current_block_number,
455+
para_id,
456+
current_para_block_head,
457+
relay_blocks_per_para_block: 1,
458+
para_blocks_per_relay_epoch: 10,
459+
upgrade_go_ahead: should_send_go_ahead.then(|| {
460+
log::info!("Detected pending validation code, sending go-ahead signal.");
461+
UpgradeGoAhead::GoAhead
462+
}),
463+
..Default::default()
464+
};
465+
466+
// Create timestamp aligned to Aura slot timing
467+
let timestamp = sp_timestamp::InherentDataProvider::new(
468+
(slot_duration.as_millis() * current_block_number as u64).into(),
469+
);
470+
471+
std::future::ready(Ok((timestamp, mocked_parachain)))
472+
}
473+
}
474+
475+
/// Start a parachain node in dev mode with instant seal.
476+
///
477+
/// This allows the parachain to run standalone without connecting to a relay chain,
478+
/// useful for development and testing.
479+
pub async fn start_dev_parachain_node(
480+
mut config: Configuration,
481+
finalize_delay_sec: u64,
482+
) -> sc_service::error::Result<TaskManager> {
483+
// Since this is a dev node, prevent it from connecting to peers
484+
config.network.default_peers_set.in_peers = 0;
485+
config.network.default_peers_set.out_peers = 0;
486+
487+
// Build client, backend, and other components manually
488+
// We can't use new_partial() because it creates an Aura import queue
489+
let telemetry = config
490+
.telemetry_endpoints
491+
.clone()
492+
.filter(|x| !x.is_empty())
493+
.map(|endpoints| -> Result<_, sc_telemetry::Error> {
494+
let worker = TelemetryWorker::new(16)?;
495+
let telemetry = worker.handle().new_telemetry(endpoints);
496+
Ok((worker, telemetry))
497+
})
498+
.transpose()?;
499+
500+
let heap_pages = config
501+
.executor
502+
.default_heap_pages
503+
.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static { extra_pages: h as _ });
504+
505+
let executor = ParachainExecutor::builder()
506+
.with_execution_method(config.executor.wasm_method)
507+
.with_onchain_heap_alloc_strategy(heap_pages)
508+
.with_offchain_heap_alloc_strategy(heap_pages)
509+
.with_max_runtime_instances(config.executor.max_runtime_instances)
510+
.with_runtime_cache_size(config.executor.runtime_cache_size)
511+
.build();
512+
513+
let (client, backend, keystore_container, mut task_manager) =
514+
sc_service::new_full_parts_record_import::<Block, RuntimeApi, _>(
515+
&config,
516+
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
517+
executor,
518+
true,
519+
)?;
520+
let client = Arc::new(client);
521+
522+
let mut telemetry = telemetry.map(|(worker, telemetry)| {
523+
task_manager.spawn_handle().spawn("telemetry", None, worker.run());
524+
telemetry
525+
});
526+
527+
let transaction_pool = Arc::from(
528+
sc_transaction_pool::Builder::new(
529+
task_manager.spawn_essential_handle(),
530+
client.clone(),
531+
config.role.is_authority().into(),
532+
)
533+
.with_options(config.transaction_pool.clone())
534+
.with_prometheus(config.prometheus_registry())
535+
.build(),
536+
);
537+
538+
// Create manual seal import queue (accepts all blocks without relay chain validation)
539+
let import_queue = sc_consensus_manual_seal::import_queue(
540+
Box::new(client.clone()),
541+
&task_manager.spawn_essential_handle(),
542+
config.prometheus_registry(),
543+
);
544+
545+
let net_config = sc_network::config::FullNetworkConfiguration::<
546+
_,
547+
_,
548+
sc_network::NetworkWorker<Block, Hash>,
549+
>::new(
550+
&config.network,
551+
config.prometheus_config.as_ref().map(|cfg| cfg.registry.clone()),
552+
);
553+
554+
let (network, system_rpc_tx, tx_handler_controller, sync_service) =
555+
sc_service::build_network(sc_service::BuildNetworkParams {
556+
config: &config,
557+
client: client.clone(),
558+
transaction_pool: transaction_pool.clone(),
559+
spawn_handle: task_manager.spawn_handle(),
560+
import_queue,
561+
net_config,
562+
block_announce_validator_builder: None,
563+
warp_sync_config: None,
564+
block_relay: None,
565+
metrics: sc_network::NetworkWorker::<Block, Hash>::register_notification_metrics(
566+
config.prometheus_config.as_ref().map(|config| &config.registry),
567+
),
568+
})?;
569+
570+
if config.offchain_worker.enabled {
571+
use futures::FutureExt;
572+
573+
let offchain_workers =
574+
sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions {
575+
runtime_api_provider: client.clone(),
576+
keystore: Some(keystore_container.keystore()),
577+
offchain_db: backend.offchain_storage(),
578+
transaction_pool: Some(OffchainTransactionPoolFactory::new(
579+
transaction_pool.clone(),
580+
)),
581+
network_provider: Arc::new(network.clone()),
582+
is_validator: config.role.is_authority(),
583+
enable_http_requests: true,
584+
custom_extensions: move |_| vec![],
585+
})?;
586+
task_manager.spawn_handle().spawn(
587+
"offchain-workers-runner",
588+
"offchain-work",
589+
offchain_workers.run(client.clone(), task_manager.spawn_handle()).boxed(),
590+
);
591+
}
592+
593+
let proposer = sc_basic_authorship::ProposerFactory::new(
594+
task_manager.spawn_handle(),
595+
client.clone(),
596+
transaction_pool.clone(),
597+
None,
598+
None,
599+
);
600+
601+
// Get slot duration from runtime
602+
let slot_duration = sc_consensus_aura::slot_duration(&*client)
603+
.expect("Slot duration is always present in Aura runtime; qed.");
604+
605+
// The aura digest provider will provide digests that match the provided timestamp data.
606+
// Without this, the AURA parachain runtime complains about slot mismatches.
607+
let aura_digest_provider = AuraConsensusDataProvider::new_with_slot_duration(slot_duration);
608+
609+
// Extract para_id from chain spec
610+
let para_id = crate::chain_spec::Extensions::try_get(&*config.chain_spec)
611+
.map(|e| e.para_id)
612+
.ok_or("Could not find parachain ID in chain-spec.")?;
613+
let para_id = ParaId::from(para_id);
614+
615+
// Create inherent data providers with mocked relay chain data
616+
let create_inherent_data_providers =
617+
create_dev_inherent_data_providers(client.clone(), para_id, slot_duration);
618+
619+
// Spawn instant seal consensus
620+
let params = sc_consensus_manual_seal::InstantSealParams {
621+
block_import: client.clone(),
622+
env: proposer,
623+
client: client.clone(),
624+
pool: transaction_pool.clone(),
625+
select_chain: LongestChain::new(backend.clone()),
626+
consensus_data_provider: Some(Box::new(aura_digest_provider)),
627+
create_inherent_data_providers,
628+
};
629+
630+
let authorship_future = sc_consensus_manual_seal::run_instant_seal(params);
631+
task_manager
632+
.spawn_essential_handle()
633+
.spawn_blocking("instant-seal", None, authorship_future);
634+
635+
// Optional delayed finalization
636+
if finalize_delay_sec > 0 {
637+
let delayed_finalize_params = sc_consensus_manual_seal::DelayedFinalizeParams {
638+
client: client.clone(),
639+
spawn_handle: task_manager.spawn_handle(),
640+
delay_sec: finalize_delay_sec,
641+
};
642+
task_manager.spawn_essential_handle().spawn_blocking(
643+
"delayed_finalize",
644+
None,
645+
sc_consensus_manual_seal::run_delayed_finalize(delayed_finalize_params),
646+
);
647+
}
648+
649+
let rpc_builder = {
650+
let client = client.clone();
651+
let transaction_pool = transaction_pool.clone();
652+
653+
Box::new(move |_| {
654+
let deps =
655+
crate::rpc::FullDeps { client: client.clone(), pool: transaction_pool.clone() };
656+
657+
crate::rpc::create_full(deps).map_err(Into::into)
658+
})
659+
};
660+
661+
let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams {
662+
network,
663+
client,
664+
keystore: keystore_container.keystore(),
665+
task_manager: &mut task_manager,
666+
transaction_pool,
667+
rpc_builder,
668+
backend,
669+
system_rpc_tx,
670+
tx_handler_controller,
671+
sync_service,
672+
config,
673+
telemetry: telemetry.as_mut(),
674+
tracing_execute_block: None,
675+
})?;
676+
677+
Ok(task_manager)
678+
}

0 commit comments

Comments
 (0)