Merge pull request #2109
97c2e449
wallet2+API: use separate callbacks for lightwallets (Jaquee)d9261867
walletAPI: correct confirmations in txHistory for unsynced wallets (Jaquee)9442b043
walletAPI: lightwallet exceptions (Jaquee)fc922934
walletAPI: add lightwallet login() and importWalletRequest() (Jaquee)79207743
walletAPI: init() lightwallet and SSL support (Jaquee)dde5a1fc
walletAPI: add tx unlock_time (Jaquee)bba5cbed
wallet2: remove obsolete get_num_rct_outputs() call from create_transactions_2 (Jaquee)7a482f30
wallet2: create_transactions_2 lightwallet support (Jaquee)ce61b818
wallet2: get_outs lightwallet support (Jaquee)1197cb71
wallet2: commit_tx() lightwallet support (Jaquee)43b57804
wallet2: refactor is_tx_spendtime_unlocked() (Jaquee)32e2b003
wallet2: add lightwallet exceptions to common functions (Jaquee)2e692fc0
wallet2: refresh() lightwallet support (Jaquee)f44d156c
my/openmonero API functions (Jaquee)288d3c75
wallet2: add remove_obsolete_pool_txs() (Jaquee)2c6aad7e
wallet2: add on_pool_tx_removed callback (Jaquee)ff7c30aa
wallet2: light wallet member variables (Jaquee)e2a276cb
wallet2: add ssl option to init() (Jaquee)a13540be
add string_tools::validate_hex() (Jaquee)fd773d88
refactor cryptonote_basic::add_tx_pub_key_to_extra (Jaquee)8bfa6c2d
lightwallet rpc server commands (Jaquee)76961ddc
Serializer: string to integer conversion for MyMonero compatibility (Jaquee)1cf940f2
epee http_client SSL support (Jaquee)eec10137
CMakeLists.txt - Add openssl include dir (Jaquee)
This commit is contained in:
commit
960886aa04
|
@ -326,7 +326,7 @@ if (UNIX AND NOT APPLE)
|
|||
find_package(Threads)
|
||||
endif()
|
||||
|
||||
# Handle OpenSSL, used for sha256sum on binary updates
|
||||
# Handle OpenSSL, used for sha256sum on binary updates and light wallet ssl http
|
||||
if (APPLE AND NOT IOS)
|
||||
if (NOT OpenSSL_DIR)
|
||||
EXECUTE_PROCESS(COMMAND brew --prefix openssl
|
||||
|
@ -337,6 +337,8 @@ if (APPLE AND NOT IOS)
|
|||
endif()
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
message(STATUS "Using OpenSSL include dir at ${OPENSSL_INCLUDE_DIR}")
|
||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
if(STATIC AND NOT IOS)
|
||||
if(UNIX)
|
||||
set(OPENSSL_LIBRARIES "${OPENSSL_LIBRARIES};${CMAKE_DL_LIBS};${CMAKE_THREAD_LIBS_INIT}")
|
||||
|
|
|
@ -274,6 +274,7 @@ using namespace std;
|
|||
chunked_state m_chunked_state;
|
||||
std::string m_chunked_cache;
|
||||
critical_section m_lock;
|
||||
bool m_ssl;
|
||||
|
||||
public:
|
||||
explicit http_simple_client()
|
||||
|
@ -291,33 +292,35 @@ using namespace std;
|
|||
, m_chunked_state()
|
||||
, m_chunked_cache()
|
||||
, m_lock()
|
||||
, m_ssl(false)
|
||||
{}
|
||||
|
||||
const std::string &get_host() const { return m_host_buff; };
|
||||
const std::string &get_port() const { return m_port; };
|
||||
|
||||
bool set_server(const std::string& address, boost::optional<login> user)
|
||||
bool set_server(const std::string& address, boost::optional<login> user, bool ssl = false)
|
||||
{
|
||||
http::url_content parsed{};
|
||||
const bool r = parse_url(address, parsed);
|
||||
CHECK_AND_ASSERT_MES(r, false, "failed to parse url: " << address);
|
||||
set_server(std::move(parsed.host), std::to_string(parsed.port), std::move(user));
|
||||
set_server(std::move(parsed.host), std::to_string(parsed.port), std::move(user), ssl);
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_server(std::string host, std::string port, boost::optional<login> user)
|
||||
void set_server(std::string host, std::string port, boost::optional<login> user, bool ssl = false)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
disconnect();
|
||||
m_host_buff = std::move(host);
|
||||
m_port = std::move(port);
|
||||
m_auth = user ? http_client_auth{std::move(*user)} : http_client_auth{};
|
||||
m_ssl = ssl;
|
||||
}
|
||||
|
||||
bool connect(std::chrono::milliseconds timeout)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
return m_net_client.connect(m_host_buff, m_port, timeout);
|
||||
return m_net_client.connect(m_host_buff, m_port, timeout, m_ssl);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
bool disconnect()
|
||||
|
@ -392,7 +395,6 @@ using namespace std;
|
|||
res = m_net_client.send(body, timeout);
|
||||
CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND");
|
||||
|
||||
|
||||
m_response_info.clear();
|
||||
m_state = reciev_machine_state_header;
|
||||
if (!handle_reciev(timeout))
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include <ostream>
|
||||
#include <string>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/preprocessor/selection/min.hpp>
|
||||
#include <boost/lambda/bind.hpp>
|
||||
|
@ -85,11 +86,13 @@ namespace net_utils
|
|||
|
||||
public:
|
||||
inline
|
||||
blocked_mode_client():m_socket(m_io_service),
|
||||
m_initialized(false),
|
||||
blocked_mode_client():m_initialized(false),
|
||||
m_connected(false),
|
||||
m_deadline(m_io_service),
|
||||
m_shutdowned(0)
|
||||
m_shutdowned(0),
|
||||
m_ssl(false),
|
||||
m_ctx(boost::asio::ssl::context::sslv23),
|
||||
m_ssl_socket(m_io_service,m_ctx)
|
||||
{
|
||||
|
||||
|
||||
|
@ -113,18 +116,25 @@ namespace net_utils
|
|||
}
|
||||
|
||||
inline
|
||||
bool connect(const std::string& addr, int port, std::chrono::milliseconds timeout, const std::string& bind_ip = "0.0.0.0")
|
||||
bool connect(const std::string& addr, int port, std::chrono::milliseconds timeout, bool ssl = false, const std::string& bind_ip = "0.0.0.0")
|
||||
{
|
||||
return connect(addr, std::to_string(port), timeout, bind_ip);
|
||||
return connect(addr, std::to_string(port), timeout, ssl, bind_ip);
|
||||
}
|
||||
|
||||
inline
|
||||
bool connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout, const std::string& bind_ip = "0.0.0.0")
|
||||
bool connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout, bool ssl = false, const std::string& bind_ip = "0.0.0.0")
|
||||
{
|
||||
m_connected = false;
|
||||
m_ssl = ssl;
|
||||
try
|
||||
{
|
||||
m_socket.close();
|
||||
m_ssl_socket.next_layer().close();
|
||||
|
||||
// Set SSL options
|
||||
// disable sslv2
|
||||
m_ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2);
|
||||
m_ctx.set_default_verify_paths();
|
||||
|
||||
// Get a list of endpoints corresponding to the server name.
|
||||
|
||||
|
||||
|
@ -147,11 +157,11 @@ namespace net_utils
|
|||
boost::asio::ip::tcp::endpoint remote_endpoint(*iterator);
|
||||
|
||||
|
||||
m_socket.open(remote_endpoint.protocol());
|
||||
m_ssl_socket.next_layer().open(remote_endpoint.protocol());
|
||||
if(bind_ip != "0.0.0.0" && bind_ip != "0" && bind_ip != "" )
|
||||
{
|
||||
boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::address::from_string(addr.c_str()), 0);
|
||||
m_socket.bind(local_endpoint);
|
||||
m_ssl_socket.next_layer().bind(local_endpoint);
|
||||
}
|
||||
|
||||
|
||||
|
@ -160,17 +170,24 @@ namespace net_utils
|
|||
|
||||
boost::system::error_code ec = boost::asio::error::would_block;
|
||||
|
||||
//m_socket.connect(remote_endpoint);
|
||||
m_socket.async_connect(remote_endpoint, boost::lambda::var(ec) = boost::lambda::_1);
|
||||
m_ssl_socket.next_layer().async_connect(remote_endpoint, boost::lambda::var(ec) = boost::lambda::_1);
|
||||
while (ec == boost::asio::error::would_block)
|
||||
{
|
||||
m_io_service.run_one();
|
||||
}
|
||||
|
||||
if (!ec && m_socket.is_open())
|
||||
if (!ec && m_ssl_socket.next_layer().is_open())
|
||||
{
|
||||
m_connected = true;
|
||||
m_deadline.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
// SSL Options
|
||||
if(m_ssl) {
|
||||
// Disable verification of host certificate
|
||||
m_ssl_socket.set_verify_mode(boost::asio::ssl::verify_peer);
|
||||
// Handshake
|
||||
m_ssl_socket.next_layer().set_option(boost::asio::ip::tcp::no_delay(true));
|
||||
m_ssl_socket.handshake(boost::asio::ssl::stream_base::client);
|
||||
}
|
||||
return true;
|
||||
}else
|
||||
{
|
||||
|
@ -193,7 +210,6 @@ namespace net_utils
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
bool disconnect()
|
||||
{
|
||||
|
@ -202,8 +218,9 @@ namespace net_utils
|
|||
if(m_connected)
|
||||
{
|
||||
m_connected = false;
|
||||
m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
|
||||
|
||||
if(m_ssl)
|
||||
shutdown_ssl();
|
||||
m_ssl_socket.next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +257,7 @@ namespace net_utils
|
|||
// object is used as a callback and will update the ec variable when the
|
||||
// operation completes. The blocking_udp_client.cpp example shows how you
|
||||
// can use boost::bind rather than boost::lambda.
|
||||
boost::asio::async_write(m_socket, boost::asio::buffer(buff), boost::lambda::var(ec) = boost::lambda::_1);
|
||||
async_write(buff.c_str(), buff.size(), ec);
|
||||
|
||||
// Block until the asynchronous operation has completed.
|
||||
while (ec == boost::asio::error::would_block)
|
||||
|
@ -302,9 +319,7 @@ namespace net_utils
|
|||
*/
|
||||
boost::system::error_code ec;
|
||||
|
||||
size_t writen = m_socket.write_some(boost::asio::buffer(data, sz), ec);
|
||||
|
||||
|
||||
size_t writen = write(data, sz, ec);
|
||||
|
||||
if (!writen || ec)
|
||||
{
|
||||
|
@ -334,10 +349,7 @@ namespace net_utils
|
|||
|
||||
bool is_connected()
|
||||
{
|
||||
return m_connected && m_socket.is_open();
|
||||
//TRY_ENTRY()
|
||||
//return m_socket.is_open();
|
||||
//CATCH_ENTRY_L0("is_connected", false)
|
||||
return m_connected && m_ssl_socket.next_layer().is_open();
|
||||
}
|
||||
|
||||
inline
|
||||
|
@ -369,8 +381,8 @@ namespace net_utils
|
|||
handler_obj hndlr(ec, bytes_transfered);
|
||||
|
||||
char local_buff[10000] = {0};
|
||||
//m_socket.async_read_some(boost::asio::buffer(local_buff, sizeof(local_buff)), hndlr);
|
||||
boost::asio::async_read(m_socket, boost::asio::buffer(local_buff, sizeof(local_buff)), boost::asio::transfer_at_least(1), hndlr);
|
||||
|
||||
async_read(local_buff, boost::asio::transfer_at_least(1), hndlr);
|
||||
|
||||
// Block until the asynchronous operation has completed.
|
||||
while (ec == boost::asio::error::would_block && !boost::interprocess::ipcdetail::atomic_read32(&m_shutdowned))
|
||||
|
@ -451,10 +463,8 @@ namespace net_utils
|
|||
|
||||
|
||||
handler_obj hndlr(ec, bytes_transfered);
|
||||
|
||||
//char local_buff[10000] = {0};
|
||||
boost::asio::async_read(m_socket, boost::asio::buffer((char*)buff.data(), buff.size()), boost::asio::transfer_at_least(buff.size()), hndlr);
|
||||
|
||||
async_read((char*)buff.data(), boost::asio::transfer_at_least(buff.size()), hndlr);
|
||||
|
||||
// Block until the asynchronous operation has completed.
|
||||
while (ec == boost::asio::error::would_block && !boost::interprocess::ipcdetail::atomic_read32(&m_shutdowned))
|
||||
{
|
||||
|
@ -500,10 +510,18 @@ namespace net_utils
|
|||
bool shutdown()
|
||||
{
|
||||
m_deadline.cancel();
|
||||
boost::system::error_code ignored_ec;
|
||||
m_socket.cancel(ignored_ec);
|
||||
m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
|
||||
m_socket.close(ignored_ec);
|
||||
boost::system::error_code ec;
|
||||
if(m_ssl)
|
||||
shutdown_ssl();
|
||||
m_ssl_socket.next_layer().cancel(ec);
|
||||
if(ec)
|
||||
MDEBUG("Problems at cancel: " << ec.message());
|
||||
m_ssl_socket.next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
|
||||
if(ec)
|
||||
MDEBUG("Problems at shutdown: " << ec.message());
|
||||
m_ssl_socket.next_layer().close(ec);
|
||||
if(ec)
|
||||
MDEBUG("Problems at close: " << ec.message());
|
||||
boost::interprocess::ipcdetail::atomic_write32(&m_shutdowned, 1);
|
||||
m_connected = false;
|
||||
return true;
|
||||
|
@ -520,7 +538,7 @@ namespace net_utils
|
|||
|
||||
boost::asio::ip::tcp::socket& get_socket()
|
||||
{
|
||||
return m_socket;
|
||||
return m_ssl_socket.next_layer();
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -537,7 +555,7 @@ namespace net_utils
|
|||
// connect(), read_line() or write_line() functions to return.
|
||||
LOG_PRINT_L3("Timed out socket");
|
||||
m_connected = false;
|
||||
m_socket.close();
|
||||
m_ssl_socket.next_layer().close();
|
||||
|
||||
// There is no longer an active deadline. The expiry is set to positive
|
||||
// infinity so that the actor takes no action until a new deadline is set.
|
||||
|
@ -547,12 +565,54 @@ namespace net_utils
|
|||
// Put the actor back to sleep.
|
||||
m_deadline.async_wait(boost::bind(&blocked_mode_client::check_deadline, this));
|
||||
}
|
||||
|
||||
|
||||
void shutdown_ssl() {
|
||||
// ssl socket shutdown blocks if server doesn't respond. We close after 2 secs
|
||||
boost::system::error_code ec = boost::asio::error::would_block;
|
||||
m_deadline.expires_from_now(std::chrono::milliseconds(2000));
|
||||
m_ssl_socket.async_shutdown(boost::lambda::var(ec) = boost::lambda::_1);
|
||||
while (ec == boost::asio::error::would_block)
|
||||
{
|
||||
m_io_service.run_one();
|
||||
}
|
||||
// Ignore "short read" error
|
||||
if (ec.category() == boost::asio::error::get_ssl_category() && ec.value() != ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ))
|
||||
MDEBUG("Problems at ssl shutdown: " << ec.message());
|
||||
}
|
||||
|
||||
protected:
|
||||
bool write(const void* data, size_t sz, boost::system::error_code& ec)
|
||||
{
|
||||
bool success;
|
||||
if(m_ssl)
|
||||
success = boost::asio::write(m_ssl_socket, boost::asio::buffer(data, sz), ec);
|
||||
else
|
||||
success = boost::asio::write(m_ssl_socket.next_layer(), boost::asio::buffer(data, sz), ec);
|
||||
return success;
|
||||
}
|
||||
|
||||
void async_write(const void* data, size_t sz, boost::system::error_code& ec)
|
||||
{
|
||||
if(m_ssl)
|
||||
boost::asio::async_write(m_ssl_socket, boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1);
|
||||
else
|
||||
boost::asio::async_write(m_ssl_socket.next_layer(), boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1);
|
||||
}
|
||||
|
||||
void async_read(char* buff, boost::asio::detail::transfer_at_least_t transfer_at_least, handler_obj& hndlr)
|
||||
{
|
||||
if(!m_ssl)
|
||||
boost::asio::async_read(m_ssl_socket.next_layer(), boost::asio::buffer(buff, sizeof(buff)), transfer_at_least, hndlr);
|
||||
else
|
||||
boost::asio::async_read(m_ssl_socket, boost::asio::buffer(buff, sizeof(buff)), transfer_at_least, hndlr);
|
||||
|
||||
}
|
||||
|
||||
protected:
|
||||
boost::asio::io_service m_io_service;
|
||||
boost::asio::ip::tcp::socket m_socket;
|
||||
boost::asio::ssl::context m_ctx;
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> m_ssl_socket;
|
||||
bool m_ssl;
|
||||
bool m_initialized;
|
||||
bool m_connected;
|
||||
boost::asio::steady_timer m_deadline;
|
||||
|
@ -618,8 +678,8 @@ namespace net_utils
|
|||
|
||||
boost::system::error_code ec;
|
||||
|
||||
size_t writen = m_socket.write_some(boost::asio::buffer(data, sz), ec);
|
||||
|
||||
size_t writen = write(data, sz, ec);
|
||||
|
||||
if (!writen || ec)
|
||||
{
|
||||
LOG_PRINT_L3("Problems at write: " << ec.message());
|
||||
|
@ -660,7 +720,7 @@ namespace net_utils
|
|||
// asynchronous operations are cancelled. This allows the blocked
|
||||
// connect(), read_line() or write_line() functions to return.
|
||||
LOG_PRINT_L3("Timed out socket");
|
||||
m_socket.close();
|
||||
m_ssl_socket.next_layer().close();
|
||||
|
||||
// There is no longer an active deadline. The expiry is set to positive
|
||||
// infinity so that the actor takes no action until a new deadline is set.
|
||||
|
|
|
@ -44,8 +44,11 @@ namespace epee
|
|||
if(!serialization::store_t_to_json(out_struct, req_param))
|
||||
return false;
|
||||
|
||||
http::fields_list additional_params;
|
||||
additional_params.push_back(std::make_pair("Content-Type","application/json; charset=utf-8"));
|
||||
|
||||
const http::http_response_info* pri = NULL;
|
||||
if(!transport.invoke(uri, method, req_param, timeout, std::addressof(pri)))
|
||||
if(!transport.invoke(uri, method, req_param, timeout, std::addressof(pri), std::move(additional_params)))
|
||||
{
|
||||
LOG_PRINT_L1("Failed to invoke http request to " << uri);
|
||||
return false;
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <regex>
|
||||
|
||||
#include "misc_language.h"
|
||||
#include "portable_storage_base.h"
|
||||
#include "warnings.h"
|
||||
|
@ -131,6 +133,31 @@ POP_WARNINGS
|
|||
}
|
||||
};
|
||||
|
||||
// For MyMonero/OpenMonero backend compatibility
|
||||
// MyMonero backend sends amount, fees and timestamp values as strings.
|
||||
// Until MM backend is updated, this is needed for compatibility between OpenMonero and MyMonero.
|
||||
template<>
|
||||
struct convert_to_integral<std::string, uint64_t, false>
|
||||
{
|
||||
static void convert(const std::string& from, uint64_t& to)
|
||||
{
|
||||
MTRACE("Converting std::string to uint64_t. Source: " << from);
|
||||
// String only contains digits
|
||||
if(std::all_of(from.begin(), from.end(), ::isdigit))
|
||||
to = boost::lexical_cast<uint64_t>(from);
|
||||
// MyMonero ISO 8061 timestamp (2017-05-06T16:27:06Z)
|
||||
else if (std::regex_match (from, std::regex("\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\dZ")))
|
||||
{
|
||||
// Convert to unix timestamp
|
||||
std::tm tm = {};
|
||||
std::istringstream ss(from);
|
||||
if (ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S"))
|
||||
to = std::mktime(&tm);
|
||||
} else
|
||||
ASSERT_AND_THROW_WRONG_CONVERSION();
|
||||
}
|
||||
};
|
||||
|
||||
template<class from_type, class to_type>
|
||||
struct is_convertable: std::integral_constant<bool,
|
||||
std::is_integral<to_type>::value &&
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <regex>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
@ -349,6 +350,11 @@ POP_WARNINGS
|
|||
s = *(t_pod_type*)bin_buff.data();
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------
|
||||
inline bool validate_hex(uint64_t length, const std::string& str)
|
||||
{
|
||||
return std::regex_match(str, std::regex("'^[0-9abcdefABCDEF]+$'")) && str.size() == length;
|
||||
}
|
||||
//----------------------------------------------------------------------------
|
||||
inline std::string get_extension(const std::string& str)
|
||||
{
|
||||
|
|
|
@ -324,9 +324,19 @@ namespace cryptonote
|
|||
//---------------------------------------------------------------
|
||||
bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key)
|
||||
{
|
||||
tx.extra.resize(tx.extra.size() + 1 + sizeof(crypto::public_key));
|
||||
tx.extra[tx.extra.size() - 1 - sizeof(crypto::public_key)] = TX_EXTRA_TAG_PUBKEY;
|
||||
*reinterpret_cast<crypto::public_key*>(&tx.extra[tx.extra.size() - sizeof(crypto::public_key)]) = tx_pub_key;
|
||||
return add_tx_pub_key_to_extra(tx.extra, tx_pub_key);
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key)
|
||||
{
|
||||
return add_tx_pub_key_to_extra(tx.extra, tx_pub_key);
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool add_tx_pub_key_to_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& tx_pub_key)
|
||||
{
|
||||
tx_extra.resize(tx_extra.size() + 1 + sizeof(crypto::public_key));
|
||||
tx_extra[tx_extra.size() - 1 - sizeof(crypto::public_key)] = TX_EXTRA_TAG_PUBKEY;
|
||||
*reinterpret_cast<crypto::public_key*>(&tx_extra[tx_extra.size() - sizeof(crypto::public_key)]) = tx_pub_key;
|
||||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
|
|
|
@ -65,6 +65,8 @@ namespace cryptonote
|
|||
crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0);
|
||||
crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0);
|
||||
bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key);
|
||||
bool add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key);
|
||||
bool add_tx_pub_key_to_extra(std::vector<uint8_t>& tx_extra, const crypto::public_key& tx_pub_key);
|
||||
std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra);
|
||||
std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx);
|
||||
bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys);
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace cryptonote
|
|||
// advance which version they will stop working with
|
||||
// Don't go over 32767 for any of these
|
||||
#define CORE_RPC_VERSION_MAJOR 1
|
||||
#define CORE_RPC_VERSION_MINOR 14
|
||||
#define CORE_RPC_VERSION_MINOR 15
|
||||
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
|
||||
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
|
||||
|
||||
|
@ -194,6 +194,358 @@ namespace cryptonote
|
|||
};
|
||||
};
|
||||
|
||||
//-----------------------------------------------
|
||||
struct COMMAND_RPC_GET_ADDRESS_TXS
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::string address;
|
||||
std::string view_key;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(address)
|
||||
KV_SERIALIZE(view_key)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct spent_output {
|
||||
uint64_t amount;
|
||||
std::string key_image;
|
||||
std::string tx_pub_key;
|
||||
uint64_t out_index;
|
||||
uint32_t mixin;
|
||||
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE(key_image)
|
||||
KV_SERIALIZE(tx_pub_key)
|
||||
KV_SERIALIZE(out_index)
|
||||
KV_SERIALIZE(mixin)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct transaction
|
||||
{
|
||||
uint64_t id;
|
||||
std::string hash;
|
||||
uint64_t timestamp;
|
||||
uint64_t total_received;
|
||||
uint64_t total_sent;
|
||||
uint64_t unlock_time;
|
||||
uint64_t height;
|
||||
std::list<spent_output> spent_outputs;
|
||||
std::string payment_id;
|
||||
bool coinbase;
|
||||
bool mempool;
|
||||
uint32_t mixin;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(id)
|
||||
KV_SERIALIZE(hash)
|
||||
KV_SERIALIZE(timestamp)
|
||||
KV_SERIALIZE(total_received)
|
||||
KV_SERIALIZE(total_sent)
|
||||
KV_SERIALIZE(unlock_time)
|
||||
KV_SERIALIZE(height)
|
||||
KV_SERIALIZE(spent_outputs)
|
||||
KV_SERIALIZE(payment_id)
|
||||
KV_SERIALIZE(coinbase)
|
||||
KV_SERIALIZE(mempool)
|
||||
KV_SERIALIZE(mixin)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
|
||||
struct response
|
||||
{
|
||||
//std::list<std::string> txs_as_json;
|
||||
uint64_t total_received;
|
||||
uint64_t total_received_unlocked = 0; // OpenMonero only
|
||||
uint64_t scanned_height;
|
||||
std::list<transaction> transactions;
|
||||
uint64_t blockchain_height;
|
||||
uint64_t scanned_block_height;
|
||||
std::string status;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(total_received)
|
||||
KV_SERIALIZE(total_received_unlocked)
|
||||
KV_SERIALIZE(scanned_height)
|
||||
KV_SERIALIZE(transactions)
|
||||
KV_SERIALIZE(blockchain_height)
|
||||
KV_SERIALIZE(scanned_block_height)
|
||||
KV_SERIALIZE(status)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
//-----------------------------------------------
|
||||
struct COMMAND_RPC_GET_ADDRESS_INFO
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::string address;
|
||||
std::string view_key;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(address)
|
||||
KV_SERIALIZE(view_key)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct spent_output
|
||||
{
|
||||
uint64_t amount;
|
||||
std::string key_image;
|
||||
std::string tx_pub_key;
|
||||
uint64_t out_index;
|
||||
uint32_t mixin;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE(key_image)
|
||||
KV_SERIALIZE(tx_pub_key)
|
||||
KV_SERIALIZE(out_index)
|
||||
KV_SERIALIZE(mixin)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct response
|
||||
{
|
||||
uint64_t locked_funds;
|
||||
uint64_t total_received;
|
||||
uint64_t total_sent;
|
||||
uint64_t scanned_height;
|
||||
uint64_t scanned_block_height;
|
||||
uint64_t start_height;
|
||||
uint64_t transaction_height;
|
||||
uint64_t blockchain_height;
|
||||
std::list<spent_output> spent_outputs;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(locked_funds)
|
||||
KV_SERIALIZE(total_received)
|
||||
KV_SERIALIZE(total_sent)
|
||||
KV_SERIALIZE(scanned_height)
|
||||
KV_SERIALIZE(scanned_block_height)
|
||||
KV_SERIALIZE(start_height)
|
||||
KV_SERIALIZE(transaction_height)
|
||||
KV_SERIALIZE(blockchain_height)
|
||||
KV_SERIALIZE(spent_outputs)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
//-----------------------------------------------
|
||||
struct COMMAND_RPC_GET_UNSPENT_OUTS
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::string amount;
|
||||
std::string address;
|
||||
std::string view_key;
|
||||
// OpenMonero specific
|
||||
uint64_t mixin;
|
||||
bool use_dust;
|
||||
std::string dust_threshold;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE(address)
|
||||
KV_SERIALIZE(view_key)
|
||||
KV_SERIALIZE(mixin)
|
||||
KV_SERIALIZE(use_dust)
|
||||
KV_SERIALIZE(dust_threshold)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
|
||||
struct output {
|
||||
uint64_t amount;
|
||||
std::string public_key;
|
||||
uint64_t index;
|
||||
uint64_t global_index;
|
||||
std::string rct;
|
||||
std::string tx_hash;
|
||||
std::string tx_pub_key;
|
||||
std::string tx_prefix_hash;
|
||||
std::vector<std::string> spend_key_images;
|
||||
uint64_t timestamp;
|
||||
uint64_t height;
|
||||
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE(public_key)
|
||||
KV_SERIALIZE(index)
|
||||
KV_SERIALIZE(global_index)
|
||||
KV_SERIALIZE(rct)
|
||||
KV_SERIALIZE(tx_hash)
|
||||
KV_SERIALIZE(tx_pub_key)
|
||||
KV_SERIALIZE(tx_prefix_hash)
|
||||
KV_SERIALIZE(spend_key_images)
|
||||
KV_SERIALIZE(timestamp)
|
||||
KV_SERIALIZE(height)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct response
|
||||
{
|
||||
uint64_t amount;
|
||||
std::list<output> outputs;
|
||||
uint64_t per_kb_fee;
|
||||
std::string status;
|
||||
std::string reason;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE(outputs)
|
||||
KV_SERIALIZE(per_kb_fee)
|
||||
KV_SERIALIZE(status)
|
||||
KV_SERIALIZE(reason)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
//-----------------------------------------------
|
||||
struct COMMAND_RPC_GET_RANDOM_OUTS
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::vector<std::string> amounts;
|
||||
uint32_t count;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amounts)
|
||||
KV_SERIALIZE(count)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
|
||||
struct output {
|
||||
std::string public_key;
|
||||
uint64_t global_index;
|
||||
std::string rct; // 64+64+64 characters long (<rct commit> + <encrypted mask> + <rct amount>)
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(public_key)
|
||||
KV_SERIALIZE(global_index)
|
||||
KV_SERIALIZE(rct)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct amount_out {
|
||||
uint64_t amount;
|
||||
std::vector<output> outputs;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE(outputs)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
|
||||
};
|
||||
|
||||
struct response
|
||||
{
|
||||
std::vector<amount_out> amount_outs;
|
||||
std::string Error;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount_outs)
|
||||
KV_SERIALIZE(Error)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
//-----------------------------------------------
|
||||
struct COMMAND_RPC_SUBMIT_RAW_TX
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::string address;
|
||||
std::string view_key;
|
||||
std::string tx;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(address)
|
||||
KV_SERIALIZE(view_key)
|
||||
KV_SERIALIZE(tx)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
|
||||
struct response
|
||||
{
|
||||
std::string status;
|
||||
std::string error;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(status)
|
||||
KV_SERIALIZE(error)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
//-----------------------------------------------
|
||||
struct COMMAND_RPC_LOGIN
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::string address;
|
||||
std::string view_key;
|
||||
bool create_account;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(address)
|
||||
KV_SERIALIZE(view_key)
|
||||
KV_SERIALIZE(create_account)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
|
||||
struct response
|
||||
{
|
||||
std::string status;
|
||||
std::string reason;
|
||||
bool new_address;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(status)
|
||||
KV_SERIALIZE(reason)
|
||||
KV_SERIALIZE(new_address)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
//-----------------------------------------------
|
||||
struct COMMAND_RPC_IMPORT_WALLET_REQUEST
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::string address;
|
||||
std::string view_key;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(address)
|
||||
KV_SERIALIZE(view_key)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
|
||||
struct response
|
||||
{
|
||||
std::string payment_id;
|
||||
uint64_t import_fee;
|
||||
bool new_request;
|
||||
bool request_fulfilled;
|
||||
std::string payment_address;
|
||||
std::string status;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(payment_id)
|
||||
KV_SERIALIZE(import_fee)
|
||||
KV_SERIALIZE(new_request)
|
||||
KV_SERIALIZE(request_fulfilled)
|
||||
KV_SERIALIZE(payment_address)
|
||||
KV_SERIALIZE(status)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
//-----------------------------------------------
|
||||
struct COMMAND_RPC_GET_TRANSACTIONS
|
||||
{
|
||||
|
|
|
@ -134,14 +134,10 @@ void TransactionHistoryImpl::refresh()
|
|||
ti->m_subaddrAccount = pd.m_subaddr_index.major;
|
||||
ti->m_label = m_wallet->m_wallet->get_subaddress_label(pd.m_subaddr_index);
|
||||
ti->m_timestamp = pd.m_timestamp;
|
||||
ti->m_confirmations = wallet_height - pd.m_block_height;
|
||||
ti->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0;
|
||||
ti->m_unlock_time = pd.m_unlock_time;
|
||||
m_history.push_back(ti);
|
||||
|
||||
/* output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%20.20s %s %s %s")
|
||||
% print_money(pd.m_amount)
|
||||
% string_tools::pod_to_hex(pd.m_tx_hash)
|
||||
% payment_id % "-").str()))); */
|
||||
}
|
||||
|
||||
// confirmed output transactions
|
||||
|
@ -181,7 +177,7 @@ void TransactionHistoryImpl::refresh()
|
|||
ti->m_subaddrAccount = pd.m_subaddr_account;
|
||||
ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : "";
|
||||
ti->m_timestamp = pd.m_timestamp;
|
||||
ti->m_confirmations = wallet_height - pd.m_block_height;
|
||||
ti->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0;
|
||||
|
||||
// single output transaction might contain multiple transfers
|
||||
for (const auto &d: pd.m_dests) {
|
||||
|
|
|
@ -155,6 +155,38 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
|
|||
// TODO;
|
||||
}
|
||||
|
||||
// Light wallet callbacks
|
||||
virtual void on_lw_new_block(uint64_t height)
|
||||
{
|
||||
if (m_listener) {
|
||||
m_listener->newBlock(height);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount)
|
||||
{
|
||||
if (m_listener) {
|
||||
std::string tx_hash = epee::string_tools::pod_to_hex(txid);
|
||||
m_listener->moneyReceived(tx_hash, amount);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount)
|
||||
{
|
||||
if (m_listener) {
|
||||
std::string tx_hash = epee::string_tools::pod_to_hex(txid);
|
||||
m_listener->unconfirmedMoneyReceived(tx_hash, amount);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount)
|
||||
{
|
||||
if (m_listener) {
|
||||
std::string tx_hash = epee::string_tools::pod_to_hex(txid);
|
||||
m_listener->moneySpent(tx_hash, amount);
|
||||
}
|
||||
}
|
||||
|
||||
WalletListener * m_listener;
|
||||
WalletImpl * m_wallet;
|
||||
};
|
||||
|
@ -703,12 +735,45 @@ string WalletImpl::keysFilename() const
|
|||
return m_wallet->get_keys_file();
|
||||
}
|
||||
|
||||
bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username, const std::string &daemon_password)
|
||||
bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username, const std::string &daemon_password, bool use_ssl, bool lightWallet)
|
||||
{
|
||||
clearStatus();
|
||||
m_wallet->set_light_wallet(lightWallet);
|
||||
if(daemon_username != "")
|
||||
m_daemon_login.emplace(daemon_username, daemon_password);
|
||||
return doInit(daemon_address, upper_transaction_size_limit);
|
||||
return doInit(daemon_address, upper_transaction_size_limit, use_ssl);
|
||||
}
|
||||
|
||||
bool WalletImpl::lightWalletLogin(bool &isNewWallet) const
|
||||
{
|
||||
return m_wallet->light_wallet_login(isNewWallet);
|
||||
}
|
||||
|
||||
bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status)
|
||||
{
|
||||
try
|
||||
{
|
||||
cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response response;
|
||||
if(!m_wallet->light_wallet_import_wallet_request(response)){
|
||||
m_errorString = tr("Failed to send import wallet request");
|
||||
m_status = Status_Error;
|
||||
return false;
|
||||
}
|
||||
fee = response.import_fee;
|
||||
payment_id = response.payment_id;
|
||||
new_request = response.new_request;
|
||||
request_fulfilled = response.request_fulfilled;
|
||||
payment_address = response.payment_address;
|
||||
status = response.status;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
LOG_ERROR("Error sending import wallet request: " << e.what());
|
||||
m_errorString = e.what();
|
||||
m_status = Status_Error;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height)
|
||||
|
@ -733,6 +798,9 @@ uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex) const
|
|||
|
||||
uint64_t WalletImpl::blockChainHeight() const
|
||||
{
|
||||
if(m_wallet->light_wallet()) {
|
||||
return m_wallet->get_light_wallet_scanned_block_height();
|
||||
}
|
||||
return m_wallet->get_blockchain_current_height();
|
||||
}
|
||||
uint64_t WalletImpl::approximateBlockChainHeight() const
|
||||
|
@ -741,6 +809,9 @@ uint64_t WalletImpl::approximateBlockChainHeight() const
|
|||
}
|
||||
uint64_t WalletImpl::daemonBlockChainHeight() const
|
||||
{
|
||||
if(m_wallet->light_wallet()) {
|
||||
return m_wallet->get_light_wallet_scanned_block_height();
|
||||
}
|
||||
if (!m_is_connected)
|
||||
return 0;
|
||||
std::string err;
|
||||
|
@ -760,6 +831,9 @@ uint64_t WalletImpl::daemonBlockChainHeight() const
|
|||
|
||||
uint64_t WalletImpl::daemonBlockChainTargetHeight() const
|
||||
{
|
||||
if(m_wallet->light_wallet()) {
|
||||
return m_wallet->get_light_wallet_blockchain_height();
|
||||
}
|
||||
if (!m_is_connected)
|
||||
return 0;
|
||||
std::string err;
|
||||
|
@ -1348,7 +1422,8 @@ Wallet::ConnectionStatus WalletImpl::connected() const
|
|||
m_is_connected = m_wallet->check_connection(&version, DEFAULT_CONNECTION_TIMEOUT_MILLIS);
|
||||
if (!m_is_connected)
|
||||
return Wallet::ConnectionStatus_Disconnected;
|
||||
if ((version >> 16) != CORE_RPC_VERSION_MAJOR)
|
||||
// Version check is not implemented in light wallets nodes/wallets
|
||||
if (!m_wallet->light_wallet() && (version >> 16) != CORE_RPC_VERSION_MAJOR)
|
||||
return Wallet::ConnectionStatus_WrongVersion;
|
||||
return Wallet::ConnectionStatus_Connected;
|
||||
}
|
||||
|
@ -1411,7 +1486,7 @@ void WalletImpl::doRefresh()
|
|||
try {
|
||||
// Syncing daemon and refreshing wallet simultaneously is very resource intensive.
|
||||
// Disable refresh if wallet is disconnected or daemon isn't synced.
|
||||
if (daemonSynced()) {
|
||||
if (m_wallet->light_wallet() || daemonSynced()) {
|
||||
m_wallet->refresh();
|
||||
if (!m_synchronized) {
|
||||
m_synchronized = true;
|
||||
|
@ -1476,13 +1551,14 @@ bool WalletImpl::isNewWallet() const
|
|||
return !(blockChainHeight() > 1 || m_recoveringFromSeed || m_rebuildWalletCache) && !watchOnly();
|
||||
}
|
||||
|
||||
bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit)
|
||||
bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl)
|
||||
{
|
||||
if (!m_wallet->init(daemon_address, m_daemon_login, upper_transaction_size_limit))
|
||||
if (!m_wallet->init(daemon_address, m_daemon_login, upper_transaction_size_limit, ssl))
|
||||
return false;
|
||||
|
||||
// in case new wallet, this will force fast-refresh (pulling hashes instead of blocks)
|
||||
// If daemon isn't synced a calculated block height will be used instead
|
||||
//TODO: Handle light wallet scenario where block height = 0.
|
||||
if (isNewWallet() && daemonSynced()) {
|
||||
LOG_PRINT_L2(__FUNCTION__ << ":New Wallet - fast refresh until " << daemonBlockChainHeight());
|
||||
m_wallet->set_refresh_from_block_height(daemonBlockChainHeight());
|
||||
|
|
|
@ -83,7 +83,7 @@ public:
|
|||
bool store(const std::string &path);
|
||||
std::string filename() const;
|
||||
std::string keysFilename() const;
|
||||
bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, const std::string &daemon_username = "", const std::string &daemon_password = "");
|
||||
bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, const std::string &daemon_username = "", const std::string &daemon_password = "", bool use_ssl = false, bool lightWallet = false);
|
||||
bool connectToDaemon();
|
||||
ConnectionStatus connected() const;
|
||||
void setTrustedDaemon(bool arg);
|
||||
|
@ -143,6 +143,8 @@ public:
|
|||
virtual void pauseRefresh();
|
||||
virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error);
|
||||
virtual std::string getDefaultDataDir() const;
|
||||
virtual bool lightWalletLogin(bool &isNewWallet) const;
|
||||
virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status);
|
||||
|
||||
private:
|
||||
void clearStatus() const;
|
||||
|
@ -151,7 +153,7 @@ private:
|
|||
bool daemonSynced() const;
|
||||
void stopRefresh();
|
||||
bool isNewWallet() const;
|
||||
bool doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit);
|
||||
bool doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false);
|
||||
|
||||
private:
|
||||
friend class PendingTransactionImpl;
|
||||
|
|
|
@ -525,7 +525,7 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia
|
|||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit)
|
||||
bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit, bool ssl)
|
||||
{
|
||||
m_checkpoints.init_default_checkpoints(m_testnet);
|
||||
if(m_http_client.is_connected())
|
||||
|
@ -534,7 +534,10 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::
|
|||
m_upper_transaction_size_limit = upper_transaction_size_limit;
|
||||
m_daemon_address = std::move(daemon_address);
|
||||
m_daemon_login = std::move(daemon_login);
|
||||
return m_http_client.set_server(get_daemon_address(), get_daemon_login());
|
||||
// When switching from light wallet to full wallet, we need to reset the height we got from lw node.
|
||||
if(m_light_wallet)
|
||||
m_local_bc_height = m_blockchain.size();
|
||||
return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::is_deterministic() const
|
||||
|
@ -1513,6 +1516,34 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei
|
|||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes)
|
||||
{
|
||||
// remove pool txes to us that aren't in the pool anymore
|
||||
std::unordered_map<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin();
|
||||
while (uit != m_unconfirmed_payments.end())
|
||||
{
|
||||
const crypto::hash &txid = uit->second.m_tx_hash;
|
||||
bool found = false;
|
||||
for (const auto &it2: tx_hashes)
|
||||
{
|
||||
if (it2 == txid)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto pit = uit++;
|
||||
if (!found)
|
||||
{
|
||||
MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool");
|
||||
m_unconfirmed_payments.erase(pit);
|
||||
if (0 != m_callback)
|
||||
m_callback->on_pool_tx_removed(txid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::update_pool_state(bool refreshed)
|
||||
{
|
||||
|
@ -1590,28 +1621,8 @@ void wallet2::update_pool_state(bool refreshed)
|
|||
// the in transfers list instead (or nowhere if it just
|
||||
// disappeared without being mined)
|
||||
if (refreshed)
|
||||
{
|
||||
std::unordered_map<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin();
|
||||
while (uit != m_unconfirmed_payments.end())
|
||||
{
|
||||
const crypto::hash &txid = uit->second.m_tx_hash;
|
||||
bool found = false;
|
||||
for (const auto &it2: res.tx_hashes)
|
||||
{
|
||||
if (it2 == txid)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto pit = uit++;
|
||||
if (!found)
|
||||
{
|
||||
MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool");
|
||||
m_unconfirmed_payments.erase(pit);
|
||||
}
|
||||
}
|
||||
}
|
||||
remove_obsolete_pool_txs(res.tx_hashes);
|
||||
|
||||
MDEBUG("update_pool_state done second loop");
|
||||
|
||||
// gather txids of new pool txes to us
|
||||
|
@ -1828,6 +1839,39 @@ bool wallet2::delete_address_book_row(std::size_t row_id) {
|
|||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& received_money)
|
||||
{
|
||||
if(m_light_wallet) {
|
||||
|
||||
// MyMonero get_address_info needs to be called occasionally to trigger wallet sync.
|
||||
// This call is not really needed for other purposes and can be removed if mymonero changes their backend.
|
||||
cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response res;
|
||||
|
||||
// Get basic info
|
||||
if(light_wallet_get_address_info(res)) {
|
||||
// Last stored block height
|
||||
uint64_t prev_height = m_light_wallet_blockchain_height;
|
||||
// Update lw heights
|
||||
m_light_wallet_scanned_block_height = res.scanned_block_height;
|
||||
m_light_wallet_blockchain_height = res.blockchain_height;
|
||||
m_local_bc_height = res.blockchain_height;
|
||||
// If new height - call new_block callback
|
||||
if(m_light_wallet_blockchain_height != prev_height)
|
||||
{
|
||||
MDEBUG("new block since last time!");
|
||||
m_callback->on_lw_new_block(m_light_wallet_blockchain_height - 1);
|
||||
}
|
||||
m_light_wallet_connected = true;
|
||||
MDEBUG("lw scanned block height: " << m_light_wallet_scanned_block_height);
|
||||
MDEBUG("lw blockchain height: " << m_light_wallet_blockchain_height);
|
||||
MDEBUG(m_light_wallet_blockchain_height-m_light_wallet_scanned_block_height << " blocks behind");
|
||||
// TODO: add wallet created block info
|
||||
|
||||
light_wallet_get_address_txs();
|
||||
} else
|
||||
m_light_wallet_connected = false;
|
||||
|
||||
// Lighwallet refresh done
|
||||
return;
|
||||
}
|
||||
received_money = false;
|
||||
blocks_fetched = 0;
|
||||
uint64_t added_blocks = 0;
|
||||
|
@ -2603,6 +2647,12 @@ bool wallet2::check_connection(uint32_t *version, uint32_t timeout)
|
|||
|
||||
boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex);
|
||||
|
||||
// TODO: Add light wallet version check.
|
||||
if(m_light_wallet) {
|
||||
version = 0;
|
||||
return m_light_wallet_connected;
|
||||
}
|
||||
|
||||
if(!m_http_client.is_connected())
|
||||
{
|
||||
m_node_rpc_proxy.invalidate();
|
||||
|
@ -2896,6 +2946,8 @@ void wallet2::store_to(const std::string &path, const std::string &password)
|
|||
uint64_t wallet2::balance(uint32_t index_major) const
|
||||
{
|
||||
uint64_t amount = 0;
|
||||
if(m_light_wallet)
|
||||
return m_light_wallet_unlocked_balance;
|
||||
for (const auto& i : balance_per_subaddress(index_major))
|
||||
amount += i.second;
|
||||
return amount;
|
||||
|
@ -2904,6 +2956,8 @@ uint64_t wallet2::balance(uint32_t index_major) const
|
|||
uint64_t wallet2::unlocked_balance(uint32_t index_major) const
|
||||
{
|
||||
uint64_t amount = 0;
|
||||
if(m_light_wallet)
|
||||
return m_light_wallet_balance;
|
||||
for (const auto& i : unlocked_balance_per_subaddress(index_major))
|
||||
amount += i.second;
|
||||
return amount;
|
||||
|
@ -3105,10 +3159,15 @@ void wallet2::rescan_blockchain(bool refresh)
|
|||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::is_transfer_unlocked(const transfer_details& td) const
|
||||
{
|
||||
if(!is_tx_spendtime_unlocked(td.m_tx.unlock_time, td.m_block_height))
|
||||
return is_transfer_unlocked(td.m_tx.unlock_time, td.m_block_height);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const
|
||||
{
|
||||
if(!is_tx_spendtime_unlocked(unlock_time, block_height))
|
||||
return false;
|
||||
|
||||
if(td.m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > m_blockchain.size())
|
||||
if(block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > m_local_bc_height)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -3119,7 +3178,7 @@ bool wallet2::is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_heig
|
|||
if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER)
|
||||
{
|
||||
//interpret as block index
|
||||
if(m_blockchain.size()-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time)
|
||||
if(m_local_bc_height-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
|
@ -3424,25 +3483,42 @@ crypto::hash8 wallet2::get_short_payment_id(const pending_tx &ptx) const
|
|||
void wallet2::commit_tx(pending_tx& ptx)
|
||||
{
|
||||
using namespace cryptonote;
|
||||
crypto::hash txid;
|
||||
|
||||
COMMAND_RPC_SEND_RAW_TX::request req;
|
||||
req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
|
||||
req.do_not_relay = false;
|
||||
COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
|
||||
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
|
||||
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason);
|
||||
|
||||
// sanity checks
|
||||
for (size_t idx: ptx.selected_transfers)
|
||||
|
||||
if(m_light_wallet)
|
||||
{
|
||||
THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error,
|
||||
"Bad output index in selected transfers: " + boost::lexical_cast<std::string>(idx));
|
||||
cryptonote::COMMAND_RPC_SUBMIT_RAW_TX::request oreq;
|
||||
cryptonote::COMMAND_RPC_SUBMIT_RAW_TX::response ores;
|
||||
oreq.address = get_account().get_public_address_str(m_testnet);
|
||||
oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
|
||||
oreq.tx = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json("/submit_raw_tx", oreq, ores, m_http_client, rpc_timeout, "POST");
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx");
|
||||
// MyMonero and OpenMonero use different status strings
|
||||
THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, ores.status, ores.error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal submit
|
||||
COMMAND_RPC_SEND_RAW_TX::request req;
|
||||
req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
|
||||
req.do_not_relay = false;
|
||||
COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
|
||||
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
|
||||
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason);
|
||||
// sanity checks
|
||||
for (size_t idx: ptx.selected_transfers)
|
||||
{
|
||||
THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error,
|
||||
"Bad output index in selected transfers: " + boost::lexical_cast<std::string>(idx));
|
||||
}
|
||||
}
|
||||
crypto::hash txid;
|
||||
|
||||
txid = get_transaction_hash(ptx.tx);
|
||||
crypto::hash payment_id = crypto::null_hash;
|
||||
|
@ -3864,6 +3940,8 @@ uint64_t wallet2::get_dynamic_per_kb_fee_estimate()
|
|||
//----------------------------------------------------------------------------------------------------
|
||||
uint64_t wallet2::get_per_kb_fee()
|
||||
{
|
||||
if(m_light_wallet)
|
||||
return m_light_wallet_per_kb_fee;
|
||||
bool use_dyn_fee = use_fork_rules(HF_VERSION_DYNAMIC_FEE, -720 * 1);
|
||||
if (!use_dyn_fee)
|
||||
return FEE_PER_KB;
|
||||
|
@ -3989,10 +4067,134 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const
|
||||
{
|
||||
if (!unlocked) // don't add locked outs
|
||||
return false;
|
||||
if (global_index == real_index) // don't re-add real one
|
||||
return false;
|
||||
auto item = std::make_tuple(global_index, tx_public_key, mask);
|
||||
if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates
|
||||
return false;
|
||||
outs.back().push_back(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) {
|
||||
|
||||
MDEBUG("LIGHTWALLET - Getting random outs");
|
||||
|
||||
cryptonote::COMMAND_RPC_GET_RANDOM_OUTS::request oreq;
|
||||
cryptonote::COMMAND_RPC_GET_RANDOM_OUTS::response ores;
|
||||
|
||||
size_t light_wallet_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1);
|
||||
|
||||
// Amounts to ask for
|
||||
// MyMonero api handle amounts and fees as strings
|
||||
for(size_t idx: selected_transfers) {
|
||||
const uint64_t ask_amount = m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount();
|
||||
std::ostringstream amount_ss;
|
||||
amount_ss << ask_amount;
|
||||
oreq.amounts.push_back(amount_ss.str());
|
||||
}
|
||||
|
||||
oreq.count = light_wallet_requested_outputs_count;
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json("/get_random_outs", oreq, ores, m_http_client, rpc_timeout, "POST");
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs");
|
||||
THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs recieved from light wallet node. Error: " + ores.Error);
|
||||
|
||||
// Check if we got enough outputs for each amount
|
||||
for(auto& out: ores.amount_outs) {
|
||||
const uint64_t out_amount = boost::lexical_cast<uint64_t>(out.amount);
|
||||
THROW_WALLET_EXCEPTION_IF(out.outputs.size() < light_wallet_requested_outputs_count , error::wallet_internal_error, "Not enough outputs for amount: " + boost::lexical_cast<std::string>(out.amount));
|
||||
MDEBUG(out.outputs.size() << " outputs for amount "+ boost::lexical_cast<std::string>(out.amount) + " received from light wallet node");
|
||||
}
|
||||
|
||||
MDEBUG("selected transfers size: " << selected_transfers.size());
|
||||
|
||||
for(size_t idx: selected_transfers)
|
||||
{
|
||||
// Create new index
|
||||
outs.push_back(std::vector<get_outs_entry>());
|
||||
outs.back().reserve(fake_outputs_count + 1);
|
||||
|
||||
// add real output first
|
||||
const transfer_details &td = m_transfers[idx];
|
||||
const uint64_t amount = td.is_rct() ? 0 : td.amount();
|
||||
outs.back().push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), rct::commit(td.amount(), td.m_mask)));
|
||||
MDEBUG("added real output " << string_tools::pod_to_hex(td.get_public_key()));
|
||||
|
||||
// Even if the lightwallet server returns random outputs, we pick them randomly.
|
||||
std::vector<size_t> order;
|
||||
order.resize(light_wallet_requested_outputs_count);
|
||||
for (size_t n = 0; n < order.size(); ++n)
|
||||
order[n] = n;
|
||||
std::shuffle(order.begin(), order.end(), std::default_random_engine(crypto::rand<unsigned>()));
|
||||
|
||||
|
||||
LOG_PRINT_L2("Looking for " << (fake_outputs_count+1) << " outputs with amounts " << print_money(td.is_rct() ? 0 : td.amount()));
|
||||
MDEBUG("OUTS SIZE: " << outs.back().size());
|
||||
for (size_t o = 0; o < light_wallet_requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o)
|
||||
{
|
||||
// Random pick
|
||||
size_t i = order[o];
|
||||
|
||||
// Find which random output key to use
|
||||
bool found_amount = false;
|
||||
size_t amount_key;
|
||||
for(amount_key = 0; amount_key < ores.amount_outs.size(); ++amount_key)
|
||||
{
|
||||
if(boost::lexical_cast<uint64_t>(ores.amount_outs[amount_key].amount) == amount) {
|
||||
found_amount = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
THROW_WALLET_EXCEPTION_IF(!found_amount , error::wallet_internal_error, "Outputs for amount " + boost::lexical_cast<std::string>(ores.amount_outs[amount_key].amount) + " not found" );
|
||||
|
||||
LOG_PRINT_L2("Index " << i << "/" << light_wallet_requested_outputs_count << ": idx " << ores.amount_outs[amount_key].outputs[i].global_index << " (real " << td.m_global_output_index << "), unlocked " << "(always in light)" << ", key " << ores.amount_outs[0].outputs[i].public_key);
|
||||
|
||||
// Convert light wallet string data to proper data structures
|
||||
crypto::public_key tx_public_key;
|
||||
rct::key mask = AUTO_VAL_INIT(mask); // decrypted mask - not used here
|
||||
rct::key rct_commit = AUTO_VAL_INIT(rct_commit);
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, ores.amount_outs[amount_key].outputs[i].public_key), error::wallet_internal_error, "Invalid public_key");
|
||||
string_tools::hex_to_pod(ores.amount_outs[amount_key].outputs[i].public_key, tx_public_key);
|
||||
const uint64_t global_index = ores.amount_outs[amount_key].outputs[i].global_index;
|
||||
if(!light_wallet_parse_rct_str(ores.amount_outs[amount_key].outputs[i].rct, tx_public_key, 0, mask, rct_commit, false))
|
||||
rct_commit = rct::zeroCommit(td.amount());
|
||||
|
||||
if (tx_add_fake_output(outs, global_index, tx_public_key, rct_commit, td.m_global_output_index, true)) {
|
||||
MDEBUG("added fake output " << ores.amount_outs[amount_key].outputs[i].public_key);
|
||||
MDEBUG("index " << global_index);
|
||||
}
|
||||
}
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(outs.back().size() < fake_outputs_count + 1 , error::wallet_internal_error, "Not enough fake outputs found" );
|
||||
|
||||
// Real output is the first. Shuffle outputs
|
||||
MTRACE(outs.back().size() << " outputs added. Sorting outputs by index:");
|
||||
std::sort(outs.back().begin(), outs.back().end(), [](const get_outs_entry &a, const get_outs_entry &b) { return std::get<0>(a) < std::get<0>(b); });
|
||||
|
||||
// Print output order
|
||||
for(auto added_out: outs.back())
|
||||
MTRACE(std::get<0>(added_out));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count)
|
||||
{
|
||||
LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count);
|
||||
outs.clear();
|
||||
|
||||
if(m_light_wallet && fake_outputs_count > 0) {
|
||||
light_wallet_get_outs(outs, selected_transfers, fake_outputs_count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fake_outputs_count > 0)
|
||||
{
|
||||
// get histogram for the amounts we need
|
||||
|
@ -4189,14 +4391,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
|
|||
{
|
||||
size_t i = base + order[o];
|
||||
LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key);
|
||||
if (req.outputs[i].index == td.m_global_output_index) // don't re-add real one
|
||||
continue;
|
||||
if (!daemon_resp.outs[i].unlocked) // don't add locked outs
|
||||
continue;
|
||||
auto item = std::make_tuple(req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask);
|
||||
if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates
|
||||
continue;
|
||||
outs.back().push_back(item);
|
||||
tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked);
|
||||
}
|
||||
if (outs.back().size() < fake_outputs_count + 1)
|
||||
{
|
||||
|
@ -4218,7 +4413,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
|
|||
const transfer_details &td = m_transfers[idx];
|
||||
std::vector<get_outs_entry> v;
|
||||
const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount());
|
||||
v.push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask));
|
||||
v.push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), mask));
|
||||
outs.push_back(v);
|
||||
}
|
||||
}
|
||||
|
@ -4433,6 +4628,9 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
|||
src.rct = td.is_rct();
|
||||
//paste mixin transaction
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(outs.size() < out_index + 1 , error::wallet_internal_error, "outs.size() < out_index + 1");
|
||||
THROW_WALLET_EXCEPTION_IF(outs[out_index].size() < fake_outputs_count , error::wallet_internal_error, "fake_outputs_count > random outputs found");
|
||||
|
||||
typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
|
||||
for (size_t n = 0; n < fake_outputs_count + 1; ++n)
|
||||
{
|
||||
|
@ -4454,7 +4652,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
|||
|
||||
tx_output_entry real_oe;
|
||||
real_oe.first = td.m_global_output_index;
|
||||
real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key);
|
||||
real_oe.second.dest = rct::pk2rct(td.get_public_key());
|
||||
real_oe.second.mask = rct::commit(td.amount(), td.m_mask);
|
||||
*it_to_replace = real_oe;
|
||||
src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index);
|
||||
|
@ -4701,6 +4899,445 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
|
|||
return count;
|
||||
}
|
||||
|
||||
bool wallet2::light_wallet_login(bool &new_address)
|
||||
{
|
||||
MDEBUG("Light wallet login request");
|
||||
m_light_wallet_connected = false;
|
||||
cryptonote::COMMAND_RPC_LOGIN::request request;
|
||||
cryptonote::COMMAND_RPC_LOGIN::response response;
|
||||
request.address = get_account().get_public_address_str(m_testnet);
|
||||
request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
|
||||
// Always create account if it doesnt exist.
|
||||
request.create_account = true;
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool connected = epee::net_utils::invoke_http_json("/login", request, response, m_http_client, rpc_timeout, "POST");
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
// MyMonero doesn't send any status message. OpenMonero does.
|
||||
m_light_wallet_connected = connected && (response.status.empty() || response.status == "success");
|
||||
new_address = response.new_address;
|
||||
MDEBUG("Status: " << response.status);
|
||||
MDEBUG("Reason: " << response.reason);
|
||||
MDEBUG("New wallet: " << response.new_address);
|
||||
if(m_light_wallet_connected)
|
||||
{
|
||||
// Clear old data on successfull login.
|
||||
// m_transfers.clear();
|
||||
// m_payments.clear();
|
||||
// m_unconfirmed_payments.clear();
|
||||
}
|
||||
return m_light_wallet_connected;
|
||||
}
|
||||
|
||||
bool wallet2::light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response)
|
||||
{
|
||||
MDEBUG("Light wallet import wallet request");
|
||||
cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::request oreq;
|
||||
oreq.address = get_account().get_public_address_str(m_testnet);
|
||||
oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json("/import_wallet_request", oreq, response, m_http_client, rpc_timeout, "POST");
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "import_wallet_request");
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void wallet2::light_wallet_get_unspent_outs()
|
||||
{
|
||||
MDEBUG("Getting unspent outs");
|
||||
|
||||
cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::request oreq;
|
||||
cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::response ores;
|
||||
|
||||
oreq.amount = "0";
|
||||
oreq.address = get_account().get_public_address_str(m_testnet);
|
||||
oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
|
||||
// openMonero specific
|
||||
oreq.dust_threshold = boost::lexical_cast<std::string>(::config::DEFAULT_DUST_THRESHOLD);
|
||||
// below are required by openMonero api - but are not used.
|
||||
oreq.mixin = 0;
|
||||
oreq.use_dust = true;
|
||||
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json("/get_unspent_outs", oreq, ores, m_http_client, rpc_timeout, "POST");
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_unspent_outs");
|
||||
THROW_WALLET_EXCEPTION_IF(ores.status == "error", error::wallet_internal_error, ores.reason);
|
||||
|
||||
m_light_wallet_per_kb_fee = ores.per_kb_fee;
|
||||
|
||||
std::unordered_map<crypto::hash,bool> transfers_txs;
|
||||
for(const auto &t: m_transfers)
|
||||
transfers_txs.emplace(t.m_txid,t.m_spent);
|
||||
|
||||
MDEBUG("FOUND " << ores.outputs.size() <<" outputs");
|
||||
|
||||
// return if no outputs found
|
||||
if(ores.outputs.empty())
|
||||
return;
|
||||
|
||||
// Clear old outputs
|
||||
m_transfers.clear();
|
||||
|
||||
for (const auto &o: ores.outputs) {
|
||||
bool spent = false;
|
||||
bool add_transfer = true;
|
||||
crypto::key_image unspent_key_image;
|
||||
crypto::public_key tx_public_key = AUTO_VAL_INIT(tx_public_key);
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field");
|
||||
string_tools::hex_to_pod(o.tx_pub_key, tx_public_key);
|
||||
|
||||
for (const std::string &ski: o.spend_key_images) {
|
||||
spent = false;
|
||||
|
||||
// Check if key image is ours
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, ski), error::wallet_internal_error, "Invalid key image");
|
||||
string_tools::hex_to_pod(ski, unspent_key_image);
|
||||
if(light_wallet_key_image_is_ours(unspent_key_image, tx_public_key, o.index)){
|
||||
MTRACE("Output " << o.public_key << " is spent. Key image: " << ski);
|
||||
spent = true;
|
||||
break;
|
||||
} {
|
||||
MTRACE("Unspent output found. " << o.public_key);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if tx already exists in m_transfers.
|
||||
crypto::hash txid;
|
||||
crypto::public_key tx_pub_key;
|
||||
crypto::public_key public_key;
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_hash), error::wallet_internal_error, "Invalid tx_hash field");
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.public_key), error::wallet_internal_error, "Invalid public_key field");
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, o.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field");
|
||||
string_tools::hex_to_pod(o.tx_hash, txid);
|
||||
string_tools::hex_to_pod(o.public_key, public_key);
|
||||
string_tools::hex_to_pod(o.tx_pub_key, tx_pub_key);
|
||||
|
||||
for(auto &t: m_transfers){
|
||||
if(t.get_public_key() == public_key) {
|
||||
t.m_spent = spent;
|
||||
add_transfer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!add_transfer)
|
||||
continue;
|
||||
|
||||
m_transfers.push_back(boost::value_initialized<transfer_details>());
|
||||
transfer_details& td = m_transfers.back();
|
||||
|
||||
td.m_block_height = o.height;
|
||||
td.m_global_output_index = o.global_index;
|
||||
td.m_txid = txid;
|
||||
|
||||
// Add to extra
|
||||
add_tx_pub_key_to_extra(td.m_tx, tx_pub_key);
|
||||
|
||||
td.m_key_image = unspent_key_image;
|
||||
td.m_key_image_known = !m_watch_only;
|
||||
td.m_amount = o.amount;
|
||||
td.m_pk_index = 0;
|
||||
td.m_internal_output_index = o.index;
|
||||
td.m_spent = spent;
|
||||
|
||||
tx_out txout;
|
||||
txout.target = txout_to_key(public_key);
|
||||
txout.amount = td.m_amount;
|
||||
|
||||
td.m_tx.vout.resize(td.m_internal_output_index + 1);
|
||||
td.m_tx.vout[td.m_internal_output_index] = txout;
|
||||
|
||||
// Add unlock time and coinbase bool got from get_address_txs api call
|
||||
std::unordered_map<crypto::hash,address_tx>::const_iterator found = m_light_wallet_address_txs.find(txid);
|
||||
THROW_WALLET_EXCEPTION_IF(found == m_light_wallet_address_txs.end(), error::wallet_internal_error, "Lightwallet: tx not found in m_light_wallet_address_txs");
|
||||
bool miner_tx = found->second.m_coinbase;
|
||||
td.m_tx.unlock_time = found->second.m_unlock_time;
|
||||
|
||||
if (!o.rct.empty())
|
||||
{
|
||||
// Coinbase tx's
|
||||
if(miner_tx)
|
||||
{
|
||||
td.m_mask = rct::identity();
|
||||
}
|
||||
else
|
||||
{
|
||||
// rct txs
|
||||
// decrypt rct mask, calculate commit hash and compare against blockchain commit hash
|
||||
rct::key rct_commit;
|
||||
light_wallet_parse_rct_str(o.rct, tx_pub_key, td.m_internal_output_index, td.m_mask, rct_commit, true);
|
||||
bool valid_commit = (rct_commit == rct::commit(td.amount(), td.m_mask));
|
||||
if(!valid_commit)
|
||||
{
|
||||
MDEBUG("output index: " << o.global_index);
|
||||
MDEBUG("mask: " + string_tools::pod_to_hex(td.m_mask));
|
||||
MDEBUG("calculated commit: " + string_tools::pod_to_hex(rct::commit(td.amount(), td.m_mask)));
|
||||
MDEBUG("expected commit: " + string_tools::pod_to_hex(rct_commit));
|
||||
MDEBUG("amount: " << td.amount());
|
||||
}
|
||||
THROW_WALLET_EXCEPTION_IF(!valid_commit, error::wallet_internal_error, "Lightwallet: rct commit hash mismatch!");
|
||||
}
|
||||
td.m_rct = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
td.m_mask = rct::identity();
|
||||
td.m_rct = false;
|
||||
}
|
||||
if(!spent)
|
||||
set_unspent(m_transfers.size()-1);
|
||||
m_key_images[td.m_key_image] = m_transfers.size()-1;
|
||||
m_pub_keys[td.get_public_key()] = m_transfers.size()-1;
|
||||
}
|
||||
}
|
||||
|
||||
bool wallet2::light_wallet_get_address_info(cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response &response)
|
||||
{
|
||||
MTRACE(__FUNCTION__);
|
||||
|
||||
cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::request request;
|
||||
|
||||
request.address = get_account().get_public_address_str(m_testnet);
|
||||
request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json("/get_address_info", request, response, m_http_client, rpc_timeout, "POST");
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_info");
|
||||
// TODO: Validate result
|
||||
return true;
|
||||
}
|
||||
|
||||
void wallet2::light_wallet_get_address_txs()
|
||||
{
|
||||
MDEBUG("Refreshing light wallet");
|
||||
|
||||
cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::request ireq;
|
||||
cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::response ires;
|
||||
|
||||
ireq.address = get_account().get_public_address_str(m_testnet);
|
||||
ireq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = epee::net_utils::invoke_http_json("/get_address_txs", ireq, ires, m_http_client, rpc_timeout, "POST");
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_address_txs");
|
||||
//OpenMonero sends status=success, Mymonero doesn't.
|
||||
THROW_WALLET_EXCEPTION_IF((!ires.status.empty() && ires.status != "success"), error::no_connection_to_daemon, "get_address_txs");
|
||||
|
||||
|
||||
// Abort if no transactions
|
||||
if(ires.transactions.empty())
|
||||
return;
|
||||
|
||||
// Create searchable vectors
|
||||
std::vector<crypto::hash> payments_txs;
|
||||
for(const auto &p: m_payments)
|
||||
payments_txs.push_back(p.second.m_tx_hash);
|
||||
std::vector<crypto::hash> unconfirmed_payments_txs;
|
||||
for(const auto &up: m_unconfirmed_payments)
|
||||
unconfirmed_payments_txs.push_back(up.second.m_tx_hash);
|
||||
|
||||
// for balance calculation
|
||||
uint64_t wallet_total_sent = 0;
|
||||
uint64_t wallet_total_unlocked_sent = 0;
|
||||
// txs in pool
|
||||
std::vector<crypto::hash> pool_txs;
|
||||
|
||||
for (const auto &t: ires.transactions) {
|
||||
const uint64_t total_received = t.total_received;
|
||||
uint64_t total_sent = t.total_sent;
|
||||
|
||||
// Check key images - subtract fake outputs from total_sent
|
||||
for(const auto &so: t.spent_outputs)
|
||||
{
|
||||
crypto::public_key tx_public_key;
|
||||
crypto::key_image key_image;
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, so.tx_pub_key), error::wallet_internal_error, "Invalid tx_pub_key field");
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, so.key_image), error::wallet_internal_error, "Invalid key_image field");
|
||||
string_tools::hex_to_pod(so.tx_pub_key, tx_public_key);
|
||||
string_tools::hex_to_pod(so.key_image, key_image);
|
||||
|
||||
if(!light_wallet_key_image_is_ours(key_image, tx_public_key, so.out_index)) {
|
||||
THROW_WALLET_EXCEPTION_IF(so.amount > t.total_sent, error::wallet_internal_error, "Lightwallet: total sent is negative!");
|
||||
total_sent -= so.amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Do not add tx if empty.
|
||||
if(total_sent == 0 && total_received == 0)
|
||||
continue;
|
||||
|
||||
crypto::hash payment_id = null_hash;
|
||||
crypto::hash tx_hash;
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, t.payment_id), error::wallet_internal_error, "Invalid payment_id field");
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, t.hash), error::wallet_internal_error, "Invalid hash field");
|
||||
string_tools::hex_to_pod(t.payment_id, payment_id);
|
||||
string_tools::hex_to_pod(t.hash, tx_hash);
|
||||
|
||||
// lightwallet specific info
|
||||
bool incoming = (total_received > total_sent);
|
||||
address_tx address_tx;
|
||||
address_tx.m_tx_hash = tx_hash;
|
||||
address_tx.m_incoming = incoming;
|
||||
address_tx.m_amount = incoming ? total_received - total_sent : total_sent - total_received;
|
||||
address_tx.m_block_height = t.height;
|
||||
address_tx.m_unlock_time = t.unlock_time;
|
||||
address_tx.m_timestamp = t.timestamp;
|
||||
address_tx.m_coinbase = t.coinbase;
|
||||
address_tx.m_mempool = t.mempool;
|
||||
m_light_wallet_address_txs.emplace(tx_hash,address_tx);
|
||||
|
||||
// populate data needed for history (m_payments, m_unconfirmed_payments, m_confirmed_txs)
|
||||
// INCOMING transfers
|
||||
if(total_received > total_sent) {
|
||||
payment_details payment;
|
||||
payment.m_tx_hash = tx_hash;
|
||||
payment.m_amount = total_received - total_sent;
|
||||
payment.m_block_height = t.height;
|
||||
payment.m_unlock_time = t.unlock_time;
|
||||
payment.m_timestamp = t.timestamp;
|
||||
|
||||
if (t.mempool) {
|
||||
if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) {
|
||||
pool_txs.push_back(tx_hash);
|
||||
m_unconfirmed_payments.emplace(tx_hash, payment);
|
||||
if (0 != m_callback) {
|
||||
m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (std::find(payments_txs.begin(), payments_txs.end(), tx_hash) == payments_txs.end()) {
|
||||
m_payments.emplace(tx_hash, payment);
|
||||
if (0 != m_callback) {
|
||||
m_callback->on_lw_money_received(t.height, payment.m_tx_hash, payment.m_amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Outgoing transfers
|
||||
} else {
|
||||
uint64_t amount_sent = total_sent - total_received;
|
||||
cryptonote::transaction dummy_tx; // not used by light wallet
|
||||
// increase wallet total sent
|
||||
wallet_total_sent += total_sent;
|
||||
if (t.mempool)
|
||||
{
|
||||
// Handled by add_unconfirmed_tx in commit_tx
|
||||
// If sent from another wallet instance we need to add it
|
||||
if(m_unconfirmed_txs.find(tx_hash) == m_unconfirmed_txs.end())
|
||||
{
|
||||
unconfirmed_transfer_details utd;
|
||||
utd.m_amount_in = amount_sent;
|
||||
utd.m_amount_out = amount_sent;
|
||||
utd.m_change = 0;
|
||||
utd.m_payment_id = payment_id;
|
||||
utd.m_timestamp = t.timestamp;
|
||||
utd.m_state = wallet2::unconfirmed_transfer_details::pending;
|
||||
m_unconfirmed_txs.emplace(tx_hash,utd);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only add if new
|
||||
auto confirmed_tx = m_confirmed_txs.find(tx_hash);
|
||||
if(confirmed_tx == m_confirmed_txs.end()) {
|
||||
// tx is added to m_unconfirmed_txs - move to confirmed
|
||||
if(m_unconfirmed_txs.find(tx_hash) != m_unconfirmed_txs.end())
|
||||
{
|
||||
process_unconfirmed(tx_hash, dummy_tx, t.height);
|
||||
}
|
||||
// Tx sent by another wallet instance
|
||||
else
|
||||
{
|
||||
confirmed_transfer_details ctd;
|
||||
ctd.m_amount_in = amount_sent;
|
||||
ctd.m_amount_out = amount_sent;
|
||||
ctd.m_change = 0;
|
||||
ctd.m_payment_id = payment_id;
|
||||
ctd.m_block_height = t.height;
|
||||
ctd.m_timestamp = t.timestamp;
|
||||
m_confirmed_txs.emplace(tx_hash,ctd);
|
||||
}
|
||||
if (0 != m_callback)
|
||||
{
|
||||
m_callback->on_lw_money_spent(t.height, tx_hash, amount_sent);
|
||||
}
|
||||
}
|
||||
// If not new - check the amount and update if necessary.
|
||||
// when sending a tx to same wallet the receiving amount has to be credited
|
||||
else
|
||||
{
|
||||
if(confirmed_tx->second.m_amount_in != amount_sent || confirmed_tx->second.m_amount_out != amount_sent)
|
||||
{
|
||||
MDEBUG("Adjusting amount sent/received for tx: <" + t.hash + ">. Is tx sent to own wallet? " << print_money(amount_sent) << " != " << print_money(confirmed_tx->second.m_amount_in));
|
||||
confirmed_tx->second.m_amount_in = amount_sent;
|
||||
confirmed_tx->second.m_amount_out = amount_sent;
|
||||
confirmed_tx->second.m_change = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: purge old unconfirmed_txs
|
||||
remove_obsolete_pool_txs(pool_txs);
|
||||
|
||||
// Calculate wallet balance
|
||||
m_light_wallet_balance = ires.total_received-wallet_total_sent;
|
||||
// MyMonero doesnt send unlocked balance
|
||||
if(ires.total_received_unlocked > 0)
|
||||
m_light_wallet_unlocked_balance = ires.total_received_unlocked-wallet_total_sent;
|
||||
else
|
||||
m_light_wallet_unlocked_balance = m_light_wallet_balance;
|
||||
}
|
||||
|
||||
bool wallet2::light_wallet_parse_rct_str(const std::string& rct_string, const crypto::public_key& tx_pub_key, uint64_t internal_output_index, rct::key& decrypted_mask, rct::key& rct_commit, bool decrypt) const
|
||||
{
|
||||
// rct string is empty if output is non RCT
|
||||
if (rct_string.empty())
|
||||
return false;
|
||||
// rct_string is a string with length 64+64+64 (<rct commit> + <encrypted mask> + <rct amount>)
|
||||
rct::key encrypted_mask;
|
||||
std::string rct_commit_str = rct_string.substr(0,64);
|
||||
std::string encrypted_mask_str = rct_string.substr(64,64);
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, rct_commit_str), error::wallet_internal_error, "Invalid rct commit hash: " + rct_commit_str);
|
||||
THROW_WALLET_EXCEPTION_IF(string_tools::validate_hex(64, encrypted_mask_str), error::wallet_internal_error, "Invalid rct mask: " + encrypted_mask_str);
|
||||
string_tools::hex_to_pod(rct_commit_str, rct_commit);
|
||||
string_tools::hex_to_pod(encrypted_mask_str, encrypted_mask);
|
||||
if (decrypt) {
|
||||
// Decrypt the mask
|
||||
crypto::key_derivation derivation;
|
||||
generate_key_derivation(tx_pub_key, get_account().get_keys().m_view_secret_key, derivation);
|
||||
crypto::secret_key scalar;
|
||||
crypto::derivation_to_scalar(derivation, internal_output_index, scalar);
|
||||
sc_sub(decrypted_mask.bytes,encrypted_mask.bytes,rct::hash_to_scalar(rct::sk2rct(scalar)).bytes);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, const crypto::public_key& tx_public_key, uint64_t out_index)
|
||||
{
|
||||
// Lookup key image from cache
|
||||
std::map<uint64_t, crypto::key_image> index_keyimage_map;
|
||||
std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> >::const_iterator found_pub_key = m_key_image_cache.find(tx_public_key);
|
||||
if(found_pub_key != m_key_image_cache.end()) {
|
||||
// pub key found. key image for index cached?
|
||||
index_keyimage_map = found_pub_key->second;
|
||||
std::map<uint64_t,crypto::key_image>::const_iterator index_found = index_keyimage_map.find(out_index);
|
||||
if(index_found != index_keyimage_map.end())
|
||||
return key_image == index_found->second;
|
||||
}
|
||||
|
||||
// Not in cache - calculate key image
|
||||
crypto::key_image calculated_key_image;
|
||||
cryptonote::keypair in_ephemeral;
|
||||
cryptonote::generate_key_image_helper(get_account().get_keys(), tx_public_key, out_index, in_ephemeral, calculated_key_image);
|
||||
index_keyimage_map.emplace(out_index, calculated_key_image);
|
||||
m_key_image_cache.emplace(tx_public_key, index_keyimage_map);
|
||||
return key_image == calculated_key_image;
|
||||
}
|
||||
|
||||
// Another implementation of transaction creation that is hopefully better
|
||||
// While there is anything left to pay, it goes through random outputs and tries
|
||||
// to fill the next destination/amount. If it fully fills it, it will use the
|
||||
|
@ -4718,6 +5355,10 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr
|
|||
// usable balance.
|
||||
std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon)
|
||||
{
|
||||
if(m_light_wallet) {
|
||||
// Populate m_transfers
|
||||
light_wallet_get_unspent_outs();
|
||||
}
|
||||
std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_transfers_indices_per_subaddr;
|
||||
std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_dust_indices_per_subaddr;
|
||||
uint64_t needed_money;
|
||||
|
@ -4880,7 +5521,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
|||
std::vector<size_t> preferred_inputs;
|
||||
uint64_t rct_outs_needed = 2 * (fake_outs_count + 1);
|
||||
rct_outs_needed += 100; // some fudge factor since we don't know how many are locked
|
||||
if (use_rct && get_num_rct_outputs() >= rct_outs_needed)
|
||||
if (use_rct)
|
||||
{
|
||||
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
|
||||
// will get us a known fee.
|
||||
|
@ -5334,6 +5975,9 @@ void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height)
|
|||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks)
|
||||
{
|
||||
// TODO: How to get fork rule info from light wallet node?
|
||||
if(m_light_wallet)
|
||||
return true;
|
||||
uint64_t height, earliest_height;
|
||||
boost::optional<std::string> result = m_node_rpc_proxy.get_height(height);
|
||||
throw_on_rpc_response_error(result, "get_info");
|
||||
|
|
|
@ -71,11 +71,19 @@ namespace tools
|
|||
class i_wallet2_callback
|
||||
{
|
||||
public:
|
||||
// Full wallet callbacks
|
||||
virtual void on_new_block(uint64_t height, const cryptonote::block& block) {}
|
||||
virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {}
|
||||
virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {}
|
||||
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {}
|
||||
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {}
|
||||
// Light wallet callbacks
|
||||
virtual void on_lw_new_block(uint64_t height) {}
|
||||
virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
||||
virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
||||
virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
||||
// Common callbacks
|
||||
virtual void on_pool_tx_removed(const crypto::hash &txid) {}
|
||||
virtual ~i_wallet2_callback() {}
|
||||
};
|
||||
|
||||
|
@ -165,7 +173,7 @@ namespace tools
|
|||
|
||||
static bool verify_password(const std::string& keys_file_name, const std::string& password, bool watch_only);
|
||||
|
||||
wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {}
|
||||
wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {}
|
||||
|
||||
struct tx_scan_info_t
|
||||
{
|
||||
|
@ -229,6 +237,13 @@ namespace tools
|
|||
cryptonote::subaddress_index m_subaddr_index;
|
||||
};
|
||||
|
||||
struct address_tx : payment_details
|
||||
{
|
||||
bool m_coinbase;
|
||||
bool m_mempool;
|
||||
bool m_incoming;
|
||||
};
|
||||
|
||||
struct unconfirmed_transfer_details
|
||||
{
|
||||
cryptonote::transaction_prefix m_tx;
|
||||
|
@ -410,7 +425,7 @@ namespace tools
|
|||
// the minimum block size.
|
||||
bool deinit();
|
||||
bool init(std::string daemon_address = "http://localhost:8080",
|
||||
boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_size_limit = 0);
|
||||
boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_size_limit = 0, bool ssl = false);
|
||||
|
||||
void stop() { m_run.store(false, std::memory_order_relaxed); }
|
||||
|
||||
|
@ -422,6 +437,15 @@ namespace tools
|
|||
*/
|
||||
bool is_deterministic() const;
|
||||
bool get_seed(std::string& electrum_words, const std::string &passphrase = std::string()) const;
|
||||
|
||||
/*!
|
||||
* \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned.
|
||||
*/
|
||||
bool light_wallet() const { return m_light_wallet; }
|
||||
void set_light_wallet(bool light_wallet) { m_light_wallet = light_wallet; }
|
||||
uint64_t get_light_wallet_scanned_block_height() const { return m_light_wallet_scanned_block_height; }
|
||||
uint64_t get_light_wallet_blockchain_height() const { return m_light_wallet_blockchain_height; }
|
||||
|
||||
/*!
|
||||
* \brief Gets the seed language
|
||||
*/
|
||||
|
@ -512,6 +536,7 @@ namespace tools
|
|||
void rescan_spent();
|
||||
void rescan_blockchain(bool refresh = true);
|
||||
bool is_transfer_unlocked(const transfer_details& td) const;
|
||||
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const;
|
||||
template <class t_archive>
|
||||
inline void serialize(t_archive &a, const unsigned int ver)
|
||||
{
|
||||
|
@ -695,6 +720,7 @@ namespace tools
|
|||
uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent);
|
||||
|
||||
void update_pool_state(bool refreshed = false);
|
||||
void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes);
|
||||
|
||||
std::string encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const;
|
||||
std::string encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated = true) const;
|
||||
|
@ -713,6 +739,24 @@ namespace tools
|
|||
uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1);
|
||||
uint64_t get_per_kb_fee();
|
||||
|
||||
// Light wallet specific functions
|
||||
// fetch unspent outs from lw node and store in m_transfers
|
||||
void light_wallet_get_unspent_outs();
|
||||
// fetch txs and store in m_payments
|
||||
void light_wallet_get_address_txs();
|
||||
// get_address_info
|
||||
bool light_wallet_get_address_info(cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response &response);
|
||||
// Login. new_address is true if address hasn't been used on lw node before.
|
||||
bool light_wallet_login(bool &new_address);
|
||||
// Send an import request to lw node. returns info about import fee, address and payment_id
|
||||
bool light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response);
|
||||
// get random outputs from light wallet server
|
||||
void light_wallet_get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count);
|
||||
// Parse rct string
|
||||
bool light_wallet_parse_rct_str(const std::string& rct_string, const crypto::public_key& tx_pub_key, uint64_t internal_output_index, rct::key& decrypted_mask, rct::key& rct_commit, bool decrypt) const;
|
||||
// check if key image is ours
|
||||
bool light_wallet_key_image_is_ours(const crypto::key_image& key_image, const crypto::public_key& tx_public_key, uint64_t out_index);
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Stores wallet information to wallet file.
|
||||
|
@ -759,6 +803,8 @@ namespace tools
|
|||
void set_spent(size_t idx, uint64_t height);
|
||||
void set_unspent(size_t idx);
|
||||
void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count);
|
||||
bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const;
|
||||
bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki);
|
||||
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
|
||||
bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
|
||||
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
|
||||
|
@ -820,6 +866,20 @@ namespace tools
|
|||
bool m_is_initialized;
|
||||
NodeRPCProxy m_node_rpc_proxy;
|
||||
std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
|
||||
|
||||
// Light wallet
|
||||
bool m_light_wallet; /* sends view key to daemon for scanning */
|
||||
uint64_t m_light_wallet_scanned_block_height;
|
||||
uint64_t m_light_wallet_blockchain_height;
|
||||
uint64_t m_light_wallet_per_kb_fee = FEE_PER_KB;
|
||||
bool m_light_wallet_connected;
|
||||
uint64_t m_light_wallet_balance;
|
||||
uint64_t m_light_wallet_unlocked_balance;
|
||||
// Light wallet info needed to populate m_payment requires 2 separate api calls (get_address_txs and get_unspent_outs)
|
||||
// We save the info from the first call in m_light_wallet_address_txs for easier lookup.
|
||||
std::unordered_map<crypto::hash, address_tx> m_light_wallet_address_txs;
|
||||
// store calculated key image for faster lookup
|
||||
std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache;
|
||||
};
|
||||
}
|
||||
BOOST_CLASS_VERSION(tools::wallet2, 20)
|
||||
|
|
|
@ -427,9 +427,12 @@ struct Wallet
|
|||
*
|
||||
* \param daemon_address - daemon address in "hostname:port" format
|
||||
* \param upper_transaction_size_limit
|
||||
* \param daemon_username
|
||||
* \param daemon_password
|
||||
* \param lightWallet - start wallet in light mode, connect to a openmonero compatible server.
|
||||
* \return - true on success
|
||||
*/
|
||||
virtual bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username = "", const std::string &daemon_password = "") = 0;
|
||||
virtual bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, const std::string &daemon_username = "", const std::string &daemon_password = "", bool use_ssl = false, bool lightWallet = false) = 0;
|
||||
|
||||
/*!
|
||||
* \brief createWatchOnly - Creates a watch only wallet
|
||||
|
@ -722,6 +725,12 @@ struct Wallet
|
|||
* \return true on success
|
||||
*/
|
||||
virtual bool rescanSpent() = 0;
|
||||
|
||||
//! Light wallet authenticate and login
|
||||
virtual bool lightWalletLogin(bool &isNewWallet) const = 0;
|
||||
|
||||
//! Initiates a light wallet import wallet request
|
||||
virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue