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 }