bitcoindevkit / bitcoindevkit/bdk

file_store: `Store::append` through a second handle overwrites changesets appended via another handle

Open Beginner friendly
#2,308 0 comments 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.