Skip to content

Commit 7512b34

Browse files
authored
Merge pull request #2024 from mintlayer/p2p_specify_min_core_version
New hidden node option: `--p2p-min-peer-software-version`
2 parents 0b720ff + 2e79b60 commit 7512b34

34 files changed

Lines changed: 905 additions & 137 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build-tools/fork-detection/README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,25 @@ Each attempt's directory has the following structure:
3131
- `node_log.txt` - the node's log.
3232

3333
Some notes:
34-
* Currently the script requires Python 3.13 to run, though we may lift this requirement later.
35-
* The script can send an email when it detects an issue using the local SMTP server
34+
- Currently the script requires Python 3.13 to run, though we may lift this requirement later.
35+
- The script can send an email when it detects an issue using the local SMTP server
3636
(if you're on Linux, google for an SMTP Postfix tutorial to set it up).
37-
* Even if the script finds a problem (e.g. a checkpoint mismatch), you're still likely
37+
- Even if the script finds a problem (e.g. a checkpoint mismatch), you're still likely
3838
to end up being on the correct chain. To download the actual fork for further investigation
3939
you can initiate a separate full sync while using the node's option `--custom-checkpoints-csv-file`
4040
to override the correct checkpoints with the wrong ones.
41-
* Once the fork has been downloaded, you'll want to examine the contents of its chainstate db.
41+
- Once the fork has been downloaded, you'll want to examine the contents of its chainstate db.
4242
Currently we have the `chainstate-db-dumper` tool that can dump certain info about blocks
4343
to a CSV file (the most interesting part of it being the ids of pools that continue producing
4444
blocks on that fork).
45-
* Once the fork has been investigated you can "permanently" ban the peers that have been sending it
45+
- Once the fork has been investigated you can "permanently" ban the peers that have been sending it
4646
to you, to prevent it from being reported again and again. To do so, you can add their ip
4747
addresses to `permabanned_peers.txt` (one address per line, '#' starts a comment) in the script's
4848
working directory (it doesn't exist by default, so you'll have to create it first).
4949
Note that the file is checked on every iteration, so you can update it while the script is already
5050
running and it will come into effect when the next iteration starts.
51-
* The script is likely to fail if a networking error occurs, e.g. if it can't query the API server.
51+
- The script is likely to fail if a networking error occurs, e.g. if it can't query the API server.
5252
So, run it in a loop in a shell script (with some delay after each run, to prevent it from spamming
53-
you with warning emails).
53+
you with warning emails).
54+
- If you expect a split to already exist due to a hard fork (in which case reporting it would be useless),
55+
use the `--min-peer-software-version` option to reject all nodes that have not been upgraded.

build-tools/fork-detection/detector.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ def __init__(self, args, email_sender):
144144
"--rpc-password", NODE_RPC_PWD,
145145
"--p2p-custom-disconnection-reason-for-banning", BAN_REASON_STRING
146146
]
147+
148+
if args.min_peer_software_version is not None:
149+
self.node_cmd += ["--p2p-min-peer-software-version", args.min_peer_software_version]
150+
147151
log.info(f"Node run command: {self.node_cmd}")
148152

149153
def run(self):
@@ -549,6 +553,11 @@ def main():
549553
help=("The from address for the notification email. "
550554
"If None, the --notification-email value will be used"),
551555
default=None)
556+
parser.add_argument(
557+
"--min-peer-software-version",
558+
help=("The minimum peer software version, e.g. '1.2.0'. "
559+
"Peers with versions below this one will be rejected and discouraged"),
560+
default=None)
552561
args = parser.parse_args()
553562

