187 lines
6.9 KiB
Rust
187 lines
6.9 KiB
Rust
// ============================================================
|
|
// EXAMPLE: twin_coupling
|
|
// PURPOSE: Demonstrate the data-convention pattern for linking a
|
|
// digital-twin computation row back to the sensor rows
|
|
// that fed it. Sensor chain and twin chain are independent
|
|
// trusty entities. The twin row carries a `source_refs`
|
|
// array in its `data` field so an external verifier can
|
|
// check the linkage from two ProofBundles alone -- no
|
|
// extra tables, no schema changes.
|
|
//
|
|
// See `docs/twin-coupling.md` for the conceptual doc.
|
|
//
|
|
// RUN: cargo run -p trusty-sqlite --example twin_coupling
|
|
// ============================================================
|
|
|
|
use serde_json::{json, Value};
|
|
use std::path::PathBuf;
|
|
use trusty_core::SecretKey;
|
|
use trusty_sqlite::Trusty;
|
|
|
|
fn tmp_db_path() -> PathBuf {
|
|
let mut p = std::env::temp_dir();
|
|
let nanos = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.map(|d| d.as_nanos())
|
|
.unwrap_or(0);
|
|
p.push(format!("trusty_twin_coupling_demo_{nanos}.db"));
|
|
p
|
|
}
|
|
|
|
fn main() {
|
|
let path = tmp_db_path();
|
|
println!("trusty twin-coupling demo");
|
|
println!("db: {}\n", path.display());
|
|
|
|
let sip_key = SecretKey::new(*b"0123456789abcdef");
|
|
let mut t = Trusty::open(&path, sip_key.clone()).expect("open trusty");
|
|
|
|
// ----------------------------------------------------------------
|
|
// step 1: append sensor readings to the sensor chain
|
|
// ----------------------------------------------------------------
|
|
println!("step 1: append three sensor readings");
|
|
|
|
let r0 = t
|
|
.append("sensor-001", &json!({"temp_c": 21.1}), "iot://sensor-001")
|
|
.expect("append r0");
|
|
println!(
|
|
" seq={} chain_id={:.16}... row_hash={}",
|
|
r0.sequence, r0.chain_id, r0.row_hash
|
|
);
|
|
|
|
let r1 = t
|
|
.append("sensor-001", &json!({"temp_c": 21.4}), "iot://sensor-001")
|
|
.expect("append r1");
|
|
println!(
|
|
" seq={} chain_id={:.16}... row_hash={}",
|
|
r1.sequence, r1.chain_id, r1.row_hash
|
|
);
|
|
|
|
let r2 = t
|
|
.append("sensor-001", &json!({"temp_c": 21.6}), "iot://sensor-001")
|
|
.expect("append r2");
|
|
println!(
|
|
" seq={} chain_id={:.16}... row_hash={}",
|
|
r2.sequence, r2.chain_id, r2.row_hash
|
|
);
|
|
|
|
// ----------------------------------------------------------------
|
|
// step 2: twin computation cites the three sensor rows by
|
|
// chain_id + sequence + row_hash (data convention)
|
|
// ----------------------------------------------------------------
|
|
println!("\nstep 2: append twin computation row with source_refs");
|
|
|
|
let avg = (21.1_f64 + 21.4 + 21.6) / 3.0;
|
|
let twin_data = json!({
|
|
"computation": "rolling_avg_temp_c",
|
|
"result": (avg * 100.0).round() / 100.0,
|
|
"source_refs": [
|
|
{"chain_id": r0.chain_id, "sequence": r0.sequence, "row_hash": r0.row_hash},
|
|
{"chain_id": r1.chain_id, "sequence": r1.sequence, "row_hash": r1.row_hash},
|
|
{"chain_id": r2.chain_id, "sequence": r2.sequence, "row_hash": r2.row_hash},
|
|
]
|
|
});
|
|
|
|
let tw = t
|
|
.append("twin-001", &twin_data, "omniverse://twin-001")
|
|
.expect("append twin");
|
|
println!(" seq={} row_hash={}", tw.sequence, tw.row_hash);
|
|
|
|
// ----------------------------------------------------------------
|
|
// step 3: verify both chains independently
|
|
// ----------------------------------------------------------------
|
|
println!("\nstep 3: verify both chains");
|
|
|
|
let sensor_report = t.verify("sensor-001").expect("verify sensor");
|
|
println!(
|
|
" sensor-001: ok={} rows={}",
|
|
sensor_report.rows_valid, sensor_report.row_count
|
|
);
|
|
assert!(sensor_report.rows_valid, "sensor chain verification failed");
|
|
|
|
let twin_report = t.verify("twin-001").expect("verify twin");
|
|
println!(
|
|
" twin-001: ok={} rows={}",
|
|
twin_report.rows_valid, twin_report.row_count
|
|
);
|
|
assert!(twin_report.rows_valid, "twin chain verification failed");
|
|
|
|
// ----------------------------------------------------------------
|
|
// step 4: external-verifier cross-chain check
|
|
// Export both ProofBundles; walk twin source_refs and
|
|
// confirm each referenced row_hash is present and matches
|
|
// in the sensor bundle.
|
|
// ----------------------------------------------------------------
|
|
println!("\nstep 4: cross-chain verify from ProofBundles");
|
|
|
|
let sensor_bundle = t
|
|
.export_bundle("sensor-001", None, None)
|
|
.expect("sensor bundle");
|
|
let twin_bundle = t
|
|
.export_bundle("twin-001", None, None)
|
|
.expect("twin bundle");
|
|
|
|
let sensor_verify = sensor_bundle.verify(&sip_key);
|
|
let twin_verify = twin_bundle.verify(&sip_key);
|
|
println!(
|
|
" sensor bundle verify: ok={} rows={}",
|
|
sensor_verify.rows_valid, sensor_verify.row_count
|
|
);
|
|
println!(
|
|
" twin bundle verify: ok={} rows={}",
|
|
twin_verify.rows_valid, twin_verify.row_count
|
|
);
|
|
assert!(sensor_verify.rows_valid);
|
|
assert!(twin_verify.rows_valid);
|
|
|
|
// Walk twin rows; for each source_ref, confirm the sensor bundle
|
|
// contains a row at that sequence with a matching row_hash.
|
|
let mut refs_checked = 0usize;
|
|
for twin_row in twin_bundle.rows.iter().filter(|r| r.sequence >= 0) {
|
|
let payload: Value = twin_row
|
|
.raw_payload
|
|
.as_ref()
|
|
.and_then(|p| serde_json::from_str(p).ok())
|
|
.expect("twin row has no payload");
|
|
|
|
let data = payload.get("data").expect("payload has no data");
|
|
let refs = match data.get("source_refs").and_then(|v| v.as_array()) {
|
|
Some(r) => r,
|
|
None => continue,
|
|
};
|
|
|
|
for sr in refs {
|
|
let ref_chain_id = sr["chain_id"].as_str().expect("chain_id");
|
|
let ref_seq = sr["sequence"].as_i64().expect("sequence");
|
|
let ref_hash = sr["row_hash"].as_str().expect("row_hash");
|
|
|
|
let matched = sensor_bundle.rows.iter().find(|r| {
|
|
r.chain_id == ref_chain_id
|
|
&& r.sequence == ref_seq
|
|
&& r.row_hash.as_deref() == Some(ref_hash)
|
|
});
|
|
|
|
match matched {
|
|
Some(m) => {
|
|
println!(
|
|
" source_ref ok: seq={} row_hash={} (sensor chain_id={:.16}...)",
|
|
m.sequence, ref_hash, ref_chain_id
|
|
);
|
|
refs_checked += 1;
|
|
}
|
|
None => {
|
|
panic!(
|
|
"source_ref not found in sensor bundle: chain_id={} seq={} row_hash={}",
|
|
ref_chain_id, ref_seq, ref_hash
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
println!("\n {refs_checked} source_refs verified across ProofBundles");
|
|
assert_eq!(refs_checked, 3, "expected 3 source_refs checked");
|
|
|
|
println!("\ndone. (cleanup: rm {})", path.display());
|
|
}
|