How to do it

Reporting a nonce is made up of three stages: first you identify the nonce you want to verify, then you verify that nonce, then depending on the result you report it or not. For visualising reports and arbitrations, we have developed https://reports.tig.foundation/.

All endpoints below are on the TIG API (https://mainnet-api.tig.foundation, or https://testnet-api.tig.foundation for testnet). See the full API reference. There is a rate limit per IP address.

Stage 1 : Identifying which nonce to verify

This section outlines how one might identify a nonce they suspect to be fraudulent. If you would prefer to just check random nonces, you can skip to the next stage — method_verification.py can pick a random reportable nonce for you.

The reporting window

Nonces from round X can be reported during round X and round X+1 (a benchmark stays reportable for submission_period rounds after the round it belongs to). Once that window closes the benchmark can no longer be reported, so focus your analysis on the current and previous round.

Finding candidate nonces

You pick which Benchmarker to audit for a round and challenge, and the script samples nonces from their benchmarks. It keeps the API footprint small — three endpoints, and only ever fetches that one Benchmarker’s benchmarks:

  • GET /get-reports?round=<round> — every report already made that round, used to skip nonces someone else has already reported (called once).
  • GET /get-reportable-benchmark-ids?round=<round>&player_id=<benchmarker>&challenge_id=<challenge> — that Benchmarker’s benchmarks for the challenge (called once).
  • GET /get-benchmark-data?benchmark_id=<id> — read each benchmark’s num_nonces, num_bundles, compute_type, and settings. Called once per benchmark of that Benchmarker.

It randomly samples up to N nonces, taking at most num_bundles nonces from any single benchmark — beyond that, extra reports on one benchmark have no consequence (see Stage 3). Already-reported nonces are skipped, since they can’t be reported again.

--algorithm, --track, and --compute-type are optional filters — omit them to sample across everything. They can’t be pushed to the API (/get-reportable-benchmark-ids is keyed only by round/Benchmarker/challenge), so they’re applied locally; filtering doesn’t reduce the number of calls. Every candidate is labelled with its compute type and track, so you can group the output afterwards instead of pre-filtering.

N is a maximum, not a target: if fewer nonces remain after filtering, the script returns all of them — it never errors or pads the list. Choosing which Benchmarker to scrutinise is the analytical judgment, and it’s yours to make — e.g. a Benchmarker with suspiciously high earnings or qualities.

⬇ Download find_candidate_nonces.py

Run it with python3 and requests (pip install requests):

# Sample across everything (any algorithm, track, compute type):
python3 find_candidate_nonces.py --round <round> --challenge c001 --benchmarker 0x... -n 20
 
# Or narrow with any combination of the optional filters:
python3 find_candidate_nonces.py --round <round> --challenge c001 --benchmarker 0x... -n 20 --algorithm c001_a098 --track <track_id> --compute-type aws_c7i aws_m7i

It prints one <benchmark_id> <nonce> <compute_type> <track> per line. Every candidate is labelled with the compute type and track its benchmark was run on, so you always know what hardware is needed to reproduce it and can group the output as you like.

Why the compute type matters for what comes next. Verification (Stage 2) must reproduce a benchmark on hardware matching its compute type, so Stages 2 and 3 must be run on a list of nonces that are all the same compute type as the machine running them. Because Stage 1 labels every nonce with its compute type, you can group its output by type and verify each group on a matching machine — or pass a single --compute-type to get just one.

Stage 2 : Verify the nonce

You can use the TIG-built method_verification.py script (in tig-benchmarker/) to verify whether a single nonce is fraudulent. It re-computes one nonce and compares the result against the quality the Benchmarker claimed — a match means the claim is honest, a mismatch means the solution quality is non-reproducible and is a candidate for reporting. It can be run as part of a larger script, in combination with Stage 1, to identify many fraudulent nonces.

⚠️

Verification runs inside the benchmarker slave environment: method_verification.py re-uses the same per-challenge containers a Benchmarker runs (it executes tig-runtime and tig-verifier inside them). If you don’t already run one, set it up first — see Setting Up Slave Nodes.

What you need

  • A running slave environment (see above). In particular, method_verification.py reads the tig-benchmarker/.env file from that setup for ALGORITHMS_DIR (where algorithm binaries are downloaded), so run it from the tig-benchmarker/ directory.
  • The challenge’s Docker container running, e.g. docker compose -f slave.yml up <challenge_name>. The script runs tig-runtime and tig-verifier inside it.
  • Matching hardware. Results must be reproduced on the compute_type the benchmark was committed to. The script detects your CPU vendor/architecture and warns if it differs (a mismatch can legitimately change the result). If the challenge is GPU-based, you need a GPU.

Running it

# Verify a specific nonce
python3 method_verification.py --benchmark_id <benchmark_id> --nonce <nonce>
 
# Or omit both to have it pick a random reportable nonce for you
python3 method_verification.py
 

The script will:

  1. Look up the benchmark’s precommit (algorithm, hyperparameters, compute_type, rand_hash, fuel budget) and the claimed quality for that nonce.
  2. Download the algorithm’s compiled binary.
  3. Re-compute the nonce with tig-runtime, then verify it with tig-verifier to obtain the actual quality.
  4. Compare the two:
    • Match → Success! Actual quality matches expected quality.
    • Mismatch → it prints both qualities and a ready-to-run submit-report command for that nonce.

To check many nonces, wrap the script in a loop over the candidates from Stage 1. Each mismatch it reports is a nonce to take to Stage 3.

Stage 3 : Reporting the nonce

For every nonce that failed verification, submit a report:

curl -X POST https://mainnet-api.tig.foundation/submit-report \
  -H 'X-Api-Key: <YOUR_API_KEY>' \
  -d '{"benchmark_id": "<benchmark_id>", "nonce": <nonce>}'
  • Reports are per nonce — there is no batch endpoint. If you flagged several nonces, send one request each.
  • Getting <YOUR_API_KEY>: the easiest way is the dashboard — connect your wallet on the Main Dashboard (or Testnet Dashboard) and click the copy icon to copy your API key. Programmatically, you can instead call POST /request-api-key with your address and a signed message.
  • You need enough fee balance to cover the submission_fee charged per report.
  • Each report is independently arbitrated; the outcome is one of nonreproducible, reproducible, or inconclusive. Track reports and their arbitrations for a round via GET /get-reports?round=<round> or at reports.tig.foundation.
⚠️

A single report does not invalidate an entire benchmark. Penalties accumulate per confirmed report, up to the caps below — which is why reporting multiple distinct frauds across a Benchmarker’s benchmarks matters.

Limits and penalty caps

The protocol bounds both how many reports can be queued against a target and how many can actually penalise it:

LimitPer benchmarkPer Benchmarker
Max reports pending arbitrationnum_bundles × 2ceil(round_earnings / 10) × 2
Max reports that penalisenum_bundlesceil(round_earnings / 10)

Two practical takeaways for your reporting strategy:

  • Reporting more than num_bundles nonces of one benchmark adds no extra penalty — spread your reports across the Benchmarker’s different benchmarks instead.
  • A Benchmarker can be penalised by at most ceil(round_earnings / 10) reports in a round, so their exposure scales with how much they earned. There is no benefit to reporting beyond that ceiling.