2018-05-09 10:43:50 +02:00
|
|
|
/*
|
|
|
|
|
* This file is part of the Flowee project
|
2021-06-20 22:44:44 +02:00
|
|
|
* Copyright (C) 2018-2020 Tom Zander <tom@flowee.org>
|
2018-05-09 10:43:50 +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/>.
|
|
|
|
|
*/
|
2018-08-14 09:59:57 +02:00
|
|
|
#include "test_utxo.h"
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
#include <WorkerThreads.h>
|
|
|
|
|
#include <util.h>
|
2026-05-14 13:13:40 +02:00
|
|
|
#include <utiltime.h>
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2019-03-05 09:05:15 +01:00
|
|
|
#include <utxo/UnspentOutputDatabase_p.h>
|
2023-07-14 21:50:44 +02:00
|
|
|
#include <utxo/UTXOInteralError.h>
|
2022-06-20 16:27:00 +02:00
|
|
|
#include <fstream>
|
2019-03-05 09:05:15 +01:00
|
|
|
|
2018-08-14 09:59:57 +02:00
|
|
|
void TestUtxo::init()
|
2018-05-09 10:43:50 +02:00
|
|
|
{
|
2019-11-13 11:46:09 +01:00
|
|
|
m_testPath = boost::filesystem::temp_directory_path() / strprintf("test_flowee_%lu", (unsigned long)GetTime());
|
2018-08-14 09:59:57 +02:00
|
|
|
boost::filesystem::remove_all(m_testPath);
|
|
|
|
|
}
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2018-08-14 09:59:57 +02:00
|
|
|
void TestUtxo::cleanup()
|
|
|
|
|
{
|
|
|
|
|
boost::filesystem::remove_all(m_testPath);
|
|
|
|
|
}
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2018-08-14 09:59:57 +02:00
|
|
|
void TestUtxo::insertTransactions(UnspentOutputDatabase &db, int number)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < number; ++i) {
|
2018-05-09 10:43:50 +02:00
|
|
|
char buf[67];
|
2018-08-14 09:59:57 +02:00
|
|
|
sprintf(buf, templateTxId, i);
|
|
|
|
|
uint256 txid = uint256S(buf);
|
2018-08-15 20:30:45 +02:00
|
|
|
db.insert(txid, 0, 100+i, 6000+i);
|
|
|
|
|
db.insert(txid, 1, 100+i, 6000+i);
|
2018-08-14 09:59:57 +02:00
|
|
|
|
|
|
|
|
UnspentOutput uo = db.find(txid, 0);
|
|
|
|
|
QCOMPARE(uo.offsetInBlock(), 6000 + i);
|
|
|
|
|
QCOMPARE(uo.blockHeight(), 100 + i);
|
2018-05-09 10:43:50 +02:00
|
|
|
}
|
2018-08-14 09:59:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint256 TestUtxo::insertedTxId(int index)
|
|
|
|
|
{
|
|
|
|
|
char buf[67];
|
|
|
|
|
sprintf(buf, templateTxId, index);
|
|
|
|
|
return uint256S(buf);
|
|
|
|
|
}
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
|
2018-08-14 09:59:57 +02:00
|
|
|
void TestUtxo::basic()
|
2018-05-09 10:43:50 +02:00
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
boost::asio::io_context ioContext;
|
|
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-05-09 10:43:50 +02:00
|
|
|
uint256 txid = uint256S("0xb4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b");
|
2018-07-23 18:05:43 +02:00
|
|
|
db.insert(txid, 0, 100, 6000);
|
2018-05-09 10:43:50 +02:00
|
|
|
UnspentOutput uo = db.find(txid, 0);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(uo.offsetInBlock(), 6000);
|
|
|
|
|
QCOMPARE(uo.blockHeight(), 100);
|
2019-02-23 15:25:04 +01:00
|
|
|
QVERIFY(((uo.rmHint() >> 32) & 0xFFFFF) > 0);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2019-02-23 15:25:04 +01:00
|
|
|
SpentOutput rmData = db.remove(txid, 0, uo.rmHint());
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid());
|
|
|
|
|
QCOMPARE(rmData.blockHeight, 100);
|
|
|
|
|
QCOMPARE(rmData.offsetInBlock, 6000);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
UnspentOutput uo2 = db.find(txid, 0);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(uo2.blockHeight(), 0);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2018-07-23 18:05:43 +02:00
|
|
|
rmData = db.remove(txid, 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QCOMPARE(rmData.isValid(), false);
|
|
|
|
|
QVERIFY(rmData.blockHeight <= 0);
|
2018-05-09 10:43:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// test if we can keep multiple entries separate
|
2018-08-14 09:59:57 +02:00
|
|
|
void TestUtxo::multiple()
|
2018-05-09 10:43:50 +02:00
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
boost::asio::io_context ioContext;
|
|
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
insertTransactions(db, 100);
|
|
|
|
|
const uint256 remove1 = insertedTxId(20);
|
|
|
|
|
const uint256 remove2 = insertedTxId(89);
|
2018-07-23 18:05:43 +02:00
|
|
|
SpentOutput rmData = db.remove(remove1, 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid());
|
|
|
|
|
QCOMPARE(rmData.blockHeight, 120);
|
|
|
|
|
QCOMPARE(rmData.offsetInBlock, 6020);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
UnspentOutput find1 = db.find(remove1, 0);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(find1.blockHeight(), 0); // we just removed it
|
2018-05-09 10:43:50 +02:00
|
|
|
UnspentOutput find2 = db.find(remove1, 1);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(find2.blockHeight(), 120); // this should not be removed
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
UnspentOutput find3 = db.find(remove2, 0);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(find3.blockHeight(), 189);
|
2018-05-09 10:43:50 +02:00
|
|
|
UnspentOutput find4 = db.find(remove2, 1);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(find4.blockHeight(), 189); // its here now
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2018-07-23 18:05:43 +02:00
|
|
|
rmData = db.remove(remove2, 1);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid());
|
|
|
|
|
QCOMPARE(rmData.blockHeight, 189);
|
|
|
|
|
QCOMPARE(rmData.offsetInBlock, 6089);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
UnspentOutput find5 = db.find(remove2, 0);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(find5.blockHeight(), 189);
|
2018-05-09 10:43:50 +02:00
|
|
|
UnspentOutput find6 = db.find(remove2, 1);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(find6.blockHeight(), 0); // poof.
|
2018-05-09 10:43:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// test if we can keep entries between restarts
|
2018-08-14 09:59:57 +02:00
|
|
|
void TestUtxo::restart()
|
2018-05-09 10:43:50 +02:00
|
|
|
{
|
|
|
|
|
WorkerThreads workers;
|
|
|
|
|
{ // scope for DB
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(workers.ioContext(), m_testPath);
|
2018-05-09 10:43:50 +02:00
|
|
|
insertTransactions(db, 50);
|
2018-07-29 13:55:30 +02:00
|
|
|
db.blockFinished(1, uint256()); // commmit
|
2018-05-09 10:43:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logDebug() << "Step 2";
|
|
|
|
|
{ // scope for DB
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(workers.ioContext(), m_testPath);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
for (int i = 0; i < 50; ++i) {
|
|
|
|
|
// logDebug() << "select" << i;
|
|
|
|
|
uint256 txid = insertedTxId(i);
|
|
|
|
|
UnspentOutput uo = db.find(txid, 0);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(uo.blockHeight(), 100 + i);
|
|
|
|
|
QCOMPARE(uo.offsetInBlock(), 6000 + i);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
UnspentOutput uo2 = db.find(txid, 1);
|
2018-08-14 09:59:57 +02:00
|
|
|
QCOMPARE(uo2.blockHeight(), 100 + i);
|
|
|
|
|
QCOMPARE(uo2.offsetInBlock(), 6000 + i);
|
2018-05-09 10:43:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-15 20:30:45 +02:00
|
|
|
void TestUtxo::commit()
|
2018-07-29 13:55:30 +02:00
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* delete is by far the most complex usecase.
|
|
|
|
|
* I should test;
|
|
|
|
|
* 1) delete a leaf from an on-disk bucket that only contains the one item
|
|
|
|
|
* 2) delete a leaf from an on-disk bucket where there are more leafs
|
|
|
|
|
* 3) delete a leaf from an in-memory bucket where there are more leafs
|
|
|
|
|
* 4) delete a leaf from an in-memory bucket where its the last leaf
|
|
|
|
|
*
|
|
|
|
|
* Also I should create a new leaf
|
|
|
|
|
* 5) in an existing bucket
|
|
|
|
|
* 6) in a new bucket
|
|
|
|
|
*/
|
2025-02-08 19:05:26 +01:00
|
|
|
boost::asio::io_context ioContext;
|
2018-07-29 13:55:30 +02:00
|
|
|
uint256 txid;
|
|
|
|
|
{ // usecase 3
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
insertTransactions(db, 100);
|
|
|
|
|
db.blockFinished(1, uint256()); // this is a 'commit'
|
|
|
|
|
|
|
|
|
|
txid = insertedTxId(99);
|
|
|
|
|
SpentOutput rmData = db.remove(txid, 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid());
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{ // usecase 2
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
// after a restart, the not committed tx is again there.
|
|
|
|
|
SpentOutput rmData = db.remove(txid, 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid());
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{ // usecase 2 && 2
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
// after a restart, the not committed tx is again there.
|
|
|
|
|
SpentOutput rmData = db.remove(txid, 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid());
|
2018-07-29 13:55:30 +02:00
|
|
|
|
|
|
|
|
db.rollback();
|
|
|
|
|
rmData = db.remove(txid, 0); // it reappeared
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid());
|
2018-07-29 13:55:30 +02:00
|
|
|
|
|
|
|
|
db.blockFinished(2, uint256()); // commit
|
|
|
|
|
|
|
|
|
|
rmData = db.remove(txid, 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(!rmData.isValid());
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
// the commit made the removed tx actually go away.
|
|
|
|
|
SpentOutput rmData = db.remove(txid, 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(!rmData.isValid());
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// because the helper method insertTransactions generates transactions
|
|
|
|
|
// that all land in the same bucket I need to create a new one to test buckets with only one tx.
|
|
|
|
|
const char *txid2 = "0x1a3454117444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b";
|
|
|
|
|
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
db.insert(uint256S(txid2), 0, 200, 2000);
|
|
|
|
|
SpentOutput rmData = db.remove(uint256S(txid2), 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid()); // delete should be Ok
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
// test usecase 5
|
|
|
|
|
SpentOutput rmData = db.remove(uint256S(txid2), 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(!rmData.isValid()); // it was never committed
|
2018-07-29 13:55:30 +02:00
|
|
|
|
|
|
|
|
// test usecase 1
|
|
|
|
|
db.insert(uint256S(txid2), 0, 200, 2000);
|
|
|
|
|
db.blockFinished(3, uint256());
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
// continue to test usecase 1
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
SpentOutput rmData = db.remove(uint256S(txid2), 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid());
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
// continue to test usecase 1
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
SpentOutput rmData = db.remove(uint256S(txid2), 0);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(rmData.isValid()); // it came back!
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *txid3 = "0x4a3454117444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b";
|
|
|
|
|
// usecase 6
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
db.insert(txid3, 2, 300, 1000);
|
|
|
|
|
}
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-07-29 13:55:30 +02:00
|
|
|
UnspentOutput uo = db.find(uint256S(txid3), 2);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(!uo.isValid()); // it was never committed
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// test usecase 5
|
|
|
|
|
char buf[67];
|
|
|
|
|
sprintf(buf, templateTxId, 200);
|
|
|
|
|
uint256 txid4 = uint256S(buf);
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-10-13 21:21:25 +02:00
|
|
|
db.insert(txid4, 5, 40, 81);
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(!db.find(txid4, 5).isValid());
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
2018-08-04 17:28:08 +02:00
|
|
|
// now separate the saving of the bucket and the leafs.
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-10-13 21:21:25 +02:00
|
|
|
db.insert(txid4, 6, 40, 81);
|
2018-08-04 17:28:08 +02:00
|
|
|
db.blockFinished(4, uint256());
|
2018-10-13 21:21:25 +02:00
|
|
|
db.insert(txid4, 7, 40, 81);
|
2018-08-04 17:28:08 +02:00
|
|
|
}
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-08-15 20:30:45 +02:00
|
|
|
QVERIFY(!db.find(txid4, 5).isValid());
|
|
|
|
|
QVERIFY(db.find(txid4, 6).isValid());
|
|
|
|
|
QVERIFY(!db.find(txid4, 7).isValid());
|
2018-08-04 17:28:08 +02:00
|
|
|
}
|
2018-09-16 17:28:47 +02:00
|
|
|
|
|
|
|
|
// new usecase; deleting from an in-memory bucket.
|
|
|
|
|
// A bucket was saved to disk, retrieved and stored in memory because I inserted
|
|
|
|
|
// a new item and then I remove an old item.
|
|
|
|
|
// We need to make sure that the on-disk bucket is the one we get after rollback()
|
|
|
|
|
sprintf(buf, templateTxId, 127);
|
|
|
|
|
uint256 txid5 = uint256S(buf);
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-10-13 21:21:25 +02:00
|
|
|
db.insert(txid5, 10, 40, 90);
|
|
|
|
|
db.insert(txid5, 11, 40, 90);
|
|
|
|
|
db.insert(txid5, 13, 40, 90);
|
2018-09-16 17:28:47 +02:00
|
|
|
db.blockFinished(5, uint256());
|
|
|
|
|
}
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-10-13 21:21:25 +02:00
|
|
|
db.insert(txid5, 20, 40, 81); // loads from disk, adds item
|
2018-09-16 17:28:47 +02:00
|
|
|
// rollback now should revert to the on-disk version.
|
|
|
|
|
}
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-09-16 17:28:47 +02:00
|
|
|
QVERIFY(db.find(txid4, 6).isValid());
|
|
|
|
|
QVERIFY(!db.find(txid5, 20).isValid());
|
|
|
|
|
QVERIFY(db.find(txid5, 10).isValid());
|
|
|
|
|
QVERIFY(db.find(txid5, 11).isValid());
|
|
|
|
|
QVERIFY(db.find(txid5, 13).isValid());
|
|
|
|
|
}
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-10-13 21:21:25 +02:00
|
|
|
db.insert(txid5, 20, 40, 81); // loads from disk, adds item
|
2018-09-16 17:28:47 +02:00
|
|
|
SpentOutput rmData = db.remove(txid5, 11); // removes from mem-bucket
|
|
|
|
|
QVERIFY(rmData.isValid());
|
|
|
|
|
// rollback now should revert to the on-disk version.
|
|
|
|
|
}
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2018-09-16 17:28:47 +02:00
|
|
|
QVERIFY(db.find(txid4, 6).isValid());
|
|
|
|
|
QVERIFY(!db.find(txid5, 20).isValid());
|
|
|
|
|
QVERIFY(db.find(txid5, 10).isValid());
|
|
|
|
|
QVERIFY(db.find(txid5, 11).isValid());
|
|
|
|
|
QVERIFY(db.find(txid5, 13).isValid());
|
|
|
|
|
}
|
2018-07-29 13:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-18 22:17:21 +02:00
|
|
|
void TestUtxo::saveInfo()
|
|
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
boost::asio::io_context ioContext;
|
2020-05-18 22:17:21 +02:00
|
|
|
{
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2020-05-18 22:17:21 +02:00
|
|
|
db.blockFinished(10, uint256());
|
|
|
|
|
}
|
|
|
|
|
QFileInfo info(QString::fromStdString((m_testPath / "data-1.2.info").string()));
|
|
|
|
|
QVERIFY(info.exists());
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2020-05-18 22:17:21 +02:00
|
|
|
QCOMPARE(db.blockheight(), 10);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-05 09:05:15 +01:00
|
|
|
void TestUtxo::cowList()
|
|
|
|
|
{
|
|
|
|
|
DataFileList list;
|
|
|
|
|
auto x = DataFile::createDatafile((m_testPath / "testdb").string(), 1, uint256());
|
|
|
|
|
list.append(x);
|
|
|
|
|
QVERIFY(x == list[0]);
|
|
|
|
|
QVERIFY(x == list.at(0));
|
|
|
|
|
|
|
|
|
|
auto copy(list);
|
|
|
|
|
QVERIFY(x == copy[0]);
|
|
|
|
|
QVERIFY(x == copy.at(0));
|
|
|
|
|
|
|
|
|
|
copy[0] = nullptr;
|
|
|
|
|
QVERIFY(x == list[0]);
|
|
|
|
|
QVERIFY(x == list.at(0));
|
|
|
|
|
QVERIFY(nullptr == copy[0]);
|
|
|
|
|
QVERIFY(nullptr == copy.at(0));
|
|
|
|
|
|
|
|
|
|
delete x;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-18 22:17:21 +02:00
|
|
|
void TestUtxo::restore_data()
|
|
|
|
|
{
|
|
|
|
|
QTest::addColumn<int>("cycles");
|
|
|
|
|
QTest::addColumn<int>("dbFiles");
|
|
|
|
|
QTest::addColumn<QStringList>("filesToDelete");
|
|
|
|
|
QTest::addColumn<int>("result");
|
|
|
|
|
|
|
|
|
|
// delete some files in the beginning of the sequence, which should have zero effect.
|
|
|
|
|
QStringList files;
|
|
|
|
|
files << "data-4.2.info" << "data-3.2.info";
|
|
|
|
|
QTest::newRow("lostFirst")
|
|
|
|
|
<< 3 << 4 << files << 3;
|
|
|
|
|
|
|
|
|
|
// delete an info file at the end, causing us to go back one block.
|
|
|
|
|
files = QStringList() << "data-2.4.info";
|
|
|
|
|
QTest::newRow("lostFirst")
|
|
|
|
|
<< 3 << 4 << files << 2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// numbering in the .info files, as defined in the cpp file of the UTXO
|
|
|
|
|
constexpr int MAX_INFO_NUM = 20;
|
|
|
|
|
files = QStringList() << "data-1.1.info";
|
|
|
|
|
QTest::newRow("goingRound")
|
|
|
|
|
<< MAX_INFO_NUM << 1 << files << 20;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestUtxo::restore()
|
|
|
|
|
{
|
|
|
|
|
QFETCH(int, cycles);
|
|
|
|
|
QFETCH(int, dbFiles);
|
|
|
|
|
QFETCH(QStringList, filesToDelete);
|
|
|
|
|
QFETCH(int, result);
|
|
|
|
|
|
2025-02-08 19:05:26 +01:00
|
|
|
boost::asio::io_context ioContext;
|
2020-05-18 22:17:21 +02:00
|
|
|
for (int cycle = 0; cycle < cycles; ++cycle) {
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2020-05-18 22:17:21 +02:00
|
|
|
logDebug() << cycle << db.blockheight();
|
|
|
|
|
UODBPrivate *d = db.priv();
|
|
|
|
|
QVERIFY(d->dataFiles.last());
|
|
|
|
|
while (d->dataFiles.size() < dbFiles) {
|
|
|
|
|
d->dataFiles.last()->m_fileFull = 1;
|
|
|
|
|
insertTransactions(db, 1);
|
|
|
|
|
}
|
|
|
|
|
db.blockFinished(db.blockheight() + 1, uint256());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString testPath = QString::fromStdString(m_testPath.string()) + "/";
|
|
|
|
|
logDebug() << testPath;
|
|
|
|
|
QVERIFY(testPath.endsWith("/"));
|
|
|
|
|
for (auto filename : filesToDelete) {
|
|
|
|
|
bool ok = QFile::remove(testPath + filename);
|
|
|
|
|
if (!ok)
|
|
|
|
|
logCritical() << "Failed to delete" << filename;
|
|
|
|
|
QVERIFY(ok);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-08 19:05:26 +01:00
|
|
|
UnspentOutputDatabase db(ioContext, m_testPath);
|
2020-05-18 22:17:21 +02:00
|
|
|
logDebug() << db.blockheight();
|
|
|
|
|
QCOMPARE(db.blockheight(), result);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-17 20:57:32 +01:00
|
|
|
static void createDBInfo(boost::filesystem::path db, int from, int to) {
|
|
|
|
|
DataFileCache cache(db);
|
|
|
|
|
DataFile df(from, to);
|
2023-12-21 15:13:56 +01:00
|
|
|
df.m_writeBuffer = std::make_shared<Streaming::BufferPool>();
|
2020-11-17 20:57:32 +01:00
|
|
|
cache.writeInfoFile(&df);
|
|
|
|
|
|
|
|
|
|
db.concat(".db");
|
2022-06-20 16:27:00 +02:00
|
|
|
std::ofstream file(db.string());
|
2020-11-17 20:57:32 +01:00
|
|
|
file.close();
|
|
|
|
|
boost::filesystem::resize_file(db, 100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TestUtxo::rollback()
|
|
|
|
|
{
|
|
|
|
|
// create a bunch of info files and try to follback to different states.
|
|
|
|
|
boost::filesystem::create_directories(m_testPath);
|
|
|
|
|
|
|
|
|
|
// create checkpoints!
|
|
|
|
|
createDBInfo(m_testPath / "data-1", 0, 500);
|
|
|
|
|
createDBInfo(m_testPath / "data-1", 0, 702);
|
|
|
|
|
createDBInfo(m_testPath / "data-1", 0, 900);
|
|
|
|
|
createDBInfo(m_testPath / "data-2", 200, 250);
|
|
|
|
|
createDBInfo(m_testPath / "data-2", 200, 400);
|
|
|
|
|
createDBInfo(m_testPath / "data-2", 200, 702);
|
|
|
|
|
createDBInfo(m_testPath / "data-3", 300, 400);
|
|
|
|
|
createDBInfo(m_testPath / "data-3", 300, 702);
|
|
|
|
|
createDBInfo(m_testPath / "data-3", 300, 900);
|
|
|
|
|
// There is only 1 valid checkpoint in this miserable setup: 702
|
|
|
|
|
|
2025-02-08 19:05:26 +01:00
|
|
|
boost::asio::io_context dummy;
|
2020-11-17 20:57:32 +01:00
|
|
|
UODBPrivate p1(dummy, m_testPath);
|
|
|
|
|
p1.memOnly = true;
|
|
|
|
|
QCOMPARE(p1.dataFiles.size(), 3);
|
|
|
|
|
DataFile *db = p1.dataFiles.at(0);
|
|
|
|
|
QCOMPARE(db->m_initialBlockHeight, 0);
|
|
|
|
|
QCOMPARE(db->m_lastBlockHeight, 702);
|
|
|
|
|
db = p1.dataFiles.at(1);
|
|
|
|
|
QCOMPARE(db->m_initialBlockHeight, 200);
|
|
|
|
|
QCOMPARE(db->m_lastBlockHeight, 702);
|
|
|
|
|
db = p1.dataFiles.at(2);
|
|
|
|
|
QCOMPARE(db->m_initialBlockHeight, 300);
|
|
|
|
|
QCOMPARE(db->m_lastBlockHeight, 702);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
UODBPrivate p2(dummy, m_testPath, 702);
|
|
|
|
|
QFAIL("Should have thrown");
|
|
|
|
|
} catch (const UTXOInternalError &e) {
|
|
|
|
|
logDebug() << "Successfully failed" << e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-04 21:48:45 +02:00
|
|
|
void TestUtxo::readLeafWithTrailingBytes()
|
|
|
|
|
{
|
|
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
|
|
|
|
const uint256 txid = uint256S("0xb4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b");
|
|
|
|
|
const int outIndex = 3;
|
|
|
|
|
const int blockHeight = 1234;
|
|
|
|
|
const int offsetInBlock = 5678;
|
|
|
|
|
|
|
|
|
|
UnspentOutput leaf(pool, txid, outIndex, blockHeight, offsetInBlock);
|
|
|
|
|
std::string storage = leaf.data().toString();
|
|
|
|
|
storage.push_back('\x07'); // Invalid message type after the leaf record.
|
|
|
|
|
|
|
|
|
|
const auto mappedFileSlice = Streaming::ConstBuffer::create(storage.data(), storage.size());
|
|
|
|
|
UnspentOutput parsed(txid.GetCheapHash(), mappedFileSlice);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(parsed.prevTxId(), txid);
|
|
|
|
|
QCOMPARE(parsed.outIndex(), outIndex);
|
|
|
|
|
QCOMPARE(parsed.blockHeight(), blockHeight);
|
|
|
|
|
QCOMPARE(parsed.offsetInBlock(), offsetInBlock);
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-14 09:59:57 +02:00
|
|
|
QTEST_MAIN(TestUtxo)
|