Skip to content

Commit

Permalink
Update serial_bridge to re-use decoys across attempts + cleaning
Browse files Browse the repository at this point in the history
  • Loading branch information
j-berman committed Jul 12, 2022
1 parent 112d682 commit e63fe57
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 60 deletions.
8 changes: 4 additions & 4 deletions src/monero_send_routine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,14 @@ LightwalletAPI_Req_GetUnspentOuts monero_send_routine::new__req_params__get_unsp
}
LightwalletAPI_Req_GetRandomOuts monero_send_routine::new__req_params__get_random_outs(
const vector<SpendableOutput> &step1__using_outs,
const optional<SpendableAndRandomAmountOutputs> &prior_attempt_unspent_outs_to_mix_outs
const optional<SpendableOutputToRandomAmountOutputs> &prior_attempt_unspent_outs_to_mix_outs
) {
// request decoys for any newly selected inputs
std::vector<SpendableOutput> decoy_requests;
if (prior_attempt_unspent_outs_to_mix_outs) {
for (size_t i = 0; i < step1__using_outs.size(); ++i) {
// only need to request decoys for outs that were not already passed in
if (prior_attempt_unspent_outs_to_mix_outs->out_pub_key_to_mix_outs.find(step1__using_outs[i].public_key) == prior_attempt_unspent_outs_to_mix_outs->out_pub_key_to_mix_outs.end()) {
if (prior_attempt_unspent_outs_to_mix_outs->find(step1__using_outs[i].public_key) == prior_attempt_unspent_outs_to_mix_outs->end()) {
decoy_requests.push_back(step1__using_outs[i]);
}
}
Expand Down Expand Up @@ -335,15 +335,15 @@ struct _SendFunds_ConstructAndSendTx_Args
const secret_key &sec_spendKey;
//
optional<uint64_t> prior_attempt_size_calcd_fee;
optional<SpendableAndRandomAmountOutputs> prior_attempt_unspent_outs_to_mix_outs;
optional<SpendableOutputToRandomAmountOutputs> prior_attempt_unspent_outs_to_mix_outs;
size_t constructionAttempt;
};
void _reenterable_construct_and_send_tx(
const _SendFunds_ConstructAndSendTx_Args &args,
//
// re-entry params
optional<uint64_t> prior_attempt_size_calcd_fee = none,
optional<SpendableAndRandomAmountOutputs> prior_attempt_unspent_outs_to_mix_outs = none,
optional<SpendableOutputToRandomAmountOutputs> prior_attempt_unspent_outs_to_mix_outs = none,
size_t constructionAttempt = 0
) {
args.status_update_fn(calculatingFee);
Expand Down
2 changes: 1 addition & 1 deletion src/monero_send_routine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ namespace monero_send_routine
}
LightwalletAPI_Req_GetRandomOuts new__req_params__get_random_outs( // used internally and by emscr async send impl
const vector<SpendableOutput> &step1__using_outs,
const optional<SpendableAndRandomAmountOutputs> &prior_attempt_unspent_outs_to_mix_outs
const optional<SpendableOutputToRandomAmountOutputs> &prior_attempt_unspent_outs_to_mix_outs
);
typedef std::function<void(
LightwalletAPI_Req_GetRandomOuts, // req_params - use these for making the request
Expand Down
14 changes: 7 additions & 7 deletions src/monero_transfer_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ void monero_transfer_utils::send_step1__prepare_params_for_get_decoys(
uint64_t fee_quantization_mask,
//
optional<uint64_t> prior_attempt_size_calcd_fee,
optional<SpendableAndRandomAmountOutputs> prior_attempt_unspent_outs_to_mix_outs
optional<SpendableOutputToRandomAmountOutputs> prior_attempt_unspent_outs_to_mix_outs
) {
retVals = {};
//
Expand Down Expand Up @@ -306,7 +306,7 @@ void monero_transfer_utils::send_step1__prepare_params_for_get_decoys(
SpendableOutput &out = remaining_unusedOuts[i];

// search for out by public key to see if it should be re-used in an attempt
if (prior_attempt_unspent_outs_to_mix_outs->out_pub_key_to_mix_outs.find(out.public_key) != prior_attempt_unspent_outs_to_mix_outs->out_pub_key_to_mix_outs.end()) {
if (prior_attempt_unspent_outs_to_mix_outs->find(out.public_key) != prior_attempt_unspent_outs_to_mix_outs->end()) {
using_outs_amount += out.amount;
retVals.using_outs.push_back(std::move(pop_index(remaining_unusedOuts, i)));
}
Expand Down Expand Up @@ -414,14 +414,14 @@ void monero_transfer_utils::pre_step2_tie_unspent_outs_to_mix_outs_for_all_futur
const vector<SpendableOutput> &using_outs,
vector<RandomAmountOutputs> mix_outs_from_server,
//
const optional<SpendableAndRandomAmountOutputs> &prior_attempt_unspent_outs_to_mix_outs
const optional<SpendableOutputToRandomAmountOutputs> &prior_attempt_unspent_outs_to_mix_outs
) {
retVals.errCode = noError;
//
// combine newly requested mix outs returned from the server, with the already known decoys from prior tx construction attempts,
// so that the same decoys will be re-used with the same outputs in all tx construction attempts. This ensures fee returned
// by calculate_fee() will be correct in the final tx, and also reduces number of needed trips to the server during tx construction.
SpendableAndRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs_new;
SpendableOutputToRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs_new;
if (prior_attempt_unspent_outs_to_mix_outs) {
prior_attempt_unspent_outs_to_mix_outs_new = *prior_attempt_unspent_outs_to_mix_outs;
}
Expand All @@ -434,7 +434,7 @@ void monero_transfer_utils::pre_step2_tie_unspent_outs_to_mix_outs_for_all_futur

// if we don't already know of a particular out's mix outs (from a prior attempt),
// then tie out to a set of mix outs retrieved from the server
if (prior_attempt_unspent_outs_to_mix_outs_new.out_pub_key_to_mix_outs.find(out.public_key) == prior_attempt_unspent_outs_to_mix_outs_new.out_pub_key_to_mix_outs.end()) {
if (prior_attempt_unspent_outs_to_mix_outs_new.find(out.public_key) == prior_attempt_unspent_outs_to_mix_outs_new.end()) {
for (size_t j = 0; j < mix_outs_from_server.size(); ++j) {
if ((out.rct != none && mix_outs_from_server[j].amount != 0) ||
(out.rct == none && mix_outs_from_server[j].amount != out.amount)) {
Expand All @@ -444,14 +444,14 @@ void monero_transfer_utils::pre_step2_tie_unspent_outs_to_mix_outs_for_all_futur
RandomAmountOutputs output_mix_outs = pop_index(mix_outs_from_server, j);

// if we need to retry constructing tx, will remember to use same mix outs for this out on subsequent attempt(s)
prior_attempt_unspent_outs_to_mix_outs_new.out_pub_key_to_mix_outs[out.public_key] = output_mix_outs.outputs;
prior_attempt_unspent_outs_to_mix_outs_new[out.public_key] = output_mix_outs.outputs;
mix_outs.push_back(std::move(output_mix_outs));

break;
}
} else {
RandomAmountOutputs output_mix_outs;
output_mix_outs.outputs = prior_attempt_unspent_outs_to_mix_outs_new.out_pub_key_to_mix_outs[out.public_key];
output_mix_outs.outputs = prior_attempt_unspent_outs_to_mix_outs_new[out.public_key];
output_mix_outs.amount = out.amount;
mix_outs.push_back(std::move(output_mix_outs));
}
Expand Down
15 changes: 6 additions & 9 deletions src/monero_transfer_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,7 @@ namespace monero_transfer_utils
uint64_t amount;
vector<RandomAmountOutput> outputs;
};
struct SpendableAndRandomAmountOutputs
{
std::unordered_map<string, std::vector<RandomAmountOutput>> out_pub_key_to_mix_outs;
};
typedef std::unordered_map<string/*public_key*/, std::vector<RandomAmountOutput>> SpendableOutputToRandomAmountOutputs;
//
// Types - Return value
enum CreateTransactionErrorCode // TODO: switch to enum class to fix namespacing
Expand Down Expand Up @@ -163,9 +160,9 @@ namespace monero_transfer_utils
case enteredAmountTooLow:
return "The amount you've entered is too low";
case notEnoughUsableDecoysFound:
return "Not enough usable decoys returned from server";
return "Not enough usable decoys found";
case tooManyDecoysRemaining:
return "Too many unused decoys returned from server";
return "Too many unused decoys remaining";
case cantGetDecryptedMaskFromRCTHex:
return "Can't get decrypted mask from 'rct' hex";
}
Expand Down Expand Up @@ -212,23 +209,23 @@ namespace monero_transfer_utils
uint64_t fee_quantization_mask,
//
optional<uint64_t> prior_attempt_size_calcd_fee, // use this for passing step2 "must-reconstruct" return values back in, i.e. re-entry; when nil, defaults to attempt at network min
optional<SpendableAndRandomAmountOutputs> prior_attempt_unspent_outs_to_mix_outs = none // use this to make sure upon re-attempting, the calculated fee will be the result of calculate_fee()
optional<SpendableOutputToRandomAmountOutputs> prior_attempt_unspent_outs_to_mix_outs = none // use this to make sure upon re-attempting, the calculated fee will be the result of calculate_fee()
);
struct Tie_Outs_to_Mix_Outs_RetVals
{
CreateTransactionErrorCode errCode; // if != noError, abort Send process
//
// Success parameters
vector<RandomAmountOutputs> mix_outs;
SpendableAndRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs_new;
SpendableOutputToRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs_new;
};
void pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts(
Tie_Outs_to_Mix_Outs_RetVals &retVals,
//
const vector<SpendableOutput> &using_outs,
vector<RandomAmountOutputs> mix_outs_from_server,
//
const optional<SpendableAndRandomAmountOutputs> &prior_attempt_unspent_outs_to_mix_outs
const optional<SpendableOutputToRandomAmountOutputs> &prior_attempt_unspent_outs_to_mix_outs
);
//
struct Send_Step2_RetVals
Expand Down
158 changes: 157 additions & 1 deletion src/serial_bridge_index.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,28 @@ string serial_bridge::send_step1__prepare_params_for_get_decoys(const string &ar
if (optl__prior_attempt_size_calcd_fee_string != none) {
optl__prior_attempt_size_calcd_fee = stoull(*optl__prior_attempt_size_calcd_fee_string);
}
optional<SpendableOutputToRandomAmountOutputs> optl__prior_attempt_unspent_outs_to_mix_outs;
SpendableOutputToRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs;
optional<boost::property_tree::ptree &> optl__prior_attempt_unspent_outs_to_mix_outs_json = json_root.get_child_optional("prior_attempt_unspent_outs_to_mix_outs");
if (optl__prior_attempt_unspent_outs_to_mix_outs_json != none)
{
BOOST_FOREACH(boost::property_tree::ptree::value_type &outs_to_mix_outs_desc, *optl__prior_attempt_unspent_outs_to_mix_outs_json)
{
string out_pub_key = outs_to_mix_outs_desc.first;
RandomAmountOutputs amountAndOuts{};
BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_output_desc, outs_to_mix_outs_desc.second)
{
assert(mix_out_output_desc.first.empty()); // array elements have no names
auto amountOutput = monero_transfer_utils::RandomAmountOutput{};
amountOutput.global_index = stoull(mix_out_output_desc.second.get<string>("global_index"));
amountOutput.public_key = mix_out_output_desc.second.get<string>("public_key");
amountOutput.rct = mix_out_output_desc.second.get_optional<string>("rct");
amountAndOuts.outputs.push_back(std::move(amountOutput));
}
prior_attempt_unspent_outs_to_mix_outs[out_pub_key] = std::move(amountAndOuts.outputs);
}
optl__prior_attempt_unspent_outs_to_mix_outs = std::move(prior_attempt_unspent_outs_to_mix_outs);
}
uint8_t fork_version = 0; // if missing
optional<string> optl__fork_version_string = json_root.get_optional<string>("fork_version");
if (optl__fork_version_string != none) {
Expand All @@ -490,7 +512,8 @@ string serial_bridge::send_step1__prepare_params_for_get_decoys(const string &ar
stoull(json_root.get<string>("fee_per_b")), // per v8
stoull(json_root.get<string>("fee_mask")),
//
optl__prior_attempt_size_calcd_fee // use this for passing step2 "must-reconstruct" return values back in, i.e. re-entry; when nil, defaults to attempt at network min
optl__prior_attempt_size_calcd_fee, // use this for passing step2 "must-reconstruct" return values back in, i.e. re-entry; when nil, defaults to attempt at network min
optl__prior_attempt_unspent_outs_to_mix_outs // on re-entry, re-use the same outs and requested decoys, in order to land on the correct calculated fee
);
boost::property_tree::ptree root;
if (retVals.errCode != noError) {
Expand Down Expand Up @@ -526,6 +549,139 @@ string serial_bridge::send_step1__prepare_params_for_get_decoys(const string &ar
}
return ret_json_from_root(root);
}
//
string serial_bridge::pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts(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
return error_ret_json_from_message("Invalid JSON");
}
//
vector<SpendableOutput> using_outs;
BOOST_FOREACH(boost::property_tree::ptree::value_type &output_desc, json_root.get_child("using_outs"))
{
assert(output_desc.first.empty()); // array elements have no names
SpendableOutput out{};
out.amount = stoull(output_desc.second.get<string>("amount"));
out.public_key = output_desc.second.get<string>("public_key");
out.rct = output_desc.second.get_optional<string>("rct");
if (out.rct != none && (*out.rct).empty() == true) {
out.rct = none; // just in case it's an empty string, send to 'none' (even though receiving code now handles empty strs)
}
out.global_index = stoull(output_desc.second.get<string>("global_index"));
out.index = stoull(output_desc.second.get<string>("index"));
out.tx_pub_key = output_desc.second.get<string>("tx_pub_key");
//
using_outs.push_back(std::move(out));
}
//
vector<RandomAmountOutputs> mix_outs_from_server;
BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_desc, json_root.get_child("mix_outs"))
{
assert(mix_out_desc.first.empty()); // array elements have no names
auto amountAndOuts = RandomAmountOutputs{};
amountAndOuts.amount = stoull(mix_out_desc.second.get<string>("amount"));
BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_output_desc, mix_out_desc.second.get_child("outputs"))
{
assert(mix_out_output_desc.first.empty()); // array elements have no names
auto amountOutput = RandomAmountOutput{};
amountOutput.global_index = stoull(mix_out_output_desc.second.get<string>("global_index"));
amountOutput.public_key = mix_out_output_desc.second.get<string>("public_key");
amountOutput.rct = mix_out_output_desc.second.get_optional<string>("rct");
amountAndOuts.outputs.push_back(std::move(amountOutput));
}
mix_outs_from_server.push_back(std::move(amountAndOuts));
}
//
optional<SpendableOutputToRandomAmountOutputs> optl__prior_attempt_unspent_outs_to_mix_outs;
SpendableOutputToRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs;
optional<boost::property_tree::ptree &> optl__prior_attempt_unspent_outs_to_mix_outs_json = json_root.get_child_optional("prior_attempt_unspent_outs_to_mix_outs");
if (optl__prior_attempt_unspent_outs_to_mix_outs_json != none)
{
BOOST_FOREACH(boost::property_tree::ptree::value_type &outs_to_mix_outs_desc, *optl__prior_attempt_unspent_outs_to_mix_outs_json)
{
string out_pub_key = outs_to_mix_outs_desc.first;
RandomAmountOutputs amountAndOuts{};
BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_output_desc, outs_to_mix_outs_desc.second)
{
assert(mix_out_output_desc.first.empty()); // array elements have no names
auto amountOutput = monero_transfer_utils::RandomAmountOutput{};
amountOutput.global_index = stoull(mix_out_output_desc.second.get<string>("global_index"));
amountOutput.public_key = mix_out_output_desc.second.get<string>("public_key");
amountOutput.rct = mix_out_output_desc.second.get_optional<string>("rct");
amountAndOuts.outputs.push_back(std::move(amountOutput));
}
prior_attempt_unspent_outs_to_mix_outs[out_pub_key] = std::move(amountAndOuts.outputs);
}
optl__prior_attempt_unspent_outs_to_mix_outs = std::move(prior_attempt_unspent_outs_to_mix_outs);
}
//
Tie_Outs_to_Mix_Outs_RetVals retVals;
monero_transfer_utils::pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts(
retVals,
//
using_outs,
mix_outs_from_server,
//
optl__prior_attempt_unspent_outs_to_mix_outs
);
boost::property_tree::ptree root;
if (retVals.errCode != noError) {
root.put(ret_json_key__any__err_code(), retVals.errCode);
root.put(ret_json_key__any__err_msg(), err_msg_from_err_code__create_transaction(retVals.errCode));
} else {
{
boost::property_tree::ptree mix_outs_ptree;
BOOST_FOREACH(RandomAmountOutputs &mix_outs, retVals.mix_outs)
{
auto mix_outs_amount_ptree_pair = std::make_pair("", boost::property_tree::ptree{});
auto& mix_outs_amount_ptree = mix_outs_amount_ptree_pair.second;
mix_outs_amount_ptree.put("amount", RetVals_Transforms::str_from(mix_outs.amount));
auto outputs_ptree_pair = std::make_pair("", boost::property_tree::ptree{});
auto& outputs_ptree = outputs_ptree_pair.second;
BOOST_FOREACH(RandomAmountOutput &out, mix_outs.outputs)
{
auto mix_out_ptree_pair = std::make_pair("", boost::property_tree::ptree{});
auto& mix_out_ptree = mix_out_ptree_pair.second;
mix_out_ptree.put("global_index", RetVals_Transforms::str_from(out.global_index));
mix_out_ptree.put("public_key", out.public_key);
if (out.rct != none && (*out.rct).empty() == false) {
mix_out_ptree.put("rct", *out.rct);
}
outputs_ptree.push_back(mix_out_ptree_pair);
}
mix_outs_amount_ptree.add_child("outputs", outputs_ptree);
mix_outs_ptree.push_back(mix_outs_amount_ptree_pair);
}
root.add_child(ret_json_key__send__mix_outs(), mix_outs_ptree);
}
//
{
boost::property_tree::ptree prior_attempt_unspent_outs_to_mix_outs_new_ptree;
for (const auto &out_pub_key_to_mix_outs : retVals.prior_attempt_unspent_outs_to_mix_outs_new)
{
auto outs_ptree_pair = std::make_pair(out_pub_key_to_mix_outs.first, boost::property_tree::ptree{});
auto& outs_ptree = outs_ptree_pair.second;
for (const auto &mix_out : out_pub_key_to_mix_outs.second)
{
auto mix_out_ptree_pair = std::make_pair("", boost::property_tree::ptree{});
auto& mix_out_ptree = mix_out_ptree_pair.second;
mix_out_ptree.put("global_index", RetVals_Transforms::str_from(mix_out.global_index));
mix_out_ptree.put("public_key", mix_out.public_key);
if (mix_out.rct != none && (*mix_out.rct).empty() == false) {
mix_out_ptree.put("rct", *mix_out.rct);
}
outs_ptree.push_back(mix_out_ptree_pair);
}
prior_attempt_unspent_outs_to_mix_outs_new_ptree.push_back(outs_ptree_pair);
}
root.add_child(ret_json_key__send__prior_attempt_unspent_outs_to_mix_outs_new(), prior_attempt_unspent_outs_to_mix_outs_new_ptree);
}
}
return ret_json_from_root(root);
}
//
string serial_bridge::send_step2__try_create_transaction(const string &args_string)
{
boost::property_tree::ptree json_root;
Expand Down
1 change: 1 addition & 0 deletions src/serial_bridge_index.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace serial_bridge
//
// Bridging Functions - these take and return JSON strings
string send_step1__prepare_params_for_get_decoys(const string &args_string);
string pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts(const string &args_string);
string send_step2__try_create_transaction(const string &args_string);
//
string decode_address(const string &args_string);
Expand Down
2 changes: 2 additions & 0 deletions src/serial_bridge_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ namespace serial_bridge_utils
static inline string ret_json_key__send__final_total_wo_fee() { return "final_total_wo_fee"; }
static inline string ret_json_key__send__change_amount() { return "change_amount"; }
static inline string ret_json_key__send__using_outs() { return "using_outs"; } // this list's members' keys should probably be declared (is this the best way to do this?)
static inline string ret_json_key__send__mix_outs() { return "mix_outs"; }
static inline string ret_json_key__send__prior_attempt_unspent_outs_to_mix_outs_new() { return "prior_attempt_unspent_outs_to_mix_outs_new"; }
//
static inline string ret_json_key__send__tx_must_be_reconstructed() { return "tx_must_be_reconstructed"; }
static inline string ret_json_key__send__fee_actually_needed() { return "fee_actually_needed"; }
Expand Down
Loading

0 comments on commit e63fe57

Please sign in to comment.