decred.org/dcrdex@v1.0.5/server/asset/dcr/dcr_test.go (about) 1 //go:build !dcrlive 2 3 // These tests will not be run if the dcrlive build tag is set. 4 5 package dcr 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/binary" 11 "encoding/hex" 12 "fmt" 13 "math" 14 "math/rand" 15 "os" 16 "path/filepath" 17 "strconv" 18 "sync" 19 "testing" 20 "time" 21 22 "decred.org/dcrdex/dex" 23 dexdcr "decred.org/dcrdex/dex/networks/dcr" 24 "decred.org/dcrdex/server/account" 25 "decred.org/dcrdex/server/asset" 26 "github.com/decred/dcrd/chaincfg/chainhash" 27 "github.com/decred/dcrd/chaincfg/v3" 28 "github.com/decred/dcrd/dcrec" 29 "github.com/decred/dcrd/dcrec/edwards/v2" 30 "github.com/decred/dcrd/dcrec/secp256k1/v4" 31 "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" 32 "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" 33 "github.com/decred/dcrd/dcrutil/v4" 34 "github.com/decred/dcrd/hdkeychain/v3" 35 chainjson "github.com/decred/dcrd/rpc/jsonrpc/types/v4" 36 "github.com/decred/dcrd/txscript/v4" 37 "github.com/decred/dcrd/txscript/v4/stdaddr" 38 "github.com/decred/dcrd/txscript/v4/stdscript" 39 "github.com/decred/dcrd/wire" 40 flags "github.com/jessevdk/go-flags" 41 ) 42 43 var tLogger = dex.StdOutLogger("TEST", dex.LevelTrace) 44 45 func TestMain(m *testing.M) { 46 // Set the global chainParams. 47 chainParams = chaincfg.MainNetParams() 48 os.Exit(m.Run()) 49 } 50 51 // TestLoadConfig checks that configuration parsing works as expected. 52 func TestLoadConfig(t *testing.T) { 53 cfg := &config{} 54 parsedCfg := &config{} 55 56 tempDir := t.TempDir() 57 filePath := filepath.Join(tempDir, "test.conf") 58 rootParser := flags.NewParser(cfg, flags.None) 59 iniParser := flags.NewIniParser(rootParser) 60 61 runCfg := func(config *config) error { 62 *cfg = *config 63 err := iniParser.WriteFile(filePath, flags.IniNone) 64 if err != nil { 65 return err 66 } 67 parsedCfg, err = loadConfig(filePath, dex.Mainnet) 68 return err 69 } 70 71 // Try with just the name. Error expected. 72 err := runCfg(&config{ 73 RPCUser: "somename", 74 }) 75 if err == nil { 76 t.Fatalf("no error when just name provided") 77 } 78 79 // Try with just the password. Error expected. 80 err = runCfg(&config{ 81 RPCPass: "somepass", 82 }) 83 if err == nil { 84 t.Fatalf("no error when just password provided") 85 } 86 87 // Give both name and password. This should not be an error. 88 err = runCfg(&config{ 89 RPCUser: "somename", 90 RPCPass: "somepass", 91 }) 92 if err != nil { 93 t.Fatalf("unexpected error when both name and password provided: %v", err) 94 } 95 if parsedCfg.RPCListen != defaultMainnet { 96 t.Fatalf("unexpected default rpc address. wanted %s, got %s", defaultMainnet, cfg.RPCListen) 97 } 98 // sanity check for name and password match 99 if parsedCfg.RPCUser != cfg.RPCUser { 100 t.Fatalf("name mismatch") 101 } 102 if parsedCfg.RPCPass != cfg.RPCPass { 103 t.Fatalf("password mismatch") 104 } 105 if parsedCfg.RPCCert != defaultRPCCert { 106 t.Errorf("RPCCert not set implicitly") 107 } 108 err = runCfg(&config{ 109 RPCUser: "abc", 110 RPCPass: "def", 111 RPCListen: "123", 112 RPCCert: "456", 113 }) 114 if err != nil { 115 t.Errorf("unexpected error when settings RPCListen/RPCCert explicitly: %v", err) 116 } 117 if cfg.RPCListen != "123" { 118 t.Errorf("RPCListen not set to provided value") 119 } 120 if cfg.RPCCert != "456" { 121 t.Errorf("RPCCert not set to provided value") 122 } 123 } 124 125 // The remaining tests use the testBlockchain which is a stub for 126 // rpcclient.Client. UTXOs, transactions and blocks are added to the blockchain 127 // as jsonrpc types to be requested by the Backend. 128 // 129 // General formula for testing 130 // 1. Create a Backend with the node field set to a testNode 131 // 2. Create a fake UTXO and all of the associated jsonrpc-type blocks and 132 // transactions and add the to the test blockchain. 133 // 3. Verify the Backend and UTXO methods are returning whatever is expected. 134 // 4. Optionally add more blocks and/or transactions to the blockchain and check 135 // return values again, as things near the top of the chain can change. 136 137 func randomBytes(len int) []byte { 138 bytes := make([]byte, len) 139 rand.Read(bytes) 140 return bytes 141 } 142 143 func randomHash() *chainhash.Hash { 144 hash := new(chainhash.Hash) 145 err := hash.SetBytes(randomBytes(32)) 146 if err != nil { 147 fmt.Printf("chainhash.Hash.SetBytes error: %v", err) 148 } 149 return hash 150 } 151 152 // A fake "blockchain" to be used for RPC calls by the dcrNode. 153 type testBlockChain struct { 154 txOuts map[string]*chainjson.GetTxOutResult 155 txRaws map[chainhash.Hash]*chainjson.TxRawResult 156 blocks map[chainhash.Hash]*chainjson.GetBlockVerboseResult 157 hashes map[int64]*chainhash.Hash 158 } 159 160 // The testChain is a "blockchain" to store RPC responses for the Backend 161 // node stub to request. 162 var testChainMtx sync.RWMutex 163 var testChain testBlockChain 164 165 // This must be called before using the testNode, and should be called 166 // in-between independent tests. 167 func cleanTestChain() { 168 testChainMtx.Lock() 169 testChain = testBlockChain{ 170 txOuts: make(map[string]*chainjson.GetTxOutResult), 171 txRaws: make(map[chainhash.Hash]*chainjson.TxRawResult), 172 blocks: make(map[chainhash.Hash]*chainjson.GetBlockVerboseResult), 173 hashes: make(map[int64]*chainhash.Hash), 174 } 175 testChainMtx.Unlock() 176 } 177 178 // A stub to replace rpcclient.Client for offline testing. 179 type testNode struct { 180 blockchainInfo *chainjson.GetBlockChainInfoResult 181 blockchainInfoErr error 182 } 183 184 // Store utxo info as a concatenated string hash:vout. 185 func txOutID(txHash *chainhash.Hash, index uint32) string { 186 return txHash.String() + ":" + strconv.Itoa(int(index)) 187 } 188 189 const optimalFeeRate uint64 = 22 190 191 func (*testNode) EstimateSmartFee(_ context.Context, confirmations int64, mode chainjson.EstimateSmartFeeMode) (*chainjson.EstimateSmartFeeResult, error) { 192 optimalRate := float64(optimalFeeRate) * 1e-5 // optimalFeeRate: 22 atoms/byte = 0.00022 DCR/KB * 1e8 atoms/DCR * 1e-3 KB/Byte 193 // fmt.Println((float64(optimalFeeRate)*1e-5)-0.00022) 194 return &chainjson.EstimateSmartFeeResult{FeeRate: optimalRate}, nil 195 } 196 197 // Part of the dcrNode interface. 198 func (*testNode) GetTxOut(_ context.Context, txHash *chainhash.Hash, index uint32, tree int8, _ bool) (*chainjson.GetTxOutResult, error) { 199 outID := txOutID(txHash, index) 200 testChainMtx.RLock() 201 defer testChainMtx.RUnlock() 202 out := testChain.txOuts[outID] 203 // Unfound is not an error for GetTxOut. 204 return out, nil 205 } 206 207 func (*testNode) SendRawTransaction(ctx context.Context, tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, error) { 208 hash := tx.TxHash() 209 return &hash, nil 210 } 211 212 // Part of the dcrNode interface. 213 func (*testNode) GetRawTransactionVerbose(_ context.Context, txHash *chainhash.Hash) (*chainjson.TxRawResult, error) { 214 testChainMtx.RLock() 215 defer testChainMtx.RUnlock() 216 tx, found := testChain.txRaws[*txHash] 217 if !found { 218 return nil, fmt.Errorf("test transaction not found") 219 } 220 return tx, nil 221 } 222 223 // Part of the dcrNode interface. 224 func (*testNode) GetBlockVerbose(_ context.Context, blockHash *chainhash.Hash, verboseTx bool) (*chainjson.GetBlockVerboseResult, error) { 225 testChainMtx.RLock() 226 defer testChainMtx.RUnlock() 227 block, found := testChain.blocks[*blockHash] 228 if !found { 229 return nil, fmt.Errorf("test block not found") 230 } 231 return block, nil 232 } 233 234 // Part of the dcrNode interface. 235 func (*testNode) GetBlockHash(_ context.Context, blockHeight int64) (*chainhash.Hash, error) { 236 testChainMtx.RLock() 237 defer testChainMtx.RUnlock() 238 hash, found := testChain.hashes[blockHeight] 239 if !found { 240 return nil, fmt.Errorf("test hash not found") 241 } 242 return hash, nil 243 } 244 245 // Part of the dcrNode interface. 246 func (*testNode) GetBestBlockHash(context.Context) (*chainhash.Hash, error) { 247 testChainMtx.RLock() 248 defer testChainMtx.RUnlock() 249 if len(testChain.hashes) == 0 { 250 return nil, fmt.Errorf("no blocks in testChain") 251 } 252 var bestHeight int64 253 for height := range testChain.hashes { 254 if height > bestHeight { 255 bestHeight = height 256 } 257 } 258 return testChain.hashes[bestHeight], nil 259 } 260 261 func (t *testNode) GetBlockChainInfo(context.Context) (*chainjson.GetBlockChainInfoResult, error) { 262 if t.blockchainInfoErr != nil { 263 return nil, t.blockchainInfoErr 264 } 265 return t.blockchainInfo, nil 266 } 267 268 // Part of the dcrNode interface. 269 func (testNode) GetRawTransaction(_ context.Context, txHash *chainhash.Hash) (*dcrutil.Tx, error) { 270 return nil, nil 271 } 272 273 // Create a chainjson.GetTxOutResult such as is returned from GetTxOut. 274 func testGetTxOut(confirmations int64, pkScript []byte) *chainjson.GetTxOutResult { 275 return &chainjson.GetTxOutResult{ 276 Confirmations: confirmations, 277 ScriptPubKey: chainjson.ScriptPubKeyResult{ 278 Hex: hex.EncodeToString(pkScript), 279 }, 280 } 281 } 282 283 // Create a *chainjson.TxRawResult such as is returned by 284 // GetRawTransactionVerbose, kinda, no Vin and other things. 285 func testRawTransactionVerbose(msgTx *wire.MsgTx, txid, blockHash *chainhash.Hash, blockHeight, 286 confirmations int64) *chainjson.TxRawResult { 287 288 var hash string 289 if blockHash != nil { 290 hash = blockHash.String() 291 } 292 hexTx, err := msgTx.Bytes() 293 if err != nil { 294 fmt.Printf("error encoding MsgTx") 295 } 296 297 vouts := make([]chainjson.Vout, 0, len(msgTx.TxOut)) 298 for i, vout := range msgTx.TxOut { 299 disbuf, _ := txscript.DisasmString(vout.PkScript) 300 sc, addrs := stdscript.ExtractAddrs(vout.Version, vout.PkScript, chainParams) 301 reqSigs := stdscript.DetermineRequiredSigs(vout.Version, vout.PkScript) 302 303 addrStrs := make([]string, len(addrs)) 304 for i, addr := range addrs { 305 addrStrs[i] = addr.String() 306 } 307 308 vouts = append(vouts, chainjson.Vout{ 309 Value: dcrutil.Amount(vout.Value).ToCoin(), 310 N: uint32(i), 311 Version: vout.Version, 312 ScriptPubKey: chainjson.ScriptPubKeyResult{ 313 Asm: disbuf, 314 Hex: hex.EncodeToString(vout.PkScript), 315 ReqSigs: int32(reqSigs), 316 Type: sc.String(), 317 Addresses: addrStrs, 318 }, 319 }) 320 } 321 322 return &chainjson.TxRawResult{ 323 Hex: hex.EncodeToString(hexTx), 324 Txid: txid.String(), 325 BlockHash: hash, 326 BlockHeight: blockHeight, 327 Confirmations: confirmations, 328 Vout: vouts, 329 } 330 } 331 332 // Add a transaction output and it's getrawtransaction data. 333 func testAddTxOut(msgTx *wire.MsgTx, vout uint32, txHash, blockHash *chainhash.Hash, blockHeight, confirmations int64) *chainjson.GetTxOutResult { 334 txOut := testGetTxOut(confirmations, msgTx.TxOut[vout].PkScript) 335 testChainMtx.Lock() 336 testChain.txOuts[txOutID(txHash, vout)] = txOut 337 testChainMtx.Unlock() 338 testAddTxVerbose(msgTx, txHash, blockHash, blockHeight, confirmations) 339 return txOut 340 } 341 342 // Add a chainjson.TxRawResult to the blockchain. 343 func testAddTxVerbose(msgTx *wire.MsgTx, txHash, blockHash *chainhash.Hash, blockHeight, confirmations int64) *chainjson.TxRawResult { 344 tx := testRawTransactionVerbose(msgTx, txHash, blockHash, blockHeight, confirmations) 345 testChainMtx.Lock() 346 defer testChainMtx.Unlock() 347 testChain.txRaws[*txHash] = tx 348 return tx 349 } 350 351 // Add a GetBlockVerboseResult to the blockchain. 352 func testAddBlockVerbose(blockHash *chainhash.Hash, confirmations int64, height uint32, voteBits uint16) *chainhash.Hash { 353 if blockHash == nil { 354 blockHash = randomHash() 355 } 356 testChainMtx.Lock() 357 defer testChainMtx.Unlock() 358 if voteBits&1 != 0 { 359 testChain.hashes[int64(height)] = blockHash 360 } 361 testChain.blocks[*blockHash] = &chainjson.GetBlockVerboseResult{ 362 Hash: blockHash.String(), 363 Confirmations: confirmations, 364 Height: int64(height), 365 VoteBits: voteBits, 366 } 367 return blockHash 368 } 369 370 // An element of the TxRawResult vout array. 371 func testVout(value float64, pkScript []byte) chainjson.Vout { 372 return chainjson.Vout{ 373 Value: value, 374 ScriptPubKey: chainjson.ScriptPubKeyResult{ 375 Hex: hex.EncodeToString(pkScript), 376 }, 377 } 378 } 379 380 // An element of the TxRawResult vin array. 381 func testVin(txHash *chainhash.Hash, vout uint32) chainjson.Vin { 382 return chainjson.Vin{ 383 Txid: txHash.String(), 384 Vout: vout, 385 } 386 } 387 388 type testAuth struct { 389 pubkey []byte 390 pkHash []byte 391 msg []byte 392 sig []byte 393 } 394 395 type testMsgTx struct { 396 tx *wire.MsgTx 397 auth *testAuth 398 vout uint32 399 script []byte 400 } 401 402 // Generate a public key on the secp256k1 curve. 403 func genPubkey() ([]byte, []byte) { 404 priv, err := secp256k1.GeneratePrivateKey() 405 if err != nil { 406 panic(err.Error()) 407 } 408 pub := priv.PubKey() 409 pubkey := pub.SerializeCompressed() 410 pkHash := dcrutil.Hash160(pubkey) 411 return pubkey, pkHash 412 } 413 414 func s256Auth(msg []byte) *testAuth { 415 priv, err := secp256k1.GeneratePrivateKey() 416 if err != nil { 417 fmt.Printf("s256Auth error: %v\n", err) 418 } 419 pubkey := priv.PubKey().SerializeCompressed() 420 if msg == nil { 421 msg = randomBytes(32) 422 } 423 hash := chainhash.HashB(msg) 424 sig := ecdsa.Sign(priv, hash) 425 return &testAuth{ 426 pubkey: pubkey, 427 pkHash: dcrutil.Hash160(pubkey), 428 msg: msg, 429 sig: sig.Serialize(), 430 } 431 } 432 433 func edwardsAuth(msg []byte) *testAuth { 434 priv, err := edwards.GeneratePrivateKey() 435 if err != nil { 436 fmt.Printf("edwardsAuth error: %v\n", err) 437 } 438 pubkey := priv.PubKey().Serialize() 439 if msg == nil { 440 msg = randomBytes(32) 441 } 442 hash := chainhash.HashB(msg) 443 sig, err := priv.Sign(hash) 444 if err != nil { 445 fmt.Printf("edwardsAuth sign error: %v\n", err) 446 } 447 return &testAuth{ 448 pubkey: pubkey, 449 pkHash: dcrutil.Hash160(pubkey), 450 msg: msg, 451 sig: sig.Serialize(), 452 } 453 } 454 455 func schnorrAuth(msg []byte) *testAuth { 456 priv, err := secp256k1.GeneratePrivateKey() 457 if err != nil { 458 fmt.Printf("schnorrAuth error: %v\n", err) 459 } 460 pubkey := priv.PubKey().SerializeCompressed() 461 if msg == nil { 462 msg = randomBytes(32) 463 } 464 hash := chainhash.HashB(msg) 465 sig, err := schnorr.Sign(priv, hash) 466 if err != nil { 467 fmt.Printf("schnorrAuth sign error: %v\n", err) 468 } 469 return &testAuth{ 470 pubkey: pubkey, 471 pkHash: dcrutil.Hash160(pubkey), 472 msg: msg, 473 sig: sig.Serialize(), 474 } 475 } 476 477 // A pay-to-script-hash pubkey script. 478 func newP2PKHScript(sigType dcrec.SignatureType) ([]byte, *testAuth) { 479 var auth *testAuth 480 var addr stdaddr.Address 481 var err error 482 switch sigType { 483 case dcrec.STEcdsaSecp256k1: 484 auth = s256Auth(nil) 485 addr, err = stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(auth.pkHash, chainParams) 486 case dcrec.STEd25519: 487 auth = edwardsAuth(nil) 488 addr, err = stdaddr.NewAddressPubKeyHashEd25519V0(auth.pkHash, chainParams) 489 case dcrec.STSchnorrSecp256k1: 490 auth = schnorrAuth(nil) 491 addr, err = stdaddr.NewAddressPubKeyHashSchnorrSecp256k1V0(auth.pkHash, chainParams) 492 default: 493 fmt.Printf("newP2PKHScript unknown sigType") 494 return nil, nil 495 } 496 if err != nil { 497 fmt.Printf("NewAddressPubKeyHash error: %v\n", err) 498 return nil, nil 499 } 500 _, pkScript := addr.PaymentScript() 501 if err != nil { 502 fmt.Printf("addr PaymentScript error: %v\n", err) 503 } 504 return pkScript, auth 505 } 506 507 // A pay-to-script-hash pubkey script, with a prepended stake-tree indicator 508 // byte. 509 func newStakeP2PKHScript(opcode byte) ([]byte, *testAuth) { 510 script, auth := newP2PKHScript(dcrec.STEcdsaSecp256k1) 511 stakeScript := make([]byte, 0, len(script)+1) 512 stakeScript = append(stakeScript, opcode) 513 stakeScript = append(stakeScript, script...) 514 return stakeScript, auth 515 } 516 517 // A MsgTx for a regular transaction with a single output. No inputs, so it's 518 // not really a valid transaction, but that's okay on testBlockchain and 519 // irrelevant to Backend. 520 func testMsgTxRegular(sigType dcrec.SignatureType) *testMsgTx { 521 pkScript, auth := newP2PKHScript(sigType) 522 msgTx := wire.NewMsgTx() 523 msgTx.AddTxOut(wire.NewTxOut(1, pkScript)) 524 return &testMsgTx{ 525 tx: msgTx, 526 auth: auth, 527 } 528 } 529 530 // Information about a swap contract. 531 type testMsgTxSwap struct { 532 tx *wire.MsgTx 533 contract []byte 534 recipient stdaddr.Address 535 refund stdaddr.Address 536 } 537 538 // Create a swap (initialization) contract with random pubkeys and return the 539 // pubkey script and addresses. 540 func testSwapContract() ([]byte, stdaddr.Address, stdaddr.Address) { 541 lockTime := time.Now().Add(time.Hour * 8).Unix() 542 secretKey := randomBytes(32) 543 _, receiverPKH := genPubkey() 544 _, senderPKH := genPubkey() 545 contract, err := txscript.NewScriptBuilder(). 546 AddOps([]byte{ 547 txscript.OP_IF, 548 txscript.OP_SIZE, 549 }).AddInt64(32). 550 AddOps([]byte{ 551 txscript.OP_EQUALVERIFY, 552 txscript.OP_SHA256, 553 }).AddData(secretKey). 554 AddOps([]byte{ 555 txscript.OP_EQUALVERIFY, 556 txscript.OP_DUP, 557 txscript.OP_HASH160, 558 }).AddData(receiverPKH). 559 AddOp(txscript.OP_ELSE). 560 AddInt64(lockTime).AddOps([]byte{ 561 txscript.OP_CHECKLOCKTIMEVERIFY, 562 txscript.OP_DROP, 563 txscript.OP_DUP, 564 txscript.OP_HASH160, 565 }).AddData(senderPKH). 566 AddOps([]byte{ 567 txscript.OP_ENDIF, 568 txscript.OP_EQUALVERIFY, 569 txscript.OP_CHECKSIG, 570 }).Script() 571 if err != nil { 572 fmt.Printf("testSwapContract error: %v\n", err) 573 } 574 receiverAddr, _ := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(receiverPKH, chainParams) 575 refundAddr, _ := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(senderPKH, chainParams) 576 return contract, receiverAddr, refundAddr 577 } 578 579 // Create a transaction with a P2SH swap output at vout 0. 580 func testMsgTxSwapInit(val int64) *testMsgTxSwap { 581 msgTx := wire.NewMsgTx() 582 contract, recipient, refund := testSwapContract() 583 scriptHash := stdaddr.Hash160(contract) 584 pkScript, err := txscript.NewScriptBuilder(). 585 AddOp(txscript.OP_HASH160). 586 AddData(scriptHash). 587 AddOp(txscript.OP_EQUAL). 588 Script() 589 if err != nil { 590 fmt.Printf("script building error in testMsgTxSwapInit: %v", err) 591 } 592 msgTx.AddTxOut(wire.NewTxOut(val, pkScript)) 593 return &testMsgTxSwap{ 594 tx: msgTx, 595 contract: contract, 596 recipient: recipient, 597 refund: refund, 598 } 599 } 600 601 // A MsgTx for a vote. Votes have a stricter set of requirements to pass 602 // txscript.IsSSGen, so some extra inputs and outputs must be constructed. 603 func testMsgTxVote() *testMsgTx { 604 msgTx := wire.NewMsgTx() 605 stakeBase := wire.NewTxIn(wire.NewOutPoint(&zeroHash, math.MaxUint32, 0), 0, nil) 606 stakeBase.BlockHeight = wire.NullBlockHeight 607 stakeBase.BlockIndex = wire.NullBlockIndex 608 msgTx.AddTxIn(stakeBase) 609 // Second outpoint needs to be stake tree 610 msgTx.AddTxIn(wire.NewTxIn(wire.NewOutPoint(&zeroHash, 0, 1), 0, nil)) 611 // First output must have OP_RETURN script 612 script1, err := txscript.NewScriptBuilder(). 613 AddOp(txscript.OP_RETURN).AddData(randomBytes(36)).Script() 614 if err != nil { 615 fmt.Printf("script1 building error in testMsgTxVote: %v", err) 616 } 617 msgTx.AddTxOut(wire.NewTxOut(0, script1)) 618 // output 2 619 script2, err := txscript.NewScriptBuilder(). 620 AddOp(txscript.OP_RETURN).AddData(randomBytes(2)).Script() 621 if err != nil { 622 fmt.Printf("script2 building error in testMsgTxVote: %v", err) 623 } 624 msgTx.AddTxOut(wire.NewTxOut(1, script2)) 625 // Now just a P2PKH script with a prepended OP_SSGEN 626 script3, auth := newStakeP2PKHScript(txscript.OP_SSGEN) 627 msgTx.AddTxOut(wire.NewTxOut(2, script3)) 628 return &testMsgTx{ 629 tx: msgTx, 630 auth: auth, 631 vout: 2, 632 } 633 } 634 635 type testMultiSigAuth struct { 636 pubkeys [][]byte 637 pkHashes [][]byte 638 msg []byte 639 sigs [][]byte 640 } 641 642 // Information about a transaction with a P2SH output. 643 type testMsgTxP2SH struct { 644 tx *wire.MsgTx 645 auth *testMultiSigAuth 646 vout uint32 647 script []byte 648 n int 649 m int 650 } 651 652 func multiSigScript(pubkeys []*stdaddr.AddressPubKeyEcdsaSecp256k1V0, numReq int64) ([]byte, error) { 653 builder := txscript.NewScriptBuilder().AddInt64(numReq) 654 for _, key := range pubkeys { 655 builder.AddData(key.SerializedPubKey()) 656 } 657 builder.AddInt64(int64(len(pubkeys))).AddOp(txscript.OP_CHECKMULTISIG) 658 return builder.Script() 659 } 660 661 // An M-of-N mutli-sig script. This script is pay-to-pubkey. 662 func testMultiSigScriptMofN(m, n int) ([]byte, *testMultiSigAuth) { 663 // serialized compressed pubkey used for multisig 664 addrs := make([]*stdaddr.AddressPubKeyEcdsaSecp256k1V0, 0, n) 665 auth := &testMultiSigAuth{ 666 msg: randomBytes(32), 667 } 668 669 for i := 0; i < m; i++ { 670 a := s256Auth(auth.msg) 671 auth.pubkeys = append(auth.pubkeys, a.pubkey) 672 auth.pkHashes = append(auth.pkHashes, a.pkHash) 673 auth.sigs = append(auth.sigs, a.sig) 674 addr, err := stdaddr.NewAddressPubKeyEcdsaSecp256k1V0Raw(a.pubkey, chainParams) 675 if err != nil { 676 fmt.Printf("error creating AddressSecpPubKey: %v", err) 677 return nil, nil 678 } 679 addrs = append(addrs, addr) 680 } 681 script, err := multiSigScript(addrs, int64(m)) 682 if err != nil { 683 fmt.Printf("error creating MultiSigScript: %v", err) 684 return nil, nil 685 } 686 return script, auth 687 } 688 689 // A pay-to-script-hash M-of-N multi-sig MsgTx. 690 func testMsgTxP2SHMofN(m, n int) *testMsgTxP2SH { 691 script, auth := testMultiSigScriptMofN(m, n) 692 pkScript := make([]byte, 0, 23) 693 pkScript = append(pkScript, txscript.OP_HASH160) 694 pkScript = append(pkScript, txscript.OP_DATA_20) 695 scriptHash := dcrutil.Hash160(script) 696 pkScript = append(pkScript, scriptHash...) 697 pkScript = append(pkScript, txscript.OP_EQUAL) 698 msgTx := wire.NewMsgTx() 699 msgTx.AddTxOut(wire.NewTxOut(1, pkScript)) 700 return &testMsgTxP2SH{ 701 tx: msgTx, 702 auth: auth, 703 script: script, 704 vout: 0, 705 n: n, 706 m: m, 707 } 708 } 709 710 // A pay-to-script hash with a P2SH output. I'm fairly certain this would be an 711 // uncommon choice in practice, but valid nonetheless. 712 func testMsgTxP2SHVote() *testMsgTx { 713 // Need to pull a little switcharoo, taking the pk script as the redeem script 714 // and subbing in a p2sh script. 715 msg := testMsgTxVote() 716 ogScript := msg.tx.TxOut[msg.vout].PkScript 717 pkScript := make([]byte, 0, 24) 718 pkScript = append(pkScript, txscript.OP_SSGEN) 719 pkScript = append(pkScript, txscript.OP_HASH160) 720 pkScript = append(pkScript, txscript.OP_DATA_20) 721 scriptHash := stdaddr.Hash160(ogScript) 722 pkScript = append(pkScript, scriptHash...) 723 pkScript = append(pkScript, txscript.OP_EQUAL) 724 msg.tx.TxOut[msg.vout].PkScript = pkScript 725 msg.script = ogScript 726 return msg 727 } 728 729 // A revocation MsgTx. 730 func testMsgTxRevocation() *testMsgTx { 731 msgTx := wire.NewMsgTx() 732 // Need a single input from stake tree 733 msgTx.AddTxIn(wire.NewTxIn(wire.NewOutPoint(&zeroHash, 0, 1), 0, nil)) 734 // All outputs must have OP_SSRTX prefix. 735 script, auth := newStakeP2PKHScript(txscript.OP_SSRTX) 736 msgTx.AddTxOut(wire.NewTxOut(1, script)) 737 return &testMsgTx{ 738 tx: msgTx, 739 auth: auth, 740 } 741 } 742 743 // Make a backend that logs to stdout. 744 func testBackend() (*Backend, func()) { 745 dcr := unconnectedDCR(&asset.BackendConfig{Logger: tLogger}, nil) // never actually Connect, so no rpc config 746 dcr.node = &testNode{} 747 748 ctx, cancel := context.WithCancel(context.Background()) 749 dcr.ctx = ctx 750 751 var wg sync.WaitGroup 752 shutdown := func() { 753 cancel() 754 wg.Wait() 755 } 756 wg.Add(1) 757 go func() { 758 dcr.run(ctx) 759 wg.Done() 760 }() 761 return dcr, shutdown 762 } 763 764 // TestUTXOs tests all UTXO related paths. 765 func TestUTXOs(t *testing.T) { 766 // The various UTXO types to check: 767 // 1. A valid UTXO in a mempool transaction 768 // 2. A valid UTXO in a mined block. All three signature types 769 // 3. A UTXO that is invalid because it is non-existent. This case covers 770 // other important cases, as dcrd will only return a result from 771 // GetTxOut if the utxo is valid and ready to spend. 772 // 4. A UTXO that is invalid because it has the wrong script type 773 // 5. A UTXO that is invalidated because it is a regular tree tx from a 774 // stakeholder invalidated block 775 // 6. A UTXO that is valid even though it is from a stakeholder invalidated 776 // block, because it is a stake tree tx 777 // 7. A UTXO that becomes invalid in a reorg 778 // 8. A UTXO that is in an orphaned block, but also included in a new 779 // mainchain block, so is still valid. 780 // 9. A UTXO with a pay-to-script-hash for a 1-of-2 multisig redeem script 781 // 10. A UTXO with a pay-to-script-hash for a 2-of-2 multisig redeem script 782 // 11. A UTXO with a pay-to-script-hash for a P2PKH redeem script. 783 // 12. A revocation. 784 785 // Create a Backend with the test node. 786 dcr, shutdown := testBackend() 787 defer shutdown() 788 ctx := dcr.ctx // for the functions then take a context, canceled on shutdown() 789 790 // The vout will be randomized during reset. 791 txHeight := uint32(32) 792 793 // A general reset function that clears the testBlockchain and the blockCache. 794 reset := func() { 795 cleanTestChain() 796 blockCache := newBlockCache(dcr.log) 797 dcr.blockCache.mtx.Lock() 798 dcr.blockCache.blocks = blockCache.blocks 799 dcr.blockCache.mainchain = blockCache.mainchain 800 dcr.blockCache.best = blockCache.best 801 dcr.blockCache.mtx.Unlock() 802 } 803 804 // CASE 1: A valid UTXO in a mempool transaction. This UTXO will have zero 805 // confirmations, a valid pkScript and will not be marked as coinbase. Then 806 // add a block that includes the transaction, and check that Confirmations 807 // updates correctly. 808 reset() 809 txHash := randomHash() 810 blockHash := randomHash() 811 msg := testMsgTxRegular(dcrec.STEcdsaSecp256k1) 812 // For a regular test tx, the output is at output index 0. Pass nil for the 813 // block hash and 0 for the block height and confirmations for a mempool tx. 814 txout := testAddTxOut(msg.tx, msg.vout, txHash, nil, 0, 0) 815 // Set the value of this one. 816 txout.Value = 2.0 817 testAddTxVerbose(msg.tx, txHash, nil, 0, 0) 818 // There is no block info to add, since this is a mempool transaction 819 utxo, err := dcr.utxo(ctx, txHash, msg.vout, nil) 820 if err != nil { 821 t.Fatalf("case 1 - unexpected error: %v", err) 822 } 823 // While we're here, check the spend size and value are correct. 824 spendSize := utxo.SpendSize() 825 wantSpendSize := uint32(dexdcr.TxInOverhead + 1 + dexdcr.P2PKHSigScriptSize) 826 if spendSize != wantSpendSize { 827 t.Fatalf("case 1 - unexpected spend script size reported. expected %d, got %d", wantSpendSize, spendSize) 828 } 829 if utxo.Value() != 200_000_000 { 830 t.Fatalf("case 1 - unexpected output value. expected 200,000,000, got %d", utxo.Value()) 831 } 832 // Now "mine" the transaction. 833 testAddBlockVerbose(blockHash, 1, txHeight, 1) 834 // Overwrite the test blockchain transaction details. 835 testAddTxOut(msg.tx, 0, txHash, blockHash, int64(txHeight), 1) 836 testAddTxVerbose(msg.tx, txHash, blockHash, int64(txHeight), 1) 837 confs, err := utxo.Confirmations(ctx) 838 if err != nil { 839 t.Fatalf("case 1 - error retrieving confirmations after transaction \"mined\": %v", err) 840 } 841 if confs != 1 { 842 // The confirmation count is not taken from the txOut.Confirmations, so 843 // need to check that it is correct. 844 t.Fatalf("case 1 - expected 1 confirmation after mining transaction, found %d", confs) 845 } 846 // Make sure the pubkey spends the output. 847 err = utxo.Auth([][]byte{msg.auth.pubkey}, [][]byte{msg.auth.sig}, msg.auth.msg) 848 if err != nil { 849 t.Fatalf("case 1 - Auth error: %v", err) 850 } 851 852 // CASE 2: A valid UTXO in a mined block. This UTXO will have non-zero 853 // confirmations, a valid pkScipt. Test all three signature types. 854 for _, sigType := range []dcrec.SignatureType{dcrec.STEcdsaSecp256k1, dcrec.STEd25519, dcrec.STSchnorrSecp256k1} { 855 reset() 856 blockHash = testAddBlockVerbose(nil, 1, txHeight, 1) 857 txHash = randomHash() 858 msg = testMsgTxRegular(sigType) 859 testAddTxOut(msg.tx, msg.vout, txHash, blockHash, int64(txHeight), 1) 860 testAddTxVerbose(msg.tx, txHash, blockHash, int64(txHeight), 1) 861 utxo, err = dcr.utxo(ctx, txHash, msg.vout, nil) 862 if err != nil { 863 t.Fatalf("case 2 - unexpected error for sig type %d: %v", int(sigType), err) 864 } 865 err = utxo.Auth([][]byte{msg.auth.pubkey}, [][]byte{msg.auth.sig}, msg.auth.msg) 866 if err != nil { 867 t.Fatalf("case 2 - Auth error with sig type %d: %v", int(sigType), err) 868 } 869 } 870 871 // CASE 3: A UTXO that is invalid because it is non-existent 872 reset() 873 _, err = dcr.utxo(ctx, randomHash(), 0, nil) 874 if err == nil { 875 t.Fatalf("case 3 - received no error for a non-existent UTXO") 876 } 877 878 // CASE 4: A UTXO that is invalid because it has the wrong script type. 879 reset() 880 blockHash = testAddBlockVerbose(nil, 1, txHeight, 1) 881 txHash = randomHash() 882 msg = testMsgTxRegular(dcrec.STEcdsaSecp256k1) 883 // make the script nonsense. 884 msg.tx.TxOut[0].PkScript = []byte{0x00, 0x01, 0x02, 0x03} 885 testAddTxOut(msg.tx, msg.vout, txHash, blockHash, int64(txHeight), 1) 886 testAddTxVerbose(msg.tx, txHash, blockHash, int64(txHeight), 1) 887 _, err = dcr.utxo(ctx, txHash, msg.vout, nil) 888 if err == nil { 889 t.Fatalf("case 4 - received no error for a UTXO with wrong script type") 890 } 891 892 // CASE 5: A UTXO that is invalid because it is a regular tree tx from a 893 // stakeholder invalidated block. The transaction is valid when it has 1 894 // confirmation, but is invalidated by the next block. 895 reset() 896 blockHash = testAddBlockVerbose(nil, 1, txHeight, 1) 897 txHash = randomHash() 898 msg = testMsgTxRegular(dcrec.STEcdsaSecp256k1) 899 testAddTxOut(msg.tx, msg.vout, txHash, blockHash, int64(txHeight), 1) 900 testAddTxVerbose(msg.tx, txHash, blockHash, int64(txHeight), 1) 901 utxo, err = dcr.utxo(ctx, txHash, msg.vout, nil) 902 if err != nil { 903 t.Fatalf("case 5 - unexpected error: %v", err) 904 } 905 // Now reject the block. First update the confirmations. 906 testAddBlockVerbose(blockHash, 2, txHeight, 1) 907 rejectingHash := testAddBlockVerbose(nil, 1, txHeight+1, 0) 908 rejectingBlock := testChain.blocks[*rejectingHash] 909 _, err = dcr.blockCache.add(rejectingBlock) 910 if err != nil { 911 t.Fatalf("case 5 - error adding to block cache: %v", err) 912 } 913 _, err = utxo.Confirmations(ctx) 914 if err == nil { 915 t.Fatalf("case 5 - block not detected as invalid after stakeholder invalidation") 916 } 917 918 // CASE 6: A UTXO that is valid even though it is from a stakeholder 919 // invalidated block, because it is a stake tree tx. First try with an 920 // immature vote output, then add a maturing block and try again. 921 reset() 922 txHash = randomHash() 923 immatureHash := testAddBlockVerbose(nil, 2, txHeight, 1) 924 msg = testMsgTxVote() 925 testAddTxOut(msg.tx, msg.vout, txHash, immatureHash, int64(txHeight), 1) 926 testAddTxVerbose(msg.tx, txHash, immatureHash, int64(txHeight), 1) 927 _, err = dcr.utxo(ctx, txHash, msg.vout, nil) 928 if err == nil { 929 t.Fatalf("case 6 - no error for immature transaction") 930 } 931 // Now reject the block, but mature the transaction. It should still be 932 // accepted since it is a stake tree transaction. 933 rejectingHash = testAddBlockVerbose(nil, 1, txHeight+1, 0) 934 rejectingBlock = testChain.blocks[*rejectingHash] 935 _, err = dcr.blockCache.add(rejectingBlock) 936 if err != nil { 937 t.Fatalf("case 6 - error adding to rejecting block cache: %v", err) 938 } 939 maturity := int64(chainParams.CoinbaseMaturity) 940 testAddBlockVerbose(blockHash, maturity, txHeight, 1) 941 testAddBlockVerbose(rejectingHash, maturity-1, txHeight, 1) 942 maturingHash := testAddBlockVerbose(nil, 1, txHeight+uint32(maturity)-1, 1) 943 maturingBlock := testChain.blocks[*maturingHash] 944 _, err = dcr.blockCache.add(maturingBlock) 945 if err != nil { 946 t.Fatalf("case 6 - error adding to maturing block cache: %v", err) 947 } 948 testAddTxOut(msg.tx, msg.vout, txHash, immatureHash, int64(txHeight), int64(txHeight)+maturity-1) 949 testAddTxVerbose(msg.tx, txHash, immatureHash, int64(txHeight), int64(txHeight)+maturity-1) 950 utxo, err = dcr.utxo(ctx, txHash, msg.vout, nil) 951 if err != nil { 952 t.Fatalf("case 6 - unexpected error after maturing block: %v", err) 953 } 954 // Since this is our first stake transaction, let's check the pubkey 955 err = utxo.Auth([][]byte{msg.auth.pubkey}, [][]byte{msg.auth.sig}, msg.auth.msg) 956 if err != nil { 957 t.Fatalf("case 6 - Auth error: %v", err) 958 } 959 960 // CASE 7: A UTXO that becomes invalid in a reorg 961 reset() 962 txHash = randomHash() 963 blockHash = testAddBlockVerbose(nil, 1, txHeight, 1) 964 msg = testMsgTxRegular(dcrec.STEcdsaSecp256k1) 965 testAddTxOut(msg.tx, msg.vout, txHash, blockHash, int64(txHeight), 1) 966 testAddTxVerbose(msg.tx, txHash, blockHash, int64(txHeight), 1) 967 utxo, err = dcr.utxo(ctx, txHash, msg.vout, nil) 968 if err != nil { 969 t.Fatalf("case 7 - received error for utxo") 970 } 971 _, err = utxo.Confirmations(ctx) 972 if err != nil { 973 t.Fatalf("case 7 - received error before reorg") 974 } 975 betterHash := testAddBlockVerbose(nil, 1, txHeight, 1) 976 dcr.blockCache.add(testChain.blocks[*betterHash]) 977 dcr.blockCache.reorg(int64(txHeight)) 978 // Remove the txout from the blockchain, since dcrd would no longer return it. 979 testChainMtx.Lock() 980 delete(testChain.txOuts, txOutID(txHash, msg.vout)) 981 testChainMtx.Unlock() 982 _, err = utxo.Confirmations(ctx) 983 if err == nil { 984 t.Fatalf("case 7 - received no error for orphaned transaction") 985 } 986 987 // CASE 8: A UTXO that is in an orphaned block, but also included in a new 988 // mainchain block, so is still valid. 989 reset() 990 txHash = randomHash() 991 orphanHash := testAddBlockVerbose(nil, 1, txHeight, 1) 992 msg = testMsgTxRegular(dcrec.STEcdsaSecp256k1) 993 testAddTxOut(msg.tx, msg.vout, txHash, orphanHash, int64(txHeight), 1) 994 testAddTxVerbose(msg.tx, txHash, orphanHash, int64(txHeight), 1) 995 utxo, err = dcr.utxo(ctx, txHash, msg.vout, nil) 996 if err != nil { 997 t.Fatalf("case 8 - received error for utxo") 998 } 999 // Now orphan the block, by doing a reorg. 1000 betterHash = testAddBlockVerbose(nil, 1, txHeight, 1) 1001 dcr.blockCache.reorg(int64(txHeight)) 1002 dcr.blockCache.add(testChain.blocks[*betterHash]) 1003 testAddTxOut(msg.tx, msg.vout, txHash, betterHash, int64(txHeight), 1) 1004 testAddTxVerbose(msg.tx, txHash, betterHash, int64(txHeight), 1) 1005 _, err = utxo.Confirmations(ctx) 1006 if err != nil { 1007 t.Fatalf("case 8 - unexpected error after reorg: %v", err) 1008 } 1009 if utxo.blockHash != *betterHash { 1010 t.Fatalf("case 8 - unexpected hash for utxo after reorg") 1011 } 1012 // Do it again, but this time, put the utxo into mempool. 1013 evenBetter := testAddBlockVerbose(nil, 1, txHeight, 1) 1014 dcr.blockCache.reorg(int64(txHeight)) 1015 dcr.blockCache.add(testChain.blocks[*evenBetter]) 1016 testAddTxOut(msg.tx, msg.vout, txHash, evenBetter, 0, 0) 1017 testAddTxVerbose(msg.tx, txHash, evenBetter, 0, 0) 1018 _, err = utxo.Confirmations(ctx) 1019 if err != nil { 1020 t.Fatalf("case 8 - unexpected error for mempool tx after reorg") 1021 } 1022 if utxo.height != 0 { 1023 t.Fatalf("case 8 - unexpected height %d after dumping into mempool", utxo.height) 1024 } 1025 1026 // CASE 9: A UTXO with a pay-to-script-hash for a 1-of-2 multisig redeem 1027 // script 1028 reset() 1029 txHash = randomHash() 1030 blockHash = testAddBlockVerbose(nil, 1, txHeight, 1) 1031 msgMultiSig := testMsgTxP2SHMofN(1, 2) 1032 testAddTxOut(msgMultiSig.tx, msgMultiSig.vout, txHash, blockHash, int64(txHeight), 1) 1033 testAddTxVerbose(msgMultiSig.tx, txHash, blockHash, int64(txHeight), 1) 1034 // First try to get the UTXO without providing the raw script. 1035 _, err = dcr.utxo(ctx, txHash, msgMultiSig.vout, nil) 1036 if err == nil { 1037 t.Fatalf("no error thrown for p2sh utxo when no script was provided") 1038 } 1039 // Now provide the script. 1040 utxo, err = dcr.utxo(ctx, txHash, msgMultiSig.vout, msgMultiSig.script) 1041 if err != nil { 1042 t.Fatalf("case 9 - received error for utxo: %v", err) 1043 } 1044 confs, err = utxo.Confirmations(ctx) 1045 if err != nil { 1046 t.Fatalf("case 9 - error getting confirmations: %v", err) 1047 } 1048 if confs != 1 { 1049 t.Fatalf("case 9 - expected 1 confirmation, got %d", confs) 1050 } 1051 err = utxo.Auth(msgMultiSig.auth.pubkeys[:1], msgMultiSig.auth.sigs[:1], msgMultiSig.auth.msg) 1052 if err != nil { 1053 t.Fatalf("case 9 - Auth error: %v", err) 1054 } 1055 1056 // CASE 10: A UTXO with a pay-to-script-hash for a 2-of-2 multisig redeem 1057 // script 1058 reset() 1059 txHash = randomHash() 1060 blockHash = testAddBlockVerbose(nil, 1, txHeight, 1) 1061 msgMultiSig = testMsgTxP2SHMofN(2, 2) 1062 testAddTxOut(msgMultiSig.tx, msgMultiSig.vout, txHash, blockHash, int64(txHeight), 1) 1063 testAddTxVerbose(msgMultiSig.tx, txHash, blockHash, int64(txHeight), 1) 1064 utxo, err = dcr.utxo(ctx, txHash, msgMultiSig.vout, msgMultiSig.script) 1065 if err != nil { 1066 t.Fatalf("case 10 - received error for utxo: %v", err) 1067 } 1068 // Try to get by with just one of the pubkeys. 1069 err = utxo.Auth(msgMultiSig.auth.pubkeys[:1], msgMultiSig.auth.sigs[:1], msgMultiSig.auth.msg) 1070 if err == nil { 1071 t.Fatalf("case 10 - no Auth error when only provided one of two required pubkeys") 1072 } 1073 // Now do both. 1074 err = utxo.Auth(msgMultiSig.auth.pubkeys, msgMultiSig.auth.sigs, msgMultiSig.auth.msg) 1075 if err != nil { 1076 t.Fatalf("case 10 - Auth error: %v", err) 1077 } 1078 // Try with a duplicate pubkey and signature. 1079 dupeKeys := [][]byte{msgMultiSig.auth.pubkeys[0], msgMultiSig.auth.pubkeys[0]} 1080 dupeSigs := [][]byte{msgMultiSig.auth.sigs[0], msgMultiSig.auth.sigs[0]} 1081 err = utxo.Auth(dupeKeys, dupeSigs, msgMultiSig.auth.msg) 1082 if err == nil { 1083 t.Fatalf("case 10 - no Auth error with duplicate keys/sigs") 1084 } 1085 1086 // CASE 11: A UTXO with a pay-to-script-hash for a P2PKH redeem script. 1087 reset() 1088 txHash = randomHash() 1089 blockHash = testAddBlockVerbose(nil, maturity, txHeight, 1) 1090 msg = testMsgTxP2SHVote() 1091 testAddTxOut(msg.tx, msg.vout, txHash, blockHash, int64(txHeight), maturity) 1092 testAddTxVerbose(msg.tx, txHash, blockHash, int64(txHeight), maturity) 1093 // mature the vote 1094 testAddBlockVerbose(nil, 1, txHeight+uint32(maturity)-1, 1) 1095 utxo, err = dcr.utxo(ctx, txHash, msg.vout, msg.script) 1096 if err != nil { 1097 t.Fatalf("case 11 - received error for utxo: %v", err) 1098 } 1099 // Make sure it's marked as stake. 1100 if !utxo.scriptType.IsStake() { 1101 t.Fatalf("case 11 - stake p2sh not marked as stake") 1102 } 1103 // Give it nonsense. 1104 err = utxo.Auth([][]byte{randomBytes(33)}, [][]byte{randomBytes(33)}, randomBytes(32)) 1105 if err == nil { 1106 t.Fatalf("case 11 - no Auth error when providing nonsense pubkey") 1107 } 1108 // Now give it the right one. 1109 err = utxo.Auth([][]byte{msg.auth.pubkey}, [][]byte{msg.auth.sig}, msg.auth.msg) 1110 if err != nil { 1111 t.Fatalf("case 11 - Auth error: %v", err) 1112 } 1113 1114 // CASE 12: A revocation. 1115 reset() 1116 txHash = randomHash() 1117 blockHash = testAddBlockVerbose(nil, maturity, txHeight, 1) 1118 msg = testMsgTxRevocation() 1119 testAddTxOut(msg.tx, msg.vout, txHash, blockHash, int64(txHeight), maturity) 1120 testAddTxVerbose(msg.tx, txHash, blockHash, int64(txHeight), maturity) 1121 // mature the revocation 1122 testAddBlockVerbose(nil, 1, txHeight+uint32(maturity)-1, 1) 1123 utxo, err = dcr.utxo(ctx, txHash, msg.vout, msg.script) 1124 if err != nil { 1125 t.Fatalf("case 12 - received error for utxo: %v", err) 1126 } 1127 // Make sure it's marked as stake. 1128 if !utxo.scriptType.IsStake() { 1129 t.Fatalf("case 12 - stake p2sh not marked as stake") 1130 } 1131 // Check the pubkey. 1132 err = utxo.Auth([][]byte{msg.auth.pubkey}, [][]byte{msg.auth.sig}, msg.auth.msg) 1133 if err != nil { 1134 t.Fatalf("case 12 - Auth error: %v", err) 1135 } 1136 1137 // CASE 13: A swap contract 1138 val := uint64(5) 1139 cleanTestChain() 1140 txHash = randomHash() 1141 blockHash = randomHash() 1142 swap := testMsgTxSwapInit(int64(val)) 1143 testAddBlockVerbose(blockHash, 1, txHeight, 1) 1144 testAddTxOut(swap.tx, 0, txHash, blockHash, int64(txHeight), 1).Value = float64(val) / 1e8 1145 testAddTxVerbose(swap.tx, txHash, blockHash, int64(txHeight), 1) 1146 verboseTx := testChain.txRaws[*txHash] 1147 spentTx := randomHash() 1148 spentVout := rand.Uint32() 1149 verboseTx.Vin = append(verboseTx.Vin, testVin(spentTx, spentVout)) 1150 txOut := swap.tx.TxOut[0] 1151 verboseTx.Vout = append(verboseTx.Vout, testVout(float64(txOut.Value)/1e8, txOut.PkScript)) 1152 utxo, err = dcr.utxo(ctx, txHash, 0, swap.contract) 1153 if err != nil { 1154 t.Fatalf("case 13 - received error for utxo: %v", err) 1155 } 1156 1157 // Now try again with the correct vout. 1158 contract, err := auditContract(utxo.Output) // sets refund and swap addresses 1159 if err != nil { 1160 t.Fatalf("case 13 - unexpected error auditing contract: %v", err) 1161 } 1162 if contract.SwapAddress != swap.recipient.String() { 1163 t.Fatalf("case 13 - wrong recipient. wanted '%s' got '%s'", contract.SwapAddress, swap.recipient.String()) 1164 } 1165 if contract.Value() != val { 1166 t.Fatalf("case 13 - unexpected output value. wanted 5, got %d", contract.Value()) 1167 } 1168 1169 } 1170 1171 func TestRedemption(t *testing.T) { 1172 dcr, shutdown := testBackend() 1173 defer shutdown() 1174 ctx := dcr.ctx // for the functions then take a context, canceled on shutdown() 1175 1176 // The vout will be randomized during reset. 1177 txHeight := uint32(32) 1178 cleanTestChain() 1179 txHash := randomHash() 1180 redemptionID := toCoinID(txHash, 0) 1181 // blockHash := randomHash() 1182 spentHash := randomHash() 1183 spentVout := uint32(5) 1184 spentID := toCoinID(spentHash, spentVout) 1185 msg := testMsgTxRegular(dcrec.STEcdsaSecp256k1) 1186 vin := chainjson.Vin{ 1187 Txid: spentHash.String(), 1188 Vout: spentVout, 1189 } 1190 1191 // A valid mempool redemption. 1192 verboseTx := testAddTxVerbose(msg.tx, txHash, nil, 0, 0) 1193 verboseTx.Vin = append(verboseTx.Vin, vin) 1194 redemption, err := dcr.Redemption(redemptionID, spentID, nil) 1195 if err != nil { 1196 t.Fatalf("Redemption error: %v", err) 1197 } 1198 confs, err := redemption.Confirmations(ctx) 1199 if err != nil { 1200 t.Fatalf("redemption Confirmations error: %v", err) 1201 } 1202 if confs != 0 { 1203 t.Fatalf("expected 0 confirmations, got %d", confs) 1204 } 1205 1206 // Missing transaction 1207 testChainMtx.Lock() 1208 delete(testChain.txRaws, *txHash) 1209 testChainMtx.Unlock() 1210 _, err = dcr.Redemption(redemptionID, spentID, nil) 1211 if err == nil { 1212 t.Fatalf("No error for missing transaction") 1213 } 1214 1215 // Doesn't spend transaction. 1216 verboseTx = testAddTxVerbose(msg.tx, txHash, nil, 0, 0) 1217 verboseTx.Vin = append(verboseTx.Vin, chainjson.Vin{ 1218 Txid: randomHash().String(), 1219 }) 1220 _, err = dcr.Redemption(redemptionID, spentID, nil) 1221 if err == nil { 1222 t.Fatalf("No error for wrong previous outpoint") 1223 } 1224 1225 // Mined transaction. 1226 blockHash := randomHash() 1227 blockHeight := txHeight - 1 1228 verboseTx = testAddTxVerbose(msg.tx, txHash, blockHash, int64(blockHeight), 1) 1229 verboseTx.Vin = append(verboseTx.Vin, vin) 1230 testAddBlockVerbose(blockHash, 1, blockHeight, 1) 1231 redemption, err = dcr.Redemption(redemptionID, spentID, nil) 1232 if err != nil { 1233 t.Fatalf("Redemption with confs error: %v", err) 1234 } 1235 confs, err = redemption.Confirmations(ctx) 1236 if err != nil { 1237 t.Fatalf("redemption with confs Confirmations error: %v", err) 1238 } 1239 if confs != 1 { 1240 t.Fatalf("expected 1 confirmation, got %d", confs) 1241 } 1242 } 1243 1244 // TestReorg sends a reorganization-causing block through the anyQ channel, and 1245 // checks that the cache is responding as expected. 1246 func TestReorg(t *testing.T) { 1247 // Create a Backend with the test node. 1248 dcr, shutdown := testBackend() 1249 defer shutdown() 1250 ctx := dcr.ctx // for the functions then take a context, canceled on shutdown() 1251 1252 // A general reset function that clears the testBlockchain and the blockCache. 1253 var tipHeight uint32 = 10 1254 var tipHash *chainhash.Hash 1255 reset := func() { 1256 cleanTestChain() 1257 dcr.blockCache = newBlockCache(dcr.log) 1258 for h := uint32(0); h <= tipHeight; h++ { 1259 blockHash := testAddBlockVerbose(nil, int64(tipHeight-h+1), h, 1) 1260 // force dcr to get and cache the block 1261 _, err := dcr.getDcrBlock(ctx, blockHash) 1262 if err != nil { 1263 t.Fatalf("getDcrBlock: %v", err) 1264 } 1265 } 1266 // Check that the tip is at the expected height and the block is mainchain. 1267 block, found := dcr.blockCache.atHeight(tipHeight) 1268 if !found { 1269 t.Fatalf("tip block not found in cache mainchain") 1270 } 1271 if block.orphaned { 1272 t.Fatalf("block unexpectedly orphaned before reorg") 1273 } 1274 _, found = dcr.blockCache.block(&block.hash) 1275 if !found { 1276 t.Fatalf("block not found with block method before reorg") 1277 } 1278 tipHash = &block.hash 1279 } 1280 1281 ensureOrphaned := func(hash *chainhash.Hash, height uint32) { 1282 // Make sure mainchain is empty at the tip height. 1283 block, found := dcr.blockCache.block(hash) 1284 if !found { 1285 t.Fatalf("orphaned block from height %d not found after reorg", height) 1286 } 1287 if !block.orphaned { 1288 t.Fatalf("reorged block from height %d (%s) not marked as orphaned", height, hash) 1289 } 1290 } 1291 1292 // A one-block reorg. 1293 reset() 1294 // Add a replacement blocks 1295 newHash := testAddBlockVerbose(nil, 1, tipHeight, 1) 1296 // Passing the hash to anyQ triggers the reorganization. 1297 dcr.blockCache.reorg(int64(tipHeight)) 1298 dcr.blockCache.add(testChain.blocks[*newHash]) 1299 ensureOrphaned(tipHash, tipHeight) 1300 newTip, found := dcr.blockCache.atHeight(tipHeight) 1301 if !found { 1302 t.Fatalf("3-deep reorg-causing new tip block not found on mainchain") 1303 } 1304 if newTip.hash != *newHash { 1305 t.Fatalf("tip hash mismatch after 1-block reorg") 1306 } 1307 1308 // A 3-block reorg 1309 reset() 1310 tip, found1 := dcr.blockCache.atHeight(tipHeight) 1311 oneDeep, found2 := dcr.blockCache.atHeight(tipHeight - 1) 1312 twoDeep, found3 := dcr.blockCache.atHeight(tipHeight - 2) 1313 if !found1 || !found2 || !found3 { 1314 t.Fatalf("not all block found for 3-block reorg (%t, %t, %t)", found1, found2, found3) 1315 } 1316 newHash = testAddBlockVerbose(nil, 1, tipHeight-2, 1) 1317 dcr.blockCache.reorg(int64(tipHeight - 2)) 1318 dcr.blockCache.add(testChain.blocks[*newHash]) 1319 ensureOrphaned(&tip.hash, tip.height) 1320 ensureOrphaned(&oneDeep.hash, tip.height) 1321 ensureOrphaned(&twoDeep.hash, tip.height) 1322 newHeight := int64(dcr.blockCache.tipHeight()) 1323 if newHeight != int64(twoDeep.height) { 1324 t.Fatalf("from tip height after 3-block reorg. expected %d, saw %d", twoDeep.height-1, newHeight) 1325 } 1326 newTip, found = dcr.blockCache.atHeight(uint32(newHeight)) 1327 if !found { 1328 t.Fatalf("3-deep reorg-causing new tip block not found on mainchain") 1329 } 1330 if newTip.hash != *newHash { 1331 t.Fatalf("tip hash mismatch after 3-block reorg") 1332 } 1333 1334 // Create a transaction at the tip, then orphan the block and move the 1335 // transaction to mempool. 1336 reset() 1337 txHash := randomHash() 1338 tip, _ = dcr.blockCache.atHeight(tipHeight) 1339 msg := testMsgTxRegular(dcrec.STEcdsaSecp256k1) 1340 1341 testAddTxOut(msg.tx, 0, txHash, &tip.hash, int64(tipHeight), 1) 1342 1343 utxo, err := dcr.utxo(ctx, txHash, msg.vout, nil) 1344 if err != nil { 1345 t.Fatalf("utxo error: %v", err) 1346 } 1347 confs, err := utxo.Confirmations(ctx) 1348 if err != nil { 1349 t.Fatalf("Confirmations error: %v", err) 1350 } 1351 if confs != 1 { 1352 t.Fatalf("wrong number of confirmations. expected 1, got %d", confs) 1353 } 1354 1355 // Orphan the block and move the transaction to mempool. 1356 dcr.blockCache.reorg(int64(tipHeight - 2)) 1357 testAddTxOut(msg.tx, 0, txHash, nil, 0, 0) 1358 confs, err = utxo.Confirmations(ctx) 1359 if err != nil { 1360 t.Fatalf("Confirmations error after reorg: %v", err) 1361 } 1362 if confs != 0 { 1363 t.Fatalf("Expected zero confirmations after reorg, found %d", confs) 1364 } 1365 1366 // Start over, but put it in a lower block instead. 1367 reset() 1368 tip, _ = dcr.blockCache.atHeight(tipHeight) 1369 testAddBlockVerbose(&tip.hash, 1, tipHeight, 1) 1370 testAddTxOut(msg.tx, 0, txHash, &tip.hash, int64(tipHeight), 1) 1371 utxo, err = dcr.utxo(ctx, txHash, msg.vout, nil) 1372 if err != nil { 1373 t.Fatalf("utxo error 2: %v", err) 1374 } 1375 1376 // Reorg and add a single block with the transaction. 1377 var reorgHeight uint32 = 5 1378 dcr.blockCache.reorg(int64(reorgHeight)) 1379 newBlockHash := randomHash() 1380 testAddTxOut(msg.tx, 0, txHash, newBlockHash, int64(reorgHeight+1), 1) 1381 testAddBlockVerbose(newBlockHash, 1, reorgHeight+1, 1) 1382 dcr.getDcrBlock(ctx, newBlockHash) // Force blockCache update 1383 confs, err = utxo.Confirmations(ctx) 1384 if err != nil { 1385 t.Fatalf("Confirmations error after reorg to lower block: %v", err) 1386 } 1387 if confs != 1 { 1388 t.Fatalf("Expected zero confirmations after reorg to lower block, found %d", confs) 1389 } 1390 } 1391 1392 // TestAuxiliary checks the UTXO convenience functions like TxHash, Vout, and 1393 // TxID. 1394 func TestAuxiliary(t *testing.T) { 1395 // Create a Backend with the test node. 1396 dcr, shutdown := testBackend() 1397 defer shutdown() 1398 ctx := dcr.ctx // for the functions then take a context, canceled on shutdown() 1399 1400 // Add a funding coin and retrieve it. Use a vote, since it has non-zero vout. 1401 cleanTestChain() 1402 maturity := int64(chainParams.CoinbaseMaturity) 1403 msg := testMsgTxVote() 1404 txid := hex.EncodeToString(randomBytes(32)) 1405 txHash, _ := chainhash.NewHashFromStr(txid) 1406 txHeight := rand.Uint32() 1407 blockHash := testAddBlockVerbose(nil, 1, txHeight, 1) 1408 testAddTxOut(msg.tx, msg.vout, txHash, blockHash, int64(txHeight), maturity) 1409 coinID := toCoinID(txHash, msg.vout) 1410 utxo, err := dcr.FundingCoin(ctx, coinID, nil) 1411 if err != nil { 1412 t.Fatalf("unexpected error: %v", err) 1413 } 1414 if txHash.String() != utxo.Coin().TxID() { 1415 t.Fatalf("utxo tx hash doesn't match") 1416 } 1417 if utxo.Coin().TxID() != txid { 1418 t.Fatalf("utxo txid doesn't match") 1419 } 1420 1421 // Check that values returned from FeeCoin are as set. 1422 cleanTestChain() 1423 msg = testMsgTxRegular(dcrec.STEcdsaSecp256k1) 1424 msg.tx.TxOut[0].Value = 8e8 // for consistency with fake TxRawResult added below 1425 1426 scriptClass, scriptAddrs := dexdcr.ExtractScriptAddrs(msg.tx.TxOut[0].Version, msg.tx.TxOut[0].PkScript, chainParams) 1427 if scriptClass == dexdcr.ScriptUnsupported { 1428 t.Errorf("vote output 0 was non-standard") 1429 } 1430 addr := scriptAddrs.PkHashes[0].String() 1431 1432 msgHash := msg.tx.TxHash() 1433 txHash = &msgHash 1434 confs := int64(3) 1435 verboseTx := testAddTxVerbose(msg.tx, txHash, blockHash, int64(txHeight), confs) 1436 1437 txAddr, v, confs, err := dcr.FeeCoin(toCoinID(txHash, 0)) 1438 if err != nil { 1439 t.Fatalf("FeeCoin error: %v", err) 1440 } 1441 if txAddr != addr { 1442 t.Fatalf("expected address %s, got %s", addr, txAddr) 1443 } 1444 expVal := toAtoms(8) 1445 if v != expVal { 1446 t.Fatalf("expected value %d, got %d", expVal, v) 1447 } 1448 if confs != 3 { 1449 t.Fatalf("expected 3 confirmations, got %d", confs) 1450 } 1451 1452 var txHashBad chainhash.Hash 1453 copy(txHashBad[:], randomBytes(32)) 1454 _, _, _, err = dcr.FeeCoin(toCoinID(&txHashBad, 0)) 1455 if err == nil { 1456 t.Fatal("FeeCoin found for non-existent txid") 1457 } 1458 1459 _, _, _, err = dcr.FeeCoin(toCoinID(txHash, 1)) 1460 if err == nil { 1461 t.Fatal("FeeCoin found for non-existent output") 1462 } 1463 1464 // make the output a stake hash 1465 stakeScript, _ := newStakeP2PKHScript(txscript.OP_SSGEN) 1466 verboseTx.Vout[0].ScriptPubKey.Hex = hex.EncodeToString(stakeScript) 1467 _, _, _, err = dcr.FeeCoin(toCoinID(txHash, 0)) 1468 if err == nil { 1469 t.Fatal("FeeCoin accepted a stake output") 1470 } 1471 1472 // make a p2sh that FeeCoin should also accept 1473 msgP2SH := testMsgTxP2SHMofN(1, 2) 1474 scriptClass, _ = dexdcr.ExtractScriptAddrs(msgP2SH.tx.TxOut[0].Version, msgP2SH.tx.TxOut[0].PkScript, chainParams) 1475 if scriptClass == dexdcr.ScriptUnsupported { 1476 t.Errorf("output 0 was non-standard") 1477 } 1478 msgHash = msgP2SH.tx.TxHash() 1479 txHash = &msgHash 1480 confs = int64(3) 1481 testAddTxVerbose(msgP2SH.tx, txHash, blockHash, int64(txHeight), confs) 1482 _, _, _, err = dcr.FeeCoin(toCoinID(txHash, 0)) 1483 if err != nil { 1484 t.Fatal("FeeCoin rejected a p2sh output") 1485 } 1486 1487 // make a p2pk that FeeCoin should reject 1488 priv, err := secp256k1.GeneratePrivateKey() 1489 if err != nil { 1490 t.Fatalf("GeneratePrivateKey error: %v", err) 1491 } 1492 pkAddr, err := stdaddr.NewAddressPubKeyEcdsaSecp256k1V0(priv.PubKey(), chainParams) 1493 if err != nil { 1494 t.Fatalf("NewAddressPubKeyEcdsaSecp256k1V0 error: %v", err) 1495 } 1496 msgTxPK := wire.NewMsgTx() 1497 _, pkScript := pkAddr.PaymentScript() 1498 msgTxPK.AddTxOut(wire.NewTxOut(1, pkScript)) 1499 chainHash := msgTxPK.TxHash() 1500 testAddTxVerbose(msgTxPK, &chainHash, blockHash, int64(txHeight), confs) 1501 _, _, _, err = dcr.FeeCoin(toCoinID(txHash, 0)) 1502 if err != nil { 1503 t.Fatal("FeeCoin rejected a p2sh output") 1504 } 1505 } 1506 1507 // TestCheckSwapAddress checks that addresses are parsing or not parsing as 1508 // expected. 1509 func TestCheckSwapAddress(t *testing.T) { 1510 dcr, shutdown := testBackend() 1511 defer shutdown() 1512 1513 type test struct { 1514 addr string 1515 wantErr bool 1516 } 1517 tests := []test{ 1518 {"", true}, 1519 {"DsYXjAK3UiTVN9js8v9G21iRbr2wPty7f12", false}, 1520 {"DeZcGyCtPq7sTvACZupjT3BC1tsSEsKaYL4", true}, // valid, but AddressPubKeyHashEd25519V0 1521 {"DSo9Qw4FZLTwFL6fg2T9XPoJA8sFoZ4idZ7", true}, // valid, but AddressPubKeyHashSchnorrSecp256k1V0 1522 {"DkM3W1518RharMSnqSiJCCGQ7RikMKCATeRvRwEW8vy1B2fjTd4Xi", true}, // valid, but AddressPubKeyEcdsaSecp256k1V0 1523 {"Dce4vLzzENaZT7D2Wq5crRZ4VwfYMDMWkD9", true}, // valid, but AddressScriptHashV0 1524 {"TsYXjAK3UiTVN9js8v9G21iRbr2wPty7f12", true}, // wrong network 1525 {"Dce4vLzzENaZT7D2Wq5crRZ4VwfYMDMWkD0", true}, // capital letter O not base 58 1526 {"Dce4vLzzE", true}, 1527 } 1528 for _, test := range tests { 1529 if dcr.CheckSwapAddress(test.addr) != !test.wantErr { 1530 t.Fatalf("wantErr = %t, address = %s", test.wantErr, test.addr) 1531 } 1532 } 1533 } 1534 1535 func TestValidateXPub(t *testing.T) { 1536 seed := randomBytes(hdkeychain.MinSeedBytes) 1537 master, err := hdkeychain.NewMaster(seed, chainParams) 1538 if err != nil { 1539 t.Fatalf("failed to generate master extended key: %v", err) 1540 } 1541 1542 // fail for private key 1543 xprivStr := master.String() 1544 if err = ValidateXPub(xprivStr); err == nil { 1545 t.Errorf("no error for extended private key") 1546 } 1547 1548 // succeed for public key 1549 xpub := master.Neuter() 1550 xpubStr := xpub.String() 1551 if err = ValidateXPub(xpubStr); err != nil { 1552 t.Error(err) 1553 } 1554 1555 // fail for invalid key of wrong length 1556 if err = ValidateXPub(xpubStr[2:]); err == nil { 1557 t.Errorf("no error for invalid key") 1558 } 1559 1560 // fail for wrong network 1561 masterTestnet, err := hdkeychain.NewMaster(seed, chaincfg.TestNet3Params()) 1562 if err != nil { 1563 t.Fatalf("failed to generate master extended key: %v", err) 1564 } 1565 xpubTestnet := masterTestnet.Neuter() 1566 if err = ValidateXPub(xpubTestnet.String()); err == nil { 1567 t.Errorf("no error for invalid wrong network") 1568 } 1569 } 1570 1571 func TestDriver_DecodeCoinID(t *testing.T) { 1572 tests := []struct { 1573 name string 1574 coinID []byte 1575 want string 1576 wantErr bool 1577 }{ 1578 { 1579 "ok", 1580 []byte{ 1581 0x16, 0x8f, 0x34, 0x3a, 0xdf, 0x17, 0xe0, 0xc3, 1582 0xa2, 0xe8, 0x88, 0x79, 0x8, 0x87, 0x17, 0xb8, 1583 0xac, 0x93, 0x47, 0xb9, 0x66, 0xd, 0xa7, 0x4b, 1584 0xde, 0x3e, 0x1d, 0x1f, 0x47, 0x94, 0x9f, 0xdf, // 32 byte hash 1585 0x0, 0x0, 0x0, 0x1, // 4 byte vout 1586 }, 1587 "df9f94471f1d3ede4ba70d66b94793acb81787087988e8a2c3e017df3a348f16:1", 1588 false, 1589 }, 1590 { 1591 "bad", 1592 []byte{ 1593 0x16, 0x8f, 0x34, 0x3a, 0xdf, 0x17, 0xe0, 0xc3, 1594 0xa2, 0xe8, 0x88, 0x79, 0x8, 0x87, 0x17, 0xb8, 1595 0xac, 0x93, 0x47, 0xb9, 0x66, 0xd, 0xa7, 0x4b, 1596 0xde, 0x3e, 0x1d, 0x1f, 0x47, 0x94, 0x9f, // 31 bytes 1597 0x0, 0x0, 0x0, 0x1, 1598 }, 1599 "", 1600 true, 1601 }, 1602 } 1603 for _, tt := range tests { 1604 t.Run(tt.name, func(t *testing.T) { 1605 d := &Driver{} 1606 got, err := d.DecodeCoinID(tt.coinID) 1607 if (err != nil) != tt.wantErr { 1608 t.Errorf("Driver.DecodeCoinID() error = %v, wantErr %v", err, tt.wantErr) 1609 return 1610 } 1611 if got != tt.want { 1612 t.Errorf("Driver.DecodeCoinID() = %v, want %v", got, tt.want) 1613 } 1614 }) 1615 } 1616 1617 // Same tests with ValidateCoinID 1618 be := &Backend{} 1619 for _, tt := range tests { 1620 t.Run(tt.name, func(t *testing.T) { 1621 got, err := be.ValidateCoinID(tt.coinID) 1622 if (err != nil) != tt.wantErr { 1623 t.Errorf("Driver.DecodeCoinID() error = %v, wantErr %v", err, tt.wantErr) 1624 return 1625 } 1626 if got != tt.want { 1627 t.Errorf("Driver.DecodeCoinID() = %v, want %v", got, tt.want) 1628 } 1629 }) 1630 } 1631 } 1632 1633 func TestSynced(t *testing.T) { 1634 dcr, shutdown := testBackend() 1635 defer shutdown() 1636 tNode := dcr.node.(*testNode) 1637 tNode.blockchainInfo = &chainjson.GetBlockChainInfoResult{ 1638 Headers: 100, 1639 Blocks: 99, 1640 } 1641 synced, err := dcr.Synced() 1642 if err != nil { 1643 t.Fatalf("Synced error: %v", err) 1644 } 1645 if !synced { 1646 t.Fatalf("not synced when should be synced") 1647 } 1648 1649 tNode.blockchainInfo = &chainjson.GetBlockChainInfoResult{ 1650 Headers: 100, 1651 Blocks: 50, 1652 } 1653 synced, err = dcr.Synced() 1654 if err != nil { 1655 t.Fatalf("Synced error: %v", err) 1656 } 1657 if synced { 1658 t.Fatalf("synced when shouldn't be synced") 1659 } 1660 1661 tNode.blockchainInfoErr = fmt.Errorf("test error") 1662 _, err = dcr.Synced() 1663 if err == nil { 1664 t.Fatalf("getblockchaininfo error not propagated") 1665 } 1666 tNode.blockchainInfoErr = nil 1667 } 1668 1669 func TestParseBondTx(t *testing.T) { 1670 const bondVer = 0 1671 var bondAmt int64 = 1e8 1672 txOK := wire.NewMsgTx() 1673 txIn := wire.NewTxIn(&wire.OutPoint{}, 1, []byte{2, 2, 3}) // junk input 1674 txOK.AddTxIn(txIn) 1675 1676 bondPriv, err := secp256k1.GeneratePrivateKey() 1677 if err != nil { 1678 t.Fatalf("GeneratePrivateKey error: %v\n", err) 1679 } 1680 bondPubKey := bondPriv.PubKey() 1681 pkh := stdaddr.Hash160(bondPubKey.SerializeCompressed()) 1682 lockTime := time.Now().Add(time.Hour) 1683 bondRedeemScript, err := dexdcr.MakeBondScript(bondVer, uint32(lockTime.Unix()), pkh) 1684 if err != nil { 1685 t.Fatalf("failed to build bond output redeem script: %v", err) 1686 } 1687 1688 bondAddr, err := stdaddr.NewAddressScriptHashV0(bondRedeemScript, chainParams) 1689 if err != nil { 1690 t.Fatalf("NewAddressScriptHashV0 error: %v\n", err) 1691 } 1692 _, bondPkScript := bondAddr.PaymentScript() 1693 1694 feeOut := wire.NewTxOut(bondAmt, bondPkScript) 1695 txOK.AddTxOut(feeOut) 1696 1697 priv, err := secp256k1.GeneratePrivateKey() 1698 if err != nil { 1699 t.Fatalf("GeneratePrivateKey error: %v\n", err) 1700 } 1701 pubkey := priv.PubKey().SerializeCompressed() 1702 acct, err := account.NewAccountFromPubKey(pubkey) 1703 if err != nil { 1704 t.Fatal(err) 1705 } 1706 1707 pushData := make([]byte, 2+len(acct.ID)+4+20) 1708 var offset int 1709 binary.BigEndian.PutUint16(pushData[offset:], bondVer) 1710 offset += 2 1711 copy(pushData[offset:], acct.ID[:]) 1712 offset += len(acct.ID) 1713 binary.BigEndian.PutUint32(pushData[offset:], uint32(lockTime.Unix())) 1714 offset += 4 1715 copy(pushData[offset:], pkh) 1716 commitPkScript, err := txscript.NewScriptBuilder(). 1717 AddOp(txscript.OP_RETURN). 1718 AddData(pushData). 1719 Script() 1720 if err != nil { 1721 t.Fatalf("script building error in testMsgTxSwapInit: %v", err) 1722 } 1723 acctOut := wire.NewTxOut(0, commitPkScript) 1724 txOK.AddTxOut(acctOut) 1725 1726 txRaw, err := txOK.Bytes() 1727 if err != nil { 1728 t.Fatal(err) 1729 } 1730 1731 gotCoinID, gotAmt, gotAddr, gotPubKeyHash, gotLockTime, gotAcct, err := ParseBondTx(bondVer, txRaw) 1732 if err != nil { 1733 t.Fatal(err) 1734 } 1735 1736 wantBondAddr := bondAddr.String() 1737 if wantBondAddr != gotAddr { 1738 t.Errorf("wrong fee address, wanted %v, got %v", wantBondAddr, gotAmt) 1739 } 1740 1741 if gotAcct != acct.ID { 1742 t.Errorf("wrong account, wanted %v, got %v", acct.ID, gotAcct) 1743 } 1744 1745 txHash, vout, err := decodeCoinID(gotCoinID) 1746 if err != nil { 1747 t.Fatalf("decodeCoinID: %v", err) 1748 } 1749 if txOK.TxHash() != *txHash { 1750 t.Fatalf("got txid %v, expected %v", txOK.TxHash(), txHash) 1751 } 1752 if vout != 0 { 1753 t.Fatalf("wanted vout 0, got %d", vout) 1754 } 1755 if !bytes.Equal(gotPubKeyHash, pkh) { 1756 t.Fatal("pubkey mismatch") 1757 } 1758 if lockTime.Unix() != gotLockTime { 1759 t.Fatalf("got lock time %d, wanted %d", gotLockTime, lockTime.Unix()) 1760 } 1761 }