2017-09-03 19:46:57 +02:00
|
|
|
/*
|
2017-11-09 19:34:51 +01:00
|
|
|
* This file is part of the Flowee project
|
2017-09-03 19:46:57 +02:00
|
|
|
* Copyright (c) 2009-2010 Satoshi Nakamoto
|
2020-02-27 13:35:56 +01:00
|
|
|
* Copyright (c) 2017-2020 Tom Zander <tomz@freedommail.ch>
|
2017-09-03 19:46:57 +02:00
|
|
|
* Copyright (c) 2017 Calin Culianu <calin.culianu@gmail.com>
|
|
|
|
|
*
|
|
|
|
|
* 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/>.
|
|
|
|
|
*/
|
2017-02-14 11:17:46 +01:00
|
|
|
|
|
|
|
|
#include "BlocksDB.h"
|
2017-04-03 15:30:03 +02:00
|
|
|
#include "BlocksDB_p.h"
|
2017-02-14 10:54:48 +01:00
|
|
|
#include "chainparams.h"
|
2017-02-21 13:44:48 +01:00
|
|
|
#include "Application.h"
|
|
|
|
|
#include "init.h" // for StartShutdown
|
2017-09-05 16:04:44 +02:00
|
|
|
#include "hash.h"
|
2019-08-24 13:20:37 +02:00
|
|
|
#include "util.h"
|
2018-01-15 15:26:12 +00:00
|
|
|
#include <validation/Engine.h>
|
2017-02-14 11:17:46 +01:00
|
|
|
|
|
|
|
|
#include "chain.h"
|
2018-09-25 16:24:05 +02:00
|
|
|
#include "scheduler.h"
|
2017-02-14 11:17:46 +01:00
|
|
|
#include "main.h"
|
|
|
|
|
#include "uint256.h"
|
2018-02-12 14:15:24 +01:00
|
|
|
#include <SettingsDefaults.h>
|
|
|
|
|
#include <primitives/FastBlock.h>
|
2017-02-14 11:17:46 +01:00
|
|
|
#include <boost/thread.hpp>
|
2017-02-14 10:54:48 +01:00
|
|
|
#include <boost/filesystem.hpp>
|
2017-09-03 21:17:27 +02:00
|
|
|
#include <boost/filesystem/fstream.hpp>
|
2020-02-27 13:35:56 +01:00
|
|
|
#include <utxo/UnspentOutputDatabase.h>
|
2017-02-14 11:17:46 +01:00
|
|
|
|
|
|
|
|
static const char DB_BLOCK_FILES = 'f';
|
|
|
|
|
static const char DB_TXINDEX = 't';
|
|
|
|
|
static const char DB_BLOCK_INDEX = 'b';
|
|
|
|
|
|
|
|
|
|
static const char DB_FLAG = 'F';
|
|
|
|
|
static const char DB_REINDEX_FLAG = 'R';
|
|
|
|
|
static const char DB_LAST_BLOCK = 'l';
|
|
|
|
|
|
|
|
|
|
namespace {
|
2020-02-27 13:35:56 +01:00
|
|
|
CBlockIndex * insertBlockIndex(const uint256 &hash)
|
2017-02-14 11:17:46 +01:00
|
|
|
{
|
|
|
|
|
if (hash.IsNull())
|
2018-01-15 15:26:12 +00:00
|
|
|
return nullptr;
|
2017-02-14 11:17:46 +01:00
|
|
|
|
|
|
|
|
// Return existing
|
2018-01-15 15:26:12 +00:00
|
|
|
auto answer = Blocks::Index::get(hash);
|
|
|
|
|
if (answer)
|
|
|
|
|
return answer;
|
2017-02-14 11:17:46 +01:00
|
|
|
|
|
|
|
|
// Create new
|
2020-02-27 13:35:56 +01:00
|
|
|
CBlockIndex *pindexNew = new CBlockIndex();
|
2018-01-15 15:26:12 +00:00
|
|
|
pindexNew->phashBlock = Blocks::Index::insert(hash, pindexNew);
|
2017-02-14 11:17:46 +01:00
|
|
|
return pindexNew;
|
|
|
|
|
}
|
2017-02-21 13:44:48 +01:00
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
bool LoadExternalBlockFile(const CDiskBlockPos &pos)
|
2017-02-21 13:44:48 +01:00
|
|
|
{
|
2018-01-15 15:26:12 +00:00
|
|
|
static_assert(MESSAGE_START_SIZE == 4, "We assume 4");
|
2017-02-21 13:44:48 +01:00
|
|
|
int64_t nStart = GetTimeMillis();
|
|
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
Streaming::ConstBuffer dataFile = Blocks::DB::instance()->loadBlockFile(pos.nFile);
|
|
|
|
|
if (!dataFile.isValid()) {
|
|
|
|
|
logWarning(Log::DB) << "LoadExternalBlockFile: Unable to open file" << pos.nFile;
|
2017-02-21 13:44:48 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
2018-01-15 15:26:12 +00:00
|
|
|
|
|
|
|
|
CBlockFileInfo info;
|
|
|
|
|
|
|
|
|
|
auto validation = Application::instance()->validation();
|
2018-06-12 19:26:26 +02:00
|
|
|
const int blockHeaderMessage = *reinterpret_cast<const int*>(Params().MessageStart());
|
2018-01-15 15:26:12 +00:00
|
|
|
const char *buf = dataFile.begin();
|
2018-08-04 22:54:12 +02:00
|
|
|
while (buf < dataFile.end() && !Application::closingDown()) {
|
2018-09-18 16:07:25 +02:00
|
|
|
buf = reinterpret_cast<const char*>(memchr(buf, blockHeaderMessage, dataFile.end() - buf));
|
2018-01-15 15:26:12 +00:00
|
|
|
if (buf == nullptr) {
|
|
|
|
|
// no valid block header found; don't complain
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
buf += 4;
|
2018-06-12 19:26:26 +02:00
|
|
|
uint32_t blockSize = le32toh(*(reinterpret_cast<const std::uint32_t*>(buf)));
|
2018-01-15 15:26:12 +00:00
|
|
|
if (blockSize < 80)
|
|
|
|
|
continue;
|
|
|
|
|
buf += 4;
|
|
|
|
|
|
|
|
|
|
validation->waitForSpace();
|
2018-06-12 19:26:26 +02:00
|
|
|
validation->addBlock(CDiskBlockPos(pos.nFile, static_cast<std::uint32_t>(buf - dataFile.begin())));
|
2018-01-15 15:26:12 +00:00
|
|
|
++info.nBlocks;
|
|
|
|
|
buf += blockSize;
|
2018-06-12 19:26:26 +02:00
|
|
|
info.nSize = static_cast<std::uint32_t>(buf - dataFile.begin());
|
2018-01-15 15:26:12 +00:00
|
|
|
}
|
|
|
|
|
if (info.nBlocks > 0) {
|
|
|
|
|
logCritical(Log::DB) << "Loaded" << info.nBlocks << "blocks from external file" << pos.nFile << "in" << (GetTimeMillis() - nStart) << "ms";
|
|
|
|
|
Blocks::DB::instance()->priv()->foundBlockFile(pos.nFile, info);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2017-02-21 13:44:48 +01:00
|
|
|
}
|
|
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
void reimportBlockFiles()
|
2017-02-21 13:44:48 +01:00
|
|
|
{
|
|
|
|
|
const CChainParams& chainparams = Params();
|
2018-08-31 18:22:55 +02:00
|
|
|
RenameThread("flowee-loadblk");
|
2018-01-15 15:26:12 +00:00
|
|
|
if (Blocks::DB::instance()->reindexing() == Blocks::ScanningFiles) {
|
2017-02-21 13:44:48 +01:00
|
|
|
int nFile = 0;
|
2018-08-05 20:56:34 +02:00
|
|
|
for (size_t indexedFiles = 0; indexedFiles < vinfoBlockFile.size(); ++indexedFiles) {
|
|
|
|
|
if (vinfoBlockFile[indexedFiles].nBlocks <= 0)
|
|
|
|
|
break;
|
|
|
|
|
nFile = indexedFiles;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
while (true) {
|
|
|
|
|
if (!LoadExternalBlockFile(CDiskBlockPos(nFile, 0)))
|
|
|
|
|
break;
|
|
|
|
|
if (Application::closingDown())
|
|
|
|
|
return;
|
2017-02-21 13:44:48 +01:00
|
|
|
nFile++;
|
|
|
|
|
}
|
2018-01-15 15:26:12 +00:00
|
|
|
Blocks::DB::instance()->setReindexing(Blocks::ParsingBlocks);
|
2017-02-21 13:44:48 +01:00
|
|
|
}
|
2018-01-15 15:26:12 +00:00
|
|
|
Application::instance()->validation()->waitValidationFinished();
|
2018-08-31 18:22:55 +02:00
|
|
|
if (!Application::closingDown()) // waitValidationFinished may not have finished then
|
|
|
|
|
Blocks::DB::instance()->setReindexing(Blocks::NoReindex);
|
2018-01-15 15:26:12 +00:00
|
|
|
FlushStateToDisk();
|
|
|
|
|
logCritical(Log::Bitcoin) << "Reindexing finished";
|
|
|
|
|
// To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked):
|
|
|
|
|
InitBlockIndex(chainparams);
|
2017-02-21 13:44:48 +01:00
|
|
|
|
2018-02-12 14:15:24 +01:00
|
|
|
if (GetBoolArg("-stopafterblockimport", Settings::DefaultStopAfterBlockImport)) {
|
2018-01-15 15:26:12 +00:00
|
|
|
logCritical(Log::Bitcoin) << "Stopping after block import";
|
2017-02-21 13:44:48 +01:00
|
|
|
StartShutdown();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 11:17:46 +01:00
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
Blocks::DB* Blocks::DB::s_instance = nullptr;
|
|
|
|
|
|
|
|
|
|
Blocks::DB *Blocks::DB::instance()
|
2017-02-14 11:17:46 +01:00
|
|
|
{
|
2017-02-27 17:22:39 +01:00
|
|
|
return Blocks::DB::s_instance;
|
2017-02-14 11:17:46 +01:00
|
|
|
}
|
|
|
|
|
|
2018-09-25 16:24:05 +02:00
|
|
|
void Blocks::DB::createInstance(size_t nCacheSize, bool fWipe, CScheduler *scheduler)
|
2017-02-14 11:17:46 +01:00
|
|
|
{
|
2017-02-27 17:22:39 +01:00
|
|
|
delete Blocks::DB::s_instance;
|
|
|
|
|
Blocks::DB::s_instance = new Blocks::DB(nCacheSize, false, fWipe);
|
2018-09-25 16:24:05 +02:00
|
|
|
if (scheduler)
|
|
|
|
|
Blocks::DB::s_instance->priv()->setScheduler(scheduler);
|
2017-02-14 11:17:46 +01:00
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
void Blocks::DB::createTestInstance(size_t nCacheSize)
|
|
|
|
|
{
|
|
|
|
|
delete Blocks::DB::s_instance;
|
|
|
|
|
Blocks::DB::s_instance = new Blocks::DB(nCacheSize, true);
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-15 13:42:40 +01:00
|
|
|
void Blocks::DB::shutdown()
|
|
|
|
|
{
|
|
|
|
|
delete s_instance;
|
2018-06-12 19:26:26 +02:00
|
|
|
s_instance = nullptr;
|
2017-12-15 13:42:40 +01:00
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
void Blocks::DB::startBlockImporter()
|
2017-02-21 13:44:48 +01:00
|
|
|
{
|
2018-01-15 15:26:12 +00:00
|
|
|
if (s_instance->reindexing() != NoReindex)
|
|
|
|
|
Application::createThread(std::bind(&reimportBlockFiles));
|
2017-02-21 13:44:48 +01:00
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
Blocks::DB::DB(size_t nCacheSize, bool fMemory, bool fWipe)
|
2017-04-03 15:30:03 +02:00
|
|
|
: CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe),
|
|
|
|
|
d(new DBPrivate())
|
2017-02-21 13:44:48 +01:00
|
|
|
{
|
2018-01-15 15:26:12 +00:00
|
|
|
int state;
|
|
|
|
|
bool exists = Read(DB_REINDEX_FLAG, state);
|
|
|
|
|
if (exists) {
|
|
|
|
|
if (state == 1)
|
|
|
|
|
d->reindexing = Blocks::ScanningFiles;
|
|
|
|
|
else
|
|
|
|
|
d->reindexing = Blocks::ParsingBlocks;
|
|
|
|
|
} else {
|
|
|
|
|
d->reindexing = Blocks::NoReindex;
|
|
|
|
|
}
|
2017-08-01 12:14:54 +02:00
|
|
|
loadConfig();
|
2017-04-03 15:30:03 +02:00
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
bool Blocks::DB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) {
|
2017-02-14 11:17:46 +01:00
|
|
|
return Read(std::make_pair(DB_BLOCK_FILES, nFile), info);
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
bool Blocks::DB::ReadLastBlockFile(int &nFile) {
|
2017-02-14 11:17:46 +01:00
|
|
|
return Read(DB_LAST_BLOCK, nFile);
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
bool Blocks::DB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo) {
|
2018-11-02 20:42:45 +01:00
|
|
|
CDBBatch batch;
|
2017-02-14 11:17:46 +01:00
|
|
|
for (std::vector<std::pair<int, const CBlockFileInfo*> >::const_iterator it=fileInfo.begin(); it != fileInfo.end(); it++) {
|
|
|
|
|
batch.Write(std::make_pair(DB_BLOCK_FILES, it->first), *it->second);
|
|
|
|
|
}
|
|
|
|
|
batch.Write(DB_LAST_BLOCK, nLastFile);
|
|
|
|
|
for (std::vector<const CBlockIndex*>::const_iterator it=blockinfo.begin(); it != blockinfo.end(); it++) {
|
|
|
|
|
batch.Write(std::make_pair(DB_BLOCK_INDEX, (*it)->GetBlockHash()), CDiskBlockIndex(*it));
|
|
|
|
|
}
|
|
|
|
|
return WriteBatch(batch, true);
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
bool Blocks::DB::ReadTxIndex(const uint256 &txid, CDiskTxPos &pos) {
|
2017-02-14 11:17:46 +01:00
|
|
|
return Read(std::make_pair(DB_TXINDEX, txid), pos);
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
bool Blocks::DB::WriteTxIndex(const std::vector<std::pair<uint256, CDiskTxPos> >&vect) {
|
2018-11-02 20:42:45 +01:00
|
|
|
CDBBatch batch;
|
2017-02-14 11:17:46 +01:00
|
|
|
for (std::vector<std::pair<uint256,CDiskTxPos> >::const_iterator it=vect.begin(); it!=vect.end(); it++)
|
|
|
|
|
batch.Write(std::make_pair(DB_TXINDEX, it->first), it->second);
|
|
|
|
|
return WriteBatch(batch);
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
bool Blocks::DB::WriteFlag(const std::string &name, bool fValue) {
|
2017-02-14 11:17:46 +01:00
|
|
|
return Write(std::make_pair(DB_FLAG, name), fValue ? '1' : '0');
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-27 17:22:39 +01:00
|
|
|
bool Blocks::DB::ReadFlag(const std::string &name, bool &fValue) {
|
2017-02-14 11:17:46 +01:00
|
|
|
char ch;
|
|
|
|
|
if (!Read(std::make_pair(DB_FLAG, name), ch))
|
|
|
|
|
return false;
|
|
|
|
|
fValue = ch == '1';
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-27 13:35:56 +01:00
|
|
|
bool Blocks::DB::CacheAllBlockInfos(const UnspentOutputDatabase *utxo)
|
2017-02-14 11:17:46 +01:00
|
|
|
{
|
|
|
|
|
boost::scoped_ptr<CDBIterator> pcursor(NewIterator());
|
|
|
|
|
|
|
|
|
|
pcursor->Seek(std::make_pair(DB_BLOCK_INDEX, uint256()));
|
2017-04-03 15:30:03 +02:00
|
|
|
int maxFile = 0;
|
2017-02-14 11:17:46 +01:00
|
|
|
|
|
|
|
|
while (pcursor->Valid()) {
|
|
|
|
|
boost::this_thread::interruption_point();
|
|
|
|
|
std::pair<char, uint256> key;
|
|
|
|
|
if (pcursor->GetKey(key) && key.first == DB_BLOCK_INDEX) {
|
|
|
|
|
CDiskBlockIndex diskindex;
|
|
|
|
|
if (pcursor->GetValue(diskindex)) {
|
|
|
|
|
// Construct block index object
|
2020-02-27 13:35:56 +01:00
|
|
|
CBlockIndex* pindexNew = insertBlockIndex(diskindex.GetBlockHash());
|
|
|
|
|
pindexNew->pprev = insertBlockIndex(diskindex.hashPrev);
|
2017-02-14 11:17:46 +01:00
|
|
|
pindexNew->nHeight = diskindex.nHeight;
|
|
|
|
|
pindexNew->nFile = diskindex.nFile;
|
2017-04-03 15:30:03 +02:00
|
|
|
maxFile = std::max(pindexNew->nFile, maxFile);
|
2017-02-14 11:17:46 +01:00
|
|
|
pindexNew->nDataPos = diskindex.nDataPos;
|
|
|
|
|
pindexNew->nUndoPos = diskindex.nUndoPos;
|
|
|
|
|
pindexNew->nVersion = diskindex.nVersion;
|
|
|
|
|
pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;
|
|
|
|
|
pindexNew->nTime = diskindex.nTime;
|
|
|
|
|
pindexNew->nBits = diskindex.nBits;
|
|
|
|
|
pindexNew->nNonce = diskindex.nNonce;
|
|
|
|
|
pindexNew->nTx = diskindex.nTx;
|
|
|
|
|
|
2020-02-27 13:35:56 +01:00
|
|
|
// status is not saved, it comes from the UTXO-state
|
|
|
|
|
if (utxo->blockIdHasFailed(pindexNew->GetBlockHash()))
|
|
|
|
|
pindexNew->nStatus = BLOCK_FAILED_VALID;
|
|
|
|
|
else if (pindexNew->nHeight > 0)
|
|
|
|
|
pindexNew->nStatus = BLOCK_VALID_TREE; // needed to actually download blocks.
|
|
|
|
|
if (pindexNew->nDataPos)
|
|
|
|
|
pindexNew->nStatus |= BLOCK_HAVE_DATA;
|
|
|
|
|
if (pindexNew->nUndoPos)
|
|
|
|
|
pindexNew->nStatus |= BLOCK_HAVE_UNDO;
|
2017-02-14 11:17:46 +01:00
|
|
|
pcursor->Next();
|
|
|
|
|
} else {
|
2017-04-03 15:30:03 +02:00
|
|
|
return error("CacheAllBlockInfos(): failed to read row");
|
2017-02-14 11:17:46 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-12 19:26:26 +02:00
|
|
|
d->datafiles.resize(static_cast<size_t>(maxFile));
|
|
|
|
|
d->revertDatafiles.resize(static_cast<size_t>(maxFile));
|
2017-02-14 11:17:46 +01:00
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
std::lock_guard<std::mutex> lock_(d->blockIndexLock);
|
|
|
|
|
for (auto iter = d->indexMap.begin(); iter != d->indexMap.end(); ++iter) {
|
2017-05-05 23:32:39 +02:00
|
|
|
iter->second->BuildSkip();
|
|
|
|
|
}
|
2018-01-15 15:26:12 +00:00
|
|
|
for (auto iter = d->indexMap.begin(); iter != d->indexMap.end(); ++iter) {
|
2017-09-04 19:48:19 +02:00
|
|
|
appendHeader(iter->second);
|
|
|
|
|
}
|
2017-04-03 15:30:03 +02:00
|
|
|
|
2018-11-18 11:57:07 +01:00
|
|
|
for (CBlockIndex *tip : d->headerChainTips) {
|
|
|
|
|
CBlockIndex *block = tip;
|
|
|
|
|
// find oldest block that didn't calculate nChainwork yet
|
|
|
|
|
while (block->nHeight > 0 && block->nChainWork.GetLow64() == 0L) {
|
|
|
|
|
block = block->pprev;
|
|
|
|
|
}
|
|
|
|
|
while (block != tip) { // calculate nChainwork from block to tip
|
|
|
|
|
block= tip->GetAncestor(block->nHeight + 1);
|
2018-11-21 12:49:18 +01:00
|
|
|
block->nChainWork = block->pprev->nChainWork + GetBlockProof(*block);
|
2018-11-18 11:57:07 +01:00
|
|
|
}
|
|
|
|
|
if (d->headersChain.Tip()->nChainWork < tip->nChainWork) {
|
|
|
|
|
d->headersChain.SetTip(tip);
|
|
|
|
|
pindexBestHeader = tip;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-27 13:35:56 +01:00
|
|
|
|
|
|
|
|
// Calculate nChainWork and nChainTx
|
|
|
|
|
std::vector<std::pair<int, CBlockIndex*> > vSortedByHeight = d->allByHeight();
|
|
|
|
|
for (const PAIRTYPE(int, CBlockIndex*) &item : vSortedByHeight) {
|
|
|
|
|
CBlockIndex* pindex = item.second;
|
|
|
|
|
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex);
|
|
|
|
|
pindex->nChainTx = pindex->nTx + (pindex->pprev ? pindex->pprev->nChainTx : 0);
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-21 12:49:18 +01:00
|
|
|
#ifndef NDEBUG
|
|
|
|
|
for (CBlockIndex *tip : d->headerChainTips) {
|
|
|
|
|
bool isMain = tip == d->headersChain.Tip();
|
|
|
|
|
logInfo(Log::DB) << "Chain-tips:" << tip->nHeight << tip->GetBlockHash() << ArithToUint256(tip->nChainWork)
|
|
|
|
|
<< (isMain ? "main" : "");
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2018-11-18 11:57:07 +01:00
|
|
|
|
2017-02-14 11:17:46 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
2017-02-14 10:54:48 +01:00
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
Blocks::ReindexingState Blocks::DB::reindexing() const
|
2017-04-03 15:30:03 +02:00
|
|
|
{
|
2018-01-15 15:26:12 +00:00
|
|
|
return d->reindexing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Blocks::DB::setReindexing(Blocks::ReindexingState state)
|
|
|
|
|
{
|
|
|
|
|
if (d->reindexing == state)
|
|
|
|
|
return;
|
|
|
|
|
d->reindexing = state;
|
|
|
|
|
switch (state) {
|
|
|
|
|
case Blocks::NoReindex:
|
|
|
|
|
Erase(DB_REINDEX_FLAG);
|
|
|
|
|
break;
|
|
|
|
|
case Blocks::ScanningFiles:
|
|
|
|
|
Write(DB_REINDEX_FLAG, 1);
|
|
|
|
|
break;
|
|
|
|
|
case Blocks::ParsingBlocks:
|
|
|
|
|
Write(DB_REINDEX_FLAG, 2);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-04-03 15:30:03 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-03 19:46:57 +02:00
|
|
|
static FILE* OpenDiskFile(const CDiskBlockPos &pos, const char *prefix, bool fReadOnly)
|
|
|
|
|
{
|
|
|
|
|
if (pos.IsNull())
|
2018-06-12 19:26:26 +02:00
|
|
|
return nullptr;
|
2017-09-12 19:41:36 +02:00
|
|
|
boost::filesystem::path path = Blocks::getFilepathForIndex(pos.nFile, prefix, true);
|
2017-09-03 19:46:57 +02:00
|
|
|
boost::filesystem::create_directories(path.parent_path());
|
|
|
|
|
FILE* file = fopen(path.string().c_str(), "rb+");
|
|
|
|
|
if (!file && !fReadOnly)
|
|
|
|
|
file = fopen(path.string().c_str(), "wb+");
|
|
|
|
|
if (!file) {
|
|
|
|
|
LogPrintf("Unable to open file %s\n", path.string());
|
2018-06-12 19:26:26 +02:00
|
|
|
return nullptr;
|
2017-09-03 19:46:57 +02:00
|
|
|
}
|
|
|
|
|
if (pos.nPos) {
|
|
|
|
|
if (fseek(file, pos.nPos, SEEK_SET)) {
|
|
|
|
|
LogPrintf("Unable to seek to position %u of %s\n", pos.nPos, path.string());
|
|
|
|
|
fclose(file);
|
2018-06-12 19:26:26 +02:00
|
|
|
return nullptr;
|
2017-09-03 19:46:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return file;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FILE* Blocks::openFile(const CDiskBlockPos &pos, bool fReadOnly) {
|
|
|
|
|
return OpenDiskFile(pos, "blk", fReadOnly);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FILE* Blocks::openUndoFile(const CDiskBlockPos &pos, bool fReadOnly) {
|
|
|
|
|
return OpenDiskFile(pos, "rev", fReadOnly);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boost::filesystem::path Blocks::getFilepathForIndex(int fileIndex, const char *prefix, bool fFindHarder)
|
|
|
|
|
{
|
|
|
|
|
auto path = GetDataDir() / "blocks" / strprintf("%s%05u.dat", prefix, fileIndex);
|
|
|
|
|
if (fFindHarder && !boost::filesystem::exists(path)) {
|
2017-12-15 13:42:40 +01:00
|
|
|
std::shared_ptr<DBPrivate> d = Blocks::DB::instance()->priv();
|
2017-09-03 19:46:57 +02:00
|
|
|
for (const std::string &dir : d->blocksDataDirs) {
|
|
|
|
|
boost::filesystem::path alternatePath(dir);
|
|
|
|
|
alternatePath = alternatePath / "blocks" / strprintf("%s%05u.dat", prefix, fileIndex);
|
|
|
|
|
if (boost::filesystem::exists(alternatePath))
|
|
|
|
|
return alternatePath;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-05 13:44:41 +02:00
|
|
|
FastBlock Blocks::DB::loadBlock(CDiskBlockPos pos)
|
2017-09-03 19:46:57 +02:00
|
|
|
{
|
2018-07-23 18:05:43 +02:00
|
|
|
return FastBlock(d->loadBlock(pos, ForwardBlock));
|
2017-09-03 19:46:57 +02:00
|
|
|
}
|
|
|
|
|
|
2018-07-23 18:05:43 +02:00
|
|
|
FastUndoBlock Blocks::DB::loadUndoBlock(CDiskBlockPos pos)
|
2017-09-05 13:44:41 +02:00
|
|
|
{
|
2018-07-23 18:05:43 +02:00
|
|
|
return FastUndoBlock(d->loadBlock(pos, RevertBlock));
|
2017-09-05 13:44:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Streaming::ConstBuffer Blocks::DB::loadBlockFile(int fileIndex)
|
2017-09-03 19:46:57 +02:00
|
|
|
{
|
2018-01-15 15:26:12 +00:00
|
|
|
size_t fileSize;
|
|
|
|
|
auto buf = d->mapFile(fileIndex, ForwardBlock, &fileSize);
|
|
|
|
|
if (buf.get() == nullptr)
|
|
|
|
|
return Streaming::ConstBuffer(); // got pruned
|
|
|
|
|
return Streaming::ConstBuffer(buf, buf.get(), buf.get() + fileSize - 1);
|
2017-09-03 19:46:57 +02:00
|
|
|
}
|
|
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
FastBlock Blocks::DB::writeBlock(const FastBlock &block, CDiskBlockPos &pos)
|
2017-09-03 21:17:27 +02:00
|
|
|
{
|
|
|
|
|
assert(block.isFullBlock());
|
2018-07-23 18:05:43 +02:00
|
|
|
std::deque<Streaming::ConstBuffer> tmp { block.data() };
|
|
|
|
|
return FastBlock(d->writeBlock(tmp, pos, ForwardBlock));
|
2017-09-05 13:44:41 +02:00
|
|
|
}
|
2017-09-03 21:17:27 +02:00
|
|
|
|
2018-07-23 18:05:43 +02:00
|
|
|
void Blocks::DB::writeUndoBlock(const UndoBlockBuilder &undoBlock, int fileIndex, uint32_t *posInFile)
|
2017-09-05 13:44:41 +02:00
|
|
|
{
|
2018-07-23 18:05:43 +02:00
|
|
|
assert(undoBlock.finish().size() > 0);
|
2017-09-05 13:44:41 +02:00
|
|
|
CDiskBlockPos pos(fileIndex, 0);
|
2018-07-23 18:05:43 +02:00
|
|
|
d->writeBlock(undoBlock.finish(), pos, RevertBlock);
|
2017-09-05 13:44:41 +02:00
|
|
|
if (posInFile)
|
|
|
|
|
*posInFile = pos.nPos;
|
2017-09-03 21:17:27 +02:00
|
|
|
}
|
|
|
|
|
|
2017-04-03 15:30:03 +02:00
|
|
|
bool Blocks::DB::appendHeader(CBlockIndex *block)
|
|
|
|
|
{
|
|
|
|
|
assert(block);
|
|
|
|
|
assert(block->phashBlock);
|
|
|
|
|
bool found = false;
|
|
|
|
|
const bool valid = (block->nStatus & BLOCK_FAILED_MASK) == 0;
|
|
|
|
|
assert(valid || block->pprev); // can't mark the genesis as invalid.
|
2017-09-04 19:40:52 +02:00
|
|
|
if (valid && d->headersChain.Contains(block)) // nothing to do.
|
|
|
|
|
return false;
|
2018-01-15 15:26:12 +00:00
|
|
|
CBlockIndex *validPrev = valid ? block : block->pprev;
|
|
|
|
|
while (validPrev->nStatus & BLOCK_FAILED_MASK) {
|
2020-02-27 13:35:56 +01:00
|
|
|
if (validPrev->pskip && (validPrev->pskip->nStatus & BLOCK_FAILED_MASK))
|
|
|
|
|
validPrev = validPrev->pskip;
|
|
|
|
|
else
|
|
|
|
|
validPrev = validPrev->pprev;
|
2018-01-15 15:26:12 +00:00
|
|
|
}
|
2018-10-21 20:38:34 +02:00
|
|
|
// try to simply append
|
2017-04-03 15:30:03 +02:00
|
|
|
for (auto i = d->headerChainTips.begin(); i != d->headerChainTips.end(); ++i) {
|
|
|
|
|
CBlockIndex *tip = *i;
|
2017-09-04 19:40:52 +02:00
|
|
|
CBlockIndex *parent = block->GetAncestor(tip->nHeight);
|
2018-10-21 20:38:34 +02:00
|
|
|
if (parent == tip) { // chain-tip is my ancestor
|
2017-04-03 15:30:03 +02:00
|
|
|
d->headerChainTips.erase(i);
|
2018-01-15 15:26:12 +00:00
|
|
|
d->headerChainTips.push_back(validPrev);
|
2018-10-21 20:38:34 +02:00
|
|
|
if (tip == d->headersChain.Tip()) { // main chain
|
2018-01-15 15:26:12 +00:00
|
|
|
d->headersChain.SetTip(validPrev);
|
|
|
|
|
pindexBestHeader = validPrev;
|
2017-04-03 15:30:03 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
bool modifyingMainChain = false;
|
2019-11-25 23:23:00 +01:00
|
|
|
if (!found) { // we could not extend an existing, find if part of existing header-chain
|
2018-01-15 15:26:12 +00:00
|
|
|
bool modified = false;
|
2018-10-21 20:38:34 +02:00
|
|
|
bool alreadyContains = false; // true if a secondairy chain already contains our new validPrev
|
2018-01-15 15:26:12 +00:00
|
|
|
auto i = d->headerChainTips.begin();
|
|
|
|
|
while (i != d->headerChainTips.end()) {
|
2017-04-03 15:30:03 +02:00
|
|
|
if ((*i)->GetAncestor(block->nHeight) == block) { // known in this chain.
|
|
|
|
|
if (valid)
|
|
|
|
|
return false;
|
2018-01-15 15:26:12 +00:00
|
|
|
modified = true;
|
|
|
|
|
const bool mainChain = d->headersChain.Contains(*i);
|
|
|
|
|
// it is invalid, remove it (and all children).
|
|
|
|
|
i = d->headerChainTips.erase(i);
|
|
|
|
|
if (mainChain)
|
|
|
|
|
d->headersChain.SetTip(validPrev);
|
|
|
|
|
modifyingMainChain |= mainChain;
|
|
|
|
|
} else {
|
|
|
|
|
if ((*i)->GetAncestor(validPrev->nHeight) == validPrev) {
|
|
|
|
|
// the new best argument is already present on another chain, this means
|
|
|
|
|
// an entire chain will end up being removed. Lets check if we need to
|
|
|
|
|
// switch main-chain.
|
|
|
|
|
alreadyContains = true;
|
|
|
|
|
if (validPrev->nChainWork < (*i)->nChainWork)
|
|
|
|
|
validPrev = *i;
|
|
|
|
|
}
|
|
|
|
|
++i;
|
2017-04-03 15:30:03 +02:00
|
|
|
}
|
|
|
|
|
}
|
2018-01-15 15:26:12 +00:00
|
|
|
if (modified && !alreadyContains) // at least one chain was removed, then add back the correct tip
|
|
|
|
|
d->headerChainTips.push_back(validPrev);
|
2017-04-03 15:30:03 +02:00
|
|
|
if (valid) {
|
|
|
|
|
d->headerChainTips.push_back(block);
|
|
|
|
|
if (d->headersChain.Height() == -1) { // add genesis
|
|
|
|
|
d->headersChain.SetTip(block);
|
|
|
|
|
pindexBestHeader = block;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-15 15:26:12 +00:00
|
|
|
assert(d->headersChain.Tip());
|
|
|
|
|
assert(validPrev);
|
2018-10-21 20:38:34 +02:00
|
|
|
for (auto tip : d->headerChainTips) { // find the longest chain
|
|
|
|
|
if (d->headersChain.Tip()->nChainWork < tip->nChainWork) {
|
|
|
|
|
// we changed what is to be considered the main-chain. Update the CChain instance.
|
|
|
|
|
d->headersChain.SetTip(tip);
|
|
|
|
|
pindexBestHeader = tip;
|
|
|
|
|
modifyingMainChain = true;
|
|
|
|
|
}
|
2017-04-03 15:30:03 +02:00
|
|
|
}
|
2018-01-15 15:26:12 +00:00
|
|
|
return modifyingMainChain;
|
2017-04-03 15:30:03 +02:00
|
|
|
}
|
|
|
|
|
|
2017-07-23 12:38:17 +02:00
|
|
|
bool Blocks::DB::appendBlock(CBlockIndex *block, int lastBlockFile)
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::pair<int, const CBlockFileInfo*> > files;
|
|
|
|
|
std::vector<const CBlockIndex*> blocks;
|
|
|
|
|
blocks.push_back(block);
|
|
|
|
|
return WriteBatchSync(files, lastBlockFile, blocks);
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-03 15:30:03 +02:00
|
|
|
const CChain &Blocks::DB::headerChain()
|
|
|
|
|
{
|
|
|
|
|
return d->headersChain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::list<CBlockIndex *> &Blocks::DB::headerChainTips()
|
|
|
|
|
{
|
|
|
|
|
return d->headerChainTips;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-01 12:14:54 +02:00
|
|
|
void Blocks::DB::loadConfig()
|
|
|
|
|
{
|
|
|
|
|
d->blocksDataDirs.clear();
|
|
|
|
|
|
|
|
|
|
for (auto dir : mapMultiArgs["-blockdatadir"]) {
|
|
|
|
|
if (boost::filesystem::is_directory(boost::filesystem::path(dir) / "blocks")) {
|
|
|
|
|
d->blocksDataDirs.push_back(dir);
|
|
|
|
|
} else {
|
|
|
|
|
logCritical(4000) << "invalid blockdatadir passed. No 'blocks' subdir found, skipping:"<< dir;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-03 15:30:03 +02:00
|
|
|
///////////////////////////////////////////////
|
|
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
bool Blocks::Index::empty()
|
|
|
|
|
{
|
|
|
|
|
auto priv = Blocks::DB::instance()->priv();
|
|
|
|
|
std::lock_guard<std::mutex> lock_(priv->blockIndexLock);
|
|
|
|
|
return priv->indexMap.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const uint256 *Blocks::Index::insert(const uint256 &hash, CBlockIndex *index)
|
|
|
|
|
{
|
|
|
|
|
assert(index);
|
|
|
|
|
auto priv = Blocks::DB::instance()->priv();
|
|
|
|
|
std::lock_guard<std::mutex> lock_(priv->blockIndexLock);
|
|
|
|
|
auto iterator = priv->indexMap.insert(std::make_pair(hash, index)).first;
|
2018-02-20 13:52:47 +01:00
|
|
|
return &iterator->first;
|
2018-01-15 15:26:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Blocks::Index::exists(const uint256 &hash)
|
|
|
|
|
{
|
|
|
|
|
auto priv = Blocks::DB::instance()->priv();
|
|
|
|
|
std::lock_guard<std::mutex> lock_(priv->blockIndexLock);
|
|
|
|
|
auto mi = priv->indexMap.find(hash);
|
|
|
|
|
return mi != priv->indexMap.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CBlockIndex *Blocks::Index::get(const uint256 &hash)
|
|
|
|
|
{
|
|
|
|
|
auto priv = Blocks::DB::instance()->priv();
|
|
|
|
|
std::lock_guard<std::mutex> lock_(priv->blockIndexLock);
|
|
|
|
|
auto mi = priv->indexMap.find(hash);
|
|
|
|
|
if (mi == priv->indexMap.end())
|
|
|
|
|
return nullptr;
|
|
|
|
|
return mi->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int Blocks::Index::size()
|
|
|
|
|
{
|
|
|
|
|
auto priv = Blocks::DB::instance()->priv();
|
|
|
|
|
std::lock_guard<std::mutex> lock_(priv->blockIndexLock);
|
2018-06-12 19:26:26 +02:00
|
|
|
return static_cast<int>(priv->indexMap.size());
|
2018-01-15 15:26:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Blocks::Index::reconsiderBlock(CBlockIndex *pindex) {
|
|
|
|
|
auto priv = Blocks::DB::instance()->priv();
|
|
|
|
|
std::lock_guard<std::mutex> lock_(priv->blockIndexLock);
|
|
|
|
|
|
|
|
|
|
int nHeight = pindex->nHeight;
|
|
|
|
|
|
|
|
|
|
// Remove the invalidity flag from this block and all its descendants.
|
|
|
|
|
auto it = priv->indexMap.begin();
|
|
|
|
|
while (it != priv->indexMap.end()) {
|
|
|
|
|
if (!it->second->IsValid() && it->second->GetAncestor(nHeight) == pindex) {
|
2018-06-12 19:26:26 +02:00
|
|
|
it->second->nStatus &= static_cast<uint32_t>(~BLOCK_FAILED_MASK);
|
2018-01-29 17:35:19 +00:00
|
|
|
MarkIndexUnsaved(it->second);
|
2018-01-15 15:26:12 +00:00
|
|
|
}
|
|
|
|
|
it++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove the invalidity flag from all ancestors too.
|
2018-06-12 19:26:26 +02:00
|
|
|
while (pindex != nullptr) {
|
2018-01-15 15:26:12 +00:00
|
|
|
if (pindex->nStatus & BLOCK_FAILED_MASK) {
|
2018-06-12 19:26:26 +02:00
|
|
|
pindex->nStatus &= static_cast<uint32_t>(~BLOCK_FAILED_MASK);
|
2018-01-29 17:35:19 +00:00
|
|
|
MarkIndexUnsaved(pindex);
|
2018-01-15 15:26:12 +00:00
|
|
|
}
|
|
|
|
|
pindex = pindex->pprev;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::set<int> Blocks::Index::fileIndexes()
|
|
|
|
|
{
|
|
|
|
|
auto priv = Blocks::DB::instance()->priv();
|
|
|
|
|
std::lock_guard<std::mutex> lock_(priv->blockIndexLock);
|
|
|
|
|
|
|
|
|
|
std::set<int> setBlkDataFiles;
|
2018-12-30 15:33:11 +01:00
|
|
|
for (const PAIRTYPE(uint256, CBlockIndex*)& item : priv->indexMap) {
|
2018-01-15 15:26:12 +00:00
|
|
|
CBlockIndex* pindex = item.second;
|
|
|
|
|
if (pindex->nStatus & BLOCK_HAVE_DATA) {
|
|
|
|
|
setBlkDataFiles.insert(pindex->nFile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return setBlkDataFiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Blocks::Index::unload()
|
|
|
|
|
{
|
|
|
|
|
auto instance = Blocks::DB::instance();
|
|
|
|
|
if (instance == nullptr)
|
|
|
|
|
return;
|
|
|
|
|
instance->priv()->unloadIndexMap();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-27 13:35:56 +01:00
|
|
|
std::vector<std::pair<int, CBlockIndex *> > Blocks::DBPrivate::allByHeight() const
|
2018-01-15 15:26:12 +00:00
|
|
|
{
|
|
|
|
|
std::vector<std::pair<int, CBlockIndex*> > vSortedByHeight;
|
2020-02-27 13:35:56 +01:00
|
|
|
vSortedByHeight.reserve(indexMap.size());
|
|
|
|
|
for (const PAIRTYPE(uint256, CBlockIndex*)& item : indexMap)
|
2018-01-15 15:26:12 +00:00
|
|
|
{
|
|
|
|
|
CBlockIndex* pindex = item.second;
|
|
|
|
|
vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex));
|
|
|
|
|
}
|
|
|
|
|
std::sort(vSortedByHeight.begin(), vSortedByHeight.end());
|
|
|
|
|
return vSortedByHeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////
|
|
|
|
|
|
2017-04-03 15:30:03 +02:00
|
|
|
Blocks::DBPrivate::DBPrivate()
|
|
|
|
|
{
|
|
|
|
|
}
|
2017-09-03 19:46:57 +02:00
|
|
|
|
|
|
|
|
Blocks::DBPrivate::~DBPrivate()
|
|
|
|
|
{
|
2018-01-15 15:26:12 +00:00
|
|
|
unloadIndexMap();
|
2017-12-15 13:42:40 +01:00
|
|
|
// this class is mostly lock-free, which means that this destructor can be called well before
|
|
|
|
|
// all the users of the datafiles are deleted.
|
|
|
|
|
// The design is that the objects stored in the datafils/revertDatafiles will be deleted when the last
|
|
|
|
|
// users stop using them. So don't delete them here, it would cause issues.
|
2018-01-15 15:26:12 +00:00
|
|
|
std::lock_guard<std::recursive_mutex> lock_(lock);
|
2017-12-15 13:42:40 +01:00
|
|
|
datafiles.clear();
|
|
|
|
|
revertDatafiles.clear();
|
2018-01-15 15:26:12 +00:00
|
|
|
fileHistory.clear();
|
2017-09-03 19:46:57 +02:00
|
|
|
}
|
|
|
|
|
|
2018-07-23 18:05:43 +02:00
|
|
|
Streaming::ConstBuffer Blocks::DBPrivate::loadBlock(CDiskBlockPos pos, BlockType type)
|
2017-09-03 19:46:57 +02:00
|
|
|
{
|
2019-02-18 18:27:50 +01:00
|
|
|
if (pos.nFile == -1)
|
|
|
|
|
throw std::runtime_error("Invalid BlockPos, does the block have data?");
|
2017-09-05 13:44:41 +02:00
|
|
|
if (pos.nPos < 4)
|
|
|
|
|
throw std::runtime_error("Blocks::loadBlock got Database corruption");
|
|
|
|
|
size_t fileSize;
|
|
|
|
|
auto buf = mapFile(pos.nFile, type, &fileSize);
|
|
|
|
|
if (buf.get() == nullptr)
|
|
|
|
|
throw std::runtime_error("Failed to memmap block");
|
|
|
|
|
if (pos.nPos >= fileSize)
|
|
|
|
|
throw std::runtime_error("position outside of file");
|
2018-06-12 19:26:26 +02:00
|
|
|
uint32_t blockSize = le32toh(*(reinterpret_cast<const std::uint32_t*>(buf.get() + pos.nPos - 4)));
|
2018-07-23 18:05:43 +02:00
|
|
|
if (pos.nPos + blockSize > fileSize)
|
2017-09-05 13:44:41 +02:00
|
|
|
throw std::runtime_error("block sized bigger than file");
|
|
|
|
|
return Streaming::ConstBuffer(buf, buf.get() + pos.nPos, buf.get() + pos.nPos + blockSize);
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-23 18:05:43 +02:00
|
|
|
Streaming::ConstBuffer Blocks::DBPrivate::writeBlock(const std::deque<Streaming::ConstBuffer> &blocks, CDiskBlockPos &pos, BlockType type)
|
2017-09-05 13:44:41 +02:00
|
|
|
{
|
2018-07-23 18:05:43 +02:00
|
|
|
int blockSize = 0;
|
|
|
|
|
for (auto b : blocks) blockSize += b.size();
|
2018-06-12 19:26:26 +02:00
|
|
|
assert(blockSize < static_cast<int>(MAX_BLOCKFILE_SIZE - 8));
|
|
|
|
|
assert(blockSize >= 0);
|
2017-09-05 13:44:41 +02:00
|
|
|
LOCK(cs_LastBlockFile);
|
|
|
|
|
|
|
|
|
|
bool newFile = false;
|
|
|
|
|
const bool useBlk = type == ForwardBlock;
|
2018-06-12 19:26:26 +02:00
|
|
|
assert(nLastBlockFile >= 0);
|
|
|
|
|
if (static_cast<int>(vinfoBlockFile.size()) <= nLastBlockFile) { // first file.
|
2017-09-05 13:44:41 +02:00
|
|
|
newFile = true;
|
2018-06-12 19:26:26 +02:00
|
|
|
vinfoBlockFile.resize(static_cast<size_t>(nLastBlockFile) + 1);
|
|
|
|
|
} else if (useBlk && vinfoBlockFile[static_cast<size_t>(nLastBlockFile)].nSize
|
|
|
|
|
+ static_cast<std::uint32_t>(blockSize) + 8 > MAX_BLOCKFILE_SIZE) {
|
2017-09-05 13:44:41 +02:00
|
|
|
// previous file full.
|
|
|
|
|
newFile = true;
|
2018-06-12 19:26:26 +02:00
|
|
|
vinfoBlockFile.resize(static_cast<size_t>(++nLastBlockFile) + 1);
|
2018-01-15 15:26:12 +00:00
|
|
|
} else if (!useBlk && nLastBlockFile < pos.nFile) { // Want new revert file to be created
|
|
|
|
|
// We can get our nLastBlockFile out of sync in a resync where the revert files are written
|
|
|
|
|
// without there having been blk files written first.
|
|
|
|
|
newFile = true;
|
|
|
|
|
nLastBlockFile = std::max(nLastBlockFile + 1, pos.nFile);
|
2018-06-12 19:26:26 +02:00
|
|
|
vinfoBlockFile.resize(static_cast<size_t>(nLastBlockFile) + 1);
|
2017-09-05 13:44:41 +02:00
|
|
|
}
|
2020-03-01 22:03:43 +01:00
|
|
|
if (useBlk) { // Lets check if our blk file is writable, and if not create a new one.
|
|
|
|
|
// to make deployment faster the user may have used a symlink or read-only copy of the blocks.
|
|
|
|
|
// lets make sure that we don't fail due to that file being RO
|
|
|
|
|
const auto path = getFilepathForIndex(nLastBlockFile, "blk");
|
|
|
|
|
if (boost::filesystem::is_symlink(path)) {
|
|
|
|
|
newFile = true;
|
|
|
|
|
vinfoBlockFile.resize(static_cast<size_t>(++nLastBlockFile) + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-05 13:44:41 +02:00
|
|
|
if (useBlk) // revert files get to tell us which file they want to be in
|
|
|
|
|
pos.nFile = nLastBlockFile;
|
2018-01-15 15:26:12 +00:00
|
|
|
assert(pos.nFile <= nLastBlockFile);
|
2018-06-12 19:26:26 +02:00
|
|
|
assert(static_cast<int>(vinfoBlockFile.size()) > pos.nFile);
|
|
|
|
|
assert(pos.nFile >= 0);
|
|
|
|
|
CBlockFileInfo &info = vinfoBlockFile[static_cast<size_t>(pos.nFile)];
|
2018-01-15 15:26:12 +00:00
|
|
|
if (newFile || (!useBlk && info.nUndoSize == 0)) { // create new file on disk
|
2017-09-05 13:44:41 +02:00
|
|
|
const auto path = getFilepathForIndex(pos.nFile, useBlk ? "blk" : "rev");
|
|
|
|
|
logDebug(Log::DB) << "Starting new file" << path.string();
|
2018-01-15 15:26:12 +00:00
|
|
|
std::lock_guard<std::recursive_mutex> lock_(lock);
|
2017-09-05 13:44:41 +02:00
|
|
|
boost::filesystem::ofstream file(path);
|
|
|
|
|
file.close();
|
2018-10-05 20:49:44 +02:00
|
|
|
boost::filesystem::resize_file(path, static_cast<size_t>(MAX_BLOCKFILE_SIZE));
|
2017-09-05 13:44:41 +02:00
|
|
|
}
|
|
|
|
|
size_t fileSize;
|
2018-01-15 15:26:12 +00:00
|
|
|
bool writable;
|
|
|
|
|
auto buf = mapFile(pos.nFile, type, &fileSize, &writable);
|
|
|
|
|
if (buf.get() == nullptr) {
|
|
|
|
|
logFatal(Log::DB).nospace() << "Wanting to write to DB file " << (newFile ? "(new)":"") << "blk0..." << pos.nFile << ".dat failed, could not open";
|
2017-09-05 13:44:41 +02:00
|
|
|
throw std::runtime_error("Failed to open file");
|
2018-01-15 15:26:12 +00:00
|
|
|
}
|
|
|
|
|
if (!writable) {
|
|
|
|
|
logFatal(Log::DB).nospace() << "Wanting to write to DB file blk0..." << pos.nFile << ".dat failed, file read-only";
|
|
|
|
|
throw std::runtime_error("File is not writable");
|
|
|
|
|
}
|
2017-09-05 13:44:41 +02:00
|
|
|
uint32_t *posInFile = useBlk ? &info.nSize : &info.nUndoSize;
|
|
|
|
|
pos.nPos = *posInFile + 8;
|
|
|
|
|
char *data = buf.get() + *posInFile;
|
|
|
|
|
memcpy(data, Params().MessageStart(), 4);
|
|
|
|
|
data += 4;
|
|
|
|
|
uint32_t networkSize = htole32(blockSize);
|
|
|
|
|
memcpy(data, &networkSize, 4);
|
|
|
|
|
data += 4;
|
2018-07-23 18:05:43 +02:00
|
|
|
char *rawBlockData = data;
|
|
|
|
|
for (auto block : blocks) {
|
|
|
|
|
memcpy(data, block.begin(), static_cast<size_t>(block.size()));
|
|
|
|
|
data += block.size();
|
2017-09-05 16:04:44 +02:00
|
|
|
}
|
2018-07-23 18:05:43 +02:00
|
|
|
if (type == ForwardBlock)
|
|
|
|
|
info.AddBlock();
|
2018-06-12 19:26:26 +02:00
|
|
|
*posInFile += static_cast<size_t>(blockSize) + 8;
|
2017-09-05 13:44:41 +02:00
|
|
|
setDirtyFileInfo.insert(pos.nFile);
|
2018-07-23 18:05:43 +02:00
|
|
|
return Streaming::ConstBuffer(buf, rawBlockData, rawBlockData + blockSize);
|
2017-09-05 13:44:41 +02:00
|
|
|
}
|
|
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
void Blocks::DBPrivate::unloadIndexMap()
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::mutex> lock_(blockIndexLock);
|
|
|
|
|
|
|
|
|
|
for (auto entry : indexMap) {
|
|
|
|
|
delete entry.second;
|
|
|
|
|
}
|
|
|
|
|
indexMap.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Blocks::DBPrivate::foundBlockFile(int index, const CBlockFileInfo &info)
|
|
|
|
|
{
|
|
|
|
|
LOCK(cs_LastBlockFile);
|
|
|
|
|
if (nLastBlockFile < index)
|
|
|
|
|
nLastBlockFile = index;
|
2018-06-12 19:26:26 +02:00
|
|
|
if (static_cast<int>(vinfoBlockFile.size()) <= nLastBlockFile)
|
|
|
|
|
vinfoBlockFile.resize(static_cast<size_t>(nLastBlockFile) + 1);
|
2018-01-15 15:26:12 +00:00
|
|
|
// copy all but the undosize since that may have been assigned already.
|
2018-06-12 19:26:26 +02:00
|
|
|
vinfoBlockFile[static_cast<size_t>(index)].nBlocks = info.nBlocks;
|
|
|
|
|
vinfoBlockFile[static_cast<size_t>(index)].nSize = info.nSize;
|
2018-08-06 13:57:09 +02:00
|
|
|
setDirtyFileInfo.insert(index);
|
2018-01-15 15:26:12 +00:00
|
|
|
logCritical(Log::DB) << "Registring block file info" << index << info.nBlocks << "blocks with a total of" << info.nSize << "bytes";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<char> Blocks::DBPrivate::mapFile(int fileIndex, Blocks::BlockType type, size_t *size_out, bool *isWritable)
|
2017-09-05 13:44:41 +02:00
|
|
|
{
|
|
|
|
|
const bool useBlk = type == ForwardBlock;
|
2017-09-03 19:46:57 +02:00
|
|
|
std::vector<DataFile*> &list = useBlk ? datafiles : revertDatafiles;
|
|
|
|
|
const char *prefix = useBlk ? "blk" : "rev";
|
|
|
|
|
|
2018-01-15 15:26:12 +00:00
|
|
|
std::lock_guard<std::recursive_mutex> lock_(lock);
|
2018-06-12 19:26:26 +02:00
|
|
|
if (static_cast<int>(list.size()) <= fileIndex)
|
2019-02-15 13:31:49 +01:00
|
|
|
list.resize(static_cast<size_t>(fileIndex) + 1);
|
2018-06-12 19:26:26 +02:00
|
|
|
DataFile *df = list.at(static_cast<size_t>(fileIndex));
|
2017-09-03 19:46:57 +02:00
|
|
|
if (df == nullptr) {
|
|
|
|
|
df = new DataFile();
|
2018-06-12 19:26:26 +02:00
|
|
|
list[static_cast<size_t>(fileIndex)] = df;
|
2017-09-03 19:46:57 +02:00
|
|
|
}
|
|
|
|
|
std::shared_ptr<char> buf = df->buffer.lock();
|
|
|
|
|
if (buf.get() == nullptr) {
|
2017-09-12 19:41:36 +02:00
|
|
|
auto path = getFilepathForIndex(fileIndex, prefix, true);
|
2020-02-27 13:42:01 +01:00
|
|
|
constexpr auto modeRO = std::ios_base::binary | std::ios_base::in;
|
|
|
|
|
constexpr auto modeRW = modeRO | std::ios_base::out;
|
2018-01-15 15:26:12 +00:00
|
|
|
try { // auto open read-write when last block, or any revert file.
|
2020-02-27 13:42:01 +01:00
|
|
|
auto mode = modeRW;
|
|
|
|
|
if (useBlk // blk files may be opened RO
|
|
|
|
|
&& fileIndex != nLastBlockFile // all historical ones are RO
|
|
|
|
|
&& reindexing != ScanningFiles) // unless we are reindexing, because any could be last
|
|
|
|
|
mode = modeRO;
|
|
|
|
|
df->file.open(path, mode);
|
2018-01-15 15:26:12 +00:00
|
|
|
} catch (...) {
|
|
|
|
|
// try to open again, read-only now.
|
|
|
|
|
// the user may have moved the files to a read-only medium.
|
2020-02-27 13:42:01 +01:00
|
|
|
try { df->file.open(path, modeRO); } catch (...) {} // avoid throwing here.
|
2018-01-15 15:26:12 +00:00
|
|
|
}
|
2017-09-03 19:46:57 +02:00
|
|
|
if (df->file.is_open()) {
|
2017-12-15 13:42:40 +01:00
|
|
|
auto weakThis = std::weak_ptr<DBPrivate>(shared_from_this());
|
2018-06-12 19:26:26 +02:00
|
|
|
auto cleanupLambda = [useBlk,fileIndex,df,weakThis] (char *) {
|
2017-12-15 13:42:40 +01:00
|
|
|
std::shared_ptr<DBPrivate> d = weakThis.lock();
|
|
|
|
|
if (d) { // mutex scope...
|
2018-01-15 15:26:12 +00:00
|
|
|
std::lock_guard<std::recursive_mutex> lockG(d->lock);
|
2017-12-15 13:42:40 +01:00
|
|
|
std::vector<DataFile*> &list = useBlk ? d->datafiles : d->revertDatafiles;
|
2018-06-12 19:26:26 +02:00
|
|
|
assert(fileIndex >= 0 && fileIndex < static_cast<int>(list.size()));
|
|
|
|
|
if (df == list.at(static_cast<size_t>(fileIndex))) {
|
2017-09-03 19:46:57 +02:00
|
|
|
// invalidate entry -- note that it's possible
|
2017-09-07 22:57:43 +02:00
|
|
|
// df != list[fileIndex] if we resized the file
|
2018-06-12 19:26:26 +02:00
|
|
|
list[static_cast<size_t>(fileIndex)] = nullptr;
|
2017-12-15 13:42:40 +01:00
|
|
|
}
|
2017-09-03 19:46:57 +02:00
|
|
|
}
|
|
|
|
|
// no need to hold lock on delete -- auto-closes mmap'd file.
|
|
|
|
|
delete df;
|
|
|
|
|
};
|
2017-09-03 21:17:27 +02:00
|
|
|
buf = std::shared_ptr<char>(const_cast<char*>(df->file.const_data()), cleanupLambda);
|
2017-09-03 19:46:57 +02:00
|
|
|
df->buffer = std::weak_ptr<char>(buf);
|
|
|
|
|
df->filesize = df->file.size();
|
|
|
|
|
} else {
|
2017-09-05 13:44:41 +02:00
|
|
|
logCritical(Log::DB) << "Blocks::DB: failed to memmap data-file" << path.string();
|
2018-06-12 19:26:26 +02:00
|
|
|
list[static_cast<size_t>(fileIndex)] = nullptr;
|
2018-01-15 15:26:12 +00:00
|
|
|
delete df;
|
|
|
|
|
if (size_out) *size_out = 0;
|
|
|
|
|
return std::shared_ptr<char>();
|
2017-09-03 19:46:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
2018-09-25 16:24:05 +02:00
|
|
|
bool found = false;
|
|
|
|
|
for (auto iter = fileHistory.begin(); iter != fileHistory.end(); ++iter) {
|
|
|
|
|
if (iter->dataFile.get() == buf.get()) {
|
|
|
|
|
iter->lastAccessed = GetTime();
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!found)
|
|
|
|
|
fileHistory.push_back(FileHistoryEntry(buf, GetTime()));
|
|
|
|
|
|
2017-09-03 19:46:57 +02:00
|
|
|
if (size_out) *size_out = df->filesize;
|
2018-01-15 15:26:12 +00:00
|
|
|
if (isWritable)
|
|
|
|
|
*isWritable = df->file.flags() == boost::iostreams::mapped_file::readwrite;
|
2017-09-03 19:46:57 +02:00
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-25 16:24:05 +02:00
|
|
|
void Blocks::DBPrivate::setScheduler(CScheduler *scheduler)
|
|
|
|
|
{
|
2018-10-05 20:27:42 +02:00
|
|
|
scheduler->scheduleEvery(std::bind(&Blocks::DBPrivate::closeFiles, this), 10);
|
2019-02-15 13:31:49 +01:00
|
|
|
scheduler->scheduleEvery(std::bind(&Blocks::DBPrivate::pruneFiles, this), 15*60);
|
2018-09-25 16:24:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Blocks::DBPrivate::closeFiles()
|
|
|
|
|
{
|
2019-02-04 14:05:09 +01:00
|
|
|
size_t before, after;
|
|
|
|
|
std::vector<FileHistoryEntry> oldEntries; // delay unmapping the files until after the mutex unlock
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock_(lock);
|
|
|
|
|
before = fileHistory.size();
|
|
|
|
|
const int64_t timeOut = GetTime() - (before < 100 ? 15 : 7); // amount of seconds to keep files open
|
|
|
|
|
for (auto iter = fileHistory.begin(); iter != fileHistory.end();) {
|
|
|
|
|
if (iter->lastAccessed < timeOut) {
|
|
|
|
|
oldEntries.push_back(*iter);
|
|
|
|
|
iter = fileHistory.erase(iter);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-09-25 16:24:05 +02:00
|
|
|
++iter;
|
2019-02-04 14:05:09 +01:00
|
|
|
}
|
|
|
|
|
after = fileHistory.size();
|
2018-09-25 16:24:05 +02:00
|
|
|
}
|
2019-02-04 14:05:09 +01:00
|
|
|
if (before != after)
|
|
|
|
|
logInfo(Log::DB).nospace() << "Close block files unmapped " << (before - after) << "/" << before << " files";
|
2018-09-25 16:24:05 +02:00
|
|
|
}
|
2019-02-15 13:31:49 +01:00
|
|
|
|
|
|
|
|
// remove old revert files and snip off the zero's from the blk files (which makes them larger when copied)
|
|
|
|
|
void Blocks::DBPrivate::pruneFiles()
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock_(lock);
|
|
|
|
|
std::map<int, boost::filesystem::path> existingRevertFiles;
|
|
|
|
|
for (int i = static_cast<int>(revertDatafiles.size()) - 1; i >= 0 ; --i) {
|
|
|
|
|
boost::filesystem::path path = Blocks::getFilepathForIndex(i, "rev", false);
|
|
|
|
|
if (boost::filesystem::exists(path))
|
|
|
|
|
existingRevertFiles.insert(std::make_pair(i, path));
|
|
|
|
|
else if (!existingRevertFiles.empty())
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
while (existingRevertFiles.size() > 3) {
|
|
|
|
|
auto iter = existingRevertFiles.begin();
|
|
|
|
|
auto path = iter->second;
|
|
|
|
|
logInfo(Log::DB) << "Deleting no longer useful revert file" << path.string();
|
|
|
|
|
boost::filesystem::remove(path);
|
|
|
|
|
existingRevertFiles.erase(iter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reindexing == ScanningFiles || datafiles.size() < 3)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
size_t i = datafiles.size() - 3;
|
|
|
|
|
do {
|
|
|
|
|
boost::filesystem::path path = Blocks::getFilepathForIndex((int) i, "blk", false); // only 'local' files
|
|
|
|
|
if (!boost::filesystem::exists(path))
|
|
|
|
|
continue;
|
2020-03-01 18:27:28 +01:00
|
|
|
if (!boost::filesystem::is_symlink(path))
|
|
|
|
|
continue;
|
2019-02-15 13:31:49 +01:00
|
|
|
|
|
|
|
|
DataFile *df = datafiles.at(i);
|
|
|
|
|
if (df) { // been opened before.
|
|
|
|
|
if (df->filesize != MAX_BLOCKFILE_SIZE)
|
|
|
|
|
break;
|
|
|
|
|
if (datafiles.at(i)->file.is_open())
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
else { // not been opened yet
|
|
|
|
|
size_t size;
|
|
|
|
|
mapFile((int) i, Blocks::ForwardBlock, &size);
|
|
|
|
|
if (size != MAX_BLOCKFILE_SIZE)
|
|
|
|
|
break;
|
|
|
|
|
df = datafiles.at(i);
|
|
|
|
|
}
|
|
|
|
|
assert(df);
|
|
|
|
|
|
|
|
|
|
std::list<FileHistoryEntry>::iterator iter = fileHistory.begin();
|
|
|
|
|
{
|
|
|
|
|
Streaming::ConstBuffer dataFile = Blocks::DB::instance()->loadBlockFile(i);
|
|
|
|
|
assert(dataFile.isValid());
|
|
|
|
|
while (iter != fileHistory.end()) {
|
|
|
|
|
if (iter->dataFile == dataFile.internal_buffer())
|
|
|
|
|
break;
|
|
|
|
|
++iter;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const uint32_t fileSize = vinfoBlockFile[i].nSize;
|
|
|
|
|
if (fileSize > 0) {
|
|
|
|
|
logInfo(Log::DB) << "truncating blk file " << i << "to content-size:" << fileSize;
|
|
|
|
|
if (iter != fileHistory.end())
|
|
|
|
|
fileHistory.erase(iter);
|
|
|
|
|
const boost::filesystem::path path = Blocks::getFilepathForIndex((int) i, "blk", false);
|
2020-03-01 18:27:28 +01:00
|
|
|
try {
|
|
|
|
|
boost::filesystem::resize_file(path, fileSize);
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
logInfo(Log::DB) << "Cleanup: Tried and failed to shrink blk file" << path.string();
|
|
|
|
|
}
|
2019-02-15 13:31:49 +01:00
|
|
|
}
|
|
|
|
|
} while (i-- > 0);
|
|
|
|
|
}
|
2019-05-26 13:27:16 +02:00
|
|
|
|
|
|
|
|
CBlockIndex *Blocks::Index::lastCommonAncestor(CBlockIndex *pa, CBlockIndex *pb)
|
|
|
|
|
{
|
|
|
|
|
if (pa->nHeight > pb->nHeight) {
|
|
|
|
|
pa = pa->GetAncestor(pb->nHeight);
|
|
|
|
|
} else if (pb->nHeight > pa->nHeight) {
|
|
|
|
|
pb = pb->GetAncestor(pa->nHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (pa != pb && pa && pb) {
|
|
|
|
|
pa = pa->pprev;
|
|
|
|
|
pb = pb->pprev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Eventually all chain branches meet at the genesis block.
|
|
|
|
|
assert(pa == pb);
|
|
|
|
|
return pa;
|
|
|
|
|
}
|