Files
thehub/unspentdb/LookupCommand.cpp
T

266 lines
9.7 KiB
C++
Raw Permalink Normal View History

2018-09-09 22:08:28 +02:00
/*
* This file is part of the Flowee project
2021-06-20 22:44:44 +02:00
* Copyright (C) 2018-2020 Tom Zander <tom@flowee.org>
2018-09-09 22:08:28 +02:00
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "LookupCommand.h"
// private header for createShortHash()
2018-09-09 22:08:28 +02:00
#include <utxo/UnspentOutputDatabase_p.h>
#include <primitives/Tx.h>
#include <server/dbwrapper.h>
#include <QDir>
#include <QFileInfo>
2019-03-29 21:06:57 +01:00
// from libs/server
#include <chain.h>
2019-08-24 13:20:37 +02:00
#include <boost/scoped_ptr.hpp>
2018-09-09 22:08:28 +02:00
static void nothing(const char *){}
namespace {
static const char DB_BLOCK_INDEX = 'b';
class BlocksDB : public CDBWrapper
{
public:
BlocksDB(const boost::filesystem::path& path)
: CDBWrapper(path / "blocks" / "index", 1000, false, false)
{
}
bool findBlock(int blockHeight, int &rFile, int &rPos)
{
boost::scoped_ptr<CDBIterator> pcursor(NewIterator());
pcursor->Seek(std::make_pair(DB_BLOCK_INDEX, uint256()));
while (pcursor->Valid()) {
std::pair<char, uint256> key;
if (pcursor->GetKey(key) && key.first == DB_BLOCK_INDEX) {
CDiskBlockIndex diskindex;
if (pcursor->GetValue(diskindex)) {
if (diskindex.nHeight == blockHeight && (diskindex.nStatus & BLOCK_FAILED_MASK) == 0) {
rFile = diskindex.nFile;
rPos = diskindex.nDataPos;
return true;
}
pcursor->Next();
} else {
return false;
}
} else {
break;
}
}
return false;
}
};
}
2018-09-09 22:08:28 +02:00
LookupCommand::LookupCommand()
2018-09-10 11:15:08 +02:00
: m_printDebug(QStringList() << "v" << "debug", "Print internal DB details"),
m_all(QStringList() << "a" << "all", "Use historical checkpoints as well"),
m_filepos(QStringList() << "filepos", "Lookup and print the leaf at a specific file [pos]", "pos")
2018-09-09 22:08:28 +02:00
{
}
QString LookupCommand::commandDescription() const
{
return "Lookup\nCheck and print if a certain utxo entry is available";
}
void LookupCommand::addArguments(QCommandLineParser &commandLineParser)
{
commandLineParser.addPositionalArgument("txid", "Transaction ID");
commandLineParser.addPositionalArgument("output", "index of the output");
commandLineParser.addOption(m_printDebug);
2018-09-10 11:15:08 +02:00
commandLineParser.addOption(m_all);
commandLineParser.addOption(m_filepos);
2018-09-09 22:08:28 +02:00
}
void LookupCommand::findTransaction(const AbstractCommand::Leaf &leaf)
{
2020-03-01 23:13:48 +01:00
QFileInfo info(dbDataFiles().first().filepath());
QDir dir = info.absoluteDir();
dir.cdUp();
BlocksDB db(dir.absolutePath().toStdString());
int fileno, blockPos;
if (db.findBlock(leaf.blockHeight, fileno, blockPos)) {
dir.cd("blocks");
QFile file(dir.filePath(QString("blk%1.dat").arg(QString::number(fileno), 5, QLatin1Char('0'))));
if (!file.open(QIODevice::ReadOnly)) {
2022-08-20 19:18:34 +02:00
err << "Failed to open block file" << file.fileName() << Qt::endl;
return;
}
if (!file.seek(blockPos - 4)) {
2022-08-20 19:18:34 +02:00
err << "Block file too small" << Qt::endl;
return;
}
QByteArray blockSizeBytes = file.read(4);
uint32_t blockSize = le32toh(*(reinterpret_cast<const std::uint32_t*>(blockSizeBytes.data())));
if (leaf.offsetInBlock >= blockSize) {
2022-08-20 19:18:34 +02:00
err << "Block smaller than offset of transaction" << Qt::endl;
return;
}
if (!file.seek(blockPos + leaf.offsetInBlock)) {
2022-08-20 19:18:34 +02:00
err << "Seek failed to move to transaction pos" << Qt::endl;
return;
}
const int size = blockSize - leaf.offsetInBlock;
Streaming::BufferPool pool(size);
file.read(pool.begin(), size);
Tx tx(pool.commit(size));
Tx::Output output = tx.output(leaf.outIndex);
2026-05-13 16:43:23 +02:00
const auto script = output.outputScript();
if (script.size() == 0) {
err << "Could not find the output";
return;
}
2022-08-20 19:18:34 +02:00
out << " +- Value: " << output.outputValue << " sat" << Qt::endl;
out << " +- Script: 0x";
2026-05-13 16:43:23 +02:00
for (int i = 0; i < script.size(); ++i) {
QString hex = QString::number(script[i], 16);
if (hex.length() < 2)
out << "0";
out << hex;
}
2022-08-20 19:18:34 +02:00
out << Qt::endl;
out << Qt::endl;
}
}
2018-09-09 22:08:28 +02:00
Flowee::ReturnCodes LookupCommand::run()
{
QStringList args = commandLineParser().positionalArguments();
if (args.isEmpty()) {
commandLineParser().showHelp();
return Flowee::InvalidOptions;
}
uint256 hash;
hash.SetHex(args.first().toLatin1().constData());
int outindex = -1;
if (args.size() > 1) {
bool ok;
outindex = args.at(1).toInt(&ok);
if (!ok || outindex < 0) {
2022-08-20 19:18:34 +02:00
err << "second argument is the out, index. Which should be a positive number." << Qt::endl;
2018-09-09 22:08:28 +02:00
return Flowee::InvalidOptions;
}
}
2022-08-20 19:18:34 +02:00
out << "Searching for " << QString::fromStdString(hash.GetHex()) << Qt::endl;
2018-09-09 22:08:28 +02:00
const uint64_t cheapHash = hash.GetCheapHash();
const uint32_t shortHash = createShortHash(cheapHash);
const bool debug = commandLineParser().isSet(m_printDebug);
if (debug)
2022-08-20 19:18:34 +02:00
out << "cheapHash: " << cheapHash << ", shortHash: " << shortHash << Qt::endl;
2018-09-09 22:08:28 +02:00
2018-09-10 11:15:08 +02:00
int filePos = -1;
if (commandLineParser().isSet(m_filepos)) {
bool ok;
filePos = commandLineParser().value(m_filepos).toInt(&ok);
if (!ok << filePos < 0) {
2022-08-20 19:18:34 +02:00
err << "Filepos has to be a positive number" << Qt::endl;
2018-09-10 11:15:08 +02:00
return Flowee::InvalidOptions;
}
}
2020-03-01 23:13:48 +01:00
2018-09-10 11:15:08 +02:00
QList<DatabaseFile> files;
2020-03-01 23:13:48 +01:00
if (commandLineParser().isSet(m_all)) {
for (auto dbFile : dbDataFiles()) {
files.append(dbFile.infoFiles());
}
} else {
2018-09-10 11:15:08 +02:00
files = highestDataFiles();
2020-03-01 23:13:48 +01:00
}
2018-09-10 11:15:08 +02:00
for (auto info : files) {
if (debug)
2022-08-20 19:18:34 +02:00
out << "Opening " << info.filepath() << Qt::endl;
2018-09-09 22:08:28 +02:00
const auto checkpoint = readInfoFile(info.filepath());
if (checkpoint.jumptableFilepos < 0) {
2022-08-20 19:18:34 +02:00
err << "failed parsing " << info.filepath() << Qt::endl;
2018-09-09 22:08:28 +02:00
continue;
}
uint32_t jumptables[0x100000];
if (!readJumptables(info.filepath(), checkpoint.jumptableFilepos, jumptables)) {
2022-08-20 19:18:34 +02:00
err << "failed parsing(2) " << info.filepath() << Qt::endl;
2018-09-09 22:08:28 +02:00
continue;
}
if (checkpoint.jumptableHash != calcChecksum(jumptables)) {
2022-08-20 19:18:34 +02:00
err << "failed parsing(3) " << info.filepath() << Qt::endl;
2018-09-09 22:08:28 +02:00
continue;
}
const DatabaseFile db = info.databaseFiles().first();
boost::iostreams::mapped_file file;
file.open(db.filepath().toStdString(), std::ios_base::binary | std::ios_base::in);
if (!file.is_open()) {
2022-08-20 19:18:34 +02:00
err << "failed parsing(4) " << info.filepath() << Qt::endl;
2018-09-09 22:08:28 +02:00
continue;
}
std::shared_ptr<char> buffer = std::shared_ptr<char>(const_cast<char*>(file.const_data()), nothing);
2018-09-10 11:15:08 +02:00
const int32_t bucketOffsetInFile = static_cast<int>(jumptables[shortHash]);
std::vector<LeafRef> leafs;
2018-09-10 11:15:08 +02:00
if (filePos >= 0) {
leafs.push_back({0, filePos});
2018-09-10 11:15:08 +02:00
}
else if (bucketOffsetInFile) {
2018-09-09 22:08:28 +02:00
if (debug)
2022-08-20 19:18:34 +02:00
out << "File has appropriate bucket " << db.filepath() << Qt::endl;
2018-09-09 22:08:28 +02:00
Streaming::ConstBuffer buf(buffer, buffer.get() + bucketOffsetInFile, buffer.get() + file.size());
2018-09-10 11:15:08 +02:00
leafs = readBucket(buf, bucketOffsetInFile);
}
bool foundOne = false;
for (auto leafRef : leafs) {
Streaming::ConstBuffer leafBuf(buffer, buffer.get() + leafRef.pos, buffer.get() + file.size());
2018-09-10 11:15:08 +02:00
if (debug)
2022-08-20 19:18:34 +02:00
out << " + checking leaf at filepos: " << leafRef.pos << Qt::endl;
Leaf leaf = readLeaf(leafBuf, leafRef.cheapHash);
2018-09-10 11:15:08 +02:00
if (leaf.txid == hash && (outindex == -1 || outindex == leaf.outIndex)) {
if (!foundOne) {
out << "In UTXO up to block height: " << checkpoint.lastBlockHeight << " (" <<
2022-08-20 19:18:34 +02:00
QString::fromStdString(checkpoint.lastBlockId.GetHex()) << ")" << Qt::endl;
2018-09-09 22:08:28 +02:00
if (debug)
2022-08-20 19:18:34 +02:00
out << "In DB file " << db.filepath() << Qt::endl;
2018-09-10 11:15:08 +02:00
}
foundOne = true;
out << "Entry is unspent; " << QString::fromStdString(leaf.txid.GetHex()) << "-"
2022-08-20 19:18:34 +02:00
<< leaf.outIndex << Qt::endl;
2018-09-10 11:15:08 +02:00
if (debug) {
2022-08-20 19:18:34 +02:00
out << " tx is in block " << leaf.blockHeight << ", tx is at bytepos in block: " << leaf.offsetInBlock << Qt::endl;
out << " Leaf file offset: " << leafRef.pos << Qt::endl;
2018-09-09 22:08:28 +02:00
}
findTransaction(leaf);
2018-09-09 22:08:28 +02:00
}
if (foundOne)
return Flowee::Ok;
2018-09-10 11:15:08 +02:00
else if (debug && filePos >= 0) {
2022-08-20 19:18:34 +02:00
out << "Recoverable data:" << Qt::endl
<< " TXID: " << QString::fromStdString(leaf.txid.GetHex()) << "-" << leaf.outIndex << Qt::endl
<< " Block height: " << leaf.blockHeight << ", offset in block: " << leaf.offsetInBlock << Qt::endl << Qt::endl;
2018-09-10 11:15:08 +02:00
}
2018-09-09 22:08:28 +02:00
}
}
return Flowee::CommandFailed;
}