Replace the JavaScript mobile app with a native Swift iOS wallet
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Copyright (c) woodser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2014-2019, The Monero Project
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
*/
|
||||
|
||||
#include "gen_utils.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "rapidjson/writer.h"
|
||||
|
||||
using namespace gen_utils;
|
||||
|
||||
// ------------------------- SERIALIZATION ---------------------------
|
||||
|
||||
std::string gen_utils::serialize(const rapidjson::Document& doc) {
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
doc.Accept(writer);
|
||||
return buffer.GetString();
|
||||
}
|
||||
|
||||
std::string gen_utils::serialize(const boost::property_tree::ptree& node) {
|
||||
std::stringstream ss;
|
||||
boost::property_tree::write_json(ss, node, false);
|
||||
std::string str = ss.str();
|
||||
return str.substr(0, str.size() - 1); // strip newline
|
||||
}
|
||||
|
||||
void gen_utils::deserialize(const std::string& json, boost::property_tree::ptree& root) {
|
||||
std::istringstream iss = json.empty() ? std::istringstream() : std::istringstream(json);
|
||||
try {
|
||||
boost::property_tree::read_json(iss, root);
|
||||
} catch (std::exception const& e) {
|
||||
throw std::runtime_error("Invalid JSON");
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------- THREAD POLLER ---------------------------
|
||||
|
||||
thread_poller::thread_poller(): m_is_polling(false), m_poll_loop_running(false), m_poll_period_ms(20000) {
|
||||
}
|
||||
|
||||
thread_poller::~thread_poller() {
|
||||
// safety net only: derived classes must stop polling in their own destructor
|
||||
// (before their members are torn down) since by the time this base destructor
|
||||
// runs, the derived part of the object is already gone and a concurrently
|
||||
// running poll() (a virtual call) would hit a partially-destructed object
|
||||
set_is_polling(false);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// stack of poller instances whose announce_scope is currently active on the current
|
||||
// thread, innermost last. Per-instance so that a callback of poller A calling into
|
||||
// a *different* poller B's wait_for_callbacks_idle() still waits normally.
|
||||
thread_local std::vector<const thread_poller*> t_active_dispatch_stack;
|
||||
}
|
||||
|
||||
thread_poller::announce_scope::announce_scope(thread_poller& poller): m_poller(poller) {
|
||||
t_active_dispatch_stack.push_back(&poller);
|
||||
boost::lock_guard<boost::mutex> lock(m_poller.m_announce_mutex);
|
||||
++m_poller.m_announce_count;
|
||||
}
|
||||
|
||||
thread_poller::announce_scope::~announce_scope() {
|
||||
{
|
||||
boost::lock_guard<boost::mutex> lock(m_poller.m_announce_mutex);
|
||||
if (--m_poller.m_announce_count == 0) m_poller.m_announce_cv.notify_all();
|
||||
}
|
||||
t_active_dispatch_stack.pop_back();
|
||||
}
|
||||
|
||||
void thread_poller::wait_for_callbacks_idle() {
|
||||
// skip the wait only if this poller's own dispatch is active on the current thread;
|
||||
// waiting would deadlock in that case. A different poller's active dispatch must not
|
||||
// short-circuit this wait.
|
||||
for (const thread_poller* active : t_active_dispatch_stack) {
|
||||
if (active == this) return;
|
||||
}
|
||||
boost::unique_lock<boost::mutex> lock(m_announce_mutex);
|
||||
m_announce_cv.wait(lock, [&]() { return m_announce_count == 0; });
|
||||
}
|
||||
|
||||
void thread_poller::init_common(const std::string& name) {
|
||||
m_name = name;
|
||||
}
|
||||
|
||||
void thread_poller::request_is_polling(bool is_polling) {
|
||||
boost::lock_guard<boost::mutex> lifecycle_lock(m_lifecycle_mutex);
|
||||
|
||||
if (is_polling) {
|
||||
if (m_is_polling) return;
|
||||
m_is_polling = true;
|
||||
run_poll_loop();
|
||||
return;
|
||||
}
|
||||
|
||||
bool was_polling = m_is_polling.exchange(false);
|
||||
if (was_polling) {
|
||||
boost::lock_guard<boost::mutex> polling_lock(m_polling_mutex);
|
||||
m_poll_cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void thread_poller::set_is_polling(bool is_polling) {
|
||||
boost::unique_lock<boost::mutex> lifecycle_lock(m_lifecycle_mutex);
|
||||
|
||||
if (is_polling) {
|
||||
if (m_is_polling) return;
|
||||
m_is_polling = true;
|
||||
run_poll_loop();
|
||||
return;
|
||||
}
|
||||
|
||||
bool was_polling = m_is_polling.exchange(false);
|
||||
if (was_polling) {
|
||||
boost::lock_guard<boost::mutex> polling_lock(m_polling_mutex);
|
||||
m_poll_cv.notify_one();
|
||||
}
|
||||
|
||||
if (!m_poll_loop_running) return; // loop already fully exited; nothing to wait for
|
||||
|
||||
if (boost::this_thread::get_id() == m_poll_thread_id) {
|
||||
// called from the poll thread itself (e.g. a listener removed itself from within a
|
||||
// callback invoked by poll()): waiting for our own loop to exit would deadlock. Just
|
||||
// return -- the loop re-checks m_is_polling itself and will exit and clear
|
||||
// m_poll_loop_running/m_poll_thread_id on its own (see run_poll_loop).
|
||||
return;
|
||||
}
|
||||
|
||||
// wait for the loop to actually finish
|
||||
m_lifecycle_cv.wait(lifecycle_lock, [&]() { return !m_poll_loop_running; });
|
||||
}
|
||||
|
||||
void thread_poller::run_poll_loop() {
|
||||
// called with m_lifecycle_mutex held
|
||||
if (m_poll_loop_running) return; // only run one loop at a time
|
||||
m_poll_loop_running = true;
|
||||
|
||||
// TODO: use global threadpool, background sync wasm wallet in c++ thread
|
||||
try {
|
||||
boost::thread([this]() {
|
||||
{
|
||||
boost::lock_guard<boost::mutex> lifecycle_lock(m_lifecycle_mutex);
|
||||
m_poll_thread_id = boost::this_thread::get_id();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
while (m_is_polling) {
|
||||
try { poll(); }
|
||||
catch (const std::exception& e) { MERROR(m_name << " failed to background poll: " << e.what()); }
|
||||
catch (...) { MERROR(m_name << " failed to background poll"); }
|
||||
|
||||
// only wait if polling still enabled
|
||||
if (m_is_polling) {
|
||||
boost::mutex::scoped_lock lock(m_polling_mutex);
|
||||
boost::posix_time::milliseconds wait_for_ms(m_poll_period_ms.load());
|
||||
m_poll_cv.timed_wait(lock, wait_for_ms, [&]() { return !m_is_polling; });
|
||||
}
|
||||
}
|
||||
|
||||
boost::unique_lock<boost::mutex> lifecycle_lock(m_lifecycle_mutex);
|
||||
if (m_is_polling) continue;
|
||||
m_poll_loop_running = false;
|
||||
m_poll_thread_id = boost::thread::id();
|
||||
m_lifecycle_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
}).detach();
|
||||
} catch (...) {
|
||||
m_poll_loop_running = false;
|
||||
m_is_polling = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* Copyright (c) woodser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2014-2019, The Monero Project
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifndef gen_utils_h
|
||||
#define gen_utils_h
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include "rapidjson/document.h"
|
||||
#include <boost/thread/recursive_mutex.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
#include <boost/thread/condition_variable.hpp>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include "include_base_utils.h"
|
||||
#include "common/util.h"
|
||||
|
||||
/**
|
||||
* Collection of generic utilities.
|
||||
*/
|
||||
namespace gen_utils
|
||||
{
|
||||
|
||||
/**
|
||||
* Return a unique identifier.
|
||||
*
|
||||
* @return a unique id
|
||||
*/
|
||||
static std::string get_uuid() {
|
||||
boost::uuids::random_generator generator;
|
||||
boost::uuids::uuid uuid = generator();
|
||||
return boost::uuids::to_string(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given duration.
|
||||
*
|
||||
* @param duration_ms the duration to wait in milliseconds
|
||||
*/
|
||||
static void wait_for(uint64_t duration_ms) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
|
||||
}
|
||||
|
||||
static bool bool_equals(bool val, const boost::optional<bool>& opt_val) {
|
||||
return opt_val == boost::none ? false : val == *opt_val;
|
||||
}
|
||||
|
||||
// ------------------------- SERIALIZATION ---------------------------
|
||||
|
||||
// TODO: fully switch from property trees to rapidjson
|
||||
|
||||
std::string serialize(const rapidjson::Document& doc);
|
||||
std::string serialize(const boost::property_tree::ptree& node);
|
||||
void deserialize(const std::string& json, boost::property_tree::ptree& root);
|
||||
|
||||
// ------------------------- VALUE RECONCILATION ----------------------------
|
||||
|
||||
// TODO: refactor common template code
|
||||
template <class T, typename std::enable_if<std::is_same<T, std::string>::value, T>::type* = nullptr>
|
||||
boost::optional<T> reconcile(const boost::optional<T>& val1, const boost::optional<T>& val2, boost::optional<bool> resolve_defined, boost::optional<bool> resolve_true, boost::optional<bool> resolve_max, const std::string& err_msg = "") {
|
||||
|
||||
// check for equality
|
||||
if (val1 == val2) return val1;
|
||||
|
||||
// resolve one value none
|
||||
if (val1 == boost::none || val2 == boost::none) {
|
||||
if (resolve_defined != boost::none && *resolve_defined == false) return boost::none;
|
||||
else return val1 == boost::none ? val2 : val1;
|
||||
}
|
||||
|
||||
throw std::runtime_error(std::string("Cannot reconcile strings: ") + boost::lexical_cast<std::string>(val1) + std::string(" vs ") + boost::lexical_cast<std::string>(val2) + (!err_msg.empty() ? std::string(". ") + err_msg : std::string("")));
|
||||
}
|
||||
template <class T, typename std::enable_if<std::is_same<T, std::string>::value, T>::type* = nullptr>
|
||||
boost::optional<T> reconcile(const boost::optional<T>& val1, const boost::optional<T>& val2, const std::string& err_msg = "") {
|
||||
return reconcile(val1, val2, boost::none, boost::none, boost::none, err_msg);
|
||||
}
|
||||
|
||||
template <class T, typename std::enable_if<std::is_integral<T>::value, T>::type* = nullptr>
|
||||
boost::optional<T> reconcile(const boost::optional<T>& val1, const boost::optional<T>& val2, boost::optional<bool> resolve_defined, boost::optional<bool> resolve_true, boost::optional<bool> resolve_max, const std::string& err_msg = "") {
|
||||
|
||||
// check for equality
|
||||
if (val1 == val2) return val1;
|
||||
|
||||
// resolve one value none
|
||||
if (val1 == boost::none || val2 == boost::none) {
|
||||
if (resolve_defined != boost::none && *resolve_defined == false) return boost::none;
|
||||
else return val1 == boost::none ? val2 : val1;
|
||||
}
|
||||
|
||||
// resolve different booleans
|
||||
if (resolve_true != boost::none) return (bool) val1 == *resolve_true ? val1 : val2; // if resolve true, return true, else return false
|
||||
|
||||
// resolve different numbers
|
||||
if (resolve_max != boost::none) return *resolve_max ? std::max(*val1, *val2) : std::min(*val1, *val2);
|
||||
|
||||
// cannot reconcile
|
||||
throw std::runtime_error(std::string("Cannot reconcile integrals: ") + boost::lexical_cast<std::string>(val1) + std::string(" vs ") + boost::lexical_cast<std::string>(val2) + (!err_msg.empty() ? std::string(". ") + err_msg : std::string("")));
|
||||
}
|
||||
template <class T, typename std::enable_if<std::is_integral<T>::value, T>::type* = nullptr>
|
||||
boost::optional<T> reconcile(const boost::optional<T>& val1, const boost::optional<T>& val2, const std::string& err_msg = "") {
|
||||
return reconcile(val1, val2, boost::none, boost::none, boost::none, err_msg);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::vector<T> reconcile(const std::vector<T>& v1, const std::vector<T>& v2, const std::string& err_msg = "") {
|
||||
|
||||
// check for equality
|
||||
if (v1 == v2) return v1;
|
||||
|
||||
// resolve one vector empty
|
||||
if (v1.empty()) return v2;
|
||||
if (v2.empty()) return v1;
|
||||
|
||||
// otherwise cannot reconcile
|
||||
throw std::runtime_error("Cannot reconcile vectors" + (!err_msg.empty() ? std::string(". ") + err_msg : std::string("")));
|
||||
}
|
||||
|
||||
// ------------------------- THREAD POLLER ----------------------------
|
||||
|
||||
// WARNING: Only destroy a thread_poller from a thread other than its own pool thread.
|
||||
// (e.g. a listener callback running on the poll thread, drops the last owning reference/unique_ptr
|
||||
// to the poller.)
|
||||
class thread_poller {
|
||||
public:
|
||||
thread_poller();
|
||||
virtual ~thread_poller();
|
||||
|
||||
bool is_polling() const { return m_is_polling; }
|
||||
void set_is_polling(bool is_polling);
|
||||
void request_is_polling(bool is_polling);
|
||||
|
||||
void set_period_in_ms(uint64_t period_ms) { m_poll_period_ms = period_ms; }
|
||||
virtual void poll() = 0;
|
||||
void wait_for_callbacks_idle();
|
||||
|
||||
class announce_scope {
|
||||
public:
|
||||
explicit announce_scope(thread_poller& poller);
|
||||
~announce_scope();
|
||||
announce_scope(const announce_scope&) = delete;
|
||||
announce_scope& operator=(const announce_scope&) = delete;
|
||||
private:
|
||||
thread_poller& m_poller;
|
||||
};
|
||||
|
||||
protected:
|
||||
std::string m_name;
|
||||
boost::recursive_mutex m_mutex;
|
||||
boost::mutex m_polling_mutex; // guards the interruptible periodic sleep (m_poll_cv)
|
||||
boost::mutex m_lifecycle_mutex; // guards m_poll_loop_running/m_poll_thread_id and start/stop coordination
|
||||
boost::thread::id m_poll_thread_id; // id of the currently running loop thread; guarded by m_lifecycle_mutex
|
||||
std::atomic<bool> m_is_polling;
|
||||
bool m_poll_loop_running; // guarded by m_lifecycle_mutex
|
||||
std::atomic<uint64_t> m_poll_period_ms;
|
||||
boost::condition_variable m_poll_cv; // periodic-sleep interrupt
|
||||
boost::condition_variable m_lifecycle_cv; // notified (under m_lifecycle_mutex) when the loop actually exits
|
||||
boost::mutex m_announce_mutex; // guards m_announce_count
|
||||
boost::condition_variable m_announce_cv; // notified (under m_announce_mutex) when m_announce_count reaches 0
|
||||
int m_announce_count = 0; // number of announce_scope instances currently alive, across all threads
|
||||
|
||||
void init_common(const std::string& name);
|
||||
void run_poll_loop();
|
||||
};
|
||||
}
|
||||
#endif /* gen_utils_h */
|
||||
@@ -0,0 +1,624 @@
|
||||
/**
|
||||
* Copyright (c) woodser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2014-2019, The Monero Project
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
*/
|
||||
|
||||
#include "monero_utils.h"
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
#include "storages/portable_storage_template_helper.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
#include "mnemonics/electrum-words.h"
|
||||
#include "mnemonics/english.h"
|
||||
#include "string_tools.h"
|
||||
#include "byte_stream.h"
|
||||
#include "gen_utils.h"
|
||||
|
||||
using namespace cryptonote;
|
||||
using namespace monero_utils;
|
||||
|
||||
void monero_utils::set_log_level(int level) {
|
||||
mlog_set_log_level(level);
|
||||
}
|
||||
|
||||
void monero_utils::set_log_categories(const std::string& categories) {
|
||||
mlog_set_categories(categories.c_str());
|
||||
}
|
||||
|
||||
void monero_utils::configure_logging(const std::string& path, bool console) {
|
||||
mlog_configure(path, console);
|
||||
}
|
||||
|
||||
// --------------------------- VALIDATION UTILS -------------------------------
|
||||
|
||||
monero_integrated_address monero_utils::get_integrated_address(monero_network_type network_type, const std::string& standard_address, const std::string& payment_id) {
|
||||
|
||||
// parse and validate address
|
||||
cryptonote::address_parse_info address_info;
|
||||
if (!get_account_address_from_str(address_info, static_cast<cryptonote::network_type>(network_type), standard_address)) throw std::runtime_error("Invalid address");
|
||||
if (address_info.has_payment_id) throw std::runtime_error("The given address already has a payment id");
|
||||
|
||||
// randomly generate payment id if not given, else validate
|
||||
crypto::hash8 payment_id_h8;
|
||||
if (payment_id.empty()) {
|
||||
payment_id_h8 = crypto::rand<crypto::hash8>();
|
||||
} else {
|
||||
cryptonote::blobdata payment_id_data;
|
||||
if (!epee::string_tools::parse_hexstr_to_binbuff(payment_id, payment_id_data) || sizeof(crypto::hash8) != payment_id_data.size()) throw std::runtime_error("Invalid payment id");
|
||||
payment_id_h8 = *reinterpret_cast<const crypto::hash8*>(payment_id_data.data());
|
||||
}
|
||||
|
||||
// build integrated address
|
||||
monero_integrated_address integrated_address;
|
||||
integrated_address.m_integrated_address = cryptonote::get_account_integrated_address_as_str(static_cast<cryptonote::network_type>(network_type), address_info.address, payment_id_h8);
|
||||
integrated_address.m_standard_address = standard_address;
|
||||
integrated_address.m_payment_id = epee::string_tools::pod_to_hex(payment_id_h8);
|
||||
return integrated_address;
|
||||
}
|
||||
|
||||
bool monero_utils::is_valid_address(const std::string& address, monero_network_type network_type) {
|
||||
try {
|
||||
validate_address(address, network_type);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool monero_utils::is_valid_private_view_key(const std::string& private_view_key) {
|
||||
try {
|
||||
validate_private_view_key(private_view_key);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool monero_utils::is_valid_private_spend_key(const std::string& private_spend_key) {
|
||||
try {
|
||||
validate_private_spend_key(private_spend_key);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool monero_utils::is_valid_public_view_key(const std::string& public_view_key) {
|
||||
try {
|
||||
validate_public_view_key(public_view_key);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool monero_utils::is_valid_public_spend_key(const std::string& public_spend_key) {
|
||||
try {
|
||||
validate_public_spend_key(public_spend_key);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool monero_utils::is_valid_payment_id(const std::string& payment_id) {
|
||||
try {
|
||||
validate_payment_id(payment_id);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool monero_utils::is_valid_mnemonic(const std::string& mnemonic, const std::string& language) {
|
||||
try {
|
||||
validate_mnemonic(mnemonic, language);
|
||||
return true;
|
||||
}
|
||||
catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void monero_utils::validate_address(const std::string& address, monero_network_type network_type) {
|
||||
cryptonote::address_parse_info info;
|
||||
if (!get_account_address_from_str(info, static_cast<cryptonote::network_type>(network_type), address)) throw std::runtime_error("Invalid address");
|
||||
}
|
||||
|
||||
void monero_utils::validate_private_view_key(const std::string& private_view_key) {
|
||||
if (private_view_key.length() != 64) throw std::runtime_error("private view key expected to be 64 hex characters");
|
||||
cryptonote::blobdata private_view_key_data;
|
||||
if (!epee::string_tools::parse_hexstr_to_binbuff(private_view_key, private_view_key_data) || private_view_key_data.size() != sizeof(crypto::secret_key)) {
|
||||
throw std::runtime_error("private view key expected to be 64 hex characters");
|
||||
}
|
||||
}
|
||||
|
||||
void monero_utils::validate_private_spend_key(const std::string& private_spend_key) {
|
||||
if (private_spend_key.length() != 64) throw std::runtime_error("private spend key expected to be 64 hex characters");
|
||||
cryptonote::blobdata private_spend_key_data;
|
||||
if (!epee::string_tools::parse_hexstr_to_binbuff(private_spend_key, private_spend_key_data) || private_spend_key_data.size() != sizeof(crypto::secret_key)) {
|
||||
throw std::runtime_error("private spend key expected to be 64 hex characters");
|
||||
}
|
||||
}
|
||||
|
||||
void monero_utils::validate_public_view_key(const std::string& public_view_key) {
|
||||
if (public_view_key.length() != 64) throw std::runtime_error("public view key expected to be 64 hex characters");
|
||||
cryptonote::blobdata public_view_key_data;
|
||||
if (!epee::string_tools::parse_hexstr_to_binbuff(public_view_key, public_view_key_data) || public_view_key_data.size() != sizeof(crypto::public_key)) {
|
||||
throw std::runtime_error("public view key expected to be 64 hex characters");
|
||||
}
|
||||
}
|
||||
|
||||
void monero_utils::validate_public_spend_key(const std::string& public_spend_key) {
|
||||
if (public_spend_key.length() != 64) throw std::runtime_error("public spend key expected to be 64 hex characters");
|
||||
cryptonote::blobdata public_spend_key_data;
|
||||
if (!epee::string_tools::parse_hexstr_to_binbuff(public_spend_key, public_spend_key_data) || public_spend_key_data.size() != sizeof(crypto::public_key)) {
|
||||
throw std::runtime_error("public spend key expected to be 64 hex characters");
|
||||
}
|
||||
}
|
||||
|
||||
void monero_utils::validate_payment_id(const std::string& payment_id) {
|
||||
if (payment_id.length() != 64 && payment_id.length() != 16) throw std::runtime_error("payment id expected to be 64 or 16 hex characters");
|
||||
cryptonote::blobdata payment_id_data;
|
||||
if(!epee::string_tools::parse_hexstr_to_binbuff(payment_id, payment_id_data) || (payment_id_data.size() != sizeof(crypto::hash8) && payment_id_data.size() != sizeof(crypto::hash))) {
|
||||
throw std::runtime_error("payment id expected to be 64 or 16 hex characters");
|
||||
}
|
||||
}
|
||||
|
||||
void monero_utils::validate_mnemonic(const std::string& mnemonic, const std::string& language) {
|
||||
if (mnemonic.empty()) throw std::runtime_error("Mnemonic phrase is empty");
|
||||
std::string seed_language;
|
||||
crypto::secret_key recovery_key;
|
||||
|
||||
if (crypto::ElectrumWords::get_is_old_style_seed(mnemonic)) throw std::runtime_error("Mnemonic phrased words must be 25");
|
||||
if(!crypto::ElectrumWords::words_to_bytes(mnemonic, recovery_key, seed_language)) throw std::runtime_error("Invalid mnemonic");
|
||||
if (seed_language == crypto::ElectrumWords::old_language_name) seed_language = "English";
|
||||
|
||||
// validate language
|
||||
if (!language.empty()) {
|
||||
if (!crypto::ElectrumWords::is_valid_language(language)) throw std::runtime_error("Invalid language: " + language);
|
||||
if (language != seed_language) throw std::runtime_error("Seed language mismatch");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------------------- BINARY SERIALIZATION ----------------------------
|
||||
|
||||
void monero_utils::json_to_binary(const std::string &json, std::string &bin) {
|
||||
epee::serialization::portable_storage ps;
|
||||
ps.load_from_json(json);
|
||||
epee::byte_stream bs;
|
||||
ps.store_to_binary(bs);
|
||||
bin = std::string((char*) bs.data(), bs.size());
|
||||
}
|
||||
|
||||
void monero_utils::binary_to_json(const std::string &bin, std::string &json) {
|
||||
epee::serialization::portable_storage ps;
|
||||
ps.load_from_binary(bin);
|
||||
ps.dump_as_json(json);
|
||||
}
|
||||
|
||||
void monero_utils::binary_blocks_to_json(const std::string &bin, std::string &json) {
|
||||
|
||||
// load binary rpc response to struct
|
||||
cryptonote::COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response resp_struct;
|
||||
epee::serialization::load_t_from_binary(resp_struct, bin);
|
||||
|
||||
// build property tree from deserialized blocks and transactions
|
||||
boost::property_tree::ptree root;
|
||||
boost::property_tree::ptree blocksNode; // array of block strings
|
||||
boost::property_tree::ptree txsNodes; // array of txs per block (array of array)
|
||||
for (int blockIdx = 0; blockIdx < resp_struct.blocks.size(); blockIdx++) {
|
||||
|
||||
// parse and validate block
|
||||
cryptonote::block block;
|
||||
if (cryptonote::parse_and_validate_block_from_blob(resp_struct.blocks[blockIdx].block, block)) {
|
||||
|
||||
// add block node to blocks node
|
||||
boost::property_tree::ptree blockNode;
|
||||
blockNode.put("", cryptonote::obj_to_json_str(block)); // TODO: no pretty print
|
||||
blocksNode.push_back(std::make_pair("", blockNode));
|
||||
} else {
|
||||
throw std::runtime_error("failed to parse block blob at index " + std::to_string(blockIdx));
|
||||
}
|
||||
|
||||
// parse and validate txs
|
||||
boost::property_tree::ptree txs_node;
|
||||
for (int txIdx = 0; txIdx < resp_struct.blocks[blockIdx].txs.size(); txIdx++) {
|
||||
cryptonote::transaction tx;
|
||||
if (cryptonote::parse_and_validate_tx_from_blob(resp_struct.blocks[blockIdx].txs[txIdx].blob, tx)) {
|
||||
|
||||
// add tx node to txs node
|
||||
boost::property_tree::ptree txNode;
|
||||
//MTRACE("PRUNED:\n" << monero_utils::get_pruned_tx_json(tx));
|
||||
txNode.put("", monero_utils::get_pruned_tx_json(tx)); // TODO: no pretty print
|
||||
txs_node.push_back(std::make_pair("", txNode));
|
||||
} else {
|
||||
throw std::runtime_error("failed to parse tx blob at index " + std::to_string(txIdx));
|
||||
}
|
||||
}
|
||||
txsNodes.push_back(std::make_pair("", txs_node)); // array of array of transactions, one array per block
|
||||
}
|
||||
root.add_child("blocks", blocksNode);
|
||||
root.add_child("txs", txsNodes);
|
||||
root.put("status", resp_struct.status);
|
||||
root.put("untrusted", resp_struct.untrusted); // TODO: loss of ints and bools
|
||||
|
||||
// convert root to string // TODO: common utility with serial_bridge
|
||||
std::stringstream ss;
|
||||
boost::property_tree::write_json(ss, root, false/*pretty*/);
|
||||
json = ss.str();
|
||||
}
|
||||
|
||||
// ------------------------------- RAPIDJSON ----------------------------------
|
||||
|
||||
void monero_utils::add_json_member(std::string key, std::string val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root, rapidjson::Value& field) {
|
||||
rapidjson::Value field_key(key.c_str(), key.size(), allocator);
|
||||
field.SetString(val.c_str(), val.size(), allocator);
|
||||
root.AddMember(field_key, field, allocator);
|
||||
}
|
||||
|
||||
void monero_utils::add_json_member(std::string key, bool val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root) {
|
||||
rapidjson::Value field_key(key.c_str(), key.size(), allocator);
|
||||
if (val) {
|
||||
rapidjson::Value field_val(rapidjson::kTrueType);
|
||||
root.AddMember(field_key, field_val, allocator);
|
||||
} else {
|
||||
rapidjson::Value field_val(rapidjson::kFalseType);
|
||||
root.AddMember(field_key, field_val, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<std::string>& strs) {
|
||||
rapidjson::Value value_arr(rapidjson::kArrayType);
|
||||
rapidjson::Value value_str(rapidjson::kStringType);
|
||||
for (const std::string& str : strs) {
|
||||
value_str.SetString(str.c_str(), str.size(), allocator);
|
||||
value_arr.PushBack(value_str, allocator);
|
||||
}
|
||||
return value_arr;
|
||||
}
|
||||
|
||||
rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<int>& nums) {
|
||||
rapidjson::Value value_arr(rapidjson::kArrayType);
|
||||
rapidjson::Value value_num(rapidjson::kNumberType);
|
||||
for (const auto& num : nums) {
|
||||
value_num.SetInt(num);
|
||||
value_arr.PushBack(value_num, allocator);
|
||||
}
|
||||
return value_arr;
|
||||
}
|
||||
|
||||
rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<uint8_t>& nums) {
|
||||
rapidjson::Value value_arr(rapidjson::kArrayType);
|
||||
rapidjson::Value value_num(rapidjson::kNumberType);
|
||||
for (const auto& num : nums) {
|
||||
value_num.SetInt(num);
|
||||
value_arr.PushBack(value_num, allocator);
|
||||
}
|
||||
return value_arr;
|
||||
}
|
||||
|
||||
// TODO: remove these redundant implementations for different sizes?
|
||||
rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<uint32_t>& nums) {
|
||||
rapidjson::Value value_arr(rapidjson::kArrayType);
|
||||
rapidjson::Value value_num(rapidjson::kNumberType);
|
||||
for (const auto& num : nums) {
|
||||
value_num.SetUint64(num);
|
||||
value_arr.PushBack(value_num, allocator);
|
||||
}
|
||||
return value_arr;
|
||||
}
|
||||
|
||||
rapidjson::Value monero_utils::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<uint64_t>& nums) {
|
||||
rapidjson::Value value_arr(rapidjson::kArrayType);
|
||||
rapidjson::Value value_num(rapidjson::kNumberType);
|
||||
for (const auto& num : nums) {
|
||||
value_num.SetUint64(num);
|
||||
value_arr.PushBack(value_num, allocator);
|
||||
}
|
||||
return value_arr;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
bool monero_utils::is_valid_language(const std::string& language) {
|
||||
std::vector<std::string> languages;
|
||||
crypto::ElectrumWords::get_language_list(languages, false);
|
||||
std::vector<std::string>::iterator it = std::find(languages.begin(), languages.end(), language);
|
||||
if (it == languages.end()) {
|
||||
crypto::ElectrumWords::get_language_list(languages, true);
|
||||
it = std::find(languages.begin(), languages.end(), language);
|
||||
}
|
||||
if (it == languages.end()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: this is unused
|
||||
std::shared_ptr<monero_block> monero_utils::cn_block_to_block(const cryptonote::block& cn_block) {
|
||||
cryptonote::block temp = cn_block;
|
||||
std::cout << cryptonote::obj_to_json_str(temp) << std::endl;
|
||||
std::shared_ptr<monero_block> block = std::make_shared<monero_block>();
|
||||
block->m_major_version = cn_block.major_version;
|
||||
block->m_minor_version = cn_block.minor_version;
|
||||
block->m_timestamp = cn_block.timestamp;
|
||||
block->m_prev_hash = epee::string_tools::pod_to_hex(cn_block.prev_id);
|
||||
block->m_nonce = cn_block.nonce;
|
||||
block->m_miner_tx = monero_utils::cn_tx_to_tx(cn_block.miner_tx);
|
||||
for (const crypto::hash& tx_hash : cn_block.tx_hashes) {
|
||||
block->m_tx_hashes.push_back(epee::string_tools::pod_to_hex(tx_hash));
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
std::shared_ptr<monero_tx> monero_utils::cn_tx_to_tx(const cryptonote::transaction& cn_tx, bool init_as_tx_wallet) {
|
||||
std::shared_ptr<monero_tx> tx = init_as_tx_wallet ? std::make_shared<monero_tx_wallet>() : std::make_shared<monero_tx>();
|
||||
tx->m_version = cn_tx.version;
|
||||
tx->m_unlock_time = cn_tx.unlock_time;
|
||||
tx->m_hash = epee::string_tools::pod_to_hex(cn_tx.hash);
|
||||
tx->m_extra = cn_tx.extra;
|
||||
|
||||
// init inputs
|
||||
for (const txin_v& cnVin : cn_tx.vin) {
|
||||
if (cnVin.which() != 0 && cnVin.which() != 3) throw std::runtime_error("Unsupported variant type");
|
||||
if (tx->m_is_miner_tx == boost::none) tx->m_is_miner_tx = cnVin.which() == 0;
|
||||
if (cnVin.which() != 3) continue; // only process txin_to_key of variant TODO: support other types, like 0 "gen" which is miner tx?
|
||||
std::shared_ptr<monero_output> input = init_as_tx_wallet ? std::make_shared<monero_output_wallet>() : std::make_shared<monero_output>();
|
||||
input->m_tx = tx;
|
||||
tx->m_inputs.push_back(input);
|
||||
const txin_to_key& txin = boost::get<txin_to_key>(cnVin);
|
||||
input->m_amount = txin.amount;
|
||||
input->m_ring_output_indices = txin.key_offsets;
|
||||
crypto::key_image cnKeyImage = txin.k_image;
|
||||
input->m_key_image = std::make_shared<monero_key_image>();
|
||||
input->m_key_image->m_hex = epee::string_tools::pod_to_hex(cnKeyImage);
|
||||
}
|
||||
|
||||
// init outputs
|
||||
for (const tx_out& cnVout : cn_tx.vout) {
|
||||
std::shared_ptr<monero_output> output = init_as_tx_wallet ? std::make_shared<monero_output_wallet>() : std::make_shared<monero_output>();
|
||||
output->m_tx = tx;
|
||||
tx->m_outputs.push_back(output);
|
||||
output->m_amount = cnVout.amount;
|
||||
|
||||
// before HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_key
|
||||
// after HF_VERSION_VIEW_TAGS, outputs with public keys are of type txout_to_tagged_key
|
||||
crypto::public_key cnStealthPublicKey;
|
||||
if (cnVout.target.type() == typeid(txout_to_key))
|
||||
cnStealthPublicKey = boost::get<txout_to_key>(cnVout.target).key;
|
||||
else if (cnVout.target.type() == typeid(txout_to_tagged_key))
|
||||
cnStealthPublicKey = boost::get<txout_to_tagged_key>(cnVout.target).key;
|
||||
else
|
||||
throw std::runtime_error(std::string("Unexpected output target type found: ") + std::string(cnVout.target.type().name()));
|
||||
output->m_stealth_public_key = epee::string_tools::pod_to_hex(cnStealthPublicKey);
|
||||
}
|
||||
|
||||
return tx;
|
||||
|
||||
// TODO: finish this, cryptonote::transaction has:
|
||||
// std::vector<std::vector<crypto::signature> > m_signatures;
|
||||
// rct::rctSig m_rct_signatures;
|
||||
// mutable size_t blob_size;
|
||||
}
|
||||
|
||||
void monero_utils::binary_blocks_to_property_tree(const std::string &bin, boost::property_tree::ptree &node) {
|
||||
std::string response_json;
|
||||
monero_utils::binary_blocks_to_json(bin, response_json);
|
||||
std::istringstream iss(response_json);
|
||||
boost::property_tree::read_json(iss, node);
|
||||
|
||||
auto blocks = node.get_child("blocks");
|
||||
boost::property_tree::ptree parsed_blocks;
|
||||
|
||||
for (auto &entry : blocks) {
|
||||
const std::string &block_str = entry.second.get_value<std::string>();
|
||||
boost::property_tree::ptree block_node;
|
||||
gen_utils::deserialize(block_str, block_node);
|
||||
parsed_blocks.push_back(std::make_pair("", block_node));
|
||||
}
|
||||
|
||||
node.put_child("blocks", parsed_blocks);
|
||||
|
||||
auto txs = node.get_child("txs");
|
||||
boost::property_tree::ptree all_txs;
|
||||
|
||||
for (auto &rpc_txs_entry : txs) {
|
||||
boost::property_tree::ptree txs_for_block;
|
||||
const auto &rpc_txs = rpc_txs_entry.second;
|
||||
|
||||
if (!rpc_txs.empty() || !rpc_txs.data().empty()) {
|
||||
for (auto &tx_entry : rpc_txs) {
|
||||
std::string tx_str = tx_entry.second.get_value<std::string>();
|
||||
|
||||
auto pos = tx_str.find(',');
|
||||
if (pos != std::string::npos) {
|
||||
tx_str.replace(pos, 1, "{");
|
||||
tx_str += "}";
|
||||
}
|
||||
|
||||
boost::property_tree::ptree tx_node;
|
||||
gen_utils::deserialize(tx_str, tx_node);
|
||||
txs_for_block.push_back(std::make_pair("", tx_node));
|
||||
}
|
||||
}
|
||||
|
||||
all_txs.push_back(std::make_pair("", txs_for_block));
|
||||
}
|
||||
|
||||
node.put_child("txs", all_txs);
|
||||
}
|
||||
|
||||
void monero_utils::merge_tx(const std::shared_ptr<monero_tx_wallet>& tx, std::map<std::string, std::shared_ptr<monero_tx_wallet>>& tx_map, std::map<uint64_t, std::shared_ptr<monero_block>>& block_map) {
|
||||
if (tx->m_hash == boost::none) throw std::runtime_error("Tx hash is not initialized");
|
||||
|
||||
// merge tx
|
||||
std::map<std::string, std::shared_ptr<monero_tx_wallet>>::const_iterator tx_iter = tx_map.find(*tx->m_hash);
|
||||
if (tx_iter == tx_map.end()) {
|
||||
tx_map[*tx->m_hash] = tx; // cache new tx
|
||||
} else {
|
||||
std::shared_ptr<monero_tx_wallet>& a_tx = tx_map[*tx->m_hash];
|
||||
a_tx->merge(a_tx, tx); // merge with existing tx
|
||||
}
|
||||
|
||||
// merge tx's block if confirmed
|
||||
if (tx->get_height() != boost::none) {
|
||||
std::map<uint64_t, std::shared_ptr<monero_block>>::const_iterator block_iter = block_map.find(tx->get_height().get());
|
||||
if (block_iter == block_map.end()) {
|
||||
block_map[tx->get_height().get()] = tx->m_block; // cache new block
|
||||
} else {
|
||||
std::shared_ptr<monero_block>& a_block = block_map[tx->get_height().get()];
|
||||
a_block->merge(a_block, tx->m_block); // merge with existing block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool monero_utils::tx_height_less_than(const std::shared_ptr<monero_tx>& tx1, const std::shared_ptr<monero_tx>& tx2) {
|
||||
if (tx1->m_block != nullptr && tx2->m_block != nullptr) return tx1->get_height() < tx2->get_height();
|
||||
else if (tx1->m_block == nullptr) return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
bool monero_utils::incoming_transfer_before(const std::shared_ptr<monero_incoming_transfer>& transfer1, const std::shared_ptr<monero_incoming_transfer>& transfer2) {
|
||||
|
||||
// compare by height
|
||||
if (tx_height_less_than(transfer1->m_tx, transfer2->m_tx)) return true;
|
||||
|
||||
// compare by account and subaddress index
|
||||
if (transfer1->m_account_index.get() < transfer2->m_account_index.get()) return true;
|
||||
else if (transfer1->m_account_index.get() == transfer2->m_account_index.get()) return transfer1->m_subaddress_index.get() < transfer2->m_subaddress_index.get();
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool monero_utils::vout_before(const std::shared_ptr<monero_output>& o1, const std::shared_ptr<monero_output>& o2) {
|
||||
if (o1 == o2) return false; // ignore equal references
|
||||
std::shared_ptr<monero_output_wallet> ow1 = std::static_pointer_cast<monero_output_wallet>(o1);
|
||||
std::shared_ptr<monero_output_wallet> ow2 = std::static_pointer_cast<monero_output_wallet>(o2);
|
||||
|
||||
// compare by height
|
||||
if (tx_height_less_than(ow1->m_tx, ow2->m_tx)) return true;
|
||||
|
||||
// compare by account index, subaddress index, output index, then key image hex
|
||||
if (ow1->m_account_index.get() < ow2->m_account_index.get()) return true;
|
||||
if (ow1->m_account_index.get() == ow2->m_account_index.get()) {
|
||||
if (ow1->m_subaddress_index.get() < ow2->m_subaddress_index.get()) return true;
|
||||
if (ow1->m_subaddress_index.get() == ow2->m_subaddress_index.get()) {
|
||||
if (ow1->m_index.get() < ow2->m_index.get()) return true;
|
||||
if (ow1->m_index.get() == ow2->m_index.get()) throw std::runtime_error("Should never sort outputs with duplicate indices");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, monero_network_type network_type) {
|
||||
cryptonote::address_parse_info info;
|
||||
|
||||
if(!get_account_address_from_str(info, static_cast<cryptonote::network_type>(network_type), address)) {
|
||||
throw std::runtime_error(std::string("Invalid address: ") + address);
|
||||
}
|
||||
if (!payment_id.empty()) {
|
||||
throw std::runtime_error("Standalone payment id deprecated, use integrated address instead");
|
||||
}
|
||||
|
||||
std::string uri = "monero:" + address;
|
||||
unsigned int n_fields = 0;
|
||||
|
||||
if (amount > 0) {
|
||||
// URI encoded amount is in decimal units, not atomic units
|
||||
uri += (n_fields++ ? "&" : "?") + std::string("tx_amount=") + cryptonote::print_money(amount);
|
||||
}
|
||||
if (!recipient_name.empty()) {
|
||||
uri += (n_fields++ ? "&" : "?") + std::string("recipient_name=") + epee::net_utils::conver_to_url_format(recipient_name);
|
||||
}
|
||||
if (!tx_description.empty()) {
|
||||
uri += (n_fields++ ? "&" : "?") + std::string("tx_description=") + epee::net_utils::conver_to_url_format(tx_description);
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
std::string monero_utils::get_payment_uri(const monero_tx_config& config, monero_network_type network_type) {
|
||||
// validate config
|
||||
std::vector<std::shared_ptr<monero_destination>> destinations = config.get_normalized_destinations();
|
||||
if (destinations.size() != 1) throw std::runtime_error("Cannot make URI from supplied parameters: must provide exactly one destination to send funds");
|
||||
if (destinations.at(0)->m_address == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination address");
|
||||
if (destinations.at(0)->m_amount == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination amount");
|
||||
|
||||
// prepare wallet2 params
|
||||
std::string address = destinations.at(0)->m_address.get();
|
||||
std::string payment_id = config.m_payment_id == boost::none ? "" : config.m_payment_id.get();
|
||||
uint64_t amount = destinations.at(0)->m_amount.get();
|
||||
std::string note = config.m_note == boost::none ? "" : config.m_note.get();
|
||||
std::string m_recipient_name = config.m_recipient_name == boost::none ? "" : config.m_recipient_name.get();
|
||||
|
||||
// make uri
|
||||
std::string uri = make_uri(address, payment_id, amount, note, m_recipient_name, network_type);
|
||||
if (uri.empty()) throw std::runtime_error("Cannot make URI from supplied parameters");
|
||||
return uri;
|
||||
}
|
||||
|
||||
uint64_t monero_utils::xmr_to_atomic_units(double amount_xmr) {
|
||||
if (!std::isfinite(amount_xmr) || amount_xmr < 0) throw std::invalid_argument("amount must be a finite, non-negative number");
|
||||
|
||||
long double atomic = std::round(static_cast<long double>(amount_xmr) * static_cast<long double>(XMR_AU_MULTIPLIER));
|
||||
|
||||
// validate the actual rounded value about to be cast, not a precomputed "amount_xmr" threshold:
|
||||
// a threshold like UINT64_MAX / XMR_AU_MULTIPLIER carries its own division rounding error and can
|
||||
// let a value that overflows uint64_t slip through, and casting an out-of-range floating point
|
||||
// value to uint64_t is undefined behavior (not a wraparound). 2^64 is exactly representable in any
|
||||
// binary floating point type with enough exponent range (it's a pure power of two), so this bound
|
||||
// check is exact regardless of long double's precision on a given platform.
|
||||
static const long double MAX_ATOMIC_UNITS_EXCLUSIVE = std::ldexp(1.0L, 64); // 2^64
|
||||
if (atomic < 0.0L || atomic >= MAX_ATOMIC_UNITS_EXCLUSIVE) throw std::invalid_argument("amount exceeds maximum representable atomic units");
|
||||
|
||||
return static_cast<uint64_t>(atomic);
|
||||
}
|
||||
|
||||
double monero_utils::atomic_units_to_xmr(uint64_t amount_atomic_units) {
|
||||
return static_cast<double>(amount_atomic_units) / static_cast<double>(XMR_AU_MULTIPLIER);
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* Copyright (c) woodser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2014-2019, The Monero Project
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef monero_utils_h
|
||||
#define monero_utils_h
|
||||
|
||||
#include "wallet/monero_wallet_model.h"
|
||||
#include "cryptonote_basic/cryptonote_basic.h"
|
||||
#include "serialization/keyvalue_serialization.h" // TODO: consolidate with other binary deps?
|
||||
#include "storages/portable_storage.h"
|
||||
|
||||
/**
|
||||
* Collection of utilities for the Monero library.
|
||||
*/
|
||||
namespace monero_utils
|
||||
{
|
||||
using namespace cryptonote;
|
||||
|
||||
// ------------------------------ CONSTANTS ---------------------------------
|
||||
|
||||
static const int RING_SIZE = 12; // network-enforced ring size
|
||||
static const uint64_t XMR_AU_MULTIPLIER = 1000000000000ULL;
|
||||
|
||||
// -------------------------------- UTILS -----------------------------------
|
||||
|
||||
void set_log_level(int level);
|
||||
void set_log_categories(const std::string& categories);
|
||||
void configure_logging(const std::string& path, bool console);
|
||||
monero_integrated_address get_integrated_address(monero_network_type network_type, const std::string& standard_address, const std::string& payment_id);
|
||||
std::string get_payment_uri(const monero_tx_config& config, monero_network_type network_type);
|
||||
bool is_valid_address(const std::string& address, monero_network_type network_type);
|
||||
bool is_valid_private_view_key(const std::string& private_view_key);
|
||||
bool is_valid_private_spend_key(const std::string& private_spend_key);
|
||||
bool is_valid_public_view_key(const std::string& public_view_key);
|
||||
bool is_valid_public_spend_key(const std::string& public_spend_key);
|
||||
bool is_valid_payment_id(const std::string& payment_id);
|
||||
bool is_valid_mnemonic(const std::string& mnemonic, const std::string& language = "");
|
||||
void validate_address(const std::string& address, monero_network_type network_type);
|
||||
void validate_private_view_key(const std::string& private_view_key);
|
||||
void validate_private_spend_key(const std::string& private_spend_key);
|
||||
void validate_public_view_key(const std::string& public_view_key);
|
||||
void validate_public_spend_key(const std::string& public_spend_key);
|
||||
void validate_payment_id(const std::string& payment_id);
|
||||
void validate_mnemonic(const std::string& mnemonic, const std::string& language = "");
|
||||
void json_to_binary(const std::string &json, std::string &bin);
|
||||
void binary_to_json(const std::string &bin, std::string &json);
|
||||
void binary_blocks_to_json(const std::string &bin, std::string &json);
|
||||
uint64_t xmr_to_atomic_units(double amount_xmr);
|
||||
double atomic_units_to_xmr(uint64_t amount_atomic_units);
|
||||
|
||||
// ------------------------------ RAPIDJSON ---------------------------------
|
||||
|
||||
/**
|
||||
* Add number, string, and boolean json members using template specialization.
|
||||
*
|
||||
* TODO: add_json_member("key", "val", ...) treated as integer instead of string literal
|
||||
*/
|
||||
template <class T>
|
||||
void add_json_member(std::string key, T val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root, rapidjson::Value& field) {
|
||||
rapidjson::Value field_key(key.c_str(), key.size(), allocator);
|
||||
if (std::is_signed<T>::value) field.SetInt64((int64_t) val);
|
||||
else field.SetUint64((uint64_t) val);
|
||||
root.AddMember(field_key, field, allocator);
|
||||
}
|
||||
void add_json_member(std::string key, std::string val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root, rapidjson::Value& field);
|
||||
void add_json_member(std::string key, bool val, rapidjson::Document::AllocatorType& allocator, rapidjson::Value& root);
|
||||
|
||||
// TODO: template implementation here, could move to monero_utils.hpp per https://stackoverflow.com/questions/3040480/c-template-function-compiles-in-header-but-not-implementation
|
||||
template <class T> rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<std::shared_ptr<T>>& vals) {
|
||||
rapidjson::Value value_arr(rapidjson::kArrayType);
|
||||
for (const auto& val : vals) {
|
||||
value_arr.PushBack(val->to_rapidjson_val(allocator), allocator);
|
||||
}
|
||||
return value_arr;
|
||||
}
|
||||
|
||||
// TODO: template implementation here, could move to monero_utils.hpp per https://stackoverflow.com/questions/3040480/c-template-function-compiles-in-header-but-not-implementation
|
||||
template <class T> rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<T>& vals) {
|
||||
rapidjson::Value value_arr(rapidjson::kArrayType);
|
||||
for (const auto& val : vals) {
|
||||
value_arr.PushBack(val.to_rapidjson_val(allocator), allocator);
|
||||
}
|
||||
return value_arr;
|
||||
}
|
||||
|
||||
rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<std::string>& strs);
|
||||
rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<int>& nums);
|
||||
rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<uint8_t>& nums);
|
||||
rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<uint32_t>& nums);
|
||||
rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator, const std::vector<uint64_t>& nums);
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Indicates if the given language is valid.
|
||||
*
|
||||
* @param language is the language to validate
|
||||
* @return true if the language is valid, false otherwise
|
||||
*/
|
||||
bool is_valid_language(const std::string& language);
|
||||
|
||||
/**
|
||||
* Convert a cryptonote::block to a block in this library's native model.
|
||||
*
|
||||
* @param cn_block is the block to convert
|
||||
* @return a block in this library's native model
|
||||
*/
|
||||
std::shared_ptr<monero_block> cn_block_to_block(const cryptonote::block& cn_block);
|
||||
|
||||
/**
|
||||
* Convert a cryptonote::transaction to a transaction in this library's
|
||||
* native model.
|
||||
*
|
||||
* @param cn_tx is the transaction to convert
|
||||
* @param init_as_tx_wallet specifies if a monero_tx xor monero_tx_wallet should be initialized
|
||||
*/
|
||||
std::shared_ptr<monero_tx> cn_tx_to_tx(const cryptonote::transaction& cn_tx, bool init_as_tx_wallet = false);
|
||||
|
||||
/**
|
||||
* Modified from core_rpc_server.cpp to return a std::string.
|
||||
*
|
||||
* TODO: remove this duplicate, use core_rpc_server instead
|
||||
*/
|
||||
static std::string get_pruned_tx_json(cryptonote::transaction &tx)
|
||||
{
|
||||
std::stringstream ss;
|
||||
json_archive<true> ar(ss);
|
||||
bool r = tx.serialize_base(ar);
|
||||
CHECK_AND_ASSERT_MES(r, std::string(), "Failed to serialize rct signatures base");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void binary_blocks_to_property_tree(const std::string &bin, boost::property_tree::ptree &node);
|
||||
|
||||
/**
|
||||
* Merges a transaction into a unique set of transactions.
|
||||
*
|
||||
* @param tx is the transaction to merge into the existing txs
|
||||
* @param tx_map maps tx hashes to txs
|
||||
* @param block_map maps block heights to blocks
|
||||
*/
|
||||
void merge_tx(const std::shared_ptr<monero_tx_wallet>& tx, std::map<std::string, std::shared_ptr<monero_tx_wallet>>& tx_map, std::map<uint64_t, std::shared_ptr<monero_block>>& block_map);
|
||||
|
||||
/**
|
||||
* Returns true iff tx1's height is known to be less than tx2's height for sorting.
|
||||
*/
|
||||
bool tx_height_less_than(const std::shared_ptr<monero_tx>& tx1, const std::shared_ptr<monero_tx>& tx2);
|
||||
|
||||
/**
|
||||
* Returns true iff transfer1 is ordered before transfer2 by ascending account and subaddress indices.
|
||||
*/
|
||||
bool incoming_transfer_before(const std::shared_ptr<monero_incoming_transfer>& transfer1, const std::shared_ptr<monero_incoming_transfer>& transfer2);
|
||||
|
||||
/**
|
||||
* Returns true iff wallet vout1 is ordered before vout2 by ascending account and subaddress indices then index.
|
||||
*/
|
||||
bool vout_before(const std::shared_ptr<monero_output>& o1, const std::shared_ptr<monero_output>& o2);
|
||||
|
||||
// ----------------------------- GATHER BLOCKS ------------------------------
|
||||
|
||||
static std::vector<std::shared_ptr<monero_block>> get_blocks_from_txs(std::vector<std::shared_ptr<monero_tx_wallet>> txs) {
|
||||
std::shared_ptr<monero_block> unconfirmed_block = nullptr; // placeholder for unconfirmed txs
|
||||
std::vector<std::shared_ptr<monero_block>> blocks;
|
||||
std::unordered_set<std::shared_ptr<monero_block>> seen_block_ptrs;
|
||||
for (const std::shared_ptr<monero_tx_wallet>& tx : txs) {
|
||||
if (tx->m_block == nullptr) {
|
||||
if (unconfirmed_block == nullptr) unconfirmed_block = std::make_shared<monero_block>();
|
||||
tx->m_block = unconfirmed_block;
|
||||
unconfirmed_block->m_txs.push_back(tx);
|
||||
}
|
||||
std::unordered_set<std::shared_ptr<monero_block>>::const_iterator got = seen_block_ptrs.find(tx->m_block);
|
||||
if (got == seen_block_ptrs.end()) {
|
||||
seen_block_ptrs.insert(tx->m_block);
|
||||
blocks.push_back(tx->m_block);
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
static std::vector<std::shared_ptr<monero_block>> get_blocks_from_transfers(std::vector<std::shared_ptr<monero_transfer>> transfers) {
|
||||
std::shared_ptr<monero_block> unconfirmed_block = nullptr; // placeholder for unconfirmed txs in return json
|
||||
std::vector<std::shared_ptr<monero_block>> blocks;
|
||||
std::unordered_set<std::shared_ptr<monero_block>> seen_block_ptrs;
|
||||
for (auto const& transfer : transfers) {
|
||||
std::shared_ptr<monero_tx_wallet> tx = transfer->m_tx;
|
||||
if (tx->m_block == nullptr) {
|
||||
if (unconfirmed_block == nullptr) unconfirmed_block = std::make_shared<monero_block>();
|
||||
tx->m_block = unconfirmed_block;
|
||||
unconfirmed_block->m_txs.push_back(tx);
|
||||
}
|
||||
std::unordered_set<std::shared_ptr<monero_block>>::const_iterator got = seen_block_ptrs.find(tx->m_block);
|
||||
if (got == seen_block_ptrs.end()) {
|
||||
seen_block_ptrs.insert(tx->m_block);
|
||||
blocks.push_back(tx->m_block);
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
static std::vector<std::shared_ptr<monero_block>> get_blocks_from_outputs(std::vector<std::shared_ptr<monero_output_wallet>> outputs) {
|
||||
std::vector<std::shared_ptr<monero_block>> blocks;
|
||||
std::unordered_set<std::shared_ptr<monero_block>> seen_block_ptrs;
|
||||
for (auto const& output : outputs) {
|
||||
std::shared_ptr<monero_tx_wallet> tx = std::static_pointer_cast<monero_tx_wallet>(output->m_tx);
|
||||
if (tx->m_block == nullptr) throw std::runtime_error("Need to handle unconfirmed output");
|
||||
std::unordered_set<std::shared_ptr<monero_block>>::const_iterator got = seen_block_ptrs.find(tx->m_block);
|
||||
if (got == seen_block_ptrs.end()) {
|
||||
seen_block_ptrs.insert(tx->m_block);
|
||||
blocks.push_back(tx->m_block);
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
// ------------------------------ FREE MEMORY -------------------------------
|
||||
|
||||
static void free(std::shared_ptr<monero_block> block) {
|
||||
for (std::shared_ptr<monero_tx>& tx : block->m_txs) {
|
||||
tx->m_block.reset();
|
||||
monero_tx_wallet* tx_wallet = dynamic_cast<monero_tx_wallet*>(tx.get());
|
||||
if (tx_wallet != nullptr) {
|
||||
if (tx_wallet->m_tx_set != nullptr) tx_wallet->m_tx_set.reset();
|
||||
if (tx_wallet->m_outgoing_transfer != nullptr) tx_wallet->m_outgoing_transfer->m_tx.reset();
|
||||
for (std::shared_ptr<monero_transfer> transfer : tx_wallet->m_incoming_transfers) transfer->m_tx.reset();
|
||||
for (std::shared_ptr<monero_output> output : tx_wallet->m_outputs) output->m_tx.reset();
|
||||
for (std::shared_ptr<monero_output> input : tx_wallet->m_inputs) {
|
||||
input->m_key_image.reset();
|
||||
input->m_tx.reset();
|
||||
}
|
||||
}
|
||||
monero_tx_query* tx_query = dynamic_cast<monero_tx_query*>(tx.get());
|
||||
if (tx_query != nullptr) {
|
||||
if (tx_query->m_transfer_query != nullptr) {
|
||||
tx_query->m_transfer_query->m_tx_query.reset();
|
||||
tx_query->m_transfer_query.reset();
|
||||
}
|
||||
if (tx_query->m_output_query != nullptr) {
|
||||
tx_query->m_output_query->m_tx_query.reset();
|
||||
tx_query->m_output_query.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
block.reset();
|
||||
}
|
||||
|
||||
static void free(std::vector<std::shared_ptr<monero_block>> blocks) {
|
||||
for (std::shared_ptr<monero_block>& block : blocks) monero_utils::free(block);
|
||||
}
|
||||
|
||||
static void free(std::shared_ptr<monero_tx> tx) {
|
||||
if (tx->m_block == nullptr) {
|
||||
std::shared_ptr<monero_block> block = std::make_shared<monero_block>();
|
||||
tx->m_block = block;
|
||||
block->m_txs.push_back(tx);
|
||||
}
|
||||
monero_utils::free(tx->m_block);
|
||||
}
|
||||
|
||||
static void free(std::vector<std::shared_ptr<monero_tx_wallet>> txs) {
|
||||
return monero_utils::free(monero_utils::get_blocks_from_txs(txs));
|
||||
}
|
||||
|
||||
static void free(std::vector<std::shared_ptr<monero_transfer>> transfers) {
|
||||
return monero_utils::free(monero_utils::get_blocks_from_transfers(transfers));
|
||||
}
|
||||
|
||||
static void free(std::vector<std::shared_ptr<monero_output_wallet>> outputs) {
|
||||
return monero_utils::free(monero_utils::get_blocks_from_outputs(outputs));
|
||||
}
|
||||
}
|
||||
#endif /* monero_utils_h */
|
||||
Reference in New Issue
Block a user