github.com/ZuluSpl0it/Sia@v1.3.7/modules/wallet/money_test.go (about) 1 package wallet 2 3 import ( 4 "sort" 5 "testing" 6 7 "github.com/NebulousLabs/Sia/modules" 8 "github.com/NebulousLabs/Sia/types" 9 ) 10 11 // TestSendSiacoins probes the SendSiacoins method of the wallet. 12 func TestSendSiacoins(t *testing.T) { 13 if testing.Short() { 14 t.SkipNow() 15 } 16 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 17 if err != nil { 18 t.Fatal(err) 19 } 20 defer wt.closeWt() 21 22 // Get the initial balance - should be 1 block. The unconfirmed balances 23 // should be 0. 24 confirmedBal, _, _, err := wt.wallet.ConfirmedBalance() 25 if err != nil { 26 t.Fatal(err) 27 } 28 unconfirmedOut, unconfirmedIn, err := wt.wallet.UnconfirmedBalance() 29 if err != nil { 30 t.Fatal(err) 31 } 32 if !confirmedBal.Equals(types.CalculateCoinbase(1)) { 33 t.Error("unexpected confirmed balance") 34 } 35 if !unconfirmedOut.Equals(types.ZeroCurrency) { 36 t.Error("unconfirmed balance should be 0") 37 } 38 if !unconfirmedIn.Equals(types.ZeroCurrency) { 39 t.Error("unconfirmed balance should be 0") 40 } 41 42 // Send 5000 hastings. The wallet will automatically add a fee. Outgoing 43 // unconfirmed siacoins - incoming unconfirmed siacoins should equal 5000 + 44 // fee. 45 sendValue := types.SiacoinPrecision.Mul64(3) 46 _, tpoolFee := wt.wallet.tpool.FeeEstimation() 47 tpoolFee = tpoolFee.Mul64(750) 48 _, err = wt.wallet.SendSiacoins(sendValue, types.UnlockHash{}) 49 if err != nil { 50 t.Fatal(err) 51 } 52 confirmedBal2, _, _, err := wt.wallet.ConfirmedBalance() 53 if err != nil { 54 t.Fatal(err) 55 } 56 unconfirmedOut2, unconfirmedIn2, err := wt.wallet.UnconfirmedBalance() 57 if err != nil { 58 t.Fatal(err) 59 } 60 if !confirmedBal2.Equals(confirmedBal) { 61 t.Error("confirmed balance changed without introduction of blocks") 62 } 63 if !unconfirmedOut2.Equals(unconfirmedIn2.Add(sendValue).Add(tpoolFee)) { 64 t.Error("sending siacoins appears to be ineffective") 65 } 66 67 // Move the balance into the confirmed set. 68 b, _ := wt.miner.FindBlock() 69 err = wt.cs.AcceptBlock(b) 70 if err != nil { 71 t.Fatal(err) 72 } 73 confirmedBal3, _, _, err := wt.wallet.ConfirmedBalance() 74 if err != nil { 75 t.Fatal(err) 76 } 77 unconfirmedOut3, unconfirmedIn3, err := wt.wallet.UnconfirmedBalance() 78 if err != nil { 79 t.Fatal(err) 80 } 81 if !confirmedBal3.Equals(confirmedBal2.Add(types.CalculateCoinbase(2)).Sub(sendValue).Sub(tpoolFee)) { 82 t.Error("confirmed balance did not adjust to the expected value") 83 } 84 if !unconfirmedOut3.Equals(types.ZeroCurrency) { 85 t.Error("unconfirmed balance should be 0") 86 } 87 if !unconfirmedIn3.Equals(types.ZeroCurrency) { 88 t.Error("unconfirmed balance should be 0") 89 } 90 } 91 92 // TestIntegrationSendOverUnder sends too many siacoins, resulting in an error, 93 // followed by sending few enough siacoins that the send should complete. 94 // 95 // This test is here because of a bug found in production where the wallet 96 // would mark outputs as spent before it knew that there was enough money to 97 // complete the transaction. This meant that, after trying to send too many 98 // coins, all outputs got marked 'sent'. This test reproduces those conditions 99 // to ensure it does not happen again. 100 func TestIntegrationSendOverUnder(t *testing.T) { 101 if testing.Short() { 102 t.SkipNow() 103 } 104 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 105 if err != nil { 106 t.Fatal(err) 107 } 108 defer wt.closeWt() 109 110 // Spend too many siacoins. 111 tooManyCoins := types.SiacoinPrecision.Mul64(1e12) 112 _, err = wt.wallet.SendSiacoins(tooManyCoins, types.UnlockHash{}) 113 if err == nil { 114 t.Error("low balance err not returned after attempting to send too many coins:", err) 115 } 116 117 // Spend a reasonable amount of siacoins. 118 reasonableCoins := types.SiacoinPrecision.Mul64(100e3) 119 _, err = wt.wallet.SendSiacoins(reasonableCoins, types.UnlockHash{}) 120 if err != nil { 121 t.Error("unexpected error: ", err) 122 } 123 } 124 125 // TestIntegrationSpendHalfHalf spends more than half of the coins, and then 126 // more than half of the coins again, to make sure that the wallet is not 127 // reusing outputs that it has already spent. 128 func TestIntegrationSpendHalfHalf(t *testing.T) { 129 if testing.Short() { 130 t.SkipNow() 131 } 132 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 133 if err != nil { 134 t.Fatal(err) 135 } 136 defer wt.closeWt() 137 138 // Spend more than half of the coins twice. 139 halfPlus := types.SiacoinPrecision.Mul64(200e3) 140 _, err = wt.wallet.SendSiacoins(halfPlus, types.UnlockHash{}) 141 if err != nil { 142 t.Error("unexpected error: ", err) 143 } 144 _, err = wt.wallet.SendSiacoins(halfPlus, types.UnlockHash{1}) 145 if err == nil { 146 t.Error("wallet appears to be reusing outputs when building transactions: ", err) 147 } 148 } 149 150 // TestIntegrationSpendUnconfirmed spends an unconfirmed siacoin output. 151 func TestIntegrationSpendUnconfirmed(t *testing.T) { 152 if testing.Short() { 153 t.SkipNow() 154 } 155 wt, err := createWalletTester(t.Name(), modules.ProdDependencies) 156 if err != nil { 157 t.Fatal(err) 158 } 159 defer wt.closeWt() 160 161 // Spend the only output. 162 halfPlus := types.SiacoinPrecision.Mul64(200e3) 163 _, err = wt.wallet.SendSiacoins(halfPlus, types.UnlockHash{}) 164 if err != nil { 165 t.Error("unexpected error: ", err) 166 } 167 someMore := types.SiacoinPrecision.Mul64(75e3) 168 _, err = wt.wallet.SendSiacoins(someMore, types.UnlockHash{1}) 169 if err != nil { 170 t.Error("wallet appears to be struggling to spend unconfirmed outputs") 171 } 172 } 173 174 // TestIntegrationSortedOutputsSorting checks that the outputs are being correctly sorted 175 // by the currency value. 176 func TestIntegrationSortedOutputsSorting(t *testing.T) { 177 if testing.Short() { 178 t.SkipNow() 179 } 180 so := sortedOutputs{ 181 ids: []types.SiacoinOutputID{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}}, 182 outputs: []types.SiacoinOutput{ 183 {Value: types.NewCurrency64(2)}, 184 {Value: types.NewCurrency64(3)}, 185 {Value: types.NewCurrency64(4)}, 186 {Value: types.NewCurrency64(7)}, 187 {Value: types.NewCurrency64(6)}, 188 {Value: types.NewCurrency64(0)}, 189 {Value: types.NewCurrency64(1)}, 190 {Value: types.NewCurrency64(5)}, 191 }, 192 } 193 sort.Sort(so) 194 195 expectedIDSorting := []types.SiacoinOutputID{{5}, {6}, {0}, {1}, {2}, {7}, {4}, {3}} 196 for i := uint64(0); i < 8; i++ { 197 if so.ids[i] != expectedIDSorting[i] { 198 t.Error("an id is out of place: ", i) 199 } 200 if !so.outputs[i].Value.Equals64(i) { 201 t.Error("a value is out of place: ", i) 202 } 203 } 204 } 205 206 // TestSendSiacoinsFailed checks if SendSiacoins and SendSiacoinsMulti behave 207 // correctly when funcing the Transaction succeeded but accepting it didn't. 208 func TestSendSiacoinsAcceptTxnSetFailed(t *testing.T) { 209 if testing.Short() { 210 t.SkipNow() 211 } 212 deps := &dependencySendSiacoinsInterrupted{} 213 wt, err := createWalletTester(t.Name(), deps) 214 if err != nil { 215 t.Fatal(err) 216 } 217 defer wt.closeWt() 218 219 // There should be no spent transactions in the database at this point 220 wt.wallet.mu.Lock() 221 wt.wallet.syncDB() 222 if wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN != 0 { 223 wt.wallet.mu.Unlock() 224 t.Fatal("bucketSpentOutputs isn't empty") 225 } 226 wt.wallet.mu.Unlock() 227 228 // Try to send coins using SendSiacoinsMulti 229 numOutputs := 10 230 scos := make([]types.SiacoinOutput, numOutputs) 231 for i := 0; i < numOutputs; i++ { 232 uc, err := wt.wallet.NextAddress() 233 if err != nil { 234 t.Fatal(err) 235 } 236 scos[i].Value = types.SiacoinPrecision 237 scos[i].UnlockHash = uc.UnlockHash() 238 } 239 deps.fail() 240 _, err = wt.wallet.SendSiacoinsMulti(scos) 241 if err == nil { 242 t.Fatal("SendSiacoinsMulti should have failed but didn't") 243 } 244 245 // Send some coins using SendSiacoins 246 uc, err := wt.wallet.NextAddress() 247 if err != nil { 248 t.Fatal(err) 249 } 250 deps.fail() 251 _, err = wt.wallet.SendSiacoins(types.SiacoinPrecision, uc.UnlockHash()) 252 if err == nil { 253 t.Fatal("SendSiacoins should have failed but didn't") 254 } 255 256 // There should still be no spent transactions in the database 257 wt.wallet.mu.Lock() 258 wt.wallet.syncDB() 259 bucket := wt.wallet.dbTx.Bucket(bucketSpentOutputs) 260 if bucket.Stats().KeyN != 0 { 261 wt.wallet.mu.Unlock() 262 t.Fatal("bucketSpentOutputs isn't empty") 263 } 264 wt.wallet.mu.Unlock() 265 266 // Send the money again without the failing dependency 267 _, err = wt.wallet.SendSiacoinsMulti(scos) 268 if err != nil { 269 t.Fatalf("SendSiacoinsMulti failed: %v", err) 270 } 271 272 // Send some coins using SendSiacoins 273 _, err = wt.wallet.SendSiacoins(types.SiacoinPrecision, uc.UnlockHash()) 274 if err != nil { 275 t.Fatalf("SendSiacoins failed: %v", err) 276 } 277 }