diff --git a/src/emscr_async_send_bridge.cpp b/src/emscr_async_send_bridge.cpp new file mode 100644 index 0000000..226c02d --- /dev/null +++ b/src/emscr_async_send_bridge.cpp @@ -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 +#include +#include +#include +#include +#include +// +#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 payment_id_string; + uint64_t sending_amount; + bool is_sweeping; + uint32_t simple_priority; + uint64_t unlock_time; + cryptonote::network_type nettype; + // + vector 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 passedIn_attemptAt_fee; + size_t constructionAttempt; + // + _Send_Task_ValsState valsState; + // + // step1_retVals held for step2 - making them optl for increased safety + optional step1_retVals__final_total_wo_fee; + optional step1_retVals__change_amount; + optional step1_retVals__using_fee; + optional step1_retVals__mixin; + vector step1_retVals__using_outs; + // + // step2_retVals held for submit tx - optl for increased safety + optional step2_retVals__signed_serialized_tx_string; + optional step2_retVals__tx_hash_string; + optional step2_retVals__tx_key_string; + optional step2_retVals__tx_pub_key_string; +}; +// +typedef std::unordered_map 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("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("from_address_string"); + auto sec_viewKey_string = json_root.get("sec_viewKey_string"); + auto sec_spendKey_string = json_root.get("sec_spendKey_string"); + auto pub_spendKey_string = json_root.get("pub_spendKey_string"); + // + uint64_t _raw_sending_amount = stoull(json_root.get("sending_amount")); + auto is_sweeping = json_root.get("is_sweeping"); + optional unlock_time_string = json_root.get_optional("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("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 unspent_outs; // to be set after getting unspent outs + vector 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("to_address_string"), + json_root.get_optional("payment_id_string"), + sending_amount, + is_sweeping, + (uint32_t)stoul(json_root.get("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("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("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("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("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("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("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 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); +} diff --git a/src/emscr_async_send_bridge.hpp b/src/emscr_async_send_bridge.hpp new file mode 100644 index 0000000..eb727f9 --- /dev/null +++ b/src/emscr_async_send_bridge.hpp @@ -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 +#include +#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 */