/* Dealing with reserving UTXOs */ #include #include #include #include #include #include #include #include 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); /** * inputs_sufficient - are we there yet? * @input: total input amount * @amount: required output amount * @feerate_per_kw: feerate we have to pay * @weight: weight of transaction so far. * @diff: (output) set to amount over or under requirements. * * Returns true if inputs >= fees + amount, otherwise false. diff is * the amount over (if returns true) or under (if returns false) */ static bool inputs_sufficient(struct amount_sat input, struct amount_sat amount, u32 feerate_per_kw, size_t weight, struct amount_sat *diff) { struct amount_sat fee; fee = amount_tx_fee(feerate_per_kw, weight); /* If we can't add fees, amount is huge (e.g. "all") */ if (!amount_sat_add(&amount, amount, fee)) return false; /* One of these must work! */ if (amount_sat_sub(diff, input, amount)) return true; if (!amount_sat_sub(diff, amount, input)) abort(); return false; } static struct command_result *finish_psbt(struct command *cmd, struct utxo **utxos, u32 feerate_per_kw, size_t weight, struct amount_sat excess, bool reserve) { struct json_stream *response; struct bitcoin_tx *tx; u32 locktime; u32 current_height = get_block_height(cmd->ld->topology); /* 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); 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 struct command_result *json_fundpsbt(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { struct utxo **utxos; u32 *feerate_per_kw; u32 *minconf, *weight; struct amount_sat *amount, input, diff; bool all, *reserve; u32 maxheight; 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); /* We keep adding until we meet their output requirements. */ utxos = tal_arr(cmd, struct utxo *, 0); input = AMOUNT_SAT(0); while (!inputs_sufficient(input, *amount, *feerate_per_kw, *weight, &diff)) { struct utxo *utxo; utxo = wallet_find_utxo(utxos, cmd->ld->wallet, cmd->ld->topology->tip->height, &diff, *feerate_per_kw, maxheight, cast_const2(const struct utxo **, utxos)); if (utxo) { 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 also adds weight */ *weight += utxo_spend_weight(utxo); 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, &diff)); } if (all) { /* Anything above 0 is "excess" */ if (!inputs_sufficient(input, AMOUNT_SAT(0), *feerate_per_kw, *weight, &diff)) return command_fail(cmd, FUND_CANNOT_AFFORD, "All %zu inputs could not afford" " fees", tal_count(utxos)); } return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, diff, *reserve); } 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); static struct command_result *param_txout(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, struct utxo ***utxos) { size_t i; const jsmntok_t *curr; *utxos = tal_arr(cmd, struct utxo *, tok->size); json_for_each_arr(i, curr, tok) { struct utxo *utxo; jsmntok_t txid_tok, outnum_tok; struct bitcoin_txid txid; u32 outnum; if (!split_tok(buffer, curr, ':', &txid_tok, &outnum_tok)) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Could not decode the outpoint from \"%s\"" " The utxos should be specified as" " 'txid:output_index'.", json_strdup(tmpctx, buffer, curr)); if (!json_to_txid(buffer, &txid_tok, &txid)) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Could not get a txid out of \"%s\"", json_strdup(tmpctx, buffer, &txid_tok)); } if (!json_to_number(buffer, &outnum_tok, &outnum)) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Could not get a vout out of \"%s\"", json_strdup(tmpctx, buffer, &outnum_tok)); } utxo = wallet_utxo_get(*utxos, cmd->ld->wallet, &txid, outnum); if (!utxo) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Unknown UTXO %s:%u", type_to_string(tmpctx, struct bitcoin_txid, &txid), outnum); } if (utxo->status == output_state_spent) { return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Already spent UTXO %s:%u", type_to_string(tmpctx, struct bitcoin_txid, &txid), outnum); } (*utxos)[i] = utxo; } if (i == 0) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "Please specify an array of 'txid:output_index'," " not \"%.*s\"", tok->end - tok->start, buffer + tok->start); return NULL; } static struct command_result *json_utxopsbt(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { struct utxo **utxos; u32 *feerate_per_kw, *weight; bool all, *reserve, *reserved_ok; struct amount_sat *amount, input, excess; u32 current_height; 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_req("utxos", param_txout, &utxos), p_opt_def("reserve", param_bool, &reserve, true), p_opt_def("reservedok", param_bool, &reserved_ok, false), NULL)) return command_param_failed(); all = amount_sat_eq(*amount, AMOUNT_SAT(-1ULL)); input = AMOUNT_SAT(0); current_height = get_block_height(cmd->ld->topology); for (size_t i = 0; i < tal_count(utxos); i++) { const struct utxo *utxo = utxos[i]; if (!*reserved_ok && is_reserved(utxo, current_height)) return command_fail(cmd, JSONRPC2_INVALID_PARAMS, "UTXO %s:%u already reserved", type_to_string(tmpctx, struct bitcoin_txid, &utxo->txid), utxo->outnum); /* It supplies more input. */ if (!amount_sat_add(&input, input, utxo->amount)) return command_fail(cmd, LIGHTNINGD, "impossible UTXO value"); /* But also adds weight */ *weight += utxo_spend_weight(utxo); } /* For all, anything above 0 is "excess" */ if (!inputs_sufficient(input, all ? AMOUNT_SAT(0) : *amount, *feerate_per_kw, *weight, &excess)) { return command_fail(cmd, FUND_CANNOT_AFFORD, "Could not afford %s using UTXOs totalling %s with weight %u at feerate %u", all ? "anything" : type_to_string(tmpctx, struct amount_sat, amount), type_to_string(tmpctx, struct amount_sat, &input), *weight, *feerate_per_kw); } return finish_psbt(cmd, utxos, *feerate_per_kw, *weight, excess, *reserve); } static const struct json_command utxopsbt_command = { "utxopsbt", "bitcoin", json_utxopsbt, "Create PSBT using these utxos", false }; AUTODATA(json_command, &utxopsbt_command);