Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

`PersistedWallet::create` empty-store check and initial write are not atomic

Abierto
#562 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 2 días

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
4/5
Tiempo estimado
3-5 días
Aptitud para principiantes
48/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
rust, sqlite
Área
database

Línea de trabajo

Start with PersistedWallet::create and create_async in src/wallet/persisted.rs, then review the WalletPersister initialize and persist contract and the rusqlite implementation. Run the concurrent reproduction from the issue under tests/ and determine how creation should behave when both callers race; done means the two calls cannot leave a PersistedWallet inconsistent with the store.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

bug

Describe the bug

PersistedWallet::create (src/wallet/persisted.rs) guards against overwriting an existing store by calling P::initialize and returning DataAlreadyExists if the changeset is non-empty, then writes the initial changeset with a separate P::persist call. The two steps are not atomic. With the rusqlite::Connection persister each step commits its own transaction, so two callers creating a wallet on the same database at the same time can both observe an empty store and both get Ok.

The store itself does not get corrupted in the descriptor sense: the SQLite ON CONFLICT ... COALESCE upsert and ChangeSet::merge keep the first descriptor. But the second PersistedWallet is then inconsistent with its store: it holds a different descriptor in memory, its later persist calls write keychain/tx data for that descriptor into the first wallet's database, and a subsequent load returns the first wallet. create_async has the same structure.

This issue was found by AI.

To Reproduce

// tests/, requires the `test-utils` and `rusqlite` features
use std::sync::{Arc, Barrier};
use std::thread;

use bdk_chain::rusqlite;
use bdk_wallet::test_utils::{get_test_tr_single_sig_xprv, get_test_wpkh};
use bdk_wallet::{ChangeSet, KeychainKind, Wallet, WalletPersister};
use bitcoin::Network;

/// Wraps the SQLite persister only to force the interleaving:
/// both `initialize`s complete before either `persist`.
struct RacingConnection {
    conn: rusqlite::Connection,
    initialized: Arc<Barrier>,
    persist_order: Arc<Barrier>,
    persist_first: bool,
}

impl WalletPersister for RacingConnection {
    type Error = rusqlite::Error;

    fn initialize(p: &mut Self) -> Result<ChangeSet, Self::Error> {
        let changeset = <rusqlite::Connection as WalletPersister>::initialize(&mut p.conn)?;
        p.initialized.wait();
        Ok(changeset)
    }

    fn persist(p: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
        if p.persist_first {
            let res = <rusqlite::Connection as WalletPersister>::persist(&mut p.conn, changeset);
            p.persist_order.wait();
            res
        } else {
            p.persist_order.wait();
            <rusqlite::Connection as WalletPersister>::persist(&mut p.conn, changeset)
        }
    }
}

#[test]
fn concurrent_create_on_same_store() {
    let temp_dir = tempfile::tempdir().unwrap();
    let db_path = temp_dir.path().join("wallet.db");
    let mut bootstrap = rusqlite::Connection::open(&db_path).unwrap();
    <rusqlite::Connection as WalletPersister>::initialize(&mut bootstrap).unwrap();
    drop(bootstrap);

    let initialized = Arc::new(Barrier::new(2));
    let persist_order = Arc::new(Barrier::new(2));
    let mut first_store = RacingConnection {
        conn: rusqlite::Connection::open(&db_path).unwrap(),
        initialized: initialized.clone(),
        persist_order: persist_order.clone(),
        persist_first: true,
    };
    let mut second_store = RacingConnection {
        conn: rusqlite::Connection::open(&db_path).unwrap(),
        initialized,
        persist_order,
        persist_first: false,
    };

    let first = thread::spawn(move || {
        Wallet::create_single(get_test_tr_single_sig_xprv())
            .network(Network::Testnet)
            .create_wallet(&mut first_store)
    });
    let second = thread::spawn(move || {
        Wallet::create_single(get_test_wpkh())
            .network(Network::Testnet)
            .create_wallet(&mut second_store)
    });
    let first = first.join().unwrap();
    let second = second.join().unwrap();

    // Both succeed. The store keeps the `tr(..)` descriptor while `second`
    // holds the `wpkh(..)` one in memory.
    assert!(first.is_ok());
    let second = second.unwrap();
    let mut conn = rusqlite::Connection::open(&db_path).unwrap();
    let loaded = Wallet::load().load_wallet(&mut conn).unwrap().unwrap();
    assert_ne!(
        second.public_descriptor(KeychainKind::External),
        loaded.public_descriptor(KeychainKind::External)
    );
}

Expected behavior

Only one of two concurrent create calls on the same store should succeed, or the resulting PersistedWallet should always match what is in the store. Whether that is solved in PersistedWallet::create, in the persister trait contract, or by documenting that callers must serialize first-time creation (e.g. via the rusqlite::Transaction persister) is up to the maintainers.

Build environment

  • BDK tag/commit: 6fc68462 (master)

Which backend(s) are relevant (if any)?

  • None / not backend-related (e.g. bdk_chain, bdk_core)

Is this blocking production use?

  • No
Lenguaje dominante
Rust
Estrellas
61
Forks
116
Merge medio
7 h 29 min
PR fusionados (30 d)
6

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de bitcoindevkit/bdk_wallet

Todos los issues de bitcoindevkit/bdk_wallet

Issues similares

Más issues de Rust

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.