github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/families/battleship/sawtooth_battleship/battleship_client.py (about) 1 # Copyright 2016 Intel Corporation 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 # ------------------------------------------------------------------------------ 15 16 import logging 17 import json 18 from base64 import b64encode, b64decode 19 import hashlib 20 import time 21 import yaml 22 import requests 23 24 from sawtooth_signing import create_context 25 from sawtooth_signing import CryptoFactory 26 from sawtooth_signing import ParseError 27 from sawtooth_signing.secp256k1 import Secp256k1PrivateKey 28 29 from sawtooth_battleship.battleship_exceptions import BattleshipException 30 from sawtooth_sdk.protobuf.transaction_pb2 import TransactionHeader 31 from sawtooth_sdk.protobuf.transaction_pb2 import Transaction 32 33 from sawtooth_sdk.protobuf.batch_pb2 import BatchList 34 from sawtooth_sdk.protobuf.batch_pb2 import BatchHeader 35 from sawtooth_sdk.protobuf.batch_pb2 import Batch 36 37 # from sawtooth.client import SawtoothClient 38 39 40 LOGGER = logging.getLogger(__name__) 41 42 43 class BattleshipClient: 44 45 def __init__(self, base_url, keyfile, wait=None): 46 """ 47 Member variables: 48 _base_url 49 _private_key 50 _public_key 51 _transaction_family 52 _family_version 53 _wait 54 """ 55 self._base_url = base_url 56 57 try: 58 with open(keyfile) as fd: 59 private_key_str = fd.read().strip() 60 except OSError as err: 61 raise IOError("Failed to read keys: {}.".format(str(err))) 62 63 try: 64 private_key = Secp256k1PrivateKey.from_hex(private_key_str) 65 except ParseError as e: 66 raise BattleshipException( 67 'Unable to load private key: {}'.format(str(e))) 68 69 self._signer = CryptoFactory( 70 create_context('secp256k1')).new_signer(private_key) 71 72 self._transaction_family = "battleship" 73 self._family_version = "1.0" 74 self._wait = wait 75 76 def _send_battleship_txn(self, update): 77 """The client needs to have the same 78 defaults as the Transaction subclass 79 before it is signed inside sendtxn 80 """ 81 if 'Name' not in update: 82 raise BattleshipException('Game name required') 83 if 'Action' not in update: 84 update['Action'] = None 85 if 'Ships' not in update: 86 update['Ships'] = None 87 if update['Action'] == 'JOIN': 88 if 'Board' not in update: 89 update['Board'] = None 90 if update['Action'] == 'FIRE': 91 if 'Column' not in update: 92 update['Column'] = None 93 if 'Row' not in update: 94 update['Row'] = None 95 96 payload = json.dumps(update).encode() 97 address = self._get_address(update['Name']) 98 99 header = TransactionHeader( 100 signer_public_key=self._signer.get_public_key().as_hex(), 101 family_name=self._transaction_family, 102 family_version=self._family_version, 103 inputs=[address], 104 outputs=[address], 105 dependencies=[], 106 payload_sha512=self._sha512(payload), 107 batcher_public_key=self.signer.get_public_key().as_hex(), 108 nonce=time.time().hex().encode() 109 ).SerializeToString() 110 111 signature = self._signer.sign(header) 112 113 transaction = Transaction( 114 header=header, 115 payload=payload, 116 header_signature=signature 117 ) 118 119 batch_list = self._create_batch_list([transaction]) 120 batch_id = batch_list.batches[0].header_signature 121 122 if self._wait and self._wait > 0: 123 wait_time = 0 124 start_time = time.time() 125 response = self._send_request( 126 "batches", batch_list.SerializeToString(), 127 'application/octet-stream' 128 ) 129 while wait_time < self._wait: 130 status = self._get_status( 131 batch_id, 132 self._wait - int(wait_time) 133 ) 134 wait_time = time.time() - start_time 135 136 if status != 'PENDING': 137 return response 138 139 return response 140 141 return self._send_request( 142 "batches", batch_list.SerializeToString(), 143 'application/octet-stream') 144 145 def create(self, name, ships): 146 """ Create battleship game 147 """ 148 update = { 149 'Action': 'CREATE', 150 'Name': name, 151 'Ships': ships 152 } 153 154 return self._send_battleship_txn(update) 155 156 def join(self, name, board): 157 """ User joins battleship game 158 """ 159 update = { 160 'Action': 'JOIN', 161 'Name': name, 162 'Board': board 163 } 164 165 return self._send_battleship_txn(update) 166 167 def fire(self, name, column, row, reveal_space, reveal_nonce): 168 """ Fire at (column, row) 169 """ 170 update = { 171 'Action': 'FIRE', 172 'Name': name, 173 'Column': column, 174 'Row': row 175 } 176 177 if reveal_space is not None: 178 update['RevealSpace'] = reveal_space 179 180 if reveal_nonce is not None: 181 update['RevealNonce'] = reveal_nonce 182 183 return self._send_battleship_txn(update) 184 185 def list_games(self, auth_user=None, auth_password=None): 186 prefix = self._get_prefix() 187 188 result = self._send_request( 189 "state?address={}".format(prefix), 190 auth_user=auth_user, 191 auth_password=auth_password 192 ) 193 194 try: 195 encoded_entries = yaml.safe_load(result)["data"] 196 197 ret = {} 198 for entry in encoded_entries: 199 d = json.loads(b64decode(entry["data"]).decode()) 200 for k, v in d.items(): 201 ret[k] = v 202 203 return ret 204 205 except BaseException: 206 return None 207 208 def _sha512(self, data): 209 return hashlib.sha512(data).hexdigest() 210 211 def _get_prefix(self): 212 return self._sha512(self._transaction_family.encode('utf-8'))[0:6] 213 214 def _get_address(self, name): 215 prefix = self._get_prefix() 216 game_address = self._sha512(name.encode('utf-8'))[0:64] 217 return prefix + game_address 218 219 def _create_batch_list(self, transactions): 220 transaction_signatures = [t.header_signature for t in transactions] 221 222 header = BatchHeader( 223 signer_public_key=self._signer.get_public_key().as_hex(), 224 transaction_ids=transaction_signatures 225 ).SerializeToString() 226 227 signature = self._signer.sign(header) 228 229 batch = Batch( 230 header=header, 231 transactions=transactions, 232 header_signature=signature 233 ) 234 return BatchList(batches=[batch]) 235 236 def _get_status(self, batch_id, wait): 237 try: 238 result = self._send_request( 239 'batch_statuses?id={}&wait={}'.format(batch_id, wait)) 240 return yaml.safe_load(result)['data'][0]['status'] 241 except BaseException as err: 242 raise BattleshipException(err) 243 244 def _send_request( 245 self, suffix, data=None, 246 content_type=None, name=None, auth_user=None, auth_password=None): 247 if self._base_url.startswith("http://"): 248 url = "{}/{}".format(self._base_url, suffix) 249 else: 250 url = "http://{}/{}".format(self._base_url, suffix) 251 252 headers = {} 253 if auth_user is not None: 254 auth_string = "{}:{}".format(auth_user, auth_password) 255 b64_string = b64encode(auth_string.encode()).decode() 256 auth_header = 'Basic {}'.format(b64_string) 257 headers['Authorization'] = auth_header 258 259 if content_type is not None: 260 headers['Content-Type'] = content_type 261 262 try: 263 if data is not None: 264 result = requests.post(url, headers=headers, data=data) 265 else: 266 result = requests.get(url, headers=headers) 267 268 if result.status_code == 404: 269 raise BattleshipException("No such game: {}".format(name)) 270 271 elif not result.ok: 272 raise BattleshipException("Error {}: {}".format( 273 result.status_code, result.reason)) 274 275 except BaseException as err: 276 raise BattleshipException(err) 277 278 return result.text