Skip to content

Commit

Permalink
Support explicit change outputs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jkauffman1 committed Apr 23, 2021
1 parent 3c39def commit 5f678ac
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 10 deletions.
44 changes: 36 additions & 8 deletions src/ga_tx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,11 +749,7 @@ namespace sdk {
// so compute what we can send (everything minus the
// fee) and exit the loop
required_total = available_total - fee;
if (is_liquid) {
set_tx_output_commitment(net_params, tx, 0, asset_tag, required_total.value());
} else {
tx->outputs[0].satoshi = required_total.value();
}
set_tx_output_value(net_params, tx, 0, asset_tag, required_total.value());
if (num_addressees == 1u) {
addressees_p->at(0)["satoshi"] = required_total.value();
}
Expand Down Expand Up @@ -809,9 +805,41 @@ namespace sdk {
continue;
}

// We have more than the dust amount of change. Add a change
// output to collect it, then loop again in case the amount
// this increases the fee by requires more UTXOs.
// We have more than the dust amount of change. First look for an explicit change
// output in the addressees and if present send the change there
amount::value_type change_amount = (total - required_total - fee).value();
bool have_explicit_change = false;
if (num_addressees) {
int addressee_index = 0;
for (auto& addressee : *addressees_p) {
const auto addressee_asset_tag
= session.asset_id_from_string(addressee.value("asset_tag", std::string{}));
if (addressee_asset_tag == asset_tag) {
if (json_get_value(addressee, "is_change", false)) {
if (have_explicit_change) {
set_tx_error(result, "Only one explicit change addressee allowed");
break;
}
// There is an assumption that the tx outputs are indexed so they match
// addressees, which is very weakly enforced but needs to be true otherwise
// other things (liquid blinding) breaks.
set_tx_output_value(net_params, tx, addressee_index, asset_tag, change_amount);
addressee["satoshi"] = change_amount;
have_explicit_change = true;
required_total += change_amount;
}
}
++addressee_index;
}
}

if (have_explicit_change) {
// No need for implicit change output
continue;
}

// No explicit change output specified, generate a change output paying back to
// the wallet.
add_tx_output(net_params, result, tx, result.at("change_address").at(asset_tag).at("address"),
is_liquid ? 1 : 0, asset_tag == "btc" ? std::string{} : asset_tag);
have_change_output = true;
Expand Down
25 changes: 23 additions & 2 deletions src/transaction_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,10 @@ namespace sdk {
}

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

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

void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index,
const std::string& asset_tag, amount::value_type satoshi)
{
const bool is_liquid = net_params.liquid();
if (is_liquid) {
set_tx_output_commitment(net_params, tx, index, asset_tag, satoshi);
} else {
tx->outputs[index].satoshi = satoshi;
}
}

void update_tx_size_info(const wally_tx_ptr& tx, nlohmann::json& result)
{
const bool valid = tx->num_inputs != 0u && tx->num_outputs != 0u;
Expand Down Expand Up @@ -473,6 +486,14 @@ namespace sdk {
if (net_params.liquid()) {
output["public_key"] = blinding_key_from_addr(address);
}
const bool is_change = json_get_value(addressee, "is_change", false);
if (is_change) {
// This is an explicit change output (as opposed to one generated by the gdk)
// Mark with 'is_change'
// Can be distinguished from a generated change output because the change_index
// will be set to -1
output["is_change"] = true;
}
++addressee_index;
}

Expand Down
3 changes: 3 additions & 0 deletions src/transaction_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ namespace sdk {
amount add_tx_addressee(ga_session& session, const network_parameters& net_params, nlohmann::json& result,
wally_tx_ptr& tx, nlohmann::json& addressee);

void set_tx_output_value(const network_parameters& net_params, wally_tx_ptr& tx, uint32_t index,
const std::string& asset_tag, amount::value_type satoshi);

vbf_t generate_final_vbf(byte_span_t input_abfs, byte_span_t input_vbfs, uint64_span_t input_values,
const std::vector<abf_t>& output_abfs, const std::vector<vbf_t>& output_vbfs, uint32_t num_inputs);

Expand Down

0 comments on commit 5f678ac

Please sign in to comment.