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 }