WIP: sync tests

pull/78/head
Jakub Matys 2018-10-01 14:32:40 +02:00
parent c5cb1e2e54
commit 5000c01f11
8 changed files with 670 additions and 266 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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