예제 #1
0
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)
예제 #2
0
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)
예제 #3
0
    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))
예제 #4
0
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))
예제 #5
0
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.')
예제 #6
0
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))
예제 #7
0
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))
예제 #8
0
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
예제 #9
0
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))
예제 #10
0
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)))
예제 #11
0
    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))
예제 #12
0
    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)
예제 #13
0
    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))
예제 #14
0
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))
예제 #15
0
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)))
예제 #16
0
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)