ic-backup 0.5.0

Host-side snapshot backup and same-release recovery for Internet Computer canisters
Documentation
use super::*;
use crate::test_support::{
    execution_settlement::{applied, record},
    membership::{hash, plan},
};
use serde_json::json;

fn schema() -> serde_json::Value {
    serde_json::from_str(include_str!(
        "../../../../../../docs/contracts/execution-settlement.schema.json"
    ))
    .unwrap()
}
#[test]
fn independent_goldens_bind_every_history_event_and_checkpoint_identity() {
    let schema = schema();
    for golden in schema["x-history-goldens"].as_array().unwrap() {
        let journal: AttemptJournalRecord =
            serde_json::from_value(golden["record"].clone()).unwrap();
        assert_eq!(
            journal.authority().digest().hash(),
            golden["authority_sha256"].as_str().unwrap()
        );
        assert_eq!(journal.digest().hash(), golden["sha256"].as_str().unwrap());
    }
    let golden = &schema["x-checkpoint-golden"];
    let record: ExecutionSettlementRecord =
        serde_json::from_value(golden["record"].clone()).unwrap();
    assert_eq!(record.digest().hash(), golden["sha256"].as_str().unwrap());
    assert_eq!(record.journals()[1].operation_sequence(), u64::MAX);
}
#[test]
fn strict_v1_rows_reject_missing_unknown_invalid_and_duplicate_identities() {
    let plan = plan();
    let record = record(&plan, &applied(&plan));
    let value = serde_json::to_value(&record).unwrap();
    for field in ["version", "plan_intent", "journals"] {
        let mut changed = value.clone();
        changed.as_object_mut().unwrap().remove(field);
        assert!(serde_json::from_value::<ExecutionSettlementRecord>(changed).is_err());
    }
    for (field, replacement) in [
        ("version", json!(2)),
        ("version", json!(null)),
        ("plan_intent", json!("invalid")),
        ("journals", json!([])),
    ] {
        let mut changed = value.clone();
        changed[field] = replacement;
        assert!(serde_json::from_value::<ExecutionSettlementRecord>(changed).is_err());
    }
    let mut changed = value.clone();
    changed["complete"] = json!(true);
    assert!(serde_json::from_value::<ExecutionSettlementRecord>(changed).is_err());
    let mut changed = value.clone();
    changed["journals"][0]["applied"] = json!(true);
    assert!(serde_json::from_value::<ExecutionSettlementRecord>(changed).is_err());
    let mut changed = value;
    changed["journals"][1]["operation_sequence"] = json!(0);
    changed["journals"][1]["history"] = json!(hash("90"));
    assert!(serde_json::from_value::<ExecutionSettlementRecord>(changed).is_err());
}
#[test]
fn canonical_rows_and_hashes_preserve_order_independent_identity() {
    let plan = plan();
    let journals = applied(&plan);
    let record = record(&plan, &journals);
    let mut value = serde_json::to_value(&record).unwrap();
    value["journals"].as_array_mut().unwrap().reverse();
    value["plan_intent"]["hash"] = json!(record.plan_intent().hash().to_uppercase());
    for row in value["journals"].as_array_mut().unwrap() {
        row["history"]["hash"] = json!(row["history"]["hash"].as_str().unwrap().to_uppercase());
    }
    let canonical: ExecutionSettlementRecord = serde_json::from_value(value).unwrap();
    assert_eq!(record, canonical);
    assert_eq!(record.digest(), canonical.digest());
    let mut changed = serde_json::to_value(record.clone()).unwrap();
    changed["plan_intent"] = json!(hash("90"));
    let changed: ExecutionSettlementRecord = serde_json::from_value(changed).unwrap();
    assert_ne!(record.digest(), changed.digest());
}
#[test]
fn exact_operation_ceiling_fits_io_and_one_more_row_rejects_before_admission() {
    let rows: Vec<_> = (0..MAX_EFFECT_OPERATIONS)
        .map(|index| ExecutionSettlementJournalRecord {
            operation_sequence: u64::MAX - index as u64,
            history: hash("12"),
        })
        .collect();
    let record = ExecutionSettlementRecord::new(hash("ab"), rows.clone()).unwrap();
    assert_eq!(record.journals().len(), MAX_EFFECT_OPERATIONS);
    assert!(
        serde_json::to_vec_pretty(&record).unwrap().len() as u64 <= MAX_EXECUTION_SETTLEMENT_BYTES
    );
    let mut excess = rows;
    excess.push(ExecutionSettlementJournalRecord {
        operation_sequence: 0,
        history: hash("12"),
    });
    assert_eq!(
        ExecutionSettlementRecord::new(hash("ab"), excess.clone()).unwrap_err(),
        ExecutionSettlementError::InvalidJournalCount
    );
    assert!(
        serde_json::from_value::<ExecutionSettlementRecord>(
            json!({"version":1,"plan_intent":hash("ab"),"journals":excess})
        )
        .is_err()
    );
}
#[test]
fn same_progress_with_changed_receipt_still_changes_history_fingerprint() {
    let plan = plan();
    let journals = applied(&plan);
    let journal = &journals[0];
    let mut value = serde_json::to_value(journal).unwrap();
    value["events"][1]["evidence"] = json!(hash("34").hash());
    let changed: AttemptJournalRecord = serde_json::from_value(value).unwrap();
    assert_eq!(journal.view(), changed.view());
    assert_ne!(journal.digest(), changed.digest());
}

#[test]
fn execution_settlement_bulk_authorities_preserve_full_original_scalar_binding() {
    let plan = plan();
    let authorities = plan.attempt_authorities().unwrap();
    assert_eq!(authorities.len(), plan.operations().len());
    for (authority, operation) in authorities.iter().zip(plan.operations()) {
        assert_eq!(
            authority,
            &plan
                .attempt_authority(operation.operation_sequence())
                .unwrap()
        );
        assert_eq!(authority.binding().intent(), plan.digest().hash());
        assert_eq!(authority.budget(), operation.budget());
    }
}