mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-18 05:12:45 +01:00
onchaind/onchaind.c: Implement scorch-the-earth.
Fixes: #3832 Changelog-Changed: onchaind: We now scorch the earth on theft attempts, RBFing up our penalty transaction as blocks arrive without a penalty transaction getting confirmed.
This commit is contained in:
parent
6c13e9b300
commit
06b44f00a3
@ -12,6 +12,7 @@
|
|||||||
#include <common/key_derive.h>
|
#include <common/key_derive.h>
|
||||||
#include <common/keyset.h>
|
#include <common/keyset.h>
|
||||||
#include <common/memleak.h>
|
#include <common/memleak.h>
|
||||||
|
#include <common/overflows.h>
|
||||||
#include <common/peer_billboard.h>
|
#include <common/peer_billboard.h>
|
||||||
#include <common/status.h>
|
#include <common/status.h>
|
||||||
#include <common/subdaemon.h>
|
#include <common/subdaemon.h>
|
||||||
@ -695,6 +696,224 @@ static struct bitcoin_tx *tx_to_us(const tal_t *ctx,
|
|||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** replace_penalty_tx_to_us
|
||||||
|
*
|
||||||
|
* @brief creates a replacement TX for
|
||||||
|
* a given penalty tx.
|
||||||
|
*
|
||||||
|
* @param ctx - the context to allocate
|
||||||
|
* off of.
|
||||||
|
* @param hsm_sign_msg - function to construct
|
||||||
|
* the signing message to HSM.
|
||||||
|
* @param penalty_tx - the original
|
||||||
|
* penalty transaction.
|
||||||
|
* @param output_amount - the output
|
||||||
|
* amount to use instead of the
|
||||||
|
* original penalty transaction.
|
||||||
|
* If this amount is below the dust
|
||||||
|
* limit, the output is replaced with
|
||||||
|
* an `OP_RETURN` instead.
|
||||||
|
*
|
||||||
|
* @return the signed transaction.
|
||||||
|
*/
|
||||||
|
static struct bitcoin_tx *
|
||||||
|
replace_penalty_tx_to_us(const tal_t *ctx,
|
||||||
|
u8 *(*hsm_sign_msg)(const tal_t *ctx,
|
||||||
|
struct bitcoin_tx *tx,
|
||||||
|
const u8 *wscript),
|
||||||
|
const struct bitcoin_tx *penalty_tx,
|
||||||
|
struct amount_sat output_amount)
|
||||||
|
{
|
||||||
|
struct bitcoin_tx *tx;
|
||||||
|
|
||||||
|
/* The penalty tx input. */
|
||||||
|
const struct wally_tx_input *input;
|
||||||
|
/* Specs of the penalty tx input. */
|
||||||
|
struct bitcoin_txid input_txid;
|
||||||
|
u32 input_outnum;
|
||||||
|
u8 *input_wscript;
|
||||||
|
u8 *input_element;
|
||||||
|
struct amount_sat input_amount;
|
||||||
|
|
||||||
|
/* Signature from the HSM. */
|
||||||
|
u8 *msg;
|
||||||
|
struct bitcoin_signature sig;
|
||||||
|
/* Witness we generate from the signature and other data. */
|
||||||
|
u8 **witness;
|
||||||
|
|
||||||
|
|
||||||
|
/* Get the single input of the penalty tx. */
|
||||||
|
input = &penalty_tx->wtx->inputs[0];
|
||||||
|
/* Extract the input-side data. */
|
||||||
|
bitcoin_tx_input_get_txid(penalty_tx, 0, &input_txid);
|
||||||
|
input_outnum = input->index;
|
||||||
|
input_wscript = tal_dup_arr(tmpctx, u8,
|
||||||
|
input->witness->items[2].witness,
|
||||||
|
input->witness->items[2].witness_len,
|
||||||
|
0);
|
||||||
|
input_element = tal_dup_arr(tmpctx, u8,
|
||||||
|
input->witness->items[1].witness,
|
||||||
|
input->witness->items[1].witness_len,
|
||||||
|
0);
|
||||||
|
input_amount = psbt_input_get_amount(penalty_tx->psbt, 0);
|
||||||
|
|
||||||
|
/* Create the replacement. */
|
||||||
|
tx = bitcoin_tx(ctx, chainparams, 1, 1, /*locktime*/ 0);
|
||||||
|
/* Reconstruct the input. */
|
||||||
|
bitcoin_tx_add_input(tx, &input_txid, input_outnum,
|
||||||
|
BITCOIN_TX_RBF_SEQUENCE,
|
||||||
|
NULL, input_amount, NULL, input_wscript);
|
||||||
|
/* Reconstruct the output with a smaller amount. */
|
||||||
|
if (amount_sat_greater(output_amount, dust_limit))
|
||||||
|
bitcoin_tx_add_output(tx,
|
||||||
|
scriptpubkey_p2wpkh(tx,
|
||||||
|
&our_wallet_pubkey),
|
||||||
|
NULL,
|
||||||
|
output_amount);
|
||||||
|
else
|
||||||
|
bitcoin_tx_add_output(tx,
|
||||||
|
scriptpubkey_opreturn_padded(tx),
|
||||||
|
NULL,
|
||||||
|
AMOUNT_SAT(0));
|
||||||
|
|
||||||
|
/* Finalize the transaction. */
|
||||||
|
bitcoin_tx_finalize(tx);
|
||||||
|
|
||||||
|
/* Ask HSM to sign it. */
|
||||||
|
if (!wire_sync_write(HSM_FD, take(hsm_sign_msg(NULL, tx,
|
||||||
|
input_wscript))))
|
||||||
|
status_failed(STATUS_FAIL_HSM_IO, "While feebumping penalty: writing sign request to hsm");
|
||||||
|
msg = wire_sync_read(tmpctx, HSM_FD);
|
||||||
|
if (!msg || !fromwire_hsmd_sign_tx_reply(msg, &sig))
|
||||||
|
status_failed(STATUS_FAIL_HSM_IO, "While feebumping penalty: reading sign_tx_reply: %s", tal_hex(tmpctx, msg));
|
||||||
|
|
||||||
|
/* Install the witness with the signature. */
|
||||||
|
witness = bitcoin_witness_sig_and_element(tx, &sig,
|
||||||
|
input_element,
|
||||||
|
tal_bytelen(input_element),
|
||||||
|
input_wscript);
|
||||||
|
bitcoin_tx_input_set_witness(tx, 0, take(witness));
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** min_rbf_bump
|
||||||
|
*
|
||||||
|
* @brief computes the minimum RBF bump required by
|
||||||
|
* BIP125, given an index.
|
||||||
|
*
|
||||||
|
* @desc BIP125 requires that an replacement transaction
|
||||||
|
* pay, not just the fee of the evicted transactions,
|
||||||
|
* but also the minimum relay fee for itself.
|
||||||
|
* This function assumes that previous RBF attempts
|
||||||
|
* paid exactly the return value for that attempt, on
|
||||||
|
* top of the initial transaction fee.
|
||||||
|
* It can serve as a baseline for other functions that
|
||||||
|
* compute a suggested fee: get whichever is higher,
|
||||||
|
* the fee this function suggests, or your own unique
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* This function is provided as a common function that
|
||||||
|
* all RBF-bump computations can use.
|
||||||
|
*
|
||||||
|
* @param weight - the weight of the transaction you
|
||||||
|
* are RBFing.
|
||||||
|
* @param index - 0 makes no sense, 1 means this is
|
||||||
|
* the first RBF attempt, 2 means this is the 2nd
|
||||||
|
* RBF attempt, etc.
|
||||||
|
*
|
||||||
|
* @return the suggested total fee.
|
||||||
|
*/
|
||||||
|
static struct amount_sat min_rbf_bump(size_t weight,
|
||||||
|
size_t index)
|
||||||
|
{
|
||||||
|
struct amount_sat min_relay_fee;
|
||||||
|
struct amount_sat min_rbf_bump;
|
||||||
|
|
||||||
|
/* Compute the minimum relay fee for a transaction of the given
|
||||||
|
* weight. */
|
||||||
|
min_relay_fee = amount_tx_fee(min_relay_feerate, weight);
|
||||||
|
|
||||||
|
/* For every RBF attempt, we add the min-relay-fee.
|
||||||
|
* Or in other words, we multiply the min-relay-fee by the
|
||||||
|
* index number of the attempt.
|
||||||
|
*/
|
||||||
|
if (mul_overflows_u64(index, min_relay_fee.satoshis)) /* Raw: multiplication. */
|
||||||
|
min_rbf_bump = AMOUNT_SAT(UINT64_MAX);
|
||||||
|
else
|
||||||
|
min_rbf_bump.satoshis = index * min_relay_fee.satoshis; /* Raw: multiplication. */
|
||||||
|
|
||||||
|
return min_rbf_bump;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** compute_penalty_output_amount
|
||||||
|
*
|
||||||
|
* @brief computes the appropriate output amount for a
|
||||||
|
* penalty transaction that spends a theft transaction
|
||||||
|
* that is already of a specific depth.
|
||||||
|
*
|
||||||
|
* @param initial_amount - the outout amount of the first
|
||||||
|
* penalty transaction.
|
||||||
|
* @param depth - the current depth of the theft
|
||||||
|
* transaction.
|
||||||
|
* @param max_depth - the maximum depth of the theft
|
||||||
|
* transaction, after which the theft transaction will
|
||||||
|
* succeed.
|
||||||
|
* @param weight - the weight of the first penalty
|
||||||
|
* transaction, in Sipa.
|
||||||
|
*/
|
||||||
|
static struct amount_sat
|
||||||
|
compute_penalty_output_amount(struct amount_sat initial_amount,
|
||||||
|
u32 depth, u32 max_depth,
|
||||||
|
size_t weight)
|
||||||
|
{
|
||||||
|
struct amount_sat max_output_amount;
|
||||||
|
struct amount_sat output_amount;
|
||||||
|
struct amount_sat deducted_amount;
|
||||||
|
|
||||||
|
assert(depth <= max_depth);
|
||||||
|
assert(depth > 0);
|
||||||
|
|
||||||
|
/* The difference between initial_amount, and the fee suggested
|
||||||
|
* by min_rbf_bump, is the largest allowed output amount.
|
||||||
|
*
|
||||||
|
* depth = 1 is the first attempt, thus maps to the 0th RBF
|
||||||
|
* (i.e. the initial attempt that is not RBFed itself).
|
||||||
|
* We actually start to replace at depth = 2, so we use
|
||||||
|
* depth - 1 as the index for min_rbf_bump.
|
||||||
|
*/
|
||||||
|
if (!amount_sat_sub(&max_output_amount,
|
||||||
|
initial_amount, min_rbf_bump(weight, depth - 1)))
|
||||||
|
/* If min_rbf_bump is larger than the initial_amount,
|
||||||
|
* we should just donate the whole output as fee,
|
||||||
|
* meaning we get 0 output amount.
|
||||||
|
*/
|
||||||
|
return AMOUNT_SAT(0);
|
||||||
|
|
||||||
|
/* Map the depth / max_depth into a number between 0->1. */
|
||||||
|
double x = (double) depth / (double) max_depth;
|
||||||
|
/* Get the cube of the above position, resulting in a graph
|
||||||
|
* where the y is close to 0 up to less than halfway through,
|
||||||
|
* then quickly rises up to 1 as depth nears the max depth.
|
||||||
|
*/
|
||||||
|
double y = x * x * x;
|
||||||
|
/* Scale according to the initial_amount. */
|
||||||
|
deducted_amount.satoshis = (u64) (y * initial_amount.satoshis); /* Raw: multiplication. */
|
||||||
|
|
||||||
|
/* output_amount = initial_amount - deducted_amount. */
|
||||||
|
if (!amount_sat_sub(&output_amount,
|
||||||
|
initial_amount, deducted_amount))
|
||||||
|
/* If underflow, force to 0. */
|
||||||
|
output_amount = AMOUNT_SAT(0);
|
||||||
|
|
||||||
|
/* If output exceeds max, return max. */
|
||||||
|
if (amount_sat_less(max_output_amount, output_amount))
|
||||||
|
return max_output_amount;
|
||||||
|
|
||||||
|
return output_amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void hsm_sign_local_htlc_tx(struct bitcoin_tx *tx,
|
static void hsm_sign_local_htlc_tx(struct bitcoin_tx *tx,
|
||||||
const u8 *wscript,
|
const u8 *wscript,
|
||||||
struct bitcoin_signature *sig)
|
struct bitcoin_signature *sig)
|
||||||
@ -824,8 +1043,110 @@ static enum wallet_tx_type onchain_txtype_to_wallet_txtype(enum tx_type t)
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** proposal_is_rbfable
|
||||||
|
*
|
||||||
|
* @brief returns true if the given proposal
|
||||||
|
* would be RBFed if the output it is tracking
|
||||||
|
* increases in depth without being spent.
|
||||||
|
*/
|
||||||
|
static bool proposal_is_rbfable(const struct proposed_resolution *proposal)
|
||||||
|
{
|
||||||
|
/* Future onchain resolutions, such as anchored commitments, might
|
||||||
|
* want to RBF as well.
|
||||||
|
*/
|
||||||
|
return proposal->tx_type == OUR_PENALTY_TX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** proposal_should_rbf
|
||||||
|
*
|
||||||
|
* @brief the given output just increased its depth,
|
||||||
|
* so the proposal for it should be RBFed and
|
||||||
|
* rebroadcast.
|
||||||
|
*
|
||||||
|
* @desc precondition: the given output must have an
|
||||||
|
* rbfable proposal as per `proposal_is_rbfable`.
|
||||||
|
*/
|
||||||
|
static void proposal_should_rbf(struct tracked_output *out, bool is_replay)
|
||||||
|
{
|
||||||
|
struct bitcoin_tx *tx = NULL;
|
||||||
|
u32 depth;
|
||||||
|
|
||||||
|
assert(out->proposal);
|
||||||
|
assert(proposal_is_rbfable(out->proposal));
|
||||||
|
|
||||||
|
depth = out->depth;
|
||||||
|
|
||||||
|
/* Do not RBF at depth 1.
|
||||||
|
*
|
||||||
|
* Since we react to *onchain* events, whatever proposal we made,
|
||||||
|
* the output for that proposal is already at depth 1.
|
||||||
|
*
|
||||||
|
* Since our initial proposal was broadcasted with the output at
|
||||||
|
* depth 1, we should not RBF until a new block arrives, which is
|
||||||
|
* at depth 2.
|
||||||
|
*/
|
||||||
|
if (depth <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (out->proposal->tx_type == OUR_PENALTY_TX) {
|
||||||
|
u32 max_depth = to_self_delay[REMOTE];
|
||||||
|
u32 my_depth = depth;
|
||||||
|
size_t weight = bitcoin_tx_weight(out->proposal->tx);
|
||||||
|
struct amount_sat initial_amount;
|
||||||
|
struct amount_sat new_amount;
|
||||||
|
|
||||||
|
if (max_depth >= 1)
|
||||||
|
max_depth -= 1;
|
||||||
|
if (my_depth >= max_depth)
|
||||||
|
my_depth = max_depth;
|
||||||
|
|
||||||
|
bitcoin_tx_output_get_amount_sat(out->proposal->tx, 0,
|
||||||
|
&initial_amount);
|
||||||
|
|
||||||
|
/* Compute the new output amount for the RBF. */
|
||||||
|
new_amount = compute_penalty_output_amount(initial_amount,
|
||||||
|
my_depth, max_depth,
|
||||||
|
weight);
|
||||||
|
assert(amount_sat_less_eq(new_amount, initial_amount));
|
||||||
|
/* Recreate the penalty tx. */
|
||||||
|
tx = replace_penalty_tx_to_us(tmpctx,
|
||||||
|
&penalty_to_us,
|
||||||
|
out->proposal->tx, new_amount);
|
||||||
|
|
||||||
|
status_debug("Created RBF OUR_PENALTY_TX with output %s "
|
||||||
|
"(originally %s) for depth %"PRIu32"/%"PRIu32".",
|
||||||
|
type_to_string(tmpctx, struct amount_sat,
|
||||||
|
&new_amount),
|
||||||
|
type_to_string(tmpctx, struct amount_sat,
|
||||||
|
&initial_amount),
|
||||||
|
depth, to_self_delay[LOCAL]);
|
||||||
|
}
|
||||||
|
/* Add other RBF-able proposals here. */
|
||||||
|
|
||||||
|
/* Broadcast the transaction. */
|
||||||
|
if (tx) {
|
||||||
|
enum wallet_tx_type wtt;
|
||||||
|
|
||||||
|
status_debug("Broadcasting RBF %s (%s) to resolve %s/%s "
|
||||||
|
"depth=%"PRIu32"",
|
||||||
|
tx_type_name(out->proposal->tx_type),
|
||||||
|
type_to_string(tmpctx, struct bitcoin_tx, tx),
|
||||||
|
tx_type_name(out->tx_type),
|
||||||
|
output_type_name(out->output_type),
|
||||||
|
depth);
|
||||||
|
|
||||||
|
wtt = onchain_txtype_to_wallet_txtype(out->proposal->tx_type);
|
||||||
|
wire_sync_write(REQ_FD,
|
||||||
|
take(towire_onchaind_broadcast_tx(NULL, tx,
|
||||||
|
wtt,
|
||||||
|
true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void proposal_meets_depth(struct tracked_output *out, bool is_replay)
|
static void proposal_meets_depth(struct tracked_output *out, bool is_replay)
|
||||||
{
|
{
|
||||||
|
bool is_rbf = false;
|
||||||
|
|
||||||
/* If we simply wanted to ignore it after some depth */
|
/* If we simply wanted to ignore it after some depth */
|
||||||
if (!out->proposal->tx) {
|
if (!out->proposal->tx) {
|
||||||
ignore_output(out);
|
ignore_output(out);
|
||||||
@ -838,12 +1159,16 @@ static void proposal_meets_depth(struct tracked_output *out, bool is_replay)
|
|||||||
tx_type_name(out->tx_type),
|
tx_type_name(out->tx_type),
|
||||||
output_type_name(out->output_type));
|
output_type_name(out->output_type));
|
||||||
|
|
||||||
|
if (out->proposal)
|
||||||
|
/* Our own penalty transactions are going to be RBFed. */
|
||||||
|
is_rbf = proposal_is_rbfable(out->proposal);
|
||||||
|
|
||||||
wire_sync_write(
|
wire_sync_write(
|
||||||
REQ_FD,
|
REQ_FD,
|
||||||
take(towire_onchaind_broadcast_tx(
|
take(towire_onchaind_broadcast_tx(
|
||||||
NULL, out->proposal->tx,
|
NULL, out->proposal->tx,
|
||||||
onchain_txtype_to_wallet_txtype(out->proposal->tx_type),
|
onchain_txtype_to_wallet_txtype(out->proposal->tx_type),
|
||||||
false)));
|
is_rbf)));
|
||||||
|
|
||||||
/* Don't wait for this if we're ignoring the tiny payment. */
|
/* Don't wait for this if we're ignoring the tiny payment. */
|
||||||
if (out->proposal->tx_type == IGNORING_TINY_PAYMENT) {
|
if (out->proposal->tx_type == IGNORING_TINY_PAYMENT) {
|
||||||
@ -1361,7 +1686,7 @@ static void steal_htlc_tx(struct tracked_output *out,
|
|||||||
* `<revocationsig> 1`
|
* `<revocationsig> 1`
|
||||||
*/
|
*/
|
||||||
tx = tx_to_us(htlc_out, penalty_to_us, htlc_out,
|
tx = tx_to_us(htlc_out, penalty_to_us, htlc_out,
|
||||||
0xFFFFFFFF, 0,
|
BITCOIN_TX_RBF_SEQUENCE, 0,
|
||||||
&ONE, sizeof(ONE),
|
&ONE, sizeof(ONE),
|
||||||
htlc_out->wscript,
|
htlc_out->wscript,
|
||||||
&tx_type, penalty_feerate);
|
&tx_type, penalty_feerate);
|
||||||
@ -1616,6 +1941,13 @@ static void tx_new_depth(struct tracked_output **outs,
|
|||||||
&& depth >= outs[i]->proposal->depth_required) {
|
&& depth >= outs[i]->proposal->depth_required) {
|
||||||
proposal_meets_depth(outs[i], is_replay);
|
proposal_meets_depth(outs[i], is_replay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Otherwise, is this an output whose proposed resolution
|
||||||
|
* we should RBF? */
|
||||||
|
if (outs[i]->proposal
|
||||||
|
&& bitcoin_txid_eq(&outs[i]->txid, txid)
|
||||||
|
&& proposal_is_rbfable(outs[i]->proposal))
|
||||||
|
proposal_should_rbf(outs[i], is_replay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2502,8 +2834,8 @@ static void steal_to_them_output(struct tracked_output *out, bool is_replay)
|
|||||||
&keyset->self_revocation_key,
|
&keyset->self_revocation_key,
|
||||||
&keyset->self_delayed_payment_key);
|
&keyset->self_delayed_payment_key);
|
||||||
|
|
||||||
tx = tx_to_us(tmpctx, penalty_to_us, out, 0xFFFFFFFF, 0, &ONE,
|
tx = tx_to_us(tmpctx, penalty_to_us, out, BITCOIN_TX_RBF_SEQUENCE, 0,
|
||||||
sizeof(ONE), wscript, &tx_type, penalty_feerate);
|
&ONE, sizeof(ONE), wscript, &tx_type, penalty_feerate);
|
||||||
|
|
||||||
propose_resolution(out, tx, 0, tx_type, is_replay);
|
propose_resolution(out, tx, 0, tx_type, is_replay);
|
||||||
}
|
}
|
||||||
@ -2522,8 +2854,9 @@ static void steal_htlc(struct tracked_output *out, bool is_replay)
|
|||||||
* <revocation_sig> <revocationpubkey>
|
* <revocation_sig> <revocationpubkey>
|
||||||
*/
|
*/
|
||||||
pubkey_to_der(der, &keyset->self_revocation_key);
|
pubkey_to_der(der, &keyset->self_revocation_key);
|
||||||
tx = tx_to_us(out, penalty_to_us, out, 0xFFFFFFFF, 0, der, sizeof(der),
|
tx = tx_to_us(out, penalty_to_us, out, BITCOIN_TX_RBF_SEQUENCE, 0,
|
||||||
out->wscript, &tx_type, penalty_feerate);
|
der, sizeof(der), out->wscript, &tx_type,
|
||||||
|
penalty_feerate);
|
||||||
|
|
||||||
propose_resolution(out, tx, 0, tx_type, is_replay);
|
propose_resolution(out, tx, 0, tx_type, is_replay);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user