diff --git a/CMakeLists.txt b/CMakeLists.txt index bd3710375d41..7155f1b72962 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,6 +176,7 @@ file(GLOB CONSENSUS_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/consensus/*.h) file(GLOB CTAES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/ctaes/*.h) file(GLOB ZRUST_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/rust/include/*.h) file(GLOB SAPLING_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/sapling/*.h) +file(GLOB EVO_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/evo/*.h) source_group("BitcoinHeaders" FILES ${HEADERS} @@ -189,6 +190,7 @@ source_group("BitcoinHeaders" FILES ${CTAES_HEADERS} ${ZRUST_HEADERS} ${SAPLING_HEADERS} + ${EVO_HEADERS} ./src/support/cleanse.h ) @@ -383,6 +385,7 @@ set(COMMON_SOURCES ./src/coins.cpp ./src/key_io.cpp ./src/compressor.cpp + ./src/evo/evodb.cpp ./src/evo/specialtx.cpp ./src/consensus/merkle.cpp ./src/consensus/zerocoin_verify.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 59e2e67dc249..94dd80a92109 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -181,6 +181,7 @@ BITCOIN_CORE_H = \ cuckoocache.h \ crypter.h \ cyclingvector.h \ + evo/evodb.h \ evo/specialtx.h \ pairresult.h \ addressbook.h \ @@ -322,6 +323,7 @@ libbitcoin_server_a_SOURCES = \ consensus/params.cpp \ consensus/tx_verify.cpp \ consensus/zerocoin_verify.cpp \ + evo/evodb.cpp \ evo/specialtx.cpp \ httprpc.cpp \ httpserver.cpp \ diff --git a/src/budget/budgetproposal.cpp b/src/budget/budgetproposal.cpp index f04aac7dae4e..4c35466bb119 100644 --- a/src/budget/budgetproposal.cpp +++ b/src/budget/budgetproposal.cpp @@ -58,7 +58,7 @@ bool CBudgetProposal::ParseBroadcast(CDataStream& broadcast) broadcast >> nBlockStart; broadcast >> nBlockEnd; broadcast >> nAmount; - broadcast >> *(CScriptBase*)(&address); + broadcast >> address; broadcast >> nFeeTXHash; } catch (std::exception& e) { return error("Unable to deserialize proposal broadcast: %s", e.what()); @@ -338,7 +338,7 @@ CDataStream CBudgetProposal::GetBroadcast() const broadcast << nBlockStart; broadcast << nBlockEnd; broadcast << nAmount; - broadcast << *(CScriptBase*)(&address); + broadcast << address; broadcast << nFeeTXHash; return broadcast; } diff --git a/src/budget/budgetproposal.h b/src/budget/budgetproposal.h index d5f64d3bd470..9b889d78ef41 100644 --- a/src/budget/budgetproposal.h +++ b/src/budget/budgetproposal.h @@ -115,7 +115,7 @@ class CBudgetProposal READWRITE(nBlockStart); READWRITE(nBlockEnd); READWRITE(nAmount); - READWRITE(*(CScriptBase*)(&address)); + READWRITE(address); READWRITE(nFeeTXHash); READWRITE(nTime); READWRITE(mapVotes); diff --git a/src/budget/finalizedbudget.h b/src/budget/finalizedbudget.h index 08cc04c1fe2c..cc2725286b54 100644 --- a/src/budget/finalizedbudget.h +++ b/src/budget/finalizedbudget.h @@ -161,7 +161,7 @@ class CTxBudgetPayment template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(*(CScriptBase*)(&payee)); + READWRITE(payee); READWRITE(nAmount); READWRITE(nProposalHash); } diff --git a/src/dbwrapper.h b/src/dbwrapper.h index 4700d3b453be..3889651a9d71 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -12,6 +12,7 @@ #include "util.h" #include "version.h" +#include #include #include @@ -68,10 +69,16 @@ class CDBBatch template void Write(const K& key, const V& value) { - CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; - leveldb::Slice slKey(ssKey.data(), ssKey.size()); + Write(ssKey, value); + ssKey.clear(); + } + + template + void Write(const CDataStream& _ssKey, const V& value) + { + leveldb::Slice slKey(_ssKey.data(), _ssKey.size()); CDataStream ssValue(SER_DISK, CLIENT_VERSION); ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE); @@ -88,17 +95,21 @@ class CDBBatch // - byte[]: value // The formula below assumes the key and value are both less than 16k. size_estimate += 3 + (slKey.size() > 127) + slKey.size() + (slValue.size() > 127) + slValue.size(); - ssKey.clear(); ssValue.clear(); } template void Erase(const K& key) { - CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; - leveldb::Slice slKey(ssKey.data(), ssKey.size()); + Erase(ssKey); + ssKey.clear(); + } + + void Erase(const CDataStream& _ssKey) + { + leveldb::Slice slKey(_ssKey.data(), _ssKey.size()); batch.Delete(slKey); @@ -108,7 +119,6 @@ class CDBBatch // - byte[]: key // The formula below assumes the key is less than 16kB. size_estimate += 2 + (slKey.size() > 127) + slKey.size(); - ssKey.clear(); } size_t SizeEstimate() const { return size_estimate; } @@ -130,20 +140,26 @@ class CDBIterator void SeekToFirst(); - template void Seek(const K& key) { + template void Seek(const K& key) + { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; + Seek(ssKey); + } + + void Seek(const CDataStream& ssKey) + { leveldb::Slice slKey(ssKey.data(), ssKey.size()); piter->Seek(slKey); } void Next(); - template bool GetKey(K& key) { - leveldb::Slice slKey = piter->key(); + template bool GetKey(K& key) + { try { - CDataStream ssKey(slKey.data(), slKey.data() + slKey.size(), SER_DISK, CLIENT_VERSION); + CDataStream ssKey = GetKey(); ssKey >> key; } catch(const std::exception& e) { return false; @@ -151,11 +167,19 @@ class CDBIterator return true; } - unsigned int GetKeySize() { + CDataStream GetKey() + { + leveldb::Slice slKey = piter->key(); + return CDataStream(slKey.data(), slKey.data() + slKey.size(), SER_DISK, CLIENT_VERSION); + } + + unsigned int GetKeySize() + { return piter->key().size(); } - template bool GetValue(V& value) { + template bool GetValue(V& value) + { leveldb::Slice slValue = piter->value(); try { CDataStream ssValue(slValue.data(), slValue.data() + slValue.size(), SER_DISK, CLIENT_VERSION); @@ -166,7 +190,8 @@ class CDBIterator return true; } - unsigned int GetValueSize() { + unsigned int GetValueSize() + { return piter->value().size(); } @@ -206,12 +231,17 @@ class CDBWrapper CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false); ~CDBWrapper(); - template - bool Read(const K& key, V& value) const + template + bool ReadDataStream(const K& key, CDataStream& ssValue) const { CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; + return ReadDataStream(ssKey, ssValue); + } + + bool ReadDataStream(const CDataStream& ssKey, CDataStream& ssValue) const + { leveldb::Slice slKey(ssKey.data(), ssKey.size()); std::string strValue; @@ -222,8 +252,28 @@ class CDBWrapper LogPrintf("LevelDB read failure: %s\n", status.ToString()); dbwrapper_private::HandleError(status); } + CDataStream ssValueTmp(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION); + ssValue = std::move(ssValueTmp); + return true; + } + + template + bool Read(const K& key, V& value) const + { + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); + ssKey << key; + return Read(ssKey, value); + } + + template + bool Read(const CDataStream& ssKey, V& value) const + { + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + if (!ReadDataStream(ssKey, ssValue)) { + return false; + } try { - CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION); ssValue >> value; } catch (const std::exception&) { return false; @@ -245,7 +295,12 @@ class CDBWrapper CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; - leveldb::Slice slKey(ssKey.data(), ssKey.size()); + return Exists(ssKey); + } + + bool Exists(const CDataStream& key) const + { + leveldb::Slice slKey(key.data(), key.size()); std::string strValue; leveldb::Status status = pdb->Get(readoptions, slKey, &strValue); @@ -307,6 +362,372 @@ class CDBWrapper return size; } + /** + * Compact a certain range of keys in the database. + */ + template + void CompactRange(const K& key_begin, const K& key_end) const + { + CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION); + ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); + ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); + ssKey1 << key_begin; + ssKey2 << key_end; + leveldb::Slice slKey1(ssKey1.data(), ssKey1.size()); + leveldb::Slice slKey2(ssKey2.data(), ssKey2.size()); + pdb->CompactRange(&slKey1, &slKey2); + } + + void CompactFull() const + { + pdb->CompactRange(nullptr, nullptr); + } + +}; + +template +class CDBTransactionIterator +{ +private: + CDBTransaction& transaction; + + typedef typename std::remove_pointer::type ParentIterator; + + // We maintain 2 iterators, one for the transaction and one for the parent + // At all times, only one of both provides the current value. The decision is made by comparing the current keys + // of both iterators, so that always the smaller key is the current one. On Next(), the previously chosen iterator + // is advanced. + typename CDBTransaction::WritesMap::iterator transactionIt; + std::unique_ptr parentIt; + CDataStream parentKey; + bool curIsParent{false}; + +public: + CDBTransactionIterator(CDBTransaction& _transaction) : + transaction(_transaction), + parentKey(SER_DISK, CLIENT_VERSION) + { + transactionIt = transaction.writes.end(); + parentIt = std::unique_ptr(transaction.parent.NewIterator()); + } + + void SeekToFirst() + { + transactionIt = transaction.writes.begin(); + parentIt->SeekToFirst(); + SkipDeletedAndOverwritten(); + DecideCur(); + } + + template + void Seek(const K& key) + { + Seek(CDBTransaction::KeyToDataStream(key)); + } + + void Seek(const CDataStream& ssKey) + { + transactionIt = transaction.writes.lower_bound(ssKey); + parentIt->Seek(ssKey); + SkipDeletedAndOverwritten(); + DecideCur(); + } + + bool Valid() + { + return transactionIt != transaction.writes.end() || parentIt->Valid(); + } + + void Next() + { + if (transactionIt == transaction.writes.end() && !parentIt->Valid()) { + return; + } + if (curIsParent) { + assert(parentIt->Valid()); + parentIt->Next(); + SkipDeletedAndOverwritten(); + } else { + assert(transactionIt != transaction.writes.end()); + ++transactionIt; + } + DecideCur(); + } + + template + bool GetKey(K& key) + { + if (!Valid()) { + return false; + } + + if (curIsParent) { + return parentIt->GetKey(key); + } else { + try { + // TODO try to avoid this copy (we need a stream that allows reading from external buffers) + CDataStream ssKey = transactionIt->first; + ssKey >> key; + } catch (const std::exception&) { + return false; + } + return true; + } + } + + CDataStream GetKey() + { + if (!Valid()) { + return CDataStream(SER_DISK, CLIENT_VERSION); + } + if (curIsParent) { + return parentIt->GetKey(); + } else { + return transactionIt->first; + } + } + + unsigned int GetKeySize() + { + if (!Valid()) { + return 0; + } + if (curIsParent) { + return parentIt->GetKeySize(); + } else { + return transactionIt->first.vKey.size(); + } + } + + template + bool GetValue(V& value) + { + if (!Valid()) { + return false; + } + if (curIsParent) { + return transaction.Read(parentKey, value); + } else { + return transaction.Read(transactionIt->first, value); + } + }; + +private: + void SkipDeletedAndOverwritten() + { + while (parentIt->Valid()) { + parentKey = parentIt->GetKey(); + if (!transaction.deletes.count(parentKey) && !transaction.writes.count(parentKey)) { + break; + } + parentIt->Next(); + } + } + + void DecideCur() + { + if (transactionIt != transaction.writes.end() && !parentIt->Valid()) { + curIsParent = false; + } else if (transactionIt == transaction.writes.end() && parentIt->Valid()) { + curIsParent = true; + } else if (transactionIt != transaction.writes.end() && parentIt->Valid()) { + if (CDBTransaction::DataStreamCmp::less(transactionIt->first, parentKey)) { + curIsParent = false; + } else { + curIsParent = true; + } + } + } +}; + +template +class CDBTransaction { + friend class CDBTransactionIterator; + +protected: + Parent &parent; + CommitTarget &commitTarget; + ssize_t memoryUsage{0}; // signed, just in case we made an error in the calculations so that we don't get an overflow + + struct DataStreamCmp { + static bool less(const CDataStream& a, const CDataStream& b) + { + return std::lexicographical_compare( + (const uint8_t*)a.data(), (const uint8_t*)a.data() + a.size(), + (const uint8_t*)b.data(), (const uint8_t*)b.data() + b.size()); + } + bool operator()(const CDataStream& a, const CDataStream& b) const { return less(a, b); } + }; + + struct ValueHolder { + size_t memoryUsage; + ValueHolder(size_t _memoryUsage) : memoryUsage(_memoryUsage) {} + virtual ~ValueHolder() = default; + virtual void Write(const CDataStream& ssKey, CommitTarget &parent) = 0; + }; + typedef std::unique_ptr ValueHolderPtr; + + template + struct ValueHolderImpl : ValueHolder { + ValueHolderImpl(const V &_value, size_t _memoryUsage) : ValueHolder(_memoryUsage), value(_value) {} + + virtual void Write(const CDataStream& ssKey, CommitTarget &commitTarget) + { + // we're moving the value instead of copying it. This means that Write() can only be called once per + // ValueHolderImpl instance. Commit() clears the write maps, so this ok. + commitTarget.Write(ssKey, std::move(value)); + } + V value; + }; + + template + static CDataStream KeyToDataStream(const K& key) + { + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); + ssKey << key; + return ssKey; + } + + typedef std::map WritesMap; + typedef std::set DeletesSet; + + WritesMap writes; + DeletesSet deletes; + +public: + CDBTransaction(Parent &_parent, CommitTarget &_commitTarget) : parent(_parent), commitTarget(_commitTarget) {} + + template + void Write(const K& key, const V& v) + { + Write(KeyToDataStream(key), v); + } + + template + void Write(const CDataStream& ssKey, const V& v) + { + auto valueMemoryUsage = ::GetSerializeSize(v, SER_DISK, CLIENT_VERSION); + if (deletes.erase(ssKey)) { + memoryUsage -= ssKey.size(); + } + auto it = writes.emplace(ssKey, nullptr).first; + if (it->second) { + memoryUsage -= ssKey.size() + it->second->memoryUsage; + } + it->second = std::make_unique>(v, valueMemoryUsage); + + memoryUsage += ssKey.size() + valueMemoryUsage; + } + + template + bool Read(const K& key, V& value) + { + return Read(KeyToDataStream(key), value); + } + + template + bool Read(const CDataStream& ssKey, V& value) + { + if (deletes.count(ssKey)) { + return false; + } + + auto it = writes.find(ssKey); + if (it != writes.end()) { + auto *impl = dynamic_cast *>(it->second.get()); + if (!impl) { + throw std::runtime_error("Read called with V != previously written type"); + } + value = impl->value; + return true; + } + + return parent.Read(ssKey, value); + } + + template + bool Exists(const K& key) + { + return Exists(KeyToDataStream(key)); + } + + bool Exists(const CDataStream& ssKey) + { + if (deletes.count(ssKey)) { + return false; + } + + if (writes.count(ssKey)) { + return true; + } + + return parent.Exists(ssKey); + } + + template + void Erase(const K& key) + { + return Erase(KeyToDataStream(key)); + } + + void Erase(const CDataStream& ssKey) + { + auto it = writes.find(ssKey); + if (it != writes.end()) { + memoryUsage -= ssKey.size() + it->second->memoryUsage; + writes.erase(it); + } + if (deletes.emplace(ssKey).second) { + memoryUsage += ssKey.size(); + } + } + + void Clear() + { + writes.clear(); + deletes.clear(); + memoryUsage = 0; + } + + void Commit() + { + for (const auto &k : deletes) { + commitTarget.Erase(k); + } + for (auto &p : writes) { + p.second->Write(p.first, commitTarget); + } + Clear(); + } + + bool IsClean() + { + return writes.empty() && deletes.empty(); + } + + size_t GetMemoryUsage() const + { + if (memoryUsage < 0) { + // something went wrong when we accounted/calculated used memory... + static volatile bool didPrint = false; + if (!didPrint) { + LogPrintf("CDBTransaction::%s -- negative memoryUsage (%d)", __func__, memoryUsage); + didPrint = true; + } + return 0; + } + return (size_t)memoryUsage; + } + + CDBTransactionIterator* NewIterator() + { + return new CDBTransactionIterator(*this); + } + std::unique_ptr> NewIteratorUniquePtr() + { + return std::make_unique>(*this); + } }; #endif // BITCOIN_DBWRAPPER_H diff --git a/src/evo/evodb.cpp b/src/evo/evodb.cpp new file mode 100644 index 000000000000..01f48ddcec64 --- /dev/null +++ b/src/evo/evodb.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2018-2021 The Dash Core developers +// Copyright (c) 2021 The PIVX Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "evodb.h" + +std::unique_ptr evoDb; + +CEvoDBScopedCommitter::CEvoDBScopedCommitter(CEvoDB &_evoDB) : + evoDB(_evoDB) +{ +} + +CEvoDBScopedCommitter::~CEvoDBScopedCommitter() +{ + if (!didCommitOrRollback) + Rollback(); +} + +void CEvoDBScopedCommitter::Commit() +{ + assert(!didCommitOrRollback); + didCommitOrRollback = true; + evoDB.CommitCurTransaction(); +} + +void CEvoDBScopedCommitter::Rollback() +{ + assert(!didCommitOrRollback); + didCommitOrRollback = true; + evoDB.RollbackCurTransaction(); +} + +CEvoDB::CEvoDB(size_t nCacheSize, bool fMemory, bool fWipe) : + db(fMemory ? "" : (GetDataDir() / "evodb"), nCacheSize, fMemory, fWipe), + rootBatch(), + rootDBTransaction(db, rootBatch), + curDBTransaction(rootDBTransaction, rootDBTransaction) +{ +} + +void CEvoDB::CommitCurTransaction() +{ + LOCK(cs); + curDBTransaction.Commit(); +} + +void CEvoDB::RollbackCurTransaction() +{ + LOCK(cs); + curDBTransaction.Clear(); +} + +bool CEvoDB::CommitRootTransaction() +{ + assert(curDBTransaction.IsClean()); + rootDBTransaction.Commit(); + bool ret = db.WriteBatch(rootBatch); + rootBatch.Clear(); + return ret; +} + +bool CEvoDB::VerifyBestBlock(const uint256& hash) +{ + uint256 hashBestBlock; + return Read(EVODB_BEST_BLOCK, hashBestBlock) && hashBestBlock == hash; +} + +void CEvoDB::WriteBestBlock(const uint256& hash) +{ + Write(EVODB_BEST_BLOCK, hash); +} diff --git a/src/evo/evodb.h b/src/evo/evodb.h new file mode 100644 index 000000000000..b5bf1ce6a6a3 --- /dev/null +++ b/src/evo/evodb.h @@ -0,0 +1,113 @@ +// Copyright (c) 2018-2021 The Dash Core developers +// Copyright (c) 2021 The PIVX Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_EVODB_H +#define PIVX_EVODB_H + +#include "dbwrapper.h" +#include "sync.h" +#include "uint256.h" + +static const std::string EVODB_BEST_BLOCK = "b_b"; + +class CEvoDB; + +class CEvoDBScopedCommitter +{ +private: + CEvoDB& evoDB; + bool didCommitOrRollback{false}; + +public: + explicit CEvoDBScopedCommitter(CEvoDB& _evoDB); + ~CEvoDBScopedCommitter(); + + void Commit(); + void Rollback(); +}; + +class CEvoDB +{ +public: + RecursiveMutex cs; + +private: + CDBWrapper db; + + typedef CDBTransaction RootTransaction; + typedef CDBTransaction CurTransaction; + + CDBBatch rootBatch; + RootTransaction rootDBTransaction; + CurTransaction curDBTransaction; + +public: + explicit CEvoDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); + + std::unique_ptr BeginTransaction() + { + LOCK(cs); + return std::make_unique(*this); + } + + CurTransaction& GetCurTransaction() + { + AssertLockHeld(cs); // lock must be held from outside as long as the DB transaction is used + return curDBTransaction; + } + + template + bool Read(const K& key, V& value) + { + LOCK(cs); + return curDBTransaction.Read(key, value); + } + + template + void Write(const K& key, const V& value) + { + LOCK(cs); + curDBTransaction.Write(key, value); + } + + template + bool Exists(const K& key) + { + LOCK(cs); + return curDBTransaction.Exists(key); + } + + template + void Erase(const K& key) + { + LOCK(cs); + curDBTransaction.Erase(key); + } + + CDBWrapper& GetRawDB() + { + return db; + } + + size_t GetMemoryUsage() + { + return rootDBTransaction.GetMemoryUsage(); + } + + bool CommitRootTransaction(); + + bool VerifyBestBlock(const uint256& hash); + void WriteBestBlock(const uint256& hash); + +private: + // only CEvoDBScopedCommitter is allowed to invoke these + friend class CEvoDBScopedCommitter; + void CommitCurTransaction(); + void RollbackCurTransaction(); +}; + +extern std::unique_ptr evoDb; + +#endif//PIVX_EVODB_H diff --git a/src/init.cpp b/src/init.cpp index d7b4cbbcf53a..9c4b89cc64bd 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -44,6 +44,7 @@ #include "scheduler.h" #include "spork.h" #include "sporkdb.h" +#include "evo/evodb.h" #include "txdb.h" #include "torcontrol.h" #include "guiinterface.h" @@ -297,6 +298,7 @@ void PrepareShutdown() zerocoinDB = NULL; delete pSporkDB; pSporkDB = NULL; + evoDb.reset(); } #ifdef ENABLE_WALLET if (pwalletMain) @@ -1547,6 +1549,7 @@ bool AppInitMain() int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache nTotalCache -= nCoinDBCache; nCoinCacheUsage = nTotalCache; // the rest goes to in-memory cache + int64_t nEvoDbCache = 1024 * 1024 * 16; // TODO LogPrintf("Cache configuration:\n"); LogPrintf("* Using %.1fMiB for block index database\n", nBlockTreeDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1fMiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); @@ -1576,6 +1579,9 @@ bool AppInitMain() zerocoinDB = new CZerocoinDB(0, false, fReindex); pSporkDB = new CSporkDB(0, false, false); + evoDb.reset(); + evoDb.reset(new CEvoDB(nEvoDbCache, false, fReindex)); + pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex); if (fReindex) { diff --git a/src/masternode-payments.h b/src/masternode-payments.h index 7199b9fcbf54..3e042dadb2db 100644 --- a/src/masternode-payments.h +++ b/src/masternode-payments.h @@ -78,7 +78,7 @@ class CMasternodePayee template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(*(CScriptBase*)(&scriptPubKey)); + READWRITE(scriptPubKey); READWRITE(nVotes); } }; @@ -199,7 +199,7 @@ class CMasternodePaymentWinner : public CSignedMessage { READWRITE(vinMasternode); READWRITE(nBlockHeight); - READWRITE(*(CScriptBase*)(&payee)); + READWRITE(payee); READWRITE(vchSig); try { diff --git a/src/test/test_pivx.cpp b/src/test/test_pivx.cpp index adad49eb9192..05ca41899038 100644 --- a/src/test/test_pivx.cpp +++ b/src/test/test_pivx.cpp @@ -9,6 +9,7 @@ #include "blockassembler.h" #include "guiinterface.h" +#include "evo/evodb.h" #include "miner.h" #include "net_processing.h" #include "random.h" @@ -46,11 +47,13 @@ BasicTestingSetup::BasicTestingSetup() InitSignatureCache(); fCheckBlockIndex = true; SelectParams(CBaseChainParams::MAIN); + evoDb.reset(new CEvoDB(1 << 20, true, true)); } BasicTestingSetup::~BasicTestingSetup() { ECC_Stop(); g_connman.reset(); + evoDb.reset(); } TestingSetup::TestingSetup() diff --git a/src/validation.cpp b/src/validation.cpp index 8f9644441d50..e518b9adcda4 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -43,6 +43,7 @@ #include "script/sigcache.h" #include "spork.h" #include "sporkdb.h" +#include "evo/evodb.h" #include "txdb.h" #include "txmempool.h" #include "undo.h" @@ -1312,6 +1313,15 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out) DisconnectResult DisconnectBlock(CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) { AssertLockHeld(cs_main); + + bool fDIP3Active = Params().GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_V6_0); + bool fHasBestBlock = evoDb->VerifyBestBlock(pindex->GetBlockHash()); + + if (fDIP3Active && !fHasBestBlock) { + AbortNode("Found EvoDB inconsistency, you must reindex to continue"); + return DISCONNECT_FAILED; + } + bool fClean = true; CBlockUndo blockUndo; @@ -1393,6 +1403,7 @@ DisconnectResult DisconnectBlock(CBlock& block, const CBlockIndex* pindex, CCoin // move best block pointer to prevout block view.SetBestBlock(pindex->pprev->GetBlockHash()); + evoDb->WriteBestBlock(pindex->pprev->GetBlockHash()); if (consensus.NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_ZC_V2) && pindex->nHeight <= consensus.height_last_ZC_AccumCheckpoint) { @@ -1465,13 +1476,23 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd LogPrintf("%s: hashPrev=%s view=%s\n", __func__, hashPrevBlock.GetHex(), view.GetBestBlock().GetHex()); assert(hashPrevBlock == view.GetBestBlock()); + if (pindex->pprev) { + bool fDIP3Active = Params().GetConsensus().NetworkUpgradeActive(pindex->nHeight, Consensus::UPGRADE_V6_0); + bool fHasBestBlock = evoDb->VerifyBestBlock(hashPrevBlock); + + if (fDIP3Active && !fHasBestBlock) { + return AbortNode(state, "Found EvoDB inconsistency, you must reindex to continue"); + } + } + const Consensus::Params& consensus = Params().GetConsensus(); // Special case for the genesis block, skipping connection of its transactions // (its coinbase is unspendable) if (block.GetHash() == consensus.hashGenesisBlock) { - if (!fJustCheck) + if (!fJustCheck) { view.SetBestBlock(pindex->GetBlockHash()); + } return true; } @@ -1756,6 +1777,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); + evoDb->WriteBestBlock(pindex->GetBlockHash()); int64_t nTime4 = GetTimeMicros(); nTimeIndex += nTime4 - nTime3; @@ -1805,6 +1827,7 @@ bool static FlushStateToDisk(CValidationState& state, FlushStateMode mode) } int64_t nMempoolSizeMax = gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; int64_t cacheSize = pcoinsTip->DynamicMemoryUsage(); + cacheSize += evoDb->GetMemoryUsage(); int64_t nTotalSpace = nCoinCacheUsage + std::max(nMempoolSizeMax - nMempoolUsage, 0); // The cache is large and we're within 10% and 10 MiB of the limit, but we have time now // (not in the middle of a block processing). @@ -1859,6 +1882,9 @@ bool static FlushStateToDisk(CValidationState& state, FlushStateMode mode) // Flush the chainstate (which may refer to block index entries). if (!pcoinsTip->Flush()) return AbortNode(state, "Failed to write to coin database"); + if (!evoDb->CommitRootTransaction()) { + return AbortNode(state, "Failed to commit EvoDB"); + } nLastFlush = nNow; // Update money supply on memory, reading data from disk if (!ShutdownRequested() && !IsInitialBlockDownload()) { @@ -1905,6 +1931,7 @@ void static UpdateTip(CBlockIndex* pindexNew) pChainTip->GetBlockHash().GetHex(), pChainTip->nHeight, pChainTip->nVersion, log(pChainTip->nChainWork.getdouble()) / log(2.0), (unsigned long)pChainTip->nChainTx, DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pChainTip->GetBlockTime()), Checkpoints::GuessVerificationProgress(pChainTip), pcoinsTip->DynamicMemoryUsage() * (1.0 / (1<<20)), pcoinsTip->GetCacheSize()); + LogPrintf("%s: evodb_cache=%.1fMiB", __func__, evoDb->GetMemoryUsage() * (1.0 / (1<<20))); // Check the version of the last 100 blocks to see if we need to upgrade: static bool fWarned = false; @@ -1954,11 +1981,15 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara const uint256& saplingAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(); int64_t nStart = GetTimeMicros(); { + auto dbTx = evoDb->BeginTransaction(); + CCoinsViewCache view(pcoinsTip); assert(view.GetBestBlock() == pindexDelete->GetBlockHash()); if (DisconnectBlock(block, pindexDelete, view) != DISCONNECT_OK) return error("DisconnectTip() : DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString()); - assert(view.Flush()); + bool flushed = view.Flush(); + assert(flushed); + dbTx->Commit(); } LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001); const uint256& saplingAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(); @@ -2103,6 +2134,8 @@ bool static ConnectTip(CValidationState& state, CBlockIndex* pindexNew, const st int64_t nTime3; LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n", (nTime2 - nTime1) * 0.001, nTimeReadFromDisk * 0.000001); { + auto dbTx = evoDb->BeginTransaction(); + CCoinsViewCache view(pcoinsTip); bool rv = ConnectBlock(blockConnecting, state, pindexNew, view, false); GetMainSignals().BlockChecked(blockConnecting, state); @@ -2114,7 +2147,9 @@ bool static ConnectTip(CValidationState& state, CBlockIndex* pindexNew, const st nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2; LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs]\n", (nTime3 - nTime2) * 0.001, nTimeConnectTotal * 0.000001); - assert(view.Flush()); + bool flushed = view.Flush(); + assert(flushed); + dbTx->Commit(); } int64_t nTime4 = GetTimeMicros(); nTimeFlush += nTime4 - nTime3; @@ -3364,7 +3399,6 @@ bool ProcessNewBlock(CValidationState& state, CNode* pfrom, const std::shared_pt // Preliminary checks int64_t nStartTime = GetTimeMillis(); - const Consensus::Params& consensus = Params().GetConsensus(); int newHeight = 0; { @@ -3416,6 +3450,9 @@ bool TestBlockValidity(CValidationState& state, const CBlock& block, CBlockIndex indexDummy.pprev = pindexPrev; indexDummy.nHeight = pindexPrev->nHeight + 1; + // begin tx and let it rollback + auto dbTx = evoDb->BeginTransaction(); + // NOTE: CheckBlockHeader is called by CheckBlock if (!ContextualCheckBlockHeader(block, state, pindexPrev)) return error("%s: ContextualCheckBlockHeader failed: %s", __func__, FormatStateMessage(state)); @@ -3655,6 +3692,10 @@ bool CVerifyDB::VerifyDB(CCoinsView* coinsview, int nCheckLevel, int nCheckDepth return true; const int chainHeight = chainActive.Height(); + + // begin tx and let it rollback + auto dbTx = evoDb->BeginTransaction(); + // Verify blocks in the best chain if (nCheckDepth <= 0) nCheckDepth = 1000000000; // suffices until the year 19000 @@ -3816,6 +3857,7 @@ bool ReplayBlocks(const CChainParams& params, CCoinsView* view) } cache.SetBestBlock(pindexNew->GetBlockHash()); + evoDb->WriteBestBlock(pindexNew->GetBlockHash()); cache.Flush(); uiInterface.ShowProgress("", 100); return true; diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 7bba72319539..bc5ffca1cc80 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -490,7 +490,7 @@ def copy_and_overwrite(from_path, to_path): node_0_datadir = os.path.join(get_datadir_path(cachedir, 0), "regtest") for i in range(from_num, MAX_NODES): node_i_datadir = os.path.join(get_datadir_path(cachedir, i), "regtest") - for subdir in ["blocks", "chainstate", "sporks", "zerocoin"]: + for subdir in ["blocks", "chainstate", "evodb", "sporks", "zerocoin"]: copy_and_overwrite(os.path.join(node_0_datadir, subdir), os.path.join(node_i_datadir, subdir)) initialize_datadir(cachedir, i) # Overwrite port/rpcport in pivx.conf @@ -510,7 +510,7 @@ def cache_path(n, *paths): for i in range(MAX_NODES): for entry in os.listdir(cache_path(i)): - if entry not in ['wallet.dat', 'chainstate', 'blocks', 'sporks', 'zerocoin', 'backups']: + if entry not in ['wallet.dat', 'chainstate', 'blocks', 'sporks', 'evodb', 'zerocoin', 'backups']: os.remove(cache_path(i, entry)) def clean_cache_dir():