Skip to content

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

Description

@noahjoeris

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:

use bdk_file_store::Store;
use std::collections::BTreeSet;

const MAGIC: &[u8] = b"bdk_test_magic";
type ChangeSet = BTreeSet<String>;

#[test]
fn append_through_second_handle_keeps_earlier_append() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("db");

    let mut first = Store::<ChangeSet>::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::<ChangeSet>::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::<ChangeSet>::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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    • Status
      In Progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions