{
 "number": 35750,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35750",
 "title": "addrman: make `m_last_good` network-specific",
 "author": "mzumsande",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-07-19T21:55:25Z",
 "updated_at": "2026-08-12T13:03:31Z",
 "age_days": 59,
 "draft": false,
 "labels": [
  "P2P"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "31191b52396b245294efecf7d601476fc6734bc4",
 "head_ref": "202607_addrman_last_good",
 "head_repo": "mzumsande/bitcoin",
 "head_history": [
  {
   "t": "2026-07-19T21:57:15Z",
   "sha": "31191b52396b245294efecf7d601476fc6734bc4"
  }
 ],
 "additions": 61,
 "deletions": 4,
 "changed_files": 3,
 "commit_count": 2,
 "size_bucket": "S",
 "mergeable_state": "blocked",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "fjahr",
      "url": "https://github.com/bitcoin/bitcoin/pull/35750#issuecomment-5020237589"
     },
     {
      "login": "brunoerg",
      "url": "https://github.com/bitcoin/bitcoin/pull/35750#issuecomment-5046398880"
     },
     {
      "login": "stratospher",
      "url": "https://github.com/bitcoin/bitcoin/pull/35750#issuecomment-5102633395"
     },
     {
      "login": "naiyoma",
      "url": "https://github.com/bitcoin/bitcoin/pull/35750#issuecomment-5184708571"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "fjahr": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-07-20T08:29:52Z",
   "stale": false
  },
  "brunoerg": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-07-22T13:26:45Z",
   "stale": false
  },
  "stratospher": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-07-28T09:56:01Z",
   "stale": false
  },
  "naiyoma": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-04T21:14:58Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 4,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "brunoerg",
   "fjahr",
   "naiyoma",
   "stratospher"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "blocked",
  "last_author_activity": "2026-08-12T13:03:31Z",
  "last_reviewer_activity": "2026-08-05T18:43:19Z",
  "last_reviewer": "naiyoma",
  "author_silent_days": 36,
  "waiting_on_author_days": 0,
  "days_since_update": 36
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "`m_last_good` is used to prevent recording failed attempts for addresses when it is our connectivity that is at fault.\nHowever, it is quite common that only some networks are unreachable - e.g. I have encountered it frequently that `IPv6` is unreachable, while `IPv4` works fine. In this case it is not correct to increase the failed counter for an `IPv6` address  and ultimately making it `Terrible`, just because we are making `IPv4` connections.\n\nFix this by making `m_last_good` network-specific.\nAlso change the initial value from 1 to 0, so that in a situation when there was no previous attempt for an address, but also no previous successful connection to its network, the failed count is not increased. The intial value was not a major issue previously because we would [only count failures](https://github.com/bitcoin/bitcoin/blob/18c05d93016b28a9afd4c716dfe00b6e0accb30b/src/net.cpp#L2935-L2939) after having at least two outbound connections (in which case `m_last_good` would have been set).",
 "commits": [
  {
   "sha": "b8840fd1c66abe91935e27280e3ca0e2e54cc3a6",
   "date": "2026-07-19T21:32:50Z",
   "message": "addrman: don't count connection failures before the first success\n\nm_last_good was initialised to 1 instead of 0, so that an entry whose\nm_last_count_attempt is still 0 is eligible for one counted failure even\nthough we have never connected to anything successfully.\n\nIn practice, this makes little difference: net.cpp normally only passes\nfCountFailure once we have at least two outbound peers, which implies\nthat Good() has already been called.\nHowever, it is changed here in preparation of making m_last_good a\nnetwork-specific array."
  },
  {
   "sha": "31191b52396b245294efecf7d601476fc6734bc4",
   "date": "2026-07-19T21:56:56Z",
   "message": "addrman: make m_last_good network-specific\n\nAttempt_() only counts a failed connection attempt if\nwe have made a successful connection since the last attempt,\nso that we don't blame addresses for our own lack of connectivity.\nThis was network-agnostic: successful connections on any\nnetwork re-enabled the check for all addresses.\n\nAs a result, a network that is unreachable for us while others keep\nworking - e.g. if IPv6 is not reachable, or a non-working Tor proxy -\nwould lead to addresses of that network becoming Terrible.\n\nAvoid this by tracking the last successful connection per\nnetwork instead."
  }
 ],
 "timeline": [
  {
   "t": "2026-07-19T21:57:15Z",
   "kind": "force_push",
   "who": "mzumsande",
   "commit": "31191b52396b245294efecf7d601476fc6734bc4"
  },
  {
   "t": "2026-07-20T08:29:52Z",
   "kind": "comment",
   "who": "fjahr",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-07-22T13:26:45Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-07-28T09:56:01Z",
   "kind": "comment",
   "who": "stratospher",
   "assoc": "CONTRIBUTOR",
   "text": "concept ACK. interesting find! this is what `m_last_good` is ideally supposed to be doing.\n\nI'm mostly wondering about the balance here between keeping vs evicting these addresses.  suppose ipv6 stays unreachable and we keep receiving ipv6 gossip (timestamps refresh) - then these addresses would stay indefinitely in the addrman (and won't be evicted unless they have nRefCount > 1) and are kind of useless to us till the connectivity issue is fixed.\n\non this branch such an entry mainly leaves the new table when:\n1. it is >30 days stale and nobody on the network gossips them anymore\n2. we can reach IPv6 again and this address is genuinely unreachable.\n\nis that too strict? that's the only confusing part. everything else looks good to me!"
  },
  {
   "t": "2026-08-04T21:14:58Z",
   "kind": "comment",
   "who": "naiyoma",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-08-05T18:43:19Z",
   "kind": "review",
   "who": "naiyoma",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "31191b52396b245294efecf7d601476fc6734bc4",
   "text": "tested `m_last_good` on `master` and on this PR.\n\nOn master, `m_last_good` is written after receiving a VERSION message and is then used to update the attempt count for the next peer we try to connect to. As a result, an address can have its failure counted and eventually become terrible based on the previous peer's handshake on an unrelated network.\n\nFrom my logs, a single onion peer completes its handshake and writes `m_last_good`, which persists and is subsequently used on the next connection just before we update the attempt count.\n\n`2026-08-04T17:25:28Z [addrman] Good_ address: 6c52w67j3fzmbig3ikjn3dx5w3ne2v57lys7u3whahwu6teb4atr35qd.onion:8333, nTime:  2026-07-27T11:29:22Z, nLastSuccess: 2026-08-04T17:25:28Z, nLastTry: 2026-08-04T17:25:28Z, fInTried: true, nRefcount 0, nAttempt 0`\n\n`2026-08-04T17:25:31Z [addrman] Attempting connection to 174.89.232.9:8333: nTime: 2026-07-07T00:47:05Z, fCountFailure 1, m_last_count_attempt 1970-01-01T00:00:00Z,  nLastSucess: 1970-01-01T00:00:00Z, nLastTry: 2026-08-04T17:25:31Z, fInTried: false, nRefcount 2, nAttempt 0, m_last_good 2026-08-04T17:25:28Z`\n\nOn this PR, an onion peer completes its handshake and updates only the onion network's `m_last_good` timestamp.\n\n`2026-08-05T14:22:12Z [addrman] Good_ flp63d7zcfzoqfzjkvwcgdxrwcf4e4jmjyyvlwjv2sepzkm2kj526oid.onion:8333, nTime:  2026-08-03T00:50:51Z, nLastSuccess: 2026-08-05T14:22:12Z, nLastTry: 2026-08-05T14:22:12Z, fInTried: true, nRefcount 0, nAttempt 0, m_last_good 2026-08-05T14:22:12Z`\n\nWhen attempting a connection on another network, a separate `m_last_good` timestamp is used instead.\n`2026-08-05T14:22:16Z [addrman] Attempting connection to 45.155.40.189:8333: nTime: 2026-07-05T11:22:31Z, fCountFailure 0, m_last_count_attempt 1970-01-01T00:00:00Z,  nLastSucess: 1970-01-01T00:00:00Z, nLastTry: 2026-08-05T14:22:16Z, fInTried: false, nRefcount 1, nAttempt 0, m_last_good 1970-01-01T00:00:00Z`\n\n`Good_` is also reached from `ResolveCollisions_` without a live connection, so `m_last_good` is advanced there as well. bt this PR does not affect that path."
  },
  {
   "t": "2026-08-12T13:03:31Z",
   "kind": "comment",
   "who": "mzumsande",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nGood point, at the end of the day I think we'd have to live with that. If the user knows that they will not be able to reach IPv6 permanently, they would best specify that we `-onlynet=ipv4`. It would be ideal if we could auto-detect IPv6 connectivity in a privacy-preserving way (that doesn't involve e.g. pinging publicly known IPv6 addresses), but that doesn't seem trivial.\n\nBut I think it is also very common, that nodes (e.g. laptops) will be run from different network environments, so that they only temporarily cannot reach IPv6 peers."
  }
 ],
 "labels_log": [
  {
   "t": "2026-07-19T21:55:29Z",
   "action": "labeled",
   "label": "P2P",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-19T21:57:44Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-19T22:58:30Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 5614,
 "text_tokens_estimate": 1403,
 "changed_paths": [
  "src/addrman.cpp",
  "src/addrman_impl.h",
  "src/test/addrman_tests.cpp"
 ],
 "files": [
  {
   "path": "src/addrman.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/addrman_impl.h",
   "add": 5,
   "del": 2
  },
  {
   "path": "src/test/addrman_tests.cpp",
   "add": 54,
   "del": 0
  }
 ],
 "test_lines": 54,
 "git": {
  "head": "31191b52396b245294efecf7d601476fc6734bc4",
  "head_matches_backup": true,
  "base": "18c05d93016b28a9afd4c716dfe00b6e0accb30b",
  "commits": [
   {
    "sha": "b8840fd1c6",
    "subject": "addrman: don't count connection failures before the first success",
    "files": 1,
    "add": 4,
    "del": 2
   },
   {
    "sha": "31191b5239",
    "subject": "addrman: make m_last_good network-specific",
    "files": 3,
    "add": 60,
    "del": 5
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "76900f593e4fbf86",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}