Exemple #1
0
def _do_local(transactions, expected_signer, arguments):
    """
    A transaction must be signed by the same key as the block. This
    rule takes a list of transaction indices in the block and enforces the
    rule on each. This rule is useful in combination with the other rules
    to ensure a client is not submitting transactions that should only be
    injected by the winning validator.
    """
    indices = arguments.split(",")
    txns_len = len(transactions)
    for index in indices:
        try:
            index = int(index.strip())
        except ValueError:
            LOGGER.warning(
                "Ignore, local requries one or more comma "
                "seperated integers that represent indices, not"
                " %s", arguments)
            return True

        if abs(index) >= txns_len:
            LOGGER.debug(
                "Ignore, Block does not have enough "
                "transactions to validate this rule local:%s", index)
            continue
        txn = transactions[index]
        header = TransactionHeader()
        header.ParseFromString(txn.header)

        if header.signer_public_key != expected_signer:
            LOGGER.debug(
                "Transaction at postion %s was not signed by the"
                " same key as the block.", index)
            return False
    return True
Exemple #2
0
    def _complete_batch(self, batch):
        valid = True
        dependencies = []
        for txn in batch.transactions:
            txn_header = TransactionHeader()
            txn_header.ParseFromString(txn.header)
            for dependency in txn_header.dependencies:
                # Check to see if the dependency has been seen or is in the
                # current chain (block_store)
                if dependency not in self._seen_txns and not \
                        self.block_cache.block_store.has_transaction(
                        dependency):
                    self._unsatisfied_dependency_count.inc()

                    # Check to see if the dependency has already been requested
                    if dependency not in self._requested:
                        dependencies.append(dependency)
                        self._requested[dependency] = None
                    if dependency not in self._incomplete_batches:
                        self._incomplete_batches[dependency] = [batch]
                    elif batch not in self._incomplete_batches[dependency]:
                        self._incomplete_batches[dependency] += [batch]
                    valid = False
        if not valid:
            self.gossip.broadcast_batch_by_transaction_id_request(dependencies)

        return valid
Exemple #3
0
def _do_nofx(transactions, arguments):
    """
    Only N of transaction type X may be included in a block. The first
    argument must be interpretable as an integer. The second argument is
    interpreted as the name of a transaction family. For example, the
    string "NofX:2,intkey" means only allow 2 intkey transactions per
    block.
    """
    try:
        num, family = arguments.split(',')
        limit = int(num.strip())
    except ValueError:
        LOGGER.warning(
            "Ignore, NofX requires arguments in the format "
            "int,family not %s", arguments)
        return True
    count = 0
    family = family.strip()
    for txn in transactions:
        header = TransactionHeader()
        header.ParseFromString(txn.header)
        if header.family_name == family:
            count += 1

        if count > limit:
            LOGGER.debug("Too many transactions of type %s", family)
            return False

    return True
Exemple #4
0
    def check_for_transaction_dependencies(self, transactions):
        """Check that all explicit dependencies in all transactions passed have
        been satisfied."""
        dependencies = []
        txn_ids = []
        for txn in transactions:
            txn_ids.append(txn.header_signature)
            txn_hdr = TransactionHeader()
            txn_hdr.ParseFromString(txn.header)
            dependencies.extend(txn_hdr.dependencies)

        for dep in dependencies:
            # Check for dependency within the given block's batches
            if dep in txn_ids:
                continue

            # Check for dependency in the uncommitted blocks
            if dep in self.uncommitted_txn_ids:
                continue

            # Check for dependency in the committe blocks
            if self.block_store.has_transaction(dep):
                committed_block =\
                    self.block_store.get_block_by_transaction_id(dep)

                # Make sure the block wouldn't be uncomitted if the given block
                # were uncommitted
                if self._block_in_chain(committed_block):
                    continue

            raise MissingDependency(dep)
Exemple #5
0
def is_valid_batch(batch):
    # validate batch signature
    header = BatchHeader()
    header.ParseFromString(batch.header)

    context = create_context('secp256k1')
    public_key = Secp256k1PublicKey.from_hex(header.signer_public_key)
    if not context.verify(batch.header_signature,
                          batch.header,
                          public_key):
        LOGGER.debug("batch failed signature validation: %s",
                     batch.header_signature)
        return False

    # validate all transactions in batch
    for txn in batch.transactions:
        if not is_valid_transaction(txn):
            return False

        txn_header = TransactionHeader()
        txn_header.ParseFromString(txn.header)
        if txn_header.batcher_public_key != header.signer_public_key:
            LOGGER.debug("txn batcher public_key does not match signer"
                         "public_key for batch: %s txn: %s",
                         batch.header_signature,
                         txn.header_signature)
            return False

    return True
Exemple #6
0
    def next_transaction(self):
        with self._condition:
            # We return the next transaction which hasn't been scheduled and
            # is not blocked by a dependency.

            next_txn = None
            for txn in self._unscheduled_transactions():
                txn_id = txn.header_signature

                if (self._has_predecessors(txn_id)
                        or self._is_outstanding(txn_id)):
                    continue

                header = TransactionHeader()
                header.ParseFromString(txn.header)
                deps = tuple(header.dependencies)

                if self._dependency_not_processed(deps):
                    continue

                if self._txn_failed_by_dep(deps):
                    self._txns_available.remove(txn)
                    self._txn_results[txn_id] = \
                        TxnExecutionResult(
                            signature=txn_id,
                            is_valid=False,
                            context_id=None,
                            state_hash=None)
                    continue

                if not self._txn_is_in_valid_batch(txn_id) and \
                        self._can_fail_fast(txn_id):
                    self._txn_results[txn_id] = \
                        TxnExecutionResult(False, None, None)
                    self._txns_available.remove(txn)
                    continue

                next_txn = txn
                break

            if next_txn is not None:
                bases = self._get_initial_state_for_transaction(next_txn)

                info = TxnInformation(txn=next_txn,
                                      state_hash=self._first_state_hash,
                                      base_context_ids=bases)
                self._scheduled.append(next_txn.header_signature)
                self._txns_available.remove(next_txn)
                self._scheduled_txn_info[next_txn.header_signature] = info
                return info
            return None
Exemple #7
0
    def create_batch(self, block_info):
        payload = BlockInfoTxn(block=block_info).SerializeToString()
        public_key = self._signer.get_public_key().as_hex()
        header = TransactionHeader(
            signer_public_key=public_key,
            family_name=FAMILY_NAME,
            family_version=FAMILY_VERSION,
            inputs=[CONFIG_ADDRESS, BLOCK_INFO_NAMESPACE],
            outputs=[CONFIG_ADDRESS, BLOCK_INFO_NAMESPACE],
            dependencies=[],
            payload_sha512=hashlib.sha512(payload).hexdigest(),
            batcher_public_key=public_key,
        ).SerializeToString()

        transaction_signature = self._signer.sign(header)

        transaction = Transaction(
            header=header,
            payload=payload,
            header_signature=transaction_signature,
        )

        header = BatchHeader(
            signer_public_key=public_key,
            transaction_ids=[transaction_signature],
        ).SerializeToString()

        batch_signature = self._signer.sign(header)

        return Batch(
            header=header,
            transactions=[transaction],
            header_signature=batch_signature,
        )
