Skip to content

Commit

Permalink
Errors improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
cryptoquick committed Jul 16, 2023
1 parent 8db1bdc commit d75f23c
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 53 deletions.
1 change: 1 addition & 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 @@ -84,6 +84,7 @@ serde_json = "1.0.91"
serde-encrypt = "0.7.0"
strict_encoding = "~2.5"
strict_types = "~1.5"
thiserror = "1.0"
tokio = { version = "1.28.2", features = ["macros", "sync"] }
zeroize = "1.6.0"

Expand Down
2 changes: 1 addition & 1 deletion src/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod wallet;

pub use crate::bitcoin::{
assets::dust_tx,
keys::{new_mnemonic, save_mnemonic},
keys::{new_mnemonic, save_mnemonic, BitcoinKeysError},
payment::{create_payjoin, create_transaction},
psbt::{sign_psbt, sign_psbt_with_multiple_wallets},
wallet::{get_blockchain, get_wallet, sync_wallet, sync_wallets, MemoryWallet},
Expand Down
67 changes: 53 additions & 14 deletions src/bitcoin/keys.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,57 @@
use std::str::FromStr;

use anyhow::{anyhow, Result};
use bdk::{
bitcoin::{
secp256k1::Secp256k1,
util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, KeySource},
},
keys::{DerivableKey, DescriptorKey, DescriptorKey::Secret as SecretDesc, DescriptorSecretKey},
miniscript::Tap,
miniscript::{descriptor::DescriptorKeyParseError, Tap},
};
use bip39::{Language, Mnemonic};
use bitcoin_hashes::{sha256, Hash};
use miniscript_crate::DescriptorPublicKey;
use nostr_sdk::prelude::{FromSkStr, ToBech32};
use thiserror::Error;
use zeroize::Zeroize;

use crate::{
constants::{BTC_PATH, NETWORK, NOSTR_PATH},
structs::{DecryptedWalletData, PrivateWalletData, PublicWalletData, SecretString},
};

