1aa3a14611
The Sweep feature needs the 'previous' transactions to build the new transaction. This shows a progress-report per-transaction as they are downloaded. Additionally this splits the incoming electron-X reply into lines before processing.
170 lines
6.2 KiB
C++
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(10007) << "Missing data in result row";
|
|
continue;
|
|
}
|
|
m_balance += amount.toInteger();
|
|
++m_coinsFound;
|
|
|
|
Output o;
|
|
o.txid = txid.toString();
|
|
if (o.txid.size() != 64) {
|
|
logCritical(10007) << "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(10007) << "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(10007) << "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(10007) << "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(10007) << "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(10007) << "Waiting for download of" << i->txid;
|
|
return;
|
|
}
|
|
}
|
|
|
|
logCritical(10007) << "Download of" << m_outputs.size() << "transactions finished";
|
|
emit finished(m_outputs);
|
|
}
|