Beispiel #1
0
def crypto_block(crypto_transaction, crypto_transaction_2):
    # TODO: Block is not from testnet and it has wrong data and signature
    # Change it once we're able to forge blocks with our code
    block = Block(
        height=2243161,
        id="10977713934532967004",
        id_hex=b"9858aca939b17a5c",
        number_of_transactions=2,
        payload_hash=(
            "3784b953afcf936bdffd43fdf005b5732b49c1fc6b11e195c364c20b2eb06282"
        ),
        payload_length=224,
        previous_block="3112633353705641986",
        previous_block_hex=b"2b324b8b33a85802",
        reward=200000000,
        timestamp=24760440,
        total_amount=3890300,
        total_fee=70000000,
        version=0,
        generator_public_key=(
            "020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a325"
        ),
        block_signature=(
            "3045022100eee6c37b5e592e99811d588532726353592923f347c701d52912e6d583443e40022"
            "0277ffe38ad31e216ba0907c4738fed19b2071246b150c72c0a52bae4477ebe29"
        ),
    )

    crypto_transaction.block_id = "7176646138626297930"
    crypto_transaction_2.block_id = "7176646138626297930"

    block.transactions = [crypto_transaction, crypto_transaction_2]
    return block
Beispiel #2
0
def test_from_dict_raises_exception_for_wrong_type(dummy_block):
    data = deepcopy(dummy_block)
    data["id"] = float(data["id"])
    with pytest.raises(TypeError) as excinfo:
        Block.from_dict(data)
    assert str(excinfo.value) == (
        "Attribute id (<class 'float'>) must be of type (<class 'str'>, <class 'bytes'>"
        ")")
Beispiel #3
0
def test_apply_transaction_raises_if_cant_be_applied_to_sender_wallet(redis, mocker):
    manager = WalletManager()

    block = Block()
    transaction = TransferTransaction()
    transaction.type = TRANSACTION_TYPE_TRANSFER
    transaction.fee = 10000
    transaction.amount = 430000
    transaction.id = "hehe"
    transaction.sender_public_key = (
        "020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a325"
    )
    transaction.recipient_id = "AZYnpgXS3x43nxqhT4q29sZScRwZeNKLpW"

    redis.set(
        "wallets:address:AThM5PNSKdU9pu1ydqQnzRWVeNCGr8HKof",
        Wallet(
            {
                "address": "AThM5PNSKdU9pu1ydqQnzRWVeNCGr8HKof",
                "public_key": (
                    "020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a325"
                ),
                "balance": 5,
            }
        ).to_json(),
    )
    with pytest.raises(Exception) as excinfo:
        manager.apply_transaction(transaction, block)

    assert str(excinfo.value) == (
        "Can't apply transaction hehe from sender AThM5PNSKdU9pu1ydqQnzRWVeNCGr8HKof"
    )
Beispiel #4
0
 def get_block_by_id(self, block_id):
     try:
         block = Block.get(Block.id == block_id)
     except Block.DoesNotExist:
         return None
     else:
         return CryptoBlock.from_object(block)
Beispiel #5
0
def test_from_serialized_correctly_deserializes_full_data(
        dummy_block_full_hash, dummy_block):
    block = Block.from_serialized(dummy_block_full_hash)
    assert block.version == 0
    assert block.timestamp == 24760440
    assert block.height == 2243161
    assert block.previous_block_hex == b"2b324b8b33a85802"
    assert block.previous_block == "3112633353705641986"
    assert block.number_of_transactions == 2
    assert block.total_amount == 3890300
    assert block.total_fee == 70000000
    assert block.reward == 200000000
    assert block.payload_length == 224
    assert block.payload_hash == (
        "3784b953afcf936bdffd43fdf005b5732b49c1fc6b11e195c364c20b2eb06282")
    assert block.generator_public_key == (
        "020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a325")
    assert block.block_signature == (
        "3045022100eee6c37b5e592e99811d588532726353592923f347c701d52912e6d583443e40022"
        "0277ffe38ad31e216ba0907c4738fed19b2071246b150c72c0a52bae4477ebe29")
    assert block.id == "10977713934532967004"
    assert block.id_hex == b"9858aca939b17a5c"
    assert block.transactions is not None
    assert len(block.transactions) == 2
    for transaction, expected in zip(block.transactions,
                                     dummy_block["transactions"]):
        assert transaction.version == 1
        assert transaction.network == 23
        assert transaction.type == expected["type"]
        assert transaction.timestamp == expected["timestamp"]
        assert transaction.sender_public_key == expected["senderPublicKey"]
        assert transaction.fee == expected["fee"]
        assert transaction.amount == expected["amount"]
        assert transaction.asset == expected["asset"]
