diff --git a/doc/PLUGINS.md b/doc/PLUGINS.md index cbf59b80875f..32f6f9050372 100644 --- a/doc/PLUGINS.md +++ b/doc/PLUGINS.md @@ -1155,8 +1155,11 @@ The plugin must respond to `gettxout` with the following fields: ### `sendrawtransaction` -This call takes one parameter, a string representing a hex-encoded Bitcoin -transaction. +This call takes two parameters, +a string `tx` representing a hex-encoded Bitcoin transaction, +and a boolean `allowhighfees`, which if set means suppress +any high-fees check implemented in the backend, since the given +transaction may have fees that are very high. The plugin must broadcast it and respond with the following fields: - `success` (boolean), which is `true` if the broadcast succeeded diff --git a/lightningd/bitcoind.c b/lightningd/bitcoind.c index 2a4b28a1887b..111813558fe2 100644 --- a/lightningd/bitcoind.c +++ b/lightningd/bitcoind.c @@ -293,11 +293,12 @@ static void sendrawtx_callback(const char *buf, const jsmntok_t *toks, tal_free(call); } -void bitcoind_sendrawtx_(struct bitcoind *bitcoind, - const char *hextx, - void (*cb)(struct bitcoind *bitcoind, - bool success, const char *err_msg, void *), - void *cb_arg) +void bitcoind_sendrawtx_ahf_(struct bitcoind *bitcoind, + const char *hextx, + bool allowhighfees, + void (*cb)(struct bitcoind *bitcoind, + bool success, const char *msg, void *), + void *cb_arg) { struct jsonrpc_request *req; struct sendrawtx_call *call = tal(bitcoind, struct sendrawtx_call); @@ -311,10 +312,20 @@ void bitcoind_sendrawtx_(struct bitcoind *bitcoind, bitcoind->log, sendrawtx_callback, call); json_add_string(req->stream, "tx", hextx); + json_add_bool(req->stream, "allowhighfees", allowhighfees); jsonrpc_request_end(req); bitcoin_plugin_send(bitcoind, req); } +void bitcoind_sendrawtx_(struct bitcoind *bitcoind, + const char *hextx, + void (*cb)(struct bitcoind *bitcoind, + bool success, const char *msg, void *), + void *arg) +{ + return bitcoind_sendrawtx_ahf_(bitcoind, hextx, false, cb, arg); +} + /* `getrawblockbyheight` * * If no block were found at that height, will set each field to `null`. diff --git a/lightningd/bitcoind.h b/lightningd/bitcoind.h index 1bc105120b5e..8fb275e342cf 100644 --- a/lightningd/bitcoind.h +++ b/lightningd/bitcoind.h @@ -74,6 +74,21 @@ void bitcoind_estimate_fees_(struct bitcoind *bitcoind, const u32 *), \ (arg)) +void bitcoind_sendrawtx_ahf_(struct bitcoind *bitcoind, + const char *hextx, + bool allowhighfees, + void (*cb)(struct bitcoind *bitcoind, + bool success, const char *msg, void *), + void *arg); +#define bitcoind_sendrawtx_ahf(bitcoind_, hextx, allowhighfees, cb, arg)\ + bitcoind_sendrawtx_ahf_((bitcoind_), (hextx), \ + (allowhighfees), \ + typesafe_cb_preargs(void, void *, \ + (cb), (arg), \ + struct bitcoind *, \ + bool, const char *),\ + (arg)) + void bitcoind_sendrawtx_(struct bitcoind *bitcoind, const char *hextx, void (*cb)(struct bitcoind *bitcoind, diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c index 5eb72591734b..a86dc2cec2d4 100644 --- a/lightningd/chaintopology.c +++ b/lightningd/chaintopology.c @@ -216,10 +216,12 @@ static void broadcast_done(struct bitcoind *bitcoind, } } -void broadcast_tx(struct chain_topology *topo, - struct channel *channel, const struct bitcoin_tx *tx, - void (*failed_or_success)(struct channel *channel, - bool success, const char *err)) +void broadcast_tx_ahf(struct chain_topology *topo, + struct channel *channel, const struct bitcoin_tx *tx, + bool allowhighfees, + void (*failed)(struct channel *channel, + bool success, + const char *err)) { /* Channel might vanish: topo owns it to start with. */ struct outgoing_tx *otx = tal(topo, struct outgoing_tx); @@ -228,7 +230,7 @@ void broadcast_tx(struct chain_topology *topo, otx->channel = channel; bitcoin_txid(tx, &otx->txid); otx->hextx = tal_hex(otx, rawtx); - otx->failed_or_success = failed_or_success; + otx->failed_or_success = failed; tal_free(rawtx); tal_add_destructor2(channel, clear_otx_channel, otx); @@ -236,8 +238,18 @@ void broadcast_tx(struct chain_topology *topo, type_to_string(tmpctx, struct bitcoin_txid, &otx->txid)); wallet_transaction_add(topo->ld->wallet, tx, 0, 0); - bitcoind_sendrawtx(topo->bitcoind, otx->hextx, broadcast_done, otx); + bitcoind_sendrawtx_ahf(topo->bitcoind, otx->hextx, allowhighfees, + broadcast_done, otx); } +void broadcast_tx(struct chain_topology *topo, + struct channel *channel, const struct bitcoin_tx *tx, + void (*failed)(struct channel *channel, + bool success, + const char *err)) +{ + return broadcast_tx_ahf(topo, channel, tx, false, failed); +} + static enum watch_result closeinfo_txid_confirmed(struct lightningd *ld, struct channel *channel, diff --git a/lightningd/chaintopology.h b/lightningd/chaintopology.h index 4f948b15fd36..5eb047f80c49 100644 --- a/lightningd/chaintopology.h +++ b/lightningd/chaintopology.h @@ -173,6 +173,14 @@ void broadcast_tx(struct chain_topology *topo, void (*failed)(struct channel *channel, bool success, const char *err)); +/* Like the above, but with an additional `allowhighfees` parameter. + * If true, suppress any high-fee checks in the backend. */ +void broadcast_tx_ahf(struct chain_topology *topo, + struct channel *channel, const struct bitcoin_tx *tx, + bool allowhighfees, + void (*failed)(struct channel *channel, + bool success, + const char *err)); struct chain_topology *new_topology(struct lightningd *ld, struct log *log); void setup_topology(struct chain_topology *topology, struct timers *timers, diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index c8c540759b11..024aceb5a45b 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -284,8 +284,12 @@ static void handle_onchain_broadcast_tx(struct channel *channel, wallet_transaction_annotate(w, &txid, type, channel->dbid); /* We don't really care if it fails, we'll respond via watch. */ - broadcast_tx(channel->peer->ld->topology, channel, tx, - is_rbf ? &handle_onchain_broadcast_rbf_tx_cb : NULL); + /* If the onchaind signals this as RBF-able, then we also + * set allowhighfees, as the transaction may be RBFed into + * high feerates as protection against the MAD-HTLC attack. */ + broadcast_tx_ahf(channel->peer->ld->topology, channel, + tx, is_rbf, + is_rbf ? &handle_onchain_broadcast_rbf_tx_cb : NULL); } static void handle_onchain_unwatch_tx(struct channel *channel, const u8 *msg) diff --git a/plugins/bcli.c b/plugins/bcli.c index e3936a359558..f6c1c279532e 100644 --- a/plugins/bcli.c +++ b/plugins/bcli.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,30 @@ struct bitcoin_cli { void *stash; }; +static u32 bitcoind_version = 0; +static void parse_getnetworkinfo_result(const char *buf) +{ + const jsmntok_t *result; + const jsmntok_t *version; + bool valid; + + result = json_parse_input(NULL, + buf, strlen(buf), + &valid); + if (!result || !valid) + goto end; + + version = json_get_member(buf, result, "version"); + if (!version) + goto end; + + if (!json_to_u32(buf, version, &bitcoind_version)) + bitcoind_version = 0; + +end: + tal_free(result); +} + /* Add the n'th arg to *args, incrementing n and keeping args of size n+1 */ static void add_arg(const char ***args, const char *arg) { @@ -790,13 +815,29 @@ static struct command_result *sendrawtransaction(struct command *cmd, const jsmntok_t *toks) { const char **params = tal_arr(cmd, const char *, 1); + bool *allowhighfees; /* bitcoin-cli wants strings. */ if (!param(cmd, buf, toks, p_req("tx", param_string, ¶ms[0]), + p_req("allowhighfees", param_bool, &allowhighfees), NULL)) return command_param_failed(); + if (*allowhighfees) { + if (bitcoind_version >= 190001) + /* Starting in 19.0.1, second argument is + * maxfeerate, which when set to 0 means + * no max feerate. + */ + tal_arr_expand(¶ms, "0"); + else + /* in older versions, second arg is allowhighfees, + * set to 1 to allow high fees. + */ + tal_arr_expand(¶ms, "1"); + } + start_bitcoin_cli(NULL, cmd, process_sendrawtransaction, true, BITCOIND_HIGH_PRIO, "sendrawtransaction", params, NULL); @@ -838,10 +879,13 @@ static void wait_for_bitcoind(struct plugin *p) { int from, status, ret; pid_t child; - const char **cmd = gather_args(bitcoind, "echo", NULL); + const char **cmd = gather_args(bitcoind, "getnetworkinfo", NULL); + char *output = NULL; bool printed = false; for (;;) { + output = tal_free(output); + child = pipecmdarr(NULL, &from, &from, cast_const2(char **,cmd)); if (child < 0) { if (errno == ENOENT) @@ -850,7 +894,7 @@ static void wait_for_bitcoind(struct plugin *p) plugin_err(p, "%s exec failed: %s", cmd[0], strerror(errno)); } - char *output = grab_fd(cmd, from); + output = grab_fd(cmd, from); while ((ret = waitpid(child, &status, 0)) < 0 && errno == EINTR); if (ret != child) @@ -881,6 +925,9 @@ static void wait_for_bitcoind(struct plugin *p) } sleep(1); } + + parse_getnetworkinfo_result(output); + tal_free(cmd); }