From a69f79f955b9aa9f3372e6c0b6c74d1995b649aa Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Fri, 21 Sep 2018 16:01:23 +0200 Subject: [PATCH 01/15] Parallel sync improved in order to write all blocks and handle OS signal --- db/sync.go | 72 ++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/db/sync.go b/db/sync.go index 6d5fd713..ca31dff3 100644 --- a/db/sync.go +++ b/db/sync.go @@ -203,14 +203,15 @@ func (w *SyncWorker) ConnectBlocksParallel(lower, higher uint32) error { } var err error var wg sync.WaitGroup - bch := make(chan *bchain.Block, w.syncWorkers) + bch := make([]chan *bchain.Block, w.syncWorkers) + for i := 0; i < w.syncWorkers; i++ { + bch[i] = make(chan *bchain.Block) + } hch := make(chan hashHeight, w.syncWorkers) hchClosed := atomic.Value{} hchClosed.Store(false) - var getBlockMux sync.Mutex - getBlockCond := sync.NewCond(&getBlockMux) - lastConnectedBlock := lower - 1 writeBlockDone := make(chan struct{}) + terminating := make(chan struct{}) writeBlockWorker := func() { defer close(writeBlockDone) bc, err := w.db.InitBulkConnect() @@ -219,15 +220,25 @@ func (w *SyncWorker) ConnectBlocksParallel(lower, higher uint32) error { } lastBlock := lower - 1 keep := uint32(w.chain.GetChainParser().KeepBlockAddresses()) - for b := range bch { - if lastBlock+1 != b.Height { - glog.Error("writeBlockWorker skipped block, last connected block", lastBlock, ", new block ", b.Height) + WriteBlockLoop: + for { + select { + case b := <-bch[(lastBlock+1)%uint32(w.syncWorkers)]: + if b == nil { + // channel is closed and empty - work is done + break WriteBlockLoop + } + if b.Height != lastBlock+1 { + glog.Fatal("writeBlockWorker skipped block, expected block ", lastBlock+1, ", new block ", b.Height) + } + err := bc.ConnectBlock(b, b.Height+keep > higher) + if err != nil { + glog.Fatal("writeBlockWorker ", b.Height, " ", b.Hash, " error ", err) + } + lastBlock = b.Height + case <-terminating: + break WriteBlockLoop } - err := bc.ConnectBlock(b, b.Height+keep > higher) - if err != nil { - glog.Error("writeBlockWorker ", b.Height, " ", b.Hash, " error ", err) - } - lastBlock = b.Height } err = bc.Close() if err != nil { @@ -239,6 +250,7 @@ func (w *SyncWorker) ConnectBlocksParallel(lower, higher uint32) error { defer wg.Done() var err error var block *bchain.Block + GetBlockLoop: for hh := range hch { for { block, err = w.chain.GetBlock(hh.hash, hh.height) @@ -258,24 +270,11 @@ func (w *SyncWorker) ConnectBlocksParallel(lower, higher uint32) error { if w.dryRun { continue } - getBlockMux.Lock() - for { - // we must make sure that the blocks are written to db in the correct order - if lastConnectedBlock+1 == hh.height { - // we have the right block, pass it to the writeBlockWorker - lastConnectedBlock = hh.height - bch <- block - getBlockCond.Broadcast() - break - } - // break the endless loop on OS signal - if hchClosed.Load() == true { - break - } - // wait for the time this block is top be passed to the writeBlockWorker - getBlockCond.Wait() + select { + case bch[hh.height%uint32(w.syncWorkers)] <- block: + case <-terminating: + break GetBlockLoop } - getBlockMux.Unlock() } glog.Info("getBlockWorker ", i, " exiting...") } @@ -292,6 +291,8 @@ ConnectLoop: select { case <-w.chanOsSignal: err = errors.Errorf("connectBlocksParallel interrupted at height %d", h) + // signal all workers to terminate their loops (error loops are interrupted below) + close(terminating) break ConnectLoop default: hash, err = w.chain.GetBlockHash(h) @@ -315,16 +316,13 @@ ConnectLoop: } } close(hch) - // signal stop to workers that are in a loop + // signal stop to workers that are in a error loop hchClosed.Store(true) - // broadcast syncWorkers times to unstuck all waiting getBlockWorkers - for i := 0; i < w.syncWorkers; i++ { - getBlockCond.Broadcast() - } - // first wait for the getBlockWorkers to finish and then close bch channel - // so that the getBlockWorkers do not write to the closed channel + // wait for workers and close bch that will stop writer loop wg.Wait() - close(bch) + for i := 0; i < w.syncWorkers; i++ { + close(bch[i]) + } <-writeBlockDone return err } From e08e923ee87bf0658645c4e55f1be366a60f2b7e Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Mon, 24 Sep 2018 13:30:09 +0200 Subject: [PATCH 02/15] Compare strings instead of floats --- tests/sync/sync.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/sync/sync.go b/tests/sync/sync.go index 5a8e56ed..fb7d4c32 100644 --- a/tests/sync/sync.go +++ b/tests/sync/sync.go @@ -12,6 +12,7 @@ import ( "path/filepath" "reflect" "sort" + "strings" "testing" ) @@ -328,19 +329,16 @@ func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler) { 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 { + if taInfo.valOutSat.Cmp(&txInfo.valOutSat) != 0 { t.Errorf("Tx %s: total output amount mismatch: got %s, want %s", - tx.Txid, taValOut.String(), txValOut.String()) + tx.Txid, taInfo.valOutSat.String(), txInfo.valOutSat.String()) } - treshold := big.NewFloat(0.0001) - if new(big.Float).Sub(taValIn, taValOut).Cmp(treshold) > 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, taValIn.String(), taValOut.String(), treshold) + tx.Txid, taInfo.valInSat.String(), taInfo.valOutSat.String(), treshold) } } } From d67eae31bbdd336df0b907d2f59ea57a4ca386d1 Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Mon, 24 Sep 2018 15:29:22 +0200 Subject: [PATCH 03/15] Test fee amount only for transactions with inputs --- tests/sync/sync.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/sync/sync.go b/tests/sync/sync.go index fb7d4c32..0a5d47a9 100644 --- a/tests/sync/sync.go +++ b/tests/sync/sync.go @@ -334,11 +334,13 @@ func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler) { tx.Txid, taInfo.valOutSat.String(), txInfo.valOutSat.String()) } - 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) + 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) + } } } } From 2531c789dd6022ddaa7da4b53e50ad6fbcac814c Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Mon, 24 Sep 2018 15:30:30 +0200 Subject: [PATCH 04/15] Allow multiple block ranges for sync test --- tests/sync/sync.go | 156 +++++++++++++++---------------- tests/sync/testdata/bitcoin.json | 113 +++++++++++++++++++--- 2 files changed, 178 insertions(+), 91 deletions(-) diff --git a/tests/sync/sync.go b/tests/sync/sync.go index 0a5d47a9..65c29079 100644 --- a/tests/sync/sync.go +++ b/tests/sync/sync.go @@ -11,7 +11,6 @@ import ( "os" "path/filepath" "reflect" - "sort" "strings" "testing" ) @@ -28,12 +27,14 @@ 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"` + SyncRanges []Range `json:"syncRanges"` + Blocks map[uint32]BlockInfo `json:"blocks"` } type BlockInfo struct { @@ -111,10 +112,6 @@ 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 } @@ -154,8 +151,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) } @@ -167,13 +179,9 @@ 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) } @@ -182,42 +190,50 @@ func withRocksDBAndSyncWorker(t *testing.T, h *TestHandler, fn func(*db.RocksDB, } 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 + for _, rng := range h.TestData.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(lowerHeight, upperHeight) - 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 != upperHeight { - t.Fatalf("Upper block height mismatch: %d != %d", height, upperHeight) - } - if hash != upperHash { - t.Fatalf("Upper block hash mismatch: %s != %s", hash, upperHash) - } + 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) }) - t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h) }) - t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h) }) - }) + 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) { - for _, block := range h.TestData.Blocks { - bi, err := d.GetBlockInfo(block.Height) +func verifyBlockInfo(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) { + for height := rng.Lower; height <= rng.Upper; height++ { + block, found := h.TestData.Blocks[height] + if !found { + continue + } + + bi, err := d.GetBlockInfo(height) if err != nil { - t.Errorf("GetBlockInfo(%d) error: %s", block.Height, err) + t.Errorf("GetBlockInfo(%d) error: %s", height, err) continue } if bi == nil { - t.Errorf("GetBlockInfo(%d) returned nil", block.Height) + t.Errorf("GetBlockInfo(%d) returned nil", height) continue } @@ -231,7 +247,7 @@ func verifyBlockInfo(t *testing.T, d *db.RocksDB, h *TestHandler) { } } -func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler) { +func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) { type txInfo struct { txid string vout uint32 @@ -240,26 +256,13 @@ func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler) { addr2txs := make(map[string][]txInfo) checkMap := make(map[string][]bool) - for _, block := range h.TestData.Blocks { + for height := rng.Lower; height <= rng.Upper; height++ { + block, found := h.TestData.Blocks[height] + if !found { + continue + } + 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}) @@ -275,10 +278,8 @@ func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler) { } } - 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 { + 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 @@ -300,10 +301,15 @@ func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler) { } } -func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler) { +func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) { parser := h.Chain.GetChainParser() - for _, block := range h.TestData.Blocks { + for height := rng.Lower; height <= rng.Upper; height++ { + block, found := h.TestData.Blocks[height] + if !found { + continue + } + for _, tx := range block.TxDetails { ta, err := d.GetTxAddresses(tx.Txid) if err != nil { @@ -316,8 +322,8 @@ func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler) { t.Fatal(err) } - if ta.Height != block.Height { - t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, block.Height) + if ta.Height != height { + t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, height) continue } @@ -394,11 +400,3 @@ func getTaInfo(parser bchain.BlockChainParser, ta *db.TxAddresses) (*txInfo, err return info, nil } - -func satToFloat(parser bchain.BlockChainParser, sat *big.Int) *big.Float { - f, ok := new(big.Float).SetString(parser.AmountToDecimalString(sat)) - if !ok { - return big.NewFloat(-1) - } - return f -} diff --git a/tests/sync/testdata/bitcoin.json b/tests/sync/testdata/bitcoin.json index e4ca6710..f58266c0 100644 --- a/tests/sync/testdata/bitcoin.json +++ b/tests/sync/testdata/bitcoin.json @@ -1,10 +1,10 @@ { - "connectBlocksParallel": { - "syncWorkers": 3, - "syncChunk": 0 - }, - "blocks": [ - { + "syncRanges": [ + {"lower": 541224, "upper": 541255}, + {"lower": 542835, "upper": 542838} + ], + "blocks": { + "541224": { "height": 541224, "hash": "00000000000000000027fe3aca0241ccee4f3b103b2108457a2dfd7490aa41a8", "noTxs": 1880, @@ -77,7 +77,7 @@ } ] }, - { + "541225": { "height": 541225, "hash": "00000000000000000023e1bf6bc0a71fb1340c00f256de589108a4768ea82c76", "noTxs": 1971, @@ -133,7 +133,7 @@ } ] }, - { + "541226": { "height": 541226, "hash": "00000000000000000023fc3b42ff5da9f74c6a672a7f793860f7c03b217628ae", "noTxs": 2479, @@ -174,7 +174,7 @@ } ] }, - { + "541227": { "height": 541227, "hash": "00000000000000000007d2b42e5ea24bed4aa02d18192c33b9ec7f6685acb16a", "noTxs": 1815, @@ -216,7 +216,7 @@ } ] }, - { + "541228": { "height": 541228, "hash": "0000000000000000001c2444541142d58b126ef608b5f52081aced02612d078f", "noTxs": 2549, @@ -257,7 +257,7 @@ } ] }, - { + "541255": { "height": 541255, "hash": "0000000000000000001375c3e88b4e53b687a70aba4b645ba81c93a4539d724d", "noTxs": 2633, @@ -305,6 +305,95 @@ ] } ] + }, + "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" + } + } + ] + } + ] } - ] + } } From db39b5ef51c7309ec9ef92d15d4cd6c9492b57c2 Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Tue, 25 Sep 2018 11:20:58 +0200 Subject: [PATCH 05/15] Interrupt connectBlocks on OS signal while initial sync --- blockbook.go | 4 ++-- db/sync.go | 51 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/blockbook.go b/blockbook.go index a02ed158..c4888ce1 100644 --- a/blockbook.go +++ b/blockbook.go @@ -260,7 +260,7 @@ func main() { } if *synchronize { - if err := syncWorker.ResyncIndex(nil); err != nil { + if err := syncWorker.ResyncIndex(nil, true); err != nil { glog.Error("resyncIndex ", err) return } @@ -392,7 +392,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)) } }) diff --git a/db/sync.go b/db/sync.go index ca31dff3..32093ab6 100644 --- a/db/sync.go +++ b/db/sync.go @@ -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 func(hash string)) error { +func (w *SyncWorker) ResyncIndex(onNewBlock func(hash string), initialSync bool) error { start := time.Now() w.is.StartedSync() - err := w.resyncIndex(onNewBlock) + err := w.resyncIndex(onNewBlock, initialSync) switch err { case nil: @@ -75,7 +75,7 @@ func (w *SyncWorker) ResyncIndex(onNewBlock func(hash string)) error { return err } -func (w *SyncWorker) resyncIndex(onNewBlock func(hash string)) error { +func (w *SyncWorker) resyncIndex(onNewBlock func(hash string), initialSync bool) error { remoteBestHash, err := w.chain.GetBestBlockHash() if err != nil { return err @@ -98,7 +98,7 @@ func (w *SyncWorker) resyncIndex(onNewBlock func(hash string)) 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 @@ -129,13 +129,13 @@ func (w *SyncWorker) resyncIndex(onNewBlock func(hash string)) 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 func(hash string)) error { +func (w *SyncWorker) handleFork(localBestHeight uint32, localBestHash string, onNewBlock func(hash string), initialSync bool) error { // find forked blocks, disconnect them and then synchronize again var height uint32 hashes := []string{localBestHash} @@ -160,18 +160,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 func(hash string)) error { +func (w *SyncWorker) connectBlocks(onNewBlock func(hash string), 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 @@ -186,6 +187,34 @@ func (w *SyncWorker) connectBlocks(onNewBlock func(hash string)) 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 { From 642310eb959295123d06fef86ded2e1ccde1b4a7 Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Wed, 26 Sep 2018 12:19:40 +0200 Subject: [PATCH 06/15] Added tests for single-thread connectBlocks --- db/test_helper.go | 11 + tests/sync/sync.go | 74 ++- tests/sync/testdata/bitcoin.json | 758 ++++++++++++++++--------------- tests/tests.json | 2 +- 4 files changed, 453 insertions(+), 392 deletions(-) create mode 100644 db/test_helper.go diff --git a/db/test_helper.go b/db/test_helper.go new file mode 100644 index 00000000..0fb16814 --- /dev/null +++ b/db/test_helper.go @@ -0,0 +1,11 @@ +// +build integration + +package db + +func ConnectBlocks(w *SyncWorker, onNewBlock func(hash string), initialSync bool) error { + return w.connectBlocks(onNewBlock, initialSync) +} + +func HandleFork(w *SyncWorker, localBestHeight uint32, localBestHash string, onNewBlock func(hash string), initialSync bool) error { + return w.handleFork(localBestHeight, localBestHash, onNewBlock, initialSync) +} diff --git a/tests/sync/sync.go b/tests/sync/sync.go index 65c29079..eaa0fe6a 100644 --- a/tests/sync/sync.go +++ b/tests/sync/sync.go @@ -6,6 +6,7 @@ import ( "blockbook/db" "encoding/json" "errors" + "fmt" "io/ioutil" "math/big" "os" @@ -16,9 +17,9 @@ import ( ) var testMap = map[string]func(t *testing.T, th *TestHandler){ - // "ConnectBlocks": nil, + "ConnectBlocks": testConnectBlocks, "ConnectBlocksParallel": testConnectBlocksParallel, - // "DisconnectBlocks": nil, + // "HandleFork": testHandleFork, } type TestHandler struct { @@ -33,13 +34,24 @@ type Range struct { } type TestData struct { - SyncRanges []Range `json:"syncRanges"` - Blocks map[uint32]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"` } @@ -56,10 +68,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) @@ -92,7 +103,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 { @@ -189,8 +200,45 @@ func withRocksDBAndSyncWorker(t *testing.T, h *TestHandler, startHeight uint32, fn(d, sw, ch) } +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) { + 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.SyncRanges { + 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 { @@ -222,7 +270,7 @@ func testConnectBlocksParallel(t *testing.T, h *TestHandler) { func verifyBlockInfo(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) { for height := rng.Lower; height <= rng.Upper; height++ { - block, found := h.TestData.Blocks[height] + block, found := h.TestData.ConnectBlocks.Blocks[height] if !found { continue } @@ -257,7 +305,7 @@ func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) checkMap := make(map[string][]bool) for height := rng.Lower; height <= rng.Upper; height++ { - block, found := h.TestData.Blocks[height] + block, found := h.TestData.ConnectBlocks.Blocks[height] if !found { continue } @@ -305,7 +353,7 @@ 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.Blocks[height] + block, found := h.TestData.ConnectBlocks.Blocks[height] if !found { continue } diff --git a/tests/sync/testdata/bitcoin.json b/tests/sync/testdata/bitcoin.json index f58266c0..457332ce 100644 --- a/tests/sync/testdata/bitcoin.json +++ b/tests/sync/testdata/bitcoin.json @@ -1,399 +1,401 @@ { - "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" + "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" - } - } - ] - } - ] - }, - "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" - } - } - ], - "vout": [ - { - "value": 0.01387900, - "n": 0, - "scriptPubKey": { - "hex": "a91473829585e38acb264c21ab7e1d8d4814337c4ba287" - } - }, - { - "value": 0.00026697, - "n": 1, - "scriptPubKey": { - "hex": "a91450be45be5aa3ee42827c1eb72370fde4665acfaa87" - } - } - ] - } - ] - }, - "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" - } - } - ], - "vout": [ - { - "value": 0.00193000, - "n": 0, - "scriptPubKey": { - "hex": "76a914b18cf542d661fc17a04a29f91b03056e3d7f2aaa88ac" - } - }, - { - "value": 0.09806673, - "n": 1, - "scriptPubkey": { - "hex": "76a914194e18702f84b5c87f307662887ca7ec18482bc488ac" - } - } - ] - } - ] - }, - "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" + ], + "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" + } } - } - ] - } - ] - }, - "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.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" + } } - } - ] - } - ] - }, - "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" + ] + } + ] + }, + "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" + } } - } - ] - } - ] - }, - "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" + ] + } + ] + }, + "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" + } } - }, - { - "txid": "38e253d741381ef069a704701fe0a27b431f1a6606a9fccffa04d491ec494dbd", - "vout": 0, - "sequence": 4294967295, - "scriptSig": { - "hex": "4830450221008c92a4c432f4b822d234ee817f544673a86263a2eb106d1fd4189f0e8aeb4f64022031ecf397a0a15eeb41ed636d2729e422f57cb8269a74ddf7696df85055d35c5b012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4" + ], + "vout": [ + { + "value": 0.01179517, + "n": 0, + "scriptPubKey": { + "hex": "a91401dfd360c8b640f9da5ede7170dc369d84c3dfcf87" + } + }, + { + "value": 0.00210000, + "n": 1, + "scriptPubkey": { + "hex": "a91454f7c31d5a1aac2f6b3d6b4773f62f333e2e8af887" + } } - }, - { - "txid": "592578dbe2395e98c73df999129ae2f699a8871d812d49a240cb709f0f1c853e", - "vout": 0, - "sequence": 4294967295, - "scriptSig": { - "hex": "47304402202f8942571366051dc870be96e602e9415996e7583ecb270d7102b51453c50bb9022021c529a26ea348377863f59b03321279d4775cce1877fb37e3d09eb24b39e45b012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4" + ] + } + ] + }, + "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" + } } - }, - { - "txid": "7392005a995c915838593c3cd01e42892acc7e9cbf4cf145d94016c677fcf4c9", - "vout": 0, - "sequence": 4294967295, - "scriptSig": { - "hex": "483045022100e83039eb8d55b21f90a0fbae004bb3b0f0681b7e7a4f1bfceadbe5b470cb483702203be7ce3567967a2a1cdaeb3bd47d57cffd645557ab504d1056c3294c08ec08a6012103f3422741129eb0bfb82ca77f3818371749de4c38eb5f8def51a0abfb81cc1957" + ], + "vout": [ + { + "value": 0.15218954, + "n": 0, + "scriptPubKey": { + "hex": "76a914a1d8d0f5edd2bf6808d7156f2884096fd86359fe88ac" + } + }, + { + "value": 1.00000000, + "n": 1, + "scriptPubkey": { + "hex": "76a914492f305f58b7469202526bff3200a9f2a446fb4988ac" + } } - }, - { - "txid": "8aca4b5e3b38a103d4189646f71555ca3ee4f16a9617213edfc0dedff7175739", - "vout": 2, - "sequence": 4294967295, - "scriptSig": { - "hex": "4730440220071c6128864f4ee9437b27a9ccdec52961f6a481c5ba01ae993a20a1d9afdfa20220248a5209af572f0af3c922e96def6c8b1967e355a8dfeaa891e69f59c9a37e96012102940f4b3a9737f208c8cb1fe5ae8b317d06d0ca6e63dca5a04f735eb7d42377e4" + ] + } + ] + }, + "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" + } } - }, - { - "txid": "9981e6f063099598afcc9011cb83faf89882ad8ff04464c48e8311681ca79e2e", - "vout": 0, - "sequence": 4294967295, - "scriptSig": { - "hex": "483045022100d84f351d070103e614487ef7e8a7f5a6d60b6c51566aa1262111f8be1cad47d702203e0f36fec153718d3ba3a1c7dd8dd7f28691f4673498680d7f094b83db357aa4012103c71e9c61b2092b415b8ae4d5107786770cff927196efd12e39ed55477baefcde" + ], + "vout": [ + { + "value": 0.00017339, + "n": 0, + "scriptPubKey": { + "hex": "76a9147270edc07db25cd4a7b99e54fd3c0c76076ba02688ac" + } + }, + { + "value": 1.00000000, + "n": 1, + "scriptPubKey": { + "hex": "a9141458b33294f0791f682c0d181ea757730faeeca887" + } } - }, - { - "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" - } - } - ] - } - ] + ] + } + ] + } } } } diff --git a/tests/tests.json b/tests/tests.json index 5abe6990..db3335c5 100644 --- a/tests/tests.json +++ b/tests/tests.json @@ -10,7 +10,7 @@ "bitcoin": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], - "sync": ["ConnectBlocksParallel"] + "sync": ["ConnectBlocksParallel", "ConnectBlocks"] }, "bitcoin_testnet": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", From 5000c01f1105255d8bf09707575f058ed94a2341 Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Mon, 1 Oct 2018 14:32:40 +0200 Subject: [PATCH 07/15] WIP: sync tests --- db/test_helper.go | 4 + tests/sync/connectblocks.go | 269 ++++++++++++++++++++++ tests/sync/fakechain.go | 53 +++++ tests/sync/handlefork.go | 238 ++++++++++++++++++++ tests/sync/sync.go | 274 +---------------------- tests/sync/testdata/bitcoin.json | 58 +++++ tests/sync/testdata/bitcoin_testnet.json | 35 +++ tests/tests.json | 5 +- 8 files changed, 670 insertions(+), 266 deletions(-) create mode 100644 tests/sync/connectblocks.go create mode 100644 tests/sync/fakechain.go create mode 100644 tests/sync/handlefork.go create mode 100644 tests/sync/testdata/bitcoin_testnet.json diff --git a/db/test_helper.go b/db/test_helper.go index ce879684..5d5d4984 100644 --- a/db/test_helper.go +++ b/db/test_helper.go @@ -6,6 +6,10 @@ 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) } diff --git a/tests/sync/connectblocks.go b/tests/sync/connectblocks.go new file mode 100644 index 00000000..af4a0590 --- /dev/null +++ b/tests/sync/connectblocks.go @@ -0,0 +1,269 @@ +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, _, err := ta.Inputs[i].Addresses(parser) + if err != nil { + return nil, err + } + for _, a := range addrs { + if !strings.HasPrefix(a, "OP_") { + info.inputs = append(info.inputs, a) + } + } + } + + 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 + } + for _, a := range addrs { + if !strings.HasPrefix(a, "OP_") { + info.outputs = append(info.outputs, a) + } + } + } + + return info, nil +} diff --git a/tests/sync/fakechain.go b/tests/sync/fakechain.go new file mode 100644 index 00000000..2000e39d --- /dev/null +++ b/tests/sync/fakechain.go @@ -0,0 +1,53 @@ +package sync + +import ( + "blockbook/bchain" + "errors" +) + +type fakeBlockChain struct { + bchain.BlockChain + returnFakes bool + fakeBlocks map[uint32]BlockID + fakeBestHeight uint32 +} + +func (c *fakeBlockChain) GetBestBlockHash() (v string, err error) { + if !c.returnFakes { + return c.BlockChain.GetBestBlockHash() + } + if b, found := c.fakeBlocks[c.fakeBestHeight]; found { + return b.Hash, nil + } else { + return "", errors.New("Not found") + } +} + +func (c *fakeBlockChain) GetBestBlockHeight() (v uint32, err error) { + if !c.returnFakes { + return c.BlockChain.GetBestBlockHeight() + } + return c.fakeBestHeight, nil +} + +func (c *fakeBlockChain) GetBlockHash(height uint32) (v string, err error) { + 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 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) +} diff --git a/tests/sync/handlefork.go b/tests/sync/handlefork.go new file mode 100644 index 00000000..759cdcf7 --- /dev/null +++ b/tests/sync/handlefork.go @@ -0,0 +1,238 @@ +package sync + +import ( + "blockbook/bchain" + "blockbook/db" + "errors" + "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) + + verifyTransactionsXXX(t, d, rng, fakeAddr2txs, true) + // verifyAddressesXXX(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) + + verifyTransactionsXXX(t, d, rng, fakeAddr2txs, false) + verifyTransactionsXXX(t, d, rng, realAddr2txs, true) + // verifyAddressesXXX(t, d, h.Chain, realBlocks) + }) + } +} + +func verifyAddressesXXX(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 verifyTransactionsXXX(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 _, b := range h.TestData.HandleFork.FakeBlocks { + if b.Height >= rng.Lower && b.Height <= rng.Upper { + 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, errors.New("Range must end with fake block in order to emulate fork") + } + mBlks := make(map[uint32]BlockID, len(blks)) + for i := 0; i < len(blks); i++ { + mBlks[blks[i].Height] = blks[i] + } + return &fakeBlockChain{ + BlockChain: chain, + returnFakes: true, + fakeBlocks: mBlks, + fakeBestHeight: 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 := 0; i < len(b.Txs); i++ { + 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 := 0; i < len(txs); i++ { + for j := 0; j < len(txs[i].Vout); j++ { + for k := 0; k < len(txs[i].Vout[j].ScriptPubKey.Addresses); k++ { + addr := txs[i].Vout[j].ScriptPubKey.Addresses[k] + txid := txs[i].Txid + addr2txs[addr] = append(addr2txs[addr], txid) + } + } + } + return addr2txs +} diff --git a/tests/sync/sync.go b/tests/sync/sync.go index f72cac19..56f0cf38 100644 --- a/tests/sync/sync.go +++ b/tests/sync/sync.go @@ -6,20 +6,16 @@ import ( "blockbook/db" "encoding/json" "errors" - "fmt" "io/ioutil" - "math/big" "os" "path/filepath" - "reflect" - "strings" "testing" ) var testMap = map[string]func(t *testing.T, th *TestHandler){ "ConnectBlocks": testConnectBlocks, "ConnectBlocksParallel": testConnectBlocksParallel, - // "HandleFork": testHandleFork, + "HandleFork": testHandleFork, } type TestHandler struct { @@ -127,18 +123,17 @@ 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 := 0; i < len(tx.Vout); i++ { + 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) { @@ -199,252 +194,3 @@ func withRocksDBAndSyncWorker(t *testing.T, h *TestHandler, startHeight uint32, fn(d, sw, ch) } - -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, _, 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 -} diff --git a/tests/sync/testdata/bitcoin.json b/tests/sync/testdata/bitcoin.json index 457332ce..006f317d 100644 --- a/tests/sync/testdata/bitcoin.json +++ b/tests/sync/testdata/bitcoin.json @@ -397,5 +397,63 @@ ] } } + }, + "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" + } + } } } diff --git a/tests/sync/testdata/bitcoin_testnet.json b/tests/sync/testdata/bitcoin_testnet.json new file mode 100644 index 00000000..6aff38b2 --- /dev/null +++ b/tests/sync/testdata/bitcoin_testnet.json @@ -0,0 +1,35 @@ +{ + "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" + } + } + } +} diff --git a/tests/tests.json b/tests/tests.json index db3335c5..b4e38b64 100644 --- a/tests/tests.json +++ b/tests/tests.json @@ -10,11 +10,12 @@ "bitcoin": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], - "sync": ["ConnectBlocksParallel", "ConnectBlocks"] + "sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"] }, "bitcoin_testnet": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", - "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"] + "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], + "sync": ["HandleFork"] }, "dash": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", From 911c62fb322da8f5f6225dbe6404c47546d205b7 Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Thu, 4 Oct 2018 10:40:25 +0200 Subject: [PATCH 08/15] Integration tests are tagged for conditional compilation --- tests/doc.go | 2 ++ tests/integration.go | 2 ++ tests/rpc/doc.go | 2 ++ tests/rpc/rpc.go | 2 ++ tests/sync/connectblocks.go | 2 ++ tests/sync/doc.go | 2 ++ tests/sync/fakechain.go | 2 ++ tests/sync/handlefork.go | 2 ++ tests/sync/sync.go | 2 ++ 9 files changed, 18 insertions(+) create mode 100644 tests/doc.go create mode 100644 tests/rpc/doc.go create mode 100644 tests/sync/doc.go diff --git a/tests/doc.go b/tests/doc.go new file mode 100644 index 00000000..12f5c534 --- /dev/null +++ b/tests/doc.go @@ -0,0 +1,2 @@ +// Package tests provides functions for loading and running integration tests +package tests diff --git a/tests/integration.go b/tests/integration.go index 990a6064..225a5736 100644 --- a/tests/integration.go +++ b/tests/integration.go @@ -1,3 +1,5 @@ +// +build integration + package tests import ( diff --git a/tests/rpc/doc.go b/tests/rpc/doc.go new file mode 100644 index 00000000..4d4eb2d9 --- /dev/null +++ b/tests/rpc/doc.go @@ -0,0 +1,2 @@ +// Package rpc implements integration tests of blockchain RPC layer +package rpc diff --git a/tests/rpc/rpc.go b/tests/rpc/rpc.go index ecca3b74..2d97329d 100644 --- a/tests/rpc/rpc.go +++ b/tests/rpc/rpc.go @@ -1,3 +1,5 @@ +// +build integration + package rpc import ( diff --git a/tests/sync/connectblocks.go b/tests/sync/connectblocks.go index af4a0590..c86faa29 100644 --- a/tests/sync/connectblocks.go +++ b/tests/sync/connectblocks.go @@ -1,3 +1,5 @@ +// +build integration + package sync import ( diff --git a/tests/sync/doc.go b/tests/sync/doc.go new file mode 100644 index 00000000..93a95c82 --- /dev/null +++ b/tests/sync/doc.go @@ -0,0 +1,2 @@ +// Package sync implements integration tests of synchronization code +package sync diff --git a/tests/sync/fakechain.go b/tests/sync/fakechain.go index 2000e39d..67635cfb 100644 --- a/tests/sync/fakechain.go +++ b/tests/sync/fakechain.go @@ -1,3 +1,5 @@ +// +build integration + package sync import ( diff --git a/tests/sync/handlefork.go b/tests/sync/handlefork.go index 759cdcf7..f30ede09 100644 --- a/tests/sync/handlefork.go +++ b/tests/sync/handlefork.go @@ -1,3 +1,5 @@ +// +build integration + package sync import ( diff --git a/tests/sync/sync.go b/tests/sync/sync.go index 56f0cf38..e943a11f 100644 --- a/tests/sync/sync.go +++ b/tests/sync/sync.go @@ -1,3 +1,5 @@ +// +build integration + package sync import ( From 37c66e81baff0f303d6112ce8882939d1edf151b Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Thu, 4 Oct 2018 10:41:43 +0200 Subject: [PATCH 09/15] Runs tests in order by name --- tests/integration.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/integration.go b/tests/integration.go index 225a5736..e0483472 100644 --- a/tests/integration.go +++ b/tests/integration.go @@ -16,6 +16,7 @@ import ( "os" "path/filepath" "reflect" + "sort" "testing" "github.com/jakm/btcutil/chaincfg" @@ -36,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) }) } From 5fc74eca31bff78004d6e3f64eec57b84acb014e Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Thu, 4 Oct 2018 10:43:22 +0200 Subject: [PATCH 10/15] Fixed fakeChain --- tests/sync/fakechain.go | 31 ++++++++++++------------------- tests/sync/handlefork.go | 15 +++++++-------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/tests/sync/fakechain.go b/tests/sync/fakechain.go index 67635cfb..6d399d1f 100644 --- a/tests/sync/fakechain.go +++ b/tests/sync/fakechain.go @@ -2,37 +2,27 @@ package sync -import ( - "blockbook/bchain" - "errors" -) +import "blockbook/bchain" type fakeBlockChain struct { bchain.BlockChain - returnFakes bool - fakeBlocks map[uint32]BlockID - fakeBestHeight uint32 + returnFakes bool + fakeBlocks map[uint32]BlockID + bestHeight uint32 } func (c *fakeBlockChain) GetBestBlockHash() (v string, err error) { - if !c.returnFakes { - return c.BlockChain.GetBestBlockHash() - } - if b, found := c.fakeBlocks[c.fakeBestHeight]; found { - return b.Hash, nil - } else { - return "", errors.New("Not found") - } + return c.GetBlockHash(c.bestHeight) } func (c *fakeBlockChain) GetBestBlockHeight() (v uint32, err error) { - if !c.returnFakes { - return c.BlockChain.GetBestBlockHeight() - } - return c.fakeBestHeight, nil + 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 @@ -42,6 +32,9 @@ func (c *fakeBlockChain) GetBlockHash(height uint32) (v string, err error) { } 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 diff --git a/tests/sync/handlefork.go b/tests/sync/handlefork.go index f30ede09..07017bbf 100644 --- a/tests/sync/handlefork.go +++ b/tests/sync/handlefork.go @@ -5,7 +5,6 @@ package sync import ( "blockbook/bchain" "blockbook/db" - "errors" "fmt" "math/big" "os" @@ -154,8 +153,8 @@ func verifyTransactionsXXX(t *testing.T, d *db.RocksDB, rng Range, addr2txs map[ func getFakeBlocks(h *TestHandler, rng Range) []BlockID { blks := make([]BlockID, 0, rng.Upper-rng.Lower+1) - for _, b := range h.TestData.HandleFork.FakeBlocks { - if b.Height >= rng.Lower && b.Height <= rng.Upper { + for i := rng.Lower; i <= rng.Upper; i++ { + if b, found := h.TestData.HandleFork.FakeBlocks[i]; found { blks = append(blks, b) } } @@ -174,17 +173,17 @@ func getRealBlocks(h *TestHandler, rng Range) []BlockID { func makeFakeChain(chain bchain.BlockChain, blks []BlockID, upper uint32) (*fakeBlockChain, error) { if blks[len(blks)-1].Height != upper { - return nil, errors.New("Range must end with fake block in order to emulate fork") + 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 := 0; i < len(blks); i++ { mBlks[blks[i].Height] = blks[i] } return &fakeBlockChain{ - BlockChain: chain, - returnFakes: true, - fakeBlocks: mBlks, - fakeBestHeight: upper, + BlockChain: chain, + returnFakes: true, + fakeBlocks: mBlks, + bestHeight: upper, }, nil } From 07493fc19842801c28ddf52cc4d929dc24eff5aa Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Thu, 4 Oct 2018 12:49:28 +0200 Subject: [PATCH 11/15] Fixed MempoolSync issues: P2PK addresses, missing txs --- tests/rpc/rpc.go | 64 ++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/tests/rpc/rpc.go b/tests/rpc/rpc.go index 2d97329d..23caba42 100644 --- a/tests/rpc/rpc.go +++ b/tests/rpc/rpc.go @@ -5,7 +5,6 @@ package rpc import ( "blockbook/bchain" "encoding/json" - "errors" "io/ioutil" "path/filepath" "reflect" @@ -13,6 +12,7 @@ import ( "time" "github.com/deckarep/golang-set" + "github.com/juju/errors" ) var testMap = map[string]func(t *testing.T, th *TestHandler){ @@ -110,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) { @@ -206,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") } @@ -215,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) @@ -343,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 { @@ -372,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 { From c66278f456316162f88400b915fcc85b52f824a0 Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Thu, 4 Oct 2018 12:57:21 +0200 Subject: [PATCH 12/15] Changed for loops to range loops --- tests/rpc/rpc.go | 2 +- tests/sync/handlefork.go | 10 +++++----- tests/sync/sync.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/rpc/rpc.go b/tests/rpc/rpc.go index 23caba42..b5f2216d 100644 --- a/tests/rpc/rpc.go +++ b/tests/rpc/rpc.go @@ -400,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 } diff --git a/tests/sync/handlefork.go b/tests/sync/handlefork.go index 07017bbf..df408200 100644 --- a/tests/sync/handlefork.go +++ b/tests/sync/handlefork.go @@ -176,7 +176,7 @@ func makeFakeChain(chain bchain.BlockChain, blks []BlockID, upper uint32) (*fake 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 := 0; i < len(blks); i++ { + for i := range blks { mBlks[blks[i].Height] = blks[i] } return &fakeBlockChain{ @@ -215,7 +215,7 @@ func getBlockTxs(chain bchain.BlockChain, hash string) ([]bchain.Tx, error) { return nil, fmt.Errorf("GetBlock: %s", err) } parser := chain.GetChainParser() - for i := 0; i < len(b.Txs); i++ { + 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) @@ -226,9 +226,9 @@ func getBlockTxs(chain bchain.BlockChain, hash string) ([]bchain.Tx, error) { func getAddr2TxsMap(txs []bchain.Tx) map[string][]string { addr2txs := make(map[string][]string) - for i := 0; i < len(txs); i++ { - for j := 0; j < len(txs[i].Vout); j++ { - for k := 0; k < len(txs[i].Vout[j].ScriptPubKey.Addresses); k++ { + 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) diff --git a/tests/sync/sync.go b/tests/sync/sync.go index e943a11f..6c01bfc0 100644 --- a/tests/sync/sync.go +++ b/tests/sync/sync.go @@ -125,7 +125,7 @@ func loadTestData(coin string, parser bchain.BlockChainParser) (*TestData, error } func setTxAddresses(parser bchain.BlockChainParser, tx *bchain.Tx) error { - for i := 0; i < len(tx.Vout); i++ { + for i := range tx.Vout { ad, err := parser.GetAddrDescFromVout(&tx.Vout[i]) if err != nil { return err From af7a8f2f64d42b7e4315e387ebeb18251b4260b4 Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Thu, 4 Oct 2018 15:27:56 +0200 Subject: [PATCH 13/15] Fixed P2PK issue in connectBlocks tests --- tests/sync/connectblocks.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/sync/connectblocks.go b/tests/sync/connectblocks.go index c86faa29..1c481ee3 100644 --- a/tests/sync/connectblocks.go +++ b/tests/sync/connectblocks.go @@ -243,12 +243,9 @@ func getTaInfo(parser bchain.BlockChainParser, ta *db.TxAddresses) (*txInfo, err 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 - } - for _, a := range addrs { - if !strings.HasPrefix(a, "OP_") { + addrs, s, err := ta.Inputs[i].Addresses(parser) + if err == nil && s { + for _, a := range addrs { info.inputs = append(info.inputs, a) } } @@ -256,12 +253,9 @@ func getTaInfo(parser bchain.BlockChainParser, ta *db.TxAddresses) (*txInfo, err 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 - } - for _, a := range addrs { - if !strings.HasPrefix(a, "OP_") { + addrs, s, err := ta.Outputs[i].Addresses(parser) + if err == nil && s { + for _, a := range addrs { info.outputs = append(info.outputs, a) } } From 24e59296a3a26bbaa95a4077ad358b3163f70653 Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Thu, 4 Oct 2018 16:06:41 +0200 Subject: [PATCH 14/15] HandleFork test verifies balance of addresses too --- tests/sync/handlefork.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/sync/handlefork.go b/tests/sync/handlefork.go index df408200..d9ff3358 100644 --- a/tests/sync/handlefork.go +++ b/tests/sync/handlefork.go @@ -40,8 +40,8 @@ func testHandleFork(t *testing.T, h *TestHandler) { } fakeAddr2txs := getAddr2TxsMap(fakeTxs) - verifyTransactionsXXX(t, d, rng, fakeAddr2txs, true) - // verifyAddressesXXX(t, d, h.Chain, fakeBlocks) + verifyTransactions2(t, d, rng, fakeAddr2txs, true) + verifyAddresses2(t, d, h.Chain, fakeBlocks) chain.returnFakes = false @@ -59,14 +59,14 @@ func testHandleFork(t *testing.T, h *TestHandler) { } realAddr2txs := getAddr2TxsMap(realTxs) - verifyTransactionsXXX(t, d, rng, fakeAddr2txs, false) - verifyTransactionsXXX(t, d, rng, realAddr2txs, true) - // verifyAddressesXXX(t, d, h.Chain, realBlocks) + verifyTransactions2(t, d, rng, fakeAddr2txs, false) + verifyTransactions2(t, d, rng, realAddr2txs, true) + verifyAddresses2(t, d, h.Chain, realBlocks) }) } } -func verifyAddressesXXX(t *testing.T, d *db.RocksDB, chain bchain.BlockChain, blks []BlockID) { +func verifyAddresses2(t *testing.T, d *db.RocksDB, chain bchain.BlockChain, blks []BlockID) { parser := chain.GetChainParser() for _, b := range blks { @@ -117,7 +117,7 @@ func verifyAddressesXXX(t *testing.T, d *db.RocksDB, chain bchain.BlockChain, bl } } -func verifyTransactionsXXX(t *testing.T, d *db.RocksDB, rng Range, addr2txs map[string][]string, exist bool) { +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)) From e9075ee4fd971c31a1593ab16f8741226b20df37 Mon Sep 17 00:00:00 2001 From: Jakub Matys Date: Thu, 4 Oct 2018 16:07:34 +0200 Subject: [PATCH 15/15] Added sync test for testnet --- tests/sync/testdata/bitcoin_testnet.json | 176 +++++++++++++++++++++++ tests/tests.json | 2 +- 2 files changed, 177 insertions(+), 1 deletion(-) diff --git a/tests/sync/testdata/bitcoin_testnet.json b/tests/sync/testdata/bitcoin_testnet.json index 6aff38b2..374f4afb 100644 --- a/tests/sync/testdata/bitcoin_testnet.json +++ b/tests/sync/testdata/bitcoin_testnet.json @@ -1,4 +1,180 @@ { + "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} diff --git a/tests/tests.json b/tests/tests.json index 402caee9..eab5cd04 100644 --- a/tests/tests.json +++ b/tests/tests.json @@ -15,7 +15,7 @@ "bitcoin_testnet": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync", "EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"], - "sync": ["HandleFork"] + "sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"] }, "dash": { "rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync",