Skip to main content

sawtooth_xo/handler/
mod.rs

1/*
2 * Copyright 2018 Bitwise IO, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 * -----------------------------------------------------------------------------
16 */
17
18mod game;
19mod payload;
20mod state;
21
22cfg_if! {
23    if #[cfg(target_arch = "wasm32")] {
24        use sabre_sdk::{ApplyError, TpProcessRequest, TransactionContext, TransactionHandler,};
25    } else {
26        use sawtooth_sdk::messages::processor::TpProcessRequest;
27        use sawtooth_sdk::processor::handler::{ApplyError, TransactionContext, TransactionHandler};
28    }
29}
30
31use crate::handler::game::Game;
32use crate::handler::payload::XoPayload;
33use crate::handler::state::{get_xo_prefix, XoState};
34
35pub struct XoTransactionHandler {
36    family_name: String,
37    family_versions: Vec<String>,
38    namespaces: Vec<String>,
39}
40
41impl XoTransactionHandler {
42    pub fn new() -> XoTransactionHandler {
43        XoTransactionHandler {
44            family_name: "xo".into(),
45            family_versions: vec!["1.0".into()],
46            namespaces: vec![get_xo_prefix()],
47        }
48    }
49}
50
51impl Default for XoTransactionHandler {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57impl TransactionHandler for XoTransactionHandler {
58    fn family_name(&self) -> String {
59        self.family_name.clone()
60    }
61
62    fn family_versions(&self) -> Vec<String> {
63        self.family_versions.clone()
64    }
65
66    fn namespaces(&self) -> Vec<String> {
67        self.namespaces.clone()
68    }
69
70    fn apply(
71        &self,
72        request: &TpProcessRequest,
73        context: &mut dyn TransactionContext,
74    ) -> Result<(), ApplyError> {
75        let signer = request.get_header().get_signer_public_key();
76
77        let payload = XoPayload::new(request.get_payload())?;
78
79        let mut state = XoState::new(context);
80
81        info!(
82            "Payload: {} {} {}",
83            payload.get_name(),
84            payload.get_action(),
85            payload.get_space(),
86        );
87
88        let game = state.get_game(payload.get_name().as_str())?;
89
90        match payload.get_action().as_str() {
91            "delete" => {
92                if game.is_none() {
93                    return Err(ApplyError::InvalidTransaction(String::from(
94                        "Invalid action: game does not exist",
95                    )));
96                }
97                state.delete_game(payload.get_name().as_str())?;
98            }
99            "create" => {
100                if game.is_none() {
101                    let game = Game::new(payload.get_name());
102                    state.set_game(payload.get_name().as_str(), game)?;
103                    info!("Created game: {}", payload.get_name().as_str());
104                } else {
105                    return Err(ApplyError::InvalidTransaction(String::from(
106                        "Invalid action: Game already exists",
107                    )));
108                }
109            }
110            "take" => {
111                if let Some(mut g) = game {
112                    match g.get_state().as_str() {
113                        "P1-WIN" | "P2-WIN" | "TIE" => {
114                            return Err(ApplyError::InvalidTransaction(String::from(
115                                "Invalid action: Game has ended",
116                            )));
117                        }
118                        "P1-NEXT" => {
119                            let p1 = g.get_player1();
120                            if !p1.is_empty() && p1.as_str() != signer {
121                                return Err(ApplyError::InvalidTransaction(String::from(
122                                    "Not player 2's turn",
123                                )));
124                            }
125                        }
126                        "P2-NEXT" => {
127                            let p2 = g.get_player2();
128                            if !p2.is_empty() && p2.as_str() != signer {
129                                return Err(ApplyError::InvalidTransaction(String::from(
130                                    "Not player 1's turn",
131                                )));
132                            }
133                        }
134                        _ => {
135                            return Err(ApplyError::InvalidTransaction(String::from(
136                                "Invalid state",
137                            )));
138                        }
139                    }
140
141                    let board_chars: Vec<char> = g.get_board().chars().collect();
142                    if board_chars[payload.get_space() - 1] != '-' {
143                        return Err(ApplyError::InvalidTransaction(format!(
144                            "Space {} is already taken",
145                            payload.get_space()
146                        )));
147                    }
148
149                    if g.get_player1().is_empty() {
150                        g.set_player1(signer);
151                    } else if g.get_player2().is_empty() {
152                        g.set_player2(signer)
153                    }
154
155                    g.mark_space(payload.get_space())?;
156                    g.update_state()?;
157
158                    g.display();
159
160                    state.set_game(payload.get_name().as_str(), g)?;
161                } else {
162                    return Err(ApplyError::InvalidTransaction(String::from(
163                        "Invalid action: Take requires an existing game",
164                    )));
165                }
166            }
167            other_action => {
168                return Err(ApplyError::InvalidTransaction(format!(
169                    "Invalid action: '{}'",
170                    other_action
171                )));
172            }
173        }
174
175        Ok(())
176    }
177}
178
179#[cfg(target_arch = "wasm32")]
180// Sabre apply must return a bool
181pub fn apply(
182    request: &TpProcessRequest,
183    context: &mut dyn TransactionContext,
184) -> Result<bool, ApplyError> {
185    let handler = XoTransactionHandler::new();
186    match handler.apply(request, context) {
187        Ok(_) => Ok(true),
188        Err(err) => {
189            info!("{}", err);
190            Err(err)
191        }
192    }
193}