From 07c656093cd0fdd5c9b45017d05863ecede3a085 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Thu, 25 Oct 2018 21:05:50 -0700 Subject: [PATCH] Remove tictactoe programs --- src/bank.rs | 17 - src/lib.rs | 2 - src/tictactoe_dashboard_program.rs | 172 ---------- src/tictactoe_program.rs | 504 ----------------------------- 4 files changed, 695 deletions(-) delete mode 100644 src/tictactoe_dashboard_program.rs delete mode 100644 src/tictactoe_program.rs diff --git a/src/bank.rs b/src/bank.rs index 6661912c97..a1a59d4d0b 100644 --- a/src/bank.rs +++ b/src/bank.rs @@ -35,8 +35,6 @@ use std::time::Instant; use storage_program::StorageProgram; use system_program::SystemProgram; use system_transaction::SystemTransaction; -use tictactoe_dashboard_program::TicTacToeDashboardProgram; -use tictactoe_program::TicTacToeProgram; use timing::{duration_as_us, timestamp}; use token_program::TokenProgram; use tokio::prelude::Future; @@ -602,21 +600,6 @@ impl Bank { { return Err(BankError::ProgramRuntimeError(instruction_index as u8)); } - } else if TicTacToeProgram::check_id(&tx_program_id) { - if TicTacToeProgram::process_transaction(&tx, instruction_index, program_accounts) - .is_err() - { - return Err(BankError::ProgramRuntimeError(instruction_index as u8)); - } - } else if TicTacToeDashboardProgram::check_id(&tx_program_id) { - if TicTacToeDashboardProgram::process_transaction( - &tx, - instruction_index, - program_accounts, - ).is_err() - { - return Err(BankError::ProgramRuntimeError(instruction_index as u8)); - } } else if TokenProgram::check_id(&tx_program_id) { if TokenProgram::process_transaction(&tx, instruction_index, program_accounts).is_err() { diff --git a/src/lib.rs b/src/lib.rs index 1f9e9024bb..2925b2b5e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,8 +72,6 @@ pub mod streamer; pub mod system_program; pub mod system_transaction; pub mod thin_client; -pub mod tictactoe_dashboard_program; -pub mod tictactoe_program; pub mod timing; pub mod token_program; pub mod tpu; diff --git a/src/tictactoe_dashboard_program.rs b/src/tictactoe_dashboard_program.rs deleted file mode 100644 index 2cb981f9c7..0000000000 --- a/src/tictactoe_dashboard_program.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! tic-tac-toe dashboard program - -use serde_cbor; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; -use tictactoe_program::{Error, Game, Result, State, TicTacToeProgram}; -use transaction::Transaction; - -#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct TicTacToeDashboardProgram { - pending: Pubkey, // Latest pending game - completed: Vec, // Last N completed games (0 is the latest) - total: usize, // Total number of completed games -} - -pub const TICTACTOE_DASHBOARD_PROGRAM_ID: [u8; 32] = [ - 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -]; - -impl TicTacToeDashboardProgram { - fn deserialize(input: &[u8]) -> Result { - if input.len() < 2 { - Err(Error::InvalidUserdata)?; - } - let len = input[0] as usize + (0x100 * input[1] as usize); - if len == 0 { - Ok(TicTacToeDashboardProgram::default()) - } else if input.len() < len + 2 { - Err(Error::InvalidUserdata) - } else { - serde_cbor::from_slice(&input[2..(2 + len)]).map_err(|err| { - error!("Unable to deserialize: {:?}", err); - Error::InvalidUserdata - }) - } - } - - fn update( - self: &mut TicTacToeDashboardProgram, - game_pubkey: &Pubkey, - game: &Game, - ) -> Result<()> { - match game.state { - State::Waiting => { - self.pending = *game_pubkey; - } - State::XMove | State::OMove => { - // Nothing to do. In progress games are not managed by the dashboard - } - State::XWon | State::OWon | State::Draw => { - if !self.completed.iter().any(|pubkey| pubkey == game_pubkey) { - // TODO: Once the PoH height is exposed to programs, it could be used to ensure - // that old games are not being re-added and causing |total| to increment - // incorrectly. - self.total += 1; - self.completed.insert(0, *game_pubkey); - - // Only track the last N completed games to - // avoid overrunning Account userdata - if self.completed.len() > 5 { - self.completed.pop(); - } - } - } - }; - - Ok(()) - } - - fn serialize(self: &TicTacToeDashboardProgram, output: &mut [u8]) -> Result<()> { - let self_serialized = serde_cbor::to_vec(self).unwrap(); - - if output.len() < 2 + self_serialized.len() { - warn!( - "{} bytes required to serialize but only have {} bytes", - self_serialized.len() + 2, - output.len() - ); - return Err(Error::UserdataTooSmall); - } - - assert!(self_serialized.len() <= 0xFFFF); - output[0] = (self_serialized.len() & 0xFF) as u8; - output[1] = (self_serialized.len() >> 8) as u8; - output[2..(2 + self_serialized.len())].clone_from_slice(&self_serialized); - Ok(()) - } - - pub fn check_id(program_id: &Pubkey) -> bool { - program_id.as_ref() == TICTACTOE_DASHBOARD_PROGRAM_ID - } - - pub fn id() -> Pubkey { - Pubkey::new(&TICTACTOE_DASHBOARD_PROGRAM_ID) - } - - pub fn process_transaction( - tx: &Transaction, - pix: usize, - accounts: &mut [&mut Account], - ) -> Result<()> { - info!("process_transaction: {:?}", tx); - // accounts[0] doesn't matter, anybody can cause a dashboard update - // accounts[1] must be a Dashboard account - // accounts[2] must be a Game account - if accounts.len() != 3 { - error!("Expected 3 accounts"); - Err(Error::InvalidArguments)?; - } - if !Self::check_id(&accounts[1].program_id) { - error!("accounts[1] is not a TICTACTOE_DASHBOARD_PROGRAM_ID"); - Err(Error::InvalidArguments)?; - } - if accounts[1].userdata.is_empty() { - error!("accounts[1] userdata is empty"); - Err(Error::InvalidArguments)?; - } - - let mut dashboard = Self::deserialize(&accounts[1].userdata)?; - - if !TicTacToeProgram::check_id(&accounts[2].program_id) { - error!("accounts[2] is not a TICTACTOE_PROGRAM_ID"); - Err(Error::InvalidArguments)?; - } - let ttt = TicTacToeProgram::deserialize(&accounts[2].userdata)?; - - match ttt.game { - None => Err(Error::NoGame), - Some(game) => dashboard.update(tx.key(pix, 2).unwrap(), &game), - }?; - - dashboard.serialize(&mut accounts[1].userdata)?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - pub fn serde() { - let mut dashboard1 = TicTacToeDashboardProgram::default(); - dashboard1.total = 1234567890; - dashboard1.pending = Pubkey::new(&[100; 32]); - for i in 1..5 { - dashboard1.completed.push(Pubkey::new(&[100 + i; 32])); - } - - let mut userdata = vec![0xff; 512]; - dashboard1.serialize(&mut userdata).unwrap(); - - let dashboard2 = TicTacToeDashboardProgram::deserialize(&userdata).unwrap(); - assert_eq!(dashboard1, dashboard2); - } - - #[test] - pub fn serde_userdata_too_small() { - let dashboard = TicTacToeDashboardProgram::default(); - let mut userdata = vec![0xff; 1]; - assert_eq!( - dashboard.serialize(&mut userdata), - Err(Error::UserdataTooSmall) - ); - - let err = TicTacToeDashboardProgram::deserialize(&userdata); - assert!(err.is_err()); - assert_eq!(err.err().unwrap(), Error::InvalidUserdata); - } - - // TODO: add tests for business logic -} diff --git a/src/tictactoe_program.rs b/src/tictactoe_program.rs deleted file mode 100644 index b2002c7d62..0000000000 --- a/src/tictactoe_program.rs +++ /dev/null @@ -1,504 +0,0 @@ -//! tic-tac-toe program - -use serde_cbor; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; -use std; -use transaction::Transaction; - -#[derive(Debug, PartialEq)] -pub enum Error { - GameInProgress, - InvalidArguments, - InvalidMove, - InvalidUserdata, - InvalidTimestamp, - NoGame, - NotYourTurn, - PlayerNotFound, - UserdataTooSmall, -} -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "error") - } -} -impl std::error::Error for Error {} - -pub type Result = std::result::Result; - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] -enum BoardItem { - F, // Free - X, - O, -} - -impl Default for BoardItem { - fn default() -> BoardItem { - BoardItem::F - } -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] -pub enum State { - Waiting, - XMove, - OMove, - XWon, - OWon, - Draw, -} -impl Default for State { - fn default() -> State { - State::Waiting - } -} - -#[repr(C)] -#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] -pub struct Game { - player_x: Pubkey, - player_o: Option, - pub state: State, - board: [BoardItem; 9], - keep_alive: [i64; 2], -} - -impl Game { - pub fn create(player_x: &Pubkey) -> Game { - let mut game = Game::default(); - game.player_x = *player_x; - assert_eq!(game.state, State::Waiting); - game - } - - #[cfg(test)] - pub fn new(player_x: Pubkey, player_o: Pubkey) -> Game { - let mut game = Game::create(&player_x); - game.join(player_o, 1).unwrap(); - game - } - - pub fn join(self: &mut Game, player_o: Pubkey, timestamp: i64) -> Result<()> { - if self.state == State::Waiting { - self.player_o = Some(player_o); - self.state = State::XMove; - - if timestamp <= self.keep_alive[1] { - Err(Error::InvalidTimestamp) - } else { - self.keep_alive[1] = timestamp; - Ok(()) - } - } else { - Err(Error::GameInProgress) - } - } - - fn same(x_or_o: BoardItem, triple: &[BoardItem]) -> bool { - triple.iter().all(|&i| i == x_or_o) - } - - pub fn next_move(self: &mut Game, player: Pubkey, x: usize, y: usize) -> Result<()> { - let board_index = y * 3 + x; - if board_index >= self.board.len() || self.board[board_index] != BoardItem::F { - Err(Error::InvalidMove)?; - } - - let (x_or_o, won_state) = match self.state { - State::XMove => { - if player != self.player_x { - return Err(Error::PlayerNotFound); - } - self.state = State::OMove; - (BoardItem::X, State::XWon) - } - State::OMove => { - if player != self.player_o.unwrap() { - return Err(Error::PlayerNotFound); - } - self.state = State::XMove; - (BoardItem::O, State::OWon) - } - _ => { - return Err(Error::NotYourTurn); - } - }; - self.board[board_index] = x_or_o; - - let winner = - // Check rows - Game::same(x_or_o, &self.board[0..3]) - || Game::same(x_or_o, &self.board[3..6]) - || Game::same(x_or_o, &self.board[6..9]) - // Check columns - || Game::same(x_or_o, &[self.board[0], self.board[3], self.board[6]]) - || Game::same(x_or_o, &[self.board[1], self.board[4], self.board[7]]) - || Game::same(x_or_o, &[self.board[2], self.board[5], self.board[8]]) - // Check both diagonals - || Game::same(x_or_o, &[self.board[0], self.board[4], self.board[8]]) - || Game::same(x_or_o, &[self.board[2], self.board[4], self.board[6]]); - - if winner { - self.state = won_state; - } else if self.board.iter().all(|&p| p != BoardItem::F) { - self.state = State::Draw; - } - - Ok(()) - } - - pub fn keep_alive(self: &mut Game, player: Pubkey, timestamp: i64) -> Result<()> { - match self.state { - State::Waiting | State::XMove | State::OMove => { - if player == self.player_x { - if timestamp <= self.keep_alive[0] { - Err(Error::InvalidTimestamp)?; - } - self.keep_alive[0] = timestamp; - } else if Some(player) == self.player_o { - if timestamp <= self.keep_alive[1] { - Err(Error::InvalidTimestamp)?; - } - self.keep_alive[1] = timestamp; - } else { - Err(Error::PlayerNotFound)?; - } - } - // Ignore keep_alive when game is no longer in progress - State::XWon | State::OWon | State::Draw => {} - }; - Ok(()) - } -} - -#[derive(Debug, Serialize, Deserialize)] -#[repr(C)] -pub enum Command { - Init, // player X initializes a new game - Join(i64), // player O wants to join (seconds since UNIX epoch) - KeepAlive(i64), // player X/O keep alive (seconds since UNIX epoch) - Move(u8, u8), // player X/O mark board position (x, y) -} - -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct TicTacToeProgram { - pub game: Option, -} - -pub const TICTACTOE_PROGRAM_ID: [u8; 32] = [ - 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -]; - -impl TicTacToeProgram { - pub fn deserialize(input: &[u8]) -> Result { - let len = input[0] as usize; - - if len == 0 { - Ok(TicTacToeProgram::default()) - } else if input.len() < len + 1 { - Err(Error::InvalidUserdata) - } else { - serde_cbor::from_slice(&input[1..=len]).map_err(|err| { - error!("Unable to deserialize game: {:?}", err); - Error::InvalidUserdata - }) - } - } - - fn dispatch_command(self: &mut TicTacToeProgram, cmd: &Command, player: &Pubkey) -> Result<()> { - info!("dispatch_command: cmd={:?} player={}", cmd, player); - info!("dispatch_command: account={:?}", self); - - if let Command::Init = cmd { - return if self.game.is_some() { - Err(Error::GameInProgress) - } else { - let game = Game::create(player); - self.game = Some(game); - Ok(()) - }; - } - - if let Some(ref mut game) = self.game { - match cmd { - Command::Join(timestamp) => game.join(*player, *timestamp), - Command::Move(x, y) => game.next_move(*player, *x as usize, *y as usize), - Command::KeepAlive(timestamp) => game.keep_alive(*player, *timestamp), - Command::Init => panic!("Unreachable"), - } - } else { - Err(Error::NoGame) - } - } - - fn serialize(self: &TicTacToeProgram, output: &mut [u8]) -> Result<()> { - let self_serialized = serde_cbor::to_vec(self).unwrap(); - - if output.len() + 1 < self_serialized.len() { - warn!( - "{} bytes required to serialize but only have {} bytes", - self_serialized.len(), - output.len() + 1 - ); - return Err(Error::UserdataTooSmall); - } - - assert!(self_serialized.len() <= 255); - output[0] = self_serialized.len() as u8; - output[1..=self_serialized.len()].clone_from_slice(&self_serialized); - Ok(()) - } - - pub fn check_id(program_id: &Pubkey) -> bool { - program_id.as_ref() == TICTACTOE_PROGRAM_ID - } - - pub fn id() -> Pubkey { - Pubkey::new(&TICTACTOE_PROGRAM_ID) - } - - pub fn process_transaction( - tx: &Transaction, - pix: usize, - accounts: &mut [&mut Account], - ) -> Result<()> { - // accounts[1] must always be the Tic-tac-toe game state account - if accounts.len() < 2 || !Self::check_id(&accounts[1].program_id) { - error!("accounts[1] is not assigned to the TICTACTOE_PROGRAM_ID"); - Err(Error::InvalidArguments)?; - } - if accounts[1].userdata.is_empty() { - error!("accounts[1] userdata is empty"); - Err(Error::InvalidArguments)?; - } - - let mut program_state = Self::deserialize(&accounts[1].userdata)?; - - let command = serde_cbor::from_slice::(tx.userdata(pix)).map_err(|err| { - error!("{:?}", err); - Error::InvalidUserdata - })?; - - if let Command::Init = command { - // Init must be signed by the game state account itself, who's private key is - // known only to player X - if !Self::check_id(&accounts[0].program_id) { - error!("accounts[0] is not assigned to the TICTACTOE_PROGRAM_ID"); - return Err(Error::InvalidArguments); - } - // player X public key is in keys[2] - if tx.key(pix, 2).is_none() { - Err(Error::InvalidArguments)?; - } - program_state.dispatch_command(&command, tx.key(pix, 2).unwrap())?; - } else { - program_state.dispatch_command(&command, tx.key(pix, 0).unwrap())?; - } - program_state.serialize(&mut accounts[1].userdata)?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - pub fn serde_no_game() { - let account = TicTacToeProgram::default(); - assert!(account.game.is_none()); - - let mut userdata = vec![0xff; 256]; - account.serialize(&mut userdata).unwrap(); - - let account = TicTacToeProgram::deserialize(&userdata).unwrap(); - assert!(account.game.is_none()); - - let account = TicTacToeProgram::deserialize(&[0]).unwrap(); - assert!(account.game.is_none()); - } - - #[test] - pub fn serde_with_game() { - let mut account = TicTacToeProgram::default(); - assert!(account.game.is_none()); - - let player_x = Pubkey::new(&[1; 32]); - account.dispatch_command(&Command::Init, &player_x).unwrap(); - - let mut userdata = vec![0xff; 256]; - account.serialize(&mut userdata).unwrap(); - - let account2 = TicTacToeProgram::deserialize(&userdata).unwrap(); - - assert_eq!(account.game.unwrap(), account2.game.unwrap()); - } - - #[test] - pub fn serde_no_room() { - let account = TicTacToeProgram::default(); - let mut userdata = vec![0xff; 1]; - assert_eq!( - account.serialize(&mut userdata), - Err(Error::UserdataTooSmall) - ); - - let err = TicTacToeProgram::deserialize(&userdata); - assert!(err.is_err()); - assert_eq!(err.err().unwrap(), Error::InvalidUserdata); - } - - #[test] - pub fn column_1_x_wins() { - /* - X|O| - -+-+- - X|O| - -+-+- - X| | - */ - - let player_x = Pubkey::new(&[1; 32]); - let player_o = Pubkey::new(&[2; 32]); - - let mut g = Game::new(player_x, player_o); - assert_eq!(g.state, State::XMove); - - g.next_move(player_x, 0, 0).unwrap(); - assert_eq!(g.state, State::OMove); - g.next_move(player_o, 1, 0).unwrap(); - assert_eq!(g.state, State::XMove); - g.next_move(player_x, 0, 1).unwrap(); - assert_eq!(g.state, State::OMove); - g.next_move(player_o, 1, 1).unwrap(); - assert_eq!(g.state, State::XMove); - g.next_move(player_x, 0, 2).unwrap(); - assert_eq!(g.state, State::XWon); - } - - #[test] - pub fn right_diagonal_x_wins() { - /* - X|O|X - -+-+- - O|X|O - -+-+- - X| | - */ - - let player_x = Pubkey::new(&[1; 32]); - let player_o = Pubkey::new(&[2; 32]); - let mut g = Game::new(player_x, player_o); - - g.next_move(player_x, 0, 0).unwrap(); - g.next_move(player_o, 1, 0).unwrap(); - g.next_move(player_x, 2, 0).unwrap(); - g.next_move(player_o, 0, 1).unwrap(); - g.next_move(player_x, 1, 1).unwrap(); - g.next_move(player_o, 2, 1).unwrap(); - g.next_move(player_x, 0, 2).unwrap(); - assert_eq!(g.state, State::XWon); - - assert_eq!(g.next_move(player_o, 1, 2), Err(Error::NotYourTurn)); - } - - #[test] - pub fn bottom_row_o_wins() { - /* - X|X| - -+-+- - X| | - -+-+- - O|O|O - */ - - let player_x = Pubkey::new(&[1; 32]); - let player_o = Pubkey::new(&[2; 32]); - let mut g = Game::new(player_x, player_o); - - g.next_move(player_x, 0, 0).unwrap(); - g.next_move(player_o, 0, 2).unwrap(); - g.next_move(player_x, 1, 0).unwrap(); - g.next_move(player_o, 1, 2).unwrap(); - g.next_move(player_x, 0, 1).unwrap(); - g.next_move(player_o, 2, 2).unwrap(); - assert_eq!(g.state, State::OWon); - - assert!(g.next_move(player_x, 1, 2).is_err()); - } - - #[test] - pub fn left_diagonal_x_wins() { - /* - X|O|X - -+-+- - O|X|O - -+-+- - O|X|X - */ - - let player_x = Pubkey::new(&[1; 32]); - let player_o = Pubkey::new(&[2; 32]); - let mut g = Game::new(player_x, player_o); - - g.next_move(player_x, 0, 0).unwrap(); - g.next_move(player_o, 1, 0).unwrap(); - g.next_move(player_x, 2, 0).unwrap(); - g.next_move(player_o, 0, 1).unwrap(); - g.next_move(player_x, 1, 1).unwrap(); - g.next_move(player_o, 2, 1).unwrap(); - g.next_move(player_x, 1, 2).unwrap(); - g.next_move(player_o, 0, 2).unwrap(); - g.next_move(player_x, 2, 2).unwrap(); - assert_eq!(g.state, State::XWon); - } - - #[test] - pub fn draw() { - /* - X|O|O - -+-+- - O|O|X - -+-+- - X|X|O - */ - - let player_x = Pubkey::new(&[1; 32]); - let player_o = Pubkey::new(&[2; 32]); - let mut g = Game::new(player_x, player_o); - - g.next_move(player_x, 0, 0).unwrap(); - g.next_move(player_o, 1, 1).unwrap(); - g.next_move(player_x, 0, 2).unwrap(); - g.next_move(player_o, 0, 1).unwrap(); - g.next_move(player_x, 2, 1).unwrap(); - g.next_move(player_o, 1, 0).unwrap(); - g.next_move(player_x, 1, 2).unwrap(); - g.next_move(player_o, 2, 2).unwrap(); - g.next_move(player_x, 2, 0).unwrap(); - - assert_eq!(g.state, State::Draw); - } - - #[test] - pub fn solo() { - /* - X|O| - -+-+- - | | - -+-+- - | | - */ - - let player_x = Pubkey::new(&[1; 32]); - - let mut g = Game::new(player_x, player_x); - assert_eq!(g.state, State::XMove); - g.next_move(player_x, 0, 0).unwrap(); - assert_eq!(g.state, State::OMove); - g.next_move(player_x, 1, 0).unwrap(); - assert_eq!(g.state, State::XMove); - } -}