-
Notifications
You must be signed in to change notification settings - Fork 24
feat: Operator Doppelgänger Protection #692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Changes from all commits
dfaf1b4
5e2b95e
fd01236
4237308
be56784
f776d11
eb99a99
3117374
67b7126
4834057
8f98ea0
91836e3
d93ef67
64146a6
e268715
982db2e
892fcf6
8006a91
741c8ad
58170c6
9276f25
632f5d4
db1d9b2
ffc12a2
90b58b1
e0bd8b2
2d1b48c
fc962b0
00c7546
c06e7ce
571c3f8
5a8ba76
433feba
b4212b1
ba86842
de1a339
26ff439
2aa1ffb
56ef9a8
7122d72
885ab36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -484,6 +484,31 @@ pub struct Node { | |
#[clap(long, help = "Disables gossipsub topic scoring.", hide = true)] | ||
pub disable_gossipsub_topic_scoring: bool, | ||
|
||
// Operator Doppelgänger Protection | ||
#[clap( | ||
long, | ||
help = "Enable operator doppelgänger protection. When enabled, the node will monitor \ | ||
for messages signed by its operator ID on startup and shut down if a twin \ | ||
(duplicate operator) is detected. Enabled by default.", | ||
display_order = 0, | ||
default_value_t = true, | ||
help_heading = FLAG_HEADER, | ||
action = ArgAction::Set | ||
)] | ||
pub operator_dg: bool, | ||
|
||
#[clap( | ||
long, | ||
value_name = "EPOCHS", | ||
help = "Number of epochs to wait in monitor mode before starting normal operation. \ | ||
During this period, the node listens for messages from its own operator ID \ | ||
to detect if another instance is running.", | ||
display_order = 0, | ||
default_value_t = 2, | ||
requires = "operator_dg" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usability Issue: Remove The The code already correctly handles these values only being used when the feature is enabled (see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to claude: |
||
)] | ||
pub operator_dg_wait_epochs: u64, | ||
dknopik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#[clap(flatten)] | ||
pub logging_flags: FileLoggingFlags, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ use libp2p::PeerId; | |
use message_validator::{ | ||
DutiesProvider, ValidatedMessage, ValidatedSSVMessage, ValidationResult, Validator, | ||
}; | ||
use operator_doppelganger::OperatorDoppelgangerService; | ||
use qbft_manager::QbftManager; | ||
use signature_collector::SignatureCollectorManager; | ||
use slot_clock::SlotClock; | ||
|
@@ -32,9 +33,11 @@ pub struct NetworkMessageReceiver<S: SlotClock, D: DutiesProvider> { | |
is_synced: watch::Receiver<bool>, | ||
outcome_tx: mpsc::Sender<Outcome>, | ||
validator: Arc<Validator<S, D>>, | ||
doppelganger_service: Option<Arc<OperatorDoppelgangerService>>, | ||
} | ||
|
||
impl<S: SlotClock + 'static, D: DutiesProvider> NetworkMessageReceiver<S, D> { | ||
#[allow(clippy::too_many_arguments)] | ||
pub fn new( | ||
processor: processor::Senders, | ||
qbft_manager: Arc<QbftManager>, | ||
|
@@ -43,6 +46,7 @@ impl<S: SlotClock + 'static, D: DutiesProvider> NetworkMessageReceiver<S, D> { | |
is_synced: watch::Receiver<bool>, | ||
outcome_tx: mpsc::Sender<Outcome>, | ||
validator: Arc<Validator<S, D>>, | ||
doppelganger_service: Option<Arc<OperatorDoppelgangerService>>, | ||
) -> Arc<Self> { | ||
Arc::new(Self { | ||
processor, | ||
|
@@ -52,6 +56,7 @@ impl<S: SlotClock + 'static, D: DutiesProvider> NetworkMessageReceiver<S, D> { | |
is_synced, | ||
outcome_tx, | ||
validator, | ||
doppelganger_service, | ||
}) | ||
} | ||
} | ||
|
@@ -159,6 +164,21 @@ impl<S: SlotClock + 'static, D: DutiesProvider> MessageReceiver | |
} | ||
} | ||
|
||
// Check for operator doppelgänger before processing any message | ||
if let Some(service) = &receiver.doppelganger_service { | ||
// If in monitoring mode, check for twin and drop message | ||
if service.is_monitoring() { | ||
// Extract QBFT message for detailed logging if twin detected | ||
let qbft_msg = match &ssv_message { | ||
ValidatedSSVMessage::QbftMessage(msg) => Some(msg), | ||
ValidatedSSVMessage::PartialSignatureMessages(_) => None, | ||
}; | ||
service.check_message(&signed_ssv_message, qbft_msg); | ||
// Drop message during monitoring period - don't process | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Observability suggestion: Consider adding metrics or periodic logging for messages dropped during monitoring. This would help operators understand:
Example: if service.is_monitoring() {
// ... existing check_message logic ...
// Optional: track dropped messages
metrics::inc_counter(&metrics::DOPPELGANGER_MESSAGES_DROPPED);
return;
} Not critical, but useful for operational visibility. |
||
return; | ||
} | ||
} | ||
|
||
match ssv_message { | ||
ValidatedSSVMessage::QbftMessage(qbft_message) => { | ||
if let Err(err) = receiver | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation improvement: Consider enhancing the help text to include:
This clarifies:
Helps operators understand the behavior without reading source code.