github.com/lbryio/lbcd@v0.22.119/integration/rpctest/rpc_harness_test.go (about) 1 // Copyright (c) 2016 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 // This file is ignored during the regular tests due to the following build tag. 6 //go:build rpctest 7 // +build rpctest 8 9 package rpctest 10 11 import ( 12 "fmt" 13 "os" 14 "testing" 15 "time" 16 17 "github.com/lbryio/lbcd/chaincfg" 18 "github.com/lbryio/lbcd/chaincfg/chainhash" 19 "github.com/lbryio/lbcd/txscript" 20 "github.com/lbryio/lbcd/wire" 21 btcutil "github.com/lbryio/lbcutil" 22 ) 23 24 func testSendOutputs(r *Harness, t *testing.T) { 25 genSpend := func(amt btcutil.Amount) *chainhash.Hash { 26 // Grab a fresh address from the wallet. 27 addr, err := r.NewAddress() 28 if err != nil { 29 t.Fatalf("unable to get new address: %v", err) 30 } 31 32 // Next, send amt BTC to this address, spending from one of our mature 33 // coinbase outputs. 34 addrScript, err := txscript.PayToAddrScript(addr) 35 if err != nil { 36 t.Fatalf("unable to generate pkscript to addr: %v", err) 37 } 38 output := wire.NewTxOut(int64(amt), addrScript) 39 txid, err := r.SendOutputs([]*wire.TxOut{output}, 10) 40 if err != nil { 41 t.Fatalf("coinbase spend failed: %v", err) 42 } 43 return txid 44 } 45 46 assertTxMined := func(txid *chainhash.Hash, blockHash *chainhash.Hash) { 47 block, err := r.Client.GetBlock(blockHash) 48 if err != nil { 49 t.Fatalf("unable to get block: %v", err) 50 } 51 52 numBlockTxns := len(block.Transactions) 53 if numBlockTxns < 2 { 54 t.Fatalf("crafted transaction wasn't mined, block should have "+ 55 "at least %v transactions instead has %v", 2, numBlockTxns) 56 } 57 58 minedTx := block.Transactions[1] 59 txHash := minedTx.TxHash() 60 if txHash != *txid { 61 t.Fatalf("txid's don't match, %v vs %v", txHash, txid) 62 } 63 } 64 65 // First, generate a small spend which will require only a single 66 // input. 67 txid := genSpend(btcutil.Amount(btcutil.SatoshiPerBitcoin)) 68 69 // Generate a single block, the transaction the wallet created should 70 // be found in this block. 71 blockHashes, err := r.Client.Generate(1) 72 if err != nil { 73 t.Fatalf("unable to generate single block: %v", err) 74 } 75 assertTxMined(txid, blockHashes[0]) 76 77 // Next, generate a spend much greater than the block reward. This 78 // transaction should also have been mined properly. 79 txid = genSpend(btcutil.Amount(10 * btcutil.SatoshiPerBitcoin)) 80 blockHashes, err = r.Client.Generate(1) 81 if err != nil { 82 t.Fatalf("unable to generate single block: %v", err) 83 } 84 assertTxMined(txid, blockHashes[0]) 85 } 86 87 func assertConnectedTo(t *testing.T, nodeA *Harness, nodeB *Harness) { 88 nodeAPeers, err := nodeA.Client.GetPeerInfo() 89 if err != nil { 90 t.Fatalf("unable to get nodeA's peer info") 91 } 92 93 nodeAddr := nodeB.node.config.listen 94 addrFound := false 95 for _, peerInfo := range nodeAPeers { 96 if peerInfo.Addr == nodeAddr { 97 addrFound = true 98 break 99 } 100 } 101 102 if !addrFound { 103 t.Fatal("nodeA not connected to nodeB") 104 } 105 } 106 107 func testConnectNode(r *Harness, t *testing.T) { 108 // Create a fresh test harness. 109 harness, err := New(&chaincfg.RegressionNetParams, nil, nil, "") 110 if err != nil { 111 t.Fatal(err) 112 } 113 if err := harness.SetUp(false, 0); err != nil { 114 t.Fatalf("unable to complete rpctest setup: %v", err) 115 } 116 defer harness.TearDown() 117 118 // Establish a p2p connection from our new local harness to the main 119 // harness. 120 if err := ConnectNode(harness, r); err != nil { 121 t.Fatalf("unable to connect local to main harness: %v", err) 122 } 123 124 // The main harness should show up in our local harness' peer's list, 125 // and vice verse. 126 assertConnectedTo(t, harness, r) 127 } 128 129 func testTearDownAll(t *testing.T) { 130 // Grab a local copy of the currently active harnesses before 131 // attempting to tear them all down. 132 initialActiveHarnesses := ActiveHarnesses() 133 134 // Tear down all currently active harnesses. 135 if err := TearDownAll(); err != nil { 136 t.Fatalf("unable to teardown all harnesses: %v", err) 137 } 138 139 // The global testInstances map should now be fully purged with no 140 // active test harnesses remaining. 141 if len(ActiveHarnesses()) != 0 { 142 t.Fatalf("test harnesses still active after TearDownAll") 143 } 144 145 for _, harness := range initialActiveHarnesses { 146 // Ensure all test directories have been deleted. 147 if _, err := os.Stat(harness.testNodeDir); err == nil { 148 t.Errorf("created test datadir was not deleted.") 149 } 150 } 151 } 152 153 func testActiveHarnesses(r *Harness, t *testing.T) { 154 numInitialHarnesses := len(ActiveHarnesses()) 155 156 // Create a single test harness. 157 harness1, err := New(&chaincfg.RegressionNetParams, nil, nil, "") 158 if err != nil { 159 t.Fatal(err) 160 } 161 defer harness1.TearDown() 162 163 // With the harness created above, a single harness should be detected 164 // as active. 165 numActiveHarnesses := len(ActiveHarnesses()) 166 if !(numActiveHarnesses > numInitialHarnesses) { 167 t.Fatalf("ActiveHarnesses not updated, should have an " + 168 "additional test harness listed.") 169 } 170 } 171 172 func testJoinMempools(r *Harness, t *testing.T) { 173 // Assert main test harness has no transactions in its mempool. 174 pooledHashes, err := r.Client.GetRawMempool() 175 if err != nil { 176 t.Fatalf("unable to get mempool for main test harness: %v", err) 177 } 178 if len(pooledHashes) != 0 { 179 t.Fatal("main test harness mempool not empty") 180 } 181 182 // Create a local test harness with only the genesis block. The nodes 183 // will be synced below so the same transaction can be sent to both 184 // nodes without it being an orphan. 185 harness, err := New(&chaincfg.RegressionNetParams, nil, nil, "") 186 if err != nil { 187 t.Fatal(err) 188 } 189 if err := harness.SetUp(false, 0); err != nil { 190 t.Fatalf("unable to complete rpctest setup: %v", err) 191 } 192 defer harness.TearDown() 193 194 nodeSlice := []*Harness{r, harness} 195 196 // Both mempools should be considered synced as they are empty. 197 // Therefore, this should return instantly. 198 if err := JoinNodes(nodeSlice, Mempools); err != nil { 199 t.Fatalf("unable to join node on mempools: %v", err) 200 } 201 202 // Generate a coinbase spend to a new address within the main harness' 203 // mempool. 204 addr, err := r.NewAddress() 205 addrScript, err := txscript.PayToAddrScript(addr) 206 if err != nil { 207 t.Fatalf("unable to generate pkscript to addr: %v", err) 208 } 209 output := wire.NewTxOut(5e8, addrScript) 210 testTx, err := r.CreateTransaction([]*wire.TxOut{output}, 10, true) 211 if err != nil { 212 t.Fatalf("coinbase spend failed: %v", err) 213 } 214 if _, err := r.Client.SendRawTransaction(testTx, true); err != nil { 215 t.Fatalf("send transaction failed: %v", err) 216 } 217 218 // Wait until the transaction shows up to ensure the two mempools are 219 // not the same. 220 harnessSynced := make(chan struct{}) 221 go func() { 222 for { 223 poolHashes, err := r.Client.GetRawMempool() 224 if err != nil { 225 t.Fatalf("failed to retrieve harness mempool: %v", err) 226 } 227 if len(poolHashes) > 0 { 228 break 229 } 230 time.Sleep(time.Millisecond * 100) 231 } 232 harnessSynced <- struct{}{} 233 }() 234 select { 235 case <-harnessSynced: 236 case <-time.After(time.Minute): 237 t.Fatalf("harness node never received transaction") 238 } 239 240 // This select case should fall through to the default as the goroutine 241 // should be blocked on the JoinNodes call. 242 poolsSynced := make(chan struct{}) 243 go func() { 244 if err := JoinNodes(nodeSlice, Mempools); err != nil { 245 t.Fatalf("unable to join node on mempools: %v", err) 246 } 247 poolsSynced <- struct{}{} 248 }() 249 select { 250 case <-poolsSynced: 251 t.Fatalf("mempools detected as synced yet harness has a new tx") 252 default: 253 } 254 255 // Establish an outbound connection from the local harness to the main 256 // harness and wait for the chains to be synced. 257 if err := ConnectNode(harness, r); err != nil { 258 t.Fatalf("unable to connect harnesses: %v", err) 259 } 260 if err := JoinNodes(nodeSlice, Blocks); err != nil { 261 t.Fatalf("unable to join node on blocks: %v", err) 262 } 263 264 // Send the transaction to the local harness which will result in synced 265 // mempools. 266 if _, err := harness.Client.SendRawTransaction(testTx, true); err != nil { 267 t.Fatalf("send transaction failed: %v", err) 268 } 269 270 // Select once again with a special timeout case after 1 minute. The 271 // goroutine above should now be blocked on sending into the unbuffered 272 // channel. The send should immediately succeed. In order to avoid the 273 // test hanging indefinitely, a 1 minute timeout is in place. 274 select { 275 case <-poolsSynced: 276 // fall through 277 case <-time.After(time.Minute): 278 t.Fatalf("mempools never detected as synced") 279 } 280 } 281 282 func testJoinBlocks(r *Harness, t *testing.T) { 283 // Create a second harness with only the genesis block so it is behind 284 // the main harness. 285 harness, err := New(&chaincfg.RegressionNetParams, nil, nil, "") 286 if err != nil { 287 t.Fatal(err) 288 } 289 if err := harness.SetUp(false, 0); err != nil { 290 t.Fatalf("unable to complete rpctest setup: %v", err) 291 } 292 defer harness.TearDown() 293 294 nodeSlice := []*Harness{r, harness} 295 blocksSynced := make(chan struct{}) 296 go func() { 297 if err := JoinNodes(nodeSlice, Blocks); err != nil { 298 t.Fatalf("unable to join node on blocks: %v", err) 299 } 300 blocksSynced <- struct{}{} 301 }() 302 303 // This select case should fall through to the default as the goroutine 304 // should be blocked on the JoinNodes calls. 305 select { 306 case <-blocksSynced: 307 t.Fatalf("blocks detected as synced yet local harness is behind") 308 default: 309 } 310 311 // Connect the local harness to the main harness which will sync the 312 // chains. 313 if err := ConnectNode(harness, r); err != nil { 314 t.Fatalf("unable to connect harnesses: %v", err) 315 } 316 317 // Select once again with a special timeout case after 1 minute. The 318 // goroutine above should now be blocked on sending into the unbuffered 319 // channel. The send should immediately succeed. In order to avoid the 320 // test hanging indefinitely, a 1 minute timeout is in place. 321 select { 322 case <-blocksSynced: 323 // fall through 324 case <-time.After(time.Minute): 325 t.Fatalf("blocks never detected as synced") 326 } 327 } 328 329 func testGenerateAndSubmitBlock(r *Harness, t *testing.T) { 330 // Generate a few test spend transactions. 331 addr, err := r.NewAddress() 332 if err != nil { 333 t.Fatalf("unable to generate new address: %v", err) 334 } 335 pkScript, err := txscript.PayToAddrScript(addr) 336 if err != nil { 337 t.Fatalf("unable to create script: %v", err) 338 } 339 output := wire.NewTxOut(btcutil.SatoshiPerBitcoin/50, pkScript) 340 341 const numTxns = 5 342 txns := make([]*btcutil.Tx, 0, numTxns) 343 for i := 0; i < numTxns; i++ { 344 tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10, true) 345 if err != nil { 346 t.Fatalf("unable to create tx: %v", err) 347 } 348 349 txns = append(txns, btcutil.NewTx(tx)) 350 } 351 352 // Now generate a block with the default block version, and a zero'd 353 // out time. 354 block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{}) 355 if err != nil { 356 t.Fatalf("unable to generate block: %v", err) 357 } 358 359 // Ensure that all created transactions were included, and that the 360 // block version was properly set to the default. 361 numBlocksTxns := len(block.Transactions()) 362 if numBlocksTxns != numTxns+1 { 363 t.Fatalf("block did not include all transactions: "+ 364 "expected %v, got %v", numTxns+1, numBlocksTxns) 365 } 366 blockVersion := block.MsgBlock().Header.Version 367 if blockVersion != BlockVersion { 368 t.Fatalf("block version is not default: expected %v, got %v", 369 BlockVersion, blockVersion) 370 } 371 372 // Next generate a block with a "non-standard" block version along with 373 // time stamp a minute after the previous block's timestamp. 374 timestamp := block.MsgBlock().Header.Timestamp.Add(time.Minute) 375 targetBlockVersion := int32(1337) 376 block, err = r.GenerateAndSubmitBlock(nil, targetBlockVersion, timestamp) 377 if err != nil { 378 t.Fatalf("unable to generate block: %v", err) 379 } 380 381 // Finally ensure that the desired block version and timestamp were set 382 // properly. 383 header := block.MsgBlock().Header 384 blockVersion = header.Version 385 if blockVersion != targetBlockVersion { 386 t.Fatalf("block version mismatch: expected %v, got %v", 387 targetBlockVersion, blockVersion) 388 } 389 if !timestamp.Equal(header.Timestamp) { 390 t.Fatalf("header time stamp mismatch: expected %v, got %v", 391 timestamp, header.Timestamp) 392 } 393 } 394 395 func testGenerateAndSubmitBlockWithCustomCoinbaseOutputs(r *Harness, 396 t *testing.T) { 397 // Generate a few test spend transactions. 398 addr, err := r.NewAddress() 399 if err != nil { 400 t.Fatalf("unable to generate new address: %v", err) 401 } 402 pkScript, err := txscript.PayToAddrScript(addr) 403 if err != nil { 404 t.Fatalf("unable to create script: %v", err) 405 } 406 output := wire.NewTxOut(btcutil.SatoshiPerBitcoin, pkScript) 407 408 const numTxns = 5 409 txns := make([]*btcutil.Tx, 0, numTxns) 410 for i := 0; i < numTxns; i++ { 411 tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10, true) 412 if err != nil { 413 t.Fatalf("unable to create tx: %v", err) 414 } 415 416 txns = append(txns, btcutil.NewTx(tx)) 417 } 418 419 // Now generate a block with the default block version, a zero'd out 420 // time, and a burn output. 421 block, err := r.GenerateAndSubmitBlockWithCustomCoinbaseOutputs(txns, 422 -1, time.Time{}, []wire.TxOut{{ 423 Value: 0, 424 PkScript: []byte{}, 425 }}) 426 if err != nil { 427 t.Fatalf("unable to generate block: %v", err) 428 } 429 430 // Ensure that all created transactions were included, and that the 431 // block version was properly set to the default. 432 numBlocksTxns := len(block.Transactions()) 433 if numBlocksTxns != numTxns+1 { 434 t.Fatalf("block did not include all transactions: "+ 435 "expected %v, got %v", numTxns+1, numBlocksTxns) 436 } 437 blockVersion := block.MsgBlock().Header.Version 438 if blockVersion != BlockVersion { 439 t.Fatalf("block version is not default: expected %v, got %v", 440 BlockVersion, blockVersion) 441 } 442 443 // Next generate a block with a "non-standard" block version along with 444 // time stamp a minute after the previous block's timestamp. 445 timestamp := block.MsgBlock().Header.Timestamp.Add(time.Minute) 446 targetBlockVersion := int32(1337) 447 block, err = r.GenerateAndSubmitBlockWithCustomCoinbaseOutputs(nil, 448 targetBlockVersion, timestamp, []wire.TxOut{{ 449 Value: 0, 450 PkScript: []byte{}, 451 }}) 452 if err != nil { 453 t.Fatalf("unable to generate block: %v", err) 454 } 455 456 // Finally ensure that the desired block version and timestamp were set 457 // properly. 458 header := block.MsgBlock().Header 459 blockVersion = header.Version 460 if blockVersion != targetBlockVersion { 461 t.Fatalf("block version mismatch: expected %v, got %v", 462 targetBlockVersion, blockVersion) 463 } 464 if !timestamp.Equal(header.Timestamp) { 465 t.Fatalf("header time stamp mismatch: expected %v, got %v", 466 timestamp, header.Timestamp) 467 } 468 } 469 470 func testMemWalletReorg(r *Harness, t *testing.T) { 471 // Create a fresh harness, we'll be using the main harness to force a 472 // re-org on this local harness. 473 harness, err := New(&chaincfg.RegressionNetParams, nil, nil, "") 474 if err != nil { 475 t.Fatal(err) 476 } 477 if err := harness.SetUp(true, 5); err != nil { 478 t.Fatalf("unable to complete rpctest setup: %v", err) 479 } 480 defer harness.TearDown() 481 482 // The internal wallet of this harness should now have 250 BTC, but BTC is 50x LBC per generated coin. 483 expectedBalance := btcutil.Amount(5 * btcutil.SatoshiPerBitcoin) 484 walletBalance := harness.ConfirmedBalance() 485 if expectedBalance != walletBalance { 486 t.Fatalf("wallet balance incorrect: expected %v, got %v", 487 expectedBalance, walletBalance) 488 } 489 490 // Now connect this local harness to the main harness then wait for 491 // their chains to synchronize. 492 if err := ConnectNode(harness, r); err != nil { 493 t.Fatalf("unable to connect harnesses: %v", err) 494 } 495 nodeSlice := []*Harness{r, harness} 496 if err := JoinNodes(nodeSlice, Blocks); err != nil { 497 t.Fatalf("unable to join node on blocks: %v", err) 498 } 499 500 // The original wallet should now have a balance of 0 BTC as its entire 501 // chain should have been decimated in favor of the main harness' 502 // chain. 503 expectedBalance = btcutil.Amount(0) 504 walletBalance = harness.ConfirmedBalance() 505 if expectedBalance != walletBalance { 506 t.Fatalf("wallet balance incorrect: expected %v, got %v", 507 expectedBalance, walletBalance) 508 } 509 } 510 511 func testMemWalletLockedOutputs(r *Harness, t *testing.T) { 512 // Obtain the initial balance of the wallet at this point. 513 startingBalance := r.ConfirmedBalance() 514 515 // First, create a signed transaction spending some outputs. 516 addr, err := r.NewAddress() 517 if err != nil { 518 t.Fatalf("unable to generate new address: %v", err) 519 } 520 pkScript, err := txscript.PayToAddrScript(addr) 521 if err != nil { 522 t.Fatalf("unable to create script: %v", err) 523 } 524 outputAmt := btcutil.Amount(btcutil.SatoshiPerBitcoin) 525 output := wire.NewTxOut(int64(outputAmt), pkScript) 526 tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10, true) 527 if err != nil { 528 t.Fatalf("unable to create transaction: %v", err) 529 } 530 531 // The current wallet balance should now be at least 50 BTC less 532 // (accounting for fees) than the period balance 533 currentBalance := r.ConfirmedBalance() 534 if !(currentBalance <= startingBalance-outputAmt) { 535 t.Fatalf("spent outputs not locked: previous balance %v, "+ 536 "current balance %v", startingBalance, currentBalance) 537 } 538 539 // Now unlocked all the spent inputs within the unbroadcast signed 540 // transaction. The current balance should now be exactly that of the 541 // starting balance. 542 r.UnlockOutputs(tx.TxIn) 543 currentBalance = r.ConfirmedBalance() 544 if currentBalance != startingBalance { 545 t.Fatalf("current and starting balance should now match: "+ 546 "expected %v, got %v", startingBalance, currentBalance) 547 } 548 } 549 550 var harnessTestCases = []HarnessTestCase{ 551 testSendOutputs, 552 testConnectNode, 553 testActiveHarnesses, 554 testJoinBlocks, 555 testJoinMempools, // Depends on results of testJoinBlocks 556 testGenerateAndSubmitBlock, 557 testGenerateAndSubmitBlockWithCustomCoinbaseOutputs, 558 testMemWalletReorg, 559 testMemWalletLockedOutputs, 560 } 561 562 var mainHarness *Harness 563 564 const ( 565 numMatureOutputs = 25 566 ) 567 568 func TestMain(m *testing.M) { 569 var err error 570 mainHarness, err = New(&chaincfg.RegressionNetParams, nil, nil, "") 571 if err != nil { 572 fmt.Println("unable to create main harness: ", err) 573 os.Exit(1) 574 } 575 576 // Initialize the main mining node with a chain of length 125, 577 // providing 25 mature coinbases to allow spending from for testing 578 // purposes. 579 if err = mainHarness.SetUp(true, numMatureOutputs); err != nil { 580 fmt.Println("unable to setup test chain: ", err) 581 582 // Even though the harness was not fully setup, it still needs 583 // to be torn down to ensure all resources such as temp 584 // directories are cleaned up. The error is intentionally 585 // ignored since this is already an error path and nothing else 586 // could be done about it anyways. 587 _ = mainHarness.TearDown() 588 os.Exit(1) 589 } 590 591 exitCode := m.Run() 592 593 // Clean up any active harnesses that are still currently running. 594 if len(ActiveHarnesses()) > 0 { 595 if err := TearDownAll(); err != nil { 596 fmt.Println("unable to tear down chain: ", err) 597 os.Exit(1) 598 } 599 } 600 601 os.Exit(exitCode) 602 } 603 604 func TestHarness(t *testing.T) { 605 expectedBalance := btcutil.Amount(numMatureOutputs * btcutil.SatoshiPerBitcoin) 606 harnessBalance := mainHarness.ConfirmedBalance() 607 if harnessBalance != expectedBalance { 608 t.Fatalf("expected wallet balance of %v instead have %v", 609 expectedBalance, harnessBalance) 610 } 611 612 // Current tip should be at a height of numMatureOutputs plus the 613 // required number of blocks for coinbase maturity. 614 nodeInfo, err := mainHarness.Client.GetInfo() 615 if err != nil { 616 t.Fatalf("unable to execute getinfo on node: %v", err) 617 } 618 expectedChainHeight := numMatureOutputs + uint32(mainHarness.ActiveNet.CoinbaseMaturity) 619 if uint32(nodeInfo.Blocks) != expectedChainHeight { 620 t.Errorf("Chain height is %v, should be %v", 621 nodeInfo.Blocks, expectedChainHeight) 622 } 623 624 for _, testCase := range harnessTestCases { 625 testCase(mainHarness, t) 626 } 627 628 testTearDownAll(t) 629 }