Skip to content

Commit b93d88b

Browse files
authored
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
1 parent 6c01204 commit b93d88b

File tree

6 files changed

+268
-0
lines changed

6 files changed

+268
-0
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ path = "src/bin/grin.rs"
2222
[dependencies]
2323
blake2-rfc = "0.2"
2424
chrono = "0.4.11"
25+
thiserror = "1"
2526
clap = { version = "2.33", features = ["yaml"] }
2627
ctrlc = { version = "3.1", features = ["termination"] }
2728
cursive_table_view = "0.15.0"
2829
humansize = "1.1.0"
2930
serde = "1"
31+
serde_derive = "1"
3032
futures = "0.3.19"
3133
serde_json = "1"
3234
log = "0.4"
@@ -40,6 +42,7 @@ grin_keychain = { path = "./keychain", version = "5.4.0-alpha.0" }
4042
grin_p2p = { path = "./p2p", version = "5.4.0-alpha.0" }
4143
grin_servers = { path = "./servers", version = "5.4.0-alpha.0" }
4244
grin_util = { path = "./util", version = "5.4.0-alpha.0" }
45+
grin_store = { path = "./store", version = "5.4.0-alpha.0" }
4346

4447
[dependencies.cursive]
4548
version = "0.21"

src/bin/grin.rs

+25
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ extern crate clap;
2121
extern crate log;
2222
use crate::config::config::SERVER_CONFIG_FILE_NAME;
2323
use crate::core::global;
24+
use crate::tools::check_seeds;
2425
use crate::util::init_logger;
2526
use clap::App;
2627
use futures::channel::oneshot;
@@ -32,9 +33,16 @@ use grin_p2p as p2p;
3233
use grin_servers as servers;
3334
use grin_util as util;
3435
use grin_util::logger::LogEntry;
36+
use std::fs::File;
37+
use std::io::Write;
3538
use std::sync::mpsc;
3639

40+
#[macro_use]
41+
extern crate serde_derive;
42+
extern crate serde_json;
43+
3744
mod cmd;
45+
mod tools;
3846
pub mod tui;
3947

4048
// include build information
@@ -197,6 +205,23 @@ fn real_main() -> i32 {
197205
}
198206
}
199207

208+
// seedcheck command
209+
("seedcheck", Some(seedcheck_args)) => {
210+
let is_testnet = seedcheck_args.is_present("testnet");
211+
let results = check_seeds(is_testnet);
212+
let output =
213+
serde_json::to_string_pretty(&results).expect("Unable to serialize results");
214+
215+
if let Some(output_file) = seedcheck_args.value_of("output") {
216+
let mut file = File::create(output_file).expect("Unable to create file");
217+
writeln!(file, "{}", output).expect("Unable to write data");
218+
println!("Results written to {}", output_file);
219+
} else {
220+
println!("{}", output);
221+
}
222+
0
223+
}
224+
200225
// If nothing is specified, try to just use the config file instead
201226
// this could possibly become the way to configure most things
202227
// with most command line options being phased out

src/bin/grin.yml

+11
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,14 @@ subcommands:
9292
- hash:
9393
help: The header hash to invalidate
9494
required: true
95+
- seedcheck:
96+
about: Check the health of seed nodes
97+
args:
98+
- testnet:
99+
help: Run seed check against Testnet (as opposed to Mainnet)
100+
long: testnet
101+
takes_value: false
102+
- output:
103+
help: Output file to write the results to
104+
long: output
105+
takes_value: true

src/bin/tools/mod.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2024 The Grin Developers
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/// Grin tools
16+
mod seedcheck;
17+
18+
pub use seedcheck::check_seeds;

