diff --git a/.gitmodules b/.gitmodules index 7e00da228..68faaf9fd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "external/miniupnp"] path = external/miniupnp url = https://github.com/miniupnp/miniupnp +[submodule "external/mx25519"] + path = external/mx25519 + url = https://github.com/tevador/mx25519 diff --git a/README.md b/README.md index aa8788b59..9b5137d1e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Salvium Zero v0.9.0-rc10 +# Salvium Zero v0.9.3 Copyright (c) 2023-2024, Salvium Portions Copyright (c) 2014-2023, The Monero Project @@ -172,7 +172,7 @@ invokes cmake commands as needed. ```bash cd salvium - git checkout v0.9.0 + git checkout v0.9.3 make ``` @@ -251,7 +251,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch ( ```bash git clone https://github.com/salvium/salvium cd salvium - git checkout v0.9.0 + git checkout v0.9.3 ``` * Build: @@ -370,10 +370,10 @@ application. cd salvium ``` -* If you would like a specific [version/tag](https://github.com/salvium/salvium/tags), do a git checkout for that version. eg. 'v0.9.0'. If you don't care about the version and just want binaries from master, skip this step: +* If you would like a specific [version/tag](https://github.com/salvium/salvium/tags), do a git checkout for that version. eg. 'v0.9.3'. If you don't care about the version and just want binaries from master, skip this step: ```bash - git checkout v0.9.0 + git checkout v0.9.3 ``` * If you are on a 64-bit system, run: diff --git a/external/mx25519 b/external/mx25519 new file mode 160000 index 000000000..84ca1290f --- /dev/null +++ b/external/mx25519 @@ -0,0 +1 @@ +Subproject commit 84ca1290fa344351c95692f20f41a174b70e0c7b diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index e290832ab..f9b5d3b80 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -215,7 +215,7 @@ namespace * yield_block_data block height {slippage_coins, locked_coins, lc_total, network_health} * yield_tx_data block height {txn hash, locked_coins, return_address} * - * audit_data block height {locked_coins, lc_total} + * audit_block_data block height {locked_coins, lc_total} * audit_tx_data block height {txn hash, locked_coins, return_address} * * Note: where the data items are of uniform size, DUPFIXED tables have @@ -3778,6 +3778,11 @@ std::map BlockchainLMDB::get_circulating_supply() const //amount += m_coinbase; } + if (amount < 0) { + // Negative number can't be converted to a 64-bit UINT, so return 0, but retain -ve number privately + LOG_PRINT_L2("BlockchainLMDB::" << __func__ << " - supply of " << currency_label << " is negative (" << amount << ") but outputting zero"); + amount = 0; + } circulating_supply[currency_label] = amount.convert_to(); } diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index c09de09a1..5f35d9c9e 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -142,17 +142,23 @@ set(blockchain_scanner_private_headers) monero_private_headers(blockchain_scanner ${blockchain_scanner_private_headers}) -if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp" AND NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp") - set(blockchain_audit_sources - blockchain_audit.cpp - ) - - set(blockchain_audit_private_headers) - - monero_private_headers(blockchain_audit - ${blockchain_audit_private_headers}) +if (BUILD_TAG) else() - message(FATAL_ERROR "blockchain_audit.cpp not found - not building the audit tool") + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp" AND NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp") + set(blockchain_audit_sources + blockchain_audit.cpp + threadpool_boost.cpp + ) + + set(blockchain_audit_private_headers + threadpool_boost.h + ) + + monero_private_headers(blockchain_audit + ${blockchain_audit_private_headers}) + else() + message(STATUS "blockchain_audit.cpp not found - not building the audit tool") + endif() endif() monero_add_executable(blockchain_import @@ -324,30 +330,36 @@ set_property(TARGET blockchain_scanner OUTPUT_NAME "salvium-blockchain-scanner") install(TARGETS blockchain_scanner DESTINATION bin) -if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp" AND NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp") - monero_add_executable(blockchain_audit - ${blockchain_audit_sources} - ${blockchain_audit_private_headers}) +if (BUILD_TAG) +else() + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp" AND NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp") + monero_add_executable(blockchain_audit + ${blockchain_audit_sources} + ${blockchain_audit_private_headers}) - target_link_libraries(blockchain_audit - PRIVATE - wallet - crypto - cncrypto - cryptonote_core - blockchain_db - version - epee - ${Boost_FILESYSTEM_LIBRARY} - ${Boost_SYSTEM_LIBRARY} - ${Boost_THREAD_LIBRARY} - ${CMAKE_THREAD_LIBS_INIT} - ${EXTRA_LIBRARIES}) + target_include_directories(blockchain_audit PRIVATE /usr/include/mysql-cppconn/jdbc) + + target_link_libraries(blockchain_audit + PRIVATE + wallet + crypto + cncrypto + cryptonote_core + blockchain_db + version + epee + mysqlcppconn + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) - set_property(TARGET blockchain_audit - PROPERTY - OUTPUT_NAME "salvium-blockchain-audit") - install(TARGETS blockchain_audit DESTINATION bin) + set_property(TARGET blockchain_audit + PROPERTY + OUTPUT_NAME "salvium-blockchain-audit") + install(TARGETS blockchain_audit DESTINATION bin) + endif() endif() monero_add_executable(blockchain_stats diff --git a/src/blockchain_utilities/threadpool_boost.cpp b/src/blockchain_utilities/threadpool_boost.cpp new file mode 100644 index 000000000..264d1d97a --- /dev/null +++ b/src/blockchain_utilities/threadpool_boost.cpp @@ -0,0 +1,44 @@ +#include +#include "threadpool_boost.h" + +ThreadPool::ThreadPool(size_t numThreads) + : workGuard(boost::asio::make_work_guard(ioService)) { + for (size_t i = 0; i < numThreads; ++i) { + workers.emplace_back([this]() { + std::cerr << "Thread started" << std::endl; + ioService.run(); + std::cerr << "Thread finished" << std::endl; + }); + } +} + +void ThreadPool::enqueue(std::function task) { + ioService.post([task]() { + try { + task(); // Run the task + } catch (const std::exception& e) { + std::cerr << "Exception in thread pool task: " << e.what() << std::endl; + } catch (...) { + std::cerr << "Unknown exception in thread pool task!" << std::endl; + } + }); +} + +bool ThreadPool::isStopping() const { + return ioService.stopped(); // Check if io_context has stopped +} + +void ThreadPool::waitForCompletion() { + std::cout << "Waiting for completion...\n"; + workGuard.reset(); // Allow ioService to stop when no more tasks + ioService.run(); // Ensure no threads are left hanging + + for (auto &worker : workers) { + if (worker.joinable()) worker.join(); + } + std::cout << "All threads joined.\n"; +} + +ThreadPool::~ThreadPool() { + waitForCompletion(); +} diff --git a/src/blockchain_utilities/threadpool_boost.h b/src/blockchain_utilities/threadpool_boost.h new file mode 100644 index 000000000..bbcc01704 --- /dev/null +++ b/src/blockchain_utilities/threadpool_boost.h @@ -0,0 +1,24 @@ +#ifndef THREADPOOL_BOOST_H +#define THREADPOOL_BOOST_H + +#include +#include +#include +#include + +class ThreadPool { +public: + explicit ThreadPool(size_t numThreads); + ~ThreadPool(); + + void enqueue(std::function task); + bool isStopping() const; + void waitForCompletion(); + +private: + boost::asio::io_service ioService; + boost::asio::executor_work_guard workGuard; + std::vector workers; +}; + +#endif // THREADPOOL_BOOST_H diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index af48252b9..e57dd38a4 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -231,6 +231,8 @@ #define HF_VERSION_AUDIT1 6 #define HF_VERSION_SALVIUM_ONE_PROOFS 6 +#define HF_VERSION_AUDIT1_PAUSE 7 + #define HF_VERSION_REQUIRE_VIEW_TAGS 255 #define HF_VERSION_ENABLE_CONVERT 255 #define HF_VERSION_ENABLE_ORACLE 255 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index c5aab80e5..c68853fe1 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1505,6 +1505,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl case HF_VERSION_ENFORCE_FULL_PROOFS: case HF_VERSION_SHUTDOWN_USER_TXS: case HF_VERSION_SALVIUM_ONE_PROOFS: + case HF_VERSION_AUDIT1_PAUSE: if (b.miner_tx.amount_burnt > 0) { CHECK_AND_ASSERT_MES(money_in_use + b.miner_tx.amount_burnt > money_in_use, false, "miner transaction is overflowed by amount_burnt"); money_in_use += b.miner_tx.amount_burnt; @@ -1556,7 +1557,7 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height, LOG_PRINT_L2("coinbase protocol transaction in the block has no outputs"); return true; } - + // Can we have matured STAKE transactions yet? uint64_t stake_lock_period = get_config(m_nettype).STAKE_LOCK_PERIOD; if (height <= stake_lock_period) { @@ -3951,6 +3952,20 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } + if (tx.type == cryptonote::transaction_type::AUDIT) { + // Make sure we are supposed to accept AUDIT txs at this point + const std::map> audit_hard_forks = get_config(m_nettype).AUDIT_HARD_FORKS; + CHECK_AND_ASSERT_MES(audit_hard_forks.find(hf_version) != audit_hard_forks.end(), false, "trying to audit outside an audit fork"); + std::string expected_asset_type = audit_hard_forks.at(hf_version).first; + CHECK_AND_ASSERT_MES(tx.source_asset_type == expected_asset_type, false, "trying to spend " << tx.source_asset_type << " coins in an AUDIT TX"); + } else { + if (hf_version >= HF_VERSION_SALVIUM_ONE_PROOFS) { + CHECK_AND_ASSERT_MES(tx.source_asset_type == "SAL1", false, "trying to spend " << tx.source_asset_type << " coins in a non-AUDIT TX"); + } else { + CHECK_AND_ASSERT_MES(tx.source_asset_type == "SAL", false, "trying to spend " << tx.source_asset_type << " coins in a non-AUDIT TX"); + } + } + std::vector> pubkeys(tx.vin.size()); std::vector < uint64_t > results; results.resize(tx.vin.size(), 0); @@ -4073,7 +4088,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, const rct::rctSig &rv = tx.rct_signatures; // Check that after full proofs are enabled, the RCT version is set to enforce full proofs - if (hf_version == HF_VERSION_SALVIUM_ONE_PROOFS) { + if (hf_version >= HF_VERSION_SALVIUM_ONE_PROOFS) { if (rv.type != rct::RCTTypeNull && rv.type != rct::RCTTypeSalviumOne) { MERROR_VER("Unsupported rct type (full proofs (with audit data) are required): " << rv.type); return false; @@ -4500,7 +4515,9 @@ bool Blockchain::calculate_audit_payouts(const uint64_t start_height, std::vecto } // Build a blacklist of staking TXs _not_ to pay out for - const std::set txs_blacklist = {}; + const std::set txs_blacklist = { + "017a79539e69ce16e91d9aa2267c102f336678c41636567c1129e3e72149499a" + }; // Get the ABI information for the 21,600 blocks that the matured TX(s), we can calculate audit for (const auto& entry: audit_entries) { diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 6ad024fe5..8e7ba0b58 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -448,6 +448,7 @@ namespace cryptonote case HF_VERSION_ENFORCE_FULL_PROOFS: case HF_VERSION_SHUTDOWN_USER_TXS: case HF_VERSION_SALVIUM_ONE_PROOFS: + case HF_VERSION_AUDIT1_PAUSE: // SRCG: subtract 20% that will be rewarded to staking users CHECK_AND_ASSERT_MES(tx.amount_burnt == 0, false, "while creating outs: amount_burnt is nonzero"); tx.amount_burnt = amount / 5; diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 2ff578c5f..9a5d3b2c3 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -46,6 +46,12 @@ const hardfork_t mainnet_hard_forks[] = { // version 5 starts from block 136100, which is on or around the 9th of January, 2025. Fork time finalised on 2025-01-08. No fork voting occurs for the v5 fork. { 5, 136100, 0, 1736265945 }, + + // version 6 starts from block 154750, which is on or around the 4th of February, 2025. Fork time finalised on 2025-01-31. No fork voting occurs for the v6 fork. + { 6, 154750, 0, 1738336000 }, + + // version 7 starts from block 161900, which is on or around the 14th of February, 2025. Fork time finalised on 2025-02-04. No fork voting occurs for the v7 fork. + { 7, 161900, 0, 1739264400 }, }; const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); const uint64_t mainnet_hard_fork_version_1_till = ((uint64_t)-1); @@ -66,8 +72,11 @@ const hardfork_t testnet_hard_forks[] = { // version 5 (TX shutdown) starts from block 800 { 5, 800, 0, 1734607005 }, - // version 6 (audit) starts from block 815 + // version 6 (audit 1) starts from block 815 { 6, 815, 0, 1734608000 }, + + // version 7 (audit 1 pause) starts from block 900 + { 7, 900, 0, 1739264400 }, }; const size_t num_testnet_hard_forks = sizeof(testnet_hard_forks) / sizeof(testnet_hard_forks[0]); const uint64_t testnet_hard_fork_version_1_till = ((uint64_t)-1); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 56cb394c3..38c587989 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -6981,12 +6981,22 @@ bool simple_wallet::transfer_main( // Skip this wallet if there is no balance unlocked to audit if (unlocked_balance_per_subaddr.count(subaddr_index) == 0) continue; - - std::set subaddr_indices_single; - subaddr_indices_single.insert(subaddr_index); - uint64_t unlock_block = get_config(m_wallet->nettype()).AUDIT_LOCK_PERIOD; - std::vector ptx_vector_audit = m_wallet->create_transactions_all(0, cryptonote::transaction_type::AUDIT, source_asset, m_wallet->get_subaddress({m_current_subaddress_account, subaddr_index}), (subaddr_index>0), 1, fake_outs_count, unlock_block, priority, extra, m_current_subaddress_account, subaddr_indices_single); - ptx_vector.insert(ptx_vector.end(), ptx_vector_audit.begin(), ptx_vector_audit.end()); + + try { + + std::set subaddr_indices_single; + subaddr_indices_single.insert(subaddr_index); + uint64_t unlock_block = get_config(m_wallet->nettype()).AUDIT_LOCK_PERIOD; + std::vector ptx_vector_audit = m_wallet->create_transactions_all(0, cryptonote::transaction_type::AUDIT, source_asset, m_wallet->get_subaddress({m_current_subaddress_account, subaddr_index}), (subaddr_index>0), 1, fake_outs_count, unlock_block, priority, extra, m_current_subaddress_account, subaddr_indices_single); + ptx_vector.insert(ptx_vector.end(), ptx_vector_audit.begin(), ptx_vector_audit.end()); + + } catch (const std::exception &e) { + + // Let's skip this wallet - we have already reported the error + if (unlocked_balance_per_subaddr[subaddr_index].first < 250000000) { + fail_msg_writer() << boost::format(tr("Subaddress index %u has insufficient funds (%s) to pay for audit")) % subaddr_index % print_money(unlocked_balance_per_subaddr[subaddr_index].first); + } + } } } else { diff --git a/src/version.cpp.in b/src/version.cpp.in index b043c6362..d28dc6af4 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_SALVIUM_VERSION_TAG "@VERSIONTAG@" -#define DEF_SALVIUM_VERSION "0.9.0-rc10" +#define DEF_SALVIUM_VERSION "0.9.3" #define DEF_MONERO_VERSION_TAG "release" #define DEF_MONERO_VERSION "0.18.3.3" #define DEF_MONERO_RELEASE_NAME "Zero" diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 94b0c19ea..ba405de38 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1444,6 +1444,34 @@ PendingTransaction *WalletImpl::createStakeTransaction(uint64_t amount, uint32_t return createTransactionMultDest(Monero::transaction_type::STAKE, std::vector {dst_addr}, payment_id, amount ? (std::vector {amount}) : (optional>()), mixin_count, asset_type, is_return, priority, subaddr_account, subaddr_indices); } +PendingTransaction *WalletImpl::createAuditTransaction( + uint32_t mixin_count, + PendingTransaction::Priority priority, + uint32_t subaddr_account, + std::set subaddr_indices +) { + // Need to populate {dst_entr, payment_id, asset_type, is_return} + const string dst_addr = m_wallet->get_subaddress_as_str({subaddr_account, 0});//MY LOCAL (SUB)ADDRESS + const string payment_id = ""; + const string asset_type = "SAL"; + const bool is_return = false; + + LOG_ERROR("createAuditTransaction: called"); + + return createTransactionMultDest( + Monero::transaction_type::AUDIT, + std::vector {dst_addr}, + payment_id, + (optional>()), + mixin_count, + asset_type, + is_return, + priority, + subaddr_account, + subaddr_indices + ); +} + // TODO: // 1 - properly handle payment id (add another menthod with explicit 'payment_id' param) // 2 - check / design how "Transaction" can be single interface @@ -1536,11 +1564,11 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transact fake_outs_count = m_wallet->adjust_mixin(mixin_count); if (amount) { - transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, "SAL", "SAL", converted_tx_type, fake_outs_count, 0 /* unlock_time */, + transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, asset_type, asset_type, converted_tx_type, fake_outs_count, 0 /* unlock_time */, adjusted_priority, extra, subaddr_account, subaddr_indices); } else { - transaction->m_pending_tx = m_wallet->create_transactions_all(0, converted_tx_type, "SAL", info.address, info.is_subaddress, 1, fake_outs_count, 0 /* unlock_time */, + transaction->m_pending_tx = m_wallet->create_transactions_all(0, converted_tx_type, asset_type, info.address, info.is_subaddress, 1, fake_outs_count, 0 /* unlock_time */, adjusted_priority, extra, subaddr_account, subaddr_indices); } diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 709bb8df1..cfdf13779 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -158,6 +158,10 @@ public: PendingTransaction::Priority priority = PendingTransaction::Priority_Low, uint32_t subaddr_account = 0, std::set subaddr_indices = {}) override; + PendingTransaction * createAuditTransaction(uint32_t mixin_count, + PendingTransaction::Priority priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, + std::set subaddr_indices = {}) override; PendingTransaction * createTransactionMultDest(const transaction_type &tx_type, const std::vector &dst_addr, const std::string &payment_id, optional> amount, uint32_t mixin_count, diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 2605f4754..071142d8d 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -58,7 +58,8 @@ enum transaction_type : uint8_t { BURN = 5, STAKE = 6, RETURN = 7, - MAX = 7 + AUDIT = 8, + MAX = 8 }; namespace Utils { @@ -97,7 +98,7 @@ struct YieldInfo virtual uint64_t yield() const = 0; virtual uint64_t yield_per_stake() const = 0; virtual std::string period() const = 0; - virtual std::vector> payouts() const = 0; + virtual std::vector> payouts() const = 0; }; @@ -878,6 +879,20 @@ struct Wallet uint32_t subaddr_account = 0, std::set subaddr_indices = {}) = 0; + /*! + * \brief createAuditTransaction creates audit transaction. + * \param mixin_count mixin count. if 0 passed, wallet will use default value + * \param subaddr_indices set of subaddress indices to use for transfer or sweeping. if set empty, all are chosen when sweeping, and one or more are automatically chosen when transferring. after execution, returns the set of actually used indices + * \param priority + * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() + * after object returned + */ + + virtual PendingTransaction * createAuditTransaction(uint32_t mixin_count, + PendingTransaction::Priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, + std::set subaddr_indices = {}) = 0; + /*! * \brief createTransactionMultDest creates transaction with multiple destinations. if dst_addr is an integrated address, payment_id is ignored * \param tx_type the type of transaction being created diff --git a/src/wallet/api/yield_info.cpp b/src/wallet/api/yield_info.cpp index 9ff401534..0780b349e 100644 --- a/src/wallet/api/yield_info.cpp +++ b/src/wallet/api/yield_info.cpp @@ -123,7 +123,7 @@ namespace Monero { return m_yield_per_stake; } - std::vector> YieldInfoImpl::payouts() const + std::vector> YieldInfoImpl::payouts() const { return m_payouts; } diff --git a/src/wallet/api/yield_info.h b/src/wallet/api/yield_info.h index 8ec98fb42..ae08c4f9b 100644 --- a/src/wallet/api/yield_info.h +++ b/src/wallet/api/yield_info.h @@ -53,7 +53,7 @@ public: uint64_t yield() const override; uint64_t yield_per_stake() const override; std::string period() const override; - std::vector> payouts() const override; + std::vector> payouts() const override; private: friend class WalletImpl; @@ -68,7 +68,7 @@ private: uint64_t m_yield_per_stake; uint64_t m_num_entries; std::string m_period; - std::vector> m_payouts; + std::vector> m_payouts; }; }