{"record":{"id":"0c0930d564d41bf9","repo":"nautechsystems/nautilus_trader","slug":"provider-failure-domains-must-be-pairwise-disjoint","errorCode":null,"errorMessage":"Provider failure domains must be pairwise disjoint","messagePattern":"Provider failure domains must be pairwise disjoint","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/blockchain/src/rpc/verification.rs","lineNumber":1195,"sourceCode":"        identities\n            .iter()\n            .map(|identity| identity.provider_id.as_str()),\n        \"provider IDs\",\n    )?;\n    ensure_distinct(\n        identities\n            .iter()\n            .map(|identity| identity.operator_id.as_str()),\n        \"operator IDs\",\n    )?;\n\n    for left in 0..identities.len() {\n        for right in left + 1..identities.len() {\n            let left_domains = identities[left]\n                .failure_domain_ids\n                .iter()\n                .collect::<HashSet<_>>();\n            anyhow::ensure!(\n                identities[right]\n                    .failure_domain_ids\n                    .iter()\n                    .all(|domain| !left_domains.contains(domain)),\n                \"Provider failure domains must be pairwise disjoint\"\n            );\n        }\n    }\n\n    let endpoints = [\n        normalize_endpoint(authoritative_url)?,\n        normalize_endpoint(config.verifiers[0].http_rpc_url.expose_secret())?,\n        normalize_endpoint(config.verifiers[1].http_rpc_url.expose_secret())?,\n    ];\n    ensure_distinct(endpoints.iter().map(String::as_str), \"provider endpoints\")?;\n\n    let anchor = &config.chain_anchor;\n    anyhow::ensure!(anchor.chain_id != 0, \"Chain anchor ID must be nonzero\");","sourceCodeStart":1177,"sourceCodeEnd":1213,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/blockchain/src/rpc/verification.rs#L1177-L1213","documentation":"`validate_config` computes the failure-domain ID sets of every pair of verifier identities and requires them to be pairwise disjoint: no two providers may share a failure domain. This guarantees the two providers are in independent failure domains, which is the basis for trustworthy cross-verification. It throws when any domain ID appears in both providers' `failure_domain_ids`.","triggerScenarios":"Any pair of configured verifiers whose `identity.failure_domain_ids` lists share at least one common domain ID, detected by the nested loop over identities.","commonSituations":"Copying one provider's config block and forgetting to change its failure-domain IDs; both providers hosted by the same cloud region/ASN labeled identically; an operator reusing a shared 'primary' domain tag for both providers.","solutions":["Give each verifier a distinct set of failure-domain IDs so no ID appears in both identities.","Remove the shared domain ID from the provider where it does not apply.","Re-tag your providers with domains reflecting real independence (different region/ASN/vendor).","Compute the intersection of the two `failure_domain_ids` sets locally to find the offending ID before editing."],"exampleFix":"// before\nverifier_a.identity.failure_domain_ids = [\"us-east-1\", \"primary\"]\nverifier_b.identity.failure_domain_ids = [\"us-west-2\", \"primary\"]\n\n// after\nverifier_a.identity.failure_domain_ids = [\"us-east-1\", \"primary\"]\nverifier_b.identity.failure_domain_ids = [\"us-west-2\", \"backup\"]","handlingStrategy":"validation","validationCode":"use std::collections::HashSet;\nfn check_disjoint_failure_domains(cfg: &BlockchainVerificationConfig) -> Result<(), String> {\n    let domains: Vec<HashSet<&str>> = cfg.verifiers.iter()\n        .map(|v| v.identity.failure_domain_ids.iter().collect())\n        .collect();\n    for i in 0..domains.len() {\n        for j in i + 1..domains.len() {\n            if domains[i].intersection(&domains[j]).next().is_some() {\n                return Err(format!(\"verifiers {i} and {j} share a failure domain\"));\n            }\n        }\n    }\n    Ok(())\n}","typeGuard":"fn failure_domains_disjoint(a: &[String], b: &[String]) -> bool {\n    let sa: HashSet<_> = a.iter().collect();\n    b.iter().all(|d| !sa.contains(d))\n}","tryCatchPattern":"match validate_config(&cfg) {\n    Err(e) if e.to_string().contains(\"pairwise disjoint\") => {\n        eprintln!(\"Providers overlap in failure domains: {e}; re-tag identities\");\n        std::process::exit(1);\n    }\n    other => other,\n}","preventionTips":["Derive failure-domain IDs from real infrastructure attributes (region, ASN, vendor) and never copy-paste provider blocks wholesale.","Assert disjointness in a config unit test with the production config file as input.","Keep domain ID naming conventions unique per provider in your infrastructure-as-code templates."],"tags":["config","validation","redundancy"],"backgroundTag":"invalid-config-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}