@@ -3019,6 +3019,314 @@ public void TestSaveAndClose()
30193019
30203020 #region Reset Tests
30213021
3022+ // Can sweep subaddresses
3023+ [ Fact ]
3024+ public void TestSweepSubaddresses ( )
3025+ {
3026+ Assert . True ( TEST_RESETS ) ;
3027+ TestUtils . WALLET_TX_TRACKER . WaitForWalletTxsToClearPool ( wallet ) ;
3028+
3029+ const int NUM_SUBADDRESSES_TO_SWEEP = 2 ;
3030+
3031+ // collect subaddresses with balance and unlocked balance
3032+ List < MoneroSubaddress > subaddresses = new List < MoneroSubaddress > ( ) ;
3033+ List < MoneroSubaddress > subaddressesBalance = new List < MoneroSubaddress > ( ) ;
3034+ List < MoneroSubaddress > subaddressesUnlocked = new List < MoneroSubaddress > ( ) ;
3035+ foreach ( MoneroAccount account in wallet . GetAccounts ( true ) )
3036+ {
3037+ if ( account . GetIndex ( ) == 0 ) continue ; // skip default account
3038+ foreach ( MoneroSubaddress subaddress in account . GetSubaddresses ( ) )
3039+ {
3040+ subaddresses . Add ( subaddress ) ;
3041+ if ( ( ( ulong ) subaddress . GetBalance ( ) ) . CompareTo ( TestUtils . MAX_FEE ) > 0 ) subaddressesBalance . Add ( subaddress ) ;
3042+ if ( ( ( ulong ) subaddress . GetUnlockedBalance ( ) ) . CompareTo ( TestUtils . MAX_FEE ) > 0 ) subaddressesUnlocked . Add ( subaddress ) ;
3043+ }
3044+ }
3045+
3046+ // test requires at least one more subaddresses than the number being swept to verify it does not change
3047+ Assert . True ( subaddressesBalance . Count >= NUM_SUBADDRESSES_TO_SWEEP + 1 , "Test requires balance in at least " + ( NUM_SUBADDRESSES_TO_SWEEP + 1 ) + " subaddresses from non-default acccount; run send-to-multiple tests" ) ;
3048+ Assert . True ( subaddressesUnlocked . Count >= NUM_SUBADDRESSES_TO_SWEEP + 1 , "Wallet is waiting on unlocked funds" ) ;
3049+
3050+ // sweep from first unlocked subaddresses
3051+ for ( int i = 0 ; i < NUM_SUBADDRESSES_TO_SWEEP ; i ++ )
3052+ {
3053+
3054+ // sweep unlocked account
3055+ MoneroSubaddress unlockedSubaddress = subaddressesUnlocked [ i ] ;
3056+ MoneroTxConfig config = new MoneroTxConfig ( )
3057+ . SetAddress ( wallet . GetPrimaryAddress ( ) )
3058+ . SetAccountIndex ( unlockedSubaddress . GetAccountIndex ( ) )
3059+ . SetSubaddressIndices ( [ ( uint ) unlockedSubaddress . GetIndex ( ) ] )
3060+ . SetRelay ( true ) ;
3061+ List < MoneroTxWallet > txs = wallet . SweepUnlocked ( config ) ;
3062+
3063+ // test transactions
3064+ Assert . True ( txs . Count > 0 ) ;
3065+ foreach ( MoneroTxWallet tx in txs )
3066+ {
3067+ Assert . True ( tx . GetTxSet ( ) . GetTxs ( ) . Contains ( tx ) ) ;
3068+ TxContext ctx = new TxContext ( ) ;
3069+ ctx . Wallet = wallet ;
3070+ ctx . Config = config ;
3071+ ctx . IsSendResponse = true ;
3072+ ctx . IsSweepResponse = true ;
3073+ TestTxWallet ( tx , ctx ) ;
3074+ }
3075+
3076+ // Assert. unlocked balance is less than max fee
3077+ MoneroSubaddress subaddress = wallet . GetSubaddress ( ( ( uint ) unlockedSubaddress . GetAccountIndex ( ) ) , ( ( uint ) unlockedSubaddress . GetIndex ( ) ) ) ;
3078+ Assert . True ( ( ( ulong ) subaddress . GetUnlockedBalance ( ) ) . CompareTo ( TestUtils . MAX_FEE ) < 0 ) ;
3079+ }
3080+
3081+ // test subaddresses after sweeping
3082+ List < MoneroSubaddress > subaddressesAfter = new List < MoneroSubaddress > ( ) ;
3083+ foreach ( MoneroAccount account in wallet . GetAccounts ( true ) )
3084+ {
3085+ if ( account . GetIndex ( ) == 0 ) continue ; // skip default account
3086+ foreach ( MoneroSubaddress subaddress in account . GetSubaddresses ( ) )
3087+ {
3088+ subaddressesAfter . Add ( subaddress ) ;
3089+ }
3090+ }
3091+ Assert . Equal ( subaddresses . Count , subaddressesAfter . Count ) ;
3092+ for ( int i = 0 ; i < subaddresses . Count ; i ++ )
3093+ {
3094+ MoneroSubaddress subaddressBefore = subaddresses [ i ] ;
3095+ MoneroSubaddress subaddressAfter = subaddressesAfter [ i ] ;
3096+
3097+ // determine if subaddress was swept
3098+ bool swept = false ;
3099+ for ( int j = 0 ; j < NUM_SUBADDRESSES_TO_SWEEP ; j ++ )
3100+ {
3101+ if ( subaddressesUnlocked [ j ] . GetAccountIndex ( ) . Equals ( subaddressBefore . GetAccountIndex ( ) ) && subaddressesUnlocked [ j ] . GetIndex ( ) . Equals ( subaddressBefore . GetIndex ( ) ) )
3102+ {
3103+ swept = true ;
3104+ break ;
3105+ }
3106+ }
3107+
3108+ // Assert. unlocked balance is less than max fee if swept, unchanged otherwise
3109+ if ( swept )
3110+ {
3111+ Assert . True ( ( ( ulong ) subaddressAfter . GetUnlockedBalance ( ) ) . CompareTo ( TestUtils . MAX_FEE ) < 0 ) ;
3112+ }
3113+ else
3114+ {
3115+ Assert . True ( ( ( ulong ) subaddressBefore . GetUnlockedBalance ( ) ) . CompareTo ( subaddressAfter . GetUnlockedBalance ( ) ) == 0 ) ;
3116+ }
3117+ }
3118+ }
3119+
3120+ // Can sweep accounts
3121+ [ Fact ]
3122+ public void TestSweepAccounts ( )
3123+ {
3124+ Assert . True ( TEST_RESETS ) ;
3125+ TestUtils . WALLET_TX_TRACKER . WaitForWalletTxsToClearPool ( wallet ) ;
3126+
3127+ const int NUM_ACCOUNTS_TO_SWEEP = 1 ;
3128+
3129+ // collect accounts with sufficient balance and unlocked balance to cover the fee
3130+ List < MoneroAccount > accounts = wallet . GetAccounts ( true ) ;
3131+ List < MoneroAccount > accountsBalance = new List < MoneroAccount > ( ) ;
3132+ List < MoneroAccount > accountsUnlocked = new List < MoneroAccount > ( ) ;
3133+ foreach ( MoneroAccount account in accounts )
3134+ {
3135+ if ( account . GetIndex ( ) == 0 ) continue ; // skip default account
3136+ if ( account . GetBalance ( ) . CompareTo ( TestUtils . MAX_FEE ) > 0 ) accountsBalance . Add ( account ) ;
3137+ if ( account . GetUnlockedBalance ( ) . CompareTo ( TestUtils . MAX_FEE ) > 0 ) accountsUnlocked . Add ( account ) ;
3138+ }
3139+
3140+ // test requires at least one more accounts than the number being swept to verify it does not change
3141+ Assert . True ( accountsBalance . Count >= NUM_ACCOUNTS_TO_SWEEP + 1 , "Test requires balance greater than the fee in at least " + ( NUM_ACCOUNTS_TO_SWEEP + 1 ) + " non-default accounts; run send-to-multiple tests" ) ;
3142+ Assert . True ( accountsUnlocked . Count >= NUM_ACCOUNTS_TO_SWEEP + 1 , "Wallet is waiting on unlocked funds" ) ;
3143+
3144+ // sweep from first unlocked accounts
3145+ for ( int i = 0 ; i < NUM_ACCOUNTS_TO_SWEEP ; i ++ )
3146+ {
3147+
3148+ // sweep unlocked account
3149+ MoneroAccount unlockedAccount = accountsUnlocked [ i ] ;
3150+ MoneroTxConfig config = new MoneroTxConfig ( ) . SetAddress ( wallet . GetPrimaryAddress ( ) ) . SetAccountIndex ( unlockedAccount . GetIndex ( ) ) . SetRelay ( true ) ;
3151+ List < MoneroTxWallet > txs = wallet . SweepUnlocked ( config ) ;
3152+
3153+ // test transactions
3154+ Assert . True ( txs . Count > 0 ) ;
3155+ foreach ( MoneroTxWallet tx in txs )
3156+ {
3157+ TxContext ctx = new TxContext ( ) ;
3158+ ctx . Wallet = wallet ;
3159+ ctx . Config = config ;
3160+ ctx . IsSendResponse = true ;
3161+ ctx . IsSweepResponse = true ;
3162+ TestTxWallet ( tx , ctx ) ;
3163+ Assert . NotNull ( tx . GetTxSet ( ) ) ;
3164+ Assert . True ( tx . GetTxSet ( ) . GetTxs ( ) . Contains ( tx ) ) ;
3165+ }
3166+
3167+ // Assert. unlocked account balance less than max fee
3168+ MoneroAccount account = wallet . GetAccount ( ( uint ) unlockedAccount . GetIndex ( ) ) ;
3169+ Assert . True ( account . GetUnlockedBalance ( ) . CompareTo ( TestUtils . MAX_FEE ) < 0 ) ;
3170+ }
3171+
3172+ // test accounts after sweeping
3173+ List < MoneroAccount > accountsAfter = wallet . GetAccounts ( true ) ;
3174+ Assert . Equal ( accounts . Count , accountsAfter . Count ) ;
3175+ for ( int i = 0 ; i < accounts . Count ; i ++ )
3176+ {
3177+ MoneroAccount accountBefore = accounts [ i ] ;
3178+ MoneroAccount accountAfter = accountsAfter [ i ] ;
3179+
3180+ // determine if account was swept
3181+ bool swept = false ;
3182+ for ( int j = 0 ; j < NUM_ACCOUNTS_TO_SWEEP ; j ++ )
3183+ {
3184+ if ( accountsUnlocked [ j ] . GetIndex ( ) . Equals ( accountBefore . GetIndex ( ) ) )
3185+ {
3186+ swept = true ;
3187+ break ;
3188+ }
3189+ }
3190+
3191+ // Assert. unlocked balance is less than max fee if swept, unchanged otherwise
3192+ if ( swept )
3193+ {
3194+ Assert . True ( accountAfter . GetUnlockedBalance ( ) . CompareTo ( TestUtils . MAX_FEE ) < 0 ) ;
3195+ }
3196+ else
3197+ {
3198+ Assert . True ( accountBefore . GetUnlockedBalance ( ) . CompareTo ( accountAfter . GetUnlockedBalance ( ) ) == 0 ) ;
3199+ }
3200+ }
3201+ }
3202+
3203+ // Can sweep the whole wallet by accounts
3204+ [ Fact ( Skip = "Disabled so tests don't sweep the whole wallet" ) ]
3205+ public void TestSweepWalletByAccounts ( )
3206+ {
3207+ Assert . True ( TEST_RESETS ) ;
3208+ TestSweepWallet ( null ) ;
3209+ }
3210+
3211+ // Can sweep the whole wallet by subaddresses
3212+ [ Fact ( Skip = "Disabled so tests don't sweep the whole wallet" ) ]
3213+ public void TestSweepWalletBySubaddresses ( )
3214+ {
3215+ Assert . True ( TEST_RESETS ) ;
3216+ TestSweepWallet ( true ) ;
3217+ }
3218+
3219+ private void TestSweepWallet ( bool ? sweepEachSubaddress )
3220+ {
3221+ TestUtils . WALLET_TX_TRACKER . WaitForWalletTxsToClearPool ( wallet ) ;
3222+
3223+ // verify 2 subaddresses with enough unlocked balance to cover the fee
3224+ List < MoneroSubaddress > subaddressesBalance = new List < MoneroSubaddress > ( ) ;
3225+ List < MoneroSubaddress > subaddressesUnlocked = new List < MoneroSubaddress > ( ) ;
3226+ foreach ( MoneroAccount account in wallet . GetAccounts ( true ) )
3227+ {
3228+ foreach ( MoneroSubaddress subaddress in account . GetSubaddresses ( ) )
3229+ {
3230+ if ( ( ( ulong ) subaddress . GetBalance ( ) ) . CompareTo ( TestUtils . MAX_FEE ) > 0 ) subaddressesBalance . Add ( subaddress ) ;
3231+ if ( ( ( ulong ) subaddress . GetUnlockedBalance ( ) ) . CompareTo ( TestUtils . MAX_FEE ) > 0 ) subaddressesUnlocked . Add ( subaddress ) ;
3232+ }
3233+ }
3234+ Assert . True ( subaddressesBalance . Count >= 2 , "Test requires multiple accounts with a balance greater than the fee; run send to multiple first" ) ;
3235+ Assert . True ( subaddressesUnlocked . Count >= 2 , "Wallet is waiting on unlocked funds" ) ;
3236+
3237+ // sweep
3238+ string destination = wallet . GetPrimaryAddress ( ) ;
3239+ MoneroTxConfig config = new MoneroTxConfig ( ) . SetAddress ( destination ) . SetSweepEachSubaddress ( sweepEachSubaddress ) . SetRelay ( true ) ;
3240+ MoneroTxConfig copy = config . Clone ( ) ;
3241+ List < MoneroTxWallet > txs = wallet . SweepUnlocked ( config ) ;
3242+ Assert . Equal ( copy , config ) ; // config is unchanged
3243+ foreach ( MoneroTxWallet tx in txs )
3244+ {
3245+ Assert . True ( tx . GetTxSet ( ) . GetTxs ( ) . Contains ( tx ) ) ;
3246+ Assert . Null ( tx . GetTxSet ( ) . GetMultisigTxHex ( ) ) ;
3247+ Assert . Null ( tx . GetTxSet ( ) . GetSignedTxHex ( ) ) ;
3248+ Assert . Null ( tx . GetTxSet ( ) . GetUnsignedTxHex ( ) ) ;
3249+ }
3250+ Assert . True ( txs . Count > 0 ) ;
3251+ foreach ( MoneroTxWallet tx in txs )
3252+ {
3253+ config = new MoneroTxConfig ( )
3254+ . SetAddress ( destination )
3255+ . SetAccountIndex ( tx . GetOutgoingTransfer ( ) . GetAccountIndex ( ) )
3256+ . SetSweepEachSubaddress ( sweepEachSubaddress )
3257+ . SetRelay ( true ) ;
3258+ TxContext ctx = new TxContext ( ) ;
3259+ ctx . Wallet = wallet ;
3260+ ctx . Config = config ;
3261+ ctx . IsSendResponse = true ;
3262+ ctx . IsSweepResponse = true ;
3263+ TestTxWallet ( tx , ctx ) ;
3264+ }
3265+
3266+ // all unspent, unlocked outputs must be less than fee
3267+ List < MoneroOutputWallet > spendableOutputs = wallet . GetOutputs ( new MoneroOutputQuery ( ) . SetIsSpent ( false ) . SetTxQuery ( new MoneroTxQuery ( ) . SetIsLocked ( false ) ) ) ;
3268+ foreach ( MoneroOutputWallet spendableOutput in spendableOutputs )
3269+ {
3270+ Assert . True ( ( ( ulong ) spendableOutput . GetAmount ( ) ) . CompareTo ( TestUtils . MAX_FEE ) < 0 , "Unspent output should have been swept\n " + spendableOutput . ToString ( ) ) ;
3271+ }
3272+
3273+ // all subaddress unlocked balances must be less than fee
3274+ subaddressesBalance . Clear ( ) ;
3275+ subaddressesUnlocked . Clear ( ) ;
3276+ foreach ( MoneroAccount account in wallet . GetAccounts ( true ) )
3277+ {
3278+ foreach ( MoneroSubaddress subaddress in account . GetSubaddresses ( ) )
3279+ {
3280+ Assert . True ( ( ( ulong ) subaddress . GetUnlockedBalance ( ) ) . CompareTo ( TestUtils . MAX_FEE ) < 0 , "No subaddress should have more unlocked than the fee" ) ;
3281+ }
3282+ }
3283+ }
3284+
3285+ // Can scan transactions by id
3286+ [ Fact ]
3287+ public void TestScanTxs ( )
3288+ {
3289+
3290+ // get a few tx hashes
3291+ List < string > txHashes = [ ] ;
3292+ List < MoneroTxWallet > txs = wallet . GetTxs ( ) ;
3293+ if ( txs . Count < 3 ) Assert . Fail ( "Not enough txs to scan" ) ;
3294+ for ( int i = 0 ; i < 3 ; i ++ ) txHashes . Add ( txs [ i ] . GetHash ( ) ) ;
3295+
3296+ // start wallet without scanning
3297+ MoneroWallet scanWallet = CreateWallet ( new MoneroWalletConfig ( ) . SetSeed ( wallet . GetSeed ( ) ) . SetRestoreHeight ( 0 ) ) ;
3298+ scanWallet . StopSyncing ( ) ; // TODO: create wallet without daemon connection (offline does not reconnect, default connects to localhost, offline then online causes confirmed txs to disappear)
3299+ Assert . True ( scanWallet . IsConnectedToDaemon ( ) ) ;
3300+
3301+ // scan txs
3302+ scanWallet . ScanTxs ( txHashes ) ;
3303+
3304+ // TODO: scanning txs causes merge problems reconciling 0 fee, isMinerTx with test txs
3305+
3306+ // // txs are scanned
3307+ // Assert.Equal(txHashes.Count, scanWallet.GetTxs().Count);
3308+ // for (int i = 0; i < txHashes.Count; i++) {
3309+ // Assert.Equal(wallet.GetTx(txHashes[i]), scanWallet.GetTx(txHashes[i]));
3310+ // }
3311+ // List<MoneroTxWallet> scannedTxs = scanWallet.GetTxs(txHashes);
3312+ // Assert.Equal(txHashes.Count, scannedTxs.Count);
3313+
3314+ // close wallet
3315+ CloseWallet ( scanWallet , false ) ;
3316+ }
3317+
3318+ // Can rescan the blockchain
3319+ [ Fact ( Skip = "Disabled so tests don't delete local cache" ) ]
3320+ public void TestRescanBlockchain ( )
3321+ {
3322+ Assert . True ( TEST_RESETS ) ;
3323+ wallet . RescanBlockchain ( ) ;
3324+ foreach ( MoneroTxWallet tx in wallet . GetTxs ( ) )
3325+ {
3326+ TestTxWallet ( tx , null ) ;
3327+ }
3328+ }
3329+
30223330 #endregion
30233331
30243332 #region Notification Tests
0 commit comments