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"
|
|
|
|
|
|
2018-09-24 22:43:02 +02:00
|
|
|
// private header for createShortHash()
|
2018-09-09 22:08:28 +02:00
|
|
|
#include <utxo/UnspentOutputDatabase_p.h>
|
|
|
|
|
|
2021-11-02 11:04:59 +01:00
|
|
|
#include <primitives/Tx.h>
|
2018-09-24 22:43:02 +02:00
|
|
|
#include <server/dbwrapper.h>
|
|
|
|
|
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QFileInfo>
|
2019-03-29 21:06:57 +01:00
|
|
|
|
|
|
|
|
// from libs/server
|
2018-09-24 22:43:02 +02:00
|
|
|
#include <chain.h>
|
|
|
|
|
|
2019-08-24 13:20:37 +02:00
|
|
|
#include <boost/scoped_ptr.hpp>
|
|
|
|
|
|
2018-09-24 22:43:02 +02:00
|
|
|
|
2018-09-09 22:08:28 +02:00
|
|
|
static void nothing(const char *){}
|
|
|
|
|
|
2018-09-24 22:43:02 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2018-09-24 22:43:02 +02:00
|
|
|
void LookupCommand::findTransaction(const AbstractCommand::Leaf &leaf)
|
|
|
|
|
{
|
2020-03-01 23:13:48 +01:00
|
|
|
QFileInfo info(dbDataFiles().first().filepath());
|
2018-09-24 22:43:02 +02:00
|
|
|
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;
|
2018-09-24 22:43:02 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!file.seek(blockPos - 4)) {
|
2022-08-20 19:18:34 +02:00
|
|
|
err << "Block file too small" << Qt::endl;
|
2018-09-24 22:43:02 +02:00
|
|
|
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;
|
2018-09-24 22:43:02 +02:00
|
|
|
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;
|
2018-09-24 22:43:02 +02:00
|
|
|
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);
|
|
|
|
|
if (output.outputScript.size() == 0) {
|
|
|
|
|
err << "Could not find the output";
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-08-20 19:18:34 +02:00
|
|
|
out << " +- Value: " << output.outputValue << " sat" << Qt::endl;
|
2018-09-24 22:43:02 +02:00
|
|
|
out << " +- Script: 0x";
|
|
|
|
|
for (int i = 0; i < output.outputScript.size(); ++i) {
|
|
|
|
|
QString hex = QString::number(output.outputScript[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-24 22:43:02 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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]);
|
2018-09-20 22:59:52 +02:00
|
|
|
std::vector<LeafRef> leafs;
|
2018-09-10 11:15:08 +02:00
|
|
|
if (filePos >= 0) {
|
2018-09-20 22:59:52 +02:00
|
|
|
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;
|
2018-09-20 22:59:52 +02:00
|
|
|
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;
|
2018-09-20 22:59:52 +02:00
|
|
|
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
|
|
|
}
|
2018-09-24 22:43:02 +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;
|
|
|
|
|
}
|