You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

326 lines
9.6 KiB

/* 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);