github.com/lbryio/lbcd@v0.22.119/integration/rpcserver_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 integration 10 11 import ( 12 "bytes" 13 "fmt" 14 "os" 15 "runtime/debug" 16 "sort" 17 "testing" 18 "time" 19 20 "github.com/lbryio/lbcd/chaincfg" 21 "github.com/lbryio/lbcd/chaincfg/chainhash" 22 "github.com/lbryio/lbcd/integration/rpctest" 23 "github.com/lbryio/lbcd/rpcclient" 24 "github.com/lbryio/lbcd/txscript" 25 "github.com/lbryio/lbcd/wire" 26 "github.com/lbryio/lbcutil" 27 ) 28 29 func testGetBestBlock(r *rpctest.Harness, t *testing.T) { 30 _, prevbestHeight, err := r.Client.GetBestBlock() 31 if err != nil { 32 t.Fatalf("Call to `getbestblock` failed: %v", err) 33 } 34 35 // Create a new block connecting to the current tip. 36 generatedBlockHashes, err := r.Client.Generate(1) 37 if err != nil { 38 t.Fatalf("Unable to generate block: %v", err) 39 } 40 41 bestHash, bestHeight, err := r.Client.GetBestBlock() 42 if err != nil { 43 t.Fatalf("Call to `getbestblock` failed: %v", err) 44 } 45 46 // Hash should be the same as the newly submitted block. 47 if !bytes.Equal(bestHash[:], generatedBlockHashes[0][:]) { 48 t.Fatalf("Block hashes do not match. Returned hash %v, wanted "+ 49 "hash %v", bestHash, generatedBlockHashes[0][:]) 50 } 51 52 // Block height should now reflect newest height. 53 if bestHeight != prevbestHeight+1 { 54 t.Fatalf("Block heights do not match. Got %v, wanted %v", 55 bestHeight, prevbestHeight+1) 56 } 57 } 58 59 func testGetBlockCount(r *rpctest.Harness, t *testing.T) { 60 // Save the current count. 61 currentCount, err := r.Client.GetBlockCount() 62 if err != nil { 63 t.Fatalf("Unable to get block count: %v", err) 64 } 65 66 if _, err := r.Client.Generate(1); err != nil { 67 t.Fatalf("Unable to generate block: %v", err) 68 } 69 70 // Count should have increased by one. 71 newCount, err := r.Client.GetBlockCount() 72 if err != nil { 73 t.Fatalf("Unable to get block count: %v", err) 74 } 75 if newCount != currentCount+1 { 76 t.Fatalf("Block count incorrect. Got %v should be %v", 77 newCount, currentCount+1) 78 } 79 } 80 81 func testGetBlockHash(r *rpctest.Harness, t *testing.T) { 82 // Create a new block connecting to the current tip. 83 generatedBlockHashes, err := r.Client.Generate(1) 84 if err != nil { 85 t.Fatalf("Unable to generate block: %v", err) 86 } 87 88 info, err := r.Client.GetInfo() 89 if err != nil { 90 t.Fatalf("call to getinfo cailed: %v", err) 91 } 92 93 blockHash, err := r.Client.GetBlockHash(int64(info.Blocks)) 94 if err != nil { 95 t.Fatalf("Call to `getblockhash` failed: %v", err) 96 } 97 98 // Block hashes should match newly created block. 99 if !bytes.Equal(generatedBlockHashes[0][:], blockHash[:]) { 100 t.Fatalf("Block hashes do not match. Returned hash %v, wanted "+ 101 "hash %v", blockHash, generatedBlockHashes[0][:]) 102 } 103 } 104 105 func testBulkClient(r *rpctest.Harness, t *testing.T) { 106 // Create a new block connecting to the current tip. 107 generatedBlockHashes, err := r.Client.Generate(20) 108 if err != nil { 109 t.Fatalf("Unable to generate block: %v", err) 110 } 111 112 var futureBlockResults []rpcclient.FutureGetBlockResult 113 for _, hash := range generatedBlockHashes { 114 futureBlockResults = append(futureBlockResults, r.BatchClient.GetBlockAsync(hash)) 115 } 116 117 err = r.BatchClient.Send() 118 if err != nil { 119 t.Fatal(err) 120 } 121 122 isKnownBlockHash := func(blockHash chainhash.Hash) bool { 123 for _, hash := range generatedBlockHashes { 124 if blockHash.IsEqual(hash) { 125 return true 126 } 127 } 128 return false 129 } 130 131 for _, block := range futureBlockResults { 132 msgBlock, err := block.Receive() 133 if err != nil { 134 t.Fatal(err) 135 } 136 blockHash := msgBlock.Header.BlockHash() 137 if !isKnownBlockHash(blockHash) { 138 t.Fatalf("expected hash %s to be in generated hash list", blockHash) 139 } 140 } 141 } 142 143 func testGetBlockStats(r *rpctest.Harness, t *testing.T) { 144 t.Parallel() 145 146 baseFeeRate := int64(10) 147 txValue := int64(50000000) 148 txQuantity := 10 149 txs := make([]*lbcutil.Tx, txQuantity) 150 fees := make([]int64, txQuantity) 151 sizes := make([]int64, txQuantity) 152 feeRates := make([]int64, txQuantity) 153 var outputCount int 154 155 // Generate test sample. 156 for i := 0; i < txQuantity; i++ { 157 address, err := r.NewAddress() 158 if err != nil { 159 t.Fatalf("Unable to generate address: %v", err) 160 } 161 162 pkScript, err := txscript.PayToAddrScript(address) 163 if err != nil { 164 t.Fatalf("Unable to generate PKScript: %v", err) 165 } 166 167 // This feerate is not the actual feerate. See comment below. 168 feeRate := baseFeeRate * int64(i) 169 170 tx, err := r.CreateTransaction([]*wire.TxOut{wire.NewTxOut(txValue, pkScript)}, lbcutil.Amount(feeRate), true) 171 if err != nil { 172 t.Fatalf("Unable to generate segwit transaction: %v", err) 173 } 174 175 txs[i] = lbcutil.NewTx(tx) 176 sizes[i] = int64(tx.SerializeSize()) 177 178 // memWallet.fundTx makes some assumptions when calculating fees. 179 // For instance, it assumes the signature script has exactly 108 bytes 180 // and it does not account for the size of the change output. 181 // This needs to be taken into account when getting the true feerate. 182 scriptSigOffset := 108 - len(tx.TxIn[0].SignatureScript) 183 changeOutputSize := tx.TxOut[len(tx.TxOut)-1].SerializeSize() 184 fees[i] = (sizes[i] + int64(scriptSigOffset) - int64(changeOutputSize)) * feeRate 185 feeRates[i] = fees[i] / sizes[i] 186 187 outputCount += len(tx.TxOut) 188 } 189 190 stats := func(slice []int64) (int64, int64, int64, int64, int64) { 191 var total, average, min, max, median int64 192 min = slice[0] 193 length := len(slice) 194 for _, item := range slice { 195 if min > item { 196 min = item 197 } 198 if max < item { 199 max = item 200 } 201 total += item 202 } 203 average = total / int64(length) 204 sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] }) 205 if length == 0 { 206 median = 0 207 } else if length%2 == 0 { 208 median = (slice[length/2-1] + slice[length/2]) / 2 209 } else { 210 median = slice[length/2] 211 } 212 return total, average, min, max, median 213 } 214 215 totalFee, avgFee, minFee, maxFee, medianFee := stats(fees) 216 totalSize, avgSize, minSize, maxSize, medianSize := stats(sizes) 217 _, avgFeeRate, minFeeRate, maxFeeRate, _ := stats(feeRates) 218 219 tests := []struct { 220 name string 221 txs []*lbcutil.Tx 222 stats []string 223 expectedResults map[string]interface{} 224 }{ 225 { 226 name: "empty block", 227 txs: []*lbcutil.Tx{}, 228 stats: []string{}, 229 expectedResults: map[string]interface{}{ 230 "avgfee": int64(0), 231 "avgfeerate": int64(0), 232 "avgtxsize": int64(0), 233 "feerate_percentiles": []int64{0, 0, 0, 0, 0}, 234 "ins": int64(0), 235 "maxfee": int64(0), 236 "maxfeerate": int64(0), 237 "maxtxsize": int64(0), 238 "medianfee": int64(0), 239 "mediantxsize": int64(0), 240 "minfee": int64(0), 241 "mintxsize": int64(0), 242 "outs": int64(1), 243 "swtotal_size": int64(0), 244 "swtotal_weight": int64(0), 245 "swtxs": int64(0), 246 "total_out": int64(0), 247 "total_size": int64(0), 248 "total_weight": int64(0), 249 "txs": int64(1), 250 "utxo_increase": int64(1), 251 }, 252 }, 253 { 254 name: "block with 10 transactions + coinbase", 255 txs: txs, 256 stats: []string{"avgfee", "avgfeerate", "avgtxsize", "feerate_percentiles", 257 "ins", "maxfee", "maxfeerate", "maxtxsize", "medianfee", "mediantxsize", 258 "minfee", "minfeerate", "mintxsize", "outs", "subsidy", "swtxs", 259 "total_size", "total_weight", "totalfee", "txs", "utxo_increase"}, 260 expectedResults: map[string]interface{}{ 261 "avgfee": avgFee, 262 "avgfeerate": avgFeeRate, 263 "avgtxsize": avgSize, 264 "feerate_percentiles": []int64{feeRates[0], feeRates[2], 265 feeRates[4], feeRates[7], feeRates[8]}, 266 "ins": int64(txQuantity), 267 "maxfee": maxFee, 268 "maxfeerate": maxFeeRate, 269 "maxtxsize": maxSize, 270 "medianfee": medianFee, 271 "mediantxsize": medianSize, 272 "minfee": minFee, 273 "minfeerate": minFeeRate, 274 "mintxsize": minSize, 275 "outs": int64(outputCount + 1), // Coinbase output also counts. 276 "subsidy": int64(100000000), 277 "swtotal_weight": nil, // This stat was not selected, so it should be nil. 278 "swtxs": int64(0), 279 "total_size": totalSize, 280 "total_weight": totalSize * 4, 281 "totalfee": totalFee, 282 "txs": int64(txQuantity + 1), // Coinbase transaction also counts. 283 "utxo_increase": int64(outputCount + 1 - txQuantity), 284 "utxo_size_inc": nil, 285 }, 286 }, 287 } 288 for _, test := range tests { 289 // Submit a new block with the provided transactions. 290 block, err := r.GenerateAndSubmitBlock(test.txs, -1, time.Time{}) 291 if err != nil { 292 t.Fatalf("Unable to generate block: %v from test %s", err, test.name) 293 } 294 295 blockStats, err := r.GetBlockStats(block.Hash(), &test.stats) 296 if err != nil { 297 t.Fatalf("Call to `getblockstats` on test %s failed: %v", test.name, err) 298 } 299 300 if blockStats.Height != (*int64)(nil) && *blockStats.Height != int64(block.Height()) { 301 t.Fatalf("Unexpected result in test %s, stat: %v, expected: %v, got: %v", test.name, "height", block.Height(), *blockStats.Height) 302 } 303 304 for stat, value := range test.expectedResults { 305 var result interface{} 306 switch stat { 307 case "avgfee": 308 result = blockStats.AverageFee 309 case "avgfeerate": 310 result = blockStats.AverageFeeRate 311 case "avgtxsize": 312 result = blockStats.AverageTxSize 313 case "feerate_percentiles": 314 result = blockStats.FeeratePercentiles 315 case "blockhash": 316 result = blockStats.Hash 317 case "height": 318 result = blockStats.Height 319 case "ins": 320 result = blockStats.Ins 321 case "maxfee": 322 result = blockStats.MaxFee 323 case "maxfeerate": 324 result = blockStats.MaxFeeRate 325 case "maxtxsize": 326 result = blockStats.MaxTxSize 327 case "medianfee": 328 result = blockStats.MedianFee 329 case "mediantime": 330 result = blockStats.MedianTime 331 case "mediantxsize": 332 result = blockStats.MedianTxSize 333 case "minfee": 334 result = blockStats.MinFee 335 case "minfeerate": 336 result = blockStats.MinFeeRate 337 case "mintxsize": 338 result = blockStats.MinTxSize 339 case "outs": 340 result = blockStats.Outs 341 case "swtotal_size": 342 result = blockStats.SegWitTotalSize 343 case "swtotal_weight": 344 result = blockStats.SegWitTotalWeight 345 case "swtxs": 346 result = blockStats.SegWitTxs 347 case "subsidy": 348 result = blockStats.Subsidy 349 case "time": 350 result = blockStats.Time 351 case "total_out": 352 result = blockStats.TotalOut 353 case "total_size": 354 result = blockStats.TotalSize 355 case "total_weight": 356 result = blockStats.TotalWeight 357 case "totalfee": 358 result = blockStats.TotalFee 359 case "txs": 360 result = blockStats.Txs 361 case "utxo_increase": 362 result = blockStats.UTXOIncrease 363 case "utxo_size_inc": 364 result = blockStats.UTXOSizeIncrease 365 } 366 367 var equality bool 368 369 // Check for nil equality. 370 if value == nil && result == (*int64)(nil) { 371 equality = true 372 break 373 } else if result == nil || value == nil { 374 equality = false 375 } 376 377 var resultValue interface{} 378 switch v := value.(type) { 379 case int64: 380 resultValue = *result.(*int64) 381 equality = v == resultValue 382 case string: 383 resultValue = *result.(*string) 384 equality = v == resultValue 385 case []int64: 386 resultValue = *result.(*[]int64) 387 resultSlice := resultValue.([]int64) 388 equality = true 389 for i, item := range resultSlice { 390 if item != v[i] { 391 equality = false 392 break 393 } 394 } 395 } 396 if !equality { 397 if result != nil { 398 t.Fatalf("Unexpected result in test %s, stat: %v, expected: %v, got: %v", test.name, stat, value, resultValue) 399 } else { 400 t.Fatalf("Unexpected result in test %s, stat: %v, expected: %v, got: %v", test.name, stat, value, "<nil>") 401 } 402 } 403 404 } 405 } 406 } 407 408 var rpcTestCases = []rpctest.HarnessTestCase{ 409 testGetBestBlock, 410 testGetBlockCount, 411 testGetBlockHash, 412 testGetBlockStats, 413 testBulkClient, 414 } 415 416 var primaryHarness *rpctest.Harness 417 418 func TestMain(m *testing.M) { 419 var err error 420 421 // In order to properly test scenarios on as if we were on mainnet, 422 // ensure that non-standard transactions aren't accepted into the 423 // mempool or relayed. 424 // Enable transaction index to be able to fully test GetBlockStats 425 btcdCfg := []string{"--rejectnonstd", "--txindex"} 426 primaryHarness, err = rpctest.New( 427 &chaincfg.SimNetParams, nil, btcdCfg, "", 428 ) 429 if err != nil { 430 fmt.Println("unable to create primary harness: ", err) 431 os.Exit(1) 432 } 433 434 // Initialize the primary mining node with a chain of length 125, 435 // providing 25 mature coinbases to allow spending from for testing 436 // purposes. 437 if err := primaryHarness.SetUp(true, 25); err != nil { 438 fmt.Println("unable to setup test chain: ", err) 439 440 // Even though the harness was not fully setup, it still needs 441 // to be torn down to ensure all resources such as temp 442 // directories are cleaned up. The error is intentionally 443 // ignored since this is already an error path and nothing else 444 // could be done about it anyways. 445 _ = primaryHarness.TearDown() 446 os.Exit(1) 447 } 448 449 exitCode := m.Run() 450 451 // Clean up any active harnesses that are still currently running.This 452 // includes removing all temporary directories, and shutting down any 453 // created processes. 454 if err := rpctest.TearDownAll(); err != nil { 455 fmt.Println("unable to tear down all harnesses: ", err) 456 os.Exit(1) 457 } 458 459 os.Exit(exitCode) 460 } 461 462 func TestRpcServer(t *testing.T) { 463 var currentTestNum int 464 defer func() { 465 // If one of the integration tests caused a panic within the main 466 // goroutine, then tear down all the harnesses in order to avoid 467 // any leaked btcd processes. 468 if r := recover(); r != nil { 469 fmt.Println("recovering from test panic: ", r) 470 if err := rpctest.TearDownAll(); err != nil { 471 fmt.Println("unable to tear down all harnesses: ", err) 472 } 473 t.Fatalf("test #%v panicked: %s", currentTestNum, debug.Stack()) 474 } 475 }() 476 477 for _, testCase := range rpcTestCases { 478 testCase(primaryHarness, t) 479 480 currentTestNum++ 481 } 482 }