Files
pay/modules/send-sweep/TransactionsFetcher.cpp
T

170 lines
6.2 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (C) 2024 Tom Zander <tom@flowee.org>
*
* 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 "TransactionsFetcher.h"
#include <QFile>
#include <QSslSocket>
#include <primitives/Tx.h>
#include <QDir>
#include <QFileInfo>
#include <qjsonarray.h>
#include <streaming/BufferPools.h>
TransactionsFetcher::TransactionsFetcher(QObject *parent)
: ElectronXClient{parent}
{
}
void TransactionsFetcher::start(const Streaming::ConstBuffer &scripthash)
{
logCritical(1007) << "Starting check online, scripthash:" << scripthash.toHex_reversed();
m_scriptHash = scripthash;
QDir current;
current.mkpath("sweep");
connectToServer();
}
void TransactionsFetcher::handshakeCompleted()
{
QString call("{\"jsonrpc\":\"2.0\","
"\"method\":\"blockchain.scripthash.listunspent\","
"\"params\": [\"%1\"], \"id\": 2}");
auto param = m_scriptHash.toHex_reversed();
call = call.arg(QString::fromLatin1(param));
m_electronServer->write(call.toLatin1());
m_electronServer->write("\n");
}
void TransactionsFetcher::handleResponse(int id, const QJsonObject &data)
{
if (id == 2) { // the list-unspent
auto result = data["result"].toArray();
QSet<QString> txids;
for (auto item = result.begin(); item != result.end(); item++) {
if (item->isObject()) {
auto result = item->toObject();
auto token = result[QLatin1String("token_data")];
if (!token.isNull()) {
++m_tokensFound;
}
// auto height = result[QLatin1String("height")];
auto txid = result[QLatin1String("tx_hash")];
auto outIndex = result[QLatin1String("tx_pos")];
auto amount = result[QLatin1String("value")];
if (txid.isNull() || outIndex.isNull() || amount.isNull()) {
logCritical(10051) << "Missing data in result row";
continue;
}
m_balance += amount.toInteger();
++m_coinsFound;
Output o;
o.txid = txid.toString();
if (o.txid.size() != 64) {
logCritical(10051) << "Malformed txid, not reaping";
continue;
}
o.outIndex = outIndex.toInt();
m_outputs.append(o);
// check for duplicates to avoid downloading the same transaction multiple
// times if there are multiple outputs on the same tx for this one address.
if (txids.contains(o.txid))
continue;
QFileInfo info(QString("sweep/%1").arg(o.txid));
if (info.exists()) {
m_outputs.back().filename = info.absoluteFilePath();
continue;
}
if (o.filename.isEmpty()) {
QString call("{\"jsonrpc\":\"2.0\","
"\"method\":\"blockchain.transaction.get\","
"\"params\": [\"%1\"], \"id\": 3}");
call = call.arg(o.txid);
m_electronServer->write(call.toLatin1());
m_electronServer->write("\n");
}
}
}
logCritical(10051) << "Remote indexer reported" << m_tokensFound << "tokens &" << m_coinsFound << "coins";
checkAllAvailable();
emit searchComplete();
}
if (id == 3) { // transaction.get
auto result = data["result"];
if (!result.isString()) {
logCritical(10051) << "Expected transaction data, didn't see that, though";
return;
}
auto hexBytes = result.toString().toLatin1();
auto pool = Streaming::pool(hexBytes.size());
pool->writeHex(hexBytes.constData(), hexBytes.size());
auto txData = pool->commit();
auto filename = QString("sweep/%1");
auto txHash = Tx(txData).createHash();
const auto txid = QString::fromStdString(txHash.ToString());
filename = filename.arg(txid);
QFile out(filename);
if (!out.open(QIODevice::WriteOnly)) {
logFatal(10051) << "Could not write the transaction";
abort();
}
out.write(txData.begin(), txData.size());
out.flush();
out.close();
bool found = false;
int totalFound = 0;
for (auto i = m_outputs.begin(); i != m_outputs.end(); ++i) {
if (txid == i->txid) {
i->filename = out.fileName();
found = true;
// note, do NOT add a break; here.
// Multiple outputs may all spend the same txid (but different output indexes).
}
if (!i->filename.isEmpty())
++totalFound;
}
if (!found)
logCritical(10051) << "Received a tx from server that I didn't request" << txid;
if (totalFound != m_utxosDownloaded) {
assert(m_utxosDownloaded < totalFound); // can only go up...
m_utxosDownloaded = totalFound;
emit fetched(m_utxosDownloaded);
}
checkAllAvailable();
}
}
void TransactionsFetcher::checkAllAvailable()
{
for (auto i = m_outputs.begin(); i != m_outputs.end(); ++i) {
if (i->filename.isEmpty()) {
logInfo(10051) << "Waiting for download of" << i->txid;
return;
}
}
logCritical(10051) << "Download of" << m_outputs.size() << "transactions finished";
emit finished(m_outputs);
}