Bring `domain_and_chain!` macro up to date, and add test coverage to keep it there (#812)

Fixes #786

Adds arbitrum, bsc, and optimism and their domains to the `domain_and_chain!` macro, which will enable portions of our monitoring to properly identify traffic relating to this chains instead of filling with "unknown" today.

Also introduces a unit test that searches for any json config files it can find, tries to parse them all, takes the union of all chain names and domain mappings it can see, and ensures certain internal consistency properties hold and that the `domain_and_chain!` macro is aware of them.

It's possible that this could be eventually adapated for json configs living in `typescript/infra` instead of `rust` if we go that direction. My hope was just to avoid a situation where we forget to keep things synchronized again.
pull/821/head
webbhorn 2 years ago committed by GitHub
parent e63950abfa
commit a8d5d0bd56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      rust/Cargo.lock
  2. 7
      rust/abacus-core/Cargo.toml
  3. 170
      rust/abacus-core/src/chain.rs

5
rust/Cargo.lock generated

@ -56,16 +56,20 @@ dependencies = [
name = "abacus-core"
version = "0.1.0"
dependencies = [
"abacus-base",
"async-trait",
"bytes",
"color-eyre",
"config",
"ethers",
"ethers-providers",
"ethers-signers",
"eyre",
"hex",
"lazy_static",
"maplit",
"num",
"num-traits",
"rocksdb",
"serde",
"serde_json",
@ -74,6 +78,7 @@ dependencies = [
"tokio",
"tracing",
"tracing-futures",
"walkdir",
]
[[package]]

@ -10,11 +10,14 @@ edition = "2021"
ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", default-features = false, features = ['legacy'] }
ethers-signers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features=["aws"] }
ethers-providers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features=["ws", "rustls"] }
config = "0.13"
hex = "0.4.3"
sha3 = "0.9.1"
lazy_static = "*"
thiserror = "*"
async-trait = { version = "0.1", default-features = false }
num-traits = "0.2"
maplit = "1.0"
tokio = { version = "1", features = ["rt", "macros"] }
tracing = "0.1"
tracing-futures = "0.2"
@ -26,8 +29,10 @@ bytes = { version = "1", features = ["serde"]}
num = {version="0", features=["serde"]}
[dev-dependencies]
tokio = {version = "1", features = ["rt", "time"]}
abacus-base = { path = "../abacus-base" }
color-eyre = "0.6"
tokio = {version = "1", features = ["rt", "time"]}
walkdir = { version = "2" }
[features]
output = []

@ -71,7 +71,9 @@ macro_rules! domain_and_chain {
}
}
// Copied from https://github.com/abacus-network/abacus-monorepo/blob/54a41d5a4bbb86a3b08d02d7ff6662478c41e221/typescript/sdk/src/chain-metadata.ts
// The unit test in this file `tests::json_mappings_match_code_map`
// tries to ensure some stability between the {chain} X {domain}
// mapping below with the agent configuration file.
domain_and_chain! {
0x63656c6f <=> "celo",
0x657468 <=> "ethereum",
@ -82,6 +84,9 @@ domain_and_chain! {
5 <=> "goerli",
3000 <=> "kovan",
80001 <=> "mumbai",
6386274 <=> "arbitrum",
6452067 <=> "bsc",
28528 <=> "optimism",
13371 <=> "test1",
13372 <=> "test2",
13373 <=> "test3",
@ -90,3 +95,166 @@ domain_and_chain! {
0x6f702d6b <=> "optimismkovan",
0x61752d74 <=> "auroratestnet",
}
#[cfg(test)]
mod tests {
use abacus_base::Settings;
use config::{Config, File, FileFormat};
use num_traits::identities::Zero;
use std::collections::BTreeSet;
use std::fs::read_to_string;
use std::path::Path;
use walkdir::WalkDir;
/// Relative path to the `abacus-monorepo/rust/config/`
/// directory, which is where the agent's config files
/// currently live.
const AGENT_CONFIG_PATH_ROOT: &str = "../config";
/// We will not include any file paths of config/settings files
/// in the test suite if *any* substring of the file path matches
/// against one of the strings included in the blacklist below.
/// This is to ensure that e.g. when a backwards-incompatible
/// change is made in config file format, and agents can't parse
/// them anymore, we don't fail the test. (E.g. agents cannot
/// currently parse the older files in `config/dev/` or
/// `config/testnet`.
const BLACKLISTED_DIRS: [&str; 5] = [
// Old envs which do not set now-required field
// "finality_blocks", which causes parsing to fail.
"config/dev/",
"config/testnet/",
// Ignore only-local names of fake chains used by
// e.g. test suites.
"test/test1_config.json",
"test/test2_config.json",
"test/test3_config.json",
];
fn is_blacklisted(path: &Path) -> bool {
BLACKLISTED_DIRS
.iter()
.any(|x| path.to_str().unwrap().contains(x))
}
#[derive(Clone, Debug, Ord, PartialEq, PartialOrd, Eq, Hash)]
struct ChainCoordinate {
name: String,
domain: u32,
}
fn config_paths(root: &Path) -> Vec<String> {
WalkDir::new(root)
.min_depth(2)
.into_iter()
.filter_map(|x| x.ok())
.map(|x| x.into_path())
.filter(|x| !is_blacklisted(x))
.map(|x| x.into_os_string())
.filter_map(|x| x.into_string().ok())
.collect()
}
/// Provides a vector of parsed `abacus_base::Settings` objects
/// built from all of the version-controlled agent configuration files.
/// This is purely a utility to allow us to test a handful of critical
/// properties related to those configs and shouldn't be used outside
/// of a test env. This test simply tries to do some sanity checks
/// against the integrity of that data.
fn abacus_settings() -> Vec<Settings> {
let root = Path::new(AGENT_CONFIG_PATH_ROOT);
let paths = config_paths(root);
let files: Vec<String> = paths
.iter()
.filter_map(|x| read_to_string(x).ok())
.collect();
paths
.iter()
.zip(files.iter())
.map(|(p, f)| {
Config::builder()
.add_source(File::from_str(f.as_str(), FileFormat::Json))
.build()
.unwrap()
.try_deserialize()
.unwrap_or_else(|e| {
panic!("!cfg({}): {:?}: {}", p, e, f);
})
})
.collect()
}
fn outbox_chain_names() -> BTreeSet<String> {
abacus_settings()
.iter()
.map(|x| x.outbox.name.clone())
.collect()
}
fn inbox_chain_names() -> BTreeSet<String> {
abacus_settings()
.iter()
.flat_map(|x: &Settings| x.inboxes.iter().map(|(k, _)| String::from(k)))
.collect()
}
fn outbox_name_domain_coords() -> BTreeSet<ChainCoordinate> {
abacus_settings()
.iter()
.map(|x| ChainCoordinate {
name: x.outbox.name.clone(),
domain: x.outbox.domain.parse().unwrap(),
})
.collect()
}
fn inbox_name_domain_records() -> BTreeSet<ChainCoordinate> {
abacus_settings()
.iter()
.flat_map(|x: &Settings| {
x.inboxes.iter().map(|(_, v)| ChainCoordinate {
name: v.name.clone(),
domain: v.domain.parse().unwrap(),
})
})
.collect()
}
#[test]
fn agent_json_config_consistency_checks() {
// Inbox/outbox and chain-presence equality
// (sanity checks that we have a complete list of
// relevant chains).
let inbox_chains = inbox_chain_names();
let outbox_chains = outbox_chain_names();
assert!(inbox_chains.symmetric_difference(&outbox_chains).count() == usize::zero());
assert_eq!(&inbox_chains.len(), &outbox_chains.len());
// Verify that the the outbox-associative chain-name
// and domain-number records agree with the
// inbox-associative chain-name and domain-number
// records, since our configuration data is /not/
// normalized and could drift out of sync.
let inbox_coords = inbox_name_domain_records();
let outbox_coords = outbox_name_domain_coords();
assert!(inbox_coords.symmetric_difference(&outbox_coords).count() == usize::zero());
assert_eq!(&inbox_coords.len(), &outbox_coords.len());
// TODO(webbhorn): Also verify with this functionality
// we have entries for all of the Gelato contract
// addresses we need hardcoded in the binary for now.
// Verify that the hard-coded, macro-maintained
// mapping in `abacus-core/src/chain.rs` named
// by the macro `domain_and_chain` is complete
// and in agreement with our on-disk json-based
// configuration data.
for ChainCoordinate { name, domain } in inbox_coords.iter().chain(outbox_coords.iter()) {
assert_eq!(
super::chain_from_domain(domain.to_owned()).unwrap(),
name.to_owned()
);
assert_eq!(super::domain_from_chain(name).unwrap(), domain.to_owned());
}
}
}

Loading…
Cancel
Save