Beispiel #1
0
    def serialize(self, include_signature=True):
        milestone = config.get_milestone(self.height - 1)
        if milestone["block"]["idFullSha256"]:
            if len(self.previous_block) != 64:
                raise Exception(
                    "Previous block shoud be SHA256, but found a non SHA256 block id"
                )
            self.previous_block_hex = self.previous_block.encode("utf-8")
        else:
            self.previous_block_hex = Block.to_bytes_hex(self.previous_block)

        bytes_data = bytes()
        bytes_data += write_bit32(self.version)
        bytes_data += write_bit32(self.timestamp)
        bytes_data += write_bit32(self.height)
        bytes_data += unhexlify(self.previous_block_hex)
        bytes_data += write_bit32(self.number_of_transactions)
        bytes_data += write_bit64(int(self.total_amount))
        bytes_data += write_bit64(int(self.total_fee))
        bytes_data += write_bit64(int(self.reward))
        bytes_data += write_bit32(self.payload_length)
        bytes_data += unhexlify(self.payload_hash.encode("utf-8"))
        bytes_data += unhexlify(self.generator_public_key)

        if include_signature and self.block_signature:
            bytes_data += unhexlify(self.block_signature.encode("utf-8"))

        return hexlify(bytes_data)
Beispiel #2
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")
Beispiel #3
0
    def can_be_applied_to_wallet(self, wallet, wallet_manager, block_height):
        """Checks if transaction can be applied to the wallet

        :param (Wallet) wallet: wallet you want to apply the transaction to
        :param (WalletManager) wallet_manager: wallet manager
        :param (int) block_height: current block height
        :returns (bool): True if can be applied, False otherwise
        """
        if wallet.multisignature:
            logger.error("Multi signatures are currently not supported")
            return False

        if self.sender_public_key != wallet.public_key:
            logger.error("Sender public key does not match the wallet")
            return False

        balance = wallet.balance - self.amount - self.fee
        if balance < 0:
            logger.error("Insufficient balance in the wallet")
            return False

        if wallet.second_public_key:
            if not self.verify_second_signature(wallet.second_public_key):
                logger.error("Failed to verify second-signature")
                return False
        elif self.second_signature or self.sign_signature:
            milestone = config.get_milestone(block_height)
            # Accept invalid second signature fields prior the applied patch
            if not milestone["ignoreInvalidSecondSignatureField"]:
                logger.error("Wallet does not allow second signatures")
                return False

        return True
Beispiel #4
0
    def load_active_delegate_wallets(self, height):
        max_delegates = config.get_milestone(height)["activeDelegates"]
        if height > 1 and height % max_delegates != 1:
            # TODO: exception
            raise Exception("Trying to build delegates outside of round change")

        delegate_wallets = []

        keys = self.redis.keys(self.key_for_username("*"))
        addresses = self.redis.mget(keys)
        for address in addresses:
            wallet = self.find_by_address(address.decode())
            delegate_wallets.append(wallet)

        if len(delegate_wallets) < max_delegates:
            raise Exception(
                "Expected to find {} delegates but only found {}.".format(
                    max_delegates, len(delegate_wallets)
                )
            )
        # Sort delegate wallets by balance and use public key as a tiebreaker
        # Sort wallets by balance descending and by public key ascending. Because
        # vote_balance is a number, use a negative number to sort it descending
        # and public_key to sort it ascending.
        delegate_wallets.sort(key=lambda x: (-x.vote_balance, x.public_key))

        # for wallet in delegate_wallets[:60]:
        #     print(wallet.username, wallet.public_key, wallet.balance)

        delegate_wallets = delegate_wallets[:max_delegates]
        logger.info("Loaded %s active delegates", len(delegate_wallets))
        return delegate_wallets
Beispiel #5
0
    def is_synced(self, last_block):
        """Checks if blockchain is synced to at least second to last height

        :param Block last_block: last block that is in the database
        """
        current_time = time.get_time()
        blocktime = config.get_milestone(last_block.height)["blocktime"]
        return (current_time - last_block.timestamp) < (3 * blocktime)
Beispiel #6
0
    def get_id_hex(self):
        payload_hash = unhexlify(self.serialize())
        full_hash = sha256(payload_hash).digest()
        milestone = config.get_milestone(self.height)
        if milestone["block"]["idFullSha256"]:
            return hexlify(full_hash)

        small_hash = full_hash[:8][::-1]
        return hexlify(small_hash)
Beispiel #7
0
    def block_exists(self, block):
        key = "process_queue:block:{}:{}".format(block.height, block.id)

        if self.db.exists(key):
            self.db.incr(key)
            return True
        else:
            blocktime = config.get_milestone(block.height)["blocktime"]
            # Expire the key after `blocktime` seconds
            self.db.set(key, 0, ex=blocktime)
            return False
