test: p2p: check that headers message with invalid proof-of-work disconnects peer #26184

pull theStack wants to merge 1 commits into bitcoin:master from theStack:202209-test-check_for_invalid_pow_headers_msg changing 1 files +33 −1
  1. theStack commented at 1:41 PM on September 26, 2022: contributor

    One of the earliest anti-DoS checks done after receiving and deserializing a headers message from a peer is verifying whether the proof-of-work is valid (called in method PeerManagerImpl::ProcessHeadersMessage): https://github.com/bitcoin/bitcoin/blob/f227e153e80c8c50c30d76e1ac638d7206c7ff61/src/net_processing.cpp#L2752-L2762 The called method PeerManagerImpl::CheckHeadersPoW calls Misbehaving with a score of 100, i.e. leading to an immediate disconnect of the peer: https://github.com/bitcoin/bitcoin/blob/f227e153e80c8c50c30d76e1ac638d7206c7ff61/src/net_processing.cpp#L2368-L2372

    This PR adds a simple test for both the misbehaving log and the resulting disconnect. For creating a block header with invalid proof-of-work, we first create one that is accepted by the node (the difficulty field nBits is copied from the genesis block) and based on that the nonce is modified until we have block header hash prefix that is too high to fulfill even the minimum difficulty.

  2. fanquake added the label Tests on Sep 26, 2022
  3. jarolrod commented at 6:49 PM on September 27, 2022: member

    concept ack

  4. in test/functional/p2p_invalid_messages.py:255 in 07342be712 outdated
     249 | @@ -248,6 +250,19 @@ def test_oversized_headers_msg(self):
     250 |          size = MAX_HEADERS_RESULTS + 1
     251 |          self.test_oversized_msg(msg_headers([CBlockHeader()] * size), size)
     252 |  
     253 | +    def test_invalid_pow_headers_msg(self):
     254 | +        self.log.info("Test headers message with invalid proof-of-work is logged as misbehaving and disconnects peer")
     255 | +        blockheader_tip = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash(), False)
    


    maflcko commented at 2:47 PM on October 3, 2022:

    The node doesn't have any blocks, so you are getting the genesis block, which can't be send over p2p anyway, as the previous block is unknown.

    received header ......: missing prev block 0000000000000000000000000000000000000000000000000000000000000000
    

    theStack commented at 3:23 PM on October 3, 2022:

    Yeah, in fact, since the PoW check is the first one that actually inspects headers data, the content of the block header doesn't matter at all for this test-case; could even be completely random bytes:

    diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
    index f5fe590c62..e85dc00082 100755
    --- a/test/functional/p2p_invalid_messages.py
    +++ b/test/functional/p2p_invalid_messages.py
    @@ -29,6 +29,7 @@ from test_framework.p2p import (
     from test_framework.test_framework import BitcoinTestFramework
     from test_framework.util import (
         assert_equal,
    +    random_bytes,
     )
    
     VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5  # Account for the 5-byte length prefix
    @@ -252,8 +253,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
    
         def test_invalid_pow_headers_msg(self):
             self.log.info("Test headers message with invalid proof-of-work is logged as misbehaving and disconnects peer")
    -        blockheader_tip = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash(), False)
    -        blockheader_invalid_pow = from_hex(CBlockHeader(), blockheader_tip)
    +        blockheader_invalid_pow = from_hex(CBlockHeader(), random_bytes(80).hex())
             blockheader_invalid_pow.rehash()
             while not blockheader_invalid_pow.hash.startswith('f'):
                 blockheader_invalid_pow.nNonce += 1
    
    

    But I agree that the test-case in its current form suggest that header would be valid with correct PoW. Will take a deeper look in a bit.

  5. theStack force-pushed on Oct 6, 2022
  6. theStack commented at 1:13 PM on October 6, 2022: contributor

    Force-pushed taking Marco's review (https://github.com/bitcoin/bitcoin/pull/26184#discussion_r985883661) into account: we first create a valid headers message now and check that it is accepted by the node, and only then we only modify its nNonce until the PoW is invalidated.

  7. in test/functional/p2p_invalid_messages.py:276 in 4bf49eb276 outdated
     271 | +
     272 | +        # invalidate PoW
     273 | +        while not blockheader.hash.startswith('f'):
     274 | +            blockheader.nNonce += 1
     275 | +            blockheader.rehash()
     276 | +        bad_peer = self.nodes[0].add_p2p_connection(P2PInterface())
    


    brunoerg commented at 1:48 PM on February 9, 2023:

    Do we need to create a new peer here (bad_peer)? Couldn't we use the same used before? e.g.:

    diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py
    index 7c6777759..39505c115 100755
    --- a/test/functional/p2p_invalid_messages.py
    +++ b/test/functional/p2p_invalid_messages.py
    @@ -265,18 +265,17 @@ class InvalidMessagesTest(BitcoinTestFramework):
             while not blockheader.hash.startswith('0'):
                 blockheader.nNonce += 1
                 blockheader.rehash()
    -        good_peer = self.nodes[0].add_p2p_connection(P2PInterface())
    -        good_peer.send_and_ping(msg_headers([blockheader]))
    +        peer = self.nodes[0].add_p2p_connection(P2PInterface())
    +        peer.send_and_ping(msg_headers([blockheader]))
             assert_equal(self.nodes[0].getblockchaininfo()['headers'], 1)
     
             # invalidate PoW
             while not blockheader.hash.startswith('f'):
                 blockheader.nNonce += 1
                 blockheader.rehash()
    -        bad_peer = self.nodes[0].add_p2p_connection(P2PInterface())
             with self.nodes[0].assert_debug_log(['Misbehaving', 'header with invalid proof of work']):
    -            bad_peer.send_message(msg_headers([blockheader]))
    -            bad_peer.wait_for_disconnect()
    +            peer.send_message(msg_headers([blockheader]))
    +            peer.wait_for_disconnect()
    

    theStack commented at 11:03 PM on February 9, 2023:

    Good point, done.

  8. DrahtBot commented at 1:48 PM on February 9, 2023: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK brunoerg, Sjors, furszy, achow101

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

  9. in test/functional/p2p_invalid_messages.py:270 in 4bf49eb276 outdated
     265 | +        while not blockheader.hash.startswith('0'):
     266 | +            blockheader.nNonce += 1
     267 | +            blockheader.rehash()
     268 | +        good_peer = self.nodes[0].add_p2p_connection(P2PInterface())
     269 | +        good_peer.send_and_ping(msg_headers([blockheader]))
     270 | +        assert_equal(self.nodes[0].getblockchaininfo()['headers'], 1)
    


    furszy commented at 2:32 PM on February 9, 2023:

    nit: Could also check the connected header hash

    assert any(x["hash"] == blockheader.hash and x["status"] == "headers-only" for x in self.nodes[0].getchaintips())
    

    theStack commented at 11:05 PM on February 9, 2023:

    Good idea, done. Decided to access the getchaintips() result at index 0 (works since the result is always returned in decreasing order of height) in order to take use of assert_equal, so if this ever fails, we see the expected/actual values immediately.

  10. furszy commented at 2:36 PM on February 9, 2023: member

    Concept ACK

    Seems that this only add coverage for the target < chain_pow_limit rule but not for the other validation checks inside CheckProofOfWork. What if we extend it to cover all of them?

  11. theStack commented at 10:20 PM on February 9, 2023: contributor

    @brunoerg: @furszy: Thanks for your reviews, will include both your nits in a bit.

    Seems that this only add coverage for the target < chain_pow_limit rule but not for the other validation checks inside CheckProofOfWork. What if we extend it to cover all of them?

    Considering that we have quite detailled CheckProofOfWork unit tests that check all those invalid causes already, I think triggering one of them for the purpose of increasing p2p disconnection coverage should be sufficient.

  12. furszy commented at 10:26 PM on February 9, 2023: member

    Considering that we have quite detailled CheckProofOfWork unit tests that check all those invalid causes already, I think triggering one of them for the purpose of increasing p2p disconnection coverage should be sufficient.

    Ah, wasn't aware of its existence. Great. Then yeah, no need to re-implement the same tests. Only verify the disconnection here. All good.

  13. test: p2p: check that headers message with invalid proof-of-work disconnects peer 772671245d
  14. theStack force-pushed on Feb 9, 2023
  15. brunoerg approved
  16. brunoerg commented at 5:26 PM on February 10, 2023: contributor

    crACK 772671245d50d94fd5087deb2542854604eba174

  17. Sjors commented at 10:12 AM on February 11, 2023: member

    ACK 772671245d50d94fd5087deb2542854604eba174

  18. furszy approved
  19. furszy commented at 12:01 PM on February 11, 2023: member

    Code review ACK 77267124 with a non-blocking speedup.

    To make the test faster, instead of increase the nonce + rehash the block, could just set the target bits to 0 (or set it above the pow limit). The same "header with invalid proof of work" error will be thrown.

    e.g.

    # Invalidate header
    blockheader.nBits = 0
    blockheader.rehash()
    with self.nodes[0].assert_debug_log(['Misbehaving', 'header with invalid proof of work']):
        peer.send_message(msg_headers([blockheader]))
        peer.wait_for_disconnect()
    
  20. achow101 commented at 11:38 PM on February 14, 2023: member

    ACK 772671245d50d94fd5087deb2542854604eba174

  21. achow101 merged this on Feb 14, 2023
  22. achow101 closed this on Feb 14, 2023

  23. sidhujag referenced this in commit 51e5e5a085 on Feb 15, 2023
  24. theStack deleted the branch on Feb 15, 2023
  25. bitcoin locked this on Feb 15, 2024

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-14 21:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me