Skip to content

Commit 5b57e2c

Browse files
committed
Support explicit change outputs
Setting the `is_change` flag on an addressee when creating a transaction routes all change to that output. The default behaviour is to generate a change address for the wallet. Only one explicit change address per asset is allowed. If an explicit change address is specified for a given asset it follows that there will not be a generated change address for that asset. The tx output in the transaction json corresponding to the explicit change output will be flagged with `is_change`, just like a generated change address output, however the `change_index` will be set to -1. IOW `change_index` should be interpreted as the index of the generated (implied) change output, if there is one.
1 parent 0aa28cb commit 5b57e2c

File tree

3 files changed

+75
-25
lines changed

3 files changed

+75
-25
lines changed

src/ga_tx.cpp

+45-21
Original file line numberDiff line numberDiff line change
@@ -607,14 +607,27 @@ namespace sdk {
607607
// Add all outputs and compute the total amount of satoshi to be sent
608608
amount required_total{ 0 };
609609

610+
uint32_t explicit_change_index = NO_CHANGE_INDEX;
611+
610612
if (num_addressees) {
613+
size_t addressee_index = 0;
611614
for (auto& addressee : *addressees_p) {
612615
const auto addressee_asset_tag
613616
= session.asset_id_from_string(addressee.value("asset_tag", std::string{}));
614617
if (addressee_asset_tag == asset_tag) {
615-
required_total += add_tx_addressee(session, net_params, result, tx, addressee);
618+
const auto amount = add_tx_addressee(session, net_params, result, tx, addressee);
619+
if (!json_get_value(addressee, "is_change", false)) {
620+
required_total += amount;
621+
} else {
622+
if (explicit_change_index != NO_CHANGE_INDEX) {
623+
set_tx_error(result, "Only one explicit change addressee allowed");
624+
break;
625+
}
626+
explicit_change_index = addressee_index;
627+
}
616628
reordered_addressees.push_back(addressee);
617629
}
630+
++addressee_index;
618631
}
619632
}
620633

@@ -749,11 +762,7 @@ namespace sdk {
749762
// so compute what we can send (everything minus the
750763
// fee) and exit the loop
751764
required_total = available_total - fee;
752-
if (is_liquid) {
753-
set_tx_output_commitment(net_params, tx, 0, asset_tag, required_total.value());
754-
} else {
755-
tx->outputs[0].satoshi = required_total.value();
756-
}
765+
set_tx_output_value(net_params, tx, 0, asset_tag, required_total.value());
757766
if (num_addressees == 1u) {
758767
addressees_p->at(0)["satoshi"] = required_total.value();
759768
}
@@ -809,17 +818,30 @@ namespace sdk {
809818
continue;
810819
}
811820

812-
// We have more than the dust amount of change. Add a change
813-
// output to collect it, then loop again in case the amount
814-
// this increases the fee by requires more UTXOs.
815-
add_tx_output(net_params, result, tx, result.at("change_address").at(asset_tag).at("address"),
816-
is_liquid ? 1 : 0, asset_tag == "btc" ? std::string{} : asset_tag);
817-
have_change_output = true;
818-
change_index = tx->num_outputs - 1;
819-
if (is_liquid && include_fee) {
820-
std::swap(tx->outputs[fee_index], tx->outputs[change_index]);
821-
std::swap(fee_index, change_index);
821+
// We have more than the dust amount of change. First look for an explicit change
822+
// output in the addressees and if present send the change there
823+
amount::value_type change_amount = (total - required_total - fee).value();
824+
825+
if (explicit_change_index == NO_CHANGE_INDEX) {
826+
// No explicit change output specified, add a change output using the generated change
827+
// address
828+
add_tx_output(net_params, result, tx, result.at("change_address").at(asset_tag).at("address"),
829+
is_liquid ? 1 : 0, asset_tag == "btc" ? std::string{} : asset_tag);
830+
have_change_output = true;
831+
change_index = tx->num_outputs - 1;
832+
if (is_liquid && include_fee) {
833+
std::swap(tx->outputs[fee_index], tx->outputs[change_index]);
834+
std::swap(fee_index, change_index);
835+
}
836+
} else {
837+
// Use explicit change output
838+
set_tx_output_value(net_params, tx, explicit_change_index, asset_tag, change_amount);
839+
auto addressees = *addressees_p;
840+
addressees[explicit_change_index]["satoshi"] = change_amount;
841+
change_index = explicit_change_index;
842+
have_change_output = true;
822843
}
844+
823845
result["have_change"][asset_tag] = have_change_output;
824846
result["change_index"][asset_tag] = change_index;
825847
}
@@ -841,11 +863,13 @@ namespace sdk {
841863
} else {
842864
auto& change_output = tx->outputs[change_index];
843865
change_output.satoshi = change_amount;
844-
const uint32_t new_change_index = get_uniform_uint32_t(tx->num_outputs);
845-
// Randomize change output
846-
if (change_index != new_change_index) {
847-
std::swap(tx->outputs[new_change_index], change_output);
848-
change_index = new_change_index;
866+
if (explicit_change_index == NO_CHANGE_INDEX) {
867+
// Randomize change output for non-explicit change
868+
const uint32_t new_change_index = get_uniform_uint32_t(tx->num_outputs);
869+
if (change_index != new_change_index) {
870+
std::swap(tx->outputs[new_change_index], change_output);
871+
change_index = new_change_index;
872+
}
849873
}
850874
}
851875
}

src/transaction_utils.cpp

+27-4
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,10 @@ namespace sdk {
355355
}
356356

357357
// Transactions with outputs below the dust threshold (except OP_RETURN)
358-
// are not relayed by network nodes
359-
if (!result.value("send_all", false) && satoshi.value() < session.get_dust_threshold()) {
358+
// are not relayed by network nodes. send_all and explicit change outputs
359+
// have amounts set to zero because they are calculated
360+
if (!result.value("send_all", false) && !json_get_value(addressee, "is_change", false)
361+
&& satoshi.value() < session.get_dust_threshold()) {
360362
result["error"] = res::id_invalid_amount;
361363
}
362364

@@ -367,6 +369,17 @@ namespace sdk {
367369
net_params, result, tx, address, satoshi.value(), addressee.value("asset_tag", std::string{}));
368370
}
369371

372+
void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index,
373+
const std::string& asset_tag, amount::value_type satoshi)
374+
{
375+
const bool is_liquid = net_params.liquid();
376+
if (is_liquid) {
377+
set_tx_output_commitment(net_params, tx, index, asset_tag, satoshi);
378+
} else {
379+
tx->outputs[index].satoshi = satoshi;
380+
}
381+
}
382+
370383
void update_tx_size_info(const wally_tx_ptr& tx, nlohmann::json& result)
371384
{
372385
const bool valid = tx->num_inputs != 0u && tx->num_outputs != 0u;
@@ -436,6 +449,10 @@ namespace sdk {
436449
const uint32_t change_index
437450
= have_change ? result.at("change_index").at(asset_id).get<uint32_t>() : NO_CHANGE_INDEX;
438451

452+
const size_t num_fee_outputs = net_params.liquid() ? 1 : 0;
453+
const size_t num_addressees = result.at("addressees").size();
454+
const bool explicit_change = have_change && tx->num_outputs == num_addressees + num_fee_outputs;
455+
439456
amount::value_type satoshi = o.satoshi;
440457
if (net_params.liquid()) {
441458
GDK_RUNTIME_ASSERT(o.value);
@@ -457,10 +474,11 @@ namespace sdk {
457474
}
458475
};
459476

477+
460478
if (is_fee) {
461479
// Nothing to do
462-
} else if (i == change_index) {
463-
// Insert our change meta-data for the change output
480+
} else if (i == change_index && !explicit_change) {
481+
// Insert our change meta-data for the generated (non-explicit) change output
464482
const auto& change_address = result.at("change_address").at(asset_id);
465483
output.insert(change_address.begin(), change_address.end());
466484
if (net_params.liquid()) {
@@ -473,6 +491,11 @@ namespace sdk {
473491
if (net_params.liquid()) {
474492
output["public_key"] = blinding_key_from_addr(address);
475493
}
494+
if (i == change_index && explicit_change) {
495+
// This is an explicit change output (as opposed to one generated by the gdk)
496+
// Mark with 'is_change'
497+
output["is_change"] = true;
498+
}
476499
++addressee_index;
477500
}
478501

src/transaction_utils.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ namespace sdk {
8484
amount add_tx_addressee(ga_session& session, const network_parameters& net_params, nlohmann::json& result,
8585
wally_tx_ptr& tx, nlohmann::json& addressee);
8686

87+
void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index,
88+
const std::string& asset_tag, amount::value_type satoshi);
89+
8790
vbf_t generate_final_vbf(byte_span_t input_abfs, byte_span_t input_vbfs, uint64_span_t input_values,
8891
const std::vector<abf_t>& output_abfs, const std::vector<vbf_t>& output_vbfs, uint32_t num_inputs);
8992

0 commit comments

Comments
 (0)