Skip to content

Commit 92d7e5e

Browse files
committed
problem: different quorum strategy from the legacy code
1 parent 41d2483 commit 92d7e5e

2 files changed

Lines changed: 89 additions & 2 deletions

File tree

src/upstream/methods/config.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

src/upstream/methods/layered.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,28 @@ mod tests {
232232
}
233233
}
234234

235+
#[test]
236+
fn enabling_a_default_method_resets_its_quorum_to_always() {
237+
// Mirrors legacy `ManagedCallMethodsSpec."Use quorum from delegate if
238+
// it supports the method"`: a method both served by the default and
239+
// re-enabled by the user stops using the default's quorum.
240+
let default = default_with(&["head_call"], &[]);
241+
let configured = cfg(
242+
vec![MethodConfig {
243+
name: "head_call".into(),
244+
quorum: None,
245+
static_value: None,
246+
}],
247+
vec![],
248+
);
249+
let layered = LayeredMethods::new(default, configured);
250+
251+
match layered.quorum_for(&"head_call".into()).selector() {
252+
SelectorHint::Available => {}
253+
other => panic!("expected the Always default, got {other:?}"),
254+
}
255+
}
256+
235257
#[test]
236258
fn user_static_response_wins_over_default() {
237259
let default = default_with(&[], &[("net_version", "\"1\"")]);

0 commit comments

Comments
 (0)