github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/money_test.go (about) 1 package wallet 2 3 import ( 4 "sort" 5 "testing" 6 7 "SiaPrime/modules" 8 "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 // Spend the only output. 170 halfPlus := types.SiacoinPrecision.Mul64(200e3) 171 _, err = wt.wallet.SendSiacoins(halfPlus, types.UnlockHash{}) 172 if err != nil { 173 t.Error("unexpected error: ", err) 174 } 175 someMore := types.SiacoinPrecision.Mul64(75e3) 176 _, err = wt.wallet.SendSiacoins(someMore, types.UnlockHash{1}) 177 if err != nil { 178 t.Error("wallet appears to be struggling to spend unconfirmed outputs") 179 } 180 } 181 182 // TestIntegrationSortedOutputsSorting checks that the outputs are being correctly sorted 183 // by the currency value. 184 func TestIntegrationSortedOutputsSorting(t *testing.T) { 185 if testing.Short() { 186 t.SkipNow() 187 } 188 so := sortedOutputs{ 189 ids: []types.SiacoinOutputID{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}}, 190 outputs: []types.SiacoinOutput{ 191 {Value: types.NewCurrency64(2)}, 192 {Value: types.NewCurrency64(3)}, 193 {Value: types.NewCurrency64(4)}, 194 {Value: types.NewCurrency64(7)}, 195 {Value: types.NewCurrency64(6)}, 196 {Value: types.NewCurrency64(0)}, 197 {Value: types.NewCurrency64(1)}, 198 {Value: types.NewCurrency64(5)}, 199 }, 200 } 201 sort.Sort(so) 202 203 expectedIDSorting := []types.SiacoinOutputID{{5}, {6}, {0}, {1}, {2}, {7}, {4}, {3}} 204 for i := uint64(0); i < 8; i++ { 205 if so.ids[i] != expectedIDSorting[i] { 206 t.Error("an id is out of place: ", i) 207 } 208 if !so.outputs[i].Value.Equals64(i) { 209 t.Error("a value is out of place: ", i) 210 } 211 } 212 } 213 214 // TestSendSiacoinsFailed checks if SendSiacoins and SendSiacoinsMulti behave 215 // correctly when funcing the Transaction succeeded but accepting it didn't. 216 func TestSendSiacoinsAcceptTxnSetFailed(t *testing.T) { 217 if testing.Short() { 218 t.SkipNow() 219 } 220 deps := &dependencySendSiacoinsInterrupted{} 221 wt, err := createWalletTester(t.Name(), deps) 222 if err != nil { 223 t.Fatal(err) 224 } 225 defer wt.closeWt() 226 227 // There should be no spent transactions in the database at this point 228 wt.wallet.mu.Lock() 229 wt.wallet.syncDB() 230 if wt.wallet.dbTx.Bucket(bucketSpentOutputs).Stats().KeyN != 0 { 231 wt.wallet.mu.Unlock() 232 t.Fatal("bucketSpentOutputs isn't empty") 233 } 234 wt.wallet.mu.Unlock() 235 236 // Try to send coins using SendSiacoinsMulti 237 numOutputs := 10 238 scos := make([]types.SiacoinOutput, numOutputs) 239 for i := 0; i < numOutputs; i++ { 240 uc, err := wt.wallet.NextAddress() 241 if err != nil { 242 t.Fatal(err) 243 } 244 scos[i].Value = types.SiacoinPrecision 245 scos[i].UnlockHash = uc.UnlockHash() 246 } 247 deps.fail() 248 _, err = wt.wallet.SendSiacoinsMulti(scos) 249 if err == nil { 250 t.Fatal("SendSiacoinsMulti should have failed but didn't") 251 } 252 253 // Send some coins using SendSiacoins 254 uc, err := wt.wallet.NextAddress() 255 if err != nil { 256 t.Fatal(err) 257 } 258 deps.fail() 259 _, err = wt.wallet.SendSiacoins(types.SiacoinPrecision, uc.UnlockHash()) 260 if err == nil { 261 t.Fatal("SendSiacoins should have failed but didn't") 262 } 263 264 // There should still be no spent transactions in the database 265 wt.wallet.mu.Lock() 266 wt.wallet.syncDB() 267 bucket := wt.wallet.dbTx.Bucket(bucketSpentOutputs) 268 if bucket.Stats().KeyN != 0 { 269 wt.wallet.mu.Unlock() 270 t.Fatal("bucketSpentOutputs isn't empty") 271 } 272 wt.wallet.mu.Unlock() 273 274 // Send the money again without the failing dependency 275 _, err = wt.wallet.SendSiacoinsMulti(scos) 276 if err != nil { 277 t.Fatalf("SendSiacoinsMulti failed: %v", err) 278 } 279 280 // Send some coins using SendSiacoins 281 _, err = wt.wallet.SendSiacoins(types.SiacoinPrecision, uc.UnlockHash()) 282 if err != nil { 283 t.Fatalf("SendSiacoins failed: %v", err) 284 } 285 }