Skip to content

Commit c113225

Browse files
bkchrtomakalexnv
authored
Fix elastic scaling support (#3141)
Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com> Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
1 parent 3ab41c2 commit c113225

File tree

11 files changed

+3056
-1421
lines changed

11 files changed

+3056
-1421
lines changed

lib/src/chain/blocks_tree/verify.rs

Lines changed: 72 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -136,70 +136,78 @@ impl<T> NonFinalizedTree<T> {
136136
&self.finalized_block_header
137137
};
138138

139-
let header_verify_result = {
140-
let consensus_config = match (&self.finalized_consensus, &parent_consensus) {
141-
(
142-
FinalizedConsensus::Aura { slot_duration, .. },
143-
Some(BlockConsensus::Aura { authorities_list }),
144-
) => verify::header_only::ConfigConsensus::Aura {
145-
current_authorities: header::AuraAuthoritiesIter::from_slice(authorities_list),
146-
now_from_unix_epoch,
147-
slot_duration: *slot_duration,
148-
},
149-
(
150-
FinalizedConsensus::Babe {
151-
slots_per_epoch, ..
139+
let (best_score_num_primary_slots, best_score_num_secondary_slots, consensus_update) = {
140+
let header_verify_result = {
141+
let consensus_config = match (&self.finalized_consensus, &parent_consensus) {
142+
(
143+
FinalizedConsensus::Aura { slot_duration, .. },
144+
Some(BlockConsensus::Aura { authorities_list }),
145+
) => verify::header_only::ConfigConsensus::Aura {
146+
current_authorities: header::AuraAuthoritiesIter::from_slice(
147+
authorities_list,
148+
),
149+
now_from_unix_epoch,
150+
slot_duration: *slot_duration,
151+
// Parachains with async backing can produce multiple blocks per
152+
// Aura slot. Outsourced finality indicates a parachain.
153+
allow_equal_slot_number: matches!(self.finality, Finality::Outsourced),
152154
},
153-
Some(BlockConsensus::Babe {
154-
current_epoch,
155-
next_epoch,
156-
}),
157-
) => verify::header_only::ConfigConsensus::Babe {
158-
parent_block_epoch: current_epoch.as_ref().map(|v| (&**v).into()),
159-
parent_block_next_epoch: (&**next_epoch).into(),
160-
slots_per_epoch: *slots_per_epoch,
161-
now_from_unix_epoch,
162-
},
163-
(FinalizedConsensus::Unknown, None) => {
164-
return Err(HeaderVerifyError::UnknownConsensusEngine);
165-
}
166-
_ => {
167-
return Err(HeaderVerifyError::ConsensusMismatch);
168-
}
169-
};
155+
(
156+
FinalizedConsensus::Babe {
157+
slots_per_epoch, ..
158+
},
159+
Some(BlockConsensus::Babe {
160+
current_epoch,
161+
next_epoch,
162+
}),
163+
) => verify::header_only::ConfigConsensus::Babe {
164+
parent_block_epoch: current_epoch.as_ref().map(|v| (&**v).into()),
165+
parent_block_next_epoch: (&**next_epoch).into(),
166+
slots_per_epoch: *slots_per_epoch,
167+
now_from_unix_epoch,
168+
},
169+
(FinalizedConsensus::Unknown, None) => {
170+
return Err(HeaderVerifyError::UnknownConsensusEngine);
171+
}
172+
_ => {
173+
return Err(HeaderVerifyError::ConsensusMismatch);
174+
}
175+
};
170176

171-
match verify::header_only::verify(verify::header_only::Config {
172-
consensus: consensus_config,
173-
finality: match &parent_finality {
174-
BlockFinality::Outsourced => verify::header_only::ConfigFinality::Outsourced,
175-
BlockFinality::Grandpa { .. } => verify::header_only::ConfigFinality::Grandpa,
176-
},
177-
allow_unknown_consensus_engines: self.allow_unknown_consensus_engines,
178-
block_header: decoded_header.clone(),
179-
block_number_bytes: self.block_number_bytes,
180-
parent_block_header: {
181-
// All headers inserted in `self` are necessarily valid, and thus this
182-
// `unwrap()` can't panic.
183-
header::decode(parent_block_header, self.block_number_bytes)
184-
.unwrap_or_else(|_| unreachable!())
185-
},
186-
}) {
187-
Ok(s) => s,
188-
Err(err) => {
189-
// The code in this module is meant to ensure that the chain is in an
190-
// appropriate state, therefore `is_invalid_chain_configuration` being `true`
191-
// would indicate a bug in the code somewhere.
192-
// We use a `debug_assert` rather than `assert` in order to avoid crashing,
193-
// as treating the header as invalid is an appropriate way to handle a bug
194-
// here.
195-
debug_assert!(!err.is_invalid_chain_configuration());
196-
return Err(HeaderVerifyError::VerificationFailed(err));
177+
match verify::header_only::verify(verify::header_only::Config {
178+
consensus: consensus_config,
179+
finality: match &parent_finality {
180+
BlockFinality::Outsourced => {
181+
verify::header_only::ConfigFinality::Outsourced
182+
}
183+
BlockFinality::Grandpa { .. } => {
184+
verify::header_only::ConfigFinality::Grandpa
185+
}
186+
},
187+
allow_unknown_consensus_engines: self.allow_unknown_consensus_engines,
188+
block_header: decoded_header.clone(),
189+
block_number_bytes: self.block_number_bytes,
190+
parent_block_header: {
191+
// All headers inserted in `self` are necessarily valid, and thus this
192+
// `unwrap()` can't panic.
193+
header::decode(parent_block_header, self.block_number_bytes)
194+
.unwrap_or_else(|_| unreachable!())
195+
},
196+
}) {
197+
Ok(s) => s,
198+
Err(err) => {
199+
// The code in this module is meant to ensure that the chain is in an
200+
// appropriate state, therefore `is_invalid_chain_configuration` being `true`
201+
// would indicate a bug in the code somewhere.
202+
// We use a `debug_assert` rather than `assert` in order to avoid crashing,
203+
// as treating the header as invalid is an appropriate way to handle a bug
204+
// here.
205+
debug_assert!(!err.is_invalid_chain_configuration());
206+
return Err(HeaderVerifyError::VerificationFailed(err));
207+
}
197208
}
198-
}
199-
};
209+
};
200210

201-
// Updated consensus information for the block being verified.
202-
let (best_score_num_primary_slots, best_score_num_secondary_slots, consensus_update) =
203211
match (
204212
header_verify_result,
205213
&parent_consensus,
@@ -358,10 +366,11 @@ impl<T> NonFinalizedTree<T> {
358366
)
359367
}
360368

361-
// Any mismatch between consensus algorithms should have been detected by the
362-
// block verification.
369+
// Any mismatch between consensus algorithms should have been detected by
370+
// the block verification.
363371
_ => unreachable!(),
364-
};
372+
}
373+
};
365374

366375
// Updated finality information for the block being verified.
367376
let finality_update = match &parent_finality {

lib/src/sync/all.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,41 @@ impl<TRq, TSrc, TBl> AllSync<TRq, TSrc, TBl> {
307307
all_forks.finalized_block_hash()
308308
}
309309

310+
/// Updates the finalized block to the given `block_hash`.
311+
///
312+
/// This should be used when the finality is outsourced.
313+
pub fn set_finalized_block(
314+
&mut self,
315+
block_hash: &[u8; 32],
316+
) -> Result<SetFinalizedBlockResult<TBl>, SetFinalizedBlockError> {
317+
let Some(all_forks) = self.all_forks.as_mut() else {
318+
unreachable!()
319+
};
320+
321+
let result = all_forks
322+
.set_finalized_block(block_hash)
323+
.map_err(|_| SetFinalizedBlockError::UnknownBlock)?;
324+
325+
Ok(SetFinalizedBlockResult {
326+
finalized_blocks: result
327+
.finalized_blocks
328+
.into_iter()
329+
.map(|b| Block {
330+
header: b.scale_encoded_header,
331+
block_hash: b.block_hash,
332+
// Should be always `Some`.
333+
user_data: b.user_data.unwrap(),
334+
})
335+
.collect(),
336+
pruned_blocks: result
337+
.pruned_blocks
338+
.into_iter()
339+
.map(|b| b.block_hash)
340+
.collect(),
341+
updates_best_block: result.updates_best_block,
342+
})
343+
}
344+
310345
/// Returns the header of the best block.
311346
///
312347
/// > **Note**: This value is provided only for informative purposes. Keep in mind that this
@@ -737,7 +772,7 @@ impl<TRq, TSrc, TBl> AllSync<TRq, TSrc, TBl> {
737772
num_blocks,
738773
request_headers: true,
739774
request_bodies,
740-
request_justification: true,
775+
request_justification: _,
741776
} if request_bodies || !self.shared.download_bodies => {
742777
let Some(all_forks) = &mut self.all_forks else {
743778
unreachable!()
@@ -2292,6 +2327,22 @@ pub enum FinalityProofVerifyOutcome<TBl> {
22922327
GrandpaCommitError(CommitVerifyError),
22932328
}
22942329

2330+
/// Returned by [`AllSync::set_finalized_block`].
2331+
pub struct SetFinalizedBlockResult<TBl> {
2332+
/// The finalized blocks.
2333+
pub finalized_blocks: Vec<Block<TBl>>,
2334+
/// The blocks that got pruned while finalizing.
2335+
pub pruned_blocks: Vec<[u8; 32]>,
2336+
/// Is set to `true`, if the best block changed.
2337+
pub updates_best_block: bool,
2338+
}
2339+
2340+
/// Potential error returned by [`AllSync::set_finalized_block`].
2341+
#[derive(Debug, derive_more::Display)]
2342+
pub enum SetFinalizedBlockError {
2343+
UnknownBlock,
2344+
}
2345+
22952346
pub struct WarpSyncFragmentVerify<TRq, TSrc, TBl> {
22962347
inner: warp_sync::VerifyWarpSyncFragment<WarpSyncSourceExtra, WarpSyncRequestExtra>,
22972348
ready_to_transition: Option<warp_sync::RuntimeInformation>,

lib/src/sync/all_forks.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,49 @@ impl<TBl, TRq, TSrc> AllForksSync<TBl, TRq, TSrc> {
460460
self.chain.finalized_block_hash()
461461
}
462462

463+
/// Updates the finalized block to the given `block_hash`.
464+
///
465+
/// This should be used when the finality is outsourced.
466+
pub fn set_finalized_block(
467+
&mut self,
468+
block_hash: &[u8; 32],
469+
) -> Result<SetFinalizedBlockResult<TBl>, blocks_tree::SetFinalizedError> {
470+
let iter = self.chain.set_finalized_block(block_hash)?;
471+
let updates_best_block = iter.updates_best_block();
472+
let mut finalized_blocks = Vec::new();
473+
let mut pruned_blocks = Vec::new();
474+
for block in iter {
475+
match block.ty {
476+
blocks_tree::RemovedBlockType::Finalized => finalized_blocks.push(RemovedBlock {
477+
block_hash: block.block_hash,
478+
block_number: block.block_number,
479+
user_data: block.user_data,
480+
scale_encoded_header: block.scale_encoded_header,
481+
}),
482+
blocks_tree::RemovedBlockType::Pruned => pruned_blocks.push(RemovedBlock {
483+
block_hash: block.block_hash,
484+
block_number: block.block_number,
485+
user_data: block.user_data,
486+
scale_encoded_header: block.scale_encoded_header,
487+
}),
488+
}
489+
}
490+
491+
if let Some(last) = finalized_blocks.last() {
492+
let _ = self
493+
.inner
494+
.blocks
495+
.set_finalized_block_height(last.block_number)
496+
.count();
497+
}
498+
499+
Ok(SetFinalizedBlockResult {
500+
finalized_blocks,
501+
pruned_blocks,
502+
updates_best_block,
503+
})
504+
}
505+
463506
/// Returns the header of the best block.
464507
///
465508
/// > **Note**: This value is provided only for informative purposes. Keep in mind that this
@@ -2385,3 +2428,13 @@ pub struct RemovedBlock<TBl> {
23852428
/// SCALE-encoded header of the block.
23862429
pub scale_encoded_header: Vec<u8>,
23872430
}
2431+
2432+
/// Result of [`AllForksSync::set_finalized_block`].
2433+
pub struct SetFinalizedBlockResult<TBl> {
2434+
/// The blocks that got finalized.
2435+
pub finalized_blocks: Vec<RemovedBlock<TBl>>,
2436+
/// The blocks that got pruned in the process of finalizing.
2437+
pub pruned_blocks: Vec<RemovedBlock<TBl>>,
2438+
/// Is set to `true`, if the best block changed.
2439+
pub updates_best_block: bool,
2440+
}

lib/src/verify/aura.rs

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ pub struct VerifyConfig<'a, TAuthList> {
7878
/// Duration of a slot in milliseconds.
7979
/// Can be found by calling the `AuraApi_slot_duration` runtime function.
8080
pub slot_duration: NonZero<u64>,
81+
82+
/// If `true`, allow the slot number to be equal to the parent's slot number.
83+
///
84+
/// This is required for parachains which can produce multiple blocks per slot.
85+
pub allow_equal_slot_number: bool,
8186
}
8287

8388
/// Information yielded back after successfully verifying a block.
@@ -132,7 +137,11 @@ pub fn verify_header<'a>(
132137
None => return Err(VerifyError::ParentIsntAuraConsensus),
133138
};
134139

135-
if slot_number <= parent_slot_number {
140+
if config.allow_equal_slot_number {
141+
if slot_number < parent_slot_number {
142+
return Err(VerifyError::SlotNumberNotIncreasing);
143+
}
144+
} else if slot_number <= parent_slot_number {
136145
return Err(VerifyError::SlotNumberNotIncreasing);
137146
}
138147
}
@@ -171,18 +180,13 @@ pub fn verify_header<'a>(
171180

172181
// The signature in the seal applies to the header from where the signature isn't present.
173182
// Extract the signature and build the hash that is expected to be signed.
174-
let (seal_signature, pre_seal_hash) = {
183+
let (seal_bytes, pre_seal_hash) = {
175184
let mut unsealed_header = config.header;
176-
let seal_signature = match unsealed_header.digest.pop_seal() {
177-
Some(header::Seal::Aura(seal)) => {
178-
schnorrkel::Signature::from_bytes(seal).map_err(|_| VerifyError::BadSignature)?
179-
}
185+
let seal_bytes: [u8; 64] = match unsealed_header.digest.pop_seal() {
186+
Some(header::Seal::Aura(seal)) => *seal,
180187
_ => return Err(VerifyError::MissingSeal),
181188
};
182-
(
183-
seal_signature,
184-
unsealed_header.hash(config.block_number_bytes),
185-
)
189+
(seal_bytes, unsealed_header.hash(config.block_number_bytes))
186190
};
187191

188192
// Fetch the authority that has supposedly signed the block.
@@ -200,21 +204,29 @@ pub fn verify_header<'a>(
200204
)
201205
.unwrap_or_else(|_| unreachable!());
202206

203-
// This `unwrap()` can only panic if `public_key` is the wrong length, which we know can't
204-
// happen as it's of type `[u8; 32]`.
205-
let authority_public_key = schnorrkel::PublicKey::from_bytes(
206-
config
207-
.current_authorities
208-
.nth(signing_authority)
209-
.unwrap()
210-
.public_key,
211-
)
212-
.unwrap();
213-
214-
// Now verifying the signature in the seal.
215-
authority_public_key
216-
.verify_simple(b"substrate", &pre_seal_hash, &seal_signature)
217-
.map_err(|_| VerifyError::BadSignature)?;
207+
let authority_public_key = config
208+
.current_authorities
209+
.nth(signing_authority)
210+
.unwrap()
211+
.public_key;
212+
213+
// Try sr25519 verification first, then fall back to ed25519.
214+
let sr25519_ok = schnorrkel::PublicKey::from_bytes(authority_public_key)
215+
.and_then(|pk| {
216+
pk.verify_simple(
217+
b"substrate",
218+
&pre_seal_hash,
219+
&schnorrkel::Signature::from_bytes(&seal_bytes)?,
220+
)
221+
})
222+
.is_ok();
223+
224+
if !sr25519_ok {
225+
ed25519_zebra::VerificationKey::try_from(*authority_public_key)
226+
.map_err(|_| VerifyError::BadSignature)?
227+
.verify(&ed25519_zebra::Signature::from(seal_bytes), &pre_seal_hash)
228+
.map_err(|_| VerifyError::BadSignature)?;
229+
}
218230

219231
// Success! 🚀
220232
Ok(VerifySuccess { authorities_change })

0 commit comments

Comments
 (0)