1+ import json
12import os
23import re
34import sys
@@ -25,7 +26,7 @@ def bitcoin():
2526@click .argument ("method" , type = str )
2627@click .argument ("params" , type = str , nargs = - 1 ) # this will capture all remaining arguments
2728@click .option ("--namespace" , default = None , show_default = True )
28- def rpc (tank : str , method : str , params : str , namespace : Optional [str ]):
29+ def rpc (tank : str , method : str , params : list [ str ] , namespace : Optional [str ]):
2930 """
3031 Call bitcoin-cli <method> [params] on <tank pod name>
3132 """
@@ -37,12 +38,49 @@ def rpc(tank: str, method: str, params: str, namespace: Optional[str]):
3738 print (result )
3839
3940
40- def _rpc (tank : str , method : str , params : str , namespace : Optional [str ] = None ):
41+ def _rpc (tank : str , method : str , params : list [ str ] , namespace : Optional [str ] = None ):
4142 # bitcoin-cli should be able to read bitcoin.conf inside the container
4243 # so no extra args like port, chain, username or password are needed
4344 namespace = get_default_namespace_or (namespace )
44- if params :
45- cmd = f"kubectl -n { namespace } exec { tank } --container { BITCOINCORE_CONTAINER } -- bitcoin-cli { method } { ' ' .join (map (str , params ))} "
45+
46+ # Reconstruct JSON parameters that may have been split by shell parsing
47+ # This fixes issues where JSON arrays like ["network"] get split into separate arguments
48+ reconstructed_params = _reconstruct_json_params (params )
49+
50+ if reconstructed_params :
51+ # Process each parameter to handle different data types correctly for bitcoin-cli
52+ processed_params = []
53+ for param in reconstructed_params :
54+ # Handle boolean and primitive values that should not be quoted
55+ if param .lower () in ["true" , "false" , "null" ]:
56+ processed_params .append (param .lower ())
57+ elif param .isdigit () or (param .startswith ("-" ) and param [1 :].isdigit ()):
58+ # Numeric values (integers, negative numbers)
59+ processed_params .append (param )
60+ else :
61+ try :
62+ # Try to parse as JSON to handle complex data structures
63+ parsed_json = json .loads (param )
64+ if isinstance (parsed_json , list ):
65+ # If it's a list, extract the elements and add them individually
66+ # This ensures bitcoin-cli receives each list element as a separate argument
67+ for element in parsed_json :
68+ if isinstance (element , str ):
69+ processed_params .append (f'"{ element } "' )
70+ else :
71+ processed_params .append (str (element ))
72+ elif isinstance (parsed_json , dict ):
73+ # If it's a dict, pass it as a single JSON argument
74+ # bitcoin-cli expects objects to be passed as JSON strings
75+ processed_params .append (param )
76+ else :
77+ # If it's a primitive value (number, boolean), pass it as-is
78+ processed_params .append (str (parsed_json ))
79+ except json .JSONDecodeError :
80+ # Not valid JSON, pass as-is (treat as plain string)
81+ processed_params .append (param )
82+
83+ cmd = f"kubectl -n { namespace } exec { tank } --container { BITCOINCORE_CONTAINER } -- bitcoin-cli { method } { ' ' .join (map (str , processed_params ))} "
4684 else :
4785 cmd = f"kubectl -n { namespace } exec { tank } --container { BITCOINCORE_CONTAINER } -- bitcoin-cli { method } "
4886 return run_command (cmd )
@@ -346,3 +384,96 @@ def to_jsonable(obj: str):
346384 return obj .hex ()
347385 else :
348386 return obj
387+
388+
389+ def _reconstruct_json_params (params : list [str ]) -> list [str ]:
390+ """
391+ Reconstruct JSON parameters that may have been split by shell parsing.
392+
393+ This function detects when parameters look like they should be JSON and
394+ reconstructs them properly. For example:
395+ - ['[network]'] -> ['["network"]']
396+ - ['[network,', 'message_type]'] -> ['["network", "message_type"]']
397+ - ['[{"key":', '"value"}]'] -> ['[{"key": "value"}]']
398+
399+ This fixes the issue described in GitHub issue #714 where shell parsing
400+ breaks JSON parameters into separate arguments.
401+ """
402+ if not params :
403+ return params
404+
405+ reconstructed = []
406+ i = 0
407+
408+ while i < len (params ):
409+ param = params [i ]
410+
411+ # Check if this looks like the start of a JSON array or object
412+ # that was split across multiple arguments by shell parsing
413+ if (param .startswith ("[" ) and not param .endswith ("]" )) or (
414+ param .startswith ("{" ) and not param .endswith ("}" )
415+ ):
416+ # This is the start of a JSON structure, collect all parts
417+ json_parts = [param ]
418+ i += 1
419+
420+ # Collect all parts until we find the closing bracket/brace
421+ while i < len (params ):
422+ next_param = params [i ]
423+ json_parts .append (next_param )
424+
425+ if (param .startswith ("[" ) and next_param .endswith ("]" )) or (
426+ param .startswith ("{" ) and next_param .endswith ("}" )
427+ ):
428+ break
429+ i += 1
430+
431+ # Reconstruct the JSON string by joining all parts
432+ json_str = " " .join (json_parts )
433+
434+ # Validate that it's valid JSON before adding
435+ try :
436+ json .loads (json_str )
437+ reconstructed .append (json_str )
438+ except json .JSONDecodeError :
439+ # If it's not valid JSON, add parts as separate parameters
440+ # This preserves the original behavior for non-JSON arguments
441+ reconstructed .extend (json_parts )
442+
443+ elif param .startswith ("[" ) and param .endswith ("]" ):
444+ # Single parameter that looks like JSON array
445+ # Check if it's missing quotes around string elements
446+ if "[" in param and "]" in param and '"' not in param :
447+ # This looks like [value] without quotes, try to add them
448+ inner_content = param [1 :- 1 ] # Remove brackets
449+ if "," in inner_content :
450+ # Multiple values: [val1, val2] -> ["val1", "val2"]
451+ values = [v .strip () for v in inner_content .split ("," )]
452+ quoted_values = [f'"{ v } "' for v in values ]
453+ reconstructed_param = f"[{ ', ' .join (quoted_values )} ]"
454+ else :
455+ # Single value: [value] -> ["value"]
456+ reconstructed_param = f'["{ inner_content .strip ()} "]'
457+
458+ # Validate the reconstructed JSON
459+ try :
460+ json .loads (reconstructed_param )
461+ reconstructed .append (reconstructed_param )
462+ except json .JSONDecodeError :
463+ # If reconstruction fails, keep original parameter
464+ reconstructed .append (param )
465+ else :
466+ # Already has quotes or is not a string array
467+ try :
468+ json .loads (param )
469+ reconstructed .append (param )
470+ except json .JSONDecodeError :
471+ reconstructed .append (param )
472+
473+ else :
474+ # Regular parameter, add as-is
475+ reconstructed .append (param )
476+
477+ i += 1
478+
479+ return reconstructed
0 commit comments