bitcoindevkit / bitcoindevkit/bdk
file_store: `Store::append` through a second handle overwrites changesets appended via another handle
- Dominant language
- Rust
- Stars
- 1.1k
- Forks
- 483
- Avg merge
- 20d 3h
- Merged PRs (30d)
- 3
Description
**Describe the bug**
`Store::append` (`crates/file_store/src/store.rs`) serializes the changeset at the file handle's current cursor. The file is opened without append mode and `append` neither seeks to the end nor takes a lock, so the cursor of each `Store` reflects the end of the file at the time that handle was loaded. When two handles for the same file exist (e.g. two processes, or a second `Store::load` while the first is still in use), an append through one handle is overwritten by a later append through the other. If the entries have equal size the file stays fully decodable, so the earlier changeset is lost silently.
#2258 appears to address this by seeking to the end of the file before each write.
This issue was found by AI.
**To Reproduce**
Add `crates/file_store/tests/test_stale_handle.rs` and run `cargo test -p bdk_file_store --test test_stale_handle`:
```rust
use bdk_file_store::Store;
use std::collections::BTreeSet;
const MAGIC: &[u8] = b"bdk_test_magic";
type ChangeSet = BTreeSet;
#[test]
fn append_through_second_handle_keeps_earlier_append() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("db");
let mut first = Store::::create(MAGIC, &path).unwrap();
first.append(&ChangeSet::from(["initial".to_string()])).unwrap();
// Second handle opened while the file ends after "initial".
let (mut second, _) = Store::::load(MAGIC, &path).unwrap();
first.append(&ChangeSet::from(["first".to_string()])).unwrap();
second.append(&ChangeSet::from(["other".to_string()])).unwrap();
drop((first, second));
let (_, recovered) = Store::::load(MAGIC, &path).unwrap();
let expected = ChangeSet::from(["initial".into(), "first".into(), "other".into()]);
assert_eq!(recovered, Some(expected));
}
```
Current output:
```
assertion `left == right` failed
left: Some({"initial", "other"})
right: Some({"first", "initial", "other"})
```
**Expected behavior**
A changeset that was successfully appended should not be lost by a later append through another handle to the same file.
Contributor guide
Research direction
Start with crates/file_store/src/store.rs and run cargo test -p bdk_file_store --test test_stale_handle after adding the reproducer at crates/file_store/tests/test_stale_handle.rs. Inspect Store::append and verify that appends through both handles preserve all three changesets, matching the expected recovered value.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100