Add wallet inactivity timeout, and made wallet2 backend compile-time optional

This commit is contained in:
Lee Clagett
2025-04-08 00:09:44 -04:00
parent 7dfdb8d8c8
commit 4a6793608c
7 changed files with 211 additions and 21 deletions
+4 -1
View File
@@ -30,7 +30,10 @@ include_directories(.)
add_subdirectory(decorate)
add_subdirectory(views)
add_executable(lwcli main.cpp)
add_executable(lwcli events.cpp main.cpp)
target_include_directories(lwcli PRIVATE ".")
target_link_libraries(lwcli PRIVATE lwsf-api component lwcli-views)
if (DEFINED ENABLE_WALLET2)
target_compile_definitions(lwcli PRIVATE LWCLI_WALLET2_ENABLED)
endif ()
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2025, Cifro Codes LLC
//
// 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 "events.h"
namespace lwcli { namespace event
{
/* Keep under 15 characters so that libstdc++ and libc++ can use small
string optmization. */
const ftxui::Event lock_wallet = ftxui::Event::Special("lwcli.lockw");
}}
+6 -1
View File
@@ -1,4 +1,3 @@
// Copyright (c) 2025, Cifro Codes LLC
//
// All rights reserved.
@@ -27,6 +26,9 @@
// 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 <ftxui/component/event.hpp>
#include <stdexcept>
namespace lwcli { namespace event
@@ -41,4 +43,7 @@ namespace lwcli { namespace event
virtual ~close() noexcept override = default;
virtual const char* what() const noexcept override final { return "close window"; }
};
extern const ftxui::Event lock_wallet;
}} // lwcli // event
+2
View File
@@ -39,6 +39,8 @@ namespace lwcli { namespace config
constexpr const std::string_view default_language{"English"};
//! Timeout interval for inactivity with open wallet
constexpr const std::chrono::minutes wallet_timeout{2};
namespace server
{
constexpr const std::string_view default_url{"http://127.0.0.1:8080"};
+101 -6
View File
@@ -26,17 +26,24 @@
// 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 <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <cstring>
#include <ftxui/component/component.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <limits>
#include <lws_frontend.h>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#include "events.h"
#include "lwcli_config.h"
#include "util.h"
#include "views/manager.h"
namespace
@@ -45,9 +52,10 @@ namespace
struct program
{
std::string file;
std::chrono::seconds wallet_timeout = lwcli::config::wallet_timeout;
rpc backend = rpc::lws;
// network is in static memory
bool failed;
bool failed = false;
};
typedef const char**(*argument_handler)(program&, const char*[]);
@@ -125,13 +133,34 @@ namespace
return ++argv;
}
const char** handle_timeout(program& prog, const char* argv[])
{
if (!argv || !argv[0])
{
fprintf(stderr, "Missing argument for --timeout\n");
return nullptr;
}
const auto value = lwcli::from_string(argv[0]);
if (!value || std::numeric_limits<std::chrono::seconds::rep>::max() < *value)
{
fprintf(stderr, "Invalid values for --timeout\n");
return nullptr;
}
prog.wallet_timeout = std::chrono::seconds{*value};
return ++argv;
}
constexpr const argument process_args[] =
{
{nullptr, "help", "\t\t\tList help", 'h'},
#ifdef LWCLI_WALLET2_ENABLED
{handle_backend, "backend", "\tlws | monerod\t\tlws = default , selects rpc backend", 'b'},
#endif
{handle_file, "file", "\t[file path]\t\tDefaults to home directory. Auto-fills TUI value on launch", 'f'},
{handle_network, "network", "\tmain | stage | test\tSelects wallet network type. main is default.", 'n'},
{handle_timeout, "timeout", "\tseconds\tClose wallet after inactivity. Default 120", 't'}
};
template<typename F>
@@ -164,11 +193,72 @@ namespace
++argv;
return current->handler(prog, argv);
}
struct screen_state
{
std::atomic<std::chrono::steady_clock::time_point::duration::rep> last_event;
ftxui::ScreenInteractive screen;
std::mutex sync;
std::condition_variable notify;
bool shutdown;
screen_state()
: last_event(std::chrono::steady_clock::now().time_since_epoch().count()),
screen(ftxui::ScreenInteractive::Fullscreen()),
sync(),
notify(),
shutdown(false)
{}
};
struct watch_inactivity
{
screen_state& state;
std::thread watcher;
const std::chrono::seconds wallet_timeout;
watch_inactivity(screen_state& st, std::chrono::seconds wt)
: state(st), watcher(), wallet_timeout(wt)
{
watcher = std::thread([this] () {
std::unique_lock lock{state.sync};
while (!state.shutdown)
{
const std::chrono::steady_clock::time_point last_event{
std::chrono::steady_clock::time_point::duration{state.last_event.load()}
};
const auto now = std::chrono::steady_clock::now();
auto time_diff = now - last_event;
if (wallet_timeout <= time_diff)
{
state.last_event = now.time_since_epoch().count();
state.screen.PostEvent(lwcli::event::lock_wallet);
time_diff = std::chrono::seconds{0};
}
state.notify.wait_for(lock, (wallet_timeout - time_diff), [this] () {
return state.shutdown;
});
}
});
}
~watch_inactivity() noexcept // abort if double exception
{
std::unique_lock lock{state.sync};
state.shutdown = true;
state.notify.notify_one();
lock.unlock();
if (watcher.joinable())
watcher.join();
}
};
}
int main(int, const char* argv[])
{
auto screen = ftxui::ScreenInteractive::Fullscreen();
screen_state state{};
try
{
if (!argv)
@@ -190,31 +280,36 @@ int main(int, const char* argv[])
case rpc::lws:
wm.reset(lwsf::WalletManagerFactory::getWalletManager());
break;
#ifdef LWCLI_WALLET2_ENABLED
case rpc::monerod:
wm.reset(Monero::WalletManagerFactory::getWalletManager());
#endif
break;
}
auto window = ftxui::CatchEvent(lwcli::view::manager(std::move(wm), std::move(prog.file)), [&] (ftxui::Event event)
{
state.last_event = std::chrono::steady_clock::now().time_since_epoch().count();
if (event == ftxui::Event::CtrlC)
{
screen.ExitLoopClosure()();
state.screen.ExitLoopClosure()();
return true;
}
return false;
});
screen.Loop(window);
const watch_inactivity watch{state, prog.wallet_timeout};
state.screen.Loop(window);
}
catch (const lwcli::event::close&)
{}
catch (const std::exception& e)
{
screen.Clear();
state.screen.Clear();
fprintf(stderr, "Fatal Error: %s\n", e.what());
return EXIT_FAILURE;
}
screen.Clear();
state.screen.Clear();
return EXIT_SUCCESS;
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) 2025, Cifro Codes LLC
//
// 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 <charconv>
#include <cstdint>
#include <optional>
#include <string_view>
namespace lwcli
{
inline std::optional<std::uint64_t> from_string(const std::string_view str) noexcept
{
std::uint64_t height = 0;
const auto end = str.data() + str.size();
auto [ptr, ec] = std::from_chars(str.data(), end, height);
if (bool(ec) || ptr != end)
return std::nullopt;
return height;
}
}
+14 -13
View File
@@ -40,6 +40,7 @@
#include "events.h"
#include "lwcli_config.h"
#include "translate.h"
#include "util.h"
#include "views/history.h"
#include "views/keys.h"
@@ -142,16 +143,6 @@ namespace lwcli { namespace view
{}
};
std::optional<std::uint64_t> from_string(const std::string_view str) noexcept
{
std::uint64_t height = 0;
const auto end = str.data() + str.size();
auto [ptr, ec] = std::from_chars(str.data(), end, height);
if (bool(ec) || ptr != end)
return std::nullopt;
return height;
}
bool init_wallet(Monero::Wallet& wal, std::string* error)
{
auto refresh = from_string(wal.getCacheAttribute(std::string{config::server::refresh_interval}));
@@ -545,7 +536,9 @@ namespace lwcli { namespace view
try
{
if (state_.overlay)
if (event == event::lock_wallet)
state_.error = _("Wallet Locked Due to Inactivity");
else if (state_.overlay)
return state_.overlay->OnEvent(std::move(event));
else if (event == ftxui::Event::CtrlQ)
throw event::close{};
@@ -619,8 +612,16 @@ namespace lwcli { namespace view
try
{
if (wallet_)
return wallet_->OnEvent(std::move(event));
else if (start_->OnEvent(std::move(event)) && data_)
{
if (event == event::lock_wallet)
{
wallet_.reset();
start_->OnEvent(std::move(event));
}
else
return wallet_->OnEvent(std::move(event));
}
else if (event != event::lock_wallet && start_->OnEvent(std::move(event)) && data_)
wallet_ = view::wallet(std::move(data_));
data_.reset();
}