{
 "number": 36224,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36224",
 "title": "test: Add test coverage for `PartiallySignedTransaction::Merge()`",
 "author": "nebula-21",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-09-10T23:24:36Z",
 "updated_at": "2026-09-11T00:32:57Z",
 "age_days": 6,
 "draft": false,
 "labels": [
  "Tests"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "4a5a082d306d9b4eab4ea9db6643efc307a9e961",
 "head_ref": "merge-psbt-coverage",
 "head_repo": "nebula-21/bitcoin",
 "head_history": [
  {
   "t": "2026-09-10T23:32:19Z",
   "sha": "4a5a082d306d9b4eab4ea9db6643efc307a9e961"
  }
 ],
 "additions": 86,
 "deletions": 0,
 "changed_files": 1,
 "commit_count": 3,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "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": 0,
  "changes_requested": 0,
  "distinct_reviewers": []
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-10T23:32:32Z",
  "last_reviewer_activity": null,
  "last_reviewer": null,
  "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": [],
 "body": "This PR adds test coverage for `PartiallySignedTransaction::Merge()` divided into 3 tests:\n\n**1.Commit 0d21098e27dff9dc930a1d1ce9f72d82f37aef89 only merge PSBTs with same version**: Lines (46-47) have no coverage prior to this PR. The test can be triggered with this mutant as an example:\n\nDiff\n\n```diff\ndiff --git a/src/psbt.cpp b/src/psbt.cpp\nindex 51fb19591a..cf5eef0089 100644\n--- a/src/psbt.cpp\n+++ b/src/psbt.cpp\n@@ -43,7 +43,7 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)\n         return false;\n     }\n     if (GetVersion() != psbt.GetVersion()) {\n-        return false;\n+        return true;\n     }\n\n     for (unsigned int i = 0; i < inputs.size(); ++i) {\n```\n\n**2.Commit 0aedfc5a5918fae10f26efccdbfc2e332c8ad458 merge PSBT `fallback_locktime` coverage**: Line 56 checks if the psbt has a fallback locktime, if not, it adopts the one from the psbt is merging. Prior to this PR no coverage was provided. The test can be triggered with this mutant as an example:\n\nDiff\n\n```diff\ndiff --git a/src/psbt.cpp b/src/psbt.cpp\nindex 51fb19591a..ce909eb062 100644\n--- a/src/psbt.cpp\n+++ b/src/psbt.cpp\n@@ -53,7 +53,7 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)\n         outputs[i].Merge(psbt.outputs[i]);\n     }\n     MergeGlobalXPubs(psbt);\n-    if (fallback_locktime == std::nullopt && psbt.fallback_locktime != std::nullopt) fallback_locktime = psbt.fallback_locktime;\n+    if (fallback_locktime == std::nullopt || psbt.fallback_locktime != std::nullopt) fallback_locktime = psbt.fallback_locktime;\n\n     // Set m_tx_modifiable only if either PSBT had it set\n     if (m_tx_modifiable.has_value() || psbt.m_tx_modifiable.has_value()) {\n```\n\n**While writing this test I had a question**:  If both PSBTs have a `fallback_locktime` value, the \"this\" (receiving side) wins silently. Is this the expected behavior? Why not keep the max or directly fail merging if we have two conflicting fallback_locktimes?\n\n**3.Commit 4a5a082d306d9b4eab4ea9db6643efc307a9e961 merge PSBT `m_tx_modifiable` coverage**: Added missing coverage for all the mutants. The test can be triggered with this mutant as one of the various examples:\n\nDiff\n\n```diff\ndiff --git a/src/psbt.cpp b/src/psbt.cpp\nindex 51fb19591a..29eb910014 100644\n--- a/src/psbt.cpp\n+++ b/src/psbt.cpp\n@@ -56,7 +56,7 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)\n     if (fallback_locktime == std::nullopt && psbt.fallback_locktime != std::nullopt) fallback_locktime = psbt.fallback_locktime;\n\n     // Set m_tx_modifiable only if either PSBT had it set\n-    if (m_tx_modifiable.has_value() || psbt.m_tx_modifiable.has_value()) {\n+    if (m_tx_modifiable.has_value() && psbt.m_tx_modifiable.has_value()) {\n         // In general, we AND the modifiable flags\n         std::bitset<8> this_modifiable = m_tx_modifiable.value_or(0);\n         std::bitset<8> psbt_modifiable = psbt.m_tx_modifiable.value_or(0);\n```",
 "commits": [
  {
   "sha": "0d21098e27dff9dc930a1d1ce9f72d82f37aef89",
   "date": "2026-09-10T22:47:37Z",
   "message": "test: only merge PSBTs with same version"
  },
  {
   "sha": "0aedfc5a5918fae10f26efccdbfc2e332c8ad458",
   "date": "2026-09-10T22:47:37Z",
   "message": "test: merge PSBT `fallback_locktime` coverage"
  },
  {
   "sha": "4a5a082d306d9b4eab4ea9db6643efc307a9e961",
   "date": "2026-09-10T23:32:32Z",
   "message": "test: merge PSBT `m_tx_modifiable` coverage"
  }
 ],
 "timeline": [
  {
   "t": "2026-09-10T23:32:19Z",
   "kind": "force_push",
   "who": "nebula-21",
   "commit": "4a5a082d306d9b4eab4ea9db6643efc307a9e961"
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-10T23:24:42Z",
   "action": "labeled",
   "label": "Tests",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-10T23:33:33Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-11T00:32:57Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 3157,
 "text_tokens_estimate": 789,
 "changed_paths": [
  "src/test/psbt_tests.cpp"
 ],
 "files": [
  {
   "path": "src/test/psbt_tests.cpp",
   "add": 86,
   "del": 0
  }
 ],
 "test_lines": 86,
 "git": {
  "head": "4a5a082d306d9b4eab4ea9db6643efc307a9e961",
  "head_matches_backup": true,
  "base": "fc6923cec5b440b611700f6629d8c6a61c6f11bd",
  "commits": [
   {
    "sha": "0d21098e27",
    "subject": "test: only merge PSBTs with same version",
    "files": 1,
    "add": 10,
    "del": 0
   },
   {
    "sha": "0aedfc5a59",
    "subject": "test: merge PSBT `fallback_locktime` coverage",
    "files": 1,
    "add": 37,
    "del": 0
   },
   {
    "sha": "4a5a082d30",
    "subject": "test: merge PSBT `m_tx_modifiable` coverage",
    "files": 1,
    "add": 39,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "466acf8d895d0f13",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}