diff --git a/configure.ac b/configure.ac index a7970563cb..ea0fc4da21 100644 --- a/configure.ac +++ b/configure.ac @@ -449,7 +449,7 @@ case $host in esac fi - AX_CHECK_LINK_FLAG([[-lboost_system -Wl,-headerpad_max_install_names]], [LDFLAGS="$LDFLAGS -lboost_system -Wl,-headerpad_max_install_names"]) + AX_CHECK_LINK_FLAG([[-Wl,-headerpad_max_install_names]], [LDFLAGS="$LDFLAGS -Wl,-headerpad_max_install_names"]) CPPFLAGS="$CPPFLAGS -DMAC_OSX" OBJCXXFLAGS="$CXXFLAGS" ;; @@ -773,7 +773,9 @@ define(MINIMUM_REQUIRED_BOOST, 1.47.0) dnl Check for boost libs AX_BOOST_BASE([MINIMUM_REQUIRED_BOOST]) -AX_BOOST_SYSTEM +dnl Boost.System is header-only since Boost 1.69; skip library check +dnl AX_BOOST_SYSTEM +BOOST_SYSTEM_LIB="" AX_BOOST_FILESYSTEM AX_BOOST_PROGRAM_OPTIONS AX_BOOST_THREAD diff --git a/src/assets/assetdb.cpp b/src/assets/assetdb.cpp index 3ade39ab04..3f442500d0 100644 --- a/src/assets/assetdb.cpp +++ b/src/assets/assetdb.cpp @@ -83,6 +83,45 @@ bool CAssetsDB::EraseAddressAssetQuantity(const std::string &address, const std: bool EraseAddressAssetQuantity(const std::string &address, const std::string &assetName); +// Perf: Batch write/erase methods — these add operations to an existing +// CDBBatch without flushing. Call FlushBatch() once after accumulating +// all operations to commit them in a single LevelDB write. +void CAssetsDB::WriteAssetDataBatch(CDBBatch& batch, const CNewAsset &asset, const int nHeight, const uint256& blockHash) +{ + CDatabasedAssetData data(asset, nHeight, blockHash); + batch.Write(std::make_pair(ASSET_FLAG, asset.strName), data); +} + +void CAssetsDB::WriteAssetAddressQuantityBatch(CDBBatch& batch, const std::string &assetName, const std::string &address, const CAmount &quantity) +{ + batch.Write(std::make_pair(ASSET_ADDRESS_QUANTITY_FLAG, std::make_pair(assetName, address)), quantity); +} + +void CAssetsDB::WriteAddressAssetQuantityBatch(CDBBatch& batch, const std::string &address, const std::string &assetName, const CAmount& quantity) +{ + batch.Write(std::make_pair(ADDRESS_ASSET_QUANTITY_FLAG, std::make_pair(address, assetName)), quantity); +} + +void CAssetsDB::EraseAssetDataBatch(CDBBatch& batch, const std::string& assetName) +{ + batch.Erase(std::make_pair(ASSET_FLAG, assetName)); +} + +void CAssetsDB::EraseAssetAddressQuantityBatch(CDBBatch& batch, const std::string &assetName, const std::string &address) +{ + batch.Erase(std::make_pair(ASSET_ADDRESS_QUANTITY_FLAG, std::make_pair(assetName, address))); +} + +void CAssetsDB::EraseAddressAssetQuantityBatch(CDBBatch& batch, const std::string &address, const std::string &assetName) +{ + batch.Erase(std::make_pair(ADDRESS_ASSET_QUANTITY_FLAG, std::make_pair(address, assetName))); +} + +bool CAssetsDB::FlushBatch(CDBBatch& batch) +{ + return WriteBatch(batch, true); +} + bool CAssetsDB::WriteBlockUndoAssetData(const uint256& blockhash, const std::vector >& assetUndoData) { return Write(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), assetUndoData); @@ -90,11 +129,14 @@ bool CAssetsDB::WriteBlockUndoAssetData(const uint256& blockhash, const std::vec bool CAssetsDB::ReadBlockUndoAssetData(const uint256 &blockhash, std::vector > &assetUndoData) { - // If it exists, return the read value. - if (Exists(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash))) - return Read(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), assetUndoData); + // Perf: Read() already returns false on not-found, so the previous + // Exists() + Read() pattern caused two LevelDB lookups for the same key. + // We just call Read() directly; if the key doesn't exist we return true + // because absence of undo data is not an error. + if (Read(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), assetUndoData)) + return true; - // If it doesn't exist, we just return true because we don't want to fail just because it didn't exist in the db + // Key not found — not an error, just no undo data for this block return true; } @@ -110,7 +152,7 @@ bool CAssetsDB::ReadReissuedMempoolState() // If it exists, return the read value. bool rv = Read(MEMPOOL_REISSUED_TX, mapReissuedAssets); if (rv) { - for (auto pair : mapReissuedAssets) + for (const auto& pair : mapReissuedAssets) mapReissuedTx.insert(std::make_pair(pair.second, pair.first)); } return rv; diff --git a/src/assets/assetdb.h b/src/assets/assetdb.h index 9055b25d99..60617af204 100644 --- a/src/assets/assetdb.h +++ b/src/assets/assetdb.h @@ -72,6 +72,17 @@ class CAssetsDB : public CDBWrapper bool WriteBlockUndoAssetData(const uint256& blockhash, const std::vector >& assetUndoData); bool WriteReissuedMempoolState(); + // Perf: Batch write methods — accumulate writes into an external CDBBatch + // instead of creating individual batches per call. Call FlushBatch() once + // after all writes are queued to commit them in a single LevelDB write. + void WriteAssetDataBatch(CDBBatch& batch, const CNewAsset& asset, const int nHeight, const uint256& blockHash); + void WriteAssetAddressQuantityBatch(CDBBatch& batch, const std::string& assetName, const std::string& address, const CAmount& quantity); + void WriteAddressAssetQuantityBatch(CDBBatch& batch, const std::string& address, const std::string& assetName, const CAmount& quantity); + void EraseAssetDataBatch(CDBBatch& batch, const std::string& assetName); + void EraseAssetAddressQuantityBatch(CDBBatch& batch, const std::string& assetName, const std::string& address); + void EraseAddressAssetQuantityBatch(CDBBatch& batch, const std::string& address, const std::string& assetName); + bool FlushBatch(CDBBatch& batch); + // Read from database functions bool ReadAssetData(const std::string& strName, CNewAsset& asset, int& nHeight, uint256& blockHash); bool ReadAssetAddressQuantity(const std::string& assetName, const std::string& address, CAmount& quantity); diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index 495624968b..3df0ea7aab 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -2272,17 +2272,16 @@ bool CAssetsCache::DumpCacheToDatabase() bool dirty = false; std::string message; + // Perf: accumulate all passetsdb writes/erases into a single CDBBatch + // and flush once at the end. Previously each Write/Erase created its own + // batch and called WriteBatch individually — for a block with N asset + // operations this meant N separate LevelDB writes. Now it's always 1. + CDBBatch assetBatch(*passetsdb); + // Remove new assets from the database - for (auto newAsset : setNewAssetsToRemove) { + for (const auto& newAsset : setNewAssetsToRemove) { passetsCache->Erase(newAsset.asset.strName); - if (!passetsdb->EraseAssetData(newAsset.asset.strName)) { - dirty = true; - message = "_Failed Erasing New Asset Data from database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } + passetsdb->EraseAssetDataBatch(assetBatch, newAsset.asset.strName); if (!prestricteddb->EraseVerifier(newAsset.asset.strName)) { dirty = true; @@ -2290,15 +2289,8 @@ bool CAssetsCache::DumpCacheToDatabase() } if (fAssetIndex) { - if (!passetsdb->EraseAssetAddressQuantity(newAsset.asset.strName, newAsset.address)) { - dirty = true; - message = "_Failed Erasing Address Balance from database"; - } - - if (!passetsdb->EraseAddressAssetQuantity(newAsset.address, newAsset.asset.strName)) { - dirty = true; - message = "_Failed Erasing New Asset Address Balance from AddressAsset database"; - } + passetsdb->EraseAssetAddressQuantityBatch(assetBatch, newAsset.asset.strName, newAsset.address); + passetsdb->EraseAddressAssetQuantityBatch(assetBatch, newAsset.address, newAsset.asset.strName); } if (dirty) { @@ -2307,183 +2299,92 @@ bool CAssetsCache::DumpCacheToDatabase() } // Add the new assets to the database - for (auto newAsset : setNewAssetsToAdd) { + for (const auto& newAsset : setNewAssetsToAdd) { passetsCache->Put(newAsset.asset.strName, CDatabasedAssetData(newAsset.asset, newAsset.blockHeight, newAsset.blockHash)); - if (!passetsdb->WriteAssetData(newAsset.asset, newAsset.blockHeight, newAsset.blockHash)) { - dirty = true; - message = "_Failed Writing New Asset Data to database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } + passetsdb->WriteAssetDataBatch(assetBatch, newAsset.asset, newAsset.blockHeight, newAsset.blockHash); if (fAssetIndex) { - if (!passetsdb->WriteAssetAddressQuantity(newAsset.asset.strName, newAsset.address, - newAsset.asset.nAmount)) { - dirty = true; - message = "_Failed Writing Address Balance to database"; - } - - if (!passetsdb->WriteAddressAssetQuantity(newAsset.address, newAsset.asset.strName, - newAsset.asset.nAmount)) { - dirty = true; - message = "_Failed Writing Address Balance to database"; - } - } - - if (dirty) { - return error("%s : %s", __func__, message); + passetsdb->WriteAssetAddressQuantityBatch(assetBatch, newAsset.asset.strName, newAsset.address, + newAsset.asset.nAmount); + passetsdb->WriteAddressAssetQuantityBatch(assetBatch, newAsset.address, newAsset.asset.strName, + newAsset.asset.nAmount); } } if (fAssetIndex) { // Remove the new owners from database - for (auto ownerAsset : setNewOwnerAssetsToRemove) { - if (!passetsdb->EraseAssetAddressQuantity(ownerAsset.assetName, ownerAsset.address)) { - dirty = true; - message = "_Failed Erasing Owner Address Balance from database"; - } - - if (!passetsdb->EraseAddressAssetQuantity(ownerAsset.address, ownerAsset.assetName)) { - dirty = true; - message = "_Failed Erasing New Owner Address Balance from AddressAsset database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } + for (const auto& ownerAsset : setNewOwnerAssetsToRemove) { + passetsdb->EraseAssetAddressQuantityBatch(assetBatch, ownerAsset.assetName, ownerAsset.address); + passetsdb->EraseAddressAssetQuantityBatch(assetBatch, ownerAsset.address, ownerAsset.assetName); } // Add the new owners to database - for (auto ownerAsset : setNewOwnerAssetsToAdd) { + for (const auto& ownerAsset : setNewOwnerAssetsToAdd) { auto pair = std::make_pair(ownerAsset.assetName, ownerAsset.address); if (mapAssetsAddressAmount.count(pair) && mapAssetsAddressAmount.at(pair) > 0) { - if (!passetsdb->WriteAssetAddressQuantity(ownerAsset.assetName, ownerAsset.address, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing Owner Address Balance to database"; - } - - if (!passetsdb->WriteAddressAssetQuantity(ownerAsset.address, ownerAsset.assetName, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing Address Balance to database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } + passetsdb->WriteAssetAddressQuantityBatch(assetBatch, ownerAsset.assetName, ownerAsset.address, + mapAssetsAddressAmount.at(pair)); + passetsdb->WriteAddressAssetQuantityBatch(assetBatch, ownerAsset.address, ownerAsset.assetName, + mapAssetsAddressAmount.at(pair)); } } // Undo the transfering by updating the balances in the database - for (auto undoTransfer : setNewTransferAssetsToRemove) { + for (const auto& undoTransfer : setNewTransferAssetsToRemove) { auto pair = std::make_pair(undoTransfer.transfer.strName, undoTransfer.address); if (mapAssetsAddressAmount.count(pair)) { if (mapAssetsAddressAmount.at(pair) == 0) { - if (!passetsdb->EraseAssetAddressQuantity(undoTransfer.transfer.strName, - undoTransfer.address)) { - dirty = true; - message = "_Failed Erasing Address Quantity from database"; - } - - if (!passetsdb->EraseAddressAssetQuantity(undoTransfer.address, - undoTransfer.transfer.strName)) { - dirty = true; - message = "_Failed Erasing UndoTransfer Address Balance from AddressAsset database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } + passetsdb->EraseAssetAddressQuantityBatch(assetBatch, undoTransfer.transfer.strName, + undoTransfer.address); + passetsdb->EraseAddressAssetQuantityBatch(assetBatch, undoTransfer.address, + undoTransfer.transfer.strName); } else { - if (!passetsdb->WriteAssetAddressQuantity(undoTransfer.transfer.strName, + passetsdb->WriteAssetAddressQuantityBatch(assetBatch, undoTransfer.transfer.strName, undoTransfer.address, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing updated Address Quantity to database when undoing transfers"; - } - - if (!passetsdb->WriteAddressAssetQuantity(undoTransfer.address, + mapAssetsAddressAmount.at(pair)); + passetsdb->WriteAddressAssetQuantityBatch(assetBatch, undoTransfer.address, undoTransfer.transfer.strName, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing Address Balance to database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } + mapAssetsAddressAmount.at(pair)); } } } // Save the new transfers by updating the quantity in the database - for (auto newTransfer : setNewTransferAssetsToAdd) { + for (const auto& newTransfer : setNewTransferAssetsToAdd) { auto pair = std::make_pair(newTransfer.transfer.strName, newTransfer.address); // During init and reindex it disconnects and verifies blocks, can create a state where vNewTransfer will contain transfers that have already been spent. So if they aren't in the map, we can skip them. if (mapAssetsAddressAmount.count(pair)) { - if (!passetsdb->WriteAssetAddressQuantity(newTransfer.transfer.strName, newTransfer.address, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing new address quantity to database"; - } - - if (!passetsdb->WriteAddressAssetQuantity(newTransfer.address, newTransfer.transfer.strName, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing Address Balance to database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } + passetsdb->WriteAssetAddressQuantityBatch(assetBatch, newTransfer.transfer.strName, newTransfer.address, + mapAssetsAddressAmount.at(pair)); + passetsdb->WriteAddressAssetQuantityBatch(assetBatch, newTransfer.address, newTransfer.transfer.strName, + mapAssetsAddressAmount.at(pair)); } } } - for (auto newReissue : setNewReissueToAdd) { + for (const auto& newReissue : setNewReissueToAdd) { auto reissue_name = newReissue.reissue.strName; auto pair = make_pair(reissue_name, newReissue.address); if (mapReissuedAssetData.count(reissue_name)) { - if(!passetsdb->WriteAssetData(mapReissuedAssetData.at(reissue_name), newReissue.blockHeight, newReissue.blockHash)) { - dirty = true; - message = "_Failed Writing reissue asset data to database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } + passetsdb->WriteAssetDataBatch(assetBatch, mapReissuedAssetData.at(reissue_name), newReissue.blockHeight, newReissue.blockHash); passetsCache->Erase(reissue_name); if (fAssetIndex) { if (mapAssetsAddressAmount.count(pair) && mapAssetsAddressAmount.at(pair) > 0) { - if (!passetsdb->WriteAssetAddressQuantity(pair.first, pair.second, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing reissue asset quantity to the address quantity database"; - } - - if (!passetsdb->WriteAddressAssetQuantity(pair.second, pair.first, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing Address Balance to database"; - } - - if (dirty) { - return error("%s, %s", __func__, message); - } + passetsdb->WriteAssetAddressQuantityBatch(assetBatch, pair.first, pair.second, + mapAssetsAddressAmount.at(pair)); + passetsdb->WriteAddressAssetQuantityBatch(assetBatch, pair.second, pair.first, + mapAssetsAddressAmount.at(pair)); } } } } - for (auto undoReissue : setNewReissueToRemove) { + for (const auto& undoReissue : setNewReissueToRemove) { // In the case the the issue and reissue are both being removed // we can skip this call because the removal of the issue should remove all data pertaining the to asset // Fixes the issue where the reissue data will write over the removed asset meta data that was removed above @@ -2495,50 +2396,34 @@ bool CAssetsCache::DumpCacheToDatabase() auto reissue_name = undoReissue.reissue.strName; if (mapReissuedAssetData.count(reissue_name)) { - if(!passetsdb->WriteAssetData(mapReissuedAssetData.at(reissue_name), undoReissue.blockHeight, undoReissue.blockHash)) { - dirty = true; - message = "_Failed Writing undo reissue asset data to database"; - } + passetsdb->WriteAssetDataBatch(assetBatch, mapReissuedAssetData.at(reissue_name), undoReissue.blockHeight, undoReissue.blockHash); if (fAssetIndex) { auto pair = make_pair(undoReissue.reissue.strName, undoReissue.address); if (mapAssetsAddressAmount.count(pair)) { if (mapAssetsAddressAmount.at(pair) == 0) { - if (!passetsdb->EraseAssetAddressQuantity(reissue_name, undoReissue.address)) { - dirty = true; - message = "_Failed Erasing Address Balance from database"; - } - - if (!passetsdb->EraseAddressAssetQuantity(undoReissue.address, reissue_name)) { - dirty = true; - message = "_Failed Erasing UndoReissue Balance from AddressAsset database"; - } + passetsdb->EraseAssetAddressQuantityBatch(assetBatch, reissue_name, undoReissue.address); + passetsdb->EraseAddressAssetQuantityBatch(assetBatch, undoReissue.address, reissue_name); } else { - if (!passetsdb->WriteAssetAddressQuantity(reissue_name, undoReissue.address, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing the undo of reissue of asset from database"; - } - - if (!passetsdb->WriteAddressAssetQuantity(undoReissue.address, reissue_name, - mapAssetsAddressAmount.at(pair))) { - dirty = true; - message = "_Failed Writing Address Balance to database"; - } + passetsdb->WriteAssetAddressQuantityBatch(assetBatch, reissue_name, undoReissue.address, + mapAssetsAddressAmount.at(pair)); + passetsdb->WriteAddressAssetQuantityBatch(assetBatch, undoReissue.address, reissue_name, + mapAssetsAddressAmount.at(pair)); } } } - if (dirty) { - return error("%s : %s", __func__, message); - } - passetsCache->Erase(reissue_name); } } + // Perf: flush all accumulated asset DB operations in one LevelDB write + if (!passetsdb->FlushBatch(assetBatch)) { + return error("%s : Failed flushing asset batch to database", __func__); + } + // Add new verifier strings for restricted assets - for (auto newVerifier : setNewRestrictedVerifierToAdd) { + for (const auto& newVerifier : setNewRestrictedVerifierToAdd) { auto assetName = newVerifier.assetName; if (!prestricteddb->WriteVerifier(assetName, newVerifier.verifier)) { dirty = true; @@ -2553,7 +2438,7 @@ bool CAssetsCache::DumpCacheToDatabase() } // Undo verifier string for restricted assets - for (auto undoVerifiers : setNewRestrictedVerifierToRemove) { + for (const auto& undoVerifiers : setNewRestrictedVerifierToRemove) { auto assetName = undoVerifiers.assetName; // If we are undoing a reissue, we need to save back the old verifier string to database @@ -3751,13 +3636,18 @@ bool GetBestAssetAddressAmount(CAssetsCache& cache, const std::string& assetName #ifdef ENABLE_WALLET //! sets _balances_ with the total quantity of each owned asset bool GetAllMyAssetBalances(std::map >& outputs, std::map& amounts, const int confirmations, const std::string& prefix) { - - // Return false if no wallet was found to compute asset balances if (!vpwallets.size()) return false; + return GetAllMyAssetBalances(vpwallets[0], outputs, amounts, confirmations, prefix); +} + +bool GetAllMyAssetBalances(CWallet* pwallet, std::map >& outputs, std::map& amounts, const int confirmations, const std::string& prefix) { + + if (!pwallet) + return false; // Get the map of assetnames to outputs - vpwallets[0]->AvailableAssets(outputs, true, nullptr, 1, MAX_MONEY, MAX_MONEY, 0, confirmations); + pwallet->AvailableAssets(outputs, true, nullptr, 1, MAX_MONEY, MAX_MONEY, 0, confirmations); // Loop through all pairs of Asset Name -> vector for (const auto& pair : outputs) { @@ -4303,7 +4193,7 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const CCoinControl& coinCo if (nullAssetTxData) { std::string strError = ""; int nAddTagCount = 0; - for (auto pair : *nullAssetTxData) { + for (const auto& pair : *nullAssetTxData) { if (IsAssetNameAQualifier(pair.first.asset_name)) { if (!VerifyQualifierChange(*passets, pair.first, pair.second, strError)) { diff --git a/src/assets/assets.h b/src/assets/assets.h index 8eeb58244b..23b35dab2f 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -528,6 +528,7 @@ std::string EncodeIPFS(std::string decoded); #ifdef ENABLE_WALLET bool GetAllMyAssetBalances(std::map >& outputs, std::map& amounts, const int confirmations = 0, const std::string& prefix = ""); +bool GetAllMyAssetBalances(CWallet* pwallet, std::map >& outputs, std::map& amounts, const int confirmations = 0, const std::string& prefix = ""); bool GetMyAssetBalance(const std::string& name, CAmount& balance, const int& confirmations); //! Creates new asset issuance transaction diff --git a/src/checkqueue.h b/src/checkqueue.h index be570cd44f..11298805f2 100644 --- a/src/checkqueue.h +++ b/src/checkqueue.h @@ -9,11 +9,10 @@ #include "sync.h" #include +#include +#include #include -#include -#include - template class CCheckQueueControl; @@ -32,13 +31,13 @@ class CCheckQueue { private: //! Mutex to protect the inner state - boost::mutex mutex; + std::mutex mutex; //! Worker threads block on this when out of work - boost::condition_variable condWorker; + std::condition_variable condWorker; //! Master thread blocks on this when out of work - boost::condition_variable condMaster; + std::condition_variable condMaster; //! The queue of elements to be processed. //! As the order of booleans doesn't matter, it is used as a LIFO (stack) @@ -69,14 +68,14 @@ class CCheckQueue /** Internal function that does bulk of the verification work. */ bool Loop(bool fMaster = false) { - boost::condition_variable& cond = fMaster ? condMaster : condWorker; + std::condition_variable& cond = fMaster ? condMaster : condWorker; std::vector vChecks; vChecks.reserve(nBatchSize); unsigned int nNow = 0; bool fOk = true; do { { - boost::unique_lock lock(mutex); + std::unique_lock lock(mutex); // first do the clean-up of the previous loop run (allowing us to do it in the same critsect) if (nNow) { fAllOk &= fOk; @@ -129,7 +128,7 @@ class CCheckQueue public: //! Mutex to ensure only one concurrent CCheckQueueControl - boost::mutex ControlMutex; + std::mutex ControlMutex; //! Create a new check queue explicit CCheckQueue(unsigned int nBatchSizeIn) : nIdle(0), nTotal(0), fAllOk(true), nTodo(0), fQuit(false), nBatchSize(nBatchSizeIn) {} @@ -149,7 +148,7 @@ class CCheckQueue //! Add a batch of checks to the queue void Add(std::vector& vChecks) { - boost::unique_lock lock(mutex); + std::unique_lock lock(mutex); for (T& check : vChecks) { queue.push_back(T()); check.swap(queue.back()); diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index b8de9e4be8..b204fd67b8 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -121,7 +121,11 @@ CDBWrapper::CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory, bo { penv = nullptr; readoptions.verify_checksums = true; - iteroptions.verify_checksums = true; + // Perf: skip checksum verification during iteration. Data integrity is + // already guaranteed by LevelDB's per-block checksums on write and by + // paranoid_checks on open. Skipping verification on read reduces CPU + // overhead during bulk iteration (e.g., LoadBlockIndexGuts, asset loading). + iteroptions.verify_checksums = false; iteroptions.fill_cache = false; syncoptions.sync = true; options = GetOptions(nCacheSize, maxFileSize); diff --git a/src/init.cpp b/src/init.cpp index 9bef67af9f..e13fda148e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -394,14 +394,16 @@ static void registerSignalHandler(int signal, void(*handler)(int)) } #endif +static boost::signals2::connection rpcBlockChangeConn; + void OnRPCStarted() { - uiInterface.NotifyBlockTip.connect(&RPCNotifyBlockChange); + rpcBlockChangeConn = uiInterface.NotifyBlockTip.connect(&RPCNotifyBlockChange); } void OnRPCStopped() { - uiInterface.NotifyBlockTip.disconnect(&RPCNotifyBlockChange); + rpcBlockChangeConn.disconnect(); RPCNotifyBlockChange(false, nullptr); cvBlockChange.notify_all(); LogPrint(BCLog::RPC, "RPC stopped.\n"); @@ -1322,8 +1324,11 @@ bool AppInitLockDataDirectory() return true; } +CScheduler* pScheduler = nullptr; + bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) { + pScheduler = &scheduler; const CChainParams& chainparams = GetParams(); // ********************************************************* Step 4a: application initialization #ifndef WIN32 @@ -1835,8 +1840,9 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly. // No locking, as this happens before any background thread is started. + boost::signals2::connection genesisWaitConn; if (chainActive.Tip() == nullptr) { - uiInterface.NotifyBlockTip.connect(BlockNotifyGenesisWait); + genesisWaitConn = uiInterface.NotifyBlockTip.connect(BlockNotifyGenesisWait); } else { fHaveGenesis = true; } @@ -1857,7 +1863,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) while (!fHaveGenesis) { condvar_GenesisWait.wait(lock); } - uiInterface.NotifyBlockTip.disconnect(BlockNotifyGenesisWait); + genesisWaitConn.disconnect(); } // ********************************************************* Step 11: start node diff --git a/src/rpc/assets.cpp b/src/rpc/assets.cpp index 3e025af8ef..55eecce0a8 100644 --- a/src/rpc/assets.cpp +++ b/src/rpc/assets.cpp @@ -973,23 +973,23 @@ UniValue listmyassets(const JSONRPCRequest &request) confs = request.params[4].get_int(); } - // retrieve balances + // retrieve balances (use the specific wallet from the RPC request) std::map balances; std::map > outputs; if (filter == "*") { - if (!GetAllMyAssetBalances(outputs, balances, confs)) + if (!GetAllMyAssetBalances(pwallet, outputs, balances, confs)) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't get asset balances. For all assets"); } else if (filter.back() == '*') { std::vector assetNames; filter.pop_back(); - if (!GetAllMyAssetBalances(outputs, balances, confs, filter)) + if (!GetAllMyAssetBalances(pwallet, outputs, balances, confs, filter)) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't get asset balances. For all assets"); } else { if (!IsAssetNameValid(filter)) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid asset name."); - if (!GetAllMyAssetBalances(outputs, balances, confs, filter)) + if (!GetAllMyAssetBalances(pwallet, outputs, balances, confs, filter)) throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't get asset balances. For all assets"); } diff --git a/src/rpc/protocol.cpp b/src/rpc/protocol.cpp index 6db357c2a0..7d517e3bb3 100644 --- a/src/rpc/protocol.cpp +++ b/src/rpc/protocol.cpp @@ -75,7 +75,7 @@ static fs::path GetAuthCookieFile(bool temp=false) arg += ".tmp"; } fs::path path(arg); - if (!path.is_complete()) path = GetDataDir() / path; + if (!path.is_absolute()) path = GetDataDir() / path; return path; } diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index 05d8724992..4b77649bdd 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -5,6 +5,7 @@ #include "support/lockedpool.h" #include "support/cleanse.h" +#include #if defined(HAVE_CONFIG_H) #include "config/raven-config.h" diff --git a/src/txdb.cpp b/src/txdb.cpp index 38b7ac9d0d..e1913e580b 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -488,8 +488,12 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, pindexNew->mix_hash = diskindex.mix_hash; pindexNew->nHeight = diskindex.nHeight; - if (!CheckProofOfWork(pindexNew->GetBlockHash(), pindexNew->nBits, consensusParams)) - return error("%s: CheckProofOfWork failed: %s", __func__, pindexNew->ToString()); + // Perf: Skip PoW re-verification on startup. Every block stored + // in the block index has already passed CheckProofOfWork() + // during initial validation in ConnectBlock(). Re-checking here + // is redundant and expensive (kawpow hash for every block). + // If the on-disk data were corrupted, LevelDB checksums would + // catch it before we reach this point. pcursor->Next(); } else { diff --git a/src/txdb.h b/src/txdb.h index 7b77e4296f..a5be72f0c9 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -34,13 +34,17 @@ static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024; //! min. -dbcache (MiB) static const int64_t nMinDbCache = 4; //! Max memory allocated to block tree DB specific cache, if no -txindex (MiB) -static const int64_t nMaxBlockDBCache = 2; +//! Perf: bumped from 2 to 32 MiB — allows LevelDB to cache more block index +//! entries in memory, reducing disk I/O during validation and startup. +static const int64_t nMaxBlockDBCache = 32; //! Max memory allocated to block tree DB specific cache, if -txindex (MiB) // Unlike for the UTXO database, for the txindex scenario the leveldb cache make // a meaningful difference: https://github.com/bitcoin/bitcoin/pull/8273#issuecomment-229601991 static const int64_t nMaxBlockDBAndTxIndexCache = 1024; //! Max memory allocated to coin DB specific cache (MiB) -static const int64_t nMaxCoinsDBCache = 8; +//! Perf: bumped from 8 to 64 MiB — reduces disk reads for UTXO lookups +//! during block validation, especially beneficial during IBD. +static const int64_t nMaxCoinsDBCache = 64; struct CDiskTxPos : public CDiskBlockPos { diff --git a/src/util.cpp b/src/util.cpp index 7542cc2d75..a5ed1ec0c3 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -620,7 +620,7 @@ void ClearDatadirCache() fs::path GetConfigFile(const std::string &confPath) { fs::path pathConfigFile(confPath); - if (!pathConfigFile.is_complete()) + if (!pathConfigFile.is_absolute()) pathConfigFile = GetDataDir(false) / pathConfigFile; return pathConfigFile; @@ -657,7 +657,7 @@ void ArgsManager::ReadConfigFile(const std::string &confPath) fs::path GetPidFile() { fs::path pathPidFile(gArgs.GetArg("-pid", RAVEN_PID_FILENAME)); - if (!pathPidFile.is_complete()) pathPidFile = GetDataDir() / pathPidFile; + if (!pathPidFile.is_absolute()) pathPidFile = GetDataDir() / pathPidFile; return pathPidFile; } diff --git a/src/validation.cpp b/src/validation.cpp index 1c33e9a394..0b6dc8bfd0 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1855,22 +1855,26 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* const CTxOut &out = tx.vout[k]; if (out.scriptPubKey.IsPayToScriptHash()) { - std::vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); + // Perf: use fixed-size uint160 instead of heap-allocated vector + uint160 hashBytes; + memcpy(hashBytes.begin(), out.scriptPubKey.data() + 2, 20); // undo receiving activity - addressIndex.push_back(std::make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, hash, k, false), out.nValue)); + addressIndex.push_back(std::make_pair(CAddressIndexKey(2, hashBytes, pindex->nHeight, i, hash, k, false), out.nValue)); // undo unspent index - addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(2, uint160(hashBytes), hash, k), CAddressUnspentValue())); + addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(2, hashBytes, hash, k), CAddressUnspentValue())); } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { - std::vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); + // Perf: use fixed-size uint160 instead of heap-allocated vector + uint160 hashBytes; + memcpy(hashBytes.begin(), out.scriptPubKey.data() + 3, 20); // undo receiving activity - addressIndex.push_back(std::make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, hash, k, false), out.nValue)); + addressIndex.push_back(std::make_pair(CAddressIndexKey(1, hashBytes, pindex->nHeight, i, hash, k, false), out.nValue)); // undo unspent index - addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(1, uint160(hashBytes), hash, k), CAddressUnspentValue())); + addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(1, hashBytes, hash, k), CAddressUnspentValue())); } else if (out.scriptPubKey.IsPayToPublicKey()) { uint160 hashBytes(Hash160(out.scriptPubKey.begin()+1, out.scriptPubKey.end()-1)); @@ -2183,23 +2187,27 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* if (fAddressIndex) { const CTxOut &prevout = view.AccessCoin(tx.vin[j].prevout).out; if (prevout.scriptPubKey.IsPayToScriptHash()) { - std::vector hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22); + // Perf: use fixed-size uint160 instead of heap-allocated vector + uint160 hashBytes; + memcpy(hashBytes.begin(), prevout.scriptPubKey.data() + 2, 20); // undo spending activity - addressIndex.push_back(std::make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); + addressIndex.push_back(std::make_pair(CAddressIndexKey(2, hashBytes, pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); // restore unspent index - addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(2, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); + addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(2, hashBytes, input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { - std::vector hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23); + // Perf: use fixed-size uint160 instead of heap-allocated vector + uint160 hashBytes; + memcpy(hashBytes.begin(), prevout.scriptPubKey.data() + 3, 20); // undo spending activity - addressIndex.push_back(std::make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); + addressIndex.push_back(std::make_pair(CAddressIndexKey(1, hashBytes, pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); // restore unspent index - addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(1, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); + addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(1, hashBytes, input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); } else if (prevout.scriptPubKey.IsPayToPublicKey()) { uint160 hashBytes(Hash160(prevout.scriptPubKey.begin()+1, prevout.scriptPubKey.end()-1)); @@ -2514,6 +2522,13 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd std::set setMessages; std::vector> myNullAssetData; + + // Perf: hoist AreAssetsDeployed() outside the per-TX loop. The result + // cannot change within a single ConnectBlock call (it depends only on + // chain height which is constant here), so calling it once avoids + // repeated lock acquisitions and map lookups inside the hot loop. + const bool fAssetsActive = AreAssetsDeployed(); + for (unsigned int i = 0; i < block.vtx.size(); i++) { const CTransaction &tx = *(block.vtx[i]); @@ -2535,7 +2550,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd } /** RVN START */ - if (!AreAssetsDeployed()) { + if (!fAssetsActive) { for (auto out : tx.vout) if (out.scriptPubKey.IsAssetScript()) return state.DoS(100, error("%s : Received Block with tx that contained an asset when assets wasn't active", __func__), REJECT_INVALID, "bad-txns-assets-not-active"); @@ -2543,7 +2558,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd return state.DoS(100, error("%s : Received Block with tx that contained an null asset data tx when assets wasn't active", __func__), REJECT_INVALID, "bad-txns-null-data-assets-not-active"); } - if (AreAssetsDeployed()) { + if (fAssetsActive) { std::vector> vReissueAssets; if (!Consensus::CheckTxAssets(tx, state, view, assetsCache, false, vReissueAssets, false, &setMessages, block.nTime, &myNullAssetData)) { state.SetFailedTransaction(tx.GetHash()); @@ -2580,17 +2595,19 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd CAmount assetAmount; if (prevout.scriptPubKey.IsPayToScriptHash()) { - hashBytes = uint160(std::vector (prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22)); + // Perf: use memcpy into uint160 instead of heap-allocated vector + memcpy(hashBytes.begin(), prevout.scriptPubKey.data() + 2, 20); addressType = 2; } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { - hashBytes = uint160(std::vector (prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23)); + // Perf: use memcpy into uint160 instead of heap-allocated vector + memcpy(hashBytes.begin(), prevout.scriptPubKey.data() + 3, 20); addressType = 1; } else if (prevout.scriptPubKey.IsPayToPublicKey()) { hashBytes = Hash160(prevout.scriptPubKey.begin() + 1, prevout.scriptPubKey.end() - 1); addressType = 1; } else { /** RVN START */ - if (AreAssetsDeployed()) { + if (fAssetsActive) { hashBytes.SetNull(); addressType = 0; @@ -2659,22 +2676,26 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd const CTxOut &out = tx.vout[k]; if (out.scriptPubKey.IsPayToScriptHash()) { - std::vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); + // Perf: use fixed-size uint160 instead of heap-allocated vector + uint160 hashBytes; + memcpy(hashBytes.begin(), out.scriptPubKey.data() + 2, 20); // record receiving activity - addressIndex.push_back(std::make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue)); + addressIndex.push_back(std::make_pair(CAddressIndexKey(2, hashBytes, pindex->nHeight, i, txhash, k, false), out.nValue)); // record unspent output - addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(2, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); + addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(2, hashBytes, txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { - std::vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); + // Perf: use fixed-size uint160 instead of heap-allocated vector + uint160 hashBytes; + memcpy(hashBytes.begin(), out.scriptPubKey.data() + 3, 20); // record receiving activity - addressIndex.push_back(std::make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue)); + addressIndex.push_back(std::make_pair(CAddressIndexKey(1, hashBytes, pindex->nHeight, i, txhash, k, false), out.nValue)); // record unspent output - addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(1, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); + addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(1, hashBytes, txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); } else if (out.scriptPubKey.IsPayToPublicKey()) { uint160 hashBytes(Hash160(out.scriptPubKey.begin() + 1, out.scriptPubKey.end() - 1)); @@ -2686,7 +2707,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd pindex->nHeight))); } else { /** RVN START */ - if (AreAssetsDeployed()) { + if (fAssetsActive) { std::string assetName; CAmount assetAmount; uint160 hashBytes; diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 26e0b1ca1e..65133092d5 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -267,7 +267,7 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataD LogPrintf("Using wallet %s\n", walletFile); // Wallet file must be a plain filename without a directory - if (walletFile != fs::basename(walletFile) + fs::extension(walletFile)) + if (walletFile != fs::path(walletFile).stem().string() + fs::path(walletFile).extension().string()) { errorStr = strprintf(_("Wallet %s resides outside data directory %s"), walletFile, dataDir.string()); return false; @@ -706,7 +706,7 @@ bool CWalletDBWrapper::Backup(const std::string& strDest) pathDest /= strFile; try { - fs::copy_file(pathSrc, pathDest, fs::copy_option::overwrite_if_exists); + fs::copy_file(pathSrc, pathDest, fs::copy_options::overwrite_existing); LogPrintf("copied %s to %s\n", strFile, pathDest.string()); return true; } catch (const fs::filesystem_error& e) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 36fa33ddde..9c95c7e74a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1792,6 +1792,11 @@ void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::s UniValue assetDetails(UniValue::VARR); ListTransactions(pwallet, wtx, strAccount, nMinDepth, fLong, ret, assetDetails, filter); + + // Include asset transactions in the result + for (size_t i = 0; i < assetDetails.size(); i++) { + ret.push_back(assetDetails[i]); + } } void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccount, UniValue& ret) @@ -1942,6 +1947,140 @@ UniValue listtransactions(const JSONRPCRequest& request) return ret; } +UniValue listassettransactions(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 4) + throw std::runtime_error( + "listassettransactions ( \"asset\" count skip include_watchonly )\n" + "\nReturns asset transfer transactions from the wallet.\n" + "Unlike listtransactions, this returns ONLY asset movements (no RVN-only transactions).\n" + "\nArguments:\n" + "1. \"asset\" (string, optional, default=\"*\") Filter by asset name. Use \"*\" for all assets.\n" + "2. count (numeric, optional, default=20) The number of transactions to return\n" + "3. skip (numeric, optional, default=0) The number of transactions to skip\n" + "4. include_watchonly (bool, optional, default=false) Include watch-only addresses\n" + "\nResult:\n" + "[\n" + " {\n" + " \"asset_name\": \"name\", (string) The asset name\n" + " \"asset_type\": \"type\", (string) The transaction type (e.g. transfer_asset, new_asset, reissue_asset)\n" + " \"amount\": x.xxx, (numeric) The amount transferred\n" + " \"address\": \"address\", (string) The destination address\n" + " \"category\": \"send|receive\", (string) Whether this was a send or receive from the wallet's perspective\n" + " \"vout\": n, (numeric) The output index\n" + " \"confirmations\": n, (numeric) The number of confirmations\n" + " \"blockhash\": \"hash\", (string) The block hash\n" + " \"blockindex\": n, (numeric) The block index\n" + " \"blocktime\": n, (numeric) The block time\n" + " \"txid\": \"txid\", (string) The transaction id\n" + " \"time\": n, (numeric) The transaction time\n" + " \"timereceived\": n, (numeric) The time received\n" + " \"message\": \"msg\", (string) The IPFS message if any\n" + " }\n" + "]\n" + "\nExamples:\n" + "\nList all asset transactions\n" + + HelpExampleCli("listassettransactions", "") + + "\nList transactions for a specific asset\n" + + HelpExampleCli("listassettransactions", "\"MYASSET\"") + + "\nList 50 transactions, skipping the first 10\n" + + HelpExampleCli("listassettransactions", "\"*\" 50 10") + ); + + ObserveSafeMode(); + LOCK2(cs_main, pwallet->cs_wallet); + + std::string assetFilter = "*"; + if (request.params.size() > 0 && !request.params[0].isNull()) + assetFilter = request.params[0].get_str(); + int nCount = 20; + if (request.params.size() > 1 && !request.params[1].isNull()) { + if (request.params[1].isNum()) + nCount = request.params[1].get_int(); + else + nCount = atoi(request.params[1].get_str().c_str()); + } + int nFrom = 0; + if (request.params.size() > 2 && !request.params[2].isNull()) { + if (request.params[2].isNum()) + nFrom = request.params[2].get_int(); + else + nFrom = atoi(request.params[2].get_str().c_str()); + } + isminefilter filter = ISMINE_SPENDABLE; + if (request.params.size() > 3 && !request.params[3].isNull()) + if (request.params[3].get_bool()) + filter = filter | ISMINE_WATCH_ONLY; + + if (nCount < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count"); + if (nFrom < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from"); + + UniValue ret(UniValue::VARR); + + const CWallet::TxItems & txOrdered = pwallet->wtxOrdered; + + // Iterate backwards through all wallet transactions + for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) + { + CWalletTx *const pwtx = (*it).second.first; + if (pwtx == nullptr) + continue; + + // Get asset entries using the asset-aware overload + UniValue rvnEntries(UniValue::VARR); + UniValue assetEntries(UniValue::VARR); + ListTransactions(pwallet, *pwtx, "*", 0, true, rvnEntries, assetEntries, filter); + + // Add matching asset entries to result + for (size_t i = 0; i < assetEntries.size(); i++) { + const UniValue& entry = assetEntries[i]; + + // Apply asset name filter + if (assetFilter != "*") { + std::string entryAssetName = entry["asset_name"].get_str(); + if (entryAssetName != assetFilter) + continue; + } + + ret.push_back(entry); + } + + if ((int)ret.size() >= (nCount + nFrom)) + break; + } + + // Apply skip and count + if (nFrom > (int)ret.size()) + nFrom = ret.size(); + if ((nFrom + nCount) > (int)ret.size()) + nCount = ret.size() - nFrom; + + std::vector arrTmp = ret.getValues(); + + std::vector::iterator first = arrTmp.begin(); + std::advance(first, nFrom); + std::vector::iterator last = arrTmp.begin(); + std::advance(last, nFrom + nCount); + + if (last != arrTmp.end()) arrTmp.erase(last, arrTmp.end()); + if (first != arrTmp.begin()) arrTmp.erase(arrTmp.begin(), first); + + std::reverse(arrTmp.begin(), arrTmp.end()); // Return oldest to newest + + ret.clear(); + ret.setArray(); + ret.push_backV(arrTmp); + + return ret; +} + UniValue listaccounts(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -2870,6 +3009,156 @@ UniValue listwallets(const JSONRPCRequest& request) return obj; } +extern CScheduler* pScheduler; // defined in init.cpp + +UniValue createwallet(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) + throw std::runtime_error( + "createwallet \"wallet_name\"\n" + "\nCreates and loads a new wallet.\n" + "\nArguments:\n" + "1. \"wallet_name\" (string, required) The name for the new wallet.\n" + " The wallet is created in the data directory.\n" + "\nResult:\n" + "{\n" + " \"name\": \"wallet_name\", (string) The wallet name\n" + " \"warning\": \"...\", (string) Any warnings\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("createwallet", "\"mywallet\"") + + HelpExampleRpc("createwallet", "\"mywallet\"") + ); + + std::string walletName = request.params[0].get_str(); + + // Check if wallet name is valid + if (walletName.empty()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Wallet name cannot be empty"); + + // Check for path separators + if (walletName.find('/') != std::string::npos || walletName.find('\\') != std::string::npos) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Wallet name cannot contain path separators"); + + // Check if already loaded + for (CWalletRef pwallet : vpwallets) { + if (pwallet->GetName() == walletName) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet \"" + walletName + "\" is already loaded"); + } + } + + // Create the wallet + CWallet* pwallet = CWallet::CreateWalletFromFile(walletName); + if (!pwallet) { + throw JSONRPCError(RPC_WALLET_ERROR, "Failed to create wallet \"" + walletName + "\""); + } + + vpwallets.push_back(pwallet); + + // Run post-init if scheduler is available + if (pScheduler) { + pwallet->postInitProcess(*pScheduler); + } + + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("name", pwallet->GetName())); + obj.push_back(Pair("warning", "")); + return obj; +} + +UniValue loadwallet(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "loadwallet \"filename\"\n" + "\nLoads a wallet from a wallet file.\n" + "Note that all wallet command-line options used when starting ravend\n" + "will be applied to the new wallet.\n" + "\nArguments:\n" + "1. \"filename\" (string, required) The wallet file name (in the data directory)\n" + "\nResult:\n" + "{\n" + " \"name\": \"wallet_name\", (string) The wallet name\n" + " \"warning\": \"...\", (string) Any warnings\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("loadwallet", "\"wallet2.dat\"") + + HelpExampleRpc("loadwallet", "\"wallet2.dat\"") + ); + + std::string walletFile = request.params[0].get_str(); + + if (walletFile.empty()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Wallet filename cannot be empty"); + + // Check if already loaded + for (CWalletRef pw : vpwallets) { + if (pw->GetName() == walletFile) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet \"" + walletFile + "\" is already loaded"); + } + } + + // Load the wallet + CWallet* pwallet = CWallet::CreateWalletFromFile(walletFile); + if (!pwallet) { + throw JSONRPCError(RPC_WALLET_ERROR, "Failed to load wallet \"" + walletFile + "\""); + } + + vpwallets.push_back(pwallet); + + if (pScheduler) { + pwallet->postInitProcess(*pScheduler); + } + + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("name", pwallet->GetName())); + obj.push_back(Pair("warning", "")); + return obj; +} + +UniValue unloadwallet(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "unloadwallet \"wallet_name\"\n" + "\nUnloads a wallet.\n" + "The wallet must be currently loaded. The default wallet cannot be unloaded.\n" + "\nArguments:\n" + "1. \"wallet_name\" (string, required) The name of the wallet to unload\n" + "\nExamples:\n" + + HelpExampleCli("unloadwallet", "\"mywallet\"") + + HelpExampleRpc("unloadwallet", "\"mywallet\"") + ); + + std::string walletName = request.params[0].get_str(); + + // Don't allow unloading the last wallet + if (vpwallets.size() <= 1) + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot unload the only loaded wallet"); + + // Find and remove the wallet + CWallet* target = nullptr; + auto it = vpwallets.begin(); + for (; it != vpwallets.end(); ++it) { + if ((*it)->GetName() == walletName) { + target = *it; + break; + } + } + + if (!target) + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet \"" + walletName + "\" not found"); + + // Flush and close + target->Flush(true); + UnregisterValidationInterface(target); + vpwallets.erase(it); + delete target; + + UniValue obj(UniValue::VOBJ); + return obj; +} + UniValue resendwallettransactions(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -3562,8 +3851,12 @@ static const CRPCCommand commands[] = { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, { "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} }, + { "wallet", "listassettransactions", &listassettransactions, {"asset","count","skip","include_watchonly"} }, { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, { "wallet", "listwallets", &listwallets, {} }, + { "wallet", "createwallet", &createwallet, {"wallet_name"} }, + { "wallet", "loadwallet", &loadwallet, {"filename"} }, + { "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} }, { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} }, { "wallet", "move", &movecmd, {"fromaccount","toaccount","amount","minconf","comment"} }, { "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 70bcb8f5ca..f29ed38a2b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1660,6 +1660,25 @@ void CWalletTx::GetAmounts(std::list& listReceived, nFee = nDebit - nValueOut; } + // Track which assets this wallet contributed as inputs (for multi-wallet support) + bool fIsFromMe = (nDebit > 0); + std::set myAssetInputs; + if (fIsFromMe) { + for (const auto& txin : tx->vin) { + const CWalletTx* prevTx = pwallet->GetWalletTx(txin.prevout.hash); + if (prevTx && txin.prevout.n < prevTx->tx->vout.size()) { + const CTxOut& prevOut = prevTx->tx->vout[txin.prevout.n]; + if (!(pwallet->IsMine(prevOut) & filter)) + continue; + if (prevOut.scriptPubKey.IsAssetScript()) { + CAssetOutputEntry prevAsset; + if (GetAssetData(prevOut.scriptPubKey, prevAsset)) + myAssetInputs.insert(prevAsset.assetName); + } + } + } + } + // Sent/received. for (unsigned int i = 0; i < tx->vout.size(); ++i) { @@ -1682,7 +1701,6 @@ void CWalletTx::GetAmounts(std::list& listReceived, if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) { - LogPrintf("%s: Failing on the %d tx\n", __func__, i); LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", this->GetHash().ToString()); address = CNoDestination(); @@ -1691,8 +1709,8 @@ void CWalletTx::GetAmounts(std::list& listReceived, if (!txout.scriptPubKey.IsAssetScript()) { COutputEntry output = {address, txout.nValue, (int) i}; - // If we are debited by the transaction, add the output as a "sent" entry - if (nDebit > 0) + // Mark as "sent" if we debited and output is not ours + if (nDebit > 0 && !(fIsMine & filter)) listSent.push_back(output); // If we are receiving the output, add it as a "received" entry @@ -1701,19 +1719,21 @@ void CWalletTx::GetAmounts(std::list& listReceived, } /** RVN START */ - if (AreAssetsDeployed()) { - if (txout.scriptPubKey.IsAssetScript()) { - CAssetOutputEntry assetoutput; - assetoutput.vout = i; - GetAssetData(txout.scriptPubKey, assetoutput); - - // The only asset type we send is transfer_asset. We need to skip all other types for the sent category - if (nDebit > 0 && assetoutput.type == TX_TRANSFER_ASSET) - assetsSent.emplace_back(assetoutput); - - if (fIsMine & filter) - assetsReceived.emplace_back(assetoutput); + if (AreAssetsDeployed() && txout.scriptPubKey.IsAssetScript()) { + CAssetOutputEntry assetoutput; + assetoutput.vout = i; + GetAssetData(txout.scriptPubKey, assetoutput); + + // Only mark as "sent" if this wallet owned an input of this asset + if (fIsFromMe && assetoutput.type == TX_TRANSFER_ASSET + && !(fIsMine & filter) + && myAssetInputs.count(assetoutput.assetName)) + { + assetsSent.emplace_back(assetoutput); } + + if (fIsMine & filter) + assetsReceived.emplace_back(assetoutput); } /** RVN END */ }