@@ -119,7 +119,11 @@ pub trait ConsensusHeader: Sized + CheckedBitPattern + NoUninit {
119119 /// boundary, so sealing them is an SDK change on both ends; [`GenericHeader`] is
120120 /// the type-erased pre-dispatch view and defers to the typed parse, where
121121 /// [`Self::verify_frame`] runs.
122- const FRAME_SEALED : bool = true ;
122+ ///
123+ /// Required, not defaulted: [`Self::seal`] on an unsealed type overwrites the
124+ /// identity checksum with a frame checksum, and in release only a `debug_assert`
125+ /// stands in the way.
126+ const FRAME_SEALED : bool ;
123127
124128 /// # Errors
125129 /// Returns `ConsensusError` if the header fields are inconsistent.
@@ -961,6 +965,21 @@ impl ConsensusHeader for RepairPrepareHeader {
961965 found : self . 0 . command ,
962966 } ) ;
963967 }
968+ // Same rule as `PrepareHeader::validate`, same reason: the regions sit inside
969+ // `identity_checksum`, and a repaired prepare is journaled and later re-read
970+ // as a DVC suffix entry, where `dvc_blank`'s exact-equality classification is
971+ // what a dirty byte defeats. Not delegated, so the command check above stays
972+ // `RepairPrepare`.
973+ if self . 0 . reserved_frame . iter ( ) . any ( |& byte| byte != 0 ) {
974+ return Err ( ConsensusError :: InvalidField (
975+ "repair_prepare: reserved_frame bytes must be zero" . to_string ( ) ,
976+ ) ) ;
977+ }
978+ if self . 0 . reserved . iter ( ) . any ( |& byte| byte != 0 ) {
979+ return Err ( ConsensusError :: InvalidField (
980+ "repair_prepare: reserved bytes must be zero" . to_string ( ) ,
981+ ) ) ;
982+ }
964983 Ok ( ( ) )
965984 }
966985}
@@ -1028,6 +1047,8 @@ impl Default for PrepareOkHeader {
10281047}
10291048
10301049impl ConsensusHeader for PrepareOkHeader {
1050+ const FRAME_SEALED : bool = true ;
1051+
10311052 const COMMAND : Command2 = Command2 :: PrepareOk ;
10321053
10331054 fn checksum ( & self ) -> u128 {
@@ -1091,6 +1112,8 @@ const _: () = {
10911112} ;
10921113
10931114impl ConsensusHeader for CommitHeader {
1115+ const FRAME_SEALED : bool = true ;
1116+
10941117 const COMMAND : Command2 = Command2 :: Commit ;
10951118
10961119 fn checksum ( & self ) -> u128 {
@@ -1150,6 +1173,8 @@ const _: () = {
11501173} ;
11511174
11521175impl ConsensusHeader for StartViewChangeHeader {
1176+ const FRAME_SEALED : bool = true ;
1177+
11531178 const COMMAND : Command2 = Command2 :: StartViewChange ;
11541179
11551180 fn checksum ( & self ) -> u128 {
@@ -1256,6 +1281,8 @@ const _: () = {
12561281pub const DVC_HEADERS_MAX : usize = 128 ;
12571282
12581283impl ConsensusHeader for DoViewChangeHeader {
1284+ const FRAME_SEALED : bool = true ;
1285+
12591286 const COMMAND : Command2 = Command2 :: DoViewChange ;
12601287
12611288 fn checksum ( & self ) -> u128 {
@@ -1315,34 +1342,46 @@ impl ConsensusHeader for DoViewChangeHeader {
13151342impl DoViewChangeHeader {
13161343 /// Number of `PrepareHeader`s in the body.
13171344 ///
1318- /// Zero is valid and means "no suffix": a replica with nothing uncommitted, or a
1319- /// peer predating the suffix. Both contribute numbers only.
1345+ /// Zero is valid and means "no suffix": a replica with nothing uncommitted
1346+ /// contributes numbers only.
13201347 ///
13211348 /// # Errors
13221349 /// [`ConsensusError::InvalidField`] when `size` is short of the header, is not a
13231350 /// whole number of headers, or exceeds what the bitsets can address.
13241351 pub fn suffix_len ( & self ) -> Result < usize , ConsensusError > {
1325- let size = self . size as usize ;
1326- let Some ( body_len) = size. checked_sub ( HEADER_SIZE ) else {
1327- return Err ( ConsensusError :: InvalidField ( format ! (
1328- "do_view_change: size {size} is shorter than the {HEADER_SIZE}-byte header"
1329- ) ) ) ;
1330- } ;
1331- if body_len % HEADER_SIZE != 0 {
1332- return Err ( ConsensusError :: InvalidField ( format ! (
1333- "do_view_change: body of {body_len} bytes is not a whole number of headers"
1334- ) ) ) ;
1335- }
1336- let suffix_len = body_len / HEADER_SIZE ;
1337- if suffix_len > DVC_HEADERS_MAX {
1338- return Err ( ConsensusError :: InvalidField ( format ! (
1339- "do_view_change: {suffix_len} suffix entries exceeds the maximum {DVC_HEADERS_MAX}"
1340- ) ) ) ;
1341- }
1342- Ok ( suffix_len)
1352+ suffix_len_of ( "do_view_change" , self . size )
13431353 }
13441354}
13451355
1356+ /// Body length of a suffix-carrying control frame, in whole [`PrepareHeader`]s.
1357+ ///
1358+ /// Shared by `DoViewChange` and `StartView`: same layout, same `DVC_HEADERS_MAX`
1359+ /// bound. `frame` only names the sender in the error text.
1360+ ///
1361+ /// # Errors
1362+ /// [`ConsensusError::InvalidField`] when `size` is short of the header, is not a
1363+ /// whole number of headers, or exceeds what a view change can address.
1364+ fn suffix_len_of ( frame : & str , size : u32 ) -> Result < usize , ConsensusError > {
1365+ let size = size as usize ;
1366+ let Some ( body_len) = size. checked_sub ( HEADER_SIZE ) else {
1367+ return Err ( ConsensusError :: InvalidField ( format ! (
1368+ "{frame}: size {size} is shorter than the {HEADER_SIZE}-byte header"
1369+ ) ) ) ;
1370+ } ;
1371+ if body_len % HEADER_SIZE != 0 {
1372+ return Err ( ConsensusError :: InvalidField ( format ! (
1373+ "{frame}: body of {body_len} bytes is not a whole number of headers"
1374+ ) ) ) ;
1375+ }
1376+ let suffix_len = body_len / HEADER_SIZE ;
1377+ if suffix_len > DVC_HEADERS_MAX {
1378+ return Err ( ConsensusError :: InvalidField ( format ! (
1379+ "{frame}: {suffix_len} suffix entries exceeds the maximum {DVC_HEADERS_MAX}"
1380+ ) ) ) ;
1381+ }
1382+ Ok ( suffix_len)
1383+ }
1384+
13461385// StartViewHeader - new view announcement (header-only)
13471386
13481387/// New primary -> all replicas: start new view. Header-only.
@@ -1372,10 +1411,9 @@ pub struct StartViewHeader {
13721411 ///
13731412 /// Carved from the tail of the former `reserved` region and placed LAST so it
13741413 /// lands 16-aligned with no padding WITHOUT moving `op`/`commit`/`namespace`.
1375- /// A peer that predates it sends zeros, decoding as `incarnation == 0`, which
1376- /// the `handle_start_view` guard treats as no claim rather than as a foreign
1377- /// one, so a mixed-version rolling upgrade is wire-compatible: the pre-upgrade
1378- /// peer's `StartView` is judged by the view checks alone, as before the field.
1414+ /// Zero is "no claim", which is what `handle_start_view` keys on and what the
1415+ /// unsolicited completion path sends. NOT mixed-version tolerance: the frame seal
1416+ /// drops a pre-seal peer before any field is read (see this module's header).
13791417 pub incarnation : u128 ,
13801418}
13811419const _: ( ) = {
@@ -1391,6 +1429,8 @@ const _: () = {
13911429} ;
13921430
13931431impl ConsensusHeader for StartViewHeader {
1432+ const FRAME_SEALED : bool = true ;
1433+
13941434 const COMMAND : Command2 = Command2 :: StartView ;
13951435
13961436 fn checksum ( & self ) -> u128 {
@@ -1436,31 +1476,14 @@ impl StartViewHeader {
14361476 /// Number of `PrepareHeader`s in the body: the view's suffix, high-to-low op
14371477 /// from `op` down toward `commit`.
14381478 ///
1439- /// Zero means numbers only, which is what a peer predating the suffix sends and
1440- /// what the probe-answer path sends. A backup then falls back to trusting `op`.
1479+ /// Zero means numbers only, which is what the probe-answer path sends. A backup
1480+ /// then falls back to trusting `op`.
14411481 ///
14421482 /// # Errors
14431483 /// [`ConsensusError::InvalidField`] when `size` is short of the header, is not a
14441484 /// whole number of headers, or exceeds what a view change can address.
14451485 pub fn suffix_len ( & self ) -> Result < usize , ConsensusError > {
1446- let size = self . size as usize ;
1447- let Some ( body_len) = size. checked_sub ( HEADER_SIZE ) else {
1448- return Err ( ConsensusError :: InvalidField ( format ! (
1449- "start_view: size {size} is shorter than the {HEADER_SIZE}-byte header"
1450- ) ) ) ;
1451- } ;
1452- if body_len % HEADER_SIZE != 0 {
1453- return Err ( ConsensusError :: InvalidField ( format ! (
1454- "start_view: body of {body_len} bytes is not a whole number of headers"
1455- ) ) ) ;
1456- }
1457- let suffix_len = body_len / HEADER_SIZE ;
1458- if suffix_len > DVC_HEADERS_MAX {
1459- return Err ( ConsensusError :: InvalidField ( format ! (
1460- "start_view: {suffix_len} suffix entries exceeds the maximum {DVC_HEADERS_MAX}"
1461- ) ) ) ;
1462- }
1463- Ok ( suffix_len)
1486+ suffix_len_of ( "start_view" , self . size )
14641487 }
14651488}
14661489
@@ -1493,9 +1516,9 @@ pub struct RequestStartViewHeader {
14931516 /// `StartView` so a reply from a previous incarnation is detectable.
14941517 ///
14951518 /// Carved from the tail of the former `reserved` region and placed LAST so it
1496- /// lands 16-aligned with no padding WITHOUT moving `namespace`. A peer that
1497- /// predates it sends zeros, decoding as ` incarnation == 0`, so a mixed-version
1498- /// rolling upgrade is wire-compatible .
1519+ /// lands 16-aligned with no padding WITHOUT moving `namespace`. Zero is "no claim
1520+ /// to echo"; see [`StartViewHeader:: incarnation`] on why that is not
1521+ /// mixed-version tolerance .
14991522 pub incarnation : u128 ,
15001523}
15011524const _: ( ) = {
@@ -1511,6 +1534,8 @@ const _: () = {
15111534} ;
15121535
15131536impl ConsensusHeader for RequestStartViewHeader {
1537+ const FRAME_SEALED : bool = true ;
1538+
15141539 const COMMAND : Command2 = Command2 :: RequestStartView ;
15151540
15161541 fn checksum ( & self ) -> u128 {
@@ -1583,6 +1608,8 @@ const _: () = {
15831608} ;
15841609
15851610impl ConsensusHeader for RequestPreparesHeader {
1611+ const FRAME_SEALED : bool = true ;
1612+
15861613 const COMMAND : Command2 = Command2 :: RequestPrepares ;
15871614
15881615 fn checksum ( & self ) -> u128 {
@@ -1654,6 +1681,8 @@ const _: () = {
16541681} ;
16551682
16561683impl ConsensusHeader for RepairRangeReplyHeader {
1684+ const FRAME_SEALED : bool = true ;
1685+
16571686 const COMMAND : Command2 = Command2 :: RepairDone ;
16581687
16591688 fn checksum ( & self ) -> u128 {
@@ -1734,6 +1763,8 @@ const _: () = {
17341763} ;
17351764
17361765impl ConsensusHeader for RequestStateTransferHeader {
1766+ const FRAME_SEALED : bool = true ;
1767+
17371768 const COMMAND : Command2 = Command2 :: RequestStateTransfer ;
17381769
17391770 fn checksum ( & self ) -> u128 {
@@ -1856,6 +1887,8 @@ const _: () = {
18561887} ;
18571888
18581889impl ConsensusHeader for StateTransferTargetHeader {
1890+ const FRAME_SEALED : bool = true ;
1891+
18591892 const COMMAND : Command2 = Command2 :: StateTransferTarget ;
18601893
18611894 fn checksum ( & self ) -> u128 {
@@ -1955,6 +1988,8 @@ const _: () = {
19551988} ;
19561989
19571990impl ConsensusHeader for RequestStateChunkHeader {
1991+ const FRAME_SEALED : bool = true ;
1992+
19581993 const COMMAND : Command2 = Command2 :: RequestStateChunk ;
19591994
19601995 fn checksum ( & self ) -> u128 {
@@ -2037,6 +2072,8 @@ const _: () = {
20372072} ;
20382073
20392074impl ConsensusHeader for StateChunkHeader {
2075+ const FRAME_SEALED : bool = true ;
2076+
20402077 const COMMAND : Command2 = Command2 :: StateChunk ;
20412078
20422079 fn checksum ( & self ) -> u128 {
0 commit comments