Skip to content

Commit cd44905

Browse files
authored
Merge pull request #3276 from autonomys/split-produce-bundle
Split produce_bundle into smaller methods
2 parents 54fb379 + 3685d2c commit cd44905

1 file changed

Lines changed: 204 additions & 80 deletions

File tree

domains/client/domain-operator/src/domain_bundle_producer.rs

Lines changed: 204 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ use sp_blockchain::HeaderBackend;
1010
use sp_consensus_slots::Slot;
1111
use sp_domains::core_api::DomainCoreApi;
1212
use sp_domains::{
13-
Bundle, BundleProducerElectionApi, DomainId, DomainsApi, OperatorId, OperatorPublicKey,
14-
OperatorSignature, SealedBundleHeader, SealedSingletonReceipt, SingletonReceipt,
13+
Bundle, BundleHeader, BundleProducerElectionApi, DomainId, DomainsApi, OperatorId,
14+
OperatorPublicKey, OperatorSignature, ProofOfElection, SealedBundleHeader,
15+
SealedSingletonReceipt, SingletonReceipt,
1516
};
1617
use sp_keystore::KeystorePtr;
1718
use sp_messenger::MessengerApi;
@@ -22,6 +23,19 @@ use std::sync::Arc;
2223
use subspace_runtime_primitives::Balance;
2324
use tracing::info;
2425

26+
/// Type alias for block hash.
27+
pub type BlockHashFor<Block> = <Block as BlockT>::Hash;
28+
29+
/// Type alias for block header.
30+
pub type HeaderFor<Block> = <Block as BlockT>::Header;
31+
32+
/// Type alias for bundle header.
33+
pub type BundleHeaderFor<Block, CBlock> =
34+
BundleHeader<NumberFor<CBlock>, BlockHashFor<CBlock>, HeaderFor<Block>, Balance>;
35+
36+
/// Type alias for extrinsics.
37+
pub type ExtrinsicFor<Block> = <Block as BlockT>::Extrinsic;
38+
2539
type OpaqueBundle<Block, CBlock> = sp_domains::OpaqueBundle<
2640
NumberFor<CBlock>,
2741
<CBlock as BlockT>::Hash,
@@ -170,18 +184,26 @@ where
170184
})
171185
}
172186

