From 0054b0b06aef0930cc9c6c3b04c660423a8f67ab Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Sat, 4 Jul 2026 18:01:33 -0400 Subject: [PATCH] Fix/claude report (#11) * Fix send_tx status check * Store feed websocket shared_ptr in-strand * Check mixin/decoy size from server * Harden/limit listener block notifications * Fix new ascii unit test * Fix CMake changes from upstream (cherry picked from commit 19cb285dab6c1962a38865a7966f823925c030bd) --- CMakeLists.txt | 18 ++++++++--- src/CMakeLists.txt | 2 ++ src/backend.cpp | 25 ++++++++++++--- src/error.cpp | 2 ++ src/error.h | 1 + src/lwsf_config.h | 1 + src/utils/ascii.cpp | 54 +++++++++++++++++++++++++++++++++ src/utils/ascii.h | 36 ++++++++++++++++++++++ src/wallet.cpp | 2 ++ tests/unit/utils/CMakeLists.txt | 30 ++++++++++++++++++ tests/unit/utils/ascii.test.cpp | 45 +++++++++++++++++++++++++++ 11 files changed, 207 insertions(+), 9 deletions(-) create mode 100644 src/utils/ascii.cpp create mode 100644 src/utils/ascii.h create mode 100644 tests/unit/utils/CMakeLists.txt create mode 100644 tests/unit/utils/ascii.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4119f2e..1865a55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,15 @@ cmake_path(CONVERT "${MONERO_SOURCE_DIR}" TO_CMAKE_PATH_LIST MONERO_SOURCE_DIR_C include(FetchContent) FetchContent_Declare(monero SOURCE_DIR ${MONERO_SOURCE_DIR_CONVERTED} EXCLUDE_FROM_ALL) +if (NOT LWSF_MASTER_ENABLE) + set(Boost_NO_BOOST_CMAKE ON) +endif () + +if(STATIC) + set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_STATIC_RUNTIME ON) +endif() + if (NOT monero_POPULATED) FetchContent_MakeAvailable(monero) endif () @@ -65,12 +74,11 @@ endif () set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -set(Boost_NO_BOOST_CMAKE ON) -if(STATIC) - set(Boost_USE_STATIC_LIBS ON) - set(Boost_USE_STATIC_RUNTIME ON) +if (LWSF_MASTER_ENABLE) + find_package(Boost 1.66 QUIET REQUIRED COMPONENTS filesystem program_options thread CONFIG) +else () + find_package(Boost 1.66 QUIET REQUIRED COMPONENTS filesystem program_options thread) endif() -find_package(Boost 1.66 QUIET REQUIRED COMPONENTS filesystem program_options thread) list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6e056e7..825f8f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,6 +41,7 @@ set(lwsf_sources subaddress_minor.cpp transaction_history.cpp transaction_info.cpp + utils/ascii.cpp utils/encrypted_file.cpp wallet.cpp wallet_manager.cpp @@ -53,6 +54,7 @@ set(lwsf_headers pending_transaction.h transaction_history.h transaction_info.h + utils/ascii.h utils/encrypted_file.h utils/numeric.h wallet.h diff --git a/src/backend.cpp b/src/backend.cpp index 9869872..d34c478 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -43,6 +43,7 @@ #include "lwsf_config.h" #include "net/websocket.h" #include "ringct/rctOps.h" +#include "utils/ascii.h" #include "wire.h" #include "wire/adapted/crypto.h" #include "wire/adapted/pair.h" @@ -715,8 +716,19 @@ namespace lwsf { namespace internal { namespace backend if (!merged.new_transactions.empty() || new_height - orig_height) listener.updated(); - for (std::uint64_t i = orig_height; i < new_height; ++i) + // Call `listener.newBlock` a max of `config::notify_block_limit` times + const std::uint64_t diff = new_height - orig_height; + const std::uint64_t shift = + std::max(diff / config::notify_block_limit, std::uint64_t(1)); + for (std::uint64_t i = orig_height; i < new_height; i += shift) + { listener.newBlock(i); + if (std::numeric_limits::max() - shift < i) + break; + } + + if (diff && diff % shift != 0) + listener.newBlock(new_height - 1); for (const auto& tx : merged.new_transactions) { @@ -1827,7 +1839,7 @@ namespace lwsf { namespace internal { namespace backend { ws_connect: BOOST_ASIO_CORO_YIELD self.client.ws_async( - rpc::feed::endpoint(), rpc::feed::protocol(), store(&self.feed, wrap(self_ptr, *this)) + rpc::feed::endpoint(), rpc::feed::protocol(), wrap(self_ptr, store(&self.feed, wrap(self_ptr, *this))) ); if (!error && self.feed) @@ -1867,7 +1879,7 @@ namespace lwsf { namespace internal { namespace backend } } - frame_->orig_scan_height = self.primary.scan_height; + frame_->orig_scan_height = std::max(self.primary.scan_height, self.primary.restore_height); frame_->login = rpc::login{self.primary.address, self.primary.view.sec}; BOOST_ASIO_CORO_YIELD rpc::invoke_async( self.client, frame_->login, std::addressof(frame_->txs_response), wrap(self_ptr, *this) @@ -2399,7 +2411,12 @@ namespace lwsf { namespace internal { namespace backend std::addressof(frame_->response), *this ); - frame_->f(error); + if (error) + frame_->f(error); + else if (!ascii_nocase_compare(frame_->response.status, "OK")) + frame_->f(error::send_not_ok); + else + frame_->f({}); } } }; diff --git a/src/error.cpp b/src/error.cpp index aab1c50..8615dc5 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -60,6 +60,8 @@ namespace lwsf return "Import/restore from height is awaiting approval"; case error::network_type: return "Mismatch on network type"; + case error::send_not_ok: + return "send_tx status was NOT ok"; case error::subaddr_ahead: return "server has limits too low for requested subaddress lookahead"; case error::subaddr_disabled: diff --git a/src/error.h b/src/error.h index d703b2b..ca9ff55 100644 --- a/src/error.h +++ b/src/error.h @@ -54,6 +54,7 @@ namespace lwsf import_pending, //!< `import_wallet_request` not fulfilled/approved invalid_scheme, //!< Invalid network scheme network_type, //!< Mismatch on network type + send_not_ok, //!< `send_tx` status was NOT `OK`. subaddr_ahead, //!< Server limits on subaddresses affects lookahead subaddr_disabled, //!< Server has subaddresses disabled subaddr_local, //!< Local limits on subaddresses too small diff --git a/src/lwsf_config.h b/src/lwsf_config.h index 7967846..42d1d97 100644 --- a/src/lwsf_config.h +++ b/src/lwsf_config.h @@ -59,6 +59,7 @@ namespace lwsf { namespace config constexpr const std::size_t max_txes_in_rpc = 2048; constexpr const std::size_t min_outputs = 2; constexpr const std::uint32_t mixin_default = 21; // Wownero ring size 22 (mixin + 1) + constexpr const unsigned notify_block_limit = 1024; // max block notifications (via listener) in one shot constexpr const std::chrono::seconds ping_timeout{10}; constexpr const std::chrono::seconds refresh_interval{30}; constexpr const std::chrono::seconds refresh_interval_min{5}; diff --git a/src/utils/ascii.cpp b/src/utils/ascii.cpp new file mode 100644 index 0000000..da232a6 --- /dev/null +++ b/src/utils/ascii.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ascii.h" + +namespace lwsf { namespace internal +{ + namespace + { + char to_low_case(char in) noexcept + { + if (u8'A' <= in && in <= u8'Z') + return in ^ 0x20; + return in; + } + } + + bool ascii_nocase_compare(const std::string_view left, const std::string_view right) + { + if (left.size() != right.size()) + return false; + for (std::size_t i = 0; i < left.size(); ++i) + { + if (to_low_case(left[i]) != to_low_case(right[i])) + return false; + } + return true; + } +}} // lwsf // internal diff --git a/src/utils/ascii.h b/src/utils/ascii.h new file mode 100644 index 0000000..fc4dc6d --- /dev/null +++ b/src/utils/ascii.h @@ -0,0 +1,36 @@ +// Copyright (c) 2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include + +namespace lwsf { namespace internal +{ + bool ascii_nocase_compare(std::string_view left, std::string_view right); +}} // lwsf // internal diff --git a/src/wallet.cpp b/src/wallet.cpp index ef7af3d..b8ba0e5 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1595,6 +1595,8 @@ namespace lwsf { namespace internal const auto ring = std::lower_bound(decoys.begin(), decoys.end(), amount, by_amount{}); if (ring == decoys.end() || ring->amount != rpc::uint64_string(amount)) throw std::runtime_error{"Missing requested decoys"}; + if (ring->outputs.size() != mixin_count) + throw std::runtime_error{"Unexpected decoy (mixin) count from server"}; for (const auto& decoy : ring->outputs) entry.outputs.emplace_back(std::uint64_t(decoy.global_index), rct::ctkey{decoy.public_key, decoy.rct}); diff --git a/tests/unit/utils/CMakeLists.txt b/tests/unit/utils/CMakeLists.txt new file mode 100644 index 0000000..6b06c18 --- /dev/null +++ b/tests/unit/utils/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (c) 2025, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +add_library(lwsf-unit-util OBJECT ascii.test.cpp) +target_link_libraries(lwsf-unit-util PRIVATE lwsf-unit-framework lwsf) diff --git a/tests/unit/utils/ascii.test.cpp b/tests/unit/utils/ascii.test.cpp new file mode 100644 index 0000000..a234842 --- /dev/null +++ b/tests/unit/utils/ascii.test.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2025, The Monero Project +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "framework.test.h" + +#include "util/ascii.h" + +LWS_CASE("util::ascii") +{ + EXPECT(lwsf::internal::ascii_nocase_compare("OK", "ok")); + EXPECT(lwsf::internal::ascii_nocase_compare("OK", "oK")); + EXPECT(lwsf::internal::ascii_nocase_compare("OK", "OK")); + EXPECT(lwsf::internal::ascii_nocase_compare("Ok", "OK")); + EXPECT(lwsf::internal::ascii_nocase_compare("oK", "OK")); + EXPECT(lwsf::internal::ascii_nocase_compare("ok", "OK")); + EXPECT(lwsf::internal::ascii_nocase_compare("ok", "Ok")); + EXPECT(lwsf::internal::ascii_nocase_compare("ok", "oK")); + EXPECT(lwsf::internal::ascii_nocase_compare("ok", "ok")); + EXPECT(lwsf::internal::ascii_nocase_compare("-->", "-->")); + EXPECT(!lwsf::internal::ascii_nocase_compare("Double spend", "OK")); +}