@@ -104,6 +104,13 @@ impl ConfiguredMethods {
104104 let method = RpcMethod :: from ( m. name . as_str ( ) ) ;
105105 out. enabled . insert ( method. clone ( ) ) ;
106106
107+ // Enabling a method resets its quorum to `Always` even when the
108+ // chain default already serves it with another strategy — legacy
109+ // `ManagedCallMethods` seeds every enabled method that way, and
110+ // operators use it to opt a method out of e.g. lag filtering. An
111+ // unknown quorum id keeps this default (legacy warns the same).
112+ out. quorum_overrides
113+ . insert ( method. clone ( ) , QuorumKind :: Always ) ;
107114 if let Some ( q) = m. quorum . as_deref ( ) {
108115 match QuorumKind :: from_str ( q) {
109116 Ok ( kind) => {
@@ -302,7 +309,7 @@ mod tests {
302309 }
303310
304311 #[ test]
305- fn unknown_quorum_is_dropped_not_fatal ( ) {
312+ fn unknown_quorum_keeps_the_always_default ( ) {
306313 let cfg = MethodsConfig {
307314 enabled : vec ! [ MethodConfig {
308315 name: "debug_x" . into( ) ,
@@ -313,7 +320,30 @@ mod tests {
313320 } ;
314321 let c = ConfiguredMethods :: from_config ( Some ( & cfg) ) ;
315322 assert ! ( c. is_callable( & "debug_x" . into( ) ) ) ;
316- assert ! ( c. quorum_override( & "debug_x" . into( ) ) . is_none( ) ) ;
323+ assert_eq ! (
324+ c. quorum_override( & "debug_x" . into( ) ) ,
325+ Some ( QuorumKind :: Always )
326+ ) ;
327+ }
328+
329+ #[ test]
330+ fn enabling_a_method_defaults_its_quorum_to_always ( ) {
331+ // Even without an explicit `quorum`, an enabled method carries an
332+ // `Always` override — legacy resets the strategy of re-enabled
333+ // methods the same way.
334+ let cfg = MethodsConfig {
335+ enabled : vec ! [ MethodConfig {
336+ name: "eth_getBalance" . into( ) ,
337+ quorum: None ,
338+ static_value: None ,
339+ } ] ,
340+ disabled : vec ! [ ] ,
341+ } ;
342+ let c = ConfiguredMethods :: from_config ( Some ( & cfg) ) ;
343+ assert_eq ! (
344+ c. quorum_override( & "eth_getBalance" . into( ) ) ,
345+ Some ( QuorumKind :: Always )
346+ ) ;
317347 }
318348
319349 #[ test]
@@ -350,4 +380,39 @@ mod tests {
350380 Some ( "\" Geth/v1.2.3\" " ) ,
351381 ) ;
352382 }
383+
384+ #[ test]
385+ fn static_value_json_shapes_forwarded_raw ( ) {
386+ // Any valid JSON goes through unchanged, whatever its type — the
387+ // legacy `executeHardcoded` semantic (validated with Jackson there).
388+ for ( given, expect) in [
389+ ( "123" , "123" ) ,
390+ ( "true" , "true" ) ,
391+ ( "null" , "null" ) ,
392+ ( r#"{"chainId":"0x1"}"# , r#"{"chainId":"0x1"}"# ) ,
393+ ( r#"["a","b"]"# , r#"["a","b"]"# ) ,
394+ // Surrounding whitespace is allowed around a JSON document and
395+ // gets normalized away.
396+ ( " 123 " , "123" ) ,
397+ ] {
398+ assert_eq ! ( parse_static_response( given) . get( ) , expect, "for {given}" ) ;
399+ }
400+ }
401+
402+ #[ test]
403+ fn static_value_almost_json_is_encoded_as_string ( ) {
404+ // Not-quite-JSON (hex tokens, trailing garbage, empty) becomes a JSON
405+ // string, so the reply always carries a valid `result`. Legacy string-
406+ // encodes the same inputs except trailing garbage, which Jackson
407+ // tolerated and legacy forwarded as broken JSON — encoding is the
408+ // safer behavior.
409+ for ( given, expect) in [
410+ ( "0x1" , "\" 0x1\" " ) ,
411+ ( "" , "\" \" " ) ,
412+ ( "123 junk" , "\" 123 junk\" " ) ,
413+ ( "{broken" , "\" {broken\" " ) ,
414+ ] {
415+ assert_eq ! ( parse_static_response( given) . get( ) , expect, "for {given}" ) ;
416+ }
417+ }
353418}
0 commit comments