github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/tests/rpc/rpc.go (about)

     1  // +build integration
     2  
     3  package rpc
     4  
     5  import (
     6  	"blockbook/bchain"
     7  	"encoding/json"
     8  	"io/ioutil"
     9  	"path/filepath"
    10  	"reflect"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/deckarep/golang-set"
    15  	"github.com/juju/errors"
    16  )
    17  
    18  var testMap = map[string]func(t *testing.T, th *TestHandler){
    19  	"GetBlockHash":             testGetBlockHash,
    20  	"GetBlock":                 testGetBlock,
    21  	"GetTransaction":           testGetTransaction,
    22  	"GetTransactionForMempool": testGetTransactionForMempool,
    23  	"MempoolSync":              testMempoolSync,
    24  	"EstimateSmartFee":         testEstimateSmartFee,
    25  	"EstimateFee":              testEstimateFee,
    26  	"GetBestBlockHash":         testGetBestBlockHash,
    27  	"GetBestBlockHeight":       testGetBestBlockHeight,
    28  	"GetBlockHeader":           testGetBlockHeader,
    29  }
    30  
    31  type TestHandler struct {
    32  	Chain    bchain.BlockChain
    33  	TestData *TestData
    34  }
    35  
    36  type TestData struct {
    37  	BlockHeight uint32                `json:"blockHeight"`
    38  	BlockHash   string                `json:"blockHash"`
    39  	BlockTime   int64                 `json:"blockTime"`
    40  	BlockTxs    []string              `json:"blockTxs"`
    41  	TxDetails   map[string]*bchain.Tx `json:"txDetails"`
    42  }
    43  
    44  func IntegrationTest(t *testing.T, coin string, chain bchain.BlockChain, testConfig json.RawMessage) {
    45  	tests, err := getTests(testConfig)
    46  	if err != nil {
    47  		t.Fatalf("Failed loading of test list: %s", err)
    48  	}
    49  
    50  	parser := chain.GetChainParser()
    51  	td, err := loadTestData(coin, parser)
    52  	if err != nil {
    53  		t.Fatalf("Failed loading of test data: %s", err)
    54  	}
    55  
    56  	h := TestHandler{Chain: chain, TestData: td}
    57  
    58  	for _, test := range tests {
    59  		if f, found := testMap[test]; found {
    60  			t.Run(test, func(t *testing.T) { f(t, &h) })
    61  		} else {
    62  			t.Errorf("%s: test not found", test)
    63  			continue
    64  		}
    65  	}
    66  }
    67  
    68  func getTests(cfg json.RawMessage) ([]string, error) {
    69  	var v []string
    70  	err := json.Unmarshal(cfg, &v)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	if len(v) == 0 {
    75  		return nil, errors.New("No tests declared")
    76  	}
    77  	return v, nil
    78  }
    79  
    80  func loadTestData(coin string, parser bchain.BlockChainParser) (*TestData, error) {
    81  	path := filepath.Join("rpc/testdata", coin+".json")
    82  	b, err := ioutil.ReadFile(path)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	var v TestData
    87  	err = json.Unmarshal(b, &v)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	for _, tx := range v.TxDetails {
    92  		// convert amounts in test json to bit.Int and clear the temporary JsonValue
    93  		for i := range tx.Vout {
    94  			vout := &tx.Vout[i]
    95  			vout.ValueSat, err = parser.AmountToBigInt(vout.JsonValue)
    96  			if err != nil {
    97  				return nil, err
    98  			}
    99  			vout.JsonValue = ""
   100  		}
   101  
   102  		// get addresses parsed
   103  		err := setTxAddresses(parser, tx)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  	}
   108  
   109  	return &v, nil
   110  }
   111  
   112  func setTxAddresses(parser bchain.BlockChainParser, tx *bchain.Tx) error {
   113  	for i := range tx.Vout {
   114  		ad, err := parser.GetAddrDescFromVout(&tx.Vout[i])
   115  		if err != nil {
   116  			return err
   117  		}
   118  		addrs := []string{}
   119  		a, s, err := parser.GetAddressesFromAddrDesc(ad)
   120  		if err == nil && s {
   121  			addrs = append(addrs, a...)
   122  		}
   123  		tx.Vout[i].ScriptPubKey.Addresses = addrs
   124  	}
   125  	return nil
   126  }
   127  
   128  func testGetBlockHash(t *testing.T, h *TestHandler) {
   129  	hash, err := h.Chain.GetBlockHash(h.TestData.BlockHeight)
   130  	if err != nil {
   131  		t.Error(err)
   132  		return
   133  	}
   134  
   135  	if hash != h.TestData.BlockHash {
   136  		t.Errorf("GetBlockHash() got %q, want %q", hash, h.TestData.BlockHash)
   137  	}
   138  }
   139  func testGetBlock(t *testing.T, h *TestHandler) {
   140  	blk, err := h.Chain.GetBlock(h.TestData.BlockHash, 0)
   141  	if err != nil {
   142  		t.Error(err)
   143  		return
   144  	}
   145  
   146  	if len(blk.Txs) != len(h.TestData.BlockTxs) {
   147  		t.Errorf("GetBlock() number of transactions: got %d, want %d", len(blk.Txs), len(h.TestData.BlockTxs))
   148  	}
   149  
   150  	for ti, tx := range blk.Txs {
   151  		if tx.Txid != h.TestData.BlockTxs[ti] {
   152  			t.Errorf("GetBlock() transaction %d: got %s, want %s", ti, tx.Txid, h.TestData.BlockTxs[ti])
   153  		}
   154  	}
   155  }
   156  func testGetTransaction(t *testing.T, h *TestHandler) {
   157  	for txid, want := range h.TestData.TxDetails {
   158  		got, err := h.Chain.GetTransaction(txid)
   159  		if err != nil {
   160  			t.Error(err)
   161  			return
   162  		}
   163  		// Confirmations is variable field, we just check if is set and reset it
   164  		if got.Confirmations <= 0 {
   165  			t.Errorf("GetTransaction() got struct with invalid Confirmations field")
   166  			continue
   167  		}
   168  		got.Confirmations = 0
   169  
   170  		if !reflect.DeepEqual(got, want) {
   171  			t.Errorf("GetTransaction() got %+v, want %+v", got, want)
   172  		}
   173  	}
   174  }
   175  func testGetTransactionForMempool(t *testing.T, h *TestHandler) {
   176  	for txid, want := range h.TestData.TxDetails {
   177  		// reset fields that are not parsed by BlockChainParser
   178  		want.Confirmations, want.Blocktime, want.Time = 0, 0, 0
   179  
   180  		got, err := h.Chain.GetTransactionForMempool(txid)
   181  		if err != nil {
   182  			t.Fatal(err)
   183  		}
   184  		// transactions parsed from JSON may contain additional data
   185  		got.Confirmations, got.Blocktime, got.Time = 0, 0, 0
   186  		if !reflect.DeepEqual(got, want) {
   187  			t.Errorf("GetTransactionForMempool() got %+v, want %+v", got, want)
   188  		}
   189  	}
   190  }
   191  func testMempoolSync(t *testing.T, h *TestHandler) {
   192  	for i := 0; i < 3; i++ {
   193  		txs := getMempool(t, h)
   194  
   195  		n, err := h.Chain.ResyncMempool(nil)
   196  		if err != nil {
   197  			t.Fatal(err)
   198  		}
   199  		if n == 0 {
   200  			// no transactions to test
   201  			continue
   202  		}
   203  
   204  		txs = intersect(txs, getMempool(t, h))
   205  		if len(txs) == 0 {
   206  			// no transactions to test
   207  			continue
   208  		}
   209  
   210  		txid2addrs := getTxid2addrs(t, h, txs)
   211  		if len(txid2addrs) == 0 {
   212  			t.Skip("Skipping test, no addresses in mempool")
   213  		}
   214  
   215  		for txid, addrs := range txid2addrs {
   216  			for _, a := range addrs {
   217  				got, err := h.Chain.GetMempoolTransactions(a)
   218  				if err != nil {
   219  					t.Fatalf("address %q: %s", a, err)
   220  				}
   221  				if !containsString(got, txid) {
   222  					t.Errorf("ResyncMempool() - for address %s, transaction %s wasn't found in mempool", a, txid)
   223  					return
   224  				}
   225  			}
   226  		}
   227  
   228  		// done
   229  		return
   230  	}
   231  	t.Skip("Skipping test, all attempts to sync mempool failed due to network state changes")
   232  }
   233  func testEstimateSmartFee(t *testing.T, h *TestHandler) {
   234  	for _, blocks := range []int{1, 2, 3, 5, 10} {
   235  		fee, err := h.Chain.EstimateSmartFee(blocks, true)
   236  		if err != nil {
   237  			t.Error(err)
   238  		}
   239  		if fee.Sign() == -1 {
   240  			sf := h.Chain.GetChainParser().AmountToDecimalString(&fee)
   241  			if sf != "-1" {
   242  				t.Errorf("EstimateSmartFee() returned unexpected fee rate: %v", sf)
   243  			}
   244  		}
   245  	}
   246  }
   247  func testEstimateFee(t *testing.T, h *TestHandler) {
   248  	for _, blocks := range []int{1, 2, 3, 5, 10} {
   249  		fee, err := h.Chain.EstimateFee(blocks)
   250  		if err != nil {
   251  			t.Error(err)
   252  		}
   253  		if fee.Sign() == -1 {
   254  			sf := h.Chain.GetChainParser().AmountToDecimalString(&fee)
   255  			if sf != "-1" {
   256  				t.Errorf("EstimateFee() returned unexpected fee rate: %v", sf)
   257  			}
   258  		}
   259  	}
   260  }
   261  func testGetBestBlockHash(t *testing.T, h *TestHandler) {
   262  	for i := 0; i < 3; i++ {
   263  		hash, err := h.Chain.GetBestBlockHash()
   264  		if err != nil {
   265  			t.Fatal(err)
   266  		}
   267  
   268  		height, err := h.Chain.GetBestBlockHeight()
   269  		if err != nil {
   270  			t.Fatal(err)
   271  		}
   272  		hh, err := h.Chain.GetBlockHash(height)
   273  		if err != nil {
   274  			t.Fatal(err)
   275  		}
   276  		if hash != hh {
   277  			time.Sleep(time.Millisecond * 100)
   278  			continue
   279  		}
   280  
   281  		// we expect no next block
   282  		_, err = h.Chain.GetBlock("", height+1)
   283  		if err != nil {
   284  			if err != bchain.ErrBlockNotFound {
   285  				t.Error(err)
   286  			}
   287  			return
   288  		}
   289  	}
   290  	t.Error("GetBestBlockHash() didn't get the best hash")
   291  }
   292  func testGetBestBlockHeight(t *testing.T, h *TestHandler) {
   293  	for i := 0; i < 3; i++ {
   294  		height, err := h.Chain.GetBestBlockHeight()
   295  		if err != nil {
   296  			t.Fatal(err)
   297  		}
   298  
   299  		// we expect no next block
   300  		_, err = h.Chain.GetBlock("", height+1)
   301  		if err != nil {
   302  			if err != bchain.ErrBlockNotFound {
   303  				t.Error(err)
   304  			}
   305  			return
   306  		}
   307  	}
   308  	t.Error("GetBestBlockHeigh() didn't get the the best heigh")
   309  }
   310  func testGetBlockHeader(t *testing.T, h *TestHandler) {
   311  	want := &bchain.BlockHeader{
   312  		Hash:   h.TestData.BlockHash,
   313  		Height: h.TestData.BlockHeight,
   314  		Time:   h.TestData.BlockTime,
   315  	}
   316  
   317  	got, err := h.Chain.GetBlockHeader(h.TestData.BlockHash)
   318  	if err != nil {
   319  		t.Fatal(err)
   320  	}
   321  
   322  	// Confirmations is variable field, we just check if is set and reset it
   323  	if got.Confirmations <= 0 {
   324  		t.Fatalf("GetBlockHeader() got struct with invalid Confirmations field")
   325  	}
   326  	got.Confirmations = 0
   327  
   328  	got.Prev, got.Next = "", ""
   329  
   330  	if !reflect.DeepEqual(got, want) {
   331  		t.Errorf("GetBlockHeader() got=%+v, want=%+v", got, want)
   332  	}
   333  }
   334  
   335  func getMempool(t *testing.T, h *TestHandler) []string {
   336  	txs, err := h.Chain.GetMempool()
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  	if len(txs) == 0 {
   341  		t.Skip("Skipping test, mempool is empty")
   342  	}
   343  
   344  	return txs
   345  }
   346  
   347  func getTxid2addrs(t *testing.T, h *TestHandler, txs []string) map[string][]string {
   348  	txid2addrs := map[string][]string{}
   349  	for i := range txs {
   350  		tx, err := h.Chain.GetTransactionForMempool(txs[i])
   351  		if err != nil {
   352  			if isMissingTx(err) {
   353  				continue
   354  			}
   355  			t.Fatal(err)
   356  		}
   357  		setTxAddresses(h.Chain.GetChainParser(), tx)
   358  		addrs := []string{}
   359  		for j := range tx.Vout {
   360  			for _, a := range tx.Vout[j].ScriptPubKey.Addresses {
   361  				addrs = append(addrs, a)
   362  			}
   363  		}
   364  		if len(addrs) > 0 {
   365  			txid2addrs[tx.Txid] = addrs
   366  		}
   367  	}
   368  	return txid2addrs
   369  }
   370  
   371  func isMissingTx(err error) bool {
   372  	switch e1 := err.(type) {
   373  	case *errors.Err:
   374  		switch e2 := e1.Cause().(type) {
   375  		case *bchain.RPCError:
   376  			if e2.Code == -5 { // "No such mempool or blockchain transaction"
   377  				return true
   378  			}
   379  		}
   380  	}
   381  
   382  	return false
   383  }
   384  
   385  func intersect(a, b []string) []string {
   386  	setA := mapset.NewSet()
   387  	for _, v := range a {
   388  		setA.Add(v)
   389  	}
   390  	setB := mapset.NewSet()
   391  	for _, v := range b {
   392  		setB.Add(v)
   393  	}
   394  	inter := setA.Intersect(setB)
   395  	res := make([]string, 0, inter.Cardinality())
   396  	for v := range inter.Iter() {
   397  		res = append(res, v.(string))
   398  	}
   399  	return res
   400  }
   401  
   402  func containsString(slice []string, s string) bool {
   403  	for i := range slice {
   404  		if slice[i] == s {
   405  			return true
   406  		}
   407  	}
   408  	return false
   409  }