{
 "number": 36470,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36470",
 "title": "test: make p2p_eviction actually exercise block-time protection",
 "author": "brunoerg",
 "author_association": "MEMBER",
 "created_at": "2026-10-08T11:54:38Z",
 "updated_at": "2026-10-26T10:22:51Z",
 "description_hash": "2f095be7911e376f",
 "age_days": 24,
 "draft": false,
 "labels": [
  "Tests"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "2fd9be21e28fecf298493e28e90ccb8085aa4ae8",
 "head_ref": "2026-10-test-p2p-eviction-block-protection",
 "head_repo": "brunoerg/bitcoin",
 "head_history": [
  {
   "t": "2026-10-14T15:22:03Z",
   "sha": "f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2"
  },
  {
   "t": "2026-10-19T14:15:05Z",
   "sha": "2fd9be21e28fecf298493e28e90ccb8085aa4ae8"
  }
 ],
 "additions": 57,
 "deletions": 43,
 "changed_files": 1,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "maflcko",
      "url": "https://github.com/bitcoin/bitcoin/pull/36470#issuecomment-6293791542"
     }
    ],
    "stale_ack": [
     {
      "login": "trakshan-mishra",
      "url": "https://github.com/bitcoin/bitcoin/pull/36470#issuecomment-6079359437"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Crypt-iQ",
   "instagibbs",
   "maflcko",
   "trakshan-mishra"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-26T10:22:51Z",
  "last_reviewer_activity": "2026-10-20T15:07:37Z",
  "last_reviewer": "maflcko",
  "author_silent_days": 6,
  "waiting_on_author_days": 0,
  "days_since_update": 6
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "test/functional/p2p_eviction.py"
 ],
 "body": "`p2p_eviction.py` tests that 4 inbound peers that recently sent us a novel block are protected from eviction. However, the test currently passes even when that protection is not in effect.\n\nThe block-sending peers are connected first, so they are the oldest connections. When block-time protection doesn't apply, `CompareNodeBlockTime` falls back to connection time and protects the 4 oldest peers, which are these same peers. Then the youngest remaining peer gets evicted. The check `evicted_peers[0] not in protected_peers` therefore holds whether or not block-time protection works.\n\nThis can be shown by deleting the line where the peers send their block (L71 on master):\n```py\nblock_peer.send_blocks_and_test([block], node, success=True)\n```",
 "commits": [
  {
   "sha": "2fd9be21e28fecf298493e28e90ccb8085aa4ae8",
   "date": "2026-10-19T14:13:48Z",
   "message": "test: make p2p_eviction actually exercise block-time protection\n\nThe 4 peers that send us a block were connected first, making them the\noldest connections. When block-time protection doesn't apply,\nCompareNodeBlockTime falls through to connection time and protects the\noldest peers anyway, and the youngest remaining peer gets evicted. So the\ntest kept passing with the block-time protection removed from\nSelectNodeToEvict, or with the peers never sending a block.\n\nSimply connecting the block peers last is not enough: netgroup protection\nruns first and, since all test peers share one netgroup, which 4 peers it\nprotects depends on the std::sort implementation. With libc++ and MSVC,\nwhich use insertion sort for small inputs, it keeps the 4 youngest peers.\n\nMake the result independent of which peers netgroup protection takes:\nconnect the eviction candidates first and give them a lower ping than the\ntx and block peers, so every protection step (ping, tx-time, block-time)\nmakes up any slots taken from its own peers with eviction candidates.\nRemoving any of these protections now gets one of its peers evicted."
  }
 ],
 "timeline": [
  {
   "t": "2026-10-09T10:47:30Z",
   "kind": "comment",
   "who": "trakshan-mishra",
   "assoc": "NONE",
   "text": "tACK c75cdce1f9430cab029a329fd86c7e6bef7bf444\n\nTested on Linux.\n\n- with the `send_blocks_and_test` line removed, the test on master still passes (15/15), with this PR it fails (15/15)\n- removed the `m_last_block_time` check in `CompareNodeBlockTime`: master's test passes 10/10, this PR's test fails 10/10\n- removed the block-time `EraseLastKElements` call: same, master passes, this PR fails\n- unmodified, the PR's test passes 30/30",
   "id": "c:6079359437",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#issuecomment-6079359437",
   "hash": "8a330c029009ad06",
   "edited": "2026-10-09T11:02:33Z"
  },
  {
   "t": "2026-10-12T14:42:44Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "review ACK c75cdce1f9430cab029a329fd86c7e6bef7bf444 \ud83c\udfdd\n\nShow signature\n\nSignature:\n\n```\nuntrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm \"${path_to_any_empty_file}\" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x \"${path_to_this_whole_four_line_signature_blob}\"\nRUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=\ntrusted comment: review ACK c75cdce1f9430cab029a329fd86c7e6bef7bf444 \ud83c\udfdd\niowQkSYIobxRbqT/a2g2EKx9ftguBZdUdiW7+G6JCmWAECglHd9yRj+ViTiazJ9UMQxi+75xB5lB1Ki3gSloAw==\n```",
   "id": "c:6126539076",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#issuecomment-6126539076",
   "hash": "75751d9f894fddf3",
   "edited": null
  },
  {
   "t": "2026-10-13T20:33:46Z",
   "kind": "review_comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "c75cdce1f9430cab029a329fd86c7e6bef7bf444",
   "in_reply_to": null,
   "text": "Apologies for slop, but my clanker says that the regression test doesn't fail under mutants for libc++/MSVC due to sort implementation details with all peers falling under the same netgroup. Suggested alternative to make it stable, feel free to do whatever with it:\n\n```\ndiff --git a/test/functional/p2p_eviction.py b/test/functional/p2p_eviction.py\n  index 3d50132..f20ff2a 100755\n  --- a/test/functional/p2p_eviction.py\n  +++ b/test/functional/p2p_eviction.py\n  @@ -26,13 +26,17 @@ from test_framework.p2p import (\n       P2PInterface,\n   )\n   from test_framework.test_framework import BitcoinTestFramework\n  -from test_framework.util import assert_equal\n  +from test_framework.util import (\n  +    assert_equal,\n  +    assert_greater_than,\n  +)\n   from test_framework.wallet import MiniWallet\n\n  +# Pings slower than SlowP2PInterface, see run_test.\n   class SlowP2PDataStore(P2PDataStore):\n       def on_ping(self, message):\n  -        time.sleep(0.1)\n  +        time.sleep(0.3)\n           self.send_without_ping(msg_pong(message.nonce))\n\n  @@ -58,6 +62,12 @@ class P2PEvict(BitcoinTestFramework):\n           node = self.nodes[0]\n           self.wallet = MiniWallet(node)\n\n  +        # Netgroup protection picks 4 arbitrary peers here (all peers share\n  one netgroup,\n  +        # so the choice depends on the std::sort implementation). Whatever it\n  takes from\n  +        # another group is made up from the eviction candidates: they connect\n  first, so\n  +        # tx- and block-time protection fall back to them as the oldest\n  connections, and\n  +        # they ping faster than the tx and block peers. Removing any\n  protection then gets\n  +        # one of its peers evicted, unless netgroup protection covered all of\n  them.\n           self.log.info(\"Create 5 slow-pinging peers, making them eviction\n  candidates\")\n           for _ in range(5):\n               node.add_p2p_connection(SlowP2PInterface())\n  @@ -65,23 +75,15 @@ class P2PEvict(BitcoinTestFramework):\n\n           self.log.info(\"Create 4 peers and protect them from eviction by\n  sending us a tx\")\n           for i in range(4):\n  -            txpeer = node.add_p2p_connection(SlowP2PInterface())\n  +            txpeer = node.add_p2p_connection(SlowP2PDataStore())\n               current_peer += 1\n               txpeer.sync_with_ping()\n\n               tx = self.wallet.create_self_transfer()['tx']\n  -            txpeer.send_without_ping(msg_tx(tx))\n  +            txpeer.send_and_ping(msg_tx(tx))\n  +\n  assert_greater_than(node.getpeerinfo()[current_peer][\"last_transaction\"], 0)\n               protected_peers.add(current_peer)\n\n  -        self.log.info(\"Create 8 peers and protect them from eviction by\n  having faster pings\")\n  -        for _ in range(8):\n  -            fastpeer = node.add_p2p_connection(P2PInterface())\n  -            current_peer += 1\n  -            self.wait_until(lambda: \"ping\" in fastpeer.last_message,\n  timeout=10)\n  -\n  -        # Connect these peers last: without block-time protection, the\n  eviction\n  -        # logic would fall back to protecting older connections and evict the\n  -        # youngest one, so only a working block-time protection keeps them\n  connected.\n           self.log.info(\"Create 4 peers and protect them from eviction by\n  sending us a block\")\n           for _ in range(4):\n               block_peer = node.add_p2p_connection(SlowP2PDataStore())\n  @@ -93,13 +95,21 @@ class P2PEvict(BitcoinTestFramework):\n               block = create_block(tip, height=node.getblockcount() + 1,\n  ntime=best_block_time + 1)\n               block.solve()\n               block_peer.send_blocks_and_test([block], node, success=True)\n  +\n  assert_greater_than(node.getpeerinfo()[current_peer][\"last_block\"], 0)\n               protected_peers.add(current_peer)\n\n  +        self.log.info(\"Create 8 peers and protect them from eviction by\n  having faster pings\")\n  +        for _ in range(8):\n  +            fastpeer = node.add_p2p_connection(P2PInterface())\n  +            current_peer += 1\n  +            self.wait_until(lambda: \"ping\" in fastpeer.last_message,\n  timeout=10)\n  +\n           # Make sure by asking the node what the actual min pings are\n  +        self.wait_until(lambda: all(\"minping\" in p for p in\n  node.getpeerinfo()))\n           peerinfo = node.getpeerinfo()\n           pings = {}\n           for i in range(len(peerinfo)):\n  -            pings[i] = peerinfo[i]['minping'] if 'minping' in peerinfo[i]\n  else 1000000\n  +            pings[i] = peerinfo[i]['minping']\n           sorted_pings = sorted(pings.items(), key=lambda x: x[1])\n\n           # Usually the 8 fast peers are protected. In rare case of unreliable\n  pings,\n```",
   "id": "rc:4263718474",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4263718474",
   "hash": "9ac4e79b0982f147",
   "edited": null
  },
  {
   "t": "2026-10-13T23:59:07Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "c75cdce1f9430cab029a329fd86c7e6bef7bf444",
   "in_reply_to": 4263718474,
   "text": "That makes sense, I will take a better look tomorrow morning. I think I can also reproduce the libc++/MSVC behavior by replacing that to `std::stable_sort`.",
   "id": "rc:4265347882",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4265347882",
   "hash": "60a380d30d983dc4",
   "edited": "2026-10-13T23:59:08Z"
  },
  {
   "t": "2026-10-14T15:22:03Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2",
   "id": "push:f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2"
  },
  {
   "t": "2026-10-14T15:23:32Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "Force-pushed addressing https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4263718474 to cover the libc++/MSVC case.",
   "id": "c:6171829532",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#issuecomment-6171829532",
   "hash": "aa1c3b13b71e9440",
   "edited": null
  },
  {
   "t": "2026-10-14T15:25:03Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "c75cdce1f9430cab029a329fd86c7e6bef7bf444",
   "in_reply_to": 4263718474,
   "text": "Addressed your suggestion, it makes sense. I Just removed the per-fast-peer ping wait because they have their own `minping` wait so we may not need it, and improved some comments.",
   "id": "rc:4272772180",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4272772180",
   "hash": "b393d2ccd1878ff5",
   "edited": "2026-10-14T15:25:04Z"
  },
  {
   "t": "2026-10-16T13:36:03Z",
   "kind": "review_comment",
   "who": "Crypt-iQ",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2",
   "in_reply_to": null,
   "text": "note: this fixes an odd scheduling flake that bedrock ran into where `\"ping\" in fastpeer.last_message` succeeds but hasn't arrived yet and so the fast peer gets a value of 1000000",
   "id": "rc:4295233978",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4295233978",
   "hash": "9ba49385f1178085",
   "edited": null
  },
  {
   "t": "2026-10-16T16:10:46Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2",
   "in_reply_to": null,
   "text": "not sure this is reliable. the tests run also in qemu in vms, which may be slow or racy. Also, if someone runs them in bedrock or similar, this may not hold, so the test may fail? Manual sleeps also seem smelly. I think it would be better to replace this with a bumpmocktime(2), and bump(1) in the fast case, so that this is fully deterministic regarless of test env?",
   "id": "rc:4296725875",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4296725875",
   "hash": "98ee1c2f62ac2a4a",
   "edited": null
  },
  {
   "t": "2026-10-19T13:20:58Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2",
   "in_reply_to": 4296725875,
   "text": "Yes, I can change it to control the ping time with mocktime. Basically we can add a `add_node` helper that connects a peer and use mocktime to make the node to have a minimum ping of exactly `ping_time` seconds, then we can move the clock forward so each peer gets its own connection time.",
   "id": "rc:4314300344",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4314300344",
   "hash": "2f43ed8ccdf24bb2",
   "edited": null
  },
  {
   "t": "2026-10-19T13:42:58Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2",
   "in_reply_to": 4296725875,
   "text": "I think `add_p2p_connection` with default args is already synchronous, so there should be no race with other connections, and a plain `bumpmocktime(2)` should work, but I haven't tried this myself. Maybe a helper makes more sense. Happy to review whatever falls out",
   "id": "rc:4314528039",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4314528039",
   "hash": "e9d81a063edcf442",
   "edited": "2026-10-19T13:43:04Z"
  },
  {
   "t": "2026-10-19T13:53:08Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2",
   "in_reply_to": 4296725875,
   "text": "Going to try but I think `bumpmocktime` may not be enough, the interface would answer the ping during the handshake before we can bump.",
   "id": "rc:4314633299",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4314633299",
   "hash": "bd1ab566469b5fb8",
   "edited": "2026-10-19T13:53:09Z"
  },
  {
   "t": "2026-10-19T14:02:03Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2",
   "in_reply_to": 4296725875,
   "text": "I meant that the interface does the bump (in the line I commented here), but anything that works should be fine here.",
   "id": "rc:4314728455",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4314728455",
   "hash": "95a5c0a006c61b96",
   "edited": "2026-10-19T14:02:04Z"
  },
  {
   "t": "2026-10-19T14:15:05Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "2fd9be21e28fecf298493e28e90ccb8085aa4ae8",
   "id": "push:2fd9be21e28fecf298493e28e90ccb8085aa4ae8"
  },
  {
   "t": "2026-10-19T14:15:29Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "f9cdfacdb4fc7420d233ea03637ab59a0aca1fe2",
   "in_reply_to": 4296725875,
   "text": "Done.",
   "id": "rc:4314876783",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4314876783",
   "hash": "ed251864987c367e",
   "edited": null
  },
  {
   "t": "2026-10-19T14:15:39Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "Force-pushed addressing https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4296725875",
   "id": "c:6269356668",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#issuecomment-6269356668",
   "hash": "fdc7c4d95f2f9e7d",
   "edited": null
  },
  {
   "t": "2026-10-20T14:02:01Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "2fd9be21e28fecf298493e28e90ccb8085aa4ae8",
   "in_reply_to": null,
   "text": "nano-nit: Int options should be kw-only:\n```suggestion\n    def add_peer(self, peer, *, ping_time):\n```",
   "id": "rc:4325857612",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4325857612",
   "hash": "afde21e712b47cbe",
   "edited": "2026-10-20T14:54:24Z"
  },
  {
   "t": "2026-10-20T14:54:03Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "Thanks for taking the nits. The test should now be deterministic and twice as fast with mocktime.\n\nreview ACK 2fd9be21e28fecf298493e28e90ccb8085aa4ae8 \ud83d\udddc\n\nShow signature\n\nSignature:\n\n```\nuntrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm \"${path_to_any_empty_file}\" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x \"${path_to_this_whole_four_line_signature_blob}\"\nRUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=\ntrusted comment: review ACK 2fd9be21e28fecf298493e28e90ccb8085aa4ae8 \ud83d\udddc\n8H6NZsZgG/T/YvKzHx9lVC7RLKCKwDlJF4+uICAIg7YRM00chlrllnuxY/I1AF9FZHQIP6IaZsyiu/CJDfhpAg==\n```",
   "id": "c:6293791542",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#issuecomment-6293791542",
   "hash": "34544796c5d623ce",
   "edited": null
  },
  {
   "t": "2026-10-20T14:54:24Z",
   "kind": "review",
   "who": "maflcko",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "2fd9be21e28fecf298493e28e90ccb8085aa4ae8",
   "text": ".",
   "id": "r:5586138829",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#pullrequestreview-5586138829",
   "hash": "cdb4ee2aea69cc6a",
   "edited": null
  },
  {
   "t": "2026-10-20T15:07:37Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "Ralph complains, but to make it happy, one would have to first connect candidates peers, then connect shuffle(tx, block, fast-ping)? Not sure if this is worth it or if it makes Ralph happy. Up to you. This is a solid improvement either way.",
   "id": "c:6294085754",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#issuecomment-6294085754",
   "hash": "2b66bceb62aee55e",
   "edited": null
  },
  {
   "t": "2026-10-26T10:22:19Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI'm not sure is worth, will leave as-is for now unless other reviewers prefer that.",
   "id": "c:6413679538",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#issuecomment-6413679538",
   "hash": "e4c93f8f0c2ea12f",
   "edited": null
  },
  {
   "t": "2026-10-26T10:22:51Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "test/functional/p2p_eviction.py",
   "commit": "2fd9be21e28fecf298493e28e90ccb8085aa4ae8",
   "in_reply_to": 4325857612,
   "text": "Tks, will leave as-is, but can change in case I need to touch it again.",
   "id": "rc:4376172020",
   "url": "https://github.com/bitcoin/bitcoin/pull/36470#discussion_r4376172020",
   "hash": "f63ee3bc879280d2",
   "edited": "2026-10-26T10:22:52Z"
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-08T11:54:41Z",
   "action": "labeled",
   "label": "Tests",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 10735,
 "text_tokens_estimate": 2683,
 "changed_paths": [
  "test/functional/p2p_eviction.py"
 ],
 "files": [
  {
   "path": "test/functional/p2p_eviction.py",
   "add": 57,
   "del": 43
  }
 ],
 "test_lines": 100,
 "git": {
  "head": "2fd9be21e28fecf298493e28e90ccb8085aa4ae8",
  "head_matches_backup": true,
  "base": "368c138231526a9437ae5166f8231d8602bc0908",
  "patch_id": "ca121529bc7079c0",
  "commits": [
   {
    "sha": "2fd9be21e2",
    "subject": "test: make p2p_eviction actually exercise block-time protection",
    "files": 1,
    "add": 57,
    "del": 43
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "ca121529bc7079c0",
 "input_hash": "509a692defc089e1",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}