Skip to content

Commit 7ee7407

Browse files
committed
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: #8634 Changelog-Fixed: plugins: `bcli` would fail with "Argument list too long" when sending a giant tx.
1 parent 0684503 commit 7ee7407

File tree

3 files changed

+34
-16
lines changed

3 files changed

+34
-16
lines changed

plugins/bcli.c

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ struct bitcoin_cli {
7676
int *exitstatus;
7777
pid_t pid;
7878
const char **args;
79+
const char **stdinargs;
7980
struct timeabs start;
8081
enum bitcoind_prio prio;
8182
char *output;
@@ -95,7 +96,8 @@ static void add_arg(const char ***args, const char *arg TAKES)
9596
tal_arr_expand(args, arg);
9697
}
9798

98-
static const char **gather_argsv(const tal_t *ctx, const char *cmd, va_list ap)
99+
/* If stdinargs is non-NULL, that is where we put additional args */
100+
static const char **gather_argsv(const tal_t *ctx, const char ***stdinargs, const char *cmd, va_list ap)
99101
{
100102
const char **args = tal_arr(ctx, const char *, 1);
101103
const char *arg;
@@ -128,23 +130,30 @@ static const char **gather_argsv(const tal_t *ctx, const char *cmd, va_list ap)
128130
// `-rpcpassword` argument - secrets in arguments can leak when listing
129131
// system processes.
130132
add_arg(&args, "-stdinrpcpass");
133+
/* To avoid giant command lines, we use -stdin (avail since bitcoin 0.13) */
134+
if (stdinargs)
135+
add_arg(&args, "-stdin");
131136

132137
add_arg(&args, cmd);
133-
while ((arg = va_arg(ap, char *)) != NULL)
134-
add_arg(&args, arg);
138+
while ((arg = va_arg(ap, char *)) != NULL) {
139+
if (stdinargs)
140+
add_arg(stdinargs, arg);
141+
else
142+
add_arg(&args, arg);
143+
}
135144
add_arg(&args, NULL);
136145

137146
return args;
138147
}
139148

140149
static LAST_ARG_NULL const char **
141-
gather_args(const tal_t *ctx, const char *cmd, ...)
150+
gather_args(const tal_t *ctx, const char ***stdinargs, const char *cmd, ...)
142151
{
143152
va_list ap;
144153
const char **ret;
145154

146155
va_start(ap, cmd);
147-
ret = gather_argsv(ctx, cmd, ap);
156+
ret = gather_argsv(ctx, stdinargs, cmd, ap);
148157
va_end(ap);
149158

150159
return ret;
@@ -170,7 +179,7 @@ static struct io_plan *output_init(struct io_conn *conn, struct bitcoin_cli *bcl
170179
static void next_bcli(enum bitcoind_prio prio);
171180

172181
/* For printing: simple string of args (no secrets!) */
173-
static char *args_string(const tal_t *ctx, const char **args)
182+
static char *args_string(const tal_t *ctx, const char **args, const char **stdinargs)
174183
{
175184
size_t i;
176185
char *ret = tal_strdup(ctx, args[0]);
@@ -185,12 +194,16 @@ static char *args_string(const tal_t *ctx, const char **args)
185194
ret = tal_strcat(ctx, take(ret), args[i]);
186195
}
187196
}
197+
for (i = 0; i < tal_count(stdinargs); i++) {
198+
ret = tal_strcat(ctx, take(ret), " ");
199+
ret = tal_strcat(ctx, take(ret), stdinargs[i]);
200+
}
188201
return ret;
189202
}
190203

191204
static char *bcli_args(const tal_t *ctx, struct bitcoin_cli *bcli)
192205
{
193-
return args_string(ctx, bcli->args);
206+
return args_string(ctx, bcli->args, bcli->stdinargs);
194207
}
195208

196209
/* Only set as destructor once bcli is in current. */
@@ -313,9 +326,14 @@ static void next_bcli(enum bitcoind_prio prio)
313326
bcli->args[0], strerror(errno));
314327

315328

