1use {
8 crate::bank::Bank,
9 agave_votor_messages::{
10 certificate::{
11 CertSignature, Certificate, CertificateType, FastFinalizeCert, FinalizeCert, NotarCert,
12 },
13 consensus_message::Block,
14 finalized_slot::FinalizedSlot,
15 unverified_vote_message::UnverifiedCertificate,
16 },
17 solana_bls_signatures::BlsError,
18 solana_clock::Slot,
19 solana_entry::block_component::{BlockFinalizationCert, VotesAggregate},
20 solana_pubkey::Pubkey,
21 solana_signer_store::{DecodeError, Decoded, decode},
22 std::collections::HashSet,
23 thiserror::Error,
24};
25
26#[derive(Debug, Error, PartialEq, Eq)]
28pub enum BlockFinalizationCertError {
29 #[error("BLS signature conversion for {0:?} failed: {1}")]
31 BlsError(CertificateType, BlsError),
32
33 #[error("Certificate verification for {0:?} failed")]
35 CertificateVerificationFailed(CertificateType),
36
37 #[error("Missing epoch stakes for {0:?}")]
39 MissingEpochStakes(CertificateType),
40
41 #[error("Finalization certificate was base 3 for {0:?}")]
43 Base3Finalization(CertificateType),
44
45 #[error("Unable to decode certificate {0:?}: {1:?}")]
47 DecodeError(CertificateType, DecodeError),
48}
49
50#[derive(Clone, Debug)]
54#[allow(clippy::large_enum_variant)]
55enum ValidatedBlockFinalizationCertKind {
56 Finalize {
58 finalize_cert: FinalizeCert,
60 notarize_cert: NotarCert,
62 },
63 FastFinalize(FastFinalizeCert),
65}
66
67#[derive(Clone, Debug)]
72pub struct ValidatedBlockFinalizationCert {
73 kind: ValidatedBlockFinalizationCertKind,
74 signers: HashSet<Pubkey>,
75}
76
77impl ValidatedBlockFinalizationCert {
78 pub fn try_from_footer(
89 block_final_cert: BlockFinalizationCert,
90 bank: &Bank,
91 shred_version: u16,
92 ) -> Result<Self, BlockFinalizationCertError> {
93 let block = Block {
94 slot: block_final_cert.slot,
95 block_id: block_final_cert.block_id,
96 };
97
98 if let Some(notar_aggregate) = block_final_cert.notar_aggregate {
99 let notarize_cert_type = CertificateType::Notarize(block);
101 let finalize_cert_type = CertificateType::Finalize(block.slot);
102
103 let unverified_notar_cert = UnverifiedCertificate {
104 cert_type: notarize_cert_type,
105 signature: notar_aggregate
106 .uncompress_signature()
107 .map_err(|e| BlockFinalizationCertError::BlsError(notarize_cert_type, e))?,
108 bitmap: notar_aggregate.into_bitmap(),
109 shred_version,
110 };
111 let unverified_finalize_cert = UnverifiedCertificate {
112 cert_type: finalize_cert_type,
113 signature: block_final_cert
114 .final_aggregate
115 .uncompress_signature()
116 .map_err(|e| BlockFinalizationCertError::BlsError(finalize_cert_type, e))?,
117 bitmap: block_final_cert.final_aggregate.into_bitmap(),
118 shred_version,
119 };
120
121 let notar_signature = Self::verify_certificate(bank, unverified_notar_cert)?;
123 let finalize_signature = Self::verify_certificate(bank, unverified_finalize_cert)?;
124
125 let finalize_signers =
126 Self::extract_signers(bank, finalize_cert_type, &finalize_signature.bitmap)?;
127 let mut signers =
128 Self::extract_signers(bank, notarize_cert_type, ¬ar_signature.bitmap)?;
129 signers.extend(finalize_signers);
130
131 let notarize_cert = NotarCert {
132 block,
133 signature: notar_signature,
134 };
135 let finalize_cert = FinalizeCert {
136 slot: block.slot,
137 signature: finalize_signature,
138 };
139
140 Ok(Self {
141 kind: ValidatedBlockFinalizationCertKind::Finalize {
142 finalize_cert,
143 notarize_cert,
144 },
145 signers,
146 })
147 } else {
148 let fast_finalize_cert_type = CertificateType::FinalizeFast(block);
150
151 let fast_finalize_cert = UnverifiedCertificate {
152 cert_type: fast_finalize_cert_type,
153 signature: block_final_cert
154 .final_aggregate
155 .uncompress_signature()
156 .map_err(|e| {
157 BlockFinalizationCertError::BlsError(fast_finalize_cert_type, e)
158 })?,
159 bitmap: block_final_cert.final_aggregate.into_bitmap(),
160 shred_version,
161 };
162
163 let signature = Self::verify_certificate(bank, fast_finalize_cert)?;
164 let signers = Self::extract_signers(bank, fast_finalize_cert_type, &signature.bitmap)?;
165 let fast_finalize_cert = FastFinalizeCert { block, signature };
166 Ok(Self {
167 kind: ValidatedBlockFinalizationCertKind::FastFinalize(fast_finalize_cert),
168 signers,
169 })
170 }
171 }
172
173 pub fn from_validated_slow(
182 finalize_cert: FinalizeCert,
183 notarize_cert: NotarCert,
184 bank: &Bank,
185 ) -> Self {
186 debug_assert_eq!(notarize_cert.block.slot, finalize_cert.slot);
187
188 let finalize_signers = Self::extract_signers(
189 bank,
190 CertificateType::Finalize(finalize_cert.slot),
191 &finalize_cert.signature.bitmap,
192 )
193 .expect("Certificate should have been validated");
194 let mut signers = Self::extract_signers(
195 bank,
196 CertificateType::Notarize(notarize_cert.block),
197 ¬arize_cert.signature.bitmap,
198 )
199 .expect("Certificate should have been validated");
200 signers.extend(finalize_signers);
201 Self {
202 kind: ValidatedBlockFinalizationCertKind::Finalize {
203 finalize_cert,
204 notarize_cert,
205 },
206 signers,
207 }
208 }
209
210 pub fn from_validated_fast(cert: FastFinalizeCert, bank: &Bank) -> Self {
219 let cert_type = CertificateType::FinalizeFast(cert.block);
220 let signers = Self::extract_signers(bank, cert_type, &cert.signature.bitmap)
221 .expect("Certificate should have been validated");
222 Self {
223 kind: ValidatedBlockFinalizationCertKind::FastFinalize(cert),
224 signers,
225 }
226 }
227
228 pub fn slot(&self) -> FinalizedSlot {
230 match &self.kind {
231 ValidatedBlockFinalizationCertKind::Finalize {
232 finalize_cert,
233 notarize_cert,
234 } => {
235 debug_assert_eq!(finalize_cert.slot, notarize_cert.block.slot);
236 FinalizedSlot::Slow(finalize_cert.slot)
237 }
238 ValidatedBlockFinalizationCertKind::FastFinalize(certificate) => {
239 FinalizedSlot::Fast(certificate.block.slot)
240 }
241 }
242 }
243
244 pub fn block(&self) -> Block {
246 match &self.kind {
247 ValidatedBlockFinalizationCertKind::Finalize { notarize_cert, .. } => {
248 notarize_cert.block
249 }
250 ValidatedBlockFinalizationCertKind::FastFinalize(cert) => cert.block,
251 }
252 }
253
254 pub fn is_fast(&self) -> bool {
256 matches!(
257 self.kind,
258 ValidatedBlockFinalizationCertKind::FastFinalize(_)
259 )
260 }
261
262 pub fn clone_certificates(&self) -> Vec<Certificate> {
267 match &self.kind {
268 ValidatedBlockFinalizationCertKind::Finalize {
269 finalize_cert,
270 notarize_cert,
271 } => {
272 let notar = Certificate {
273 cert_type: CertificateType::Notarize(notarize_cert.block),
274 signature: notarize_cert.signature.signature,
275 bitmap: notarize_cert.signature.bitmap.clone(),
276 };
277 let finalize = Certificate {
278 cert_type: CertificateType::Finalize(finalize_cert.slot),
279 signature: finalize_cert.signature.signature,
280 bitmap: finalize_cert.signature.bitmap.clone(),
281 };
282 vec![finalize, notar]
283 }
284 ValidatedBlockFinalizationCertKind::FastFinalize(cert) => {
285 let cert = Certificate {
286 cert_type: CertificateType::FinalizeFast(cert.block),
287 signature: cert.signature.signature,
288 bitmap: cert.signature.bitmap.clone(),
289 };
290 vec![cert]
291 }
292 }
293 }
294
295 pub fn vote_rewards_input(&self) -> (&HashSet<Pubkey>, Slot) {
297 (&self.signers, self.slot().slot())
298 }
299
300 pub(crate) fn into_parts(self) -> (HashSet<Pubkey>, Certificate, Option<Certificate>) {
305 let signers = self.signers;
306 let (final_cert, notar_cert) = match self.kind {
307 ValidatedBlockFinalizationCertKind::Finalize {
308 finalize_cert,
309 notarize_cert,
310 } => {
311 let notar = Certificate {
312 cert_type: CertificateType::Notarize(notarize_cert.block),
313 signature: notarize_cert.signature.signature,
314 bitmap: notarize_cert.signature.bitmap,
315 };
316 let finalize = Certificate {
317 cert_type: CertificateType::Finalize(finalize_cert.slot),
318 signature: finalize_cert.signature.signature,
319 bitmap: finalize_cert.signature.bitmap,
320 };
321 (finalize, Some(notar))
322 }
323 ValidatedBlockFinalizationCertKind::FastFinalize(cert) => {
324 let cert = Certificate {
325 cert_type: CertificateType::FinalizeFast(cert.block),
326 signature: cert.signature.signature,
327 bitmap: cert.signature.bitmap,
328 };
329 (cert, None)
330 }
331 };
332 (signers, final_cert, notar_cert)
333 }
334
335 pub fn to_block_final_cert(&self) -> BlockFinalizationCert {
337 match &self.kind {
338 ValidatedBlockFinalizationCertKind::Finalize {
339 finalize_cert,
340 notarize_cert,
341 } => {
342 let slot = finalize_cert.slot;
343 let block_id = notarize_cert.block.block_id;
344 BlockFinalizationCert {
345 slot,
346 block_id,
347 final_aggregate: VotesAggregate::from_cert_signature(
348 finalize_cert.signature.clone(),
349 ),
350 notar_aggregate: Some(VotesAggregate::from_cert_signature(
351 notarize_cert.signature.clone(),
352 )),
353 }
354 }
355 ValidatedBlockFinalizationCertKind::FastFinalize(cert) => {
356 let block = cert.block;
357 BlockFinalizationCert {
358 slot: block.slot,
359 block_id: block.block_id,
360 final_aggregate: VotesAggregate::from_cert_signature(cert.signature.clone()),
361 notar_aggregate: None,
362 }
363 }
364 }
365 }
366
367 fn verify_certificate(
369 bank: &Bank,
370 cert: UnverifiedCertificate,
371 ) -> Result<CertSignature, BlockFinalizationCertError> {
372 let cert_type = cert.cert_type;
373 let cert = bank
374 .verify_certificate(cert)
375 .map_err(|_| BlockFinalizationCertError::CertificateVerificationFailed(cert_type))?;
376 debug_assert_eq!(cert.cert_type, cert_type);
377 Ok(CertSignature {
378 signature: cert.signature,
379 bitmap: cert.bitmap,
380 })
381 }
382
383 fn extract_signers(
384 bank: &Bank,
385 cert_type: CertificateType,
386 bitmap: &[u8],
387 ) -> Result<HashSet<Pubkey>, BlockFinalizationCertError> {
388 let Some(epoch_stakes) = bank.epoch_stakes_from_slot(cert_type.slot()) else {
389 return Err(BlockFinalizationCertError::MissingEpochStakes(cert_type));
390 };
391 let rank_map = epoch_stakes.bls_pubkey_to_rank_map();
392 let decoded = decode(bitmap, rank_map.len());
393 let ranks = match decoded {
394 Ok(Decoded::Base2(ranks)) => ranks,
395 Ok(Decoded::Base3(_, _)) => {
396 return Err(BlockFinalizationCertError::Base3Finalization(cert_type));
397 }
398 Err(err) => return Err(BlockFinalizationCertError::DecodeError(cert_type, err)),
399 };
400
401 Ok(ranks
402 .iter_ones()
403 .filter_map(|rank| {
404 rank_map
405 .get_pubkey_stake_entry(rank)
406 .map(|entry| entry.vote_account_pubkey)
407 })
408 .collect())
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use {
415 super::*,
416 crate::genesis_utils::{
417 ValidatorVoteKeypairs, create_genesis_config_with_alpenglow_vote_accounts,
418 },
419 agave_votor_messages::{vote::Vote, wire::get_vote_payload_to_sign},
420 bitvec::prelude::*,
421 rand::Rng,
422 solana_bls_signatures::SignatureProjective,
423 solana_entry::block_component::VotesAggregate,
424 solana_signer_store::encode_base2,
425 std::sync::Arc,
426 };
427
428 fn create_bank_with_bls_validators(
431 num_validators: usize,
432 stakes: Vec<u64>,
433 ) -> (Arc<Bank>, Vec<ValidatorVoteKeypairs>) {
434 assert_eq!(num_validators, stakes.len());
435 let validator_keypairs: Vec<ValidatorVoteKeypairs> = (0..num_validators)
436 .map(|_| ValidatorVoteKeypairs::new_rand())
437 .collect();
438
439 let genesis_config_info = create_genesis_config_with_alpenglow_vote_accounts(
440 10_000_000,
441 &validator_keypairs,
442 stakes,
443 );
444
445 let bank = Arc::new(Bank::new_for_tests(&genesis_config_info.genesis_config));
446 (bank, validator_keypairs)
447 }
448
449 fn build_cert_signature(
452 vote: Vote,
453 shred_version: u16,
454 signing_ranks: &[usize],
455 validator_keypairs: &[ValidatorVoteKeypairs],
456 ) -> CertSignature {
457 let serialized_vote = get_vote_payload_to_sign(vote, shred_version);
458
459 let mut signature = SignatureProjective::identity();
461 for &rank in signing_ranks {
462 let sig = validator_keypairs[rank].bls_keypair.sign(&serialized_vote);
463 signature.aggregate_with(std::iter::once(&sig)).unwrap();
464 }
465
466 let max_rank = signing_ranks.iter().copied().max().unwrap_or(0);
468 let mut bitvec = BitVec::<u8, Lsb0>::repeat(false, max_rank.saturating_add(1));
469 for &rank in signing_ranks {
470 bitvec.set(rank, true);
471 }
472 let bitmap = encode_base2(&bitvec).expect("Failed to encode bitmap");
473
474 CertSignature {
475 signature: signature.into(),
476 bitmap,
477 }
478 }
479
480 fn vote_pubkeys_for_ranks(bank: &Bank, ranks: &[usize]) -> HashSet<Pubkey> {
481 let rank_map = bank
482 .epoch_stakes_from_slot(bank.slot())
483 .unwrap()
484 .bls_pubkey_to_rank_map();
485 ranks
486 .iter()
487 .map(|rank| {
488 rank_map
489 .get_pubkey_stake_entry(*rank)
490 .unwrap()
491 .vote_account_pubkey
492 })
493 .collect()
494 }
495
496 #[test]
497 fn test_verify_final_cert_valid() {
498 let num_validators = 10;
501 let stakes: Vec<u64> = (0..num_validators)
502 .map(|i| (1000u64).saturating_sub((i as u64).saturating_mul(100)))
503 .collect();
504 let (bank, validator_keypairs) = create_bank_with_bls_validators(num_validators, stakes);
505 let shred_version = rand::rng().random();
506
507 let block = Block::new_unique(bank.slot());
508
509 {
512 let cert_type = CertificateType::FinalizeFast(block);
513 let vote = Vote::new_notarization_vote(block);
514 let signing_ranks: Vec<usize> = (0..6).collect();
515 let fast_finalize_cert_sig =
516 build_cert_signature(vote, shred_version, &signing_ranks, &validator_keypairs);
517 let fast_finalize_cert = Certificate {
518 cert_type,
519 signature: fast_finalize_cert_sig.signature,
520 bitmap: fast_finalize_cert_sig.bitmap.clone(),
521 };
522
523 let block_final_cert = BlockFinalizationCert {
524 slot: block.slot,
525 block_id: block.block_id,
526 final_aggregate: VotesAggregate::from_cert_signature(fast_finalize_cert_sig),
527 notar_aggregate: None,
528 };
529
530 let validated = ValidatedBlockFinalizationCert::try_from_footer(
531 block_final_cert,
532 &bank,
533 shred_version,
534 )
535 .expect("Valid fast finalize certificate should pass verification");
536 assert_eq!(validated.clone_certificates(), vec![fast_finalize_cert]);
537 }
538
539 {
542 let notarize_cert_type = CertificateType::Notarize(block);
543 let notarize_vote = Vote::new_notarization_vote(block);
544 let notarize_signing_ranks: Vec<usize> = (0..4).collect();
545 let notarize_cert_sig = build_cert_signature(
546 notarize_vote,
547 shred_version,
548 ¬arize_signing_ranks,
549 &validator_keypairs,
550 );
551 let notarize_cert = Certificate {
552 cert_type: notarize_cert_type,
553 signature: notarize_cert_sig.signature,
554 bitmap: notarize_cert_sig.bitmap.clone(),
555 };
556
557 let finalize_cert_type = CertificateType::Finalize(block.slot);
558 let finalize_vote = Vote::new_finalization_vote(block.slot);
559 let finalize_signing_ranks: Vec<usize> = (0..4).collect();
560 let finalize_cert_sig = build_cert_signature(
561 finalize_vote,
562 shred_version,
563 &finalize_signing_ranks,
564 &validator_keypairs,
565 );
566 let finalize_cert = Certificate {
567 cert_type: finalize_cert_type,
568 signature: finalize_cert_sig.signature,
569 bitmap: finalize_cert_sig.bitmap.clone(),
570 };
571
572 let block_final_cert = BlockFinalizationCert {
573 slot: block.slot,
574 block_id: block.block_id,
575 final_aggregate: VotesAggregate::from_cert_signature(finalize_cert_sig),
576 notar_aggregate: Some(VotesAggregate::from_cert_signature(notarize_cert_sig)),
577 };
578
579 let validated = ValidatedBlockFinalizationCert::try_from_footer(
580 block_final_cert,
581 &bank,
582 shred_version,
583 )
584 .expect("Valid slow finalize certificate should pass verification");
585 assert_eq!(
586 validated.clone_certificates(),
587 vec![finalize_cert, notarize_cert]
588 );
589 }
590 }
591
592 #[test]
593 fn test_slow_finalization_signers_include_notarize_and_finalize_signers() {
594 let stakes = vec![1000, 900, 800, 700, 100];
595 let (bank, validator_keypairs) = create_bank_with_bls_validators(stakes.len(), stakes);
596 let shred_version = rand::rng().random();
597
598 let block = Block::new_unique(bank.slot());
599
600 let notarize_vote = Vote::new_notarization_vote(block);
601 let notarize_signing_ranks = vec![0, 1, 3];
602 let notarize_cert = NotarCert {
603 block,
604 signature: build_cert_signature(
605 notarize_vote,
606 shred_version,
607 ¬arize_signing_ranks,
608 &validator_keypairs,
609 ),
610 };
611
612 let finalize_vote = Vote::new_finalization_vote(block.slot);
613 let finalize_signing_ranks = vec![0, 1, 2];
614 let finalize_cert = FinalizeCert {
615 slot: block.slot,
616 signature: build_cert_signature(
617 finalize_vote,
618 shred_version,
619 &finalize_signing_ranks,
620 &validator_keypairs,
621 ),
622 };
623
624 let mut expected_signers = vote_pubkeys_for_ranks(&bank, ¬arize_signing_ranks);
625 expected_signers.extend(vote_pubkeys_for_ranks(&bank, &finalize_signing_ranks));
626
627 let block_final_cert = BlockFinalizationCert {
628 slot: block.slot,
629 block_id: block.block_id,
630 final_aggregate: VotesAggregate::from_cert_signature(finalize_cert.signature.clone()),
631 notar_aggregate: Some(VotesAggregate::from_cert_signature(
632 notarize_cert.signature.clone(),
633 )),
634 };
635 let validated =
636 ValidatedBlockFinalizationCert::try_from_footer(block_final_cert, &bank, shred_version)
637 .expect("Valid slow finalize certificate should pass verification");
638 let (signers, _, _) = validated.into_parts();
639 assert_eq!(signers, expected_signers);
640
641 let validated = ValidatedBlockFinalizationCert::from_validated_slow(
642 finalize_cert,
643 notarize_cert,
644 &bank,
645 );
646 let (signers, _, _) = validated.into_parts();
647 assert_eq!(signers, expected_signers);
648 }
649
650 #[test]
651 fn test_verify_final_cert_insufficient_stake() {
652 let num_validators = 10;
655 let stakes: Vec<u64> = (0..num_validators)
656 .map(|i| (1000u64).saturating_sub((i as u64).saturating_mul(100)))
657 .collect();
658 let (bank, validator_keypairs) = create_bank_with_bls_validators(num_validators, stakes);
659 let shred_version = rand::rng().random();
660
661 let block = Block::new_unique(bank.slot());
662
663 {
666 let vote = Vote::new_notarization_vote(block);
667 let signing_ranks: Vec<usize> = (0..5).collect();
668 let fast_finalize_cert_sig =
669 build_cert_signature(vote, shred_version, &signing_ranks, &validator_keypairs);
670
671 let block_final_cert = BlockFinalizationCert {
672 slot: block.slot,
673 block_id: block.block_id,
674 final_aggregate: VotesAggregate::from_cert_signature(fast_finalize_cert_sig),
675 notar_aggregate: None,
676 };
677
678 let err = ValidatedBlockFinalizationCert::try_from_footer(
679 block_final_cert,
680 &bank,
681 shred_version,
682 )
683 .unwrap_err();
684 assert!(
685 matches!(
686 err,
687 BlockFinalizationCertError::CertificateVerificationFailed { .. }
688 ),
689 "Fast finalize with insufficient stake should fail verification. {err:?}"
690 );
691 }
692
693 {
696 let notarize_vote = Vote::new_notarization_vote(block);
697 let notarize_signing_ranks: Vec<usize> = (0..3).collect();
698 let notarize_cert_sig = build_cert_signature(
699 notarize_vote,
700 shred_version,
701 ¬arize_signing_ranks,
702 &validator_keypairs,
703 );
704
705 let finalize_vote = Vote::new_finalization_vote(block.slot);
707 let finalize_signing_ranks: Vec<usize> = (0..4).collect();
708 let finalize_cert_sig = build_cert_signature(
709 finalize_vote,
710 shred_version,
711 &finalize_signing_ranks,
712 &validator_keypairs,
713 );
714
715 let block_final_cert = BlockFinalizationCert {
716 slot: block.slot,
717 block_id: block.block_id,
718 final_aggregate: VotesAggregate::from_cert_signature(finalize_cert_sig),
719 notar_aggregate: Some(VotesAggregate::from_cert_signature(notarize_cert_sig)),
720 };
721
722 let err = ValidatedBlockFinalizationCert::try_from_footer(
723 block_final_cert,
724 &bank,
725 shred_version,
726 )
727 .unwrap_err();
728 assert!(
729 matches!(
730 err,
731 BlockFinalizationCertError::CertificateVerificationFailed { .. }
732 ),
733 "Slow finalize with insufficient notarize stake should fail verification"
734 );
735 }
736
737 {
740 let notarize_vote = Vote::new_notarization_vote(block);
742 let notarize_signing_ranks: Vec<usize> = (0..4).collect();
743 let notarize_cert_sig = build_cert_signature(
744 notarize_vote,
745 shred_version,
746 ¬arize_signing_ranks,
747 &validator_keypairs,
748 );
749
750 let finalize_vote = Vote::new_finalization_vote(block.slot);
753 let finalize_signing_ranks: Vec<usize> = (0..3).collect();
754 let finalize_cert_sig = build_cert_signature(
755 finalize_vote,
756 shred_version,
757 &finalize_signing_ranks,
758 &validator_keypairs,
759 );
760
761 let block_final_cert = BlockFinalizationCert {
762 slot: block.slot,
763 block_id: block.block_id,
764 final_aggregate: VotesAggregate::from_cert_signature(finalize_cert_sig),
765 notar_aggregate: Some(VotesAggregate::from_cert_signature(notarize_cert_sig)),
766 };
767
768 let err = ValidatedBlockFinalizationCert::try_from_footer(
769 block_final_cert,
770 &bank,
771 shred_version,
772 )
773 .unwrap_err();
774 assert!(
775 matches!(
776 err,
777 BlockFinalizationCertError::CertificateVerificationFailed { .. }
778 ),
779 "Slow finalize with insufficient finalize stake should fail verification"
780 );
781 }
782 }
783}