@@ -13,7 +13,9 @@ use ic_protobuf::registry::subnet::v1::SubnetRecord;
13
13
use ic_registry_client_helpers:: subnet:: { NotarizationDelaySettings , SubnetRegistry } ;
14
14
use ic_replicated_state:: ReplicatedState ;
15
15
use ic_types:: {
16
- consensus:: { Block , BlockProposal , HasCommittee , HasHeight , HasRank , Rank } ,
16
+ consensus:: {
17
+ ecdsa:: EcdsaPayload , Block , BlockProposal , HasCommittee , HasHeight , HasRank , Rank ,
18
+ } ,
17
19
crypto:: {
18
20
threshold_sig:: ni_dkg:: { NiDkgTag , NiDkgTranscript } ,
19
21
CryptoHash , CryptoHashable , Signed ,
@@ -521,13 +523,52 @@ pub fn get_subnet_record(
521
523
}
522
524
}
523
525
526
+ /// Return the oldest registry version of transcripts in the given ECDSA summary payload that are
527
+ /// referenced by the given replicated state.
528
+ pub fn get_oldest_ecdsa_state_registry_version (
529
+ ecdsa : & EcdsaPayload ,
530
+ state : & ReplicatedState ,
531
+ ) -> Option < RegistryVersion > {
532
+ state
533
+ . sign_with_ecdsa_contexts ( )
534
+ . values ( )
535
+ . flat_map ( |context| context. matched_quadruple . as_ref ( ) )
536
+ . flat_map ( |( quadruple_id, _) | ecdsa. available_quadruples . get ( quadruple_id) )
537
+ . flat_map ( |quadruple| quadruple. get_refs ( ) )
538
+ . flat_map ( |transcript_ref| ecdsa. idkg_transcripts . get ( & transcript_ref. transcript_id ) )
539
+ . map ( |transcript| transcript. registry_version )
540
+ . min ( )
541
+ }
542
+
524
543
#[ cfg( test) ]
525
544
mod tests {
545
+ use std:: str:: FromStr ;
546
+
526
547
use super :: * ;
527
548
use ic_consensus_mocks:: { dependencies, Dependencies } ;
528
- use ic_test_utilities:: types:: ids:: node_test_id;
549
+ use ic_ic00_types:: EcdsaKeyId ;
550
+ use ic_replicated_state:: metadata_state:: subnet_call_context_manager:: SignWithEcdsaContext ;
551
+ use ic_test_utilities:: {
552
+ mock_time,
553
+ state:: ReplicatedStateBuilder ,
554
+ types:: {
555
+ ids:: { node_test_id, subnet_test_id} ,
556
+ messages:: RequestBuilder ,
557
+ } ,
558
+ } ;
529
559
use ic_types:: {
530
- crypto:: { ThresholdSigShare , ThresholdSigShareOf } ,
560
+ consensus:: ecdsa:: {
561
+ EcdsaKeyTranscript , EcdsaUIDGenerator , KeyTranscriptCreation , MaskedTranscript ,
562
+ PreSignatureQuadrupleRef , QuadrupleId , UnmaskedTranscript ,
563
+ } ,
564
+ crypto:: {
565
+ canister_threshold_sig:: idkg:: {
566
+ IDkgMaskedTranscriptOrigin , IDkgReceivers , IDkgTranscript , IDkgTranscriptId ,
567
+ IDkgTranscriptType , IDkgUnmaskedTranscriptOrigin ,
568
+ } ,
569
+ ThresholdSigShare , ThresholdSigShareOf ,
570
+ } ,
571
+ messages:: CallbackId ,
531
572
signature:: ThresholdSignatureShare ,
532
573
} ;
533
574
@@ -665,4 +706,172 @@ mod tests {
665
706
assert_eq ! ( round_robin. call_next( & calls) , vec![ 1 ] ) ;
666
707
assert_eq ! ( round_robin. call_next( & calls) , vec![ 1 ] ) ;
667
708
}
709
+
710
+ fn empty_ecdsa_payload ( ) -> EcdsaPayload {
711
+ EcdsaPayload {
712
+ signature_agreements : BTreeMap :: new ( ) ,
713
+ available_quadruples : BTreeMap :: new ( ) ,
714
+ ongoing_signatures : BTreeMap :: new ( ) ,
715
+ quadruples_in_creation : BTreeMap :: new ( ) ,
716
+ uid_generator : EcdsaUIDGenerator :: new ( subnet_test_id ( 0 ) , Height :: new ( 0 ) ) ,
717
+ idkg_transcripts : BTreeMap :: new ( ) ,
718
+ ongoing_xnet_reshares : BTreeMap :: new ( ) ,
719
+ xnet_reshare_agreements : BTreeMap :: new ( ) ,
720
+ key_transcript : EcdsaKeyTranscript {
721
+ current : None ,
722
+ next_in_creation : KeyTranscriptCreation :: Begin ,
723
+ key_id : EcdsaKeyId :: from_str ( "Secp256k1:some_key" ) . unwrap ( ) ,
724
+ } ,
725
+ }
726
+ }
727
+
728
+ fn fake_transcript ( id : IDkgTranscriptId , registry_version : RegistryVersion ) -> IDkgTranscript {
729
+ IDkgTranscript {
730
+ transcript_id : id,
731
+ receivers : IDkgReceivers :: new ( BTreeSet :: from_iter ( [ node_test_id ( 0 ) ] ) ) . unwrap ( ) ,
732
+ registry_version,
733
+ verified_dealings : Default :: default ( ) ,
734
+ transcript_type : IDkgTranscriptType :: Unmasked (
735
+ IDkgUnmaskedTranscriptOrigin :: ReshareMasked ( fake_transcript_id ( 0 ) ) ,
736
+ ) ,
737
+ algorithm_id : ic_types:: crypto:: AlgorithmId :: EcdsaSecp256k1 ,
738
+ internal_transcript_raw : vec ! [ ] ,
739
+ }
740
+ }
741
+
742
+ fn fake_transcript_id ( id : u64 ) -> IDkgTranscriptId {
743
+ IDkgTranscriptId :: new ( subnet_test_id ( 0 ) , id, Height :: from ( 0 ) )
744
+ }
745
+
746
+ // Create a fake quadruple, it will use transcripts with ids
747
+ // id, id+1, id+2, and id+3.
748
+ fn fake_quadruple ( id : u64 ) -> PreSignatureQuadrupleRef {
749
+ let temp_rv = RegistryVersion :: from ( 0 ) ;
750
+ let kappa_unmasked = fake_transcript ( fake_transcript_id ( id) , temp_rv) ;
751
+ let mut lambda_masked = kappa_unmasked. clone ( ) ;
752
+ lambda_masked. transcript_id = fake_transcript_id ( id + 1 ) ;
753
+ lambda_masked. transcript_type =
754
+ IDkgTranscriptType :: Masked ( IDkgMaskedTranscriptOrigin :: Random ) ;
755
+ let mut kappa_times_lambda = lambda_masked. clone ( ) ;
756
+ kappa_times_lambda. transcript_id = fake_transcript_id ( id + 2 ) ;
757
+ let mut key_times_lambda = lambda_masked. clone ( ) ;
758
+ key_times_lambda. transcript_id = fake_transcript_id ( id + 3 ) ;
759
+ let h = Height :: from ( 0 ) ;
760
+ PreSignatureQuadrupleRef {
761
+ kappa_unmasked_ref : UnmaskedTranscript :: try_from ( ( h, & kappa_unmasked) ) . unwrap ( ) ,
762
+ lambda_masked_ref : MaskedTranscript :: try_from ( ( h, & lambda_masked) ) . unwrap ( ) ,
763
+ kappa_times_lambda_ref : MaskedTranscript :: try_from ( ( h, & kappa_times_lambda) ) . unwrap ( ) ,
764
+ key_times_lambda_ref : MaskedTranscript :: try_from ( ( h, & key_times_lambda) ) . unwrap ( ) ,
765
+ key_unmasked_ref : None ,
766
+ }
767
+ }
768
+
769
+ fn fake_context ( quadruple_id : Option < QuadrupleId > ) -> SignWithEcdsaContext {
770
+ SignWithEcdsaContext {
771
+ request : RequestBuilder :: new ( ) . build ( ) ,
772
+ key_id : EcdsaKeyId :: from_str ( "Secp256k1:some_key" ) . unwrap ( ) ,
773
+ message_hash : [ 0 ; 32 ] ,
774
+ derivation_path : vec ! [ ] ,
775
+ pseudo_random_id : [ 0 ; 32 ] ,
776
+ matched_quadruple : quadruple_id. map ( |qid| ( qid, Height :: from ( 0 ) ) ) ,
777
+ nonce : None ,
778
+ batch_time : mock_time ( ) ,
779
+ }
780
+ }
781
+
782
+ fn fake_state_with_contexts ( contexts : Vec < SignWithEcdsaContext > ) -> ReplicatedState {
783
+ let mut state = ReplicatedStateBuilder :: default ( ) . build ( ) ;
784
+ let iter = contexts
785
+ . into_iter ( )
786
+ . enumerate ( )
787
+ . map ( |( i, context) | ( CallbackId :: from ( i as u64 ) , context) ) ;
788
+ state
789
+ . metadata
790
+ . subnet_call_context_manager
791
+ . sign_with_ecdsa_contexts = BTreeMap :: from_iter ( iter) ;
792
+ state
793
+ }
794
+
795
+ // Create an ECDSA payload with 10 quadruples, each using registry version 2, 3 or 4.
796
+ fn ecdsa_payload_with_quadruples ( ) -> EcdsaPayload {
797
+ let mut ecdsa = empty_ecdsa_payload ( ) ;
798
+ let key_id = ecdsa. key_transcript . key_id . clone ( ) ;
799
+ let mut rvs = [
800
+ RegistryVersion :: from ( 2 ) ,
801
+ RegistryVersion :: from ( 3 ) ,
802
+ RegistryVersion :: from ( 4 ) ,
803
+ ]
804
+ . into_iter ( )
805
+ . cycle ( ) ;
806
+ for i in ( 0 ..40 ) . step_by ( 4 ) {
807
+ let quadruple = fake_quadruple ( i as u64 ) ;
808
+ let rv = rvs. next ( ) . unwrap ( ) ;
809
+ for r in quadruple. get_refs ( ) {
810
+ ecdsa
811
+ . idkg_transcripts
812
+ . insert ( r. transcript_id , fake_transcript ( r. transcript_id , rv) ) ;
813
+ }
814
+ ecdsa
815
+ . available_quadruples
816
+ . insert ( QuadrupleId ( i as u64 , Some ( key_id. clone ( ) ) ) , quadruple) ;
817
+ }
818
+ ecdsa
819
+ }
820
+
821
+ #[ test]
822
+ fn test_empty_state_should_return_no_registry_version ( ) {
823
+ let ecdsa = ecdsa_payload_with_quadruples ( ) ;
824
+ let state = fake_state_with_contexts ( vec ! [ ] ) ;
825
+ assert_eq ! (
826
+ None ,
827
+ get_oldest_ecdsa_state_registry_version( & ecdsa, & state)
828
+ ) ;
829
+ }
830
+
831
+ #[ test]
832
+ fn test_state_without_matches_should_return_no_registry_version ( ) {
833
+ let ecdsa = ecdsa_payload_with_quadruples ( ) ;
834
+ let state = fake_state_with_contexts ( vec ! [ fake_context( None ) ] ) ;
835
+ assert_eq ! (
836
+ None ,
837
+ get_oldest_ecdsa_state_registry_version( & ecdsa, & state)
838
+ ) ;
839
+ }
840
+
841
+ #[ test]
842
+ fn test_should_return_oldest_registry_version ( ) {
843
+ let ecdsa = ecdsa_payload_with_quadruples ( ) ;
844
+ // create contexts for all quadruples, but only create a match for
845
+ // quadruples with registry version >= 3 (not 2!). Thus the oldest
846
+ // registry version referenced by the state should be 3.
847
+ let contexts = ecdsa
848
+ . available_quadruples
849
+ . iter ( )
850
+ . map ( |( id, quad) | {
851
+ let t_id = quad. lambda_masked_ref . as_ref ( ) . transcript_id ;
852
+ let transcript = ecdsa. idkg_transcripts . get ( & t_id) . unwrap ( ) ;
853
+ ( transcript. registry_version . get ( ) >= 3 ) . then_some ( id. clone ( ) )
854
+ } )
855
+ . map ( fake_context)
856
+ . collect ( ) ;
857
+ let state = fake_state_with_contexts ( contexts) ;
858
+ assert_eq ! (
859
+ Some ( RegistryVersion :: from( 3 ) ) ,
860
+ get_oldest_ecdsa_state_registry_version( & ecdsa, & state)
861
+ ) ;
862
+
863
+ let mut ecdsa_without_transcripts = ecdsa. clone ( ) ;
864
+ ecdsa_without_transcripts. idkg_transcripts = BTreeMap :: new ( ) ;
865
+ assert_eq ! (
866
+ None ,
867
+ get_oldest_ecdsa_state_registry_version( & ecdsa_without_transcripts, & state)
868
+ ) ;
869
+
870
+ let mut ecdsa_without_quadruples = ecdsa. clone ( ) ;
871
+ ecdsa_without_quadruples. available_quadruples = BTreeMap :: new ( ) ;
872
+ assert_eq ! (
873
+ None ,
874
+ get_oldest_ecdsa_state_registry_version( & ecdsa_without_quadruples, & state)
875
+ ) ;
876
+ }
668
877
}
0 commit comments