src/bin/tools/seedcheck.rs

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Copyright 2024 The Grin Developers
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/// Relatively self-contained seed health checker
16+
use std::sync::Arc;
17+
18+
use grin_core::core::hash::Hashed;
19+
use grin_core::pow::Difficulty;
20+
use grin_core::{genesis, global};
21+
use grin_p2p as p2p;
22+
use grin_servers::{resolve_dns_to_addrs, MAINNET_DNS_SEEDS, TESTNET_DNS_SEEDS};
23+
use std::fs;
24+
use std::net::{SocketAddr, TcpStream};
25+
use std::time::Duration;
26+
27+
use thiserror::Error;
28+
29+
#[derive(Error, Debug)]
30+
pub enum SeedCheckError {
31+
#[error("Seed Connect Error {0}")]
32+
SeedConnectError(String),
33+
#[error("Grin Store Error {0}")]
34+
StoreError(String),
35+
}
36+
37+
impl From<p2p::Error> for SeedCheckError {
38+
fn from(e: p2p::Error) -> Self {
39+
SeedCheckError::SeedConnectError(format!("{:?}", e))
40+
}
41+
}
42+
43+
impl From<grin_store::lmdb::Error> for SeedCheckError {
44+
fn from(e: grin_store::lmdb::Error) -> Self {
45+
SeedCheckError::StoreError(format!("{:?}", e))
46+
}
47+
}
48+
49+
#[derive(Serialize, Deserialize, Debug)]
50+
pub struct SeedCheckResults {
51+
pub mainnet: Vec<SeedCheckResult>,
52+
pub testnet: Vec<SeedCheckResult>,
53+
}
54+
55+
impl Default for SeedCheckResults {
56+
fn default() -> Self {
57+
Self {
58+
mainnet: vec![],
59+
testnet: vec![],
60+
}
61+
}
62+
}
63+
64+
#[derive(Debug, Serialize, Deserialize)]
65+
pub struct SeedCheckResult {
66+
pub url: String,
67+
pub dns_resolutions_found: bool,
68+
pub success: bool,
69+
pub successful_attempts: Vec<SeedCheckConnectAttempt>,
70+
pub unsuccessful_attempts: Vec<SeedCheckConnectAttempt>,
71+
}
72+
73+
impl Default for SeedCheckResult {
74+
fn default() -> Self {
75+
Self {
76+
url: "".into(),
77+
dns_resolutions_found: false,
78+
success: false,
79+
successful_attempts: vec![],
80+
unsuccessful_attempts: vec![],
81+
}
82+
}
83+
}
84+
85+
#[derive(Debug, Serialize, Deserialize)]
86+
pub struct SeedCheckConnectAttempt {
87+
pub ip_addr: String,
88+
pub handshake_success: bool,
89+
pub user_agent: Option<String>,
90+
pub capabilities: Option<String>,
91+
}
92+
93+
pub fn check_seeds(is_testnet: bool) -> Vec<SeedCheckResult> {
94+
let mut result = vec![];
95+
let (default_seeds, port) = match is_testnet {
96+
true => (TESTNET_DNS_SEEDS, "13414"),
97+
false => (MAINNET_DNS_SEEDS, "3414"),
98+
};
99+
100+
if is_testnet {
101+
global::set_local_chain_type(global::ChainTypes::Testnet);
102+
}
103+
104+
let config = p2p::types::P2PConfig::default();
105+
let adapter = Arc::new(p2p::DummyAdapter {});
106+
let peers = Arc::new(p2p::Peers::new(
107+
p2p::store::PeerStore::new(".__grintmp__/peer_store_root").unwrap(),
108+
adapter,
109+
config.clone(),
110+
));
111+
112+
for s in default_seeds.iter() {
113+
info!("Checking seed health for {}", s);
114+
let mut seed_result = SeedCheckResult::default();
115+
seed_result.url = s.to_string();
116+
let resolved_dns_entries = resolve_dns_to_addrs(&vec![format!("{}:{}", s, port)]);
117+
if resolved_dns_entries.is_empty() {
118+
info!("FAIL - No dns entries found for {}", s);
119+
result.push(seed_result);
120+
continue;
121+
}
122+
seed_result.dns_resolutions_found = true;
123+
// Check backwards, last contains the latest (at least on my machine!)
124+
for r in resolved_dns_entries.iter().rev() {
125+
let res = check_seed_health(*r, is_testnet, &peers);
126+
if let Ok(p) = res {
127+
info!(
128+
"SUCCESS - Performed Handshake with seed for {} at {}. {} - {:?}",
129+
s, r, p.info.user_agent, p.info.capabilities
130+
);
131+
//info!("{:?}", p);
132+
seed_result.success = true;
133+
seed_result
134+
.successful_attempts
135+
.push(SeedCheckConnectAttempt {
136+
ip_addr: r.to_string(),
137+
handshake_success: true,
138+
user_agent: Some(p.info.user_agent),
139+
capabilities: Some(format!("{:?}", p.info.capabilities)),
140+
});
141+
} else {
142+
seed_result
143+
.unsuccessful_attempts
144+
.push(SeedCheckConnectAttempt {
145+
ip_addr: r.to_string(),
146+
handshake_success: false,
147+
user_agent: None,
148+
capabilities: None,
149+
});
150+
}
151+
}
152+
153+
if !seed_result.success {
154+
info!(
155+
"FAIL - Unable to handshake at any known DNS resolutions for {}",
156+
s
157+
);
158+
}
159+
160+
result.push(seed_result);
161+
}
162+
163+
// Clean up temporary files
164+
fs::remove_dir_all(".__grintmp__").expect("Unable to delete temporary files");
165+
166+
result
167+
}
168+
169+
fn check_seed_health(
170+
addr: p2p::PeerAddr,
171+
is_testnet: bool,
172+
peers: &Arc<p2p::Peers>,
173+
) -> Result<p2p::Peer, SeedCheckError> {
174+
let config = p2p::types::P2PConfig::default();
175+
let capabilities = p2p::types::Capabilities::default();
176+
let genesis_hash = match is_testnet {
177+
true => genesis::genesis_test().hash(),
178+
false => genesis::genesis_main().hash(),
179+
};
180+
181+
let handshake = p2p::handshake::Handshake::new(genesis_hash, config.clone());
182+
183+
match TcpStream::connect_timeout(&addr.0, Duration::from_secs(5)) {
184+
Ok(stream) => {
185+
let addr = SocketAddr::new(config.host, config.port);
186+
let total_diff = Difficulty::from_num(1);
187+
188+
let peer = p2p::Peer::connect(
189+
stream,
190+
capabilities,
191+
total_diff,
192+
p2p::PeerAddr(addr),
193+
&handshake,
194+
peers.clone(),
195+
)?;
196+
Ok(peer)
197+
}
198+
Err(e) => {
199+
trace!(
200+
"connect_peer: on {}:{}. Could not connect to {}: {:?}",
201+
config.host,
202+
config.port,
203+
addr,
204+
e
205+
);
206+
Err(p2p::Error::Connection(e).into())
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)