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