2020-11-06 18:16:39 +01:00
|
|
|
/*
|
|
|
|
|
* This file is part of the Flowee project
|
2021-11-02 14:24:47 +01:00
|
|
|
* Copyright (C) 2020-2021 Tom Zander <tom@flowee.org>
|
2020-11-06 18:16:39 +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/>.
|
|
|
|
|
*/
|
2021-11-02 14:24:47 +01:00
|
|
|
#include <QString>
|
2020-11-06 18:16:39 +01:00
|
|
|
#include "Wallet.h"
|
|
|
|
|
#include "Wallet_p.h"
|
2021-11-02 14:24:47 +01:00
|
|
|
#include "FloweePay.h"
|
2020-11-06 18:16:39 +01:00
|
|
|
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QThread>
|
|
|
|
|
|
2021-11-02 14:24:47 +01:00
|
|
|
#include <primitives/script.h>
|
|
|
|
|
|
2020-11-06 18:16:39 +01:00
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
// helper method for sortTransactions
|
|
|
|
|
void copyTx(size_t index, std::deque<Tx> &answer, const std::vector<QSet<int> > &order, std::vector<bool> &done, const std::deque<Tx> &in) {
|
|
|
|
|
if (done.at(index))
|
|
|
|
|
return;
|
|
|
|
|
done[index] = true;
|
|
|
|
|
for (auto dependencies : order.at(index)) {
|
|
|
|
|
copyTx(dependencies, answer, order, done, in);
|
|
|
|
|
}
|
|
|
|
|
answer.push_back(in.at(index));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::deque<Tx> WalletPriv::sortTransactions(const std::deque<Tx> &in) {
|
|
|
|
|
if (in.size() < 2)
|
|
|
|
|
return in;
|
|
|
|
|
boost::unordered_map<uint256, int, HashShortener> txHashes;
|
|
|
|
|
for (size_t i = 0; i < in.size(); ++i) {
|
|
|
|
|
txHashes.insert(std::make_pair(in.at(i).createHash(), int(i)));
|
|
|
|
|
}
|
|
|
|
|
std::vector<QSet<int> > order;
|
|
|
|
|
order.reserve(in.size());
|
|
|
|
|
for (size_t i = 0; i < in.size(); ++i) {
|
|
|
|
|
Tx::Iterator iter(in.at(i));
|
|
|
|
|
QSet<int> depends;
|
|
|
|
|
for (auto input : Tx::findInputs(iter)) {
|
|
|
|
|
auto prev = txHashes.find(input.txid);
|
|
|
|
|
if (prev != txHashes.end()) {
|
|
|
|
|
depends.insert(prev->second);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
order.push_back(depends);
|
|
|
|
|
}
|
|
|
|
|
std::vector<bool> done(in.size(), false);
|
|
|
|
|
std::deque<Tx> answer;
|
|
|
|
|
for (size_t i = 0; i < in.size(); ++i) {
|
|
|
|
|
copyTx(i, answer, order, done, in);
|
|
|
|
|
}
|
|
|
|
|
return answer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
Wallet::OutputRef::OutputRef(uint64_t encoded)
|
|
|
|
|
{
|
|
|
|
|
m_outputIndex = encoded & 0xFFFF;
|
|
|
|
|
encoded >>= 16;
|
2021-10-31 16:56:03 +01:00
|
|
|
assert(encoded < 0xFFFFFFFF);
|
|
|
|
|
m_txid = encoded & 0x7FFFFFFF;
|
2020-11-06 18:16:39 +01:00
|
|
|
assert(m_txid >= 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Wallet::OutputRef::OutputRef(int txIndex, int outputIndex)
|
|
|
|
|
: m_txid(txIndex),
|
|
|
|
|
m_outputIndex(outputIndex)
|
|
|
|
|
{
|
2021-10-31 16:56:03 +01:00
|
|
|
assert(txIndex >= 0); // zero is the 'invalid' state, which is allowed here to make an invalid OutputRef
|
2020-11-06 18:16:39 +01:00
|
|
|
assert(outputIndex >= 0);
|
2021-10-31 16:56:03 +01:00
|
|
|
assert(outputIndex <= 0XFFFF);
|
2020-11-06 18:16:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint64_t Wallet::OutputRef::encoded() const
|
|
|
|
|
{
|
|
|
|
|
uint64_t answer = m_txid;
|
|
|
|
|
answer <<= 16;
|
|
|
|
|
answer += m_outputIndex;
|
|
|
|
|
return answer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// //////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
WalletInfoObject::WalletInfoObject(Wallet *wallet, int txIndex, const Tx &tx)
|
|
|
|
|
: BroadcastTxData(tx),
|
|
|
|
|
m_wallet(wallet),
|
|
|
|
|
m_txIndex(txIndex)
|
|
|
|
|
{
|
|
|
|
|
connect(this, SIGNAL(finished(int,bool)),
|
|
|
|
|
m_wallet, SLOT(broadcastTxFinished(int,bool)), Qt::QueuedConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WalletInfoObject::txRejected(RejectReason reason, const std::string &message)
|
|
|
|
|
{
|
|
|
|
|
// reason is hinted using BroadcastTxData::RejectReason
|
|
|
|
|
logCritical() << "Transaction rejected" << reason << message;
|
|
|
|
|
++m_rejectedPeerCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WalletInfoObject::sentOne()
|
|
|
|
|
{
|
|
|
|
|
if (++m_sentPeerCount >= 2) {
|
|
|
|
|
// Two stage singleshots. First (Qt requires that to be zero ms) to move
|
|
|
|
|
// to a thread that Qt owns.
|
|
|
|
|
// Second (in startCheckState()) to actually wait several seconds.
|
|
|
|
|
QTimer::singleShot(0, this, SLOT(startCheckState()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WalletInfoObject::startCheckState()
|
|
|
|
|
{
|
|
|
|
|
Q_ASSERT(thread() == QThread::currentThread());
|
|
|
|
|
QTimer::singleShot(5 * 1000, this, SLOT(checkState()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WalletInfoObject::checkState()
|
|
|
|
|
{
|
|
|
|
|
Q_ASSERT(thread() == QThread::currentThread());
|
|
|
|
|
if (m_sentPeerCount >= 2) {
|
|
|
|
|
emit finished(m_txIndex, m_sentPeerCount - m_rejectedPeerCount >= 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int WalletInfoObject::txIndex() const
|
|
|
|
|
{
|
|
|
|
|
return m_txIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint16_t WalletInfoObject::privSegment() const
|
|
|
|
|
{
|
|
|
|
|
assert(m_wallet);
|
|
|
|
|
assert(m_wallet->segment());
|
|
|
|
|
return m_wallet->segment()->segmentId();
|
|
|
|
|
}
|
2021-10-29 12:42:31 +02:00
|
|
|
|
|
|
|
|
|
2021-11-09 20:49:37 +01:00
|
|
|
// //////////////////////////////////////////////////
|
|
|
|
|
bool Wallet::WalletTransaction::isUnconfirmed() const
|
|
|
|
|
{
|
|
|
|
|
return minedBlockHeight == WalletPriv::Unconfirmed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Wallet::WalletTransaction::isRejected() const
|
|
|
|
|
{
|
|
|
|
|
return minedBlockHeight == WalletPriv::Rejected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-10-29 12:42:31 +02:00
|
|
|
// //////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
Wallet::HierarchicallyDeterministicWalletData::HierarchicallyDeterministicWalletData(const std::string &seedWords, const std::string &pwd)
|
|
|
|
|
: masterKey(HDMasterKey::fromMnemonic(seedWords, pwd))
|
|
|
|
|
{
|
|
|
|
|
walletMnemonic = QString::fromUtf8(seedWords.c_str());
|
|
|
|
|
walletMnemonicPwd = QString::fromUtf8(pwd.c_str());
|
|
|
|
|
}
|
2021-11-02 14:24:47 +01:00
|
|
|
|
|
|
|
|
// //////////////////////////////////////////////////
|
|
|
|
|
// Apologies for the nested loop design, I promise this is not representative code and should only ever be ran once per wallet, ever.
|
|
|
|
|
void Wallet::populateSigType()
|
|
|
|
|
{
|
|
|
|
|
const auto &txs = m_walletTransactions; // short alias for readability
|
|
|
|
|
|
|
|
|
|
logCritical().nospace() << "Upgrading wallet '" << m_name << "', Finding signature types from seen transactions";
|
|
|
|
|
// iterate though each private key
|
|
|
|
|
for (auto s = m_walletSecrets.begin(); s != m_walletSecrets.end(); ++s) {
|
|
|
|
|
auto &secret = s->second;
|
|
|
|
|
// logDebug() << "Secret" << s->first << "Hd:" << secret.fromHdWallet << secret.fromChangeChain << "index:" << secret.hdDerivationIndex;
|
|
|
|
|
// iterate through transactions and outputs to find one that deposited funds there.
|
|
|
|
|
for (auto t1 = txs.cbegin(); secret.signatureType == NotUsedYet && t1 != txs.cend(); ++t1) {
|
|
|
|
|
const auto &tx1 = t1->second;
|
|
|
|
|
for (auto o = tx1.outputs.cbegin(); secret.signatureType == NotUsedYet && o != tx1.outputs.cend(); ++o) {
|
|
|
|
|
if (o->second.walletSecretId == s->first) {
|
|
|
|
|
// logDebug() << " Found an out for secret" << t1->first << o->first;
|
|
|
|
|
const auto ref = OutputRef(t1->first, o->first).encoded();
|
|
|
|
|
// check UTXO, if still there, then its unspent.
|
|
|
|
|
// Unspent means no signature, so find another output to check.
|
|
|
|
|
if (m_unspentOutputs.find(ref) != m_unspentOutputs.end())
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// iterate through transactions and inputs and try to find the spending input.
|
|
|
|
|
for (auto t2 = txs.cbegin(); secret.signatureType == NotUsedYet && t2 != txs.cend(); ++t2) {
|
|
|
|
|
const auto &tx2 = t2->second;
|
|
|
|
|
for (auto i = tx2.inputToWTX.cbegin(); i != tx2.inputToWTX.cend(); ++i) {
|
|
|
|
|
if (i->second == ref) {
|
|
|
|
|
// found one, now fetch the input script and check the type.
|
|
|
|
|
// logDebug() << "Found tx2 which spends output. Tx2:" << t2->first << i->first;
|
|
|
|
|
Tx tx = loadTransaction(tx2.txid, FloweePay::pool(0));
|
|
|
|
|
Tx::Iterator txIter(tx);
|
|
|
|
|
for (int x = i->first; x >= 0; --x) {
|
|
|
|
|
if (txIter.next(Tx::TxInScript) != Tx::TxInScript) {
|
|
|
|
|
logCritical() << "Internal error; tx has too little inputs" << tx2.txid << i->first;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
assert(txIter.tag() == Tx::TxInScript);
|
|
|
|
|
CScript script(txIter.byteData());
|
|
|
|
|
auto scriptIter = script.begin();
|
|
|
|
|
opcodetype type;
|
|
|
|
|
script.GetOp(scriptIter, type);
|
|
|
|
|
secret.signatureType = type == 65 ? SignedAsSchnorr : SignedAsEcdsa;
|
|
|
|
|
logInfo() << "Secret key" << s->first << "detected signature type from tx-out:"
|
|
|
|
|
<< tx1.txid << o->first
|
|
|
|
|
<< "signed by tx-input:" << tx2.txid << i->first
|
|
|
|
|
<< "SigType:" << (type == 65 ? "SignedAsSchnorr" : "SignedAsEcdsa");
|
|
|
|
|
m_secretsChanged = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
logCritical() << "Wallet upgrade finished";
|
|
|
|
|
}
|