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 }