Module M.04

Wrong Equivalence

The Precision Trap

GenLayer uses equivalence functions to determine if validator outputs agree. strict_eq requires exact match. For live prices, feeds, or any real-time data, validators will fetch at slightly different times and get slightly different values. strict_eq will always fail. Use custom equivalence with tolerance bands.

Side-by-side · Vulnerable vs. Patched

two contracts · proven by paired transactions
vulnerablecontracts/vulnerable/VulnerablePrice.py
Failed TX
> consensus failed · validators diverged
1# { "Depends": "py-genlayer:15qfivjvy80800rh998pcxmd2m8va1wq2qzqhz850n8ggcr4i9q0" }23from genlayer import *4import json56# Module 4 (Vulnerable) -- Wrong equivalence principle.7# Live ETH spot price changes between the moment each validator fetches8# CoinGecko. Wrapping the fetch in strict_eq REQUIRES byte-for-byte9# agreement. Validators land on slightly different float values --> the10# round fails to reach consensus and the tx ends FINISHED_WITH_ERROR.1112PRICE_URL = "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd"131415class VulnerablePrice(gl.Contract):16    last_price: str1718    def __init__(self):19        self.last_price = ""2021    @gl.public.write22    def fetch_eth(self) -> None:23        def _fetch() -> str:24            resp = gl.nondet.web.request(PRICE_URL, method='GET')25            data = json.loads(resp.body.decode("utf-8"))26            return str(data["ethereum"]["usd"])2728        # BUG: strict_eq on a continuously-changing float.29        self.last_price = gl.eq_principle.strict_eq(_fetch)3031    @gl.public.view32    def get_last_price(self) -> str:33        return self.last_price
patchedcontracts/patched/TolerantPrice.py
Success TX
> consensus reached · all validators agree
1# { "Depends": "py-genlayer:15qfivjvy80800rh998pcxmd2m8va1wq2qzqhz850n8ggcr4i9q0" }23from genlayer import *4import json56# Module 4 (Patched) -- Tolerant equivalence.7# Same upstream feed, but the consensus principle is comparative: each8# validator returns a price; agreement is judged by an LLM-mediated check9# that the values are within a sane spread. This is the correct shape of10# eq_principle for live, slowly-drifting data.1112PRICE_URL = "https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd"13TOLERANCE_PERCENT = 2.0141516class TolerantPrice(gl.Contract):17    last_price: str18    tolerance_percent: str  # stored as string for cross-validator stability1920    owner: str2122    def __init__(self):23        self.owner = str(gl.message.sender_address)24        self.last_price = ""25        self.tolerance_percent = str(TOLERANCE_PERCENT)2627    @gl.public.write28    def fetch_eth(self) -> None:29        tol = self.tolerance_percent3031        def _fetch() -> str:32            resp = gl.nondet.web.request(PRICE_URL, method='GET')33            data = json.loads(resp.body.decode("utf-8"))34            return str(data["ethereum"]["usd"])3536        principle = (37            f"All values must be numeric ETH/USD prices and be within {tol}% "38            "of each other. Pick any one of them."39        )40        self.last_price = gl.eq_principle.prompt_comparative(_fetch, principle=principle)4142    @gl.public.write43    def set_tolerance(self, percent_str: str) -> None:44        """Owner can tune the equivalence tolerance on-chain -- deterministic,45        no LLM. Demonstrates that the comparative principle is parameterised,46        which is the structural fix relative to hardcoded strict_eq."""47        if str(gl.message.sender_address) != self.owner:48            raise Exception("only owner")49        self.tolerance_percent = percent_str5051    @gl.public.view52    def get_tolerance(self) -> str:53        return self.tolerance_percent5455    @gl.public.view56    def get_last_price(self) -> str:57        return self.last_price
Call invoked
fetch_eth()

strict_eq on live ETH/USD -> validators fetch slightly different prices -> divergence

Call invoked
set_tolerance("2.0")

owner tunes the comparative tolerance on-chain -- proves the equivalence is parameterised

On-chain receipts

Knowledge check · M.04

01 / 02

Two questions on this incident. Pick the best answer; the question locks once committed.

Question 01 / 02
Why does strict_eq fail for live price data?