WIP: sync integration tests

pull/68/head
Jakub Matys 2018-09-20 09:39:13 +02:00
parent f6fffefec9
commit 5754523317
36 changed files with 1025 additions and 297 deletions

View File

@ -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
}

View File

@ -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"]
}
}

View File

@ -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"]
}
}

View File

@ -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"]
}
}

View File

@ -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"]
}
}

View File

@ -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"]
}
}

View File

@ -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"]
}
}

View File

@ -63,8 +63,5 @@
"meta": {
"package_maintainer": "Jakub Matys",
"package_maintainer_email": "jakub.matys@satoshilabs.com"
},
"integration_tests": {
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync"]
}
}

View File

@ -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"]
}
}

View File

@ -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"]
}
}

View File

@ -60,9 +60,5 @@
"meta": {
"package_maintainer": "wakiyamap",
"package_maintainer_email": "wakiyamap@gmail.com"
},
"integration_tests": {
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee",
"EstimateFee"]
}
}

View File

@ -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"]
}
}

View File

@ -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"]
}
}

View File

@ -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"]
}
}

View File

@ -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"]
}
}

View File

@ -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

View File

@ -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
}

View File

@ -0,0 +1,11 @@
// +build integration
package tests
import (
"testing"
)
func TestIntegration(t *testing.T) {
runIntegrationTests(t)
}

View File

@ -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)
}

404
tests/sync/sync.go 100644
View File

@ -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
}

310
tests/sync/testdata/bitcoin.json vendored 100644
View File

@ -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"
}
}
]
}
]
}
]
}

58
tests/tests.json 100644
View File

@ -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"]
}
}