316-
if (bitcoind->rpcpass)
329+
if (bitcoind->rpcpass) {
317330
write_all(in, bitcoind->rpcpass, strlen(bitcoind->rpcpass));
318-
331+
write_all(in, "\n", strlen("\n"));
332+
}
333+
for (size_t i = 0; i < tal_count(bcli->stdinargs); i++) {
334+
write_all(in, bcli->stdinargs[i], strlen(bcli->stdinargs[i]));
335+
write_all(in, "\n", strlen("\n"));
336+
}
319337
close(in);
320338

321339
bcli->start = time_now();
@@ -351,7 +369,8 @@ start_bitcoin_cliv(const tal_t *ctx,
351369
else
352370
bcli->exitstatus = NULL;
353371

354-
bcli->args = gather_argsv(bcli, method, ap);
372+
bcli->stdinargs = tal_arr(bcli, const char *, 0);
373+
bcli->args = gather_argsv(bcli, &bcli->stdinargs, method, ap);
355374
bcli->stash = stash;
356375

357376
list_add_tail(&bitcoind->pending[bcli->prio], &bcli->list);
@@ -994,14 +1013,14 @@ static struct command_result *getutxout(struct command *cmd,
9941013

9951014
static void bitcoind_failure(struct plugin *p, const char *error_message)
9961015
{
997-
const char **cmd = gather_args(bitcoind, "echo", NULL);
1016+
const char **cmd = gather_args(bitcoind, NULL, "echo", NULL);
9981017
plugin_err(p, "\n%s\n\n"
9991018
"Make sure you have bitcoind running and that bitcoin-cli"
10001019
" is able to connect to bitcoind.\n\n"
10011020
"You can verify that your Bitcoin Core installation is"
10021021
" ready for use by running:\n\n"
10031022
" $ %s 'hello world'\n", error_message,
1004-
args_string(cmd, cmd));
1023+
args_string(cmd, cmd, NULL));
10051024
}
10061025

10071026
/* 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)
10161035
if (!result)
10171036
plugin_err(p, "Invalid response to '%s': '%s'. Can not "
10181037
"continue without proceeding to sanity checks.",
1019-
args_string(tmpctx, gather_args(bitcoind, "getnetworkinfo", NULL)),
1038+
args_string(tmpctx, gather_args(bitcoind, NULL, "getnetworkinfo", NULL), NULL),
10201039
buf);
10211040

10221041
/* Check that we have a fully-featured `estimatesmartfee`. */
@@ -1047,7 +1066,7 @@ static void wait_and_check_bitcoind(struct plugin *p)
10471066
int in, from, status;
10481067
pid_t child;
10491068
const char **cmd = gather_args(
1050-
bitcoind, "-rpcwait", "-rpcwaittimeout=30", "getnetworkinfo", NULL);
1069+
bitcoind, NULL, "-rpcwait", "-rpcwaittimeout=30", "getnetworkinfo", NULL);
10511070
char *output = NULL;
10521071

10531072
child = pipecmdarr(&in, &from, &from, cast_const2(char **, cmd));

tests/test_misc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2186,7 +2186,7 @@ def test_bitcoind_fail_first(node_factory, bitcoind):
21862186
# first.
21872187
timeout = 5 if 5 < TIMEOUT // 3 else TIMEOUT // 3
21882188
l1 = node_factory.get_node(start=False,
2189-
broken_log=r'plugin-bcli: .*(-stdinrpcpass getblockhash 100 exited 1 \(after [0-9]* other errors\)|we have been retrying command for)',
2189+
broken_log=r'plugin-bcli: .*(-stdinrpcpass -stdin getblockhash 100 exited 1 \(after [0-9]* other errors\)|we have been retrying command for)',
21902190
may_fail=True,
21912191
options={'bitcoin-retry-timeout': timeout})
21922192

tests/test_plugin.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1875,7 +1875,6 @@ def test_bitcoin_backend(node_factory, bitcoind):
18751875
" bitcoind")
18761876

18771877

1878-
@pytest.mark.xfail(strict=True)
18791878
def test_bitcoin_backend_gianttx(node_factory, bitcoind):
18801879
"""Test that a giant tx doesn't crash bcli"""
18811880
l1 = node_factory.get_node(start=False)

0 commit comments

Comments
 (0)