Skip to content

Commit f47cb0b

Browse files
committed
Merge #2174: fix(chain): prevent merge_chains from replacing the genesis block
be8e156 fix(chain): prevent `merge_chains` from replacing the genesis block (Elias Rohrer) Pull request description: ### Description Other code paths (`disconnect_from`, `CheckPoint::insert`) already protect height 0 from modification, but `merge_chains` allowed an update chain with a different genesis hash to silently replace the wallet's existing genesis block. Return `CannotConnectError` when the update attempts to change the block hash at height 0. Co-Authored-By: HAL 9000 ### Checklists #### All Submissions: * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) #### Bugfixes: * [ ] This pull request breaks the existing API * [x] I've added tests to reproduce the issue which are now passing * [ ] I'm linking the issue being fixed by this PR ACKs for top commit: ValuedMammal: ACK be8e156 evanlinjin: ACK be8e156 Tree-SHA512: 743c95f281faf39f7f825693e684ad96a70b2994a6186721a68eacd0c3d4488092883e6dfe3b349e5241fc9a85a02dad222da7e932a50be5324df69d73cbb4a6
2 parents f07423a + be8e156 commit f47cb0b

2 files changed

Lines changed: 22 additions & 4 deletions

File tree

crates/chain/src/local_chain.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,12 @@ fn merge_chains(
648648
}
649649
}
650650
} else {
651+
// The genesis block must never be replaced.
652+
if u.height() == 0 {
653+
return Err(CannotConnectError {
654+
try_include_height: 0,
655+
});
656+
}
651657
// We have an invalidation height so we set the height to the updated hash and
652658
// also purge all the original chain block hashes above this block.
653659
changeset.blocks.insert(u.height(), Some(u.hash()));

crates/chain/tests/test_local_chain.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,11 @@ fn update_local_chain() {
171171
},
172172
TestLocalChain {
173173
name: "fix blockhash before agreement point",
174-
chain: local_chain![(0, hash!("im-wrong")), (1, hash!("we-agree"))],
175-
update: chain_update![(0, hash!("fix")), (1, hash!("we-agree"))],
174+
chain: local_chain![(0, hash!("genesis")), (1, hash!("im-wrong")), (2, hash!("we-agree"))],
175+
update: chain_update![(0, hash!("genesis")), (1, hash!("fix")), (2, hash!("we-agree"))],
176176
exp: ExpectedResult::Ok {
177-
changeset: &[(0, Some(hash!("fix")))],
178-
init_changeset: &[(0, Some(hash!("fix"))), (1, Some(hash!("we-agree")))],
177+
changeset: &[(1, Some(hash!("fix")))],
178+
init_changeset: &[(0, Some(hash!("genesis"))), (1, Some(hash!("fix"))), (2, Some(hash!("we-agree")))],
179179
},
180180
},
181181
// B and C are in both chain and update
@@ -315,6 +315,18 @@ fn update_local_chain() {
315315
],
316316
},
317317
},
318+
// Reject update that replaces the genesis block
319+
// | 0 | 1 | 2
320+
// chain | A B C
321+
// update | A' B' C'
322+
TestLocalChain {
323+
name: "reject genesis block replacement",
324+
chain: local_chain![(0, hash!("A")), (1, hash!("B")), (2, hash!("C"))],
325+
update: chain_update![(0, hash!("A'")), (1, hash!("B'")), (2, hash!("C'"))],
326+
exp: ExpectedResult::Err(CannotConnectError {
327+
try_include_height: 0,
328+
}),
329+
},
318330
]
319331
.into_iter()
320332
.for_each(TestLocalChain::run);

0 commit comments

Comments
 (0)