Beispiel #8
0
    def _deserialize_previous_block(self, buff):
        """
        For deserializing previous block id, we need to check the milestone for
        previous block
        """
        milestone = config.get_milestone(self.height - 1)
        if milestone["block"]["idFullSha256"]:
            self.previous_block_hex = hexlify(buff.pop_bytes(32))
            self.previous_block = self.previous_block_hex.decode("utf-8")
        else:

            self.previous_block_hex = hexlify(buff.pop_bytes(8))
            self.previous_block = str(int(self.previous_block_hex, 16))
Beispiel #9
0
def get_slot_number(height, epoch_time):
    # TODO: find a better way to get milestone data
    milestone = config.get_milestone(height)
    return math.floor(epoch_time / milestone["blocktime"])
Beispiel #10
0
def is_forging_allowed(height, epoch_time):
    milestone = config.get_milestone(height)
    return epoch_time % milestone["blocktime"] < milestone["blocktime"] / 2
Beispiel #11
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
Beispiel #12
0
 def get_id(self):
     id_hex = self.get_id_hex()
     milestone = config.get_milestone(self.height)
     if milestone["block"]["idFullSha256"]:
         return id_hex.decode("utf-8")
     return str(int(id_hex, 16))
Beispiel #13
0
    def start(self):
        """Starts the blockchain. Depending of the state of the blockchain it will
        decide what needs to be done in order to correctly start syncing.
        """
        logger.info("Starting the blockchain")

        apply_genesis_round = False
        try:
            block = self.database.get_last_block()

            # If block is not found in the db, insert a genesis block
            if not block:
                logger.info("No block found in the database")
                block = Block.from_dict(config.genesis_block)
                if block.payload_hash != config.network["nethash"]:
                    logger.error(
                        "FATAL: The genesis block payload hash is different from "
                        "the configured nethash"
                    )
                    self.stop()
                    return

                else:
                    self.database.save_block(block)
                    apply_genesis_round = True

            logger.info("Verifying database integrity")
            is_valid = False
            errors = None
            for _ in range(5):
                is_valid, errors = self.database.verify_blockchain()
                if is_valid:
                    break
                else:
                    logger.error("Database is corrupted: {}".format(errors))
                    milestone = config.get_milestone(block.height)
                    previous_round = math.floor(
                        (block.height - 1) / milestone["activeDelegates"]
                    )
                    if previous_round <= 1:
                        raise Exception(
                            "FATAL: Database is corrupted: {}".format(errors)
                        )

                    logger.info("Rolling back to round {}".format(previous_round))
                    self.database.rollback_to_round(previous_round)
                    logger.info("Rolled back to round {}".format(previous_round))
            else:
                raise Exception(
                    "FATAL: After rolling back for 5 rounds, database is still "
                    "corrupted: {}".format(errors)
                )

            logger.info("Verified database integrity")

            # if (stateStorage.networkStart) {
            #     await blockchain.database.buildWallets(block.data.height);
            #     await blockchain.database.saveWallets(true);
            #     await blockchain.database.applyRound(block.data.height);
            #     await blockchain.transactionPool.buildWallets();

            #     return blockchain.dispatch("STARTED");
            # }

            logger.info("Last block in database: %s", block.height)

            # if the node is shutdown between round, the round has already been applied
            # so we delete it to start a new, fresh round
            if is_new_round(block.height + 1):
                current_round, _, _ = calculate_round(block.height + 1)
                logger.info(
                    "Start of new round detected %s. Removing it in order to correctly "
                    "start the chain with new round.",
                    current_round,
                )
                self.database.delete_round(current_round)

            # Rebuild wallets
            self.database.wallets.build()
            self.transaction_pool.build_wallets()

            if apply_genesis_round:
                self.database.apply_round(block.height)

            self.sync_chain()

            logger.info("Blockhain is syced!")

            # Blockchain was just synced, so remove all blocks from process queue
            # as it was just synced. We clear it only on the start of the chain, to
            # awoid processing old blocks. If we ever run sync while it's already
            # runing, we don't want to run clear after sync as that might leave us
            # with missing blocks which will cause the blockchain to always sync back
            # rather than sync by accepting block from peers.
            self.process_queue.clear()

            self.consume_queue()
        except Exception as e:
            self.stop()
            # TODO: log exception
            raise e  # TODO:
Beispiel #14
0
def _calculate_static_fee(transaction, block_height):
    fees = config.get_milestone(block_height)["fees"]["staticFees"]
    static_fee = fees[str(transaction.type)]
    if transaction.type == TRANSACTION_TYPE_MULTI_SIGNATURE:
        return static_fee * (len(transaction.asset["multisignature"]["keysgroup"]) + 1)
    return static_fee
Beispiel #15
0
def is_new_round(height):
    """Checks if height is at the start of new round
    """
    max_delegates = config.get_milestone(height)["activeDelegates"]
    return height % max_delegates == 1
Beispiel #16
0
def calculate_round(height):
    max_delegates = config.get_milestone(height)["activeDelegates"]
    current_round = math.floor((height - 1) / max_delegates) + 1
    next_round = math.floor(height / max_delegates) + 1
    return current_round, next_round, max_delegates