From 575452331721b4ba4f102d17866311ac3783472b Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Thu, 20 Sep 2018 09:39:13 +0200 Subject: [PATCH] WIP: sync integration tests --- bchain/tests/rpc/data.go | 74 ---- configs/coins/bcash.json | 4 - configs/coins/bcash_testnet.json | 4 - configs/coins/bitcoin.json | 4 - configs/coins/bitcoin_testnet.json | 4 - configs/coins/dash.json | 4 - configs/coins/dash_testnet.json | 4 - configs/coins/dogecoin.json | 3 - configs/coins/ethereum_testnet_ropsten.json | 3 - configs/coins/litecoin.json | 4 - configs/coins/monacoin.json | 4 - configs/coins/namecoin.json | 4 - configs/coins/vertcoin.json | 4 - configs/coins/zcash.json | 4 - configs/coins/zcash_testnet.json | 4 - contrib/scripts/start-backend-tunnels.sh | 23 +- tests/integration.go | 157 +++++++ tests/integration_test.go | 11 + .../tests/rpc/rpc_test.go => tests/rpc/rpc.go | 231 +++------- .../tests => tests}/rpc/testdata/bcash.json | 0 .../rpc/testdata/bcash_testnet.json | 0 .../tests => tests}/rpc/testdata/bitcoin.json | 0 .../rpc/testdata/bitcoin_testnet.json | 0 .../tests => tests}/rpc/testdata/dash.json | 0 .../rpc/testdata/dash_testnet.json | 0 .../rpc/testdata/dogecoin.json | 0 .../testdata/ethereum_testnet_ropsten.json | 0 .../rpc/testdata/litecoin.json | 0 .../rpc/testdata/monacoin.json | 0 .../rpc/testdata/namecoin.json | 0 .../rpc/testdata/vertcoin.json | 0 .../tests => tests}/rpc/testdata/zcash.json | 0 .../rpc/testdata/zcash_testnet.json | 0 tests/sync/sync.go | 404 ++++++++++++++++++ tests/sync/testdata/bitcoin.json | 310 ++++++++++++++ tests/tests.json | 58 +++ 36 files changed, 1025 insertions(+), 297 deletions(-) delete mode 100644 bchain/tests/rpc/data.go create mode 100644 tests/integration.go create mode 100644 tests/integration_test.go rename bchain/tests/rpc/rpc_test.go => tests/rpc/rpc.go (63%) rename {bchain/tests => tests}/rpc/testdata/bcash.json (100%) rename {bchain/tests => tests}/rpc/testdata/bcash_testnet.json (100%) rename {bchain/tests => tests}/rpc/testdata/bitcoin.json (100%) rename {bchain/tests => tests}/rpc/testdata/bitcoin_testnet.json (100%) rename {bchain/tests => tests}/rpc/testdata/dash.json (100%) rename {bchain/tests => tests}/rpc/testdata/dash_testnet.json (100%) rename {bchain/tests => tests}/rpc/testdata/dogecoin.json (100%) rename {bchain/tests => tests}/rpc/testdata/ethereum_testnet_ropsten.json (100%) rename {bchain/tests => tests}/rpc/testdata/litecoin.json (100%) rename {bchain/tests => tests}/rpc/testdata/monacoin.json (100%) rename {bchain/tests => tests}/rpc/testdata/namecoin.json (100%) rename {bchain/tests => tests}/rpc/testdata/vertcoin.json (100%) rename {bchain/tests => tests}/rpc/testdata/zcash.json (100%) rename {bchain/tests => tests}/rpc/testdata/zcash_testnet.json (100%) create mode 100644 tests/sync/sync.go create mode 100644 tests/sync/testdata/bitcoin.json create mode 100644 tests/tests.json diff --git a/bchain/tests/rpc/data.go b/bchain/tests/rpc/data.go deleted file mode 100644 index dbe8c1f8..00000000 --- a/bchain/tests/rpc/data.go +++ /dev/null @@ -1,74 +0,0 @@ -package rpc - -import ( - "blockbook/bchain" - "encoding/json" - "errors" - "io/ioutil" - "path/filepath" - "strings" -) - -type TestData struct { - BlockHeight uint32 `json:"blockHeight"` - BlockHash string `json:"blockHash"` - BlockTime int64 `json:"blockTime"` - BlockTxs []string `json:"blockTxs"` - TxDetails map[string]*bchain.Tx `json:"txDetails"` -} - -func joinPathsWithCommonElement(p1, p2 string) (string, bool) { - idx := strings.IndexRune(p2, filepath.Separator) - if idx <= 0 { - return "", false - } - p2root := p2[:idx] - idx = strings.LastIndex(p1, p2root) - if idx < 0 { - return "", false - } - prefix := p1[:idx] - return filepath.Join(prefix, p2), true -} - -func readDataFile(dir, relDir, filename string) ([]byte, error) { - var err error - dir, err = filepath.Abs(dir) - if err == nil { - dir, err = filepath.EvalSymlinks(dir) - } - if err != nil { - return nil, err - } - path, ok := joinPathsWithCommonElement(dir, relDir) - if !ok { - return nil, errors.New("Path not found") - } - filename = strings.Replace(filename, " ", "_", -1) - path = filepath.Join(path, filename) - return ioutil.ReadFile(path) -} - -func LoadTestData(coin string, parser bchain.BlockChainParser) (*TestData, error) { - b, err := readDataFile(".", "bchain/tests/rpc/testdata", coin+".json") - if err != nil { - return nil, err - } - var v TestData - err = json.Unmarshal(b, &v) - if err != nil { - return nil, err - } - // convert amounts in test json to bit.Int and clear the temporary JsonValue - for _, tx := range v.TxDetails { - for i := range tx.Vout { - vout := &tx.Vout[i] - vout.ValueSat, err = parser.AmountToBigInt(vout.JsonValue) - if err != nil { - return nil, err - } - vout.JsonValue = "" - } - } - return &v, nil -} diff --git a/configs/coins/bcash.json b/configs/coins/bcash.json index 69035ab0..78c74336 100644 --- a/configs/coins/bcash.json +++ b/configs/coins/bcash.json @@ -59,9 +59,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/bcash_testnet.json b/configs/coins/bcash_testnet.json index 462430ea..ad6bda08 100644 --- a/configs/coins/bcash_testnet.json +++ b/configs/coins/bcash_testnet.json @@ -59,9 +59,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/bitcoin.json b/configs/coins/bitcoin.json index 76ee0ead..ec93e446 100644 --- a/configs/coins/bitcoin.json +++ b/configs/coins/bitcoin.json @@ -60,9 +60,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/bitcoin_testnet.json b/configs/coins/bitcoin_testnet.json index 7d76c723..760163be 100644 --- a/configs/coins/bitcoin_testnet.json +++ b/configs/coins/bitcoin_testnet.json @@ -60,9 +60,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/dash.json b/configs/coins/dash.json index 9da990c2..d2895671 100644 --- a/configs/coins/dash.json +++ b/configs/coins/dash.json @@ -61,9 +61,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/dash_testnet.json b/configs/coins/dash_testnet.json index da73d850..38407e37 100644 --- a/configs/coins/dash_testnet.json +++ b/configs/coins/dash_testnet.json @@ -61,9 +61,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/dogecoin.json b/configs/coins/dogecoin.json index a04bda04..cb9b7a68 100644 --- a/configs/coins/dogecoin.json +++ b/configs/coins/dogecoin.json @@ -63,8 +63,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync"] } } diff --git a/configs/coins/ethereum_testnet_ropsten.json b/configs/coins/ethereum_testnet_ropsten.json index 6d567f80..a236e16d 100644 --- a/configs/coins/ethereum_testnet_ropsten.json +++ b/configs/coins/ethereum_testnet_ropsten.json @@ -53,8 +53,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/litecoin.json b/configs/coins/litecoin.json index 8bd0e15e..e4541bf9 100644 --- a/configs/coins/litecoin.json +++ b/configs/coins/litecoin.json @@ -60,9 +60,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee"] } } diff --git a/configs/coins/monacoin.json b/configs/coins/monacoin.json index 1a1040a7..563b1db6 100644 --- a/configs/coins/monacoin.json +++ b/configs/coins/monacoin.json @@ -60,9 +60,5 @@ "meta": { "package_maintainer": "wakiyamap", "package_maintainer_email": "wakiyamap@gmail.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee"] } } diff --git a/configs/coins/namecoin.json b/configs/coins/namecoin.json index 5514625d..754ee628 100644 --- a/configs/coins/namecoin.json +++ b/configs/coins/namecoin.json @@ -67,9 +67,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/vertcoin.json b/configs/coins/vertcoin.json index 5669577a..d68a0a0f 100644 --- a/configs/coins/vertcoin.json +++ b/configs/coins/vertcoin.json @@ -58,9 +58,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/zcash.json b/configs/coins/zcash.json index 539029ac..ab65b246 100644 --- a/configs/coins/zcash.json +++ b/configs/coins/zcash.json @@ -60,9 +60,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/configs/coins/zcash_testnet.json b/configs/coins/zcash_testnet.json index c34270ea..c2793c96 100644 --- a/configs/coins/zcash_testnet.json +++ b/configs/coins/zcash_testnet.json @@ -60,9 +60,5 @@ "meta": { "package_maintainer": "Jakub Matys", "package_maintainer_email": "jakub.matys@satoshilabs.com" - }, - "integration_tests": { - "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", - "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] } } diff --git a/contrib/scripts/start-backend-tunnels.sh b/contrib/scripts/start-backend-tunnels.sh index 3f77437e..aca4eae1 100755 --- a/contrib/scripts/start-backend-tunnels.sh +++ b/contrib/scripts/start-backend-tunnels.sh @@ -7,13 +7,19 @@ fi host=$1 +get_port() { + data=$1 + key=$2 + echo "${data}" | gawk "match(\$0, /\"${key}\":\s+([0-9]+)/, a) {print a[1]}" - +} + # change dir to root of git repository cd $(cd $(dirname $(readlink -f $0)) && git rev-parse --show-toplevel) # get all testnet ports from configs/ -testnet_ports=$(gawk 'match($0, /"rpcURL":\s+"(http|ws):\/\/[^:]+:([0-9]+)"/, a) {print a[2]}' configs/*_testnet*.json) +ports=$(gawk 'match($0, /"backend_rpc":\s+([0-9]+)/, a) {print a[1]}' configs/coins/*.json) -for port in $testnet_ports +for port in $ports do ssh -nNT -L $port:localhost:$port $host & pid=$! @@ -26,9 +32,10 @@ at_exit() { trap at_exit EXIT -wait -n -code=$? - -if [ $code != 0 ]; then - exit $code -fi +sleep inf +# wait -n +# code=$? +# +# if [ $code != 0 ]; then +# exit $code +# fi diff --git a/tests/integration.go b/tests/integration.go new file mode 100644 index 00000000..f057a5f3 --- /dev/null +++ b/tests/integration.go @@ -0,0 +1,157 @@ +package tests + +import ( + "blockbook/bchain" + "blockbook/bchain/coins" + "blockbook/build/tools" + "blockbook/tests/rpc" + "blockbook/tests/sync" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "os" + "path/filepath" + "reflect" + "testing" +) + +type TestFunc func(t *testing.T, coin string, chain bchain.BlockChain, testConfig json.RawMessage) + +var integrationTests = map[string]TestFunc{ + "rpc": rpc.IntegrationTest, + "sync": sync.IntegrationTest, +} + +var notConnectedError = errors.New("Not connected to backend server") + +func runIntegrationTests(t *testing.T) { + tests, err := loadTests("tests.json") + if err != nil { + t.Fatal(err) + } + + for coin, cfg := range tests { + t.Run(coin, func(t *testing.T) { runTests(t, coin, cfg) }) + + } +} + +func loadTests(path string) (map[string]map[string]json.RawMessage, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + v := make(map[string]map[string]json.RawMessage) + err = json.Unmarshal(b, &v) + return v, err +} + +func runTests(t *testing.T, coin string, cfg map[string]json.RawMessage) { + if cfg == nil || len(cfg) == 0 { + t.Skip("No tests to run") + } + + bc, err := makeBlockChain(coin) + if err != nil { + if err == notConnectedError { + t.Fatal(err) + } + t.Fatalf("Cannot make blockchain config: %s", err) + } + + for test, c := range cfg { + if fn, found := integrationTests[test]; found { + t.Run(test, func(t *testing.T) { fn(t, coin, bc, c) }) + } else { + t.Errorf("Test not found: %s", test) + } + } +} + +func makeBlockChain(coin string) (bchain.BlockChain, error) { + c, err := build.LoadConfig("../configs", coin) + if err != nil { + return nil, err + } + + outputDir, err := ioutil.TempDir("", "integration_test") + if err != nil { + return nil, err + } + defer os.RemoveAll(outputDir) + + err = build.GeneratePackageDefinitions(c, "../build/templates", outputDir) + if err != nil { + return nil, err + } + + b, err := ioutil.ReadFile(filepath.Join(outputDir, "blockbook", "blockchaincfg.json")) + if err != nil { + return nil, err + } + + var cfg json.RawMessage + err = json.Unmarshal(b, &cfg) + if err != nil { + return nil, err + } + + coinName, err := getName(cfg) + if err != nil { + return nil, err + } + + return initBlockChain(coinName, cfg) +} + +func getName(raw json.RawMessage) (string, error) { + var cfg map[string]interface{} + err := json.Unmarshal(raw, &cfg) + if err != nil { + return "", err + } + if n, found := cfg["coin_name"]; found { + switch n := n.(type) { + case string: + return n, nil + default: + return "", fmt.Errorf("Unexpected type of field `name`: %s", reflect.TypeOf(n)) + } + } else { + return "", errors.New("Missing field `name`") + } +} + +func initBlockChain(coinName string, cfg json.RawMessage) (bchain.BlockChain, error) { + factory, found := coins.BlockChainFactories[coinName] + if !found { + return nil, fmt.Errorf("Factory function not found") + } + + cli, err := factory(cfg, func(_ bchain.NotificationType) {}) + if err != nil { + if isNetError(err) { + return nil, notConnectedError + } + return nil, fmt.Errorf("Factory function failed: %s", err) + } + + err = cli.Initialize() + if err != nil { + if isNetError(err) { + return nil, notConnectedError + } + return nil, fmt.Errorf("BlockChain initialization failed: %s", err) + } + + return cli, nil +} + +func isNetError(err error) bool { + if _, ok := err.(net.Error); ok { + return true + } + return false +} diff --git a/tests/integration_test.go b/tests/integration_test.go new file mode 100644 index 00000000..2e0e385a --- /dev/null +++ b/tests/integration_test.go @@ -0,0 +1,11 @@ +// +build integration + +package tests + +import ( + "testing" +) + +func TestIntegration(t *testing.T) { + runIntegrationTests(t) +} diff --git a/bchain/tests/rpc/rpc_test.go b/tests/rpc/rpc.go similarity index 63% rename from bchain/tests/rpc/rpc_test.go rename to tests/rpc/rpc.go index b5658846..ecca3b74 100644 --- a/bchain/tests/rpc/rpc_test.go +++ b/tests/rpc/rpc.go @@ -1,20 +1,12 @@ -// +build integration - package rpc import ( "blockbook/bchain" - "blockbook/bchain/coins" - "blockbook/build/tools" "encoding/json" "errors" - "fmt" "io/ioutil" - "net" - "os" "path/filepath" "reflect" - "strings" "testing" "time" @@ -35,124 +27,31 @@ var testMap = map[string]func(t *testing.T, th *TestHandler){ } type TestHandler struct { - Client bchain.BlockChain - TestData *TestData - connected bool + Chain bchain.BlockChain + TestData *TestData } -var notConnectedError = errors.New("Not connected to backend server") - -func TestRPCIntegration(t *testing.T) { - src := os.Getenv("BLOCKBOOK_SRC") - if src == "" { - t.Fatalf("Missing environment variable BLOCKBOOK_SRC") - } - - configsDir := filepath.Join(src, "configs") - templateDir := filepath.Join(src, "build/templates") - - noTests := 0 - skippedTests := make([]string, 0, 10) - - err := filepath.Walk(filepath.Join(configsDir, "coins"), func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() || info.Name()[0] == '.' { - return nil - } - - n := strings.TrimSuffix(info.Name(), ".json") - c, err := build.LoadConfig(configsDir, n) - if err != nil { - t.Errorf("%s: cannot load configuration: %s", n, err) - return nil - } - if len(c.IntegrationTests["rpc"]) == 0 { - return nil - } - - cfg, err := makeBlockChainConfig(c, templateDir) - if err != nil { - t.Errorf("%s: cannot make blockchain config: %s", n, err) - return nil - } - - t.Run(c.Coin.Alias, func(t *testing.T) { - noTests += 1 - err := runTests(t, c.Coin.Name, c.Coin.Alias, cfg, c.IntegrationTests["rpc"]) - if err != nil { - if err == notConnectedError { - skippedTests = append(skippedTests, c.Coin.Alias) - t.Skip(err) - } - t.Fatal(err) - } - }) - - return nil - }) - - if err != nil { - t.Fatal(err) - } - - if len(skippedTests) > 0 { - t.Errorf("Too many skipped tests due to connection issues: %q", skippedTests) - } +type TestData struct { + BlockHeight uint32 `json:"blockHeight"` + BlockHash string `json:"blockHash"` + BlockTime int64 `json:"blockTime"` + BlockTxs []string `json:"blockTxs"` + TxDetails map[string]*bchain.Tx `json:"txDetails"` } -func makeBlockChainConfig(c *build.Config, templateDir string) (json.RawMessage, error) { - outputDir, err := ioutil.TempDir("", "rpc_test") +func IntegrationTest(t *testing.T, coin string, chain bchain.BlockChain, testConfig json.RawMessage) { + tests, err := getTests(testConfig) if err != nil { - return nil, err + t.Fatalf("Failed loading of test list: %s", err) } - defer os.RemoveAll(outputDir) - err = build.GeneratePackageDefinitions(c, templateDir, outputDir) + parser := chain.GetChainParser() + td, err := loadTestData(coin, parser) if err != nil { - return nil, err + t.Fatalf("Failed loading of test data: %s", err) } - b, err := ioutil.ReadFile(filepath.Join(outputDir, "blockbook", "blockchaincfg.json")) - if err != nil { - return nil, err - } - - var v json.RawMessage - err = json.Unmarshal(b, &v) - if err != nil { - return nil, err - } - - return v, nil -} - -func runTests(t *testing.T, coinName, coinAlias string, cfg json.RawMessage, tests []string) error { - cli, err := initBlockChain(coinName, cfg) - if err != nil { - if err == notConnectedError { - return err - } - t.Fatal(err) - } - td, err := LoadTestData(coinAlias, cli.GetChainParser()) - if err != nil { - t.Fatalf("Test data loading failed: %s", err) - } - - if td.TxDetails != nil { - parser := cli.GetChainParser() - - for _, tx := range td.TxDetails { - err := setTxAddresses(parser, tx) - if err != nil { - t.Fatalf("Test data loading failed: %s", err) - } - } - } - - h := TestHandler{Client: cli, TestData: td} + h := TestHandler{Chain: chain, TestData: td} for _, test := range tests { if f, found := testMap[test]; found { @@ -162,40 +61,50 @@ func runTests(t *testing.T, coinName, coinAlias string, cfg json.RawMessage, tes continue } } - - return nil } -func initBlockChain(coinName string, cfg json.RawMessage) (bchain.BlockChain, error) { - factory, found := coins.BlockChainFactories[coinName] - if !found { - return nil, fmt.Errorf("Factory function not found") - } - - cli, err := factory(cfg, func(_ bchain.NotificationType) {}) +func getTests(cfg json.RawMessage) ([]string, error) { + var v []string + err := json.Unmarshal(cfg, &v) if err != nil { - if isNetError(err) { - return nil, notConnectedError - } - return nil, fmt.Errorf("Factory function failed: %s", err) + return nil, err } - - err = cli.Initialize() - if err != nil { - if isNetError(err) { - return nil, notConnectedError - } - return nil, fmt.Errorf("BlockChain initialization failed: %s", err) + if len(v) == 0 { + return nil, errors.New("No tests declared") } - - return cli, nil + return v, nil } -func isNetError(err error) bool { - if _, ok := err.(net.Error); ok { - return true +func loadTestData(coin string, parser bchain.BlockChainParser) (*TestData, error) { + path := filepath.Join("rpc/testdata", coin+".json") + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err } - return false + var v TestData + err = json.Unmarshal(b, &v) + if err != nil { + return nil, err + } + for _, tx := range v.TxDetails { + // convert amounts in test json to bit.Int and clear the temporary JsonValue + for i := range tx.Vout { + vout := &tx.Vout[i] + vout.ValueSat, err = parser.AmountToBigInt(vout.JsonValue) + if err != nil { + return nil, err + } + vout.JsonValue = "" + } + + // get addresses parsed + err := setTxAddresses(parser, tx) + if err != nil { + return nil, err + } + } + + return &v, nil } func setTxAddresses(parser bchain.BlockChainParser, tx *bchain.Tx) error { @@ -214,7 +123,7 @@ func setTxAddresses(parser bchain.BlockChainParser, tx *bchain.Tx) error { } func testGetBlockHash(t *testing.T, h *TestHandler) { - hash, err := h.Client.GetBlockHash(h.TestData.BlockHeight) + hash, err := h.Chain.GetBlockHash(h.TestData.BlockHeight) if err != nil { t.Error(err) return @@ -225,7 +134,7 @@ func testGetBlockHash(t *testing.T, h *TestHandler) { } } func testGetBlock(t *testing.T, h *TestHandler) { - blk, err := h.Client.GetBlock(h.TestData.BlockHash, 0) + blk, err := h.Chain.GetBlock(h.TestData.BlockHash, 0) if err != nil { t.Error(err) return @@ -243,7 +152,7 @@ func testGetBlock(t *testing.T, h *TestHandler) { } func testGetTransaction(t *testing.T, h *TestHandler) { for txid, want := range h.TestData.TxDetails { - got, err := h.Client.GetTransaction(txid) + got, err := h.Chain.GetTransaction(txid) if err != nil { t.Error(err) return @@ -265,7 +174,7 @@ func testGetTransactionForMempool(t *testing.T, h *TestHandler) { // reset fields that are not parsed by BlockChainParser want.Confirmations, want.Blocktime, want.Time = 0, 0, 0 - got, err := h.Client.GetTransactionForMempool(txid) + got, err := h.Chain.GetTransactionForMempool(txid) if err != nil { t.Fatal(err) } @@ -280,7 +189,7 @@ func testMempoolSync(t *testing.T, h *TestHandler) { for i := 0; i < 3; i++ { txs := getMempool(t, h) - n, err := h.Client.ResyncMempool(nil) + n, err := h.Chain.ResyncMempool(nil) if err != nil { t.Fatal(err) } @@ -302,7 +211,7 @@ func testMempoolSync(t *testing.T, h *TestHandler) { for txid, addrs := range txid2addrs { for _, a := range addrs { - got, err := h.Client.GetMempoolTransactions(a) + got, err := h.Chain.GetMempoolTransactions(a) if err != nil { t.Fatal(err) } @@ -320,12 +229,12 @@ func testMempoolSync(t *testing.T, h *TestHandler) { } func testEstimateSmartFee(t *testing.T, h *TestHandler) { for _, blocks := range []int{1, 2, 3, 5, 10} { - fee, err := h.Client.EstimateSmartFee(blocks, true) + fee, err := h.Chain.EstimateSmartFee(blocks, true) if err != nil { t.Error(err) } if fee.Sign() == -1 { - sf := h.Client.GetChainParser().AmountToDecimalString(&fee) + sf := h.Chain.GetChainParser().AmountToDecimalString(&fee) if sf != "-1" { t.Errorf("EstimateSmartFee() returned unexpected fee rate: %v", sf) } @@ -334,12 +243,12 @@ func testEstimateSmartFee(t *testing.T, h *TestHandler) { } func testEstimateFee(t *testing.T, h *TestHandler) { for _, blocks := range []int{1, 2, 3, 5, 10} { - fee, err := h.Client.EstimateFee(blocks) + fee, err := h.Chain.EstimateFee(blocks) if err != nil { t.Error(err) } if fee.Sign() == -1 { - sf := h.Client.GetChainParser().AmountToDecimalString(&fee) + sf := h.Chain.GetChainParser().AmountToDecimalString(&fee) if sf != "-1" { t.Errorf("EstimateFee() returned unexpected fee rate: %v", sf) } @@ -348,16 +257,16 @@ func testEstimateFee(t *testing.T, h *TestHandler) { } func testGetBestBlockHash(t *testing.T, h *TestHandler) { for i := 0; i < 3; i++ { - hash, err := h.Client.GetBestBlockHash() + hash, err := h.Chain.GetBestBlockHash() if err != nil { t.Fatal(err) } - height, err := h.Client.GetBestBlockHeight() + height, err := h.Chain.GetBestBlockHeight() if err != nil { t.Fatal(err) } - hh, err := h.Client.GetBlockHash(height) + hh, err := h.Chain.GetBlockHash(height) if err != nil { t.Fatal(err) } @@ -367,7 +276,7 @@ func testGetBestBlockHash(t *testing.T, h *TestHandler) { } // we expect no next block - _, err = h.Client.GetBlock("", height+1) + _, err = h.Chain.GetBlock("", height+1) if err != nil { if err != bchain.ErrBlockNotFound { t.Error(err) @@ -379,13 +288,13 @@ func testGetBestBlockHash(t *testing.T, h *TestHandler) { } func testGetBestBlockHeight(t *testing.T, h *TestHandler) { for i := 0; i < 3; i++ { - height, err := h.Client.GetBestBlockHeight() + height, err := h.Chain.GetBestBlockHeight() if err != nil { t.Fatal(err) } // we expect no next block - _, err = h.Client.GetBlock("", height+1) + _, err = h.Chain.GetBlock("", height+1) if err != nil { if err != bchain.ErrBlockNotFound { t.Error(err) @@ -402,7 +311,7 @@ func testGetBlockHeader(t *testing.T, h *TestHandler) { Time: h.TestData.BlockTime, } - got, err := h.Client.GetBlockHeader(h.TestData.BlockHash) + got, err := h.Chain.GetBlockHeader(h.TestData.BlockHash) if err != nil { t.Fatal(err) } @@ -421,7 +330,7 @@ func testGetBlockHeader(t *testing.T, h *TestHandler) { } func getMempool(t *testing.T, h *TestHandler) []string { - txs, err := h.Client.GetMempool() + txs, err := h.Chain.GetMempool() if err != nil { t.Fatal(err) } @@ -435,7 +344,7 @@ func getMempool(t *testing.T, h *TestHandler) []string { func getMempoolAddresses(t *testing.T, h *TestHandler, txs []string) map[string][]string { txid2addrs := map[string][]string{} for i := 0; i < len(txs); i++ { - tx, err := h.Client.GetTransactionForMempool(txs[i]) + tx, err := h.Chain.GetTransactionForMempool(txs[i]) if err != nil { t.Fatal(err) } diff --git a/bchain/tests/rpc/testdata/bcash.json b/tests/rpc/testdata/bcash.json similarity index 100% rename from bchain/tests/rpc/testdata/bcash.json rename to tests/rpc/testdata/bcash.json diff --git a/bchain/tests/rpc/testdata/bcash_testnet.json b/tests/rpc/testdata/bcash_testnet.json similarity index 100% rename from bchain/tests/rpc/testdata/bcash_testnet.json rename to tests/rpc/testdata/bcash_testnet.json diff --git a/bchain/tests/rpc/testdata/bitcoin.json b/tests/rpc/testdata/bitcoin.json similarity index 100% rename from bchain/tests/rpc/testdata/bitcoin.json rename to tests/rpc/testdata/bitcoin.json diff --git a/bchain/tests/rpc/testdata/bitcoin_testnet.json b/tests/rpc/testdata/bitcoin_testnet.json similarity index 100% rename from bchain/tests/rpc/testdata/bitcoin_testnet.json rename to tests/rpc/testdata/bitcoin_testnet.json diff --git a/bchain/tests/rpc/testdata/dash.json b/tests/rpc/testdata/dash.json similarity index 100% rename from bchain/tests/rpc/testdata/dash.json rename to tests/rpc/testdata/dash.json diff --git a/bchain/tests/rpc/testdata/dash_testnet.json b/tests/rpc/testdata/dash_testnet.json similarity index 100% rename from bchain/tests/rpc/testdata/dash_testnet.json rename to tests/rpc/testdata/dash_testnet.json diff --git a/bchain/tests/rpc/testdata/dogecoin.json b/tests/rpc/testdata/dogecoin.json similarity index 100% rename from bchain/tests/rpc/testdata/dogecoin.json rename to tests/rpc/testdata/dogecoin.json diff --git a/bchain/tests/rpc/testdata/ethereum_testnet_ropsten.json b/tests/rpc/testdata/ethereum_testnet_ropsten.json similarity index 100% rename from bchain/tests/rpc/testdata/ethereum_testnet_ropsten.json rename to tests/rpc/testdata/ethereum_testnet_ropsten.json diff --git a/bchain/tests/rpc/testdata/litecoin.json b/tests/rpc/testdata/litecoin.json similarity index 100% rename from bchain/tests/rpc/testdata/litecoin.json rename to tests/rpc/testdata/litecoin.json diff --git a/bchain/tests/rpc/testdata/monacoin.json b/tests/rpc/testdata/monacoin.json similarity index 100% rename from bchain/tests/rpc/testdata/monacoin.json rename to tests/rpc/testdata/monacoin.json diff --git a/bchain/tests/rpc/testdata/namecoin.json b/tests/rpc/testdata/namecoin.json similarity index 100% rename from bchain/tests/rpc/testdata/namecoin.json rename to tests/rpc/testdata/namecoin.json diff --git a/bchain/tests/rpc/testdata/vertcoin.json b/tests/rpc/testdata/vertcoin.json similarity index 100% rename from bchain/tests/rpc/testdata/vertcoin.json rename to tests/rpc/testdata/vertcoin.json diff --git a/bchain/tests/rpc/testdata/zcash.json b/tests/rpc/testdata/zcash.json similarity index 100% rename from bchain/tests/rpc/testdata/zcash.json rename to tests/rpc/testdata/zcash.json diff --git a/bchain/tests/rpc/testdata/zcash_testnet.json b/tests/rpc/testdata/zcash_testnet.json similarity index 100% rename from bchain/tests/rpc/testdata/zcash_testnet.json rename to tests/rpc/testdata/zcash_testnet.json diff --git a/tests/sync/sync.go b/tests/sync/sync.go new file mode 100644 index 00000000..5a8e56ed --- /dev/null +++ b/tests/sync/sync.go @@ -0,0 +1,404 @@ +package sync + +import ( + "blockbook/bchain" + "blockbook/common" + "blockbook/db" + "encoding/json" + "errors" + "io/ioutil" + "math/big" + "os" + "path/filepath" + "reflect" + "sort" + "testing" +) + +var testMap = map[string]func(t *testing.T, th *TestHandler){ + // "ConnectBlocks": nil, + "ConnectBlocksParallel": testConnectBlocksParallel, + // "DisconnectBlocks": nil, +} + +type TestHandler struct { + Coin string + Chain bchain.BlockChain + TestData *TestData +} + +type TestData struct { + ConnectBlocksParallel struct { + SyncWorkers int `json:"syncWorkers"` + SyncChunk int `json:"syncChunk"` + } `json:"connectBlocksParallel"` + Blocks []BlockInfo `json:"blocks"` +} + +type BlockInfo struct { + Height uint32 `json:"height"` + Hash string `json:"hash"` + NoTxs uint32 `json:"noTxs"` + TxDetails []*bchain.Tx `json:"txDetails"` +} + +func IntegrationTest(t *testing.T, coin string, chain bchain.BlockChain, testConfig json.RawMessage) { + tests, err := getTests(testConfig) + if err != nil { + t.Fatalf("Failed loading of test list: %s", err) + } + + parser := chain.GetChainParser() + td, err := loadTestData(coin, parser) + if err != nil { + t.Fatalf("Failed loading of test data: %s", err) + } + + h := TestHandler{Coin: coin, Chain: chain, TestData: td} + + for _, test := range tests { + if f, found := testMap[test]; found { + t.Run(test, func(t *testing.T) { f(t, &h) }) + } else { + t.Errorf("%s: test not found", test) + continue + } + } +} + +func getTests(cfg json.RawMessage) ([]string, error) { + var v []string + err := json.Unmarshal(cfg, &v) + if err != nil { + return nil, err + } + if len(v) == 0 { + return nil, errors.New("No tests declared") + } + return v, nil +} + +func loadTestData(coin string, parser bchain.BlockChainParser) (*TestData, error) { + path := filepath.Join("sync/testdata", coin+".json") + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + var v TestData + err = json.Unmarshal(b, &v) + if err != nil { + return nil, err + } + + for _, b := range v.Blocks { + for _, tx := range b.TxDetails { + // convert amounts in test json to bit.Int and clear the temporary JsonValue + for i := range tx.Vout { + vout := &tx.Vout[i] + vout.ValueSat, err = parser.AmountToBigInt(vout.JsonValue) + if err != nil { + return nil, err + } + vout.JsonValue = "" + } + + // get addresses parsed + err := setTxAddresses(parser, tx) + if err != nil { + return nil, err + } + } + } + + sort.Slice(v.Blocks, func(i, j int) bool { + return v.Blocks[i].Height < v.Blocks[j].Height + }) + + return &v, nil +} + +func setTxAddresses(parser bchain.BlockChainParser, tx *bchain.Tx) error { + // pack and unpack transaction in order to get addresses decoded - ugly but works + var tmp *bchain.Tx + b, err := parser.PackTx(tx, 0, 0) + if err == nil { + tmp, _, err = parser.UnpackTx(b) + if err == nil { + for i := 0; i < len(tx.Vout); i++ { + tx.Vout[i].ScriptPubKey.Addresses = tmp.Vout[i].ScriptPubKey.Addresses + } + } + } + return err +} + +func makeRocksDB(parser bchain.BlockChainParser, m *common.Metrics, is *common.InternalState) (*db.RocksDB, func(), error) { + p, err := ioutil.TempDir("", "sync_test") + if err != nil { + return nil, nil, err + } + + d, err := db.NewRocksDB(p, 100000, parser, m) + if err != nil { + return nil, nil, err + } + + d.SetInternalState(is) + + closer := func() { + d.Close() + os.RemoveAll(p) + } + + return d, closer, nil +} + +func withRocksDBAndSyncWorker(t *testing.T, h *TestHandler, fn func(*db.RocksDB, *db.SyncWorker, chan os.Signal)) { + m, err := common.GetMetrics(h.Coin) + if err != nil { + t.Fatal(err) + } + is := &common.InternalState{} + + d, closer, err := makeRocksDB(h.Chain.GetChainParser(), m, is) + if err != nil { + t.Fatal(err) + } + defer closer() + + if len(h.TestData.Blocks) == 0 { + t.Fatal("No test data") + } + + ch := make(chan os.Signal) + + sw, err := db.NewSyncWorker(d, h.Chain, 3, 0, int(h.TestData.Blocks[0].Height), false, ch, m, is) + if err != nil { + t.Fatal(err) + } + + fn(d, sw, ch) +} + +func testConnectBlocksParallel(t *testing.T, h *TestHandler) { + withRocksDBAndSyncWorker(t, h, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) { + lowerHeight := h.TestData.Blocks[0].Height + upperHeight := h.TestData.Blocks[len(h.TestData.Blocks)-1].Height + upperHash := h.TestData.Blocks[len(h.TestData.Blocks)-1].Hash + + err := sw.ConnectBlocksParallel(lowerHeight, upperHeight) + if err != nil { + t.Fatal(err) + } + + height, hash, err := d.GetBestBlock() + if err != nil { + t.Fatal(err) + } + if height != upperHeight { + t.Fatalf("Upper block height mismatch: %d != %d", height, upperHeight) + } + if hash != upperHash { + t.Fatalf("Upper block hash mismatch: %s != %s", hash, upperHash) + } + + t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h) }) + t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h) }) + t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h) }) + }) +} + +func verifyBlockInfo(t *testing.T, d *db.RocksDB, h *TestHandler) { + for _, block := range h.TestData.Blocks { + bi, err := d.GetBlockInfo(block.Height) + if err != nil { + t.Errorf("GetBlockInfo(%d) error: %s", block.Height, err) + continue + } + if bi == nil { + t.Errorf("GetBlockInfo(%d) returned nil", block.Height) + continue + } + + if bi.Hash != block.Hash { + t.Errorf("Block hash mismatch: %s != %s", bi.Hash, block.Hash) + } + + if bi.Txs != block.NoTxs { + t.Errorf("Number of transactions in block %s mismatch: %d != %d", bi.Hash, bi.Txs, block.NoTxs) + } + } +} + +func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler) { + type txInfo struct { + txid string + vout uint32 + isOutput bool + } + addr2txs := make(map[string][]txInfo) + checkMap := make(map[string][]bool) + + for _, block := range h.TestData.Blocks { + for _, tx := range block.TxDetails { + // for _, vin := range tx.Vin { + // if vin.Txid != "" { + // ta, err := d.GetTxAddresses(vin.Txid) + // if err != nil { + // t.Fatal(err) + // } + // if ta != nil { + // if len(ta.Outputs) > int(vin.Vout) { + // output := &ta.Outputs[vin.Vout] + // voutAddr, _, err := output.Addresses(h.Chain.GetChainParser()) + // if err != nil { + // t.Fatal(err) + // } + // t.Logf("XXX: %q", voutAddr) + // } + // } + // } + // } + for _, vin := range tx.Vin { + for _, a := range vin.Addresses { + addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, vin.Vout, false}) + checkMap[a] = append(checkMap[a], false) + } + } + for _, vout := range tx.Vout { + for _, a := range vout.ScriptPubKey.Addresses { + addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, vout.N, true}) + checkMap[a] = append(checkMap[a], false) + } + } + } + } + + lowerHeight := h.TestData.Blocks[0].Height + upperHeight := h.TestData.Blocks[len(h.TestData.Blocks)-1].Height + for addr, txs := range addr2txs { + err := d.GetTransactions(addr, lowerHeight, upperHeight, func(txid string, vout uint32, isOutput bool) error { + for i, tx := range txs { + if txid == tx.txid && vout == tx.vout && isOutput == tx.isOutput { + checkMap[addr][i] = true + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } + } + + for addr, txs := range addr2txs { + for i, tx := range txs { + if !checkMap[addr][i] { + t.Errorf("%s: transaction not found %+v", addr, tx) + } + } + } +} + +func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler) { + parser := h.Chain.GetChainParser() + + for _, block := range h.TestData.Blocks { + for _, tx := range block.TxDetails { + ta, err := d.GetTxAddresses(tx.Txid) + if err != nil { + t.Fatal(err) + } + + txInfo := getTxInfo(tx) + taInfo, err := getTaInfo(parser, ta) + if err != nil { + t.Fatal(err) + } + + if ta.Height != block.Height { + t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, block.Height) + continue + } + + if len(txInfo.inputs) > 0 && !reflect.DeepEqual(taInfo.inputs, txInfo.inputs) { + t.Errorf("Tx %s: inputs mismatch: got %q, want %q", tx.Txid, taInfo.inputs, txInfo.inputs) + } + + if !reflect.DeepEqual(taInfo.outputs, txInfo.outputs) { + t.Errorf("Tx %s: outputs mismatch: got %q, want %q", tx.Txid, taInfo.outputs, txInfo.outputs) + } + + taValIn := satToFloat(parser, &taInfo.valInSat) + taValOut := satToFloat(parser, &taInfo.valOutSat) + txValOut := satToFloat(parser, &txInfo.valOutSat) + + if taValOut.Cmp(txValOut) != 0 { + t.Errorf("Tx %s: total output amount mismatch: got %s, want %s", + tx.Txid, taValOut.String(), txValOut.String()) + } + + treshold := big.NewFloat(0.0001) + if new(big.Float).Sub(taValIn, taValOut).Cmp(treshold) > 0 { + t.Errorf("Tx %s: suspicious amounts: input ∑ [%s] - output ∑ [%s] > %s", + tx.Txid, taValIn.String(), taValOut.String(), treshold) + } + } + } +} + +type txInfo struct { + inputs []string + outputs []string + valInSat big.Int + valOutSat big.Int +} + +func getTxInfo(tx *bchain.Tx) *txInfo { + info := &txInfo{inputs: []string{}, outputs: []string{}} + + for _, vin := range tx.Vin { + for _, a := range vin.Addresses { + info.inputs = append(info.inputs, a) + } + } + for _, vout := range tx.Vout { + for _, a := range vout.ScriptPubKey.Addresses { + info.outputs = append(info.outputs, a) + } + info.valOutSat.Add(&info.valOutSat, &vout.ValueSat) + } + + return info +} + +func getTaInfo(parser bchain.BlockChainParser, ta *db.TxAddresses) (*txInfo, error) { + info := &txInfo{inputs: []string{}, outputs: []string{}} + + for i := range ta.Inputs { + info.valInSat.Add(&info.valInSat, &ta.Inputs[i].ValueSat) + addrs, _, err := ta.Inputs[i].Addresses(parser) + if err != nil { + return nil, err + } + info.inputs = append(info.inputs, addrs...) + } + + for i := range ta.Outputs { + info.valOutSat.Add(&info.valOutSat, &ta.Outputs[i].ValueSat) + addrs, _, err := ta.Outputs[i].Addresses(parser) + if err != nil { + return nil, err + } + info.outputs = append(info.outputs, addrs...) + } + + return info, nil +} + +func satToFloat(parser bchain.BlockChainParser, sat *big.Int) *big.Float { + f, ok := new(big.Float).SetString(parser.AmountToDecimalString(sat)) + if !ok { + return big.NewFloat(-1) + } + return f +} diff --git a/tests/sync/testdata/bitcoin.json b/tests/sync/testdata/bitcoin.json new file mode 100644 index 00000000..e4ca6710 --- /dev/null +++ b/tests/sync/testdata/bitcoin.json @@ -0,0 +1,310 @@ +{ + "connectBlocksParallel": { + "syncWorkers": 3, + "syncChunk": 0 + }, + "blocks": [ + { + "height": 541224, + "hash": "00000000000000000027fe3aca0241ccee4f3b103b2108457a2dfd7490aa41a8", + "noTxs": 1880, + "txDetails": [ + { + "hex": "02000000000101138032859ee12b6b46f2d20ab5bce9564dfcceef9b366c024c5b1bf21b33a47a01000000171600143544c8e7eacc7f624a7748e3623273145394f005feffffff02e09fc92c0000000017a91449c60d71775a5768a139d83e01665602a7319e598740420f00000000001976a91483b47502199d2599b7839ee96011b4340c5554d688ac02483045022100a788bfbc3ebb62ce034061dfe4a500de5ecad7db7c6f40c8d4af55c2d224b3dd02207aff39a1e2c422a96930a977dcb920b0ab80b35e978a7631692f887b0a49d0f0012103919187d19426a9b50012da2cfdebaf8fe45a730066f67ff628c38ce681231a9f22420800", + "txid": "04bfdfaa2babb96581657c77729daa4da4fb1d221ba516393bab3e1a44e45c56", + "blocktime": 1536829299, + "time": 1536829299, + "version": 2, + "vin": [ + { + "txid": "7aa4331bf21b5b4c026c369befcefc4d56e9bcb50ad2f2466b2be19e85328013", + "vout": 1, + "sequence": 4294967294, + "scriptSig": { + "hex": "1600143544c8e7eacc7f624a7748e3623273145394f005" + } + } + ], + "vout": [ + { + "value": 7.51411168, + "n": 0, + "scriptPubKey": { + "hex": "a91449c60d71775a5768a139d83e01665602a7319e5987" + } + }, + { + "value": 0.01000000, + "n": 1, + "scriptPubKey": { + "hex": "76a91483b47502199d2599b7839ee96011b4340c5554d688ac" + } + } + ] + }, + { + "hex": "02000000000101565ce4441a3eab3b3916a51b221dfba44daa9d72777c658165b9ab2baadfbf040000000017160014ef684f89a6656212f5659078c1111523be97f72bfeffffff0240420f00000000001976a914a885121ba438d8145a402e12b91ea1a43a8ba54b88acad5cba2c0000000017a91447f4c3bd4ecb0bd7f4d3489bf640352cab6fb6c58702483045022100f31cb0e57281dd732b20e468cd40dbd018b03631f0c0760565d1349ab46e5e29022046f754795d3a191a7731b470d5b6c5ba9f515c54d354a81e9c265fdfdcfbfd3e012103a596a6b5a398bf1d40778104d56a6317cc65981b6c28082989a4cbf74691d6f722420800", + "txid": "b3f61e880609417a18a18a9e089e706822edd230e0a28316a30595f41eafc45f", + "blocktime": 1536829299, + "time": 1536829299, + "version": 2, + "vin": [ + { + "txid": "04bfdfaa2babb96581657c77729daa4da4fb1d221ba516393bab3e1a44e45c56", + "vout": 0, + "sequence": 4294967294, + "scriptSig": { + "hex": "160014ef684f89a6656212f5659078c1111523be97f72b" + } + } + ], + "vout": [ + { + "value": 0.01000000, + "n": 0, + "scriptPubKey": { + "hex": "76a914a885121ba438d8145a402e12b91ea1a43a8ba54b88ac" + } + }, + { + "value": 7.50410925, + "n": 1, + "scriptPubKey": { + "hex": "a91447f4c3bd4ecb0bd7f4d3489bf640352cab6fb6c587" + } + } + ] + } + ] + }, + { + "height": 541225, + "hash": "00000000000000000023e1bf6bc0a71fb1340c00f256de589108a4768ea82c76", + "noTxs": 1971, + "txDetails": [ + { + "hex": "010000000001030d493576a4a74be8c2de4ffbc5307e27fe354241487c3c5a3a00435f960dca9a01000000232200203676b8ea63f8a38db4bd2a36ad3ecb080ff389a4fc56f85c8042b8136f1a5525ffffffffa531960755251678f3e1f0a76657dc39e71d2d069ab01f6269dc5c8d526e78af14000000fdfe0000483045022100ef26a403ec643750e5dd88211fc2a0432e1b7aa76b5f3eec6c88694947ea67bf02201942f492871eb3178e381f96372658f65e5144ade142d502be99738021f2ae7401483045022100d960c87e0e62362bb2f275c0b4f46a7e1ca1957b25585ba95310511910cca4c102200620f7535478428a2458628c0b25eeadf75fc24fafd480a01396f75dd0c1e7e9014c6952210289a0758ddbdee43fb55af018757ae15d880759ae87c152d1831056e042a3a4362102dc2256a7fcdadc5df21bb668681dcaf234cd0f933066cfc3ccb7de71ea6ab4c421038fdea78199b4554e16aa28d76741b8ad75d5265926d16b5c4cd68e2ec1f66f2253aeffffffff58a39080604b5e99dedba40beb18f5316a66d8c1c8fc054a77d72300476ff05e0000000023220020af51de0aec71ce9d8529b5d196d04c00ca5c63888ae1318ad2e8b682481e8763ffffffff027c2d15000000000017a91473829585e38acb264c21ab7e1d8d4814337c4ba287496800000000000017a91450be45be5aa3ee42827c1eb72370fde4665acfaa870400473044022079fa3a1abf6c9265592cbe2b4a7cac5855bd764f66fc7ceed9e15b5d81184b00022024e6c885cb5c9300f0a6a070b1a6a613ea1137a863253d3737a8e75327cb620f01483045022100dc8f2f4b43cf94849e238b1a94cf9ce0a9fe6e875dcef200d4a9704e29fcc1a902200186ac5327fb4c67c2b663b9ca89b7fd1078cff5851abca987beb0ae3faee31001695221021dd05af6e6e36aa7c7fa3a366f8ba5aefbfa96f8d2014d52bb912f413d4ff1662102b5ed803ebd815d2af641793fcc96dc87bbb1b982ba26b82c59d64cce2e44d2522103c0e71c8ebcf021ceb93ac97182e22c89f9b3ef3e82a59750f593f27f0b8f32c553ae00040047304402204236e7aac97e6df9af208cc0238b409e122ea4bdbee67f4df3f69c3b99ca76a60220202fe1ee41236e997ce9f96f07129dd19d7b98a0c084d74e621a01b742c5425001483045022100b34e348784d84dd9a97c3da856676d035460b11b7e62d66c2f04e770a72bafb40220794eebb88a764b14e0c648cc54f7133115348c1063e3d74b1c92282f479a01d501695221027894bff7c2f0a6faab636188dc8e9915a18f4927f21d25a3a927827e42a22f4c210284efcef0f2f18068454982e29aedbae12e8b6e463e21dfe97a29bd7a1caa559a2102d3b7ad3ad3a5719caa7d9e66ed8412f56eb3b6ce09752c2474695b7e01ec345553ae00000000", + "txid": "3a05d0b3bbb23b23888708dbdb89b729871e1f8cce69874a03a52d401a745f2b", + "blocktime": 1536830751, + "time": 1536830751, + "version": 1, + "vin": [ + { + "txid": "9aca0d965f43003a5a3c7c48414235fe277e30c5fb4fdec2e84ba7a47635490d", + "vout": 1, + "sequence": 4294967295, + "scriptSig": { + "hex": "2200203676b8ea63f8a38db4bd2a36ad3ecb080ff389a4fc56f85c8042b8136f1a5525" + } + }, + { + "txid": "af786e528d5cdc69621fb09a062d1de739dc5766a7f0e1f378162555079631a5", + "vout": 20, + "sequence": 4294967295, + "scriptSig": { + "hex": "00483045022100ef26a403ec643750e5dd88211fc2a0432e1b7aa76b5f3eec6c88694947ea67bf02201942f492871eb3178e381f96372658f65e5144ade142d502be99738021f2ae7401483045022100d960c87e0e62362bb2f275c0b4f46a7e1ca1957b25585ba95310511910cca4c102200620f7535478428a2458628c0b25eeadf75fc24fafd480a01396f75dd0c1e7e9014c6952210289a0758ddbdee43fb55af018757ae15d880759ae87c152d1831056e042a3a4362102dc2256a7fcdadc5df21bb668681dcaf234cd0f933066cfc3ccb7de71ea6ab4c421038fdea78199b4554e16aa28d76741b8ad75d5265926d16b5c4cd68e2ec1f66f2253ae" + } + }, + { + "txid": "5ef06f470023d7774a05fcc8c1d8666a31f518eb0ba4dbde995e4b608090a358", + "vout": 0, + "sequence": 4294967295, + "scriptSig": { + "hex": "220020af51de0aec71ce9d8529b5d196d04c00ca5c63888ae1318ad2e8b682481e8763" + } + } + ], + "vout": [ + { + "value": 0.01387900, + "n": 0, + "scriptPubKey": { + "hex": "a91473829585e38acb264c21ab7e1d8d4814337c4ba287" + } + }, + { + "value": 0.00026697, + "n": 1, + "scriptPubKey": { + "hex": "a91450be45be5aa3ee42827c1eb72370fde4665acfaa87" + } + } + ] + } + ] + }, + { + "height": 541226, + "hash": "00000000000000000023fc3b42ff5da9f74c6a672a7f793860f7c03b217628ae", + "noTxs": 2479, + "txDetails": [ + { + "hex": "02000000014dd8ff786e18c1c48fbaece303c6da31b02013d9122091d5bb5c14e351178bd1030000006a47304402200f46c8a00c84dfc980a39f1db80b9008a06b1fdbe9893bca36ac9278ce1201e30220455a67d71d968a0a32e7d7fd99f1f32055c0d692abb009d9fdfdfb95d011f14f012102133c477d27b26eac6fc51c4452e7b7a6071b77af1869b750c14601e3821a51cafeffffff02e8f10200000000001976a914b18cf542d661fc17a04a29f91b03056e3d7f2aaa88ac51a39500000000001976a914194e18702f84b5c87f307662887ca7ec18482bc488ac28420800", + "txid": "62665cce55418efe3dfe035d6f2c72259f1bf4a4a95dbb6bc27df39e9fd6f1fb", + "blocktime": 1536831767, + "time": 1536831767, + "locktime": 541224, + "version": 2, + "vin": [ + { + "txid": "d18b1751e3145cbbd5912012d91320b031dac603e3ecba8fc4c1186e78ffd84d", + "vout": 3, + "sequence": 4294967294, + "scriptSig": { + "hex": "47304402200f46c8a00c84dfc980a39f1db80b9008a06b1fdbe9893bca36ac9278ce1201e30220455a67d71d968a0a32e7d7fd99f1f32055c0d692abb009d9fdfdfb95d011f14f012102133c477d27b26eac6fc51c4452e7b7a6071b77af1869b750c14601e3821a51ca" + } + } + ], + "vout": [ + { + "value": 0.00193000, + "n": 0, + "scriptPubKey": { + "hex": "76a914b18cf542d661fc17a04a29f91b03056e3d7f2aaa88ac" + } + }, + { + "value": 0.09806673, + "n": 1, + "scriptPubkey": { + "hex": "76a914194e18702f84b5c87f307662887ca7ec18482bc488ac" + } + } + ] + } + ] + }, + { + "height": 541227, + "hash": "00000000000000000007d2b42e5ea24bed4aa02d18192c33b9ec7f6685acb16a", + "noTxs": 1815, + "txDetails": [ + { + "hex": "010000000176ccfc9ac16ced1eebb4e886169a03b9cff16610b8e1dcd8d380110537f6eff4000000006b483045022100b5417fc0e95b39ce2036d67621eb332dd9091734081108630f0521bd9fec1867022070080f1a568eb39dcbad467eb041862daedf8e7b14e3986bc8ab89947a5c8dc6012103aae7dca1bc6f38f77b767ddc6b3dd0ef86c294543c65bfd5cd6ac535f4f54dcdffffffff0206bfc901000000001976a914059272d492ba10fab46e2fe0154ae9200027974d88ac801d2c04000000001976a9145c02a3783346f0a8cd4c5918c625ab13c5be698e88ac00000000", + "txid": "d37262282c3bdd3da47caa663fc21eda2c9db51b43f2a4bde39527fdad569308", + "blocktime": 1536831879, + "time": 1536831879, + "locktime": 0, + "version": 1, + "vin": [ + { + "txid": "f4eff637051180d3d8dce1b81066f1cfb9039a1686e8b4eb1eed6cc19afccc76", + "vout": 0, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100b5417fc0e95b39ce2036d67621eb332dd9091734081108630f0521bd9fec1867022070080f1a568eb39dcbad467eb041862daedf8e7b14e3986bc8ab89947a5c8dc6012103aae7dca1bc6f38f77b767ddc6b3dd0ef86c294543c65bfd5cd6ac535f4f54dcd" + }, + "addresses": ["1PE9dMki67qUYnX5J8APFTHvUJVWxEUoQP"] + } + ], + "vout": [ + { + "value": 0.29998854, + "n": 0, + "scriptPubKey": { + "hex": "76a914059272d492ba10fab46e2fe0154ae9200027974d88ac" + } + }, + { + "value": 0.70000000, + "n": 1, + "scriptPubKey": { + "hex": "76a9145c02a3783346f0a8cd4c5918c625ab13c5be698e88ac" + } + } + ] + } + ] + }, + { + "height": 541228, + "hash": "0000000000000000001c2444541142d58b126ef608b5f52081aced02612d078f", + "noTxs": 2549, + "txDetails": [ + { + "hex": "020000000001012790904fd89e78bf372a8d79bac24ae21c0b60d65761a7fbf1002ce28d4db8fd00000000171600146432f02f2f8f1d1a7d58d83b1815cd300417404dfeffffff027dff11000000000017a91401dfd360c8b640f9da5ede7170dc369d84c3dfcf87503403000000000017a91454f7c31d5a1aac2f6b3d6b4773f62f333e2e8af8870247304402205a89be503e481e37b7175a988f7f1276e6f56d477570d3745dda126f9521bba402201cfafd0f575b6617a6f1d2c3062f398e7be43830a2786d5c9848092cc8382f600121020dc52cfdf50ad27ce9b6701e0d88d2177ad118d43c864a3cc491bd4b7ddf5dc827420800", + "txid": "3dbf8bf9d9dc161024c08fc22c938426f975da4a1da7830257cf149c260e6f89", + "blocktime": 1536832036, + "time": 1536832036, + "locktime": 541223, + "version": 2, + "vin": [ + { + "txid": "fdb84d8de22c00f1fba76157d6600b1ce24ac2ba798d2a37bf789ed84f909027", + "vout": 0, + "sequence": 4294967294, + "scriptSig": { + "hex": "1600146432f02f2f8f1d1a7d58d83b1815cd300417404d" + } + } + ], + "vout": [ + { + "value": 0.01179517, + "n": 0, + "scriptPubKey": { + "hex": "a91401dfd360c8b640f9da5ede7170dc369d84c3dfcf87" + } + }, + { + "value": 0.00210000, + "n": 1, + "scriptPubkey": { + "hex": "a91454f7c31d5a1aac2f6b3d6b4773f62f333e2e8af887" + } + } + ] + } + ] + }, + { + "height": 541255, + "hash": "0000000000000000001375c3e88b4e53b687a70aba4b645ba81c93a4539d724d", + "noTxs": 2633, + "txDetails": [ + { + "hex": "010000000263bc2bd6ba344d39487c3c8a29eb0b71d118bef0c4318e3f9d6cd6c0a63b2e34010000006b483045022100fe6a3fb0fafa305a7cf0dd55f670e6a6469392d16bd7395ac3eecd31b48f04dc022008e4c23e7657135bb6e11727e3bc4f67aa4c827f4cfb45b27a8df531295b6a5a0121030e2844042326a3dad34937b2a577977b6d13d87a50812f351735fe1ebd999fb7ffffffff17809d201a1cb3a7da30de83f9d92b52e6f78f37c5bc6f3c105127bdb6dc9291000000006a473044022053e8788b622f6ed5ed69bffb6f2c568d42d1bb398eb6c8b9d7d011e1280c261a0220243a3346883ed0acd7a291140ddc520d43100f60e68fd4a242ec70bae95c097e0121030e2844042326a3dad34937b2a577977b6d13d87a50812f351735fe1ebd999fb7ffffffff020a39e800000000001976a914a1d8d0f5edd2bf6808d7156f2884096fd86359fe88ac00e1f505000000001976a914492f305f58b7469202526bff3200a9f2a446fb4988ac00000000", + "txid": "dcc55f1443084d97bcf17e7d7ac1d589ea947f4940030779a40cfd9731a24a9e", + "blocktime": 1536832036, + "time": 1536832036, + "locktime": 541223, + "version": 2, + "vin": [ + { + "txid": "342e3ba6c0d66c9d3f8e31c4f0be18d1710beb298a3c7c48394d34bad62bbc63", + "vout": 1, + "sequence": 4294967295, + "scriptSig": { + "hex": "483045022100fe6a3fb0fafa305a7cf0dd55f670e6a6469392d16bd7395ac3eecd31b48f04dc022008e4c23e7657135bb6e11727e3bc4f67aa4c827f4cfb45b27a8df531295b6a5a0121030e2844042326a3dad34937b2a577977b6d13d87a50812f351735fe1ebd999fb7" + } + }, + { + "txid": "9192dcb6bd2751103c6fbcc5378ff7e6522bd9f983de30daa7b31c1a209d8017", + "vout": 0, + "sequence": 4294967295, + "scriptSig": { + "hex": "473044022053e8788b622f6ed5ed69bffb6f2c568d42d1bb398eb6c8b9d7d011e1280c261a0220243a3346883ed0acd7a291140ddc520d43100f60e68fd4a242ec70bae95c097e0121030e2844042326a3dad34937b2a577977b6d13d87a50812f351735fe1ebd999fb7" + } + } + ], + "vout": [ + { + "value": 0.15218954, + "n": 0, + "scriptPubKey": { + "hex": "76a914a1d8d0f5edd2bf6808d7156f2884096fd86359fe88ac" + } + }, + { + "value": 1.00000000, + "n": 1, + "scriptPubkey": { + "hex": "76a914492f305f58b7469202526bff3200a9f2a446fb4988ac" + } + } + ] + } + ] + } + ] +} diff --git a/tests/tests.json b/tests/tests.json new file mode 100644 index 00000000..5abe6990 --- /dev/null +++ b/tests/tests.json @@ -0,0 +1,58 @@ +{ + "bcash": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + }, + "bcash_testnet": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + }, + "bitcoin": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], + "sync": ["ConnectBlocksParallel"] + }, + "bitcoin_testnet": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + }, + "dash": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + }, + "dash_testnet": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + }, + "dogecoin": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync"] + }, + "ethereum_testnet_ropsten": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", + "GetBlockHeader"] + }, + "litecoin": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee"] + }, + "monacoin": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee"] + }, + "namecoin": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "MempoolSync", "EstimateSmartFee", + "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + }, + "vertcoin": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + }, + "zcash": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + }, + "zcash_testnet": { + "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + } +}