Beispiel #1
0
    def handle_block_received(self, header: MessageHeader,
                              message: DataMessage) -> None:

        block: Block = message.data  # type: ignore

        coinstate_prior = self.local_peer.chain_manager.coinstate

        block_hash = block.hash()
        self.remove_from_inventory(block_hash)

        if block_hash not in coinstate_prior.block_by_hash:

            if block.header.summary.previous_block_hash not in coinstate_prior.block_by_hash:
                # This is not common, so it doesn't need special handling.
                self.local_peer.logger.info(
                    "%15s at height=%d, block received out of order for height=%d: %s"
                    % (self.host, coinstate_prior.head().height, block.height,
                       human(block_hash)))
                return

            validate_block_by_itself(block, int(time()))
            coinstate_changed = coinstate_prior.add_block_no_validation(block)

            if header.in_response_to == 0 or block.height % IBD_VALIDATION_SKIP == 0:
                # Validation is very slow, and we don't have to validate every block in a blockchain, so
                # during IBD, we only validate every Nth block where N := IBD_VALIDATION_SKIP.
                # Because the BLOCKS are part of a CHAIN of hashes, every valid block[n] guarantees a valid
                # block[n-1]. Just to keep things clean, we avoid writing unvalidated blocks to disk until
                # their next "validated descendent" is encountered (this is unnecessary, but neat).
                # During normal operation (non-IBD) we just validate every block because we're not in a hurry.
                try:
                    validate_block_in_coinstate(block,
                                                coinstate_prior)  # very slow

                except Exception:
                    self.local_peer.logger.info(
                        "%15s INVALID block: %s" %
                        (self.host, traceback.format_exc()))
                    if self.local_peer.chain_manager.last_known_valid_coinstate:
                        self.local_peer.chain_manager.set_coinstate(
                            self.local_peer.chain_manager.
                            last_known_valid_coinstate)
                    return

                self.local_peer.chain_manager.set_coinstate(coinstate_changed,
                                                            validated=True)
                self.local_peer.disk_interface.write_chain_cache_to_disk(
                    coinstate_changed)
            else:
                self.local_peer.chain_manager.set_coinstate(coinstate_changed,
                                                            validated=False)

            if block == coinstate_changed.head(
            ) and header.in_response_to == 0:
                # "header.in_response_to == 0" is being used as a bit of a proxy for "not in IBD" here, but it would be
                # better to check for that state more explicitly. We don't want to broadcast blocks while in IBD,
                # because in that state the fact that some block is our new head doesn't mean at all that we're talking
                # about the real chain's new head, and only the latter is relevant to the rest of the world.
                self.local_peer.network_manager.broadcast_block(block)
Beispiel #2
0
def test_validate_block_by_itself_invalid_coinbase_transaction():
    coinstate = CoinState.empty()

    transactions = [
        Transaction(inputs=[
            Input(
                OutputReference(b'x' * 32, 1),
                SECP256k1Signature(b'y' * 64),
            )
        ],
                    outputs=[Output(1, example_public_key)])
    ]

    summary = construct_minable_summary(coinstate, transactions, 1615209942,
                                        39)
    evidence = construct_pow_evidence(coinstate, summary, 0, transactions)
    block = Block(BlockHeader(summary, evidence), transactions)

    with pytest.raises(ValidateTransactionError, match=".*thin air.*"):
        validate_block_by_itself(block, 1615209942)
Beispiel #3
0
def test_validate_block_by_itself_max_block_size(mocker):
    # work around get_block_fees... because we would need a coinstate w/ 50_000 unspent outputs to make it work.
    mocker.patch("skepticoin.consensus.get_block_fees", return_value=0)

    transactions = [
        Transaction(inputs=[
            Input(
                OutputReference(i.to_bytes(32, 'big'), 1),
                SECP256k1Signature(b'y' * 64),
            )
        ],
                    outputs=[Output(1, example_public_key)])
        for i in range(50_000)
    ]

    block = construct_block_for_mining_genesis(transactions,
                                               example_public_key, 1231006505,
                                               b'', 22)

    with pytest.raises(ValidateBlockError, match=".*MAX_BLOCK_SIZE.*"):
        validate_block_by_itself(block, 1615209942)
Beispiel #4
0
    def add_block(self, block, current_timestamp):
        from skepticoin.consensus import validate_block_by_itself, validate_block_in_coinstate
        validate_block_by_itself(block, current_timestamp)
        validate_block_in_coinstate(block, self)

        return self.add_block_no_validation(block)
Beispiel #5
0
def test_validate_block_by_itself_for_mismatched_heights():
    block = get_example_genesis_block()
    block.height = 1

    with pytest.raises(ValidateBlockError, match=".*height.*"):
        validate_block_by_itself(block, 1615209942)
Beispiel #6
0
def test_validate_block_by_itself_for_correct_block():
    validate_block_by_itself(get_example_genesis_block(), 1615209942)
Beispiel #7
0
def test_validate_block_by_itself_no_transactions():
    block = get_example_genesis_block()
    block.transactions = []

    with pytest.raises(ValidateBlockError, match=".*No transactions.*"):
        validate_block_by_itself(block, 1615209942)