Skip to content

Commit 7d22984

Browse files
committed
fix: robust JSON parameter handling for warnet bitcoin rpc
- Accepts JSON params with or without backslash-escaped quotes (e.g. '[http]' and '["http"]') - Wraps JSON params in single quotes for correct shell and bitcoin-cli parsing
1 parent 86c22e6 commit 7d22984

File tree

1 file changed

+14
-126
lines changed

1 file changed

+14
-126
lines changed

src/warnet/bitcoin.py

Lines changed: 14 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -43,44 +43,22 @@ def _rpc(tank: str, method: str, params: list[str], namespace: Optional[str] = N
4343
# so no extra args like port, chain, username or password are needed
4444
namespace = get_default_namespace_or(namespace)
4545

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
46+
if params:
47+
# Process parameters to ensure proper shell escaping
5248
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)
49+
for param in params:
50+
# If the parameter looks like JSON (starts with [ or {), fix malformed patterns
51+
if param.startswith('[') or param.startswith('{'):
52+
# Fix common malformed JSON patterns
53+
if '\\"' in param:
54+
# Convert [\"value\"] to ["value"]
55+
param = param.replace('\\"', '"')
56+
# Wrap JSON in single quotes to preserve it as a single argument
57+
processed_params.append(f"'{param}'")
6058
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))}"
59+
processed_params.append(param)
60+
61+
cmd = f"kubectl -n {namespace} exec {tank} --container {BITCOINCORE_CONTAINER} -- bitcoin-cli {method} {' '.join(processed_params)}"
8462
else:
8563
cmd = f"kubectl -n {namespace} exec {tank} --container {BITCOINCORE_CONTAINER} -- bitcoin-cli {method}"
8664
return run_command(cmd)
@@ -386,94 +364,4 @@ def to_jsonable(obj: str):
386364
return obj
387365

388366

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
478367

479-
return reconstructed

0 commit comments

Comments
 (0)