/* * This file is part of the Flowee project * Copyright (C) 2018-2020 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "CheckCommand.h" #include #include #include #include // private header for createShortHash() #include static void nothing(const char *){} namespace { void updateOutput(QTextStream &out, int current, int max) { const int progressBefore = ((current - 1) * 50) / max; const int progressAfter = (current * 50) / max; for (int i = progressBefore; i < progressAfter; ++i) { out << "."; if ((progressAfter % 10) == 0) out << (progressAfter * 2) << "%"; out.flush(); } } } CheckCommand::CheckCommand() { } QString CheckCommand::commandDescription() const { return "Check\nValidate the internal structure of the database"; } Flowee::ReturnCodes CheckCommand::run() { for (auto dataFile : dbDataFiles()) { for (auto infoFile : dataFile.infoFiles()) { out << "Working on info file; " << infoFile.filepath() << endl; const auto checkpoint = readInfoFile(infoFile.filepath()); if (checkpoint.jumptableFilepos < 0) continue; uint32_t jumptables[0x100000]; if (!readJumptables(infoFile.filepath(), checkpoint.jumptableFilepos, jumptables)) continue; if (checkpoint.jumptableHash != calcChecksum(jumptables)) { err << "CHECKSUM Failed" << endl; continue; } out << "Checking jumptable"; out.flush(); // check if jump table links to positions after our highest filepos for (int i = 0; i < 0x100000; ++i) { if (jumptables[i] > 0 && jumptables[i] >= checkpoint.positionInFile) { err << "shorthash: " << i << " points to disk pos " << (jumptables[i] - checkpoint.positionInFile) << " bytes after checkpoint file-pos" << endl; jumptables[i] = 0; } } out << " ok" << endl; auto dbs = infoFile.databaseFiles(); if (dbs.isEmpty()) { err << "Don't know which database file to open" << endl; continue; } out << "Opening DB file"; out.flush(); // open db file boost::iostreams::mapped_file file; file.open(dbs.first().filepath().toStdString(), std::ios_base::binary | std::ios_base::in); if (!file.is_open()) { err << "Failed to open db file" << endl; continue; } std::shared_ptr buffer = std::shared_ptr(const_cast(file.const_data()), nothing); int bucketCount = 0; for (int shorthash = 0; shorthash < 0x100000; ++shorthash) { if (jumptables[shorthash] != 0) ++bucketCount; } out << " ok\nChecking buckets: "; out.flush(); // read buckets int bucketsChecked = 0; for (int shorthash = 0; shorthash < 0x100000; ++shorthash) { if (jumptables[shorthash] == 0) continue; updateOutput(out, ++bucketsChecked, bucketCount); int32_t bucketOffsetInFile = static_cast(jumptables[shorthash]); Streaming::ConstBuffer buf(buffer, buffer.get() + bucketOffsetInFile, buffer.get() + file.size()); std::vector leafRefs = readBucket(buf, bucketOffsetInFile); for (auto leafRef : leafRefs) { if (leafRef.pos > checkpoint.positionInFile) { err << "Leaf after checkpoint pos" << endl; continue; } Streaming::ConstBuffer leafBuf(buffer, buffer.get() + leafRef.pos, buffer.get() + file.size()); Leaf leaf = readLeaf(leafBuf, leafRef.cheapHash); const uint64_t cheapHash = leaf.txid.GetCheapHash(); const uint32_t leafShorthash = createShortHash(cheapHash); if (shorthash != leafShorthash) err << "Leaf found under bucket with different shorthashes " << shorthash << " != " << leafShorthash << endl << " " << QString::fromStdString(leaf.txid.GetHex()) << "-" << leaf.outIndex << ", b: " << leaf.blockHeight << endl; if (leaf.blockHeight > checkpoint.lastBlockHeight) err << "Leaf belongs to a block newer than this checkpoint" << leaf.blockHeight << endl; else if (leaf.blockHeight < checkpoint.firstBlockHeight) err << "Leaf belongs to a block before this db file" << leaf.blockHeight << endl; } for (size_t n = 0; n < leafRefs.size(); ++n) { const int leafPos = leafRefs.at(n).pos; if (leafPos > checkpoint.positionInFile) continue; Streaming::ConstBuffer leafBuf(buffer, buffer.get() + leafPos, buffer.get() + file.size()); Leaf leaf = readLeaf(leafBuf, leafRefs.at(n).cheapHash); if (leaf.txid == uint256()) continue; for (size_t m = n + 1; m < leafRefs.size(); ++m) { const int leafPos2 = leafRefs.at(m).pos; Streaming::ConstBuffer leafBuf2(buffer, buffer.get() + leafPos2, buffer.get() + file.size()); Leaf leaf2 = readLeaf(leafBuf2, leafRefs.at(m).cheapHash); if (leaf.outIndex == leaf2.outIndex && leaf.txid == leaf2.txid) { err << "One utxo-entry is duplicated. " << QString::fromStdString(leaf.txid.GetHex()) << " | " << leaf.outIndex; } } } } out << endl; } } out << "Check finished" << endl; return Flowee::Ok; }