@@ -582,7 +582,7 @@ impl State {
582582 . ok_or ( AmountOverflowError ) ?;
583583 }
584584 if let InPoint :: Withdrawal { .. } = spent_output. inpoint {
585- total_withdrawal_stxo_value = total_deposit_stxo_value
585+ total_withdrawal_stxo_value = total_withdrawal_stxo_value
586586 . checked_add ( spent_output. output . get_value ( ) )
587587 . ok_or ( AmountOverflowError ) ?;
588588 }
@@ -692,28 +692,38 @@ mod tests {
692692 types:: {
693693 Address , AuthorizedTransaction , BitName , BitcoinOutputContent ,
694694 FilledOutput , FilledOutputContent , FilledTransaction , Hash ,
695- MutableBitNameData , OutPoint , OutPointKey , Output , OutputContent ,
696- Transaction , TxData , Txid , VerifyingKey ,
695+ InPoint , MutableBitNameData , OutPoint , OutPointKey , Output ,
696+ OutputContent , SpentOutput , Transaction , TxData , Txid ,
697+ VerifyingKey ,
697698 } ,
698699 } ;
699700
700- /// Open a fresh state in a unique temporary directory.
701- fn new_state ( dir_name_suffix : & str ) -> ( sneed:: Env , State ) {
702- let dir_name = if dir_name_suffix. is_empty ( ) {
703- format ! ( "plain_bitnames_{}" , std:: process:: id( ) )
704- } else {
705- format ! ( "plain_bitnames_{dir_name_suffix}_{}" , std:: process:: id( ) )
706- } ;
707- let path = std:: env:: temp_dir ( ) . join ( dir_name) ;
708- let _remove_result = std:: fs:: remove_dir_all ( & path) ;
709- std:: fs:: create_dir_all ( & path) . unwrap ( ) ;
710- let env = {
711- let mut opts = heed:: EnvOpenOptions :: new ( ) ;
712- opts. map_size ( 10 * 1024 * 1024 ) . max_dbs ( State :: NUM_DBS ) ;
713- unsafe { sneed:: Env :: open ( & opts, & path) } . unwrap ( )
714- } ;
715- let state = State :: new ( & env) . unwrap ( ) ;
716- ( env, state)
701+ fn temp_env_path ( test_name : & str ) -> anyhow:: Result < std:: path:: PathBuf > {
702+ let mut path = std:: env:: temp_dir ( ) ;
703+ let nanos = std:: time:: SystemTime :: now ( )
704+ . duration_since ( std:: time:: UNIX_EPOCH ) ?
705+ . as_nanos ( ) ;
706+ path. push ( format ! (
707+ "bitnames-{test_name}-{}-{nanos}" ,
708+ std:: process:: id( )
709+ ) ) ;
710+ Ok ( path)
711+ }
712+
713+ // open a fresh state-backed env in a unique temp dir
714+ fn temp_env ( test_name : & str ) -> anyhow:: Result < sneed:: Env > {
715+ let path = temp_env_path ( test_name) ?;
716+ std:: fs:: create_dir_all ( & path) ?;
717+ let mut opts = heed:: EnvOpenOptions :: new ( ) ;
718+ opts. map_size ( 16 * 1024 * 1024 ) . max_dbs ( State :: NUM_DBS ) ;
719+ let res = unsafe { sneed:: Env :: open ( & opts, & path) } ?;
720+ Ok ( res)
721+ }
722+
723+ fn fresh_state ( test_name : & str ) -> anyhow:: Result < ( sneed:: Env , State ) > {
724+ let env = temp_env ( test_name) ?;
725+ let state = State :: new ( & env) ?;
726+ Ok ( ( env, state) )
717727 }
718728
719729 /// Fund `address` with a single bitcoin UTXO of `value` sats, returning its
@@ -782,8 +792,9 @@ mod tests {
782792 /// spent UTXOs silently skips the unauthorized input, allowing any UTXO to
783793 /// be spent without a signature.
784794 #[ test]
785- fn validate_transaction_rejects_missing_authorization ( ) {
786- let ( env, state) = new_state ( "auth_count" ) ;
795+ fn validate_transaction_rejects_missing_authorization ( ) -> anyhow:: Result < ( ) >
796+ {
797+ let ( env, state) = fresh_state ( "auth_count" ) ?;
787798 let signing_key = SigningKey :: from_bytes ( & [ 1u8 ; 32 ] ) ;
788799 let verifying_key: VerifyingKey = signing_key. verifying_key ( ) . into ( ) ;
789800 let address = authorization:: get_address ( & verifying_key) ;
@@ -804,11 +815,11 @@ mod tests {
804815 transaction : transaction. clone ( ) ,
805816 authorizations : Vec :: new ( ) ,
806817 } ;
807- let rotxn = env. read_txn ( ) . unwrap ( ) ;
818+ let rotxn = env. read_txn ( ) ? ;
808819 let err = state
809820 . validate_transaction ( & rotxn, & unauthorized)
810821 . expect_err ( "tx with no authorizations must be rejected" ) ;
811- assert ! (
822+ anyhow :: ensure !(
812823 matches!(
813824 err,
814825 Error :: WrongNumberOfAuthorizations {
@@ -821,21 +832,22 @@ mod tests {
821832
822833 // The same transaction with a valid authorization is accepted.
823834 let authorized =
824- authorization:: authorize ( & [ ( address, & signing_key) ] , transaction)
825- . unwrap ( ) ;
835+ authorization:: authorize ( & [ ( address, & signing_key) ] , transaction) ?;
826836 state
827837 . validate_transaction ( & rotxn, & authorized)
828838 . expect ( "correctly authorized tx should validate" ) ;
839+ Ok ( ( ) )
829840 }
830841
831842 /// A registration whose spent reservation does not commit to the
832843 /// registered name must be rejected. Otherwise it passes validation and
833844 /// later panics in `apply_registration`, which fails to find the
834845 /// reservation to burn.
835846 #[ test]
836- fn validate_bitnames_rejects_registration_without_matching_reservation ( ) {
837- let ( env, state) = new_state ( "registration" ) ;
838- let rotxn = env. read_txn ( ) . unwrap ( ) ;
847+ fn validate_bitnames_rejects_registration_without_matching_reservation ( )
848+ -> anyhow:: Result < ( ) > {
849+ let ( env, state) = fresh_state ( "registration" ) ?;
850+ let rotxn = env. read_txn ( ) ?;
839851 let name_hash = BitName ( [ 7 ; 32 ] ) ;
840852 let revealed_nonce: Hash = [ 3 ; 32 ] ;
841853 let implied_commitment: Hash =
@@ -849,7 +861,7 @@ mod tests {
849861 let err = state. validate_bitnames ( & rotxn, & tx) . expect_err (
850862 "registration without a matching reservation must be rejected" ,
851863 ) ;
852- assert ! (
864+ anyhow :: ensure !(
853865 matches!(
854866 err,
855867 Error :: BitName (
@@ -864,5 +876,91 @@ mod tests {
864876 state. validate_bitnames ( & rotxn, & tx) . expect (
865877 "registration burning the matching reservation should validate" ,
866878 ) ;
879+ Ok ( ( ) )
880+ }
881+
882+ #[ test]
883+ fn sidechain_wealth ( ) -> anyhow:: Result < ( ) > {
884+ use std:: str:: FromStr ;
885+
886+ use bitcoin:: hashes:: Hash as _;
887+
888+ let value_output = |sats : u64 | FilledOutput {
889+ address : Address :: ALL_ZEROS ,
890+ content : FilledOutputContent :: Bitcoin ( BitcoinOutputContent (
891+ bitcoin:: Amount :: from_sat ( sats) ,
892+ ) ) ,
893+ memo : Vec :: new ( ) ,
894+ } ;
895+ let ( env, state) = fresh_state ( "sidechain-wealth" ) ?;
896+ {
897+ let mut rwtxn = env. write_txn ( ) ?;
898+
899+ // One unspent DEPOSIT UTXO: 50 sats.
900+ let deposit_utxo_op = OutPoint :: Deposit ( bitcoin:: OutPoint {
901+ txid : bitcoin:: Txid :: from_str (
902+ "0000000000000000000000000000000000000000000000000000000000000001" ,
903+ ) ?,
904+ vout : 0 ,
905+ } ) ;
906+ state. utxos . put (
907+ & mut rwtxn,
908+ & OutPointKey :: from ( & deposit_utxo_op) ,
909+ & value_output ( 50 ) ,
910+ ) ?;
911+
912+ // Two spent DEPOSIT STXOs: 100 + 100 sats.
913+ for ( i, sats) in [ ( 2u8 , 100u64 ) , ( 3u8 , 100u64 ) ] {
914+ let op = OutPoint :: Deposit ( bitcoin:: OutPoint {
915+ txid : bitcoin:: Txid :: from_byte_array ( [ i; 32 ] ) ,
916+ vout : 0 ,
917+ } ) ;
918+ let stxo = SpentOutput {
919+ output : value_output ( sats) ,
920+ inpoint : InPoint :: Regular {
921+ txid : [ i; 32 ] . into ( ) ,
922+ vin : 0 ,
923+ } ,
924+ } ;
925+ state
926+ . stxos
927+ . put ( & mut rwtxn, & OutPointKey :: from ( & op) , & stxo) ?;
928+ }
929+
930+ // Two WITHDRAWAL STXOs: 10 + 10 sats
931+ for ( i, sats) in [ ( 4u8 , 10u64 ) , ( 5u8 , 10u64 ) ] {
932+ let op = OutPoint :: Regular {
933+ txid : [ i; 32 ] . into ( ) ,
934+ vout : 0 ,
935+ } ;
936+ let stxo = SpentOutput {
937+ output : value_output ( sats) ,
938+ inpoint : InPoint :: Withdrawal {
939+ m6id : crate :: types:: M6id (
940+ bitcoin:: Txid :: from_byte_array ( [ i; 32 ] ) ,
941+ ) ,
942+ } ,
943+ } ;
944+ state
945+ . stxos
946+ . put ( & mut rwtxn, & OutPointKey :: from ( & op) , & stxo) ?;
947+ }
948+
949+ rwtxn. commit ( ) ?;
950+ }
951+
952+ let rotxn = env. read_txn ( ) ?;
953+ let sidechain_wealth = state. sidechain_wealth ( & rotxn) ?;
954+
955+ // Correct value: deposit UTXO 50 + deposit STXOs 200 - withdrawal
956+ // STXOs 20 = 230 sats.
957+ let expected_sidechain_wealth = bitcoin:: Amount :: from_sat ( 230 ) ;
958+ anyhow:: ensure!(
959+ sidechain_wealth == expected_sidechain_wealth,
960+ "Expected sidechain wealth ({}), but computed ({})" ,
961+ expected_sidechain_wealth,
962+ sidechain_wealth,
963+ ) ;
964+ Ok ( ( ) )
867965 }
868966}
0 commit comments