parent
2c3f0dc7bd
commit
1ba894aed3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1,587 @@
|
||||
//
|
||||
// emscr_async_bridge_index.cpp
|
||||
// Copyright (c) 2014-2018, 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 <future>
|
||||
#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
|
||||
struct _Send_ArgsContainer
|
||||
{
|
||||
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;
|
||||
//
|
||||
// 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;
|
||||
//
|
||||
// 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;
|
||||
};
|
||||
struct Send_HeapValsContainer
|
||||
{
|
||||
string task_id;
|
||||
_Send_ArgsContainer args;
|
||||
};
|
||||
//
|
||||
std::unordered_map<string, Send_HeapValsContainer *> _heap_vals_ptrs_by_task_id;
|
||||
Send_HeapValsContainer *_heap_vals_ptr_for(const string &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: no waiting heap vals container ptr found");
|
||||
return NULL;
|
||||
}
|
||||
return _heap_vals_ptrs_by_task_id[task_id];
|
||||
}
|
||||
void _delete_and_remove_heap_vals_ptr_for(const string &task_id)
|
||||
{
|
||||
auto ptr = _heap_vals_ptr_for(task_id);
|
||||
if (ptr != NULL) {
|
||||
delete ptr;
|
||||
_heap_vals_ptrs_by_task_id.erase(task_id);
|
||||
} 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
|
||||
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 task_id = json_root.get<string>("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_HeapValsContainer *ptrTo_heapValsContainer = new Send_HeapValsContainer{
|
||||
task_id,
|
||||
_Send_ArgsContainer{
|
||||
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
|
||||
//
|
||||
// cached
|
||||
sec_viewKey,
|
||||
sec_spendKey,
|
||||
pub_spendKey,
|
||||
//
|
||||
// re-entry param initialization/prep
|
||||
none, // passedIn_attemptAt_fee
|
||||
0, // (re-)construction attempt,
|
||||
//
|
||||
// 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_heapValsContainer == NULL) {
|
||||
send_app_handler__error_msg(task_id, "Out of memory (heap vals container)");
|
||||
return;
|
||||
}
|
||||
_heap_vals_ptrs_by_task_id[task_id] = ptrTo_heapValsContainer; // 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 task_id = json_root.get<string>("task_id"); // required
|
||||
// TODO throw if task_id not found in json
|
||||
// TODO: if args_string actually contains a server error, call error fn with it
|
||||
// TODO: factor that into a shared function
|
||||
//
|
||||
Send_HeapValsContainer *ptrTo_heapValsContainer = _heap_vals_ptr_for(task_id);
|
||||
if (ptrTo_heapValsContainer == NULL) { // an error will have been returned already - just bail.
|
||||
return;
|
||||
}
|
||||
_Send_ArgsContainer &args = ptrTo_heapValsContainer->args; // TODO: does it matter making this a ref? / is this correct?
|
||||
//
|
||||
auto parsed_res = new__parsed_res__get_unspent_outs(
|
||||
json_root.get_child("res"),
|
||||
args.sec_viewKey,
|
||||
args.sec_spendKey,
|
||||
args.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(args.unspent_outs.size() != 0, error::wallet_internal_error, "Expected 0 args.unspent_outs in cb I");
|
||||
BOOST_FOREACH(SpendableOutput &out, *(parsed_res.unspent_outs)) {
|
||||
args.unspent_outs.push_back(out); // move structs from stack's vector to heap's vector
|
||||
}
|
||||
args.fee_per_b = *(parsed_res.per_byte_fee);
|
||||
_reenterable_construct_and_send_tx(task_id);
|
||||
}
|
||||
void emscr_async_bridge::_reenterable_construct_and_send_tx(const string &task_id)
|
||||
{
|
||||
Send_HeapValsContainer *ptrTo_heapValsContainer = _heap_vals_ptr_for(task_id);
|
||||
if (ptrTo_heapValsContainer == NULL) { // an error will have been returned already - just bail.
|
||||
return;
|
||||
}
|
||||
_Send_ArgsContainer &args = ptrTo_heapValsContainer->args; // TODO: does it matter making this a ref? / is this correct?
|
||||
//
|
||||
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,
|
||||
//
|
||||
args.payment_id_string,
|
||||
args.sending_amount,
|
||||
args.is_sweeping,
|
||||
args.simple_priority,
|
||||
[] (uint8_t version, int64_t early_blocks) -> bool
|
||||
{
|
||||
return lightwallet_hardcoded__use_fork_rules(version, early_blocks);
|
||||
},
|
||||
args.unspent_outs,
|
||||
args.fee_per_b,
|
||||
//
|
||||
args.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;
|
||||
}
|
||||
// now store step1_retVals for step2
|
||||
args.step1_retVals__final_total_wo_fee = step1_retVals.final_total_wo_fee;
|
||||
args.step1_retVals__using_fee = step1_retVals.using_fee;
|
||||
args.step1_retVals__change_amount = step1_retVals.change_amount;
|
||||
args.step1_retVals__mixin = step1_retVals.mixin;
|
||||
THROW_WALLET_EXCEPTION_IF(args.step1_retVals__using_outs.size() != 0, error::wallet_internal_error, "Expected 0 using_outs");
|
||||
BOOST_FOREACH(SpendableOutput &out, step1_retVals.using_outs) {
|
||||
args.step1_retVals__using_outs.push_back(out); // move structs from stack's vector to heap's vector
|
||||
}
|
||||
//
|
||||
send_app_handler__status_update(task_id, fetchingDecoyOutputs);
|
||||
//
|
||||
auto req_params = new__req_params__get_random_outs(step1_retVals.using_outs);
|
||||
boost::property_tree::ptree req_params_root;
|
||||
boost::property_tree::ptree amounts_ptree;
|
||||
BOOST_FOREACH(const string &amount_string, req_params.amounts)
|
||||
{
|
||||
req_params_root.add("amounts", amount_string);
|
||||
}
|
||||
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 task_id = json_root.get<string>("task_id"); // required
|
||||
// TODO throw if task_id not found in json
|
||||
// TODO: if args_string actually contains a server error, call error fn with it
|
||||
// TODO: factor that into a shared function
|
||||
//
|
||||
Send_HeapValsContainer *ptrTo_heapValsContainer = _heap_vals_ptr_for(task_id);
|
||||
if (ptrTo_heapValsContainer == NULL) { // an error will have been returned already - just bail.
|
||||
return;
|
||||
}
|
||||
_Send_ArgsContainer &args = ptrTo_heapValsContainer->args;
|
||||
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(args.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,
|
||||
//
|
||||
args.from_address_string,
|
||||
args.sec_viewKey_string,
|
||||
args.sec_spendKey_string,
|
||||
args.to_address_string,
|
||||
args.payment_id_string,
|
||||
*(args.step1_retVals__final_total_wo_fee),
|
||||
*(args.step1_retVals__change_amount),
|
||||
*(args.step1_retVals__using_fee),
|
||||
args.simple_priority,
|
||||
args.step1_retVals__using_outs,
|
||||
args.fee_per_b,
|
||||
*(parsed_res.mix_outs),
|
||||
[] (uint8_t version, int64_t early_blocks) -> bool
|
||||
{
|
||||
return lightwallet_hardcoded__use_fork_rules(version, early_blocks);
|
||||
},
|
||||
args.unlock_time,
|
||||
args.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 (args.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;
|
||||
}
|
||||
args.constructionAttempt += 1; // increment for re-entry
|
||||
args.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)
|
||||
args.step1_retVals__final_total_wo_fee = none;
|
||||
args.step1_retVals__change_amount = none;
|
||||
args.step1_retVals__using_fee = none;
|
||||
args.step1_retVals__mixin = none;
|
||||
args.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:
|
||||
args.step2_retVals__signed_serialized_tx_string = none;
|
||||
args.step2_retVals__tx_hash_string = none;
|
||||
args.step2_retVals__tx_key_string = none;
|
||||
args.step2_retVals__tx_pub_key_string = none;
|
||||
//
|
||||
_reenterable_construct_and_send_tx(task_id);
|
||||
return;
|
||||
}
|
||||
// move step2 vals onto heap for later:
|
||||
args.step2_retVals__signed_serialized_tx_string = *(step2_retVals.signed_serialized_tx_string);
|
||||
args.step2_retVals__tx_hash_string = *(step2_retVals.tx_hash_string);
|
||||
args.step2_retVals__tx_key_string = *(step2_retVals.tx_key_string);
|
||||
args.step2_retVals__tx_pub_key_string = *(step2_retVals.tx_pub_key_string);
|
||||
//
|
||||
send_app_handler__status_update(task_id, submittingTransaction);
|
||||
//
|
||||
auto req_params = LightwalletAPI_Req_SubmitRawTx{
|
||||
args.from_address_string,
|
||||
args.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 task_id = json_root.get<string>("task_id"); // required
|
||||
// TODO throw if task_id not found in json
|
||||
// TODO: if args_string actually contains a server error, call error fn with it
|
||||
// TODO: factor that into a shared function
|
||||
//
|
||||
Send_HeapValsContainer *ptrTo_heapValsContainer = _heap_vals_ptr_for(task_id);
|
||||
if (ptrTo_heapValsContainer == NULL) { // an error will have been returned already - just bail.
|
||||
return;
|
||||
}
|
||||
_Send_ArgsContainer &args = ptrTo_heapValsContainer->args;
|
||||
//
|
||||
// not actually expecting anything in a success response, so no need to parse it
|
||||
//
|
||||
SendFunds_Success_RetVals success_retVals;
|
||||
success_retVals.used_fee = *(args.step1_retVals__using_fee); // NOTE: not the same thing as step2_retVals.fee_actually_needed
|
||||
success_retVals.total_sent = *(args.step1_retVals__final_total_wo_fee) + *(args.step1_retVals__using_fee);
|
||||
success_retVals.mixin = *(args.step1_retVals__mixin);
|
||||
{
|
||||
optional<string> returning__payment_id = args.payment_id_string; // separated from submit_raw_tx_fn so that it can be captured w/o capturing all of args (FIXME: does this matter?)
|
||||
if (returning__payment_id == none) {
|
||||
auto decoded = monero::address_utils::decodedAddress(args.to_address_string, args.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 = *(args.step2_retVals__signed_serialized_tx_string);
|
||||
success_retVals.tx_hash_string = *(args.step2_retVals__tx_hash_string);
|
||||
success_retVals.tx_key_string = *(args.step2_retVals__tx_key_string);
|
||||
success_retVals.tx_pub_key_string = *(args.step2_retVals__tx_pub_key_string);
|
||||
//
|
||||
send_app_handler__success(task_id, success_retVals);
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
//
|
||||
// emscr_async_bridge_index.hpp
|
||||
// Copyright (c) 2014-2018, 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.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef emscr_async_bridge_index_hpp
|
||||
#define emscr_async_bridge_index_hpp
|
||||
//
|
||||
#include <string>
|
||||
#include <boost/optional.hpp>
|
||||
#include "cryptonote_config.h"
|
||||
#include "monero_send_routine.hpp"
|
||||
//
|
||||
namespace emscr_async_bridge
|
||||
{
|
||||
using namespace std;
|
||||
using namespace boost;
|
||||
using namespace cryptonote;
|
||||
using namespace monero_send_routine;
|
||||
//
|
||||
// Bridging Functions - these take and return JSON strings plus unique ids that are used internally for matching up calls from C++ to JS over emscripten, with the cbs that will fulfill the promises and futures being used to coordinate e.g. async routines like send_funds.
|
||||
//
|
||||
// To use these functions, the appropriate emscripten-side JS fn handlers must exist, which must be hooked up to perform the e.g. networking or transport requests they are specced to perform, then upon the async completion of those requests, call the appropate "cb_I+"-named function to allow the internal evaluation of the routine entrypoint to complete.
|
||||
//
|
||||
// Public interface:
|
||||
void send_funds(const string &args_string);
|
||||
void send_cb_I__got_unspent_outs(const string &args_string);
|
||||
void send_cb_II__got_random_outs(const string &args_string);
|
||||
void send_cb_III__submitted_tx(const string &args_string);
|
||||
//
|
||||
// Internal
|
||||
void send_app_handler__error_json(const string &task_id, const string &ret_json_string);
|
||||
void send_app_handler__error_msg(const string &task_id, const string &err_msg);
|
||||
void 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 = 0, // (effectively but not the same as spendable_balance)
|
||||
uint64_t required_balance = 0 // for display / information purposes on errCode=needMoreMoneyThanFound during step1
|
||||
);
|
||||
//
|
||||
void _reenterable_construct_and_send_tx(const string &task_id);
|
||||
}
|
||||
|
||||
#endif /* serial_bridge_index_hpp */
|
@ -1 +1 @@
|
||||
Subproject commit cf11304a9d123706273def9907468b677438be04
|
||||
Subproject commit 9920e09d5af633a21e18dfd1be72660d98ec7ca4
|
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue