plugins/bcli: use -stdin to feed arguments, in case we have a giant tx.

```
lightningd-1 2025-10-27T11:26:04.285Z **BROKEN** plugin-bcli: bitcoin-cli exec failed: Argument list too long
```

Use -stdin to bitcoin-cli: we can then handle arguments of arbitrary length.

Fixes: https://github.com/ElementsProject/lightning/issues/8634
Changelog-Fixed: plugins: `bcli` would fail with "Argument list too long" when sending a giant tx.
This commit is contained in:
Rusty Russell
2025-11-03 13:04:51 +10:30
parent 5d64eb9ac4
commit 43d7db7581
3 changed files with 34 additions and 16 deletions

View File

@@ -76,6 +76,7 @@ struct bitcoin_cli {
int *exitstatus;
pid_t pid;
const char **args;
const char **stdinargs;
struct timeabs start;
enum bitcoind_prio prio;
char *output;
@@ -95,7 +96,8 @@ static void add_arg(const char ***args, const char *arg TAKES)
tal_arr_expand(args, arg);
}
static const char **gather_argsv(const tal_t *ctx, const char *cmd, va_list ap)
/* If stdinargs is non-NULL, that is where we put additional args */
static const char **gather_argsv(const tal_t *ctx, const char ***stdinargs, const char *cmd, va_list ap)
{
const char **args = tal_arr(ctx, const char *, 1);
const char *arg;
@@ -128,23 +130,30 @@ static const char **gather_argsv(const tal_t *ctx, const char *cmd, va_list ap)
// `-rpcpassword` argument - secrets in arguments can leak when listing
// system processes.
add_arg(&args, "-stdinrpcpass");
/* To avoid giant command lines, we use -stdin (avail since bitcoin 0.13) */
if (stdinargs)
add_arg(&args, "-stdin");
add_arg(&args, cmd);
while ((arg = va_arg(ap, char *)) != NULL)
add_arg(&args, arg);
while ((arg = va_arg(ap, char *)) != NULL) {
if (stdinargs)
add_arg(stdinargs, arg);
else
add_arg(&args, arg);
}
add_arg(&args, NULL);
return args;
}
static LAST_ARG_NULL const char **
gather_args(const tal_t *ctx, const char *cmd, ...)
gather_args(const tal_t *ctx, const char ***stdinargs, const char *cmd, ...)
{
va_list ap;
const char **ret;
va_start(ap, cmd);
ret = gather_argsv(ctx, cmd, ap);
ret = gather_argsv(ctx, stdinargs, cmd, ap);
va_end(ap);
return ret;
@@ -170,7 +179,7 @@ static struct io_plan *output_init(struct io_conn *conn, struct bitcoin_cli *bcl
static void next_bcli(enum bitcoind_prio prio);
/* For printing: simple string of args (no secrets!) */
static char *args_string(const tal_t *ctx, const char **args)
static char *args_string(const tal_t *ctx, const char **args, const char **stdinargs)
{
size_t i;
char *ret = tal_strdup(ctx, args[0]);
@@ -185,12 +194,16 @@ static char *args_string(const tal_t *ctx, const char **args)
ret = tal_strcat(ctx, take(ret), args[i]);
}
}
for (i = 0; i < tal_count(stdinargs); i++) {
ret = tal_strcat(ctx, take(ret), " ");
ret = tal_strcat(ctx, take(ret), stdinargs[i]);
}
return ret;
}
static char *bcli_args(const tal_t *ctx, struct bitcoin_cli *bcli)
{
return args_string(ctx, bcli->args);
return args_string(ctx, bcli->args, bcli->stdinargs);
}
/* Only set as destructor once bcli is in current. */
@@ -313,9 +326,14 @@ static void next_bcli(enum bitcoind_prio prio)
bcli->args[0], strerror(errno));
if (bitcoind->rpcpass)
if (bitcoind->rpcpass) {
write_all(in, bitcoind->rpcpass, strlen(bitcoind->rpcpass));
write_all(in, "\n", strlen("\n"));
}
for (size_t i = 0; i < tal_count(bcli->stdinargs); i++) {
write_all(in, bcli->stdinargs[i], strlen(bcli->stdinargs[i]));
write_all(in, "\n", strlen("\n"));
}
close(in);
bcli->start = time_now();
@@ -351,7 +369,8 @@ start_bitcoin_cliv(const tal_t *ctx,
else
bcli->exitstatus = NULL;
bcli->args = gather_argsv(bcli, method, ap);
bcli->stdinargs = tal_arr(bcli, const char *, 0);
bcli->args = gather_argsv(bcli, &bcli->stdinargs, method, ap);
bcli->stash = stash;
list_add_tail(&bitcoind->pending[bcli->prio], &bcli->list);
@@ -994,14 +1013,14 @@ static struct command_result *getutxout(struct command *cmd,
static void bitcoind_failure(struct plugin *p, const char *error_message)
{
const char **cmd = gather_args(bitcoind, "echo", NULL);
const char **cmd = gather_args(bitcoind, NULL, "echo", NULL);
plugin_err(p, "\n%s\n\n"
"Make sure you have bitcoind running and that bitcoin-cli"
" is able to connect to bitcoind.\n\n"
"You can verify that your Bitcoin Core installation is"
" ready for use by running:\n\n"
" $ %s 'hello world'\n", error_message,
args_string(cmd, cmd));
args_string(cmd, cmd, NULL));
}
/* Do some sanity checks on bitcoind based on the output of `getnetworkinfo`. */
@@ -1016,7 +1035,7 @@ static void parse_getnetworkinfo_result(struct plugin *p, const char *buf)
if (!result)
plugin_err(p, "Invalid response to '%s': '%s'. Can not "
"continue without proceeding to sanity checks.",
args_string(tmpctx, gather_args(bitcoind, "getnetworkinfo", NULL)),
args_string(tmpctx, gather_args(bitcoind, NULL, "getnetworkinfo", NULL), NULL),
buf);
/* Check that we have a fully-featured `estimatesmartfee`. */
@@ -1047,7 +1066,7 @@ static void wait_and_check_bitcoind(struct plugin *p)
int in, from, status;
pid_t child;
const char **cmd = gather_args(
bitcoind, "-rpcwait", "-rpcwaittimeout=30", "getnetworkinfo", NULL);
bitcoind, NULL, "-rpcwait", "-rpcwaittimeout=30", "getnetworkinfo", NULL);
char *output = NULL;
child = pipecmdarr(&in, &from, &from, cast_const2(char **, cmd));