Beispiel #6
0
    def get_blocks(self, height, limit, serialized, with_transactions=False):
        blocks = (Block.select().where(
            Block.height.between(height,
                                 height + limit)).order_by(Block.height.asc()))

        if with_transactions:
            block_ids = [block.id for block in blocks]
            transactions = (Transaction.select().where(
                Transaction.block_id.in_(block_ids)).order_by(
                    Transaction.block_id.asc(), Transaction.sequence.asc()))

            transactions_map = defaultdict(list)
            for trans in transactions:
                # TODO: implement from_object on transaction and use that, instead of
                # creating it from serialized data.
                if serialized:
                    transactions_map[trans.block_id].append(trans.serialized)
                else:
                    transactions_map[trans.block_id].append(
                        from_serialized(trans.serialized))
        crypto_blocks = []
        for block in blocks:
            crypto_block = CryptoBlock.from_object(block)
            if with_transactions:
                crypto_block.transactions = transactions_map[block.id]
            crypto_blocks.append(crypto_block)
        return crypto_blocks
Beispiel #7
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 #8
0
 def fetch_blocks_from_height(self, from_height):
     payload = {
         "lastBlockHeight": from_height,
         "serialized": True,
         "headersOnly": False,
     }
     blocks = self._fetch("p2p.peer.getBlocks", payload)
     return [Block.from_dict(block) for block in blocks]
Beispiel #9
0
def _create_genesis_block():
    _clear_db()
    block = CryptoBlock.from_dict(config.genesis_block)

    db_block = Block.from_crypto(block)
    db_block.save(force_insert=True)

    for transaction in block.transactions:
        db_trans = Transaction.from_crypto(transaction)
        db_trans.save(force_insert=True)
Beispiel #10
0
 def get_last_block(self):
     """Get the last block
     Returns None if block can't be found.
     """
     try:
         block = Block.select().order_by(Block.height.desc()).get()
     except Block.DoesNotExist:
         return None
     else:
         crypto_block = CryptoBlock.from_object(block)
         return crypto_block
Beispiel #11
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)
Beispiel #12
0
def test_apply_transaction_force_apply_skips_can_be_applied_check(redis, mocker):
    manager = WalletManager()

    balances_mock = mocker.patch(
        "chain.plugins.database.wallet_manager.WalletManager._update_vote_balances"
    )

    mocker.patch(
        "chain.plugins.database.wallet_manager.is_transaction_exception",
        return_value=True,
    )

    block = Block()
    transaction = TransferTransaction()
    transaction.type = TRANSACTION_TYPE_TRANSFER
    transaction.fee = 10000
    transaction.amount = 430000
    transaction.id = "hehe"
    transaction.sender_public_key = (
        "020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a325"
    )
    transaction.recipient_id = "AZYnpgXS3x43nxqhT4q29sZScRwZeNKLpW"

    redis.set(
        "wallets:address:AThM5PNSKdU9pu1ydqQnzRWVeNCGr8HKof",
        Wallet(
            {
                "address": "AThM5PNSKdU9pu1ydqQnzRWVeNCGr8HKof",
                "public_key": (
                    "020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a325"
                ),
                "balance": 0,
            }
        ).to_json(),
    )

    manager.apply_transaction(transaction, block)

    # updated sender wallet is saved back to redis
    sender = Wallet(
        json.loads(redis.get("wallets:address:AThM5PNSKdU9pu1ydqQnzRWVeNCGr8HKof"))
    )
    assert sender.balance == -440000

    # updated recipient wallet is saved back to redis
    recipient = Wallet(
        json.loads(redis.get("wallets:address:AZYnpgXS3x43nxqhT4q29sZScRwZeNKLpW"))
    )
    assert recipient.balance == 430000

    balances_mock.assert_called_once()
Beispiel #13
0
def test_apply_block_updates_vote_balance_of_voted_delegate(redis, mocker):
    manager = WalletManager()

    apply_transaction_mock = mocker.patch(
        "chain.plugins.database.wallet_manager.WalletManager.apply_transaction"
    )
    transaction_1 = TransferTransaction()
    transaction_2 = TransferTransaction()

    block = Block()
    block.height = 1337
    block.reward = 2
    block.transactions = [transaction_1, transaction_2]
    block.generator_public_key = (
        "0316510c1409d3307d9f205cac58f1a871499c3ffea3878ddbbb48c821cfbc079a"
    )

    redis.set(
        "wallets:address:AWoysqF1xm1LXYLQvmRDpfVNKzzaLVwPVM",
        json.dumps(
            {
                "address": "AWoysqF1xm1LXYLQvmRDpfVNKzzaLVwPVM",
                "public_key": (
                    "0316b3dc139c1a35927ecbdcb8d8b628ad06bd4f1869fe3ad0e23c8106678a460f"
                ),
                "vote_balance": 2000000,
            }
        ),
    )

    redis.set(
        "wallets:address:ASt5oBHKDW8AeJe2Ybc1RucMLS7mRCiuRe",
        Wallet(
            {
                "address": "ASt5oBHKDW8AeJe2Ybc1RucMLS7mRCiuRe",
                "public_key": (
                    "0316510c1409d3307d9f205cac58f1a871499c3ffea3878ddbbb48c821cfbc079a"
                ),
                "balance": 0,
                "vote": (
                    "0316b3dc139c1a35927ecbdcb8d8b628ad06bd4f1869fe3ad0e23c8106678a460f"
                ),
            }
        ).to_json(),
    )

    manager.apply_block(block)

    assert apply_transaction_mock.call_count == 2
    delegate = Wallet(
        json.loads(redis.get("wallets:address:ASt5oBHKDW8AeJe2Ybc1RucMLS7mRCiuRe"))
    )
    assert delegate.balance == 2

    vote_wallet = Wallet(
        json.loads(redis.get("wallets:address:AWoysqF1xm1LXYLQvmRDpfVNKzzaLVwPVM"))
    )
    assert vote_wallet.vote_balance == 2000002
