1+ use assert_matches:: assert_matches;
12use miden_lib:: account:: wallets:: BasicWallet ;
23use miden_lib:: errors:: tx_kernel_errors:: ERR_TX_ALREADY_EXECUTED ;
34use miden_objects:: account:: {
@@ -19,8 +20,13 @@ use miden_objects::transaction::OutputNote;
1920use miden_objects:: vm:: AdviceMap ;
2021use miden_objects:: { Felt , Hasher , Word } ;
2122use miden_testing:: { Auth , MockChainBuilder , assert_transaction_executor_error} ;
22- use miden_tx:: TransactionExecutorError ;
2323use miden_tx:: auth:: { BasicAuthenticator , SigningInputs , TransactionAuthenticator } ;
24+ use miden_tx:: {
25+ NoteConsumptionChecker ,
26+ NoteConsumptionStatus ,
27+ TransactionExecutor ,
28+ TransactionExecutorError ,
29+ } ;
2430use rand:: SeedableRng ;
2531use rand_chacha:: ChaCha20Rng ;
2632
@@ -325,3 +331,102 @@ async fn test_multisig_replay_protection() -> anyhow::Result<()> {
325331
326332 Ok ( ( ) )
327333}
334+
335+ #[ tokio:: test]
336+ async fn test_check_note_consumability_multisig ( ) -> anyhow:: Result < ( ) > {
337+ // Setup keys and authenticators
338+ let ( _secret_keys, public_keys, authenticators) = setup_keys_and_authenticators ( 2 , 2 ) ?;
339+
340+ // Create multisig account
341+ let multisig_account = create_multisig_account ( 2 , & public_keys, 10 ) ?;
342+
343+ let mut mock_chain_builder =
344+ MockChainBuilder :: with_accounts ( [ multisig_account. clone ( ) ] ) . unwrap ( ) ;
345+
346+ let p2id_note = mock_chain_builder. add_p2id_note (
347+ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE . try_into ( ) . unwrap ( ) ,
348+ multisig_account. id ( ) ,
349+ & [ FungibleAsset :: mock ( 1 ) ] ,
350+ NoteType :: Public ,
351+ ) ?;
352+
353+ let mock_chain = mock_chain_builder. build ( ) . unwrap ( ) ;
354+
355+ let salt = Word :: from ( [ Felt :: new ( 1 ) ; 4 ] ) ;
356+
357+ // get the transaction context without signatures
358+ let tx_context_without_signatures = mock_chain
359+ . build_tx_context ( multisig_account. id ( ) , & [ p2id_note. id ( ) ] , & [ ] ) ?
360+ . auth_args ( salt)
361+ . build ( ) ?;
362+
363+ let block_ref = tx_context_without_signatures. tx_inputs ( ) . block_header ( ) . block_num ( ) ;
364+ let tx_args = tx_context_without_signatures. tx_args ( ) ;
365+ let tx_executor = TransactionExecutor :: < ' _ , ' _ , _ , BasicAuthenticator < ChaCha20Rng > > :: new (
366+ & tx_context_without_signatures,
367+ ) ;
368+
369+ let notes_checker = NoteConsumptionChecker :: new ( & tx_executor) ;
370+
371+ // this check should return `UnconsumableWithoutAuthorization` variant: the note is consumable,
372+ // but authentication is failing
373+ let unconsumable_without_authorization = notes_checker
374+ . can_consume (
375+ multisig_account. id ( ) ,
376+ block_ref,
377+ miden_objects:: transaction:: InputNote :: Unauthenticated { note : p2id_note. clone ( ) } ,
378+ tx_args. clone ( ) ,
379+ )
380+ . await ?;
381+ assert_matches ! (
382+ unconsumable_without_authorization,
383+ NoteConsumptionStatus :: UnconsumableWithoutAuthorization
384+ ) ;
385+
386+ // execute the transaction to get the summary
387+ let tx_summary = match tx_context_without_signatures. execute ( ) . await . unwrap_err ( ) {
388+ TransactionExecutorError :: Unauthorized ( tx_effects) => tx_effects,
389+ error => panic ! ( "expected abort with tx effects: {error:?}" ) ,
390+ } ;
391+
392+ // Get signatures from both approvers
393+ let msg = tx_summary. as_ref ( ) . to_commitment ( ) ;
394+ let tx_summary = SigningInputs :: TransactionSummary ( tx_summary) ;
395+
396+ let sig_1 = authenticators[ 0 ] . get_signature ( public_keys[ 0 ] . into ( ) , & tx_summary) . await ?;
397+ let sig_2 = authenticators[ 1 ] . get_signature ( public_keys[ 1 ] . into ( ) , & tx_summary) . await ?;
398+
399+ // Populate advice map with signatures
400+ let mut advice_map = AdviceMap :: default ( ) ;
401+ advice_map. insert ( Hasher :: merge ( & [ public_keys[ 0 ] . into ( ) , msg] ) , sig_1) ;
402+ advice_map. insert ( Hasher :: merge ( & [ public_keys[ 1 ] . into ( ) , msg] ) , sig_2) ;
403+
404+ // get the transaction context with signatures
405+ let tx_context_with_signatures = mock_chain
406+ . build_tx_context ( multisig_account. id ( ) , & [ p2id_note. id ( ) ] , & [ ] ) ?
407+ . extend_expected_output_notes ( vec ! [ OutputNote :: Full ( p2id_note) ] )
408+ . extend_advice_map ( advice_map. iter ( ) . map ( |( k, v) | ( * k, v. to_vec ( ) ) ) )
409+ . auth_args ( salt)
410+ . build ( ) ?;
411+
412+ let block_num = tx_context_with_signatures. tx_inputs ( ) . block_header ( ) . block_num ( ) ;
413+ let notes = tx_context_with_signatures. tx_inputs ( ) . input_notes ( ) . clone ( ) ;
414+ let tx_args = tx_context_with_signatures. tx_args ( ) . clone ( ) ;
415+
416+ let mut tx_executor = TransactionExecutor :: new ( & tx_context_with_signatures)
417+ . with_source_manager ( tx_context_with_signatures. source_manager ( ) ) ;
418+ if let Some ( authenticator) = tx_context_with_signatures. authenticator ( ) {
419+ tx_executor = tx_executor. with_authenticator ( authenticator) ;
420+ }
421+
422+ let notes_checker = NoteConsumptionChecker :: new ( & tx_executor) ;
423+
424+ // this check should return `Consumable` variant: we provided the signatures, so the transaction
425+ // should execute successfully.
426+ let consumable_with_authorization = notes_checker
427+ . can_consume ( multisig_account. id ( ) , block_num, notes. get_note ( 0 ) . clone ( ) , tx_args)
428+ . await ?;
429+ assert_matches ! ( consumable_with_authorization, NoteConsumptionStatus :: Consumable ) ;
430+
431+ Ok ( ( ) )
432+ }
0 commit comments