added emscr async send bridge, beginning to implement new async send bridge

pull/71/head
Paul Shapiro 6 years ago
parent 2c3f0dc7bd
commit 1ba894aed3

@ -35,6 +35,8 @@ set(
SRC_FILES
#
src/index.cpp
src/emscr_async_send_bridge.hpp
src/emscr_async_send_bridge.cpp
#
${MYMONERO_CORE_CPP_SRC}/monero_address_utils.hpp
${MYMONERO_CORE_CPP_SRC}/monero_address_utils.cpp
@ -52,6 +54,10 @@ set(
${MYMONERO_CORE_CPP_SRC}/monero_wallet_utils.cpp
${MYMONERO_CORE_CPP_SRC}/serial_bridge_index.hpp
${MYMONERO_CORE_CPP_SRC}/serial_bridge_index.cpp
${MYMONERO_CORE_CPP_SRC}/monero_send_routine.hpp
${MYMONERO_CORE_CPP_SRC}/monero_send_routine.cpp
${MYMONERO_CORE_CPP_SRC}/serial_bridge_utils.hpp
${MYMONERO_CORE_CPP_SRC}/serial_bridge_utils.cpp
${MYMONERO_CORE_CPP_SRC}/tools__ret_vals.hpp
${MYMONERO_CORE_CPP_SRC}/tools__ret_vals.cpp
#
@ -102,60 +108,11 @@ set(
set(boost_DIR ${CMAKE_SOURCE_DIR}/build/boost)
# add_library(boost_atomic STATIC IMPORTED)
# set_target_properties(
# boost_atomic PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_atomic.a
# )
# #
add_library(boost_chrono STATIC IMPORTED)
set_target_properties(
boost_chrono PROPERTIES IMPORTED_LOCATION
${boost_DIR}/lib/libboost_chrono.a
)
# #
# add_library(boost_date_time STATIC IMPORTED)
# set_target_properties(
# boost_date_time PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_date_time.a
# )
# #
# add_library(boost_filesystem STATIC IMPORTED)
# set_target_properties(
# boost_filesystem PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_filesystem.a
# )
# #
# add_library(boost_locale STATIC IMPORTED)
# set_target_properties(
# boost_locale PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_locale.a
# )
# #
# add_library(boost_program_options STATIC IMPORTED)
# set_target_properties(
# boost_program_options PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_program_options.a
# )
# #
# add_library(boost_regex STATIC IMPORTED)
# set_target_properties(
# boost_regex PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_regex.a
# )
# #
# add_library(boost_serialization STATIC IMPORTED)
# set_target_properties(
# boost_serialization PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_serialization.a
# )
# #
# add_library(boost_signals STATIC IMPORTED)
# set_target_properties(
# boost_signals PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_signals.a
# )
#
add_library(boost_system STATIC IMPORTED)
set_target_properties(
boost_system PROPERTIES IMPORTED_LOCATION
@ -168,19 +125,6 @@ set_target_properties(
${boost_DIR}/lib/libboost_thread.a
)
#
# add_library(boost_timer STATIC IMPORTED)
# set_target_properties(
# boost_timer PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_timer.a
# )
# #
# add_library(boost_wserialization STATIC IMPORTED)
# set_target_properties(
# boost_wserialization PROPERTIES IMPORTED_LOCATION
# ${boost_DIR}/lib/libboost_wserialization.a
# )
#
#
#
set (
EMCC_LINKER_FLAGS___BASE
@ -192,20 +136,22 @@ set (
-s 'EXPORT_NAME=\"MyMoneroCoreCpp\"' \
--llvm-lto 1 \
-s ERROR_ON_UNDEFINED_SYMBOLS=0 \
-s ASSERTIONS=1 \
-s ASSERTIONS=2 \
-s EXIT_RUNTIME=0 \
-s \"BINARYEN_TRAP_MODE='clamp'\" \
-s PRECISE_F32=1 \
-s DISABLE_EXCEPTION_CATCHING=1 \
-s DISABLE_EXCEPTION_CATCHING=0 \
-s EXCEPTION_DEBUG=1 \
-s DEMANGLE_SUPPORT=1 \
-s NO_DYNAMIC_EXECUTION=1 \
-s EXTRA_EXPORTED_RUNTIME_METHODS='[\"UTF8ToString\"]' \
"
# Disabling exception catching does not introduce silent failures
# Probably don't need PRECISE_F32 but wouldn't want to not use it
#
#
# -s SAFE_HEAP=1
# -s SAFE_HEAP=1 \
# -g \
)
set(
EMCC_LINKER_FLAGS__ASMJS

@ -329,6 +329,9 @@ class MyMoneroCoreBridge
if (spend_sec.length !== 64) {
return { err_msg: "Invalid spend_sec length" };
}
if (typeof output_index === 'undefined' || output_index === "" || output_index === null) {
return { err_msg: "Missing output_index" };
}
const args =
{
sec_viewKey_string: view_sec,
@ -363,13 +366,19 @@ class MyMoneroCoreBridge
}
return ret.retVal;
}
derive_public_key(derivation, out_index, pub)
derive_public_key(derivation, out_index, pub) // TODO: fix legacy interface here by moving out_index to last arg pos
{
if (typeof pub === 'undefined' || pub === "" || pub === null) {
return { err_msg: "Missing pub arg (arg pos idx 2)" };
}
if (typeof out_index === 'undefined' || out_index === "" || out_index === null) {
return { err_msg: "Missing out_index arg (arg pos idx 1)" };
}
const args =
{
pub: pub,
derivation: derivation,
out_index: out_index,
out_index: ""+out_index,
};
const args_str = JSON.stringify(args);
const ret_string = this.Module.derive_public_key(args_str);
@ -381,9 +390,12 @@ class MyMoneroCoreBridge
}
derive_subaddress_public_key(
output_key,
derivation,
derivation,
out_index
) {
if (typeof out_index === 'undefined' || out_index === "" || out_index === null) {
return { err_msg: "Missing out_index arg (arg pos idx 2)" };
}
const args =
{
output_key: output_key,

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

@ -207,6 +207,10 @@ function SendFunds( // TODO: migrate this to take a map of args
__trampolineFor_err_withStr(errStr);
return;
}
if (step1_retVals.err_msg) { // FIXME: is this necessary unless the version of monero_utils in use is a variant which doesn't support exceptions?
__trampolineFor_err_withErr(step1_retVals.err_msg)
return
}
//
// prep for step2
// first, grab RandomOuts, then enter step2

@ -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 */

@ -33,12 +33,14 @@
#include <emscripten/bind.h>
//
#include "serial_bridge_index.hpp"
#include "emscr_async_send_bridge.hpp"
//
EMSCRIPTEN_BINDINGS(my_module)
{ // C++ -> JS
//
emscripten::function("send_step1__prepare_params_for_get_decoys", &serial_bridge::send_step1__prepare_params_for_get_decoys);
emscripten::function("send_step2__try_create_transaction", &serial_bridge::send_step2__try_create_transaction);
emscripten::function("send_funds", &emscr_async_bridge::send_funds);
emscripten::function("send_cb_I__got_unspent_outs", &emscr_async_bridge::send_cb_I__got_unspent_outs);
emscripten::function("send_cb_II__got_random_outs", &emscr_async_bridge::send_cb_II__got_random_outs);
emscripten::function("send_cb_III__submitted_tx", &emscr_async_bridge::send_cb_III__submitted_tx);
//
emscripten::function("decode_address", &serial_bridge::decode_address);
emscripten::function("is_subaddress", &serial_bridge::is_subaddress);

@ -1 +1 @@
Subproject commit cf11304a9d123706273def9907468b677438be04
Subproject commit 9920e09d5af633a21e18dfd1be72660d98ec7ca4

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save