[net] Ignore getheaders requests for very old side blocks #11113

pull jimpo wants to merge 2 commits into bitcoin:master from jimpo:net-getheaders-fingerprint changing 4 files +189 −11
  1. jimpo commented at 9:15 PM on August 22, 2017: contributor

    Sending a getheaders message with an empty locator and a stop hash is a request for a single header by hash. The node will respond with headers for blocks not in the main chain as well as those in the main chain. To avoid fingerprinting, the node should, however, ignore requests for headers on side branches that are too old. This replicates the logic that currently exists for getdata requests for blocks.

  2. jimpo force-pushed on Aug 22, 2017
  3. TheBlueMatt commented at 9:44 PM on August 22, 2017: member

    Hmm, we should also handle the case where they have orphans in their locator similarly (ie parse their locator further back to find the first locator entry that is on our main chain).

  4. jimpo commented at 10:03 PM on August 22, 2017: contributor

    @TheBlueMatt This is the case where the locator is empty and only the stopHash is set. It's a special case request for a particular header by hash.

  5. TheBlueMatt commented at 10:13 PM on August 22, 2017: member

    @jimpo yes, sorry, I had since edited my comment to note that we should do something similar for locator-present requests.

  6. in src/net_processing.cpp:731 in 4f559f1d88 outdated
     724 | @@ -725,6 +725,17 @@ void Misbehaving(NodeId pnode, int howmuch)
     725 |  // blockchain -> download logic notification
     726 |  //
     727 |  
     728 | +// To prevent fingerprinting attacks, only send blocks/headers outside of the
     729 | +// active chain if they are no more than a month older (both in time, and in
     730 | +// best equivalent proof of work) than the best header chain we know about.
     731 | +static bool SideBlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams)
    


    TheBlueMatt commented at 10:24 PM on August 22, 2017:

    Generally referred to as "Orphans" not "SideBlocks".

  7. TheBlueMatt commented at 10:27 PM on August 22, 2017: member

    Oops, I misread FindForkInGlobalIndex, sorry, code looks fine, though note nomenclature is wrong.

  8. jimpo force-pushed on Aug 22, 2017
  9. laanwj added the label P2P on Aug 23, 2017
  10. sdaftuar commented at 8:49 PM on August 24, 2017: member

    Concept ACK, good catch. Would be great to have a p2p test case for this too.

  11. jimpo force-pushed on Aug 26, 2017
  12. jimpo commented at 12:26 AM on August 26, 2017: contributor

    @sdaftuar Added a test with 58bc23d.

  13. sdaftuar commented at 2:07 AM on August 26, 2017: member

    Code review ACK, and thanks for writing the test! I just looked at the travis failure, and it appears to be a subtle merge conflict because the semantics of wait_until was just changed in master, see #11068.

  14. jimpo force-pushed on Aug 26, 2017
  15. sipa commented at 7:30 PM on August 27, 2017: member

    utACK fed7483751cc94a7ce27fc6057a2080af8178223. Only had a casual look at the tests.

  16. sdaftuar commented at 7:10 PM on August 30, 2017: member

    ACK 3d61d72

  17. in src/net_processing.cpp:733 in 3d61d728d1 outdated
     724 | @@ -725,6 +725,17 @@ void Misbehaving(NodeId pnode, int howmuch)
     725 |  // blockchain -> download logic notification
     726 |  //
     727 |  
     728 | +// To prevent fingerprinting attacks, only send blocks/headers outside of the
     729 | +// active chain if they are no more than a month older (both in time, and in
     730 | +// best equivalent proof of work) than the best header chain we know about.
     731 | +static bool OrphanBlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams)
     732 | +{
     733 | +    static const int nOneMonth = 30 * 24 * 60 * 60;
    


    meshcollider commented at 10:13 PM on September 4, 2017:

    Constants should be caps with snake case

  18. in src/net_processing.cpp:720 in 3d61d728d1 outdated
     724 | @@ -725,6 +725,17 @@ void Misbehaving(NodeId pnode, int howmuch)
     725 |  // blockchain -> download logic notification
     726 |  //
     727 |  
     728 | +// To prevent fingerprinting attacks, only send blocks/headers outside of the
     729 | +// active chain if they are no more than a month older (both in time, and in
     730 | +// best equivalent proof of work) than the best header chain we know about.
     731 | +static bool OrphanBlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams)
    


    meshcollider commented at 10:15 PM on September 4, 2017:

    snake_case argument names


    jimpo commented at 2:09 AM on September 7, 2017:

    consensusParams is camelCase everywhere else in the file, so I think it's better to leave as is. Feel free to replace everywhere in a separate PR.


    meshcollider commented at 9:19 PM on September 8, 2017:

    Style-only PRs are highly discouraged, developer guide says to use the style in all new code regardless of style around it, because eventually it will all be replaced


    promag commented at 10:20 PM on September 18, 2017:

    Just params?


    jimpo commented at 5:32 PM on September 19, 2017:

    @promag The variable is referred to as consensusParams everywhere else, probably to differentiate from an instance of CChainParams.

  19. meshcollider commented at 10:36 PM on September 4, 2017: contributor

    utACK

  20. jimpo force-pushed on Sep 7, 2017
  21. jimpo force-pushed on Sep 7, 2017
  22. jimpo force-pushed on Sep 7, 2017
  23. jimpo force-pushed on Sep 7, 2017
  24. jimpo force-pushed on Sep 8, 2017
  25. gmaxwell commented at 6:35 PM on September 8, 2017: contributor

    utACK.

    Perhaps a small test nit, if I'm not misreading the test, I think the test would pass if it started failing for all really old headers even if they're on the active chain. We should have a check that that is still successful. We should also include a note in the tests that we don't test both criteria (both time and PET) as those tests would be good to add later.

    obNitpickSummoning: @sipa you're okay with calling this function 'orphanblock' rather than something like 'staleblock'? :)

  26. jimpo force-pushed on Sep 8, 2017
  27. jimpo commented at 8:21 PM on September 8, 2017: contributor

    @gmaxwell Thanks, added another test for that case. What do you mean by PET?

    Also, yeah, I don't like the term orphan block (because it's ambiguous). Is stale block the preferred term now? I originally named it "side block" but I hear no one else calls it that.

    EDIT: Nevermind, PET = Proof (of work) Equivalent Time. Will add the comment.

  28. jimpo force-pushed on Sep 8, 2017
  29. in src/net_processing.cpp:65 in d07a788f25 outdated
      60 | @@ -61,6 +61,14 @@ static std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUAR
      61 |  
      62 |  static const uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; // SHA256("main address relay")[0:8]
      63 |  
      64 | +/// Age after which an orphan block will no longer be served if requested as
      65 | +/// protection against fingerprinting.
    


    promag commented at 10:19 PM on September 18, 2017:

    Specify unit.

  30. in src/net_processing.cpp:69 in d07a788f25 outdated
      60 | @@ -61,6 +61,14 @@ static std::vector<std::pair<uint256, CTransactionRef>> vExtraTxnForCompact GUAR
      61 |  
      62 |  static const uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; // SHA256("main address relay")[0:8]
      63 |  
      64 | +/// Age after which an orphan block will no longer be served if requested as
      65 | +/// protection against fingerprinting.
      66 | +static const int ORPHAN_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60;
      67 | +
      68 | +/// Age after which a block is considered historical for purposes of rate
      69 | +/// limiting block relay. Set to one week.
    


    promag commented at 10:19 PM on September 18, 2017:

    Specify unit.

  31. in test/functional/test_framework/mininode.py:1314 in d07a788f25 outdated
    1309 | @@ -1310,8 +1310,8 @@ def __repr__(self):
    1310 |  class msg_headers(object):
    1311 |      command = b"headers"
    1312 |  
    1313 | -    def __init__(self):
    1314 | -        self.headers = []
    1315 | +    def __init__(self, headers=None):
    1316 | +        self.headers = headers if headers is not None else []
    


    promag commented at 10:30 PM on September 18, 2017:

    Just?

        def __init__(self, headers=[]):
            self.headers = headers
    

    jimpo commented at 5:34 PM on September 19, 2017:

    promag commented at 8:34 PM on September 25, 2017:

    Oh! nice, ty!

  32. in test/functional/p2p-fingerprint.py:143 in d07a788f25 outdated
     138 | +        # Request for very old orphan block header should now fail
     139 | +        self.send_header_request(orphan_hash, node0)
     140 | +        time.sleep(3)
     141 | +        assert not self.last_header_equals(orphan_hash, node0)
     142 | +
     143 | +        # Verify we can fetch very old blocks and headers on the active chain.
    


    promag commented at 10:31 PM on September 18, 2017:

    Nit, I also prefer periods, but at least be consistent, remove?

  33. in test/functional/p2p-fingerprint.py:11 in d07a788f25 outdated
       6 | +
       7 | +If an orphan block more than a month old or its header are requested by a peer,
       8 | +the node should pretend that it does not have it to avoid fingerprinting.
       9 | +"""
      10 | +
      11 | +import time
    


    promag commented at 10:32 PM on September 18, 2017:

    Nit

    from time import (sleep, time)
    

    jimpo commented at 5:40 PM on September 19, 2017:

    If other people feel strongly, I'm willing to change it, but I prefer to only import packages and modules, not values. This makes it a bit more clear where the function is coming from.

  34. in src/net_processing.cpp:723 in d07a788f25 outdated
     713 | @@ -706,6 +714,16 @@ void Misbehaving(NodeId pnode, int howmuch)
     714 |  // blockchain -> download logic notification
     715 |  //
     716 |  
     717 | +// To prevent fingerprinting attacks, only send blocks/headers outside of the
     718 | +// active chain if they are no more than a month older (both in time, and in
     719 | +// best equivalent proof of work) than the best header chain we know about.
     720 | +static bool OrphanBlockRequestAllowed(const CBlockIndex* pindex, const Consensus::Params& consensusParams)
     721 | +{
     722 | +    return (pindexBestHeader != nullptr) &&
    


    promag commented at 10:36 PM on September 18, 2017:

    Missing AssertLockHeld because of pindexBestHeader?


    theuni commented at 6:40 PM on September 19, 2017:

    +1

  35. jimpo force-pushed on Sep 19, 2017
  36. theuni commented at 6:41 PM on September 19, 2017: member

    utACK, though I would prefer to see the AssertLockHeld added as @promag suggested.

  37. jimpo commented at 6:48 PM on September 19, 2017: contributor

    @theuni Added.

  38. TheBlueMatt commented at 1:41 AM on September 21, 2017: member

    utACK non-test parts of 8e57e82942c6ccb6ac905bd44d0d476bcf1651ec

  39. gmaxwell approved
  40. gmaxwell commented at 7:40 PM on September 25, 2017: contributor

    re-ACK

  41. promag commented at 8:37 PM on September 25, 2017: member

    utACK modulus periods in commit messages and PR title 😛.

  42. jimpo renamed this:
    [net] Ignore getheaders requests for very old side blocks.
    [net] Ignore getheaders requests for very old side blocks
    on Sep 26, 2017
  43. jonasschnelli commented at 5:53 PM on September 27, 2017: contributor

    @promag: can you retest/re-ack if the requested changes are ok?

  44. jimpo force-pushed on Sep 27, 2017
  45. in test/functional/p2p-fingerprint.py:114 in b74b0b788f outdated
     109 | +
     110 | +        orphan_hash = int(block_hashes[-1], 16)
     111 | +
     112 | +        # Check that getdata request for orphan block succeeds
     113 | +        self.send_block_request(orphan_hash, node0)
     114 | +        test_function = lambda: self.last_block_equals(orphan_hash, node0)
    


    promag commented at 10:52 PM on September 27, 2017:

    Inline lambdas in wait_until?


    jimpo commented at 11:36 PM on September 27, 2017:

    I'd rather not.

  46. in test/functional/p2p-fingerprint.py:135 in b74b0b788f outdated
     130 | +        self.send_header_request(block_hash, node0)
     131 | +        node0.sync_with_ping()
     132 | +
     133 | +        # Request for very old orphan block should now fail
     134 | +        self.send_block_request(orphan_hash, node0)
     135 | +        time.sleep(3)
    


    promag commented at 11:00 PM on September 27, 2017:

    Also use wait_until instead of fixed sleep?


    jimpo commented at 11:35 PM on September 27, 2017:

    That doesn't work. We're waiting to make sure a change does not happen.

  47. in test/functional/p2p-fingerprint.py:140 in b74b0b788f outdated
     135 | +        time.sleep(3)
     136 | +        assert not self.last_block_equals(orphan_hash, node0)
     137 | +
     138 | +        # Request for very old orphan block header should now fail
     139 | +        self.send_header_request(orphan_hash, node0)
     140 | +        time.sleep(3)
    


    promag commented at 11:04 PM on September 27, 2017:

    Same as above.

  48. in test/functional/p2p-fingerprint.py:97 in b74b0b788f outdated
      92 | +        # Generating a chain of 10 blocks
      93 | +        block_hashes = self.nodes[0].generate(nblocks=10)
      94 | +
      95 | +        # Create longer chain starting 2 blocks before current tip
      96 | +        height = len(block_hashes) - 2
      97 | +        block_hash = block_hashes[height - 1]
    


    promag commented at 11:06 PM on September 27, 2017:

    Nit, remove above height and:

    block_hash = block_hashes[-3]
    

    jimpo commented at 11:37 PM on September 27, 2017:

    I think this better represents my intent as is. Also, height is used two lines below.

  49. in test/functional/p2p-fingerprint.py:37 in b74b0b788f outdated
      32 | +    def set_test_params(self):
      33 | +        self.setup_clean_chain = True
      34 | +        self.num_nodes = 1
      35 | +
      36 | +    # Build a chain of blocks on top of given one
      37 | +    def build_chain(self, nblocks, prev_hash, prev_height, prev_median_time):
    


    promag commented at 11:23 PM on September 27, 2017:

    Determine prev_height and prev_median_time here and simplify code below.


    jimpo commented at 11:36 PM on September 27, 2017:

    I don't understand. Example code?


    promag commented at 8:32 AM on September 28, 2017:

    I mean something like:

    def build_chain(self, nblocks, prev_hash):
        header = self.nodes[0].getblockheader(prev_hash)
        height = header["height"]
        cur_time = header["mediantime"]
    
        blocks = []
        for _ in range(nblocks):
            height += 1
            cur_time += 1
            
            coinbase = create_coinbase(height)
            block = create_block(int(prev_hash, 16), coinbase, cur_time)
            block.solve()
    
            blocks.append(block)
            prev_hash = block.hash
        return blocks
    
    ...
    
        new_blocks = self.build_chain(5, block_hashes[-3])
    

    This way height below is not needed.

  50. promag commented at 11:28 PM on September 27, 2017: member

    Commit [test] P2P functional test for certain fingerprinting protections has changes that belong to 1st commit.

  51. jimpo force-pushed on Sep 27, 2017
  52. laanwj commented at 11:15 AM on September 28, 2017: member

    obNitpickSummoning: @sipa you're okay with calling this function 'orphanblock' rather than something like 'staleblock'? :)

    Yes, a small nit regarding language: there is some ambiguity with regard to "orphan". Sometimes this is used for disconnected blocks/transactions (of which the parent is not known), sometimes for blocks/transactions outside the active chain. "stale" would be more specific, maybe.

    utACK otherwise.

  53. promag commented at 1:19 PM on September 28, 2017: member

    utACK 00ec5a3. Nits and other possible improvements can be worked out later.

  54. jimpo force-pushed on Oct 3, 2017
  55. [net] Ignore getheaders requests for very old side blocks
    Sending a getheaders message with an empty locator and a stop hash
    is a request for a single header by hash. The node will respond with
    headers for blocks not in the main chain as well as those in the main
    chain. To avoid fingerprinting, the node should, however, ignore
    requests for headers on side branches that are too old.
    a2be3b66b5
  56. [test] P2P functional test for certain fingerprinting protections eff4bd8ab2
  57. jimpo force-pushed on Oct 3, 2017
  58. laanwj merged this on Oct 11, 2017
  59. laanwj closed this on Oct 11, 2017

  60. laanwj referenced this in commit fef65c4f5e on Oct 11, 2017
  61. jimpo deleted the branch on Oct 11, 2017
  62. codablock referenced this in commit 953d74bcea on Mar 12, 2019
  63. codablock referenced this in commit d1a6022605 on Mar 12, 2019
  64. DrahtBot locked this on Sep 8, 2021

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-21 12:15 UTC

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