def _read_signer(key_filename): """Reads the given file as a hex key. Args: key_filename: The filename where the key is stored. If None, defaults to the default key for the current user. Returns: Signer: the signer Raises: CliException: If unable to read the file. """ filename = key_filename if filename is None: filename = os.path.join(os.path.expanduser('~'), '.sawtooth', 'keys', getpass.getuser() + '.priv') try: with open(filename, 'r') as key_file: signing_key = key_file.read().strip() except IOError as e: raise CliException('Unable to read key file: {}'.format(str(e))) try: private_key = Secp256k1PrivateKey.from_hex(signing_key) except ParseError as e: raise CliException('Unable to read key in file: {}'.format(str(e))) context = create_context('secp256k1') crypto_factory = CryptoFactory(context) return crypto_factory.new_signer(private_key)
def _do_config_proposal_vote(args): """Executes the 'proposal vote' subcommand. Given a key file, a proposal id and a vote value, it generates a batch of hashblock_resource transactions in a BatchList instance. The BatchList is file or submitted to a validator. """ signer = _read_signer(args.key) rest_client = RestClient(args.url) proposals = _get_proposals(rest_client) proposal = None for candidate in proposals.candidates: if candidate.proposal_id == args.proposal_id: proposal = candidate break if proposal is None: raise CliException('No proposal exists with the given id') for vote_record in proposal.votes: if vote_record.public_key == signer.get_public_key().as_hex(): raise CliException( 'A vote has already been recorded with this signing key') txn = _create_vote_txn(signer, args.proposal_id, proposal.proposal.code, args.vote_value) batch = _create_batch(signer, [txn]) batch_list = BatchList(batches=[batch]) rest_client.send_batches(batch_list)
def _submit_request(self, url, params=None, data=None, headers=None, method="GET"): """Submits the given request, and handles the errors appropriately. Args: url (str): the request to send. params (dict): params to be passed along to get/post data (bytes): the data to include in the request. headers (dict): the headers to include in the request. method (str): the method to use for the request, "POST" or "GET". Returns: tuple of (int, str): The response status code and the json parsed body, or the error message. Raises: `CliException`: If any issues occur with the URL. """ if headers is None: headers = {} if self._auth_header is not None: headers['Authorization'] = self._auth_header try: if method == 'POST': result = requests.post(url, params=params, data=data, headers=headers) elif method == 'GET': result = requests.get(url, params=params, data=data, headers=headers) result.raise_for_status() return (result.status_code, result.json()) except requests.exceptions.HTTPError as e: return (e.response.status_code, e.response.reason) except RemoteDisconnected as e: raise CliException(e) except (requests.exceptions.MissingSchema, requests.exceptions.InvalidURL) as e: raise CliException(e) except requests.exceptions.ConnectionError as e: raise CliException( ('Unable to connect to "{}": ' 'make sure URL is correct').format(self._base_url))
def main(prog_name=os.path.basename(sys.argv[0]), args=None, with_loggers=True): parser = create_parser(prog_name) if args is None: args = sys.argv[1:] args = parser.parse_args(args) load_cli_config(args) if with_loggers is True: if args.verbose is None: verbose_level = 0 else: verbose_level = args.verbose setup_loggers(verbose_level=verbose_level) if args.command == 'keygen': do_keygen(args) elif args.command == 'units': do_units(args) elif args.command == 'events': do_events(args) elif args.command == 'resource': do_resource(args) else: raise CliException("invalid command: {}".format(args.command))
def _do_config_proposal_create(args): """Executes the 'proposal create' subcommand. Given a key file, and a series of code/value pairs, it generates batches of hashblock_resource transactions in a BatchList instance. The BatchList is either stored to a file or submitted to a validator, depending on the supplied CLI arguments. """ resources = [s.split('=', 1) for s in args.resource] signer = _read_signer(args.key) txns = [_create_propose_txn(signer, resource) for resource in resources] batch = _create_batch(signer, txns) batch_list = BatchList(batches=[batch]) if args.output is not None: try: with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) except IOError as e: raise CliException('Unable to write to batch file: {}'.format( str(e))) elif args.url is not None: rest_client = RestClient(args.url) rest_client.send_batches(batch_list) else: raise AssertionError('No target for create set.')
def main(prog_name=os.path.basename(sys.argv[0]), args=None, with_loggers=True): parser = create_parser(prog_name) if args is None: args = sys.argv[1:] args = parser.parse_args(args) if with_loggers is True: if args.verbose is None: verbose_level = 0 else: verbose_level = args.verbose setup_loggers(verbose_level=verbose_level) if args.subcommand == 'proposal' and args.proposal_cmd == 'create': _do_config_proposal_create(args) elif args.subcommand == 'proposal' and args.proposal_cmd == 'list': _do_config_proposal_list(args) elif args.subcommand == 'proposal' and args.proposal_cmd == 'vote': _do_config_proposal_vote(args) elif args.subcommand == 'genesis': _do_config_genesis(args) else: raise CliException('"{}" is not a valid subcommand of "config"'.format( args.subcommand))
def main(prog_name=os.path.basename(sys.argv[0]), args=None, with_loggers=True): parser = create_txq_cli_parser(create_parent_parser(prog_name)) # parser = create_parser(prog_name) if args is None: args = sys.argv[1:] args = parser.parse_args(args) if with_loggers is True: if args.verbose is None: verbose_level = 0 else: verbose_level = args.verbose setup_loggers(verbose_level=verbose_level) # print(args) if args.cmd in INITIATE_CMDSET: _do_match_initiate(args) elif args.cmd in RECIPROCATE_CMDSET: _do_match_reciprocate(args) elif args.cmd == 'list': listing_of(args.listtype, args.format, RestClient(args.url)) else: raise CliException('"{}" is not a valid subcommand of "event"'.format( args.cmd))
def _read_key(key_filename): """Reads the given file as a hex key. Args: key_filename: The filename where the key is stored. If None, defaults to the default key for the current user. Returns: Key: the public key Raises: CliException: If unable to read the file. """ filename = key_filename if filename is None: filename = os.path.join(os.path.expanduser('~'), '.sawtooth', 'keys', getpass.getuser() + '.pub') try: with open(filename, 'r') as key_file: public_key = key_file.read().strip() except IOError as e: raise CliException('Unable to read key file: {}'.format(str(e))) return public_key
def _do_resource_list(args): """Lists the current on-chain resource values. """ rest_client = RestClient(args.url) state = rest_client.list_state(subtree=RESOURCE_NAMESPACE) prefix = args.filter head = state['head'] state_values = state['data'] printable_resource = [] proposals_address = _key_to_address('hashblock.resource.vote.proposals') for state_value in state_values: if state_value['address'] == proposals_address: # This is completely internal resource and we won't list it here continue decoded = b64decode(state_value['data']) resource = Resource() resource.ParseFromString(decoded) for entry in resource.entries: if entry.key.startswith(prefix): printable_resource.append(entry) printable_resource.sort(key=lambda s: s.key) if args.format == 'default': tty_width = tty.width() for resource in printable_resource: # Set value width to the available terminal space, or the min width width = tty_width - len(resource.key) - 3 width = width if width > _MIN_PRINT_WIDTH else _MIN_PRINT_WIDTH value = (resource.value[:width] + '...' if len(resource.value) > width else resource.value) print('{}: {}'.format(resource.key, value)) elif args.format == 'csv': try: writer = csv.writer(sys.stdout, quoting=csv.QUOTE_ALL) writer.writerow(['KEY', 'VALUE']) for resource in printable_resource: writer.writerow([resource.key, resource.value]) except csv.Error: raise CliException('Error writing CSV') elif args.format == 'json' or args.format == 'yaml': resource_snapshot = { 'head': head, 'resource': {resource.key: resource.value for resource in printable_resource} } if args.format == 'json': print(json.dumps(resource_snapshot, indent=2, sort_keys=True)) else: print(yaml.dump(resource_snapshot, default_flow_style=False)[0:-1]) else: raise AssertionError('Unknown format {}'.format(args.format))
def _do_config_genesis(args): signer = _read_signer(args.key) public_key = signer.get_public_key().as_hex() authorized_keys = args.authorized_key if args.authorized_key else \ [public_key] if public_key not in authorized_keys: authorized_keys.append(public_key) txns = [] txns.append( _create_propose_txn(signer, ('hashblock.resource.vote.authorized_keys', ','.join(authorized_keys)))) if args.approval_threshold is not None: if args.approval_threshold < 1: raise CliException('approval threshold must not be less than 1') if args.approval_threshold > len(authorized_keys): raise CliException( 'approval threshold must not be greater than the number of ' 'authorized keys') txns.append( _create_propose_txn(signer, ('hashblock.resource.vote.approval_threshold', str(args.approval_threshold)))) batch = _create_batch(signer, txns) batch_list = BatchList(batches=[batch]) try: with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) print('Generated {}'.format(args.output)) except IOError as e: raise CliException('Unable to write to batch file: {}'.format(str(e)))
def _get(self, path, **queries): code, json_result = self._submit_request( self._base_url + path, params=self._format_queries(queries), ) # concat any additional pages of data while code == 200 and 'next' in json_result.get('paging', {}): previous_data = json_result.get('data', []) code, json_result = self._submit_request( json_result['paging']['next']) json_result['data'] = previous_data + json_result.get('data', []) if code == 200: return json_result elif code == 404: raise CliException( '{}: There is no resource with the identifier "{}"'.format( self._base_url, path.split('/')[-1])) else: raise CliException("{}: {} {}".format(self._base_url, code, json_result))
def _get_data(self, path, **queries): url = self._base_url + path params = self._format_queries(queries) while url: code, json_result = self._submit_request( url, params=params, ) if code == 404: raise CliException( '{}: There is no resource with the identifier "{}"'.format( self._base_url, path.split('/')[-1])) elif code != 200: raise CliException("{}: {} {}".format(self._base_url, code, json_result)) for item in json_result.get('data', []): yield item url = json_result['paging'].get('next', None)
def _post(self, path, data, **queries): if isinstance(data, bytes): headers = {'Content-Type': 'application/octet-stream'} else: data = json.dumps(data).encode() headers = {'Content-Type': 'application/json'} headers['Content-Length'] = '%d' % len(data) code, json_result = self._submit_request( self._base_url + path, params=self._format_queries(queries), data=data, headers=headers, method='POST') if code == 200 or code == 201 or code == 202: return json_result else: raise CliException("({}): {}".format(code, json_result))
def _do_events_list(args): """Lists the current on-chain event values. """ rest_client = RestClient(args.url) state_leaf = rest_client.list_state(RECIPROCATE_LIST_ADDRESS) printable_events = [] for event_state_leaf in state_leaf['data']: if event_state_leaf is not None: decoded = b64decode(event_state_leaf['data']) event = MTXQ() event.ParseFromString(decoded) printable_events.append([event_state_leaf['address'], event]) if args.format == 'default': for event in printable_events: i_value = int.from_bytes(event[1].initiateEvent.quantity.value, byteorder='little') i_value_units = _hash_reverse_lookup( int.from_bytes(event[1].initiateEvent.quantity.valueUnit, byteorder='little')) i_value_unit = i_value_units[0] if i_value > 1 and i_value_unit.endswith('s') == False: i_value_unit = i_value_units[1] i_resource_unit = _hash_reverse_lookup( int.from_bytes(event[1].initiateEvent.quantity.resourceUnit, byteorder='little'))[0] n_value = int.from_bytes(event[1].ratio.numerator.value, byteorder='little') n_value_units = _hash_reverse_lookup( int.from_bytes(event[1].ratio.numerator.valueUnit, byteorder='little')) n_value_unit = n_value_units[0] n_resource_unit = _hash_reverse_lookup( int.from_bytes(event[1].ratio.numerator.resourceUnit, byteorder='little'))[0] d_value = int.from_bytes(event[1].ratio.denominator.value, byteorder='little') d_value_units = _hash_reverse_lookup( int.from_bytes(event[1].ratio.denominator.valueUnit, byteorder='little')) d_value_unit = d_value_units[0] if d_value > 1 and d_value_unit.endswith('s') == False: d_value_unit = d_value_units[1] d_resource_unit = _hash_reverse_lookup( int.from_bytes(event[1].ratio.denominator.resourceUnit, byteorder='little'))[0] r_value = int.from_bytes(event[1].quantity.value, byteorder='little') r_value_units = _hash_reverse_lookup( int.from_bytes(event[1].quantity.valueUnit, byteorder='little')) r_value_unit = r_value_units[0] r_resource_unit = _hash_reverse_lookup( int.from_bytes(event[1].quantity.resourceUnit, byteorder='little'))[0] print('{}\n\r\t({}.{}{} * {}{}{}) / {}.{}{} = {}{}{}'.format( event[0], i_value, i_value_unit, i_resource_unit, n_value_unit, n_value, n_resource_unit, d_value, d_value_unit, d_resource_unit, r_value_unit, r_value, r_resource_unit)) elif args.format == 'csv': try: writer = csv.writer(sys.stdout, quoting=csv.QUOTE_ALL) writer.writerow(['KEY', 'VALUE']) for event in printable_events: writer.writerow([event.key, event.value]) except csv.Error: raise CliException('Error writing CSV') elif args.format == 'json' or args.format == 'yaml': events_snapshot = { 'head': head, 'events': {event.key: event.value for event in printable_events} } if args.format == 'json': print(json.dumps(events_snapshot, indent=2, sort_keys=True)) else: print(yaml.dump(events_snapshot, default_flow_style=False)[0:-1]) else: raise AssertionError('Unknown format {}'.format(args.format))
def do_keygen(args): if args.key_name is not None: key_name = args.key_name else: key_name = getpass.getuser() if args.key_dir is not None: key_dir = args.key_dir if not os.path.exists(key_dir): raise CliException('no such directory: {}'.format(key_dir)) else: key_dir = os.path.join(os.path.expanduser('~'), '.sawtooth', 'keys') if not os.path.exists(key_dir): if not args.quiet: print('creating key directory: {}'.format(key_dir)) try: os.makedirs(key_dir, 0o755) except IOError as e: raise CliException('IOError: {}'.format(str(e))) priv_filename = os.path.join(key_dir, key_name + '.priv') pub_filename = os.path.join(key_dir, key_name + '.pub') if not args.force: file_exists = False for filename in [priv_filename, pub_filename]: if os.path.exists(filename): file_exists = True print('file exists: {}'.format(filename), file=sys.stderr) if file_exists: raise CliException( 'files exist, rerun with --force to overwrite existing files') context = create_context('secp256k1') private_key = context.new_random_private_key() public_key = context.get_public_key(private_key) try: priv_exists = os.path.exists(priv_filename) with open(priv_filename, 'w') as priv_fd: if not args.quiet: if priv_exists: print('overwriting file: {}'.format(priv_filename)) else: print('writing file: {}'.format(priv_filename)) priv_fd.write(private_key.as_hex()) priv_fd.write('\n') # Set the private key u+rw g+r os.chmod(priv_filename, 0o640) pub_exists = os.path.exists(pub_filename) with open(pub_filename, 'w') as pub_fd: if not args.quiet: if pub_exists: print('overwriting file: {}'.format(pub_filename)) else: print('writing file: {}'.format(pub_filename)) pub_fd.write(public_key.as_hex()) pub_fd.write('\n') # Set the public key u+rw g+r o+r os.chmod(pub_filename, 0o644) except IOError as ioe: raise CliException('IOError: {}'.format(str(ioe)))
def _do_match_reciprocate(args): """Executes a reciprocate type subcommand. Given a key file, an event id, a quantity, and a ratop, it generates a batch of hashblock-match transactions in a BatchList instance. The BatchList is file or submitted to a validator. """ signer = _read_signer(args.skey) rest_client = RestClient(args.url) unmatched_events = _get_unmatched_event_list( exchange_list_for('utxq', args.format, rest_client)) initiate_event_id = None for unmatched_event in unmatched_events: if unmatched_event['event_id'] == args.utxq: initiate_event_id = unmatched_event break if initiate_event_id is None: raise CliException( 'No unmatched initiating event exists with the given id:{}'.format( args.utxq)) quantities = args.expr if quantities[1] != '@' and quantities[3].lower() != 'for': raise AssertionError('Invalid specification.') r_quantity = Quantity() event_quantity = parser.parse(quantities[0]) if event_quantity[0].lower() != 'event_quantity': raise AssertionError('Invalid quantity specification.') else: quantity_prefix = event_quantity[1] if quantity_prefix[0].lower() != 'quantity_prefix': raise AssertionError('Invalid quantity specification.') else: value_unit = quantity_prefix[1] term = quantity_prefix[2] if term[0].lower() != 'term': raise AssertionError('Invalid quantity specification.') else: component_unary = term[1] if component_unary[0].lower() != 'component_unary': raise AssertionError('Invalid quantity specification.') else: factor_annotation = component_unary[1] if factor_annotation[0].lower() != 'factor_annotation': raise AssertionError('Invalid quantity specification.') else: r_quantity.value = (int( factor_annotation[1])).to_bytes(2, byteorder='little') r_quantity.valueUnit = (int( hash_lookup[value_unit])).to_bytes( 2, byteorder='little') r_quantity.resourceUnit = (int( hash_lookup[factor_annotation[2]])).to_bytes( 2, byteorder='little') numerator = Quantity() event_quantity = parser.parse(quantities[2]) if event_quantity[0].lower() != 'event_quantity': raise AssertionError('Invalid quantity specification.') else: quantity_prefix = event_quantity[1] if quantity_prefix[0].lower() != 'quantity_prefix': raise AssertionError('Invalid quantity specification.') else: value_unit = quantity_prefix[1] term = quantity_prefix[2] if term[0].lower() != 'term': raise AssertionError('Invalid quantity specification.') else: component_unary = term[1] if component_unary[0].lower() != 'component_unary': raise AssertionError('Invalid quantity specification.') else: factor_annotation = component_unary[1] if factor_annotation[0].lower() != 'factor_annotation': raise AssertionError('Invalid quantity specification.') else: numerator.value = (int(factor_annotation[1])).to_bytes( 2, byteorder='little') numerator.valueUnit = (int( hash_lookup[value_unit])).to_bytes( 2, byteorder='little') numerator.resourceUnit = (int( hash_lookup[factor_annotation[2]])).to_bytes( 2, byteorder='little') denominator = Quantity() event_quantity = parser.parse(quantities[4]) if event_quantity[0].lower() != 'event_quantity': raise AssertionError('Invalid quantity specification.') else: quantity = event_quantity[1] if quantity[0].lower() != 'quantity': raise AssertionError('Invalid quantity specification.') else: term_binary = quantity[1] if term_binary[0].lower( ) != 'term_binary' and term_binary[1].lower() != '.': raise AssertionError('Invalid quantity specification.') else: term = term_binary[2] if term[0].lower() != 'term': raise AssertionError('Invalid quantity specification.') else: component_unary = term[1] if component_unary[0].lower() != 'component_unary': raise AssertionError('Invalid quantity specification.') else: factor = component_unary[1] if factor[0].lower() != 'factor': raise AssertionError( 'Invalid quantity specification.') else: denominator.value = (int(factor[1])).to_bytes( 2, byteorder='little') component_binary = term_binary[3] if component_binary[0].lower() != 'component_binary': raise AssertionError('Invalid quantity specification.') else: annotatable = component_binary[1] resource_unit = component_binary[2] if annotatable[0].lower() != 'annotatable': raise AssertionError('Invalid quantity specification.') else: simple_unit = annotatable[1] if simple_unit[0].lower() != 'simple_unit': raise AssertionError( 'Invalid quantity specification.') else: value_unit = simple_unit[1] denominator.valueUnit = (int( hash_lookup[value_unit])).to_bytes( 2, byteorder='little') denominator.resourceUnit = (int( hash_lookup[resource_unit])).to_bytes( 2, byteorder='little') ratio = Ratio(numerator=numerator, denominator=denominator) txn = _create_reciprocate_txn(args.cmd, signer, args.utxq, r_quantity, ratio) batch = _create_batch(signer, [txn]) batch_list = BatchList(batches=[batch]) rest_client.send_batches(batch_list)