mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-06 05:49:30 +01:00
73a5f5b313
fundpsbt forces the caller to manually add their weight * feerate to the satoshis they ask for. That means no named feerates. Instead, create a startweight parameter and do the calc for them internally, and return the feerate we used (and, while we're at it, the estimated final weight). This API change is best done now, as it would otherwise have to be appended as a parameter. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
327 lines
9.6 KiB
C
327 lines
9.6 KiB
C
/* Dealing with reserving UTXOs */
|
|
#include <common/json_command.h>
|
|
#include <common/json_helpers.h>
|
|
#include <common/jsonrpc_errors.h>
|
|
#include <common/wallet_tx.h>
|
|
#include <lightningd/jsonrpc.h>
|
|
#include <lightningd/lightningd.h>
|
|
#include <wallet/wallet.h>
|
|
#include <wallet/walletrpc.h>
|
|
|
|
static bool was_reserved(enum output_status oldstatus,
|
|
const u32 *reserved_til,
|
|
u32 current_height)
|
|
{
|
|
if (oldstatus != output_state_reserved)
|
|
return false;
|
|
|
|
return *reserved_til > current_height;
|
|
}
|
|
|
|
static void json_add_reservestatus(struct json_stream *response,
|
|
const struct utxo *utxo,
|
|
enum output_status oldstatus,
|
|
u32 old_res,
|
|
u32 current_height)
|
|
{
|
|
json_object_start(response, NULL);
|
|
json_add_txid(response, "txid", &utxo->txid);
|
|
json_add_u32(response, "vout", utxo->outnum);
|
|
json_add_bool(response, "was_reserved",
|
|
was_reserved(oldstatus, &old_res, current_height));
|
|
json_add_bool(response, "reserved",
|
|
is_reserved(utxo, current_height));
|
|
if (utxo->reserved_til)
|
|
json_add_u32(response, "reserved_to_block",
|
|
*utxo->reserved_til);
|
|
json_object_end(response);
|
|
}
|
|
|
|
/* Reserve these UTXOs and print to JSON */
|
|
static void reserve_and_report(struct json_stream *response,
|
|
struct wallet *wallet,
|
|
u32 current_height,
|
|
struct utxo **utxos)
|
|
{
|
|
json_array_start(response, "reservations");
|
|
for (size_t i = 0; i < tal_count(utxos); i++) {
|
|
enum output_status oldstatus;
|
|
u32 old_res;
|
|
|
|
oldstatus = utxos[i]->status;
|
|
old_res = utxos[i]->reserved_til ? *utxos[i]->reserved_til : 0;
|
|
|
|
if (!wallet_reserve_utxo(wallet,
|
|
utxos[i],
|
|
current_height)) {
|
|
fatal("Unable to reserve %s:%u!",
|
|
type_to_string(tmpctx,
|
|
struct bitcoin_txid,
|
|
&utxos[i]->txid),
|
|
utxos[i]->outnum);
|
|
}
|
|
json_add_reservestatus(response, utxos[i], oldstatus, old_res,
|
|
current_height);
|
|
}
|
|
json_array_end(response);
|
|
}
|
|
|
|
static struct command_result *json_reserveinputs(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct json_stream *response;
|
|
struct wally_psbt *psbt;
|
|
struct utxo **utxos = tal_arr(cmd, struct utxo *, 0);
|
|
bool *exclusive;
|
|
u32 current_height;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_req("psbt", param_psbt, &psbt),
|
|
p_opt_def("exclusive", param_bool, &exclusive, true),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
current_height = get_block_height(cmd->ld->topology);
|
|
for (size_t i = 0; i < psbt->tx->num_inputs; i++) {
|
|
struct bitcoin_txid txid;
|
|
struct utxo *utxo;
|
|
|
|
wally_tx_input_get_txid(&psbt->tx->inputs[i], &txid);
|
|
utxo = wallet_utxo_get(cmd, cmd->ld->wallet,
|
|
&txid, psbt->tx->inputs[i].index);
|
|
if (!utxo)
|
|
continue;
|
|
if (*exclusive && is_reserved(utxo, current_height)) {
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"%s:%u already reserved",
|
|
type_to_string(tmpctx,
|
|
struct bitcoin_txid,
|
|
&utxo->txid),
|
|
utxo->outnum);
|
|
}
|
|
if (utxo->status == output_state_spent)
|
|
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
|
"%s:%u already spent",
|
|
type_to_string(tmpctx,
|
|
struct bitcoin_txid,
|
|
&utxo->txid),
|
|
utxo->outnum);
|
|
tal_arr_expand(&utxos, utxo);
|
|
}
|
|
|
|
response = json_stream_success(cmd);
|
|
reserve_and_report(response, cmd->ld->wallet, current_height, utxos);
|
|
return command_success(cmd, response);
|
|
}
|
|
|
|
static const struct json_command reserveinputs_command = {
|
|
"reserveinputs",
|
|
"bitcoin",
|
|
json_reserveinputs,
|
|
"Reserve utxos (or increase their reservation)",
|
|
false
|
|
};
|
|
AUTODATA(json_command, &reserveinputs_command);
|
|
|
|
static struct command_result *json_unreserveinputs(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct json_stream *response;
|
|
struct wally_psbt *psbt;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_req("psbt", param_psbt, &psbt),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
response = json_stream_success(cmd);
|
|
json_array_start(response, "reservations");
|
|
for (size_t i = 0; i < psbt->tx->num_inputs; i++) {
|
|
struct bitcoin_txid txid;
|
|
struct utxo *utxo;
|
|
enum output_status oldstatus;
|
|
u32 old_res;
|
|
|
|
wally_tx_input_get_txid(&psbt->tx->inputs[i], &txid);
|
|
utxo = wallet_utxo_get(cmd, cmd->ld->wallet,
|
|
&txid, psbt->tx->inputs[i].index);
|
|
if (!utxo || utxo->status != output_state_reserved)
|
|
continue;
|
|
|
|
oldstatus = utxo->status;
|
|
old_res = *utxo->reserved_til;
|
|
|
|
wallet_unreserve_utxo(cmd->ld->wallet,
|
|
utxo,
|
|
get_block_height(cmd->ld->topology));
|
|
|
|
json_add_reservestatus(response, utxo, oldstatus, old_res,
|
|
get_block_height(cmd->ld->topology));
|
|
}
|
|
json_array_end(response);
|
|
return command_success(cmd, response);
|
|
}
|
|
|
|
static const struct json_command unreserveinputs_command = {
|
|
"unreserveinputs",
|
|
"bitcoin",
|
|
json_unreserveinputs,
|
|
"Unreserve utxos (or at least, reduce their reservation)",
|
|
false
|
|
};
|
|
AUTODATA(json_command, &unreserveinputs_command);
|
|
|
|
|
|
static struct command_result *json_fundpsbt(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
struct json_stream *response;
|
|
struct utxo **utxos;
|
|
u32 *feerate_per_kw;
|
|
u32 *minconf, *weight;
|
|
struct amount_sat *amount, input, needed, excess, total_fee;
|
|
bool all, *reserve;
|
|
u32 locktime, maxheight, current_height;
|
|
struct bitcoin_tx *tx;
|
|
|
|
if (!param(cmd, buffer, params,
|
|
p_req("satoshi", param_sat_or_all, &amount),
|
|
p_req("feerate", param_feerate, &feerate_per_kw),
|
|
p_req("startweight", param_number, &weight),
|
|
p_opt_def("minconf", param_number, &minconf, 1),
|
|
p_opt_def("reserve", param_bool, &reserve, true),
|
|
NULL))
|
|
return command_param_failed();
|
|
|
|
all = amount_sat_eq(*amount, AMOUNT_SAT(-1ULL));
|
|
maxheight = minconf_to_maxheight(*minconf, cmd->ld);
|
|
|
|
current_height = get_block_height(cmd->ld->topology);
|
|
|
|
/* Can overflow if amount is "all" */
|
|
if (!amount_sat_add(amount, *amount,
|
|
amount_tx_fee(*feerate_per_kw, *weight)))
|
|
;
|
|
|
|
/* We keep adding until we meet their output requirements. */
|
|
input = AMOUNT_SAT(0);
|
|
utxos = tal_arr(cmd, struct utxo *, 0);
|
|
total_fee = amount_tx_fee(*feerate_per_kw, *weight);
|
|
while (amount_sat_sub(&needed, *amount, input)
|
|
&& !amount_sat_eq(needed, AMOUNT_SAT(0))) {
|
|
struct utxo *utxo;
|
|
|
|
utxo = wallet_find_utxo(utxos, cmd->ld->wallet,
|
|
cmd->ld->topology->tip->height,
|
|
&needed,
|
|
*feerate_per_kw,
|
|
maxheight,
|
|
cast_const2(const struct utxo **, utxos));
|
|
if (utxo) {
|
|
struct amount_sat fee;
|
|
tal_arr_expand(&utxos, utxo);
|
|
|
|
/* It supplies more input. */
|
|
if (!amount_sat_add(&input, input, utxo->amount))
|
|
return command_fail(cmd, LIGHTNINGD,
|
|
"impossible UTXO value");
|
|
|
|
/* But increase amount needed, to pay for new input */
|
|
*weight += utxo_spend_weight(utxo);
|
|
fee = amount_tx_fee(*feerate_per_kw,
|
|
utxo_spend_weight(utxo));
|
|
if (!amount_sat_add(amount, *amount, fee))
|
|
/* Either they specified "all", or we
|
|
* will fail anyway. */
|
|
*amount = AMOUNT_SAT(-1ULL);
|
|
if (!amount_sat_add(&total_fee, total_fee, fee))
|
|
return command_fail(cmd, LIGHTNINGD,
|
|
"impossible fee value");
|
|
continue;
|
|
}
|
|
|
|
/* If they said "all", we expect to run out of utxos. */
|
|
if (all) {
|
|
/* If we have none at all though, fail */
|
|
if (!tal_count(utxos))
|
|
return command_fail(cmd, FUND_CANNOT_AFFORD,
|
|
"No available UTXOs");
|
|
break;
|
|
}
|
|
|
|
return command_fail(cmd, FUND_CANNOT_AFFORD,
|
|
"Could not afford %s using all %zu available UTXOs: %s short",
|
|
type_to_string(tmpctx,
|
|
struct amount_sat,
|
|
amount),
|
|
tal_count(utxos),
|
|
type_to_string(tmpctx,
|
|
struct amount_sat,
|
|
&needed));
|
|
}
|
|
|
|
/* Setting the locktime to the next block to be mined has multiple
|
|
* benefits:
|
|
* - anti fee-snipping (even if not yet likely)
|
|
* - less distinguishable transactions (with this we create
|
|
* general-purpose transactions which looks like bitcoind:
|
|
* native segwit, nlocktime set to tip, and sequence set to
|
|
* 0xFFFFFFFD by default. Other wallets are likely to implement
|
|
* this too).
|
|
*/
|
|
locktime = current_height;
|
|
|
|
/* Eventually fuzz it too. */
|
|
if (locktime > 100 && pseudorand(10) == 0)
|
|
locktime -= pseudorand(100);
|
|
|
|
/* FIXME: tx_spending_utxos does more than we need, but there
|
|
* are other users right now. */
|
|
tx = tx_spending_utxos(cmd, chainparams,
|
|
cast_const2(const struct utxo **, utxos),
|
|
cmd->ld->wallet->bip32_base,
|
|
false, 0, locktime,
|
|
BITCOIN_TX_RBF_SEQUENCE);
|
|
|
|
if (all) {
|
|
/* Count everything not going towards fees as excess. */
|
|
if (!amount_sat_sub(&excess, input, total_fee))
|
|
return command_fail(cmd, FUND_CANNOT_AFFORD,
|
|
"All %zu inputs could not afford"
|
|
" %s fees",
|
|
tal_count(utxos),
|
|
type_to_string(tmpctx,
|
|
struct amount_sat,
|
|
&total_fee));
|
|
} else {
|
|
/* This was the condition of exiting the loop above! */
|
|
if (!amount_sat_sub(&excess, input, *amount))
|
|
abort();
|
|
}
|
|
|
|
response = json_stream_success(cmd);
|
|
json_add_psbt(response, "psbt", tx->psbt);
|
|
json_add_num(response, "feerate_per_kw", *feerate_per_kw);
|
|
json_add_num(response, "estimated_final_weight", *weight);
|
|
json_add_amount_sat_only(response, "excess_msat", excess);
|
|
if (*reserve)
|
|
reserve_and_report(response, cmd->ld->wallet, current_height,
|
|
utxos);
|
|
return command_success(cmd, response);
|
|
}
|
|
|
|
static const struct json_command fundpsbt_command = {
|
|
"fundpsbt",
|
|
"bitcoin",
|
|
json_fundpsbt,
|
|
"Create PSBT using enough utxos to allow an output of {satoshi} at {feerate}",
|
|
false
|
|
};
|
|
AUTODATA(json_command, &fundpsbt_command);
|