diff --git a/README.md b/README.md index 1b42c67ce..9d9f38947 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Salvium Zero v0.9.4 +# Salvium Zero v0.9.5 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.4 + git checkout v0.9.5 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.4 + git checkout v0.9.5 ``` * 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.4'. 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.5'. If you don't care about the version and just want binaries from master, skip this step: ```bash - git checkout v0.9.4 + git checkout v0.9.5 ``` * If you are on a 64-bit system, run: diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat index 8ba5bc6e7..8455b1dab 100644 Binary files a/src/blocks/checkpoints.dat and b/src/blocks/checkpoints.dat differ diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 9d1edb39d..b7d447af0 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1611,103 +1611,57 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height, break; } } - /* - const std::map>> audit_hard_forks = get_config(m_nettype).AUDIT_HARD_FORKS; - uint64_t audit_lock_period = get_config(m_nettype).AUDIT_LOCK_PERIOD; - uint64_t matured_audit_height = height - audit_lock_period - 1; - uint8_t hf = m_hardfork->get_ideal_version(matured_audit_height); - if (audit_hard_forks.find(hf) != audit_hard_forks.end()) { - // Maturing height was during an audit - process accordingly - cryptonote::audit_block_info abi_matured; - ok = get_abi_entry(matured_audit_height, abi_matured); - if (!ok) { - LOG_PRINT_L1("Block at height: " << height << " - failed to obtain audit block information - aborting"); - return false; - } else if (abi_matured.locked_coins_this_block == 0) { - LOG_PRINT_L1("Block at height: " << height << " - no audit payouts due - skipping"); - } else { - // Iterate over the cached data for audits, calculating the audit payouts due - if (!calculate_audit_payouts(matured_audit_height, audit_payouts)) { - LOG_ERROR("Block at height: " << height << " - Failed to obtain audit payout information - aborting"); - return false; - } - } - } - */ + // Check we have the correct number of entries CHECK_AND_ASSERT_MES(b.protocol_tx.vout.size() == yield_payouts.size() + audit_payouts.size(), false, "Invalid number of outputs in protocol_tx - aborting"); // go through each vout and validate - std::set used_keys; - for (auto& o : b.protocol_tx.vout) { - // gather the output data - uint64_t out_amount; - uint64_t out_unlock_time; - std::string out_asset_type; + //std::set used_keys; + + // Merge the yield and audit payouts into an iterable vector + std::vector> payouts{yield_payouts}; + payouts.insert(payouts.end(), audit_payouts.begin(), audit_payouts.end()); + /* + if (hf_version >= HF_VERSION_AUDIT2) { + std::sort(payouts.begin(), payouts.end(), [](const auto& lhs, const auto& rhs) { + // If block heights are different (only possible with mixed AUDIT+STAKE) sort by them first + if (lhs.first.block_height < rhs.first.block_height) return true; + if (lhs.first.block_height > rhs.first.block_height) return false; + + // If output keys are different, sort by them second + if (lhs.first.return_address < rhs.first.return_address) return true; + if (lhs.first.return_address > rhs.first.return_address) return false; + + // If block heights _and_ output keys are same, sort by amount third + return lhs.second < rhs.second; + }); + } + */ + + size_t output_idx = 0; + for (auto it = payouts.begin(); it != payouts.end(); it++, output_idx++) { + + // Verify the output key crypto::public_key out_key; - if (o.target.type() == typeid(txout_to_key)) { - txout_to_key out = boost::get(o.target); - out_unlock_time = out.unlock_time; - out_asset_type = out.asset_type; - out_key = out.key; - out_amount = o.amount; - } else if (o.target.type() == typeid(txout_to_tagged_key)) { - txout_to_tagged_key out = boost::get(o.target); - out_unlock_time = out.unlock_time; - out_asset_type = out.asset_type; - out_key = out.key; - out_amount = o.amount; - } else { - MERROR("Block at height: " << height << " attempting to add protocol transaction with invalid type " << o.target.type().name()); - return false; - } + cryptonote::get_output_public_key(b.protocol_tx.vout[output_idx], out_key); + CHECK_AND_ASSERT_MES(out_key == it->first.return_address, false, "Incorrect output key detected in protocol_tx"); - // Check if key has already been seen - if (used_keys.count(out_key) != 0) { - LOG_ERROR("Block at height: " << height << " - Duplicated output key " << out_key << " for protocol TX - aborting"); - return false; - } - - // Add key to list of already-seen - used_keys.insert(out_key); - - // check if there is entry in the yield payouts or audit payouts for this output - std::string expected_output_asset_type = "SAL"; - auto found_yield = std::find_if(yield_payouts.begin(), yield_payouts.end(), [&](const std::pair& p) { - return p.first.return_address == out_key; - }); - auto found_audit = std::find_if(audit_payouts.begin(), audit_payouts.end(), [&](const std::pair& p) { - return p.first.return_address == out_key; - }); - if (found_yield == yield_payouts.end() && found_audit == audit_payouts.end()) { - MERROR("Block at height: " << height << " - Failed to locate output for protocol TX - rejecting block"); - return false; - } else if (found_audit == audit_payouts.end()) { + // Verify the output amount + CHECK_AND_ASSERT_MES(b.protocol_tx.vout[output_idx].amount == it->second, false, "Incorrect output amount detected in protocol_tx"); - // Found a YIELD entry - CHECK_AND_ASSERT_MES(out_amount == found_yield->second, false, "Incorrect value for protocol TX YIELD amount"); - uint8_t hf_yield = m_hardfork->get_ideal_version(found_yield->first.block_height); - if (hf_yield >= HF_VERSION_SALVIUM_ONE_PROOFS) - expected_output_asset_type = "SAL1"; + // Verify the output asset type + std::string out_asset_type; + cryptonote::get_output_asset_type(b.protocol_tx.vout[output_idx], out_asset_type); + uint8_t hf_yield = m_hardfork->get_ideal_version(it->first.block_height); + if (hf_yield >= HF_VERSION_SALVIUM_ONE_PROOFS) + CHECK_AND_ASSERT_MES(out_asset_type == "SAL1", false, "Incorrect output asset_type (!= SAL1) detected in protocol_tx"); + else + CHECK_AND_ASSERT_MES(out_asset_type == "SAL", false, "Incorrect output asset_type (!= SAL) detected in protocol_tx"); - } else if (found_yield == yield_payouts.end()) { - - // Found an AUDIT entry - CHECK_AND_ASSERT_MES(out_amount == found_audit->second, false, "Incorrect value for protocol TX AUDIT amount"); - uint8_t hf_audit = m_hardfork->get_ideal_version(found_audit->first.block_height); - if (hf_audit >= HF_VERSION_SALVIUM_ONE_PROOFS) - expected_output_asset_type = "SAL1"; - - } else { - - // Duplicate entry in yield + audit?!?!? - MERROR("Block at height: " << height << " - Duplicated YIELD and AUDIT keys found for protocol TX - rejecting block"); - return false; - } - - // check other fields - CHECK_AND_ASSERT_MES(out_unlock_time == CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "Invalid unlock time on protocol_tx output"); - CHECK_AND_ASSERT_MES(expected_output_asset_type == out_asset_type, false, "Incorrect asset type detected for protocol TX ouput - rejecting block"); + // Verify the output unlock time + uint64_t out_unlock_time; + cryptonote::get_output_unlock_time(b.protocol_tx.vout[output_idx], out_unlock_time); + CHECK_AND_ASSERT_MES(out_unlock_time == CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "Invalid output unlock time on protocol_tx output"); } // Everything checks out @@ -2003,6 +1957,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, entry.type = cryptonote::transaction_type::STAKE; entry.P_change = yield_entry.first.P_change; entry.return_pubkey = yield_entry.first.return_pubkey; + entry.origin_height = start_height; protocol_entries.push_back(entry); } } @@ -2046,12 +2001,31 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, entry.type = cryptonote::transaction_type::AUDIT; entry.P_change = audit_entry.first.P_change; entry.return_pubkey = audit_entry.first.return_pubkey; + entry.origin_height = matured_audit_height; protocol_entries.push_back(entry); } } break; } } + + /* + // From v8, we sort the protocol_tx outputs by ORIGIN_HEIGHT, OUTPUT_KEY, AMOUNT + if (b.major_version >= HF_VERSION_AUDIT2) { + std::sort(protocol_entries.begin(), protocol_entries.end(), [](const auto& lhs, const auto& rhs) { + // If origin block heights are different (only possible with mixed AUDIT+STAKE) sort by them first + if (lhs.origin_height < rhs.origin_height) return true; + if (lhs.origin_height > rhs.origin_height) return false; + + // If output keys are different, sort by them second + if (lhs.return_address < rhs.return_address) return true; + if (lhs.return_address > rhs.return_address) return false; + + // If block heights _and_ output keys are same, sort by amount third + return lhs.amount_burnt < rhs.amount_burnt; + }); + } + */ // Time to construct the protocol_tx uint64_t protocol_fee = 0; @@ -6309,7 +6283,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "5065d5361119a526b7a45e9e5bdf1d5be86f80e9eb43b0398bf0e47489c81c6d"; +static const char expected_block_hashes_hash[] = "131b18108fb3382b4fa82d4eb6cca8f9e1e0ee2aa7893e572361ca0c2c4118e6"; void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { if (get_checkpoints == nullptr || !m_fast_sync) diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 4fbb9956d..02f162c31 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -66,6 +66,7 @@ namespace cryptonote uint8_t type; crypto::public_key P_change; crypto::public_key return_pubkey; + uint64_t origin_height; }; //--------------------------------------------------------------- diff --git a/src/version.cpp.in b/src/version.cpp.in index 4f481c494..35d942a24 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.4" +#define DEF_SALVIUM_VERSION "0.9.5" #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 174c91978..662658de3 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1569,27 +1569,38 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transact extra, subaddr_account, subaddr_indices); } else { std::vector m_pending_txs; - for (auto it = subaddr_indices.begin(); it != subaddr_indices.end(); ++it) { + for (const auto subaddr_index : subaddr_indices) { // Skip this wallet if there is no balance unlocked to audit - const auto unlocked_balance_per_subaddr = m_wallet->unlocked_balance_per_subaddress(subaddr_account, "SAL", true); - if (unlocked_balance_per_subaddr.count(*it) == 0) continue; + std::map>> unlocked_balance_per_subaddr = m_wallet->unlocked_balance_per_subaddress(subaddr_account, asset_type, true); + if (unlocked_balance_per_subaddr.count(subaddr_index) == 0) continue; - const auto result = m_wallet->create_transactions_all( - 0, - converted_tx_type, - asset_type, - m_wallet->get_subaddress({subaddr_account, *it}), - ((*it) > 0), - 1, - fake_outs_count, - 0 /* unlock_time */, - adjusted_priority, - extra, - subaddr_account, - std::set {*it} - ); - m_pending_txs.insert(m_pending_txs.end(), result.begin(), result.end()); + try { + + const auto result = m_wallet->create_transactions_all(0, + converted_tx_type, + asset_type, + m_wallet->get_subaddress({subaddr_account, subaddr_index}), + (subaddr_index > 0), + 1, + fake_outs_count, + 0 /* unlock_time */, + adjusted_priority, + extra, + subaddr_account, + std::set {subaddr_index} + ); + m_pending_txs.insert(m_pending_txs.end(), result.begin(), result.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) { + std::ostringstream writer; + 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); + setStatusError(writer.str()); + } + } } transaction->m_pending_tx = m_pending_txs; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c1566d001..f3f824e01 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2768,7 +2768,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote crypto::public_key pk_locked_coins = crypto::null_pkey; THROW_WALLET_EXCEPTION_IF(!get_output_public_key(td_origin.m_tx.vout[td_origin.m_internal_output_index], pk_locked_coins), error::wallet_internal_error, "Failed to get output public key for locked coins"); // At this point, we need to clear the "locked coins" count, because otherwise we will be counting yield stakes twice in our balance - THROW_WALLET_EXCEPTION_IF(!m_locked_coins.erase(pk_locked_coins), error::wallet_internal_error, "Failed to remove protocol_tx entry from m_locked_coins"); + //THROW_WALLET_EXCEPTION_IF(!m_locked_coins.erase(pk_locked_coins), error::wallet_internal_error, "Failed to remove protocol_tx entry from m_locked_coins"); + if (!m_locked_coins.erase(pk_locked_coins)) { + LOG_ERROR("Failed to remove protocol_tx entry from m_locked_coins - possible duplicate output key detected"); + } } } }