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