Skip to content

Commit

Permalink
Add tic-tac-toe dashboard program
Browse files Browse the repository at this point in the history
  • Loading branch information
mvines authored and solana-grimes committed Sep 29, 2018
1 parent 9fd6ffe commit aff5649
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 4 deletions.
5 changes: 5 additions & 0 deletions src/bank.rs
Expand Up @@ -28,6 +28,7 @@ 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 transaction::Transaction;
Expand Down Expand Up @@ -405,6 +406,10 @@ impl Bank {
if TicTacToeProgram::process_transaction(&tx, accounts).is_err() {
return Err(BankError::ProgramRuntimeError);
}
} else if TicTacToeDashboardProgram::check_id(&tx.program_id) {
if TicTacToeDashboardProgram::process_transaction(&tx, accounts).is_err() {
return Err(BankError::ProgramRuntimeError);
}
} else if self.loaded_contract(&tx, accounts) {
} else {
return Err(BankError::UnknownContractId);
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -60,6 +60,7 @@ 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 tpu;
Expand Down
165 changes: 165 additions & 0 deletions src/tictactoe_dashboard_program.rs
@@ -0,0 +1,165 @@
//! tic-tac-toe dashboard program

use serde_cbor;
use solana_program_interface::account::Account;
use solana_program_interface::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<Pubkey>, // 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<TicTacToeDashboardProgram> {
if input.len() < 2 {
Err(Error::InvalidUserdata)?;
}
let len = input[0] as usize + (0xFF * 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 game: {:?}", 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 high 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, accounts: &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.keys[2], &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 = 123;

let mut userdata = vec![0xff; 256];
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
}
8 changes: 4 additions & 4 deletions src/tictactoe_program.rs
Expand Up @@ -24,7 +24,7 @@ impl std::fmt::Display for Error {
}
impl std::error::Error for Error {}

type Result<T> = std::result::Result<T, Error>;
pub type Result<T> = std::result::Result<T, Error>;

#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
enum BoardItem {
Expand Down Expand Up @@ -55,7 +55,7 @@ impl Default for State {
}

#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
struct Game {
pub struct Game {
player_x: Pubkey,
player_o: Option<Pubkey>,
pub state: State,
Expand Down Expand Up @@ -164,15 +164,15 @@ enum Command {

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct TicTacToeProgram {
game: Option<Game>,
pub game: Option<Game>,
}

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 {
fn deserialize(input: &[u8]) -> Result<TicTacToeProgram> {
pub fn deserialize(input: &[u8]) -> Result<TicTacToeProgram> {
let len = input[0] as usize;

if len == 0 {
Expand Down

0 comments on commit aff5649

Please sign in to comment.