fn get_descriptor(xprv: &ExtendedPrivKey, path: &str, change: u32) -> Result<DescriptorSecretKey> {
#[derive(Error, Debug, Display)]
#[display(doc_comments)]
pub enum BitcoinKeysError {
/// Unexpected key variant in get_descriptor
UnexpectedKey,
/// Unexpected xpub descriptor
UnexpectedWatcherXpubDescriptor,
/// Unexpected key variant in nostr_keypair
UnexpectedKeyVariantInNostrKeypair,
/// BIP-32 error
Bip32Error(#[from] bitcoin::util::bip32::Error),
/// BIP-39 error
Bip39Error(#[from] bip39::Error),
/// BDK key error
BdkKeyError(#[from] bdk::keys::KeyError),
/// Miniscript descriptor key parse error
MiniscriptDescriptorKeyParseError(#[from] DescriptorKeyParseError),
/// getrandom error
GetRandomError(#[from] getrandom::Error),
/// Nostr SDK key error
NostrKeyError(#[from] nostr_sdk::key::Error),
/// Nostr SDK key error
NostrNip19Error(#[from] nostr_sdk::nips::nip19::Error),
}

fn get_descriptor(
xprv: &ExtendedPrivKey,
path: &str,
change: u32,
) -> Result<DescriptorSecretKey, BitcoinKeysError> {
let secp = Secp256k1::new();
let deriv_descriptor: DerivationPath = DerivationPath::from_str(path)?;
let deriv_descriptor = DerivationPath::from_str(path)?;
let derived_xprv = &xprv.derive_priv(&secp, &deriv_descriptor)?;
let origin: KeySource = (xprv.fingerprint(&secp), deriv_descriptor);
let derived_xprv_desc_key: DescriptorKey<Tap> = derived_xprv.into_descriptor_key(
Expand All @@ -33,37 +62,45 @@ fn get_descriptor(xprv: &ExtendedPrivKey, path: &str, change: u32) -> Result<Des
if let SecretDesc(desc_seckey, _, _) = derived_xprv_desc_key {
Ok(desc_seckey)
} else {
Err(anyhow!("Unexpected key variant in get_descriptor"))
Err(BitcoinKeysError::UnexpectedKey)
}
}

fn xprv_desc(xprv: &ExtendedPrivKey, path: &str, change: u32) -> Result<String> {
fn xprv_desc(xprv: &ExtendedPrivKey, path: &str, change: u32) -> Result<String, BitcoinKeysError> {
let xprv = get_descriptor(xprv, path, change)?;

Ok(format!("tr({xprv})"))
}

fn xpub_desc(xprv: &ExtendedPrivKey, path: &str, change: u32) -> Result<String> {
fn xpub_desc(xprv: &ExtendedPrivKey, path: &str, change: u32) -> Result<String, BitcoinKeysError> {
let secp = Secp256k1::new();
let xprv = get_descriptor(xprv, path, change)?;
let xpub = xprv.to_public(&secp)?;

Ok(format!("tr({xpub})"))
}

fn watcher_xpub(xprv: &ExtendedPrivKey, path: &str, change: u32) -> Result<String> {
fn watcher_xpub(
xprv: &ExtendedPrivKey,
path: &str,
change: u32,
) -> Result<String, BitcoinKeysError> {
let secp = Secp256k1::new();
let xprv = get_descriptor(xprv, path, change)?;
let xpub = xprv.to_public(&secp)?;

if let DescriptorPublicKey::XPub(desc) = xpub {
Ok(desc.xkey.to_string())
} else {
Err(anyhow!("Unexpected xpub descriptor"))
Err(BitcoinKeysError::UnexpectedWatcherXpubDescriptor)
}
}

fn nostr_keypair(xprv: &ExtendedPrivKey, path: &str, change: u32) -> Result<(String, String)> {
fn nostr_keypair(
xprv: &ExtendedPrivKey,
path: &str,
change: u32,
) -> Result<(String, String), BitcoinKeysError> {
let secp = Secp256k1::new();
let xprv = get_descriptor(xprv, path, change)?;

Expand All @@ -78,11 +115,13 @@ fn nostr_keypair(xprv: &ExtendedPrivKey, path: &str, change: u32) -> Result<(Str
hex::encode(first_keypair.x_only_public_key().0.serialize()),
))
} else {
Err(anyhow!("Unexpected key variant in nostr_keypair"))
Err(BitcoinKeysError::UnexpectedKeyVariantInNostrKeypair)
}
}

pub async fn new_mnemonic(seed_password: &SecretString) -> Result<DecryptedWalletData> {
pub async fn new_mnemonic(
seed_password: &SecretString,
) -> Result<DecryptedWalletData, BitcoinKeysError> {
let mut entropy = [0u8; 32];
getrandom::getrandom(&mut entropy)?;
let mnemonic = Mnemonic::from_entropy_in(Language::English, &entropy)?;
Expand All @@ -94,7 +133,7 @@ pub async fn new_mnemonic(seed_password: &SecretString) -> Result<DecryptedWalle
pub async fn save_mnemonic(
mnemonic_phrase: &SecretString,
seed_password: &SecretString,
) -> Result<DecryptedWalletData> {
) -> Result<DecryptedWalletData, BitcoinKeysError> {
let mnemonic = Mnemonic::from_str(&mnemonic_phrase.0)?;

get_mnemonic(mnemonic, seed_password).await
Expand All @@ -103,7 +142,7 @@ pub async fn save_mnemonic(
pub async fn get_mnemonic(
mnemonic_phrase: Mnemonic,
seed_password: &SecretString,
) -> Result<DecryptedWalletData> {
) -> Result<DecryptedWalletData, BitcoinKeysError> {
let seed = mnemonic_phrase.to_seed_normalized(&seed_password.0);

let network = NETWORK.read().await;
Expand Down
12 changes: 12 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use thiserror::Error;

use crate::{bitcoin::BitcoinKeysError, rgb::IssuerOperationError};

#[derive(Error, Debug, Display)]
#[display(doc_comments)]
pub enum BitMaskCoreError {
/// Bitcoin Keys Error
BitcoinKeysError(#[from] BitcoinKeysError),
/// RGB Issuer Operation Error
RgbIssuerOperationError(#[from] IssuerOperationError),
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extern crate amplify;
pub mod bitcoin;
pub mod carbonado;
pub mod constants;
pub mod error;
pub mod lightning;
pub mod nostr;
pub mod rgb;
Expand Down
72 changes: 35 additions & 37 deletions src/rgb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use rgbstd::{
persistence::{Stash, Stock},
};
use strict_encoding::StrictSerialize;
use thiserror::Error;

pub mod accept;
pub mod carbonado;
Expand Down Expand Up @@ -73,7 +74,7 @@ use self::{
},
};

#[derive(Debug, Clone, Eq, PartialEq, Display, From)]
#[derive(Debug, Clone, Eq, PartialEq, Display, From, Error)]
#[display(doc_comments)]
pub enum IssuerOperationError {
// Some request data is missing. {0}
Expand All @@ -91,7 +92,10 @@ pub enum IssuerOperationError {
}

/// RGB Operations
pub async fn issue_contract(sk: &str, request: IssueRequest) -> Result<IssueResponse> {
pub async fn issue_contract(
sk: &str,
request: IssueRequest,
) -> Result<IssueResponse, IssuerOperationError> {
let IssueRequest {
ticker,
name,
Expand All @@ -103,24 +107,19 @@ pub async fn issue_contract(sk: &str, request: IssueRequest) -> Result<IssueResp
meta,
} = request;

let mut stock = match retrieve_stock(sk, ASSETS_STOCK).await {
Ok(stock) => stock,
_ => {
return Err(anyhow!(IssuerOperationError::Retrive(
CARBONADO_UNAVALIABLE.to_string(),
STOCK_UNAVALIABLE.to_string(),
)))
}
};
let mut rgb_account = match retrieve_wallets(sk, ASSETS_WALLETS).await {
Ok(rgb_account) => rgb_account,
_ => {
return Err(anyhow!(IssuerOperationError::Retrive(
CARBONADO_UNAVALIABLE.to_string(),
WALLET_UNAVALIABLE.to_string(),
)))
}
};
let mut stock = retrieve_stock(sk, ASSETS_STOCK).await.map_err(|_| {
IssuerOperationError::Retrive(
CARBONADO_UNAVALIABLE.to_string(),
STOCK_UNAVALIABLE.to_string(),
)
})?;

let mut rgb_account = retrieve_wallets(sk, ASSETS_WALLETS).await.map_err(|_| {
IssuerOperationError::Retrive(
CARBONADO_UNAVALIABLE.to_string(),
WALLET_UNAVALIABLE.to_string(),
)
})?;

let mut resolver = ExplorerResolver {
explorer_url: BITCOIN_EXPLORER_API.read().await.to_string(),
Expand All @@ -142,7 +141,7 @@ pub async fn issue_contract(sk: &str, request: IssueRequest) -> Result<IssueResp
_ => None,
};

let contract = match create_contract(
let contract = create_contract(
&ticker,
&name,
&description,
Expand All @@ -154,10 +153,8 @@ pub async fn issue_contract(sk: &str, request: IssueRequest) -> Result<IssueResp
meta,
&mut resolver,
&mut stock,
) {
Ok(new_contract) => new_contract,
Err(err) => return Err(anyhow!(IssuerOperationError::Issue(err))),
};
)
.map_err(IssuerOperationError::Issue)?;

let ContractResponse {
contract_id,
Expand All @@ -173,15 +170,13 @@ pub async fn issue_contract(sk: &str, request: IssueRequest) -> Result<IssueResp
contract,
genesis,
meta,
} = match export_contract(
} = export_contract(
contract.contract_id(),
&mut stock,
&mut resolver,
&mut wallet,
) {
Ok(contract) => contract,
Err(err) => return Err(anyhow!(IssuerOperationError::Export(err))),
};
)
.map_err(IssuerOperationError::Export)?;

if let Some(wallet) = wallet {
rgb_account
Expand All @@ -190,19 +185,19 @@ pub async fn issue_contract(sk: &str, request: IssueRequest) -> Result<IssueResp
};

if store_stock(sk, ASSETS_STOCK, &stock).await.is_err() {
return Err(anyhow!(IssuerOperationError::Write(
return Err(IssuerOperationError::Write(
CARBONADO_UNAVALIABLE.to_string(),
STOCK_UNAVALIABLE.to_string()
)));
STOCK_UNAVALIABLE.to_string(),
));
}
if store_wallets(sk, ASSETS_WALLETS, &rgb_account)
.await
.is_err()
{
return Err(anyhow!(IssuerOperationError::Write(
return Err(IssuerOperationError::Write(
CARBONADO_UNAVALIABLE.to_string(),
WALLET_UNAVALIABLE.to_string()
)));
WALLET_UNAVALIABLE.to_string(),
));
}

Ok(IssueResponse {
Expand Down Expand Up @@ -673,7 +668,10 @@ pub async fn import(sk: &str, request: ImportRequest) -> Result<ContractResponse
Ok(resp)
}

pub async fn create_watcher(sk: &str, request: WatcherRequest) -> Result<WatcherResponse> {
pub async fn create_watcher(
sk: &str,
request: WatcherRequest,
) -> Result<WatcherResponse, IssuerOperationError> {
let WatcherRequest { name, xpub, force } = request;
let mut rgb_account = retrieve_wallets(sk, ASSETS_WALLETS).await?;

Check failure on line 676 in src/rgb.rs

View workflow job for this annotation

GitHub Actions / lint-wasm

error[E0277]: `?` couldn't convert the error to `rgb::IssuerOperationError` --> src/rgb.rs:676:69 | 676 | let mut rgb_account = retrieve_wallets(sk, ASSETS_WALLETS).await?; | ^ the trait `std::convert::From<anyhow::Error>` is not implemented for `rgb::IssuerOperationError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = help: the following other types implement trait `std::ops::FromResidual<R>`: <std::result::Result<T, F> as std::ops::FromResidual<std::ops::Yeet<E>>> <std::result::Result<T, F> as std::ops::FromResidual<std::result::Result<std::convert::Infallible, E>>> = note: required for `std::result::Result<structs::WatcherResponse, rgb::IssuerOperationError>` to implement `std::ops::FromResidual<std::result::Result<std::convert::Infallible, anyhow::Error>>`

Check failure on line 676 in src/rgb.rs

View workflow job for this annotation

GitHub Actions / lint

error[E0277]: `?` couldn't convert the error to `rgb::IssuerOperationError` --> src/rgb.rs:676:69 | 676 | let mut rgb_account = retrieve_wallets(sk, ASSETS_WALLETS).await?; | ^ the trait `std::convert::From<anyhow::Error>` is not implemented for `rgb::IssuerOperationError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = help: the following other types implement trait `std::ops::FromResidual<R>`: <std::result::Result<T, F> as std::ops::FromResidual<std::ops::Yeet<E>>> <std::result::Result<T, F> as std::ops::FromResidual<std::result::Result<std::convert::Infallible, E>>> = note: required for `std::result::Result<structs::WatcherResponse, rgb::IssuerOperationError>` to implement `std::ops::FromResidual<std::result::Result<std::convert::Infallible, anyhow::Error>>`

Expand Down
3 changes: 2 additions & 1 deletion tests/rgb/integration/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{collections::HashMap, env, process::Stdio};
use bdk::wallet::AddressIndex;
use bitmask_core::{
bitcoin::{get_wallet, get_wallet_data, save_mnemonic, sync_wallet},
error::BitMaskCoreError,
rgb::{
create_invoice, create_psbt, create_watcher, import, issue_contract, transfer_asset,
watcher_details, watcher_next_address, watcher_next_utxo, watcher_unspent_utxos,
Expand Down Expand Up @@ -145,7 +146,7 @@ pub async fn issuer_issue_contract(
force: bool,
send_coins: bool,
meta: Option<IssueMetaRequest>,
) -> Result<IssueResponse, anyhow::Error> {
) -> Result<IssueResponse, BitMaskCoreError> {
setup_regtest(force, None).await;
let issuer_keys = save_mnemonic(
&SecretString(ISSUER_MNEMONIC.to_string()),
Expand Down

0 comments on commit d75f23c

Please sign in to comment.