From fe19cf67123d1958f70ad0d6b47c052e6a38b7b9 Mon Sep 17 00:00:00 2001 From: muhmad Date: Wed, 25 Mar 2026 23:51:19 +0100 Subject: [PATCH] fix: return None instead of raw invalid data in invalid_to_none The invalid_to_none() WrapValidator was returning the raw invalid value v instead of None when Pydantic validation failed. This defeated the purpose of the function - malformed miner responses passed through unmodified. Example of the bug: # Miner sends: simulation_output = "not_a_tuple" # handler(v) raises ValidationError # Function returned "not_a_tuple" instead of None # This invalid data then flows into DB storage and CRPS workers # With fix: ValidationError -> returns None -> safely handled downstream --- synth/protocol.py | 2 +- tests/test_protocol.py | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/test_protocol.py diff --git a/synth/protocol.py b/synth/protocol.py index 6be96199..7acfca96 100644 --- a/synth/protocol.py +++ b/synth/protocol.py @@ -35,7 +35,7 @@ def invalid_to_none(v: Any, handler: Callable[[Any], Any]) -> Any: try: return handler(v) except ValidationError: - return v + return None class Simulation(bt.Synapse): diff --git a/tests/test_protocol.py b/tests/test_protocol.py new file mode 100644 index 00000000..047cfb66 --- /dev/null +++ b/tests/test_protocol.py @@ -0,0 +1,52 @@ +from pydantic import ValidationError + +from synth.protocol import invalid_to_none + + +def mock_handler(v): + """Simulates Pydantic validation that succeeds.""" + if not isinstance(v, tuple): + raise ValidationError.from_exception_data( + title="test", + line_errors=[], + ) + return v + + +class TestInvalidToNone: + def test_valid_data_passes_through(self): + """Valid data should be returned as-is.""" + valid = (1, [1.0, 2.0], [3.0, 4.0]) + result = invalid_to_none(valid, mock_handler) + assert result == valid + + def test_invalid_data_returns_none(self): + """Invalid data should return None, not the raw invalid value. + + This was the bug: previously returned the raw invalid value `v`, + allowing malformed miner responses to bypass validation. + + Example: a miner sends "garbage_string" as simulation_output. + Before fix: invalid_to_none returned "garbage_string" + After fix: invalid_to_none returns None + """ + result = invalid_to_none("not_a_tuple", mock_handler) + assert result is None, ( + "invalid_to_none must return None for invalid data, " + "not the raw value" + ) + + def test_invalid_dict_returns_none(self): + """Malformed dict input should return None.""" + result = invalid_to_none({"malicious": "data"}, mock_handler) + assert result is None + + def test_invalid_list_returns_none(self): + """List instead of tuple should return None.""" + result = invalid_to_none([1, 2, 3], mock_handler) + assert result is None + + def test_none_input_returns_none(self): + """None input that fails validation should return None.""" + result = invalid_to_none(None, mock_handler) + assert result is None