#![allow(dead_code)] use levcs_core::{Blob, ObjectId, ObjectType}; use levcs_identity::keys::SecretKey; use levcs_protocol::v2::*; use levcs_protocol::CanonicalCodec; pub fn id(byte: u8) -> ObjectId { ObjectId([byte; 32]) } pub fn key(byte: u8) -> SecretKey { SecretKey::from_seed([byte; 32]) } pub fn evidence_context() -> AdministrativeEvidenceContextV1 { AdministrativeEvidenceContextV1 { destination_repo: id(3), operation_id: [0x41; 16], } } pub fn evidence_context_for(evidence: &TransactionEvidenceV1) -> AdministrativeEvidenceContextV1 { match evidence { TransactionEvidenceV1::LegacyMigrationV1 { migration_id, repo_id, chunk_ordinal, chunk_count, chunk_digest, .. } => AdministrativeEvidenceContextV1 { destination_repo: *repo_id, operation_id: legacy_migration_operation_id( *migration_id, *repo_id, *chunk_ordinal, *chunk_count, *chunk_digest, ), }, _ => evidence_context(), } } pub fn signed_init() -> SignedInitOperationV2 { let key = key(3); SignedInitOperationV2::sign( InitOperationV2 { operation_id: [0x31; 16], repo_id: id(2), issued_at_micros: 1_000, retry_until_micros: 5_000, nonce: [0x32; 16], genesis_len: 777, genesis_hash: id(4), }, &key, ) .unwrap() } pub fn signed_normal_push() -> SignedPushOperationV2 { let key = key(1); SignedPushOperationV2::sign( PushOperationV2 { operation_id: [0x11; 16], repo_id: id(2), issued_at_micros: 1_000, retry_until_micros: 5_000, nonce: [0x12; 16], expected_authority: id(4), authority_update: Some(id(5)), updates: vec![ TypedRefCas { target: RefTarget::Branch("main".into()), expected: Some(id(6)), mutation: RefMutation::Set(id(7)), force: false, }, TypedRefCas { target: RefTarget::Release("v0".into()), expected: Some(id(8)), mutation: RefMutation::Delete, force: true, }, ], pack_len: 128, pack_hash: id(9), snapshot_generation_digest: id(10), projection_stage: None, kind: PushKindV2::Normal, }, &key, ) .unwrap() } pub fn signed_fork_push() -> SignedPushOperationV2 { let key = key(2); SignedPushOperationV2::sign( PushOperationV2 { operation_id: [0x21; 16], repo_id: id(20), issued_at_micros: 2_000, retry_until_micros: 8_000, nonce: [0x22; 16], expected_authority: id(21), authority_update: None, updates: vec![TypedRefCas { target: RefTarget::Branch("main".into()), expected: None, mutation: RefMutation::Set(id(22)), force: false, }], pack_len: 256, pack_hash: id(23), snapshot_generation_digest: id(24), projection_stage: None, kind: PushKindV2::Fork(ForkProofV2 { source_repo_id: id(25), source_genesis: id(26), source_tip: id(27), source_authority: id(28), }), }, &key, ) .unwrap() } pub fn snapshot() -> SignedRepoSnapshotV1 { let key = key(7); SignedRepoSnapshotV1::sign( RepoSnapshotV1 { source_instance: key.public().0, source_key_epoch: 9, repo_id: id(2), genesis_authority: id(4), current_authority: id(5), projection: ProjectionMode::Full, refs: vec![ RefStateV1 { target: RefTarget::Branch("main".into()), object: id(7), }, RefStateV1 { target: RefTarget::Release("v1".into()), object: id(8), }, ], event_low_repo_sequence: 10, object_low_repo_sequence: 9, high_repo_sequence: 12, high_event_digest: id(40), resulting_state_digest: id(41), config_epoch: 3, capabilities_digest: id(42), }, &key, ) .unwrap() } pub fn committed_transaction() -> CommittedTransactionV1 { CommittedTransactionV1 { repo_id: id(2), repo_sequence: 12, previous_event_digest: id(39), sequenced_at_micros: 12_345, source_kind: SourceKindV1::Client, actor: key(1).public().0, operation_id: [0x11; 16], operation_digest: signed_normal_push().stable_digest().unwrap(), signed_evidence_digest: id(43), old_authority: id(4), new_authority: id(5), refs: vec![AppliedRefV1 { target: RefTarget::Branch("main".into()), old: Some(id(6)), new: Some(id(7)), force: false, }], object_ids: vec![id(31), id(32), id(33)], commit_ids: vec![id(33)], resulting_state_digest: id(41), } } pub fn signed_event() -> SignedCommittedTransactionV1 { SignedCommittedTransactionV1::sign(committed_transaction(), 9, &key(7)).unwrap() } pub fn receipt() -> CommitReceiptV1 { CommitReceiptV1 { operation_id: [0x11; 16], operation_digest: signed_normal_push().stable_digest().unwrap(), repo_sequence: 12, current_authority: id(5), refs: vec![AppliedRefV1 { target: RefTarget::Branch("main".into()), old: Some(id(6)), new: Some(id(7)), force: false, }], objects_new: 3, retry_until_micros: 5_000, first_visible_at_micros: 4_500, receipt_visible_until_micros: 6_500, } } pub fn signed_read() -> SignedReadRequestV2 { SignedReadRequestV2::sign( CanonicalReadRequestV2 { method: HttpMethodV2::Post, route: ReadRouteV2::MissingObjects, repo_id: id(2), body_digest: id(70), snapshot_token_digest: id(71), timestamp_micros: 3_000, nonce: [0x44; 16], }, &key(4), ) .unwrap() } /// One `SignedReadRequestV2` per HTTP method the typed routes use, including /// a public (zero snapshot-token-digest) acquisition request, so the /// method/route pairing itself is byte-frozen and not just one POST route. pub fn signed_reads() -> Vec { vec![ SignedReadRequestV2::sign( CanonicalReadRequestV2 { method: HttpMethodV2::Get, route: ReadRouteV2::Snapshot, repo_id: id(2), body_digest: id(0), snapshot_token_digest: ObjectId([0; 32]), timestamp_micros: 2_000, nonce: [0x43; 16], }, &key(4), ) .unwrap(), SignedReadRequestV2::sign( CanonicalReadRequestV2 { method: HttpMethodV2::Get, route: ReadRouteV2::Object { object_id: id(9) }, repo_id: id(2), body_digest: id(0), snapshot_token_digest: id(71), timestamp_micros: 2_500, nonce: [0x45; 16], }, &key(4), ) .unwrap(), SignedReadRequestV2::sign( CanonicalReadRequestV2 { method: HttpMethodV2::Post, route: ReadRouteV2::Pack, repo_id: id(2), body_digest: id(70), snapshot_token_digest: id(71), timestamp_micros: 2_600, nonce: [0x46; 16], }, &key(4), ) .unwrap(), signed_read(), SignedReadRequestV2::sign( CanonicalReadRequestV2 { method: HttpMethodV2::Put, route: ReadRouteV2::ProjectionStageChunk { session_id: [0x61; 16], ordinal: 0, }, repo_id: id(2), body_digest: id(70), snapshot_token_digest: id(0), timestamp_micros: 2_700, nonce: [0x47; 16], }, &key(4), ) .unwrap(), SignedReadRequestV2::sign( CanonicalReadRequestV2 { method: HttpMethodV2::Delete, route: ReadRouteV2::ReleaseLease, repo_id: id(2), body_digest: id(0), snapshot_token_digest: id(71), timestamp_micros: 2_800, nonce: [0x48; 16], }, &key(4), ) .unwrap(), ] } pub fn snapshot_lease_token() -> SnapshotLeaseTokenV1 { let generation_digest = snapshot().snapshot.generation_digest().unwrap(); SnapshotLeaseTokenV1::mint( SnapshotLeaseClaimsV1 { version: SNAPSHOT_LEASE_TOKEN_VERSION, mac_key_epoch: 3, token_id: [0x51; 16], repo_id: id(2), projection: ProjectionMode::Full, generation_digest, high_repo_sequence: 12, high_event_digest: id(40), issued_at_micros: 9_000, expires_at_micros: 10_000, reader_key_digest: reader_key_digest_or_zero(Some(key(4).public().0)), }, &[0xa5; 32], ) .unwrap() } pub fn transaction_page() -> TransactionPageV1 { let transaction = committed_transaction(); TransactionPageV1 { repo_id: transaction.repo_id, after_repo_sequence: 11, after_event_digest: id(39), fixed_upper_repo_sequence: 12, events: vec![signed_event()], } } pub fn maintenance_cutover() -> MaintenanceCutoverV1 { MaintenanceCutoverV1 { storage_format_version: 2, minimum_protocol_version: 2, v1_post_disabled: true, writer_set_digest: id(1), peer_set_digest: id(2), maintenance_generation: 1, } } pub fn all_evidence() -> Vec { let context = evidence_context(); let admin_key = key(8); let legacy = TransactionEvidenceV1::LegacyMigrationV1 { actor: admin_key.public().0, actor_key_epoch: 2, migration_id: [0x51; 16], repo_id: id(3), chunk_ordinal: 1, chunk_count: 4, chunk_digest: id(81), manifest_digest: id(82), source_layout_digest: id(83), signature: [0; 64], }; let legacy_context = evidence_context_for(&legacy); vec![ TransactionEvidenceV1::ClientV2 { signed_envelope: SignedClientOperationV2::Push(signed_normal_push()), }, TransactionEvidenceV1::ClientV2 { signed_envelope: SignedClientOperationV2::Push(signed_fork_push()), }, TransactionEvidenceV1::MirrorEventV1 { source_instance: key(7).public().0, source_key_epoch: 9, source_snapshot_digest: snapshot().snapshot.generation_digest().unwrap(), source_event: signed_event(), }, TransactionEvidenceV1::MirrorSnapshotV1 { source_instance: key(7).public().0, source_key_epoch: 9, source_snapshot: snapshot(), destination_projection: ProjectionMode::Release, projected_manifest_digest: id(80), projected_object_count: 3, projected_object_bytes: 2048, }, legacy .sign_administrative(legacy_context, &admin_key) .unwrap(), TransactionEvidenceV1::ProjectionAdminV1 { actor: admin_key.public().0, actor_key_epoch: 2, command_digest: id(84), previous_projection: ProjectionMode::Full, new_projection: ProjectionMode::Metadata, signature: [0; 64], } .sign_administrative(context, &admin_key) .unwrap(), TransactionEvidenceV1::AdministrativeV1 { actor: admin_key.public().0, actor_key_epoch: 2, command_digest: id(85), signature: [0; 64], } .sign_administrative(context, &admin_key) .unwrap(), ] } pub fn staged_projection() -> ( ProjectionStageSessionV1, Vec, ProjectionStageManifestV1, StagedProjectionInstallV1, ) { let session_id = [0x61; 16]; let raw = Blob::new(b"phase-zero-staged-object".to_vec()).serialize(); let object_id = levcs_core::blake3_hash(&raw); let descriptor = StagedObjectV1 { object_id, object_type: ObjectType::Blob as u8, raw_len: raw.len() as u64, raw_digest: object_id, }; let chunk = ProjectionStageChunkV1 { session_id, ordinal: 0, chunk_count: 1, objects: vec![StagedChunkObjectV1 { descriptor: descriptor.clone(), raw_bytes: raw, }], }; let chunk_digest = chunk.chunk_digest().unwrap(); let manifest = ProjectionStageManifestV1 { session_id, chunk_digests: vec![chunk_digest], objects: vec![descriptor.clone()], membership_root: id(90), }; let manifest_digest = manifest.manifest_digest().unwrap(); let session = ProjectionStageSessionV1 { session_id, destination_repo: id(3), destination_genesis: id(4), expected_authority: id(4), projection: ProjectionMode::Full, source_kind: StageSourceKindV1::Mirror, actor: key(7).public().0, actor_key_epoch: 9, source_generation_digest: snapshot().snapshot.generation_digest().unwrap(), fork_proof: None, final_operation_id: [0x62; 16], final_operation_digest: id(91), final_evidence_digest: id(92), total_object_count: 1, total_object_bytes: descriptor.raw_len, chunk_count: 1, manifest_digest, expires_at_micros: 100_000, }; let install = StagedProjectionInstallV1 { session_id, manifest_digest, projection: ProjectionMode::Full, object_count: 1, object_bytes: descriptor.raw_len, membership_root: manifest.membership_root, artifact_set_digest: id(93), }; (session, vec![chunk], manifest, install) } pub fn roundtrip(value: &T) where T: CanonicalCodec + std::fmt::Debug + PartialEq, { let encoded = value.encode_canonical().unwrap(); assert_eq!(&T::decode_canonical(&encoded).unwrap(), value); } pub fn golden_value() -> serde_json::Value { let evidence = all_evidence(); let signed_snapshot = snapshot(); let committed = committed_transaction(); let receipt = receipt(); let (_, chunks, manifest, _) = staged_projection(); let blob = Blob::new(b"phase-zero-object-golden".to_vec()); let blob_bytes = blob.serialize(); let mut retry_operation = signed_normal_push().operation; retry_operation.issued_at_micros += 10; retry_operation.nonce = [0x99; 16]; let retry_push = SignedPushOperationV2::sign(retry_operation, &key(1)).unwrap(); let mut different_genesis_operation = signed_init().operation; different_genesis_operation.genesis_hash = id(99); let different_genesis = SignedInitOperationV2::sign(different_genesis_operation, &key(3)).unwrap(); let mut pack = levcs_protocol::Pack::new(); pack.push(ObjectType::Blob as u8, b"phase-0-pack".to_vec()); let init_client_evidence = SignedClientOperationV2::Init(signed_init()); let push_body = encode_push_request_body( &signed_normal_push(), &vec![0xab; signed_normal_push().operation.pack_len as usize], ) .unwrap(); let init_body = encode_init_request_body( &signed_init(), &vec![0xcd; signed_init().operation.genesis_len as usize], ) .unwrap(); let (session, _, _, install) = staged_projection(); let lease_token = snapshot_lease_token(); let page = transaction_page(); let cutover = maintenance_cutover(); let reads = signed_reads(); let statuses = [ TransactionStatusV1::Pending { operation_digest: receipt.operation_digest, retry_until_micros: receipt.retry_until_micros, phase: PendingPhase::Queued, }, TransactionStatusV1::Resolving { operation_digest: receipt.operation_digest, retry_until_micros: receipt.retry_until_micros, shard_sequence: Some(77), }, TransactionStatusV1::Committed(receipt.clone()), TransactionStatusV1::Expired { operation_digest: receipt.operation_digest, retry_until_micros: receipt.retry_until_micros, tombstone_until_micros: 7_000, }, TransactionStatusV1::Unknown, ]; serde_json::json!({ "schema_version": 1, "objects": { "blob_hex": hex::encode(&blob_bytes), "blob_id": blob.object_id().to_hex(), "pack_v1_hex": hex::encode(pack.encode()) }, "vectors": { "init_envelope_hex": hex::encode(signed_init().encode_canonical().unwrap()), "init_same_id_different_genesis_hex": hex::encode(different_genesis.encode_canonical().unwrap()), "normal_push_envelope_hex": hex::encode(signed_normal_push().encode_canonical().unwrap()), "normal_push_retry_envelope_hex": hex::encode(retry_push.encode_canonical().unwrap()), "fork_evidence_hex": hex::encode(evidence[1].encode_canonical().unwrap()), "mirror_event_evidence_hex": hex::encode(evidence[2].encode_canonical().unwrap()), "mirror_snapshot_evidence_hex": hex::encode(evidence[3].encode_canonical().unwrap()), "legacy_evidence_hex": hex::encode(evidence[4].encode_canonical().unwrap()), "projection_admin_evidence_hex": hex::encode(evidence[5].encode_canonical().unwrap()), "administrative_evidence_hex": hex::encode(evidence[6].encode_canonical().unwrap()), "signed_event_hex": hex::encode(signed_event().encode_canonical().unwrap()), "signed_snapshot_hex": hex::encode(signed_snapshot.encode_canonical().unwrap()), "signed_private_read_hex": hex::encode(signed_read().encode_canonical().unwrap()), "missing_request_hex": hex::encode(MissingObjectsRequestV1 { generation_digest: id(71), object_ids: vec![id(1), id(2), id(3)] }.encode_canonical().unwrap()), "stage_chunk_hex": hex::encode(chunks[0].encode_canonical().unwrap()), "stage_manifest_hex": hex::encode(manifest.encode_canonical().unwrap()), "init_client_evidence_hex": hex::encode(init_client_evidence.encode_canonical().unwrap()), "push_body_hex": hex::encode(&push_body), "init_body_hex": hex::encode(&init_body), "stage_session_hex": hex::encode(session.encode_canonical().unwrap()), "staged_install_hex": hex::encode(install.encode_canonical().unwrap()), "snapshot_lease_token_hex": hex::encode(lease_token.encode_canonical().unwrap()), "transaction_page_hex": hex::encode(page.encode_canonical().unwrap()), "maintenance_cutover_hex": hex::encode(cutover.encode_canonical().unwrap()), "signed_reads_hex": reads.iter().map(|r| hex::encode(r.encode_canonical().unwrap())).collect::>() }, "digests": { "ref_state": ref_state_digest(&signed_snapshot.snapshot.refs).unwrap().to_hex(), "snapshot_generation": signed_snapshot.snapshot.generation_digest().unwrap().to_hex(), "event": committed.event_digest().unwrap().to_hex(), "fork_evidence": evidence[1].evidence_digest().unwrap().to_hex(), "mirror_event_evidence": evidence[2].evidence_digest().unwrap().to_hex(), "mirror_snapshot_evidence": evidence[3].evidence_digest().unwrap().to_hex(), "legacy_evidence": evidence[4].evidence_digest().unwrap().to_hex(), "projection_admin_evidence": evidence[5].evidence_digest().unwrap().to_hex(), "administrative_evidence": evidence[6].evidence_digest().unwrap().to_hex(), "status_pending": statuses[0].status_digest().unwrap().to_hex(), "status_resolving": statuses[1].status_digest().unwrap().to_hex(), "status_committed": statuses[2].status_digest().unwrap().to_hex(), "status_expired": statuses[3].status_digest().unwrap().to_hex(), "status_unknown": statuses[4].status_digest().unwrap().to_hex(), "stage_chunk": chunks[0].chunk_digest().unwrap().to_hex(), "stage_manifest": manifest.manifest_digest().unwrap().to_hex(), "init_stable": signed_init().stable_digest().unwrap().to_hex(), "stage_session": session.session_digest().unwrap().to_hex() } }) }