You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The aggregator held one signing key and signed every RAV with it, whatever payer the receipts named. It now picks the key belonging to that payer, and refuses payers it holds no key for.
fn check_and_aggregate_receipts(receipts, previous_rav, signers) {
+ let payer = receipts.first().payer;+ let (signing_key, accepted) = signers.resolve(payer)+ .ok_or("no signing key configured for payer")?;+- verify_each(receipts, &accepted_addresses)?; // one global set- verify(previous_rav, &accepted_addresses)?;+ verify_each(receipts, accepted)?; // scoped to this payer+ verify(previous_rav, accepted)?;
...
- sign(rav, &wallet) // one key, any payer+ sign(rav, signing_key)
}
The payer is read before any signature check, because it selects both the signing key and the accepted-signer set. grafana/aggregator.json is a new dashboard and accounts for most of the line count; it needs no review.
Approach
Accepted signers are scoped per payer rather than one global set. A signer accepted for one payer must not vouch for another's receipts.
A payer with no configured key is refused, not signed with whatever key is loaded. The collector requires a RAV's signer to be authorized for that RAV's payer.
No single-payer mode is kept. A key alone cannot say which payer it serves, so single-payer is one map entry.
Configuration
GRAPH_TALLY_SIGNERS — new and required. Every deployment fails to start until set. Format: ;-separated <payer address>=<signer private key>.
GRAPH_TALLY_ACCEPTED_SIGNERS — new, optional. No action needed.
GRAPH_TALLY_PRIVATE_KEY — removed, superseded by GRAPH_TALLY_SIGNERS. Left set, it is now ignored.
GRAPH_TALLY_PUBLIC_KEYS — removed, superseded by GRAPH_TALLY_ACCEPTED_SIGNERS.
Motivation
The RAV carries the payer named in its receipts, but was signed with this process's own key. Across a payer migration that produced RAVs carrying the old payer signed by the new signer — rejected by indexers, uncollectable on chain.
Cross-component
The JSON-RPC handler now keys its gateway_ravs Kafka record by payer instead of the signing wallet, matching the gRPC handlers — downstream consumers of that topic see a changed key on that path. Separately, a request for an unconfigured payer now errors where an invalid RAV was returned, a semantic change no signature reveals. Not verified against consumers; confirm before merging.
Risk assessment
blast_radius: cross-component — every deployment must be reconfigured before it starts, and one Kafka record key changes for downstream consumers.
reviewer_effort: careful — the logic is ~200 lines, but the ordering and the per-payer scoping carry the correctness; the rest of the diff is a dashboard.
primary_concern — accepted_signers is trusted from config with no on-chain check, so anything listed can have receipts aggregated against that payer's escrow.
touches_access_control — changes which keys sign RAVs and which receipt signers are accepted, per payer.
breaks_api — the config surface changes incompatibly: one new required variable and two removed. The title carries ! so release-please marks it breaking.
…signers
`run_server` now takes a `SignerRegistry` instead of a wallet plus a flat
accepted-signer set. The registry is keyed by payer, so the test helper
builds a one-entry map for the `payer` fixture that every receipt in these
tests carries.
Signed-off-by: Tomás Migone <tomas@thegraph.foundation>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Update stale startup description for signer registry parsing
grafana/aggregator.json:639
This description still refers to the removed single private-key parsing path and stale line numbers. The current startup sequence starts metrics before parsing the signer registry, so update the text to describe that behavior without referencing a nonexistent private key.
The “Instances Up” panel colors a total outage green: its only threshold maps value 0 to green. Add a red threshold at zero and switch to green at one so a down deployment is not presented as healthy.
Description uses incorrect line references and obsolete key terminology
grafana/aggregator.json:639
This new description documents the removed single-private-key flow and already has incorrect line references: main.rs:105 parses arguments, while metrics starts at line 110 and the signer registry is parsed at lines 112-121. Describe the ordering without brittle source locations or the obsolete “private key” wording.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
The aggregator held one signing key and signed every RAV with it, whatever payer the receipts named. It now picks the key belonging to that payer, and refuses payers it holds no key for.
fn check_and_aggregate_receipts(receipts, previous_rav, signers) { + let payer = receipts.first().payer; + let (signing_key, accepted) = signers.resolve(payer) + .ok_or("no signing key configured for payer")?; + - verify_each(receipts, &accepted_addresses)?; // one global set - verify(previous_rav, &accepted_addresses)?; + verify_each(receipts, accepted)?; // scoped to this payer + verify(previous_rav, accepted)?; ... - sign(rav, &wallet) // one key, any payer + sign(rav, signing_key) }The payer is read before any signature check, because it selects both the signing key and the accepted-signer set.
grafana/aggregator.jsonis a new dashboard and accounts for most of the line count; it needs no review.Approach
Configuration
GRAPH_TALLY_SIGNERS— new and required. Every deployment fails to start until set. Format:;-separated<payer address>=<signer private key>.GRAPH_TALLY_ACCEPTED_SIGNERS— new, optional. No action needed.GRAPH_TALLY_PRIVATE_KEY— removed, superseded byGRAPH_TALLY_SIGNERS. Left set, it is now ignored.GRAPH_TALLY_PUBLIC_KEYS— removed, superseded byGRAPH_TALLY_ACCEPTED_SIGNERS.Motivation
The RAV carries the payer named in its receipts, but was signed with this process's own key. Across a payer migration that produced RAVs carrying the old payer signed by the new signer — rejected by indexers, uncollectable on chain.
Cross-component
The JSON-RPC handler now keys its
gateway_ravsKafka record by payer instead of the signing wallet, matching the gRPC handlers — downstream consumers of that topic see a changed key on that path. Separately, a request for an unconfigured payer now errors where an invalid RAV was returned, a semantic change no signature reveals. Not verified against consumers; confirm before merging.Risk assessment
blast_radius: cross-component— every deployment must be reconfigured before it starts, and one Kafka record key changes for downstream consumers.reviewer_effort: careful— the logic is ~200 lines, but the ordering and the per-payer scoping carry the correctness; the rest of the diff is a dashboard.primary_concern—accepted_signersis trusted from config with no on-chain check, so anything listed can have receipts aggregated against that payer's escrow.touches_access_control— changes which keys sign RAVs and which receipt signers are accepted, per payer.breaks_api— the config surface changes incompatibly: one new required variable and two removed. The title carries!so release-please marks it breaking.