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

     1  //go:build integration
     2  
     3  package sync
     4  
     5  import (
     6  	"fmt"
     7  	"math/big"
     8  	"os"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/cryptohub-digital/blockbook/bchain"
    14  	"github.com/cryptohub-digital/blockbook/db"
    15  )
    16  
    17  func testHandleFork(t *testing.T, h *TestHandler) {
    18  	for _, rng := range h.TestData.HandleFork.SyncRanges {
    19  		withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
    20  			fakeBlocks := getFakeBlocks(h, rng)
    21  			chain, err := makeFakeChain(h.Chain, fakeBlocks, rng.Upper)
    22  			if err != nil {
    23  				t.Fatal(err)
    24  			}
    25  
    26  			db.SetBlockChain(sw, chain)
    27  
    28  			sw.ConnectBlocksParallel(rng.Lower, rng.Upper)
    29  
    30  			height, _, err := d.GetBestBlock()
    31  			if err != nil {
    32  				t.Fatal(err)
    33  			}
    34  			if height != rng.Upper {
    35  				t.Fatalf("Upper block height mismatch: %d != %d", height, rng.Upper)
    36  			}
    37  
    38  			fakeTxs, err := getTxs(h, d, rng, fakeBlocks)
    39  			if err != nil {
    40  				t.Fatal(err)
    41  			}
    42  			fakeAddr2txs := getAddr2TxsMap(fakeTxs)
    43  
    44  			verifyTransactions2(t, d, rng, fakeAddr2txs, true)
    45  			verifyAddresses2(t, d, h.Chain, fakeBlocks)
    46  
    47  			chain.returnFakes = false
    48  
    49  			upperHash := fakeBlocks[len(fakeBlocks)-1].Hash
    50  			db.HandleFork(sw, rng.Upper, upperHash, func(hash string, height uint32) {
    51  				if hash == upperHash {
    52  					close(ch)
    53  				}
    54  			}, true)
    55  
    56  			realBlocks := getRealBlocks(h, rng)
    57  			realTxs, err := getTxs(h, d, rng, realBlocks)
    58  			if err != nil {
    59  				t.Fatal(err)
    60  			}
    61  			realAddr2txs := getAddr2TxsMap(realTxs)
    62  
    63  			verifyTransactions2(t, d, rng, fakeAddr2txs, false)
    64  			verifyTransactions2(t, d, rng, realAddr2txs, true)
    65  			verifyAddresses2(t, d, h.Chain, realBlocks)
    66  		})
    67  	}
    68  }
    69  
    70  func verifyAddresses2(t *testing.T, d *db.RocksDB, chain bchain.BlockChain, blks []BlockID) {
    71  	parser := chain.GetChainParser()
    72  
    73  	for _, b := range blks {
    74  		txs, err := getBlockTxs(chain, b.Hash)
    75  		if err != nil {
    76  			t.Fatal(err)
    77  		}
    78  
    79  		for _, tx := range txs {
    80  			ta, err := d.GetTxAddresses(tx.Txid)
    81  			if err != nil {
    82  				t.Fatal(err)
    83  			}
    84  			if ta == nil {
    85  				t.Errorf("Tx %s: not found in TxAddresses", tx.Txid)
    86  				continue
    87  			}
    88  
    89  			txInfo := getTxInfo(&tx)
    90  			taInfo, err := getTaInfo(parser, ta)
    91  			if err != nil {
    92  				t.Fatal(err)
    93  			}
    94  
    95  			if ta.Height != b.Height {
    96  				t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, b.Height)
    97  				continue
    98  			}
    99  
   100  			if len(txInfo.inputs) > 0 && !reflect.DeepEqual(taInfo.inputs, txInfo.inputs) {
   101  				t.Errorf("Tx %s: inputs mismatch: got %q, want %q", tx.Txid, taInfo.inputs, txInfo.inputs)
   102  			}
   103  
   104  			if !reflect.DeepEqual(taInfo.outputs, txInfo.outputs) {
   105  				t.Errorf("Tx %s: outputs mismatch: got %q, want %q", tx.Txid, taInfo.outputs, txInfo.outputs)
   106  			}
   107  
   108  			if taInfo.valOutSat.Cmp(&txInfo.valOutSat) != 0 {
   109  				t.Errorf("Tx %s: total output amount mismatch: got %s, want %s",
   110  					tx.Txid, taInfo.valOutSat.String(), txInfo.valOutSat.String())
   111  			}
   112  
   113  			if len(txInfo.inputs) > 0 {
   114  				treshold := "0.0001"
   115  				fee := new(big.Int).Sub(&taInfo.valInSat, &taInfo.valOutSat)
   116  				if strings.Compare(parser.AmountToDecimalString(fee), treshold) > 0 {
   117  					t.Errorf("Tx %s: suspicious amounts: input ∑ [%s] - output ∑ [%s] > %s",
   118  						tx.Txid, taInfo.valInSat.String(), taInfo.valOutSat.String(), treshold)
   119  				}
   120  			}
   121  		}
   122  	}
   123  }
   124  
   125  func verifyTransactions2(t *testing.T, d *db.RocksDB, rng Range, addr2txs map[string][]string, exist bool) {
   126  	noErrs := 0
   127  	for addr, txs := range addr2txs {
   128  		checkMap := make(map[string]bool, len(txs))
   129  		for _, txid := range txs {
   130  			checkMap[txid] = false
   131  		}
   132  
   133  		err := d.GetTransactions(addr, rng.Lower, rng.Upper, func(txid string, height uint32, indexes []int32) error {
   134  			for _, index := range indexes {
   135  				if index >= 0 {
   136  					checkMap[txid] = true
   137  					break
   138  				}
   139  			}
   140  			return nil
   141  		})
   142  		if err != nil {
   143  			t.Fatal(err)
   144  		}
   145  
   146  		for _, txid := range txs {
   147  			if checkMap[txid] != exist {
   148  				auxverb := "wasn't"
   149  				if !exist {
   150  					auxverb = "was"
   151  				}
   152  				t.Errorf("%s: transaction %s %s found [expected = %t]", addr, txid, auxverb, exist)
   153  				noErrs++
   154  				if noErrs >= 10 {
   155  					t.Fatal("Too many errors")
   156  				}
   157  			}
   158  		}
   159  	}
   160  }
   161  
   162  func getFakeBlocks(h *TestHandler, rng Range) []BlockID {
   163  	blks := make([]BlockID, 0, rng.Upper-rng.Lower+1)
   164  	for i := rng.Lower; i <= rng.Upper; i++ {
   165  		if b, found := h.TestData.HandleFork.FakeBlocks[i]; found {
   166  			blks = append(blks, b)
   167  		}
   168  	}
   169  	return blks
   170  }
   171  
   172  func getRealBlocks(h *TestHandler, rng Range) []BlockID {
   173  	blks := make([]BlockID, 0, rng.Upper-rng.Lower+1)
   174  	for _, b := range h.TestData.HandleFork.RealBlocks {
   175  		if b.Height >= rng.Lower && b.Height <= rng.Upper {
   176  			blks = append(blks, b)
   177  		}
   178  	}
   179  	return blks
   180  }
   181  
   182  func makeFakeChain(chain bchain.BlockChain, blks []BlockID, upper uint32) (*fakeBlockChain, error) {
   183  	if blks[len(blks)-1].Height != upper {
   184  		return nil, fmt.Errorf("Range must end with fake block in order to emulate fork [%d != %d]", blks[len(blks)-1].Height, upper)
   185  	}
   186  	mBlks := make(map[uint32]BlockID, len(blks))
   187  	for i := range blks {
   188  		mBlks[blks[i].Height] = blks[i]
   189  	}
   190  	return &fakeBlockChain{
   191  		BlockChain:  chain,
   192  		returnFakes: true,
   193  		fakeBlocks:  mBlks,
   194  		bestHeight:  upper,
   195  	}, nil
   196  }
   197  
   198  func getTxs(h *TestHandler, d *db.RocksDB, rng Range, blks []BlockID) ([]bchain.Tx, error) {
   199  	res := make([]bchain.Tx, 0, (rng.Upper-rng.Lower+1)*2000)
   200  
   201  	for _, b := range blks {
   202  		bi, err := d.GetBlockInfo(b.Height)
   203  		if err != nil {
   204  			return nil, err
   205  		}
   206  		if bi.Hash != b.Hash {
   207  			return nil, fmt.Errorf("Block hash mismatch: %s != %s", bi.Hash, b.Hash)
   208  		}
   209  
   210  		txs, err := getBlockTxs(h.Chain, b.Hash)
   211  		if err != nil {
   212  			return nil, err
   213  		}
   214  		res = append(res, txs...)
   215  	}
   216  
   217  	return res, nil
   218  }
   219  
   220  func getBlockTxs(chain bchain.BlockChain, hash string) ([]bchain.Tx, error) {
   221  	b, err := chain.GetBlock(hash, 0)
   222  	if err != nil {
   223  		return nil, fmt.Errorf("GetBlock: %s", err)
   224  	}
   225  	parser := chain.GetChainParser()
   226  	for i := range b.Txs {
   227  		err := setTxAddresses(parser, &b.Txs[i])
   228  		if err != nil {
   229  			return nil, fmt.Errorf("setTxAddresses [%s]: %s", b.Txs[i].Txid, err)
   230  		}
   231  	}
   232  	return b.Txs, nil
   233  }
   234  
   235  func getAddr2TxsMap(txs []bchain.Tx) map[string][]string {
   236  	addr2txs := make(map[string][]string)
   237  	for i := range txs {
   238  		for j := range txs[i].Vout {
   239  			for k := range txs[i].Vout[j].ScriptPubKey.Addresses {
   240  				addr := txs[i].Vout[j].ScriptPubKey.Addresses[k]
   241  				txid := txs[i].Txid
   242  				addr2txs[addr] = append(addr2txs[addr], txid)
   243  			}
   244  		}
   245  	}
   246  	return addr2txs
   247  }