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