From 2a5d944e872daf0ea842c87f4ff790f2423cd58a Mon Sep 17 00:00:00 2001 From: j-berman Date: Mon, 28 Feb 2022 02:58:43 -0500 Subject: [PATCH 1/6] Re-use the same set of outs and associated mix outs across tx construction attempts Upon completing round 1 of tx construction, the client determines if the fee used in the tx is > result of calculate_fee(). If not, it re-attempts the tx using the result of calculate_fee(). If the client does not re-use the same set of mix_outs from the prior attempt for each out, then the size of the transaction likely ends up being a little different (because transactions reference mix outs by on optimized offset, which varies in size depending on the set of mix outs), even though the attempt is targeting a fee for a transaction of an expected size. This can result in the transaction including a fee that is *not* the result of calculate_fee(final_tx). Thus, this change provides a way for the client to re-use mix outs across attempts, so that the final fee is equivalent ot the result of calculate_fee. This is now implemented the same way as wallet2 (which also re-uses the same set of decoys across attempts). Bonus: it avoids trips to the server to get new decoys every tx attempt as well. --- README.md | 27 ++++++++++- src/monero_send_routine.cpp | 31 ++++++++++--- src/monero_send_routine.hpp | 3 +- src/monero_transfer_utils.cpp | 86 ++++++++++++++++++++++++++++++++++- src/monero_transfer_utils.hpp | 29 +++++++++++- 5 files changed, 165 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e80a332..174465c 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ Useful for displaying an estimated fee – To obtain exact fees, see "Creating a As mentioned, implementing the Send procedure without making use of one of our existing libraries or examples involves two bridge calls surrounded by server API calls, and mandatory reconstruction logic, and is simplified by various opportunities to pass values directly between the steps. -The values which must be passed between functions have (almost entirely) consistent names, simplifying integration. The only current exception is the name of the explicit `fee_actually_needed` which should be passed to step1 as the optional `passedIn_attemptAt_fee` after being received by calling step2 (see below). +The values which must be passed between functions have (almost entirely) consistent names, simplifying integration. The only current exceptions are the names of the explicit `fee_actually_needed` and `outs_to_mix_outs`, which should be passed to step1 as the optional `passedIn_attemptAt_fee` and `passedIn_outs_to_mix_outs` respectively after calling step2, when the transaction must be reconstructed (see below). ##### Examples * [JS implementation of SendFunds](https://github.com/mymonero/mymonero-core-js/blob/master/monero_utils/monero_sendingFunds_utils.js#L100) @@ -324,6 +324,7 @@ The values which must be passed between functions have (almost entirely) consist * `unspent_outs: [UnspentOutput]` - fully parsed server response * `payment_id_string: Optional` * `passedIn_attemptAt_fee: Optional` + * `passedIn_outs_to_mix_outs: Optional>` - map of output public keys to mix outs, explained below * Returns: @@ -345,6 +346,28 @@ The values which must be passed between functions have (almost entirely) consist * `using_outs: [UnspentOutput]` passable directly to step2 * `final_total_wo_fee: UInt64String` +##### `tie_outs_to_mix_outs` + +`passedIn_outs_to_mix_outs` functions as a cache, tying used outputs to a constant set of mix outs across construciton attempts. If the transaction construction steps need to be repated, you should re-use the same outs and their respective selected mix outs as used in prior attempts. This has 2 benefits: (1) it ensures the fee calculation is done correctly to prevent leaving transactions on chain that are fingerprintable by their fee, (2) no need to keep re-querying the server for decoys. This step should be called *after* receiving `mix_outs` from an API call, and before step2. The resulting `passedIn_outs_to_mix_outs_new` should become the new `passedIn_outs_to_mix_outs` in future tx construction attempts. + +* Args: + * `using_outs: [UnspentOutput]` returned by step1 + * `mix_outs: [MixAmountAndOuts]` defined below + * `passedIn_outs_to_mix_outs: Map` + +* Returns: + + * `err_code: CreateTransactionErrorCode`==`notEnoughUsableDecoysFound(22)` + + *OR* + + * `err_code: CreateTransactionErrorCode`==`tooManyDecoysRemaining(23)` + + *OR* + + * `mix_outs: [MixAmountAndOuts]` passable directly to step2 + * `passedIn_outs_to_mix_outs_new: Optional` + ##### `send_step2__try_create_transaction` @@ -366,7 +389,7 @@ The values which must be passed between functions have (almost entirely) consist * `nettype_string: NettypeString` * `payment_id_string: Optional` - * `MixAmountAndOuts: Dictionary` decoys obtained from API call with + * `MixAmountAndOuts: Dictionary` decoys obtained from API call with, or from `tie_outs_to_mix_outs` * `amount: UInt64String` * `outputs: [MixOut]` where * `MixOut: Dictionary` with diff --git a/src/monero_send_routine.cpp b/src/monero_send_routine.cpp index ef8a41b..fe4996b 100644 --- a/src/monero_send_routine.cpp +++ b/src/monero_send_routine.cpp @@ -86,10 +86,24 @@ LightwalletAPI_Req_GetUnspentOuts monero_send_routine::new__req_params__get_unsp }; } LightwalletAPI_Req_GetRandomOuts monero_send_routine::new__req_params__get_random_outs( - vector &step1__using_outs + const vector &step1__using_outs, + const optional &passedIn_outs_to_mix_outs ) { + // request decoys for any newly selected inputs + std::vector decoy_requests; + if (passedIn_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 (passedIn_outs_to_mix_outs->out_pub_key_to_mix_outs.find(step1__using_outs[i].public_key) == passedIn_outs_to_mix_outs->out_pub_key_to_mix_outs.end()) { + decoy_requests.push_back(step1__using_outs[i]); + } + } + } else { + decoy_requests = step1__using_outs; + } + vector decoy_req__amounts; - BOOST_FOREACH(SpendableOutput &using_out, step1__using_outs) + BOOST_FOREACH(SpendableOutput &using_out, decoy_requests) { if (using_out.rct != none && (*(using_out.rct)).size() > 0) { decoy_req__amounts.push_back("0"); @@ -321,14 +335,16 @@ struct _SendFunds_ConstructAndSendTx_Args const secret_key &sec_spendKey; // optional passedIn_attemptAt_fee; + optional passedIn_outs_to_mix_outs; size_t constructionAttempt; }; void _reenterable_construct_and_send_tx( const _SendFunds_ConstructAndSendTx_Args &args, // // re-entry params - optional passedIn_attemptAt_fee = none, - size_t constructionAttempt = 0 + optional passedIn_attemptAt_fee = none, + optional passedIn_outs_to_mix_outs = none, + size_t constructionAttempt = 0 ) { args.status_update_fn(calculatingFee); // @@ -347,7 +363,8 @@ void _reenterable_construct_and_send_tx( args.fee_per_b, args.fee_quantization_mask, // - passedIn_attemptAt_fee // use this for passing step2 "must-reconstruct" return values back in, i.e. re-entry; when nil, defaults to attempt at network min + passedIn_attemptAt_fee, // use this for passing step2 "must-reconstruct" return values back in, i.e. re-entry; when nil, defaults to attempt at network min + passedIn_outs_to_mix_outs // on re-entry, re-use the same outs and requested decoys, in order to land on the correct calculated fee ); if (step1_retVals.errCode != noError) { SendFunds_Error_RetVals error_retVals; @@ -360,6 +377,7 @@ void _reenterable_construct_and_send_tx( api_fetch_cb_fn get_random_outs_fn__cb_fn = [ args, step1_retVals, + passedIn_outs_to_mix_outs, constructionAttempt, use_fork_rules ] ( @@ -412,6 +430,7 @@ void _reenterable_construct_and_send_tx( args, // step2_retVals.fee_actually_needed, // -> reconstruction attempt's step1's passedIn_attemptAt_fee + passedIn_outs_to_mix_outs, constructionAttempt+1 ); return; @@ -463,7 +482,7 @@ void _reenterable_construct_and_send_tx( args.status_update_fn(fetchingDecoyOutputs); // args.get_random_outs_fn( - new__req_params__get_random_outs(step1_retVals.using_outs), + new__req_params__get_random_outs(step1_retVals.using_outs, passedIn_outs_to_mix_outs), get_random_outs_fn__cb_fn ); } diff --git a/src/monero_send_routine.hpp b/src/monero_send_routine.hpp index 4b0e2a4..7cceb1f 100644 --- a/src/monero_send_routine.hpp +++ b/src/monero_send_routine.hpp @@ -115,7 +115,8 @@ namespace monero_send_routine return req_params_ss.str(); } LightwalletAPI_Req_GetRandomOuts new__req_params__get_random_outs( // used internally and by emscr async send impl - vector &step1__using_outs + const vector &step1__using_outs, + const optional &passedIn_outs_to_mix_outs ); typedef std::function passedIn_attemptAt_fee + optional passedIn_attemptAt_fee, + optional passedIn_outs_to_mix_outs ) { retVals = {}; // @@ -299,6 +300,20 @@ void monero_transfer_utils::send_step1__prepare_params_for_get_decoys( // Gather outputs and amount to use for getting decoy outputs… uint64_t using_outs_amount = 0; vector remaining_unusedOuts = unspent_outs; // take copy so not to modify original + + // start by using all the passed in outs that were selected in a prior tx construction attempt + if (passedIn_outs_to_mix_outs != none) { + for (size_t i = 0; i < remaining_unusedOuts.size(); ++i) { + SpendableOutput &out = remaining_unusedOuts[i]; + + // search for out by public key to see if it should be re-used in an attempt + if (passedIn_outs_to_mix_outs->out_pub_key_to_mix_outs.find(out.public_key) != passedIn_outs_to_mix_outs->out_pub_key_to_mix_outs.end()) { + using_outs_amount += out.amount; + retVals.using_outs.push_back(std::move(pop_index(remaining_unusedOuts, i))); + } + } + } + // TODO: factor this out to get spendable balance for display in the MM wallet: while (using_outs_amount < potential_total && remaining_unusedOuts.size() > 0) { auto out = pop_random_value(remaining_unusedOuts); @@ -392,6 +407,75 @@ void monero_transfer_utils::send_step1__prepare_params_for_get_decoys( // // TODO? // } } +// +// +void monero_transfer_utils::tie_outs_to_mix_outs( + Tie_Outs_to_Mix_Outs_RetVals &retVals, + // + const vector &using_outs, + vector mix_outs_from_server, + // + const optional &passedIn_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 construciton 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 passedIn_outs_to_mix_outs_new; + if (passedIn_outs_to_mix_outs) { + passedIn_outs_to_mix_outs_new = *passedIn_outs_to_mix_outs; + } + + std::vector mix_outs; + mix_outs.reserve(using_outs.size()); + + for (size_t i = 0; i < using_outs.size(); ++i) { + auto out = using_outs[i]; + + // 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 (passedIn_outs_to_mix_outs_new.out_pub_key_to_mix_outs.find(out.public_key) == passedIn_outs_to_mix_outs_new.out_pub_key_to_mix_outs.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)) { + continue; + } + + 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) + passedIn_outs_to_mix_outs_new.out_pub_key_to_mix_outs[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 = passedIn_outs_to_mix_outs_new.out_pub_key_to_mix_outs[out.public_key]; + output_mix_outs.amount = out.amount; + mix_outs.push_back(std::move(output_mix_outs)); + } + } + + // we expect to have a set of mix outs for every output in the tx + if (mix_outs.size() != using_outs.size()) { + retVals.errCode = notEnoughUsableDecoysFound; + return; + } + + // we expect to use up all mix outs returned by the server + if (!mix_outs_from_server.empty()) { + retVals.errCode = tooManyDecoysRemaining; + return; + } + + retVals.mix_outs = std::move(mix_outs); + retVals.passedIn_outs_to_mix_outs_new = std::move(passedIn_outs_to_mix_outs_new); +} +// +// +// void monero_transfer_utils::send_step2__try_create_transaction( Send_Step2_RetVals &retVals, // diff --git a/src/monero_transfer_utils.hpp b/src/monero_transfer_utils.hpp index d791f91..05e8e1e 100644 --- a/src/monero_transfer_utils.hpp +++ b/src/monero_transfer_utils.hpp @@ -80,6 +80,10 @@ namespace monero_transfer_utils uint64_t amount; vector outputs; }; + struct SpendableAndRandomAmountOutputs + { + std::unordered_map> out_pub_key_to_mix_outs; + }; // // Types - Return value enum CreateTransactionErrorCode // TODO: switch to enum class to fix namespacing @@ -107,6 +111,8 @@ namespace monero_transfer_utils invalidPID = 19, enteredAmountTooLow = 20, cantGetDecryptedMaskFromRCTHex = 21, + notEnoughUsableDecoysFound = 22, + tooManyDecoysRemaining = 23, needMoreMoneyThanFound = 90 }; static inline const char *err_msg_from_err_code__create_transaction(CreateTransactionErrorCode code) @@ -156,6 +162,10 @@ namespace monero_transfer_utils return "Invalid payment ID"; case enteredAmountTooLow: return "The amount you've entered is too low"; + case notEnoughUsableDecoysFound: + return "Not enough usable decoys returned from server"; + case tooManyDecoysRemaining: + return "Too many unused decoys returned from server"; case cantGetDecryptedMaskFromRCTHex: return "Can't get decrypted mask from 'rct' hex"; } @@ -200,7 +210,24 @@ namespace monero_transfer_utils uint64_t fee_per_b, // per v8 uint64_t fee_quantization_mask, // - optional passedIn_attemptAt_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 passedIn_attemptAt_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 passedIn_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 mix_outs; + SpendableAndRandomAmountOutputs passedIn_outs_to_mix_outs_new; + }; + void tie_outs_to_mix_outs( + Tie_Outs_to_Mix_Outs_RetVals &retVals, + // + const vector &using_outs, + vector mix_outs_from_server, + // + const optional &passedIn_outs_to_mix_outs ); // struct Send_Step2_RetVals From 304e3067366cad5de0f594661200a6e4de63c56f Mon Sep 17 00:00:00 2001 From: j-berman Date: Mon, 7 Mar 2022 07:20:17 -0500 Subject: [PATCH 2/6] endogenic comments --- README.md | 16 ++++++++-------- src/monero_send_routine.cpp | 26 +++++++++++++------------- src/monero_send_routine.hpp | 2 +- src/monero_transfer_utils.cpp | 34 +++++++++++++++++----------------- src/monero_transfer_utils.hpp | 18 ++++++++++-------- src/serial_bridge_index.cpp | 10 +++++----- test/test_all.cpp | 12 ++++++------ 7 files changed, 60 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 174465c..adb283d 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ Useful for displaying an estimated fee – To obtain exact fees, see "Creating a As mentioned, implementing the Send procedure without making use of one of our existing libraries or examples involves two bridge calls surrounded by server API calls, and mandatory reconstruction logic, and is simplified by various opportunities to pass values directly between the steps. -The values which must be passed between functions have (almost entirely) consistent names, simplifying integration. The only current exceptions are the names of the explicit `fee_actually_needed` and `outs_to_mix_outs`, which should be passed to step1 as the optional `passedIn_attemptAt_fee` and `passedIn_outs_to_mix_outs` respectively after calling step2, when the transaction must be reconstructed (see below). +The values which must be passed between functions have (almost entirely) consistent names, simplifying integration. The only current exceptions are the names of the explicit `fee_actually_needed` and `outs_to_mix_outs`, which should be passed to step1 as the optional `prior_attempt_size_calcd_fee` and `prior_attempt_unspent_outs_to_mix_outs` respectively after calling step2, when the transaction must be reconstructed (see below). ##### Examples * [JS implementation of SendFunds](https://github.com/mymonero/mymonero-core-js/blob/master/monero_utils/monero_sendingFunds_utils.js#L100) @@ -323,8 +323,8 @@ The values which must be passed between functions have (almost entirely) consist * `fork_version: UInt8String` * `unspent_outs: [UnspentOutput]` - fully parsed server response * `payment_id_string: Optional` - * `passedIn_attemptAt_fee: Optional` - * `passedIn_outs_to_mix_outs: Optional>` - map of output public keys to mix outs, explained below + * `prior_attempt_size_calcd_fee: Optional` + * `prior_attempt_unspent_outs_to_mix_outs: Optional>` - map of output public keys to mix outs, explained below * Returns: @@ -346,14 +346,14 @@ The values which must be passed between functions have (almost entirely) consist * `using_outs: [UnspentOutput]` passable directly to step2 * `final_total_wo_fee: UInt64String` -##### `tie_outs_to_mix_outs` +##### `pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts` -`passedIn_outs_to_mix_outs` functions as a cache, tying used outputs to a constant set of mix outs across construciton attempts. If the transaction construction steps need to be repated, you should re-use the same outs and their respective selected mix outs as used in prior attempts. This has 2 benefits: (1) it ensures the fee calculation is done correctly to prevent leaving transactions on chain that are fingerprintable by their fee, (2) no need to keep re-querying the server for decoys. This step should be called *after* receiving `mix_outs` from an API call, and before step2. The resulting `passedIn_outs_to_mix_outs_new` should become the new `passedIn_outs_to_mix_outs` in future tx construction attempts. +`prior_attempt_unspent_outs_to_mix_outs` functions as a cache, tying used outputs to a constant set of mix outs across construction attempts. If the transaction construction steps need to be repated, you should re-use the same outs and their respective selected mix outs as used in prior attempts. This has 2 benefits: (1) it ensures the fee calculation is done correctly to prevent leaving transactions on chain that are fingerprintable by their fee, (2) no need to keep re-querying the server for decoys. This step should be called *after* receiving `mix_outs` from an API call, and before step2. The resulting `prior_attempt_unspent_outs_to_mix_outs_new` should become the new `prior_attempt_unspent_outs_to_mix_outs` in future tx construction attempts. * Args: * `using_outs: [UnspentOutput]` returned by step1 * `mix_outs: [MixAmountAndOuts]` defined below - * `passedIn_outs_to_mix_outs: Map` + * `prior_attempt_unspent_outs_to_mix_outs: Map` * Returns: @@ -366,7 +366,7 @@ The values which must be passed between functions have (almost entirely) consist *OR* * `mix_outs: [MixAmountAndOuts]` passable directly to step2 - * `passedIn_outs_to_mix_outs_new: Optional` + * `prior_attempt_unspent_outs_to_mix_outs_new: Optional>` ##### `send_step2__try_create_transaction` @@ -400,7 +400,7 @@ The values which must be passed between functions have (almost entirely) consist * Returns: * `tx_must_be_reconstructed: BoolString`==`true` - * `fee_actually_needed: UInt64String` pass this back to step1 as `passedIn_attemptAt_fee` + * `fee_actually_needed: UInt64String` pass this back to step1 as `prior_attempt_size_calcd_fee` *OR* diff --git a/src/monero_send_routine.cpp b/src/monero_send_routine.cpp index fe4996b..916c18f 100644 --- a/src/monero_send_routine.cpp +++ b/src/monero_send_routine.cpp @@ -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 &step1__using_outs, - const optional &passedIn_outs_to_mix_outs + const optional &prior_attempt_unspent_outs_to_mix_outs ) { // request decoys for any newly selected inputs std::vector decoy_requests; - if (passedIn_outs_to_mix_outs) { + 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 (passedIn_outs_to_mix_outs->out_pub_key_to_mix_outs.find(step1__using_outs[i].public_key) == passedIn_outs_to_mix_outs->out_pub_key_to_mix_outs.end()) { + 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()) { decoy_requests.push_back(step1__using_outs[i]); } } @@ -334,16 +334,16 @@ struct _SendFunds_ConstructAndSendTx_Args const secret_key &sec_viewKey; const secret_key &sec_spendKey; // - optional passedIn_attemptAt_fee; - optional passedIn_outs_to_mix_outs; + optional prior_attempt_size_calcd_fee; + optional 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 passedIn_attemptAt_fee = none, - optional passedIn_outs_to_mix_outs = none, + optional prior_attempt_size_calcd_fee = none, + optional prior_attempt_unspent_outs_to_mix_outs = none, size_t constructionAttempt = 0 ) { args.status_update_fn(calculatingFee); @@ -363,8 +363,8 @@ void _reenterable_construct_and_send_tx( args.fee_per_b, args.fee_quantization_mask, // - passedIn_attemptAt_fee, // use this for passing step2 "must-reconstruct" return values back in, i.e. re-entry; when nil, defaults to attempt at network min - passedIn_outs_to_mix_outs // on re-entry, re-use the same outs and requested decoys, in order to land on the correct calculated fee + 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 + 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 ); if (step1_retVals.errCode != noError) { SendFunds_Error_RetVals error_retVals; @@ -377,7 +377,7 @@ void _reenterable_construct_and_send_tx( api_fetch_cb_fn get_random_outs_fn__cb_fn = [ args, step1_retVals, - passedIn_outs_to_mix_outs, + prior_attempt_unspent_outs_to_mix_outs, constructionAttempt, use_fork_rules ] ( @@ -429,8 +429,8 @@ void _reenterable_construct_and_send_tx( _reenterable_construct_and_send_tx( args, // - step2_retVals.fee_actually_needed, // -> reconstruction attempt's step1's passedIn_attemptAt_fee - passedIn_outs_to_mix_outs, + step2_retVals.fee_actually_needed, // -> reconstruction attempt's step1's prior_attempt_size_calcd_fee + prior_attempt_unspent_outs_to_mix_outs, constructionAttempt+1 ); return; @@ -482,7 +482,7 @@ void _reenterable_construct_and_send_tx( args.status_update_fn(fetchingDecoyOutputs); // args.get_random_outs_fn( - new__req_params__get_random_outs(step1_retVals.using_outs, passedIn_outs_to_mix_outs), + new__req_params__get_random_outs(step1_retVals.using_outs, prior_attempt_unspent_outs_to_mix_outs), get_random_outs_fn__cb_fn ); } diff --git a/src/monero_send_routine.hpp b/src/monero_send_routine.hpp index 7cceb1f..2b77f48 100644 --- a/src/monero_send_routine.hpp +++ b/src/monero_send_routine.hpp @@ -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 &step1__using_outs, - const optional &passedIn_outs_to_mix_outs + const optional &prior_attempt_unspent_outs_to_mix_outs ); typedef std::function passedIn_attemptAt_fee, - optional passedIn_outs_to_mix_outs + optional prior_attempt_size_calcd_fee, + optional prior_attempt_unspent_outs_to_mix_outs ) { retVals = {}; // @@ -275,12 +275,12 @@ void monero_transfer_utils::send_step1__prepare_params_for_get_decoys( const uint64_t fee_multiplier = get_fee_multiplier(simple_priority, default_priority(), get_fee_algorithm(use_fork_rules_fn), use_fork_rules_fn); // uint64_t attempt_at_min_fee; - if (passedIn_attemptAt_fee == none) { + if (prior_attempt_size_calcd_fee == none) { attempt_at_min_fee = estimate_fee(true/*use_per_byte_fee*/, true/*use_rct*/, 1/*est num inputs*/, fake_outs_count, 2, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask); // use a minimum viable estimate_fee() with 1 input. It would be better to under-shoot this estimate, and then need to use a higher fee from calculate_fee() because the estimate is too low, // versus the worse alternative of over-estimating here and getting stuck using too high of a fee that leads to fingerprinting } else { - attempt_at_min_fee = *passedIn_attemptAt_fee; + attempt_at_min_fee = *prior_attempt_size_calcd_fee; } struct Total { @@ -302,12 +302,12 @@ void monero_transfer_utils::send_step1__prepare_params_for_get_decoys( vector remaining_unusedOuts = unspent_outs; // take copy so not to modify original // start by using all the passed in outs that were selected in a prior tx construction attempt - if (passedIn_outs_to_mix_outs != none) { + if (prior_attempt_unspent_outs_to_mix_outs != none) { for (size_t i = 0; i < remaining_unusedOuts.size(); ++i) { SpendableOutput &out = remaining_unusedOuts[i]; // search for out by public key to see if it should be re-used in an attempt - if (passedIn_outs_to_mix_outs->out_pub_key_to_mix_outs.find(out.public_key) != passedIn_outs_to_mix_outs->out_pub_key_to_mix_outs.end()) { + 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()) { using_outs_amount += out.amount; retVals.using_outs.push_back(std::move(pop_index(remaining_unusedOuts, i))); } @@ -343,7 +343,7 @@ void monero_transfer_utils::send_step1__prepare_params_for_get_decoys( bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask ); // if newNeededFee < neededFee, use neededFee instead (should only happen on the 2nd or later times through (due to estimated fee being too low)) - if (passedIn_attemptAt_fee != none && needed_fee < attempt_at_min_fee) { + if (prior_attempt_size_calcd_fee != none && needed_fee < attempt_at_min_fee) { needed_fee = attempt_at_min_fee; } // @@ -409,22 +409,22 @@ void monero_transfer_utils::send_step1__prepare_params_for_get_decoys( } // // -void monero_transfer_utils::tie_outs_to_mix_outs( +void monero_transfer_utils::pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts( Tie_Outs_to_Mix_Outs_RetVals &retVals, // const vector &using_outs, vector mix_outs_from_server, // - const optional &passedIn_outs_to_mix_outs + const optional &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 construciton attempts. This ensures fee returned + // 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 passedIn_outs_to_mix_outs_new; - if (passedIn_outs_to_mix_outs) { - passedIn_outs_to_mix_outs_new = *passedIn_outs_to_mix_outs; + SpendableAndRandomAmountOutputs 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; } std::vector mix_outs; @@ -435,7 +435,7 @@ void monero_transfer_utils::tie_outs_to_mix_outs( // 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 (passedIn_outs_to_mix_outs_new.out_pub_key_to_mix_outs.find(out.public_key) == passedIn_outs_to_mix_outs_new.out_pub_key_to_mix_outs.end()) { + 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()) { 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)) { @@ -445,14 +445,14 @@ void monero_transfer_utils::tie_outs_to_mix_outs( 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) - passedIn_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_pub_key_to_mix_outs[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 = passedIn_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_pub_key_to_mix_outs[out.public_key]; output_mix_outs.amount = out.amount; mix_outs.push_back(std::move(output_mix_outs)); } @@ -471,7 +471,7 @@ void monero_transfer_utils::tie_outs_to_mix_outs( } retVals.mix_outs = std::move(mix_outs); - retVals.passedIn_outs_to_mix_outs_new = std::move(passedIn_outs_to_mix_outs_new); + retVals.prior_attempt_unspent_outs_to_mix_outs_new = std::move(prior_attempt_unspent_outs_to_mix_outs_new); } // // diff --git a/src/monero_transfer_utils.hpp b/src/monero_transfer_utils.hpp index 05e8e1e..98f1f43 100644 --- a/src/monero_transfer_utils.hpp +++ b/src/monero_transfer_utils.hpp @@ -178,9 +178,11 @@ namespace monero_transfer_utils // Send_Step* functions procedure for integrators: // 1. call GetUnspentOuts endpoint // 2. call step1__prepare_params_for_get_decoys to get params for calling RandomOuts; call GetRandomOuts - // 3. call step2__try_… with retVals from Step1 (incl using_outs, RandomOuts) - // 3a. While tx must be reconstructed, re-call step1 passing step2 fee_actually_needed as passedIn_attemptAt_fee, then re-request RandomOuts again, and call step2 again - // 3b. If good tx constructed, proceed to submit/save the tx + // 3. call pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts to use constant set of mix outs for each unpsent out across tx construction attempts + // 4. call step2__try_… with retVals from Step1 and pre_Step2 (incl using_outs, RandomOuts) + // 4a. While tx must be reconstructed, re-call step1 passing step2 fee_actually_needed as prior_attempt_size_calcd_fee AND + // passing pre_step2 unspent_outs_to_mix_outs_new as prior_attempt_unspent_outs_to_mix_outs, then repeat steps 2-4 + // 4b. If good tx constructed, proceed to submit/save the tx // Note: This separation of steps fully encodes SendFunds_ProcessStep // struct Send_Step1_RetVals @@ -210,8 +212,8 @@ namespace monero_transfer_utils uint64_t fee_per_b, // per v8 uint64_t fee_quantization_mask, // - optional passedIn_attemptAt_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 passedIn_outs_to_mix_outs = none // use this to make sure upon re-attempting, the calculated fee will be the result of calculate_fee() + optional 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 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 { @@ -219,15 +221,15 @@ namespace monero_transfer_utils // // Success parameters vector mix_outs; - SpendableAndRandomAmountOutputs passedIn_outs_to_mix_outs_new; + SpendableAndRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs_new; }; - void tie_outs_to_mix_outs( + void pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts( Tie_Outs_to_Mix_Outs_RetVals &retVals, // const vector &using_outs, vector mix_outs_from_server, // - const optional &passedIn_outs_to_mix_outs + const optional &prior_attempt_unspent_outs_to_mix_outs ); // struct Send_Step2_RetVals diff --git a/src/serial_bridge_index.cpp b/src/serial_bridge_index.cpp index 22fe45a..c1c41b1 100644 --- a/src/serial_bridge_index.cpp +++ b/src/serial_bridge_index.cpp @@ -470,10 +470,10 @@ string serial_bridge::send_step1__prepare_params_for_get_decoys(const string &ar // unspent_outs.push_back(std::move(out)); } - optional optl__passedIn_attemptAt_fee_string = json_root.get_optional("passedIn_attemptAt_fee"); - optional optl__passedIn_attemptAt_fee = none; - if (optl__passedIn_attemptAt_fee_string != none) { - optl__passedIn_attemptAt_fee = stoull(*optl__passedIn_attemptAt_fee_string); + optional optl__prior_attempt_size_calcd_fee_string = json_root.get_optional("prior_attempt_size_calcd_fee"); + optional optl__prior_attempt_size_calcd_fee = none; + if (optl__prior_attempt_size_calcd_fee_string != none) { + optl__prior_attempt_size_calcd_fee = stoull(*optl__prior_attempt_size_calcd_fee_string); } uint8_t fork_version = 0; // if missing optional optl__fork_version_string = json_root.get_optional("fork_version"); @@ -493,7 +493,7 @@ string serial_bridge::send_step1__prepare_params_for_get_decoys(const string &ar stoull(json_root.get("fee_per_b")), // per v8 stoull(json_root.get("fee_mask")), // - optl__passedIn_attemptAt_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 ); boost::property_tree::ptree root; if (retVals.errCode != noError) { diff --git a/test/test_all.cpp b/test/test_all.cpp index 92ba2ec..ae0cdda 100644 --- a/test/test_all.cpp +++ b/test/test_all.cpp @@ -181,8 +181,8 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send__sweepDust) if (fee_actually_needed_string != none) { BOOST_REQUIRE(construction_attempt_n > 1); // - // for next round's integration - if it needs to re-enter... arg "passedIn_attemptAt_fee" - root.put("passedIn_attemptAt_fee", *fee_actually_needed_string); + // for next round's integration - if it needs to re-enter... arg "prior_attempt_size_calcd_fee" + root.put("prior_attempt_size_calcd_fee", *fee_actually_needed_string); } auto ret_string = serial_bridge::send_step1__prepare_params_for_get_decoys(args_string_from_root(root)); stringstream ret_stream; @@ -392,8 +392,8 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send__amount) if (fee_actually_needed_string != none) { BOOST_REQUIRE(construction_attempt_n > 1); // - // for next round's integration - if it needs to re-enter... arg "passedIn_attemptAt_fee" - root.put("passedIn_attemptAt_fee", *fee_actually_needed_string); + // for next round's integration - if it needs to re-enter... arg "prior_attempt_size_calcd_fee" + root.put("prior_attempt_size_calcd_fee", *fee_actually_needed_string); } auto ret_string = serial_bridge::send_step1__prepare_params_for_get_decoys(args_string_from_root(root)); stringstream ret_stream; @@ -1483,8 +1483,8 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send_stagenet_coinbase) if (fee_actually_needed_string != none) { BOOST_REQUIRE(construction_attempt_n > 1); // - // for next round's integration - if it needs to re-enter... arg "passedIn_attemptAt_fee" - root.put("passedIn_attemptAt_fee", *fee_actually_needed_string); + // for next round's integration - if it needs to re-enter... arg "prior_attempt_size_calcd_fee" + root.put("prior_attempt_size_calcd_fee", *fee_actually_needed_string); } auto ret_string = serial_bridge::send_step1__prepare_params_for_get_decoys(args_string_from_root(root)); stringstream ret_stream; From 77205f17404429f19d6eb0983153d97fe5dc5409 Mon Sep 17 00:00:00 2001 From: j-berman Date: Mon, 7 Mar 2022 07:27:09 -0500 Subject: [PATCH 3/6] add test for new tie outs to mix outs function as per endogenic --- test/test_all.cpp | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/test/test_all.cpp b/test/test_all.cpp index ae0cdda..c1b29da 100644 --- a/test/test_all.cpp +++ b/test/test_all.cpp @@ -102,6 +102,11 @@ BOOST_AUTO_TEST_CASE(wallet) // #include "../src/monero_transfer_utils.hpp" #include "../src/monero_fork_rules.hpp" +// +string pre_step2__unspent_outs_json = "{\"unspent_outs\":[{\"amount\":\"210000000\",\"public_key\":\"89eb08cf704d4473a17646331d2c425307ef03477e5f18ee6a31a3601ba9cdd0\",\"index\":0,\"global_index\":7510705,\"rct\":\"befe623ad1dcae239e4d9d31e3080db5c339ea8c5c2894444966967a051f27839f1f713d6f6bdc13fec3c20f78bbae6cf08ce185273fa6c913db6ae1f44e270ea9dcfa48ecbae364125e0c4b0cb7a11fe6c250ec9aca1a668a0708e821d6550b\",\"tx_id\":5292354,\"tx_hash\":\"22fa4aaee9399901ece7d9521067aa7791a727ade2dfe9d5e17481800ccbc625\",\"tx_pub_key\":\"4f151192723d3d45372b43e4bf93df8ad7ba5283513c09226fd0603c60683e00\",\"tx_prefix_hash\":\"689580f0804eff0fd9bd76587ed9656e4cda8e70a33f065b5461206bcf9051b7\",\"height\":1681636},{\"amount\":\"230000000\",\"public_key\":\"f659694299d97fc93db504122d40dea1681a896567933635dc6337abc4339c10\",\"index\":1,\"global_index\":7551823,\"rct\":\"dd06d546553044cda0f083fd189cd8ad93ebeca557169eefe1e34dc48c6fac27110a3ff8dc24a61b595a03a034009a6d1f0ced61f19fb6e0d7c2b1a67bb39d06c7d5713e0a394551ec978b64927802f9307ac29c8ddec3857f551b945ef6a407\",\"tx_id\":5309604,\"tx_hash\":\"05704e7402d1373d14dccd383e4071bfae0c2af6eb075e67075b43fd7d26b4c4\",\"tx_pub_key\":\"3511d9117fdeac0423314827188aa187f1eb742a44ab0c01390053b68b00909c\",\"tx_prefix_hash\":\"1b89ac0c818454806686073cd2d6bd501923d6eec2c0e54e300e3ae68a2c5344\",\"height\":1684479}]}"; +// +string pre_step2__mix_outs_from_server_json = "{\"mix_outs\":[{\"amount\":\"0\",\"outputs\":[{\"global_index\":\"6986524\",\"public_key\":\"3ce9f1231ecebf100a8d0e9c165a2b88a766249cb03eac2c6dbe7587a1f0e9ae\",\"rct\":\"c3b81a937c12c017b4c4eee0ab9acbd10d83f28c1586971b13791c7b475e469b\"},{\"global_index\":\"7282304\",\"public_key\":\"278450b855e4d66dbc1a9ae2801a2f101a10afd22c27466c3cfcc3b434a25047\",\"rct\":\"dd05d1d973be19b4e754c24c6d21e9252a9b99db52ff291930d4cd8c1cd344df\"},{\"global_index\":\"7386837\",\"public_key\":\"0d3cf94dd4e9059900f14bd8d5b71ce43e444efd2b8a1a63a1f9705851d195a1\",\"rct\":\"5c124e0c007e8a2f6371a6d35d50165178667fa9470270e8d7a95ffda34df30d\"},{\"global_index\":\"7459325\",\"public_key\":\"badabeeb71f08917b0cb76ae128e869dab7291d58c7a6b2fbd31d3eed0f003df\",\"rct\":\"a5ca005346fad19624c185dfefb2c4013f6b769f0f0de4b2c8f507ede1cb46a5\"},{\"global_index\":\"7507948\",\"public_key\":\"6f08278bc9d064cfdaa6d896ef70d28fbb3dca84e0a99ea21325f9aaef3bd783\",\"rct\":\"4a70f95a4cc19d9e43cc6b60f30f60571029240df21fb06188766bf92e8d8738\"},{\"global_index\":\"7529692\",\"public_key\":\"8b13f88507f5ca60c72c076ce6bc8ee142abc6e5115ab0c08e10a919c93f912a\",\"rct\":\"6055a2a847938471bd6f00a4d9789e6dc9d70962bb1dc2f51879d04211aaa0b7\"},{\"global_index\":\"7563051\",\"public_key\":\"d44a722cdca3c372081af6e32b758a2bbab9f2534f68a08b71d38c3540209c50\",\"rct\":\"b5ebd41d0c75877cdf109d6b5939072c22a84aee4c46a8299bec8eafc82789e9\"},{\"global_index\":\"7564143\",\"public_key\":\"c12f9e3c53dee0d1327dbca66129b27f8c6174a777976615ee442278960ba369\",\"rct\":\"a8423b9491162813589d3af5e18677f2f38050c10cb5074c097f101ccef089c5\"},{\"global_index\":\"7567982\",\"public_key\":\"9e4347089b0e1cb065cb443899d77b4bd4d61598e80a8946336440920c8a6731\",\"rct\":\"00fc0e9c631a4a2538785b647e6146ba39743d9dc987059f850d1c5a4f97bd2b\"},{\"global_index\":\"7570259\",\"public_key\":\"1be949046425c646a86ac37961a6301ea3d25711426d80a48b11e9282acd222b\",\"rct\":\"7db9d60ac0286189a1833f39db7f3e5372763c557fe2240b4537bf580a902798\"},{\"global_index\":\"7570451\",\"public_key\":\"82a27a521340220805de27aae18a4663b81067145c0b0c3e7ec42341067bf270\",\"rct\":\"a3f46fdc3e4a252604e3f3d082ab1d2cbc3ce34bf62b641b76849c5382199a32\"}]},{\"amount\":\"0\",\"outputs\":[{\"global_index\":\"7442603\",\"public_key\":\"ba89de37e26056629c89b14b3b05a73400c62149fa0de2794d3876f17faeb28f\",\"rct\":\"aa2edfca6622db354add0813ff2b471f6dc20f0d9e56d1f9b6c04b1369ceb1a9\"},{\"global_index\":\"7445670\",\"public_key\":\"a0c3a8bd0d6fa37e7bd514a10ebe6970609919e2f781dc489b771f305f1da4cc\",\"rct\":\"eb78b914307a54cd95481ba8844df3dd2d12cd14cee07de441c2c607b9cfcb24\"},{\"global_index\":\"7474646\",\"public_key\":\"3d325a1222b77d82192e1c051b241e0f79e1cc731c5f03749df33cf1a7165be8\",\"rct\":\"821bfcb255fc815aeab23d890ba252dc590c743c5733bcd278dbd1763e921e4d\"},{\"global_index\":\"7545722\",\"public_key\":\"ec62838ef1ab75055940fd8f31126698af9ff2128a53def09bdaa0d315174d80\",\"rct\":\"547de3a10658167afee6aaf8f3481921d2b1ee3014d40fa4cacc86940b244985\"},{\"global_index\":\"7556262\",\"public_key\":\"4dab027c001473b775f70503b9d68c156d2a8bfa0d7534aaff12a2ab1d8d5f89\",\"rct\":\"5aa838a2f5450408932b53181899861600d3cac864dee8197ac7e9543fbab148\"},{\"global_index\":\"7557709\",\"public_key\":\"bd1813a780e4df3c8ba25b825c3d7be12ce8c5d05f6731384e0d2d8cb8bf3134\",\"rct\":\"49ce757933cdca4a51f77ae41b951a2175d0a0a0378c10c3a02432e5aeb9f79f\"},{\"global_index\":\"7560040\",\"public_key\":\"ea53143df34ccba3c29743964ddc14094f224fa92d45c8fa8e86d7ff1394e51a\",\"rct\":\"455a6083ab6c3d4f026d2b4e1545467666f7affa0cdec365a295c097eefeac46\"},{\"global_index\":\"7563671\",\"public_key\":\"9af80a727bdb148851e79a9a11f55e97435daf65b3d57b54f4d64833cd483f2b\",\"rct\":\"622855010cd03a04d66d71a20d6113cb0507276b4c6ef050297a12e0a6767004\"},{\"global_index\":\"7564234\",\"public_key\":\"404aedc1c299e9a1538bdf7619f42cbf92cb3bb556e0356dce275945e318633d\",\"rct\":\"a1978e496622c2fac054939227a4edb31c4a50215cf8db74b0f1a7ce3477e3cf\"},{\"global_index\":\"7565705\",\"public_key\":\"070c5adc791d0a33390fecb02376e8953e46661a0173a64c003b5ae5709eea3c\",\"rct\":\"09f6c3c9139eefa0ed9ff9613e57bf3fc1b7d2bc42bad4caeb9118cc768cc52f\"},{\"global_index\":\"7566892\",\"public_key\":\"76c03aad2fae21aa7d36bbda699c462b222a76359d92813c06e4ccf4508e77e2\",\"rct\":\"9905946004a01e2884aedfa41b2482ca309226166519c558b5c794eeae109f98\"}]}]}"; +// BOOST_AUTO_TEST_CASE(transfers__fee) { uint8_t fork_version = 10; @@ -112,6 +117,201 @@ BOOST_AUTO_TEST_CASE(transfers__fee) std::cout << "transfers__fee: est_fee with fee_per_b " << fee_per_b << ": " << est_fee << std::endl; BOOST_REQUIRE(est_fee > 0); } +BOOST_AUTO_TEST_CASE(pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts__use_all_server_mix_outs) +{ + // *** START SETUP *** + // this being input as JSON merely for convenience + boost::property_tree::ptree pt; + stringstream ss; + ss << pre_step2__unspent_outs_json; + boost::property_tree::json_parser::read_json(ss, pt); + // + vector unspent_outs; + BOOST_FOREACH(boost::property_tree::ptree::value_type &output_desc, pt.get_child("unspent_outs")) + { + assert(output_desc.first.empty()); // array elements have no names + monero_transfer_utils::SpendableOutput out{}; + out.amount = stoull(output_desc.second.get("amount")); + out.public_key = output_desc.second.get("public_key"); + out.rct = output_desc.second.get_optional("rct"); + if (out.rct != none && (*out.rct).empty() == true) { + out.rct = none; + } + out.global_index = stoull(output_desc.second.get("global_index")); + out.index = stoull(output_desc.second.get("index")); + out.tx_pub_key = output_desc.second.get("tx_pub_key"); + // + unspent_outs.push_back(std::move(out)); + } + // + vector mix_outs_from_server; + { + boost::property_tree::ptree pt; + stringstream ss; + ss << pre_step2__mix_outs_from_server_json; + boost::property_tree::json_parser::read_json(ss, pt); + + BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_desc, pt.get_child("mix_outs")) + { + assert(mix_out_desc.first.empty()); // array elements have no names + auto amountAndOuts = monero_transfer_utils::RandomAmountOutputs{}; + amountAndOuts.amount = stoull(mix_out_desc.second.get("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 = monero_transfer_utils::RandomAmountOutput{}; + amountOutput.global_index = stoull(mix_out_output_desc.second.get("global_index")); + amountOutput.public_key = mix_out_output_desc.second.get("public_key"); + amountOutput.rct = mix_out_output_desc.second.get_optional("rct"); + amountAndOuts.outputs.push_back(std::move(amountOutput)); + } + mix_outs_from_server.push_back(std::move(amountAndOuts)); + } + } + assert(unspent_outs.size() == mix_outs_from_server.size()); + // *** END SETUP *** + // + monero_transfer_utils::Tie_Outs_to_Mix_Outs_RetVals tie_outs_to_mix_outs_retVals; + monero_transfer_utils::pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts( + tie_outs_to_mix_outs_retVals, + unspent_outs, + mix_outs_from_server, + boost::none/*prior_attempt_unspent_outs_to_mix_outs*/ + ); + // + BOOST_REQUIRE_MESSAGE(tie_outs_to_mix_outs_retVals.errCode == monero_transfer_utils::noError, "expected no error"); + BOOST_REQUIRE_MESSAGE(tie_outs_to_mix_outs_retVals.mix_outs.size() == mix_outs_from_server.size(), "expected resulting mix outs to use for step 2 to be same as server response"); + // + for (size_t i = 0; i < unspent_outs.size(); ++i) + { + const vector &mix_outs = tie_outs_to_mix_outs_retVals.mix_outs[i].outputs; + const monero_transfer_utils::SpendableOutput &unspent_out = unspent_outs[i]; + const vector &tied_mix_outs = tie_outs_to_mix_outs_retVals.prior_attempt_unspent_outs_to_mix_outs_new.out_pub_key_to_mix_outs[unspent_out.public_key]; + // + BOOST_REQUIRE_MESSAGE(mix_outs.size() == tied_mix_outs.size(), "mix outs from server size does not match tied mix outs size"); + for (size_t j = 0; j < mix_outs.size(); ++j) + { + BOOST_REQUIRE_MESSAGE(mix_outs[j].global_index == tied_mix_outs[j].global_index, "new outs to mix outs did not tie as expected: global index"); + BOOST_REQUIRE_MESSAGE(mix_outs[j].public_key == tied_mix_outs[j].public_key, "new outs to mix outs did not tie as expected: public key"); + BOOST_REQUIRE_MESSAGE(mix_outs[j].rct == tied_mix_outs[j].rct, "new outs to mix outs did not tie as expected: rct"); + // + BOOST_REQUIRE_MESSAGE(mix_outs[j].global_index == mix_outs_from_server[i].outputs[j].global_index, "mix outs to mix outs from server did not tie as expected: global index"); + BOOST_REQUIRE_MESSAGE(mix_outs[j].public_key == mix_outs_from_server[i].outputs[j].public_key, "mix outs to mix outs from server did not tie as expected: public key"); + BOOST_REQUIRE_MESSAGE(mix_outs[j].rct == mix_outs_from_server[i].outputs[j].rct, "mix outs to mix outs from server did not tie as expected: rct"); + } + } +} +// +BOOST_AUTO_TEST_CASE(pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts__use_prior_attempt_mix_outs) +{ + // *** START SETUP *** + // this being input as JSON merely for convenience + boost::property_tree::ptree pt; + stringstream ss; + ss << pre_step2__unspent_outs_json; + boost::property_tree::json_parser::read_json(ss, pt); + // + vector unspent_outs; + BOOST_FOREACH(boost::property_tree::ptree::value_type &output_desc, pt.get_child("unspent_outs")) + { + assert(output_desc.first.empty()); // array elements have no names + monero_transfer_utils::SpendableOutput out{}; + out.amount = stoull(output_desc.second.get("amount")); + out.public_key = output_desc.second.get("public_key"); + out.rct = output_desc.second.get_optional("rct"); + if (out.rct != none && (*out.rct).empty() == true) { + out.rct = none; + } + out.global_index = stoull(output_desc.second.get("global_index")); + out.index = stoull(output_desc.second.get("index")); + out.tx_pub_key = output_desc.second.get("tx_pub_key"); + // + unspent_outs.push_back(std::move(out)); + } + // + std::vector mix_outs_from_server; + monero_transfer_utils::SpendableAndRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs; + size_t index_of_unspent_out_used_in_prior_attempt = 0; + { + boost::property_tree::ptree pt; + stringstream ss; + ss << pre_step2__mix_outs_from_server_json; + boost::property_tree::json_parser::read_json(ss, pt); + // + size_t i = 0; + BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_desc, pt.get_child("mix_outs")) + { + assert(mix_out_desc.first.empty()); // array elements have no names + auto amountAndOuts = monero_transfer_utils::RandomAmountOutputs{}; + amountAndOuts.amount = stoull(mix_out_desc.second.get("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 = monero_transfer_utils::RandomAmountOutput{}; + amountOutput.global_index = stoull(mix_out_output_desc.second.get("global_index")); + amountOutput.public_key = mix_out_output_desc.second.get("public_key"); + amountOutput.rct = mix_out_output_desc.second.get_optional("rct"); + amountAndOuts.outputs.push_back(std::move(amountOutput)); + } + if (i == index_of_unspent_out_used_in_prior_attempt) + { + // will tie the first unspent output to the first set of mix outs returned from the server + prior_attempt_unspent_outs_to_mix_outs.out_pub_key_to_mix_outs[unspent_outs[i].public_key] = std::move(amountAndOuts.outputs); + } + else + { + mix_outs_from_server.push_back(std::move(amountAndOuts)); + } + ++i; + } + } + assert(unspent_outs.size() == (1 + mix_outs_from_server.size())); + // *** END SETUP *** + // + monero_transfer_utils::Tie_Outs_to_Mix_Outs_RetVals tie_outs_to_mix_outs_retVals; + monero_transfer_utils::pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts( + tie_outs_to_mix_outs_retVals, + unspent_outs, + mix_outs_from_server, + prior_attempt_unspent_outs_to_mix_outs + ); + // + BOOST_REQUIRE_MESSAGE(tie_outs_to_mix_outs_retVals.errCode == monero_transfer_utils::noError, "expected no error"); + BOOST_REQUIRE_MESSAGE(tie_outs_to_mix_outs_retVals.mix_outs.size() == unspent_outs.size(), "expected resulting mix outs to use for step 2 to be same as unspent_outs"); + // + for (size_t i = 0; i < unspent_outs.size(); ++i) + { + const vector &mix_outs = tie_outs_to_mix_outs_retVals.mix_outs[i].outputs; + const monero_transfer_utils::SpendableOutput &unspent_out = unspent_outs[i]; + const vector &tied_mix_outs = tie_outs_to_mix_outs_retVals.prior_attempt_unspent_outs_to_mix_outs_new.out_pub_key_to_mix_outs[unspent_out.public_key]; + // + vector prior_tied_mix_outs; + if (i == index_of_unspent_out_used_in_prior_attempt) + prior_tied_mix_outs = prior_attempt_unspent_outs_to_mix_outs.out_pub_key_to_mix_outs[unspent_out.public_key]; + // + BOOST_REQUIRE_MESSAGE(mix_outs.size() == tied_mix_outs.size(), "mix outs from server size does not match tied mix outs size"); + for (size_t j = 0; j < mix_outs.size(); ++j) + { + BOOST_REQUIRE_MESSAGE(mix_outs[j].global_index == tied_mix_outs[j].global_index, "new outs to mix outs did not tie as expected: global index"); + BOOST_REQUIRE_MESSAGE(mix_outs[j].public_key == tied_mix_outs[j].public_key, "new outs to mix outs did not tie as expected: public key"); + BOOST_REQUIRE_MESSAGE(mix_outs[j].rct == tied_mix_outs[j].rct, "new outs to mix outs did not tie as expected: rct"); + // + if (i == index_of_unspent_out_used_in_prior_attempt) + { + BOOST_REQUIRE_MESSAGE(prior_tied_mix_outs[j].global_index == tied_mix_outs[j].global_index, "prior tied mix outs to tied mix outs from server did not tie as expected: global index"); + BOOST_REQUIRE_MESSAGE(prior_tied_mix_outs[j].public_key == tied_mix_outs[j].public_key, "prior tied mix outs to tied mix outs from server did not tie as expected: public key"); + BOOST_REQUIRE_MESSAGE(prior_tied_mix_outs[j].rct == tied_mix_outs[j].rct, "prior tied mix outs to tied mix outs from server did not tie as expected: rct"); + } + else + { + monero_transfer_utils::RandomAmountOutput server_mix_out = mix_outs_from_server[i - 1].outputs[j]; + BOOST_REQUIRE_MESSAGE(mix_outs[j].global_index == server_mix_out.global_index, "mix outs to mix outs from server did not tie as expected: global index"); + BOOST_REQUIRE_MESSAGE(mix_outs[j].public_key == server_mix_out.public_key, "mix outs to mix outs from server did not tie as expected: public key"); + BOOST_REQUIRE_MESSAGE(mix_outs[j].rct == server_mix_out.rct, "mix outs to mix outs from server did not tie as expected: rct"); + } + } + } +} // // // Serialization bridge From f85744e969bbd4c9cfcc6e4492c8c11879f003b9 Mon Sep 17 00:00:00 2001 From: j-berman Date: Mon, 7 Mar 2022 21:07:51 -0500 Subject: [PATCH 4/6] small documentation fixes --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index adb283d..a4f44d1 100644 --- a/README.md +++ b/README.md @@ -352,8 +352,8 @@ The values which must be passed between functions have (almost entirely) consist * Args: * `using_outs: [UnspentOutput]` returned by step1 - * `mix_outs: [MixAmountAndOuts]` defined below - * `prior_attempt_unspent_outs_to_mix_outs: Map` + * `mix_outs_from_server: [MixAmountAndOuts]` defined below + * `prior_attempt_unspent_outs_to_mix_outs: Optional>` * Returns: @@ -366,7 +366,7 @@ The values which must be passed between functions have (almost entirely) consist *OR* * `mix_outs: [MixAmountAndOuts]` passable directly to step2 - * `prior_attempt_unspent_outs_to_mix_outs_new: Optional>` + * `prior_attempt_unspent_outs_to_mix_outs_new: Map` ##### `send_step2__try_create_transaction` @@ -389,7 +389,7 @@ The values which must be passed between functions have (almost entirely) consist * `nettype_string: NettypeString` * `payment_id_string: Optional` - * `MixAmountAndOuts: Dictionary` decoys obtained from API call with, or from `tie_outs_to_mix_outs` + * `MixAmountAndOuts: Dictionary` decoys obtained from API call with, or from `pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts` * `amount: UInt64String` * `outputs: [MixOut]` where * `MixOut: Dictionary` with From 9feaadeeddcf34b65913b240303205edbb66d464 Mon Sep 17 00:00:00 2001 From: j-berman Date: Mon, 11 Jul 2022 17:24:41 -0700 Subject: [PATCH 5/6] Update serial_bridge to re-use decoys across attempts + cleaning --- src/monero_send_routine.cpp | 8 +- src/monero_send_routine.hpp | 2 +- src/monero_transfer_utils.cpp | 14 +-- src/monero_transfer_utils.hpp | 15 +-- src/serial_bridge_index.cpp | 158 +++++++++++++++++++++++- src/serial_bridge_index.hpp | 1 + src/serial_bridge_utils.hpp | 2 + test/test_all.cpp | 226 ++++++++++++++++++++++++++++------ 8 files changed, 366 insertions(+), 60 deletions(-) diff --git a/src/monero_send_routine.cpp b/src/monero_send_routine.cpp index 916c18f..54900f9 100644 --- a/src/monero_send_routine.cpp +++ b/src/monero_send_routine.cpp @@ -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 &step1__using_outs, - const optional &prior_attempt_unspent_outs_to_mix_outs + const optional &prior_attempt_unspent_outs_to_mix_outs ) { // request decoys for any newly selected inputs std::vector 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]); } } @@ -335,7 +335,7 @@ struct _SendFunds_ConstructAndSendTx_Args const secret_key &sec_spendKey; // optional prior_attempt_size_calcd_fee; - optional prior_attempt_unspent_outs_to_mix_outs; + optional prior_attempt_unspent_outs_to_mix_outs; size_t constructionAttempt; }; void _reenterable_construct_and_send_tx( @@ -343,7 +343,7 @@ void _reenterable_construct_and_send_tx( // // re-entry params optional prior_attempt_size_calcd_fee = none, - optional prior_attempt_unspent_outs_to_mix_outs = none, + optional prior_attempt_unspent_outs_to_mix_outs = none, size_t constructionAttempt = 0 ) { args.status_update_fn(calculatingFee); diff --git a/src/monero_send_routine.hpp b/src/monero_send_routine.hpp index 2b77f48..08a68af 100644 --- a/src/monero_send_routine.hpp +++ b/src/monero_send_routine.hpp @@ -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 &step1__using_outs, - const optional &prior_attempt_unspent_outs_to_mix_outs + const optional &prior_attempt_unspent_outs_to_mix_outs ); typedef std::function prior_attempt_size_calcd_fee, - optional prior_attempt_unspent_outs_to_mix_outs + optional prior_attempt_unspent_outs_to_mix_outs ) { retVals = {}; // @@ -307,7 +307,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))); } @@ -415,14 +415,14 @@ void monero_transfer_utils::pre_step2_tie_unspent_outs_to_mix_outs_for_all_futur const vector &using_outs, vector mix_outs_from_server, // - const optional &prior_attempt_unspent_outs_to_mix_outs + const optional &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; } @@ -435,7 +435,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)) { @@ -445,14 +445,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)); } diff --git a/src/monero_transfer_utils.hpp b/src/monero_transfer_utils.hpp index 98f1f43..f52a9c2 100644 --- a/src/monero_transfer_utils.hpp +++ b/src/monero_transfer_utils.hpp @@ -80,10 +80,7 @@ namespace monero_transfer_utils uint64_t amount; vector outputs; }; - struct SpendableAndRandomAmountOutputs - { - std::unordered_map> out_pub_key_to_mix_outs; - }; + typedef std::unordered_map> SpendableOutputToRandomAmountOutputs; // // Types - Return value enum CreateTransactionErrorCode // TODO: switch to enum class to fix namespacing @@ -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"; } @@ -213,7 +210,7 @@ namespace monero_transfer_utils uint64_t fee_quantization_mask, // optional 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 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 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 { @@ -221,7 +218,7 @@ namespace monero_transfer_utils // // Success parameters vector 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, @@ -229,7 +226,7 @@ namespace monero_transfer_utils const vector &using_outs, vector mix_outs_from_server, // - const optional &prior_attempt_unspent_outs_to_mix_outs + const optional &prior_attempt_unspent_outs_to_mix_outs ); // struct Send_Step2_RetVals diff --git a/src/serial_bridge_index.cpp b/src/serial_bridge_index.cpp index c1c41b1..89250f0 100644 --- a/src/serial_bridge_index.cpp +++ b/src/serial_bridge_index.cpp @@ -475,6 +475,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 optl__prior_attempt_unspent_outs_to_mix_outs; + SpendableOutputToRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs; + optional 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("global_index")); + amountOutput.public_key = mix_out_output_desc.second.get("public_key"); + amountOutput.rct = mix_out_output_desc.second.get_optional("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 optl__fork_version_string = json_root.get_optional("fork_version"); if (optl__fork_version_string != none) { @@ -493,7 +515,8 @@ string serial_bridge::send_step1__prepare_params_for_get_decoys(const string &ar stoull(json_root.get("fee_per_b")), // per v8 stoull(json_root.get("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) { @@ -529,6 +552,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 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("amount")); + out.public_key = output_desc.second.get("public_key"); + out.rct = output_desc.second.get_optional("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("global_index")); + out.index = stoull(output_desc.second.get("index")); + out.tx_pub_key = output_desc.second.get("tx_pub_key"); + // + using_outs.push_back(std::move(out)); + } + // + vector 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("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("global_index")); + amountOutput.public_key = mix_out_output_desc.second.get("public_key"); + amountOutput.rct = mix_out_output_desc.second.get_optional("rct"); + amountAndOuts.outputs.push_back(std::move(amountOutput)); + } + mix_outs_from_server.push_back(std::move(amountAndOuts)); + } + // + optional optl__prior_attempt_unspent_outs_to_mix_outs; + SpendableOutputToRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs; + optional 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("global_index")); + amountOutput.public_key = mix_out_output_desc.second.get("public_key"); + amountOutput.rct = mix_out_output_desc.second.get_optional("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; diff --git a/src/serial_bridge_index.hpp b/src/serial_bridge_index.hpp index f0c3494..c6f6ab1 100644 --- a/src/serial_bridge_index.hpp +++ b/src/serial_bridge_index.hpp @@ -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); diff --git a/src/serial_bridge_utils.hpp b/src/serial_bridge_utils.hpp index 7a58597..c1b6a15 100644 --- a/src/serial_bridge_utils.hpp +++ b/src/serial_bridge_utils.hpp @@ -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"; } diff --git a/test/test_all.cpp b/test/test_all.cpp index c1b29da..d400468 100644 --- a/test/test_all.cpp +++ b/test/test_all.cpp @@ -186,7 +186,7 @@ BOOST_AUTO_TEST_CASE(pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_at { const vector &mix_outs = tie_outs_to_mix_outs_retVals.mix_outs[i].outputs; const monero_transfer_utils::SpendableOutput &unspent_out = unspent_outs[i]; - const vector &tied_mix_outs = tie_outs_to_mix_outs_retVals.prior_attempt_unspent_outs_to_mix_outs_new.out_pub_key_to_mix_outs[unspent_out.public_key]; + const vector &tied_mix_outs = tie_outs_to_mix_outs_retVals.prior_attempt_unspent_outs_to_mix_outs_new[unspent_out.public_key]; // BOOST_REQUIRE_MESSAGE(mix_outs.size() == tied_mix_outs.size(), "mix outs from server size does not match tied mix outs size"); for (size_t j = 0; j < mix_outs.size(); ++j) @@ -230,7 +230,7 @@ BOOST_AUTO_TEST_CASE(pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_at } // std::vector mix_outs_from_server; - monero_transfer_utils::SpendableAndRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs; + monero_transfer_utils::SpendableOutputToRandomAmountOutputs prior_attempt_unspent_outs_to_mix_outs; size_t index_of_unspent_out_used_in_prior_attempt = 0; { boost::property_tree::ptree pt; @@ -256,7 +256,7 @@ BOOST_AUTO_TEST_CASE(pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_at if (i == index_of_unspent_out_used_in_prior_attempt) { // will tie the first unspent output to the first set of mix outs returned from the server - prior_attempt_unspent_outs_to_mix_outs.out_pub_key_to_mix_outs[unspent_outs[i].public_key] = std::move(amountAndOuts.outputs); + prior_attempt_unspent_outs_to_mix_outs[unspent_outs[i].public_key] = std::move(amountAndOuts.outputs); } else { @@ -283,11 +283,11 @@ BOOST_AUTO_TEST_CASE(pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_at { const vector &mix_outs = tie_outs_to_mix_outs_retVals.mix_outs[i].outputs; const monero_transfer_utils::SpendableOutput &unspent_out = unspent_outs[i]; - const vector &tied_mix_outs = tie_outs_to_mix_outs_retVals.prior_attempt_unspent_outs_to_mix_outs_new.out_pub_key_to_mix_outs[unspent_out.public_key]; + const vector &tied_mix_outs = tie_outs_to_mix_outs_retVals.prior_attempt_unspent_outs_to_mix_outs_new[unspent_out.public_key]; // vector prior_tied_mix_outs; if (i == index_of_unspent_out_used_in_prior_attempt) - prior_tied_mix_outs = prior_attempt_unspent_outs_to_mix_outs.out_pub_key_to_mix_outs[unspent_out.public_key]; + prior_tied_mix_outs = prior_attempt_unspent_outs_to_mix_outs[unspent_out.public_key]; // BOOST_REQUIRE_MESSAGE(mix_outs.size() == tied_mix_outs.size(), "mix outs from server size does not match tied mix outs size"); for (size_t j = 0; j < mix_outs.size(); ++j) @@ -343,17 +343,8 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send__sweepDust) ss << DG_presweep__unspent_outs_json; boost::property_tree::json_parser::read_json(ss, pt); boost::property_tree::ptree unspent_outs = pt.get_child("unspent_outs"); + optional prior_attempt_unspent_outs_to_mix_outs = none; // - // NOTE: in the real algorithm you should re-request this _each time step2 must be called_ - // this being input as JSON merely for convenience - boost::property_tree::ptree mix_outs; - { - boost::property_tree::ptree pt; - stringstream ss; - ss << DG_presweep__rand_outs_json; - boost::property_tree::json_parser::read_json(ss, pt); - mix_outs = pt.get_child("mix_outs"); - } // // Send algorithm: // (Not implemented in C++ b/c the algorithm is split at the points (function interfaces) where requests must be done in e.g. JS-land, and implementing the retry integration in C++ would effectively be emscripten-only since it'd have to call out to C++. Plus this lets us retain the choice to retain synchrony @@ -380,9 +371,17 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send__sweepDust) root.add_child("unspent_outs", unspent_outs); if (fee_actually_needed_string != none) { BOOST_REQUIRE(construction_attempt_n > 1); + BOOST_REQUIRE(prior_attempt_unspent_outs_to_mix_outs != none); // - // for next round's integration - if it needs to re-enter... arg "prior_attempt_size_calcd_fee" + // for next round's integration - if it needs to re-enter... arg "prior_attempt_size_calcd_fee" and "prior_attempt_unspent_outs_to_mix_outs" root.put("prior_attempt_size_calcd_fee", *fee_actually_needed_string); + BOOST_FOREACH(boost::property_tree::ptree::value_type &outs_to_mix_outs_desc, *prior_attempt_unspent_outs_to_mix_outs) + { + string out_pub_key = outs_to_mix_outs_desc.first; + cout << "bridge__transfers__send__sweepDust: step1: prior output " << out_pub_key << endl; + BOOST_REQUIRE(outs_to_mix_outs_desc.second.size() == 11); + } + root.add_child("prior_attempt_unspent_outs_to_mix_outs", *prior_attempt_unspent_outs_to_mix_outs); } auto ret_string = serial_bridge::send_step1__prepare_params_for_get_decoys(args_string_from_root(root)); stringstream ret_stream; @@ -446,6 +445,57 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send__sweepDust) cout << "bridge__transfers__send__sweepDust: step1: final_total_wo_fee " << *final_total_wo_fee_string << endl; // } + boost::property_tree::ptree mix_outs; + { + boost::property_tree::ptree root; + root.add_child("using_outs", using_outs); // from step1 + // NOTE: in the real algorithm you should request _previously unseen + // mixouts from prior attempts each time pre_step2 must be called_ + // this being input as JSON merely for convenience + boost::property_tree::ptree mix_outs_from_server; + if (construction_attempt_n == 1) + { + boost::property_tree::ptree pt; + stringstream ss; + ss << DG_presweep__rand_outs_json; + boost::property_tree::json_parser::read_json(ss, pt); + mix_outs_from_server = pt.get_child("mix_outs"); + } + else + { + root.add_child("prior_attempt_unspent_outs_to_mix_outs", *prior_attempt_unspent_outs_to_mix_outs); + } + root.add_child("mix_outs", mix_outs_from_server); + // + boost::property_tree::ptree ret_tree; + auto ret_string = serial_bridge::pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts(args_string_from_root(root)); + stringstream ret_stream; + ret_stream << ret_string; + boost::property_tree::read_json(ret_stream, ret_tree); + optional err_code = ret_tree.get_optional(ret_json_key__any__err_code()); + if (err_code != none && (CreateTransactionErrorCode)*err_code != monero_transfer_utils::noError) { + auto err_msg = err_msg_from_err_code__create_transaction((CreateTransactionErrorCode)*err_code); + BOOST_REQUIRE_MESSAGE(false, err_msg); + } + mix_outs = ret_tree.get_child(ret_json_key__send__mix_outs()); + BOOST_REQUIRE(mix_outs.size() == using_outs.size()); + BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_desc, mix_outs) + { + assert(mix_out_desc.first.empty()); // array elements have no names + cout << "bridge__transfers__send__sweepDust: pre_step2: amount " << mix_out_desc.second.get("amount") << endl; + BOOST_REQUIRE(mix_out_desc.second.get_child("outputs").size() == 11); + } + prior_attempt_unspent_outs_to_mix_outs = ret_tree.get_child(ret_json_key__send__prior_attempt_unspent_outs_to_mix_outs_new()); + size_t outs_to_mix_outs_count = 0; + BOOST_FOREACH(boost::property_tree::ptree::value_type &outs_to_mix_outs_desc, *prior_attempt_unspent_outs_to_mix_outs) + { + ++outs_to_mix_outs_count; + string out_pub_key = outs_to_mix_outs_desc.first; + cout << "bridge__transfers__send__sweepDust: pre_step2: output " << out_pub_key << endl; + BOOST_REQUIRE(outs_to_mix_outs_desc.second.size() == 11); + } + BOOST_REQUIRE(outs_to_mix_outs_count == using_outs.size()); + } { boost::property_tree::ptree root; root.put("final_total_wo_fee", *final_total_wo_fee_string); @@ -555,17 +605,8 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send__amount) ss << DG_postsweep__unspent_outs_json; boost::property_tree::json_parser::read_json(ss, pt); boost::property_tree::ptree unspent_outs = pt.get_child("unspent_outs"); + optional prior_attempt_unspent_outs_to_mix_outs = none; // - // NOTE: in the real algorithm you should re-request this _each time step2 must be called_ - // this being input as JSON merely for convenience - boost::property_tree::ptree mix_outs; - { - boost::property_tree::ptree pt; - stringstream ss; - ss << DG_postsweep__rand_outs_json; - boost::property_tree::json_parser::read_json(ss, pt); - mix_outs = pt.get_child("mix_outs"); - } // // Send algorithm: bool tx_must_be_reconstructed = true; // for ease of writing this code, start this off true & structure whole thing as while loop @@ -591,9 +632,17 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send__amount) root.add_child("unspent_outs", unspent_outs); if (fee_actually_needed_string != none) { BOOST_REQUIRE(construction_attempt_n > 1); + BOOST_REQUIRE(prior_attempt_unspent_outs_to_mix_outs != none); // - // for next round's integration - if it needs to re-enter... arg "prior_attempt_size_calcd_fee" + // for next round's integration - if it needs to re-enter... arg "prior_attempt_size_calcd_fee" and "prior_attempt_unspent_outs_to_mix_outs" root.put("prior_attempt_size_calcd_fee", *fee_actually_needed_string); + BOOST_FOREACH(boost::property_tree::ptree::value_type &outs_to_mix_outs_desc, *prior_attempt_unspent_outs_to_mix_outs) + { + string out_pub_key = outs_to_mix_outs_desc.first; + cout << "bridge__transfers__send__sweepDust: step1: prior output " << out_pub_key << endl; + BOOST_REQUIRE(outs_to_mix_outs_desc.second.size() == 11); + } + root.add_child("prior_attempt_unspent_outs_to_mix_outs", *prior_attempt_unspent_outs_to_mix_outs); } auto ret_string = serial_bridge::send_step1__prepare_params_for_get_decoys(args_string_from_root(root)); stringstream ret_stream; @@ -657,6 +706,57 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send__amount) cout << "bridge__transfers__send__amount: step1: final_total_wo_fee " << *final_total_wo_fee_string << endl; // } + boost::property_tree::ptree mix_outs; + { + boost::property_tree::ptree root; + root.add_child("using_outs", using_outs); // from step1 + // NOTE: in the real algorithm you should request _previously unseen + // mixouts from prior attempts each time pre_step2 must be called_ + // this being input as JSON merely for convenience + boost::property_tree::ptree mix_outs_from_server; + if (construction_attempt_n == 1) + { + boost::property_tree::ptree pt; + stringstream ss; + ss << DG_postsweep__rand_outs_json; + boost::property_tree::json_parser::read_json(ss, pt); + mix_outs_from_server = pt.get_child("mix_outs"); + } + else + { + root.add_child("prior_attempt_unspent_outs_to_mix_outs", *prior_attempt_unspent_outs_to_mix_outs); + } + root.add_child("mix_outs", mix_outs_from_server); + // + boost::property_tree::ptree ret_tree; + auto ret_string = serial_bridge::pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts(args_string_from_root(root)); + stringstream ret_stream; + ret_stream << ret_string; + boost::property_tree::read_json(ret_stream, ret_tree); + optional err_code = ret_tree.get_optional(ret_json_key__any__err_code()); + if (err_code != none && (CreateTransactionErrorCode)*err_code != monero_transfer_utils::noError) { + auto err_msg = err_msg_from_err_code__create_transaction((CreateTransactionErrorCode)*err_code); + BOOST_REQUIRE_MESSAGE(false, err_msg); + } + mix_outs = ret_tree.get_child(ret_json_key__send__mix_outs()); + BOOST_REQUIRE(mix_outs.size() == using_outs.size()); + BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_desc, mix_outs) + { + assert(mix_out_desc.first.empty()); // array elements have no names + cout << "bridge__transfers__send__amount: pre_step2: amount " << mix_out_desc.second.get("amount") << endl; + BOOST_REQUIRE(mix_out_desc.second.get_child("outputs").size() == 11); + } + prior_attempt_unspent_outs_to_mix_outs = ret_tree.get_child(ret_json_key__send__prior_attempt_unspent_outs_to_mix_outs_new()); + size_t outs_to_mix_outs_count = 0; + BOOST_FOREACH(boost::property_tree::ptree::value_type &outs_to_mix_outs_desc, *prior_attempt_unspent_outs_to_mix_outs) + { + ++outs_to_mix_outs_count; + string out_pub_key = outs_to_mix_outs_desc.first; + cout << "bridge__transfers__send__amount: pre_step2: output " << out_pub_key << endl; + BOOST_REQUIRE(outs_to_mix_outs_desc.second.size() == 11); + } + BOOST_REQUIRE(outs_to_mix_outs_count == using_outs.size()); + } { boost::property_tree::ptree root; root.put("final_total_wo_fee", *final_total_wo_fee_string); @@ -1646,17 +1746,8 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send_stagenet_coinbase) ss << OM_stagenet__unspent_outs_json; boost::property_tree::json_parser::read_json(ss, pt); boost::property_tree::ptree unspent_outs = pt.get_child("unspent_outs"); + optional prior_attempt_unspent_outs_to_mix_outs = none; // - // NOTE: in the real algorithm you should re-request this _each time step2 must be called_ - // this being input as JSON merely for convenience - boost::property_tree::ptree mix_outs; - { - boost::property_tree::ptree pt; - stringstream ss; - ss << OM_stagenet__rand_outs_json; - boost::property_tree::json_parser::read_json(ss, pt); - mix_outs = pt.get_child("mix_outs"); - } // // Send algorithm: bool tx_must_be_reconstructed = true; // for ease of writing this code, start this off true & structure whole thing as while loop @@ -1682,9 +1773,17 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send_stagenet_coinbase) root.add_child("unspent_outs", unspent_outs); if (fee_actually_needed_string != none) { BOOST_REQUIRE(construction_attempt_n > 1); + BOOST_REQUIRE(prior_attempt_unspent_outs_to_mix_outs != none); // - // for next round's integration - if it needs to re-enter... arg "prior_attempt_size_calcd_fee" + // for next round's integration - if it needs to re-enter... arg "prior_attempt_size_calcd_fee" and "prior_attempt_unspent_outs_to_mix_outs" root.put("prior_attempt_size_calcd_fee", *fee_actually_needed_string); + BOOST_FOREACH(boost::property_tree::ptree::value_type &outs_to_mix_outs_desc, *prior_attempt_unspent_outs_to_mix_outs) + { + string out_pub_key = outs_to_mix_outs_desc.first; + cout << "bridge__transfers__send__sweepDust: step1: prior output " << out_pub_key << endl; + BOOST_REQUIRE(outs_to_mix_outs_desc.second.size() == 11); + } + root.add_child("prior_attempt_unspent_outs_to_mix_outs", *prior_attempt_unspent_outs_to_mix_outs); } auto ret_string = serial_bridge::send_step1__prepare_params_for_get_decoys(args_string_from_root(root)); stringstream ret_stream; @@ -1748,6 +1847,57 @@ BOOST_AUTO_TEST_CASE(bridge__transfers__send_stagenet_coinbase) cout << "bridge__transfers__send_stagenet_coinbase: step1: final_total_wo_fee " << *final_total_wo_fee_string << endl; // } + boost::property_tree::ptree mix_outs; + { + boost::property_tree::ptree root; + root.add_child("using_outs", using_outs); // from step1 + // NOTE: in the real algorithm you should request _previously unseen + // mixouts from prior attempts each time pre_step2 must be called_ + // this being input as JSON merely for convenience + boost::property_tree::ptree mix_outs_from_server; + if (construction_attempt_n == 1) + { + boost::property_tree::ptree pt; + stringstream ss; + ss << DG_postsweep__rand_outs_json; + boost::property_tree::json_parser::read_json(ss, pt); + mix_outs_from_server = pt.get_child("mix_outs"); + } + else + { + root.add_child("prior_attempt_unspent_outs_to_mix_outs", *prior_attempt_unspent_outs_to_mix_outs); + } + root.add_child("mix_outs", mix_outs_from_server); + // + boost::property_tree::ptree ret_tree; + auto ret_string = serial_bridge::pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts(args_string_from_root(root)); + stringstream ret_stream; + ret_stream << ret_string; + boost::property_tree::read_json(ret_stream, ret_tree); + optional err_code = ret_tree.get_optional(ret_json_key__any__err_code()); + if (err_code != none && (CreateTransactionErrorCode)*err_code != monero_transfer_utils::noError) { + auto err_msg = err_msg_from_err_code__create_transaction((CreateTransactionErrorCode)*err_code); + BOOST_REQUIRE_MESSAGE(false, err_msg); + } + mix_outs = ret_tree.get_child(ret_json_key__send__mix_outs()); + BOOST_REQUIRE(mix_outs.size() == using_outs.size()); + BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_desc, mix_outs) + { + assert(mix_out_desc.first.empty()); // array elements have no names + cout << "bridge__transfers__send__amount: pre_step2: amount " << mix_out_desc.second.get("amount") << endl; + BOOST_REQUIRE(mix_out_desc.second.get_child("outputs").size() == 11); + } + prior_attempt_unspent_outs_to_mix_outs = ret_tree.get_child(ret_json_key__send__prior_attempt_unspent_outs_to_mix_outs_new()); + size_t outs_to_mix_outs_count = 0; + BOOST_FOREACH(boost::property_tree::ptree::value_type &outs_to_mix_outs_desc, *prior_attempt_unspent_outs_to_mix_outs) + { + ++outs_to_mix_outs_count; + string out_pub_key = outs_to_mix_outs_desc.first; + cout << "bridge__transfers__send__amount: pre_step2: output " << out_pub_key << endl; + BOOST_REQUIRE(outs_to_mix_outs_desc.second.size() == 11); + } + BOOST_REQUIRE(outs_to_mix_outs_count == using_outs.size()); + } { boost::property_tree::ptree root; root.put("final_total_wo_fee", *final_total_wo_fee_string); From 71e798370f809cc5a2209fca5a8f7498d871df07 Mon Sep 17 00:00:00 2001 From: j-berman Date: Mon, 11 Jul 2022 18:58:31 -0700 Subject: [PATCH 6/6] update async__send_funds to re-use decoys across tx attempts --- src/monero_send_routine.cpp | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/monero_send_routine.cpp b/src/monero_send_routine.cpp index 54900f9..f479683 100644 --- a/src/monero_send_routine.cpp +++ b/src/monero_send_routine.cpp @@ -383,13 +383,32 @@ void _reenterable_construct_and_send_tx( ] ( const property_tree::ptree &res ) -> void { - auto parsed_res = new__parsed_res__get_random_outs(res); + auto parsed_res = (res != boost::property_tree::ptree{}) + ? new__parsed_res__get_random_outs(res) + : LightwalletAPI_Res_GetRandomOuts{ boost::none/*err_msg*/, vector{}/*mix_outs*/ }; if (parsed_res.err_msg != none) { SendFunds_Error_RetVals error_retVals; error_retVals.explicit_errMsg = std::move(*(parsed_res.err_msg)); args.error_cb_fn(error_retVals); return; } + + Tie_Outs_to_Mix_Outs_RetVals tie_outs_to_mix_outs_retVals; + monero_transfer_utils::pre_step2_tie_unspent_outs_to_mix_outs_for_all_future_tx_attempts( + tie_outs_to_mix_outs_retVals, + // + step1_retVals.using_outs, + *(parsed_res.mix_outs), + // + prior_attempt_unspent_outs_to_mix_outs + ); + if (tie_outs_to_mix_outs_retVals.errCode != noError) { + SendFunds_Error_RetVals error_retVals; + error_retVals.errCode = tie_outs_to_mix_outs_retVals.errCode; + args.error_cb_fn(error_retVals); + return; + } + Send_Step2_RetVals step2_retVals; monero_transfer_utils::send_step2__try_create_transaction( step2_retVals, @@ -406,7 +425,7 @@ void _reenterable_construct_and_send_tx( step1_retVals.using_outs, args.fee_per_b, args.fee_quantization_mask, - *(parsed_res.mix_outs), + tie_outs_to_mix_outs_retVals.mix_outs, std::move(use_fork_rules), args.unlock_time, args.nettype @@ -430,7 +449,7 @@ void _reenterable_construct_and_send_tx( args, // step2_retVals.fee_actually_needed, // -> reconstruction attempt's step1's prior_attempt_size_calcd_fee - prior_attempt_unspent_outs_to_mix_outs, + tie_outs_to_mix_outs_retVals.prior_attempt_unspent_outs_to_mix_outs_new, constructionAttempt+1 ); return; @@ -481,10 +500,16 @@ void _reenterable_construct_and_send_tx( // args.status_update_fn(fetchingDecoyOutputs); // - args.get_random_outs_fn( - new__req_params__get_random_outs(step1_retVals.using_outs, prior_attempt_unspent_outs_to_mix_outs), - get_random_outs_fn__cb_fn + // we won't need to make request for random outs every tx construction attempt, if already passed in out for all outs + auto req_params = new__req_params__get_random_outs( + step1_retVals.using_outs, + prior_attempt_unspent_outs_to_mix_outs ); + if (req_params.amounts.size() > 0) { + args.get_random_outs_fn(req_params, get_random_outs_fn__cb_fn); + } else { + get_random_outs_fn__cb_fn(boost::property_tree::ptree{}); + } } // //