554563
email_sender = EmailSender(

common/src/chain/config/builder.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,8 @@ impl Builder {
385385
dns_seeds: chain_type.dns_seeds(),
386386
predefined_peer_addresses: chain_type.predefined_peer_addresses(),
387387
default_rpc_port: chain_type.default_rpc_port(),
388-
software_version: SemVer::try_from(env!("CARGO_PKG_VERSION"))
388+
software_version: env!("CARGO_PKG_VERSION")
389+
.parse()
389390
.expect("invalid CARGO_PKG_VERSION value"),
390391
max_block_header_size: super::MAX_BLOCK_HEADER_SIZE,
391392
max_block_size_with_standard_txs: super::MAX_BLOCK_TXS_SIZE,

common/src/primitives/semver.rs

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16+
use std::str::FromStr;
17+
1618
use serde::Serialize;
1719
use serialization::{Decode, Encode};
1820

@@ -33,16 +35,10 @@ impl SemVer {
3335
}
3436
}
3537

36-
impl From<SemVer> for String {
37-
fn from(v: SemVer) -> String {
38-
format!("{}.{}.{}", v.major, v.minor, v.patch)
39-
}
40-
}
41-
42-
impl TryFrom<&str> for SemVer {
43-
type Error = &'static str;
38+
impl FromStr for SemVer {
39+
type Err = &'static str;
4440

45-
fn try_from(v: &str) -> Result<SemVer, Self::Error> {
41+
fn from_str(v: &str) -> Result<SemVer, Self::Err> {
4642
let split_version = v.split('.').collect::<Vec<_>>();
4743
if split_version.len() != 3 {
4844
return Err("Invalid version. Number of components is wrong.");
@@ -61,20 +57,22 @@ impl TryFrom<&str> for SemVer {
6157
}
6258
}
6359

64-
impl TryFrom<String> for SemVer {
65-
type Error = &'static str;
66-
67-
fn try_from(v: String) -> Result<SemVer, Self::Error> {
68-
Self::try_from(v.as_str())
69-
}
70-
}
71-
7260
impl std::fmt::Display for SemVer {
7361
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7462
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
7563
}
7664
}
7765

66+
// TODO: this is redundant, but it's still used inside a macro in `regtest_chain_config_builder`.
67+
// Refactor the macro and remove this.
68+
impl TryFrom<String> for SemVer {
69+
type Error = <SemVer as FromStr>::Err;
70+
71+
fn try_from(v: String) -> Result<SemVer, Self::Error> {
72+
Self::from_str(v.as_str())
73+
}
74+
}
75+
7876
#[cfg(test)]
7977
mod tests {
8078
use super::*;
@@ -84,84 +82,90 @@ mod tests {
8482
#[test]
8583
fn vertest_string() {
8684
let version = SemVer::new(1, 2, 3);
87-
assert_eq!(String::from(version), "1.2.3");
85+
assert_eq!(version.to_string(), "1.2.3");
8886

8987
let version = SemVer::new(0xff, 0xff, 0xff);
90-
assert_eq!(String::from(version), "255.255.255");
88+
assert_eq!(version.to_string(), "255.255.255");
9189

9290
let version = SemVer::new(0xff, 0xff, 0xffff);
93-
assert_eq!(String::from(version), "255.255.65535");
91+
assert_eq!(version.to_string(), "255.255.65535");
9492

9593
let version = SemVer::new(1, 2, 0x500);
96-
assert_eq!(String::from(version), "1.2.1280");
94+
assert_eq!(version.to_string(), "1.2.1280");
9795

9896
assert_eq!(
99-
SemVer::try_from(" "),
97+
SemVer::from_str(" "),
10098
Err("Invalid version. Number of components is wrong.")
10199
);
102100

103101
assert_eq!(
104-
SemVer::try_from(""),
102+
SemVer::from_str(""),
105103
Err("Invalid version. Number of components is wrong.")
106104
);
107105

108106
assert_eq!(
109-
SemVer::try_from("1.2"),
107+
SemVer::from_str("1.2"),
110108
Err("Invalid version. Number of components is wrong.")
111109
);
112110

113111
assert_eq!(
114-
SemVer::try_from("1"),
112+
SemVer::from_str("1"),
115113
Err("Invalid version. Number of components is wrong.")
116114
);
117115

118116
let version = "hello";
119117
assert_eq!(
120-
SemVer::try_from(version),
118+
SemVer::from_str(version),
121119
Err("Invalid version. Number of components is wrong.")
122120
);
123121
assert_eq!(
124-
SemVer::try_from(version),
122+
SemVer::from_str(version),
125123
Err("Invalid version. Number of components is wrong.")
126124
);
127125

128-
let version = "1.2.3".to_string();
129-
assert_eq!(SemVer::try_from(version.clone()), Ok(SemVer::new(1, 2, 3)));
130-
assert_eq!(SemVer::try_from(version), Ok(SemVer::new(1, 2, 3)));
126+
let version = "1.2.3";
127+
assert_eq!(SemVer::from_str(version), Ok(SemVer::new(1, 2, 3)));
128+
assert_eq!(
129+
SemVer::try_from(version.to_owned()),
130+
Ok(SemVer::new(1, 2, 3))
131+
);
131132

132133
let version = "255.255.255";
133-
assert_eq!(SemVer::try_from(version), Ok(SemVer::new(255, 255, 255)));
134-
assert_eq!(SemVer::try_from(version), Ok(SemVer::new(255, 255, 255)));
134+
assert_eq!(SemVer::from_str(version), Ok(SemVer::new(255, 255, 255)));
135+
assert_eq!(
136+
SemVer::try_from(version.to_owned()),
137+
Ok(SemVer::new(255, 255, 255))
138+
);
135139

136-
let version = "255.255.65535".to_string();
140+
let version = "255.255.65535";
141+
assert_eq!(SemVer::from_str(version), Ok(SemVer::new(255, 255, 65535)));
137142
assert_eq!(
138-
SemVer::try_from(version.clone()),
143+
SemVer::try_from(version.to_owned()),
139144
Ok(SemVer::new(255, 255, 65535))
140145
);
141-
assert_eq!(SemVer::try_from(version), Ok(SemVer::new(255, 255, 65535)));
142146

143147
let version = "255.255.65536";
144148
assert_eq!(
145-
SemVer::try_from(version),
149+
SemVer::from_str(version),
146150
Err("Parsing SemVer component to integer failed")
147151
);
148152
assert_eq!(
149-
SemVer::try_from(version.to_string()),
153+
SemVer::try_from(version.to_owned()),
150154
Err("Parsing SemVer component to integer failed")
151155
);
152156

153157
assert_eq!(
154-
SemVer::try_from("1.2.a"),
158+
SemVer::from_str("1.2.a"),
155159
Err("Parsing SemVer component to integer failed")
156160
);
157161

158162
assert_eq!(
159-
SemVer::try_from("1.2."),
163+
SemVer::from_str("1.2."),
160164
Err("Parsing SemVer component to integer failed")
161165
);
162166

163167
assert_eq!(
164-
SemVer::try_from("1..3"),
168+
SemVer::from_str("1..3"),
165169
Err("Parsing SemVer component to integer failed")
166170
);
167171
}

node-daemon/docs/RPC.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,8 +1071,7 @@ nothing
10711071

10721072
Attempt to connect to a remote node (just once).
10731073

1074-
For persistent connections see `add_reserved_node` should be used.
1075-
Keep in mind that `add_reserved_node` works completely differently.
1074+
For persistent connections consider using `add_reserved_node`.
10761075

10771076

10781077
Parameters:
@@ -1087,7 +1086,10 @@ nothing
10871086

10881087
### Method `p2p_disconnect`
10891088

1090-
Disconnect peer, given its id.
1089+
Disconnect a peer given its id.
1090+
1091+
If it was an outbound connection, the peer address will be removed from the peer database,
1092+
and if the connection was inbound, the address will be kept.
10911093

10921094

10931095
Parameters:

node-lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ fs4.workspace = true
2929
jsonrpsee = { workspace = true, features = ["macros"] }
3030
paste.workspace = true
3131
serde = { workspace = true, features = ["derive"] }
32+
serde_with.workspace = true
3233
thiserror.workspace = true
3334
tokio = { workspace = true, default-features = false }
3435
toml.workspace = true

node-lib/src/config_files/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ fn p2p_config(config: P2pConfigFile, options: &RunOptions) -> P2pConfigFile {
199199
node_type,
200200
force_dns_query_if_no_global_addresses_known,
201201
custom_disconnection_reason_for_banning,
202+
min_peer_software_version,
202203
} = config;
203204

204205
let networking_enabled = options.p2p_networking_enabled.or(networking_enabled);
@@ -226,6 +227,8 @@ fn p2p_config(config: P2pConfigFile, options: &RunOptions) -> P2pConfigFile {
226227
.p2p_custom_disconnection_reason_for_banning
227228
.clone()
228229
.or(custom_disconnection_reason_for_banning);
230+
let min_peer_software_version =
231+
options.p2p_min_peer_software_version.or(min_peer_software_version);
229232

230233
P2pConfigFile {
231234
networking_enabled,
@@ -246,6 +249,7 @@ fn p2p_config(config: P2pConfigFile, options: &RunOptions) -> P2pConfigFile {
246249
node_type,
247250
force_dns_query_if_no_global_addresses_known,
248251
custom_disconnection_reason_for_banning,
252+
min_peer_software_version,
249253
}
250254
}
251255

node-lib/src/config_files/p2p.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ use std::{
2020
time::Duration,
2121
};
2222

23-
use common::primitives::user_agent::mintlayer_core_user_agent;
2423
use serde::{Deserialize, Serialize};
2524

25+
use common::primitives::{semver::SemVer, user_agent::mintlayer_core_user_agent};
2626
use p2p::{
2727
ban_config::BanConfig,
2828
config::{NodeType, P2pConfig},
@@ -61,6 +61,7 @@ impl FromStr for NodeTypeConfigFile {
6161

6262
/// The p2p subsystem configuration.
6363
#[must_use]
64+
#[serde_with::serde_as]
6465
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
6566
#[serde(deny_unknown_fields)]
6667
pub struct P2pConfigFile {
@@ -102,6 +103,11 @@ pub struct P2pConfigFile {
102103
pub force_dns_query_if_no_global_addresses_known: Option<bool>,
103104
/// If set, this text will be sent to banned peers as part of the DisconnectionReason.
104105
pub custom_disconnection_reason_for_banning: Option<String>,
106+
/// If the peer's user agent is MintlayerCore (which is always true at the moment),
107+
/// the connection will be rejected and the peer discouraged if the peer's software version
108+
/// is less than the one specified.
109+
#[serde_as(as = "Option<serde_with::DisplayFromStr>")]
110+
pub min_peer_software_version: Option<SemVer>,
105111
}
106112

107113
impl From<P2pConfigFile> for P2pConfig {
@@ -125,6 +131,7 @@ impl From<P2pConfigFile> for P2pConfig {
125131
node_type,
126132
force_dns_query_if_no_global_addresses_known,
127133
custom_disconnection_reason_for_banning,
134+
min_peer_software_version,
128135
} = config_file;
129136

130137
P2pConfig {
@@ -179,6 +186,8 @@ impl From<P2pConfigFile> for P2pConfig {
179186
allow_same_ip_connections: Default::default(),
180187

181188
peerdb_config: Default::default(),
189+
190+
min_peer_software_version,
182191
},
183192
protocol_config: Default::default(),
184193
peer_handshake_timeout: Default::default(),

0 commit comments

Comments
 (0)