feat(node): probe foreign-chain RPC providers on startup - #3848
feat(node): probe foreign-chain RPC providers on startup#3848haiyuechen-nearone wants to merge 5 commits into
Conversation
131a75f to
e9e4b81
Compare
05a55bc to
ad30f88
Compare
4eeed22 to
8cdfbfa
Compare
ad30f88 to
a1145b2
Compare
a1145b2 to
9031612
Compare
8cdfbfa to
36e439e
Compare
a2805eb to
3817516
Compare
30c77ea to
208c837
Compare
208c837 to
7958f4b
Compare
Pull request overviewAdds a startup health check that probes every configured foreign-chain RPC provider once against a golden reference transaction, then logs per-provider results and a Changes:
Reviewed changesPer-file summary
FindingsNon-blocking (nits, follow-ups, suggestions):
✅ Approved |
8e97b7e to
47f58d5
Compare
| pub fn resolve_network_from_config( | ||
| near_init: Option<&NearInitConfig>, | ||
| contract_id: &str, | ||
| ) -> NetworkKind { | ||
| match near_init { | ||
| Some(near_init) => match near_init.chain_id { | ||
| ChainId::Mainnet => NetworkKind::Public(Network::Mainnet), | ||
| ChainId::Testnet => NetworkKind::Public(Network::Testnet), | ||
| ChainId::Localnet | ChainId::Sandbox | ChainId::Custom(_) => NetworkKind::Local, | ||
| }, | ||
| None => match network_from_contract_id(contract_id) { | ||
| Some(network) => NetworkKind::Public(network), | ||
| None => NetworkKind::Undetermined, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| /// Classify mainnet/testnet from a contract-id suffix. | ||
| pub fn network_from_contract_id(contract_id: &str) -> Option<Network> { | ||
| if contract_id.ends_with(".testnet") { | ||
| Some(Network::Testnet) | ||
| } else if contract_id.ends_with(".near") || contract_id == "v1.signer" { | ||
| Some(Network::Mainnet) | ||
| } else { | ||
| None | ||
| } | ||
| } |
There was a problem hiding this comment.
To reviewers: please double check that I have the correct understanding of our network and contract naming conventions
netrome
left a comment
There was a problem hiding this comment.
Started looking at this now, some initial thoughts:
- We should not expect RPCs to retain individual TX information forever (blocking)
- I'd prefer to minimize network-specific code paths. Ideally we should not have any network-specific code paths, but I can see the point of providing defaults for mainnet and testnet for convenience.
| // A config-supplied golden is only meaningful on a local chain; on | ||
| // mainnet/testnet the built-in set is always used. |
There was a problem hiding this comment.
Why don't we provide golden configurations on mainnet/testnet? Hard-coding values feels like a time bomb, as many RPCs might not serve requests for old transactions.
There was a problem hiding this comment.
My understanding was that we want to always check for the same transaction, which is baked in to the binary, maybe I misunderstood.
@anodar Judging from the foreign chain tester CLI code I assumed that we would like to continue using hard coded golden transactions for health check, maybe you had something else in mind?
| "foreign_chain_health_check_golden is set in config but ignored on \ | ||
| mainnet/testnet; the built-in golden set is always used — remove it \ | ||
| from config.yaml" |
There was a problem hiding this comment.
Wouldn't it make sense to override the golden set if provided manually? The hard-coded values could exist as a convenience so we don't have to ask all operators to manually add these but retaining the ability to always override sounds like it would simplify our code. I'd prefer if we have as few network-specific code paths as possible. Ideally none.
| /// Hashes are hex, with or without a `0x` prefix. | ||
| #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] | ||
| pub struct BlockHashGolden { | ||
| pub tx: String, | ||
| pub block_hash: String, | ||
| } |
There was a problem hiding this comment.
I think this is a dangerous construction. Many RPCs won't serve transactions forever. It's better to ask for genesis hash or chain ID, like we do for Sui.
485eb48 to
315514c
Compare
Replace Starknet's pinned golden transaction with the Sui-style hybrid probe: verify the chain identity (starknet_chainId, a genesis-derived constant that is never pruned), then run the real inspector over a transaction from the latest L1-accepted block at AcceptedOnL1 finality. This removes the archive-node dependency and the per-network golden-vector maintenance while keeping the production-path smoke test (receipt -> finality -> canonicality). AcceptedOnL1 matches what real verify-foreign-tx requests use and actually exercises the finality check (the AcceptedOnL2 branch is a no-op). Discovery uses the l1_accepted block tag, so the probe requires provider JSON-RPC v0.9+. To stay robust against load-balanced providers whose backends disagree on the L1-accepted frontier, the probe walks back from the head — doubling the step each time, capped at MAX_WALKBACK_BLOCKS — past empty blocks and past receipts a lagging backend still reports below L1 finality. Expected identities carry no built-in values: they come from configuration (foreign_chain_health_check.identities), so any network — including local or custom ones — is checkable, and a configured identity-probed chain fails until its identity is set. Sui's built-in reference moves to configuration on the same terms. Operator templates ship with the identities prefilled.
…ed transaction Extend the identity-based probe to the 6 EVM chains (base, bnb, arbitrum, polygon, hyper_evm, abstract): verify eth_chainId against the configured expected identity, then run the inspector over a transaction from a block 10 below the finalized head, walking back past empty blocks. The EVM golden vectors are deleted: expected chain ids come from configuration like the other identity chains (well-known values in the config-tester README).
…ction Extend the identity-based probe to Bitcoin: verify the genesis block hash (getblockhash 0 — never pruned, and a proof-of-data identity since the provider must actually hold block 0) against the configured expected identity, then run the inspector over the coinbase of a block 10 below the tip. Tip height comes from getbestblockhash + getblock rather than getblockcount: some provider edges serve getblockcount as a JSON-RPC 1.0-style response the 2.0 transport rejects. The pinned Bitcoin golden vectors are deleted; the expected genesis comes from configuration like the other identity chains.
…action Extend the identity-based probe to Aptos, the last golden-transaction chain: verify the ledger chain_id (GET /v1; 1 = mainnet, 2 = testnet) against the configured expected identity, then run the inspector over a committed transaction 100 ledger versions behind the tip. With every chain now identity-based and configuration the sole source of expected identities, the golden module's vectors and the entire network machinery are deleted: the Network enum, the CLI's --network flag, and the chain-id/contract-id auto-detection all existed only to pick between built-in reference sets. The golden module becomes a small parse module of identity decoders.
Spawn a detached, diagnostic-only startup task that concurrently probes every configured foreign-chain RPC provider against its expected chain identity and logs a per-provider result plus an `x/y providers healthy` summary — so config typos and un-enabled API keys surface at startup instead of on the first real verification request. The probe never blocks startup, and failure reasons are not logged (they can carry secrets). Expected identities are read from the node's typed config (`foreign_chain_health_check.identities`); `ExpectedIdentities` moves to node-config so the node and the config-tester share one type.
315514c to
748639d
Compare
|
Superseded by #4013 |
Part of #3764. Wires the identity-based check into the node. Merge the identity-probe stack (#3932 → #3962 → #3963 → #3964) first, then this trio bottom-up:
configured/healthyPrometheus gauges/debug/node_configSpawns a detached startup task that concurrently probes every configured foreign-chain RPC provider once — verifying its chain identity and inspecting a recent transaction, the same inspector and auth the real verification path uses — and logs a per-provider result plus an
x/y providers healthysummary. The probe never blocks startup, and failure reasons aren't logged (they can carry secrets).Expected identities come from
foreign_chain_health_check.identities(no built-ins, so any network — including local — is checkable; a configured chain with no identity fails).ExpectedIdentitiesmoves to node-config so the node and config-tester share one type.