Files
pay/modules/big-transfer/QMLTransferManager.cpp
T

406 lines
12 KiB
C++
Raw Permalink Normal View History

2024-12-24 15:09:20 +01:00
/*
* This file is part of the Flowee project
2025-01-01 17:36:58 +01:00
* Copyright (C) 2024-2025 Tom Zander <tom@flowee.org>
2024-12-24 15:09:20 +01: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 "QMLTransferManager.h"
#include "FloweePay.h"
2024-12-29 19:13:22 +01:00
#include "TxInfoObject.h"
#include <QFile>
#include <QTimer>
#include <TransactionBuilder.h>
2024-12-24 15:09:20 +01:00
QMLTransferManager::QMLTransferManager(QObject *parent)
: QObject(parent)
{
}
void QMLTransferManager::prepare()
{
if (m_prepareRunning)
return;
assert(m_fromAccount);
m_prepareRunning = true;
2025-01-06 18:00:20 +01:00
freePreparedTransactions(); // also clears the transactions list.
// the following may take a bit longer if the wallet is large.
// do this a tad later in order to avoid the button press not
// seeming to do anything.
const Wallet *wIn = m_fromAccount->wallet();
if (m_fromAccount->isSingleAddressAccount()) {
// We create one transaction per UTXO, since the alternative
// is one big transaction for this kind of wallet.
QTimer::singleShot(100, this, [=]() {
const auto walletSecrets = wIn->walletSecrets();
assert(walletSecrets.size() == 1);
auto secret = walletSecrets.begin();
const auto utxos = wIn->unspentOutputsForKey(secret->first);
for (const auto &utxo : utxos) {
// NOTICE in future we may want to support token moving as well.
if (wIn->txOutput(utxo).hasCashToken == false) {
PreviewTx *tx = new PreviewTx(this);
tx->m_utxos.push_back(utxo);
tx->m_value += wIn->utxoOutputValue(utxo);
const auto &fromSecret = secret->second;
tx->m_from = new Address(renderAddress(fromSecret.address),
fromSecret.cloakedAddress(), tx);
m_transactions.append(tx);
}
}
emit transactionsChanged();
emit unsentTxCountChanged();
m_prepareRunning = false;
});
return;
}
2024-12-29 19:13:22 +01:00
QTimer::singleShot(100, this, [=]() {
// we create one transaction for each address (aka private key).
// Regardless how many inputs this creates in a transaction.
const auto walletSecrets = wIn->walletSecrets();
for (auto i = walletSecrets.begin(); i != walletSecrets.end(); ++i) {
const auto utxos = wIn->unspentOutputsForKey(i->first);
bool foundOne = false;
for (const auto &utxo : utxos) {
// NOTICE in future we may want to support token moving as well.
if (wIn->txOutput(utxo).hasCashToken == false) {
foundOne = true;
break;
}
}
if (foundOne) {
2025-01-06 18:00:20 +01:00
PreviewTx *tx = new PreviewTx(this);
tx->m_utxos = utxos;
for (const auto &utxo : utxos) {
2025-01-06 18:00:20 +01:00
tx->m_value += wIn->utxoOutputValue(utxo);
}
const auto &fromSecret = i->second;
2025-01-06 18:00:20 +01:00
tx->m_from = new Address(renderAddress(fromSecret.address),
fromSecret.cloakedAddress(), tx);
m_transactions.append(tx);
}
}
2024-12-29 19:13:22 +01:00
emit transactionsChanged();
emit unsentTxCountChanged();
m_prepareRunning = false;
});
}
2024-12-29 19:13:22 +01:00
void QMLTransferManager::send(QObject *previewTx)
{
assert(previewTx);
auto data = qobject_cast<PreviewTx*>(previewTx);
if (!data)
return;
if (data->m_sent)
return;
// create the actual minable transaction
2025-01-09 17:35:57 +01:00
Tx tx;
try {
tx = createTx(data);
} catch (const std::exception &e) {
logCritical() << e;
return;
}
2024-12-29 19:13:22 +01:00
data->m_sent = true;
2024-12-31 15:55:36 +01:00
// call to fromWallet to mark outputs locked and save tx.
auto fromWallet = m_fromAccount->wallet();
fromWallet->addTransaction(tx, Wallet::NoNotification);
2024-12-31 15:55:36 +01:00
fromWallet->setTransactionComment(tx, tr("Migrated Coin"));
// call to toWallet to have it too!
auto toWallet = m_toAccount->wallet();
toWallet->addTransaction(tx, Wallet::NoNotification);
2024-12-31 15:55:36 +01:00
toWallet->setTransactionComment(tx, tr("Migrated Coin"));
2024-12-29 19:13:22 +01:00
// and broadcast it.
auto txInfo = std::make_shared<TxInfoObject>(m_toAccount->wallet(), tx);
2025-04-21 17:37:53 +02:00
FloweePay::instance()->broadcastTransaction(txInfo);
2025-01-01 17:36:58 +01:00
// the txInfo is a sharedPtr, we need to store it somewhere to not get deleted
// when it goes out of scope at the end of this method.
data->setFinalTx(txInfo);
2024-12-29 19:13:22 +01:00
emit data->sentChanged();
emit unsentTxCountChanged();
}
void QMLTransferManager::sendAll()
{
assert(m_fromAccount);
assert(m_toAccount);
2024-12-29 19:13:22 +01:00
for (auto data_ : std::as_const(m_transactions)) {
auto *data = qobject_cast<PreviewTx*>(data_);
send(data);
}
2024-12-29 19:13:22 +01:00
emit unsentTxCountChanged();
}
int QMLTransferManager::coinCount() const
{
return m_coinCount;
}
2024-12-29 19:13:22 +01:00
int QMLTransferManager::unsentTxCount() const
{
int count = 0;
for (auto *tx_ : m_transactions) {
auto *tx = qobject_cast<PreviewTx*>(tx_);
if (!tx->m_sent)
++count;
}
return count;
}
bool QMLTransferManager::inputsOk() const
{
if (m_fromAccount == nullptr)
return false;
2024-12-31 15:55:36 +01:00
if (m_addressCount == 0)
return false;
if (!m_fromAccount->isDecrypted())
return false;
if (m_toAccount == nullptr)
return false;
2024-12-31 15:55:36 +01:00
if (m_toAccount->needsPinToOpen() && !m_toAccount->isDecrypted())
return false;
if (m_toAccount->isSingleAddressAccount())
return false;
2024-12-29 19:13:22 +01:00
return m_toAccount != m_fromAccount;
}
void QMLTransferManager::setCoinCount(int c)
{
if (m_coinCount == c)
return;
m_coinCount = c;
emit coinCountChanged();
}
2025-01-06 18:00:20 +01:00
void QMLTransferManager::freePreparedTransactions()
{
2025-01-06 18:00:20 +01:00
if (!m_transactions.isEmpty()) {
qDeleteAll(m_transactions);
m_transactions.clear();
emit transactionsChanged();
}
}
2024-12-29 19:13:22 +01:00
Tx QMLTransferManager::createTx(PreviewTx *tx)
{
assert(tx);
assert(!tx->m_sent);
assert(m_fromAccount);
2025-01-06 18:00:20 +01:00
assert(m_toAccount);
2025-01-09 17:35:57 +01:00
assert(tx->outputCount() >= 1);
2025-01-09 18:30:13 +01:00
assert(tx->outputCount() <= 400);
2024-12-29 19:13:22 +01:00
const Wallet *wIn = m_fromAccount->wallet();
assert(wIn);
2025-01-06 18:00:20 +01:00
Wallet *wTo = m_toAccount->wallet();
assert(wTo);
2024-12-29 19:13:22 +01:00
TransactionBuilder builder;
builder.setFeeTarget(1000);
builder.setAnonimize(true);
uint64_t value = 0;
for (auto ref : tx->m_utxos) {
auto output = wIn->txOutput(ref);
if (output.hasCashToken)
continue;
value += output.outputValue;
builder.appendInput(wIn->txid(ref), ref.outputIndex());
auto priv = wIn->unlockKey(ref);
if (priv.sigType == Wallet::NotUsedYet)
priv.sigType = Wallet::SignedAsSchnorr;
TransactionBuilder::SignatureType typeToUse =
(priv.sigType == Wallet::SignedAsEcdsa) ? TransactionBuilder::ECDSA : TransactionBuilder::Schnorr;
assert(priv.key.isValid());
builder.pushInputSignature(priv.key, output.outputScript, output.outputValue, typeToUse);
}
2025-01-09 17:35:57 +01:00
uint64_t perOutput = value / tx->outputCount();
if (perOutput < 700)
throw std::runtime_error("Too low");
for (int i = 0; i < tx->outputCount(); ++i) {
2025-01-09 19:47:37 +01:00
// diff is a fun thing.
// The idea is to make each output have a slightly random different amount.
// The anonimize feature of the builder will then sort them based on amount,
// and this will effectively randomize the outputs ordering.
// This avoids a large number of addresses being in perfect order on-chain for forever,
// just in case someday in the future someone can reverse engineer a HD seed from that.
uint32_t diff = std::rand() % 15;
builder.appendOutput(perOutput - diff);
2025-01-09 17:35:57 +01:00
KeyId targetAddress;
wTo->reserveUnusedAddress(targetAddress, Wallet::ChangePath);
builder.pushOutputPay2Address(targetAddress);
builder.setOutputFeeSource(0);
}
2024-12-29 19:13:22 +01:00
return builder.createTransaction();
}
QList<QObject *> QMLTransferManager::transactions() const
{
return m_transactions;
}
2025-01-09 17:35:57 +01:00
NumberModel *QMLTransferManager::outputModel()
{
if (m_model == nullptr)
m_model = new NumberModel(this);
return m_model;
}
int QMLTransferManager::addressCount() const
{
return m_addressCount;
}
void QMLTransferManager::setAddressCount(int c)
{
if (m_addressCount == c)
return;
m_addressCount = c;
emit addressCountChanged();
}
AccountInfo *QMLTransferManager::toAccount() const
{
return m_toAccount;
}
void QMLTransferManager::setToAccount(AccountInfo *newToAccount)
{
if (m_toAccount == newToAccount)
return;
2025-01-06 18:00:20 +01:00
freePreparedTransactions();
m_toAccount = newToAccount;
emit toAccountChanged();
2024-12-29 19:13:22 +01:00
emit inputsOkChanged();
}
AccountInfo *QMLTransferManager::fromAccount() const
{
return m_fromAccount;
}
void QMLTransferManager::setFromAccount(AccountInfo *account)
{
if (m_fromAccount == account)
return;
m_fromAccount = account;
emit fromAccountChanged();
2025-01-06 18:00:20 +01:00
freePreparedTransactions();
int addresses = 0;
int totalCoins = 0;
if (account->wallet()) {
const Wallet *wallet = account->wallet();
2024-12-31 15:55:36 +01:00
if (!account->needsPinToOpen() || account->isDecrypted()) {
const auto walletSecrets = wallet->walletSecrets();
for (auto i = walletSecrets.begin(); i != walletSecrets.end(); ++i) {
auto details = wallet->fetchKeyDetails(i->first);
if (details.coins > 0) {
++addresses;
totalCoins += details.coins;
}
}
}
}
setAddressCount(addresses);
setCoinCount(totalCoins);
2024-12-29 19:13:22 +01:00
emit inputsOkChanged();
}
// -----------------------------------------------------------
Address::Address(const QString &address, const QString &cloaked, QObject *parent)
: QObject(parent),
m_address(address),
m_cloaked(cloaked)
2024-12-24 15:09:20 +01:00
{
}
QString Address::address() const
{
return m_address;
}
QString Address::cloakedAddress() const
{
return m_cloaked;
}
// -----------------------------------------------------------
PreviewTx::PreviewTx(QObject *parent)
: QObject(parent)
{
}
int64_t PreviewTx::value() const
{
return m_value;
}
QObject *PreviewTx::from() const
{
return m_from;
}
int PreviewTx::inputCount() const
{
return m_utxos.size();
}
2024-12-29 19:13:22 +01:00
2025-01-01 17:36:58 +01:00
void PreviewTx::setFinalTx(const std::shared_ptr<TxInfoObject> &finalTx)
{
assert(m_finalTx.get() == nullptr); // avoid bugs with connects and only allow this once
m_finalTx = finalTx;
if (m_finalTx) {
connect (m_finalTx.get(), SIGNAL(broadcastStatusChanged()),
this, SIGNAL(broadcastStatusChanged()));
}
}
FloweePay::BroadcastStatus PreviewTx::broadcastStatus() const
{
if (m_finalTx)
return m_finalTx->broadcastStatus();
return FloweePay::NotStarted;
}
2025-01-09 17:35:57 +01:00
int PreviewTx::outputCount() const
{
return m_outputCount;
}
void PreviewTx::setOutputCount(int c)
{
if (m_outputCount == c)
return;
m_outputCount = c;
emit outputCountChanged();
}
2024-12-29 19:13:22 +01:00
bool PreviewTx::sent() const
{
return m_sent;
}