@@ -72,13 +72,15 @@ const CORE_STREAM_SPARSE_INDEX_MAGIC: &[u8; 8] = b"ANSSIX1\0";
7272const CORE_ACTIVE_STREAM_MAGIC : & [ u8 ; 8 ] = b"ANASTR1\0 " ;
7373const CORE_ROOT_ANCHOR_MAGIC : & [ u8 ; 8 ] = b"ANROOT1\0 " ;
7474const CORE_ROOT_REGISTER_MAGIC : & [ u8 ; 8 ] = b"ANREGRT1" ;
75+ const CORE_TRANSACTION_MANIFEST_MAGIC : & [ u8 ; 8 ] = b"ANXACT1\0 " ;
7576const CORE_BLOCK_SHARD_MAGIC : & [ u8 ; 8 ] = b"ANBLK\n \0 \0 " ;
7677const CORE_WAL_FILE_MAGIC : & [ u8 ; 6 ] = b"ANWAL\n " ;
7778const CORE_WAL_FRAME_MAGIC : & [ u8 ; 4 ] = b"AWF1" ;
7879const CORE_STREAM_SEGMENT_VERSION : u16 = 1 ;
7980const CORE_ACTIVE_STREAM_VERSION : u16 = 1 ;
8081const CORE_ROOT_ANCHOR_VERSION : u16 = 1 ;
8182const CORE_ROOT_REGISTER_VERSION : u16 = 1 ;
83+ const CORE_TRANSACTION_MANIFEST_VERSION : u16 = 1 ;
8284const CORE_BLOCK_SHARD_VERSION : u16 = 1 ;
8385const CORE_WAL_VERSION : u16 = 1 ;
8486const CORE_WAL_EPOCH : u64 = 1 ;
@@ -4700,8 +4702,7 @@ impl CoreStore {
47004702 let transaction_manifest_bytes = self
47014703 . read_logical_file_plaintext ( & transaction_manifest)
47024704 . await ?;
4703- let transaction: CoreTransactionManifestRecord =
4704- serde_json:: from_slice ( & transaction_manifest_bytes) ?;
4705+ let transaction = decode_transaction_manifest_record ( & transaction_manifest_bytes) ?;
47054706 validate_transaction_manifest_record ( & transaction, anchor. root_generation ) ?;
47064707 let state_locator = transaction
47074708 . logical_manifests
@@ -4749,7 +4750,7 @@ impl CoreStore {
47494750 "core_control" ,
47504751 format ! ( "lf_core_transaction_manifest_{post_root_generation:020}" ) ,
47514752 post_root_generation,
4752- serde_json :: to_vec ( & transaction) ?,
4753+ encode_transaction_manifest_record ( & transaction) ?,
47534754 format ! ( "core_transaction_manifest_{post_root_generation:020}" ) ,
47544755 "local" . to_string ( ) ,
47554756 )
@@ -7672,6 +7673,74 @@ fn validate_transaction_manifest_record(
76727673 Ok ( ( ) )
76737674}
76747675
7676+ fn encode_transaction_manifest_record (
7677+ transaction : & CoreTransactionManifestRecord ,
7678+ ) -> Result < Vec < u8 > > {
7679+ let header = serde_json:: json!( {
7680+ "schema" : & transaction. schema,
7681+ "pre_root_generation" : transaction. pre_root_generation,
7682+ "post_root_generation" : transaction. post_root_generation,
7683+ "mutation_count" : transaction. mutation_ids. len( ) ,
7684+ "logical_manifest_count" : transaction. logical_manifests. len( ) ,
7685+ } ) ;
7686+ let header_json = serde_json:: to_vec ( & header) ?;
7687+ let body_json = serde_json:: to_vec ( transaction) ?;
7688+ let mut out = Vec :: with_capacity (
7689+ CORE_TRANSACTION_MANIFEST_MAGIC . len ( ) + 2 + 4 + 8 + header_json. len ( ) + body_json. len ( ) + 4 ,
7690+ ) ;
7691+ out. extend_from_slice ( CORE_TRANSACTION_MANIFEST_MAGIC ) ;
7692+ out. extend_from_slice ( & CORE_TRANSACTION_MANIFEST_VERSION . to_le_bytes ( ) ) ;
7693+ out. extend_from_slice ( & ( header_json. len ( ) as u32 ) . to_le_bytes ( ) ) ;
7694+ out. extend_from_slice ( & ( body_json. len ( ) as u64 ) . to_le_bytes ( ) ) ;
7695+ out. extend_from_slice ( & header_json) ;
7696+ out. extend_from_slice ( & body_json) ;
7697+ let mut crc_input = Vec :: with_capacity ( header_json. len ( ) + body_json. len ( ) ) ;
7698+ crc_input. extend_from_slice ( & header_json) ;
7699+ crc_input. extend_from_slice ( & body_json) ;
7700+ out. extend_from_slice ( & crc32c ( & crc_input) . to_le_bytes ( ) ) ;
7701+ Ok ( out)
7702+ }
7703+
7704+ fn decode_transaction_manifest_record ( bytes : & [ u8 ] ) -> Result < CoreTransactionManifestRecord > {
7705+ let mut offset = 0usize ;
7706+ let magic = read_exact ( bytes, & mut offset, CORE_TRANSACTION_MANIFEST_MAGIC . len ( ) ) ?;
7707+ if magic != CORE_TRANSACTION_MANIFEST_MAGIC {
7708+ bail ! ( "CoreStore transaction manifest has invalid magic" ) ;
7709+ }
7710+ let version = read_u16_le ( bytes, & mut offset) ?;
7711+ if version != CORE_TRANSACTION_MANIFEST_VERSION {
7712+ bail ! ( "CoreStore transaction manifest has unsupported version {version}" ) ;
7713+ }
7714+ let header_len = read_u32_le ( bytes, & mut offset) ? as usize ;
7715+ let body_len = read_u64_le ( bytes, & mut offset) ? as usize ;
7716+ let header_json = read_exact ( bytes, & mut offset, header_len) ?;
7717+ let body_json = read_exact ( bytes, & mut offset, body_len) ?;
7718+ let expected_crc = read_u32_le ( bytes, & mut offset) ?;
7719+ if offset != bytes. len ( ) {
7720+ bail ! ( "CoreStore transaction manifest has trailing bytes" ) ;
7721+ }
7722+ let mut crc_input = Vec :: with_capacity ( header_json. len ( ) + body_json. len ( ) ) ;
7723+ crc_input. extend_from_slice ( header_json) ;
7724+ crc_input. extend_from_slice ( body_json) ;
7725+ if crc32c ( & crc_input) != expected_crc {
7726+ bail ! ( "CoreStore transaction manifest checksum mismatch" ) ;
7727+ }
7728+ let header: serde_json:: Value = serde_json:: from_slice ( header_json) ?;
7729+ if json_required_string ( & header, "schema" ) ? != "anvil.core.transaction_manifest.v1" {
7730+ bail ! ( "CoreStore transaction manifest header has invalid schema" ) ;
7731+ }
7732+ let transaction: CoreTransactionManifestRecord = serde_json:: from_slice ( body_json) ?;
7733+ if json_required_u64 ( & header, "pre_root_generation" ) ? != transaction. pre_root_generation
7734+ || json_required_u64 ( & header, "post_root_generation" ) ? != transaction. post_root_generation
7735+ || json_required_u64 ( & header, "mutation_count" ) ? != transaction. mutation_ids . len ( ) as u64
7736+ || json_required_u64 ( & header, "logical_manifest_count" ) ?
7737+ != transaction. logical_manifests . len ( ) as u64
7738+ {
7739+ bail ! ( "CoreStore transaction manifest header/body mismatch" ) ;
7740+ }
7741+ Ok ( transaction)
7742+ }
7743+
76757744fn hash_root_anchor_record ( anchor : & CoreRootAnchorRecord ) -> Result < String > {
76767745 Ok ( format ! (
76777746 "sha256:{}" ,
@@ -11050,8 +11119,9 @@ mod tests {
1105011119 . read_logical_file_plaintext ( & transaction_manifest)
1105111120 . await
1105211121 . unwrap ( ) ;
11053- let transaction_manifest: CoreTransactionManifestRecord =
11054- serde_json:: from_slice ( & transaction_manifest_bytes) . unwrap ( ) ;
11122+ assert ! ( transaction_manifest_bytes. starts_with( CORE_TRANSACTION_MANIFEST_MAGIC ) ) ;
11123+ let transaction_manifest = decode_transaction_manifest_record ( & transaction_manifest_bytes)
11124+ . expect ( "decode transaction manifest frame" ) ;
1105511125 assert_eq ! (
1105611126 transaction_manifest. post_root_generation,
1105711127 latest_anchor. root_generation
0 commit comments