decred.org/dcrdex@v1.0.5/client/asset/dcr/simnet_test.go (about) 1 //go:build harness 2 3 package dcr 4 5 // Simnet tests expect the DCR test harness to be running. 6 // 7 // Sim harness info: 8 // The harness has two wallets, alpha and beta 9 // Both wallets have confirmed UTXOs. 10 // The alpha wallet has large coinbase outputs and smaller change outputs. 11 // The beta wallet has mature vote outputs and regular transaction outputs of 12 // varying size and confirmation count. Value:Confirmations = 13 // 10:8, 18:7, 5:6, 7:5, 1:4, 15:3, 3:2, 25:1 14 15 import ( 16 "bytes" 17 "context" 18 "crypto/sha256" 19 "encoding/hex" 20 "errors" 21 "fmt" 22 "math" 23 "math/rand" 24 "os" 25 "os/exec" 26 "os/user" 27 "path/filepath" 28 "testing" 29 "time" 30 31 "decred.org/dcrdex/client/asset" 32 "decred.org/dcrdex/dex" 33 "decred.org/dcrdex/dex/config" 34 "decred.org/dcrdex/dex/encode" 35 dexdcr "decred.org/dcrdex/dex/networks/dcr" 36 "decred.org/dcrwallet/v5/wallet" 37 "github.com/davecgh/go-spew/spew" 38 "github.com/decred/dcrd/chaincfg/v3" 39 "github.com/decred/dcrd/dcrec/secp256k1/v4" 40 "github.com/decred/dcrd/dcrutil/v4" 41 "github.com/decred/dcrd/wire" 42 ) 43 44 const ( 45 alphaAddress = "SsWKp7wtdTZYabYFYSc9cnxhwFEjA5g4pFc" 46 betaAddress = "Ssge52jCzbixgFC736RSTrwAnvH3a4hcPRX" 47 gammaSeed = "1285a47d6a59f9c548b2a72c2c34a2de97967bede3844090102bbba76707fe9d" 48 vspAddr = "http://127.0.0.1:19591" 49 ) 50 51 var ( 52 tLogger dex.Logger 53 tCtx context.Context 54 tLotSize uint64 = 1e7 55 tRateStep uint64 = 100 56 tDCR = &dex.Asset{ 57 ID: 42, 58 Symbol: "dcr", 59 Version: version, 60 MaxFeeRate: 10, 61 SwapConf: 1, 62 } 63 walletPassword = []byte("abc") 64 ) 65 66 func mineAlpha() error { 67 return exec.Command("tmux", "send-keys", "-t", "dcr-harness:0", "./mine-alpha 1", "C-m").Run() 68 } 69 70 func tBackend(t *testing.T, name string, isInternal bool, blkFunc func(string)) (*ExchangeWallet, *dex.ConnectionMaster) { 71 t.Helper() 72 user, err := user.Current() 73 if err != nil { 74 t.Fatalf("error getting current user: %v", err) 75 } 76 settings := make(map[string]string) 77 if !isInternal { 78 cfgPath := filepath.Join(user.HomeDir, "dextest", "dcr", name, name+".conf") 79 var err error 80 settings, err = config.Parse(cfgPath) 81 if err != nil { 82 t.Fatalf("error reading config options: %v", err) 83 } 84 } 85 notes := make(chan asset.WalletNotification, 1) 86 walletCfg := &asset.WalletConfig{ 87 Settings: settings, 88 Emit: asset.NewWalletEmitter(notes, BipID, tLogger), 89 PeersChange: func(num uint32, err error) { 90 t.Logf("peer count = %d, err = %v", num, err) 91 }, 92 } 93 if isInternal { 94 seed, err := hex.DecodeString(gammaSeed) 95 if err != nil { 96 t.Fatal(err) 97 } 98 dataDir := t.TempDir() 99 createSPVWallet(walletPassword, seed, dataDir, 0, 0, wallet.DefaultGapLimit, chaincfg.SimNetParams()) 100 walletCfg.Type = walletTypeSPV 101 walletCfg.DataDir = dataDir 102 } 103 var backend asset.Wallet 104 backend, err = NewWallet(walletCfg, tLogger, dex.Simnet) 105 if err != nil { 106 t.Fatalf("error creating backend: %v", err) 107 } 108 cm := dex.NewConnectionMaster(backend) 109 err = cm.Connect(tCtx) 110 if err != nil { 111 t.Fatalf("error connecting backend: %v", err) 112 } 113 114 go func() { 115 for { 116 select { 117 case ni := <-notes: 118 switch ni.(type) { 119 case *asset.TipChangeNote: 120 blkFunc(name) 121 } 122 case <-tCtx.Done(): 123 return 124 } 125 } 126 }() 127 128 if isInternal { 129 i := 0 130 for { 131 ss, err := backend.SyncStatus() 132 if err != nil { 133 t.Fatal(err) 134 } 135 if ss.Synced { 136 break 137 } 138 if i == 5 { 139 t.Fatal("spv wallet not synced after 5 seconds") 140 } 141 i++ 142 time.Sleep(time.Second) 143 } 144 } 145 146 if nativeWallet, is := backend.(*NativeWallet); is { 147 return nativeWallet.ExchangeWallet, cm 148 } 149 150 return backend.(*ExchangeWallet), cm 151 } 152 153 type testRig struct { 154 backends map[string]*ExchangeWallet 155 connectionMasters map[string]*dex.ConnectionMaster 156 } 157 158 func newTestRig(t *testing.T, blkFunc func(string)) *testRig { 159 t.Helper() 160 rig := &testRig{ 161 backends: make(map[string]*ExchangeWallet), 162 connectionMasters: make(map[string]*dex.ConnectionMaster, 3), 163 } 164 rig.backends["alpha"], rig.connectionMasters["alpha"] = tBackend(t, "alpha", false, blkFunc) 165 rig.backends["beta"], rig.connectionMasters["beta"] = tBackend(t, "beta", false, blkFunc) 166 rig.backends["gamma"], rig.connectionMasters["gamma"] = tBackend(t, "gamma", true, blkFunc) 167 return rig 168 } 169 170 // alpha is an external wallet connected to dcrd. 171 func (rig *testRig) alpha() *ExchangeWallet { 172 return rig.backends["alpha"] 173 } 174 175 // beta is an external spv wallet. 176 func (rig *testRig) beta() *ExchangeWallet { 177 return rig.backends["beta"] 178 } 179 180 // gamma is an internal spv wallet. 181 func (rig *testRig) gamma() *ExchangeWallet { 182 return rig.backends["gamma"] 183 } 184 185 func (rig *testRig) close(t *testing.T) { 186 t.Helper() 187 for name, cm := range rig.connectionMasters { 188 closed := make(chan struct{}) 189 go func() { 190 cm.Disconnect() 191 close(closed) 192 }() 193 select { 194 case <-closed: 195 case <-time.NewTimer(time.Second).C: 196 t.Fatalf("failed to disconnect from %s", name) 197 } 198 } 199 } 200 201 func randBytes(l int) []byte { 202 b := make([]byte, l) 203 rand.Read(b) 204 return b 205 } 206 207 func waitNetwork() { 208 time.Sleep(time.Second * 3 / 2) 209 } 210 211 func TestMain(m *testing.M) { 212 tLogger = dex.StdOutLogger("TEST", dex.LevelTrace) 213 var shutdown func() 214 tCtx, shutdown = context.WithCancel(context.Background()) 215 doIt := func() int { 216 defer shutdown() 217 return m.Run() 218 } 219 os.Exit(doIt()) 220 } 221 222 func TestMakeBondTx(t *testing.T) { 223 rig := newTestRig(t, func(name string) { 224 tLogger.Infof("%s has reported a new block", name) 225 }) 226 defer rig.close(t) 227 228 // Get a private key for the bond script. This would come from the client's 229 // HD key chain. 230 priv, err := secp256k1.GeneratePrivateKey() 231 if err != nil { 232 t.Fatal(err) 233 } 234 pubkey := priv.PubKey() 235 236 acctID := randBytes(32) 237 fee := uint64(10_2030_4050) // ~10.2 DCR 238 const bondVer = 0 239 240 wallet := rig.beta() 241 242 // Unlock the wallet to sign the tx and get keys. 243 err = wallet.Unlock(walletPassword) 244 if err != nil { 245 t.Fatalf("error unlocking beta wallet: %v", err) 246 } 247 248 lockTime := time.Now().Add(5 * time.Minute) 249 bond, _, err := wallet.MakeBondTx(bondVer, fee, 10, lockTime, priv, acctID) 250 if err != nil { 251 t.Fatal(err) 252 } 253 coinhash, _, err := decodeCoinID(bond.CoinID) 254 if err != nil { 255 t.Fatalf("decodeCoinID: %v", err) 256 } 257 t.Logf("bond txid %v\n", coinhash) 258 t.Logf("signed tx: %x\n", bond.SignedTx) 259 t.Logf("unsigned tx: %x\n", bond.UnsignedTx) 260 t.Logf("bond script: %x\n", bond.Data) 261 t.Logf("redeem tx: %x\n", bond.RedeemTx) 262 bondMsgTx, err := msgTxFromBytes(bond.SignedTx) 263 if err != nil { 264 t.Fatalf("invalid bond tx: %v", err) 265 } 266 bondOutVersion := bondMsgTx.TxOut[0].Version 267 268 pkh := dcrutil.Hash160(pubkey.SerializeCompressed()) 269 270 lockTimeUint, pkhPush, err := dexdcr.ExtractBondDetailsV0(bondOutVersion, bond.Data) 271 if err != nil { 272 t.Fatalf("ExtractBondDetailsV0: %v", err) 273 } 274 if !bytes.Equal(pkh, pkhPush) { 275 t.Fatalf("mismatching pubkeyhash in bond script and signature (%x != %x)", pkh, pkhPush) 276 } 277 278 if lockTime.Unix() != int64(lockTimeUint) { 279 t.Fatalf("mismatching locktimes (%d != %d)", lockTime.Unix(), lockTimeUint) 280 } 281 lockTimePush := time.Unix(int64(lockTimeUint), 0) 282 t.Logf("lock time in bond script: %v", lockTimePush) 283 284 sendBondTx, err := wallet.SendTransaction(bond.SignedTx) 285 if err != nil { 286 t.Fatalf("RefundBond: %v", err) 287 } 288 sendBondTxid, _, err := decodeCoinID(sendBondTx) 289 if err != nil { 290 t.Fatalf("decodeCoinID: %v", err) 291 } 292 t.Logf("sendBondTxid: %v\n", sendBondTxid) 293 294 waitNetwork() // wait for alpha to see the txn 295 mineAlpha() 296 waitNetwork() // wait for beta to see the new block (bond must be mined for RefundBond) 297 298 refundCoin, err := wallet.RefundBond(context.Background(), bondVer, bond.CoinID, 299 bond.Data, bond.Amount, priv) 300 if err != nil { 301 t.Fatalf("RefundBond: %v", err) 302 } 303 t.Logf("refundCoin: %v\n", refundCoin) 304 } 305 306 func TestWallet(t *testing.T) { 307 tLogger.Infof("////////// WITHOUT SPLIT FUNDING TRANSACTIONS //////////") 308 runTest(t, false) 309 tLogger.Infof("////////// WITH SPLIT FUNDING TRANSACTIONS //////////") 310 runTest(t, true) 311 } 312 313 func runTest(t *testing.T, splitTx bool) { 314 tStart := time.Now() 315 blockReported := false 316 rig := newTestRig(t, func(name string) { 317 blockReported = true 318 tLogger.Infof("%s has reported a new block", name) 319 }) 320 defer rig.close(t) 321 contractValue := toAtoms(2) 322 lots := contractValue / tLotSize 323 324 inUTXOs := func(utxo asset.Coin, utxos []asset.Coin) bool { 325 for _, u := range utxos { 326 if bytes.Equal(u.ID(), utxo.ID()) { 327 return true 328 } 329 } 330 return false 331 } 332 333 // Check available amount. 334 for name, wallet := range rig.backends { 335 bal, err := wallet.Balance() 336 if err != nil { 337 t.Fatalf("error getting available: %v", err) 338 } 339 tLogger.Debugf("%s %f available, %f unconfirmed, %f locked", 340 name, float64(bal.Available)/1e8, float64(bal.Immature)/1e8, float64(bal.Locked)/1e8) 341 wallet.config().useSplitTx = splitTx 342 } 343 344 // Unlock the wallet for use. 345 err := rig.beta().Unlock(walletPassword) 346 if err != nil { 347 t.Fatalf("error unlocking beta wallet: %v", err) 348 } 349 350 ord := &asset.Order{ 351 Version: tDCR.Version, 352 Value: contractValue * 3, 353 MaxSwapCount: lots * 3, 354 MaxFeeRate: tDCR.MaxFeeRate, 355 } 356 setOrderValue := func(v uint64) { 357 ord.Value = v 358 ord.MaxSwapCount = v / tLotSize 359 } 360 361 // Grab some coins. 362 utxos, _, _, err := rig.beta().FundOrder(ord) 363 if err != nil { 364 t.Fatalf("Funding error: %v", err) 365 } 366 utxo := utxos[0] 367 368 // Coins should be locked 369 utxos, _, _, _ = rig.beta().FundOrder(ord) 370 if !splitTx && inUTXOs(utxo, utxos) { 371 t.Fatalf("received locked output") 372 } 373 rig.beta().ReturnCoins([]asset.Coin{utxo}) 374 rig.beta().ReturnCoins(utxos) 375 // Make sure we get the first utxo back with Fund. 376 utxos, _, _, _ = rig.beta().FundOrder(ord) 377 if !splitTx && !inUTXOs(utxo, utxos) { 378 t.Fatalf("unlocked output not returned") 379 } 380 rig.beta().ReturnCoins(utxos) 381 382 // Get a separate set of UTXOs for each contract. 383 setOrderValue(contractValue) 384 utxos1, _, _, err := rig.beta().FundOrder(ord) 385 if err != nil { 386 t.Fatalf("error funding first contract: %v", err) 387 } 388 // Get a separate set of UTXOs for each contract. 389 setOrderValue(contractValue * 2) 390 utxos2, _, _, err := rig.beta().FundOrder(ord) 391 if err != nil { 392 t.Fatalf("error funding second contract: %v", err) 393 } 394 395 secretKey1 := randBytes(32) 396 keyHash1 := sha256.Sum256(secretKey1) 397 secretKey2 := randBytes(32) 398 keyHash2 := sha256.Sum256(secretKey2) 399 lockTime := time.Now().Add(time.Hour * 8).UTC() 400 // Have beta send a swap contract to the alpha address. 401 contract1 := &asset.Contract{ 402 Address: alphaAddress, 403 Value: contractValue, 404 SecretHash: keyHash1[:], 405 LockTime: uint64(lockTime.Unix()), 406 } 407 contract2 := &asset.Contract{ 408 Address: alphaAddress, 409 Value: contractValue * 2, 410 SecretHash: keyHash2[:], 411 LockTime: uint64(lockTime.Unix()), 412 } 413 swaps := &asset.Swaps{ 414 Inputs: append(utxos1, utxos2...), 415 Contracts: []*asset.Contract{contract1, contract2}, 416 FeeRate: tDCR.MaxFeeRate, 417 } 418 419 receipts, _, _, err := rig.beta().Swap(swaps) 420 if err != nil { 421 t.Fatalf("error sending swap transaction: %v", err) 422 } 423 424 if len(receipts) != 2 { 425 t.Fatalf("expected 1 receipt, got %d", len(receipts)) 426 } 427 428 confCoin := receipts[0].Coin() 429 confContract := receipts[0].Contract() 430 checkConfs := func(minN uint32, expSpent bool) { 431 t.Helper() 432 confs, spent, err := rig.beta().SwapConfirmations(tCtx, confCoin.ID(), confContract, tStart) 433 if err != nil { 434 t.Fatalf("error getting %d confs: %v", minN, err) 435 } 436 if confs < minN { 437 t.Fatalf("expected %d confs, got %d", minN, confs) 438 } 439 // Not using checkConfs until after redemption, so expect spent. 440 if spent != expSpent { 441 t.Fatalf("checkConfs: expected spent = %t, got %t", expSpent, spent) 442 } 443 } 444 // Let alpha get and process that transaction. 445 waitNetwork() 446 // Check that there are 0 confirmations. 447 checkConfs(0, false) 448 449 // Unlock the wallet for use. 450 err = rig.alpha().Unlock(walletPassword) 451 if err != nil { 452 t.Fatalf("error unlocking alpha wallet: %v", err) 453 } 454 455 makeRedemption := func(swapVal uint64, receipt asset.Receipt, secret []byte) *asset.Redemption { 456 t.Helper() 457 swapOutput := receipt.Coin() 458 op := swapOutput.(*output) 459 tx, err := rig.beta().wallet.GetTransaction(tCtx, op.txHash()) 460 if err != nil || tx == nil { 461 t.Fatalf("GetTransaction: %v", err) 462 } 463 txData, err := tx.MsgTx.Bytes() 464 if err != nil { 465 t.Fatalf("msgTx.Bytes: %v", err) 466 } 467 ci, err := rig.alpha().AuditContract(swapOutput.ID(), receipt.Contract(), txData, false) 468 if err != nil { 469 t.Fatalf("error auditing contract: %v", err) 470 } 471 swapOutput = ci.Coin 472 if ci.Recipient != alphaAddress { 473 t.Fatalf("wrong address. %s != %s", ci.Recipient, alphaAddress) 474 } 475 if swapOutput.Value() != swapVal { 476 t.Fatalf("wrong contract value. wanted %d, got %d", swapVal, swapOutput.Value()) 477 } 478 _, spent, err := rig.alpha().SwapConfirmations(context.TODO(), swapOutput.ID(), receipt.Contract(), tStart) 479 if err != nil { 480 t.Fatalf("error getting confirmations: %v", err) 481 } 482 if spent { 483 t.Fatalf("swap spent") 484 } 485 if ci.Expiration.Equal(lockTime) { 486 t.Fatalf("wrong lock time. wanted %s, got %s", lockTime, ci.Expiration) 487 } 488 return &asset.Redemption{ 489 Spends: ci, 490 Secret: secret, 491 } 492 } 493 494 redemptions := []*asset.Redemption{ 495 makeRedemption(contractValue, receipts[0], secretKey1), 496 makeRedemption(contractValue*2, receipts[1], secretKey2), 497 } 498 499 _, _, _, err = rig.alpha().Redeem(&asset.RedeemForm{ 500 Redemptions: redemptions, 501 }) 502 if err != nil { 503 t.Fatalf("redemption error: %v", err) 504 } 505 506 betaSPV := rig.beta().wallet.SpvMode() 507 508 // Find the redemption 509 swapReceipt := receipts[0] 510 // The mempool find redemption request does not work in SPV mode. 511 if !betaSPV { 512 waitNetwork() 513 ctx, cancel := context.WithDeadline(tCtx, time.Now().Add(time.Second*5)) 514 defer cancel() 515 _, checkKey, err := rig.beta().FindRedemption(ctx, swapReceipt.Coin().ID(), nil) 516 if err != nil { 517 t.Fatalf("error finding unconfirmed redemption: %v", err) 518 } 519 if !bytes.Equal(checkKey, secretKey1) { 520 t.Fatalf("findRedemption (unconfirmed) key mismatch. %x != %x", checkKey, secretKey1) 521 } 522 } 523 524 // Mine a block and find the redemption again. 525 mineAlpha() 526 waitNetwork() 527 // Check that the swap has one confirmation. 528 checkConfs(1, true) 529 if !blockReported { 530 t.Fatalf("no block reported") 531 } 532 ctx, cancel2 := context.WithDeadline(tCtx, time.Now().Add(time.Second*5)) 533 defer cancel2() 534 _, _, err = rig.beta().FindRedemption(ctx, swapReceipt.Coin().ID(), nil) 535 if err != nil { 536 t.Fatalf("error finding confirmed redemption: %v", err) 537 } 538 539 // Now send another one with lockTime = now and try to refund it. 540 secretKey := randBytes(32) 541 keyHash := sha256.Sum256(secretKey) 542 lockTime = time.Now().Add(-8 * time.Hour) 543 544 // Have beta send a swap contract to the alpha address. 545 setOrderValue(contractValue) 546 utxos, _, _, _ = rig.beta().FundOrder(ord) 547 contract := &asset.Contract{ 548 Address: alphaAddress, 549 Value: contractValue, 550 SecretHash: keyHash[:], 551 LockTime: uint64(lockTime.Unix()), 552 } 553 swaps = &asset.Swaps{ 554 Inputs: utxos, 555 Contracts: []*asset.Contract{contract}, 556 FeeRate: tDCR.MaxFeeRate, 557 } 558 559 receipts, _, _, err = rig.beta().Swap(swaps) 560 if err != nil { 561 t.Fatalf("error sending swap transaction: %v", err) 562 } 563 564 if len(receipts) != 1 { 565 t.Fatalf("expected 1 receipt, got %d", len(receipts)) 566 } 567 swapReceipt = receipts[0] 568 569 waitNetwork() 570 _, err = rig.beta().Refund(swapReceipt.Coin().ID(), swapReceipt.Contract(), tDCR.MaxFeeRate/4) 571 if err != nil { 572 t.Fatalf("refund error: %v", err) 573 } 574 575 // Test Send 576 coin, err := rig.beta().Send(alphaAddress, 1e8, defaultFee) 577 if err != nil { 578 t.Fatalf("error sending fees: %v", err) 579 } 580 tLogger.Infof("fee paid with tx %s", coin.String()) 581 582 // Test Withdraw 583 coin, err = rig.beta().Withdraw(alphaAddress, 5e7, tDCR.MaxFeeRate/4) 584 if err != nil { 585 t.Fatalf("error withdrawing: %v", err) 586 } 587 tLogger.Infof("withdrew with tx %s", coin.String()) 588 589 // Lock the wallet 590 err = rig.beta().Lock() 591 if err != nil { 592 t.Fatalf("error locking wallet: %v", err) 593 } 594 } 595 596 func TestTickets(t *testing.T) { 597 rig := newTestRig(t, func(name string) { 598 tLogger.Infof("%s has reported a new block", name) 599 }) 600 defer rig.close(t) 601 tLogger.Info("Testing ticket methods with the alpha rig.") 602 testTickets(t, false, rig.alpha()) 603 // TODO: beta wallet is spv but has no vsp in its config. Add it and 604 // test here. 605 tLogger.Info("Testing ticket methods with the gamma rig.") 606 testTickets(t, true, rig.gamma()) 607 } 608 609 func testTickets(t *testing.T, isInternal bool, ew *ExchangeWallet) { 610 611 // Test StakeStatus. 612 ss, err := ew.StakeStatus() 613 if err != nil { 614 t.Fatalf("unable to get stake status: %v", err) 615 } 616 tLogger.Info("The following are stake status before setting vsp or purchasing tickets.") 617 spew.Dump(ss) 618 619 // Test SetVSP. 620 err = ew.SetVSP(vspAddr) 621 if isInternal { 622 if err != nil { 623 t.Fatalf("unexpected error setting vsp for internal wallet: %v", err) 624 } 625 } else { 626 if err == nil { 627 t.Fatal("expected error setting vsp for external wallet") 628 } 629 } 630 631 // Test PurchaseTickets. 632 if err := ew.Unlock(walletPassword); err != nil { 633 t.Fatalf("unable to unlock wallet: %v", err) 634 } 635 636 if err := ew.PurchaseTickets(3, 20); err != nil { 637 t.Fatalf("error purchasing tickets: %v", err) 638 } 639 640 var currentDeployments []chaincfg.ConsensusDeployment 641 var bestVer uint32 642 for ver, deps := range ew.chainParams.Deployments { 643 if bestVer == 0 || ver > bestVer { 644 currentDeployments = deps 645 bestVer = ver 646 } 647 } 648 649 choices := make(map[string]string) 650 for _, d := range currentDeployments { 651 choices[d.Vote.Id] = d.Vote.Choices[rand.Int()%len(d.Vote.Choices)].Id 652 } 653 654 aPubKey := "034a43df1b95bf1b0dd77b53b8d880d4a5f47376fb036a5be5c1f3ba8d12ef65d7" 655 bPubKey := "02028dd2bbabbcd262c3e2326ae27a61fcc935beb2bbc2f0f76f3f5025ba4a3c5d" 656 657 treasuryPolicy := map[string]string{ 658 aPubKey: "yes", 659 bPubKey: "no", 660 } 661 662 var tspendPolicy map[string]string 663 if spvw, is := ew.wallet.(*spvWallet); is { 664 if dcrw, is := spvw.dcrWallet.(*extendedWallet); is { 665 txIn := &wire.TxIn{SignatureScript: encode.RandomBytes(66 + secp256k1.PubKeyBytesLenCompressed)} 666 tspendA := &wire.MsgTx{Expiry: math.MaxUint32 - 1, TxIn: []*wire.TxIn{txIn}} 667 tspendB := &wire.MsgTx{Expiry: math.MaxUint32 - 2, TxIn: []*wire.TxIn{txIn}} 668 txHashA, txHashB := tspendA.TxHash(), tspendB.TxHash() 669 tspendPolicy = map[string]string{ 670 txHashA.String(): "yes", 671 txHashB.String(): "no", 672 } 673 for _, tx := range []*wire.MsgTx{tspendA, tspendB} { 674 if err := dcrw.AddTSpend(*tx); err != nil { 675 t.Fatalf("Error adding tspend: %v", err) 676 } 677 } 678 } 679 } 680 681 // Test SetVotingPreferences. 682 if err := ew.SetVotingPreferences(choices, tspendPolicy, treasuryPolicy); err != nil { 683 t.Fatalf("Error setting voting preferences: %v", err) 684 } 685 686 // Test StakeStatus again. 687 ss, err = ew.StakeStatus() 688 if err != nil { 689 t.Fatalf("Unable to get stake status: %v", err) 690 } 691 tLogger.Info("The following are stake status after setting vsp and purchasing tickets.") 692 spew.Dump(ss) 693 694 if len(ss.Stances.Agendas) != len(choices) { 695 t.Fatalf("wrong number of vote choices. expected %d, got %d", len(choices), len(ss.Stances.Agendas)) 696 } 697 698 for _, agenda := range ss.Stances.Agendas { 699 choiceID, found := choices[agenda.ID] 700 if !found { 701 t.Fatalf("unknown agenda %s", agenda.ID) 702 } 703 if agenda.CurrentChoice != choiceID { 704 t.Fatalf("wrong choice reported. expected %s, got %s", choiceID, agenda.CurrentChoice) 705 } 706 } 707 708 if len(ss.Stances.TreasuryKeys) != len(treasuryPolicy) { 709 t.Fatalf("wrong number of treasury keys. expected %d, got %d", len(treasuryPolicy), len(ss.Stances.TreasuryKeys)) 710 } 711 712 for _, tp := range ss.Stances.TreasuryKeys { 713 policy, found := treasuryPolicy[tp.Key] 714 if !found { 715 t.Fatalf("unknown treasury key %s", tp.Key) 716 } 717 if tp.Policy != policy { 718 t.Fatalf("wrong policy reported. expected %s, got %s", policy, tp.Policy) 719 } 720 } 721 722 if len(ss.Stances.TreasurySpends) != len(tspendPolicy) { 723 t.Fatalf("wrong number of tspends. expected %d, got %d", len(tspendPolicy), len(ss.Stances.TreasurySpends)) 724 } 725 726 for _, tspend := range ss.Stances.TreasurySpends { 727 policy, found := tspendPolicy[tspend.Hash] 728 if !found { 729 t.Fatalf("unknown tspend tx %s", tspend.Hash) 730 } 731 if tspend.CurrentPolicy != policy { 732 t.Fatalf("wrong policy reported. expected %s, got %s", policy, tspend.CurrentPolicy) 733 } 734 } 735 } 736 737 func TestExternalFeeRate(t *testing.T) { 738 fetchRateWithTimeout(t, dex.Mainnet) 739 fetchRateWithTimeout(t, dex.Testnet) 740 } 741 742 func fetchRateWithTimeout(t *testing.T, net dex.Network) { 743 ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 744 defer cancel() 745 feeRate, err := fetchFeeFromOracle(ctx, net, 2) 746 if err != nil { 747 t.Fatalf("error fetching %s fees: %v", net, err) 748 } 749 fmt.Printf("##### Fee rate fetched for %s! %.8f DCR/kB \n", net, feeRate) 750 } 751 752 func TestWalletTxBalanceSync(t *testing.T) { 753 rig := newTestRig(t, func(name string) { 754 tLogger.Infof("%s has reported a new block", name) 755 }) 756 defer rig.close(t) 757 758 beta := rig.beta() 759 gamma := rig.gamma() 760 761 err := beta.Unlock(walletPassword) 762 if err != nil { 763 t.Fatalf("error unlocking beta wallet: %v", err) 764 } 765 err = gamma.Unlock(walletPassword) 766 if err != nil { 767 t.Fatalf("error unlocking gamma wallet: %v", err) 768 } 769 770 t.Run("rpc", func(t *testing.T) { 771 testWalletTxBalanceSync(t, gamma, beta) 772 }) 773 t.Run("spv", func(t *testing.T) { 774 testWalletTxBalanceSync(t, beta, gamma) 775 }) 776 } 777 778 // This tests that redemptions becoming available in the balance and the 779 // asset.WalletTransaction returned from WalletTransaction becomes confirmed 780 // at the same time. 781 func testWalletTxBalanceSync(t *testing.T, fromWallet, toWallet *ExchangeWallet) { 782 receivingAddr, err := toWallet.DepositAddress() 783 if err != nil { 784 t.Fatalf("error getting deposit address: %v", err) 785 } 786 787 order := &asset.Order{ 788 Value: toAtoms(1), 789 FeeSuggestion: 10, 790 MaxSwapCount: 1, 791 MaxFeeRate: 20, 792 } 793 coins, _, _, err := fromWallet.FundOrder(order) 794 if err != nil { 795 t.Fatalf("error funding order: %v", err) 796 } 797 798 secret := randBytes(32) 799 secretHash := sha256.Sum256(secret) 800 contract := &asset.Contract{ 801 Address: receivingAddr, 802 Value: order.Value, 803 SecretHash: secretHash[:], 804 LockTime: uint64(time.Now().Add(-1 * time.Hour).Unix()), 805 } 806 swaps := &asset.Swaps{ 807 Inputs: coins, 808 FeeRate: 10, 809 Contracts: []*asset.Contract{ 810 contract, 811 }, 812 } 813 receipts, _, _, err := fromWallet.Swap(swaps) 814 if err != nil { 815 t.Fatalf("error swapping: %v", err) 816 } 817 receipt := receipts[0] 818 819 var auditInfo *asset.AuditInfo 820 for i := 0; i < 10; i++ { 821 auditInfo, err = toWallet.AuditContract(receipt.Coin().ID(), receipt.Contract(), []byte{}, false) 822 if err == nil { 823 break 824 } 825 826 time.Sleep(5 * time.Second) 827 } 828 if err != nil { 829 t.Fatalf("error auditing contract: %v", err) 830 } 831 832 balance, err := toWallet.Balance() 833 if err != nil { 834 t.Fatalf("error getting balance: %v", err) 835 } 836 _, out, _, err := toWallet.Redeem(&asset.RedeemForm{ 837 Redemptions: []*asset.Redemption{ 838 { 839 Spends: auditInfo, 840 Secret: secret, 841 }, 842 }, 843 FeeSuggestion: 10, 844 }) 845 if err != nil { 846 t.Fatalf("error redeeming: %v", err) 847 } 848 849 confirmSync := func(originalBalance uint64, coinID []byte) { 850 t.Helper() 851 852 for i := 0; i < 10; i++ { 853 balance, err := toWallet.Balance() 854 if err != nil { 855 t.Fatalf("error getting balance: %v", err) 856 } 857 balDiff := balance.Available - originalBalance 858 859 var confirmed bool 860 var txDiff uint64 861 if wt, err := toWallet.WalletTransaction(context.Background(), hex.EncodeToString(coinID)); err == nil { 862 confirmed = wt.Confirmed 863 txDiff = wt.Amount 864 if wt.Type != asset.Receive { 865 txDiff -= wt.Fees 866 } 867 } else if !errors.Is(err, asset.CoinNotFoundError) { 868 t.Fatal(err) 869 } 870 871 balanceChanged := balance.Available != originalBalance 872 if confirmed != balanceChanged { 873 if balanceChanged && !confirmed { 874 for j := 0; j < 20; j++ { 875 if wt, err := toWallet.WalletTransaction(context.Background(), hex.EncodeToString(coinID)); err == nil && wt.Confirmed { 876 t.Fatalf("took %d seconds after balance changed before tx was confirmed", j/2) 877 } 878 time.Sleep(500 * time.Millisecond) 879 } 880 } 881 t.Fatalf("confirmed status does not match balance change. confirmed = %v, balance changed = %d", confirmed, balDiff) 882 } 883 884 if confirmed { 885 if balDiff != txDiff { 886 t.Fatalf("balance and transaction diffs do not match. balance diff = %d, tx diff = %d", balDiff, txDiff) 887 } 888 return 889 } 890 891 time.Sleep(5 * time.Second) 892 } 893 894 t.Fatal("timed out waiting for balance and transaction to sync") 895 } 896 897 confirmSync(balance.Available, out.ID()) 898 899 balance, err = toWallet.Balance() 900 if err != nil { 901 t.Fatalf("error getting balance: %v", err) 902 } 903 904 receivingAddr, err = toWallet.DepositAddress() 905 if err != nil { 906 t.Fatalf("error getting deposit address: %v", err) 907 } 908 909 coin, err := fromWallet.Send(receivingAddr, toAtoms(1), 10) 910 if err != nil { 911 t.Fatalf("error sending: %v", err) 912 } 913 914 confirmSync(balance.Available, coin.ID()) 915 }