diff --git a/.gitignore b/.gitignore index 5a1551353..21f3cf4fd 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,5 @@ dist/ osx_volname dist/ src/test/runtest.sh +configure~ +src/dashbls/configure~ diff --git a/configure.ac b/configure.ac index 8487c0cd4..ba32e900f 100644 --- a/configure.ac +++ b/configure.ac @@ -411,7 +411,7 @@ if test "$CXXFLAGS_overridden" = "no"; then AX_CHECK_COMPILE_FLAG([-Wduplicated-branches], [WARN_CXXFLAGS="$WARN_CXXFLAGS -Wduplicated-branches"], [], [$CXXFLAG_WERROR]) AX_CHECK_COMPILE_FLAG([-Wduplicated-cond], [WARN_CXXFLAGS="$WARN_CXXFLAGS -Wduplicated-cond"], [], [$CXXFLAG_WERROR]) AX_CHECK_COMPILE_FLAG([-Wlogical-op], [WARN_CXXFLAGS="$WARN_CXXFLAGS -Wlogical-op"], [], [$CXXFLAG_WERROR]) - AX_CHECK_COMPILE_FLAG([-Woverloaded-virtual], [WARN_CXXFLAGS="$WARN_CXXFLAGS -Woverloded-virtual"], [], [$CXXFLAG_WERROR]) + AX_CHECK_COMPILE_FLAG([-Woverloaded-virtual], [WARN_CXXFLAGS="$WARN_CXXFLAGS -Woverloaded-virtual"], [], [$CXXFLAG_WERROR]) dnl -Wsuggest-override is broken with GCC before 9.2 dnl https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78010 AX_CHECK_COMPILE_FLAG([-Wsuggest-override], [WARN_CXXFLAGS="$WARN_CXXFLAGS -Wsuggest-override"], [], [$CXXFLAG_WERROR], diff --git a/src/Makefile.am b/src/Makefile.am index 981e52789..759d94671 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -729,6 +729,7 @@ libraptoreum_util_a_SOURCES = \ threadinterrupt.cpp \ util/bip32.cpp \ util/system.cpp \ + bootstrap/bootstrapmanager.cpp \ util/asmap.cpp \ util/moneystr.cpp \ util/spanparsing.cpp \ @@ -779,7 +780,7 @@ raptoreumd_LDADD = \ $(LIBMEMENV) \ $(LIBSECP256K1) -raptoreumd_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) $(LIBDASHBLS) $(GMP_LIBS) +raptoreumd_LDADD += $(BACKTRACE_LIB) $(BOOST_LIBS) $(BDB_LIBS) $(MINIUPNPC_LIBS) $(NATPMP_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) $(LIBDASHBLS) $(GMP_LIBS) -lcurl -larchive # raptoreum-cli binary # raptoreum_cli_SOURCES = raptoreum-cli.cpp diff --git a/src/bootstrap/bootstrapmanager.cpp b/src/bootstrap/bootstrapmanager.cpp new file mode 100644 index 000000000..2b3f406c4 --- /dev/null +++ b/src/bootstrap/bootstrapmanager.cpp @@ -0,0 +1,117 @@ +#include "bootstrapmanager.h" +#include +#include +#include +#include + +static size_t WriteCallback(void* ptr, size_t size, size_t nmemb, FILE* stream) { + return fwrite(ptr, size, nmemb, stream); +} + +bool BootstrapManager::DownloadFile(const std::string& url, const std::string& dest, ProgressCallback onProgress) { + CURL* curl = curl_easy_init(); + if (!curl) return false; + + FILE* fp = fopen(dest.c_str(), "wb"); + if (!fp) { + curl_easy_cleanup(curl); + return false; + } + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + CURLcode res = curl_easy_perform(curl); + fclose(fp); + curl_easy_cleanup(curl); + + return (res == CURLE_OK); +} + +bool BootstrapManager::ExtractArchive(const std::string& archivePath, const std::string& destDir, ProgressCallback onProgress) { + struct archive* a = archive_read_new(); + struct archive* ext = archive_write_disk_new(); + struct archive_entry* entry; + + archive_read_support_format_zip(a); + archive_read_support_format_tar(a); + archive_read_support_filter_xz(a); + + if (archive_read_open_filename(a, archivePath.c_str(), 10240) != ARCHIVE_OK) { + return false; + } + + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + // Zieldpfad setzen + std::string fullPath = destDir + "/" + archive_entry_pathname(entry); + archive_entry_set_pathname(entry, fullPath.c_str()); + archive_write_header(ext, entry); + + const void* buff; + size_t size; + la_int64_t offset; + while (archive_read_data_block(a, &buff, &size, &offset) == ARCHIVE_OK) { + archive_write_data_block(ext, buff, size, offset); + } + } + + archive_read_close(a); + archive_read_free(a); + archive_write_close(ext); + archive_write_free(ext); + + return true; +} + +bool BootstrapManager::RunIfNeeded(const std::string& dataDir, ProgressCallback onProgress) { + + // Schritt 1: Bootstrap-Prüfung ankündigen + onProgress("Checking if bootstrap is needed...", 0); + + // Schritt 2: Prüfen ob blocks/ Ordner fehlt + if (!IsFirstRun(dataDir)) { + onProgress("Bootstrap not needed, blocks directory already exists.", 100); + return true; + } + + onProgress("No blockchain data found, starting bootstrap download...", 5); + + // Schritt 3: Download + onProgress("Downloading bootstrap.zip from bootstrap.raptoreum.com...", 10); + std::string zipPath = dataDir + "/bootstrap.zip"; + if (!DownloadFile("https://bootstrap.raptoreum.com/bootstraps/bootstrap.zip", zipPath, onProgress)) { + // Schritt 4: Server nicht erreichbar + onProgress("ERROR: Bootstrap server not reachable or download failed!", -1); + return false; + } + onProgress("Download complete.", 70); + + // Schritt 5: Entpacken + onProgress("Extracting bootstrap.zip...", 75); + if (!ExtractArchive(zipPath, dataDir, onProgress)) { + onProgress("ERROR: Extraction of bootstrap.zip failed!", -1); + return false; + } + onProgress("Extraction complete.", 85); + + // powcache.dat laden + onProgress("Downloading powcache.dat...", 90); + if (!DownloadFile("https://bootstrap.raptoreum.com/bootstraps/powcache.dat", + dataDir + "/powcache.dat", onProgress)) { + onProgress("WARNING: Could not download powcache.dat, continuing without it.", 95); + } else { + onProgress("powcache.dat downloaded successfully.", 95); + } + + onProgress("Bootstrap complete! Starting normal sync...", 100); + return true; +} + +bool BootstrapManager::IsFirstRun(const std::string& dataDir) { + boost::filesystem::path blockFile(dataDir); + blockFile /= "blocks/blk00000.dat"; + // Wenn diese Datei fehlt, ist die Chain leer = Bootstrap nötig + return !boost::filesystem::exists(blockFile); +} \ No newline at end of file diff --git a/src/bootstrap/bootstrapmanager.h b/src/bootstrap/bootstrapmanager.h new file mode 100644 index 000000000..f68c22477 --- /dev/null +++ b/src/bootstrap/bootstrapmanager.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +// Callback types for progress bar +using ProgressCallback = std::function; + +class BootstrapManager { +public: + // Returns true if bootstrap was necessary and successful, false if not needed or failed + static bool RunIfNeeded(const std::string& dataDir, ProgressCallback onProgress); + +private: + static bool IsFirstRun(const std::string& dataDir); + static bool DownloadFile(const std::string& url, const std::string& dest, ProgressCallback onProgress); + static bool ExtractArchive(const std::string& archivePath, const std::string& destDir, ProgressCallback onProgress); +}; \ No newline at end of file diff --git a/src/init.cpp b/src/init.cpp index d96fae79c..d3f83b12a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -1836,6 +1837,21 @@ bool AppInitMain(const util::Ref &context, NodeContext &node, interfaces::BlockA LogPrintf("Config file: %s not found, skipping\n", config_file_path.string()); } + // ********************************************************* Bootstrap + { + std::string dataDir = GetDataDir().string(); + BootstrapManager::RunIfNeeded(dataDir, [](const std::string& status, int percent) { + if (percent < 0) { + // Fehler + LogPrintf("Bootstrap ERROR: %s\n", status); + } else { + LogPrintf("Bootstrap [%d%%]: %s\n", percent, status); + } + }); + } + // ********************************************************* End Bootstrap + + LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD); // Warn about relative -datadir path. diff --git a/src/rpc/rpcassets.cpp b/src/rpc/rpcassets.cpp index 2dc6eff22..5b3ec7d24 100644 --- a/src/rpc/rpcassets.cpp +++ b/src/rpc/rpcassets.cpp @@ -72,6 +72,7 @@ UniValue createasset(const JSONRPCRequest &request) { " \"issueFrequency:\" (numeric) mint specific amount of token every x blocks\n" " \"amount:\" (numeric, (max 500 for unique) amount to distribute each time if type is not manual.\n" " \"ownerAddress:\" (string) address that this asset is owned by. Only key holder of this address will be able to mint new tokens\n" + " \"feeAddress:\" (string, optional) address that pays the burn fee and receives change\n" "}\n" "\nResult:\n" "\"txid\" (string) The transaction id for the new asset\n" @@ -255,6 +256,37 @@ UniValue createasset(const JSONRPCRequest &request) { std::string strFailReason; std::vector vecSend; CCoinControl coinControl; + const UniValue &feeAddressObj = find_value(asset, "feeAddress"); + + if (!feeAddressObj.isNull()) { + std::string feeAddrStr = feeAddressObj.get_str(); + CTxDestination feeDest = DecodeDestination(feeAddrStr); + + if (!IsValidDestination(feeDest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid feeAddress"); + } + + coinControl.destChange = feeDest; + + std::vector vAvailableCoins; + pwallet->AvailableCoins(vAvailableCoins, true, &coinControl); + + bool foundCoins = false; + for (const COutput& out : vAvailableCoins) { + CTxDestination address; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address) && address == feeDest) { + coinControl.Select(COutPoint(out.tx->GetHash(), out.i)); + foundCoins = true; + } + } + + if (!foundCoins) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Error: No spendable UTXOs found on the specified feeAddress"); + } + + coinControl.fAllowOtherInputs = false; + } + assetTx.fee = getAssetsFees(); int Payloadsize; @@ -292,7 +324,7 @@ UniValue updateasset(const JSONRPCRequest &request) { request.params.size() > 1) throw std::runtime_error( "updateasset asset_metadata\n" - "Create a new asset\n" + "Update an existing asset\n" "\nArguments:\n" "1. \"asset\" (string, required) A json object with asset metadata\n" "{\n" @@ -305,6 +337,7 @@ UniValue updateasset(const JSONRPCRequest &request) { " \"issueFrequency:\" (numeric, optional) mint specific amount of token every x blocks\n" " \"amount:\" (numeric, optional) amount to distribute each time if type is not manual.\n" " \"ownerAddress:\" (string, optional) address that this asset is owned by. Only key holder of this address will be able to mint new tokens\n" + " \"feeAddress:\" (string, optional) address that pays the burn fee and receives RTM change\n" "}\n" "\nResult:\n" "\"txid\" (string) The transaction hash\n" @@ -313,7 +346,7 @@ UniValue updateasset(const JSONRPCRequest &request) { + HelpExampleCli("updateasset", "'{\"name\":\"test asset\", \"updatable\":true, \"maxMintCount\":10, \"referenceHash\":\"\"\n," "\"type\":0, \"targetAddress\":\"yQPzaDmnF3FtRsoWijUN7aZDcEdyNAcmVk\", \"issueFrequency\":0\n," - "\"amount\":10000,\"ownerAddress\":\"yRyiTCKfqMG2dQ9oUvs932TjN1R1MNUTWM\"}'") + "\"amount\":10000,\"ownerAddress\":\"yRyiTCKfqMG2dQ9oUvs932TjN1R1MNUTWM\",\"feeAddress\":\"yRyiTCKfqMG2dQ9oUvs932TjN1R1MNUTWM\"}'") ); if (getAssetsFees() == 0) { @@ -473,6 +506,38 @@ UniValue updateasset(const JSONRPCRequest &request) { std::string strFailReason; std::vector vecSend; assetTx.fee = getAssetsFees(); + + // feeAddress: optional parameter to control which address pays the fee and receives RTM change + const UniValue &feeAddressObj = find_value(asset, "feeAddress"); + if (!feeAddressObj.isNull()) { + std::string feeAddrStr = feeAddressObj.get_str(); + CTxDestination feeDest = DecodeDestination(feeAddrStr); + + if (!IsValidDestination(feeDest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid feeAddress"); + } + + coinControl.destChange = feeDest; + + std::vector vAvailableCoins; + pwallet->AvailableCoins(vAvailableCoins, true, &coinControl); + + bool foundCoins = false; + for (const COutput& out : vAvailableCoins) { + CTxDestination address; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address) && address == feeDest) { + coinControl.Select(COutPoint(out.tx->GetHash(), out.i)); + foundCoins = true; + } + } + + if (!foundCoins) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Error: No spendable UTXOs found on the specified feeAddress"); + } + + coinControl.fAllowOtherInputs = false; + } + int Payloadsize; if (!pwallet->CreateTransaction(vecSend, newTx, nFee, nChangePos, strFailReason, coinControl, true, Payloadsize, @@ -503,20 +568,19 @@ UniValue updateasset(const JSONRPCRequest &request) { UniValue mintasset(const JSONRPCRequest &request) { if (request.fHelp || !Updates().IsAssetsActive(::ChainActive().Tip()) || request.params.size() < 1 || - request.params.size() > 1) + request.params.size() > 2) throw std::runtime_error( - "mintasset txid\n" - "Mint assset\n" + "mintasset \"asset_id\" (\"fee_address\")\n" + "Mint asset\n" "\nArguments:\n" - "1. \"txid\" (string, required) asset txid reference\n" + "1. \"asset_id\" (string, required) asset id reference\n" + "2. \"fee_address\" (string, optional) address that pays the burn fee and receives RTM change\n" "\nResult:\n" "\"txid\" (string) The transaction id for the new issued asset\n" "\nExamples:\n" - + HelpExampleCli("mintasset", "773cf7e057127048711d16839e4612ffb0f1599aef663d96e60f5190eb7de9a9") - + HelpExampleCli("mintasset", - "773cf7e057127048711d16839e4612ffb0f1599aef663d96e60f5190eb7de9a9" "yZBvV16YFvPx11qP2XhCRDi7y2e1oSMpKH" "1000") - + + HelpExampleCli("mintasset", "\"773cf7e057127048711d16839e4612ffb0f1599aef663d96e60f5190eb7de9a9\"") + + HelpExampleCli("mintasset", "\"773cf7e057127048711d16839e4612ffb0f1599aef663d96e60f5190eb7de9a9\" \"yZBvV16YFvPx11qP2XhCRDi7y2e1oSMpKH\"") ); if (getAssetsFees() == 0) { @@ -571,6 +635,36 @@ UniValue mintasset(const JSONRPCRequest &request) { std::string strFailReason; std::vector vecSend; + // feeAddress: optional second parameter to control which address pays the fee and receives RTM change + if (request.params.size() > 1 && !request.params[1].isNull()) { + std::string feeAddrStr = request.params[1].get_str(); + CTxDestination feeDest = DecodeDestination(feeAddrStr); + + if (!IsValidDestination(feeDest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid fee_address"); + } + + coinControl.destChange = feeDest; + + std::vector vAvailableCoins; + pwallet->AvailableCoins(vAvailableCoins, true, &coinControl); + + bool foundCoins = false; + for (const COutput& out : vAvailableCoins) { + CTxDestination address; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address) && address == feeDest) { + coinControl.Select(COutPoint(out.tx->GetHash(), out.i)); + foundCoins = true; + } + } + + if (!foundCoins) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Error: No spendable UTXOs found on the specified fee_address"); + } + + coinControl.fAllowOtherInputs = false; + } + if (tmpAsset.isUnique) { //build unique output using current supply as start unique id uint64_t id = tmpAsset.circulatingSupply; @@ -610,9 +704,9 @@ UniValue mintasset(const JSONRPCRequest &request) { UniValue sendasset(const JSONRPCRequest &request) { if (request.fHelp || !Updates().IsAssetsActive(::ChainActive().Tip()) || request.params.size() < 3 || - request.params.size() > 7) + request.params.size() > 6) throw std::runtime_error( - "sendasset \"asset_id\" \"qty\" \"to_address\" \"change_address\" \"asset_change_address\"\n" + "sendasset \"asset_id\" \"qty\" \"to_address\" (\"change_address\") (\"asset_change_address\") (\"fee_address\")\n" "\nTransfers a quantity of an owned asset to a given address" "\nArguments:\n" @@ -621,6 +715,7 @@ UniValue sendasset(const JSONRPCRequest &request) { "3. \"to_address\" (string, required) address to send the asset to\n" "4. \"change_address\" (string, optional, default = \"\") the transactions RTM change will be sent to this address\n" "5. \"asset_change_address\" (string, optional, default = \"\") the transactions Asset change will be sent to this address\n" + "6. \"fee_address\" (string, optional, default = \"\") address that provides RTM inputs for the fee (only UTXOs from this address will be used)\n" "\nResult:\n" "txid" @@ -629,8 +724,8 @@ UniValue sendasset(const JSONRPCRequest &request) { "]\n" "\nExamples:\n" - + HelpExampleCli("transfer", "\"ASSET_NAME\" 20 \"address\"") - + HelpExampleCli("transfer", "\"ASSET_NAME\" 20 \"address\"") + + HelpExampleCli("sendasset", "\"ASSET_NAME\" 20 \"to_address\"") + + HelpExampleCli("sendasset", "\"ASSET_NAME\" 20 \"to_address\" \"change_address\" \"asset_change_address\" \"fee_address\"") ); std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); @@ -701,6 +796,41 @@ UniValue sendasset(const JSONRPCRequest &request) { coinControl.destChange = change_dest; coinControl.assetDestChange = asset_change_dest; + // fee_address: optional 6th parameter - only UTXOs from this address will be used for RTM fees + if (request.params.size() > 5 && !request.params[5].isNull()) { + std::string feeAddrStr = request.params[5].get_str(); + if (!feeAddrStr.empty()) { + CTxDestination feeDest = DecodeDestination(feeAddrStr); + + if (!IsValidDestination(feeDest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Error: Invalid fee_address"); + } + + // If no explicit change_address was given, use fee_address as change destination too + if (change_address.empty()) { + coinControl.destChange = feeDest; + } + + std::vector vAvailableCoins; + pwallet->AvailableCoins(vAvailableCoins, true, &coinControl); + + bool foundCoins = false; + for (const COutput& out : vAvailableCoins) { + CTxDestination address; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address) && address == feeDest) { + coinControl.Select(COutPoint(out.tx->GetHash(), out.i)); + foundCoins = true; + } + } + + if (!foundCoins) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Error: No spendable UTXOs found on the specified fee_address"); + } + + coinControl.fAllowOtherInputs = false; + } + } + CTransactionRef wtx; CAmount nFee; int nChangePos = -1; @@ -794,6 +924,7 @@ UniValue getassetdetailsbyid(const JSONRPCRequest &request) { return result; } +#ifdef ENABLE_WALLET UniValue listassetsbalance(const JSONRPCRequest &request) { if (request.fHelp || !Updates().IsAssetsActive(::ChainActive().Tip()) || request.params.size() > 0) throw std::runtime_error( @@ -849,6 +980,8 @@ UniValue listassetsbalance(const JSONRPCRequest &request) { return result; } +#endif // ENABLE_WALLET +#ifdef ENABLE_WALLET UniValue listunspentassets(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 5) @@ -1043,6 +1176,7 @@ UniValue listunspentassets(const JSONRPCRequest& request) return results; } +#endif // ENABLE_WALLET UniValue listassets(const JSONRPCRequest &request) { RPCHelpMan{"listassets", @@ -1308,4 +1442,4 @@ void RegisterAssetsRPCCommands(CRPCTable &tableRPC) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]); } -} +} \ No newline at end of file diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index 73e00681c..577278518 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #if defined(HAVE_CONFIG_H) diff --git a/src/util/bip32.h b/src/util/bip32.h index c2645e186..91121914f 100644 --- a/src/util/bip32.h +++ b/src/util/bip32.h @@ -8,6 +8,7 @@ #include #include #include +#include /** Parse an HD keypaths like "m/7/0'/2000". */ [[nodiscard]] bool ParseHDKeypath(const std::string &keypath_str, std::vector &keypath);