2020-10-17 16:40:48 +02:00
|
|
|
/*
|
|
|
|
|
* This file is part of the Flowee project
|
2024-03-13 11:59:09 +01:00
|
|
|
* Copyright (C) 2020-2024 Tom Zander <tom@flowee.org>
|
2020-10-17 16:40:48 +02: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/>.
|
|
|
|
|
*/
|
2020-10-17 16:38:37 +02:00
|
|
|
#include "TestWallet.h"
|
2021-10-14 14:42:27 +02:00
|
|
|
#include <utils/cashaddr.h>
|
2021-04-19 19:22:37 +02:00
|
|
|
#include <Wallet.h>
|
2020-10-17 16:38:37 +02:00
|
|
|
#include <Wallet_p.h>
|
|
|
|
|
|
|
|
|
|
#include <QtTest/QtTest>
|
2020-11-02 21:49:06 +01:00
|
|
|
#include <QStandardPaths>
|
2020-10-17 16:38:37 +02:00
|
|
|
#include <TransactionBuilder.h>
|
|
|
|
|
|
2021-10-14 14:42:27 +02:00
|
|
|
|
2021-11-08 19:35:48 +01:00
|
|
|
class MockWallet : public Wallet
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
MockWallet(const boost::filesystem::path &basedir, uint16_t segmentId)
|
|
|
|
|
: Wallet(basedir, segmentId) {}
|
|
|
|
|
|
|
|
|
|
void findBroadcastTransaction() {
|
|
|
|
|
broadcastUnconfirmed();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void markTxRejected(int txIndex) {
|
|
|
|
|
broadcastTxFinished(txIndex, false);
|
|
|
|
|
}
|
2022-05-17 16:21:59 +02:00
|
|
|
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx loadTx(const uint256 &txHash, const std::shared_ptr<Streaming::BufferPool> &pool) const {
|
2022-05-17 16:21:59 +02:00
|
|
|
return loadTransaction(txHash, pool);
|
|
|
|
|
}
|
2021-11-08 19:35:48 +01:00
|
|
|
};
|
|
|
|
|
|
2020-10-17 16:38:37 +02:00
|
|
|
void TestWallet::transactionOrdering()
|
|
|
|
|
{
|
|
|
|
|
TransactionBuilder b1;
|
|
|
|
|
uint256 prevTxId = uint256S("0x12830924807308721309128309128");
|
|
|
|
|
b1.appendInput(prevTxId, 0);
|
|
|
|
|
b1.appendOutput(10000);
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
|
|
|
|
Tx t1 = b1.createTransaction(pool);
|
2020-10-17 16:38:37 +02:00
|
|
|
|
|
|
|
|
TransactionBuilder b2;
|
|
|
|
|
b2.appendInput(t1.createHash(), 0); // t2 spends an output of t1
|
|
|
|
|
b2.appendOutput(10000);
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t2 = b2.createTransaction(pool);
|
2020-10-17 16:38:37 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
std::deque<Tx> transactions;
|
|
|
|
|
transactions.push_back(t1);
|
|
|
|
|
transactions.push_back(t2);
|
|
|
|
|
|
|
|
|
|
auto answer = WalletPriv::sortTransactions(transactions);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(answer.size(), 2l);
|
|
|
|
|
QCOMPARE(answer.at(0).createHash(), t1.createHash());
|
|
|
|
|
QCOMPARE(answer.at(1).createHash(), t2.createHash());
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
std::deque<Tx> transactions;
|
|
|
|
|
transactions.push_back(t2);
|
|
|
|
|
transactions.push_back(t1);
|
|
|
|
|
|
|
|
|
|
auto answer = WalletPriv::sortTransactions(transactions);
|
|
|
|
|
QCOMPARE(answer.size(), 2l);
|
|
|
|
|
QCOMPARE(answer.at(0).createHash(), t1.createHash());
|
|
|
|
|
QCOMPARE(answer.at(1).createHash(), t2.createHash());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-02 21:49:06 +01:00
|
|
|
void TestWallet::addingTransactions()
|
|
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
TransactionBuilder b1;
|
|
|
|
|
uint256 prevTxId = uint256S("0x12830924807308721309128309128");
|
|
|
|
|
b1.appendInput(prevTxId, 0);
|
|
|
|
|
b1.appendOutput(1000000);
|
2021-01-06 15:26:46 +01:00
|
|
|
b1.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
|
|
|
|
Tx t1 = b1.createTransaction(pool);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
wallet->newTransaction(t1);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
|
|
|
|
|
// adding again is ignored.
|
|
|
|
|
wallet->newTransaction(t1);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
|
|
|
|
|
// now confirm it.
|
|
|
|
|
std::deque<Tx> list;
|
|
|
|
|
list.push_back(t1);
|
2023-04-04 13:36:07 +02:00
|
|
|
wallet->newTransactions(dummyBlockId, 1, list);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// create a new transaction spending the above output and creating 3 outputs,
|
|
|
|
|
// 2 for me one to a random address.
|
2020-11-03 20:16:36 +01:00
|
|
|
// 1000000 -> 200000, 500000 [and 290000 we send elsewhere]
|
2020-11-02 21:49:06 +01:00
|
|
|
TransactionBuilder b2;
|
|
|
|
|
b2.appendOutput(200000);
|
2021-01-06 15:26:46 +01:00
|
|
|
b2.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2020-11-02 21:49:06 +01:00
|
|
|
b2.appendOutput(500000);
|
2021-01-06 15:26:46 +01:00
|
|
|
b2.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2020-11-02 21:49:06 +01:00
|
|
|
b2.appendOutput(290000);
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId id("99999999999999999999");
|
2020-11-02 21:49:06 +01:00
|
|
|
b2.pushOutputPay2Address(id);
|
|
|
|
|
|
|
|
|
|
int64_t change = -1;
|
2023-12-22 14:56:21 +01:00
|
|
|
auto funding = wallet->findInputsFor(990000, 1, b2.createTransaction(pool).size(), change);
|
2020-11-02 21:49:06 +01:00
|
|
|
QCOMPARE(funding.outputs.size(), 1L);
|
|
|
|
|
|
|
|
|
|
for (auto ref : funding.outputs) {
|
|
|
|
|
b2.appendInput(wallet->txid(ref), ref.outputIndex());
|
2021-11-08 15:21:53 +01:00
|
|
|
auto output = wallet->txOutput(ref);
|
2021-10-31 16:56:03 +01:00
|
|
|
QCOMPARE(wallet->unlockKey(ref).sigType, Wallet::NotUsedYet);
|
2021-05-04 17:41:49 +02:00
|
|
|
b2.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA);
|
2020-11-02 21:49:06 +01:00
|
|
|
}
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t2 = b2.createTransaction(pool);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
// add as unconfirmed
|
|
|
|
|
wallet->newTransaction(t2);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 200000 + 500000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
|
2021-10-31 16:56:03 +01:00
|
|
|
// since we signed, lets check the signature type
|
|
|
|
|
for (auto ref : funding.outputs) {
|
|
|
|
|
QCOMPARE(wallet->unlockKey(ref).sigType, Wallet::SignedAsEcdsa);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-02 21:49:06 +01:00
|
|
|
// try to spend it again, if its properly locked, it will not get funded.
|
|
|
|
|
funding = wallet->findInputsFor(990000, 1, t2.size(), change);
|
|
|
|
|
QVERIFY(funding.outputs.empty());
|
|
|
|
|
|
|
|
|
|
// create a new transaction extending the unspent chain and spent both
|
|
|
|
|
// UTXOs in the wallet.
|
|
|
|
|
TransactionBuilder b3;
|
|
|
|
|
b3.appendOutput(698700);
|
2021-01-06 15:26:46 +01:00
|
|
|
b3.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
funding = wallet->findInputsFor(698700, 1, b3.createTransaction(pool).size(), change);
|
2020-11-02 21:49:06 +01:00
|
|
|
QCOMPARE(funding.outputs.size(), 2L);
|
|
|
|
|
for (auto ref : funding.outputs) {
|
|
|
|
|
b3.appendInput(wallet->txid(ref), ref.outputIndex());
|
2021-11-08 15:21:53 +01:00
|
|
|
auto output = wallet->txOutput(ref);
|
2021-05-04 17:41:49 +02:00
|
|
|
b3.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA);
|
2020-11-02 21:49:06 +01:00
|
|
|
}
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t3 = b3.createTransaction(pool);
|
2020-11-02 21:49:06 +01:00
|
|
|
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 200000 + 500000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
// add as unconfirmed
|
|
|
|
|
wallet->newTransaction(t3);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 698700);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
|
|
|
|
|
// Create a double spending transaction and spend one of the two UTXOs only
|
|
|
|
|
TransactionBuilder b4;
|
|
|
|
|
// reuse the output we funded t3 with.
|
2021-10-27 19:44:19 +02:00
|
|
|
bool found = false;
|
|
|
|
|
for (auto &ref : funding.outputs) {
|
|
|
|
|
// there are 2 outputs, we shall not assume the order of the funding request but just look for the right amount
|
2021-11-08 15:21:53 +01:00
|
|
|
auto output = wallet->txOutput(ref);
|
2021-10-27 19:44:19 +02:00
|
|
|
if (output.outputValue == 200000) {
|
|
|
|
|
found = true;
|
|
|
|
|
b4.appendInput(wallet->txid(ref), ref.outputIndex());
|
|
|
|
|
b4.appendOutput(output.outputValue - 1673);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(found);
|
2021-01-06 15:26:46 +01:00
|
|
|
b4.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t4 = b4.createTransaction(pool);
|
2020-11-02 21:49:06 +01:00
|
|
|
|
|
|
|
|
// then add the double spend as confirmed.
|
|
|
|
|
// This should replace the previous one.
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 698700);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
list.clear();
|
|
|
|
|
list.push_back(t4);
|
2023-04-04 13:36:07 +02:00
|
|
|
wallet->newTransactions(dummyBlockId, 2, list);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 200000 - 1673); // output from b4 is confirmed
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 500000);
|
2020-11-02 21:49:06 +01:00
|
|
|
}
|
|
|
|
|
|
2023-04-04 13:36:07 +02:00
|
|
|
void TestWallet::addingTransactions2()
|
|
|
|
|
{
|
|
|
|
|
/*
|
2024-03-13 11:59:09 +01:00
|
|
|
* newTransaction() is for adding unconfirmed transactions.
|
|
|
|
|
* newTransactions() for confirmed transactions.
|
2023-04-04 13:36:07 +02:00
|
|
|
*
|
|
|
|
|
* The wallet may receive transactions out of order, for various reasons a first
|
|
|
|
|
* scan may miss transactions (private keys (HD) not generated yet, for instance).
|
|
|
|
|
* and thus we may get a transaction that is before the latest ones.
|
|
|
|
|
*
|
|
|
|
|
* We need to handle that.
|
|
|
|
|
*
|
|
|
|
|
* Test setup;
|
|
|
|
|
*
|
|
|
|
|
* tx1: deposits funds into 2 UTXOs. (1000 / 5000)
|
|
|
|
|
* tx2: spends tx1, utxo1 and deposits into a new utxo. (1000 -> 999)
|
|
|
|
|
* tx3: spends tx1 utxo2 and tx2 utxo and deposits it into a new utxo
|
|
|
|
|
* (1000 + 5000 -> 5990)
|
|
|
|
|
*
|
|
|
|
|
* add tx1 at block 200
|
|
|
|
|
* add tx3 at block 210
|
|
|
|
|
* add tx2 at block 202
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
|
|
|
|
|
uint256 blockId200 = uint256S("0x09830942309482");
|
|
|
|
|
uint256 blockId202 = uint256S("0x4905009200a93a");
|
|
|
|
|
uint256 blockId210 = uint256S("0x590684209398322");
|
|
|
|
|
|
|
|
|
|
TransactionBuilder b1;
|
|
|
|
|
uint256 prevTxId = uint256S("0x12830924807308721309128309128");
|
|
|
|
|
b1.appendInput(prevTxId, 0);
|
|
|
|
|
b1.appendOutput(1000);
|
|
|
|
|
b1.pushOutputPay2Address(wallet->nextUnusedAddress());
|
|
|
|
|
b1.appendOutput(5000);
|
|
|
|
|
b1.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
|
|
|
|
Tx tx1 = b1.createTransaction(pool);
|
2023-04-04 13:36:07 +02:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
wallet->newTransactions(blockId200, 200, {tx1});
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 6000);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
|
|
|
|
|
TransactionBuilder b2;
|
|
|
|
|
b2.appendInput(tx1.createHash(), 0);
|
|
|
|
|
b2.appendOutput(999);
|
|
|
|
|
b2.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx tx2 = b2.createTransaction(pool);
|
2023-04-04 13:36:07 +02:00
|
|
|
|
|
|
|
|
TransactionBuilder b3;
|
|
|
|
|
b3.appendInput(tx1.createHash(), 1);
|
|
|
|
|
b3.appendInput(tx2.createHash(), 0);
|
|
|
|
|
b3.appendOutput(5990);
|
|
|
|
|
b3.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx tx3 = b3.createTransaction(pool);
|
2023-04-04 13:36:07 +02:00
|
|
|
|
|
|
|
|
wallet->newTransactions(blockId210, 210, {tx3});
|
|
|
|
|
// the wallet doesn't know we spent one of its own
|
|
|
|
|
// outputs, so it assumes its new money.
|
|
|
|
|
// as such we deposited 1000 + 5990
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 6990);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
|
|
|
|
|
wallet->newTransactions(blockId202, 202, {tx2});
|
|
|
|
|
// the code should insert tx2 AND re-apply tx3
|
|
|
|
|
// therefore coming up with the proper amount
|
|
|
|
|
// which removes the 1000 and leaves just the 5990
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 5990);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
// should have no effect:
|
|
|
|
|
wallet->newTransactions(blockId210, 210, {tx3});
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 5990);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-08 15:24:48 +01:00
|
|
|
void TestWallet::lockingOutputs()
|
|
|
|
|
{
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
2021-11-08 15:24:48 +01:00
|
|
|
{
|
|
|
|
|
// See if locking outputs manually has an effect on findInputs
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
wallet->addTestTransactions();
|
|
|
|
|
int64_t change = 0;
|
|
|
|
|
auto walletSet = wallet->findInputsFor(12899000, 1, 1, change);
|
|
|
|
|
QCOMPARE(walletSet.outputs.size(), 6L);
|
|
|
|
|
|
|
|
|
|
// now lock one and see that we can no longer fulfill the request.
|
|
|
|
|
const Wallet::OutputRef ref(1, 3);
|
|
|
|
|
bool ok = wallet->lockUTXO(ref);
|
|
|
|
|
QVERIFY(ok);
|
|
|
|
|
walletSet = wallet->findInputsFor(12899000, 1, 1, change);
|
|
|
|
|
QVERIFY(walletSet.outputs.empty());
|
|
|
|
|
// check that all other outputs are still available.
|
|
|
|
|
walletSet = wallet->findInputsFor(12799000, 1, 1, change);
|
|
|
|
|
QCOMPARE(walletSet.outputs.size(), 5L);
|
|
|
|
|
|
|
|
|
|
// unlock and check again.
|
|
|
|
|
ok = wallet->unlockUTXO(ref);
|
|
|
|
|
QVERIFY(ok);
|
|
|
|
|
walletSet = wallet->findInputsFor(12899000, 1, 1, change);
|
|
|
|
|
QCOMPARE(walletSet.outputs.size(), 6L);
|
|
|
|
|
|
|
|
|
|
// -- Create a transaction and add it, then check if the outputs are locked --
|
|
|
|
|
walletSet = wallet->findInputsFor(11000000, 1, 1, change);
|
|
|
|
|
QCOMPARE(walletSet.outputs.size(), 3L);
|
|
|
|
|
TransactionBuilder b1;
|
2022-04-15 17:54:46 +02:00
|
|
|
for (auto ref2 : walletSet.outputs) {
|
2021-11-08 15:24:48 +01:00
|
|
|
try {
|
2022-04-15 17:54:46 +02:00
|
|
|
b1.appendInput(wallet->txid(ref2), ref2.outputIndex());
|
2023-12-22 14:56:21 +01:00
|
|
|
b1.pushInputSignature(wallet->unlockKey(ref2).key, pool->commit(100), 1, TransactionBuilder::Schnorr);
|
2021-11-08 15:24:48 +01:00
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
logFatal() << e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
b1.appendOutput(5000); // 11000000 was available, so the fee is enormous. But this makes it easy to test.
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId address;
|
2021-11-08 15:24:48 +01:00
|
|
|
wallet->reserveUnusedAddress(address);
|
|
|
|
|
b1.pushOutputPay2Address(address);
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t1 = b1.createTransaction(pool);
|
2021-11-08 15:24:48 +01:00
|
|
|
wallet->newTransaction(t1);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 5000);
|
|
|
|
|
|
|
|
|
|
// now, outputs from the walletSet should be locked.
|
2022-04-15 17:54:46 +02:00
|
|
|
for (auto ref2 : walletSet.outputs) {
|
|
|
|
|
bool lockSuccess = wallet->lockUTXO(ref2);
|
2021-11-08 15:24:48 +01:00
|
|
|
QVERIFY(!lockSuccess); // can't lock them again.
|
2022-04-15 17:54:46 +02:00
|
|
|
bool unlockSuccess = wallet->unlockUTXO(ref2);
|
2021-11-08 15:24:48 +01:00
|
|
|
QVERIFY(!unlockSuccess); // can't unlock auto-referenced outputs.
|
|
|
|
|
}
|
|
|
|
|
walletSet = wallet->findInputsFor(12899000, 1, 1, change);
|
|
|
|
|
QVERIFY(walletSet.outputs.empty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 5000);
|
|
|
|
|
int64_t change = 0;
|
|
|
|
|
auto walletSet = wallet->findInputsFor(12899000, 1, 1, change);
|
|
|
|
|
QVERIFY(walletSet.outputs.empty());
|
|
|
|
|
|
|
|
|
|
/* Chain the transaction and to spent an unconfirmed output. */
|
|
|
|
|
Wallet::OutputRef unconfirmedOut(7, 0);
|
|
|
|
|
bool lockSuccess = wallet->lockUTXO(unconfirmedOut);
|
|
|
|
|
QVERIFY(lockSuccess);
|
|
|
|
|
bool unlockSuccess = wallet->unlockUTXO(unconfirmedOut);
|
|
|
|
|
QVERIFY(unlockSuccess);
|
|
|
|
|
|
2022-04-12 22:54:40 +02:00
|
|
|
walletSet = wallet->findInputsFor(1801000, 1, 1, change);
|
2023-05-02 20:07:39 +02:00
|
|
|
if (walletSet.outputs.size() == 0) {
|
|
|
|
|
// findInputsFor is probabilistic, and about 1 in 10 runs it selects
|
|
|
|
|
// for the above B1/T1 the bigger 400000 sats output instead of the
|
|
|
|
|
// smaller 100000 output, which is thus now locked.
|
|
|
|
|
// In case that happens, just request less.
|
|
|
|
|
walletSet = wallet->findInputsFor(1501000, 1, 1, change);
|
|
|
|
|
}
|
2021-11-08 15:24:48 +01:00
|
|
|
QCOMPARE(walletSet.outputs.size(), 4L); // should be all outputs, including the unconfirmed one.
|
|
|
|
|
TransactionBuilder b1;
|
|
|
|
|
for (auto ref : walletSet.outputs) {
|
|
|
|
|
try {
|
|
|
|
|
b1.appendInput(wallet->txid(ref), ref.outputIndex());
|
2023-12-22 14:56:21 +01:00
|
|
|
b1.pushInputSignature(wallet->unlockKey(ref).key, pool->commit(100), 1, TransactionBuilder::Schnorr);
|
2021-11-08 15:24:48 +01:00
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
logFatal() << e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
b1.appendOutput(700000);
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId address;
|
2021-11-08 15:24:48 +01:00
|
|
|
wallet->reserveUnusedAddress(address);
|
|
|
|
|
b1.pushOutputPay2Address(address);
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t1 = b1.createTransaction(pool);
|
2021-11-08 15:24:48 +01:00
|
|
|
wallet->newTransaction(t1);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 700000);
|
|
|
|
|
|
|
|
|
|
// now, outputs from the walletSet should be locked.
|
2022-04-15 17:54:46 +02:00
|
|
|
for (auto ref2 : walletSet.outputs) {
|
|
|
|
|
lockSuccess = wallet->lockUTXO(ref2);
|
2021-11-08 15:24:48 +01:00
|
|
|
QVERIFY(!lockSuccess); // can't lock them again.
|
2022-04-15 17:54:46 +02:00
|
|
|
unlockSuccess = wallet->unlockUTXO(ref2);
|
2021-11-08 15:24:48 +01:00
|
|
|
QVERIFY(!unlockSuccess); // can't unlock auto-referenced outputs.
|
|
|
|
|
}
|
|
|
|
|
walletSet = wallet->findInputsFor(904000, 1, 1, change);
|
|
|
|
|
QVERIFY(walletSet.outputs.empty());
|
|
|
|
|
walletSet = wallet->findInputsFor(690000, 1, 1, change);
|
|
|
|
|
QVERIFY(!walletSet.outputs.empty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 700000);
|
|
|
|
|
int64_t change = 0;
|
|
|
|
|
auto walletSet = wallet->findInputsFor(904000, 1, 1, change);
|
|
|
|
|
QVERIFY(walletSet.outputs.empty());
|
|
|
|
|
walletSet = wallet->findInputsFor(690000, 1, 1, change);
|
|
|
|
|
QVERIFY(!walletSet.outputs.empty());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-01 14:15:16 +01:00
|
|
|
void TestWallet::testSpam()
|
|
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
TransactionBuilder b1;
|
|
|
|
|
uint256 prevTxId = uint256S("0x12830924807308721309128309128");
|
|
|
|
|
b1.appendInput(prevTxId, 0);
|
|
|
|
|
b1.appendOutput(1000000);
|
|
|
|
|
b1.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
|
|
|
|
Tx t1 = b1.createTransaction(pool);
|
2023-04-04 13:36:07 +02:00
|
|
|
wallet->newTransactions(dummyBlockId, 1, { t1 });
|
2021-11-01 14:15:16 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 1000000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// create a new transaction spending the above output and creating at least one 'spam' output.
|
|
|
|
|
TransactionBuilder b2;
|
|
|
|
|
b2.appendOutput(200000);
|
|
|
|
|
b2.pushOutputPay2Address(wallet->nextUnusedAddress());
|
|
|
|
|
b2.appendOutput(547); // Da SPAM.
|
|
|
|
|
b2.pushOutputPay2Address(wallet->nextUnusedAddress());
|
|
|
|
|
b2.appendOutput(290000);
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId id("99999999999999999999");
|
2021-11-01 14:15:16 +01:00
|
|
|
b2.pushOutputPay2Address(id);
|
|
|
|
|
|
|
|
|
|
int64_t change = -1;
|
2023-12-22 14:56:21 +01:00
|
|
|
auto funding = wallet->findInputsFor(990000, 1, b2.createTransaction(pool).size(), change);
|
2021-11-01 14:15:16 +01:00
|
|
|
for (auto ref : funding.outputs) {
|
|
|
|
|
b2.appendInput(wallet->txid(ref), ref.outputIndex());
|
2021-11-08 15:21:53 +01:00
|
|
|
auto output = wallet->txOutput(ref);
|
2021-11-01 14:15:16 +01:00
|
|
|
b2.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA);
|
|
|
|
|
}
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t2 = b2.createTransaction(pool);
|
2023-04-04 13:36:07 +02:00
|
|
|
wallet->newTransactions(dummyBlockId, 2, { t2 } );
|
2021-11-01 14:15:16 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 200000); // does NOT include the 547
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-02 21:49:06 +01:00
|
|
|
void TestWallet::saveTransaction()
|
|
|
|
|
{
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
2020-11-02 21:49:06 +01:00
|
|
|
// add a simple transaction and see if it saves.
|
|
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
TransactionBuilder b1;
|
|
|
|
|
uint256 prevTxId = uint256S("0x12830924807308721309128309128");
|
|
|
|
|
b1.appendInput(prevTxId, 0);
|
|
|
|
|
b1.appendOutput(1000000);
|
2021-01-06 15:26:46 +01:00
|
|
|
b1.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t1 = b1.createTransaction(pool);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
wallet->newTransaction(t1);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check it saved (we have balance) and then
|
|
|
|
|
// add an unconfirmed transaction spending our single UTXO
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
|
|
|
|
|
TransactionBuilder b2;
|
|
|
|
|
b2.appendOutput(200000);
|
2021-01-06 15:26:46 +01:00
|
|
|
b2.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2020-11-02 21:49:06 +01:00
|
|
|
b2.appendOutput(790000);
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId id("99999999999999999999");
|
2020-11-02 21:49:06 +01:00
|
|
|
b2.pushOutputPay2Address(id);
|
|
|
|
|
|
|
|
|
|
int64_t change = -1;
|
2023-12-22 14:56:21 +01:00
|
|
|
auto funding = wallet->findInputsFor(990000, 1, b2.createTransaction(pool).size(), change);
|
2020-11-02 21:49:06 +01:00
|
|
|
QCOMPARE(funding.outputs.size(), 1L);
|
|
|
|
|
auto ref = funding.outputs.front();
|
|
|
|
|
b2.appendInput(wallet->txid(ref), ref.outputIndex());
|
2021-11-08 15:21:53 +01:00
|
|
|
auto output = wallet->txOutput(ref);
|
2021-05-04 17:41:49 +02:00
|
|
|
b2.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA);
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t2 = b2.createTransaction(pool);
|
2020-11-02 21:49:06 +01:00
|
|
|
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
// add as unconfirmed
|
|
|
|
|
wallet->newTransaction(t2);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 200000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2020-11-02 21:49:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we likely have both transactions, the question is if the output from the first,
|
2020-11-06 22:15:03 +01:00
|
|
|
// that is spent by the second, is locked and thus not counted in balances
|
2020-11-02 21:49:06 +01:00
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 200000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestWallet::saveTransaction2()
|
|
|
|
|
{
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
2020-11-06 22:15:03 +01:00
|
|
|
// add a simple transaction and see if it saves.
|
|
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
TransactionBuilder b1;
|
|
|
|
|
b1.appendInput(uint256(), 0); // coinbase
|
|
|
|
|
b1.appendOutput(50);
|
2021-01-06 15:26:46 +01:00
|
|
|
b1.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t1 = b1.createTransaction(pool);
|
2020-11-06 22:15:03 +01:00
|
|
|
std::deque<Tx> list;
|
|
|
|
|
list.push_back(t1);
|
2023-04-04 13:36:07 +02:00
|
|
|
wallet->newTransactions(dummyBlockId, 10, list);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 50);
|
|
|
|
|
|
|
|
|
|
TransactionBuilder b2;
|
|
|
|
|
b2.appendInput(uint256(), 0); // coinbase
|
|
|
|
|
b2.appendOutput(51);
|
2021-01-06 15:26:46 +01:00
|
|
|
b2.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t2 = b2.createTransaction(pool);
|
2020-11-06 22:15:03 +01:00
|
|
|
list[0] = t2;
|
2023-04-04 13:36:07 +02:00
|
|
|
wallet->newTransactions(dummyBlockId, 50, list);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 101);
|
|
|
|
|
|
|
|
|
|
TransactionBuilder b3;
|
|
|
|
|
b3.appendInput(t1.createHash(), 0);
|
|
|
|
|
b3.appendOutput(40);
|
2021-01-06 15:26:46 +01:00
|
|
|
b3.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2020-11-06 22:15:03 +01:00
|
|
|
b3.appendOutput(10);
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId id("66666666666666666666");
|
2020-11-06 22:15:03 +01:00
|
|
|
b3.pushOutputPay2Address(id);
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t3 = b3.createTransaction(pool);
|
2020-11-06 22:15:03 +01:00
|
|
|
list[0] = t3;
|
2023-04-04 13:36:07 +02:00
|
|
|
wallet->newTransactions(dummyBlockId, 140, list);
|
2020-11-06 22:15:03 +01:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 40);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 51);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 40);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 51);
|
2020-11-02 21:49:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-31 22:51:55 +01:00
|
|
|
void TestWallet::findInputs()
|
|
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
wallet->addTestTransactions();
|
|
|
|
|
|
|
|
|
|
int64_t change = 0;
|
|
|
|
|
// Test that we prefer a simple solution over one with multiple aged coins
|
|
|
|
|
auto walletSet = wallet->findInputsFor(4000000, 1, 1, change);
|
|
|
|
|
QCOMPARE(walletSet.outputs.size(), 1);
|
2022-04-12 22:54:40 +02:00
|
|
|
// we also prefer the coin with the least overpayment, and lower change (within reason).
|
|
|
|
|
QCOMPARE(walletSet.totalSats, 5000000);
|
2021-01-31 22:51:55 +01:00
|
|
|
QCOMPARE(walletSet.fee, 150);
|
2022-04-12 22:54:40 +02:00
|
|
|
QCOMPARE(change, 999850);
|
2021-01-31 22:51:55 +01:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 19:22:37 +02:00
|
|
|
void TestWallet::unconfirmed()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Create a wallet with a coin.
|
|
|
|
|
* Then spend one with a transaction that places new coins in the wallet.
|
|
|
|
|
* Make sure that after reload the balance is proper.
|
|
|
|
|
*/
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
2021-04-19 19:22:37 +02:00
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
TransactionBuilder b1;
|
|
|
|
|
uint256 prevTxId = uint256S("0x12830924807308721309128309128");
|
|
|
|
|
b1.appendInput(prevTxId, 0);
|
|
|
|
|
b1.appendOutput(1000000);
|
|
|
|
|
b1.pushOutputPay2Address(wallet->nextUnusedAddress());
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t1 = b1.createTransaction(pool);
|
2021-04-19 19:22:37 +02:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
2023-04-04 13:36:07 +02:00
|
|
|
wallet->newTransactions(dummyBlockId, 10, std::deque<Tx>() = { t1 });
|
2021-04-19 19:22:37 +02:00
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check it saved (we have balance) and then
|
|
|
|
|
// add an unconfirmed transaction spending our single UTXO, we additionally make sure
|
|
|
|
|
// that this tx creates a new UTXO for our wallet.
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
|
|
|
|
|
TransactionBuilder b2;
|
|
|
|
|
b2.appendOutput(900000);
|
|
|
|
|
b2.pushOutputPay2Address(wallet->nextUnusedAddress());
|
|
|
|
|
|
|
|
|
|
int64_t change = -1;
|
2023-12-22 14:56:21 +01:00
|
|
|
auto funding = wallet->findInputsFor(900000, 1, b2.createTransaction(pool).size(), change);
|
2021-04-19 19:22:37 +02:00
|
|
|
QCOMPARE(funding.outputs.size(), 1L);
|
|
|
|
|
auto ref = funding.outputs.front();
|
|
|
|
|
b2.appendInput(wallet->txid(ref), ref.outputIndex());
|
2021-11-08 15:21:53 +01:00
|
|
|
auto output = wallet->txOutput(ref);
|
2021-05-04 17:41:49 +02:00
|
|
|
b2.pushInputSignature(wallet->unlockKey(ref).key, output.outputScript, output.outputValue, TransactionBuilder::ECDSA);
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t2 = b2.createTransaction(pool);
|
2021-04-19 19:22:37 +02:00
|
|
|
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
// add as unconfirmed
|
|
|
|
|
wallet->newTransaction(t2);
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 900000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we likely have both transactions, the question is if the output from the first,
|
|
|
|
|
// that is spent by the second, is locked and thus not counted in balances
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 900000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-14 14:42:27 +02:00
|
|
|
void TestWallet::hierarchicallyDeterministic()
|
|
|
|
|
{
|
|
|
|
|
const QString southMonkey("south monkey fire corn link estate burger lucky bronze pet chapter lamp");
|
|
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
QVERIFY(!wallet->isHDWallet());
|
|
|
|
|
QCOMPARE(wallet->hdWalletMnemonic(), QString());
|
|
|
|
|
QCOMPARE(wallet->hdWalletMnemonicPwd(), QString());
|
|
|
|
|
QCOMPARE(wallet->derivationPath(), QString());
|
|
|
|
|
|
|
|
|
|
std::vector<uint32_t> derivationPath = { HDMasterKey::Hardened + 44, HDMasterKey::Hardened + 145, HDMasterKey::Hardened };
|
2024-12-29 20:09:04 +01:00
|
|
|
wallet->createHDMasterKey(southMonkey, QString(), derivationPath, 1, true);
|
2021-10-14 14:42:27 +02:00
|
|
|
|
|
|
|
|
QVERIFY(wallet->isHDWallet());
|
2024-12-29 20:09:04 +01:00
|
|
|
QVERIFY(wallet->walletIsImporting()); // thats the true in the call above
|
2021-10-14 14:42:27 +02:00
|
|
|
QCOMPARE(wallet->hdWalletMnemonic(), southMonkey);
|
|
|
|
|
QCOMPARE(wallet->hdWalletMnemonicPwd(), QString());
|
|
|
|
|
QCOMPARE(wallet->derivationPath(), QString("m/44'/145'/0'"));
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
|
|
|
|
QVERIFY(wallet->isHDWallet());
|
|
|
|
|
QCOMPARE(wallet->hdWalletMnemonic(), southMonkey);
|
|
|
|
|
QCOMPARE(wallet->hdWalletMnemonicPwd(), QString());
|
|
|
|
|
QCOMPARE(wallet->derivationPath(), QString("m/44'/145'/0'"));
|
|
|
|
|
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId id;
|
2021-10-14 14:42:27 +02:00
|
|
|
int num = wallet->reserveUnusedAddress(id);
|
|
|
|
|
QCOMPARE(num, 1); // the first maps to the non-HD one created in our unit-test below (in createWallet())
|
|
|
|
|
|
|
|
|
|
// Create a new HD wallet, which should use base derivation plus "/0/0" appended.
|
|
|
|
|
num = wallet->reserveUnusedAddress(id);
|
|
|
|
|
QCOMPARE(num, 2);
|
|
|
|
|
|
|
|
|
|
QVERIFY(!id.IsNull());
|
|
|
|
|
auto address = CashAddress::encodeCashAddr("bitcoincash",
|
|
|
|
|
{ CashAddress::PUBKEY_TYPE, std::vector<uint8_t>(id.begin(), id.end())});
|
|
|
|
|
QCOMPARE(QString::fromStdString(address), "bitcoincash:qrg0jddykyfeal70xduvyeathd3ulhm7hv22m3t7na");
|
|
|
|
|
QCOMPARE(wallet->derivationPath(), QString("m/44'/145'/0'"));
|
|
|
|
|
}
|
2021-10-21 14:57:20 +02:00
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
|
|
|
|
QVERIFY(wallet->isHDWallet());
|
|
|
|
|
|
|
|
|
|
auto secrets = wallet->walletSecrets();
|
|
|
|
|
bool plain = false; // found the plain one.
|
|
|
|
|
QSet<int> change;
|
|
|
|
|
QSet<int> main;
|
|
|
|
|
for (const auto &i : secrets) {
|
|
|
|
|
const auto &secret = i.second;
|
|
|
|
|
if (secret.fromHdWallet) {
|
|
|
|
|
if (secret.fromChangeChain) {
|
|
|
|
|
QVERIFY(!change.contains(secret.hdDerivationIndex));
|
|
|
|
|
change.insert(secret.hdDerivationIndex);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
QVERIFY(!main.contains(secret.hdDerivationIndex));
|
|
|
|
|
main.insert(secret.hdDerivationIndex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
QCOMPARE(plain, false);
|
|
|
|
|
plain = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// we found (after loading) at least these many pre-calculated wallet-secrets.
|
|
|
|
|
QVERIFY(change.size() >= 20);
|
|
|
|
|
QVERIFY(main.size() >= 20);
|
|
|
|
|
}
|
2021-10-14 14:42:27 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-08 19:35:48 +01:00
|
|
|
void TestWallet::rejectTx()
|
|
|
|
|
{
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
2021-11-08 19:35:48 +01:00
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
wallet->addTestTransactions();
|
|
|
|
|
int64_t change = 0;
|
|
|
|
|
auto walletSet = wallet->findInputsFor(9000000, 1, 1, change);
|
|
|
|
|
QCOMPARE(walletSet.outputs.size(), 2L);
|
|
|
|
|
TransactionBuilder b1;
|
|
|
|
|
for (auto ref : walletSet.outputs) {
|
|
|
|
|
b1.appendInput(wallet->txid(ref), ref.outputIndex());
|
2023-12-22 14:56:21 +01:00
|
|
|
b1.pushInputSignature(wallet->unlockKey(ref).key, pool->commit(100), 1, TransactionBuilder::Schnorr);
|
2021-11-08 19:35:48 +01:00
|
|
|
}
|
|
|
|
|
b1.appendOutput(5000); // 9000000 was available, so the fee is enormous. But this makes it easy to test.
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId address;
|
2021-11-08 19:35:48 +01:00
|
|
|
wallet->reserveUnusedAddress(address);
|
|
|
|
|
b1.pushOutputPay2Address(address);
|
2023-12-22 14:56:21 +01:00
|
|
|
Tx t1 = b1.createTransaction(pool);
|
2021-11-08 19:35:48 +01:00
|
|
|
wallet->newTransaction(t1);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 5000); // simple check to know its been accepted
|
|
|
|
|
|
|
|
|
|
// base check
|
|
|
|
|
for (auto ref : walletSet.outputs) {
|
|
|
|
|
bool lockSuccess = wallet->lockUTXO(ref);
|
|
|
|
|
QVERIFY(!lockSuccess); // can't lock them again.
|
|
|
|
|
bool unlockSuccess = wallet->unlockUTXO(ref);
|
|
|
|
|
QVERIFY(!unlockSuccess); // can't unlock auto-referenced outputs.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// start the broadcast, which finds all the unconfirmed transactions.
|
|
|
|
|
wallet->findBroadcastTransaction();
|
|
|
|
|
wallet->markTxRejected(7);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Now check that the resources are made available again.
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
auto ws = wallet->findInputsFor(12899000, 1, 1, change);
|
|
|
|
|
QCOMPARE(ws.outputs.size(), 6L);
|
|
|
|
|
|
|
|
|
|
// should behave like unlocked outputs now.
|
|
|
|
|
for (auto ref : walletSet.outputs) {
|
|
|
|
|
bool lockSuccess = wallet->lockUTXO(ref);
|
|
|
|
|
QVERIFY(lockSuccess);
|
|
|
|
|
bool unlockSuccess = wallet->unlockUTXO(ref);
|
|
|
|
|
QVERIFY(unlockSuccess);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-13 11:47:38 +01:00
|
|
|
void TestWallet::txVersion()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* At the time of writing this test, the network only allows a couple of versions of transactions.
|
|
|
|
|
* In the future a new upgrade may happen that supports a new transaction version, but then today's
|
|
|
|
|
* software is not going to be able to parse it correctly. As such we should reject the transaction
|
|
|
|
|
* until such a time that the code has been changed to support this specific future version.
|
|
|
|
|
*/
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
TransactionBuilder builder;
|
|
|
|
|
uint256 prevTxId = uint256S("0x12830924807308721309128309128");
|
|
|
|
|
builder.appendInput(prevTxId, 0);
|
|
|
|
|
builder.appendOutput(1000000);
|
|
|
|
|
builder.pushOutputPay2Address(wallet->nextUnusedAddress());
|
|
|
|
|
builder.setTransactionVersion(10);
|
|
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
|
|
|
|
Tx tx = builder.createTransaction(pool);
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
wallet->newTransaction(tx);
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
|
|
|
|
|
// valid version should get accepted.
|
|
|
|
|
builder.setTransactionVersion(2);
|
|
|
|
|
tx = builder.createTransaction(pool);
|
|
|
|
|
wallet->newTransaction(tx);
|
|
|
|
|
QCOMPARE(wallet->balanceConfirmed(), 0);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 1000000);
|
|
|
|
|
QCOMPARE(wallet->balanceImmature(), 0);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-13 19:29:25 +02:00
|
|
|
void TestWallet::testEncryption1()
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
wallet->addTestTransactions();
|
|
|
|
|
int64_t change = 0;
|
|
|
|
|
QCOMPARE(wallet->encryptionSeed(), 0);
|
|
|
|
|
QCOMPARE(wallet->encryption(), Wallet::NotEncrypted);
|
|
|
|
|
try {
|
|
|
|
|
auto walletSet = wallet->findInputsFor(9000000, 1, 1, change);
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
QFAIL(e.what());
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-19 14:29:27 +02:00
|
|
|
wallet->setEncryption(Wallet::NotEncrypted, QString());
|
2022-05-13 19:29:25 +02:00
|
|
|
QCOMPARE(wallet->encryptionSeed(), 0);
|
|
|
|
|
QCOMPARE(wallet->encryption(), Wallet::NotEncrypted);
|
|
|
|
|
try {
|
|
|
|
|
auto walletSet = wallet->findInputsFor(9000000, 1, 1, change);
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
QFAIL(e.what());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-14 14:27:51 +02:00
|
|
|
const QString PWD("Hello this is a password");
|
2022-05-13 19:29:25 +02:00
|
|
|
uint32_t seed = 0; // the seed is stored outside of the wallet.
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
|
|
|
|
QCOMPARE(wallet->encryption(), Wallet::NotEncrypted);
|
|
|
|
|
QCOMPARE(wallet->encryptionSeed(), 0);
|
|
|
|
|
|
2022-06-19 14:29:27 +02:00
|
|
|
wallet->setEncryption(Wallet::SecretsEncrypted, PWD);
|
2022-05-13 19:29:25 +02:00
|
|
|
QVERIFY(wallet->encryptionSeed() != 0);
|
|
|
|
|
seed = wallet->encryptionSeed();
|
|
|
|
|
int64_t change = 0;
|
|
|
|
|
try {
|
|
|
|
|
auto walletSet = wallet->findInputsFor(9000000, 1, 1, change);
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
QFAIL(e.what());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto &secrets = wallet->walletSecrets();
|
|
|
|
|
for (auto i = secrets.begin(); i != secrets.end(); ++i) {
|
|
|
|
|
QVERIFY(i->second.privKey.isValid() == false);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-14 14:27:51 +02:00
|
|
|
// decrypt wallet and check
|
2022-06-19 14:29:27 +02:00
|
|
|
wallet->decrypt(PWD);
|
2022-05-14 14:27:51 +02:00
|
|
|
auto walletSet = wallet->findInputsFor(9000000, 1, 1, change);
|
|
|
|
|
const auto &secrets2 = wallet->walletSecrets();
|
|
|
|
|
for (auto i = secrets2.begin(); i != secrets2.end(); ++i) {
|
|
|
|
|
QVERIFY(i->second.privKey.isValid());
|
|
|
|
|
}
|
2022-05-13 19:29:25 +02:00
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet();
|
|
|
|
|
QCOMPARE(wallet->encryption(), Wallet::SecretsEncrypted);
|
|
|
|
|
int64_t change = 0;
|
|
|
|
|
try {
|
|
|
|
|
auto walletSet = wallet->findInputsFor(9000000, 1, 1, change);
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
QFAIL(e.what());
|
|
|
|
|
}
|
|
|
|
|
const auto &secrets = wallet->walletSecrets();
|
|
|
|
|
for (auto i = secrets.begin(); i != secrets.end(); ++i) {
|
|
|
|
|
QVERIFY(i->second.privKey.isValid() == false);
|
|
|
|
|
}
|
2022-05-14 14:27:51 +02:00
|
|
|
// decrypt wallet and check
|
|
|
|
|
wallet->setEncryptionSeed(seed); // we need to set it to allow decryption to work
|
2022-06-19 14:29:27 +02:00
|
|
|
bool success = wallet->decrypt(PWD);
|
|
|
|
|
QVERIFY(success);
|
2022-05-14 14:27:51 +02:00
|
|
|
auto walletSet = wallet->findInputsFor(9000000, 1, 1, change);
|
|
|
|
|
const auto &secrets2 = wallet->walletSecrets();
|
|
|
|
|
for (auto i = secrets2.begin(); i != secrets2.end(); ++i) {
|
|
|
|
|
QVERIFY(i->second.privKey.isValid());
|
|
|
|
|
}
|
2022-05-13 19:29:25 +02:00
|
|
|
}
|
2022-05-17 00:44:51 +02:00
|
|
|
}
|
2022-05-13 19:29:25 +02:00
|
|
|
|
2022-05-17 00:44:51 +02:00
|
|
|
// test wallet encryption level Full
|
|
|
|
|
void TestWallet::testEncryption2()
|
|
|
|
|
{
|
|
|
|
|
const QString PWD("Hello this is a password");
|
|
|
|
|
uint32_t seed = 0; // the seed is stored outside of the wallet.
|
2022-05-17 16:21:59 +02:00
|
|
|
Tx theTx;
|
2023-12-22 14:56:21 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
2022-05-17 00:44:51 +02:00
|
|
|
{
|
|
|
|
|
auto wallet = createWallet();
|
|
|
|
|
QCOMPARE(wallet->encryptionSeed(), 0);
|
|
|
|
|
wallet->addTestTransactions();
|
|
|
|
|
// add a transaction that will be saved on disk.
|
|
|
|
|
TransactionBuilder builder;
|
|
|
|
|
builder.appendInput(uint256S("7690458423233793949274923986540813794798131233949274923986540813"), 1);
|
|
|
|
|
builder.appendOutput(6012412);
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId address;
|
2022-05-17 00:44:51 +02:00
|
|
|
wallet->reserveUnusedAddress(address);
|
|
|
|
|
builder.pushOutputPay2Address(address);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 0);
|
2022-05-17 16:21:59 +02:00
|
|
|
theTx = builder.createTransaction();
|
|
|
|
|
wallet->newTransaction(theTx);
|
2022-05-17 00:44:51 +02:00
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 6012412);
|
|
|
|
|
}
|
2022-05-17 16:21:59 +02:00
|
|
|
QString txHash = QString::fromStdString(theTx.createHash().ToString());
|
2022-05-17 22:49:01 +02:00
|
|
|
const QString baseName("%1/wallet-1111/%2/%3");
|
2022-09-26 16:23:21 +02:00
|
|
|
QString txFile = baseName.arg(m_dir).arg(txHash.left(2), txHash.mid(2));
|
2022-05-17 00:44:51 +02:00
|
|
|
QVERIFY(QFile::exists(txFile));
|
|
|
|
|
|
|
|
|
|
{
|
2022-10-07 22:45:02 +02:00
|
|
|
auto wallet = openWallet(/* seed = */ 0); // not encrypted yet
|
2022-05-17 00:44:51 +02:00
|
|
|
auto secrets = wallet->walletSecrets();
|
|
|
|
|
QCOMPARE(secrets.size(), (size_t)10);
|
|
|
|
|
for (auto i = secrets.begin(); i != secrets.end(); ++i) {
|
|
|
|
|
QVERIFY(i->second.privKey.isValid());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QCOMPARE(wallet->encryptionSeed(), 0);
|
2022-06-19 14:29:27 +02:00
|
|
|
wallet->setEncryption(Wallet::FullyEncrypted, PWD);
|
2022-05-17 00:44:51 +02:00
|
|
|
QVERIFY(wallet->encryptionSeed() != 0);
|
2022-07-21 14:21:31 +02:00
|
|
|
QVERIFY(wallet->walletSecrets().empty());
|
|
|
|
|
try {
|
|
|
|
|
Tx txCopy = wallet->loadTx(theTx.createHash(), pool);
|
|
|
|
|
QFAIL("Loading from an encrypted wallet shoud fail");
|
|
|
|
|
} catch (...) {}
|
2022-10-07 22:45:02 +02:00
|
|
|
|
|
|
|
|
seed = wallet->encryptionSeed();
|
2022-05-17 00:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// a wallet that has been fully encrypted should have encrypted the
|
|
|
|
|
// on disk transactions and renamed the files.
|
|
|
|
|
QCOMPARE(QFile::exists(txFile), false);
|
|
|
|
|
|
|
|
|
|
{
|
2022-05-17 22:26:48 +02:00
|
|
|
auto wallet = openWallet(seed);
|
2022-05-17 00:44:51 +02:00
|
|
|
QCOMPARE(wallet->encryption(), Wallet::FullyEncrypted);
|
|
|
|
|
QVERIFY(wallet->walletSecrets().empty());
|
|
|
|
|
|
|
|
|
|
// decrypt wallet and check
|
|
|
|
|
wallet->setEncryptionSeed(seed); // we need to set it to allow decryption to work
|
2022-06-19 14:29:27 +02:00
|
|
|
QCOMPARE(wallet->decrypt("Not the password"), false); // check if we correctly fail on wrong pwd
|
|
|
|
|
QCOMPARE(wallet->isDecrypted(), false);
|
|
|
|
|
|
|
|
|
|
bool ok = wallet->decrypt(PWD);
|
|
|
|
|
QVERIFY(ok);
|
|
|
|
|
QCOMPARE(wallet->isDecrypted(), true);
|
2022-05-17 00:44:51 +02:00
|
|
|
int64_t change;
|
|
|
|
|
auto walletSet = wallet->findInputsFor(9000000, 1, 1, change);
|
|
|
|
|
const auto &secrets = wallet->walletSecrets();
|
|
|
|
|
QCOMPARE(secrets.size(), (size_t)10);
|
|
|
|
|
for (auto i = secrets.begin(); i != secrets.end(); ++i) {
|
|
|
|
|
QVERIFY(i->second.privKey.isValid());
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-17 16:21:59 +02:00
|
|
|
// fetch tx info + compare contents
|
|
|
|
|
Tx txCopy = wallet->loadTx(theTx.createHash(), pool);
|
|
|
|
|
QVERIFY(txCopy.isValid());
|
|
|
|
|
QCOMPARE(txCopy.createHash(), theTx.createHash());
|
2022-05-17 22:49:01 +02:00
|
|
|
|
|
|
|
|
// does it detect duplicates?
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 6012412);
|
|
|
|
|
wallet->newTransaction(theTx);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 6012412);
|
|
|
|
|
|
|
|
|
|
TransactionBuilder builder;
|
|
|
|
|
builder.appendInput(uint256S("7690458423233793949274923986540813794798131233949274923986540813"), 2);
|
|
|
|
|
builder.appendOutput(1283482);
|
2022-07-06 22:06:58 +02:00
|
|
|
KeyId address;
|
2022-05-17 22:49:01 +02:00
|
|
|
wallet->reserveUnusedAddress(address);
|
|
|
|
|
builder.pushOutputPay2Address(address);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 6012412);
|
|
|
|
|
auto newTx = builder.createTransaction();
|
|
|
|
|
wallet->newTransaction(newTx);
|
|
|
|
|
QCOMPARE(wallet->balanceUnconfirmed(), 6012412 + 1283482);
|
|
|
|
|
|
|
|
|
|
// the name if it was saved without obfuscation
|
|
|
|
|
auto hash = QString::fromStdString(newTx.createHash().ToString());
|
2022-09-26 16:23:21 +02:00
|
|
|
txFile = baseName.arg(m_dir).arg(hash.left(2), hash.mid(2));
|
2022-05-17 22:49:01 +02:00
|
|
|
QVERIFY(QFile::exists(txFile) == false);
|
|
|
|
|
|
|
|
|
|
auto savedTx = wallet->loadTx(newTx.createHash(), pool);
|
|
|
|
|
QVERIFY(savedTx.isValid());
|
|
|
|
|
QCOMPARE(savedTx.size(), newTx.size());
|
|
|
|
|
QCOMPARE(savedTx.createHash(), newTx.createHash());
|
2022-05-17 00:44:51 +02:00
|
|
|
}
|
2022-05-18 12:59:01 +02:00
|
|
|
|
|
|
|
|
// clear the secrets and then decrypt to see if they come back.
|
|
|
|
|
{
|
|
|
|
|
auto wallet = openWallet(seed);
|
|
|
|
|
QCOMPARE(wallet->encryption(), Wallet::FullyEncrypted);
|
|
|
|
|
QVERIFY(wallet->walletSecrets().empty());
|
2022-06-19 14:29:27 +02:00
|
|
|
QCOMPARE(wallet->isDecrypted(), false);
|
2022-05-18 12:59:01 +02:00
|
|
|
|
|
|
|
|
// decrypt wallet and check
|
|
|
|
|
wallet->setEncryptionSeed(seed); // we need to set it to allow decryption to work
|
2022-06-19 14:29:27 +02:00
|
|
|
bool ok = wallet->decrypt(PWD);
|
|
|
|
|
QCOMPARE(wallet->isDecrypted(), true);
|
|
|
|
|
QVERIFY(ok);
|
2022-05-18 12:59:01 +02:00
|
|
|
{
|
|
|
|
|
const auto &secrets = wallet->walletSecrets();
|
|
|
|
|
QCOMPARE(secrets.size(), (size_t)10);
|
|
|
|
|
for (auto i = secrets.begin(); i != secrets.end(); ++i) {
|
|
|
|
|
QVERIFY(i->second.privKey.isValid());
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-19 14:29:27 +02:00
|
|
|
wallet->forgetEncryptedSecrets();
|
|
|
|
|
QCOMPARE(wallet->isDecrypted(), false);
|
2022-05-18 12:59:01 +02:00
|
|
|
{
|
|
|
|
|
const auto &secrets = wallet->walletSecrets();
|
2022-07-06 22:54:37 +02:00
|
|
|
QVERIFY(secrets.empty()); // a fully encrypted wallet gets cleaned out
|
2022-05-18 12:59:01 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-19 14:29:27 +02:00
|
|
|
wallet->decrypt(PWD);
|
|
|
|
|
QCOMPARE(wallet->isDecrypted(), true);
|
2022-05-18 12:59:01 +02:00
|
|
|
{
|
|
|
|
|
const auto &secrets = wallet->walletSecrets();
|
|
|
|
|
QCOMPARE(secrets.size(), (size_t)10);
|
|
|
|
|
for (auto i = secrets.begin(); i != secrets.end(); ++i) {
|
|
|
|
|
QVERIFY(i->second.privKey.isValid());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-17 00:44:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestWallet::testEncryption3()
|
|
|
|
|
{
|
|
|
|
|
// Test HD wallet encryption of level SecretsEncrypted
|
2022-05-18 12:59:01 +02:00
|
|
|
|
|
|
|
|
// Test a wallet that is set to secrets encryption and then in
|
|
|
|
|
// encrypted state moved to fully encrypted. Check if it doesn't lose any secrets.
|
2022-05-13 19:29:25 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-08 19:35:48 +01:00
|
|
|
std::unique_ptr<MockWallet> TestWallet::createWallet()
|
2020-11-02 21:49:06 +01:00
|
|
|
{
|
|
|
|
|
if (m_dir.isEmpty()) {
|
|
|
|
|
QString basedir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
|
|
|
|
m_dir = basedir + QString("/floweepay-%1/").arg(QCoreApplication::instance()->applicationPid());
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-08 19:35:48 +01:00
|
|
|
std::unique_ptr<MockWallet> wallet(
|
|
|
|
|
static_cast<MockWallet*>(Wallet::createWallet(m_dir.toStdString(), 1111, "test")));
|
2020-11-02 21:49:06 +01:00
|
|
|
wallet->createNewPrivateKey(0);
|
|
|
|
|
|
|
|
|
|
return wallet;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-17 22:26:48 +02:00
|
|
|
std::unique_ptr<MockWallet> TestWallet::openWallet(uint32_t encryptionSeed)
|
2020-11-02 21:49:06 +01:00
|
|
|
{
|
|
|
|
|
Q_ASSERT(!m_dir.isEmpty());
|
2021-11-08 19:35:48 +01:00
|
|
|
std::unique_ptr<MockWallet> wallet(
|
2022-05-17 22:26:48 +02:00
|
|
|
static_cast<MockWallet*>(new Wallet(m_dir.toStdString(), 1111, encryptionSeed)));
|
2020-11-02 21:49:06 +01:00
|
|
|
return wallet;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-04 13:36:07 +02:00
|
|
|
TestWallet::TestWallet()
|
|
|
|
|
: dummyBlockId(uint256S("0x198795438759712937981273"))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-02 21:49:06 +01:00
|
|
|
void TestWallet::cleanup()
|
|
|
|
|
{
|
|
|
|
|
if (m_dir.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
QDir dir(m_dir);
|
|
|
|
|
dir.removeRecursively();
|
|
|
|
|
m_dir.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-31 16:56:03 +01:00
|
|
|
void TestWallet::testRef()
|
|
|
|
|
{
|
|
|
|
|
Wallet::OutputRef a(10, 100);
|
|
|
|
|
Wallet::OutputRef b(a.encoded());
|
|
|
|
|
QCOMPARE(a.outputIndex(), 100);
|
|
|
|
|
QCOMPARE(b.outputIndex(), 100);
|
|
|
|
|
QCOMPARE(a.txIndex(), 10);
|
|
|
|
|
QCOMPARE(b.txIndex(), 10);
|
2021-04-19 19:22:37 +02:00
|
|
|
|
2021-10-31 16:56:03 +01:00
|
|
|
a.setTxIndex(12);
|
|
|
|
|
Wallet::OutputRef c(a.encoded());
|
|
|
|
|
QCOMPARE(a.outputIndex(), 100);
|
|
|
|
|
QCOMPARE(c.outputIndex(), 100);
|
|
|
|
|
QCOMPARE(a.txIndex(), 12);
|
|
|
|
|
QCOMPARE(c.txIndex(), 12);
|
|
|
|
|
}
|
2021-04-19 19:22:37 +02:00
|
|
|
|
2020-10-17 16:38:37 +02:00
|
|
|
QTEST_MAIN(TestWallet)
|