moved emscr_async_send_bridge to this repo from core-js

pull/23/head
Paul Shapiro 5 years ago
parent 1820b0e399
commit cd8490837e

@ -0,0 +1,625 @@
//
// 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;
//
// 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
//
// 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);
_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,
[] (uint8_t version, int64_t early_blocks) -> bool
{
return lightwallet_hardcoded__use_fork_rules(version, early_blocks);
},
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),
[] (uint8_t version, int64_t early_blocks) -> bool
{
return lightwallet_hardcoded__use_fork_rules(version, early_blocks);
},
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);
}

@ -0,0 +1,72 @@
//
// emscr_async_bridge_index.hpp
// 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.
//
//
#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 */
Loading…
Cancel
Save