From f85b2f457fa93c56713e26cdbd3bbe5ef7d172af Mon Sep 17 00:00:00 2001 From: rob Date: Fri, 10 Jul 2026 15:04:43 +0000 Subject: [PATCH] dshackle overrides weave: host-local method/option overrides survive config regens /root/rpc-local/dshackle-overrides.yaml (host-local, like rpc-local/compose.d) is re-woven into main_configs/*.yaml by reload_dshackle.sh on every config push. Method-disable decisions are local to each gateway's market (operator eviction rule: cost_ratio = time_share/call_share at flat pay); this makes them durable inputs instead of fragile output patches. Step 1 of moving dshackle config generation onto the gateway hosts (step 2: ssh forced-command upstreams fetch, generic dshackle.yaml include list derived from drpc-gateways.json). Co-Authored-By: Claude Fable 5 --- apply-dshackle-overrides.py | 99 +++++++++++++++++++++++++++++++++++++ reload_dshackle.sh | 5 ++ 2 files changed, 104 insertions(+) create mode 100755 apply-dshackle-overrides.py diff --git a/apply-dshackle-overrides.py b/apply-dshackle-overrides.py new file mode 100755 index 00000000..73635662 --- /dev/null +++ b/apply-dshackle-overrides.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""apply-dshackle-overrides.py - weave HOST-LOCAL dshackle upstream overrides into +the generated main_configs/*.yaml so they survive every configure-drpc regeneration. + +Override file (NOT in the repo - host-local, like rpc-local/compose.d): + /root/rpc-local/dshackle-overrides.yaml + +Schema (match by upstream id OR by chain; id wins): + upstreams: + uk-4-base-mainnet-op-reth-pruned: # or e.g. "chain:base" + method_groups_disabled: [trace, debug] # removed from enabled, added to disabled + methods_disabled: [eth_getFilterLogs] # per-method disables (methods.disabled) + options: + disable-validation: true # merged into upstream options + +Rationale: heavy methods at flat per-call pay poison the chain's latency scoring +(operator method-eviction rule 2026-07-10; see method-economics.py). The disable +decision is local to the gateway's market, so the config lives on the gateway. + +Called by reload_dshackle.sh before signaling dshackle - i.e. re-applied on every +ansible config push. NOTE: dshackle only re-reads upstream method lists on RESTART; +this keeps the on-disk config permanently correct, the next restart activates it. +Idempotent. Silent no-op when the override file is absent. +""" +import glob +import os +import sys + +import yaml + +OVERRIDES = '/root/rpc-local/dshackle-overrides.yaml' +CONFIG_DIR = '/root/rpc/main_configs' + + +def apply(upstream, ov): + changed = False + mg = upstream.setdefault('method-groups', {}) or {} + for grp in ov.get('method_groups_disabled') or []: + en = mg.get('enabled') or [] + if grp in en: + en.remove(grp) + mg['enabled'] = en + changed = True + dis = mg.get('disabled') or [] + if grp not in dis: + dis.append(grp) + mg['disabled'] = dis + changed = True + if mg: + upstream['method-groups'] = mg + for meth in ov.get('methods_disabled') or []: + methods = upstream.setdefault('methods', {}) or {} + dis = methods.get('disabled') or [] + if not any((d.get('name') if isinstance(d, dict) else d) == meth for d in dis): + dis.append({'name': meth}) + methods['disabled'] = dis + upstream['methods'] = methods + changed = True + for k, v in (ov.get('options') or {}).items(): + opts = upstream.setdefault('options', {}) or {} + if opts.get(k) != v: + opts[k] = v + upstream['options'] = opts + changed = True + return changed + + +def main(): + if not os.path.exists(OVERRIDES): + return + spec = (yaml.safe_load(open(OVERRIDES)) or {}).get('upstreams') or {} + if not spec: + return + total = 0 + for path in glob.glob(os.path.join(CONFIG_DIR, '*.yaml')): + try: + doc = yaml.safe_load(open(path)) + except yaml.YAMLError: + continue + if not isinstance(doc, dict) or 'upstreams' not in doc: + continue + changed = False + for up in doc['upstreams'] or []: + if not isinstance(up, dict): + continue + ov = spec.get(up.get('id')) or spec.get(f"chain:{up.get('chain')}") + if ov and apply(up, ov): + changed = True + if changed: + with open(path, 'w') as f: + yaml.safe_dump(doc, f, default_flow_style=False, sort_keys=False) + total += 1 + print(f'dshackle-overrides: rewove {path}') + if total: + print(f'dshackle-overrides: {total} file(s) updated (restart dshackle to activate method changes)') + + +if __name__ == '__main__': + main() diff --git a/reload_dshackle.sh b/reload_dshackle.sh index 78352067..8d2bc934 100755 --- a/reload_dshackle.sh +++ b/reload_dshackle.sh @@ -1,4 +1,9 @@ #!/bin/bash +# Re-weave host-local upstream overrides (method disables etc.) into the freshly +# pushed configs BEFORE signaling dshackle - makes /root/rpc-local/dshackle-overrides.yaml +# survive every configure-drpc regeneration. See apply-dshackle-overrides.py. +[ -f /root/rpc/apply-dshackle-overrides.py ] && python3 /root/rpc/apply-dshackle-overrides.py + docker ps -q -f "name=dshackle" | xargs -r docker kill --signal=HUP docker ps -q -f "name=dshackle-free" | xargs -r docker kill --signal=HUP