Skip to content

Commit 5e15f59

Browse files
authored
Merge pull request #3528 from autonomys/block-preprocessor-improve
Block preprocessor improvements
2 parents 3967663 + 404c494 commit 5e15f59

File tree

11 files changed

+629
-45
lines changed

11 files changed

+629
-45
lines changed

crates/sp-domains/src/core_api.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ use subspace_runtime_primitives::{ExtrinsicFor, Moment};
1616

1717
sp_api::decl_runtime_apis! {
1818
/// Base API that every domain runtime must implement.
19+
#[api_version(2)]
20+
// `allow(clippy::ptr_arg` is needed because Clippy complains to replace `&Vec<T>` with `&[T]`
21+
// but the latter fails to compile.
22+
#[allow(clippy::ptr_arg)]
1923
pub trait DomainCoreApi {
2024
/// Extracts the optional signer per extrinsic.
2125
fn extract_signer(
@@ -28,6 +32,14 @@ sp_api::decl_runtime_apis! {
2832
tx_range: &U256,
2933
) -> bool;
3034

35+
/// Extract signer of a given list of extrinsic if all of them are within the
36+
/// tx range, otherwise return the index if the first tx not in the tx range.
37+
fn extract_signer_if_all_within_tx_range(
38+
extrinsic: &Vec<Block::Extrinsic>,
39+
bundle_vrf_hash: &U256,
40+
tx_range: &U256,
41+
) -> Result<Vec<Option<opaque::AccountId>> , u32>;
42+
3143
/// Returns the storage root after initializing the block.
3244
fn initialize_block_with_post_state_root(header: &Block::Header) -> Vec<u8>;
3345

@@ -49,6 +61,9 @@ sp_api::decl_runtime_apis! {
4961
/// Returns true if the extrinsic is an inherent extrinsic.
5062
fn is_inherent_extrinsic(extrinsic: &Block::Extrinsic) -> bool;
5163

64+
/// Find the first inherent extrinsic
65+
fn find_first_inherent_extrinsic(extrinsics: &Vec<Block::Extrinsic>) -> Option<u32>;
66+
5267
/// Checks the validity of array of extrinsics + pre_dispatch
5368
/// returning failure on first extrinsic that fails runtime call.
5469
/// IMPORTANT: Change `CHECK_EXTRINSICS_AND_DO_PRE_DISPATCH_METHOD_NAME` constant when this method name is changed
@@ -60,6 +75,12 @@ sp_api::decl_runtime_apis! {
6075
opaque_extrinsic: sp_runtime::OpaqueExtrinsic,
6176
) -> Result<Block::Extrinsic, DecodeExtrinsicError>;
6277

78+
/// Decodes a list of domain extrinsics from opaque extrinsics, returning all extrinsics
79+
/// up to the first undecodable tx. (Or the full list if all are decodable.)
80+
fn decode_extrinsics_prefix(
81+
opaque_extrinsics: Vec<sp_runtime::OpaqueExtrinsic>,
82+
) -> Vec<Block::Extrinsic>;
83+
6384
/// Returns extrinsic Era if present.
6485
fn extrinsic_era(
6586
extrinsic: &Block::Extrinsic
@@ -68,6 +89,9 @@ sp_api::decl_runtime_apis! {
6889
/// Returns the extrinsic weight.
6990
fn extrinsic_weight(ext: &Block::Extrinsic) -> Weight;
7091

92+
/// Returns the sum of a given set of extrinsics weight.
93+
fn extrinsics_weight(ext: &Vec<Block::Extrinsic>) -> Weight;
94+
7195
/// The accumulated transaction fee of all transactions included in the block.
7296
fn block_fees() -> BlockFees<Balance>;
7397

crates/subspace-fake-runtime-api/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ sp_api::impl_runtime_apis! {
351351
unreachable!()
352352
}
353353

354+
fn batch_extract_xdm_mmr_proof(_extrinsics: &Vec<ExtrinsicFor<Block>>) -> BTreeMap<u32, ConsensusChainMmrLeafProof<BlockNumber, BlockHashFor<Block>, sp_core::H256>> {
355+
unreachable!()
356+
}
357+
354358
fn confirmed_domain_block_storage_key(_domain_id: DomainId) -> Vec<u8> {
355359
unreachable!()
356360
}

crates/subspace-runtime/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,20 @@ impl_runtime_apis! {
16181618
}
16191619
}
16201620

1621+
fn batch_extract_xdm_mmr_proof(extrinsics: &Vec<ExtrinsicFor<Block>>) -> BTreeMap<u32, ConsensusChainMmrLeafProof<BlockNumber, BlockHashFor<Block>, sp_core::H256>> {
1622+
let mut mmr_proofs = BTreeMap::new();
1623+
for (index, ext) in extrinsics.iter().enumerate() {
1624+
match &ext.function {
1625+
RuntimeCall::Messenger(pallet_messenger::Call::relay_message { msg })
1626+
| RuntimeCall::Messenger(pallet_messenger::Call::relay_message_response { msg }) => {
1627+
mmr_proofs.insert(index as u32, msg.proof.consensus_mmr_proof());
1628+
}
1629+
_ => {},
1630+
}
1631+
}
1632+
mmr_proofs
1633+
}
1634+
16211635
fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec<u8> {
16221636
Domains::confirmed_domain_block_storage_key(domain_id)
16231637
}

domains/client/block-preprocessor/src/lib.rs

Lines changed: 167 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ pub mod stateless_runtime;
1717
use crate::inherents::is_runtime_upgraded;
1818
use crate::stateless_runtime::StatelessRuntime;
1919
use domain_runtime_primitives::opaque::AccountId;
20-
use domain_runtime_primitives::CheckExtrinsicsValidityError;
20+
use domain_runtime_primitives::{opaque, CheckExtrinsicsValidityError};
2121
use parity_scale_codec::Encode;
2222
use sc_client_api::{backend, BlockBackend};
2323
use sc_executor::RuntimeVersionOf;
24-
use sp_api::{ApiError, ProvideRuntimeApi};
24+
use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
2525
use sp_blockchain::HeaderBackend;
2626
use sp_core::traits::{CodeExecutor, FetchRuntimeCode};
2727
use sp_core::H256;
@@ -50,6 +50,8 @@ type DomainBlockElements<CBlock> = (Vec<ExtrinsicFor<CBlock>>, Randomness);
5050
enum BundleValidity<Extrinsic> {
5151
/// A valid bundle contents.
5252
Valid(Vec<Extrinsic>),
53+
/// A valid bundle contents with signer of each extrinsic.
54+
ValidWithSigner(Vec<(Option<opaque::AccountId>, Extrinsic)>),
5355
/// An invalid bundle reason.
5456
Invalid(InvalidBundleType),
5557
}
@@ -213,6 +215,30 @@ where
213215
}))
214216
}
215217

218+
/// NOTE: this is needed for compatible with Taurus
219+
fn is_batch_api_available(
220+
&self,
221+
parent_domain_hash: Block::Hash,
222+
) -> sp_blockchain::Result<bool> {
223+
let domain_runtime_api = self.client.runtime_api();
224+
225+
let domain_core_api_version = domain_runtime_api
226+
.api_version::<dyn DomainCoreApi<Block>>(parent_domain_hash)?
227+
.ok_or(sp_blockchain::Error::Application(Box::from(format!(
228+
"DomainCoreApi not found at {parent_domain_hash:?}"
229+
))))?;
230+
231+
let messenger_api_version = domain_runtime_api
232+
.api_version::<dyn MessengerApi<Block, NumberFor<CBlock>, CBlock::Hash>>(
233+
parent_domain_hash,
234+
)?
235+
.ok_or(sp_blockchain::Error::Application(Box::from(format!(
236+
"MessengerApi not found at {parent_domain_hash:?}"
237+
))))?;
238+
239+
Ok(domain_core_api_version >= 2 && messenger_api_version >= 3)
240+
}
241+
216242
/// Filter out the invalid bundles first and then convert the remaining valid ones to
217243
/// a list of extrinsics.
218244
#[allow(clippy::type_complexity)]
@@ -254,12 +280,22 @@ where
254280
}
255281

