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",