Пример #1
0
def is_block_chained(previous_block, next_block):
    follows_previous = next_block.previous_block == previous_block.id
    is_plus_one = next_block.height == previous_block.height + 1

    previous_slot = slots.get_slot_number(
        previous_block.height, previous_block.timestamp
    )
    next_slot = slots.get_slot_number(next_block.height, next_block.timestamp)
    is_after_previous_slot = previous_slot < next_slot
    return follows_previous and is_plus_one and is_after_previous_slot
Пример #2
0
    async def post_block(self, data, ip):
        # TODO: Wrap everything in try except

        # TODO: Validate request data that it's correct block structure
        block_data = data["block"]
        # if not block_data:
        #     raise Exception(
        #         "There was no block in request to the /peer/blocks endpoint"
        #     )

        block = Block.from_dict(block_data)
        self.socket.log_info(
            "Received new block at height %s with %s transactions, from %s",
            block.height,
            block.number_of_transactions,
            ip,
        )

        is_verified, errors = block.verify()
        if not is_verified:
            self.socket.log_error(errors)  # TODO:
            raise Exception("Verification failed")

        last_block = self.db.get_last_block()

        if last_block.height >= block.height:
            self.socket.log_info(
                "Received block with height %s which was already processed. Our last "
                "block height %s. Skipping process queue.",
                block.height,
                last_block.height,
            )
            return

        if self.process_queue.block_exists(block):
            self.socket.log_info(
                "Received block with height %s is already in process queue.",
                block.height,
            )
            return

        current_slot = slots.get_slot_number(last_block.height,
                                             time.get_time())
        received_slot = slots.get_slot_number(last_block.height,
                                              block.timestamp)

        if current_slot >= received_slot and is_block_chained(
                last_block, block):
            # Put the block to process queue
            self.process_queue.push_block(block)
        else:
            self.socket.log_info(
                "Discarded block %s because it takes a future slot",
                block.height)
Пример #3
0
 async def get_status(self):
     last_block = self.db.get_last_block()
     return {
         "state": {
             "height":
             last_block.height if last_block else 0,
             "forgingAllowed":
             slots.is_forging_allowed(last_block.height, time.get_time()),
             "currentSlot":
             slots.get_slot_number(last_block.height, time.get_time()),
             "header":
             last_block.get_header() if last_block else {},
         },
         "config": {
             "version": get_chain_version(),
             "network": {
                 "version": config.network["pubKeyHash"],
                 "name": config.network["name"],
                 "nethash": config.network["nethash"],
                 "explorer": config.network["client"]["explorer"],
                 "token": {
                     "name": config.network["client"]["token"],
                     "symbol": config.network["client"]["symbol"],
                 },
             },
         },
     }
Пример #4
0
    def consume_queue(self):
        while True:
            serialized_block = self.process_queue.pop_block()
            if serialized_block:
                last_block = self.database.get_last_block()
                block = Block.from_serialized(serialized_block)
                status = self.process_block(block, last_block)
                logger.info(status)
                if status in [BLOCK_ACCEPTED, BLOCK_DISCARDED_BUT_CAN_BE_BROADCASTED]:
                    # TODO: Broadcast only current block
                    milestone = config.get_milestone(block.height)
                    current_slot = slots.get_slot_number(block.height, time.get_time())
                    if current_slot * milestone["blocktime"] <= block.timestamp:
                        # TODO: THIS IS MISSING
                        logger.error("MISSING: IMPLEMENT BROADCASTING")
            else:
                # TODO: change this
                logger.info("Nothing to process. Sleeping for 1 sec")
                sleep(1)

            # Our chain can get out of sync when it doesn't receive all the blocks
            # to the p2p endpoint, so if we're not in sync, force sync to the last
            # block
            last_block = self.database.get_last_block()
            if not self.is_synced(last_block):
                logger.info("Force syncing with the network as we got out of sync")
                self.sync_chain()
                logger.info("Done force syncing")
Пример #5
0
    def _validate_generator(self, block):
        delegates = self.database.get_active_delegates(block.height)
        if not delegates:
            logger.error("Could not find delegates for block height %s", block.height)
            return False
        slot_number = slots.get_slot_number(block.height, block.timestamp)
        generator_username = self.database.wallets.find_by_public_key(
            block.generator_public_key
        ).username
        forging_delegate = None

        forging_delegate = delegates[slot_number % len(delegates)]

        if (
            forging_delegate
            and forging_delegate.public_key != block.generator_public_key
        ):
            forging_username = self.database.wallets.find_by_public_key(
                forging_delegate.public_key
            ).username
            logger.error(
                "Delegate %s (%s) not allowed to forge, should be %s (%s)",
                generator_username,
                block.generator_public_key,
                forging_username,
                forging_delegate.public_key,
            )
            return False
        # TODO: this seems weird as we can't decide if delegate is allowed to forge, but
        # we still accept it as a valid generator
        if not forging_delegate:
            logger.info(
                "Could not decide if delegate %s (%s) is allowed to forge block %s",
                generator_username,
                block.generator_public_key,
                block.height,
            )
            # TODO: This return is not in the official ark core implementation!
            return False

        logger.info(
            "Delegate %s (%s) allowed to forge block %s",
            generator_username,
            block.generator_public_key,
            block.height,
        )
        return True
Пример #6
0
    def verify(self):
        errors = []

        # TODO: find a better way to get milestone data
        milestone = config.get_milestone(self.height)
        # Check that the previous block is set if it's not a genesis block
        if self.height > 1 and not self.previous_block:
            errors.append("Invalid previous block")

        # Chech that the block reward matches with the one specified in config
        if self.reward != milestone["reward"]:
            errors.append("Invalid block reward: {} expected: {}".format(
                self.reward, milestone["reward"]))

        # Verify block signature
        is_valid_signature = self.verify_signature()
        if not is_valid_signature:
            errors.append("Failed to verify block signature")

        # Check if version is correct on the block
        if self.version != milestone["block"]["version"]:
            errors.append("Invalid block version")

        # Check that the block timestamp is not in the future
        is_invalid_timestamp = slots.get_slot_number(
            self.height, self.timestamp) > slots.get_slot_number(
                self.height, time.get_time())
        if is_invalid_timestamp:
            errors.append("Invalid block timestamp")

        # Check if all transactions are valid
        invalid_transactions = [
            trans for trans in self.transactions if not trans.verify()
        ]

        if len(invalid_transactions) > 0:
            errors.append("One or more transactions are not verified")

        # Check that number of transactions and block.number_of_transactions match
        if len(self.transactions) != self.number_of_transactions:
            errors.append("Invalid number of transactions")

        # Check that number of transactions is not too high (except for genesis block)
        if (self.height > 1 and len(self.transactions) >
                milestone["block"]["maxTransactions"]):
            errors.append("Too many transactions")

        # Check if transactions add up to the block values
        applied_transactions = []
        total_amount = 0
        total_fee = 0
        bytes_data = bytes()
        for transaction in self.transactions:
            if transaction.id in applied_transactions:
                errors.append("Encountered duplicate transaction: {}".format(
                    transaction.id))

            applied_transactions.append(transaction.id)
            total_amount += transaction.amount
            total_fee += transaction.fee
            bytes_data += unhexlify(transaction.id)

        if total_amount != self.total_amount:
            errors.append("Invalid total amount")

        if total_fee != self.total_fee:
            errors.append("Invalid total fee")

        if len(bytes_data) > milestone["block"]["maxPayload"]:
            errors.append("Payload is too large")

        if sha256(bytes_data).hexdigest() != self.payload_hash:
            errors.append("Invalid payload hash")

        return len(errors) == 0, errors