gui: update liana with multipath support

This commit is contained in:
edouard 2023-03-29 10:09:13 +02:00
parent 045182e7ea
commit 35dbb47bc1
18 changed files with 710 additions and 516 deletions

1092
gui/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -71,13 +71,13 @@ impl App {
menu::Menu::Home => Home::new(self.wallet.clone(), &self.cache.coins).into(),
menu::Menu::Coins => CoinsPanel::new(
&self.cache.coins,
self.wallet.main_descriptor.timelock_value(),
self.wallet.main_descriptor.first_timelock_value(),
)
.into(),
menu::Menu::Recovery => RecoveryPanel::new(
self.wallet.clone(),
&self.cache.coins,
self.wallet.main_descriptor.timelock_value(),
self.wallet.main_descriptor.first_timelock_value(),
self.cache.blockheight as u32,
)
.into(),

View File

@ -15,11 +15,11 @@ pub struct CoinsPanel {
selected: Vec<usize>,
warning: Option<Error>,
/// timelock value to pass for the heir to consume a coin.
timelock: u32,
timelock: u16,
}
impl CoinsPanel {
pub fn new(coins: &[Coin], timelock: u32) -> Self {
pub fn new(coins: &[Coin], timelock: u16) -> Self {
let mut panel = Self {
coins: Vec::new(),
selected: Vec::new(),

View File

@ -124,12 +124,12 @@ impl State for Home {
for coin in coins {
if coin.spend_info.is_none() && coin.block_height.is_some() {
self.balance += coin.amount;
let timelock = self.wallet.main_descriptor.timelock_value();
let timelock = self.wallet.main_descriptor.first_timelock_value();
let seq = remaining_sequence(&coin, cache.blockheight as u32, timelock);
if seq == 0 {
recovery_alert.0 += coin.amount;
recovery_alert.1 += 1;
} else if seq < timelock * 10 / 100 {
} else if seq < timelock as u32 * 10 / 100 {
recovery_warning.0 += coin.amount;
recovery_warning.1 += 1;
}

View File

@ -33,11 +33,11 @@ pub struct RecoveryPanel {
recipient: form::Value<String>,
generated: Option<detail::SpendTxState>,
/// timelock value to pass for the heir to consume a coin.
timelock: u32,
timelock: u16,
}
impl RecoveryPanel {
pub fn new(wallet: Arc<Wallet>, coins: &[Coin], timelock: u32, blockheight: u32) -> Self {
pub fn new(wallet: Arc<Wallet>, coins: &[Coin], timelock: u16, blockheight: u32) -> Self {
let mut locked_coins = (0, Amount::from_sat(0));
let mut recoverable_coins = (0, Amount::from_sat(0));
for coin in coins {

View File

@ -302,13 +302,13 @@ impl Setting for RescanSetting {
}
}
view::SettingsEditMessage::Confirm => {
let date_time = NaiveDate::from_ymd(
let date_time = NaiveDate::from_ymd_opt(
i32::from_str(&self.year.value).unwrap_or(1),
u32::from_str(&self.month.value).unwrap_or(1),
u32::from_str(&self.day.value).unwrap_or(1),
)
.and_hms(0, 0, 0);
let t = date_time.timestamp() as u32;
.unwrap();
let t = date_time.and_hms_opt(0, 0, 0).unwrap().timestamp() as u32;
self.processing = true;
info!("Asking deamon to rescan with timestamp: {}", t);
return Command::perform(

View File

@ -138,7 +138,7 @@ pub struct CreateSpendPanel {
impl CreateSpendPanel {
pub fn new(wallet: Arc<Wallet>, coins: &[Coin], blockheight: u32) -> Self {
let descriptor = wallet.main_descriptor.clone();
let timelock = descriptor.timelock_value();
let timelock = descriptor.first_timelock_value();
Self {
draft: step::TransactionDraft::default(),
current: 0,

View File

@ -224,7 +224,7 @@ impl Recipient {
pub struct ChooseCoins {
descriptor: LianaDescriptor,
timelock: u32,
timelock: u16,
coins: Vec<(Coin, bool)>,
recipients: Vec<(Address, Amount)>,
@ -238,7 +238,7 @@ impl ChooseCoins {
pub fn new(
descriptor: LianaDescriptor,
coins: Vec<Coin>,
timelock: u32,
timelock: u16,
blockheight: u32,
) -> Self {
let mut coins: Vec<(Coin, bool)> = coins

View File

@ -19,7 +19,7 @@ use crate::{
pub fn coins_view<'a>(
cache: &Cache,
coins: &'a [Coin],
timelock: u32,
timelock: u16,
selected: &[usize],
) -> Element<'a, Message> {
Column::new()
@ -55,7 +55,7 @@ pub fn coins_view<'a>(
#[allow(clippy::collapsible_else_if)]
fn coin_list_view(
coin: &Coin,
timelock: u32,
timelock: u16,
blockheight: u32,
index: usize,
collapsed: bool,
@ -84,7 +84,7 @@ fn coin_list_view(
)
.align_items(Alignment::Center),
))
} else if seq < timelock * 10 / 100 {
} else if seq < timelock as u32 * 10 / 100 {
Some(Container::new(
Row::new()
.spacing(5)
@ -138,7 +138,7 @@ fn coin_list_view(
.spacing(5)
.push_maybe(if coin.spend_info.is_none() {
if let Some(b) = coin.block_height {
if blockheight > b as u32 + timelock {
if blockheight > b as u32 + timelock as u32 {
Some(Container::new(
text("The recovery path is available")
.bold()
@ -147,7 +147,7 @@ fn coin_list_view(
))
} else {
Some(Container::new(
text(format!("The recovery path will be available in {} blocks", b as u32 + timelock - blockheight))
text(format!("The recovery path will be available in {} blocks", b as u32 + timelock as u32 - blockheight))
.bold()
.small(),
))

View File

@ -127,8 +127,11 @@ fn event_list_view<'a>(i: usize, event: &HistoryTransaction) -> Element<'a, Mess
})
.push(if let Some(t) = event.time {
Container::new(
text(format!("{}", NaiveDateTime::from_timestamp(t as i64, 0)))
.small(),
text(format!(
"{}",
NaiveDateTime::from_timestamp_opt(t as i64, 0).unwrap(),
))
.small(),
)
} else {
badge::unconfirmed()
@ -186,7 +189,7 @@ pub fn event_view<'a>(cache: &Cache, event: &'a HistoryTransaction) -> Element<'
.push(card::simple(
Column::new()
.push_maybe(event.time.map(|t| {
let date = NaiveDateTime::from_timestamp(t as i64, 0);
let date = NaiveDateTime::from_timestamp_opt(t as i64, 0).unwrap();
Row::new()
.width(Length::Fill)
.push(Container::new(text("Date:").bold()).width(Length::Fill))

View File

@ -203,7 +203,7 @@ fn spend_header<'a>(tx: &SpendTx) -> Element<'a, Message> {
.push(
Row::new()
.push(badge::Badge::new(icon::send_icon()).style(theme::Badge::Standard))
.push(if tx.sigs.recovery_path().is_some() {
.push(if !tx.sigs.recovery_paths().is_empty() {
text("Recovery").bold()
} else {
text("Spend").bold()
@ -363,8 +363,8 @@ pub fn signatures<'a>(
Column::new()
.padding(15)
.spacing(10)
.push(text(if tx.sigs.recovery_path().is_some() {
"2 spending paths available. Finalizing this transaction requires either:"
.push(text(if !tx.sigs.recovery_paths().is_empty() {
"Multiple spending paths available. Finalizing this transaction requires either:"
} else {
"1 spending path available. Finalizing this transaction requires:"
}))
@ -373,9 +373,9 @@ pub fn signatures<'a>(
tx.sigs.primary_path(),
keys_aliases,
))
.push_maybe(tx.sigs.recovery_path().as_ref().map(|path| {
let (_, keys) = desc_info.recovery_path();
path_view(keys, path, keys_aliases)
.push(tx.sigs.recovery_paths().iter().fold(Column::new().spacing(10), |col, (seq, path)| {
let keys = &desc_info.recovery_paths()[seq];
col.push(path_view(keys, path, keys_aliases))
})),
),
)

View File

@ -110,30 +110,12 @@ fn spend_tx_list_view<'a>(i: usize, tx: &SpendTx) -> Element<'a, Message> {
.push(
Row::new()
.push(badge::spend())
.push(if let Some(sigs) = tx.sigs.recovery_path() {
Row::new()
.spacing(10)
.align_items(Alignment::Center)
.push(
Row::new()
.spacing(5)
.align_items(Alignment::Center)
.push(text(format!(
"{}/{}",
if sigs.sigs_count <= sigs.threshold {
sigs.sigs_count
} else {
sigs.threshold
},
sigs.threshold
)))
.push(icon::key_icon()),
)
.push(
Container::new(text(" Recovery ").small())
.padding(3)
.style(theme::Container::Pill(theme::Pill::Simple)),
)
.push(if !tx.sigs.recovery_paths().is_empty() {
Row::new().push(
Container::new(text(" Recovery ").small())
.padding(3)
.style(theme::Container::Pill(theme::Pill::Simple)),
)
} else {
let sigs = tx.sigs.primary_path();
Row::new()

View File

@ -120,7 +120,7 @@ pub fn recipient_view<'a>(
pub fn choose_coins_view<'a>(
cache: &Cache,
timelock: u32,
timelock: u16,
coins: &[(Coin, bool)],
amount_left: Option<&Amount>,
feerate: &form::Value<String>,
@ -192,7 +192,7 @@ pub fn choose_coins_view<'a>(
fn coin_list_view<'a>(
i: usize,
coin: &Coin,
timelock: u32,
timelock: u16,
blockheight: u32,
selected: bool,
) -> Element<'a, Message> {
@ -223,7 +223,7 @@ fn coin_list_view<'a>(
)
.align_items(Alignment::Center),
))
} else if seq < timelock * 10 / 100 {
} else if seq < timelock as u32 * 10 / 100 {
Some(Container::new(
Row::new()
.spacing(5)

View File

@ -55,8 +55,10 @@ impl Wallet {
for (fingerprint, _) in info.primary_path().thresh_origins().1.iter() {
descriptor_keys.insert(*fingerprint);
}
for (fingerprint, _) in info.recovery_path().1.thresh_origins().1.iter() {
descriptor_keys.insert(*fingerprint);
for path in info.recovery_paths().values() {
for (fingerprint, _) in path.thresh_origins().1.iter() {
descriptor_keys.insert(*fingerprint);
}
}
descriptor_keys
}

View File

@ -201,7 +201,7 @@ impl Daemon for EmbeddedDaemon {
.read()
.unwrap()
.control
.create_recovery(address, feerate_vb)
.create_recovery(address, feerate_vb, None)
.map_err(|e| DaemonError::Unexpected(e.to_string()))
.map(|res| res.psbt)
}

View File

@ -9,15 +9,15 @@ pub use liana::{
pub type Coin = ListCoinsEntry;
pub fn remaining_sequence(coin: &Coin, blockheight: u32, timelock: u32) -> u32 {
pub fn remaining_sequence(coin: &Coin, blockheight: u32, timelock: u16) -> u32 {
if let Some(coin_blockheight) = coin.block_height {
if blockheight > coin_blockheight as u32 + timelock {
if blockheight > coin_blockheight as u32 + timelock as u32 {
0
} else {
coin_blockheight as u32 + timelock - blockheight
coin_blockheight as u32 + timelock as u32 - blockheight
}
} else {
timelock
timelock as u32
}
}
@ -89,12 +89,10 @@ impl SpendTx {
if path.sigs_count >= path.threshold {
return Some(path);
}
if let Some(path) = self.sigs.recovery_path() {
if path.sigs_count >= path.threshold {
return Some(path);
}
}
None
self.sigs
.recovery_paths()
.values()
.find(|&path| path.sigs_count >= path.threshold)
}
}

View File

@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
@ -424,7 +424,10 @@ impl Step for DefineDescriptor {
PathInfo::Multi(self.recovery_threshold, recovery_keys)
};
let policy = match LianaPolicy::new(spending_keys, recovery_keys, sequence.unwrap()) {
let mut recovery_paths = BTreeMap::new();
recovery_paths.insert(sequence.unwrap(), recovery_keys);
let policy = match LianaPolicy::new(spending_keys, recovery_paths) {
Ok(policy) => policy,
Err(e) => {
self.error = Some(e.to_string());

View File

@ -136,8 +136,10 @@ impl Step for RecoverMnemonic {
for (fingerprint, _) in info.primary_path().thresh_origins().1.iter() {
descriptor_keys.insert(*fingerprint);
}
for (fingerprint, _) in info.recovery_path().1.thresh_origins().1.iter() {
descriptor_keys.insert(*fingerprint);
for (_, path) in info.recovery_paths().iter() {
for (fingerprint, _) in path.thresh_origins().1.iter() {
descriptor_keys.insert(*fingerprint);
}
}
if !descriptor_keys.contains(&fingerprint) {
self.error =