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 }