Skip to content

Commit

Permalink
Implement peristence scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph committed Feb 17, 2025
1 parent 9740886 commit c966091
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 9 deletions.
68 changes: 68 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ tokio-stream = "0.1.9"
tokio-util = {version = "0.7.3", features = ["compat"] }
tower-http = { version = "0.6.2", features = ["auth", "compression-br", "compression-gzip", "cors", "set-header"] }
urlencoding = "2.1.3"
bdk_wallet = "1.1.0"

[dev-dependencies]
criterion = "0.5.1"
Expand Down
1 change: 0 additions & 1 deletion blacklist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ wallet::burn::oversize_metadata_requires_no_limit_flag
wallet::burn::runic_outputs_are_protected
wallet::cardinals::cardinals
wallet::cardinals::cardinals_does_not_show_runic_outputs
wallet::create::create
wallet::create::create_with_different_name
wallet::create::detect_wrong_descriptors
wallet::create::seed_phrases_are_twelve_words_long
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl Restore {
Source::Mnemonic => {
io::stdin().read_line(&mut buffer)?;
let mnemonic = Mnemonic::from_str(&buffer)?;
Wallet::initialize(
Wallet::initialize_old(
name,
settings,
mnemonic.to_seed(self.passphrase.unwrap_or_default()),
Expand Down
110 changes: 103 additions & 7 deletions src/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
super::*,
batch::ParentInfo,
bdk_wallet as bdk,
bitcoin::{
bip32::{ChildNumber, DerivationPath, Xpriv},
psbt::Psbt,
Expand All @@ -12,19 +13,23 @@ use {
index::entry::Entry,
indicatif::{ProgressBar, ProgressStyle},
log::log_enabled,
miniscript::descriptor::{DescriptorSecretKey, DescriptorXKey, Wildcard},
miniscript::descriptor::{
Descriptor, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, Wildcard,
},
redb::{Database, DatabaseError, ReadableTable, RepairSession, StorageError, TableDefinition},
std::sync::Once,
transaction_builder::TransactionBuilder,
};

pub mod batch;
pub mod entry;
pub mod persister;
pub mod transaction_builder;
pub mod wallet_constructor;

const SCHEMA_VERSION: u64 = 1;
const SCHEMA_VERSION: u64 = 2;

define_table! { CHANGESET, (), &str }
define_table! { RUNE_TO_ETCHING, u128, EtchingEntryValue }
define_table! { STATISTICS, u64, u64 }

Expand All @@ -46,7 +51,7 @@ impl From<Statistic> for u64 {
}

#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct Descriptor {
pub struct DescriptorJson {
pub desc: String,
pub timestamp: bitcoincore_rpc::bitcoincore_rpc_json::Timestamp,
pub active: bool,
Expand All @@ -58,7 +63,7 @@ pub struct Descriptor {
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct ListDescriptorsResult {
pub wallet_name: String,
pub descriptors: Vec<Descriptor>,
pub descriptors: Vec<DescriptorJson>,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -465,7 +470,10 @@ impl Wallet {
})
}

fn check_descriptors(wallet_name: &str, descriptors: Vec<Descriptor>) -> Result<Vec<Descriptor>> {
fn check_descriptors(
wallet_name: &str,
descriptors: Vec<DescriptorJson>,
) -> Result<Vec<DescriptorJson>> {
let tr = descriptors
.iter()
.filter(|descriptor| descriptor.desc.starts_with("tr("))
Expand All @@ -486,7 +494,7 @@ impl Wallet {
pub(crate) fn initialize_from_descriptors(
name: String,
settings: &Settings,
descriptors: Vec<Descriptor>,
descriptors: Vec<DescriptorJson>,
) -> Result {
let client = Self::check_version(settings.bitcoin_rpc_client(Some(name.clone()))?)?;

Expand Down Expand Up @@ -526,8 +534,66 @@ impl Wallet {
seed: [u8; 64],
timestamp: bitcoincore_rpc::json::Timestamp,
) -> Result {
panic!("attempt to initialize bitcoin client");
let database = Wallet::create_database(&name, settings)?;

let network = settings.chain().network();

let secp = Secp256k1::new();

let master_private_key = Xpriv::new_master(network, &seed)?;

let fingerprint = master_private_key.fingerprint(&secp);

let derivation_path = DerivationPath::master()
.child(ChildNumber::Hardened { index: 86 })
.child(ChildNumber::Hardened {
index: u32::from(network != Network::Bitcoin),
})
.child(ChildNumber::Hardened { index: 0 });

let derived_private_key = master_private_key.derive_priv(&secp, &derivation_path)?;

let descriptor = |change: bool| -> Result<(
Descriptor<DescriptorPublicKey>,
BTreeMap<DescriptorPublicKey, DescriptorSecretKey>,
)> {
let secret_key = DescriptorSecretKey::XPrv(DescriptorXKey {
origin: Some((fingerprint, derivation_path.clone())),
xkey: derived_private_key,
derivation_path: DerivationPath::master().child(ChildNumber::Normal {
index: change.into(),
}),
wildcard: Wildcard::Unhardened,
});

let public_key = secret_key.to_public(&secp)?;

let mut key_map = BTreeMap::new();
key_map.insert(public_key.clone(), secret_key);

let descriptor = Descriptor::new_tr(public_key, None)?;

Ok((descriptor, key_map))
};

let mut persister = persister::Persister(Arc::new(database));

let mut wallet = bdk::Wallet::create(descriptor(false)?, descriptor(true)?)
.network(network)
.create_wallet(&mut persister)?;

wallet.persist(&mut persister)?;

Ok(())
}

#[allow(unused_variables, unreachable_code)]
pub(crate) fn initialize_old(
name: String,
settings: &Settings,
seed: [u8; 64],
timestamp: bitcoincore_rpc::json::Timestamp,
) -> Result {
Self::check_version(settings.bitcoin_rpc_client(None)?)?.create_wallet(
&name,
None,
Expand Down Expand Up @@ -631,6 +697,36 @@ impl Wallet {
)
}

pub(crate) fn create_database(wallet_name: &String, settings: &Settings) -> Result<Database> {
let path = settings
.data_dir()
.join("wallets")
.join(format!("{wallet_name}.redb"));

if let Err(err) = fs::create_dir_all(path.parent().unwrap()) {
bail!(
"failed to create data dir `{}`: {err}",
path.parent().unwrap().display()
);
}

let database = Database::builder().create(&path)?;

let mut tx = database.begin_write()?;
tx.set_quick_repair(true);

tx.open_table(CHANGESET)?;

tx.open_table(RUNE_TO_ETCHING)?;

tx.open_table(STATISTICS)?
.insert(&Statistic::Schema.key(), &SCHEMA_VERSION)?;

tx.commit()?;

Ok(database)
}

pub(crate) fn open_database(wallet_name: &String, settings: &Settings) -> Result<Database> {
let path = settings
.data_dir()
Expand Down
29 changes: 29 additions & 0 deletions src/wallet/persister.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use {
super::*,
bdk::{ChangeSet, WalletPersister},
};

pub(crate) struct Persister(pub(crate) Arc<Database>);

impl WalletPersister for Persister {
type Error = Error;

fn initialize(persister: &mut Self) -> std::result::Result<bdk_wallet::ChangeSet, Self::Error> {
Ok(ChangeSet::default())
}

fn persist(
persister: &mut Self,
changeset: &bdk_wallet::ChangeSet,
) -> std::result::Result<(), Self::Error> {
let wtx = persister.0.begin_write()?;

wtx
.open_table(CHANGESET)?
.insert((), serde_json::to_string(changeset)?.as_str())?;

wtx.commit()?;

Ok(())
}
}
5 changes: 5 additions & 0 deletions tests/wallet/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,8 @@ fn create_with_different_name() {

assert!(core.wallets().contains("inscription-wallet"));
}

#[test]
fn create_with_same_name_fails() {
todo!()
}

0 comments on commit c966091

Please sign in to comment.