diff --git a/offchain/core/src/arena/arena.rs b/offchain/core/src/arena/arena.rs index 494dffc4..9e41006e 100644 --- a/offchain/core/src/arena/arena.rs +++ b/offchain/core/src/arena/arena.rs @@ -73,6 +73,12 @@ pub trait Arena: Send + Sync { proofs: MachineProof, ) -> Result<(), Box>; + async fn eliminate_match( + &self, + tournament: Address, + match_id: MatchID, + ) -> Result<(), Box>; + async fn created_tournament( &self, tournament: Address, @@ -173,6 +179,30 @@ pub struct ClockState { pub start_instant: u64, pub block_time: U256, } + +impl ClockState { + pub fn has_time(&self) -> bool { + if self.start_instant == 0 { + true + } else { + self.deadline() > self.block_time.as_u64() + } + } + + pub fn time_since_timeout(&self) -> u64 { + if self.start_instant == 0 { + 0 + } else { + self.block_time.as_u64() - self.deadline() + } + } + + // deadline of clock if it's ticking + fn deadline(&self) -> u64 { + self.start_instant + self.allowance + } +} + impl std::fmt::Display for ClockState { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if self.start_instant == 0 { diff --git a/offchain/core/src/arena/ethers_arena.rs b/offchain/core/src/arena/ethers_arena.rs index 5e83cbc2..b7799c37 100644 --- a/offchain/core/src/arena/ethers_arena.rs +++ b/offchain/core/src/arena/ethers_arena.rs @@ -27,6 +27,7 @@ use crate::{ merkle::{Digest, MerkleProof}, }; +#[derive(Clone)] /// The [EthersArena] struct implements the [Arena] trait for an ethereum node. pub struct EthersArena { config: ArenaConfig, @@ -116,12 +117,6 @@ impl EthersArena { .await?; Ok(contract.address()) } - - pub fn clone_with_new_key(&self, new_key: String) -> Result> { - let mut new_arena_config = self.config.clone(); - new_arena_config.web3_private_key = new_key; - Self::new(new_arena_config, Some(self.tournament_factory)) - } } #[async_trait] @@ -305,6 +300,24 @@ impl Arena for EthersArena { Ok(()) } + async fn eliminate_match( + &self, + tournament: Address, + match_id: MatchID, + ) -> Result<(), Box> { + let tournament = tournament::Tournament::new(tournament, self.client.clone()); + let match_id = tournament::Id { + commitment_one: match_id.commitment_one.into(), + commitment_two: match_id.commitment_two.into(), + }; + tournament + .eliminate_match_by_timeout(match_id) + .send() + .await? + .await?; + Ok(()) + } + async fn created_tournament( &self, tournament_address: Address, diff --git a/offchain/core/src/lib.rs b/offchain/core/src/lib.rs index 7f48e6b6..8db4c52d 100644 --- a/offchain/core/src/lib.rs +++ b/offchain/core/src/lib.rs @@ -5,5 +5,5 @@ pub mod arena; pub mod contract; pub mod machine; pub mod merkle; -pub mod player; +pub mod strategy; pub mod utils; diff --git a/offchain/core/src/strategy/gc.rs b/offchain/core/src/strategy/gc.rs new file mode 100644 index 00000000..82021cb8 --- /dev/null +++ b/offchain/core/src/strategy/gc.rs @@ -0,0 +1,93 @@ +use std::{collections::HashMap, error::Error}; + +use ::log::info; +use async_recursion::async_recursion; +use ethers::types::Address; + +use crate::arena::{Arena, MatchState, TournamentState}; + +#[derive(Debug)] +pub enum PlayerTournamentResult { + TournamentWon, + TournamentLost, +} + +pub struct GarbageCollector { + arena: A, + root_tournamet: Address, +} + +impl GarbageCollector { + pub fn new(arena: A, root_tournamet: Address) -> Self { + Self { + arena, + root_tournamet, + } + } + + pub async fn react(&mut self) -> Result<(), Box> { + let tournament_states = self.arena.fetch_from_root(self.root_tournamet).await?; + self.react_tournament(self.root_tournamet, tournament_states) + .await + } + + #[async_recursion] + async fn react_tournament( + &mut self, + tournament_address: Address, + tournament_states: HashMap, + ) -> Result<(), Box> { + info!("Enter tournament at address: {}", tournament_address); + let tournament_state = tournament_states + .get(&tournament_address) + .expect("tournament state not found"); + + for m in tournament_state.matches.clone() { + self.react_match(&m, tournament_states.clone()).await?; + + let status_1 = tournament_state + .commitment_states + .get(&m.id.commitment_one) + .expect("status of commitment 1 not found"); + let status_2 = tournament_state + .commitment_states + .get(&m.id.commitment_two) + .expect("status of commitment 2 not found"); + if (!status_1.clock.has_time() + && (status_1.clock.time_since_timeout() > status_2.clock.allowance)) + || (!status_2.clock.has_time() + && (status_2.clock.time_since_timeout() > status_1.clock.allowance)) + { + info!( + "eliminate match for commitment {} and {} at tournament {} of level {}", + m.id.commitment_one, + m.id.commitment_two, + tournament_address, + tournament_state.level + ); + + self.arena + .eliminate_match(tournament_address, m.id) + .await + .expect("fail to eliminate match"); + } + } + Ok(()) + } + + #[async_recursion] + async fn react_match( + &mut self, + match_state: &MatchState, + tournament_states: HashMap, + ) -> Result<(), Box> { + info!("Enter match at HEIGHT: {}", match_state.current_height); + if let Some(inner_tournament) = match_state.inner_tournament { + return self + .react_tournament(inner_tournament, tournament_states) + .await; + } + + Ok(()) + } +} diff --git a/offchain/core/src/strategy/mod.rs b/offchain/core/src/strategy/mod.rs new file mode 100644 index 00000000..74f5adec --- /dev/null +++ b/offchain/core/src/strategy/mod.rs @@ -0,0 +1,2 @@ +pub mod gc; +pub mod player; diff --git a/offchain/core/src/player.rs b/offchain/core/src/strategy/player.rs similarity index 99% rename from offchain/core/src/player.rs rename to offchain/core/src/strategy/player.rs index ff7f0eba..b47b0355 100644 --- a/offchain/core/src/player.rs +++ b/offchain/core/src/strategy/player.rs @@ -32,7 +32,7 @@ impl Player { commitment_builder: CachingMachineCommitmentBuilder, root_tournamet: Address, ) -> Self { - Player { + Self { arena, machine_factory, machine_path, diff --git a/offchain/dave-compute/src/main.rs b/offchain/dave-compute/src/main.rs index a43c3730..3090e5b3 100644 --- a/offchain/dave-compute/src/main.rs +++ b/offchain/dave-compute/src/main.rs @@ -1,6 +1,6 @@ use cartesi_compute_core::arena::{ArenaConfig, ContractArtifactsConfig, EthersArena}; use cartesi_compute_core::machine::{CachingMachineCommitmentBuilder, MachineFactory}; -use cartesi_compute_core::player::Player; +use cartesi_compute_core::strategy::{gc::GarbageCollector, player::Player}; use ethers::types::Address; use log::info; use std::str::FromStr; @@ -46,22 +46,27 @@ async fn main() -> Result<(), Box> { let player1_machine_factory = MachineFactory::new(String::from(machine_rpc_host), machine_rpc_port).await?; + let root_tournament = Address::from_str("0xcafac3dd18ac6c6e92c921884f9e4176737c052c")?; let mut player1 = Player::new( - player1_arena, + player1_arena.clone(), player1_machine_factory.clone(), simple_linux_program.clone(), CachingMachineCommitmentBuilder::new(player1_machine_factory, simple_linux_program), - Address::from_str("0xcafac3dd18ac6c6e92c921884f9e4176737c052c")?, + root_tournament.clone(), ); + let mut player1_gc = GarbageCollector::new(player1_arena, root_tournament); + loop { let res = player1.react().await?; if let Some(r) = res { info!("Tournament finished, {:?}", r); break; } - // player2.react().await?; + tokio::time::sleep(Duration::from_secs(1)).await; + + player1_gc.react().await?; tokio::time::sleep(Duration::from_secs(1)).await; }