Beispiel #14
0
def test_apply_transaction_raises_if_voted_delegate_doesnt_exist():
    """
    Should raise exception if delegate already exists
    """
    manager = WalletManager()
    public_key = "020f5df4d2bc736d12ce43af5b1663885a893fade7ee5e62b3cc59315a63e6a325"
    block = Block()
    transaction = TransferTransaction()
    transaction.type = TRANSACTION_TYPE_VOTE
    transaction.id = "hehe"
    transaction.asset["votes"] = ["+{}".format(public_key)]

    with pytest.raises(Exception) as excinfo:
        manager.apply_transaction(transaction, block)

    assert str(
        excinfo.value
    ) == "Can't apply transaction hehe: delegate {} does not exist".format(public_key)
Beispiel #15
0
def test_from_serialized_correctly_sets_deserialized_types(
        dummy_block_hash, dummy_block):
    block = Block.from_serialized(dummy_block_hash)

    assert isinstance(block.version, int)
    assert isinstance(block.timestamp, int)
    assert isinstance(block.height, int)
    assert isinstance(block.previous_block_hex, bytes)
    assert isinstance(block.previous_block, str)
    assert isinstance(block.number_of_transactions, int)
    assert isinstance(block.total_amount, int)
    assert isinstance(block.total_fee, int)
    assert isinstance(block.reward, int)
    assert isinstance(block.payload_length, int)
    assert isinstance(block.payload_hash, str)
    assert isinstance(block.generator_public_key, str)
    assert isinstance(block.block_signature, str)
    assert isinstance(block.id, str)
    assert isinstance(block.id_hex, bytes)
    assert isinstance(block.transactions, list)
Beispiel #16
0
def test_apply_transaction_raises_if_delegate_already_exists_for_delegate_reg(redis):
    """
    Should raise exception if delegate already exists
    """
    manager = WalletManager()

    block = Block()
    transaction = TransferTransaction()
    transaction.type = TRANSACTION_TYPE_DELEGATE_REGISTRATION
    transaction.id = "hehe"
    transaction.asset["delegate"] = {"username": "******"}

    manager = WalletManager()

    redis.set("wallets:username:harambe", "")

    with pytest.raises(Exception) as excinfo:
        manager.apply_transaction(transaction, block)

    assert "Can't apply transaction hehe: delegate name harambe already taken" == str(
        excinfo.value
    )
Beispiel #17
0
def test_apply_block_raises_if_not_genesis_and_no_delegate(redis):
    manager = WalletManager()

    transaction_1 = TransferTransaction()
    transaction_2 = TransferTransaction()

    block = Block()
    block.height = 1337
    block.reward = 2
    block.transactions = [transaction_1, transaction_2]
    block.generator_public_key = (
        "0316510c1409d3307d9f205cac58f1a871499c3ffea3878ddbbb48c821cfbc079a"
    )

    with pytest.raises(Exception) as excinfo:
        manager.apply_block(block)

    assert str(excinfo.value) == (
        "Could not find a delegate with public key: "
        "0316510c1409d3307d9f205cac58f1a871499c3ffea3878ddbbb48c821cfbc079a"
    )
Beispiel #18
0
def test_apply_block_correctly_handles_genesis_block(redis, mocker):
    manager = WalletManager()

    apply_transaction_mock = mocker.patch(
        "chain.plugins.database.wallet_manager.WalletManager.apply_transaction"
    )
    transaction_1 = TransferTransaction()
    transaction_2 = TransferTransaction()

    block = Block()
    block.height = 1
    block.reward = 2
    block.transactions = [transaction_1, transaction_2]
    block.generator_public_key = (
        "0316510c1409d3307d9f205cac58f1a871499c3ffea3878ddbbb48c821cfbc079a"
    )
    manager.apply_block(block)

    assert apply_transaction_mock.call_count == 2
    delegate = Wallet(
        json.loads(redis.get("wallets:address:ASt5oBHKDW8AeJe2Ybc1RucMLS7mRCiuRe"))
    )
    assert delegate.balance == 2
Beispiel #19
0
 def get_blocks_by_id(self, block_ids):
     blocks = (Block.select().where(Block.id.in_(block_ids)).order_by(
         Block.height.desc()))
     return [CryptoBlock.from_object(block) for block in blocks]
Beispiel #20
0
def test_to_bytes_hex_returns_correct_hex_value(value, expected):
    result = Block.to_bytes_hex(value)
    assert result == expected
Beispiel #21
0
    def get_blocks_by_heights(self, heights):
        if not isinstance(heights, list):
            raise Exception("heights must be a type of list")

        blocks = Block.select().where(Block.height.in_(heights))
        return [CryptoBlock.from_object(block) for block in blocks]
Beispiel #22
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: