Skip to content

Commit

Permalink
✨ Basic FaultDisputeGame state solver
Browse files Browse the repository at this point in the history
🧹
  • Loading branch information
clabby committed Sep 9, 2023
1 parent 4abb867 commit f3c3a30
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 11 deletions.
11 changes: 11 additions & 0 deletions crates/fault/src/providers/alphabet.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! This module contains the implementation of the [crate::TraceProvider] trait for the
//! mock Alphabet VM.

#![allow(dead_code, unused_variables)]

use crate::{Gindex, Position, TraceProvider, VMStatus};
use alloy_primitives::{keccak256, U256};
use alloy_sol_types::{sol, SolType};
Expand All @@ -20,6 +22,15 @@ pub struct AlphabetTraceProvider {
pub max_depth: u8,
}

impl AlphabetTraceProvider {
pub fn new(absolute_prestate: u8, max_depth: u8) -> Self {
Self {
absolute_prestate,
max_depth,
}
}
}

impl TraceProvider<[u8; 1]> for AlphabetTraceProvider {
fn absolute_prestate(&self) -> [u8; 1] {
[self.absolute_prestate]
Expand Down
137 changes: 126 additions & 11 deletions crates/fault/src/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ use crate::{
use durin_primitives::{DisputeGame, DisputeSolver};
use std::marker::PhantomData;

#[cfg(test)]
use proptest::prelude::*;

/// A [FaultDisputeSolver] is a [DisputeSolver] that is played over a fault proof VM backend. The
/// solver is responsible for honestly responding to any given [ClaimData] in a given
/// [FaultDisputeState]. It uses a [TraceProvider] to fetch the absolute prestate of the VM as
Expand Down Expand Up @@ -41,8 +38,7 @@ where
game.state()
.iter()
.filter(|c| !c.visited)
.enumerate()
.map(|(i, c)| self.solve_claim(game, c, attacking_root))
.map(|c| self.solve_claim(game, c, attacking_root))
.collect()
}
}
Expand All @@ -54,27 +50,146 @@ where
{
const ROOT_CLAIM_POSITION: Position = 1;

fn new(provider: P) -> Self {
Self {
provider,
_phantom: PhantomData,
}
}

#[inline]
fn solve_claim(
&self,
game: &FaultDisputeState,
claim: &ClaimData,
attacking_root: bool,
) -> anyhow::Result<FaultSolverResponse> {
let claim_depth = claim.position.depth();

// In the case that the claim's opinion about the root claim is the same as the local
// opinion, we can skip the claim. It does not matter if this claim is valid or not
// because it supports the local opinion of the root claim. Countering it would put the
// solver in an opposing position to its final objective.
if claim_depth % 2 == attacking_root as u8 {
return Ok(FaultSolverResponse::Skip);
}

// Fetch the local trace provider's opinion of the state hash at the claim's position
// as well as whether or not the claim agrees with the local opinion on the root claim.
let self_state_hash = self.provider.state_hash(claim.position)?;
let agree_with_level = claim.position.depth() % 2 == attacking_root as u8;

todo!()
let move_direction = if self_state_hash == claim.value {
// If the local opinion of the state hash at the claim's position is the same as the
// claim's opinion about the state, then the proper move is to defend the claim.
FaultSolverResponse::Defend
} else {
// If the local opinion of the state hash at the claim's position is different than
// the claim's opinion about the state, then the proper move is to attack the claim.
FaultSolverResponse::Attack
};

// If the next move will be at the max depth of the game, then the proper move is to
// perform a VM step against the claim. Otherwise, move in the appropriate direction.
//
// TODO(clabby): Return the data necessary for the inputs to the contract calls in the
// `FaultSolverResponse` variants.
if claim_depth == game.max_depth - 1 {
Ok(FaultSolverResponse::Step(Box::new(move_direction)))
} else {
Ok(move_direction)
}
}
}

// TODO: prop tests for solving claims.
#[cfg(test)]
proptest! {
mod test {
use super::*;
use crate::providers::AlphabetTraceProvider;
use alloy_primitives::hex;
use durin_primitives::{Claim, GameStatus};

fn mocks() -> (FaultDisputeSolver<[u8; 1], AlphabetTraceProvider>, Claim) {
let provider = AlphabetTraceProvider::new(b'a', 4);
let solver = FaultDisputeSolver::new(provider);
let root_claim = Claim::from_slice(&hex!(
"c0ffee00c0de0000000000000000000000000000000000000000000000000000"
));
(solver, root_claim)
}

#[test]
fn available_moves_root_only() {
let (solver, root_claim) = mocks();
let moves = [
(
solver.provider.state_hash(1).unwrap(),
FaultSolverResponse::Skip,
),
(root_claim, FaultSolverResponse::Attack),
];

for (claim, expected_move) in moves {
let state = FaultDisputeState::new(
vec![ClaimData {
parent_index: u32::MAX,
visited: false,
value: claim,
position: 1,
clock: 0,
}],
claim,
GameStatus::InProgress,
4,
);

let moves = solver.available_moves(&state).unwrap();
assert_eq!(&[expected_move], moves.as_slice());
}
}

#[test]
fn test_solve(s in any::<u8>()) {
assert!(s <= u8::MAX);
fn available_moves_static() {
let (solver, root_claim) = mocks();
let moves = [
(
solver.provider.state_hash(4).unwrap(),
FaultSolverResponse::Defend,
),
(root_claim, FaultSolverResponse::Attack),
];

for (claim, expected_move) in moves {
let state = FaultDisputeState::new(
vec![
ClaimData {
parent_index: u32::MAX,
visited: true,
value: root_claim,
position: 1,
clock: 0,
},
ClaimData {
parent_index: 0,
visited: true,
value: solver.provider.state_hash(2).unwrap(),
position: 2,
clock: 0,
},
ClaimData {
parent_index: 1,
visited: false,
value: claim,
position: 4,
clock: 0,
},
],
root_claim,
GameStatus::InProgress,
4,
);

let moves = solver.available_moves(&state).unwrap();
assert_eq!(&[expected_move], moves.as_slice());
}
}
}
18 changes: 18 additions & 0 deletions crates/fault/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ pub struct FaultDisputeState {
root_claim: Claim,
/// The status of the dispute game.
status: GameStatus,
/// The max depth of the position tree.
pub max_depth: u8,
}

impl FaultDisputeState {
pub fn new(
state: Vec<ClaimData>,
root_claim: Claim,
status: GameStatus,
max_depth: u8,
) -> Self {
Self {
state,
root_claim,
status,
max_depth,
}
}
}

impl DisputeGame for FaultDisputeState {
Expand Down
5 changes: 5 additions & 0 deletions crates/fault/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,25 @@ pub type Clock = u128;

/// The [FaultSolverResponse] enum describes the response that a solver should
/// return when asked to make a move.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FaultSolverResponse {
/// A response indicating that the proper move is to attack the given claim.
Attack,
/// A response indicating that the proper move is to defend the given claim.
Defend,
/// A response indicating that the proper move is to skip the given claim.
Skip,
/// A response indicating that the proper move is to perform a VM step against
/// the given claim.
Step(Box<FaultSolverResponse>),
}

/// The [VMStatus] enum describes the status of a VM at a given position.
/// - [VMStatus::Valid]: The VM is exited with a valid status.
/// - [VMStatus::Invalid]: The VM is exited with an invalid status.
/// - [VMStatus::Panic]: The VM is exited with a panic status.
/// - [VMStatus::Unfinished]: The VM is not yet exited.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VMStatus {
Valid = 0,
Invalid = 1,
Expand Down

0 comments on commit f3c3a30

Please sign in to comment.