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  }