From f5b0316f64ceb2ae613fc590e73f891b3586d529 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Wed, 20 Nov 2024 12:17:22 +0000 Subject: [PATCH] Addition of initial seed check logic --- Cargo.lock | 2 + Cargo.toml | 3 + src/bin/grin.rs | 5 + src/bin/tools/mod.rs | 16 +++ src/bin/tools/seedcheck.rs | 200 +++++++++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+) create mode 100644 src/bin/tools/mod.rs create mode 100644 src/bin/tools/seedcheck.rs diff --git a/Cargo.lock b/Cargo.lock index 773aa76c1..7f89ef038 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -952,8 +952,10 @@ dependencies = [ "humansize", "log", "serde", + "serde_derive", "serde_json", "term", + "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3ae72b9ff..4e0adbf6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,13 @@ path = "src/bin/grin.rs" [dependencies] blake2-rfc = "0.2" chrono = "0.4.11" +thiserror = "1" clap = { version = "2.33", features = ["yaml"] } ctrlc = { version = "3.1", features = ["termination"] } cursive_table_view = "0.15.0" humansize = "1.1.0" serde = "1" +serde_derive = "1" futures = "0.3.19" serde_json = "1" log = "0.4" @@ -40,6 +42,7 @@ grin_keychain = { path = "./keychain", version = "5.4.0-alpha.0" } grin_p2p = { path = "./p2p", version = "5.4.0-alpha.0" } grin_servers = { path = "./servers", version = "5.4.0-alpha.0" } grin_util = { path = "./util", version = "5.4.0-alpha.0" } +grin_store = { path = "./store", version = "5.4.0-alpha.0" } [dependencies.cursive] version = "0.21" diff --git a/src/bin/grin.rs b/src/bin/grin.rs index e50afbb7c..691380d28 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -34,7 +34,12 @@ use grin_util as util; use grin_util::logger::LogEntry; use std::sync::mpsc; +#[macro_use] +extern crate serde_derive; +extern crate serde_json; + mod cmd; +mod tools; pub mod tui; // include build information diff --git a/src/bin/tools/mod.rs b/src/bin/tools/mod.rs new file mode 100644 index 000000000..5a7f590eb --- /dev/null +++ b/src/bin/tools/mod.rs @@ -0,0 +1,16 @@ +// Copyright 2024 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Grin tools +mod seedcheck; diff --git a/src/bin/tools/seedcheck.rs b/src/bin/tools/seedcheck.rs new file mode 100644 index 000000000..31389a859 --- /dev/null +++ b/src/bin/tools/seedcheck.rs @@ -0,0 +1,200 @@ +// Copyright 2024 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Relatively self-contained seed health checker +use std::sync::Arc; + +use grin_core::core::hash::Hashed; +use grin_core::pow::Difficulty; +use grin_core::{genesis, global}; +use grin_p2p as p2p; +use grin_servers::{resolve_dns_to_addrs, MAINNET_DNS_SEEDS, TESTNET_DNS_SEEDS}; +use std::net::{SocketAddr, TcpStream}; +use std::time::Duration; + +use thiserror::Error; + +use chrono::*; + +#[derive(Error, Debug)] +pub enum SeedCheckError { + #[error("Seed Connect Error {0}")] + SeedConnectError(String), + #[error("Grin Store Error {0}")] + StoreError(String), +} + +impl From for SeedCheckError { + fn from(e: p2p::Error) -> Self { + SeedCheckError::SeedConnectError(format!("{:?}", e)) + } +} + +impl From for SeedCheckError { + fn from(e: grin_store::lmdb::Error) -> Self { + SeedCheckError::StoreError(format!("{:?}", e)) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SeedCheckResults { + pub mainnet: Vec, + pub testnet: Vec, +} + +impl Default for SeedCheckResults { + fn default() -> Self { + Self { + mainnet: vec![], + testnet: vec![], + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SeedCheckResult { + pub url: String, + pub dns_resolutions_found: bool, + pub success: bool, + pub successful_attempts: Vec, + pub unsuccessful_attempts: Vec, +} + +impl Default for SeedCheckResult { + fn default() -> Self { + Self { + url: "".into(), + dns_resolutions_found: false, + success: false, + successful_attempts: vec![], + unsuccessful_attempts: vec![], + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SeedCheckConnectAttempt { + pub ip_addr: String, + pub handshake_success: bool, + pub user_agent: Option, + pub capabilities: Option, +} + +pub fn check_seeds(is_testnet: bool) -> Vec { + let mut result = vec![]; + let (default_seeds, port) = match is_testnet { + true => (TESTNET_DNS_SEEDS, "13414"), + false => (MAINNET_DNS_SEEDS, "3414"), + }; + + if is_testnet { + global::set_local_chain_type(global::ChainTypes::Testnet); + } + + for s in default_seeds.iter() { + info!("Checking seed health for {}", s); + let mut seed_result = SeedCheckResult::default(); + seed_result.url = s.to_string(); + let resolved_dns_entries = resolve_dns_to_addrs(&vec![format!("{}:{}", s, port)]); + if resolved_dns_entries.is_empty() { + info!("FAIL - No dns entries found for {}", s); + result.push(seed_result); + continue; + } + seed_result.dns_resolutions_found = true; + // Check backwards, last contains the latest (at least on my machine!) + for r in resolved_dns_entries.iter().rev() { + let res = check_seed_health(*r, is_testnet); + if let Ok(p) = res { + info!( + "SUCCESS - Performed Handshake with seed for {} at {}. {} - {:?}", + s, r, p.info.user_agent, p.info.capabilities + ); + //info!("{:?}", p); + seed_result.success = true; + seed_result + .successful_attempts + .push(SeedCheckConnectAttempt { + ip_addr: r.to_string(), + handshake_success: true, + user_agent: Some(p.info.user_agent), + capabilities: Some(format!("{:?}", p.info.capabilities)), + }); + } else { + seed_result + .unsuccessful_attempts + .push(SeedCheckConnectAttempt { + ip_addr: r.to_string(), + handshake_success: false, + user_agent: None, + capabilities: None, + }); + } + } + + if !seed_result.success { + info!( + "FAIL - Unable to handshake at any known DNS resolutions for {}", + s + ); + } + + result.push(seed_result); + } + result +} + +fn check_seed_health(addr: p2p::PeerAddr, is_testnet: bool) -> Result { + let capabilities = p2p::types::Capabilities::default(); + let config = p2p::types::P2PConfig::default(); + let adapter = Arc::new(p2p::DummyAdapter {}); + let peers = Arc::new(p2p::Peers::new( + p2p::store::PeerStore::new("peer_store_root")?, + adapter, + config.clone(), + )); + let genesis_hash = match is_testnet { + true => genesis::genesis_test().hash(), + false => genesis::genesis_main().hash(), + }; + + let handshake = p2p::handshake::Handshake::new(genesis_hash, config.clone()); + + match TcpStream::connect_timeout(&addr.0, Duration::from_secs(5)) { + Ok(stream) => { + let addr = SocketAddr::new(config.host, config.port); + let total_diff = Difficulty::from_num(1); + + let peer = p2p::Peer::connect( + stream, + capabilities, + total_diff, + p2p::PeerAddr(addr), + &handshake, + peers, + )?; + Ok(peer) + } + Err(e) => { + trace!( + "connect_peer: on {}:{}. Could not connect to {}: {:?}", + config.host, + config.port, + addr, + e + ); + Err(p2p::Error::Connection(e).into()) + } + } +}