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