Module M.01
URL Rot
The Disappearing Oracle
When you hardcode a single URL in a GenLayer contract, you create a single point of failure. If that site goes down, changes its API, or adds anti-bot protection, your contract will fail to reach consensus forever. This is the most common failure mode for oracle-based contracts.
Side-by-side · Vulnerable vs. Patched
two contracts · proven by paired transactions
vulnerable ▸contracts/vulnerable/VulnerableOracle.py
Failed TX> consensus failed · validators diverged
1# { "Depends": "py-genlayer:15qfivjvy80800rh998pcxmd2m8va1wq2qzqhz850n8ggcr4i9q0" }23from genlayer import *45# Module 1 (Vulnerable) -- URL Rot.6# Hardcoded single endpoint on a reserved-invalid TLD. DNS resolution will7# fail on every validator, producing a deterministic FINISHED_WITH_ERROR8# execution receipt. The point isn't that the URL is invalid -- it's that9# there is no fallback path, so any change to a real endpoint (downtime,10# anti-bot, schema change) bricks the contract forever.1112PRICE_URL = "https://oracle-rot-demo.invalid/eth.json"131415class VulnerableOracle(gl.Contract):16 last_value: str1718 def __init__(self):19 self.last_value = ""2021 @gl.public.write22 def fetch_price(self) -> None:23 def _fetch() -> str:24 resp = gl.nondet.web.request(PRICE_URL, method='GET')25 # No schema validation, no fallback, no error handling: if the26 # call raises (DNS, 5xx), the tx goes FINISHED_WITH_ERROR.27 return resp.body.decode("utf-8", errors="replace").strip()2829 self.last_value = gl.eq_principle.strict_eq(_fetch)3031 @gl.public.view32 def get_last_value(self) -> str:33 return self.last_value
patched ▸contracts/patched/ResilientOracle.py
Success TX> consensus reached · all validators agree
1# { "Depends": "py-genlayer:15qfivjvy80800rh998pcxmd2m8va1wq2qzqhz850n8ggcr4i9q0" }23from genlayer import *4import json56# Module 1 (Patched) -- Resilient oracle.7# Mutable allow-list of endpoints. Tries each in order; first success wins.8# Owner can add/remove endpoints without redeploying.910DEFAULT_ENDPOINTS = [11 # Intentionally-broken endpoint to prove fallback works.12 "https://oracle-rot-demo.invalid/eth.json",13 # GenLayer-hosted static fixture; returns the same body on every14 # validator so strict_eq converges.15 "https://test-server.genlayer.com/static/genvm/hello.html",16]1718# A substring guaranteed to appear in the static fixture's body.19STABLE_MARKER = "Hello"202122class ResilientOracle(gl.Contract):23 owner: str24 endpoints_json: str25 last_value: str2627 def __init__(self):28 self.owner = str(gl.message.sender_address)29 self.endpoints_json = json.dumps(DEFAULT_ENDPOINTS)30 self.last_value = ""3132 def _require_owner(self) -> None:33 if str(gl.message.sender_address) != self.owner:34 raise Exception("only owner")3536 @gl.public.write37 def fetch_value(self) -> None:38 endpoints = json.loads(self.endpoints_json)3940 def _fetch() -> str:41 for url in endpoints:42 try:43 resp = gl.nondet.web.request(url, method='GET')44 if 200 <= resp.status_code < 300:45 body = resp.body.decode("utf-8", errors="replace")46 if STABLE_MARKER in body:47 return f"OK:{url}:{STABLE_MARKER}"48 except Exception:49 continue50 raise Exception("all endpoints failed")5152 self.last_value = gl.eq_principle.strict_eq(_fetch)5354 @gl.public.write55 def add_endpoint(self, url: str) -> None:56 self._require_owner()57 if not url.startswith("https://"):58 raise ValueError("https only")59 endpoints = json.loads(self.endpoints_json)60 if url not in endpoints:61 endpoints.append(url)62 self.endpoints_json = json.dumps(endpoints)6364 @gl.public.write65 def remove_endpoint(self, url: str) -> None:66 self._require_owner()67 endpoints = json.loads(self.endpoints_json)68 endpoints = [u for u in endpoints if u != url]69 self.endpoints_json = json.dumps(endpoints)7071 @gl.public.view72 def get_last_value(self) -> str:73 return self.last_value7475 @gl.public.view76 def get_endpoints(self) -> str:77 return self.endpoints_json
Call invoked
fetch_price()web.request hits oracle-rot-demo.invalid -> NXDOMAIN -> execution error
Call invoked
add_endpoint("https://new-oracle.example.com/price")owner adds an endpoint on-chain -- proves the URL list is mutable, which is the fix
On-chain receipts
Knowledge check · M.01
01 / 02
Two questions on this incident. Pick the best answer; the question locks once committed.
Question 01 / 02
Why does a hardcoded URL fail in GenLayer?