Skip to content

Commit

Permalink
Addition of seed-check logic to top-level crate (#3801)
Browse files Browse the repository at this point in the history
* Addition of initial seed check logic

* updated to call from command line, now need to do something about peer store root output

* rework check to delete temp files, add output options, testing
  • Loading branch information
yeastplume authored Nov 27, 2024
1 parent 6c01204 commit b93d88b
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
25 changes: 25 additions & 0 deletions src/bin/grin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extern crate clap;
extern crate log;
use crate::config::config::SERVER_CONFIG_FILE_NAME;
use crate::core::global;
use crate::tools::check_seeds;
use crate::util::init_logger;
use clap::App;
use futures::channel::oneshot;
Expand All @@ -32,9 +33,16 @@ use grin_p2p as p2p;
use grin_servers as servers;
use grin_util as util;
use grin_util::logger::LogEntry;
use std::fs::File;
use std::io::Write;
use std::sync::mpsc;

#[macro_use]
extern crate serde_derive;
extern crate serde_json;

mod cmd;
mod tools;
pub mod tui;

// include build information
Expand Down Expand Up @@ -197,6 +205,23 @@ fn real_main() -> i32 {
}
}

// seedcheck command
("seedcheck", Some(seedcheck_args)) => {
let is_testnet = seedcheck_args.is_present("testnet");
let results = check_seeds(is_testnet);
let output =
serde_json::to_string_pretty(&results).expect("Unable to serialize results");

if let Some(output_file) = seedcheck_args.value_of("output") {
let mut file = File::create(output_file).expect("Unable to create file");
writeln!(file, "{}", output).expect("Unable to write data");
println!("Results written to {}", output_file);
} else {
println!("{}", output);
}
0
}

// If nothing is specified, try to just use the config file instead
// this could possibly become the way to configure most things
// with most command line options being phased out
Expand Down
11 changes: 11 additions & 0 deletions src/bin/grin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,14 @@ subcommands:
- hash:
help: The header hash to invalidate
required: true
- seedcheck:
about: Check the health of seed nodes
args:
- testnet:
help: Run seed check against Testnet (as opposed to Mainnet)
long: testnet
takes_value: false
- output:
help: Output file to write the results to
long: output
takes_value: true
18 changes: 18 additions & 0 deletions src/bin/tools/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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;

pub use seedcheck::check_seeds;
209 changes: 209 additions & 0 deletions src/bin/tools/seedcheck.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// 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::fs;
use std::net::{SocketAddr, TcpStream};
use std::time::Duration;

use thiserror::Error;

#[derive(Error, Debug)]
pub enum SeedCheckError {
#[error("Seed Connect Error {0}")]
SeedConnectError(String),
#[error("Grin Store Error {0}")]
StoreError(String),
}

impl From<p2p::Error> for SeedCheckError {
fn from(e: p2p::Error) -> Self {
SeedCheckError::SeedConnectError(format!("{:?}", e))
}
}

impl From<grin_store::lmdb::Error> for SeedCheckError {
fn from(e: grin_store::lmdb::Error) -> Self {
SeedCheckError::StoreError(format!("{:?}", e))
}
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SeedCheckResults {
pub mainnet: Vec<SeedCheckResult>,
pub testnet: Vec<SeedCheckResult>,
}

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<SeedCheckConnectAttempt>,
pub unsuccessful_attempts: Vec<SeedCheckConnectAttempt>,
}

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<String>,
pub capabilities: Option<String>,
}

pub fn check_seeds(is_testnet: bool) -> Vec<SeedCheckResult> {
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);
}

let config = p2p::types::P2PConfig::default();
let adapter = Arc::new(p2p::DummyAdapter {});
let peers = Arc::new(p2p::Peers::new(
p2p::store::PeerStore::new(".__grintmp__/peer_store_root").unwrap(),
adapter,
config.clone(),
));

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, &peers);
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);
}

// Clean up temporary files
fs::remove_dir_all(".__grintmp__").expect("Unable to delete temporary files");

result
}

fn check_seed_health(
addr: p2p::PeerAddr,
is_testnet: bool,
peers: &Arc<p2p::Peers>,
) -> Result<p2p::Peer, SeedCheckError> {
let config = p2p::types::P2PConfig::default();
let capabilities = p2p::types::Capabilities::default();
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.clone(),
)?;
Ok(peer)
}
Err(e) => {
trace!(
"connect_peer: on {}:{}. Could not connect to {}: {:?}",
config.host,
config.port,
addr,
e
);
Err(p2p::Error::Connection(e).into())
}
}
}

0 comments on commit b93d88b

Please sign in to comment.