You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
623 lines
28 KiB
623 lines
28 KiB
//
|
|
// emscr_async_bridge_index.cpp
|
|
// Copyright (c) 2014-2019, MyMonero.com
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without modification, are
|
|
// permitted provided that the following conditions are met:
|
|
//
|
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
// conditions and the following disclaimer.
|
|
//
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
// of conditions and the following disclaimer in the documentation and/or other
|
|
// materials provided with the distribution.
|
|
//
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
// used to endorse or promote products derived from this software without specific
|
|
// prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
//
|
|
//
|
|
#include "emscr_async_send_bridge.hpp"
|
|
//
|
|
#include <boost/property_tree/ptree.hpp>
|
|
#include <boost/property_tree/json_parser.hpp>
|
|
#include <boost/foreach.hpp>
|
|
#include <emscripten.h>
|
|
#include <unordered_map>
|
|
#include <memory>
|
|
//
|
|
#include "string_tools.h"
|
|
//
|
|
#include "monero_fork_rules.hpp"
|
|
#include "monero_send_routine.hpp"
|
|
#include "serial_bridge_utils.hpp"
|
|
#include "monero_address_utils.hpp"
|
|
//
|
|
#include "wallet_errors.h"
|
|
using namespace tools;
|
|
//
|
|
//
|
|
using namespace std;
|
|
using namespace boost;
|
|
using namespace monero_send_routine;
|
|
//
|
|
using namespace serial_bridge_utils;
|
|
using namespace emscr_async_bridge;
|
|
//
|
|
// Runtime - Memory container - To ensure values stick around until end of async process
|
|
enum _Send_Task_ValsState
|
|
{
|
|
WAIT_FOR_STEP1,
|
|
WAIT_FOR_STEP2,
|
|
WAIT_FOR_FINISH
|
|
};
|
|
struct Send_Task_AsyncContext
|
|
{
|
|
string task_id;
|
|
//
|
|
string from_address_string;
|
|
string sec_viewKey_string;
|
|
string sec_spendKey_string;
|
|
string to_address_string;
|
|
optional<string> payment_id_string;
|
|
uint64_t sending_amount;
|
|
bool is_sweeping;
|
|
uint32_t simple_priority;
|
|
uint64_t unlock_time;
|
|
cryptonote::network_type nettype;
|
|
//
|
|
vector<SpendableOutput> unspent_outs;
|
|
uint64_t fee_per_b;
|
|
uint64_t fee_mask;
|
|
monero_fork_rules::use_fork_rules_fn_type use_fork_rules;
|
|
//
|
|
// cached
|
|
secret_key sec_viewKey;
|
|
secret_key sec_spendKey;
|
|
public_key pub_spendKey;
|
|
//
|
|
// re-entry params
|
|
optional<uint64_t> passedIn_attemptAt_fee;
|
|
size_t constructionAttempt;
|
|
//
|
|
_Send_Task_ValsState valsState;
|
|
//
|
|
// step1_retVals held for step2 - making them optl for increased safety
|
|
optional<uint64_t> step1_retVals__final_total_wo_fee;
|
|
optional<uint64_t> step1_retVals__change_amount;
|
|
optional<uint64_t> step1_retVals__using_fee;
|
|
optional<uint32_t> step1_retVals__mixin;
|
|
vector<SpendableOutput> step1_retVals__using_outs;
|
|
//
|
|
// step2_retVals held for submit tx - optl for increased safety
|
|
optional<string> step2_retVals__signed_serialized_tx_string;
|
|
optional<string> step2_retVals__tx_hash_string;
|
|
optional<string> step2_retVals__tx_key_string;
|
|
optional<string> step2_retVals__tx_pub_key_string;
|
|
};
|
|
//
|
|
typedef std::unordered_map<string, Send_Task_AsyncContext *> context_map;
|
|
static context_map _heap_vals_ptrs_by_task_id;
|
|
static context_map::iterator _heap_vals_iter_for(const string &task_id)
|
|
{
|
|
auto found = _heap_vals_ptrs_by_task_id.find(task_id);
|
|
if (found == _heap_vals_ptrs_by_task_id.end()) {
|
|
send_app_handler__error_msg(task_id, "Code fault: no waiting heap vals container ptr found");
|
|
}
|
|
return found;
|
|
}
|
|
static Send_Task_AsyncContext *_heap_vals_ptr_for(const string &task_id)
|
|
{
|
|
auto iter = _heap_vals_iter_for(task_id);
|
|
//
|
|
return iter != _heap_vals_ptrs_by_task_id.end() ? iter->second : nullptr;
|
|
}
|
|
void _delete_and_remove_heap_vals_ptr_for(const string &task_id)
|
|
{
|
|
auto iter = _heap_vals_iter_for(task_id);
|
|
if (iter != _heap_vals_ptrs_by_task_id.end()) {
|
|
delete iter->second;
|
|
_heap_vals_ptrs_by_task_id.erase(iter);
|
|
} else {
|
|
THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Expected _heap_vals_ptr_for(task_id)");
|
|
}
|
|
}
|
|
//
|
|
// To-JS fn decls - Status updates and routine completions
|
|
static void send_app_handler__status_update(const string &task_id, SendFunds_ProcessStep code)
|
|
{
|
|
boost::property_tree::ptree root;
|
|
root.put("code", code); // not 64bit so sendable in JSON
|
|
root.put("msg", std::move(err_msg_from_err_code__send_funds_step(code)));
|
|
auto ret_json_string = ret_json_from_root(root);
|
|
//
|
|
EM_ASM_(
|
|
{
|
|
const JS__task_id = Module.UTF8ToString($0);
|
|
const JS__req_params_string = Module.UTF8ToString($1);
|
|
const JS__req_params = JSON.parse(JS__req_params_string);
|
|
Module.fromCpp__send_funds__status_update(JS__task_id, JS__req_params); // Module must implement this!
|
|
},
|
|
task_id.c_str(),
|
|
ret_json_string.c_str()
|
|
);
|
|
}
|
|
void emscr_async_bridge::send_app_handler__error_json(const string &task_id, const string &ret_json_string)
|
|
{
|
|
EM_ASM_(
|
|
{
|
|
const JS__task_id = Module.UTF8ToString($0);
|
|
const JS__req_params_string = Module.UTF8ToString($1);
|
|
const JS__req_params = JSON.parse(JS__req_params_string);
|
|
Module.fromCpp__send_funds__error(JS__task_id, JS__req_params); // Module must implement this!
|
|
},
|
|
task_id.c_str(),
|
|
ret_json_string.c_str()
|
|
);
|
|
_delete_and_remove_heap_vals_ptr_for(task_id); // having finished
|
|
}
|
|
void emscr_async_bridge::send_app_handler__error_msg(const string &task_id, const string &err_msg)
|
|
{
|
|
send_app_handler__error_json(task_id, error_ret_json_from_message(std::move(err_msg)));
|
|
}
|
|
void emscr_async_bridge::send_app_handler__error_code(
|
|
const string &task_id,
|
|
CreateTransactionErrorCode err_code,
|
|
// for display / information purposes on errCode=needMoreMoneyThanFound during step1:
|
|
uint64_t spendable_balance, // (effectively but not the same as spendable_balance)
|
|
uint64_t required_balance // for display / information purposes on errCode=needMoreMoneyThanFound during step1
|
|
) {
|
|
boost::property_tree::ptree root;
|
|
root.put(ret_json_key__any__err_code(), err_code);
|
|
root.put(ret_json_key__any__err_msg(), err_msg_from_err_code__create_transaction(err_code));
|
|
// The following will be set if errCode==needMoreMoneyThanFound
|
|
root.put(ret_json_key__send__spendable_balance(), std::move(RetVals_Transforms::str_from(spendable_balance)));
|
|
root.put(ret_json_key__send__required_balance(), std::move(RetVals_Transforms::str_from(required_balance)));
|
|
//
|
|
send_app_handler__error_json(task_id, ret_json_from_root(root));
|
|
}
|
|
//
|
|
void send_app_handler__success(const string &task_id, const SendFunds_Success_RetVals &success_retVals)
|
|
{
|
|
boost::property_tree::ptree root;
|
|
root.put(ret_json_key__send__used_fee(), std::move(RetVals_Transforms::str_from(success_retVals.used_fee)));
|
|
root.put(ret_json_key__send__total_sent(), std::move(RetVals_Transforms::str_from(success_retVals.total_sent)));
|
|
root.put(ret_json_key__send__mixin(), success_retVals.mixin); // this is a uint32 so it can be sent in JSON
|
|
if (success_retVals.final_payment_id) {
|
|
root.put(ret_json_key__send__final_payment_id(), std::move(*(success_retVals.final_payment_id)));
|
|
}
|
|
root.put(ret_json_key__send__serialized_signed_tx(), std::move(success_retVals.signed_serialized_tx_string));
|
|
root.put(ret_json_key__send__tx_hash(), std::move(success_retVals.tx_hash_string));
|
|
root.put(ret_json_key__send__tx_key(), std::move(success_retVals.tx_key_string));
|
|
root.put(ret_json_key__send__tx_pub_key(), std::move(success_retVals.tx_pub_key_string));
|
|
//
|
|
EM_ASM_(
|
|
{
|
|
const JS__task_id = Module.UTF8ToString($0);
|
|
const JS__req_params_string = Module.UTF8ToString($1);
|
|
const JS__req_params = JSON.parse(JS__req_params_string);
|
|
Module.fromCpp__send_funds__success(JS__task_id, JS__req_params); // Module must implement this!
|
|
},
|
|
task_id.c_str(),
|
|
ret_json_from_root(root).c_str()
|
|
);
|
|
_delete_and_remove_heap_vals_ptr_for(task_id); // having finished
|
|
}
|
|
//
|
|
// From-JS function decls
|
|
void emscr_async_bridge::send_funds(const string &args_string)
|
|
{
|
|
boost::property_tree::ptree json_root;
|
|
if (!parsed_json_root(args_string, json_root)) {
|
|
// it will already have thrown an exception .. but let's throw another one here (and not try to send an error response bc we dont have a task_id)
|
|
THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Invalid JSON");
|
|
// send_app_handler__error(error_ret_json_from_message("Invalid JSON"));
|
|
return;
|
|
}
|
|
auto optl__task_id = json_root.get_optional<string>("task_id");
|
|
THROW_WALLET_EXCEPTION_IF(optl__task_id == none, error::wallet_internal_error, "Code fault: expected task_id (send_funds)");
|
|
const string &task_id = *optl__task_id;
|
|
if (_heap_vals_ptrs_by_task_id.find(task_id) != _heap_vals_ptrs_by_task_id.end()) {
|
|
send_app_handler__error_msg(task_id, "Code fault: existing waiting heap vals container ptr found with that task id");
|
|
return;
|
|
}
|
|
auto from_address_string = json_root.get<string>("from_address_string");
|
|
auto sec_viewKey_string = json_root.get<string>("sec_viewKey_string");
|
|
auto sec_spendKey_string = json_root.get<string>("sec_spendKey_string");
|
|
auto pub_spendKey_string = json_root.get<string>("pub_spendKey_string");
|
|
//
|
|
uint64_t _raw_sending_amount = stoull(json_root.get<string>("sending_amount"));
|
|
auto is_sweeping = json_root.get<bool>("is_sweeping");
|
|
optional<string> unlock_time_string = json_root.get_optional<string>("unlock_time");
|
|
uint64_t unlock_time = 0;
|
|
if (unlock_time_string) {
|
|
unlock_time = stoull(*unlock_time_string);
|
|
}
|
|
auto nettype = nettype_from_string(json_root.get<string>("nettype_string"));
|
|
//
|
|
uint64_t sending_amount = is_sweeping ? 0 : _raw_sending_amount;
|
|
crypto::secret_key sec_viewKey{};
|
|
crypto::secret_key sec_spendKey{};
|
|
crypto::public_key pub_spendKey{};
|
|
{
|
|
bool r = false;
|
|
r = epee::string_tools::hex_to_pod(sec_viewKey_string, sec_viewKey);
|
|
if (!r) {
|
|
send_app_handler__error_msg(task_id, "Invalid secret view key");
|
|
return;
|
|
}
|
|
r = epee::string_tools::hex_to_pod(sec_spendKey_string, sec_spendKey);
|
|
if (!r) {
|
|
send_app_handler__error_msg(task_id, "Invalid sec spend key");
|
|
return;
|
|
}
|
|
r = epee::string_tools::hex_to_pod(pub_spendKey_string, pub_spendKey);
|
|
if (!r) {
|
|
send_app_handler__error_msg(task_id, "Invalid public spend key");
|
|
return;
|
|
}
|
|
}
|
|
vector<SpendableOutput> unspent_outs; // to be set after getting unspent outs
|
|
vector<SpendableOutput> using_outs; // to be set after step1
|
|
Send_Task_AsyncContext *ptrTo_taskAsyncContext = new Send_Task_AsyncContext{
|
|
task_id,
|
|
//
|
|
from_address_string,
|
|
sec_viewKey_string,
|
|
sec_spendKey_string,
|
|
json_root.get<string>("to_address_string"),
|
|
json_root.get_optional<string>("payment_id_string"),
|
|
sending_amount,
|
|
is_sweeping,
|
|
(uint32_t)stoul(json_root.get<string>("priority")),
|
|
unlock_time,
|
|
nettype,
|
|
//
|
|
unspent_outs, // this gets pushed to after getting unspent outs
|
|
0, // fee_per_b - this gets set after getting unspent outs
|
|
0, // fee_mask - this gets set after getting unspent outs
|
|
monero_fork_rules::make_use_fork_rules_fn(0), // this will get set again after getting unspent outs - though it's slightly unsafe to set it to 0 like this
|
|
//
|
|
// cached
|
|
sec_viewKey,
|
|
sec_spendKey,
|
|
pub_spendKey,
|
|
//
|
|
// re-entry param initialization/prep
|
|
none, // passedIn_attemptAt_fee
|
|
0, // (re-)construction attempt,
|
|
//
|
|
WAIT_FOR_STEP1,
|
|
//
|
|
// step1 vals init
|
|
none, // final_total_wo_fee
|
|
none, // change_amount
|
|
none, // using_fee
|
|
none, // mixin
|
|
using_outs,
|
|
//
|
|
// step2 vals init
|
|
none, // signed_serialized_tx_string
|
|
none, // tx_hash_string
|
|
none, // tx_key_string
|
|
none // tx_pub_key_string
|
|
};
|
|
// exception will be thrown if oom but JIC, since null ptrs are somehow legal in WASM
|
|
if (!ptrTo_taskAsyncContext) {
|
|
send_app_handler__error_msg(task_id, "Out of memory (heap vals container)");
|
|
return;
|
|
}
|
|
_heap_vals_ptrs_by_task_id[task_id] = ptrTo_taskAsyncContext; // store for later lookup
|
|
//
|
|
send_app_handler__status_update(task_id, fetchingLatestBalance);
|
|
//
|
|
auto req_params = new__req_params__get_unspent_outs(from_address_string, sec_viewKey_string);
|
|
boost::property_tree::ptree req_params_root;
|
|
req_params_root.put("address", req_params.address);
|
|
req_params_root.put("view_key", req_params.view_key);
|
|
req_params_root.put("amount", req_params.amount);
|
|
req_params_root.put("dust_threshold", req_params.dust_threshold);
|
|
req_params_root.put("use_dust", req_params.use_dust);
|
|
req_params_root.put("mixin", req_params.mixin);
|
|
stringstream req_params_ss;
|
|
boost::property_tree::write_json(req_params_ss, req_params_root, false/*pretty*/);
|
|
EM_ASM_(
|
|
{
|
|
const JS__task_id = Module.UTF8ToString($0);
|
|
const JS__req_params_string = Module.UTF8ToString($1);
|
|
const JS__req_params = JSON.parse(JS__req_params_string);
|
|
Module.fromCpp__send_funds__get_unspent_outs(JS__task_id, JS__req_params); // Module must implement this!
|
|
},
|
|
task_id.c_str(),
|
|
req_params_ss.str().c_str()
|
|
);
|
|
// now wait for JS to call returning bridged function
|
|
}
|
|
//
|
|
void emscr_async_bridge::send_cb_I__got_unspent_outs(const string &args_string)
|
|
{
|
|
boost::property_tree::ptree json_root;
|
|
if (!parsed_json_root(args_string, json_root)) {
|
|
// it will already have thrown an exception .. but let's throw another one here (and not try to send an error response bc we dont have a task_id)
|
|
THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Invalid JSON");
|
|
// send_app_handler__error(error_ret_json_from_message("Invalid JSON"));
|
|
return;
|
|
}
|
|
auto optl__task_id = json_root.get_optional<string>("task_id");
|
|
THROW_WALLET_EXCEPTION_IF(optl__task_id == none, error::wallet_internal_error, "Code fault: expected task_id (send_funds)");
|
|
const string &task_id = *optl__task_id;
|
|
//
|
|
auto optl__err_msg = json_root.get_optional<string>("err_msg");
|
|
if (optl__err_msg != none && (*optl__err_msg).size() > 0) { // if args_string actually contains a server error, call error fn with it - this must be done so that the heap alloc'd vals container can be freed
|
|
stringstream err_msg_ss;
|
|
err_msg_ss << "An error occurred while getting your latest balance: " << *(optl__err_msg);
|
|
send_app_handler__error_msg(task_id, err_msg_ss.str());
|
|
return;
|
|
}
|
|
Send_Task_AsyncContext *ptrTo_taskAsyncContext = _heap_vals_ptr_for(task_id);
|
|
if (!ptrTo_taskAsyncContext) { // an error will have been returned already - just bail.
|
|
return;
|
|
}
|
|
//
|
|
auto parsed_res = new__parsed_res__get_unspent_outs(
|
|
json_root.get_child("res"),
|
|
ptrTo_taskAsyncContext->sec_viewKey,
|
|
ptrTo_taskAsyncContext->sec_spendKey,
|
|
ptrTo_taskAsyncContext->pub_spendKey
|
|
);
|
|
if (parsed_res.err_msg != none) {
|
|
send_app_handler__error_msg(task_id, std::move(*(parsed_res.err_msg)));
|
|
return;
|
|
}
|
|
THROW_WALLET_EXCEPTION_IF(ptrTo_taskAsyncContext->unspent_outs.size() != 0, error::wallet_internal_error, "Expected 0 ptrTo_taskAsyncContext->unspent_outs in cb I");
|
|
ptrTo_taskAsyncContext->unspent_outs = std::move(*(parsed_res.unspent_outs)); // move structs from stack's vector to heap's vector
|
|
ptrTo_taskAsyncContext->fee_per_b = *(parsed_res.per_byte_fee);
|
|
ptrTo_taskAsyncContext->fee_mask = *(parsed_res.fee_mask);
|
|
ptrTo_taskAsyncContext->use_fork_rules = monero_fork_rules::make_use_fork_rules_fn(parsed_res.fork_version);
|
|
_reenterable_construct_and_send_tx(task_id);
|
|
}
|
|
void emscr_async_bridge::_reenterable_construct_and_send_tx(const string &task_id)
|
|
{
|
|
Send_Task_AsyncContext *ptrTo_taskAsyncContext = _heap_vals_ptr_for(task_id);
|
|
if (!ptrTo_taskAsyncContext) { // an error will have been returned already - just bail.
|
|
return;
|
|
}
|
|
send_app_handler__status_update(task_id, calculatingFee);
|
|
//
|
|
Send_Step1_RetVals step1_retVals;
|
|
monero_transfer_utils::send_step1__prepare_params_for_get_decoys(
|
|
step1_retVals,
|
|
//
|
|
ptrTo_taskAsyncContext->payment_id_string,
|
|
ptrTo_taskAsyncContext->sending_amount,
|
|
ptrTo_taskAsyncContext->is_sweeping,
|
|
ptrTo_taskAsyncContext->simple_priority,
|
|
ptrTo_taskAsyncContext->use_fork_rules,
|
|
ptrTo_taskAsyncContext->unspent_outs,
|
|
ptrTo_taskAsyncContext->fee_per_b,
|
|
ptrTo_taskAsyncContext->fee_mask,
|
|
//
|
|
ptrTo_taskAsyncContext->passedIn_attemptAt_fee // use this for passing step2 "must-reconstruct" return values back in, i.e. re-entry; when none, defaults to attempt at network min
|
|
// ^- and this will be 'none' as initial value
|
|
);
|
|
if (step1_retVals.errCode != noError) {
|
|
send_app_handler__error_code(task_id, step1_retVals.errCode, step1_retVals.spendable_balance, step1_retVals.required_balance);
|
|
return;
|
|
}
|
|
THROW_WALLET_EXCEPTION_IF(ptrTo_taskAsyncContext->valsState != WAIT_FOR_STEP1, error::wallet_internal_error, "Expected valsState of WAIT_FOR_STEP1"); // just for addtl safety
|
|
// now store step1_retVals for step2
|
|
ptrTo_taskAsyncContext->step1_retVals__final_total_wo_fee = step1_retVals.final_total_wo_fee;
|
|
ptrTo_taskAsyncContext->step1_retVals__using_fee = step1_retVals.using_fee;
|
|
ptrTo_taskAsyncContext->step1_retVals__change_amount = step1_retVals.change_amount;
|
|
ptrTo_taskAsyncContext->step1_retVals__mixin = step1_retVals.mixin;
|
|
THROW_WALLET_EXCEPTION_IF(ptrTo_taskAsyncContext->step1_retVals__using_outs.size() != 0, error::wallet_internal_error, "Expected 0 using_outs");
|
|
ptrTo_taskAsyncContext->step1_retVals__using_outs = std::move(step1_retVals.using_outs); // move structs from stack's vector to heap's vector
|
|
ptrTo_taskAsyncContext->valsState = WAIT_FOR_STEP2;
|
|
//
|
|
send_app_handler__status_update(task_id, fetchingDecoyOutputs);
|
|
//
|
|
auto req_params = new__req_params__get_random_outs(ptrTo_taskAsyncContext->step1_retVals__using_outs); // use the one on the heap, since we've moved the one from step1_retVals
|
|
boost::property_tree::ptree req_params_root;
|
|
boost::property_tree::ptree amounts_ptree;
|
|
BOOST_FOREACH(const string &amount_string, req_params.amounts)
|
|
{
|
|
property_tree::ptree amount_child;
|
|
amount_child.put("", amount_string);
|
|
amounts_ptree.push_back(std::make_pair("", amount_child));
|
|
}
|
|
req_params_root.add_child("amounts", amounts_ptree);
|
|
req_params_root.put("count", req_params.count);
|
|
stringstream req_params_ss;
|
|
boost::property_tree::write_json(req_params_ss, req_params_root, false/*pretty*/);
|
|
EM_ASM_(
|
|
{
|
|
const JS__task_id = Module.UTF8ToString($0);
|
|
const JS__req_params_string = Module.UTF8ToString($1);
|
|
const JS__req_params = JSON.parse(JS__req_params_string);
|
|
Module.fromCpp__send_funds__get_random_outs(JS__task_id, JS__req_params); // Module must implement this!
|
|
},
|
|
task_id.c_str(),
|
|
req_params_ss.str().c_str()
|
|
);
|
|
}
|
|
void emscr_async_bridge::send_cb_II__got_random_outs(const string &args_string)
|
|
{
|
|
boost::property_tree::ptree json_root;
|
|
if (!parsed_json_root(args_string, json_root)) {
|
|
// it will already have thrown an exception .. but let's throw another one here (and not try to send an error response bc we dont have a task_id)
|
|
THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Invalid JSON");
|
|
// send_app_handler__error(error_ret_json_from_message("Invalid JSON"));
|
|
return;
|
|
}
|
|
auto optl__task_id = json_root.get_optional<string>("task_id");
|
|
THROW_WALLET_EXCEPTION_IF(optl__task_id == none, error::wallet_internal_error, "Code fault: expected task_id (send_funds)");
|
|
const string &task_id = *optl__task_id;
|
|
//
|
|
auto optl__err_msg = json_root.get_optional<string>("err_msg");
|
|
if (optl__err_msg != none && (*optl__err_msg).size() > 0) { // if args_string actually contains a server error, call error fn with it - this must be done so that the heap alloc'd vals container can be freed
|
|
stringstream err_msg_ss;
|
|
err_msg_ss << "An error occurred while getting decoy outputs: " << *(optl__err_msg);
|
|
send_app_handler__error_msg(task_id, err_msg_ss.str());
|
|
return;
|
|
}
|
|
Send_Task_AsyncContext *ptrTo_taskAsyncContext = _heap_vals_ptr_for(task_id);
|
|
if (!ptrTo_taskAsyncContext) { // an error will have been returned already - just bail.
|
|
return;
|
|
}
|
|
auto parsed_res = new__parsed_res__get_random_outs(json_root.get_child("res"));
|
|
if (parsed_res.err_msg != none) {
|
|
send_app_handler__error_msg(task_id, std::move(*(parsed_res.err_msg)));
|
|
return;
|
|
}
|
|
THROW_WALLET_EXCEPTION_IF(ptrTo_taskAsyncContext->step1_retVals__using_outs.size() == 0, error::wallet_internal_error, "Expected non-0 using_outs");
|
|
Send_Step2_RetVals step2_retVals;
|
|
monero_transfer_utils::send_step2__try_create_transaction(
|
|
step2_retVals,
|
|
//
|
|
ptrTo_taskAsyncContext->from_address_string,
|
|
ptrTo_taskAsyncContext->sec_viewKey_string,
|
|
ptrTo_taskAsyncContext->sec_spendKey_string,
|
|
ptrTo_taskAsyncContext->to_address_string,
|
|
ptrTo_taskAsyncContext->payment_id_string,
|
|
*(ptrTo_taskAsyncContext->step1_retVals__final_total_wo_fee),
|
|
*(ptrTo_taskAsyncContext->step1_retVals__change_amount),
|
|
*(ptrTo_taskAsyncContext->step1_retVals__using_fee),
|
|
ptrTo_taskAsyncContext->simple_priority,
|
|
ptrTo_taskAsyncContext->step1_retVals__using_outs,
|
|
ptrTo_taskAsyncContext->fee_per_b,
|
|
ptrTo_taskAsyncContext->fee_mask,
|
|
*(parsed_res.mix_outs),
|
|
ptrTo_taskAsyncContext->use_fork_rules,
|
|
ptrTo_taskAsyncContext->unlock_time,
|
|
ptrTo_taskAsyncContext->nettype
|
|
);
|
|
if (step2_retVals.errCode != noError) {
|
|
send_app_handler__error_code(task_id, step2_retVals.errCode);
|
|
return;
|
|
}
|
|
if (step2_retVals.tx_must_be_reconstructed) {
|
|
// this will update status back to .calculatingFee
|
|
if (ptrTo_taskAsyncContext->constructionAttempt > 15) { // just going to avoid an infinite loop here or particularly long stack
|
|
send_app_handler__error_msg(task_id, "Unable to construct a transaction with sufficient fee for unknown reason.");
|
|
return;
|
|
}
|
|
ptrTo_taskAsyncContext->valsState = WAIT_FOR_STEP1; // must reset this
|
|
//
|
|
ptrTo_taskAsyncContext->constructionAttempt += 1; // increment for re-entry
|
|
ptrTo_taskAsyncContext->passedIn_attemptAt_fee = step2_retVals.fee_actually_needed; // -> reconstruction attempt's step1's passedIn_attemptAt_fee
|
|
// reset step1 vals for correctness: (otherwise we end up, for example, with duplicate outs added)
|
|
ptrTo_taskAsyncContext->step1_retVals__final_total_wo_fee = none;
|
|
ptrTo_taskAsyncContext->step1_retVals__change_amount = none;
|
|
ptrTo_taskAsyncContext->step1_retVals__using_fee = none;
|
|
ptrTo_taskAsyncContext->step1_retVals__mixin = none;
|
|
ptrTo_taskAsyncContext->step1_retVals__using_outs.clear(); // critical!
|
|
// and let's reset step2 just for clarity/explicitness, though we don't expect them to have values yet:
|
|
ptrTo_taskAsyncContext->step2_retVals__signed_serialized_tx_string = none;
|
|
ptrTo_taskAsyncContext->step2_retVals__tx_hash_string = none;
|
|
ptrTo_taskAsyncContext->step2_retVals__tx_key_string = none;
|
|
ptrTo_taskAsyncContext->step2_retVals__tx_pub_key_string = none;
|
|
//
|
|
_reenterable_construct_and_send_tx(task_id);
|
|
return;
|
|
}
|
|
THROW_WALLET_EXCEPTION_IF(ptrTo_taskAsyncContext->valsState != WAIT_FOR_STEP2, error::wallet_internal_error, "Expected valsState of WAIT_FOR_STEP2"); // just for addtl safety
|
|
// move step2 vals onto heap for later:
|
|
ptrTo_taskAsyncContext->step2_retVals__signed_serialized_tx_string = *(step2_retVals.signed_serialized_tx_string);
|
|
ptrTo_taskAsyncContext->step2_retVals__tx_hash_string = *(step2_retVals.tx_hash_string);
|
|
ptrTo_taskAsyncContext->step2_retVals__tx_key_string = *(step2_retVals.tx_key_string);
|
|
ptrTo_taskAsyncContext->step2_retVals__tx_pub_key_string = *(step2_retVals.tx_pub_key_string);
|
|
//
|
|
ptrTo_taskAsyncContext->valsState = WAIT_FOR_FINISH;
|
|
//
|
|
send_app_handler__status_update(task_id, submittingTransaction);
|
|
//
|
|
auto req_params = LightwalletAPI_Req_SubmitRawTx{
|
|
ptrTo_taskAsyncContext->from_address_string,
|
|
ptrTo_taskAsyncContext->sec_viewKey_string,
|
|
*(step2_retVals.signed_serialized_tx_string)
|
|
};
|
|
boost::property_tree::ptree req_params_root;
|
|
boost::property_tree::ptree amounts_ptree;
|
|
req_params_root.put("address", std::move(req_params.address));
|
|
req_params_root.put("view_key", std::move(req_params.view_key));
|
|
req_params_root.put("tx", std::move(req_params.tx));
|
|
stringstream req_params_ss;
|
|
boost::property_tree::write_json(req_params_ss, req_params_root, false/*pretty*/);
|
|
auto req_params_string = req_params_ss.str();
|
|
EM_ASM_(
|
|
{
|
|
const JS__task_id = Module.UTF8ToString($0);
|
|
const JS__req_params_string = Module.UTF8ToString($1);
|
|
const JS__req_params = JSON.parse(JS__req_params_string);
|
|
Module.fromCpp__send_funds__submit_raw_tx(JS__task_id, JS__req_params); // Module must implement this!
|
|
},
|
|
task_id.c_str(),
|
|
req_params_ss.str().c_str()
|
|
);
|
|
}
|
|
void emscr_async_bridge::send_cb_III__submitted_tx(const string &args_string)
|
|
{
|
|
boost::property_tree::ptree json_root;
|
|
if (!parsed_json_root(args_string, json_root)) {
|
|
// it will already have thrown an exception .. but let's throw another one here (and not try to send an error response bc we dont have a task_id)
|
|
THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Invalid JSON");
|
|
// send_app_handler__error(error_ret_json_from_message("Invalid JSON"));
|
|
return;
|
|
}
|
|
auto optl__task_id = json_root.get_optional<string>("task_id");
|
|
THROW_WALLET_EXCEPTION_IF(optl__task_id == none, error::wallet_internal_error, "Code fault: expected task_id (send_funds)");
|
|
const string &task_id = *optl__task_id;
|
|
//
|
|
auto optl__err_msg = json_root.get_optional<string>("err_msg");
|
|
if (optl__err_msg != none && (*optl__err_msg).size() > 0) { // if args_string actually contains a server error, call error fn with it - this must be done so that the heap alloc'd vals container can be freed
|
|
stringstream err_msg_ss;
|
|
err_msg_ss << "An error occurred while getting submitting your transaction: " << *(optl__err_msg);
|
|
send_app_handler__error_msg(task_id, err_msg_ss.str());
|
|
return;
|
|
}
|
|
Send_Task_AsyncContext *ptrTo_taskAsyncContext = _heap_vals_ptr_for(task_id);
|
|
if (!ptrTo_taskAsyncContext) { // an error will have been returned already - just bail.
|
|
return;
|
|
}
|
|
THROW_WALLET_EXCEPTION_IF(ptrTo_taskAsyncContext->valsState != WAIT_FOR_FINISH, error::wallet_internal_error, "Expected valsState of WAIT_FOR_FINISH"); // just for addtl safety
|
|
// not actually expecting anything in a success response, so no need to parse it
|
|
//
|
|
SendFunds_Success_RetVals success_retVals;
|
|
success_retVals.used_fee = *(ptrTo_taskAsyncContext->step1_retVals__using_fee); // NOTE: not the same thing as step2_retVals.fee_actually_needed
|
|
success_retVals.total_sent = *(ptrTo_taskAsyncContext->step1_retVals__final_total_wo_fee) + *(ptrTo_taskAsyncContext->step1_retVals__using_fee);
|
|
success_retVals.mixin = *(ptrTo_taskAsyncContext->step1_retVals__mixin);
|
|
{
|
|
optional<string> returning__payment_id = ptrTo_taskAsyncContext->payment_id_string; // separated from submit_raw_tx_fn so that it can be captured w/o capturing all of args
|
|
if (returning__payment_id == none) {
|
|
auto decoded = monero::address_utils::decodedAddress(ptrTo_taskAsyncContext->to_address_string, ptrTo_taskAsyncContext->nettype);
|
|
if (decoded.did_error) { // would be very strange...
|
|
send_app_handler__error_msg(task_id, std::move(*(decoded.err_string)));
|
|
return;
|
|
}
|
|
if (decoded.paymentID_string != none) {
|
|
returning__payment_id = std::move(*(decoded.paymentID_string)); // just preserving this as an original return value - this can probably eventually be removed
|
|
}
|
|
}
|
|
success_retVals.final_payment_id = returning__payment_id;
|
|
}
|
|
success_retVals.signed_serialized_tx_string = *(ptrTo_taskAsyncContext->step2_retVals__signed_serialized_tx_string);
|
|
success_retVals.tx_hash_string = *(ptrTo_taskAsyncContext->step2_retVals__tx_hash_string);
|
|
success_retVals.tx_key_string = *(ptrTo_taskAsyncContext->step2_retVals__tx_key_string);
|
|
success_retVals.tx_pub_key_string = *(ptrTo_taskAsyncContext->step2_retVals__tx_pub_key_string);
|
|
//
|
|
send_app_handler__success(task_id, success_retVals);
|
|
}
|