github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/tests/sync/connectblocks.go (about)

     1  // +build integration
     2  
     3  package sync
     4  
     5  import (
     6  	"blockbook/bchain"
     7  	"blockbook/db"
     8  	"math/big"
     9  	"os"
    10  	"reflect"
    11  	"strings"
    12  	"testing"
    13  )
    14  
    15  func testConnectBlocks(t *testing.T, h *TestHandler) {
    16  	for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
    17  		withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
    18  			upperHash, err := h.Chain.GetBlockHash(rng.Upper)
    19  			if err != nil {
    20  				t.Fatal(err)
    21  			}
    22  
    23  			err = db.ConnectBlocks(sw, func(hash string, height uint32) {
    24  				if hash == upperHash {
    25  					close(ch)
    26  				}
    27  			}, true)
    28  			if err != nil {
    29  				if !strings.HasPrefix(err.Error(), "connectBlocks interrupted at height") {
    30  					t.Fatal(err)
    31  				}
    32  			}
    33  
    34  			height, _, err := d.GetBestBlock()
    35  			if err != nil {
    36  				t.Fatal(err)
    37  			}
    38  			if height < rng.Upper {
    39  				t.Fatalf("Best block height mismatch: %d < %d", height, rng.Upper)
    40  			}
    41  
    42  			t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
    43  			t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
    44  			t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
    45  		})
    46  	}
    47  }
    48  
    49  func testConnectBlocksParallel(t *testing.T, h *TestHandler) {
    50  	for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
    51  		withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
    52  			upperHash, err := h.Chain.GetBlockHash(rng.Upper)
    53  			if err != nil {
    54  				t.Fatal(err)
    55  			}
    56  
    57  			err = sw.ConnectBlocksParallel(rng.Lower, rng.Upper)
    58  			if err != nil {
    59  				t.Fatal(err)
    60  			}
    61  
    62  			height, hash, err := d.GetBestBlock()
    63  			if err != nil {
    64  				t.Fatal(err)
    65  			}
    66  			if height != rng.Upper {
    67  				t.Fatalf("Best block height mismatch: %d != %d", height, rng.Upper)
    68  			}
    69  			if hash != upperHash {
    70  				t.Fatalf("Best block hash mismatch: %s != %s", hash, upperHash)
    71  			}
    72  
    73  			t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
    74  			t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
    75  			t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
    76  		})
    77  	}
    78  }
    79  
    80  func verifyBlockInfo(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
    81  	for height := rng.Lower; height <= rng.Upper; height++ {
    82  		block, found := h.TestData.ConnectBlocks.Blocks[height]
    83  		if !found {
    84  			continue
    85  		}
    86  
    87  		bi, err := d.GetBlockInfo(height)
    88  		if err != nil {
    89  			t.Errorf("GetBlockInfo(%d) error: %s", height, err)
    90  			continue
    91  		}
    92  		if bi == nil {
    93  			t.Errorf("GetBlockInfo(%d) returned nil", height)
    94  			continue
    95  		}
    96  
    97  		if bi.Hash != block.Hash {
    98  			t.Errorf("Block hash mismatch: %s != %s", bi.Hash, block.Hash)
    99  		}
   100  
   101  		if bi.Txs != block.NoTxs {
   102  			t.Errorf("Number of transactions in block %s mismatch: %d != %d", bi.Hash, bi.Txs, block.NoTxs)
   103  		}
   104  	}
   105  }
   106  
   107  func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
   108  	type txInfo struct {
   109  		txid     string
   110  		vout     uint32
   111  		isOutput bool
   112  	}
   113  	addr2txs := make(map[string][]txInfo)
   114  	checkMap := make(map[string][]bool)
   115  
   116  	for height := rng.Lower; height <= rng.Upper; height++ {
   117  		block, found := h.TestData.ConnectBlocks.Blocks[height]
   118  		if !found {
   119  			continue
   120  		}
   121  
   122  		for _, tx := range block.TxDetails {
   123  			for _, vin := range tx.Vin {
   124  				for _, a := range vin.Addresses {
   125  					addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, vin.Vout, false})
   126  					checkMap[a] = append(checkMap[a], false)
   127  				}
   128  			}
   129  			for _, vout := range tx.Vout {
   130  				for _, a := range vout.ScriptPubKey.Addresses {
   131  					addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, vout.N, true})
   132  					checkMap[a] = append(checkMap[a], false)
   133  				}
   134  			}
   135  		}
   136  	}
   137  
   138  	for addr, txs := range addr2txs {
   139  		err := d.GetTransactions(addr, rng.Lower, rng.Upper, func(txid string, vout uint32, isOutput bool) error {
   140  			for i, tx := range txs {
   141  				if txid == tx.txid && vout == tx.vout && isOutput == tx.isOutput {
   142  					checkMap[addr][i] = true
   143  				}
   144  			}
   145  			return nil
   146  		})
   147  		if err != nil {
   148  			t.Fatal(err)
   149  		}
   150  	}
   151  
   152  	for addr, txs := range addr2txs {
   153  		for i, tx := range txs {
   154  			if !checkMap[addr][i] {
   155  				t.Errorf("%s: transaction not found %+v", addr, tx)
   156  			}
   157  		}
   158  	}
   159  }
   160  
   161  func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
   162  	parser := h.Chain.GetChainParser()
   163  
   164  	for height := rng.Lower; height <= rng.Upper; height++ {
   165  		block, found := h.TestData.ConnectBlocks.Blocks[height]
   166  		if !found {
   167  			continue
   168  		}
   169  
   170  		for _, tx := range block.TxDetails {
   171  			ta, err := d.GetTxAddresses(tx.Txid)
   172  			if err != nil {
   173  				t.Fatal(err)
   174  			}
   175  			if ta == nil {
   176  				t.Errorf("Tx %s: not found in TxAddresses", tx.Txid)
   177  				continue
   178  			}
   179  
   180  			txInfo := getTxInfo(tx)
   181  			taInfo, err := getTaInfo(parser, ta)
   182  			if err != nil {
   183  				t.Fatal(err)
   184  			}
   185  
   186  			if ta.Height != height {
   187  				t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, height)
   188  				continue
   189  			}
   190  
   191  			if len(txInfo.inputs) > 0 && !reflect.DeepEqual(taInfo.inputs, txInfo.inputs) {
   192  				t.Errorf("Tx %s: inputs mismatch: got %q, want %q", tx.Txid, taInfo.inputs, txInfo.inputs)
   193  			}
   194  
   195  			if !reflect.DeepEqual(taInfo.outputs, txInfo.outputs) {
   196  				t.Errorf("Tx %s: outputs mismatch: got %q, want %q", tx.Txid, taInfo.outputs, txInfo.outputs)
   197  			}
   198  
   199  			if taInfo.valOutSat.Cmp(&txInfo.valOutSat) != 0 {
   200  				t.Errorf("Tx %s: total output amount mismatch: got %s, want %s",
   201  					tx.Txid, taInfo.valOutSat.String(), txInfo.valOutSat.String())
   202  			}
   203  
   204  			if len(txInfo.inputs) > 0 {
   205  				treshold := "0.0001"
   206  				fee := new(big.Int).Sub(&taInfo.valInSat, &taInfo.valOutSat)
   207  				if strings.Compare(parser.AmountToDecimalString(fee), treshold) > 0 {
   208  					t.Errorf("Tx %s: suspicious amounts: input ∑ [%s] - output ∑ [%s] > %s",
   209  						tx.Txid, taInfo.valInSat.String(), taInfo.valOutSat.String(), treshold)
   210  				}
   211  			}
   212  		}
   213  	}
   214  }
   215  
   216  type txInfo struct {
   217  	inputs    []string
   218  	outputs   []string
   219  	valInSat  big.Int
   220  	valOutSat big.Int
   221  }
   222  
   223  func getTxInfo(tx *bchain.Tx) *txInfo {
   224  	info := &txInfo{inputs: []string{}, outputs: []string{}}
   225  
   226  	for _, vin := range tx.Vin {
   227  		for _, a := range vin.Addresses {
   228  			info.inputs = append(info.inputs, a)
   229  		}
   230  	}
   231  	for _, vout := range tx.Vout {
   232  		for _, a := range vout.ScriptPubKey.Addresses {
   233  			info.outputs = append(info.outputs, a)
   234  		}
   235  		info.valOutSat.Add(&info.valOutSat, &vout.ValueSat)
   236  	}
   237  
   238  	return info
   239  }
   240  
   241  func getTaInfo(parser bchain.BlockChainParser, ta *db.TxAddresses) (*txInfo, error) {
   242  	info := &txInfo{inputs: []string{}, outputs: []string{}}
   243  
   244  	for i := range ta.Inputs {
   245  		info.valInSat.Add(&info.valInSat, &ta.Inputs[i].ValueSat)
   246  		addrs, s, err := ta.Inputs[i].Addresses(parser)
   247  		if err == nil && s {
   248  			for _, a := range addrs {
   249  				info.inputs = append(info.inputs, a)
   250  			}
   251  		}
   252  	}
   253  
   254  	for i := range ta.Outputs {
   255  		info.valOutSat.Add(&info.valOutSat, &ta.Outputs[i].ValueSat)
   256  		addrs, s, err := ta.Outputs[i].Addresses(parser)
   257  		if err == nil && s {
   258  			for _, a := range addrs {
   259  				info.outputs = append(info.outputs, a)
   260  			}
   261  		}
   262  	}
   263  
   264  	return info, nil
   265  }