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
feat(node): repeat the foreign chain probe every hour
A verdict taken once at boot goes stale: a provider can start serving
another network, or go down, while the node is up. The probe now runs on a
ticker and the gauges carry the latest round.
Tick and its test double move out of remote_attestation into a shared tick
module, so both periodic loops use one abstraction.
Copy file name to clipboardExpand all lines: docs/foreign-chain-transactions.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -409,7 +409,7 @@ The per-participant registration model above leaves the network with no shared n
409
409
| What the operator picks | Full URL, auth scheme, token reference |`provider_id` (label) + token reference |
410
410
| Adding a new provider | Every operator updates their yaml; the network effectively supports a chain once enough do | Threshold of participants vote in `(chain, ProviderEntry)`; operators reference it by `provider_id` only |
411
411
| Removing a compromised provider | Every operator manually edits their yaml; coordination problem | Threshold of participants vote remove; nodes pick up the change via the indexer and drop the provider on next reconfigure |
412
-
| Testnet vs mainnet separation | Implicit — operator decides what URL goes under which chain | Per-`ForeignChain` map slot, plus a startup*network fingerprint probe* that calls the chain's self-identifying RPC and compares the response against the chain's `expected_network_fingerprint` from operator config — catches both lookup-level (wrong bucket) and content-level (wrong URL voted into the right bucket) confusion. |
412
+
| Testnet vs mainnet separation | Implicit — operator decides what URL goes under which chain | Per-`ForeignChain` map slot, plus a recurring*network fingerprint probe* that calls the chain's self-identifying RPC and compares the response against the chain's `expected_network_fingerprint` from operator config — catches both lookup-level (wrong bucket) and content-level (wrong URL voted into the right bucket) confusion. |
The per-chain map key prevents *lookup* confusion: when the node resolves the operator's `ethereum:` section, only `entries[Ethereum]` is consulted, never `entries[Sepolia]`. What it doesn't prevent is a `ChainVote { chain: Ethereum, providers: [ProviderEntry { provider_id: "ankr", chain_routing: PathSegment { segment: "eth_sepolia" }, … }, …], threshold: _ }` getting voted in — the contract just stores what threshold consensus produces; it can't tell whether `"eth_sepolia"` actually corresponds to Ethereum mainnet. Threshold voter review is the first line of defense; the fan-out across a chain's providers is the structural one. The network fingerprint probe is a per-node diagnostic on top of both.
567
567
568
-
At startup, every provider of a chain the node can identify gets its self-identifying RPC called and the response is compared against that chain's `expected_network_fingerprint` from the operator's config. The probe is report-only: a provider serving the wrong network is logged, but is not dropped, because a boot-time network blip should not take a chain out of signing. It runs detached, so it never delays startup. A node with no foreign chain configured at all is warned instead, because it cannot verify foreign-chain transactions.
568
+
At startup and every hour after that, every provider of a chain the node can identify gets its self-identifying RPC called and the response is compared against that chain's `expected_network_fingerprint` from the operator's config. The probe is report-only: a provider serving the wrong network is logged, but is not dropped, because a network blip should not take a chain out of signing. It runs detached, so it never delays startup. Repeating it turns a single snapshot taken at boot into a signal that also catches a provider that starts serving another network, or goes down, while the node is up. A node with no foreign chain configured at all is warned once at startup instead, because it cannot verify foreign-chain transactions.
569
569
570
-
The result is a line per provider, an `x/y providers healthy` summary counting only the providers a probe covers, and two gauges labelled by chain: `mpc_foreign_chain_rpc_providers_configured` and `mpc_foreign_chain_rpc_providers_healthy`. The gauges are per chain rather than per provider, because a provider name is operator chosen and would put an unbounded label on a time series. A chain whose providers cannot be identified, such as Solana, is left out of both the summary and the gauges: reporting `0` healthy against its configured count would read as every provider failing.
570
+
The result of each round is a line per provider, an `x/y providers healthy` summary counting only the providers a probe covers, and two gauges labelled by chain, which carry the verdicts of the round that ran last: `mpc_foreign_chain_rpc_providers_configured` and `mpc_foreign_chain_rpc_providers_healthy`. The gauges are per chain rather than per provider, because a provider name is operator chosen and would put an unbounded label on a time series. A chain whose providers cannot be identified, such as Solana, is left out of both the summary and the gauges: reporting `0` healthy against its configured count would read as every provider failing.
571
571
572
572
Taking the expected value from operator config rather than a constant in the attested binary is a deliberate trade. It makes mixed-network and local deployments checkable at all, since a config may pair one chain's mainnet with another's testnet and no binary can ship a value for a devnet. The cost is that the check no longer binds an operator: they can set the wrong value, or omit the field and get no check at all, and either way they fool only their own node's diagnostics. The network-level defenses against a wrong URL are unchanged: threshold voter review of the whitelist, and the provider fan-out, which fails the individual request when a provider disagrees with its siblings.
0 commit comments