From b215787503cf5e3af214d07745d58e10f5b89ad7 Mon Sep 17 00:00:00 2001 From: Stephen Chen <20940639+stephenctw@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:19:10 +0800 Subject: [PATCH] feat(offchain): split reader and sender, use different design pattern for `react` --- offchain/core/src/arena/arena.rs | 572 +-------------------------- offchain/core/src/arena/mod.rs | 14 +- offchain/core/src/arena/reader.rs | 325 +++++++++++++++ offchain/core/src/arena/sender.rs | 285 +++++++++++++ offchain/core/src/strategy/gc.rs | 30 +- offchain/core/src/strategy/player.rs | 57 +-- offchain/dave-compute/src/main.rs | 16 +- 7 files changed, 682 insertions(+), 617 deletions(-) create mode 100644 offchain/core/src/arena/reader.rs create mode 100644 offchain/core/src/arena/sender.rs diff --git a/offchain/core/src/arena/arena.rs b/offchain/core/src/arena/arena.rs index 2b6d6f4e..3e9a9629 100644 --- a/offchain/core/src/arena/arena.rs +++ b/offchain/core/src/arena/arena.rs @@ -1,575 +1,11 @@ -//! This module defines the struct [Reader] that is responsible for the reading the states -//! of tournaments; and the struct [Sender] that is responsible for the sending transactions -//! to tournaments +//! This module defines the structs that are used for the interacting to tournaments -use std::{collections::HashMap, error::Error, str::FromStr, sync::Arc, time::Duration}; - -use async_recursion::async_recursion; -use async_trait::async_trait; - -use ethers::{ - middleware::SignerMiddleware, - providers::{Http, Middleware, Provider}, - signers::{LocalWallet, Signer}, - types::{Address, BlockNumber::Latest, Bytes, ValueOrArray::Value, H256, U256}, -}; - -use crate::{ - arena::config::ArenaConfig, - contract::{ - leaf_tournament, non_leaf_tournament, non_root_tournament, root_tournament, tournament, - }, - machine::{constants, MachineProof}, - merkle::{Digest, MerkleProof}, -}; - -#[derive(Clone)] -pub struct Arena { - client: Arc, LocalWallet>>, -} +use crate::merkle::Digest; +use ethers::types::{Address, U256}; +use std::collections::HashMap; pub type TournamentStateMap = HashMap; -impl Arena { - pub fn new(config: ArenaConfig) -> Result> { - let provider = Provider::::try_from(config.web3_rpc_url.clone())? - .interval(Duration::from_millis(10u64)); - let wallet = LocalWallet::from_str(config.web3_private_key.as_str())?; - let client = Arc::new(SignerMiddleware::new( - provider, - wallet.with_chain_id(config.web3_chain_id), - )); - - Ok(Self { client }) - } - - async fn created_tournament( - &self, - tournament_address: Address, - match_id: MatchID, - ) -> Result, Box> { - let tournament = - non_leaf_tournament::NonLeafTournament::new(tournament_address, self.client.clone()); - let events = tournament - .new_inner_tournament_filter() - .address(Value(tournament_address)) - .topic1(H256::from_slice(match_id.hash().slice())) - .from_block(0) - .to_block(Latest) - .query() - .await?; - if let Some(event) = events.last() { - Ok(Some(TournamentCreatedEvent { - parent_match_id_hash: match_id.hash(), - new_tournament_address: event.1, - })) - } else { - Ok(None) - } - } - - async fn created_matches( - &self, - tournament_address: Address, - ) -> Result, Box> { - let tournament = tournament::Tournament::new(tournament_address, self.client.clone()); - let events: Vec = tournament - .match_created_filter() - .address(Value(tournament_address)) - .from_block(0) - .to_block(Latest) - .query() - .await? - .iter() - .map(|event| MatchCreatedEvent { - id: MatchID { - commitment_one: event.one.into(), - commitment_two: event.two.into(), - }, - left_hash: event.left_of_two.into(), - }) - .collect(); - Ok(events) - } - - async fn joined_commitments( - &self, - tournament_address: Address, - ) -> Result, Box> { - let tournament = tournament::Tournament::new(tournament_address, self.client.clone()); - let events = tournament - .commitment_joined_filter() - .address(Value(tournament_address)) - .from_block(0) - .to_block(Latest) - .query() - .await? - .iter() - .map(|c| CommitmentJoinedEvent { - root: Digest::from(c.root), - }) - .collect(); - Ok(events) - } - - async fn get_commitment( - &self, - tournament: Address, - commitment_hash: Digest, - ) -> Result> { - let tournament = tournament::Tournament::new(tournament, self.client.clone()); - let (clock_state, hash) = tournament - .get_commitment(commitment_hash.into()) - .call() - .await?; - let block_time = self - .client - .get_block(Latest) - .await? - .expect("cannot get last block") - .timestamp; - let clock_state = ClockState { - allowance: clock_state.allowance, - start_instant: clock_state.start_instant, - block_time, - }; - Ok(CommitmentState { - clock: clock_state, - final_state: Digest::from(hash), - latest_match: None, - }) - } - - pub async fn fetch_from_root( - &self, - root_tournament: Address, - ) -> Result> { - self.fetch_tournament(TournamentState::new_root(root_tournament), HashMap::new()) - .await - } - - #[async_recursion] - async fn fetch_tournament( - &self, - tournament_state: TournamentState, - states: TournamentStateMap, - ) -> Result> { - let tournament = tournament::Tournament::new(tournament_state.address, self.client.clone()); - let mut state = tournament_state.clone(); - - ( - state.max_level, - state.level, - state.log2_stride, - state.log2_stride_count, - ) = tournament.tournament_level_constants().await?; - - assert!(state.level < state.max_level, "level out of bounds"); - - let created_matches = self.created_matches(tournament_state.address).await?; - let commitments_joined = self.joined_commitments(tournament_state.address).await?; - - let mut commitment_states = HashMap::new(); - for commitment in commitments_joined { - let commitment_state = self - .get_commitment(tournament_state.address, commitment.root) - .await?; - commitment_states.insert(commitment.root, commitment_state); - } - - let mut matches = vec![]; - let mut new_states = states.clone(); - for match_event in created_matches { - let match_id = match_event.id; - let m = tournament.get_match(match_id.hash().into()).call().await?; - - let running_leaf_position = m.running_leaf_position.as_u64(); - let base = tournament_state.base_big_cycle; - let step = 1 << state.log2_stride; - let leaf_cycle = base + (step * running_leaf_position); - let base_big_cycle = leaf_cycle >> constants::LOG2_UARCH_SPAN; - let prev_states = new_states.clone(); - let match_state; - - // if !Digest::from(m.other_parent).is_zeroed() { - (match_state, new_states) = self - .fetch_match( - MatchState { - id: match_id, - other_parent: m.other_parent.into(), - left_node: m.left_node.into(), - right_node: m.right_node.into(), - running_leaf_position, - current_height: m.current_height, - tournament_address: tournament_state.address, - level: m.level, - leaf_cycle, - base_big_cycle, - inner_tournament: None, - }, - prev_states, - tournament_state.level, - ) - .await?; - - commitment_states - .get_mut(&match_id.commitment_one) - .expect("cannot find commitment one state") - .latest_match = Some(matches.len()); - commitment_states - .get_mut(&match_id.commitment_two) - .expect("cannot find commitment two state") - .latest_match = Some(matches.len()); - matches.push(match_state); - } - // } - - let winner = match tournament_state.parent { - Some(_) => self.tournament_winner(tournament_state.address).await?, - None => { - self.root_tournament_winner(tournament_state.address) - .await? - } - }; - - state.winner = winner; - state.matches = matches; - state.commitment_states = commitment_states; - - new_states.insert(tournament_state.address, state); - - Ok(new_states) - } - - #[async_recursion] - async fn fetch_match( - &self, - match_state: MatchState, - states: TournamentStateMap, - tournament_level: u64, - ) -> Result<(MatchState, TournamentStateMap), Box> { - let mut state = match_state.clone(); - let created_tournament = self - .created_tournament(match_state.tournament_address, match_state.id) - .await?; - if let Some(inner) = created_tournament { - let inner_tournament = TournamentState::new_inner( - inner.new_tournament_address, - tournament_level, - match_state.base_big_cycle, - match_state.tournament_address, - ); - let new_states = self.fetch_tournament(inner_tournament, states).await?; - state.inner_tournament = Some(inner.new_tournament_address); - - return Ok((state, new_states)); - } - - Ok((state, states)) - } - - async fn root_tournament_winner( - &self, - root_tournament: Address, - ) -> Result, Box> { - let root_tournament = - root_tournament::RootTournament::new(root_tournament, self.client.clone()); - let (finished, commitment, state) = root_tournament.arbitration_result().call().await?; - if finished { - Ok(Some(TournamentWinner::Root( - Digest::from(commitment), - Digest::from(state), - ))) - } else { - Ok(None) - } - } - - async fn tournament_winner( - &self, - tournament: Address, - ) -> Result, Box> { - let tournament = - non_root_tournament::NonRootTournament::new(tournament, self.client.clone()); - let (finished, parent_commitment, dangling_commitment) = - tournament.inner_tournament_winner().call().await?; - if finished { - Ok(Some(TournamentWinner::Inner( - Digest::from(parent_commitment), - Digest::from(dangling_commitment), - ))) - } else { - Ok(None) - } - } -} - -/// The [ArenaSender] trait defines the interface for the creation and management of tournaments. -#[async_trait] -pub trait ArenaSender: Send + Sync { - async fn join_tournament( - &self, - tournament: Address, - final_state: Digest, - proof: MerkleProof, - left_child: Digest, - right_child: Digest, - ) -> Result<(), Box>; - - async fn advance_match( - &self, - tournament: Address, - match_id: MatchID, - left_node: Digest, - right_node: Digest, - new_left_node: Digest, - new_right_node: Digest, - ) -> Result<(), Box>; - - async fn seal_inner_match( - &self, - tournament: Address, - match_id: MatchID, - left_leaf: Digest, - right_leaf: Digest, - initial_hash: Digest, - initial_hash_proof: MerkleProof, - ) -> Result<(), Box>; - - async fn win_inner_match( - &self, - tournament: Address, - child_tournament: Address, - left_node: Digest, - right_node: Digest, - ) -> Result<(), Box>; - - async fn seal_leaf_match( - &self, - tournament: Address, - match_id: MatchID, - left_leaf: Digest, - right_leaf: Digest, - initial_hash: Digest, - initial_hash_proof: MerkleProof, - ) -> Result<(), Box>; - - async fn win_leaf_match( - &self, - tournament: Address, - match_id: MatchID, - left_node: Digest, - right_node: Digest, - proofs: MachineProof, - ) -> Result<(), Box>; - - async fn eliminate_match( - &self, - tournament: Address, - match_id: MatchID, - ) -> Result<(), Box>; -} - -#[async_trait] -impl ArenaSender for Arena { - async fn join_tournament( - &self, - tournament: Address, - final_state: Digest, - proof: MerkleProof, - left_child: Digest, - right_child: Digest, - ) -> Result<(), Box> { - let tournament = tournament::Tournament::new(tournament, self.client.clone()); - let proof = proof.iter().map(|h| -> [u8; 32] { (*h).into() }).collect(); - tournament - .join_tournament( - final_state.into(), - proof, - left_child.into(), - right_child.into(), - ) - .send() - .await? - .await?; - Ok(()) - } - - async fn advance_match( - &self, - tournament: Address, - match_id: MatchID, - left_node: Digest, - right_node: Digest, - new_left_node: Digest, - new_right_node: Digest, - ) -> 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 - .advance_match( - match_id, - left_node.into(), - right_node.into(), - new_left_node.into(), - new_right_node.into(), - ) - .send() - .await? - .await?; - Ok(()) - } - - async fn seal_inner_match( - &self, - tournament: Address, - match_id: MatchID, - left_leaf: Digest, - right_leaf: Digest, - initial_hash: Digest, - initial_hash_proof: MerkleProof, - ) -> Result<(), Box> { - let tournament = - non_leaf_tournament::NonLeafTournament::new(tournament, self.client.clone()); - let match_id = non_leaf_tournament::Id { - commitment_one: match_id.commitment_one.into(), - commitment_two: match_id.commitment_two.into(), - }; - let initial_hash_proof = initial_hash_proof - .iter() - .map(|h| -> [u8; 32] { (*h).into() }) - .collect(); - tournament - .seal_inner_match_and_create_inner_tournament( - match_id, - left_leaf.into(), - right_leaf.into(), - initial_hash.into(), - initial_hash_proof, - ) - .send() - .await? - .await?; - Ok(()) - } - - async fn win_inner_match( - &self, - tournament: Address, - child_tournament: Address, - left_node: Digest, - right_node: Digest, - ) -> Result<(), Box> { - let tournament = - non_leaf_tournament::NonLeafTournament::new(tournament, self.client.clone()); - tournament - .win_inner_match(child_tournament, left_node.into(), right_node.into()) - .send() - .await? - .await?; - Ok(()) - } - - async fn seal_leaf_match( - &self, - tournament: Address, - match_id: MatchID, - left_leaf: Digest, - right_leaf: Digest, - initial_hash: Digest, - initial_hash_proof: MerkleProof, - ) -> Result<(), Box> { - let tournament = leaf_tournament::LeafTournament::new(tournament, self.client.clone()); - let match_id = leaf_tournament::Id { - commitment_one: match_id.commitment_one.into(), - commitment_two: match_id.commitment_two.into(), - }; - let initial_hash_proof = initial_hash_proof - .iter() - .map(|h| -> [u8; 32] { (*h).into() }) - .collect(); - tournament - .seal_leaf_match( - match_id, - left_leaf.into(), - right_leaf.into(), - initial_hash.into(), - initial_hash_proof, - ) - .send() - .await? - .await?; - Ok(()) - } - - async fn win_leaf_match( - &self, - tournament: Address, - match_id: MatchID, - left_node: Digest, - right_node: Digest, - proofs: MachineProof, - ) -> Result<(), Box> { - let tournament = leaf_tournament::LeafTournament::new(tournament, self.client.clone()); - let match_id = leaf_tournament::Id { - commitment_one: match_id.commitment_one.into(), - commitment_two: match_id.commitment_two.into(), - }; - tournament - .win_leaf_match( - match_id, - left_node.into(), - right_node.into(), - Bytes::from(proofs), - ) - .send() - .await? - .await?; - 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(()) - } -} - -/// This struct is used to communicate the creation of a new tournament. -#[derive(Clone, Copy)] -pub struct TournamentCreatedEvent { - pub parent_match_id_hash: Digest, - pub new_tournament_address: Address, -} - -/// This struct is used to communicate the enrollment of a new commitment. -#[derive(Clone, Copy)] -pub struct CommitmentJoinedEvent { - pub root: Digest, -} - -/// This struct is used to communicate the creation of a new match. -#[derive(Clone, Copy)] -pub struct MatchCreatedEvent { - pub id: MatchID, - pub left_hash: Digest, -} - /// Struct used to identify a match. #[derive(Clone, Copy)] pub struct MatchID { diff --git a/offchain/core/src/arena/mod.rs b/offchain/core/src/arena/mod.rs index edd38582..86bb68ea 100644 --- a/offchain/core/src/arena/mod.rs +++ b/offchain/core/src/arena/mod.rs @@ -1,9 +1,15 @@ -//! This module defines the struct [Reader] that is responsible for the reading the states -//! of tournaments; and the struct [Sender] that is responsible for the sending transactions +//! This module defines the struct [StateReader] that is responsible for the reading the states +//! of tournaments; and the struct [EthArenaSender] that is responsible for the sending transactions //! to tournaments +mod arena; +pub use arena::*; + mod config; pub use config::*; -mod arena; -pub use arena::*; +mod reader; +pub use reader::*; + +mod sender; +pub use sender::*; diff --git a/offchain/core/src/arena/reader.rs b/offchain/core/src/arena/reader.rs new file mode 100644 index 00000000..5b6435eb --- /dev/null +++ b/offchain/core/src/arena/reader.rs @@ -0,0 +1,325 @@ +//! This module defines the struct [StateReader] that is responsible for the reading the states +//! of tournaments + +use std::{collections::HashMap, error::Error, str::FromStr, sync::Arc, time::Duration}; + +use async_recursion::async_recursion; + +use ethers::{ + middleware::SignerMiddleware, + providers::{Http, Middleware, Provider}, + signers::{LocalWallet, Signer}, + types::{Address, BlockNumber::Latest, ValueOrArray::Value, H256}, +}; + +use crate::{ + arena::{ + arena::{ + ClockState, CommitmentState, MatchID, MatchState, TournamentState, TournamentStateMap, + TournamentWinner, + }, + config::ArenaConfig, + }, + contract::{non_leaf_tournament, non_root_tournament, root_tournament, tournament}, + machine::constants, + merkle::Digest, +}; + +#[derive(Clone)] +pub struct StateReader { + client: Arc, LocalWallet>>, +} + +impl StateReader { + pub fn new(config: ArenaConfig) -> Result> { + let provider = Provider::::try_from(config.web3_rpc_url.clone())? + .interval(Duration::from_millis(10u64)); + let wallet = LocalWallet::from_str(config.web3_private_key.as_str())?; + let client = Arc::new(SignerMiddleware::new( + provider, + wallet.with_chain_id(config.web3_chain_id), + )); + + Ok(Self { client }) + } + + async fn created_tournament( + &self, + tournament_address: Address, + match_id: MatchID, + ) -> Result, Box> { + let tournament = + non_leaf_tournament::NonLeafTournament::new(tournament_address, self.client.clone()); + let events = tournament + .new_inner_tournament_filter() + .address(Value(tournament_address)) + .topic1(H256::from_slice(match_id.hash().slice())) + .from_block(0) + .to_block(Latest) + .query() + .await?; + if let Some(event) = events.last() { + Ok(Some(TournamentCreatedEvent { + parent_match_id_hash: match_id.hash(), + new_tournament_address: event.1, + })) + } else { + Ok(None) + } + } + + async fn created_matches( + &self, + tournament_address: Address, + ) -> Result, Box> { + let tournament = tournament::Tournament::new(tournament_address, self.client.clone()); + let events: Vec = tournament + .match_created_filter() + .address(Value(tournament_address)) + .from_block(0) + .to_block(Latest) + .query() + .await? + .iter() + .map(|event| MatchCreatedEvent { + id: MatchID { + commitment_one: event.one.into(), + commitment_two: event.two.into(), + }, + left_hash: event.left_of_two.into(), + }) + .collect(); + Ok(events) + } + + async fn joined_commitments( + &self, + tournament_address: Address, + ) -> Result, Box> { + let tournament = tournament::Tournament::new(tournament_address, self.client.clone()); + let events = tournament + .commitment_joined_filter() + .address(Value(tournament_address)) + .from_block(0) + .to_block(Latest) + .query() + .await? + .iter() + .map(|c| CommitmentJoinedEvent { + root: Digest::from(c.root), + }) + .collect(); + Ok(events) + } + + async fn get_commitment( + &self, + tournament: Address, + commitment_hash: Digest, + ) -> Result> { + let tournament = tournament::Tournament::new(tournament, self.client.clone()); + let (clock_state, hash) = tournament + .get_commitment(commitment_hash.into()) + .call() + .await?; + let block_time = self + .client + .get_block(Latest) + .await? + .expect("cannot get last block") + .timestamp; + let clock_state = ClockState { + allowance: clock_state.allowance, + start_instant: clock_state.start_instant, + block_time, + }; + Ok(CommitmentState { + clock: clock_state, + final_state: Digest::from(hash), + latest_match: None, + }) + } + + pub async fn fetch_from_root( + &self, + root_tournament: Address, + ) -> Result> { + self.fetch_tournament(TournamentState::new_root(root_tournament), HashMap::new()) + .await + } + + #[async_recursion] + async fn fetch_tournament( + &self, + tournament_state: TournamentState, + states: TournamentStateMap, + ) -> Result> { + let tournament = tournament::Tournament::new(tournament_state.address, self.client.clone()); + let mut state = tournament_state.clone(); + + ( + state.max_level, + state.level, + state.log2_stride, + state.log2_stride_count, + ) = tournament.tournament_level_constants().await?; + + assert!(state.level < state.max_level, "level out of bounds"); + + let created_matches = self.created_matches(tournament_state.address).await?; + let commitments_joined = self.joined_commitments(tournament_state.address).await?; + + let mut commitment_states = HashMap::new(); + for commitment in commitments_joined { + let commitment_state = self + .get_commitment(tournament_state.address, commitment.root) + .await?; + commitment_states.insert(commitment.root, commitment_state); + } + + let mut matches = vec![]; + let mut new_states = states.clone(); + for match_event in created_matches { + let match_id = match_event.id; + let m = tournament.get_match(match_id.hash().into()).call().await?; + + let running_leaf_position = m.running_leaf_position.as_u64(); + let base = tournament_state.base_big_cycle; + let step = 1 << state.log2_stride; + let leaf_cycle = base + (step * running_leaf_position); + let base_big_cycle = leaf_cycle >> constants::LOG2_UARCH_SPAN; + let prev_states = new_states.clone(); + let match_state; + + // if !Digest::from(m.other_parent).is_zeroed() { + (match_state, new_states) = self + .fetch_match( + MatchState { + id: match_id, + other_parent: m.other_parent.into(), + left_node: m.left_node.into(), + right_node: m.right_node.into(), + running_leaf_position, + current_height: m.current_height, + tournament_address: tournament_state.address, + level: m.level, + leaf_cycle, + base_big_cycle, + inner_tournament: None, + }, + prev_states, + tournament_state.level, + ) + .await?; + + commitment_states + .get_mut(&match_id.commitment_one) + .expect("cannot find commitment one state") + .latest_match = Some(matches.len()); + commitment_states + .get_mut(&match_id.commitment_two) + .expect("cannot find commitment two state") + .latest_match = Some(matches.len()); + matches.push(match_state); + } + // } + + let winner = match tournament_state.parent { + Some(_) => self.tournament_winner(tournament_state.address).await?, + None => { + self.root_tournament_winner(tournament_state.address) + .await? + } + }; + + state.winner = winner; + state.matches = matches; + state.commitment_states = commitment_states; + + new_states.insert(tournament_state.address, state); + + Ok(new_states) + } + + #[async_recursion] + async fn fetch_match( + &self, + match_state: MatchState, + states: TournamentStateMap, + tournament_level: u64, + ) -> Result<(MatchState, TournamentStateMap), Box> { + let mut state = match_state.clone(); + let created_tournament = self + .created_tournament(match_state.tournament_address, match_state.id) + .await?; + if let Some(inner) = created_tournament { + let inner_tournament = TournamentState::new_inner( + inner.new_tournament_address, + tournament_level, + match_state.base_big_cycle, + match_state.tournament_address, + ); + let new_states = self.fetch_tournament(inner_tournament, states).await?; + state.inner_tournament = Some(inner.new_tournament_address); + + return Ok((state, new_states)); + } + + Ok((state, states)) + } + + async fn root_tournament_winner( + &self, + root_tournament: Address, + ) -> Result, Box> { + let root_tournament = + root_tournament::RootTournament::new(root_tournament, self.client.clone()); + let (finished, commitment, state) = root_tournament.arbitration_result().call().await?; + if finished { + Ok(Some(TournamentWinner::Root( + Digest::from(commitment), + Digest::from(state), + ))) + } else { + Ok(None) + } + } + + async fn tournament_winner( + &self, + tournament: Address, + ) -> Result, Box> { + let tournament = + non_root_tournament::NonRootTournament::new(tournament, self.client.clone()); + let (finished, parent_commitment, dangling_commitment) = + tournament.inner_tournament_winner().call().await?; + if finished { + Ok(Some(TournamentWinner::Inner( + Digest::from(parent_commitment), + Digest::from(dangling_commitment), + ))) + } else { + Ok(None) + } + } +} + +/// This struct is used to communicate the creation of a new tournament. +#[derive(Clone, Copy)] +pub struct TournamentCreatedEvent { + pub parent_match_id_hash: Digest, + pub new_tournament_address: Address, +} + +/// This struct is used to communicate the enrollment of a new commitment. +#[derive(Clone, Copy)] +pub struct CommitmentJoinedEvent { + pub root: Digest, +} + +/// This struct is used to communicate the creation of a new match. +#[derive(Clone, Copy)] +pub struct MatchCreatedEvent { + pub id: MatchID, + pub left_hash: Digest, +} diff --git a/offchain/core/src/arena/sender.rs b/offchain/core/src/arena/sender.rs new file mode 100644 index 00000000..751bd463 --- /dev/null +++ b/offchain/core/src/arena/sender.rs @@ -0,0 +1,285 @@ +//! This module defines the struct [EthArenaSender] that is responsible for the sending transactions +//! to tournaments + +use std::{error::Error, str::FromStr, sync::Arc, time::Duration}; + +use async_trait::async_trait; + +use ethers::{ + middleware::SignerMiddleware, + providers::{Http, Provider}, + signers::{LocalWallet, Signer}, + types::{Address, Bytes}, +}; + +use crate::{ + arena::{arena::MatchID, config::ArenaConfig}, + contract::{leaf_tournament, non_leaf_tournament, tournament}, + machine::MachineProof, + merkle::{Digest, MerkleProof}, +}; + +#[derive(Clone)] +pub struct EthArenaSender { + client: Arc, LocalWallet>>, +} + +impl EthArenaSender { + pub fn new(config: ArenaConfig) -> Result> { + let provider = Provider::::try_from(config.web3_rpc_url.clone())? + .interval(Duration::from_millis(10u64)); + let wallet = LocalWallet::from_str(config.web3_private_key.as_str())?; + let client = Arc::new(SignerMiddleware::new( + provider, + wallet.with_chain_id(config.web3_chain_id), + )); + + Ok(Self { client }) + } +} + +/// The [ArenaSender] trait defines the interface for the creation and management of tournaments. +#[async_trait] +pub trait ArenaSender: Send + Sync { + async fn join_tournament( + &self, + tournament: Address, + final_state: Digest, + proof: MerkleProof, + left_child: Digest, + right_child: Digest, + ) -> Result<(), Box>; + + async fn advance_match( + &self, + tournament: Address, + match_id: MatchID, + left_node: Digest, + right_node: Digest, + new_left_node: Digest, + new_right_node: Digest, + ) -> Result<(), Box>; + + async fn seal_inner_match( + &self, + tournament: Address, + match_id: MatchID, + left_leaf: Digest, + right_leaf: Digest, + initial_hash: Digest, + initial_hash_proof: MerkleProof, + ) -> Result<(), Box>; + + async fn win_inner_match( + &self, + tournament: Address, + child_tournament: Address, + left_node: Digest, + right_node: Digest, + ) -> Result<(), Box>; + + async fn seal_leaf_match( + &self, + tournament: Address, + match_id: MatchID, + left_leaf: Digest, + right_leaf: Digest, + initial_hash: Digest, + initial_hash_proof: MerkleProof, + ) -> Result<(), Box>; + + async fn win_leaf_match( + &self, + tournament: Address, + match_id: MatchID, + left_node: Digest, + right_node: Digest, + proofs: MachineProof, + ) -> Result<(), Box>; + + async fn eliminate_match( + &self, + tournament: Address, + match_id: MatchID, + ) -> Result<(), Box>; +} + +#[async_trait] +impl ArenaSender for EthArenaSender { + async fn join_tournament( + &self, + tournament: Address, + final_state: Digest, + proof: MerkleProof, + left_child: Digest, + right_child: Digest, + ) -> Result<(), Box> { + let tournament = tournament::Tournament::new(tournament, self.client.clone()); + let proof = proof.iter().map(|h| -> [u8; 32] { (*h).into() }).collect(); + tournament + .join_tournament( + final_state.into(), + proof, + left_child.into(), + right_child.into(), + ) + .send() + .await? + .await?; + Ok(()) + } + + async fn advance_match( + &self, + tournament: Address, + match_id: MatchID, + left_node: Digest, + right_node: Digest, + new_left_node: Digest, + new_right_node: Digest, + ) -> 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 + .advance_match( + match_id, + left_node.into(), + right_node.into(), + new_left_node.into(), + new_right_node.into(), + ) + .send() + .await? + .await?; + Ok(()) + } + + async fn seal_inner_match( + &self, + tournament: Address, + match_id: MatchID, + left_leaf: Digest, + right_leaf: Digest, + initial_hash: Digest, + initial_hash_proof: MerkleProof, + ) -> Result<(), Box> { + let tournament = + non_leaf_tournament::NonLeafTournament::new(tournament, self.client.clone()); + let match_id = non_leaf_tournament::Id { + commitment_one: match_id.commitment_one.into(), + commitment_two: match_id.commitment_two.into(), + }; + let initial_hash_proof = initial_hash_proof + .iter() + .map(|h| -> [u8; 32] { (*h).into() }) + .collect(); + tournament + .seal_inner_match_and_create_inner_tournament( + match_id, + left_leaf.into(), + right_leaf.into(), + initial_hash.into(), + initial_hash_proof, + ) + .send() + .await? + .await?; + Ok(()) + } + + async fn win_inner_match( + &self, + tournament: Address, + child_tournament: Address, + left_node: Digest, + right_node: Digest, + ) -> Result<(), Box> { + let tournament = + non_leaf_tournament::NonLeafTournament::new(tournament, self.client.clone()); + tournament + .win_inner_match(child_tournament, left_node.into(), right_node.into()) + .send() + .await? + .await?; + Ok(()) + } + + async fn seal_leaf_match( + &self, + tournament: Address, + match_id: MatchID, + left_leaf: Digest, + right_leaf: Digest, + initial_hash: Digest, + initial_hash_proof: MerkleProof, + ) -> Result<(), Box> { + let tournament = leaf_tournament::LeafTournament::new(tournament, self.client.clone()); + let match_id = leaf_tournament::Id { + commitment_one: match_id.commitment_one.into(), + commitment_two: match_id.commitment_two.into(), + }; + let initial_hash_proof = initial_hash_proof + .iter() + .map(|h| -> [u8; 32] { (*h).into() }) + .collect(); + tournament + .seal_leaf_match( + match_id, + left_leaf.into(), + right_leaf.into(), + initial_hash.into(), + initial_hash_proof, + ) + .send() + .await? + .await?; + Ok(()) + } + + async fn win_leaf_match( + &self, + tournament: Address, + match_id: MatchID, + left_node: Digest, + right_node: Digest, + proofs: MachineProof, + ) -> Result<(), Box> { + let tournament = leaf_tournament::LeafTournament::new(tournament, self.client.clone()); + let match_id = leaf_tournament::Id { + commitment_one: match_id.commitment_one.into(), + commitment_two: match_id.commitment_two.into(), + }; + tournament + .win_leaf_match( + match_id, + left_node.into(), + right_node.into(), + Bytes::from(proofs), + ) + .send() + .await? + .await?; + 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(()) + } +} diff --git a/offchain/core/src/strategy/gc.rs b/offchain/core/src/strategy/gc.rs index f6bf4ea3..39f9d583 100644 --- a/offchain/core/src/strategy/gc.rs +++ b/offchain/core/src/strategy/gc.rs @@ -6,30 +6,28 @@ use ethers::types::Address; use crate::arena::{ArenaSender, MatchState, TournamentStateMap}; -pub struct GarbageCollector { - arena_sender: A, +pub struct GarbageCollector { root_tournamet: Address, } -impl GarbageCollector { - pub fn new(arena_sender: A, root_tournamet: Address) -> Self { - Self { - arena_sender, - root_tournamet, - } +impl GarbageCollector { + pub fn new(root_tournamet: Address) -> Self { + Self { root_tournamet } } - pub async fn react( + pub async fn react<'a>( &mut self, + arena_sender: &'a impl ArenaSender, tournament_states: TournamentStateMap, ) -> Result<(), Box> { - self.react_tournament(self.root_tournamet, tournament_states) + self.react_tournament(arena_sender, self.root_tournamet, tournament_states) .await } #[async_recursion] - async fn react_tournament( + async fn react_tournament<'a>( &mut self, + arena_sender: &'a impl ArenaSender, tournament_address: Address, tournament_states: TournamentStateMap, ) -> Result<(), Box> { @@ -39,7 +37,8 @@ impl GarbageCollector { .expect("tournament state not found"); for m in tournament_state.matches.clone() { - self.react_match(&m, tournament_states.clone()).await?; + self.react_match(arena_sender, &m, tournament_states.clone()) + .await?; let status_1 = tournament_state .commitment_states @@ -62,7 +61,7 @@ impl GarbageCollector { tournament_state.level ); - self.arena_sender + arena_sender .eliminate_match(tournament_address, m.id) .await .expect("fail to eliminate match"); @@ -72,15 +71,16 @@ impl GarbageCollector { } #[async_recursion] - async fn react_match( + async fn react_match<'a>( &mut self, + arena_sender: &'a impl ArenaSender, match_state: &MatchState, tournament_states: TournamentStateMap, ) -> 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) + .react_tournament(arena_sender, inner_tournament, tournament_states) .await; } diff --git a/offchain/core/src/strategy/player.rs b/offchain/core/src/strategy/player.rs index 89a7d778..1464f65f 100644 --- a/offchain/core/src/strategy/player.rs +++ b/offchain/core/src/strategy/player.rs @@ -16,24 +16,21 @@ pub enum PlayerTournamentResult { TournamentLost, } -pub struct Player { - arena_sender: A, +pub struct Player { machine_factory: MachineFactory, machine_path: String, commitment_builder: CachingMachineCommitmentBuilder, root_tournamet: Address, } -impl Player { +impl Player { pub fn new( - arena_sender: A, machine_factory: MachineFactory, machine_path: String, commitment_builder: CachingMachineCommitmentBuilder, root_tournamet: Address, ) -> Self { Self { - arena_sender, machine_factory, machine_path, commitment_builder, @@ -41,17 +38,24 @@ impl Player { } } - pub async fn react( + pub async fn react<'a>( &mut self, + arena_sender: &'a impl ArenaSender, tournament_states: TournamentStateMap, ) -> Result, Box> { - self.react_tournament(HashMap::new(), self.root_tournamet, tournament_states) - .await + self.react_tournament( + arena_sender, + HashMap::new(), + self.root_tournamet, + tournament_states, + ) + .await } #[async_recursion] - async fn react_tournament( + async fn react_tournament<'a>( &mut self, + arena_sender: &'a impl ArenaSender, commitments: HashMap, tournament_address: Address, tournament_states: TournamentStateMap, @@ -108,7 +112,7 @@ impl Player { commitment.merkle.root_hash(), ); let (left, right) = old_commitment.merkle.root_children(); - self.arena_sender + arena_sender .win_inner_match( tournament_state .parent @@ -133,6 +137,7 @@ impl Player { info!("{}", c.clock); if let Some(m) = c.latest_match { self.react_match( + arena_sender, &tournament_state .matches .get(m) @@ -148,7 +153,7 @@ impl Player { } } None => { - self.join_tournament_if_needed(tournament_state, &commitment) + self.join_tournament_if_needed(arena_sender, tournament_state, &commitment) .await?; } } @@ -156,8 +161,9 @@ impl Player { Ok(None) } - async fn join_tournament_if_needed( + async fn join_tournament_if_needed<'a>( &mut self, + arena_sender: &'a impl ArenaSender, tournament_state: &TournamentState, commitment: &MachineCommitment, ) -> Result<(), Box> { @@ -170,14 +176,15 @@ impl Player { tournament_state.level, commitment.merkle.root_hash(), ); - self.arena_sender + arena_sender .join_tournament(tournament_state.address, last, proof, left, right) .await } #[async_recursion] - async fn react_match( + async fn react_match<'a>( &mut self, + arena_sender: &'a impl ArenaSender, match_state: &MatchState, commitment: &MachineCommitment, commitments: HashMap, @@ -188,6 +195,7 @@ impl Player { info!("Enter match at HEIGHT: {}", match_state.current_height); if match_state.current_height == 0 { self.react_sealed_match( + arena_sender, match_state, commitment, commitments, @@ -198,6 +206,7 @@ impl Player { .await } else if match_state.current_height == 1 { self.react_unsealed_match( + arena_sender, match_state, commitment, tournament_level, @@ -205,14 +214,15 @@ impl Player { ) .await } else { - self.react_running_match(match_state, commitment, tournament_level) + self.react_running_match(arena_sender, match_state, commitment, tournament_level) .await } } #[async_recursion] - async fn react_sealed_match( + async fn react_sealed_match<'a>( &mut self, + arena_sender: &'a impl ArenaSender, match_state: &MatchState, commitment: &MachineCommitment, commitments: HashMap, @@ -243,7 +253,7 @@ impl Player { tournament_level, commitment.merkle.root_hash(), ); - self.arena_sender + arena_sender .win_leaf_match( match_state.tournament_address, match_state.id, @@ -254,6 +264,7 @@ impl Player { .await?; } else { self.react_tournament( + arena_sender, commitments, match_state .inner_tournament @@ -266,8 +277,9 @@ impl Player { Ok(()) } - async fn react_unsealed_match( + async fn react_unsealed_match<'a>( &mut self, + arena_sender: &'a impl ArenaSender, match_state: &MatchState, commitment: &MachineCommitment, tournament_level: u64, @@ -295,7 +307,7 @@ impl Player { tournament_level, commitment.merkle.root_hash(), ); - self.arena_sender + arena_sender .seal_leaf_match( match_state.tournament_address, match_state.id, @@ -312,7 +324,7 @@ impl Player { tournament_level, commitment.merkle.root_hash(), ); - self.arena_sender + arena_sender .seal_inner_match( match_state.tournament_address, match_state.id, @@ -327,8 +339,9 @@ impl Player { Ok(()) } - async fn react_running_match( + async fn react_running_match<'a>( &mut self, + arena_sender: &'a impl ArenaSender, match_state: &MatchState, commitment: &MachineCommitment, tournament_level: u64, @@ -359,7 +372,7 @@ impl Player { tournament_level, commitment.merkle.root_hash(), ); - self.arena_sender + arena_sender .advance_match( match_state.tournament_address, match_state.id, diff --git a/offchain/dave-compute/src/main.rs b/offchain/dave-compute/src/main.rs index 159f5147..3dbfa04e 100644 --- a/offchain/dave-compute/src/main.rs +++ b/offchain/dave-compute/src/main.rs @@ -1,4 +1,4 @@ -use cartesi_compute_core::arena::{Arena, ArenaConfig}; +use cartesi_compute_core::arena::{ArenaConfig, EthArenaSender, StateReader}; use cartesi_compute_core::machine::{CachingMachineCommitmentBuilder, MachineFactory}; use cartesi_compute_core::strategy::{gc::GarbageCollector, player::Player}; use ethers::types::Address; @@ -22,7 +22,8 @@ async fn main() -> Result<(), Box> { web3_private_key: private_key, }; - let arena = Arena::new(arena_config)?; + let reader = StateReader::new(arena_config.clone())?; + let sender = EthArenaSender::new(arena_config)?; let machine_path = std::env::var("MACHINE_PATH").expect("MACHINE_PATH is not set"); // String::from("/root/permissionless-arbitration/lua_node/program/simple-program"); @@ -35,26 +36,25 @@ async fn main() -> Result<(), Box> { let root_tournament = Address::from_str("0xcafac3dd18ac6c6e92c921884f9e4176737c052c")?; let mut player = Player::new( - arena.clone(), machine_factory.clone(), machine_path.clone(), CachingMachineCommitmentBuilder::new(machine_factory, machine_path), root_tournament.clone(), ); - let mut gc = GarbageCollector::new(arena.clone(), root_tournament.clone()); + let mut gc = GarbageCollector::new(root_tournament.clone()); loop { - let tournament_states = arena.fetch_from_root(root_tournament).await?; - let res = player.react(tournament_states).await?; + let tournament_states = reader.fetch_from_root(root_tournament).await?; + let res = player.react(&sender, tournament_states).await?; if let Some(r) = res { info!("Tournament finished, {:?}", r); break; } tokio::time::sleep(Duration::from_secs(1)).await; - let tournament_states = arena.fetch_from_root(root_tournament).await?; - gc.react(tournament_states).await?; + let tournament_states = reader.fetch_from_root(root_tournament).await?; + gc.react(&sender, tournament_states).await?; tokio::time::sleep(Duration::from_secs(1)).await; }