Exemple #8
0
 def _check_transaction_dependencies(self, txn, committed_txn_cache):
     """Check that all this transactions dependencies are present.
     :param txn: the transaction to check
     :param committed_txn_cache: The cache holding the set of committed
     transactions to check against.
     :return: Boolean, True if dependencies checkout, False otherwise.
     """
     txn_hdr = TransactionHeader()
     txn_hdr.ParseFromString(txn.header)
     for dep in txn_hdr.dependencies:
         if dep not in committed_txn_cache:
             LOGGER.debug(
                 "Transaction rejected due missing dependency, "
                 "transaction %s depends on %s", txn.header_signature, dep)
             return False
     return True
    def check_off_chain_transaction_roles(self, transactions):
        """ Check the transaction signing key against the allowed off-chain
            transactor permissions. The roles being checked are the following,
            from first to last:
                "transactor.transaction_signer.<TP_Name>"
                "transactor.transaction_signer"
                "transactor"

            The first role that is set will be the one used to enforce if the
            transaction signer is allowed.

            Args:
                transactions (List of Transactions): The transactions that are
                    being verified.
        """
        policy = None
        if "transactor.transaction_signer" in self._permissions:
            policy = self._permissions["transactor.transaction_signer"]

        elif "transactor" in self._permissions:
            policy = self._permissions["transactor"]

        for transaction in transactions:
            header = TransactionHeader()
            header.ParseFromString(transaction.header)
            family_role = "transactor.transaction_signer." + \
                header.family_name
            family_policy = None
            if family_role in self._permissions:
                family_policy = self._permissions[family_role]

            if family_policy is not None:
                if not self._allowed(header.signer_public_key, family_policy):
                    LOGGER.debug(
                        "Transaction Signer: %s is not permitted"
                        "by local configuration.", header.signer_public_key)
                    return False

            elif policy is not None:
                if not self._allowed(header.signer_public_key, policy):
                    LOGGER.debug(
                        "Transaction Signer: %s is not permitted"
                        "by local configuration.", header.signer_public_key)
                    return False

        return True
Exemple #10
0
def is_valid_transaction(txn):
    # validate transactions signature
    header = TransactionHeader()
    header.ParseFromString(txn.header)

    context = create_context('secp256k1')
    public_key = Secp256k1PublicKey.from_hex(header.signer_public_key)
    if not context.verify(txn.header_signature,
                          txn.header,
                          public_key):
        LOGGER.debug("transaction signature invalid for txn: %s",
                     txn.header_signature)
        return False

    # verify the payload field matches the header
    txn_payload_sha512 = hashlib.sha512(txn.payload).hexdigest()
    if txn_payload_sha512 != header.payload_sha512:
        LOGGER.debug("payload doesn't match payload_sha512 of the header"
                     "for txn: %s", txn.header_signature)
        return False

    return True
Exemple #11
0
def _do_xaty(transactions, arguments):
    """
    A transaction of type X must be in the block at position Y. The
    first argument is interpreted as the name of a transaction family.
    The second argument must be interpretable as an integer and defines
    the index of the transaction in the block that must be checked.
    Negative numbers can be used and count backwards from the last
    transaction in the block. The first transaction in the block has
    index 0. The last transaction in the block has index -1. If abs(Y)
    is larger than the number of transactions per block, then there
    would not be a transaction of type X at Y and the block would be
    invalid. For example, the string "XatY:intkey,0" means the first
    transaction in the block must be an intkey transaction.
    """
    try:
        family, num = arguments.split(',')
        position = int(num.strip())
    except ValueError:
        LOGGER.warning(
            "Ignore, XatY requires arguments in the format "
            "family,position not %s", arguments)
        return True

    family = family.strip()
    if abs(position) >= len(transactions):
        LOGGER.debug(
            "Block does not have enough transactions to "
            "validate this rule XatY:%s", arguments)
        return False
    txn = transactions[position]

    header = TransactionHeader()
    header.ParseFromString(txn.header)
    if header.family_name != family:
        LOGGER.debug("Transaction at postion %s is not of type %s", position,
                     family)
        return False
    return True
 def _get_dependencies(self, transaction):
     header = TransactionHeader()
     header.ParseFromString(transaction.header)
     return list(header.dependencies)
Exemple #13
0
    def add_batch(self, batch, state_hash=None, required=False):
        with self._condition:
            if self._final:
                raise SchedulerError('Invalid attempt to add batch to '
                                     'finalized scheduler; batch: {}'.format(
                                         batch.header_signature))
            if not self._batches:
                self._least_batch_id_wo_results = batch.header_signature

            preserve = required
            if not required:
                # If this is the first non-required batch, it is preserved for
                # the schedule to be completed (i.e. no empty schedules in the
                # event of unschedule_incomplete_batches being called before
                # the first batch is completed).
                preserve = _first(
                    filterfalse(lambda sb: sb.required,
                                self._batches_by_id.values())) is None

            self._batches.append(batch)
            self._batches_by_id[batch.header_signature] = \
                _AnnotatedBatch(batch, required=required, preserve=preserve)
            for txn in batch.transactions:
                self._batches_by_txn_id[txn.header_signature] = batch
                self._txns_available.append(txn)
                self._transactions[txn.header_signature] = txn

            if state_hash is not None:
                b_id = batch.header_signature
                self._batches_with_state_hash[b_id] = state_hash

            # For dependency handling: First, we determine our dependencies
            # based on the current state of the predecessor tree.  Second,
            # we update the predecessor tree with reader and writer
            # information based on input and outputs.
            for txn in batch.transactions:
                header = TransactionHeader()
                header.ParseFromString(txn.header)

                # Calculate predecessors (transaction ids which must come
                # prior to the current transaction).
                predecessors = self._find_input_dependencies(header.inputs)
                predecessors.extend(
                    self._find_output_dependencies(header.outputs))

                txn_id = txn.header_signature
                # Update our internal state with the computed predecessors.
                self._txn_predecessors[txn_id] = set(predecessors)

                # Update the predecessor tree.
                #
                # Order of reader/writer operations is relevant.  A writer
                # may overshadow a reader.  For example, if the transaction
                # has the same input/output address, the end result will be
                # this writer (txn.header_signature) stored at the address of
                # the predecessor tree.  The reader information will have been
                # discarded.  Write operations to partial addresses will also
                # overshadow entire parts of the predecessor tree.
                #
                # Thus, the order here (inputs then outputs) will cause the
                # minimal amount of relevant information to be stored in the
                # predecessor tree, with duplicate information being
                # automatically discarded by the set_writer() call.
                for address in header.inputs:
                    self._predecessor_tree.add_reader(address, txn_id)
                for address in header.outputs:
                    self._predecessor_tree.set_writer(address, txn_id)

            self._condition.notify_all()
    def is_transaction_signer_authorized(self, transactions, state_root,
                                         from_state):
        """ Check the transaction signing key against the allowed transactor
            permissions. The roles being checked are the following, from first
            to last:
                "transactor.transaction_signer.<TP_Name>"
                "transactor.transaction_signer"
                "transactor"
                "default"

            The first role that is set will be the one used to enforce if the
            transaction signer is allowed.

            Args:
                transactions (List of Transactions): The transactions that are
                    being verified.
                state_root(string): The state root of the previous block. If
                    this is None, the current state root hash will be
                    retrieved.
                from_state (bool): Whether the identity value should be read
                    directly from state, instead of using the cached values.
                    This should be used when the state_root passed is not from
                    the current chain head.
        """
        role = None
        if role is None:
            role = self._cache.get_role("transactor.transaction_signer",
                                        state_root, from_state)

        if role is None:
            role = self._cache.get_role("transactor", state_root, from_state)

        if role is None:
            policy_name = "default"
        else:
            policy_name = role.policy_name

        policy = self._cache.get_policy(policy_name, state_root, from_state)

        family_roles = {}
        for transaction in transactions:
            header = TransactionHeader()
            header.ParseFromString(transaction.header)
            family_policy = None
            if header.family_name not in family_roles:
                role = self._cache.get_role(
                    "transactor.transaction_signer." + header.family_name,
                    state_root, from_state)

                if role is not None:
                    family_policy = self._cache.get_policy(
                        role.policy_name, state_root, from_state)
                family_roles[header.family_name] = family_policy
            else:
                family_policy = family_roles[header.family_name]

            if family_policy is not None:
                if not self._allowed(header.signer_public_key, family_policy):
                    LOGGER.debug("Transaction Signer: %s is not permitted.",
                                 header.signer_public_key)
                    return False
            else:
                if policy is not None:
                    if not self._allowed(header.signer_public_key, policy):
                        LOGGER.debug(
                            "Transaction Signer: %s is not permitted.",
                            header.signer_public_key)
                        return False
        return True