Merge branch 'tests'

pull/78/head
Jakub Matys 2018-10-04 16:59:39 +02:00
commit 503ff9582a
15 changed files with 1381 additions and 601 deletions

View File

@ -293,7 +293,7 @@ func main() {
if *synchronize {
internalState.SyncMode = true
internalState.InitialSync = true
if err := syncWorker.ResyncIndex(nil); err != nil {
if err := syncWorker.ResyncIndex(nil, true); err != nil {
glog.Error("resyncIndex ", err)
return
}
@ -427,7 +427,7 @@ func syncIndexLoop() {
glog.Info("syncIndexLoop starting")
// resync index about every 15 minutes if there are no chanSyncIndex requests, with debounce 1 second
tickAndDebounce(time.Duration(*resyncIndexPeriodMs)*time.Millisecond, debounceResyncIndexMs*time.Millisecond, chanSyncIndex, func() {
if err := syncWorker.ResyncIndex(onNewBlockHash); err != nil {
if err := syncWorker.ResyncIndex(onNewBlockHash, false); err != nil {
glog.Error("syncIndexLoop ", errors.ErrorStack(err))
}
})

View File

@ -47,11 +47,11 @@ var errSynced = errors.New("synced")
// ResyncIndex synchronizes index to the top of the blockchain
// onNewBlock is called when new block is connected, but not in initial parallel sync
func (w *SyncWorker) ResyncIndex(onNewBlock bchain.OnNewBlockFunc) error {
func (w *SyncWorker) ResyncIndex(onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
start := time.Now()
w.is.StartedSync()
err := w.resyncIndex(onNewBlock)
err := w.resyncIndex(onNewBlock, initialSync)
switch err {
case nil:
@ -76,7 +76,7 @@ func (w *SyncWorker) ResyncIndex(onNewBlock bchain.OnNewBlockFunc) error {
return err
}
func (w *SyncWorker) resyncIndex(onNewBlock bchain.OnNewBlockFunc) error {
func (w *SyncWorker) resyncIndex(onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
remoteBestHash, err := w.chain.GetBestBlockHash()
if err != nil {
return err
@ -99,7 +99,7 @@ func (w *SyncWorker) resyncIndex(onNewBlock bchain.OnNewBlockFunc) error {
if remoteHash != localBestHash {
// forked - the remote hash differs from the local hash at the same height
glog.Info("resync: local is forked at height ", localBestHeight, ", local hash ", localBestHash, ", remote hash", remoteHash)
return w.handleFork(localBestHeight, localBestHash, onNewBlock)
return w.handleFork(localBestHeight, localBestHash, onNewBlock, initialSync)
}
glog.Info("resync: local at ", localBestHeight, " is behind")
w.startHeight = localBestHeight + 1
@ -130,13 +130,13 @@ func (w *SyncWorker) resyncIndex(onNewBlock bchain.OnNewBlockFunc) error {
}
// after parallel load finish the sync using standard way,
// new blocks may have been created in the meantime
return w.resyncIndex(onNewBlock)
return w.resyncIndex(onNewBlock, initialSync)
}
}
return w.connectBlocks(onNewBlock)
return w.connectBlocks(onNewBlock, initialSync)
}
func (w *SyncWorker) handleFork(localBestHeight uint32, localBestHash string, onNewBlock bchain.OnNewBlockFunc) error {
func (w *SyncWorker) handleFork(localBestHeight uint32, localBestHash string, onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
// find forked blocks, disconnect them and then synchronize again
var height uint32
hashes := []string{localBestHash}
@ -161,18 +161,19 @@ func (w *SyncWorker) handleFork(localBestHeight uint32, localBestHash string, on
if err := w.DisconnectBlocks(height+1, localBestHeight, hashes); err != nil {
return err
}
return w.resyncIndex(onNewBlock)
return w.resyncIndex(onNewBlock, initialSync)
}
func (w *SyncWorker) connectBlocks(onNewBlock bchain.OnNewBlockFunc) error {
func (w *SyncWorker) connectBlocks(onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
bch := make(chan blockResult, 8)
done := make(chan struct{})
defer close(done)
go w.getBlockChain(bch, done)
var lastRes blockResult
for res := range bch {
var lastRes, empty blockResult
connect := func(res blockResult) error {
lastRes = res
if res.err != nil {
return res.err
@ -187,6 +188,34 @@ func (w *SyncWorker) connectBlocks(onNewBlock bchain.OnNewBlockFunc) error {
if res.block.Height > 0 && res.block.Height%1000 == 0 {
glog.Info("connected block ", res.block.Height, " ", res.block.Hash)
}
return nil
}
if initialSync {
ConnectLoop:
for {
select {
case <-w.chanOsSignal:
return errors.Errorf("connectBlocks interrupted at height %d", lastRes.block.Height)
case res := <-bch:
if res == empty {
break ConnectLoop
}
err := connect(res)
if err != nil {
return err
}
}
}
} else {
// while regular sync, OS sig is handled by waitForSignalAndShutdown
for res := range bch {
err := connect(res)
if err != nil {
return err
}
}
}
if lastRes.block != nil {

19
db/test_helper.go 100644
View File

@ -0,0 +1,19 @@
// +build integration
package db
import (
"blockbook/bchain"
)
func SetBlockChain(w *SyncWorker, chain bchain.BlockChain) {
w.chain = chain
}
func ConnectBlocks(w *SyncWorker, onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
return w.connectBlocks(onNewBlock, initialSync)
}
func HandleFork(w *SyncWorker, localBestHeight uint32, localBestHash string, onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
return w.handleFork(localBestHeight, localBestHash, onNewBlock, initialSync)
}

2
tests/doc.go 100644
View File

@ -0,0 +1,2 @@
// Package tests provides functions for loading and running integration tests
package tests

View File

@ -1,3 +1,5 @@
// +build integration
package tests
import (
@ -14,6 +16,7 @@ import (
"os"
"path/filepath"
"reflect"
"sort"
"testing"
"github.com/jakm/btcutil/chaincfg"
@ -34,7 +37,14 @@ func runIntegrationTests(t *testing.T) {
t.Fatal(err)
}
for coin, cfg := range tests {
keys := make([]string, 0, len(tests))
for k := range tests {
keys = append(keys, k)
}
sort.Strings(keys)
for _, coin := range keys {
cfg := tests[coin]
t.Run(coin, func(t *testing.T) { runTests(t, coin, cfg) })
}

2
tests/rpc/doc.go 100644
View File

@ -0,0 +1,2 @@
// Package rpc implements integration tests of blockchain RPC layer
package rpc

View File

@ -1,9 +1,10 @@
// +build integration
package rpc
import (
"blockbook/bchain"
"encoding/json"
"errors"
"io/ioutil"
"path/filepath"
"reflect"
@ -11,6 +12,7 @@ import (
"time"
"github.com/deckarep/golang-set"
"github.com/juju/errors"
)
var testMap = map[string]func(t *testing.T, th *TestHandler){
@ -108,18 +110,19 @@ func loadTestData(coin string, parser bchain.BlockChainParser) (*TestData, error
}
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
}
for i := range tx.Vout {
ad, err := parser.GetAddrDescFromVout(&tx.Vout[i])
if err != nil {
return err
}
addrs := []string{}
a, s, err := parser.GetAddressesFromAddrDesc(ad)
if err == nil && s {
addrs = append(addrs, a...)
}
tx.Vout[i].ScriptPubKey.Addresses = addrs
}
return err
return nil
}
func testGetBlockHash(t *testing.T, h *TestHandler) {
@ -204,7 +207,7 @@ func testMempoolSync(t *testing.T, h *TestHandler) {
continue
}
txid2addrs := getMempoolAddresses(t, h, txs)
txid2addrs := getTxid2addrs(t, h, txs)
if len(txid2addrs) == 0 {
t.Skip("Skipping test, no addresses in mempool")
}
@ -213,7 +216,7 @@ func testMempoolSync(t *testing.T, h *TestHandler) {
for _, a := range addrs {
got, err := h.Chain.GetMempoolTransactions(a)
if err != nil {
t.Fatal(err)
t.Fatalf("address %q: %s", a, err)
}
if !containsString(got, txid) {
t.Errorf("ResyncMempool() - for address %s, transaction %s wasn't found in mempool", a, txid)
@ -341,26 +344,21 @@ func getMempool(t *testing.T, h *TestHandler) []string {
return txs
}
func getMempoolAddresses(t *testing.T, h *TestHandler, txs []string) map[string][]string {
func getTxid2addrs(t *testing.T, h *TestHandler, txs []string) map[string][]string {
txid2addrs := map[string][]string{}
for i := 0; i < len(txs); i++ {
for i := range txs {
tx, err := h.Chain.GetTransactionForMempool(txs[i])
if err != nil {
if isMissingTx(err) {
continue
}
t.Fatal(err)
}
setTxAddresses(h.Chain.GetChainParser(), tx)
addrs := []string{}
for _, vin := range tx.Vin {
for _, a := range vin.Addresses {
if isSearchableAddr(a) {
addrs = append(addrs, a)
}
}
}
for _, vout := range tx.Vout {
for _, a := range vout.ScriptPubKey.Addresses {
if isSearchableAddr(a) {
addrs = append(addrs, a)
}
for j := range tx.Vout {
for _, a := range tx.Vout[j].ScriptPubKey.Addresses {
addrs = append(addrs, a)
}
}
if len(addrs) > 0 {
@ -370,8 +368,18 @@ func getMempoolAddresses(t *testing.T, h *TestHandler, txs []string) map[string]
return txid2addrs
}
func isSearchableAddr(addr string) bool {
return len(addr) > 3 && addr[:3] != "OP_"
func isMissingTx(err error) bool {
switch e1 := err.(type) {
case *errors.Err:
switch e2 := e1.Cause().(type) {
case *bchain.RPCError:
if e2.Code == -5 { // "No such mempool or blockchain transaction"
return true
}
}
}
return false
}
func intersect(a, b []string) []string {
@ -392,7 +400,7 @@ func intersect(a, b []string) []string {
}
func containsString(slice []string, s string) bool {
for i := 0; i < len(slice); i++ {
for i := range slice {
if slice[i] == s {
return true
}

View File

@ -0,0 +1,265 @@
// +build integration
package sync
import (
"blockbook/bchain"
"blockbook/db"
"fmt"
"math/big"
"os"
"reflect"
"strings"
"testing"
)
func testConnectBlocks(t *testing.T, h *TestHandler) {
for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
upperHash, err := h.Chain.GetBlockHash(rng.Upper)
if err != nil {
t.Fatal(err)
}
err = db.ConnectBlocks(sw, func(hash string, height uint32) {
if hash == upperHash {
close(ch)
}
}, true)
if err != nil {
if err.Error() != fmt.Sprintf("connectBlocks interrupted at height %d", rng.Upper) {
t.Fatal(err)
}
}
height, hash, err := d.GetBestBlock()
if err != nil {
t.Fatal(err)
}
if height != rng.Upper {
t.Fatalf("Upper block height mismatch: %d != %d", height, rng.Upper)
}
if hash != upperHash {
t.Fatalf("Upper block hash mismatch: %s != %s", hash, upperHash)
}
t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
})
}
}
func testConnectBlocksParallel(t *testing.T, h *TestHandler) {
for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
upperHash, err := h.Chain.GetBlockHash(rng.Upper)
if err != nil {
t.Fatal(err)
}
err = sw.ConnectBlocksParallel(rng.Lower, rng.Upper)
if err != nil {
t.Fatal(err)
}
height, hash, err := d.GetBestBlock()
if err != nil {
t.Fatal(err)
}
if height != rng.Upper {
t.Fatalf("Upper block height mismatch: %d != %d", height, rng.Upper)
}
if hash != upperHash {
t.Fatalf("Upper block hash mismatch: %s != %s", hash, upperHash)
}
t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
})
}
}
func verifyBlockInfo(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
for height := rng.Lower; height <= rng.Upper; height++ {
block, found := h.TestData.ConnectBlocks.Blocks[height]
if !found {
continue
}
bi, err := d.GetBlockInfo(height)
if err != nil {
t.Errorf("GetBlockInfo(%d) error: %s", height, err)
continue
}
if bi == nil {
t.Errorf("GetBlockInfo(%d) returned nil", 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, rng Range) {
type txInfo struct {
txid string
vout uint32
isOutput bool
}
addr2txs := make(map[string][]txInfo)
checkMap := make(map[string][]bool)
for height := rng.Lower; height <= rng.Upper; height++ {
block, found := h.TestData.ConnectBlocks.Blocks[height]
if !found {
continue
}
for _, tx := range block.TxDetails {
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)
}
}
}
}
for addr, txs := range addr2txs {
err := d.GetTransactions(addr, rng.Lower, rng.Upper, 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, rng Range) {
parser := h.Chain.GetChainParser()
for height := rng.Lower; height <= rng.Upper; height++ {
block, found := h.TestData.ConnectBlocks.Blocks[height]
if !found {
continue
}
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 != height {
t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, 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)
}
if taInfo.valOutSat.Cmp(&txInfo.valOutSat) != 0 {
t.Errorf("Tx %s: total output amount mismatch: got %s, want %s",
tx.Txid, taInfo.valOutSat.String(), txInfo.valOutSat.String())
}
if len(txInfo.inputs) > 0 {
treshold := "0.0001"
fee := new(big.Int).Sub(&taInfo.valInSat, &taInfo.valOutSat)
if strings.Compare(parser.AmountToDecimalString(fee), treshold) > 0 {
t.Errorf("Tx %s: suspicious amounts: input ∑ [%s] - output ∑ [%s] > %s",
tx.Txid, taInfo.valInSat.String(), taInfo.valOutSat.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, s, err := ta.Inputs[i].Addresses(parser)
if err == nil && s {
for _, a := range addrs {
info.inputs = append(info.inputs, a)
}
}
}
for i := range ta.Outputs {
info.valOutSat.Add(&info.valOutSat, &ta.Outputs[i].ValueSat)
addrs, s, err := ta.Outputs[i].Addresses(parser)
if err == nil && s {
for _, a := range addrs {
info.outputs = append(info.outputs, a)
}
}
}
return info, nil
}

View File

@ -0,0 +1,2 @@
// Package sync implements integration tests of synchronization code
package sync

View File

@ -0,0 +1,48 @@
// +build integration
package sync
import "blockbook/bchain"
type fakeBlockChain struct {
bchain.BlockChain
returnFakes bool
fakeBlocks map[uint32]BlockID
bestHeight uint32
}
func (c *fakeBlockChain) GetBestBlockHash() (v string, err error) {
return c.GetBlockHash(c.bestHeight)
}
func (c *fakeBlockChain) GetBestBlockHeight() (v uint32, err error) {
return c.bestHeight, nil
}
func (c *fakeBlockChain) GetBlockHash(height uint32) (v string, err error) {
if height > c.bestHeight {
return "", bchain.ErrBlockNotFound
}
if c.returnFakes {
if b, found := c.fakeBlocks[height]; found {
return b.Hash, nil
}
}
return c.BlockChain.GetBlockHash(height)
}
func (c *fakeBlockChain) GetBlock(hash string, height uint32) (*bchain.Block, error) {
if height > 0 && height > c.bestHeight {
return nil, bchain.ErrBlockNotFound
}
if c.returnFakes {
if hash == "" && height > 0 {
var err error
hash, err = c.GetBlockHash(height)
if err != nil {
return nil, err
}
}
}
return c.BlockChain.GetBlock(hash, height)
}

View File

@ -0,0 +1,239 @@
// +build integration
package sync
import (
"blockbook/bchain"
"blockbook/db"
"fmt"
"math/big"
"os"
"reflect"
"strings"
"testing"
)
func testHandleFork(t *testing.T, h *TestHandler) {
for _, rng := range h.TestData.HandleFork.SyncRanges {
withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
fakeBlocks := getFakeBlocks(h, rng)
chain, err := makeFakeChain(h.Chain, fakeBlocks, rng.Upper)
if err != nil {
t.Fatal(err)
}
db.SetBlockChain(sw, chain)
sw.ConnectBlocksParallel(rng.Lower, rng.Upper)
height, _, err := d.GetBestBlock()
if err != nil {
t.Fatal(err)
}
if height != rng.Upper {
t.Fatalf("Upper block height mismatch: %d != %d", height, rng.Upper)
}
fakeTxs, err := getTxs(h, d, rng, fakeBlocks)
if err != nil {
t.Fatal(err)
}
fakeAddr2txs := getAddr2TxsMap(fakeTxs)
verifyTransactions2(t, d, rng, fakeAddr2txs, true)
verifyAddresses2(t, d, h.Chain, fakeBlocks)
chain.returnFakes = false
upperHash := fakeBlocks[len(fakeBlocks)-1].Hash
db.HandleFork(sw, rng.Upper, upperHash, func(hash string, height uint32) {
if hash == upperHash {
close(ch)
}
}, true)
realBlocks := getRealBlocks(h, rng)
realTxs, err := getTxs(h, d, rng, realBlocks)
if err != nil {
t.Fatal(err)
}
realAddr2txs := getAddr2TxsMap(realTxs)
verifyTransactions2(t, d, rng, fakeAddr2txs, false)
verifyTransactions2(t, d, rng, realAddr2txs, true)
verifyAddresses2(t, d, h.Chain, realBlocks)
})
}
}
func verifyAddresses2(t *testing.T, d *db.RocksDB, chain bchain.BlockChain, blks []BlockID) {
parser := chain.GetChainParser()
for _, b := range blks {
txs, err := getBlockTxs(chain, b.Hash)
if err != nil {
t.Fatal(err)
}
for _, tx := range txs {
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 != b.Height {
t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, b.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)
}
if taInfo.valOutSat.Cmp(&txInfo.valOutSat) != 0 {
t.Errorf("Tx %s: total output amount mismatch: got %s, want %s",
tx.Txid, taInfo.valOutSat.String(), txInfo.valOutSat.String())
}
if len(txInfo.inputs) > 0 {
treshold := "0.0001"
fee := new(big.Int).Sub(&taInfo.valInSat, &taInfo.valOutSat)
if strings.Compare(parser.AmountToDecimalString(fee), treshold) > 0 {
t.Errorf("Tx %s: suspicious amounts: input ∑ [%s] - output ∑ [%s] > %s",
tx.Txid, taInfo.valInSat.String(), taInfo.valOutSat.String(), treshold)
}
}
}
}
}
func verifyTransactions2(t *testing.T, d *db.RocksDB, rng Range, addr2txs map[string][]string, exist bool) {
noErrs := 0
for addr, txs := range addr2txs {
checkMap := make(map[string]bool, len(txs))
for _, txid := range txs {
checkMap[txid] = false
}
err := d.GetTransactions(addr, rng.Lower, rng.Upper, func(txid string, vout uint32, isOutput bool) error {
if isOutput {
checkMap[txid] = true
}
return nil
})
if err != nil {
t.Fatal(err)
}
for _, txid := range txs {
if checkMap[txid] != exist {
auxverb := "wasn't"
if !exist {
auxverb = "was"
}
t.Errorf("%s: transaction %s %s found [expected = %t]", addr, txid, auxverb, exist)
noErrs++
if noErrs >= 10 {
t.Fatal("Too many errors")
}
}
}
}
}
func getFakeBlocks(h *TestHandler, rng Range) []BlockID {
blks := make([]BlockID, 0, rng.Upper-rng.Lower+1)
for i := rng.Lower; i <= rng.Upper; i++ {
if b, found := h.TestData.HandleFork.FakeBlocks[i]; found {
blks = append(blks, b)
}
}
return blks
}
func getRealBlocks(h *TestHandler, rng Range) []BlockID {
blks := make([]BlockID, 0, rng.Upper-rng.Lower+1)
for _, b := range h.TestData.HandleFork.RealBlocks {
if b.Height >= rng.Lower && b.Height <= rng.Upper {
blks = append(blks, b)
}
}
return blks
}
func makeFakeChain(chain bchain.BlockChain, blks []BlockID, upper uint32) (*fakeBlockChain, error) {
if blks[len(blks)-1].Height != upper {
return nil, fmt.Errorf("Range must end with fake block in order to emulate fork [%d != %d]", blks[len(blks)-1].Height, upper)
}
mBlks := make(map[uint32]BlockID, len(blks))
for i := range blks {
mBlks[blks[i].Height] = blks[i]
}
return &fakeBlockChain{
BlockChain: chain,
returnFakes: true,
fakeBlocks: mBlks,
bestHeight: upper,
}, nil
}
func getTxs(h *TestHandler, d *db.RocksDB, rng Range, blks []BlockID) ([]bchain.Tx, error) {
res := make([]bchain.Tx, 0, (rng.Upper-rng.Lower+1)*2000)
for _, b := range blks {
bi, err := d.GetBlockInfo(b.Height)
if err != nil {
return nil, err
}
if bi.Hash != b.Hash {
return nil, fmt.Errorf("Block hash mismatch: %s != %s", bi.Hash, b.Hash)
}
txs, err := getBlockTxs(h.Chain, b.Hash)
if err != nil {
return nil, err
}
res = append(res, txs...)
}
return res, nil
}
func getBlockTxs(chain bchain.BlockChain, hash string) ([]bchain.Tx, error) {
b, err := chain.GetBlock(hash, 0)
if err != nil {
return nil, fmt.Errorf("GetBlock: %s", err)
}
parser := chain.GetChainParser()
for i := range b.Txs {
err := setTxAddresses(parser, &b.Txs[i])
if err != nil {
return nil, fmt.Errorf("setTxAddresses [%s]: %s", b.Txs[i].Txid, err)
}
}
return b.Txs, nil
}
func getAddr2TxsMap(txs []bchain.Tx) map[string][]string {
addr2txs := make(map[string][]string)
for i := range txs {
for j := range txs[i].Vout {
for k := range txs[i].Vout[j].ScriptPubKey.Addresses {
addr := txs[i].Vout[j].ScriptPubKey.Addresses[k]
txid := txs[i].Txid
addr2txs[addr] = append(addr2txs[addr], txid)
}
}
}
return addr2txs
}

View File

@ -1,3 +1,5 @@
// +build integration
package sync
import (
@ -7,18 +9,15 @@ import (
"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,
"ConnectBlocks": testConnectBlocks,
"ConnectBlocksParallel": testConnectBlocksParallel,
// "DisconnectBlocks": nil,
"HandleFork": testHandleFork,
}
type TestHandler struct {
@ -27,17 +26,30 @@ type TestHandler struct {
TestData *TestData
}
type Range struct {
Lower uint32 `json:"lower"`
Upper uint32 `json:"upper"`
}
type TestData struct {
ConnectBlocksParallel struct {
SyncWorkers int `json:"syncWorkers"`
SyncChunk int `json:"syncChunk"`
} `json:"connectBlocksParallel"`
Blocks []BlockInfo `json:"blocks"`
ConnectBlocks struct {
SyncRanges []Range `json:"syncRanges"`
Blocks map[uint32]BlockInfo `json:"blocks"`
} `json:"connectBlocks"`
HandleFork struct {
SyncRanges []Range `json:"syncRanges"`
FakeBlocks map[uint32]BlockID `json:"fakeBlocks"`
RealBlocks map[uint32]BlockID `json:"realBlocks"`
} `json:"handleFork"`
}
type BlockID struct {
Height uint32 `json:"height"`
Hash string `json:"hash"`
}
type BlockInfo struct {
Height uint32 `json:"height"`
Hash string `json:"hash"`
BlockID
NoTxs uint32 `json:"noTxs"`
TxDetails []*bchain.Tx `json:"txDetails"`
}
@ -54,10 +66,9 @@ func IntegrationTest(t *testing.T, coin string, chain bchain.BlockChain, testCon
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 {
h := TestHandler{Coin: coin, Chain: chain, TestData: td}
t.Run(test, func(t *testing.T) { f(t, &h) })
} else {
t.Errorf("%s: test not found", test)
@ -90,7 +101,7 @@ func loadTestData(coin string, parser bchain.BlockChainParser) (*TestData, error
return nil, err
}
for _, b := range v.Blocks {
for _, b := range v.ConnectBlocks.Blocks {
for _, tx := range b.TxDetails {
// convert amounts in test json to bit.Int and clear the temporary JsonValue
for i := range tx.Vout {
@ -110,26 +121,21 @@ func loadTestData(coin string, parser bchain.BlockChainParser) (*TestData, error
}
}
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
}
for i := range tx.Vout {
ad, err := parser.GetAddrDescFromVout(&tx.Vout[i])
if err != nil {
return err
}
a, s, err := parser.GetAddressesFromAddrDesc(ad)
if err == nil && s {
tx.Vout[i].ScriptPubKey.Addresses = a
}
}
return err
return nil
}
func makeRocksDB(parser bchain.BlockChainParser, m *common.Metrics, is *common.InternalState) (*db.RocksDB, func(), error) {
@ -153,8 +159,23 @@ func makeRocksDB(parser bchain.BlockChainParser, m *common.Metrics, is *common.I
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)
var metricsRegistry = map[string]*common.Metrics{}
func getMetrics(name string) (*common.Metrics, error) {
if m, found := metricsRegistry[name]; found {
return m, nil
} else {
m, err := common.GetMetrics(name)
if err != nil {
return nil, err
}
metricsRegistry[name] = m
return m, nil
}
}
func withRocksDBAndSyncWorker(t *testing.T, h *TestHandler, startHeight uint32, fn func(*db.RocksDB, *db.SyncWorker, chan os.Signal)) {
m, err := getMetrics(h.Coin)
if err != nil {
t.Fatal(err)
}
@ -166,239 +187,12 @@ func withRocksDBAndSyncWorker(t *testing.T, h *TestHandler, fn func(*db.RocksDB,
}
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)
sw, err := db.NewSyncWorker(d, h.Chain, 8, 0, int(startHeight), 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
}

View File

@ -1,310 +1,459 @@
{
"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"
"connectBlocks": {
"syncRanges": [
{"lower": 541224, "upper": 541255},
{"lower": 542835, "upper": 542838}
],
"blocks": {
"541224": {
"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"
],
"vout": [
{
"value": 7.51411168,
"n": 0,
"scriptPubKey": {
"hex": "a91449c60d71775a5768a139d83e01665602a7319e5987"
}
},
"addresses": ["1PE9dMki67qUYnX5J8APFTHvUJVWxEUoQP"]
}
],
"vout": [
{
"value": 0.29998854,
"n": 0,
"scriptPubKey": {
"hex": "76a914059272d492ba10fab46e2fe0154ae9200027974d88ac"
{
"value": 0.01000000,
"n": 1,
"scriptPubKey": {
"hex": "76a91483b47502199d2599b7839ee96011b4340c5554d688ac"
}
}
},
{
"value": 0.70000000,
"n": 1,
"scriptPubKey": {
"hex": "76a9145c02a3783346f0a8cd4c5918c625ab13c5be698e88ac"
]
},
{
"hex": "02000000000101565ce4441a3eab3b3916a51b221dfba44daa9d72777c658165b9ab2baadfbf040000000017160014ef684f89a6656212f5659078c1111523be97f72bfeffffff0240420f00000000001976a914a885121ba438d8145a402e12b91ea1a43a8ba54b88acad5cba2c0000000017a91447f4c3bd4ecb0bd7f4d3489bf640352cab6fb6c58702483045022100f31cb0e57281dd732b20e468cd40dbd018b03631f0c0760565d1349ab46e5e29022046f754795d3a191a7731b470d5b6c5ba9f515c54d354a81e9c265fdfdcfbfd3e012103a596a6b5a398bf1d40778104d56a6317cc65981b6c28082989a4cbf74691d6f722420800",
"txid": "b3f61e880609417a18a18a9e089e706822edd230e0a28316a30595f41eafc45f",
"blocktime": 1536829299,
"time": 1536829299,
"version": 2,
"vin": [
{
"txid": "04bfdfaa2babb96581657c77729daa4da4fb1d221ba516393bab3e1a44e45c56",
"vout": 0,
"sequence": 4294967294,
"scriptSig": {
"hex": "160014ef684f89a6656212f5659078c1111523be97f72b"
}
}
}
]
}
]
},
{
"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.01000000,
"n": 0,
"scriptPubKey": {
"hex": "76a914a885121ba438d8145a402e12b91ea1a43a8ba54b88ac"
}
},
{
"value": 7.50410925,
"n": 1,
"scriptPubKey": {
"hex": "a91447f4c3bd4ecb0bd7f4d3489bf640352cab6fb6c587"
}
}
}
],
"vout": [
{
"value": 0.01179517,
"n": 0,
"scriptPubKey": {
"hex": "a91401dfd360c8b640f9da5ede7170dc369d84c3dfcf87"
]
}
]
},
"541225": {
"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"
}
}
},
{
"value": 0.00210000,
"n": 1,
"scriptPubkey": {
"hex": "a91454f7c31d5a1aac2f6b3d6b4773f62f333e2e8af887"
],
"vout": [
{
"value": 0.01387900,
"n": 0,
"scriptPubKey": {
"hex": "a91473829585e38acb264c21ab7e1d8d4814337c4ba287"
}
},
{
"value": 0.00026697,
"n": 1,
"scriptPubKey": {
"hex": "a91450be45be5aa3ee42827c1eb72370fde4665acfaa87"
}
}
}
]
}
]
},
{
"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"
]
}
]
},
"541226": {
"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"
}
}
},
{
"txid": "9192dcb6bd2751103c6fbcc5378ff7e6522bd9f983de30daa7b31c1a209d8017",
"vout": 0,
"sequence": 4294967295,
"scriptSig": {
"hex": "473044022053e8788b622f6ed5ed69bffb6f2c568d42d1bb398eb6c8b9d7d011e1280c261a0220243a3346883ed0acd7a291140ddc520d43100f60e68fd4a242ec70bae95c097e0121030e2844042326a3dad34937b2a577977b6d13d87a50812f351735fe1ebd999fb7"
],
"vout": [
{
"value": 0.00193000,
"n": 0,
"scriptPubKey": {
"hex": "76a914b18cf542d661fc17a04a29f91b03056e3d7f2aaa88ac"
}
},
{
"value": 0.09806673,
"n": 1,
"scriptPubkey": {
"hex": "76a914194e18702f84b5c87f307662887ca7ec18482bc488ac"
}
}
}
],
"vout": [
{
"value": 0.15218954,
"n": 0,
"scriptPubKey": {
"hex": "76a914a1d8d0f5edd2bf6808d7156f2884096fd86359fe88ac"
]
}
]
},
"541227": {
"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"]
}
},
{
"value": 1.00000000,
"n": 1,
"scriptPubkey": {
"hex": "76a914492f305f58b7469202526bff3200a9f2a446fb4988ac"
],
"vout": [
{
"value": 0.29998854,
"n": 0,
"scriptPubKey": {
"hex": "76a914059272d492ba10fab46e2fe0154ae9200027974d88ac"
}
},
{
"value": 0.70000000,
"n": 1,
"scriptPubKey": {
"hex": "76a9145c02a3783346f0a8cd4c5918c625ab13c5be698e88ac"
}
}
}
]
}
]
]
}
]
},
"541228": {
"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"
}
}
]
}
]
},
"541255": {
"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"
}
}
]
}
]
},
"542838": {
"height": 542838,
"hash": "00000000000000000001e973be0770d428d3494d4e1b661aafcd8924c892648d",
"noTxs": 31,
"txDetails": [
{
"hex": "0100000007763056a30225a7f9f1af67e74a7ec67b514bcf094f8790da301b931ad2c8110e000000006b48304502210095e355273d421b93123786510757e10301508de7c4d9fb0c10900c43b6d6488a02206ddcd52d5e3e09083b50b8e020e2ce3f520e45b7be748e2cf79f1dafc44e54380121037d662ed0eab74137653a05001ada5b16c9b13ee4c542666d762564ebf68e3c8bffffffffbd4d49ec91d404facffca906661a1f437ba2e01f7004a769f01e3841d753e238000000006b4830450221008c92a4c432f4b822d234ee817f544673a86263a2eb106d1fd4189f0e8aeb4f64022031ecf397a0a15eeb41ed636d2729e422f57cb8269a74ddf7696df85055d35c5b012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4ffffffff3e851c0f9f70cb40a2492d811d87a899f6e29a1299f93dc7985e39e2db782559000000006a47304402202f8942571366051dc870be96e602e9415996e7583ecb270d7102b51453c50bb9022021c529a26ea348377863f59b03321279d4775cce1877fb37e3d09eb24b39e45b012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4ffffffffc9f4fc77c61640d945f14cbf9c7ecc2a89421ed03c3c593858915c995a009273000000006b483045022100e83039eb8d55b21f90a0fbae004bb3b0f0681b7e7a4f1bfceadbe5b470cb483702203be7ce3567967a2a1cdaeb3bd47d57cffd645557ab504d1056c3294c08ec08a6012103f3422741129eb0bfb82ca77f3818371749de4c38eb5f8def51a0abfb81cc1957ffffffff395717f7dfdec0df3e2117966af1e43eca5515f7469618d403a1383b5e4bca8a020000006a4730440220071c6128864f4ee9437b27a9ccdec52961f6a481c5ba01ae993a20a1d9afdfa20220248a5209af572f0af3c922e96def6c8b1967e355a8dfeaa891e69f59c9a37e96012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4ffffffff2e9ea71c6811838ec46444f08fad8298f8fa83cb1190ccaf98950963f0e68199000000006b483045022100d84f351d070103e614487ef7e8a7f5a6d60b6c51566aa1262111f8be1cad47d702203e0f36fec153718d3ba3a1c7dd8dd7f28691f4673498680d7f094b83db357aa4012103c71e9c61b2092b415b8ae4d5107786770cff927196efd12e39ed55477baefcdeffffffff39e161ac068370063eddd3ede85c9301f23e088f1ee72ac8e12603d7e0e8d6d6000000006a473044022053eb77ad34f47bcf00fdc845119d673948e2c6c0d21e6ec82d903598b32194df0220379127f024b85b964b52d3a7f501d7dad0b77321675d5d1ba6f0dc8f09305837012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4ffffffff02bb430000000000001976a9147270edc07db25cd4a7b99e54fd3c0c76076ba02688ac00e1f5050000000017a9141458b33294f0791f682c0d181ea757730faeeca88700000000",
"txid": "17da91b3b0590ee84b51174a76c0a61126d2ce82830230371cae549e0c070cd0",
"blocktime": 1537791290,
"time": 1537791290,
"locktime": 0,
"version": 1,
"vin": [
{
"txid": "48304502210095e355273d421b93123786510757e10301508de7c4d9fb0c10900c43b6d6488a02206ddcd52d5e3e09083b50b8e020e2ce3f520e45b7be748e2cf79f1dafc44e54380121037d662ed0eab74137653a05001ada5b16c9b13ee4c542666d762564ebf68e3c8b",
"vout": 0,
"sequence": 4294967295,
"scriptSig": {
"hex": "48304502210095e355273d421b93123786510757e10301508de7c4d9fb0c10900c43b6d6488a02206ddcd52d5e3e09083b50b8e020e2ce3f520e45b7be748e2cf79f1dafc44e54380121037d662ed0eab74137653a05001ada5b16c9b13ee4c542666d762564ebf68e3c8b"
}
},
{
"txid": "38e253d741381ef069a704701fe0a27b431f1a6606a9fccffa04d491ec494dbd",
"vout": 0,
"sequence": 4294967295,
"scriptSig": {
"hex": "4830450221008c92a4c432f4b822d234ee817f544673a86263a2eb106d1fd4189f0e8aeb4f64022031ecf397a0a15eeb41ed636d2729e422f57cb8269a74ddf7696df85055d35c5b012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4"
}
},
{
"txid": "592578dbe2395e98c73df999129ae2f699a8871d812d49a240cb709f0f1c853e",
"vout": 0,
"sequence": 4294967295,
"scriptSig": {
"hex": "47304402202f8942571366051dc870be96e602e9415996e7583ecb270d7102b51453c50bb9022021c529a26ea348377863f59b03321279d4775cce1877fb37e3d09eb24b39e45b012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4"
}
},
{
"txid": "7392005a995c915838593c3cd01e42892acc7e9cbf4cf145d94016c677fcf4c9",
"vout": 0,
"sequence": 4294967295,
"scriptSig": {
"hex": "483045022100e83039eb8d55b21f90a0fbae004bb3b0f0681b7e7a4f1bfceadbe5b470cb483702203be7ce3567967a2a1cdaeb3bd47d57cffd645557ab504d1056c3294c08ec08a6012103f3422741129eb0bfb82ca77f3818371749de4c38eb5f8def51a0abfb81cc1957"
}
},
{
"txid": "8aca4b5e3b38a103d4189646f71555ca3ee4f16a9617213edfc0dedff7175739",
"vout": 2,
"sequence": 4294967295,
"scriptSig": {
"hex": "4730440220071c6128864f4ee9437b27a9ccdec52961f6a481c5ba01ae993a20a1d9afdfa20220248a5209af572f0af3c922e96def6c8b1967e355a8dfeaa891e69f59c9a37e96012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4"
}
},
{
"txid": "9981e6f063099598afcc9011cb83faf89882ad8ff04464c48e8311681ca79e2e",
"vout": 0,
"sequence": 4294967295,
"scriptSig": {
"hex": "483045022100d84f351d070103e614487ef7e8a7f5a6d60b6c51566aa1262111f8be1cad47d702203e0f36fec153718d3ba3a1c7dd8dd7f28691f4673498680d7f094b83db357aa4012103c71e9c61b2092b415b8ae4d5107786770cff927196efd12e39ed55477baefcde"
}
},
{
"txid": "d6d6e8e0d70326e1c82ae71e8f083ef201935ce8edd3dd3e06708306ac61e139",
"vout": 0,
"sequence": 4294967295,
"scriptSig": {
"hex": "473044022053eb77ad34f47bcf00fdc845119d673948e2c6c0d21e6ec82d903598b32194df0220379127f024b85b964b52d3a7f501d7dad0b77321675d5d1ba6f0dc8f09305837012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4"
}
}
],
"vout": [
{
"value": 0.00017339,
"n": 0,
"scriptPubKey": {
"hex": "76a9147270edc07db25cd4a7b99e54fd3c0c76076ba02688ac"
}
},
{
"value": 1.00000000,
"n": 1,
"scriptPubKey": {
"hex": "a9141458b33294f0791f682c0d181ea757730faeeca887"
}
}
]
}
]
}
}
]
},
"handleFork": {
"syncRanges": [
{"lower": 541224, "upper": 541255},
{"lower": 542835, "upper": 542838}
],
"fakeBlocks": {
"541253": {
"height": 541253,
"hash": "0000000000000000001af0987bd9f319c5a8105537b3deb54c423b299b133bb6"
},
"541254": {
"height": 541254,
"hash": "00000000000000000011eebe554e91cf26ba7897195b42997b03ec4f929b27d2"
},
"541255": {
"height": 541255,
"hash": "0000000000000000000feb2cbefe55cb4bb8cf045fac7ed4b4cc09e8f645a64c"
},
"542836": {
"height": 542836,
"hash": "000000000000000000218866194a2bc15b92d7dccca1d328a2fa6a9c0befb039"
},
"542837": {
"height": 542837,
"hash": "00000000000000000023936c5189062f9f9b05895d778c202e2f83f0d119a370"
},
"542838": {
"height": 542838,
"hash": "000000000000000000155f1289c445127d0cfc360b8cc9f2c46c5850de607307"
}
},
"realBlocks": {
"541253": {
"height": 541253,
"hash": "000000000000000000045d55641e32a86ff313cd9d8a557d0fe9d0ab4e7eae7f"
},
"541254": {
"height": 541254,
"hash": "0000000000000000000e08972d3e7e26f30c58313dcd15ce5817b09aef0e69b7"
},
"541255": {
"height": 541255,
"hash": "0000000000000000001375c3e88b4e53b687a70aba4b645ba81c93a4539d724d"
},
"542836": {
"height": 542836,
"hash": "0000000000000000000927c61a2a3174c64f2beee103ead0f62b30354f323456"
},
"542837": {
"height": 542837,
"hash": "000000000000000000158353b8c9865c2261dc96bd780ef7d0017b2c2798c5c7"
},
"542838": {
"height": 542838,
"hash": "00000000000000000001e973be0770d428d3494d4e1b661aafcd8924c892648d"
}
}
}
}

View File

@ -0,0 +1,211 @@
{
"connectBlocks": {
"syncRanges": [
{"lower": 1414976, "upper": 1414996}
],
"blocks": {
"1414996": {
"height": 1414996,
"hash": "00000000c55bb26fac57344bcb4a2c26579ca009a0e39ab25d9dacccbc0f6dc5",
"noTxs": 61,
"txDetails": [
{
"hex": "02000000012735ecfd2c0f7948e004b8084fc75b4c37026d0b287d9d4766ab445b5938ca00000000006b483045022100de880217ae17daeff18b9533fbacd4090b8440f61a967c6fed0226c33d609b0b02201f4c01e967e6242c3e93a5da63c7a882b7fa97f4b389b4df96453edce7c051760121035610f74fe29e7065cf5ec6ab97d145fd4de788512c3b73a93f4177585c0f085affffffff0280969800000000001976a9141689469a15bff8d874f11e3b5355737d9e6dbbc488ac5c384703000000001976a914a13dbce34513db0f24c7d533b27a6ae257e4652d88ac00000000",
"txid": "481386544e3bdfa57d0b868cb680b7bd1b5ae41d01bb8be4e298cad1577ac208",
"time": 1538659297,
"blocktime": 1538659297,
"version": 2,
"vin": [
{
"txid": "00ca38595b44ab66479d7d280b6d02374c5bc74f08b804e048790f2cfdec3527",
"vout": 0,
"scriptSig": {
"hex": "483045022100de880217ae17daeff18b9533fbacd4090b8440f61a967c6fed0226c33d609b0b02201f4c01e967e6242c3e93a5da63c7a882b7fa97f4b389b4df96453edce7c051760121035610f74fe29e7065cf5ec6ab97d145fd4de788512c3b73a93f4177585c0f085a"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.1,
"n": 0,
"scriptPubKey": {
"hex": "76a9141689469a15bff8d874f11e3b5355737d9e6dbbc488ac"
}
},
{
"value": 0.54999132,
"n": 1,
"scriptPubKey": {
"hex": "76a914a13dbce34513db0f24c7d533b27a6ae257e4652d88ac"
}
}
]
},
{
"hex": "01000000023198e0c324fc3b0361d8a54069de19879b56d728d4d8d0fa96fce4445b7e2045000000006b483045022100a7d66715aae07e3086d6c13210c2b0faf49d9068c2cdb12ff8cf65884abc833c0220116c502fe699053b43b520da08deeef78d8a86b33c6e9866856a659ae8142f0d01210376f1c7f3ef21133651d7df9dbb37602beb82a20f3791f4518d38dccae9d6f165feffffff2b0e1fc2ca525eccf44b868d18189fb853a54e4646626051a93e9819809a90c8000000006a4730440220508f022bd2f7f016535309eba6d8906297e32fd678d9f8d4d1ae2e3ad026734e0220434239e26b3e60f29e25d46fb8c5d00c8e9ab3fb2c50490c667d50d8d8abdc520121022a9bb7d67b6087ffdc8e071a427ed29fb5569f5fdeab981fc4cb321c0c135f59feffffff0210270000000000001976a9145731d95537f4ce729a141690bccea71fecf533bb88ac5e580f00000000001976a914e9ad64791d16f8317020e53a31c992b832094d3088ac53971500",
"txid": "bdcb85ad80693488efcb29f747040bc78ce3d3ba3dd298a4b1e2f9a55d15e6e8",
"time": 1538659297,
"blocktime": 1538659297,
"version": 1,
"vin": [
{
"txid": "45207e5b44e4fc96fad0d8d428d7569b8719de6940a5d861033bfc24c3e09831",
"vout": 0,
"scriptSig": {
"hex": "483045022100a7d66715aae07e3086d6c13210c2b0faf49d9068c2cdb12ff8cf65884abc833c0220116c502fe699053b43b520da08deeef78d8a86b33c6e9866856a659ae8142f0d01210376f1c7f3ef21133651d7df9dbb37602beb82a20f3791f4518d38dccae9d6f165"
},
"sequence": 4294967294
},
{
"txid": "c8909a8019983ea951606246464ea553b89f18188d864bf4cc5e52cac21f0e2b",
"vout": 0,
"scriptSig": {
"hex": "4730440220508f022bd2f7f016535309eba6d8906297e32fd678d9f8d4d1ae2e3ad026734e0220434239e26b3e60f29e25d46fb8c5d00c8e9ab3fb2c50490c667d50d8d8abdc520121022a9bb7d67b6087ffdc8e071a427ed29fb5569f5fdeab981fc4cb321c0c135f59"
},
"sequence": 4294967294
}
],
"vout": [
{
"value": 0.0001,
"n": 0,
"scriptPubKey": {
"hex": "76a9145731d95537f4ce729a141690bccea71fecf533bb88ac"
}
},
{
"value": 0.01005662,
"n": 1,
"scriptPubKey": {
"hex": "76a914e9ad64791d16f8317020e53a31c992b832094d3088ac"
}
}
]
}
]
},
"1414983": {
"height": 1414983,
"hash": "00000000128873524843cec7a4a5718736f7b3c80d6936414e6b5b01b25be443",
"noTxs": 53,
"txDetails": [
{
"txid": "a485c102d173cd577d6c9d2594c3584e45def4290f8832a6a47817862dba7799",
"version": 1,
"vin": [
{
"txid": "d60199408b33442d7b16de2dcf2538a815575774150e485f58a573a4b51c13e0",
"vout": 1,
"scriptSig": {
"hex": "483045022100c544080e0f8a02d4f6d76d343da538d4ee348d716f4a1984e440d063ac4230f3022044e987da4e6baf5505876a269a090b3f37a19de8c09f3a4ad0bff4fdaff604e60121025fcc6fa8cea73b326f50d8f4b6aa05a47bc9672b1ba4cb5427d013df9d0c6141"
},
"sequence": 4294967293
}
],
"vout": [
{
"value": 0.01,
"n": 0,
"scriptPubKey": {
"hex": "76a91472cbe0f81ef95f9a628c81a5c640d70074d3affb88ac"
}
},
{
"value": 0.09962844,
"n": 1,
"scriptPubKey": {
"hex": "76a91457753cae491018bb85ef2a2b6608720c968a291e88ac"
}
}
],
"hex": "0100000001e0131cb5a473a5585f480e1574575715a83825cf2dde167b2d44338b409901d6010000006b483045022100c544080e0f8a02d4f6d76d343da538d4ee348d716f4a1984e440d063ac4230f3022044e987da4e6baf5505876a269a090b3f37a19de8c09f3a4ad0bff4fdaff604e60121025fcc6fa8cea73b326f50d8f4b6aa05a47bc9672b1ba4cb5427d013df9d0c6141fdffffff0240420f00000000001976a91472cbe0f81ef95f9a628c81a5c640d70074d3affb88ac5c059800000000001976a91457753cae491018bb85ef2a2b6608720c968a291e88ac46971500",
"time": 1538643603,
"blocktime": 1538643603
},
{
"txid": "06d5505f7e78c9979fdcf9507a88a6d386318a752ac5f49d4aa02eeb70bcf873",
"version": 1,
"vin": [
{
"txid": "5624a2579c3bf48f50008c6d8e5489af61a6d57b97aa802d7bc0d07d1d94bd9a",
"vout": 2,
"scriptSig": {
"hex": "4830450221009a12c53cd7264c2658035a6492c517b6719bd8908e8271ed0c270a44bcda8a260220336351e99c8fdc2a3b752a0e8f1c260ac9e0ac3eb2a5f0d09d347badf33dc7fc012103c043bdba1042b9abafed3bd48ebe43b9a2f0b5f30a34246551130933aa8c2cd1"
},
"sequence": 4294967295
},
{
"txid": "0bb54703799d5346d19caf320a6363b42444d6741b1a322072033bff7f74f763",
"vout": 0,
"scriptSig": {
"hex": "473044022005753709e182bb4cc5770c54b483907e9fa2658fc4dfac774121b4d7b31da340022033ac23bce026c730306a293f8c6de5cd4c557e5cc3e7b70224e1ace1cae64268012102a1f29b6570e5001e526f7bf296ab607539722726f672e7f07c59189cc49afdc8"
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 1.233122,
"n": 0,
"scriptPubKey": {
"hex": "76a914a617880fd0e8118f06e8db07ad09f3e381205aee88ac"
}
},
{
"value": 0,
"n": 1,
"scriptPubKey": {
"hex": "6a146f6d6e6900000000000000010000000000019577"
}
},
{
"value": 0.00000546,
"n": 2,
"scriptPubKey": {
"hex": "76a914277d14e23c46f50b43ae63fe6ba420f4d1568fb788ac"
}
}
],
"hex": "01000000029abd941d7dd0c07b2d80aa977bd5a661af89548e6d8c00508ff43b9c57a22456020000006b4830450221009a12c53cd7264c2658035a6492c517b6719bd8908e8271ed0c270a44bcda8a260220336351e99c8fdc2a3b752a0e8f1c260ac9e0ac3eb2a5f0d09d347badf33dc7fc012103c043bdba1042b9abafed3bd48ebe43b9a2f0b5f30a34246551130933aa8c2cd1ffffffff63f7747fff3b037220321a1b74d64424b463630a32af9cd146539d790347b50b000000006a473044022005753709e182bb4cc5770c54b483907e9fa2658fc4dfac774121b4d7b31da340022033ac23bce026c730306a293f8c6de5cd4c557e5cc3e7b70224e1ace1cae64268012102a1f29b6570e5001e526f7bf296ab607539722726f672e7f07c59189cc49afdc8ffffffff0348985907000000001976a914a617880fd0e8118f06e8db07ad09f3e381205aee88ac0000000000000000166a146f6d6e690000000000000001000000000001957722020000000000001976a914277d14e23c46f50b43ae63fe6ba420f4d1568fb788ac00000000",
"time": 1538643603,
"blocktime": 1538643603
}
]
}
}
},
"handleFork": {
"syncRanges": [
{"lower": 1414526, "upper": 1414546}
],
"fakeBlocks": {
"1414544": {
"height": 1414544,
"hash": "00000000aa4bbe624efc4cc0be98f1d49b03abca094a9785ad1e50c20e7634b1"
},
"1414545": {
"height": 1414545,
"hash": "0000000000035fd65c471e0645190d5392de4dc626358bc001c753ade3b8a44c"
},
"1414546": {
"height": 1414546,
"hash": "000000000003b4360cebb2c29d7798c22ae31b8ba83941ebed0c61fb56beb765"
}
},
"realBlocks": {
"1414544": {
"height": 1414544,
"hash": "0000000000012b55c84faf61e201a1c64c6a57e8be3d2b5e11868c39cab5027e"
},
"1414545": {
"height": 1414545,
"hash": "0000000017020556bdeac9d3bbd2361d11aba79ac39f8a161170ac22d225856d"
},
"1414546": {
"height": 1414546,
"hash": "0000000000000000b169d17aee62e9f5758a38c7bead2676fc4e4a22a51f17b9"
}
}
}
}

View File

@ -9,11 +9,13 @@
},
"bitcoin": {
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync",
"EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"]
"EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"],
"sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"]
},
"bitcoin_testnet": {
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync",
"EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"]
"EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"],
"sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"]
},
"dash": {
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync",