Skip to content

Commit 4221eb0

Browse files
committed
Add node option to specify custom disconnection reason when a peer is banned, and use it in the fork detector
1 parent b98b34d commit 4221eb0

File tree

31 files changed

+105
-13
lines changed

31 files changed

+105
-13
lines changed

build-tools/fork-detection/detector.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@
8181

8282
DEFAULT_BAN_DURATION_HOURS = 12
8383

84+
# Custom disconnection reason to send to peers when banning them. We don't want the default
85+
# "Your address is banned" to be sent, because it sounds like the peer's node is faulty.
86+
# We also don't want to be too specific, e.g. the peer doesn't need to know thar something
87+
# called "fork detector" is running somewhere. So we choose a reason that is somewhat vague.
88+
# Also note that since we ban peers when networking is already disabled, the peer can only
89+
# get this message when attempting an outbound connectiion to the detector's node.
90+
BAN_REASON_STRING = "Cannot accept connections at this moment"
91+
8492
# We use Queue.shutdown which is only available since Python v3.13
8593
MIN_PYTHON_VERSION_MAJOR = 3
8694
MIN_PYTHON_VERSION_MINOR = 13
@@ -89,7 +97,6 @@
8997
PERMABAN_DURATION_DAYS = 30
9098
PERMABAN_DURATION_SECS = 3600 * 24 * PERMABAN_DURATION_DAYS
9199

92-
93100
class Handler():
94101
def __init__(self, args, email_sender):
95102
CONSOLE_PRINTER.set_status("Initializing")
@@ -135,6 +142,7 @@ def __init__(self, args, email_sender):
135142
"--rpc-bind-address", args.node_rpc_bind_address,
136143
"--rpc-username", NODE_RPC_USER,
137144
"--rpc-password", NODE_RPC_PWD,
145+
"--p2p-custom-disconnection-reason-for-banning", BAN_REASON_STRING
138146
]
139147
log.info(f"Node run command: {self.node_cmd}")
140148

@@ -315,10 +323,8 @@ def on_attempt_completion():
315323

316324
peer_ips_to_ban = self.get_node_peer_ip_addrs_to_ban()
317325

318-
# Before banning, force the disconnection of all peers by disabling networking,
319-
# to avoid sending them the "scary" disconnection reason "Your address is banned"
320-
# (though nodes may still see this reason if they try connecting to our node
321-
# during the next attempt).
326+
# Before banning, disable networking; this will disconnect all peers and prevent them
327+
# from reconnecting again.
322328
self.node_rpc_client.enable_networking(False)
323329
# Give the node some time to actually disconnect all peers.
324330
time.sleep(2)

dns-server/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ async fn run(options: DnsServerRunOptions) -> anyhow::Result<Never> {
8484
sync_stalling_timeout: Default::default(),
8585
peer_manager_config: Default::default(),
8686
protocol_config: Default::default(),
87+
custom_disconnection_reason_for_banning: Default::default(),
8788
});
8889

8990
let transport = p2p::make_p2p_transport();

node-lib/src/config_files/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ fn p2p_config(config: P2pConfigFile, options: &RunOptions) -> P2pConfigFile {
196196
sync_stalling_timeout,
197197
node_type,
198198
force_dns_query_if_no_global_addresses_known,
199+
custom_disconnection_reason_for_banning,
199200
} = config;
200201

201202
let networking_enabled = options.p2p_networking_enabled.or(networking_enabled);
@@ -219,6 +220,10 @@ fn p2p_config(config: P2pConfigFile, options: &RunOptions) -> P2pConfigFile {
219220
let force_dns_query_if_no_global_addresses_known = options
220221
.p2p_force_dns_query_if_no_global_addresses_known
221222
.or(force_dns_query_if_no_global_addresses_known);
223+
let custom_disconnection_reason_for_banning = options
224+
.p2p_custom_disconnection_reason_for_banning
225+
.clone()
226+
.or(custom_disconnection_reason_for_banning);
222227

223228
P2pConfigFile {
224229
networking_enabled,
@@ -238,6 +243,7 @@ fn p2p_config(config: P2pConfigFile, options: &RunOptions) -> P2pConfigFile {
238243
sync_stalling_timeout,
239244
node_type,
240245
force_dns_query_if_no_global_addresses_known,
246+
custom_disconnection_reason_for_banning,
241247
}
242248
}
243249

node-lib/src/config_files/p2p.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ pub struct P2pConfigFile {
100100
/// If true, the node will perform an early dns query if the peer db doesn't contain
101101
/// any global addresses at startup.
102102
pub force_dns_query_if_no_global_addresses_known: Option<bool>,
103+
/// If set, this text will be sent to banned peers as part of the DisconnectionReason.
104+
pub custom_disconnection_reason_for_banning: Option<String>,
103105
}
104106

105107
impl From<P2pConfigFile> for P2pConfig {
@@ -122,6 +124,7 @@ impl From<P2pConfigFile> for P2pConfig {
122124
sync_stalling_timeout,
123125
node_type,
124126
force_dns_query_if_no_global_addresses_known,
127+
custom_disconnection_reason_for_banning,
125128
} = config_file;
126129

127130
P2pConfig {
@@ -179,6 +182,7 @@ impl From<P2pConfigFile> for P2pConfig {
179182
},
180183
protocol_config: Default::default(),
181184
peer_handshake_timeout: Default::default(),
185+
custom_disconnection_reason_for_banning,
182186
}
183187
}
184188
}

