Begin post-v1.0.1 protocol and runtime improvements
This commit is contained in:
1 parent
ecb1721ff4
commit
3225509713
25 files changed
+929
-187
No files matched your search
@@ -55,6 +55,10 @@ impl std::fmt::Display for GeneError {
|
||||
|
||||
impl std::error::Error for GeneError {}
|
||||
|
||||
/// Creates a new, blank `GeneState` with the specified gene buffer size.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `gene_size` - The length of the gene byte buffer. Must be within `1..=MAX_GENE_SIZE`.
|
||||
pub fn new_state(gene_size: usize) -> Result<GeneState, GeneError> {
|
||||
if !(1..=MAX_GENE_SIZE).contains(&gene_size) {
|
||||
return Err(GeneError::InvalidGeneSize { size: gene_size });
|
||||
@@ -65,6 +69,7 @@ pub fn new_state(gene_size: usize) -> Result<GeneState, GeneError> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new `GeneState` with the default gene buffer size (`DEFAULT_GENE_SIZE`).
|
||||
pub fn default_state() -> GeneState {
|
||||
GeneState {
|
||||
gene: vec![0; DEFAULT_GENE_SIZE],
|
||||
@@ -72,6 +77,10 @@ pub fn default_state() -> GeneState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates the structural invariants of the given `GeneState`.
|
||||
///
|
||||
/// Ensures the gene size is within valid bounds and the environment records are
|
||||
/// properly sorted, non-empty, and free of duplicates.
|
||||
pub fn validate_state(state: &GeneState) -> Result<(), GeneError> {
|
||||
if !(1..=MAX_GENE_SIZE).contains(&state.gene.len()) {
|
||||
return Err(GeneError::InvalidGeneSize {
|
||||
@@ -81,6 +90,13 @@ pub fn validate_state(state: &GeneState) -> Result<(), GeneError> {
|
||||
validate_environment(&state.environment)
|
||||
}
|
||||
|
||||
/// Retrieves the quantity associated with a specific environment symbol.
|
||||
///
|
||||
/// Performs a binary search over the sorted environment records. Returns 0 if the symbol is missing.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The gene state to query.
|
||||
/// * `symbol` - The 16-bit key to search for.
|
||||
pub fn get_env_quantity(state: &GeneState, symbol: u16) -> u32 {
|
||||
match state
|
||||
.environment
|
||||
@@ -91,6 +107,14 @@ pub fn get_env_quantity(state: &GeneState, symbol: u16) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the quantity of an environment symbol in a `GeneState`.
|
||||
///
|
||||
/// If quantity is 0, the record is removed. The environment is kept sorted alphabetically by symbol.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state to update.
|
||||
/// * `symbol` - The 16-bit key.
|
||||
/// * `quantity` - The quantity to assign.
|
||||
pub fn set_env_quantity(
|
||||
state: &mut GeneState,
|
||||
symbol: u16,
|
||||
@@ -125,6 +149,12 @@ pub fn set_env_quantity(
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a quantity to an environment symbol with saturating arithmetic.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state.
|
||||
/// * `symbol` - The 16-bit key.
|
||||
/// * `quantity` - The quantity to add.
|
||||
pub fn add_env_quantity(
|
||||
state: &mut GeneState,
|
||||
symbol: u16,
|
||||
@@ -136,6 +166,14 @@ pub fn add_env_quantity(
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
/// Subtracts a quantity from an environment symbol with saturating arithmetic.
|
||||
///
|
||||
/// If the resulting quantity drops to 0, the symbol is removed.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state.
|
||||
/// * `symbol` - The 16-bit key.
|
||||
/// * `quantity` - The quantity to subtract.
|
||||
pub fn sub_env_quantity(
|
||||
state: &mut GeneState,
|
||||
symbol: u16,
|
||||
@@ -147,6 +185,12 @@ pub fn sub_env_quantity(
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
/// Encodes the environment records list into a compact byte slice.
|
||||
///
|
||||
/// Each record is written as a little-endian `u16` symbol followed by a little-endian `u32` quantity.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `records` - The sorted environment records.
|
||||
pub fn encode_environment(records: &[EnvironmentRecord]) -> Result<Vec<u8>, GeneError> {
|
||||
validate_environment(records)?;
|
||||
let mut out = Vec::with_capacity(records.len() * 6);
|
||||
@@ -157,6 +201,12 @@ pub fn encode_environment(records: &[EnvironmentRecord]) -> Result<Vec<u8>, Gene
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Decodes environment records from a byte slice.
|
||||
///
|
||||
/// Validates that the length is a multiple of 6 and that records conform to sorting and quantity invariants.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `blob` - The serialized byte slice.
|
||||
pub fn decode_environment(blob: &[u8]) -> Result<Vec<EnvironmentRecord>, GeneError> {
|
||||
if !blob.len().is_multiple_of(6) {
|
||||
return Err(GeneError::EnvironmentBlobLengthInvalid { len: blob.len() });
|
||||
@@ -180,6 +230,9 @@ pub fn decode_environment(blob: &[u8]) -> Result<Vec<EnvironmentRecord>, GeneErr
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
/// Computes the raw Blake3 cryptographic commitment of the `GeneState`.
|
||||
///
|
||||
/// Includes gene length, gene buffer, environment record count, and individual record key/values.
|
||||
pub fn commitment(state: &GeneState) -> [u8; 32] {
|
||||
let mut h = blake3::Hasher::new();
|
||||
h.update(b"chronoseal/gene/v1");
|
||||
@@ -193,10 +246,19 @@ pub fn commitment(state: &GeneState) -> [u8; 32] {
|
||||
*h.finalize().as_bytes()
|
||||
}
|
||||
|
||||
/// Computes the hex-encoded cryptographic commitment of the `GeneState`.
|
||||
pub fn commitment_hex(state: &GeneState) -> String {
|
||||
hex::encode(commitment(state))
|
||||
}
|
||||
|
||||
/// Computes a context-bound Blake3 cryptographic commitment of the `GeneState`.
|
||||
///
|
||||
/// Integrates `session_id` and the current `step` index into the hash to bind the commitment.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The gene state.
|
||||
/// * `session_id` - The client session ID.
|
||||
/// * `step` - The mutation step index.
|
||||
pub fn commitment_with_context(state: &GeneState, session_id: &str, step: u64) -> [u8; 32] {
|
||||
let mut h = blake3::Hasher::new();
|
||||
h.update(b"chronoseal/gene/v1");
|
||||
@@ -206,10 +268,17 @@ pub fn commitment_with_context(state: &GeneState, session_id: &str, step: u64) -
|
||||
*h.finalize().as_bytes()
|
||||
}
|
||||
|
||||
/// Computes a context-bound, hex-encoded Blake3 cryptographic commitment of the `GeneState`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The gene state.
|
||||
/// * `session_id` - The client session ID.
|
||||
/// * `step` - The mutation step index.
|
||||
pub fn commitment_hex_with_context(state: &GeneState, session_id: &str, step: u64) -> String {
|
||||
hex::encode(commitment_with_context(state, session_id, step))
|
||||
}
|
||||
|
||||
/// Helper function to validate sorting, uniqueness, and non-zero properties of environment records.
|
||||
fn validate_environment(records: &[EnvironmentRecord]) -> Result<(), GeneError> {
|
||||
if records.len() > MAX_ENV_RECORDS {
|
||||
return Err(GeneError::TooManyEnvironmentRecords { len: records.len() });
|
||||
|
||||
+32
-8
@@ -1,7 +1,15 @@
|
||||
use crate::protocol::{EntropyData, StackState};
|
||||
use blake3::Hasher;
|
||||
|
||||
/// Initial hash for a brand-new session: Blake3(session_id || pub_key || salt)
|
||||
/// Computes the initial hash for a brand-new attestation session.
|
||||
///
|
||||
/// The hash is constructed as:
|
||||
/// `Blake3(session_id || pub_key || salt)`
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - The unique hex-encoded identifier for the session.
|
||||
/// * `pub_key` - The client's Ed25519 public key.
|
||||
/// * `salt` - The initial server-issued salt.
|
||||
pub fn initial_hash(session_id: &str, pub_key: &[u8], salt: &[u8]) -> Vec<u8> {
|
||||
let mut h = Hasher::new();
|
||||
h.update(session_id.as_bytes());
|
||||
@@ -10,7 +18,18 @@ pub fn initial_hash(session_id: &str, pub_key: &[u8], salt: &[u8]) -> Vec<u8> {
|
||||
h.finalize().as_bytes().to_vec()
|
||||
}
|
||||
|
||||
/// Next hash in the chain: Blake3 with the salt mixed in (no keyed mode needed)
|
||||
/// Computes the next hash in the Blake3 attestation chain.
|
||||
///
|
||||
/// This mixes in the previous hash head, the client timestamp, the serialized entropy data,
|
||||
/// the VM stack state, and the server-issued salt. Uses `serde_json::to_vec` to avoid
|
||||
/// intermediate heap string allocations and UTF-8 verification checks.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `prev_hash` - The previous hash-chain head.
|
||||
/// * `timestamp` - The client-supplied heartbeat timestamp.
|
||||
/// * `entropy` - The collected browser interaction entropy.
|
||||
/// * `stack` - The final VM stack state after running the opcode program.
|
||||
/// * `salt` - The server-issued salt for rotation.
|
||||
pub fn next_chain_hash(
|
||||
prev_hash: &[u8],
|
||||
timestamp: u64,
|
||||
@@ -18,14 +37,13 @@ pub fn next_chain_hash(
|
||||
stack: &StackState,
|
||||
salt: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let entropy_json = serde_json::to_string(entropy).unwrap();
|
||||
let stack_json = serde_json::to_string(stack).unwrap();
|
||||
let entropy_bytes = serde_json::to_vec(entropy).unwrap();
|
||||
let stack_bytes = serde_json::to_vec(stack).unwrap();
|
||||
|
||||
let entropy_hash = blake3::hash(entropy_json.as_bytes());
|
||||
let stack_hash = blake3::hash(stack_json.as_bytes());
|
||||
let entropy_hash = blake3::hash(&entropy_bytes);
|
||||
let stack_hash = blake3::hash(&stack_bytes);
|
||||
|
||||
let mut h = Hasher::new();
|
||||
// Mix the salt into the hash state
|
||||
h.update(salt);
|
||||
h.update(prev_hash);
|
||||
h.update(×tamp.to_le_bytes());
|
||||
@@ -34,9 +52,15 @@ pub fn next_chain_hash(
|
||||
h.finalize().as_bytes().to_vec()
|
||||
}
|
||||
|
||||
/// Hash of all stack items for VM HASH opcode
|
||||
/// Computes a 32-bit FNV-like Blake3 hash of all stack elements.
|
||||
///
|
||||
/// This is used by the VM `HASH` opcode to fold the current stack state into a single value.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `stack` - The list of u32 stack elements to hash.
|
||||
pub fn hash_stack(stack: &[u32]) -> u32 {
|
||||
let data: Vec<u8> = stack.iter().flat_map(|x| x.to_le_bytes()).collect();
|
||||
let hash = blake3::hash(&data);
|
||||
u32::from_le_bytes(hash.as_bytes()[..4].try_into().unwrap())
|
||||
}
|
||||
|
||||
@@ -1,73 +1,119 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Request payload sent by the client to initialize a new attestation session.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct InitRequest {
|
||||
/// Hex-encoded 32-byte Ed25519 public verifying key generated by the client.
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
/// Response payload returned by the server upon successful session initialization.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InitResponse {
|
||||
/// The unique hex-encoded session identifier.
|
||||
pub session_id: String,
|
||||
/// The initial server-issued salt to be mixed in the first heartbeat's hash.
|
||||
pub salt: String,
|
||||
/// Base64-encoded initial VM program for client stack execution.
|
||||
pub opcodes_b64: String,
|
||||
/// The computed initial hash of the attestation chain.
|
||||
pub initial_hash: String,
|
||||
/// Timestamp in milliseconds indicating when the session expires.
|
||||
pub expires_at: u64,
|
||||
/// Minimum time in milliseconds allowed between subsequent heartbeats.
|
||||
pub heartbeat_min_interval_ms: u64,
|
||||
/// Maximum time in milliseconds allowed between subsequent heartbeats.
|
||||
pub heartbeat_max_interval_ms: u64,
|
||||
/// Size of the synthetic gene byte buffer.
|
||||
pub gene_size: u32,
|
||||
/// The current mutation step index (starts at 1).
|
||||
pub mutation_step: u64,
|
||||
/// Base64-encoded initial gene mutation program.
|
||||
pub mutation_order_b64: String,
|
||||
/// The number of mutation rounds configured on the server.
|
||||
pub mutation_rounds: u8,
|
||||
}
|
||||
|
||||
/// Heartbeat request payload submitted periodically by the client to prove session continuity.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct HeartbeatRequest {
|
||||
/// The session identifier.
|
||||
pub session_id: String,
|
||||
/// The expected hash from the previous heartbeat/initialization step.
|
||||
pub prev_hash: String,
|
||||
/// The client's current system timestamp in milliseconds.
|
||||
pub timestamp: u64,
|
||||
/// The collected client entropy data (such as mouse events).
|
||||
pub entropy_data: EntropyData,
|
||||
/// The final execution state of the client's VM stack program.
|
||||
pub stack_state: StackState,
|
||||
/// The client's browser hardware and layout fingerprint.
|
||||
pub fingerprint: Fingerprint,
|
||||
/// The mutation step index corresponding to the pending mutation.
|
||||
pub mutation_step: u64,
|
||||
/// Hex-encoded commitment of the mutated gene state.
|
||||
pub gene_commitment: String,
|
||||
/// Ed25519 signature of the canonical JSON-serialized payload.
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
/// Response payload returned by the server for heartbeat submissions.
|
||||
///
|
||||
/// In case of silent rejection, all fields except `status` are omitted.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HeartbeatResponse {
|
||||
/// Attestation status, typically "ok" even on silent failures.
|
||||
pub status: String,
|
||||
/// The next server-issued salt for hash chain progression.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_salt: Option<String>,
|
||||
/// The next expected mutation step index.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_mutation_step: Option<u64>,
|
||||
/// Base64-encoded next mutation program for client gene progression.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub next_mutation_order_b64: Option<String>,
|
||||
}
|
||||
|
||||
/// Client browser fingerprint metadata used for basic sanity checks.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Fingerprint {
|
||||
/// Aspect ratio of the client screen.
|
||||
#[serde(rename = "aspectRatio")]
|
||||
pub aspect_ratio: String,
|
||||
/// Device pixel ratio of the screen.
|
||||
#[serde(rename = "devicePixelRatio")]
|
||||
pub device_pixel_ratio: String,
|
||||
/// Number of logical processor cores available.
|
||||
#[serde(rename = "hardwareConcurrency")]
|
||||
pub hardware_concurrency: u32,
|
||||
}
|
||||
|
||||
/// Wrapper for browser-side entropy collection.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct EntropyData {
|
||||
/// A chronological list of mouse movement events.
|
||||
pub events: Vec<MouseEvent>,
|
||||
}
|
||||
|
||||
/// Information about a single mouse movement interaction.
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct MouseEvent {
|
||||
/// Absolute horizontal coordinate of the cursor.
|
||||
pub x: f64,
|
||||
/// Absolute vertical coordinate of the cursor.
|
||||
pub y: f64,
|
||||
/// Relative timestamp in milliseconds of the event occurrence.
|
||||
#[serde(rename = "t")]
|
||||
pub timestamp_ms: f64,
|
||||
}
|
||||
|
||||
/// The state of the VM stack machine after executing a program.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StackState {
|
||||
/// The elements remaining on the stack.
|
||||
pub stack: Vec<u32>,
|
||||
/// The final instruction pointer location at program completion or termination.
|
||||
pub ip: u16,
|
||||
}
|
||||
|
||||
@@ -88,10 +88,18 @@ impl From<GeneError> for MutationError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes a `MutationOrder` into standard Base64 representation of its bytecode.
|
||||
pub fn encode_order_b64(order: &MutationOrder) -> String {
|
||||
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &order.program)
|
||||
}
|
||||
|
||||
/// Decodes a `MutationOrder` from its Base64 representation.
|
||||
///
|
||||
/// Validates that the decoded program size does not exceed the allowed maximum budget size.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `step` - The step index associated with this mutation order.
|
||||
/// * `b64` - The Base64 string containing the raw bytecode.
|
||||
pub fn decode_order_b64(step: u64, b64: &str) -> Result<MutationOrder, MutationError> {
|
||||
let program = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, b64)
|
||||
.map_err(MutationError::Base64)?;
|
||||
@@ -101,11 +109,27 @@ pub fn decode_order_b64(step: u64, b64: &str) -> Result<MutationOrder, MutationE
|
||||
Ok(MutationOrder { step, program })
|
||||
}
|
||||
|
||||
/// Generates a randomized `MutationOrder` program for a given step and gene size.
|
||||
///
|
||||
/// Uses thread-local random number generator.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `step` - The step index.
|
||||
/// * `gene_size` - The length of the gene byte buffer.
|
||||
pub fn generate_order(step: u64, gene_size: usize) -> MutationOrder {
|
||||
let mut rng = rand::thread_rng();
|
||||
generate_order_with_rng(&mut rng, step, gene_size)
|
||||
}
|
||||
|
||||
/// Generates a randomized `MutationOrder` program using a specific custom RNG.
|
||||
///
|
||||
/// Builds a program containing between 20 and 36 mutation instructions (e.g. loads, point changes,
|
||||
/// insertions, deletions, env modifications) and ensures a minimum number of finalize hash steps are included.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `rng` - The random number generator.
|
||||
/// * `step` - The step index.
|
||||
/// * `gene_size` - The length of the gene byte buffer.
|
||||
pub fn generate_order_with_rng<R: Rng + ?Sized>(
|
||||
rng: &mut R,
|
||||
step: u64,
|
||||
@@ -213,10 +237,12 @@ pub fn generate_order_with_rng<R: Rng + ?Sized>(
|
||||
MutationOrder { step, program }
|
||||
}
|
||||
|
||||
/// Clones the `GeneState` and executes the mutation program for `DEFAULT_MUTATION_ROUNDS`.
|
||||
pub fn apply_program_clone(state: &GeneState, program: &[u8]) -> Result<GeneState, MutationError> {
|
||||
apply_program_clone_with_rounds(state, program, DEFAULT_MUTATION_ROUNDS)
|
||||
}
|
||||
|
||||
/// Clones the `GeneState` and executes the mutation program for a specific number of rounds.
|
||||
pub fn apply_program_clone_with_rounds(
|
||||
state: &GeneState,
|
||||
program: &[u8],
|
||||
@@ -227,6 +253,7 @@ pub fn apply_program_clone_with_rounds(
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
/// Executes the mutation program on the mutable `GeneState` reference for a specific number of rounds.
|
||||
pub fn apply_program_with_rounds(
|
||||
state: &mut GeneState,
|
||||
program: &[u8],
|
||||
@@ -236,11 +263,22 @@ pub fn apply_program_with_rounds(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Executes the mutation program on the mutable `GeneState` reference for `DEFAULT_MUTATION_ROUNDS`.
|
||||
pub fn apply_program(state: &mut GeneState, program: &[u8]) -> Result<(), MutationError> {
|
||||
let _ = execute_program_with_rounds(state, program, DEFAULT_MUTATION_ROUNDS)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Executes the mutation program on the mutable `GeneState` reference for multiple rounds.
|
||||
///
|
||||
/// Implements a soft instruction cost-budget cap check to prevent hostile/inefficient
|
||||
/// programs from lagging the host server thread or client runtime.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state buffer.
|
||||
/// * `program` - The raw bytecode sequence.
|
||||
/// * `rounds` - The requested number of execution rounds.
|
||||
pub fn execute_program_with_rounds(
|
||||
state: &mut GeneState,
|
||||
program: &[u8],
|
||||
@@ -317,6 +355,13 @@ fn estimate_program_cost(program: &[u8]) -> usize {
|
||||
cost.max(1)
|
||||
}
|
||||
|
||||
/// Executes the VM mutation program on the mutable `GeneState` reference.
|
||||
///
|
||||
/// This interprets VM mutation opcodes to modify the gene byte array and environment records.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `state` - The mutable gene state to mutate.
|
||||
/// * `program` - The raw instruction bytecode slice.
|
||||
pub fn execute_program(
|
||||
state: &mut GeneState,
|
||||
program: &[u8],
|
||||
@@ -835,4 +880,25 @@ mod tests {
|
||||
"mutation execution too slow: {elapsed:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vm_instruction_budget_soft_cap() {
|
||||
let state = new_state(8).unwrap();
|
||||
// Construct a program with 130 OP_FINALIZE_GENE_HASH instructions.
|
||||
// HASH has HASH_OPCODE_INSTRUCTION_COST = 16.
|
||||
// Total cost will be 130 * 16 = 2080, which exceeds MAX_MUTATION_INSTRUCTION_BUDGET (2048).
|
||||
let program = vec![OP_FINALIZE_GENE_HASH; 130];
|
||||
let cost = estimate_program_cost(&program);
|
||||
assert!(cost >= 2080);
|
||||
|
||||
// Assert that the max allowed rounds is calculated as 1 since cost > budget.
|
||||
let expected_rounds = std::cmp::max(1, MAX_MUTATION_INSTRUCTION_BUDGET / cost);
|
||||
assert_eq!(expected_rounds, 1);
|
||||
|
||||
// Execute the program with a requested 10 rounds.
|
||||
// The runtime should execute it successfully without panic, while applying the round limitation.
|
||||
let mut test_state = state.clone();
|
||||
let trace = execute_program_with_rounds(&mut test_state, &program, 10).unwrap();
|
||||
assert_eq!(trace.final_ip, program.len());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user