1use crate::keys::bls::{BlsKeypair, BlsSignature};
6use crate::metrics;
7use crate::policy::ethereum::EthereumPolicy;
8use crate::policy::types::{PolicyDecision, RefusalCode, SigningType};
9use crate::state::integrity::{DecisionRecord, IntegrityError, SigningContext, StateIntegrity};
10use crate::state::validator::ValidatorState;
11use std::collections::HashMap;
12use std::sync::{Arc, RwLock};
13use std::time::Instant;
14use thiserror::Error;
15
16pub struct SigningService {
18 keypairs: RwLock<HashMap<[u8; 48], BlsKeypair>>,
20
21 validators: RwLock<HashMap<[u8; 48], ValidatorState>>,
23
24 integrity: RwLock<StateIntegrity>,
26
27 policy: EthereumPolicy,
29
30 genesis_validators_root: RwLock<Option<[u8; 32]>>,
32}
33
34impl SigningService {
35 pub fn new(keypairs: Vec<BlsKeypair>) -> Self {
37 let keypair_map: HashMap<[u8; 48], BlsKeypair> = keypairs
38 .into_iter()
39 .map(|kp| {
40 let pubkey = kp.public_key_bytes();
41 (pubkey, kp)
42 })
43 .collect();
44
45 Self {
46 keypairs: RwLock::new(keypair_map),
47 validators: RwLock::new(HashMap::new()),
48 integrity: RwLock::new(StateIntegrity::new()),
49 policy: EthereumPolicy::new(),
50 genesis_validators_root: RwLock::new(None),
51 }
52 }
53
54 pub fn with_state(
56 keypairs: Vec<BlsKeypair>,
57 validators: HashMap<[u8; 48], ValidatorState>,
58 integrity: StateIntegrity,
59 ) -> Self {
60 let keypair_map: HashMap<[u8; 48], BlsKeypair> = keypairs
61 .into_iter()
62 .map(|kp| {
63 let pubkey = kp.public_key_bytes();
64 (pubkey, kp)
65 })
66 .collect();
67
68 let genesis_root = integrity.genesis_validators_root;
69
70 Self {
71 keypairs: RwLock::new(keypair_map),
72 validators: RwLock::new(validators),
73 integrity: RwLock::new(integrity),
74 policy: EthereumPolicy::new(),
75 genesis_validators_root: RwLock::new(genesis_root),
76 }
77 }
78
79 pub fn public_keys(&self) -> Vec<[u8; 48]> {
81 self.keypairs.read().unwrap().keys().copied().collect()
82 }
83
84 pub fn public_keys_hex(&self) -> Vec<String> {
86 self.keypairs
87 .read()
88 .unwrap()
89 .keys()
90 .map(|pk| format!("0x{}", hex::encode(pk)))
91 .collect()
92 }
93
94 pub fn has_validator(&self, pubkey: &[u8; 48]) -> bool {
96 self.keypairs.read().unwrap().contains_key(pubkey)
97 }
98
99 pub fn add_keys(&self, keypairs: Vec<BlsKeypair>) -> usize {
103 let mut keypair_map = self.keypairs.write().unwrap();
104 let initial_count = keypair_map.len();
105
106 for kp in keypairs {
107 let pubkey = kp.public_key_bytes();
108 keypair_map.entry(pubkey).or_insert(kp);
109 }
110
111 keypair_map.len() - initial_count
112 }
113
114 pub fn validator_count(&self) -> usize {
116 self.keypairs.read().unwrap().len()
117 }
118
119 pub fn set_genesis_validators_root(
121 &self,
122 root: [u8; 32],
123 ) -> Result<(), SigningServiceError> {
124 let mut genesis_root = self.genesis_validators_root.write().unwrap();
125
126 match *genesis_root {
127 Some(existing) if existing != root => {
128 Err(SigningServiceError::GenesisRootMismatch {
129 expected: existing,
130 actual: root,
131 })
132 }
133 Some(_) => Ok(()), None => {
135 *genesis_root = Some(root);
136 let mut integrity = self.integrity.write().unwrap();
138 integrity
139 .set_genesis_validators_root(root)
140 .map_err(SigningServiceError::Integrity)?;
141 Ok(())
142 }
143 }
144 }
145
146 pub fn sign_block_proposal(
150 &self,
151 pubkey: &[u8; 48],
152 slot: u64,
153 signing_root: [u8; 32],
154 ) -> Result<SigningResult, SigningServiceError> {
155 let start = Instant::now();
156 let validator_hex = format!("0x{}...{}", &hex::encode(&pubkey[..4]), &hex::encode(&pubkey[44..]));
157
158 let keypairs = self.keypairs.read().unwrap();
160 let keypair = keypairs
161 .get(pubkey)
162 .ok_or(SigningServiceError::UnknownValidator(*pubkey))?;
163
164 let mut validators = self.validators.write().unwrap();
166 let state = validators
167 .entry(*pubkey)
168 .or_insert_with(|| ValidatorState::new(*pubkey));
169
170 let decision = self.policy.check_block_proposal(state, slot, &signing_root);
172
173 match decision {
174 PolicyDecision::Allow => {
175 let signature = keypair.sign(&signing_root);
177
178 state.record_block_signing(slot, signing_root);
180
181 let mut integrity = self.integrity.write().unwrap();
183 let record = integrity.prepare_record_with_context(
184 *pubkey,
185 SigningType::BlockProposal,
186 PolicyDecision::Allow,
187 signing_root,
188 SigningContext::BlockProposal { slot },
189 );
190 integrity
191 .record_decision(&record)
192 .map_err(SigningServiceError::Integrity)?;
193
194 let elapsed = start.elapsed().as_secs_f64();
196 metrics::record_signing_success("block_proposal", &validator_hex);
197 metrics::record_signing_latency("block_proposal", elapsed);
198 metrics::record_block_signed(&validator_hex);
199 metrics::set_last_signed_slot(&validator_hex, slot);
200 metrics::set_state_sequence(integrity.sequence_number);
201
202 Ok(SigningResult {
203 signature,
204 decision_record: record,
205 })
206 }
207 PolicyDecision::Refuse(code) => {
208 let mut integrity = self.integrity.write().unwrap();
210 let record = integrity.prepare_record_with_context(
211 *pubkey,
212 SigningType::BlockProposal,
213 PolicyDecision::Refuse(code),
214 signing_root,
215 SigningContext::BlockProposal { slot },
216 );
217 integrity
218 .record_decision(&record)
219 .map_err(SigningServiceError::Integrity)?;
220
221 let elapsed = start.elapsed().as_secs_f64();
223 metrics::record_signing_refusal("block_proposal", &code.to_string(), &validator_hex);
224 metrics::record_signing_latency("block_proposal", elapsed);
225
226 Err(SigningServiceError::SlashingProtection(code))
227 }
228 }
229 }
230
231 pub fn sign_attestation(
235 &self,
236 pubkey: &[u8; 48],
237 source_epoch: u64,
238 target_epoch: u64,
239 signing_root: [u8; 32],
240 ) -> Result<SigningResult, SigningServiceError> {
241 let start = Instant::now();
242 let validator_hex = format!("0x{}...{}", &hex::encode(&pubkey[..4]), &hex::encode(&pubkey[44..]));
243
244 let keypairs = self.keypairs.read().unwrap();
246 let keypair = keypairs
247 .get(pubkey)
248 .ok_or(SigningServiceError::UnknownValidator(*pubkey))?;
249
250 let mut validators = self.validators.write().unwrap();
252 let state = validators
253 .entry(*pubkey)
254 .or_insert_with(|| ValidatorState::new(*pubkey));
255
256 let decision = self
258 .policy
259 .check_attestation(state, source_epoch, target_epoch, &signing_root);
260
261 match decision {
262 PolicyDecision::Allow => {
263 let signature = keypair.sign(&signing_root);
265
266 state.record_attestation_signing(source_epoch, target_epoch, signing_root);
268
269 let mut integrity = self.integrity.write().unwrap();
271 let record = integrity.prepare_record_with_context(
272 *pubkey,
273 SigningType::Attestation,
274 PolicyDecision::Allow,
275 signing_root,
276 SigningContext::Attestation {
277 source_epoch,
278 target_epoch,
279 },
280 );
281 integrity
282 .record_decision(&record)
283 .map_err(SigningServiceError::Integrity)?;
284
285 let elapsed = start.elapsed().as_secs_f64();
287 metrics::record_signing_success("attestation", &validator_hex);
288 metrics::record_signing_latency("attestation", elapsed);
289 metrics::record_attestation_signed(&validator_hex);
290 metrics::set_last_signed_target_epoch(&validator_hex, target_epoch);
291 metrics::set_state_sequence(integrity.sequence_number);
292
293 Ok(SigningResult {
294 signature,
295 decision_record: record,
296 })
297 }
298 PolicyDecision::Refuse(code) => {
299 let mut integrity = self.integrity.write().unwrap();
301 let record = integrity.prepare_record_with_context(
302 *pubkey,
303 SigningType::Attestation,
304 PolicyDecision::Refuse(code),
305 signing_root,
306 SigningContext::Attestation {
307 source_epoch,
308 target_epoch,
309 },
310 );
311 integrity
312 .record_decision(&record)
313 .map_err(SigningServiceError::Integrity)?;
314
315 let elapsed = start.elapsed().as_secs_f64();
317 metrics::record_signing_refusal("attestation", &code.to_string(), &validator_hex);
318 metrics::record_signing_latency("attestation", elapsed);
319
320 Err(SigningServiceError::SlashingProtection(code))
321 }
322 }
323 }
324
325 pub fn sign_generic(
329 &self,
330 pubkey: &[u8; 48],
331 signing_type: SigningType,
332 signing_root: [u8; 32],
333 ) -> Result<SigningResult, SigningServiceError> {
334 let start = Instant::now();
335 let validator_hex = format!("0x{}...{}", &hex::encode(&pubkey[..4]), &hex::encode(&pubkey[44..]));
336 let type_name = signing_type.as_str();
337
338 let keypairs = self.keypairs.read().unwrap();
340 let keypair = keypairs
341 .get(pubkey)
342 .ok_or(SigningServiceError::UnknownValidator(*pubkey))?;
343
344 let signature = keypair.sign(&signing_root);
346
347 let mut integrity = self.integrity.write().unwrap();
349 let record = integrity.prepare_record_with_context(
350 *pubkey,
351 signing_type,
352 PolicyDecision::Allow,
353 signing_root,
354 SigningContext::Other,
355 );
356 integrity
357 .record_decision(&record)
358 .map_err(SigningServiceError::Integrity)?;
359
360 let elapsed = start.elapsed().as_secs_f64();
362 metrics::record_signing_success(type_name, &validator_hex);
363 metrics::record_signing_latency(type_name, elapsed);
364 metrics::set_state_sequence(integrity.sequence_number);
365
366 Ok(SigningResult {
367 signature,
368 decision_record: record,
369 })
370 }
371
372 pub fn replay_records(&self, records: Vec<DecisionRecord>) -> Result<u64, SigningServiceError> {
378 let mut validators = self.validators.write().unwrap();
379 let mut integrity = self.integrity.write().unwrap();
380
381 let mut replayed = 0u64;
382 let mut state_restored = 0u64;
383
384 for record in records {
385 if record.sequence <= integrity.sequence_number {
387 continue;
388 }
389
390 integrity
392 .record_decision(&record)
393 .map_err(SigningServiceError::Integrity)?;
394
395 if record.decision.is_allowed() {
397 let state = validators
398 .entry(record.validator_pubkey)
399 .or_insert_with(|| ValidatorState::new(record.validator_pubkey));
400
401 match &record.signing_context {
403 Some(SigningContext::BlockProposal { slot }) => {
404 state.record_block_signing(*slot, record.signing_root);
405 tracing::debug!(
406 sequence = record.sequence,
407 slot = slot,
408 "Replayed block proposal - restored slot state"
409 );
410 state_restored += 1;
411 }
412 Some(SigningContext::Attestation {
413 source_epoch,
414 target_epoch,
415 }) => {
416 state.record_attestation_signing(
417 *source_epoch,
418 *target_epoch,
419 record.signing_root,
420 );
421 tracing::debug!(
422 sequence = record.sequence,
423 source_epoch = source_epoch,
424 target_epoch = target_epoch,
425 "Replayed attestation - restored epoch state"
426 );
427 state_restored += 1;
428 }
429 Some(SigningContext::CosmosVote {
430 height,
431 round,
432 vote_type,
433 block_hash,
434 }) => {
435 if let Some(cosmos_state) = state.cosmos_state_mut() {
437 let msg_type = match vote_type {
438 1 => crate::state::validator::CosmosSignedMsgType::Prevote,
439 2 => crate::state::validator::CosmosSignedMsgType::Precommit,
440 _ => crate::state::validator::CosmosSignedMsgType::Prevote,
441 };
442 cosmos_state.record_vote(*height, *round, msg_type, *block_hash);
443 tracing::debug!(
444 sequence = record.sequence,
445 height = height,
446 round = round,
447 "Replayed Cosmos vote - restored state"
448 );
449 state_restored += 1;
450 }
451 }
452 Some(SigningContext::CosmosProposal {
453 height,
454 round,
455 block_hash,
456 }) => {
457 if let Some(cosmos_state) = state.cosmos_state_mut() {
459 cosmos_state.record_vote(
460 *height,
461 *round,
462 crate::state::validator::CosmosSignedMsgType::Proposal,
463 Some(*block_hash),
464 );
465 tracing::debug!(
466 sequence = record.sequence,
467 height = height,
468 round = round,
469 "Replayed Cosmos proposal - restored state"
470 );
471 state_restored += 1;
472 }
473 }
474 Some(SigningContext::Other) | None => {
475 tracing::debug!(
477 sequence = record.sequence,
478 request_type = ?record.request_type,
479 "Replayed generic signing record (no state to restore)"
480 );
481 }
482 }
483 } else {
484 tracing::debug!(
485 sequence = record.sequence,
486 decision = ?record.decision,
487 "Replayed refusal record"
488 );
489 }
490
491 replayed += 1;
492 }
493
494 tracing::info!(
495 replayed = replayed,
496 state_restored = state_restored,
497 "Replayed decision records"
498 );
499 Ok(replayed)
500 }
501
502 pub fn validator_states(&self) -> HashMap<[u8; 48], ValidatorState> {
504 self.validators.read().unwrap().clone()
505 }
506
507 pub fn integrity(&self) -> StateIntegrity {
509 self.integrity.read().unwrap().clone()
510 }
511
512 pub fn last_sequence(&self) -> u64 {
514 self.integrity.read().unwrap().sequence_number
515 }
516}
517
518#[derive(Debug, Clone)]
520pub struct SigningResult {
521 pub signature: BlsSignature,
523
524 pub decision_record: DecisionRecord,
526}
527
528impl SigningResult {
529 pub fn signature_hex(&self) -> String {
531 self.signature.to_hex()
532 }
533
534 pub fn signature_bytes(&self) -> [u8; 96] {
536 self.signature.to_bytes()
537 }
538}
539
540#[derive(Debug, Error)]
542pub enum SigningServiceError {
543 #[error("Unknown validator: 0x{}", hex::encode(.0))]
544 UnknownValidator([u8; 48]),
545
546 #[error("Slashing protection: {0}")]
547 SlashingProtection(RefusalCode),
548
549 #[error("Integrity error: {0}")]
550 Integrity(#[from] IntegrityError),
551
552 #[error("Genesis validators root mismatch: expected 0x{}, got 0x{}", hex::encode(.expected), hex::encode(.actual))]
553 GenesisRootMismatch { expected: [u8; 32], actual: [u8; 32] },
554
555 #[error("Invalid signing root: {0}")]
556 InvalidSigningRoot(String),
557}
558
559pub type SharedSigningService = Arc<SigningService>;
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565
566 fn make_service() -> SigningService {
567 let keypair = BlsKeypair::generate();
568 SigningService::new(vec![keypair])
569 }
570
571 #[test]
572 fn test_sign_block_proposal() {
573 let service = make_service();
574 let pubkey = service.public_keys()[0];
575 let signing_root = [1u8; 32];
576
577 let result = service.sign_block_proposal(&pubkey, 100, signing_root);
578 assert!(result.is_ok());
579
580 let signing_result = result.unwrap();
581 assert!(!signing_result.signature_bytes().iter().all(|&b| b == 0));
582 }
583
584 #[test]
585 fn test_double_proposal_rejected() {
586 let service = make_service();
587 let pubkey = service.public_keys()[0];
588
589 let result1 = service.sign_block_proposal(&pubkey, 100, [1u8; 32]);
591 assert!(result1.is_ok());
592
593 let result2 = service.sign_block_proposal(&pubkey, 100, [2u8; 32]);
595 assert!(matches!(
596 result2,
597 Err(SigningServiceError::SlashingProtection(RefusalCode::DoubleProposal))
598 ));
599 }
600
601 #[test]
602 fn test_sign_attestation() {
603 let service = make_service();
604 let pubkey = service.public_keys()[0];
605 let signing_root = [1u8; 32];
606
607 let result = service.sign_attestation(&pubkey, 10, 11, signing_root);
608 assert!(result.is_ok());
609 }
610
611 #[test]
612 fn test_double_vote_rejected() {
613 let service = make_service();
614 let pubkey = service.public_keys()[0];
615
616 let result1 = service.sign_attestation(&pubkey, 10, 11, [1u8; 32]);
618 assert!(result1.is_ok());
619
620 let result2 = service.sign_attestation(&pubkey, 10, 11, [2u8; 32]);
622 assert!(matches!(
623 result2,
624 Err(SigningServiceError::SlashingProtection(RefusalCode::DoubleVote))
625 ));
626 }
627
628 #[test]
629 fn test_surround_vote_rejected() {
630 let service = make_service();
631 let pubkey = service.public_keys()[0];
632
633 let result1 = service.sign_attestation(&pubkey, 5, 10, [1u8; 32]);
635 assert!(result1.is_ok());
636
637 let result2 = service.sign_attestation(&pubkey, 3, 12, [2u8; 32]);
639 assert!(matches!(
640 result2,
641 Err(SigningServiceError::SlashingProtection(RefusalCode::SurroundVote))
642 ));
643 }
644
645 #[test]
646 fn test_unknown_validator() {
647 let service = make_service();
648 let unknown_pubkey = [99u8; 48];
649
650 let result = service.sign_block_proposal(&unknown_pubkey, 100, [1u8; 32]);
651 assert!(matches!(
652 result,
653 Err(SigningServiceError::UnknownValidator(_))
654 ));
655 }
656
657 #[test]
658 fn test_sign_generic() {
659 let service = make_service();
660 let pubkey = service.public_keys()[0];
661
662 let result = service.sign_generic(&pubkey, SigningType::RandaoReveal, [1u8; 32]);
663 assert!(result.is_ok());
664 }
665
666 #[test]
667 fn test_genesis_root() {
668 let service = make_service();
669 let root1 = [1u8; 32];
670 let root2 = [2u8; 32];
671
672 assert!(service.set_genesis_validators_root(root1).is_ok());
674
675 assert!(service.set_genesis_validators_root(root1).is_ok());
677
678 assert!(matches!(
680 service.set_genesis_validators_root(root2),
681 Err(SigningServiceError::GenesisRootMismatch { .. })
682 ));
683 }
684}