256282
let extrinsic_root = bundle.extrinsics_root();
257-
match self.check_bundle_validity(
258-
&bundle,
259-
&tx_range,
260-
(parent_domain_hash, parent_domain_number),
261-
at_consensus_hash,
262-
)? {
283+
let bundle_validity = if self.is_batch_api_available(parent_domain_hash)? {
284+
self.batch_check_bundle_validity(
285+
bundle,
286+
&tx_range,
287+
(parent_domain_hash, parent_domain_number),
288+
at_consensus_hash,
289+
)?
290+
} else {
291+
self.check_bundle_validity(
292+
&bundle,
293+
&tx_range,
294+
(parent_domain_hash, parent_domain_number),
295+
at_consensus_hash,
296+
)?
297+
};
298+
match bundle_validity {
263299
BundleValidity::Valid(extrinsics) => {
264300
let extrinsics: Vec<_> = match runtime_api
265301
.extract_signer(parent_domain_hash, extrinsics)
@@ -287,6 +323,24 @@ where
287323
));
288324
valid_extrinsics.extend(extrinsics);
289325
}
326+
BundleValidity::ValidWithSigner(signer_and_extrinsics) => {
327+
let bundle_digest: Vec<_> = signer_and_extrinsics
328+
.iter()
329+
.map(|(signer, tx)| {
330+
(
331+
signer.clone(),
332+
ExtrinsicDigest::new::<LayoutV1<HeaderHashingFor<Block::Header>>>(
333+
tx.encode(),
334+
),
335+
)
336+
})
337+
.collect();
338+
inboxed_bundles.push(InboxedBundle::valid(
339+
HeaderHashingFor::<Block::Header>::hash_of(&bundle_digest),
340+
extrinsic_root,
341+
));
342+
valid_extrinsics.extend(signer_and_extrinsics);
343+
}
290344
BundleValidity::Invalid(invalid_bundle_type) => {
291345
inboxed_bundles
292346
.push(InboxedBundle::invalid(invalid_bundle_type, extrinsic_root));
@@ -433,6 +487,111 @@ where
433487

434488
Ok(BundleValidity::Valid(extrinsics))
435489
}
490+
491+
fn batch_check_bundle_validity(
492+
&self,
493+
bundle: OpaqueBundle<NumberFor<CBlock>, CBlock::Hash, Block::Header, Balance>,
494+
tx_range: &U256,
495+
(parent_domain_hash, parent_domain_number): (Block::Hash, NumberFor<Block>),
496+
at_consensus_hash: CBlock::Hash,
497+
) -> sp_blockchain::Result<BundleValidity<Block::Extrinsic>> {
498+
let bundle_vrf_hash =
499+
U256::from_be_bytes(*bundle.sealed_header.header.proof_of_election.vrf_hash());
500+
let bundle_length = bundle.extrinsics.len();
501+
let bundle_estimated_weight = bundle.estimated_weight();
502+
let mut maybe_invalid_bundle_type = None;
503+
504+
let stateless_runtime_api = self.stateless_runtime_api(parent_domain_hash)?;
505+
let consensus_runtime_api = self.consensus_client.runtime_api();
506+
507+
// Check the validity of extrinsic inside the bundle, the goal is trying to find the first
508+
// invalid tx and the first check it failed to pass, thus even an invalid tx that failed to
509+
// pass a given check is found we still continue the following check for other txs that before
510+
// it.
511+
//
512+
// NOTE: the checking order must follow `InvalidBundleType::checking_order`
513+
514+
let mut extrinsics = stateless_runtime_api.decode_extrinsics_prefix(bundle.extrinsics)?;
515+
if extrinsics.len() != bundle_length {
516+
// If the length changed meaning there is undecodable tx at index `extrinsics.len()`
517+
maybe_invalid_bundle_type
518+
.replace(InvalidBundleType::UndecodableTx(extrinsics.len() as u32));
519+
}
520+
521+
let signers = match stateless_runtime_api.extract_signer_if_all_within_tx_range(
522+
&extrinsics,
523+
&bundle_vrf_hash,
524+
tx_range,
525+
)? {
526+
Err(index) => {
527+
maybe_invalid_bundle_type.replace(InvalidBundleType::OutOfRangeTx(index));
528+
extrinsics.truncate(index as usize);
529+
530+
// This will never used since there is an invalid tx
531+
Vec::default()
532+
}
533+
Ok(signers) => signers,
534+
};
535+
536+
// Check if this extrinsic is an inherent extrinsic.
537+
// If so, this is an invalid bundle since these extrinsics should not be included in the
538+
// bundle. Extrinsic is always decodable due to the check above.
539+
if let Some(index) = stateless_runtime_api.find_first_inherent_extrinsic(&extrinsics)? {
540+
maybe_invalid_bundle_type.replace(InvalidBundleType::InherentExtrinsic(index));
541+
extrinsics.truncate(index as usize);
542+
}
543+
544+
let batch_xdm_mmr_proof =
545+
stateless_runtime_api.batch_extract_native_xdm_mmr_proof(&extrinsics)?;
546+
for (index, xdm_mmr_proof) in batch_xdm_mmr_proof {
547+
let ConsensusChainMmrLeafProof {
548+
opaque_mmr_leaf,
549+
proof,
550+
..
551+
} = xdm_mmr_proof;
552+
553+
if consensus_runtime_api
554+
.verify_proof(at_consensus_hash, vec![opaque_mmr_leaf], proof)?
555+
.is_err()
556+
{
557+
maybe_invalid_bundle_type.replace(InvalidBundleType::InvalidXDM(index));
558+
extrinsics.truncate(index as usize);
559+
break;
560+
}
561+
}
562+
563+
// Using `check_extrinsics_and_do_pre_dispatch` instead of `check_transaction_validity`
564+
// to maintain side-effect between tx in the storage buffer.
565+
if let Err(CheckExtrinsicsValidityError {
566+
extrinsic_index, ..
567+
}) = self
568+
.client
569+
.runtime_api()
570+
.check_extrinsics_and_do_pre_dispatch(
571+
parent_domain_hash,
572+
extrinsics.clone(),
573+
parent_domain_number,
574+
parent_domain_hash,
575+
)?
576+
{
577+
maybe_invalid_bundle_type.replace(InvalidBundleType::IllegalTx(extrinsic_index));
578+
}
579+
580+
// If there is any invalid tx then return the error before checking the bundle weight,
581+
// which is a check of the whole bundle and should only perform when all tx are valid.
582+
if let Some(invalid_bundle_type) = maybe_invalid_bundle_type {
583+
return Ok(BundleValidity::Invalid(invalid_bundle_type));
584+
}
585+
586+
if bundle_estimated_weight != stateless_runtime_api.extrinsics_weight(&extrinsics)? {
587+
return Ok(BundleValidity::Invalid(
588+
InvalidBundleType::InvalidBundleWeight,
589+
));
590+
}
591+
592+
let signer_and_extrinsics = signers.into_iter().zip(extrinsics).collect();
593+
Ok(BundleValidity::ValidWithSigner(signer_and_extrinsics))
594+
}
436595
}
437596

438597
#[cfg(test)]

0 commit comments

Comments
 (0)