github.com/cryptohub-digital/blockbook@v0.3.5-0.20240403155730-99ab40b9104c/tests/sync/connectblocks.go (about)

     1  //go:build integration
     2  
     3  package sync
     4  
     5  import (
     6  	"math/big"
     7  	"os"
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/cryptohub-digital/blockbook/bchain"
    13  	"github.com/cryptohub-digital/blockbook/db"
    14  )
    15  
    16  func testConnectBlocks(t *testing.T, h *TestHandler) {
    17  	for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
    18  		withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
    19  			upperHash, err := h.Chain.GetBlockHash(rng.Upper)
    20  			if err != nil {
    21  				t.Fatal(err)
    22  			}
    23  
    24  			err = db.ConnectBlocks(sw, func(hash string, height uint32) {
    25  				if hash == upperHash {
    26  					close(ch)
    27  				}
    28  			}, true)
    29  			if err != nil && err != db.ErrOperationInterrupted {
    30  				t.Fatal(err)
    31  			}
    32  
    33  			height, _, err := d.GetBestBlock()
    34  			if err != nil {
    35  				t.Fatal(err)
    36  			}
    37  			if height < rng.Upper {
    38  				t.Fatalf("Best block height mismatch: %d < %d", height, rng.Upper)
    39  			}
    40  
    41  			t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
    42  			t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
    43  			t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
    44  		})
    45  	}
    46  }
    47  
    48  func testConnectBlocksParallel(t *testing.T, h *TestHandler) {
    49  	for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
    50  		withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
    51  			upperHash, err := h.Chain.GetBlockHash(rng.Upper)
    52  			if err != nil {
    53  				t.Fatal(err)
    54  			}
    55  
    56  			err = sw.ConnectBlocksParallel(rng.Lower, rng.Upper)
    57  			if err != nil {
    58  				t.Fatal(err)
    59  			}
    60  
    61  			height, hash, err := d.GetBestBlock()
    62  			if err != nil {
    63  				t.Fatal(err)
    64  			}
    65  			if height != rng.Upper {
    66  				t.Fatalf("Best block height mismatch: %d != %d", height, rng.Upper)
    67  			}
    68  			if hash != upperHash {
    69  				t.Fatalf("Best block hash mismatch: %s != %s", hash, upperHash)
    70  			}
    71  
    72  			t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
    73  			t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
    74  			t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
    75  		})
    76  	}
    77  }
    78  
    79  func verifyBlockInfo(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
    80  	for height := rng.Lower; height <= rng.Upper; height++ {
    81  		block, found := h.TestData.ConnectBlocks.Blocks[height]
    82  		if !found {
    83  			continue
    84  		}
    85  
    86  		bi, err := d.GetBlockInfo(height)
    87  		if err != nil {
    88  			t.Errorf("GetBlockInfo(%d) error: %s", height, err)
    89  			continue
    90  		}
    91  		if bi == nil {
    92  			t.Errorf("GetBlockInfo(%d) returned nil", height)
    93  			continue
    94  		}
    95  
    96  		if bi.Hash != block.Hash {
    97  			t.Errorf("Block hash mismatch: %s != %s", bi.Hash, block.Hash)
    98  		}
    99  
   100  		if bi.Txs != block.NoTxs {
   101  			t.Errorf("Number of transactions in block %s mismatch: %d != %d", bi.Hash, bi.Txs, block.NoTxs)
   102  		}
   103  	}
   104  }
   105  
   106  func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
   107  	type txInfo struct {
   108  		txid  string
   109  		index int32
   110  	}
   111  	addr2txs := make(map[string][]txInfo)
   112  	checkMap := make(map[string][]bool)
   113  
   114  	for height := rng.Lower; height <= rng.Upper; height++ {
   115  		block, found := h.TestData.ConnectBlocks.Blocks[height]
   116  		if !found {
   117  			continue
   118  		}
   119  
   120  		for _, tx := range block.TxDetails {
   121  			for _, vin := range tx.Vin {
   122  				for _, a := range vin.Addresses {
   123  					addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, ^int32(vin.Vout)})
   124  					checkMap[a] = append(checkMap[a], false)
   125  				}
   126  			}
   127  			for _, vout := range tx.Vout {
   128  				for _, a := range vout.ScriptPubKey.Addresses {
   129  					addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, int32(vout.N)})
   130  					checkMap[a] = append(checkMap[a], false)
   131  				}
   132  			}
   133  		}
   134  	}
   135  
   136  	for addr, txs := range addr2txs {
   137  		err := d.GetTransactions(addr, rng.Lower, rng.Upper, func(txid string, height uint32, indexes []int32) error {
   138  			for i, tx := range txs {
   139  				for _, index := range indexes {
   140  					if txid == tx.txid && index == tx.index {
   141  						checkMap[addr][i] = true
   142  					}
   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  }