mirror of
https://github.com/mimblewimble/grin.git
synced 2025-05-03 15:51:14 +03:00
Addition of seed-check logic to top-level crate (#3801)
* 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
This commit is contained in:
parent
6c012043c0
commit
b93d88b58c
6 changed files with 268 additions and 0 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -952,8 +952,10 @@ dependencies = [
|
|||
"humansize",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"term",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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
src/bin/tools/mod.rs
Normal file
18
src/bin/tools/mod.rs
Normal file
|
@ -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
src/bin/tools/seedcheck.rs
Normal file
209
src/bin/tools/seedcheck.rs
Normal file
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue