diff --git a/crates/fault/src/providers/alphabet.rs b/crates/fault/src/providers/alphabet.rs index 2a4bbe9..3e4420d 100644 --- a/crates/fault/src/providers/alphabet.rs +++ b/crates/fault/src/providers/alphabet.rs @@ -7,7 +7,7 @@ use crate::{Gindex, Position, TraceProvider, VMStatus}; use alloy_primitives::{keccak256, U256}; use alloy_sol_types::{sol, SolType}; use durin_primitives::Claim; -use std::convert::TryInto; +use std::{convert::TryInto, sync::Arc}; type AlphabetClaimConstruction = sol! { tuple(uint256, uint256) }; @@ -32,8 +32,8 @@ impl AlphabetTraceProvider { } impl TraceProvider<[u8; 1]> for AlphabetTraceProvider { - fn absolute_prestate(&self) -> [u8; 1] { - [self.absolute_prestate] + fn absolute_prestate(&self) -> Arc<[u8; 1]> { + Arc::new([self.absolute_prestate]) } fn absolute_prestate_hash(&self) -> Claim { @@ -43,14 +43,14 @@ impl TraceProvider<[u8; 1]> for AlphabetTraceProvider { prestate_hash } - fn state_at(&self, position: Position) -> anyhow::Result<[u8; 1]> { + fn state_at(&self, position: Position) -> anyhow::Result> { let absolute_prestate = self.absolute_prestate as u64; let trace_index = position.trace_index(self.max_depth); let state = (absolute_prestate + trace_index + 1) .try_into() .unwrap_or(self.absolute_prestate + 2u8.pow(self.max_depth as u32)); - Ok([state]) + Ok(Arc::new([state])) } fn state_hash(&self, position: Position) -> anyhow::Result { @@ -62,6 +62,10 @@ impl TraceProvider<[u8; 1]> for AlphabetTraceProvider { state_hash[0] = VMStatus::Invalid as u8; Ok(state_hash) } + + fn proof_at(&self, position: Position) -> anyhow::Result> { + Ok(Arc::new([])) + } } #[cfg(test)] diff --git a/crates/fault/src/solver.rs b/crates/fault/src/solver.rs index 485f475..51f07af 100644 --- a/crates/fault/src/solver.rs +++ b/crates/fault/src/solver.rs @@ -7,7 +7,7 @@ use crate::{ TraceProvider, }; use durin_primitives::{Claim, DisputeGame, DisputeSolver}; -use std::marker::PhantomData; +use std::{marker::PhantomData, sync::Arc}; /// 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 @@ -22,7 +22,7 @@ where _phantom: PhantomData, } -impl DisputeSolver for FaultDisputeSolver +impl DisputeSolver> for FaultDisputeSolver where T: AsRef<[u8]>, P: TraceProvider, @@ -30,7 +30,7 @@ where fn available_moves( &self, game: &mut FaultDisputeState, - ) -> anyhow::Result> { + ) -> anyhow::Result]>> { // Fetch the local opinion on the root claim. let attacking_root = self.provider.state_hash(Self::ROOT_CLAIM_POSITION)? != game.root_claim(); @@ -79,7 +79,7 @@ where world: &mut FaultDisputeState, claim_index: usize, attacking_root: bool, - ) -> anyhow::Result { + ) -> anyhow::Result> { // Fetch the maximum depth of the game's position tree. let max_depth = world.max_depth; @@ -122,11 +122,37 @@ where // 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 == max_depth - 1 { - Ok(FaultSolverResponse::Step(is_attack)) + if claim_depth == max_depth { + // There is a special case when we are attacking the first leaf claim at the max + // level where we have to provide the absolute prestate. Otherwise, we can derive + // the prestate position based off of `is_attack` and the incorrect claim's + // position. + let (pre_state, proof) = if claim.position.index_at_depth() == 0 && is_attack { + let pre_state = self.provider.absolute_prestate(); + // TODO(clabby): There may be a proof for the absolute prestate in Cannon. + let proof: Arc<[u8]> = Arc::new([]); + + (pre_state, proof) + } else { + // If the move is an attack, the pre-state is left of the attacked claim's + // position. If the move is a defense, the pre-state for the step is at the + // claim's position. + // + // SAFETY: We can subtract 1 here due to the above check - we will never + // underflow the level. + let pre_state_pos = claim.position - is_attack as u128; + + let pre_state = Self::fetch_state_at(&self.provider, pre_state_pos, claim)?; + let proof = Self::fetch_proof_at(&self.provider, pre_state_pos, claim)?; + (pre_state, proof) + }; + + Ok(FaultSolverResponse::Step( + is_attack, + claim_index, + pre_state, + proof, + )) } else { // Fetch the local trace provider's opinion of the state hash at the move's position. let claim_hash = @@ -158,6 +184,32 @@ where })?; Ok(state_hash) } + + #[inline] + fn fetch_state_at( + provider: &P, + position: Position, + observed_claim: &mut ClaimData, + ) -> anyhow::Result> { + let state_at = provider.state_at(position).map_err(|e| { + observed_claim.visited = false; + e + })?; + Ok(state_at) + } + + #[inline] + fn fetch_proof_at( + provider: &P, + position: Position, + observed_claim: &mut ClaimData, + ) -> anyhow::Result> { + let proof_at = provider.proof_at(position).map_err(|e| { + observed_claim.visited = false; + e + })?; + Ok(proof_at) + } } // TODO: prop tests for solving claims. @@ -206,7 +258,7 @@ mod test { ); let moves = solver.available_moves(&mut state).unwrap(); - assert_eq!(&[expected_move], moves.as_slice()); + assert_eq!(&[expected_move], moves.as_ref()); } } @@ -255,7 +307,7 @@ mod test { ); let moves = solver.available_moves(&mut state).unwrap(); - assert_eq!(&[expected_move], moves.as_slice()); + assert_eq!(&[expected_move], moves.as_ref()); } } @@ -310,7 +362,79 @@ mod test { FaultSolverResponse::Move(false, 2, solver.provider.state_hash(10).unwrap()), FaultSolverResponse::Skip(3) ], - moves.as_slice() + moves.as_ref() ); } + + #[test] + fn available_moves_static_step() { + let (solver, root_claim) = mocks(); + let cases = [ + ( + FaultSolverResponse::Step(true, 4, Arc::new([b'a']), Arc::new([])), + true, + ), + ( + FaultSolverResponse::Step(false, 4, Arc::new([b'b']), Arc::new([])), + false, + ), + ]; + + for (expected_response, wrong_leaf) in cases { + let mut state = FaultDisputeState::new( + vec![ + // Invalid root claim - ATTACK + ClaimData { + parent_index: u32::MAX, + visited: true, + value: root_claim, + position: 1, + clock: 0, + }, + // Honest Attack + ClaimData { + parent_index: 0, + visited: true, + value: solver.provider.state_hash(2).unwrap(), + position: 2, + clock: 0, + }, + // Wrong level; Wrong claim - ATTACK + ClaimData { + parent_index: 1, + visited: true, + value: root_claim, + position: 4, + clock: 0, + }, + // Honest Attack + ClaimData { + parent_index: 2, + visited: true, + value: solver.provider.state_hash(8).unwrap(), + position: 8, + clock: 0, + }, + // Wrong level; Wrong claim - ATTACK STEP + ClaimData { + parent_index: 3, + visited: false, + value: if wrong_leaf { + root_claim + } else { + solver.provider.state_hash(16).unwrap() + }, + position: 16, + clock: 0, + }, + ], + root_claim, + GameStatus::InProgress, + 4, + ); + + let moves = solver.available_moves(&mut state).unwrap(); + assert_eq!(&[expected_response], moves.as_ref()); + } + } } diff --git a/crates/fault/src/traits.rs b/crates/fault/src/traits.rs index ab0fa09..f27b237 100644 --- a/crates/fault/src/traits.rs +++ b/crates/fault/src/traits.rs @@ -2,6 +2,7 @@ use crate::{state::ClaimData, Position}; use durin_primitives::{Claim, DisputeGame}; +use std::sync::Arc; /// A [FaultDisputeGame] is a [DisputeGame] that is played over a FaultVM backend. This /// trait extends the [DisputeGame] trait with functionality that is specific to the @@ -18,16 +19,19 @@ pub trait FaultDisputeGame: DisputeGame { /// [Position] within a [FaultDisputeGame]. pub trait TraceProvider> { /// Returns the raw absolute prestate (in bytes). - fn absolute_prestate(&self) -> P; + fn absolute_prestate(&self) -> Arc

; /// Returns the absolute prestate hash. fn absolute_prestate_hash(&self) -> Claim; /// Returns the raw state (in bytes) at the given position. - fn state_at(&self, position: Position) -> anyhow::Result

; + fn state_at(&self, position: Position) -> anyhow::Result>; /// Returns the state hash at the given position. fn state_hash(&self, position: Position) -> anyhow::Result; + + /// Returns the raw proof for the commitment at the given position. + fn proof_at(&self, position: Position) -> anyhow::Result>; } /// The [Gindex] trait defines the interface of a generalized index within a binary tree. diff --git a/crates/fault/src/types.rs b/crates/fault/src/types.rs index 0a0f4fb..292fe35 100644 --- a/crates/fault/src/types.rs +++ b/crates/fault/src/types.rs @@ -1,5 +1,7 @@ //! The position module holds the types specific to the [crate::FaultDisputeGame] solver. +use std::sync::Arc; + use crate::ChessClock; use crate::Gindex; use durin_primitives::Claim; @@ -10,14 +12,14 @@ 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 { +pub enum FaultSolverResponse> { /// A response indicating that the proper move is to attack or defend the given claim. Move(bool, usize, Claim), /// A response indicating that the proper move is to skip the given claim. Skip(usize), /// A response indicating that the proper move is to perform a VM step against /// the given claim. - Step(bool), + Step(bool, usize, Arc, Arc<[u8]>), } /// The [VMStatus] enum describes the status of a VM at a given position. diff --git a/crates/primitives/src/traits.rs b/crates/primitives/src/traits.rs index 1f8aadd..c294d78 100644 --- a/crates/primitives/src/traits.rs +++ b/crates/primitives/src/traits.rs @@ -1,6 +1,7 @@ //! The traits module contains traits used throughout the library. use crate::{dispute_game::Claim, GameStatus}; +use std::sync::Arc; /// The [DisputeGame] trait is the highest level trait in the library, describing /// the state of a simple primitive dispute. It has several key properties: @@ -38,5 +39,5 @@ pub trait DisputeSolver { /// Returns any available responses computed by the solver provided a [DisputeGame]. /// The consumer of the response is responsible for dispatching the action associated /// with the responses. - fn available_moves(&self, game: &mut DG) -> anyhow::Result>; + fn available_moves(&self, game: &mut DG) -> anyhow::Result>; }