node-lib/src/options.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,10 @@ pub struct RunOptions {
318318
#[arg(hide = true)]
319319
pub p2p_force_dns_query_if_no_global_addresses_known: Option<bool>,
320320

321+
/// If specified, this text will be sent to banned peers as part of the DisconnectionReason.
322+
#[clap(long, hide = true)]
323+
pub p2p_custom_disconnection_reason_for_banning: Option<String>,
324+
321325
/// A maximum tip age in seconds.
322326
///
323327
/// The initial block download is finished if the difference between the current time and the
@@ -426,6 +430,7 @@ mod tests {
426430
p2p_sync_stalling_timeout: Default::default(),
427431
p2p_max_clock_diff: Default::default(),
428432
p2p_force_dns_query_if_no_global_addresses_known: Default::default(),
433+
p2p_custom_disconnection_reason_for_banning: Default::default(),
429434
max_tip_age: Default::default(),
430435
rpc_bind_address: Default::default(),
431436
rpc_enabled: Default::default(),

node-lib/tests/cli.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ fn read_config_override_values() {
114114
let p2p_sync_stalling_timeout = NonZeroU64::new(37).unwrap();
115115
let p2p_max_clock_diff = 15;
116116
let p2p_force_dns_query_if_no_global_addresses_known = true;
117+
let p2p_custom_disconnection_reason_for_banning = "foo".to_owned();
117118
let rpc_bind_address = "127.0.0.1:5432".parse().unwrap();
118119
let backend_type = StorageBackendConfigFile::InMemory;
119120
let node_type = NodeTypeConfigFile::FullNode;
@@ -152,6 +153,9 @@ fn read_config_override_values() {
152153
p2p_force_dns_query_if_no_global_addresses_known: Some(
153154
p2p_force_dns_query_if_no_global_addresses_known,
154155
),
156+
p2p_custom_disconnection_reason_for_banning: (Some(
157+
p2p_custom_disconnection_reason_for_banning.clone(),
158+
)),
155159
max_tip_age: Some(max_tip_age),
156160
rpc_bind_address: Some(rpc_bind_address),
157161
rpc_enabled: Some(true),
@@ -273,6 +277,10 @@ fn read_config_override_values() {
273277
config.p2p.as_ref().unwrap().force_dns_query_if_no_global_addresses_known,
274278
Some(p2p_force_dns_query_if_no_global_addresses_known)
275279
);
280+
assert_eq!(
281+
config.p2p.as_ref().unwrap().custom_disconnection_reason_for_banning,
282+
Some(p2p_custom_disconnection_reason_for_banning)
283+
);
276284

277285
assert_eq!(
278286
config.rpc.clone().unwrap().bind_address,

p2p/backend-test-suite/src/block_announcement.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ where
193193
sync_stalling_timeout: Default::default(),
194194
peer_manager_config: Default::default(),
195195
protocol_config: Default::default(),
196+
custom_disconnection_reason_for_banning: Default::default(),
196197
});
197198
let shutdown = Arc::new(SeqCstAtomicBool::new(false));
198199
let (shutdown_sender_1, shutdown_receiver) = oneshot::channel();

p2p/src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ pub struct P2pConfig {
107107
pub peer_manager_config: PeerManagerConfig,
108108
/// Various limits related to the protocol; these should only be overridden in tests.
109109
pub protocol_config: ProtocolConfig,
110+
/// If set, this text will be sent to banned peers as part of the DisconnectionReason.
111+
pub custom_disconnection_reason_for_banning: Option<String>,
110112
}
111113

112114
impl P2pConfig {

p2p/src/disconnection_reason.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use networking::error::{MessageCodecError, NetworkingError};
1919
use p2p_types::services::Services;
2020

2121
use crate::{
22+
config::P2pConfig,
2223
error::{ConnectionValidationError, P2pError},
2324
protocol::MIN_SUPPORTED_PROTOCOL_VERSION,
2425
};
@@ -107,13 +108,24 @@ pub enum DisconnectionReason {
107108
// Another possible reason for message decoding failure.
108109
#[display("Your message size {actual_size} exceeded the maximum size {max_size}")]
109110
MessageTooLarge { actual_size: usize, max_size: usize },
111+
112+
#[display("{_0}")]
113+
CustomMessage(String),
110114
}
111115

112116
impl DisconnectionReason {
113-
pub fn from_result<T>(res: &crate::Result<T>) -> Option<Self> {
117+
pub fn from_result<T>(res: &crate::Result<T>, p2p_config: &P2pConfig) -> Option<Self> {
114118
match res {
115119
Ok(_) => None,
116-
Err(err) => Self::from_error(err),
120+
Err(err) => Self::from_error(err, p2p_config),
121+
}
122+
}
123+
124+
pub fn ban_reason(p2p_config: &P2pConfig) -> Self {
125+
if let Some(custom_reason) = p2p_config.custom_disconnection_reason_for_banning.as_ref() {
126+
Self::CustomMessage(custom_reason.clone())
127+
} else {
128+
Self::AddressBanned
117129
}
118130
}
119131

@@ -163,7 +175,7 @@ impl DisconnectionReason {
163175
}
164176
}
165177

166-
pub fn from_error(err: &P2pError) -> Option<Self> {
178+
pub fn from_error(err: &P2pError, p2p_config: &P2pConfig) -> Option<Self> {
167179
match err {
168180
P2pError::NetworkingError(err) => Self::from_networking_error(err),
169181
P2pError::ProtocolError(_)
@@ -203,7 +215,7 @@ impl DisconnectionReason {
203215
Some(Self::TooManyInboundPeersAndCannotEvictAnyone)
204216
}
205217
ConnectionValidationError::AddressBanned { address: _ } => {
206-
Some(Self::AddressBanned)
218+
Some(Self::ban_reason(p2p_config))
207219
}
208220
ConnectionValidationError::AddressDiscouraged { address: _ } => {
209221
Some(Self::AddressDiscouraged)

p2p/src/net/default_backend/peer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ where
229229
})();
230230

231231
self.maybe_send_will_disconnect_for_protocol_version(
232-
DisconnectionReason::from_result(&result),
232+
DisconnectionReason::from_result(&result, &self.p2p_config),
233233
peer_protocol_version,
234234
)
235235
.await?;
@@ -498,7 +498,7 @@ where
498498
log::info!("Connection closed for peer {}, reason: {err:?}", self.peer_id);
499499

500500
let err = P2pError::NetworkingError(err);
501-
self.maybe_send_will_disconnect(DisconnectionReason::from_error(&err)).await?;
501+
self.maybe_send_will_disconnect(DisconnectionReason::from_error(&err, &self.p2p_config)).await?;
502502

503503
let ban_score = err.ban_score();
504504
if ban_score > 0 {

0 commit comments

Comments
 (0)