/* * This file is part of the Flowee project * Copyright (C) 2024 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 "TransactionsFetcher.h" #include #include #include #include #include #include #include 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 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); }