@@ -10,8 +10,9 @@ use sp_blockchain::HeaderBackend;
1010use sp_consensus_slots:: Slot ;
1111use sp_domains:: core_api:: DomainCoreApi ;
1212use 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} ;
1617use sp_keystore:: KeystorePtr ;
1718use sp_messenger:: MessengerApi ;
@@ -22,6 +23,19 @@ use std::sync::Arc;
2223use subspace_runtime_primitives:: Balance ;
2324use 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+
2539type 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