173-
pub async fn produce_bundle(
174-
&mut self,
187+
#[expect(clippy::type_complexity)]
188+
pub fn claim_bundle_slot(
189+
&self,
175190
operator_id: OperatorId,
176-
slot_info: OperatorSlotInfo,
177-
) -> sp_blockchain::Result<Option<DomainProposal<Block, CBlock>>> {
191+
slot_info: &OperatorSlotInfo,
192+
domain_best_number: NumberFor<Block>,
193+
consensus_chain_best_hash: BlockHashFor<CBlock>,
194+
) -> sp_blockchain::Result<
195+
Option<(
196+
NumberFor<Block>,
197+
NumberFor<Block>,
198+
ProofOfElection,
199+
OperatorPublicKey,
200+
)>,
201+
> {
178202
let OperatorSlotInfo {
179203
slot,
180204
proof_of_time,
181205
} = slot_info;
182206

183-
let domain_best_number = self.client.info().best_number;
184-
let consensus_chain_best_hash = self.consensus_client.info().best_hash;
185207
let domain_best_number_onchain = self
186208
.consensus_client
187209
.runtime_api()
@@ -211,7 +233,7 @@ where
211233
let skip_out_of_order_slot = self.skip_out_of_order_slot
212234
&& self
213235
.last_processed_slot
214-
.map(|last_slot| last_slot >= slot)
236+
.map(|last_slot| last_slot >= *slot)
215237
.unwrap_or(false);
216238

217239
is_operator_lagging || skip_out_of_order_slot
@@ -227,98 +249,200 @@ where
227249

228250
if let Some((proof_of_election, operator_signing_key)) =
229251
self.bundle_producer_election_solver.solve_challenge(
230-
slot,
252+
*slot,
231253
consensus_chain_best_hash,
232254
self.domain_id,
233255
operator_id,
234-
proof_of_time,
256+
*proof_of_time,
235257
)?
236258
{
237259
tracing::info!("📦 Claimed slot {slot}");
238260

261+
Ok(Some((
262+
domain_best_number_onchain,
263+
head_receipt_number,
264+
proof_of_election,
265+
operator_signing_key,
266+
)))
267+
} else {
268+
Ok(None)
269+
}
270+
}
271+
272+
pub fn prepare_receipt(
273+
&self,
274+
slot_info: &OperatorSlotInfo,
275+
domain_best_number_onchain: NumberFor<Block>,
276+
head_receipt_number: NumberFor<Block>,
277+
proof_of_election: &ProofOfElection,
278+
operator_signing_key: &OperatorPublicKey,
279+
) -> sp_blockchain::Result<Option<DomainProposal<Block, CBlock>>> {
280+
// When the receipt gap is greater than one, the operator needs to produce a receipt
281+
// instead of a bundle
282+
if domain_best_number_onchain.saturating_sub(head_receipt_number) > 1u32.into() {
283+
info!(
284+
?domain_best_number_onchain,
285+
?head_receipt_number,
286+
"🔖 Producing singleton receipt at slot {:?}",
287+
slot_info.slot
288+
);
289+
239290
let receipt = self
240291
.domain_bundle_proposer
241292
.load_next_receipt(domain_best_number_onchain, head_receipt_number)?;
242293

243-
// When the receipt gap is greater than one the operator need to produce receipt
244-
// instead of bundle
245-
if domain_best_number_onchain.saturating_sub(head_receipt_number) > 1u32.into() {
246-
info!(
247-
?domain_best_number_onchain,
248-
?head_receipt_number,
249-
"🔖 Producing singleton receipt at slot {:?}",
250-
slot_info.slot
251-
);
252-
253-
let singleton_receipt = SingletonReceipt {
254-
proof_of_election,
255-
receipt,
256-
};
294+
let singleton_receipt = SingletonReceipt {
295+
proof_of_election: proof_of_election.clone(),
296+
receipt,
297+
};
298+
299+
let signature = {
300+
let to_sign: BlockHashFor<Block> = singleton_receipt.hash();
301+
self.sign(operator_signing_key, to_sign.as_ref())?
302+
};
257303

258-
let signature = {
259-
let to_sign: <Block as BlockT>::Hash = singleton_receipt.hash();
260-
self.sign(&operator_signing_key, to_sign.as_ref())?
304+
let sealed_singleton_receipt: SealedSingletonReceiptFor<Block, CBlock> =
305+
SealedSingletonReceipt {
306+
singleton_receipt,
307+
signature,
261308
};
262309

263-
let sealed_singleton_receipt: SealedSingletonReceiptFor<Block, CBlock> =
264-
SealedSingletonReceipt {
265-
singleton_receipt,
266-
signature,
267-
};
268-
return Ok(Some(DomainProposal::Receipt(sealed_singleton_receipt)));
269-
}
310+
Ok(Some(DomainProposal::Receipt(sealed_singleton_receipt)))
311+
} else {
312+
Ok(None)
313+
}
314+
}
315+
316+
#[expect(clippy::too_many_arguments)]
317+
pub async fn prepare_bundle(
318+
&mut self,
319+
operator_id: OperatorId,
320+
slot_info: &OperatorSlotInfo,
321+
consensus_chain_best_hash: BlockHashFor<CBlock>,
322+
domain_best_number_onchain: NumberFor<Block>,
323+
head_receipt_number: NumberFor<Block>,
324+
proof_of_election: ProofOfElection,
325+
// TODO: remove when skip_empty_bundle_production is split out
326+
domain_best_number: NumberFor<Block>,
327+
// TODO: remove Option when skip_empty_bundle_production is split out
328+
) -> sp_blockchain::Result<Option<(BundleHeaderFor<Block, CBlock>, Vec<ExtrinsicFor<Block>>)>>
329+
{
330+
let tx_range = self
331+
.consensus_client
332+
.runtime_api()
333+
.domain_tx_range(consensus_chain_best_hash, self.domain_id)
334+
.map_err(|error| {
335+
sp_blockchain::Error::Application(Box::from(format!(
336+
"Error getting tx range: {error}"
337+
)))
338+
})?;
339+
340+
let receipt = self
341+
.domain_bundle_proposer
342+
.load_next_receipt(domain_best_number_onchain, head_receipt_number)?;
270343

271-
let tx_range = self
344+
let (bundle_header, extrinsics) = self
345+
.domain_bundle_proposer
346+
.propose_bundle_at(proof_of_election.clone(), tx_range, operator_id, receipt)
347+
.await?;
348+
349+
// if there are no extrinsics and no receipts to confirm, skip the bundle
350+
if self.skip_empty_bundle_production
351+
&& extrinsics.is_empty()
352+
&& !self
272353
.consensus_client
273354
.runtime_api()
274-
.domain_tx_range(consensus_chain_best_hash, self.domain_id)
275-
.map_err(|error| {
276-
sp_blockchain::Error::Application(Box::from(format!(
277-
"Error getting tx range: {error}"
278-
)))
279-
})?;
280-
let (bundle_header, extrinsics) = self
281-
.domain_bundle_proposer
282-
.propose_bundle_at(proof_of_election, tx_range, operator_id, receipt)
283-
.await?;
284-
285-
// if there are no extrinsics and no receipts to confirm, skip the bundle
286-
if self.skip_empty_bundle_production
287-
&& extrinsics.is_empty()
288-
&& !self
289-
.consensus_client
290-
.runtime_api()
291-
.non_empty_er_exists(consensus_chain_best_hash, self.domain_id)?
292-
{
293-
tracing::warn!(
294-
?domain_best_number,
295-
"Skipping empty bundle production on slot {slot}"
296-
);
297-
return Ok(None);
298-
}
299-
300-
self.last_processed_slot.replace(slot);
301-
302-
info!("🔖 Producing bundle at slot {:?}", slot_info.slot);
355+
.non_empty_er_exists(consensus_chain_best_hash, self.domain_id)?
356+
{
357+
tracing::warn!(
358+
?domain_best_number,
359+
"Skipping empty bundle production on slot {}",
360+
slot_info.slot,
361+
);
362+
return Ok(None);
363+
}
303364

304-
let signature = {
305-
let to_sign = bundle_header.hash();
306-
self.sign(&operator_signing_key, to_sign.as_ref())?
307-
};
365+
self.last_processed_slot.replace(slot_info.slot);
308366

309-
let bundle = Bundle {
310-
sealed_header: SealedBundleHeader::new(bundle_header, signature),
311-
extrinsics,
312-
};
367+
Ok(Some((bundle_header, extrinsics)))
368+
}
313369

314-
// TODO: Re-enable the bundle gossip over X-Net when the compact bundle is supported.
315-
// if let Err(e) = self.bundle_sender.unbounded_send(signed_bundle.clone()) {
316-
// tracing::error!(error = ?e, "Failed to send transaction bundle");
317-
// }
370+
pub fn seal_bundle(
371+
&self,
372+
bundle_header: BundleHeaderFor<Block, CBlock>,
373+
operator_signing_key: &OperatorPublicKey,
374+
extrinsics: Vec<ExtrinsicFor<Block>>,
375+
) -> sp_blockchain::Result<DomainProposal<Block, CBlock>> {
376+
let signature = {
377+
let to_sign = bundle_header.hash();
378+
self.sign(operator_signing_key, to_sign.as_ref())?
379+
};
318380

319-
Ok(Some(DomainProposal::Bundle(bundle.into_opaque_bundle())))
320-
} else {
321-
Ok(None)
381+
let bundle = Bundle {
382+
sealed_header: SealedBundleHeader::new(bundle_header, signature),
383+
extrinsics,
384+
};
385+
386+
// TODO: Re-enable the bundle gossip over X-Net when the compact bundle is supported.
387+
// if let Err(e) = self.bundle_sender.unbounded_send(signed_bundle.clone()) {
388+
// tracing::error!(error = ?e, "Failed to send transaction bundle");
389+
// }
390+
391+
Ok(DomainProposal::Bundle(bundle.into_opaque_bundle()))
392+
}
393+
394+
pub async fn produce_bundle(
395+
&mut self,
396+
operator_id: OperatorId,
397+
slot_info: OperatorSlotInfo,
398+
) -> sp_blockchain::Result<Option<DomainProposal<Block, CBlock>>> {
399+
let domain_best_number = self.client.info().best_number;
400+
let consensus_chain_best_hash = self.consensus_client.info().best_hash;
401+
402+
let Some((
403+
domain_best_number_onchain,
404+
head_receipt_number,
405+
proof_of_election,
406+
operator_signing_key,
407+
)) = self.claim_bundle_slot(
408+
operator_id,
409+
&slot_info,
410+
domain_best_number,
411+
consensus_chain_best_hash,
412+
)?
413+
else {
414+
return Ok(None);
415+
};
416+
417+
if let Some(receipt) = self.prepare_receipt(
418+
&slot_info,
419+
domain_best_number_onchain,
420+
head_receipt_number,
421+
&proof_of_election,
422+
&operator_signing_key,
423+
)? {
424+
return Ok(Some(receipt));
322425
}
426+
427+
let Some((bundle_header, extrinsics)) = self
428+
.prepare_bundle(
429+
operator_id,
430+
&slot_info,
431+
consensus_chain_best_hash,
432+
domain_best_number_onchain,
433+
head_receipt_number,
434+
proof_of_election,
435+
domain_best_number,
436+
)
437+
.await?
438+
else {
439+
return Ok(None);
440+
};
441+
442+
info!("🔖 Producing bundle at slot {:?}", slot_info.slot);
443+
444+
let bundle = self.seal_bundle(bundle_header, &operator_signing_key, extrinsics)?;
445+
446+
Ok(Some(bundle))
323447
}
324448
}

0 commit comments

Comments
 (0)