Phase 8 Sprint 1: FFI Export for Slash Protocol
- Zig L1: Implemented qvl_issue_slash_signal (constructs SlashSignal) - Rust L2: Added FFI binding and safe wrapper issue_slash_signal - Config: Wired l1_slash_mod into qvl_ffi build - Verified: Unit test for signal creation passing The active defense loop is closed. L2 can now pull the trigger.
This commit is contained in:
parent
a60fd16e45
commit
cbb73d16b8
|
|
@ -303,6 +303,7 @@ pub fn build(b: *std.Build) void {
|
|||
.optimize = optimize,
|
||||
});
|
||||
l1_qvl_ffi_mod.addImport("qvl", l1_qvl_mod);
|
||||
l1_qvl_ffi_mod.addImport("slash", l1_slash_mod);
|
||||
l1_qvl_ffi_mod.addImport("time", time_mod);
|
||||
|
||||
// QVL FFI static library (for Rust L2 Membrane Agent)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const qvl = @import("qvl.zig");
|
|||
const pop_mod = @import("proof_of_path.zig");
|
||||
const trust_graph = @import("trust_graph.zig");
|
||||
const time = @import("time");
|
||||
const slash = @import("slash");
|
||||
|
||||
const RiskGraph = qvl.types.RiskGraph;
|
||||
const RiskEdge = qvl.types.RiskEdge;
|
||||
|
|
@ -260,6 +261,38 @@ export fn qvl_revoke_trust_edge(
|
|||
return -2; // Not found
|
||||
}
|
||||
|
||||
/// Issue a SlashSignal for a detected betrayal
|
||||
/// Returns 0 on success, < 0 on error
|
||||
/// If 'out_signal' is non-null, writes serialized signal (82 bytes)
|
||||
export fn qvl_issue_slash_signal(
|
||||
ctx: ?*QvlContext,
|
||||
target_did: [*c]const u8,
|
||||
reason: u8,
|
||||
out_signal: [*c]u8,
|
||||
) callconv(.c) c_int {
|
||||
_ = ctx; // Context not strictly needed for constructing signal, but good for future validation
|
||||
if (target_did == null) return -2;
|
||||
|
||||
var did: [32]u8 = undefined;
|
||||
@memcpy(&did, target_did[0..32]);
|
||||
|
||||
const signal = slash.SlashSignal{
|
||||
.target_did = did,
|
||||
.reason = @enumFromInt(reason),
|
||||
.punishment = .Quarantine, // Default to Quarantine
|
||||
.evidence_hash = [_]u8{0} ** 32, // TODO: Hash actual evidence
|
||||
.timestamp = std.time.timestamp(),
|
||||
.nonce = 0,
|
||||
};
|
||||
|
||||
if (out_signal != null) {
|
||||
const bytes = signal.serializeForSigning();
|
||||
@memcpy(out_signal[0..82], &bytes);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TESTS (C ABI validation)
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -83,6 +83,13 @@ extern "C" {
|
|||
from: u32,
|
||||
to: u32,
|
||||
) -> c_int;
|
||||
|
||||
fn qvl_issue_slash_signal(
|
||||
ctx: *mut QvlContext,
|
||||
target_did: *const u8,
|
||||
reason: u8,
|
||||
out_signal: *mut u8,
|
||||
) -> c_int;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -261,6 +268,29 @@ impl QvlClient {
|
|||
Err(QvlError::MutationFailed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Issue a SlashSignal (returns 82-byte serialized signal for signing/broadcast)
|
||||
pub fn issue_slash_signal(&self, target_did: &[u8; 32], reason: u8) -> Result<[u8; 82], QvlError> {
|
||||
if self.ctx.is_null() {
|
||||
return Err(QvlError::NullContext);
|
||||
}
|
||||
|
||||
let mut out = [0u8; 82];
|
||||
let result = unsafe {
|
||||
qvl_issue_slash_signal(
|
||||
self.ctx,
|
||||
target_did.as_ptr(),
|
||||
reason,
|
||||
out.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
|
||||
if result == 0 {
|
||||
Ok(out)
|
||||
} else {
|
||||
Err(QvlError::MutationFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for QvlClient {
|
||||
|
|
@ -321,4 +351,19 @@ mod tests {
|
|||
assert_eq!(anomaly.score, 0.0);
|
||||
assert_eq!(anomaly.reason, AnomalyReason::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_issue_slash_signal() {
|
||||
let client = QvlClient::new().unwrap();
|
||||
let target = [1u8; 32];
|
||||
let reason = 1; // BetrayalNegativeCycle
|
||||
|
||||
let signal = client.issue_slash_signal(&target, reason).unwrap();
|
||||
// Verify first byte (target DID[0] = 1)
|
||||
assert_eq!(signal[0], 1);
|
||||
// Verify reason (offset 32 = 1)
|
||||
assert_eq!(signal[32], 1);
|
||||
// Verify punishment (offset 33 = 1 Quarantine)
|
||||
assert_eq!(signal[33], 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue