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/>.
|
|
|
|
|
*/
|
2024-01-22 19:32:15 +01:00
|
|
|
#ifndef FLOWEE_UNSPENTOUTPUTDATABASEPRIVATE_H
|
|
|
|
|
#define FLOWEE_UNSPENTOUTPUTDATABASEPRIVATE_H
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2019-04-06 12:19:13 +02:00
|
|
|
/*
|
|
|
|
|
* WARNING USAGE OF THIS HEADER IS RESTRICTED.
|
|
|
|
|
* This Header file is part of the private API and is meant to be used solely by the UTXO component.
|
|
|
|
|
*
|
|
|
|
|
* Usage of this API will likely mean your code will break in interesting ways in the future,
|
|
|
|
|
* or even stop to compile.
|
|
|
|
|
*
|
|
|
|
|
* YOU HAVE BEEN WARNED!!
|
|
|
|
|
*/
|
|
|
|
|
|
2018-05-09 10:43:50 +02:00
|
|
|
#include "UnspentOutputDatabase.h"
|
2019-02-23 15:25:04 +01:00
|
|
|
#include "BucketMap.h"
|
2019-03-05 09:05:15 +01:00
|
|
|
#include "DataFileList.h"
|
2018-05-09 10:43:50 +02:00
|
|
|
#include <streaming/BufferPool.h>
|
|
|
|
|
|
|
|
|
|
#include <boost/iostreams/device/mapped_file.hpp>
|
|
|
|
|
#include <boost/filesystem/path.hpp>
|
|
|
|
|
#include <boost/thread/shared_mutex.hpp>
|
|
|
|
|
|
2018-07-23 18:05:43 +02:00
|
|
|
#include <unordered_map>
|
2018-05-09 10:43:50 +02:00
|
|
|
#include <list>
|
2018-07-29 13:55:30 +02:00
|
|
|
#include <set>
|
2018-05-09 10:43:50 +02:00
|
|
|
#include <mutex>
|
2023-12-21 15:13:56 +01:00
|
|
|
#include <memory>
|
2018-05-09 10:43:50 +02:00
|
|
|
#include <uint256.h>
|
|
|
|
|
|
2019-02-23 15:25:04 +01:00
|
|
|
#define MEMBIT 0x80000000
|
|
|
|
|
#define MEMMASK 0x7FFFFFFF
|
|
|
|
|
|
2018-09-08 18:57:41 +02:00
|
|
|
namespace {
|
|
|
|
|
inline std::uint32_t createShortHash(uint64_t cheapHash) {
|
|
|
|
|
std::uint32_t answer = static_cast<uint32_t>(cheapHash & 0xFF) << 12;
|
|
|
|
|
answer += (cheapHash & 0xFF00) >> 4;
|
|
|
|
|
answer += (cheapHash & 0xF00000) >> 20;
|
|
|
|
|
return answer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-23 15:25:04 +01:00
|
|
|
struct FlexLockGuard {
|
|
|
|
|
FlexLockGuard(std::recursive_mutex &mutex) : mutex(mutex) { mutex.lock(); }
|
|
|
|
|
inline ~FlexLockGuard() { unlock(); }
|
|
|
|
|
inline void unlock() { if (m_locked) { mutex.unlock(); m_locked = false; } }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
std::recursive_mutex &mutex;
|
|
|
|
|
bool m_locked = true;
|
2018-05-09 10:43:50 +02:00
|
|
|
};
|
|
|
|
|
|
2018-05-10 01:08:45 +02:00
|
|
|
enum ForceBool {
|
|
|
|
|
ForceSave,
|
|
|
|
|
NormalSave
|
|
|
|
|
};
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2019-02-23 15:25:04 +01:00
|
|
|
// used internally in the flush to disk method
|
|
|
|
|
struct SavedBucket {
|
|
|
|
|
SavedBucket(const std::vector<OutputRef> &uo, uint32_t offset, int saveCount) : unspentOutputs(uo), offsetInFile(offset), saveCount(saveCount) {}
|
|
|
|
|
std::vector<OutputRef> unspentOutputs;
|
|
|
|
|
uint32_t offsetInFile;
|
|
|
|
|
int saveCount = 0;
|
2018-05-09 10:43:50 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace UODB {
|
|
|
|
|
enum MessageTags {
|
|
|
|
|
Separator = 0,
|
|
|
|
|
|
|
|
|
|
// tags to store the leaf
|
|
|
|
|
TXID,
|
|
|
|
|
OutIndex,
|
|
|
|
|
BlockHeight,
|
|
|
|
|
OffsetInBlock,
|
|
|
|
|
|
|
|
|
|
// tags to store the bucket
|
|
|
|
|
LeafPosition,
|
|
|
|
|
LeafPosRelToBucket,
|
|
|
|
|
CheapHash,
|
|
|
|
|
|
|
|
|
|
// tags to store the jump-index
|
|
|
|
|
LastBlockId, // uint256
|
|
|
|
|
FirstBlockHeight,
|
|
|
|
|
LastBlockHeight,
|
|
|
|
|
JumpTableHash,
|
2018-09-20 22:59:52 +02:00
|
|
|
PositionInFile,
|
|
|
|
|
|
|
|
|
|
// Additional bucket-positioning tags
|
|
|
|
|
LeafPosOn512MB,
|
2018-12-07 20:59:16 +01:00
|
|
|
LeafPosFromPrevLeaf,
|
2019-04-10 23:59:10 +02:00
|
|
|
LeafPosRepeat,
|
|
|
|
|
|
|
|
|
|
// Additional tags for the jump-index
|
2020-02-22 14:31:02 +01:00
|
|
|
ChangesSincePrune,
|
2018-09-20 22:59:52 +02:00
|
|
|
|
2020-02-22 14:31:02 +01:00
|
|
|
// Is only present and true in the info that is the latest, tip, DB.
|
|
|
|
|
IsTip,
|
2020-02-27 13:35:56 +01:00
|
|
|
|
2020-03-01 21:05:53 +01:00
|
|
|
// Initial size of the buckets section of the DB (just after pruning)
|
|
|
|
|
InitialBucketSegmentSize,
|
|
|
|
|
|
2020-02-27 13:35:56 +01:00
|
|
|
// In the worldvie wof this UTXO a block stored in the 'block-index'
|
|
|
|
|
// that was invalid stores its sha256 blockId here.
|
|
|
|
|
InvalidBlockHash
|
2018-05-09 10:43:50 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class DataFile;
|
|
|
|
|
class DataFileCache {
|
|
|
|
|
public:
|
|
|
|
|
DataFileCache(const boost::filesystem::path &baseFilename);
|
|
|
|
|
|
|
|
|
|
struct InfoFile {
|
|
|
|
|
int index = -1;
|
|
|
|
|
int initialBlockHeight = -1;
|
|
|
|
|
int lastBlockHeight = -1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
InfoFile parseInfoFile(int index) const;
|
2018-09-08 22:50:32 +02:00
|
|
|
boost::filesystem::path filenameFor(int index) const;
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2018-09-16 21:14:42 +02:00
|
|
|
std::string writeInfoFile(DataFile *source);
|
2018-05-09 10:43:50 +02:00
|
|
|
bool load(const InfoFile &info, DataFile *target);
|
|
|
|
|
|
|
|
|
|
std::list<InfoFile> m_validInfoFiles;
|
|
|
|
|
private:
|
|
|
|
|
const boost::filesystem::path m_baseFilename;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// represents one storage-DB file.
|
|
|
|
|
class DataFile {
|
|
|
|
|
/*
|
|
|
|
|
* We start with the below array of 1 million unsigned ints.
|
|
|
|
|
*
|
|
|
|
|
* This starts zero-filled, as new entries come in we insert the offset at the right place.
|
|
|
|
|
* We use the first 2.5 bytes of the prev-txid hash as index in the array.
|
|
|
|
|
*
|
|
|
|
|
* The offset points to a variable-length list.
|
|
|
|
|
* We either use the on-disk datafile to store that list, or we store it in memory (because
|
|
|
|
|
* it has changed and we didn't flush yet).
|
|
|
|
|
* The first bit in the offset decides between those two options. 1 = in-memory.
|
|
|
|
|
* For the on-disk case, we just use the offset in the file.
|
|
|
|
|
* For in-memory we use the lower 31 bits as offset in the bucket-list.
|
|
|
|
|
*
|
|
|
|
|
* Buckets have lists of OutputRefs. The first 64 bits of the prev-txid are used here as a shorthash,
|
|
|
|
|
* we follow up with a leaf-pos which is again a pointer in the file, to UnspentOutput this time, or unsaved
|
2018-10-08 22:29:27 +02:00
|
|
|
* ones in m_leafs.
|
2018-05-09 10:43:50 +02:00
|
|
|
*/
|
|
|
|
|
public:
|
2020-02-27 14:48:54 +01:00
|
|
|
DataFile(const boost::filesystem::path &filename, int beforeHeight = INT_MAX);
|
2020-11-17 20:57:32 +01:00
|
|
|
// This constructor is only used for unit testing.
|
|
|
|
|
DataFile(int startHeight, int endHeight);
|
2020-12-24 14:14:22 +01:00
|
|
|
DataFile(const DataFile &) = delete;
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2018-11-21 14:24:16 +01:00
|
|
|
void insert(const UODBPrivate *priv, const uint256 &txid, int firstOutput, int lastOutput, int blockHeight, int offsetInBlock);
|
2019-03-25 11:58:45 +01:00
|
|
|
void insertAll(const UODBPrivate *priv, const UnspentOutputDatabase::BlockData &data, size_t start, size_t end);
|
2018-05-09 10:43:50 +02:00
|
|
|
UnspentOutput find(const uint256 &txid, int index) const;
|
2018-08-30 20:28:11 +02:00
|
|
|
SpentOutput remove(const UODBPrivate *priv, const uint256 &txid, int index, uint32_t leafHint = 0);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2020-03-01 21:05:53 +01:00
|
|
|
/// checks jumptable fragmentation, returns amount of bytes its larger than after latest prune
|
|
|
|
|
int fragmentationLevel();
|
|
|
|
|
|
2018-07-29 13:55:30 +02:00
|
|
|
// writing to disk. Return if there are still unsaved items left
|
2019-02-23 15:25:04 +01:00
|
|
|
void flushSomeNodesToDisk(ForceBool force);
|
2018-11-18 14:42:14 +01:00
|
|
|
void flushSomeNodesToDisk_callback(); // calls flush repeatedly, used as an asio callback
|
2018-09-16 21:14:42 +02:00
|
|
|
std::string flushAll();
|
2019-02-23 15:25:04 +01:00
|
|
|
int32_t saveLeaf(const UnspentOutput *uo);
|
2018-07-29 13:55:30 +02:00
|
|
|
|
|
|
|
|
// session management.
|
2019-06-15 21:29:27 +02:00
|
|
|
void commit(const UODBPrivate *priv);
|
2018-07-29 13:55:30 +02:00
|
|
|
void rollback();
|
|
|
|
|
|
2018-08-30 20:28:11 +02:00
|
|
|
// update m_changeCount
|
2019-06-15 21:29:27 +02:00
|
|
|
void addChange(int count = 1);
|
2018-08-30 20:28:11 +02:00
|
|
|
|
2018-08-05 19:29:53 +02:00
|
|
|
bool openInfo(int targetHeight);
|
|
|
|
|
|
2020-02-27 22:59:45 +01:00
|
|
|
bool m_needsSave = false;
|
2019-03-23 23:51:39 +01:00
|
|
|
std::atomic_int m_fileFull;
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
// in-memory representation
|
2023-12-21 15:13:56 +01:00
|
|
|
std::shared_ptr<Streaming::BufferPool> m_memBuffers;
|
2018-05-09 10:43:50 +02:00
|
|
|
uint32_t m_jumptables[0x100000];
|
2019-02-23 15:25:04 +01:00
|
|
|
mutable BucketMap m_buckets;
|
|
|
|
|
std::atomic_int m_nextBucketIndex;
|
|
|
|
|
std::atomic_int m_nextLeafIndex;
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
// on-disk file.
|
|
|
|
|
const boost::filesystem::path m_path;
|
|
|
|
|
std::shared_ptr<char> m_buffer;
|
2023-12-21 15:13:56 +01:00
|
|
|
std::shared_ptr<Streaming::BufferPool> m_writeBuffer;
|
2018-05-09 10:43:50 +02:00
|
|
|
boost::iostreams::mapped_file m_file;
|
|
|
|
|
|
2020-02-27 13:35:56 +01:00
|
|
|
// metadata not really part of the UTXO
|
|
|
|
|
std::set<uint256> m_rejectedBlocks;
|
|
|
|
|
|
2018-05-09 10:43:50 +02:00
|
|
|
/// wipes and creates a new datafile
|
2018-07-23 18:05:43 +02:00
|
|
|
static DataFile *createDatafile(const boost::filesystem::path &filename, int firstBlockindex, const uint256 &firstHash);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2018-08-30 20:28:11 +02:00
|
|
|
mutable std::recursive_mutex m_lock, m_saveLock;
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
int m_initialBlockHeight = 0;
|
2018-08-05 19:29:53 +02:00
|
|
|
int m_lastBlockHeight = 0;
|
2018-05-09 10:43:50 +02:00
|
|
|
uint256 m_lastBlockHash;
|
2018-07-23 18:05:43 +02:00
|
|
|
|
|
|
|
|
// Amount of inserts/deletes since last flush
|
2019-06-15 21:29:27 +02:00
|
|
|
std::atomic_int m_changeCountBlock; // changes made that can't be saved yet.
|
2019-06-22 11:02:46 +02:00
|
|
|
std::atomic_int m_changeCount; // changes that are waiting to be saved
|
2023-12-19 23:47:29 +01:00
|
|
|
uint32_t m_changesSinceJumptableWritten = 0;
|
2019-04-10 23:59:10 +02:00
|
|
|
int m_changesSincePrune = 0;
|
2020-03-01 21:05:53 +01:00
|
|
|
int m_initialBucketSize = 0; // the size of the buckets-segment immediately after the last prune.
|
|
|
|
|
boost::posix_time::ptime m_fragmentationCalcTimestamp;
|
2020-02-22 14:31:02 +01:00
|
|
|
bool m_dbIsTip = false;
|
2020-03-01 21:05:53 +01:00
|
|
|
int32_t m_fragmentationLevel = false;
|
2019-02-03 15:36:57 +01:00
|
|
|
std::atomic_bool m_flushScheduled;
|
2018-07-29 13:55:30 +02:00
|
|
|
|
2018-09-11 19:05:17 +02:00
|
|
|
// --- rollback info ---
|
2019-02-23 15:25:04 +01:00
|
|
|
std::list<UnspentOutput*> m_leafsBackup; //< contains leafs deleted and never saved
|
2018-09-16 17:28:47 +02:00
|
|
|
/// contains leaf-ids deleted related to a certain bucketId (so they can be re-added to bucket)
|
2018-09-11 19:05:17 +02:00
|
|
|
std::list<OutputRef> m_leafIdsBackup;
|
|
|
|
|
/// buckets that were in memory when we committed last and have since been modified. We refuse to save them (for now).
|
|
|
|
|
/// all values should have MEMBIT set
|
|
|
|
|
std::set<uint32_t> m_bucketsToNotSave;
|
|
|
|
|
/// buckets that have a good state on disk, have been loaded into memory to add
|
|
|
|
|
/// or remove something and thus the jumptable forgot where on disk the original was.
|
|
|
|
|
/// shorthash -> position on disk
|
|
|
|
|
std::unordered_map<uint32_t, uint32_t> m_committedBucketLocations;
|
|
|
|
|
uint32_t m_lastCommittedBucketIndex = 0;
|
|
|
|
|
uint32_t m_lastCommittedLeafIndex = 0;
|
2018-10-20 21:57:23 +02:00
|
|
|
|
|
|
|
|
mutable std::atomic_int m_usageCount;
|
|
|
|
|
|
|
|
|
|
struct LockGuard {
|
|
|
|
|
LockGuard(const DataFile *parent) : parent(parent) {
|
|
|
|
|
assert(parent);
|
|
|
|
|
++parent->m_usageCount;
|
|
|
|
|
}
|
|
|
|
|
~LockGuard() {
|
|
|
|
|
if (!--parent->m_usageCount)
|
|
|
|
|
delete parent;
|
|
|
|
|
}
|
|
|
|
|
void deleteLater() {
|
|
|
|
|
--parent->m_usageCount;
|
|
|
|
|
}
|
|
|
|
|
const DataFile *parent = nullptr;
|
|
|
|
|
};
|
2020-12-24 14:14:22 +01:00
|
|
|
|
|
|
|
|
DataFile &operator=(const DataFile&o) = delete;
|
2018-05-09 10:43:50 +02:00
|
|
|
};
|
|
|
|
|
|
2018-10-21 13:21:24 +02:00
|
|
|
struct Limits
|
|
|
|
|
{
|
|
|
|
|
uint32_t DBFileSize = 2147483600; // 2GiB
|
2024-04-07 20:18:23 +02:00
|
|
|
/*
|
|
|
|
|
* Be careful with FileFull, it only counts the actual file-size.
|
|
|
|
|
* So if we have a large number of buckets in memory then we need to
|
|
|
|
|
* lower the file-fill limit in order to not go over the 2GB limit that
|
|
|
|
|
* 31 bits gives us.
|
|
|
|
|
*/
|
|
|
|
|
int32_t FileFull = 1600000000; // 1.6GB
|
|
|
|
|
uint32_t AutoFlush = 50000000; // every 50 million inserts/deletes, auto-flush jumptables
|
|
|
|
|
int32_t ChangesToSave = 8000000; // every 8M inserts/deletes, start a save-round.
|
2018-10-21 13:21:24 +02:00
|
|
|
};
|
|
|
|
|
|
2018-05-09 10:43:50 +02:00
|
|
|
class UODBPrivate
|
|
|
|
|
{
|
|
|
|
|
public:
|
2025-02-08 19:05:26 +01:00
|
|
|
UODBPrivate(boost::asio::io_context &context, const boost::filesystem::path &basedir, int beforeHeight = INT_MAX);
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2018-07-23 18:05:43 +02:00
|
|
|
// find existing DataFiles
|
|
|
|
|
void init();
|
|
|
|
|
|
2018-05-09 10:43:50 +02:00
|
|
|
boost::filesystem::path filepathForIndex(int fileIndex);
|
2019-03-23 23:51:39 +01:00
|
|
|
DataFile *checkCapacity();
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2025-02-08 19:05:26 +01:00
|
|
|
boost::asio::io_context& ioContext;
|
2018-05-09 10:43:50 +02:00
|
|
|
|
2018-07-23 18:05:43 +02:00
|
|
|
bool memOnly = false; //< if true, we never flush to disk.
|
2018-09-16 21:14:42 +02:00
|
|
|
bool doPrune = false;
|
2018-05-09 10:43:50 +02:00
|
|
|
|
|
|
|
|
const boost::filesystem::path basedir;
|
|
|
|
|
|
2019-03-05 09:05:15 +01:00
|
|
|
DataFileList dataFiles;
|
2018-10-21 13:21:24 +02:00
|
|
|
|
|
|
|
|
static Limits limits;
|
2018-05-09 10:43:50 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#endif
|