2024-10-02 23:04:38 +02:00
|
|
|
/*
|
|
|
|
|
* 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 "QMLSweepHandler.h"
|
2024-10-03 14:29:05 +02:00
|
|
|
#include "IndexerServices.h"
|
2024-10-02 23:04:38 +02:00
|
|
|
#include <FloweePay.h>
|
2024-10-06 14:16:43 +02:00
|
|
|
#include <QDir>
|
2024-10-04 16:56:34 +02:00
|
|
|
#include <QFile>
|
2024-10-02 23:04:38 +02:00
|
|
|
#include <QTimer>
|
|
|
|
|
#include <base58.h>
|
|
|
|
|
#include <cashaddr.h>
|
2024-10-04 16:56:34 +02:00
|
|
|
#include <streaming/BufferPools.h>
|
2024-10-02 23:04:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
QMLSweepHandler::QMLSweepHandler(QObject *parent)
|
2024-10-03 14:29:05 +02:00
|
|
|
: QObject(parent),
|
|
|
|
|
m_fetcher(new TransactionsFetcher(this))
|
2024-10-02 23:04:38 +02:00
|
|
|
{
|
2024-10-03 14:29:05 +02:00
|
|
|
// make sure that we'll have a list of indexer services when we
|
|
|
|
|
// need them later.
|
|
|
|
|
FloweePay::instance()->indexerServices()->populate();
|
|
|
|
|
|
2024-10-06 22:36:10 +02:00
|
|
|
m_builder.setAnonimize(true);
|
|
|
|
|
|
|
|
|
|
connect (m_fetcher, &TransactionsFetcher::failed, this, [=](int failedLevel) {
|
2024-10-03 14:29:05 +02:00
|
|
|
auto services = FloweePay::instance()->indexerServices();
|
2024-10-06 22:36:10 +02:00
|
|
|
services->punish(m_fetcher->service(), failedLevel);
|
2024-10-03 14:29:05 +02:00
|
|
|
// try again
|
|
|
|
|
this->start();
|
|
|
|
|
}, Qt::QueuedConnection);
|
2024-10-03 17:55:26 +02:00
|
|
|
|
2024-10-06 14:07:33 +02:00
|
|
|
connect (m_fetcher, &TransactionsFetcher::searchComplete, this, [=]() {
|
|
|
|
|
auto f = m_fetcher;
|
|
|
|
|
if (f) {
|
|
|
|
|
setNumOutputsFound(m_fetcher->numOutputsFound());
|
|
|
|
|
setNumTokensFound(m_fetcher->numTokensFound());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-03 17:55:26 +02:00
|
|
|
connect (m_fetcher, &TransactionsFetcher::finished, this, &QMLSweepHandler::startTxBuilder);
|
2024-10-14 19:55:53 +02:00
|
|
|
|
|
|
|
|
connect (m_fetcher, &TransactionsFetcher::fetched, this, [=](int utxoCount) {
|
|
|
|
|
int progress = 1000;
|
|
|
|
|
auto f = m_fetcher;
|
|
|
|
|
if (f)
|
|
|
|
|
progress = std::round(utxoCount / (float) m_fetcher->numOutputsFound() * 1000);
|
|
|
|
|
setDownloadProgress(progress);
|
|
|
|
|
});
|
2024-10-02 23:04:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QMLSweepHandler::privKey() const
|
|
|
|
|
{
|
|
|
|
|
return m_privKey;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::setPrivKey(const QString &newPrivKey)
|
|
|
|
|
{
|
|
|
|
|
if (m_privKey == newPrivKey)
|
|
|
|
|
return;
|
|
|
|
|
m_privKey = newPrivKey;
|
|
|
|
|
m_addressHash.clear();
|
|
|
|
|
emit privKeyChanged();
|
|
|
|
|
|
|
|
|
|
// if the private key is valid, find the address and outputscript hash which
|
|
|
|
|
// we'll use to ask electrum-cash for content.
|
|
|
|
|
CBase58Data string;
|
|
|
|
|
if (string.SetString(newPrivKey.toStdString())) {
|
|
|
|
|
auto chain = FloweePay::instance()->chain();
|
|
|
|
|
if ((chain == P2PNet::MainChain && string.isMainnetPrivKey())
|
|
|
|
|
|| (chain == P2PNet::Testnet4Chain && string.isTestnetPrivKey())) {
|
2024-10-06 22:36:10 +02:00
|
|
|
m_key.set(string.data().begin(), string.data().begin() + 32,
|
2024-10-02 23:04:38 +02:00
|
|
|
string.data().size() > 32 && string.data().at(32) == 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-06 22:36:10 +02:00
|
|
|
if (!m_key.isValid()) {
|
2024-10-02 23:04:38 +02:00
|
|
|
setError(InvalidInput);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-10-06 22:36:10 +02:00
|
|
|
const auto id = m_key.getPubKey().getKeyId();
|
2024-10-02 23:04:38 +02:00
|
|
|
assert(id.size() == 20);
|
|
|
|
|
CashAddress::Content cashContent;
|
|
|
|
|
cashContent.type = CashAddress::PUBKEY_TYPE;
|
|
|
|
|
cashContent.hash = std::vector<uint8_t>(id.begin(), id.end());
|
|
|
|
|
m_addressHash = CashAddress::createHashedOutputScript(cashContent);
|
|
|
|
|
|
2024-10-06 22:36:10 +02:00
|
|
|
const std::string &chainPref = FloweePay::instance()->chainPrefix();
|
|
|
|
|
auto s = CashAddress::encodeCashAddr(chainPref, cashContent);
|
|
|
|
|
const auto size = chainPref.size();
|
|
|
|
|
setSweepAddress(QString::fromLatin1(s.c_str() + size + 1, s.size() - size -1)); // the 1 is for the colon
|
|
|
|
|
|
2024-10-02 23:04:38 +02:00
|
|
|
QTimer::singleShot(10, this, SLOT(start()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QMLSweepHandler::Error QMLSweepHandler::error() const
|
|
|
|
|
{
|
|
|
|
|
return m_error;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-03 14:29:05 +02:00
|
|
|
void QMLSweepHandler::setError(Error err)
|
2024-10-02 23:04:38 +02:00
|
|
|
{
|
2024-10-03 14:29:05 +02:00
|
|
|
if (m_error == err)
|
2024-10-02 23:04:38 +02:00
|
|
|
return;
|
2024-10-03 14:29:05 +02:00
|
|
|
m_error = err;
|
2024-10-02 23:04:38 +02:00
|
|
|
emit errorChanged();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-04 16:56:34 +02:00
|
|
|
AccountInfo *QMLSweepHandler::currentAccount() const
|
|
|
|
|
{
|
|
|
|
|
return m_account;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::setCurrentAccount(AccountInfo *account)
|
|
|
|
|
{
|
|
|
|
|
if (m_account == account)
|
|
|
|
|
return;
|
2024-10-05 21:13:14 +02:00
|
|
|
assert(!m_txBroadcastStarted);
|
|
|
|
|
if (m_txBroadcastStarted)
|
|
|
|
|
return;
|
2024-10-04 16:56:34 +02:00
|
|
|
m_account = account;
|
|
|
|
|
emit currentAccountChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::markUserApproved()
|
|
|
|
|
{
|
2024-10-05 21:13:14 +02:00
|
|
|
if (m_txBroadcastStarted)
|
|
|
|
|
return;
|
2024-10-04 16:56:34 +02:00
|
|
|
assert(m_account);
|
|
|
|
|
if (!m_account) {
|
|
|
|
|
logFatal(10007) << "Missing account";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_builder.outputCount() == 0 || m_builder.inputCount() == 0) {
|
|
|
|
|
logFatal(10007) << "No Tx to approve";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-05 21:13:14 +02:00
|
|
|
KeyId address;
|
|
|
|
|
/*int privKeyId = */ m_account->wallet()->reserveUnusedAddress(address, Wallet::ChangePath);
|
2024-10-06 22:36:10 +02:00
|
|
|
// ignore the returned value as we have no intention of ever 'unreserving' the key
|
|
|
|
|
// as we just broadcast in this same flow.
|
2024-10-05 21:13:14 +02:00
|
|
|
m_builder.selectOutput(0);
|
|
|
|
|
m_builder.pushOutputPay2Address(address);
|
2024-10-06 00:17:04 +02:00
|
|
|
setTargetAddress(renderAddress(address));
|
|
|
|
|
|
2024-10-06 22:36:10 +02:00
|
|
|
const auto tx = m_builder.createTransaction();
|
2024-10-05 21:13:14 +02:00
|
|
|
m_account->wallet()->newTransaction(tx);
|
|
|
|
|
m_account->wallet()->setTransactionComment(tx, tr("Swept funds"));
|
|
|
|
|
|
|
|
|
|
m_infoObject = std::make_shared<TxInfoObject>(m_account->wallet(), tx);
|
2024-10-09 16:43:05 +02:00
|
|
|
connect(m_infoObject.get(), SIGNAL(broadcastStatusChanged()), this, SIGNAL(broadcastStatusChanged()));
|
2024-10-05 21:13:14 +02:00
|
|
|
FloweePay::instance()->p2pNet()->connectionManager().broadcastTransaction(m_infoObject);
|
|
|
|
|
m_txBroadcastStarted = true;
|
|
|
|
|
emit broadcastStatusChanged();
|
2024-10-06 14:16:43 +02:00
|
|
|
|
|
|
|
|
// the transactions we spent were stored in a subdir.
|
|
|
|
|
// lets clean up after ourselves.
|
|
|
|
|
QDir subdir("sweep");
|
|
|
|
|
if (subdir.exists())
|
|
|
|
|
subdir.removeRecursively();
|
2024-10-05 21:13:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FloweePay::BroadcastStatus QMLSweepHandler::broadcastStatus() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_txBroadcastStarted)
|
|
|
|
|
return FloweePay::NotStarted;
|
2024-10-06 00:17:04 +02:00
|
|
|
#if 0
|
|
|
|
|
// This default-disabled code-snippet is fun to allow developing the UX/GUI by stepping through the steps.
|
|
|
|
|
// Alter the 'i == 4' to another value to make it stop at the step you want to see longer.
|
|
|
|
|
static int i = 0;
|
|
|
|
|
static QTimer *timer = nullptr;
|
|
|
|
|
if (timer == nullptr) {
|
|
|
|
|
timer = new QTimer(const_cast<QMLSweepHandler*>(this));
|
|
|
|
|
timer->start(3000);
|
|
|
|
|
connect(timer, &QTimer::timeout, [=]() {
|
|
|
|
|
if (++i == 4)
|
|
|
|
|
timer->stop();
|
|
|
|
|
emit const_cast<QMLSweepHandler*>(this)->broadcastStatusChanged();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
switch (i) {
|
|
|
|
|
case 0: return FloweePay::NotStarted;
|
|
|
|
|
case 1: return FloweePay::TxOffered;
|
|
|
|
|
case 3: return FloweePay::TxRejected;
|
|
|
|
|
case 4: return FloweePay::TxBroadcastSuccess;
|
|
|
|
|
default: return FloweePay::TxWaiting;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2024-10-05 21:13:14 +02:00
|
|
|
auto infoObject = m_infoObject;
|
|
|
|
|
if (infoObject.get() == nullptr)
|
|
|
|
|
return FloweePay::TxBroadcastSuccess;
|
|
|
|
|
return infoObject->broadcastStatus();
|
2024-10-04 16:56:34 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-02 23:04:38 +02:00
|
|
|
void QMLSweepHandler::start()
|
|
|
|
|
{
|
2024-10-03 14:29:05 +02:00
|
|
|
auto service = FloweePay::instance()->indexerServices()->service();
|
|
|
|
|
if (service.hostname.empty()) {
|
|
|
|
|
// lets not translate this, since this is likely an
|
|
|
|
|
// internal error (aka bug) or simply a lack of Internet.
|
|
|
|
|
setError(NoBackendFound);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_fetcher->setService(service);
|
|
|
|
|
m_fetcher->start(m_addressHash);
|
2024-10-02 23:04:38 +02:00
|
|
|
}
|
2024-10-03 17:55:26 +02:00
|
|
|
|
|
|
|
|
void QMLSweepHandler::startTxBuilder(const QList<TransactionsFetcher::Output> &result)
|
|
|
|
|
{
|
2024-10-04 16:56:34 +02:00
|
|
|
assert(m_fetcher);
|
|
|
|
|
assert(m_builder.inputCount() == 0);
|
|
|
|
|
assert(m_builder.outputCount() == 0);
|
2024-10-06 22:36:10 +02:00
|
|
|
assert(m_key.isValid());
|
2024-10-06 14:07:33 +02:00
|
|
|
setNumOutputsFound(m_fetcher->numOutputsFound());
|
|
|
|
|
setNumTokensFound(m_fetcher->numTokensFound());
|
2024-10-03 17:55:26 +02:00
|
|
|
m_fetcher->deleteLater();
|
|
|
|
|
m_fetcher = nullptr;
|
2024-10-04 16:56:34 +02:00
|
|
|
|
|
|
|
|
int64_t inputs = 0;
|
|
|
|
|
for (const auto &prevOut : result) {
|
2024-10-06 22:36:10 +02:00
|
|
|
logDebug(10007) << "Using input:" << prevOut.txid << prevOut.outIndex;
|
|
|
|
|
uint256 txid = uint256S(prevOut.txid.toStdString());
|
|
|
|
|
m_builder.appendInput(txid, prevOut.outIndex);
|
2024-10-04 16:56:34 +02:00
|
|
|
|
|
|
|
|
QFile txIn(prevOut.filename);
|
|
|
|
|
if (!txIn.open(QIODevice::ReadOnly)) {
|
|
|
|
|
logCritical(10007) << "Failed to open file" << prevOut.filename;
|
|
|
|
|
setError(FileError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
auto pool = Streaming::pool(txIn.size());
|
|
|
|
|
txIn.read(pool->begin(), txIn.size());
|
|
|
|
|
Tx tx(pool->commit(txIn.size()));
|
|
|
|
|
Tx::Iterator iter(tx);
|
|
|
|
|
Tx::Output out;
|
|
|
|
|
for (int index = 0; index <= prevOut.outIndex; ++index) {
|
|
|
|
|
out = Tx::nextOutput(iter);
|
|
|
|
|
}
|
|
|
|
|
if (out.outputValue < 0) {
|
|
|
|
|
logCritical(10007) << "Invalid indexer data";
|
|
|
|
|
setError(DataInconsistency);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
inputs += out.outputValue;
|
|
|
|
|
m_builder.pushInputSignature(m_key, out.outputScript, out.outputValue, TransactionBuilder::Schnorr);
|
|
|
|
|
}
|
|
|
|
|
m_builder.appendOutput(inputs);
|
|
|
|
|
m_builder.setOutputFeeSource(0);
|
|
|
|
|
// We're not doing the last step here of assigning our address since the user may change account before we send.
|
|
|
|
|
|
|
|
|
|
logInfo(10007) << "Built a transaction with" << m_builder.inputCount() << " => " << m_builder.outputCount();
|
|
|
|
|
logInfo(10007) << "Total sats:" << inputs;
|
2024-10-06 00:17:04 +02:00
|
|
|
setSweepTotal(inputs);
|
|
|
|
|
setPrepared(true);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-14 19:55:53 +02:00
|
|
|
int QMLSweepHandler::downloadProgress() const
|
|
|
|
|
{
|
|
|
|
|
return m_downloadProgress;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::setDownloadProgress(int progress)
|
|
|
|
|
{
|
|
|
|
|
if (m_downloadProgress == progress)
|
|
|
|
|
return;
|
|
|
|
|
m_downloadProgress = progress;
|
|
|
|
|
emit downloadProgressChanged();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-06 14:07:33 +02:00
|
|
|
QString QMLSweepHandler::sweepAddress() const
|
|
|
|
|
{
|
|
|
|
|
return m_sweepAddress;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::setSweepAddress(const QString &newSweepAddress)
|
|
|
|
|
{
|
|
|
|
|
if (m_sweepAddress == newSweepAddress)
|
|
|
|
|
return;
|
|
|
|
|
m_sweepAddress = newSweepAddress;
|
|
|
|
|
emit sweepAddressChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int QMLSweepHandler::numOutputsFound() const
|
|
|
|
|
{
|
|
|
|
|
return m_numOutputsFound;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::setNumOutputsFound(int newNumOutputsFound)
|
|
|
|
|
{
|
|
|
|
|
if (m_numOutputsFound == newNumOutputsFound)
|
|
|
|
|
return;
|
|
|
|
|
m_numOutputsFound = newNumOutputsFound;
|
|
|
|
|
emit numOutputsFoundChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int QMLSweepHandler::numTokensFound() const
|
|
|
|
|
{
|
|
|
|
|
return m_numTokensFound;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::setNumTokensFound(int newNumTokensFound)
|
|
|
|
|
{
|
|
|
|
|
if (m_numTokensFound == newNumTokensFound)
|
|
|
|
|
return;
|
|
|
|
|
m_numTokensFound = newNumTokensFound;
|
|
|
|
|
emit numTokensFoundChanged();
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-06 00:17:04 +02:00
|
|
|
double QMLSweepHandler::sweepTotal() const
|
|
|
|
|
{
|
|
|
|
|
return m_sweepTotal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::setSweepTotal(double newSweepTotal)
|
|
|
|
|
{
|
|
|
|
|
if (qFuzzyCompare(m_sweepTotal, newSweepTotal))
|
|
|
|
|
return;
|
|
|
|
|
m_sweepTotal = newSweepTotal;
|
|
|
|
|
emit sweepTotalChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QMLSweepHandler::prepared() const
|
|
|
|
|
{
|
|
|
|
|
return m_prepared;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::setPrepared(bool newPrepared)
|
|
|
|
|
{
|
|
|
|
|
if (m_prepared == newPrepared)
|
|
|
|
|
return;
|
|
|
|
|
m_prepared = newPrepared;
|
|
|
|
|
emit preparedChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QMLSweepHandler::targetAddress() const
|
|
|
|
|
{
|
|
|
|
|
return m_targetAddress;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QMLSweepHandler::setTargetAddress(const QString &newTargetAddress)
|
|
|
|
|
{
|
|
|
|
|
if (m_targetAddress == newTargetAddress)
|
|
|
|
|
return;
|
|
|
|
|
m_targetAddress = newTargetAddress;
|
|
|
|
|
emit targetAddressChanged();
|
2024-10-03 17:55:26 +02:00
|
|
|
}
|