Skip to content

Commit 72b2c24

Browse files
authored
QBFT Message Signing (#134)
1 parent 0978e19 commit 72b2c24

File tree

8 files changed

+140
-79
lines changed

8 files changed

+140
-79
lines changed

Cargo.lock

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

anchor/client/src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ use network::Network;
1717
use openssl::pkey::Private;
1818
use openssl::rsa::Rsa;
1919
use parking_lot::RwLock;
20-
use qbft::Message;
2120
use qbft_manager::QbftManager;
2221
use sensitive_url::SensitiveUrl;
2322
use signature_collector::SignatureCollectorManager;
2423
use slashing_protection::SlashingDatabase;
2524
use slot_clock::{SlotClock, SystemTimeSlotClock};
25+
use ssv_types::message::SignedSSVMessage;
2626
use ssv_types::OperatorId;
2727
use std::fs::File;
2828
use std::io::{ErrorKind, Read, Write};
@@ -349,13 +349,16 @@ impl Client {
349349
SignatureCollectorManager::new(processor_senders.clone(), slot_clock.clone())
350350
.map_err(|e| format!("Unable to initialize signature collector manager: {e:?}"))?;
351351

352+
// Network sender/receiver
353+
let (network_tx, _network_rx) = mpsc::unbounded_channel::<SignedSSVMessage>();
354+
352355
// Create the qbft manager
353-
let (qbft_sender, _qbft_receiver) = mpsc::channel::<Message>(500);
354356
let qbft_manager = QbftManager::new(
355357
processor_senders.clone(),
356358
operator_id,
357359
slot_clock.clone(),
358-
qbft_sender,
360+
key.clone(),
361+
network_tx.clone(),
359362
)
360363
.map_err(|e| format!("Unable to initialize qbft manager: {e:?}"))?;
361364

anchor/common/qbft/src/lib.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ where
187187
// Ensure that this message is for the correct round
188188
let current_round = self.current_round.get();
189189
if (wrapped_msg.qbft_message.round < current_round as u64)
190-
|| (current_round > self.config.max_rounds())
190+
|| (wrapped_msg.qbft_message.round > self.config.max_rounds() as u64)
191191
{
192192
warn!(
193193
propose_round = wrapped_msg.qbft_message.round,
@@ -322,13 +322,6 @@ where
322322

323323
// Send the initial proposal and then the following prepare
324324
self.send_proposal(data_hash, data);
325-
self.send_prepare(data_hash);
326-
327-
// Since we are the leader and sent the proposal, switch to prepare state and accept
328-
// proposal
329-
self.state = InstanceState::Prepare;
330-
self.proposal_accepted_for_current_round = true;
331-
self.proposal_root = Some(data_hash);
332325
}
333326
}
334327

anchor/common/qbft/src/qbft_types.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@ pub enum Message {
121121
RoundChange(OperatorId, UnsignedSSVMessage),
122122
}
123123

124+
impl Message {
125+
pub fn desugar(&self) -> (OperatorId, UnsignedSSVMessage) {
126+
match self {
127+
Message::Propose(id, msg)
128+
| Message::Prepare(id, msg)
129+
| Message::Commit(id, msg)
130+
| Message::RoundChange(id, msg) => (*id, msg.clone()),
131+
}
132+
}
133+
}
134+
124135
/// Type definitions for the allowable messages
125136
/// This holds the consensus data for a given round.
126137
#[derive(Debug, Clone)]

anchor/common/ssv_types/src/consensus.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub trait QbftData: Debug + Clone + Encode + Decode {
3535
}
3636

3737
/// A SSV Message that has not been signed yet.
38-
#[derive(Clone, Debug)]
38+
#[derive(Clone, Debug, Encode)]
3939
pub struct UnsignedSSVMessage {
4040
/// The SSV Message to be send. This is either a consensus message which contains a serialized
4141
/// QbftMessage, or a partial signature message which contains a PartialSignatureMessage

anchor/qbft_manager/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = { workspace = true }
88
dashmap = { workspace = true }
99
ethereum_ssz = { workspace = true }
1010
ethereum_ssz_derive = { workspace = true }
11+
openssl = { workspace = true }
1112
processor = { workspace = true }
1213
qbft = { workspace = true }
1314
slot_clock = { workspace = true }
@@ -19,6 +20,7 @@ types = { workspace = true }
1920
[dev-dependencies]
2021
async-channel = { workspace = true }
2122
futures = { workspace = true }
23+
openssl = { workspace = true }
2224
rand = { workspace = true }
2325
task_executor = { workspace = true }
2426
tokio = { workspace = true, features = ["test-util"] }

anchor/qbft_manager/src/lib.rs

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
use dashmap::DashMap;
2+
use openssl::hash::MessageDigest;
3+
use openssl::pkey::{PKey, Private};
4+
use openssl::rsa::Rsa;
5+
use openssl::sign::Signer;
6+
27
use processor::{DropOnFinish, Senders, WorkItem};
38
use qbft::{
49
Completed, ConfigBuilder, ConfigBuilderError, DefaultLeaderFunction, InstanceHeight, Message,
510
WrappedQbftMessage,
611
};
712
use slot_clock::SlotClock;
8-
use ssv_types::consensus::{BeaconVote, QbftData, ValidatorConsensusData};
13+
use ssv_types::consensus::{BeaconVote, QbftData, UnsignedSSVMessage, ValidatorConsensusData};
14+
use std::error::Error;
915

16+
use ssv_types::message::SignedSSVMessage;
1017
use ssv_types::OperatorId as QbftOperatorId;
1118
use ssv_types::{Cluster, ClusterId, OperatorId};
19+
use ssz::Encode;
1220
use std::fmt::Debug;
1321
use std::hash::Hash;
1422
use std::sync::Arc;
@@ -27,6 +35,7 @@ mod tests;
2735
const QBFT_INSTANCE_NAME: &str = "qbft_instance";
2836
const QBFT_MESSAGE_NAME: &str = "qbft_message";
2937
const QBFT_CLEANER_NAME: &str = "qbft_cleaner";
38+
const QBFT_SIGNER_NAME: &str = "qbft_signer";
3039

3140
/// Number of slots to keep before the current slot
3241
const QBFT_RETAIN_SLOTS: u64 = 1;
@@ -94,9 +103,10 @@ pub struct QbftManager<T: SlotClock + 'static> {
94103
validator_consensus_data_instances: Map<ValidatorInstanceId, ValidatorConsensusData>,
95104
// All of the QBFT instances that are voting on beacon data
96105
beacon_vote_instances: Map<CommitteeInstanceId, BeaconVote>,
97-
// Takes messages from qbft instances and sends them to be signed
98-
// TODO!(). This will be the network channel for passing signatures from processor -> network
99-
qbft_out: mpsc::Sender<Message>,
106+
// Private key used for signing messages
107+
pkey: Arc<PKey<Private>>,
108+
// Channel to pass signed messages along to the network
109+
network_tx: mpsc::UnboundedSender<SignedSSVMessage>,
100110
}
101111

102112
impl<T: SlotClock> QbftManager<T> {
@@ -105,15 +115,19 @@ impl<T: SlotClock> QbftManager<T> {
105115
processor: Senders,
106116
operator_id: OperatorId,
107117
slot_clock: T,
108-
qbft_out: mpsc::Sender<Message>,
118+
key: Rsa<Private>,
119+
network_tx: mpsc::UnboundedSender<SignedSSVMessage>,
109120
) -> Result<Arc<Self>, QbftError> {
121+
let pkey = Arc::new(PKey::from_rsa(key).expect("Failed to create PKey from RSA"));
122+
110123
let manager = Arc::new(QbftManager {
111124
processor,
112125
operator_id,
113126
slot_clock,
114127
validator_consensus_data_instances: DashMap::new(),
115128
beacon_vote_instances: DashMap::new(),
116-
qbft_out,
129+
pkey,
130+
network_tx,
117131
});
118132

119133
// Start a long running task that will clean up old instances
@@ -147,7 +161,7 @@ impl<T: SlotClock> QbftManager<T> {
147161

148162
// Get or spawn a new qbft instance. This will return the sender that we can use to send
149163
// new messages to the specific instance
150-
let sender = D::get_or_spawn_instance(self, id, self.qbft_out.clone());
164+
let sender = D::get_or_spawn_instance(self, id);
151165
self.processor.urgent_consensus.send_immediate(
152166
move |drop_on_finish: DropOnFinish| {
153167
// A message to initialize this instance
@@ -173,7 +187,7 @@ impl<T: SlotClock> QbftManager<T> {
173187
id: D::Id,
174188
data: WrappedQbftMessage,
175189
) -> Result<(), QbftError> {
176-
let sender = D::get_or_spawn_instance(self, id, self.qbft_out.clone());
190+
let sender = D::get_or_spawn_instance(self, id);
177191
self.processor.urgent_consensus.send_immediate(
178192
move |drop_on_finish: DropOnFinish| {
179193
let _ = sender.send(QbftMessage {
@@ -214,7 +228,6 @@ pub trait QbftDecidable<T: SlotClock + 'static>: QbftData<Hash = Hash256> + Send
214228
fn get_or_spawn_instance(
215229
manager: &QbftManager<T>,
216230
id: Self::Id,
217-
qbft_out: mpsc::Sender<Message>,
218231
) -> UnboundedSender<QbftMessage<Self>> {
219232
let map = Self::get_map(manager);
220233
let ret = match map.entry(id) {
@@ -224,10 +237,15 @@ pub trait QbftDecidable<T: SlotClock + 'static>: QbftData<Hash = Hash256> + Send
224237
// with the reeiver
225238
let (tx, rx) = mpsc::unbounded_channel();
226239
let tx = entry.insert(tx);
227-
let _ = manager
228-
.processor
229-
.permitless
230-
.send_async(Box::pin(qbft_instance(rx, qbft_out)), QBFT_INSTANCE_NAME);
240+
let _ = manager.processor.permitless.send_async(
241+
Box::pin(qbft_instance(
242+
rx,
243+
manager.network_tx.clone(),
244+
manager.pkey.clone(),
245+
manager.processor.clone(),
246+
)),
247+
QBFT_INSTANCE_NAME,
248+
);
231249
tx.clone()
232250
}
233251
};
@@ -282,7 +300,9 @@ enum QbftInstance<D: QbftData<Hash = Hash256>, S: FnMut(Message)> {
282300

283301
async fn qbft_instance<D: QbftData<Hash = Hash256>>(
284302
mut rx: UnboundedReceiver<QbftMessage<D>>,
285-
tx: mpsc::Sender<Message>,
303+
network_tx: mpsc::UnboundedSender<SignedSSVMessage>,
304+
pkey: Arc<PKey<Private>>,
305+
processor: Senders,
286306
) {
287307
// Signal a new instance that is uninitialized
288308
let mut instance = QbftInstance::Uninitialized {
@@ -325,18 +345,24 @@ async fn qbft_instance<D: QbftData<Hash = Hash256>>(
325345
QbftInstance::Uninitialized { message_buffer } => {
326346
// Create a new instance and receive any buffered messages
327347
let mut instance = Box::new(Qbft::new(config, initial, |message| {
328-
match tx.try_send(message) {
329-
Ok(()) => (),
330-
Err(TrySendError::Full(msg)) => {
331-
// Queue is full - drop message under constrained bandwidth
332-
warn!(?msg, "Dropping QBFT message due to full queue");
333-
}
334-
Err(TrySendError::Closed(_)) => {
335-
// Channel closed - critical failure or shutdown
336-
error!("QBFT message channel closed - initiating shutdown");
337-
// todo!() need some sort of shutdown
338-
}
339-
}
348+
let (id, unsigned) = message.desugar();
349+
let serialized = unsigned.as_ssz_bytes();
350+
let pkey = pkey.clone();
351+
let network_tx = network_tx.clone();
352+
353+
processor
354+
.urgent_consensus
355+
.send_blocking(
356+
move || {
357+
if let Err(e) = sign_and_send_message(
358+
pkey, id, unsigned, serialized, network_tx,
359+
) {
360+
error!("Signing failed: {}", e);
361+
}
362+
},
363+
QBFT_SIGNER_NAME,
364+
)
365+
.unwrap_or_else(|e| warn!("Failed to send to processor: {}", e));
340366
}));
341367
for message in message_buffer {
342368
instance.receive(message);
@@ -417,6 +443,33 @@ async fn qbft_instance<D: QbftData<Hash = Hash256>>(
417443
}
418444
}
419445

446+
// Sign a message and send it to the network via the network_tx
447+
fn sign_and_send_message(
448+
pkey: Arc<PKey<Private>>,
449+
id: OperatorId,
450+
unsigned: UnsignedSSVMessage,
451+
serialized: Vec<u8>,
452+
network_tx: UnboundedSender<SignedSSVMessage>,
453+
) -> Result<(), Box<dyn Error>> {
454+
// Create the signature
455+
let mut signer = Signer::new(MessageDigest::sha256(), &pkey)?;
456+
signer.update(&serialized)?;
457+
let sig = signer.sign_to_vec()?;
458+
459+
// Build the signed ssv message, then serialize it and send to the network
460+
let signed = SignedSSVMessage::new(
461+
vec![sig],
462+
vec![*id],
463+
unsigned.ssv_message,
464+
unsigned.full_data,
465+
)?;
466+
network_tx
467+
.send(signed)
468+
.map_err(|e| format!("Failed to send signed ssv message to network: {}", e))?;
469+
470+
Ok(())
471+
}
472+
420473
#[derive(Debug, Clone)]
421474
pub enum QbftError {
422475
QueueClosedError,

0 commit comments

Comments
 (0)