Skip to main content

tycho_collator/
types.rs

1use std::borrow::Borrow;
2use std::collections::BTreeMap;
3use std::fmt;
4use std::sync::Arc;
5
6use anyhow::Result;
7pub(crate) use processed_upto::{ProcessedUptoInfoExtension, ProcessedUptoInfoStuff};
8use serde::{Deserialize, Serialize};
9use tycho_block_util::block::{BlockStuffAug, ValidatorSubsetInfo};
10use tycho_block_util::queue::{QueueDiffStuffAug, QueueKey, QueuePartitionIdx};
11use tycho_block_util::state::{RefMcStateHandle, ShardStateStuff};
12use tycho_crypto::ed25519::KeyPair;
13use tycho_network::PeerId;
14use tycho_types::models::*;
15use tycho_types::prelude::*;
16use tycho_util::FastHashMap;
17use tycho_util::config::PartialConfig;
18
19use crate::collator::ForceMasterCollation;
20use crate::mempool::MempoolAnchorId;
21use crate::utils::block::detect_top_processed_to_anchor;
22use crate::validator::ValidationSessionId;
23
24pub mod processed_upto;
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialConfig)]
27#[serde(default)]
28pub struct CollatorConfig {
29    /// Supported (and produced) block version.
30    ///
31    /// NOTE: Not loaded from config but provided as a constant instead.
32    #[serde(skip_deserializing, skip_serializing)]
33    pub supported_block_version: u32,
34
35    /// Supported blockchain capabilities.
36    ///
37    /// Default: [`supported_capabilities`].
38    ///
39    /// NOTE: Not loaded from config but provided as a constant instead.
40    #[serde(skip_deserializing, skip_serializing)]
41    pub supported_capabilities: GlobalCapabilities,
42
43    /// Blocks diff threshold after which the node will sync instead of collating.
44    ///
45    /// Default: `3` blocks.
46    pub min_mc_block_delta_from_bc_to_sync: u32,
47    /// Run additional value flow check on collated blocks.
48    ///
49    /// Default: `false`.
50    #[important]
51    pub check_value_flow: bool,
52    /// Run additional blockchain config check on collated blocks.
53    ///
54    /// Default: `true`.
55    #[important]
56    pub validate_config: bool,
57    /// Skip some parts of collator logic during sync.
58    ///
59    /// Default: `true`.
60    #[important]
61    pub fast_sync: bool,
62    /// Which "virtual shards depth" to use when processing [`ShardAccounts`].
63    ///
64    /// Default: `4` (means 16 shards).
65    pub accounts_split_depth: u8,
66    /// Which "virtual shards depth" to use when processing [`MerkleUpdate`].
67    ///
68    /// Default: `5` (means 32 shards).
69    pub merkle_split_depth: u8,
70}
71
72impl Default for CollatorConfig {
73    fn default() -> Self {
74        Self {
75            supported_block_version: 100,
76            supported_capabilities: supported_capabilities(),
77            min_mc_block_delta_from_bc_to_sync: 3,
78            check_value_flow: false,
79            validate_config: true,
80            fast_sync: true,
81            accounts_split_depth: 4,
82            merkle_split_depth: 5,
83        }
84    }
85}
86
87pub fn supported_capabilities() -> GlobalCapabilities {
88    GlobalCapabilities::from([
89        GlobalCapability::CapCreateStatsEnabled,
90        GlobalCapability::CapBounceMsgBody,
91        GlobalCapability::CapReportVersion,
92        GlobalCapability::CapShortDequeue,
93        GlobalCapability::CapInitCodeHash,
94        GlobalCapability::CapOffHypercube,
95        GlobalCapability::CapFixTupleIndexBug,
96        GlobalCapability::CapFastStorageStat,
97        GlobalCapability::CapMyCode,
98        GlobalCapability::CapFullBodyInBounced,
99        GlobalCapability::CapStorageFeeToTvm,
100        GlobalCapability::CapWorkchains,
101        GlobalCapability::CapStcontNewFormat,
102        GlobalCapability::CapFastStorageStatBugfix,
103        GlobalCapability::CapResolveMerkleCell,
104        GlobalCapability::CapFeeInGasUnits,
105        GlobalCapability::CapSignatureWithId,
106        GlobalCapability::CapBounceAfterFailedAction,
107        GlobalCapability::CapSuspendedList,
108        GlobalCapability::CapsTvmBugfixes2022,
109        GlobalCapability::CapSuspendByMarks,
110        GlobalCapability::CapOmitMasterBlockHistory,
111        GlobalCapability::CapSignatureDomain,
112    ])
113}
114
115pub struct BlockCollationResult {
116    pub collation_session_id: CollationSessionId,
117    pub candidate: Box<BlockCandidate>,
118    pub prev_mc_block_id: BlockId,
119    pub mc_data: Option<Arc<McData>>,
120    pub collation_config: Arc<CollationConfig>,
121    pub force_next_mc_block: ForceMasterCollation,
122    /// Whether any external has been executed in this block
123    pub has_processed_externals: bool,
124}
125
126#[derive(Debug)]
127pub struct McData {
128    pub global_id: i32,
129    pub block_id: BlockId,
130
131    /// Last known key block seqno. Will be equal to `McData.block_id.seqno` if it is a key block.
132    pub prev_key_block_seqno: u32,
133
134    pub gen_lt: u64,
135    pub gen_chain_time: u64,
136    pub libraries: Dict<HashBytes, LibDescr>,
137
138    pub total_validator_fees: CurrencyCollection,
139
140    pub global_balance: CurrencyCollection,
141    pub shards: Vec<(ShardIdent, ShardDescriptionShort)>,
142    pub config: BlockchainConfig,
143    pub validator_info: ValidatorInfo,
144    pub consensus_info: ConsensusInfo,
145
146    pub processed_upto: ProcessedUptoInfoStuff,
147
148    /// Minimal of top processed to anchors
149    /// from master block and its top shards
150    pub top_processed_to_anchor: MempoolAnchorId,
151
152    pub ref_mc_state_handle: RefMcStateHandle,
153
154    pub shards_processed_to_by_partitions: FastHashMap<ShardIdent, (bool, ProcessedToByPartitions)>,
155
156    /// Previous `McData`. Can be None on start from persistent or zerostate.
157    pub prev_mc_data: Option<PrevMcData>,
158}
159
160impl McData {
161    /// Creates `McData` from mc state.
162    ///
163    /// [`all_shards_processed_to_by_partitions`]: internals `processed_to` from master and all shards
164    pub fn load_from_state(
165        state_stuff: &ShardStateStuff,
166        prev_mc_state: Option<&ShardStateStuff>,
167        all_shards_processed_to_by_partitions: FastHashMap<
168            ShardIdent,
169            (bool, ProcessedToByPartitions),
170        >,
171    ) -> Result<Arc<Self>> {
172        let block_id = *state_stuff.block_id();
173        let extra = state_stuff.state_extra()?;
174        let state = state_stuff.as_ref();
175
176        let prev_key_block_seqno = if extra.after_key_block {
177            block_id.seqno
178        } else if let Some(block_ref) = &extra.last_key_block {
179            block_ref.seqno
180        } else {
181            0
182        };
183
184        let processed_upto: ProcessedUptoInfoStuff = state.processed_upto.load()?.try_into()?;
185
186        let shards = extra.shards.as_vec()?;
187        let top_processed_to_anchor = detect_top_processed_to_anchor(
188            shards.iter().map(|(_, d)| *d),
189            processed_upto.get_min_externals_processed_to()?.0,
190        );
191
192        let shards_processed_to_by_partitions = all_shards_processed_to_by_partitions
193            .into_iter()
194            .filter(|(shard_id, _)| !shard_id.is_masterchain())
195            .collect();
196
197        let prev_mc_data = if let Some(prev_mc_state) = prev_mc_state {
198            let prev_extra = prev_mc_state.state_extra()?;
199            Some(PrevMcData {
200                shards: prev_extra.shards.as_vec()?,
201                consensus_info: prev_extra.consensus_info,
202            })
203        } else {
204            None
205        };
206
207        Ok(Arc::new(Self {
208            global_id: state.global_id,
209            block_id,
210
211            prev_key_block_seqno,
212            gen_lt: state.gen_lt,
213            gen_chain_time: state_stuff.get_gen_chain_time(),
214            libraries: state.libraries.clone(),
215            total_validator_fees: state.total_validator_fees.clone(),
216
217            global_balance: extra.global_balance.clone(),
218            shards,
219            config: extra.config.clone(),
220            validator_info: extra.validator_info,
221            consensus_info: extra.consensus_info,
222
223            processed_upto,
224            top_processed_to_anchor,
225
226            ref_mc_state_handle: state_stuff.ref_mc_state_handle().clone(),
227            shards_processed_to_by_partitions,
228
229            prev_mc_data,
230        }))
231    }
232
233    pub fn make_block_ref(&self) -> BlockRef {
234        BlockRef {
235            end_lt: self.gen_lt,
236            seqno: self.block_id.seqno,
237            root_hash: self.block_id.root_hash,
238            file_hash: self.block_id.file_hash,
239        }
240    }
241
242    pub fn lt_align(&self) -> u64 {
243        1000000
244    }
245
246    pub fn get_blocks_count_between_masters(&self, current_shard: &ShardIdent) -> u64 {
247        if current_shard.is_masterchain() {
248            1
249        } else {
250            let seqno_from_last_mc_data = self
251                .shards
252                .iter()
253                .find(|(s, _)| s == current_shard)
254                .map(|(_, descr)| descr.seqno);
255            let seqno_from_prev_mc_data = self.prev_mc_data.as_ref().and_then(|prev| {
256                prev.shards
257                    .iter()
258                    .find(|(s, _)| s == current_shard)
259                    .map(|(_, descr)| descr.seqno)
260            });
261
262            match (seqno_from_last_mc_data, seqno_from_prev_mc_data) {
263                (Some(seqno_from_last_mc_data), Some(seqno_from_prev_mc_data)) => {
264                    seqno_from_last_mc_data.saturating_sub(seqno_from_prev_mc_data) as u64
265                }
266                _ => 0,
267            }
268        }
269    }
270}
271
272#[derive(Debug)]
273pub struct PrevMcData {
274    pub shards: Vec<(ShardIdent, ShardDescriptionShort)>,
275    pub consensus_info: ConsensusInfo,
276}
277
278#[derive(Clone)]
279pub struct BlockCandidate {
280    pub ref_by_mc_seqno: u32,
281    pub block: BlockStuffAug,
282    pub is_key_block: bool,
283    /// If current block is a key master block and `ConsensusConfig` was changed.
284    /// `None` - if it is a shard block or not a key master block.
285    pub consensus_config_changed: Option<bool>,
286    pub prev_blocks_ids: Vec<BlockId>,
287    pub top_shard_blocks_ids: Vec<BlockId>,
288    pub collated_file_hash: HashBytes,
289    pub chain_time: u64,
290    pub processed_to_anchor_id: u32,
291    pub value_flow: ValueFlow,
292    pub created_by: HashBytes,
293    pub queue_diff_aug: QueueDiffStuffAug,
294    pub consensus_info: ConsensusInfo,
295    pub processed_upto: ProcessedUptoInfoStuff,
296}
297
298#[derive(Default, Clone)]
299pub struct BlockSignatures {
300    pub signatures: FastHashMap<HashBytes, ArcSignature>,
301}
302
303pub type ArcSignature = Arc<[u8; 64]>;
304
305pub struct ValidatedBlock {
306    block: BlockId,
307    signatures: BlockSignatures,
308    valid: bool,
309}
310
311impl ValidatedBlock {
312    pub fn new(block: BlockId, signatures: BlockSignatures, valid: bool) -> Self {
313        Self {
314            block,
315            signatures,
316            valid,
317        }
318    }
319
320    pub fn id(&self) -> &BlockId {
321        &self.block
322    }
323
324    pub fn signatures(&self) -> &BlockSignatures {
325        &self.signatures
326    }
327
328    pub fn is_valid(&self) -> bool {
329        self.valid
330    }
331    pub fn extract_signatures(self) -> BlockSignatures {
332        self.signatures
333    }
334}
335
336pub struct BlockStuffForSync {
337    /// A masterchain block seqno which will reference this block.
338    pub ref_by_mc_seqno: u32,
339
340    pub block_stuff_aug: BlockStuffAug,
341    pub queue_diff_aug: QueueDiffStuffAug,
342    pub signatures: FastHashMap<PeerId, ArcSignature>,
343    pub total_signature_weight: u64,
344    pub prev_blocks_ids: Vec<BlockId>,
345    pub top_shard_blocks_ids: Vec<BlockId>,
346
347    pub consensus_info: ConsensusInfo,
348}
349
350/// (`ShardIdent`, seqno, subset `short_hash`)
351pub(crate) type CollationSessionId = (ShardIdent, u32, u32);
352
353#[derive(Clone)]
354pub struct CollationSessionInfo {
355    shard: ShardIdent,
356    /// Sequence number of the collation session
357    seqno: u32,
358    collators: ValidatorSubsetInfo,
359    current_collator_keypair: Option<Arc<KeyPair>>,
360}
361impl CollationSessionInfo {
362    pub fn new(
363        shard: ShardIdent,
364        seqno: u32,
365        collators: ValidatorSubsetInfo,
366        current_collator_keypair: Option<Arc<KeyPair>>,
367    ) -> Self {
368        Self {
369            shard,
370            seqno,
371            collators,
372            current_collator_keypair,
373        }
374    }
375
376    pub fn id(&self) -> CollationSessionId {
377        (self.shard, self.seqno, self.collators.short_hash)
378    }
379
380    pub fn get_validation_session_id(&self) -> ValidationSessionId {
381        (self.seqno, self.collators.short_hash)
382    }
383
384    pub fn shard(&self) -> ShardIdent {
385        self.shard
386    }
387    pub fn seqno(&self) -> u32 {
388        self.seqno
389    }
390
391    pub fn collators(&self) -> &ValidatorSubsetInfo {
392        &self.collators
393    }
394
395    pub fn current_collator_keypair(&self) -> Option<&Arc<KeyPair>> {
396        self.current_collator_keypair.as_ref()
397    }
398}
399impl fmt::Debug for CollationSessionInfo {
400    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401        f.debug_struct("CollationSessionInfo")
402            .field("shard", &self.shard)
403            .field("seqno", &self.seqno)
404            .field("collators", &self.collators)
405            .field(
406                "current_collator_pubkey",
407                &self
408                    .current_collator_keypair
409                    .as_ref()
410                    .map(|kp| kp.public_key),
411            )
412            .finish()
413    }
414}
415
416pub trait IntAdrExt {
417    fn get_address(&self) -> HashBytes;
418}
419impl IntAdrExt for IntAddr {
420    fn get_address(&self) -> HashBytes {
421        match self {
422            Self::Std(std_addr) => std_addr.address,
423            Self::Var(var_addr) => HashBytes::from_slice(var_addr.address.as_slice()),
424        }
425    }
426}
427
428#[derive(Debug, Clone)]
429pub struct TopBlockDescription {
430    pub block_id: BlockId,
431    pub block_info: BlockInfo,
432    pub processed_to_anchor_id: u32,
433    pub value_flow: ValueFlow,
434    pub proof_funds: ShardFeeCreated,
435    #[cfg(feature = "block-creator-stats")]
436    pub creators: Vec<HashBytes>,
437    pub processed_to_by_partitions: ProcessedToByPartitions,
438}
439
440#[derive(Debug, Clone)]
441pub struct TopShardBlockInfo {
442    pub block_id: BlockId,
443    pub processed_to_by_partitions: ProcessedToByPartitions,
444}
445
446pub type ProcessedTo = BTreeMap<ShardIdent, QueueKey>;
447pub type ProcessedToByPartitions = FastHashMap<QueuePartitionIdx, ProcessedTo>;
448
449#[derive(Debug, Clone)]
450pub struct TopBlockIdUpdated {
451    pub block: TopBlockId,
452    pub updated: bool,
453}
454
455#[derive(Debug, Clone)]
456pub struct TopBlockId {
457    pub ref_by_mc_seqno: u32,
458    pub block_id: BlockId,
459}
460
461#[derive(Debug, Clone)]
462pub struct ShardPair {
463    pub shard_ident: ShardIdent,
464    pub prev_block_id: TopBlockId,
465    pub current_block_id: TopBlockId,
466}
467
468#[derive(Debug)]
469pub struct ShortAddr {
470    workchain: i32,
471    prefix: u64,
472}
473
474impl ShortAddr {
475    pub fn new(workchain: i32, prefix: u64) -> Self {
476        Self { workchain, prefix }
477    }
478}
479
480impl Addr for ShortAddr {
481    fn workchain(&self) -> i32 {
482        self.workchain
483    }
484
485    fn prefix(&self) -> u64 {
486        self.prefix
487    }
488}
489
490pub trait BlockIdExt {
491    fn get_next_id_short(&self) -> BlockIdShort;
492}
493impl BlockIdExt for BlockId {
494    fn get_next_id_short(&self) -> BlockIdShort {
495        BlockIdShort {
496            shard: self.shard,
497            seqno: self.seqno + 1,
498        }
499    }
500}
501impl BlockIdExt for BlockIdShort {
502    fn get_next_id_short(&self) -> BlockIdShort {
503        BlockIdShort {
504            shard: self.shard,
505            seqno: self.seqno + 1,
506        }
507    }
508}
509
510pub trait ShardDescriptionShortExt {
511    fn get_block_id(&self, shard_id: ShardIdent) -> BlockId;
512}
513impl ShardDescriptionShortExt for ShardDescription {
514    fn get_block_id(&self, shard_id: ShardIdent) -> BlockId {
515        BlockId {
516            shard: shard_id,
517            seqno: self.seqno,
518            root_hash: self.root_hash,
519            file_hash: self.file_hash,
520        }
521    }
522}
523
524pub struct DebugDisplay<T>(pub T);
525impl<T: std::fmt::Display> std::fmt::Debug for DebugDisplay<T> {
526    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
527        std::fmt::Display::fmt(&self.0, f)
528    }
529}
530
531pub struct DebugDisplayOpt<T>(pub Option<T>);
532impl<T: std::fmt::Display> std::fmt::Debug for DebugDisplayOpt<T> {
533    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
534        std::fmt::Debug::fmt(&self.0.as_ref().map(DebugDisplay), f)
535    }
536}
537
538pub(super) struct DisplayIter<I>(pub I);
539impl<I> std::fmt::Display for DisplayIter<I>
540where
541    I: Iterator<Item: std::fmt::Display> + Clone,
542{
543    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
544        f.debug_list()
545            .entries(self.0.clone().map(DebugDisplay))
546            .finish()
547    }
548}
549
550pub(super) struct DisplayIntoIter<I>(pub I);
551impl<I> std::fmt::Display for DisplayIntoIter<I>
552where
553    I: IntoIterator<Item: std::fmt::Display> + Clone,
554{
555    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
556        f.debug_list()
557            .entries(self.0.clone().into_iter().map(DebugDisplay))
558            .finish()
559    }
560}
561
562pub(super) struct DebugIter<I>(pub I);
563impl<I> std::fmt::Debug for DebugIter<I>
564where
565    I: Iterator<Item: std::fmt::Debug> + Clone,
566{
567    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
568        f.debug_list().entries(self.0.clone()).finish()
569    }
570}
571
572pub(super) struct DisplayAsShortId<'a>(pub &'a BlockId);
573impl std::fmt::Debug for DisplayAsShortId<'_> {
574    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
575        std::fmt::Display::fmt(self, f)
576    }
577}
578impl std::fmt::Display for DisplayAsShortId<'_> {
579    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580        write!(f, "{}", self.0.as_short_id())
581    }
582}
583
584pub(super) struct DisplayBlockIdsIter<I>(pub I);
585impl<'a, I> std::fmt::Debug for DisplayBlockIdsIter<I>
586where
587    I: Iterator<Item = &'a BlockId> + Clone,
588{
589    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
590        std::fmt::Display::fmt(self, f)
591    }
592}
593impl<'a, I> std::fmt::Display for DisplayBlockIdsIter<I>
594where
595    I: Iterator<Item = &'a BlockId> + Clone,
596{
597    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
598        f.debug_list()
599            .entries(self.0.clone().map(DisplayAsShortId))
600            .finish()
601    }
602}
603
604pub(super) struct DisplayBlockIdsIntoIter<I>(pub I);
605impl<'a, I> std::fmt::Debug for DisplayBlockIdsIntoIter<I>
606where
607    I: IntoIterator<Item = &'a BlockId> + Clone,
608{
609    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
610        std::fmt::Display::fmt(self, f)
611    }
612}
613impl<'a, I> std::fmt::Display for DisplayBlockIdsIntoIter<I>
614where
615    I: IntoIterator<Item = &'a BlockId> + Clone,
616{
617    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
618        f.debug_list()
619            .entries(self.0.clone().into_iter().map(DisplayAsShortId))
620            .finish()
621    }
622}
623
624#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
625pub struct ShardDescriptionShort {
626    pub ext_processed_to_anchor_id: u32,
627    pub top_sc_block_updated: bool,
628    pub reg_mc_seqno: u32,
629    pub end_lt: u64,
630    pub seqno: u32,
631    pub root_hash: HashBytes,
632    pub file_hash: HashBytes,
633}
634
635impl<BorrowShardDescription: Borrow<ShardDescription>> From<BorrowShardDescription>
636    for ShardDescriptionShort
637{
638    fn from(borrow_shard: BorrowShardDescription) -> ShardDescriptionShort {
639        let shard = borrow_shard.borrow();
640        Self {
641            ext_processed_to_anchor_id: shard.ext_processed_to_anchor_id,
642            top_sc_block_updated: shard.top_sc_block_updated,
643            reg_mc_seqno: shard.reg_mc_seqno,
644            end_lt: shard.end_lt,
645            seqno: shard.seqno,
646            root_hash: shard.root_hash,
647            file_hash: shard.file_hash,
648        }
649    }
650}
651
652impl ShardDescriptionShortExt for ShardDescriptionShort {
653    fn get_block_id(&self, shard_id: ShardIdent) -> BlockId {
654        BlockId {
655            shard: shard_id,
656            seqno: self.seqno,
657            root_hash: self.root_hash,
658            file_hash: self.file_hash,
659        }
660    }
661}
662
663pub trait ShardHashesExt<T> {
664    fn as_vec(&self) -> Result<Vec<(ShardIdent, T)>>;
665}
666impl<T> ShardHashesExt<T> for ShardHashes
667where
668    T: From<ShardDescription>,
669{
670    fn as_vec(&self) -> Result<Vec<(ShardIdent, T)>> {
671        let mut res = vec![];
672        for item in self.iter() {
673            let (shard_id, descr) = item?;
674            res.push((shard_id, descr.into()));
675        }
676        Ok(res)
677    }
678}
679
680pub trait ShardIdentExt {
681    fn contains_prefix(&self, workchain_id: i32, prefix_without_tag: u64) -> bool;
682}
683
684impl ShardIdentExt for ShardIdent {
685    fn contains_prefix(&self, workchain_id: i32, prefix_without_tag: u64) -> bool {
686        if self.workchain() == workchain_id {
687            if self.prefix() == 0x8000_0000_0000_0000u64 {
688                return true;
689            }
690            let shift = 64 - self.prefix_len();
691            return (self.prefix() >> shift) == (prefix_without_tag >> shift);
692        }
693        false
694    }
695}
696
697pub trait SaturatingAddAssign {
698    fn saturating_add_assign(&mut self, rhs: Self);
699}
700
701macro_rules! impl_saturating_add_assign {
702    ($($t:ty),+ $(,)?) => {
703        $(
704            impl SaturatingAddAssign for $t {
705                fn saturating_add_assign(&mut self, rhs: Self) {
706                    *self = self.saturating_add(rhs);
707                }
708            }
709        )+
710    };
711}
712
713impl_saturating_add_assign!(u32, u64, usize);