github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/siatest/wallet/wallet_test.go (about) 1 package wallet 2 3 import ( 4 "errors" 5 "path/filepath" 6 "testing" 7 "time" 8 9 "SiaPrime/build" 10 "SiaPrime/crypto" 11 "SiaPrime/modules" 12 "SiaPrime/node" 13 "SiaPrime/siatest" 14 "SiaPrime/types" 15 ) 16 17 // TestTransactionReorg makes sure that a processedTransaction isn't returned 18 // by the API after bein reverted. 19 func TestTransactionReorg(t *testing.T) { 20 if testing.Short() { 21 t.SkipNow() 22 } 23 24 // Create testing directory. 25 testdir := walletTestDir(t.Name()) 26 27 // Create two miners 28 miner1, err := siatest.NewNode(siatest.Miner(filepath.Join(testdir, "miner1"))) 29 if err != nil { 30 t.Fatal(err) 31 } 32 defer func() { 33 if err := miner1.Close(); err != nil { 34 t.Fatal(err) 35 } 36 }() 37 // miner1 sends a txn to itself and mines it. 38 uc, err := miner1.WalletAddressGet() 39 if err != nil { 40 t.Fatal(err) 41 } 42 wsp, err := miner1.WalletSiacoinsPost(types.SiacoinPrecision, uc.Address) 43 if err != nil { 44 t.Fatal(err) 45 } 46 blocks := 1 47 for i := 0; i < blocks; i++ { 48 if err := miner1.MineBlock(); err != nil { 49 t.Fatal(err) 50 } 51 } 52 // wait until the transaction from before shows up as processed. 53 txn := wsp.TransactionIDs[len(wsp.TransactionIDs)-1] 54 err = build.Retry(100, 100*time.Millisecond, func() error { 55 cg, err := miner1.ConsensusGet() 56 if err != nil { 57 return err 58 } 59 wtg, err := miner1.WalletTransactionsGet(1, cg.Height) 60 if err != nil { 61 return err 62 } 63 for _, t := range wtg.ConfirmedTransactions { 64 if t.TransactionID == txn { 65 return nil 66 } 67 } 68 return errors.New("txn isn't processed yet") 69 }) 70 if err != nil { 71 t.Fatal(err) 72 } 73 miner2, err := siatest.NewNode(siatest.Miner(filepath.Join(testdir, "miner2"))) 74 if err != nil { 75 t.Fatal(err) 76 } 77 defer func() { 78 if err := miner2.Close(); err != nil { 79 t.Fatal(err) 80 } 81 }() 82 83 // miner2 mines 2 blocks now to create a longer chain than miner1. 84 for i := 0; i < blocks+1; i++ { 85 if err := miner2.MineBlock(); err != nil { 86 t.Fatal(err) 87 } 88 } 89 // miner1 and miner2 connect. This should cause a reorg that reverts the 90 // transaction from before. 91 if err := miner1.GatewayConnectPost(miner2.GatewayAddress()); err != nil { 92 t.Fatal(err) 93 } 94 err = build.Retry(100, 100*time.Millisecond, func() error { 95 cg, err := miner1.ConsensusGet() 96 if err != nil { 97 return err 98 } 99 wtg, err := miner1.WalletTransactionsGet(1, cg.Height) 100 if err != nil { 101 return err 102 } 103 for _, t := range wtg.ConfirmedTransactions { 104 if t.TransactionID == txn { 105 return errors.New("txn is still processed") 106 } 107 } 108 return nil 109 }) 110 if err != nil { 111 t.Fatal(err) 112 } 113 } 114 115 // TestSignTransaction is a integration test for signing transaction offline 116 // using the API. 117 func TestSignTransaction(t *testing.T) { 118 if testing.Short() { 119 t.SkipNow() 120 } 121 122 // Create a new server 123 testNode, err := siatest.NewNode(node.AllModules(siatest.TestDir(t.Name()))) 124 if err != nil { 125 t.Fatal(err) 126 } 127 defer func() { 128 if err := testNode.Close(); err != nil { 129 t.Fatal(err) 130 } 131 }() 132 133 // get two outputs to spend 134 unspentResp, err := testNode.WalletUnspentGet() 135 if err != nil { 136 t.Fatal("failed to get spendable outputs:", err) 137 } 138 outputs := unspentResp.Outputs 139 wucg1, err := testNode.WalletUnlockConditionsGet(outputs[0].UnlockHash) 140 if err != nil { 141 t.Fatal("failed to get unlock conditions:", err) 142 } 143 wucg2, err := testNode.WalletUnlockConditionsGet(outputs[1].UnlockHash) 144 if err != nil { 145 t.Fatal("failed to get unlock conditions:", err) 146 } 147 148 // create a transaction that sends the outputs to the void, with no 149 // signatures 150 txn := types.Transaction{ 151 SiacoinInputs: []types.SiacoinInput{ 152 { 153 ParentID: types.SiacoinOutputID(outputs[0].ID), 154 UnlockConditions: wucg1.UnlockConditions, 155 }, 156 { 157 ParentID: types.SiacoinOutputID(outputs[1].ID), 158 UnlockConditions: wucg2.UnlockConditions, 159 }, 160 }, 161 SiacoinOutputs: []types.SiacoinOutput{{ 162 Value: outputs[0].Value.Add(outputs[1].Value), 163 UnlockHash: types.UnlockHash{}, 164 }}, 165 TransactionSignatures: []types.TransactionSignature{ 166 {ParentID: crypto.Hash(outputs[0].ID), CoveredFields: types.CoveredFields{WholeTransaction: true}}, 167 {ParentID: crypto.Hash(outputs[1].ID), CoveredFields: types.CoveredFields{WholeTransaction: true}}, 168 }, 169 } 170 171 // sign the first input 172 signResp, err := testNode.WalletSignPost(txn, []crypto.Hash{txn.TransactionSignatures[0].ParentID}) 173 if err != nil { 174 t.Fatal("failed to sign the transaction:", err) 175 } 176 txn = signResp.Transaction 177 178 // txn should now have one signature 179 if len(txn.TransactionSignatures[0].Signature) == 0 { 180 t.Fatal("transaction was not signed") 181 } else if len(txn.TransactionSignatures[1].Signature) != 0 { 182 t.Fatal("second input was also signed") 183 } 184 185 // sign the second input 186 signResp, err = testNode.WalletSignPost(txn, []crypto.Hash{txn.TransactionSignatures[1].ParentID}) 187 if err != nil { 188 t.Fatal("failed to sign the transaction:", err) 189 } 190 txn = signResp.Transaction 191 192 // txn should now have both signatures 193 if len(txn.TransactionSignatures[0].Signature) == 0 || len(txn.TransactionSignatures[1].Signature) == 0 { 194 t.Fatal("transaction was not signed") 195 } 196 197 // the resulting transaction should be valid; submit it to the tpool and 198 // mine a block to confirm it 199 if err := testNode.TransactionPoolRawPost(txn, nil); err != nil { 200 t.Fatal("failed to add transaction to pool:", err) 201 } 202 if err := testNode.MineBlock(); err != nil { 203 t.Fatal("failed to mine block", err) 204 } 205 206 // the wallet should no longer list the resulting output as spendable 207 unspentResp, err = testNode.WalletUnspentGet() 208 if err != nil { 209 t.Fatal("failed to get spendable outputs") 210 } 211 for _, output := range unspentResp.Outputs { 212 if output.ID == types.OutputID(txn.SiacoinInputs[0].ParentID) { 213 t.Fatal("spent output still listed as spendable") 214 } 215 } 216 } 217 218 // TestWatchOnly tests the ability of the wallet to track addresses that it 219 // does not own. 220 func TestWatchOnly(t *testing.T) { 221 if testing.Short() { 222 t.SkipNow() 223 } 224 225 // Create a new server 226 testNode, err := siatest.NewNode(node.AllModules(siatest.TestDir(t.Name()))) 227 if err != nil { 228 t.Fatal(err) 229 } 230 defer func() { 231 if err := testNode.Close(); err != nil { 232 t.Fatal(err) 233 } 234 }() 235 236 // create an address manually and send coins to it 237 sk, pk := crypto.GenerateKeyPair() 238 uc := types.UnlockConditions{ 239 PublicKeys: []types.SiaPublicKey{types.Ed25519PublicKey(pk)}, 240 SignaturesRequired: 1, 241 } 242 addr := uc.UnlockHash() 243 244 _, err = testNode.WalletSiacoinsPost(types.SiacoinPrecision.Mul64(77), addr) 245 if err != nil { 246 t.Fatal(err) 247 } 248 testNode.MineBlock() 249 250 // the output should not show up in UnspentOutputs, because the address is 251 // not being tracked yet 252 unspentResp, err := testNode.WalletUnspentGet() 253 if err != nil { 254 t.Fatal("failed to get spendable outputs:", err) 255 } else if len(unspentResp.Outputs) == 0 { 256 t.Fatal("expected at least one unspent output") 257 } 258 for _, o := range unspentResp.Outputs { 259 if o.UnlockHash == addr { 260 t.Fatal("shouldn't see addr in UnspentOutputs yet") 261 } 262 if o.IsWatchOnly { 263 t.Error("no outputs should be marked watch-only yet") 264 } 265 } 266 267 // track the address 268 err = testNode.WalletWatchAddPost([]types.UnlockHash{addr}, false) 269 if err != nil { 270 t.Fatal(err) 271 } 272 273 // output should now show up 274 unspentResp, err = testNode.WalletUnspentGet() 275 if err != nil { 276 t.Fatal("failed to get spendable outputs:", err) 277 } 278 var output modules.UnspentOutput 279 for _, o := range unspentResp.Outputs { 280 if o.UnlockHash == addr { 281 output = o 282 break 283 } 284 } 285 if output.ID == (types.OutputID{}) { 286 t.Fatal("addr not present in UnspentOutputs after WatchAddresses") 287 } 288 if !output.IsWatchOnly { 289 t.Error("output should be marked watch-only") 290 } 291 292 // create a transaction that sends an output to the void 293 txn := types.Transaction{ 294 SiacoinInputs: []types.SiacoinInput{{ 295 ParentID: types.SiacoinOutputID(output.ID), 296 UnlockConditions: uc, 297 }}, 298 SiacoinOutputs: []types.SiacoinOutput{{ 299 Value: output.Value, 300 UnlockHash: types.UnlockHash{}, 301 }}, 302 TransactionSignatures: []types.TransactionSignature{{ 303 ParentID: crypto.Hash(output.ID), 304 CoveredFields: types.CoveredFields{WholeTransaction: true}, 305 }}, 306 } 307 308 // sign the transaction 309 cg, err := testNode.ConsensusGet() 310 if err != nil { 311 t.Fatal(err) 312 } 313 sig := crypto.SignHash(txn.SigHash(0, cg.Height), sk) 314 txn.TransactionSignatures[0].Signature = sig[:] 315 316 // the resulting transaction should be valid; submit it to the tpool and 317 // mine a block to confirm it 318 err = testNode.TransactionPoolRawPost(txn, nil) 319 if err != nil { 320 t.Fatal(err) 321 } 322 testNode.MineBlock() 323 324 // the wallet should no longer list the resulting output as spendable 325 unspentResp, err = testNode.WalletUnspentGet() 326 if err != nil { 327 t.Fatal("failed to get spendable outputs:", err) 328 } 329 for _, o := range unspentResp.Outputs { 330 if o.UnlockHash == addr { 331 t.Fatal("spent output still listed as spendable") 332 } 333 } 334 } 335 336 // TestUnspentOutputs tests the UnspentOutputs method of the wallet. 337 func TestUnspentOutputs(t *testing.T) { 338 if testing.Short() { 339 t.SkipNow() 340 } 341 342 // Create a new server 343 testNode, err := siatest.NewNode(node.AllModules(siatest.TestDir(t.Name()))) 344 if err != nil { 345 t.Fatal(err) 346 } 347 defer func() { 348 if err := testNode.Close(); err != nil { 349 t.Fatal(err) 350 } 351 }() 352 353 // create a dummy address and send coins to it 354 addr := types.UnlockHash{1} 355 356 _, err = testNode.WalletSiacoinsPost(types.SiacoinPrecision.Mul64(77), addr) 357 if err != nil { 358 t.Fatal(err) 359 } 360 testNode.MineBlock() 361 362 // define a helper function to check whether addr appears in 363 // UnspentOutputs 364 addrIsPresent := func() bool { 365 wug, err := testNode.WalletUnspentGet() 366 if err != nil { 367 t.Fatal(err) 368 } 369 for _, o := range wug.Outputs { 370 if o.UnlockHash == addr { 371 return true 372 } 373 } 374 return false 375 } 376 377 // initially, the output should not show up in UnspentOutputs, because the 378 // address is not being tracked yet 379 if addrIsPresent() { 380 t.Fatal("shouldn't see addr in UnspentOutputs yet") 381 } 382 383 // add the address, but tell the wallet it hasn't been used yet. The 384 // wallet won't rescan, so it still won't see any outputs. 385 err = testNode.WalletWatchAddPost([]types.UnlockHash{addr}, true) 386 if err != nil { 387 t.Fatal(err) 388 } 389 if addrIsPresent() { 390 t.Fatal("shouldn't see addr in UnspentOutputs yet") 391 } 392 393 // remove the address, then add it again, this time telling the wallet 394 // that it has been used. 395 err = testNode.WalletWatchRemovePost([]types.UnlockHash{addr}, true) 396 if err != nil { 397 t.Fatal(err) 398 } 399 err = testNode.WalletWatchAddPost([]types.UnlockHash{addr}, false) 400 if err != nil { 401 t.Fatal(err) 402 } 403 404 // output should now show up 405 if !addrIsPresent() { 406 t.Fatal("addr not present in UnspentOutputs after AddWatchAddresses") 407 } 408 409 // remove the address, but tell the wallet that the address hasn't been 410 // used. The wallet won't rescan, so the output should still show up. 411 err = testNode.WalletWatchRemovePost([]types.UnlockHash{addr}, true) 412 if err != nil { 413 t.Fatal(err) 414 } 415 if !addrIsPresent() { 416 t.Fatal("addr should still be present in UnspentOutputs") 417 } 418 419 // add and remove the address again, this time triggering a rescan. The 420 // output should no longer appear. 421 err = testNode.WalletWatchAddPost([]types.UnlockHash{addr}, true) 422 if err != nil { 423 t.Fatal(err) 424 } 425 err = testNode.WalletWatchRemovePost([]types.UnlockHash{addr}, false) 426 if err != nil { 427 t.Fatal(err) 428 } 429 if addrIsPresent() { 430 t.Fatal("shouldn't see addr in UnspentOutputs") 431 } 432 }