@@ -670,3 +670,103 @@ impl Watchable<()> for State {
670670 tokio_stream:: wrappers:: WatchStream :: new ( self . tip . watch ( ) . clone ( ) )
671671 }
672672}
673+
674+ #[ cfg( test) ]
675+ mod tests {
676+ use super :: { Error , State , error} ;
677+ use crate :: types:: {
678+ Address , BitName , FilledOutput , FilledOutputContent , FilledTransaction ,
679+ Hash , MutableBitNameData , OutPoint , Output , OutputContent , Transaction ,
680+ TxData , Txid ,
681+ } ;
682+
683+ /// Open a fresh state in a unique temporary directory.
684+ fn new_state ( ) -> ( sneed:: Env , State ) {
685+ let path = std:: env:: temp_dir ( ) . join ( format ! (
686+ "plain_bitnames_registration_{}" ,
687+ std:: process:: id( )
688+ ) ) ;
689+ let _remove_result = std:: fs:: remove_dir_all ( & path) ;
690+ std:: fs:: create_dir_all ( & path) . unwrap ( ) ;
691+ let env = {
692+ let mut opts = heed:: EnvOpenOptions :: new ( ) ;
693+ opts. map_size ( 10 * 1024 * 1024 ) . max_dbs ( State :: NUM_DBS ) ;
694+ unsafe { sneed:: Env :: open ( & opts, & path) } . unwrap ( )
695+ } ;
696+ let state = State :: new ( & env) . unwrap ( ) ;
697+ ( env, state)
698+ }
699+
700+ /// Build a BitName registration that registers `name_hash` with
701+ /// `revealed_nonce`, while spending a single reservation that commits to
702+ /// `reservation_commitment`.
703+ fn registration_tx (
704+ name_hash : BitName ,
705+ revealed_nonce : Hash ,
706+ reservation_commitment : Hash ,
707+ ) -> FilledTransaction {
708+ let address = Address ( [ 0 ; 20 ] ) ;
709+ let mut transaction = Transaction :: new (
710+ vec ! [ OutPoint :: Regular {
711+ txid: Txid ( [ 0 ; 32 ] ) ,
712+ vout: 0 ,
713+ } ] ,
714+ vec ! [ Output :: new( address, OutputContent :: BitName ) ] ,
715+ ) ;
716+ transaction. data = Some ( TxData :: BitNameRegistration {
717+ name_hash,
718+ revealed_nonce,
719+ bitname_data : Box :: new ( MutableBitNameData :: default ( ) ) ,
720+ } ) ;
721+ let reservation = FilledOutput :: new (
722+ address,
723+ FilledOutputContent :: BitNameReservation (
724+ Txid ( [ 0 ; 32 ] ) ,
725+ reservation_commitment,
726+ ) ,
727+ ) ;
728+ FilledTransaction {
729+ transaction,
730+ spent_utxos : vec ! [ reservation] ,
731+ }
732+ }
733+
734+ /// A registration whose spent reservation does not commit to the
735+ /// registered name must be rejected. Otherwise it passes validation and
736+ /// later panics in `apply_registration`, which fails to find the
737+ /// reservation to burn.
738+ #[ test]
739+ fn validate_bitnames_rejects_registration_without_matching_reservation ( ) {
740+ let ( env, state) = new_state ( ) ;
741+ let rotxn = env. read_txn ( ) . unwrap ( ) ;
742+ let name_hash = BitName ( [ 7 ; 32 ] ) ;
743+ let revealed_nonce: Hash = [ 3 ; 32 ] ;
744+ let implied_commitment: Hash =
745+ blake3:: keyed_hash ( & revealed_nonce, & name_hash. 0 ) . into ( ) ;
746+
747+ // The reservation commits to something other than the registered name.
748+ let mismatched_commitment: Hash = [ 0 ; 32 ] ;
749+ assert_ne ! ( mismatched_commitment, implied_commitment) ;
750+ let tx =
751+ registration_tx ( name_hash, revealed_nonce, mismatched_commitment) ;
752+ let err = state. validate_bitnames ( & rotxn, & tx) . expect_err (
753+ "registration without a matching reservation must be rejected" ,
754+ ) ;
755+ assert ! (
756+ matches!(
757+ err,
758+ Error :: BitName (
759+ error:: BitName :: NoReservationForRegistration { bitname }
760+ ) if bitname == name_hash
761+ ) ,
762+ "unexpected error: {err:?}"
763+ ) ;
764+
765+ // The same registration burning the matching reservation is accepted.
766+ let tx =
767+ registration_tx ( name_hash, revealed_nonce, implied_commitment) ;
768+ state. validate_bitnames ( & rotxn, & tx) . expect (
769+ "registration burning the matching reservation should validate" ,
770+ ) ;
771+ }
772+ }
0 commit comments