github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/internal/ledgerutil/compare.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package ledgerutil 8 9 import ( 10 "bufio" 11 "bytes" 12 "encoding/json" 13 "fmt" 14 "io/ioutil" 15 "os" 16 "path/filepath" 17 "sort" 18 "strings" 19 20 "github.com/golang/protobuf/proto" 21 "github.com/hechain20/hechain/common/ledger/util" 22 "github.com/hechain20/hechain/core/ledger/kvledger" 23 "github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/privacyenabledstate" 24 "github.com/hechain20/hechain/internal/fileutil" 25 "github.com/pkg/errors" 26 ) 27 28 const ( 29 // AllPubDiffsByKey - Filename for the json output that contains all public differences ordered by key 30 AllPubDiffsByKey = "all_pub_diffs_by_key.json" 31 // AllPvtDiffsByKey - Filename for the json output that contains all private differences ordered by key 32 AllPvtDiffsByKey = "all_pvt_diffs_by_key.json" 33 // FirstDiffsByHeight - Filename for the json output that contains the first n differences ordered by height 34 FirstDiffsByHeight = "first_diffs_by_height.json" 35 ) 36 37 // Compare - Compares two ledger snapshots and outputs the differences in snapshot records 38 // This function will throw an error if the output directory already exist in the outputDirLoc 39 // Function will return count of -1 if the public state and private state hashes are the same 40 func Compare(snapshotDir1 string, snapshotDir2 string, outputDirLoc string, firstDiffs int) (count int, outputDirPath string, err error) { 41 // firstRecords - Slice of diffRecords that stores found differences based on block height, used to generate first n differences output file 42 firstRecords := &firstRecords{records: &diffRecordSlice{}, highestRecord: &diffRecord{}, highestIndex: 0, limit: firstDiffs} 43 44 // Check the hashes between two files 45 hashPath1 := filepath.Join(snapshotDir1, kvledger.SnapshotSignableMetadataFileName) 46 hashPath2 := filepath.Join(snapshotDir2, kvledger.SnapshotSignableMetadataFileName) 47 48 equalPub, equalPvt, channelName, blockHeight, err := hashesEqual(hashPath1, hashPath2) 49 if err != nil { 50 return 0, "", err 51 } 52 // Snapshot public and private hashes are the same 53 if equalPub && equalPvt { 54 return -1, "", nil 55 } 56 57 // Output directory creation 58 outputDirName := fmt.Sprintf("%s_%d_comparison", channelName, blockHeight) 59 outputDirPath = filepath.Join(outputDirLoc, outputDirName) 60 61 empty, err := fileutil.CreateDirIfMissing(outputDirPath) 62 if err != nil { 63 return 0, "", err 64 } 65 if !empty { 66 switch outputDirLoc { 67 case ".": 68 outputDirLoc = "the current directory" 69 case "..": 70 outputDirLoc = "the parent directory" 71 } 72 return 0, "", errors.Errorf("%s already exists in %s. Choose a different location or remove the existing results. Aborting compare", outputDirName, outputDirLoc) 73 } 74 75 // Generate all public data differences between snapshots 76 if !equalPub { 77 snapshotPubReader1, err := privacyenabledstate.NewSnapshotReader(snapshotDir1, 78 privacyenabledstate.PubStateDataFileName, privacyenabledstate.PubStateMetadataFileName) 79 if err != nil { 80 return 0, "", err 81 } 82 snapshotPubReader2, err := privacyenabledstate.NewSnapshotReader(snapshotDir2, 83 privacyenabledstate.PubStateDataFileName, privacyenabledstate.PubStateMetadataFileName) 84 if err != nil { 85 return 0, "", err 86 } 87 outputPubFileWriter, err := findAndWriteDifferences(outputDirPath, AllPubDiffsByKey, snapshotPubReader1, snapshotPubReader2, firstDiffs, firstRecords) 88 if err != nil { 89 return 0, "", err 90 } 91 count += outputPubFileWriter.count 92 } 93 94 // Generate all private data differences between snapshots 95 if !equalPvt { 96 snapshotPvtReader1, err := privacyenabledstate.NewSnapshotReader(snapshotDir1, 97 privacyenabledstate.PvtStateHashesFileName, privacyenabledstate.PvtStateHashesMetadataFileName) 98 if err != nil { 99 return 0, "", err 100 } 101 snapshotPvtReader2, err := privacyenabledstate.NewSnapshotReader(snapshotDir2, 102 privacyenabledstate.PvtStateHashesFileName, privacyenabledstate.PvtStateHashesMetadataFileName) 103 if err != nil { 104 return 0, "", err 105 } 106 outputPvtFileWriter, err := findAndWriteDifferences(outputDirPath, AllPvtDiffsByKey, snapshotPvtReader1, snapshotPvtReader2, firstDiffs, firstRecords) 107 if err != nil { 108 return 0, "", err 109 } 110 count += outputPvtFileWriter.count 111 } 112 113 // Generate early differences output file 114 if firstDiffs != 0 { 115 firstDiffsOutputFileWriter, err := newJSONFileWriter(filepath.Join(outputDirPath, FirstDiffsByHeight)) 116 if err != nil { 117 return 0, "", err 118 } 119 sort.Sort(*firstRecords.records) 120 for _, r := range *firstRecords.records { 121 firstDiffsOutputFileWriter.addRecord(*r) 122 } 123 err = firstDiffsOutputFileWriter.close() 124 if err != nil { 125 return 0, "", err 126 } 127 } 128 129 return count, outputDirPath, nil 130 } 131 132 // Finds the differing records between two snapshot data files using SnapshotReaders and saves differences 133 // to an output file. Simultaneously, keep track of the first n differences. 134 func findAndWriteDifferences(outputDirPath string, outputFilename string, snapshotReader1 *privacyenabledstate.SnapshotReader, snapshotReader2 *privacyenabledstate.SnapshotReader, 135 firstDiffs int, firstRecords *firstRecords) (outputFileWriter *jsonArrayFileWriter, err error) { 136 // Create the output file 137 outputFileWriter, err = newJSONFileWriter(filepath.Join(outputDirPath, outputFilename)) 138 if err != nil { 139 return nil, err 140 } 141 142 // Read each snapshot record to begin looking for differences 143 namespace1, snapshotRecord1, err := snapshotReader1.Next() 144 if err != nil { 145 return nil, err 146 } 147 namespace2, snapshotRecord2, err := snapshotReader2.Next() 148 if err != nil { 149 return nil, err 150 } 151 152 // Main snapshot record comparison loop 153 for snapshotRecord1 != nil && snapshotRecord2 != nil { 154 155 // nsKeys used for comparing snapshot records 156 key1 := &nsKey{namespace: namespace1, key: snapshotRecord1.Key} 157 key2 := &nsKey{namespace: namespace2, key: snapshotRecord2.Key} 158 159 // Determine the difference in records by comparing nsKeys 160 switch nsKeyCompare(key1, key2) { 161 162 case 0: // Keys are the same, look for a difference in records 163 if !(proto.Equal(snapshotRecord1, snapshotRecord2)) { 164 // Keys are the same but records are different 165 diffRecord, err := newDiffRecord(namespace1, snapshotRecord1, snapshotRecord2) 166 if err != nil { 167 return nil, err 168 } 169 // Add difference to output JSON file 170 err = outputFileWriter.addRecord(*diffRecord) 171 if err != nil { 172 return nil, err 173 } 174 if firstDiffs != 0 { 175 firstRecords.addRecord(diffRecord) 176 } 177 } 178 // Advance both snapshot readers 179 namespace1, snapshotRecord1, err = snapshotReader1.Next() 180 if err != nil { 181 return nil, err 182 } 183 namespace2, snapshotRecord2, err = snapshotReader2.Next() 184 if err != nil { 185 return nil, err 186 } 187 188 case 1: // Key 1 is bigger, snapshot 1 is missing a record 189 // Snapshot 2 has the missing record, add missing to output JSON file 190 diffRecord, err := newDiffRecord(namespace2, nil, snapshotRecord2) 191 if err != nil { 192 return nil, err 193 } 194 // Add missing record to output JSON file 195 err = outputFileWriter.addRecord(*diffRecord) 196 if err != nil { 197 return nil, err 198 } 199 if firstDiffs != 0 { 200 firstRecords.addRecord(diffRecord) 201 } 202 // Advance the second snapshot reader 203 namespace2, snapshotRecord2, err = snapshotReader2.Next() 204 if err != nil { 205 return nil, err 206 } 207 208 case -1: // Key 2 is bigger, snapshot 2 is missing a record 209 // Snapshot 1 has the missing record, add missing to output JSON file 210 diffRecord, err := newDiffRecord(namespace1, snapshotRecord1, nil) 211 if err != nil { 212 return nil, err 213 } 214 // Add missing record to output JSON file 215 err = outputFileWriter.addRecord(*diffRecord) 216 if err != nil { 217 return nil, err 218 } 219 if firstDiffs != 0 { 220 firstRecords.addRecord(diffRecord) 221 } 222 // Advance the first snapshot reader 223 namespace1, snapshotRecord1, err = snapshotReader1.Next() 224 if err != nil { 225 return nil, err 226 } 227 228 default: 229 panic("unexpected code path: bug") 230 } 231 } 232 233 // Check for tailing records 234 switch { 235 236 case snapshotRecord1 != nil: // Snapshot 2 is missing a record 237 for snapshotRecord1 != nil { 238 // Add missing to output JSON file 239 diffRecord, err := newDiffRecord(namespace1, snapshotRecord1, nil) 240 if err != nil { 241 return nil, err 242 } 243 err = outputFileWriter.addRecord(*diffRecord) 244 if err != nil { 245 return nil, err 246 } 247 if firstDiffs != 0 { 248 firstRecords.addRecord(diffRecord) 249 } 250 namespace1, snapshotRecord1, err = snapshotReader1.Next() 251 if err != nil { 252 return nil, err 253 } 254 } 255 256 case snapshotRecord2 != nil: // Snapshot 1 is missing a record 257 for snapshotRecord2 != nil { 258 // Add missing to output JSON file 259 diffRecord, err := newDiffRecord(namespace2, nil, snapshotRecord2) 260 if err != nil { 261 return nil, err 262 } 263 err = outputFileWriter.addRecord(*diffRecord) 264 if err != nil { 265 return nil, err 266 } 267 if firstDiffs != 0 { 268 firstRecords.addRecord(diffRecord) 269 } 270 namespace2, snapshotRecord2, err = snapshotReader2.Next() 271 if err != nil { 272 return nil, err 273 } 274 } 275 } 276 277 err = outputFileWriter.close() 278 if err != nil { 279 return nil, err 280 } 281 282 return outputFileWriter, nil 283 } 284 285 // firstRecords is a struct used to hold only the earliest records up to a given limit 286 type firstRecords struct { 287 records *diffRecordSlice 288 highestRecord *diffRecord 289 highestIndex int 290 limit int 291 } 292 293 func (s *firstRecords) addRecord(r *diffRecord) { 294 if len(*s.records) < s.limit { 295 *s.records = append(*s.records, r) 296 s.setHighestRecord() 297 } else { 298 if r.lessThan(s.highestRecord) { 299 (*s.records)[s.highestIndex] = r 300 s.setHighestRecord() 301 } 302 } 303 } 304 305 func (s *firstRecords) setHighestRecord() { 306 if len(*s.records) == 1 { 307 s.highestRecord = (*s.records)[0] 308 s.highestIndex = 0 309 return 310 } 311 for i, r := range *s.records { 312 if s.highestRecord.lessThan(r) { 313 s.highestRecord = r 314 s.highestIndex = i 315 } 316 } 317 } 318 319 type diffRecordSlice []*diffRecord 320 321 func (s diffRecordSlice) Len() int { 322 return len(s) 323 } 324 325 func (s diffRecordSlice) Swap(i, j int) { 326 s[i], s[j] = s[j], s[i] 327 } 328 329 func (s diffRecordSlice) Less(i, j int) bool { 330 return (s[i]).lessThan(s[j]) 331 } 332 333 // diffRecord represents a diverging record in json 334 type diffRecord struct { 335 Namespace string `json:"namespace,omitempty"` 336 Key string `json:"key,omitempty"` 337 Record1 *snapshotRecord `json:"snapshotrecord1"` 338 Record2 *snapshotRecord `json:"snapshotrecord2"` 339 } 340 341 // Creates a new diffRecord 342 func newDiffRecord(namespace string, record1 *privacyenabledstate.SnapshotRecord, 343 record2 *privacyenabledstate.SnapshotRecord) (*diffRecord, error) { 344 var s1, s2 *snapshotRecord = nil, nil // snapshot records 345 var k string // key 346 var err error 347 348 // Snapshot2 has a missing record 349 if record1 != nil { 350 k = string(record1.Key) 351 s1, err = newSnapshotRecord(record1) 352 if err != nil { 353 return nil, err 354 } 355 } 356 // Snapshot1 has a missing record 357 if record2 != nil { 358 k = string(record2.Key) 359 s2, err = newSnapshotRecord(record2) 360 if err != nil { 361 return nil, err 362 } 363 } 364 365 return &diffRecord{ 366 Namespace: namespace, 367 Key: k, 368 Record1: s1, 369 Record2: s2, 370 }, nil 371 } 372 373 // Get height from a diffRecord 374 func (d *diffRecord) getHeight() (blockNum uint64, txNum uint64) { 375 r := earlierRecord(d.Record1, d.Record2) 376 return r.BlockNum, r.TxNum 377 } 378 379 // Returns true if d is an earlier diffRecord than e 380 func (d *diffRecord) lessThan(e *diffRecord) bool { 381 dBlockNum, dTxNum := d.getHeight() 382 eBlockNum, eTxNum := e.getHeight() 383 384 if dBlockNum == eBlockNum { 385 return dTxNum <= eTxNum 386 } 387 return dBlockNum < eBlockNum 388 } 389 390 // snapshotRecord represents the data of a snapshot record in json 391 type snapshotRecord struct { 392 Value string `json:"value"` 393 BlockNum uint64 `json:"blockNum"` 394 TxNum uint64 `json:"txNum"` 395 } 396 397 // Returns the snapshotRecord with the earlier height 398 func earlierRecord(r1 *snapshotRecord, r2 *snapshotRecord) *snapshotRecord { 399 if r1 == nil { 400 return r2 401 } 402 if r2 == nil { 403 return r1 404 } 405 // Determine earlier record by block height 406 if r1.BlockNum < r2.BlockNum { 407 return r1 408 } 409 if r2.BlockNum < r1.BlockNum { 410 return r2 411 } 412 // Record block heights are the same, determine earlier transaction 413 if r1.TxNum < r2.TxNum { 414 return r1 415 } 416 return r2 417 } 418 419 // Creates a new SnapshotRecord 420 func newSnapshotRecord(record *privacyenabledstate.SnapshotRecord) (*snapshotRecord, error) { 421 blockNum, txNum, err := heightFromBytes(record.Version) 422 if err != nil { 423 return nil, err 424 } 425 426 return &snapshotRecord{ 427 Value: string(record.Value), 428 BlockNum: blockNum, 429 TxNum: txNum, 430 }, nil 431 } 432 433 // Obtain the block height and transaction height of a snapshot from its version bytes 434 func heightFromBytes(b []byte) (uint64, uint64, error) { 435 blockNum, n1, err := util.DecodeOrderPreservingVarUint64(b) 436 if err != nil { 437 return 0, 0, err 438 } 439 txNum, _, err := util.DecodeOrderPreservingVarUint64(b[n1:]) 440 if err != nil { 441 return 0, 0, err 442 } 443 444 return blockNum, txNum, nil 445 } 446 447 // nsKey is used to compare between snapshot records using both the namespace and key 448 type nsKey struct { 449 namespace string 450 key []byte 451 } 452 453 // Compares two nsKeys 454 // Returns: 455 // -1 if k1 > k2 456 // 1 if k1 < k2 457 // 0 if k1 == k2 458 func nsKeyCompare(k1, k2 *nsKey) int { 459 res := strings.Compare(k1.namespace, k2.namespace) 460 if res != 0 { 461 return res 462 } 463 return bytes.Compare(k1.key, k2.key) 464 } 465 466 // Extracts metadata from provided filepath 467 func readMetadata(fpath string) (*kvledger.SnapshotSignableMetadata, error) { 468 var mdata kvledger.SnapshotSignableMetadata 469 470 // Open file 471 f, err := ioutil.ReadFile(fpath) 472 if err != nil { 473 return nil, err 474 } 475 // Unmarshal bytes 476 err = json.Unmarshal([]byte(f), &mdata) 477 if err != nil { 478 return nil, err 479 } 480 481 return &mdata, nil 482 } 483 484 // Compares hashes of snapshots to determine if they can be compared, then returns channel name and block height for the output directory name 485 // Return values: 486 // equalPub - True if snapshot public data hashes are the same, false otherwise. If true, public differences will not be generated. 487 // equalPvt - True if snapshot private data hashes are the same, false otherwise. If true, private differences will not be generated. 488 // chName - Channel name shared between snapshots, used to name output directory. If channel names are not the same, no comparison is made. 489 // lastBN - Block height shared between snapshots, used to name output directory. If block heights are not the same, no comparison is made. 490 func hashesEqual(fpath1 string, fpath2 string) (equalPub bool, equalPvt bool, chName string, lastBN uint64, err error) { 491 var mdata1, mdata2 *kvledger.SnapshotSignableMetadata 492 493 // Read metadata from snapshot metadata filepaths 494 mdata1, err = readMetadata(fpath1) 495 if err != nil { 496 return false, false, "", 0, err 497 } 498 mdata2, err = readMetadata(fpath2) 499 if err != nil { 500 return false, false, "", 0, err 501 } 502 503 if mdata1.ChannelName != mdata2.ChannelName { 504 return false, false, "", 0, errors.Errorf("the supplied snapshots appear to be non-comparable. Channel names do not match."+ 505 "\nSnapshot1 channel name: %s\nSnapshot2 channel name: %s", mdata1.ChannelName, mdata2.ChannelName) 506 } 507 508 if mdata1.LastBlockNumber != mdata2.LastBlockNumber { 509 return false, false, "", 0, errors.Errorf("the supplied snapshots appear to be non-comparable. Last block numbers do not match."+ 510 "\nSnapshot1 last block number: %v\nSnapshot2 last block number: %v", mdata1.LastBlockNumber, mdata2.LastBlockNumber) 511 } 512 513 if mdata1.LastBlockHashInHex != mdata2.LastBlockHashInHex { 514 return false, false, "", 0, errors.Errorf("the supplied snapshots appear to be non-comparable. Last block hashes do not match."+ 515 "\nSnapshot1 last block hash: %s\nSnapshot2 last block hash: %s", mdata1.LastBlockHashInHex, mdata2.LastBlockHashInHex) 516 } 517 518 if mdata1.StateDBType != mdata2.StateDBType { 519 return false, false, "", 0, errors.Errorf("the supplied snapshots appear to be non-comparable. State db types do not match."+ 520 "\nSnapshot1 state db type: %s\nSnapshot2 state db type: %s", mdata1.StateDBType, mdata2.StateDBType) 521 } 522 523 pubDataHash1 := mdata1.FilesAndHashes[privacyenabledstate.PubStateDataFileName] 524 pubMdataHash1 := mdata1.FilesAndHashes[privacyenabledstate.PubStateMetadataFileName] 525 pvtDataHash1 := mdata1.FilesAndHashes[privacyenabledstate.PvtStateHashesFileName] 526 pvtMdataHash1 := mdata1.FilesAndHashes[privacyenabledstate.PvtStateHashesMetadataFileName] 527 528 pubDataHash2 := mdata2.FilesAndHashes[privacyenabledstate.PubStateDataFileName] 529 pubMdataHash2 := mdata2.FilesAndHashes[privacyenabledstate.PubStateMetadataFileName] 530 pvtDataHash2 := mdata2.FilesAndHashes[privacyenabledstate.PvtStateHashesFileName] 531 pvtMdataHash2 := mdata2.FilesAndHashes[privacyenabledstate.PvtStateHashesMetadataFileName] 532 533 equalPub = pubDataHash1 == pubDataHash2 && pubMdataHash1 == pubMdataHash2 534 equalPvt = pvtDataHash1 == pvtDataHash2 && pvtMdataHash1 == pvtMdataHash2 535 return equalPub, equalPvt, mdata1.ChannelName, mdata1.LastBlockNumber, nil 536 } 537 538 // jsonArrayFileWriter writes a list of diffRecords to a json file 539 type jsonArrayFileWriter struct { 540 file *os.File 541 buffer *bufio.Writer 542 encoder *json.Encoder 543 firstRecordWritten bool 544 count int 545 } 546 547 func newJSONFileWriter(filePath string) (*jsonArrayFileWriter, error) { 548 f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0o644) 549 if err != nil { 550 return nil, err 551 } 552 553 b := bufio.NewWriter(f) 554 // Opening bracket for beginning of diffRecord list 555 _, err = b.Write([]byte("[\n")) 556 if err != nil { 557 return nil, err 558 } 559 560 return &jsonArrayFileWriter{ 561 file: f, 562 buffer: b, 563 encoder: json.NewEncoder(b), 564 }, nil 565 } 566 567 func (w *jsonArrayFileWriter) addRecord(r interface{}) error { 568 // Add commas for records after the first in the list 569 if w.firstRecordWritten { 570 _, err := w.buffer.Write([]byte(",\n")) 571 if err != nil { 572 return err 573 } 574 } else { 575 w.firstRecordWritten = true 576 } 577 578 err := w.encoder.Encode(r) 579 if err != nil { 580 return err 581 } 582 w.count++ 583 584 return nil 585 } 586 587 func (w *jsonArrayFileWriter) close() error { 588 _, err := w.buffer.Write([]byte("]\n")) 589 if err != nil { 590 return err 591 } 592 593 err = w.buffer.Flush() 594 if err != nil { 595 return err 596 } 597 598 err = w.file.Sync() 599 if err != nil { 600 return err 601 } 602 603 err = fileutil.SyncParentDir(w.file.Name()) 604 if err != nil { 605 return err 606 } 607 608 return w.file.Close() 609 }