@@ -34,7 +34,7 @@ use polkadot_primitives::{
3434 ValidationCodeHash ,
3535} ;
3636use sc_consensus_aura:: { standalone as aura_internal, AuraApi } ;
37- use sp_api:: ProvideRuntimeApi ;
37+ use sp_api:: { ApiExt , ProvideRuntimeApi } ;
3838use sp_core:: Pair ;
3939use sp_keystore:: KeystorePtr ;
4040use sp_timestamp:: Timestamp ;
@@ -160,7 +160,8 @@ async fn cores_scheduled_for_para(
160160// Checks if we own the slot at the given block and whether there
161161// is space in the unincluded segment.
162162async fn can_build_upon < Block : BlockT , Client , P > (
163- slot : Slot ,
163+ para_slot : Slot ,
164+ relay_slot : Slot ,
164165 timestamp : Timestamp ,
165166 parent_hash : Block :: Hash ,
166167 included_block : Block :: Hash ,
@@ -169,25 +170,35 @@ async fn can_build_upon<Block: BlockT, Client, P>(
169170) -> Option < SlotClaim < P :: Public > >
170171where
171172 Client : ProvideRuntimeApi < Block > ,
172- Client :: Api : AuraApi < Block , P :: Public > + AuraUnincludedSegmentApi < Block > ,
173+ Client :: Api : AuraApi < Block , P :: Public > + AuraUnincludedSegmentApi < Block > + ApiExt < Block > ,
173174 P : Pair ,
174175 P :: Public : Codec ,
175176 P :: Signature : Codec ,
176177{
177178 let runtime_api = client. runtime_api ( ) ;
178179 let authorities = runtime_api. authorities ( parent_hash) . ok ( ) ?;
179- let author_pub = aura_internal:: claim_slot :: < P > ( slot , & authorities, keystore) . await ?;
180+ let author_pub = aura_internal:: claim_slot :: < P > ( para_slot , & authorities, keystore) . await ?;
180181
181- // Here we lean on the property that building on an empty unincluded segment must always
182- // be legal. Skipping the runtime API query here allows us to seamlessly run this
183- // collator against chains which have not yet upgraded their runtime.
184- if parent_hash != included_block &&
185- !runtime_api . can_build_upon ( parent_hash , included_block , slot ) . ok ( ) ?
186- {
187- return None
182+ // This function is typically called when we want to build block N. At that point, the
183+ // unincluded segment in the runtime is unaware of the hash of block N-1. If the unincluded
184+ // segment in the runtime is full, but block N-1 is the included block, the unincluded segment
185+ // should have length 0 and we can build. Since the hash is not available to the runtime
186+ // however, we need this extra check here.
187+ if parent_hash == included_block {
188+ return Some ( SlotClaim :: unchecked :: < P > ( author_pub , para_slot , timestamp ) ) ;
188189 }
189190
190- Some ( SlotClaim :: unchecked :: < P > ( author_pub, slot, timestamp) )
191+ let api_version = runtime_api
192+ . api_version :: < dyn AuraUnincludedSegmentApi < Block > > ( parent_hash)
193+ . ok ( )
194+ . flatten ( ) ?;
195+
196+ let slot = if api_version > 1 { relay_slot } else { para_slot } ;
197+
198+ runtime_api
199+ . can_build_upon ( parent_hash, included_block, slot)
200+ . ok ( ) ?
201+ . then ( || SlotClaim :: unchecked :: < P > ( author_pub, para_slot, timestamp) )
191202}
192203
193204/// Use [`cumulus_client_consensus_common::find_potential_parents`] to find parachain blocks that
@@ -239,3 +250,116 @@ where
239250 . max_by_key ( |a| a. depth )
240251 . map ( |parent| ( included_block, parent) )
241252}
253+
254+ #[ cfg( test) ]
255+ mod tests {
256+ use crate :: collators:: can_build_upon;
257+ use codec:: Encode ;
258+ use cumulus_primitives_aura:: Slot ;
259+ use cumulus_primitives_core:: BlockT ;
260+ use cumulus_relay_chain_interface:: PHash ;
261+ use cumulus_test_client:: {
262+ runtime:: { Block , Hash } ,
263+ Client , DefaultTestClientBuilderExt , InitBlockBuilder , TestClientBuilder ,
264+ TestClientBuilderExt ,
265+ } ;
266+ use cumulus_test_relay_sproof_builder:: RelayStateSproofBuilder ;
267+ use polkadot_primitives:: HeadData ;
268+ use sc_consensus:: { BlockImport , BlockImportParams , ForkChoiceStrategy } ;
269+ use sp_consensus:: BlockOrigin ;
270+ use sp_keystore:: { Keystore , KeystorePtr } ;
271+ use sp_timestamp:: Timestamp ;
272+ use std:: sync:: Arc ;
273+
274+ async fn import_block < I : BlockImport < Block > > (
275+ importer : & I ,
276+ block : Block ,
277+ origin : BlockOrigin ,
278+ import_as_best : bool ,
279+ ) {
280+ let ( header, body) = block. deconstruct ( ) ;
281+
282+ let mut block_import_params = BlockImportParams :: new ( origin, header) ;
283+ block_import_params. fork_choice = Some ( ForkChoiceStrategy :: Custom ( import_as_best) ) ;
284+ block_import_params. body = Some ( body) ;
285+ importer. import_block ( block_import_params) . await . unwrap ( ) ;
286+ }
287+
288+ fn sproof_with_parent_by_hash ( client : & Client , hash : PHash ) -> RelayStateSproofBuilder {
289+ let header = client. header ( hash) . ok ( ) . flatten ( ) . expect ( "No header for parent block" ) ;
290+ let included = HeadData ( header. encode ( ) ) ;
291+ let mut builder = RelayStateSproofBuilder :: default ( ) ;
292+ builder. para_id = cumulus_test_client:: runtime:: PARACHAIN_ID . into ( ) ;
293+ builder. included_para_head = Some ( included) ;
294+
295+ builder
296+ }
297+ async fn build_and_import_block ( client : & Client , included : Hash ) -> Block {
298+ let sproof = sproof_with_parent_by_hash ( client, included) ;
299+
300+ let block_builder = client. init_block_builder ( None , sproof) . block_builder ;
301+
302+ let block = block_builder. build ( ) . unwrap ( ) . block ;
303+
304+ let origin = BlockOrigin :: NetworkInitialSync ;
305+ import_block ( client, block. clone ( ) , origin, true ) . await ;
306+ block
307+ }
308+
309+ fn set_up_components ( ) -> ( Arc < Client > , KeystorePtr ) {
310+ let keystore = Arc :: new ( sp_keystore:: testing:: MemoryKeystore :: new ( ) ) as Arc < _ > ;
311+ for key in sp_keyring:: Sr25519Keyring :: iter ( ) {
312+ Keystore :: sr25519_generate_new (
313+ & * keystore,
314+ sp_application_crypto:: key_types:: AURA ,
315+ Some ( & key. to_seed ( ) ) ,
316+ )
317+ . expect ( "Can insert key into MemoryKeyStore" ) ;
318+ }
319+ ( Arc :: new ( TestClientBuilder :: new ( ) . build ( ) ) , keystore)
320+ }
321+
322+ /// This tests a special scenario where the unincluded segment in the runtime
323+ /// is full. We are calling `can_build_upon`, passing the last built block as the
324+ /// included one. In the runtime we will not find the hash of the included block in the
325+ /// unincluded segment. The `can_build_upon` runtime API would therefore return `false`, but
326+ /// we are ensuring on the node side that we are are always able to build on the included block.
327+ #[ tokio:: test]
328+ async fn test_can_build_upon ( ) {
329+ let ( client, keystore) = set_up_components ( ) ;
330+
331+ let genesis_hash = client. chain_info ( ) . genesis_hash ;
332+ let mut last_hash = genesis_hash;
333+
334+ // Fill up the unincluded segment tracker in the runtime.
335+ while can_build_upon :: < _ , _ , sp_consensus_aura:: sr25519:: AuthorityPair > (
336+ Slot :: from ( u64:: MAX ) ,
337+ Slot :: from ( u64:: MAX ) ,
338+ Timestamp :: default ( ) ,
339+ last_hash,
340+ genesis_hash,
341+ & * client,
342+ & keystore,
343+ )
344+ . await
345+ . is_some ( )
346+ {
347+ let block = build_and_import_block ( & client, genesis_hash) . await ;
348+ last_hash = block. header ( ) . hash ( ) ;
349+ }
350+
351+ // Blocks were built with the genesis hash set as included block.
352+ // We call `can_build_upon` with the last built block as the included block.
353+ let result = can_build_upon :: < _ , _ , sp_consensus_aura:: sr25519:: AuthorityPair > (
354+ Slot :: from ( u64:: MAX ) ,
355+ Slot :: from ( u64:: MAX ) ,
356+ Timestamp :: default ( ) ,
357+ last_hash,
358+ last_hash,
359+ & * client,
360+ & keystore,
361+ )
362+ . await ;
363+ assert ! ( result. is_some( ) ) ;
364+ }
365+ }
0 commit comments