1
0
forked from GRIN/grim

Payment proofs (#52)

- Ability to export and verify payment proofs

Some fixes:
- Migrated tx heights store from lmdb (also changed heights key from local id to slate_id to avoid conflict between wallets)
- Close address panel on wallet change

Reviewed-on: https://code.gri.mw/GUI/grim/pulls/52
This commit is contained in:
ardocrat
2026-03-03 19:54:46 +00:00
parent 2a41689231
commit beb1a80c6a
23 changed files with 830 additions and 256 deletions
Generated
+4 -29
View File
@@ -4077,6 +4077,7 @@ dependencies = [
"ur",
"url",
"usvg",
"uuid 0.8.2",
"wgpu",
"winit",
]
@@ -5765,29 +5766,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "lmdb-rkv"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447a296f7aca299cfbb50f4e4f3d49451549af655fb7215d7f8c0c3d64bad42b"
dependencies = [
"bitflags 1.3.2",
"byteorder",
"libc",
"lmdb-rkv-sys",
]
[[package]]
name = "lmdb-rkv-sys"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b9ce6b3be08acefa3003c57b7565377432a89ec24476bbe72e11d101f852fe"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "lmdb-zero"
version = "0.4.4"
@@ -8433,23 +8411,20 @@ dependencies = [
[[package]]
name = "rkv"
version = "0.19.0"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6d906922d99c677624d2042a93f89b2b7df0f6411032237d5d99a602c2487c"
checksum = "0f67a9dbc634fcd36a2d1d800ca818065dcf71a1d907dc35130c2d1552c6e1dc"
dependencies = [
"arrayref",
"bincode 1.3.3",
"bitflags 2.10.0",
"byteorder",
"id-arena",
"lazy_static",
"lmdb-rkv",
"log",
"ordered-float 3.9.2",
"paste",
"serde",
"serde_derive",
"thiserror 1.0.69",
"thiserror 2.0.18",
"url",
"uuid 1.20.0",
]
+2 -1
View File
@@ -76,7 +76,7 @@ qrcodegen = "1.8.0"
qrcode = "0.14.1"
ur = "0.4.1"
gif = "0.14.1"
rkv = { version = "0.19.0", features = ["lmdb"] }
rkv = "0.20.0"
usvg = "0.45.1"
ring = "0.16.20"
hyper = { version = "1.6.0", features = ["full"], package = "hyper" }
@@ -87,6 +87,7 @@ hyper-socks2 = "0.9.1"
hyper-proxy2 = "0.1.0"
hyper-tls = "0.6.0"
async-std = "1.13.2"
uuid = { version = "0.8.2", features = ["v4"] }
## tor
arti-client = { version = "0.38.0", features = ["pt-client", "static", "onion-service-service", "onion-service-client"] }
+4
View File
@@ -135,6 +135,10 @@ wallets:
rec_phrase_not_found: Wiederhestellungsphrase nicht gefunden.
restore_wallet_desc: Stellen Sie das Wallet wieder her, indem Sie alle Dateien löschen. Wenn die normale Reparatur nicht geholfen hat, müssen Sie Ihr Wallet erneut öffnen.
fee_base_desc: 'Gebühr (basiswert%{value}):'
payment_proof: Zahlungsnachweis
payment_proof_desc: 'Geben Sie den erhaltenen Zahlungsnachweis ein, um die Transaktion zu verifizieren:'
payment_proof_valid: 'Der eingegebene Zahlungsnachweis ist gültig:'
payment_proof_error: 'Der eingetragene Zahlungsnachweis ist nicht gültig:'
transport:
desc: 'Transport verwenden, um Nachrichten synchron zu empfangen oder zu senden:'
tor_network: Tor Netzwek
+4
View File
@@ -135,6 +135,10 @@ wallets:
rec_phrase_not_found: Recovery phrase not found.
restore_wallet_desc: Restore wallet by deleting all files if usual repair not helped, you will need to re-open your wallet.
fee_base_desc: 'Fee (base value%{value}):'
payment_proof: Payment proof
payment_proof_desc: 'Enter received payment proof to verify transaction:'
payment_proof_valid: 'Entered payment proof is valid:'
payment_proof_error: 'Entered payment proof is not valid:'
transport:
desc: 'Use transport to receive or send messages synchronously:'
tor_network: Tor network
+4
View File
@@ -135,6 +135,10 @@ wallets:
rec_phrase_not_found: Phrase de récupération non trouvée.
restore_wallet_desc: "Restaurer le portefeuille en supprimant tous les fichiers si la réparation habituelle n'a pas aidé. Vous devrez rouvrir votre portefeuille."
fee_base_desc: 'Frais (valeur de base%{value}):'
payment_proof: Preuve de paiement
payment_proof_desc: 'Saisissez la preuve de paiement reçue pour vérifier la transaction:'
payment_proof_valid: 'La preuve de paiement saisie est valide:'
payment_proof_error: "La preuve de paiement saisie n'est pas valide:"
transport:
desc: 'Utilisez le transport pour recevoir ou envoyer des messages de manière synchronisée:'
tor_network: Réseau Tor
+4
View File
@@ -135,6 +135,10 @@ wallets:
rec_phrase_not_found: Фраза восстановления не найдена.
restore_wallet_desc: Восстановить кошелёк, удалив все файлы, если обычное исправление не помогло. Необходимо переоткрыть кошелёк.
fee_base_desc: 'Комиссия (базовое значение%{value}):'
payment_proof: Подтверждение оплаты
payment_proof_desc: 'Введите полученное подтверждение оплаты для проверки транзакции:'
payment_proof_valid: 'Введённое подтверждение оплаты действительно:'
payment_proof_error: 'Введённое подтверждение оплаты недействительно:'
transport:
desc: 'Используйте транспорт для синхронных получения или отправки сообщений:'
tor_network: Сеть Tor
+4
View File
@@ -135,6 +135,10 @@ wallets:
rec_phrase_not_found: Sifre kelime bulunmuyor
restore_wallet_desc: Cuzdani restore et
fee_base_desc: 'Ücret (taban değeri%{value}):'
payment_proof: Ödeme kaniti
payment_proof_desc: 'Islemi doğrulamak için alinan ödeme kanitini girin:'
payment_proof_valid: 'Girilen ödeme kaniti geçerlidir:'
payment_proof_error: 'Girilen ödeme kaniti geçerli değildir:'
transport:
desc: 'Adresten senkronize GONDER veya AL:'
tor_network: Tor network
+5 -1
View File
@@ -85,7 +85,6 @@ wallets:
tx_sent: 已发送
tx_received: 已接收
tx_sending: 发送中
tx_sending_tor: 通过 Tor 发送
tx_receiving: 接收中
tx_confirming: 等待确认
tx_canceled: 已取消
@@ -136,6 +135,10 @@ wallets:
rec_phrase_not_found: 找不到恢复助记词.
restore_wallet_desc: 如果常规修复没有帮助,通过删除所有文件来恢复钱包.您将需要重新打开您的钱包.
fee_base_desc: '费用 (基值%{value}):'
payment_proof: 付款證明
payment_proof_desc: '輸入已收款證明以驗證交易:'
payment_proof_valid: '輸入的付款證明有效:'
payment_proof_error: '輸入的付款證明無效:'
transport:
desc: '使用传输同步接收或发送消息:'
tor_network: Tor 网络
@@ -148,6 +151,7 @@ transport:
incorrect_addr_err: '输入的地址不正确:'
tor_send_error: 通过 Tor 发送时出错,请确保接收方在线, 交易已取消.
tor_autorun_desc: 是否在开钱包时启动 Tor 服务以同步接收交易.
tor_sending: 通过 Tor 发送
tor_settings: Tor 设置
bridges: 桥梁
bridges_desc: 如果常规连接不正常,设置网桥,可以绕过 Tor 网络审查.
+1 -1
View File
@@ -31,7 +31,7 @@ use crate::gui::Colors;
/// QR code image from text.
pub struct QrCodeContent {
/// QR code text.
text: String,
pub text: String,
/// Maximum QR code size.
max_size: f32,
+85 -37
View File
@@ -31,6 +31,7 @@ use crate::node::Node;
use crate::wallet::types::{ConnectionMethod, WalletTask};
use crate::wallet::{ExternalConnection, Wallet};
use crate::AppConfig;
use crate::gui::views::wallets::wallet::proof::PaymentProofContent;
/// Wallet content.
pub struct WalletContent {
@@ -267,43 +268,7 @@ impl WalletContentContainer for WalletContent {
}
// Handle wallet task result.
if let Some((id, t)) = wallet.consume_task_result() {
match Modal::opened() {
None => {
// Show transaction modal on wallet task result.
if let Some(id) = id {
let tx = wallet.get_data().unwrap().tx_by_slate_id(id);
if tx.is_some() {
self.txs_content = Some(WalletTransactionsContent::new(tx));
self.settings_content = None;
}
}
}
Some(modal_id) => {
match modal_id {
SEND_MODAL_ID => {
match t {
WalletTask::CalculateFee(_, f) => {
// Setup calculated tx fee at modal.
if let Some(m) = self.send_content.as_mut() {
if m.max_calculating {
let data = wallet.get_data().unwrap();
let a = data.info.amount_currently_spendable;
let max = a - f;
m.on_max_amount_calculated(max, f);
} else {
m.on_fee_calculated(f);
}
}
}
_ => {}
}
}
_ => {}
}
}
}
}
self.handle_task_result(wallet);
}
let rect = {
let mut r = rect.clone();
@@ -466,6 +431,89 @@ impl WalletContent {
});
});
}
/// Handle wallet task result.
fn handle_task_result(&mut self, wallet: &Wallet) {
let res = wallet.consume_task_result();
if res.is_none() {
return;
}
let (id, t) = res.unwrap();
match Modal::opened() {
None => {
// Show transaction modal on wallet task result.
if let Some(id) = id {
let tx = wallet.get_data().unwrap().tx_by_slate_id(id);
if tx.is_some() {
self.txs_content = Some(WalletTransactionsContent::new(tx));
self.settings_content = None;
}
}
}
Some(modal_id) => {
match modal_id {
SEND_MODAL_ID => {
match t {
WalletTask::CalculateFee(_, f) => {
// Setup calculated tx fee at modal.
if let Some(m) = self.send_content.as_mut() {
if m.max_calculating {
let data = wallet.get_data().unwrap();
let a = data.info.amount_currently_spendable;
let max = if f > a {
0
} else {
a - f
};
m.on_max_amount_calculated(max, f);
} else {
m.on_fee_calculated(f);
}
}
}
_ => {}
}
}
MESSAGE_MODAL_ID => {
match t {
WalletTask::VerifyProof(proof, res) => {
if let Some(res) = res {
// Update message content on validation error
// or when tx not belongs to current wallet.
if res.is_err() || (!res.as_ref().unwrap().1 &&
!res.as_ref().unwrap().2) {
if let Some(m) = self.message_content.as_mut() {
if let Ok(p) = serde_json::to_string_pretty(&proof) {
let mut c = PaymentProofContent::new(Some(p));
c.validation_result = Some(res);
m.proof_content = Some(c);
}
}
} else if let Ok((id, _, _)) = res {
let tx = wallet.get_data().unwrap().tx_by_id(id);
if let Some(tx) = tx {
let mut tx_c = WalletTransactionsContent::new(Some(tx));
if let Ok(p) = serde_json::to_string_pretty(&proof) {
let proof_c = PaymentProofContent::new(Some(p));
tx_c.tx_info_content
.as_mut()
.unwrap()
.proof_content = Some(proof_c);
self.txs_content = Some(tx_c);
self.settings_content = None;
}
}
}
}
}
_ => {}
}
}
_ => {}
}
}
}
}
}
/// Draw content when wallet is syncing and not ready to use, returns `true` at this case.
+39 -5
View File
@@ -15,10 +15,11 @@
use egui::scroll_area::ScrollBarVisibility;
use egui::{Id, RichText, ScrollArea};
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, SCAN};
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, SCAN, SEAL_CHECK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{CameraContent, FilePickContent, FilePickContentType, Modal, View};
use crate::gui::Colors;
use crate::gui::views::wallets::wallet::proof::PaymentProofContent;
use crate::wallet::types::WalletTask;
use crate::wallet::Wallet;
@@ -27,12 +28,13 @@ pub struct MessageInputContent {
message_edit: String,
/// Flag to check if error happened at Slatepack message parsing.
parse_error: bool,
/// QR code scanner content.
scan_qr_content: Option<CameraContent>,
/// Button to parse picked file content.
file_pick_button: FilePickContent,
/// Payment proof input content.
pub proof_content: Option<PaymentProofContent>,
}
/// Hint for Slatepack message input.
@@ -45,6 +47,7 @@ impl Default for MessageInputContent {
parse_error: false,
scan_qr_content: None,
file_pick_button: FilePickContent::new(FilePickContentType::Button),
proof_content: None,
}
}
}
@@ -88,6 +91,19 @@ impl MessageInputContent {
});
});
});
} else if let Some(proof_content) = self.proof_content.as_mut() {
proof_content.input_ui(ui, wallet, cb);
ui.add_space(8.0);
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(8.0);
// Show button to close modal.
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
self.message_edit = "".to_string();
Modal::close();
});
});
} else {
ui.add_space(6.0);
ui.vertical_centered(|ui| {
@@ -114,14 +130,18 @@ impl MessageInputContent {
.show(ui, |ui| {
ui.add_space(7.0);
let input_id = scroll_id.with("_input");
egui::TextEdit::multiline(&mut self.message_edit)
let resp = egui::TextEdit::multiline(&mut self.message_edit)
.id(input_id)
.font(egui::TextStyle::Small)
.desired_rows(5)
.interactive(true)
.hint_text(SLATEPACK_MESSAGE_HINT)
.desired_width(f32::INFINITY)
.show(ui).response;
.show(ui)
.response;
if View::is_desktop() {
resp.request_focus();
}
ui.add_space(6.0);
});
});
@@ -181,6 +201,20 @@ impl MessageInputContent {
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(8.0);
ui.vertical_centered(|ui| {
let proof_label = format!("{} {}", SEAL_CHECK, t!("wallets.payment_proof"));
View::colored_text_button(ui,
proof_label,
Colors::gold_dark(),
Colors::white_or_black(false), || {
Modal::set_title(t!("wallets.payment_proof"));
self.proof_content = Some(PaymentProofContent::new(None));
});
});
ui.add_space(8.0);
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(8.0);
// Show button to close modal.
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
+2 -1
View File
@@ -26,4 +26,5 @@ pub use content::WalletContent;
mod account;
mod transport;
mod request;
mod message;
mod message;
mod proof;
+214
View File
@@ -0,0 +1,214 @@
use egui::scroll_area::ScrollBarVisibility;
use egui::{Id, RichText, ScrollArea};
use grin_wallet_libwallet::{Error, PaymentProof};
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, COPY, FILE_TEXT, SEAL_CHECK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{FilePickContent, FilePickContentType, Modal, View};
use crate::gui::Colors;
use crate::wallet::types::WalletTask;
use crate::wallet::Wallet;
pub struct PaymentProofContent {
/// Payment proof text.
input_edit: String,
/// Button to pick payment proof file.
pick_button: FilePickContent,
/// Flag to check if an error occurred during proof parsing.
parse_error: bool,
/// Proof validation result.
pub validation_result: Option<Result<(u32, bool, bool), Error>>,
}
impl PaymentProofContent {
/// Create new content to share or validate payment proof.
pub fn new(proof_text: Option<String>) -> Self {
Self {
input_edit: proof_text.unwrap_or("".to_string()),
pick_button: FilePickContent::new(FilePickContentType::Button),
parse_error: false,
validation_result: None,
}
}
/// Draw transaction payment proof input.
pub fn input_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
ui.add_space(6.0);
ui.vertical_centered(|ui| {
if self.parse_error {
let label_text = t!("wallets.payment_proof_error");
ui.label(RichText::new(label_text).size(16.0).color(Colors::red()));
} else if let Some(proof) = self.validation_result.as_ref() {
match proof {
Ok(_) => {
let label_text = t!("wallets.payment_proof_valid");
ui.label(RichText::new(label_text).size(16.0).color(Colors::green()));
}
Err(e) => {
let error_text = t!("wallets.payment_proof_error");
let label_text = format!("{} ({:?})", error_text, e);
ui.label(RichText::new(label_text).size(16.0).color(Colors::red()));
}
}
} else {
let desc_label = t!("wallets.payment_proof_desc");
ui.label(RichText::new(desc_label).size(16.0).color(Colors::inactive_text()));
}
});
ui.add_space(6.0);
ui.vertical_centered(|ui| {
let scroll_id = Id::from("tx_info_payment_proof_input");
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(3.0);
ScrollArea::vertical()
.id_salt(scroll_id)
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
.max_height(128.0)
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.add_space(7.0);
let input_id = scroll_id.with("edit");
let proof_input_before = self.input_edit.clone();
let resp = egui::TextEdit::multiline(&mut self.input_edit)
.id(input_id)
.font(egui::TextStyle::Small)
.desired_rows(5)
.interactive(!wallet.payment_proof_verifying())
.desired_width(f32::INFINITY)
.show(ui)
.response;
if View::is_desktop() {
resp.request_focus();
}
// Parse payment proof on input change.
if self.input_edit != proof_input_before {
self.on_proof_edit_change(wallet);
}
ui.add_space(6.0);
});
});
ui.add_space(2.0);
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(8.0);
if wallet.payment_proof_verifying() {
ui.vertical_centered(|ui| {
View::small_loading_spinner(ui);
});
} else {
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
if self.parse_error || (self.validation_result.is_some() &&
self.validation_result.as_ref().unwrap().is_err()) {
// Draw button to clear message input.
let clear_text = format!("{} {}", BROOM, t!("clear"));
View::button(ui, clear_text, Colors::white_or_black(false), || {
self.input_edit = "".to_string();
self.parse_error = false;
self.validation_result = None;
});
} else {
// Draw button to paste proof text.
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
View::button(ui, paste_text, Colors::white_or_black(false), || {
self.input_edit = cb.get_string_from_buffer();
self.on_proof_edit_change(wallet);
});
}
});
columns[1].vertical_centered_justified(|ui| {
let mut changed = false;
self.pick_button.ui(ui, cb, |data| {
self.input_edit = data.clone();
changed = true;
});
if changed {
self.on_proof_edit_change(wallet);
}
});
});
}
}
/// Callback on payment proof input change.
fn on_proof_edit_change(&mut self, wallet: &Wallet) {
if wallet.payment_proof_verifying() {
return;
}
if self.input_edit.is_empty() {
self.parse_error = false;
return;
}
if let Ok(p) = serde_json::from_str::<PaymentProof>(self.input_edit.as_str()) {
wallet.task(WalletTask::VerifyProof(p, None));
} else {
self.parse_error = true;
}
}
/// Draw transaction payment proof content to share.
pub fn share_ui(&mut self, ui: &mut egui::Ui, file_name: String, cb: &dyn PlatformCallbacks) {
ui.add_space(6.0);
ui.vertical_centered(|ui| {
let desc = format!("{} {}:", SEAL_CHECK, t!("wallets.payment_proof"));
ui.label(RichText::new(desc).size(16.0).color(Colors::inactive_text()));
});
ui.add_space(6.0);
ui.vertical_centered(|ui| {
let scroll_id = Id::from("tx_info_payment_proof_share");
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(3.0);
ScrollArea::vertical()
.id_salt(scroll_id)
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
.max_height(128.0)
.auto_shrink([false; 2])
.show(ui, |ui| {
ui.add_space(7.0);
let input_id = scroll_id.with("edit");
egui::TextEdit::multiline(&mut self.input_edit)
.id(input_id)
.font(egui::TextStyle::Small)
.desired_rows(5)
.interactive(false)
.desired_width(f32::INFINITY)
.show(ui);
ui.add_space(6.0);
});
});
ui.add_space(2.0);
View::horizontal_line(ui, Colors::item_stroke());
ui.add_space(8.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
ui.columns(2, |columns| {
columns[0].vertical_centered_justified(|ui| {
// Draw copy button.
let copy_text = format!("{} {}", COPY, t!("copy"));
View::button(ui, copy_text, Colors::white_or_black(false), || {
cb.copy_string_to_buffer(self.input_edit.clone());
Modal::close();
});
});
columns[1].vertical_centered_justified(|ui| {
let share_text = format!("{} {}", FILE_TEXT, t!("share"));
View::colored_text_button(ui,
share_text,
Colors::blue(),
Colors::white_or_black(false), || {
let file_name = format!("{}.txt", file_name);
let data = self.input_edit.as_bytes().to_vec();
cb.share_data(file_name, data).unwrap_or_default();
Modal::close();
});
});
});
}
}
+7 -2
View File
@@ -63,8 +63,13 @@ impl SendRequestContent {
/// Setup maximum amount to send and fee.
pub fn on_max_amount_calculated(&mut self, amount: u64, fee: u64) {
self.max_calculating = false;
self.amount_edit = amount_to_hr_string(amount, true);
self.fee_edit = amount_to_hr_string(fee, true);
if amount == 0 {
self.amount_edit = "".to_string();
self.fee_edit = "".to_string();
} else {
self.amount_edit = amount_to_hr_string(amount, true);
self.fee_edit = amount_to_hr_string(fee, true);
}
}
/// Draw [`Modal`] content.
@@ -40,11 +40,16 @@ impl WalletContentContainer for WalletTransportContent {
fn container_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
if let Some(content) = self.qr_address_content.as_mut() {
ui.add_space(6.0);
// Close panel on wallet change.
if let Some(address) = wallet.slatepack_address() {
if address != content.text {
self.qr_address_content = None;
return;
}
}
// Draw QR code content.
ui.add_space(6.0);
content.ui(ui, cb);
ui.vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
self.qr_address_content = None;
+8 -7
View File
@@ -34,7 +34,7 @@ use crate::wallet::Wallet;
/// Wallet transactions tab content.
pub struct WalletTransactionsContent {
/// Transaction information [`Modal`] content.
tx_info_content: Option<WalletTransactionContent>,
pub tx_info_content: Option<WalletTransactionContent>,
/// Transaction identifier to use at confirmation [`Modal`].
confirm_cancel_tx_id: Option<u32>,
@@ -69,6 +69,7 @@ impl WalletContentContainer for WalletTransactionsContent {
/// Identifier for transaction information [`Modal`].
const TX_INFO_MODAL: &'static str = "tx_info_modal";
/// Identifier for transaction cancellation confirmation [`Modal`].
const CANCEL_TX_CONFIRMATION_MODAL: &'static str = "cancel_tx_conf_modal";
@@ -171,7 +172,7 @@ impl WalletTransactionsContent {
let tx = txs.get(index).unwrap();
Self::tx_item_ui(ui, tx, rect, &data, |ui| {
// Draw button to show transaction info.
if tx.data.tx_slate_id.is_some() {
if tx.data.tx_slate_id.is_some() || tx.data.payment_proof.is_some() {
r.nw = 0.0 as u8;
r.sw = 0.0 as u8;
View::item_button(ui, r, FILE_TEXT, None, || {
@@ -459,17 +460,17 @@ impl WalletTransactionsContent {
let (icon, color) = (ARROWS_CLOCKWISE, Some(Colors::green()));
View::item_button(ui, rounding, icon, color, || {
if repost {
wallet.task(WalletTask::Post(None, tx.data.id));
wallet.task(WalletTask::Post(tx.data.id));
} else {
match tx.action.as_ref().unwrap() {
WalletTransactionAction::Cancelling => {
wallet.task(WalletTask::Cancel(tx.clone()));
wallet.task(WalletTask::Cancel(tx.data.clone()));
}
WalletTransactionAction::Finalizing => {
wallet.task(WalletTask::Finalize(None, tx.data.id));
wallet.task(WalletTask::Finalize(tx.data.id));
}
WalletTransactionAction::Posting => {
wallet.task(WalletTask::Post(None, tx.data.id));
wallet.task(WalletTask::Post(tx.data.id));
}
WalletTransactionAction::SendingTor => {
if let Some(a) = &tx.receiver {
@@ -536,7 +537,7 @@ impl WalletTransactionsContent {
});
columns[1].vertical_centered_justified(|ui| {
View::button(ui, "OK".to_string(), Colors::white_or_black(false), || {
wallet.task(WalletTask::Cancel(tx.clone()));
wallet.task(WalletTask::Cancel(tx.data.clone()));
self.confirm_cancel_tx_id = None;
Modal::close();
});
+48 -11
View File
@@ -19,8 +19,9 @@ use grin_util::ToHex;
use grin_wallet_libwallet::TxLogEntryType;
use std::fs;
use crate::gui::icons::{CIRCLE_HALF, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE};
use crate::gui::icons::{CIRCLE_HALF, COPY, CUBE, FILE_ARCHIVE, FILE_TEXT, HASH_STRAIGHT, PROHIBIT, QR_CODE, SEAL_CHECK};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::proof::PaymentProofContent;
use crate::gui::views::wallets::wallet::txs::WalletTransactionsContent;
use crate::gui::views::{Modal, QrCodeContent, View};
use crate::gui::Colors;
@@ -36,15 +37,19 @@ pub struct WalletTransactionContent {
/// QR code Slatepack message image content.
qr_code_content: Option<QrCodeContent>,
/// Payment proof sharing content.
pub proof_content: Option<PaymentProofContent>,
}
impl WalletTransactionContent {
/// Create new content instance with [`Wallet`] from provided [`WalletTransaction`].
pub fn new(id: u32) -> Self {
pub fn new(tx_id: u32) -> Self {
Self {
tx_id: id,
tx_id,
message: None,
qr_code_content: None,
proof_content: None,
}
}
@@ -92,9 +97,35 @@ impl WalletTransactionContent {
// Show transaction information.
self.info_ui(ui, tx, wallet, cb);
// Show transaction sharing content when can cancel or finalized.
if tx.can_cancel() && !tx.finalized() {
// Show transaction sharing content or payment proof.
if self.proof_content.is_none() && tx.can_cancel() && !tx.finalized() {
self.share_ui(ui, wallet, tx, cb);
} else {
if let Some(proof_content) = self.proof_content.as_mut() {
// Payment proof file name setup.
let file_name = if let Some(slate_id) = tx.data.tx_slate_id {
slate_id.to_string()
} else {
tx.data.id.to_string()
};
// Draw payment proof sharing content.
proof_content.share_ui(ui, file_name, cb);
} else if tx.proof.is_some() && !tx.sending_tor() &&
tx.action_error.is_none() {
ui.vertical_centered(|ui| {
ui.add_space(8.0);
let label = format!("{} {}", SEAL_CHECK, t!("wallets.payment_proof"));
let text_color = Colors::gold_dark();
let btn_color = Colors::white_or_black(false);
// Draw button to show payment proof sharing content.
View::colored_text_button(ui, label, text_color, btn_color, || {
if let Ok(p) = serde_json::to_string_pretty(&tx.proof) {
let c = PaymentProofContent::new(Some(p));
self.proof_content = Some(c);
}
});
});
}
}
ui.add_space(8.0);
@@ -125,6 +156,7 @@ impl WalletTransactionContent {
if m.is_empty() {
return;
}
let amount = amount_to_hr_string(tx.amount, true);
let desc_text = if tx.can_finalize() {
if tx.data.tx_type == TxLogEntryType::TxSent {
@@ -146,7 +178,7 @@ impl WalletTransactionContent {
ui.add_space(6.0);
let mut message = m.clone();
// Draw slatepack message content.
// Draw Slatepack message content.
ui.vertical_centered(|ui| {
let scroll_id = Id::from("tx_info_message_request").with(tx.data.id);
View::horizontal_line(ui, Colors::item_stroke());
@@ -165,7 +197,7 @@ impl WalletTransactionContent {
.desired_rows(5)
.interactive(false)
.desired_width(f32::INFINITY)
.show(ui).response;
.show(ui);
ui.add_space(6.0);
});
});
@@ -255,7 +287,7 @@ impl WalletTransactionContent {
if tx.can_cancel() || rebroadcast {
let r = View::item_rounding(0, 2, true);
View::item_button(ui, r, PROHIBIT, Some(Colors::red()), || {
wallet.task(WalletTask::Cancel(tx.clone()));
wallet.task(WalletTask::Cancel(tx.data.clone()));
Modal::close();
});
}
@@ -282,10 +314,15 @@ impl WalletTransactionContent {
let label = format!("{} {}", FILE_ARCHIVE, t!("kernel"));
info_item_ui(ui, kernel.0.to_hex(), label, true, cb);
}
// Show receiver address.
if let Some(rec) = &tx.receiver {
// Show receiver or sender address.
let addr = if tx.data.tx_type == TxLogEntryType::TxSent {
&tx.receiver
} else {
&tx.sender
};
if let Some(addr) = addr {
let label = format!("{} {}", CIRCLE_HALF, t!("network_mining.address"));
info_item_ui(ui, rec.to_string(), label, true, cb);
info_item_ui(ui, addr.to_string(), label, true, cb);
}
}
}
+4 -3
View File
@@ -14,6 +14,8 @@
#![windows_subsystem = "windows"]
use log::error;
pub fn main() {
#[allow(dead_code)]
#[cfg(not(target_os = "android"))]
@@ -23,7 +25,6 @@ pub fn main() {
#[allow(dead_code)]
#[cfg(not(target_os = "android"))]
fn real_main() {
#[cfg(debug_assertions)]
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.parse_default_env()
@@ -249,7 +250,7 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
let opts = ListenerOptions::new().name(name);
let listener = match opts.create_tokio() {
Err(e) if e.kind() == io::ErrorKind::AddrInUse => {
eprintln!("Socket file is occupied.");
error!("Socket file is occupied.");
return Err::<Listener, io::Error>(e);
}
x => x?,
@@ -259,7 +260,7 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
let conn = match listener.accept().await {
Ok(c) => c,
Err(e) => {
println!("{:?}", e);
error!("{:?}", e);
continue
}
};
-1
View File
@@ -398,7 +398,6 @@ impl NodeConfig {
.unwrap()
.wallet_listener_url = id.to_string();
w_config.save();
println!()
}
/// Get the amount of time in seconds to attempt to mine on a particular header.
+16 -10
View File
@@ -203,26 +203,33 @@ impl WalletConfig {
}
}
/// Get wallet seed path.
pub fn seed_path(&self) -> String {
/// Get base wallet data path.
fn get_base_data_path(&self) -> String {
let mut path = PathBuf::from(self.get_data_path());
path.push(Self::DATA_DIR_NAME);
if !path.exists() {
let _ = fs::create_dir_all(path.clone());
}
path.to_str().unwrap().to_string()
}
/// Get wallet seed path.
pub fn seed_path(&self) -> String {
let mut path = PathBuf::from(self.get_base_data_path());
path.push(SEED_FILE);
path.to_str().unwrap().to_string()
}
/// Get wallet database data path.
pub fn get_db_path(&self) -> String {
let mut path = PathBuf::from(self.get_data_path());
path.push(Self::DATA_DIR_NAME);
let mut path = PathBuf::from(self.get_base_data_path());
path.push(DB_DIR_NAME);
path.to_str().unwrap().to_string()
}
/// Get Slatepack file path for transaction.
pub fn get_tx_slate_path(&self, tx: &WalletTransaction) -> PathBuf {
let mut path = PathBuf::from(self.get_data_path());
path.push(Self::DATA_DIR_NAME);
let mut path = PathBuf::from(self.get_base_data_path());
path.push(SLATEPACKS_DIR_NAME);
if !path.exists() {
let _ = fs::create_dir_all(path.clone());
@@ -234,8 +241,7 @@ impl WalletConfig {
/// Get Slatepack file path for Slate.
pub fn get_slate_path(&self, slate: &Slate) -> PathBuf {
let mut path = PathBuf::from(self.get_data_path());
path.push(Self::DATA_DIR_NAME);
let mut path = PathBuf::from(self.get_base_data_path());
path.push(SLATEPACKS_DIR_NAME);
if !path.exists() {
let _ = fs::create_dir_all(path.clone());
@@ -246,13 +252,13 @@ impl WalletConfig {
}
/// Get path to extra db storage.
pub fn get_extra_db_path(&self) -> String {
pub fn get_extra_db_path(&self) -> PathBuf {
let mut path = PathBuf::from(self.get_db_path());
path.push("extra");
if !path.exists() {
let _ = fs::create_dir_all(path.clone());
}
path.to_str().unwrap().to_string()
path
}
/// Check config version to migrate if needed.
+24 -23
View File
@@ -12,41 +12,42 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use rkv::backend::{Lmdb, LmdbDatabase, LmdbEnvironment};
use rkv::{IntegerStore, Manager, Rkv, StoreOptions, Value};
use rkv::backend::{SafeMode, SafeModeDatabase, SafeModeEnvironment};
use rkv::{Manager, Rkv, SingleStore, StoreOptions, Value};
/// Transaction height storage.
pub struct TxHeightStore {
env: Arc<RwLock<Rkv<LmdbEnvironment>>>,
env: Arc<RwLock<Rkv<SafeModeEnvironment>>>,
/// Confirmed heights.
confirmed: IntegerStore<LmdbDatabase, u32>,
confirmed: SingleStore<SafeModeDatabase>,
/// Broadcasting heights.
broadcasting: IntegerStore<LmdbDatabase, u32>
broadcasting: SingleStore<SafeModeDatabase>
}
impl TxHeightStore {
/// Create new transaction height storage from provided directory.
pub fn new(dir: String) -> Self {
let mut manager = Manager::<LmdbEnvironment>::singleton().write().unwrap();
let env_arc = manager.get_or_create(std::path::Path::new(&dir), Rkv::new::<Lmdb>).unwrap();
pub fn new(dir: PathBuf) -> Self {
let mut manager = Manager::<SafeModeEnvironment>::singleton().write().unwrap();
let created_arc = manager.get_or_create(dir.as_path(), Rkv::new::<SafeMode>).unwrap();
let env = created_arc.clone();
let k = created_arc.read().unwrap();
let env_arc_store = env_arc.clone();
let env = env_arc_store.read().unwrap();
let confirmed = env.open_integer("tx_height", StoreOptions::create()).unwrap();
let broadcasting = env.open_integer("broadcast_tx_height", StoreOptions::create()).unwrap();
let confirmed = k.open_single("tx_height", StoreOptions::create()).unwrap();
let broadcasting = k.open_single("broadcast_tx_height", StoreOptions::create()).unwrap();
Self {
env: env_arc,
env,
confirmed,
broadcasting
}
}
/// Read transaction height from database.
pub fn read_tx_height(&self, id: u32) -> Option<u64> {
pub fn read_tx_height(&self, slate_id: &String) -> Option<u64> {
let env = self.env.read().unwrap();
let reader = env.read().unwrap();
if let Ok(value) = self.confirmed.get(&reader, id) {
if let Ok(value) = self.confirmed.get(&reader, slate_id) {
if let Some(height) = value {
return match height {
Value::U64(v) => Some(v),
@@ -59,18 +60,18 @@ impl TxHeightStore {
}
/// Write transaction height to database.
pub fn write_tx_height(&self, id: u32, height: u64) {
pub fn write_tx_height(&self, slate_id: &String, height: u64) {
let env = self.env.read().unwrap();
let mut writer = env.write().unwrap();
self.confirmed.put(&mut writer, id, &Value::U64(height)).unwrap();
self.confirmed.put(&mut writer, slate_id, &Value::U64(height)).unwrap();
writer.commit().unwrap();
}
/// Read broadcasting height from database.
pub fn read_broadcasting_height(&self, id: u32) -> Option<u64> {
pub fn read_broadcasting_height(&self, slate_id: &String) -> Option<u64> {
let env = self.env.read().unwrap();
let reader = env.read().unwrap();
if let Ok(value) = self.broadcasting.get(&reader, id) {
if let Ok(value) = self.broadcasting.get(&reader, slate_id) {
if let Some(height) = value {
return match height {
Value::U64(v) => Some(v),
@@ -83,18 +84,18 @@ impl TxHeightStore {
}
/// Write broadcasting height to database.
pub fn write_broadcasting_height(&self, id: u32, height: u64) {
pub fn write_broadcasting_height(&self, slate_id: &String, height: u64) {
let env = self.env.read().unwrap();
let mut writer = env.write().unwrap();
self.broadcasting.put(&mut writer, id, &Value::U64(height)).unwrap();
self.broadcasting.put(&mut writer, slate_id, &Value::U64(height)).unwrap();
writer.commit().unwrap();
}
/// Delete broadcasting height from database.
pub fn delete_broadcasting_height(&self, id: u32) {
pub fn delete_broadcasting_height(&self, slate_id: &String) {
let env = self.env.read().unwrap();
let mut writer = env.write().unwrap();
self.broadcasting.delete(&mut writer, id).unwrap_or_default();
self.broadcasting.delete(&mut writer, slate_id).unwrap_or_default();
writer.commit().unwrap();
}
}
+46 -12
View File
@@ -15,7 +15,7 @@
use grin_keychain::ExtKeychain;
use grin_util::Mutex;
use grin_wallet_impls::{DefaultLCProvider, HTTPNodeClient};
use grin_wallet_libwallet::{Error, Slate, SlateState, SlatepackAddress, TxLogEntry, TxLogEntryType, WalletInfo, WalletInst};
use grin_wallet_libwallet::{Error, PaymentProof, Slate, SlateState, SlatepackAddress, TxLogEntry, TxLogEntryType, WalletInfo, WalletInst};
use grin_wallet_util::OnionV3Address;
use serde_derive::{Deserialize, Serialize};
use std::sync::Arc;
@@ -198,6 +198,19 @@ impl WalletData {
}
None
}
/// Get transaction by identifier.
pub fn tx_by_id(&self, id: u32) -> Option<WalletTransaction> {
if self.txs.is_none() {
return None;
}
for tx in self.txs.as_ref().unwrap() {
if tx.data.id == id {
return Some(tx.clone());
}
}
None
}
}
/// Wallet transaction action.
@@ -213,11 +226,15 @@ pub struct WalletTransaction {
pub data: TxLogEntry,
/// State of transaction Slate.
pub state: SlateState,
/// Payment proof.
pub(crate) proof: Option<PaymentProof>,
/// Transaction amount without fees.
pub amount: u64,
/// Possible receiver of transaction.
pub receiver: Option<SlatepackAddress>,
/// Possible sender of transaction.
pub sender: Option<SlatepackAddress>,
/// Block height where tx was included.
pub height: Option<u64>,
/// Block height where tx started broadcasting.
@@ -232,6 +249,7 @@ pub struct WalletTransaction {
impl WalletTransaction {
/// Create new wallet transaction.
pub fn new(tx: TxLogEntry,
proof: Option<PaymentProof>,
wallet: &Wallet,
height: Option<u64>,
broadcasting_height: Option<u64>,
@@ -243,17 +261,24 @@ impl WalletTransaction {
tx.amount_credited - tx.amount_debited
};
let mut receiver: Option<SlatepackAddress> = None;
let mut sender: Option<SlatepackAddress> = None;
if let Some(proof) = &tx.payment_proof {
let onion_addr = OnionV3Address::from_bytes(proof.receiver_address.to_bytes());
if let Ok(addr) = SlatepackAddress::try_from(onion_addr) {
let rec_onion_addr = OnionV3Address::from_bytes(proof.receiver_address.to_bytes());
if let Ok(addr) = SlatepackAddress::try_from(rec_onion_addr) {
receiver = Some(addr);
}
let send_onion_addr = OnionV3Address::from_bytes(proof.sender_address.to_bytes());
if let Ok(addr) = SlatepackAddress::try_from(send_onion_addr) {
sender = Some(addr);
}
}
let mut t = Self {
data: tx,
state: SlateState::Unknown,
proof,
amount,
receiver,
sender,
height,
broadcasting_height,
action,
@@ -289,10 +314,15 @@ impl WalletTransaction {
self.state = slate.state;
} else {
// Transaction just was created.
self.state = match tx.tx_type {
slate.state = match tx.tx_type {
TxLogEntryType::TxReceived => SlateState::Invoice1,
_ => SlateState::Standard1
};
if wallet.slatepack_exists(&slate) {
self.state = slate.state;
} else {
self.state = SlateState::Unknown;
}
}
}
}
@@ -361,9 +391,8 @@ impl WalletTransaction {
/// Check if possible to repeat transaction action.
pub fn can_repeat_action(&self) -> bool {
if let Some(a) = &self.action {
return self.action_error.is_some() && a != &WalletTransactionAction::SendingTor &&
a != &WalletTransactionAction::Cancelling
if let Some(a) = &self.action {
return self.action_error.is_some() && a != &WalletTransactionAction::Cancelling
}
false
}
@@ -379,6 +408,9 @@ impl WalletTransaction {
if self.broadcasting() {
let last_height = data.info.last_confirmed_height;
let broadcasting_height = self.broadcasting_height.unwrap_or(0);
if broadcasting_height == 0 {
return false;
}
let delay = wallet.broadcasting_delay();
return last_height - broadcasting_height > delay;
}
@@ -396,6 +428,10 @@ pub enum WalletTask {
/// * amount
/// * fee (to read at result)
CalculateFee(u64, u64),
/// Verify payment proof.
/// * payment proof
/// * result (tx id, sender mine, receiver mine)
VerifyProof(PaymentProof, Option<Result<(u32, bool, bool), Error>>),
/// Create request to send.
/// * amount
/// * receiver
@@ -408,14 +444,12 @@ pub enum WalletTask {
/// * amount
Receive(u64),
/// Transaction finalization.
/// * tx
/// * local tx id
Finalize(Option<Slate>, u32),
Finalize(u32),
/// Post transaction to blockchain.
/// * tx
/// * local tx id
Post(Option<Slate>, u32),
Post(u32),
/// Cancel transaction.
/// * tx
Cancel(WalletTransaction),
Cancel(TxLogEntry),
}
+297 -109
View File
@@ -32,8 +32,8 @@ use grin_wallet_controller::command::parse_slatepack;
use grin_wallet_controller::controller;
use grin_wallet_controller::controller::ForeignAPIHandlerV2;
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient, LMDBBackend};
use grin_wallet_libwallet::api_impl::owner::{cancel_tx, init_send_tx, retrieve_summary_info, retrieve_txs};
use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlateState, SlateVersion, SlatepackAddress, StatusMessage, TxLogEntry, TxLogEntryType, VersionedSlate, WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider};
use grin_wallet_libwallet::api_impl::owner::{cancel_tx, init_send_tx, retrieve_summary_info, retrieve_txs, verify_payment_proof};
use grin_wallet_libwallet::{address, Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, RetrieveTxQueryArgs, RetrieveTxQuerySortField, RetrieveTxQuerySortOrder, Slate, SlateState, SlateVersion, SlatepackAddress, StatusMessage, StoredProofInfo, TxLogEntry, TxLogEntryType, VersionedSlate, WalletBackend, WalletInfo, WalletInitStatus, WalletInst, WalletLCProvider};
use grin_wallet_util::OnionV3Address;
use parking_lot::RwLock;
use rand::Rng;
@@ -49,6 +49,8 @@ use std::sync::{mpsc, Arc};
use std::thread::Thread;
use std::time::Duration;
use std::{fs, thread};
use log::error;
use uuid::Uuid;
/// Contains wallet instance, configuration and state, handles wallet commands.
#[derive(Clone)]
@@ -59,6 +61,8 @@ pub struct Wallet {
instance: Arc<RwLock<Option<WalletInstance>>>,
/// Connection of current wallet instance.
connection: Arc<RwLock<ConnectionMethod>>,
/// Wallet secret key for transport service.
keychain_mask: Arc<RwLock<Option<SecretKey>>>,
/// Wallet Slatepack address to receive txs at transport.
slatepack_address: Arc<RwLock<Option<String>>>,
@@ -109,11 +113,15 @@ pub struct Wallet {
/// Amount requests to calculate fee.
fee_calculating: Arc<AtomicU8>,
/// Flag to check if sending request is creating.
send_creating: Arc<AtomicBool>,
/// Flag to check if invoice is creating.
invoice_creating: Arc<AtomicBool>,
/// Amount requests to calculate fee.
proof_verifying: Arc<AtomicBool>,
/// Tasks sender.
tasks_sender: Arc<RwLock<Option<Sender<WalletTask>>>>,
/// Task result with optional transaction identifier.
@@ -128,6 +136,7 @@ impl Wallet {
config: Arc::new(RwLock::new(config)),
instance: Arc::new(RwLock::new(None)),
connection: Arc::new(RwLock::new(connection)),
keychain_mask: Arc::new(RwLock::new(None)),
slatepack_address: Arc::new(RwLock::new(None)),
sync_thread: Arc::from(RwLock::new(None)),
foreign_api_server: Arc::new(RwLock::new(None)),
@@ -150,6 +159,7 @@ impl Wallet {
send_creating: Arc::new(AtomicBool::new(false)),
fee_calculating: Arc::new(AtomicU8::new(0)),
invoice_creating: Arc::new(AtomicBool::new(false)),
proof_verifying: Arc::new(AtomicBool::new(false)),
tasks_sender: Arc::new(RwLock::new(None)),
task_result: Arc::new(RwLock::new(None)),
}
@@ -325,8 +335,12 @@ impl Wallet {
};
let mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider()?;
match lc.open_wallet(None, password, false, false) {
Ok(_) => {
match lc.open_wallet(None, password, true, false) {
Ok(m) => {
{
let mut w_mask = self.keychain_mask.write();
*w_mask = m;
}
// Reset an error on opening.
self.set_sync_error(false);
self.reset_sync_attempts();
@@ -360,7 +374,7 @@ impl Wallet {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut api = Owner::new(instance, None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
let mut w_address = self.slatepack_address.write();
*w_address = Some(api.get_slatepack_address(m, 0)?.to_string());
Ok(())
@@ -379,6 +393,12 @@ impl Wallet {
Ok(w_inst.parent_key_id())
}
/// Get keychain mask [`SecretKey`].
pub fn keychain_mask(&self) -> Option<SecretKey> {
let r_key = self.keychain_mask.read();
r_key.clone()
}
/// Get wallet [`SecretKey`] for transport.
pub fn secret_key(&self) -> Option<SecretKey> {
let r_key = self.secret_key.read();
@@ -392,7 +412,7 @@ impl Wallet {
let mut w_lock = instance.lock();
let lc = w_lock.lc_provider()?;
let w_inst = lc.wallet_inst()?;
let k = w_inst.keychain((&None).as_ref())?;
let k = w_inst.keychain(self.keychain_mask().as_ref())?;
let parent_key_id = w_inst.parent_key_id();
let sec_key = address::address_from_derivation_path(&k, &parent_key_id, 0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
@@ -608,6 +628,20 @@ impl Wallet {
self.sync_attempts.store(0, Ordering::Relaxed);
}
/// Select transaction by slate id.
fn tx_by_id(&self, id: Uuid) -> Option<TxLogEntry> {
let r_inst = self.instance.as_ref().read();
let inst = r_inst.clone().unwrap();
let mask = self.keychain_mask();
let tx_id = Some(id);
if let Ok((_, txs)) = retrieve_txs(inst, mask.as_ref(), &None, false, None, tx_id, None) {
if !txs.is_empty() {
return Some(txs.get(0).unwrap().clone())
}
}
None
}
/// Send a task to the wallet.
pub fn task(&self, task: WalletTask) {
let r_tasks = self.tasks_sender.read();
@@ -628,7 +662,7 @@ impl Wallet {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut api = Owner::new(instance, None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
let id = api.create_account_path(m, label)?;
if self.get_data().is_none() {
return Err(Error::GenericError("No wallet data".to_string()));
@@ -652,7 +686,7 @@ impl Wallet {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut api = Owner::new(instance, None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
api.set_active_account(m, label)?;
// Set Slatepack address.
let mut w_address = self.slatepack_address.write();
@@ -749,24 +783,30 @@ impl Wallet {
}
/// Parse Slatepack message into [`Slate`].
pub fn parse_slatepack(&self, text: &String) -> Result<Slate, grin_wallet_controller::Error> {
pub fn parse_slatepack(&self, text: &String)
-> Result<(Slate, Option<SlatepackAddress>), grin_wallet_controller::Error> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut api = Owner::new(instance, None);
match parse_slatepack(&mut api, None, None, Some(text.clone())) {
Ok(s) => Ok(s.0),
match parse_slatepack(&mut api, self.keychain_mask().as_ref(), None, Some(text.clone())) {
Ok(s) => Ok(s),
Err(e) => Err(e)
}
}
/// Create Slatepack message from provided slate.
fn create_slatepack_message(&self, slate: &Slate) -> Result<String, Error> {
fn create_slatepack_message(&self, slate: &Slate, dest: Option<SlatepackAddress>)
-> Result<String, Error> {
let mut message = "".to_string();
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut api = Owner::new(instance, None);
controller::owner_single_use(None, None, Some(&mut api), |api, m| {
message = api.create_slatepack_message(m, &slate, Some(0), vec![])?;
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
let recipients = match dest {
Some(a) => vec![a],
None => vec![],
};
message = api.create_slatepack_message(m, &slate, Some(0), recipients)?;
Ok(())
})?;
@@ -800,7 +840,7 @@ impl Wallet {
estimate_only: Some(true),
..Default::default()
};
let res = init_send_tx(&mut **w, None, args, false);
let res = init_send_tx(&mut **w, self.keychain_mask().as_ref(), args, false);
match res {
Ok(slate) => {
Ok(slate.fee_fields.fee())
@@ -824,10 +864,10 @@ impl Wallet {
}
/// Initialize a transaction to send amount.
fn send(&self, a: u64, r: Option<SlatepackAddress>) -> Result<Slate, Error> {
fn send(&self, a: u64, dest: Option<SlatepackAddress>) -> Result<Slate, Error> {
let config = self.get_config();
let args = InitTxArgs {
payment_proof_recipient_address: r,
payment_proof_recipient_address: dest.clone(),
src_acct_name: Some(config.account),
amount: a,
minimum_confirmations: config.min_confirmations,
@@ -837,16 +877,23 @@ impl Wallet {
};
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let api = Owner::new(instance, None);
let slate = api.init_send_tx(None, args)?;
// Lock outputs to for this transaction.
api.tx_lock_outputs(None, &slate)?;
// Create Slatepack message response.
let _ = self.create_slatepack_message(&slate)?;
Ok(slate)
let mut api = Owner::new(instance, None);
let mut slate = None;
let keychain_mask = self.keychain_mask();
controller::owner_single_use(None, keychain_mask.as_ref(), Some(&mut api), |api, m| {
let s = api.init_send_tx(m, args)?;
// Create Slatepack message response.
let _ = self.create_slatepack_message(&s, dest)?;
// Lock outputs to for this transaction.
api.tx_lock_outputs(m, &s)?;
slate = Some(s);
Ok(())
})?;
if let Some(slate) = slate {
Ok(slate)
} else {
Err(Error::GenericError("slate was not created".to_string()))
}
}
/// Send slate to Tor address.
@@ -874,17 +921,17 @@ impl Wallet {
}
// Parse response.
let res: Value = serde_json::from_str(&req_res.unwrap()).unwrap();
if res["error"] != json!(null) {
return Err(Error::GenericError("Response error".to_string()));
if let Ok(res) = serde_json::from_str::<Value>(&req_res.unwrap()) {
if res["error"] != json!(null) {
return Err(Error::GenericError("Response error".to_string()));
}
let slate_value = res["result"]["Ok"].clone();
if let Ok(res) = &serde_json::to_string::<Value>(&slate_value) {
let res = Slate::deserialize_upgrade(res);
return res;
}
}
let slate_value = res["result"]["Ok"].clone();
let res = Slate::deserialize_upgrade(&serde_json::to_string(&slate_value).unwrap());
// Clear tx action.
self.on_tx_action(slate.id.to_string(), None);
res
Err(Error::GenericError("Parse error".to_string()))
}
/// Check if request to send funds is creating.
@@ -902,19 +949,14 @@ impl Wallet {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let api = Owner::new(instance, None);
let slate = api.issue_invoice_tx(None, args)?;
let slate = api.issue_invoice_tx(self.keychain_mask().as_ref(), args)?;
// Create Slatepack message response.
let _ = self.create_slatepack_message(&slate)?;
let _ = self.create_slatepack_message(&slate, None)?;
Ok(slate)
}
/// Check if request to receive funds is creating.
pub fn invoice_creating(&self) -> bool {
self.invoice_creating.load(Ordering::Relaxed)
}
/// Handle message from the invoice issuer to send founds, return response for funds receiver.
fn pay(&self, slate: &Slate) -> Result<Slate, Error> {
let config = self.get_config();
@@ -928,28 +970,33 @@ impl Wallet {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let api = Owner::new(instance, None);
let slate = api.process_invoice_tx(None, &slate, args)?;
api.tx_lock_outputs(None, &slate)?;
let slate = api.process_invoice_tx(self.keychain_mask().as_ref(), &slate, args)?;
api.tx_lock_outputs(self.keychain_mask().as_ref(), &slate)?;
// Create Slatepack message response.
let _ = self.create_slatepack_message(&slate)?;
let _ = self.create_slatepack_message(&slate, None)?;
Ok(slate)
}
/// Check if request to receive funds is creating.
pub fn invoice_creating(&self) -> bool {
self.invoice_creating.load(Ordering::Relaxed)
}
/// Create response to sender to receive funds.
fn receive(&self, slate: &Slate) -> Result<Slate, Error> {
fn receive(&self, slate: &Slate, dest: Option<SlatepackAddress>) -> Result<Slate, Error> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let api = Owner::new(instance, None);
let mut slate = slate.clone();
controller::foreign_single_use(api.wallet_inst.clone(), None, |api| {
controller::foreign_single_use(api.wallet_inst.clone(), self.keychain_mask(), |api| {
slate = api.receive_tx(&slate, Some(self.get_config().account.as_str()), None)?;
Ok(())
})?;
// Create Slatepack message response.
let _ = self.create_slatepack_message(&slate)?;
let _ = self.create_slatepack_message(&slate, dest)?;
Ok(slate)
}
@@ -961,10 +1008,14 @@ impl Wallet {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let api = Owner::new(instance, None);
let slate = api.finalize_tx(None, slate)?;
let mut slate = slate.clone();
controller::foreign_single_use(api.wallet_inst.clone(), self.keychain_mask(), |api| {
slate = api.finalize_tx(&slate, false)?;
Ok(())
})?;
// Save Slatepack message to file.
let _ = self.create_slatepack_message(&slate)?;
let _ = self.create_slatepack_message(&slate, None)?;
// Clear tx action.
self.on_tx_action(slate.id.to_string(), None);
@@ -978,8 +1029,11 @@ impl Wallet {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let api = Owner::new(instance, None);
api.post_tx(None, slate, self.can_use_dandelion())?;
let mut api = Owner::new(instance, None);
controller::owner_single_use(None, self.keychain_mask().as_ref(), Some(&mut api), |api, m| {
api.post_tx(m, &slate, self.can_use_dandelion())?;
Ok(())
})?;
// Clear tx action.
self.on_tx_action(slate.id.to_string(), None);
@@ -988,13 +1042,13 @@ impl Wallet {
}
/// Cancel transaction.
fn cancel(&self, tx: &WalletTransaction) -> Result<(), Error> {
let id = tx.data.tx_slate_id.unwrap().to_string();
fn cancel(&self, tx: &TxLogEntry) -> Result<(), Error> {
let id = tx.tx_slate_id.unwrap().to_string();
self.on_tx_action(id.clone(), Some(WalletTransactionAction::Cancelling));
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
cancel_tx(instance, None, &None, Some(tx.data.id), None)?;
cancel_tx(instance, self.keychain_mask().as_ref(), &None, Some(tx.id), None)?;
// Clear tx action.
self.on_tx_action(id, None);
@@ -1026,7 +1080,7 @@ impl Wallet {
let r_res = self.task_result.read();
r_res.clone()
};
// Clear result for transaction task.
// Clear result for task.
let mut w_res = self.task_result.write();
*w_res = None;
res
@@ -1059,12 +1113,12 @@ impl Wallet {
Ok(tx_height)
}
/// Get transaction from database.
/// Get transaction Slate from database.
fn get_tx(&self, tx_id: u32) -> Option<Slate> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let api = Owner::new(instance, None);
if let Ok(s) = api.get_stored_tx(None, Some(tx_id), None) {
if let Ok(s) = api.get_stored_tx(self.keychain_mask().as_ref(), Some(tx_id), None) {
return s;
}
None
@@ -1162,12 +1216,107 @@ impl Wallet {
self.files_moving.load(Ordering::Relaxed)
}
/// Retrieve payment proof.
pub fn get_payment_proof(&self, tx_id: Option<u32>, slate_id: Option<Uuid>)
-> Result<Option<PaymentProof>, Error> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let key_mask = self.keychain_mask();
let mut api = Owner::new(instance, None);
let mut proof = None;
controller::owner_single_use(None, key_mask.as_ref(), Some(&mut api), |api, m| {
let result = api.retrieve_payment_proof(m, false, tx_id, slate_id);
proof = match result {
Ok(p) => Some(p),
Err(e) => {
error!("retrieve_payment_proof error: {}", e);
None
}
};
Ok(())
})?;
Ok(proof)
}
/// Verify payment proof.
fn verify_payment_proof(&self, proof: &PaymentProof) -> Result<(u32, bool, bool), Error> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let keychain_mask = self.keychain_mask();
let verify_res = verify_payment_proof(instance.clone(), keychain_mask.as_ref(), proof);
let res = match verify_res {
Ok((send, rec)) => {
// Update proof at local database for valid proof.
if send || rec {
let mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider()?;
let w = lc.wallet_inst()?;
// Find wallet transaction to update or create.
let txs = w.tx_log_iter().filter(|entry| {
if let Some(excess) = entry.kernel_excess {
return excess == proof.excess;
}
false
}).collect::<Vec<TxLogEntry>>();
if let Some(tx) = txs.get(0) {
let mut tx = tx.clone();
let mut batch = w.batch(keychain_mask.as_ref())?;
let parent_key = &tx.parent_key_id;
tx.payment_proof = Some(StoredProofInfo {
receiver_address: proof.recipient_address.pub_key,
receiver_signature: Some(proof.recipient_sig),
sender_address_path: 0,
sender_address: proof.sender_address.pub_key,
sender_signature: Some(proof.sender_sig),
});
batch.save_tx_log_entry(tx.clone(), &parent_key)?;
batch.commit()?;
Ok((tx.id, send, rec))
} else {
let parent_key = w.parent_key_id();
let mut batch = w.batch(keychain_mask.as_ref())?;
let log_id = batch.next_tx_log_id(&parent_key)?;
let log_type = TxLogEntryType::TxSent;
let mut tx = TxLogEntry::new(parent_key.clone(), log_type, log_id);
tx.amount_debited = proof.amount;
tx.kernel_excess = Some(proof.excess);
tx.tx_type = TxLogEntryType::TxSent;
tx.confirmed = true;
tx.payment_proof = Some(StoredProofInfo {
receiver_address: proof.recipient_address.pub_key,
receiver_signature: Some(proof.recipient_sig),
sender_address_path: 0,
sender_address: proof.sender_address.pub_key,
sender_signature: Some(proof.sender_sig),
});
batch.save_tx_log_entry(tx.clone(), &parent_key)?;
batch.commit()?;
Ok((tx.id, send, rec))
}
} else {
Ok((0, send, rec))
}
}
Err(e) => Err(e)
};
// Sync wallet data on success.
if res.is_ok() {
sync_wallet_data(self, false);
}
res
}
/// Check if payment proof is verifying.
pub fn payment_proof_verifying(&self) -> bool {
self.proof_verifying.load(Ordering::Relaxed)
}
/// Get recovery phrase.
pub fn get_recovery(&self, password: String) -> Result<ZeroingString, Error> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut wallet_lock = instance.lock();
let lc = wallet_lock.lc_provider().unwrap();
let lc = wallet_lock.lc_provider()?;
lc.get_mnemonic(None, ZeroingString::from(password))
}
@@ -1376,16 +1525,22 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
w.on_task_result(Some(id), &t);
}
Err(e) => {
error!("send tor post error: {:?}", e);
w.on_tx_error(id, Some(e));
}
}
}
Err(e) => {
if let Some(tx) = w.tx_by_id(s.id) {
let _ = w.cancel(&tx);
}
error!("send tor finalize error: {:?}", e);
w.on_tx_error(id, Some(e));
}
}
}
Err(e) => {
error!("send tor error: {:?}", e);
w.on_tx_error(id.clone(), Some(e));
w.on_task_result(Some(id), &t);
}
@@ -1401,7 +1556,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
let msg = m.clone();
thread::spawn(move || {
load.store(true, Ordering::Relaxed);
if let Ok(s) = w.parse_slatepack(&msg) {
if let Ok((s, dest)) = w.parse_slatepack(&msg) {
let id = s.id.to_string();
// Check if message already exists.
let exists = {
@@ -1432,7 +1587,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
w.on_task_result(Some(id), &t);
}
} else {
if let Ok(_) = w.receive(&s) {
if let Ok(_) = w.receive(&s, dest) {
sync_wallet_data(&w, false);
w.on_task_result(Some(id), &t);
}
@@ -1446,11 +1601,16 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
sync_wallet_data(&w, false);
}
Err(e) => {
error!("message tx post error: {:?}", e);
w.on_tx_error(id, Some(e));
}
}
}
Err(e) => {
if let Some(tx) = w.tx_by_id(s.id) {
let _ = w.cancel(&tx);
}
error!("message tx finalize error: {:?}", e);
w.on_tx_error(id, Some(e));
}
}
@@ -1486,9 +1646,10 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
w.send_creating.store(true, Ordering::Relaxed);
if let Ok(s) = w.send(*a, r.clone()) {
sync_wallet_data(&w, false);
if let Some(r) = r {
if r.is_some() && Tor::is_service_running(&w.identifier()) {
w.send_creating.store(false, Ordering::Relaxed);
send_tor(&s, r).await;
let addr = r.as_ref().unwrap();
send_tor(&s, addr).await;
return;
} else {
w.on_task_result(Some(s.id.to_string()), &t);
@@ -1509,11 +1670,8 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
}
w.invoice_creating.store(false, Ordering::Relaxed);
},
WalletTask::Finalize(s, id) => {
let slate = match s {
None => &w.get_tx(*id).unwrap(),
Some(s) => s
};
WalletTask::Finalize(id) => {
let slate = &w.get_tx(*id).unwrap();
w.on_tx_error(slate.id.to_string(), None);
match w.finalize(slate) {
Ok(s) => {
@@ -1522,27 +1680,27 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
sync_wallet_data(&w, false);
}
Err(e) => {
error!("tx finalize post error: {:?}", e);
w.on_tx_error(slate.id.to_string(), Some(e));
}
}
}
Err(e) => {
if let Some(tx) = w.tx_by_id(slate.id) {
let _ = w.cancel(&tx);
}
error!("tx finalize error: {:?}", e);
w.on_tx_error(slate.id.to_string(), Some(e));
}
}
}
WalletTask::Post(s, id) => {
w.on_tx_action(id.to_string(), Some(WalletTransactionAction::Posting));
let slate = match s {
None => &w.get_tx(*id).unwrap(),
Some(s) => s
};
WalletTask::Post(id) => {
let slate = &w.get_tx(*id).unwrap();
w.on_tx_error(slate.id.to_string(), None);
// Cleanup broadcasting tx height.
let tx_height_store = TxHeightStore::new(w.get_config().get_extra_db_path());
tx_height_store.delete_broadcasting_height(*id);
tx_height_store.delete_broadcasting_height(&slate.id.to_string());
let has_data = {
let r_data = w.data.read();
@@ -1563,6 +1721,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
sync_wallet_data(&w, false);
}
Err(e) => {
error!("tx post error: {:?}", e);
w.on_tx_error(slate.id.to_string(), Some(e));
}
}
@@ -1574,11 +1733,18 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
sync_wallet_data(&w, false);
}
Err(e) => {
let id = tx.data.tx_slate_id.unwrap().to_string();
error!("tx cancel error: {:?}", e);
let id = tx.tx_slate_id.unwrap().to_string();
w.on_tx_error(id, Some(e));
}
}
}
WalletTask::VerifyProof(p, _) => {
w.proof_verifying.store(true, Ordering::Relaxed);
let res = w.verify_payment_proof(p);
w.proof_verifying.store(false, Ordering::Relaxed);
w.on_task_result(None, &WalletTask::VerifyProof(p.clone(), Some(res)));
}
};
}
@@ -1612,7 +1778,7 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
let instance = r_inst.clone().unwrap();
if let Ok((_, info)) = retrieve_summary_info(
instance.clone(),
None,
wallet.keychain_mask().as_ref(),
&Some(info_tx),
from_node,
config.min_confirmations
@@ -1679,16 +1845,18 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) {
}
/// Update wallet transactions.
fn update_txs(wallet: &Wallet, instance: WalletInstance, info: WalletInfo)
-> Result<(), Error> {
fn update_txs(wallet: &Wallet, inst: WalletInstance, info: WalletInfo)
-> Result<(), Error> {
// Retrieve txs from local database.
let txs_args = RetrieveTxQueryArgs {
exclude_cancelled: Some(false),
sort_field: Some(RetrieveTxQuerySortField::CreationTimestamp),
sort_order: Some(RetrieveTxQuerySortOrder::Desc),
min_amount: Some(1),
..Default::default()
};
let txs = retrieve_txs(instance, None, &None, false, None, None, Some(txs_args.clone()))?;
let mask = wallet.keychain_mask();
let txs = retrieve_txs(inst, mask.as_ref(), &None, false, None, None, Some(txs_args.clone()))?;
// Exit if wallet was closed.
if !wallet.is_open() || wallet.is_closing() {
@@ -1699,7 +1867,8 @@ fn update_txs(wallet: &Wallet, instance: WalletInstance, info: WalletInfo)
let account_txs = txs.1.iter().map(|v| v.clone()).filter(|tx| {
match wallet.get_parent_key_id() {
Ok(key) => {
tx.parent_key_id == key && tx.tx_slate_id.is_some()
tx.parent_key_id == key && (tx.tx_slate_id.is_some() ||
(tx.tx_slate_id.is_none() && tx.payment_proof.is_some()))
}
Err(_) => {
true
@@ -1716,6 +1885,7 @@ fn update_txs(wallet: &Wallet, instance: WalletInstance, info: WalletInfo)
let mut broadcasting_height: Option<u64> = None;
let mut action: Option<WalletTransactionAction> = None;
let mut action_error: Option<Error> = None;
let mut proof: Option<PaymentProof> = None;
for t in &data_txs {
if t.data.id == tx.id {
action = t.action.clone();
@@ -1726,6 +1896,7 @@ fn update_txs(wallet: &Wallet, instance: WalletInstance, info: WalletInfo)
}
}
let mut new = WalletTransaction::new(tx.clone(),
proof.clone(),
wallet,
height,
broadcasting_height,
@@ -1738,28 +1909,43 @@ fn update_txs(wallet: &Wallet, instance: WalletInstance, info: WalletInfo)
new.update_slate_state(wallet);
}
// Setup initial tx heights.
if height.is_none() && tx.confirmed {
height = if let Some(height) = tx_height_store.read_tx_height(tx.id) {
Some(height)
} else {
tx_height_store.delete_broadcasting_height(tx.id);
let h = wallet.tx_height(&new)?;
if let Some(h) = h {
tx_height_store.write_tx_height(tx.id, h);
}
h
};
new.height = height;
} else if broadcasting_height.is_none() && new.broadcasting() {
broadcasting_height = if let Some(h) = tx_height_store.read_broadcasting_height(tx.id) {
Some(h)
} else {
let h = data.info.last_confirmed_height;
tx_height_store.write_broadcasting_height(tx.id, h);
Some(h)
};
new.broadcasting_height = Some(broadcasting_height.unwrap_or(0));
// Payment proof setup.
if proof.is_none() && tx.payment_proof.is_some() &&
tx.payment_proof.as_ref().unwrap().receiver_signature.is_some() &&
tx.payment_proof.as_ref().unwrap().sender_signature.is_some() &&
tx.kernel_excess.is_some() {
if let Ok(p) = wallet.get_payment_proof(Some(tx.id), tx.tx_slate_id) {
proof = p.clone();
new.proof = proof;
}
}
// Initial tx heights setup.
if let Some(slate_id) = tx.tx_slate_id {
let id = slate_id.to_string();
if height.is_none() && tx.confirmed {
height = if let Some(height) = tx_height_store.read_tx_height(&id) {
Some(height)
} else {
tx_height_store.delete_broadcasting_height(&id);
let h = wallet.tx_height(&new)?;
if let Some(h) = h {
tx_height_store.write_tx_height(&id, h);
}
h
};
new.height = height;
} else if broadcasting_height.is_none() && new.broadcasting() {
let br_height = tx_height_store.read_broadcasting_height(&id);
broadcasting_height = if br_height.is_none() || br_height.unwrap() == 0 {
let h = data.info.last_confirmed_height;
tx_height_store.write_broadcasting_height(&id, h);
Some(h)
} else {
Some(br_height.unwrap())
};
new.broadcasting_height = broadcasting_height;
}
}
new_txs.push(new);
@@ -1797,8 +1983,9 @@ fn start_api_server(wallet: &Wallet) -> Result<(ApiServer, u16), Error> {
// Start Foreign API server thread.
let r_inst = wallet.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let keychain_mask = wallet.keychain_mask();
let api_handler_v2 = ForeignAPIHandlerV2::new(instance,
Arc::new(Mutex::new(None)),
Arc::new(Mutex::new(keychain_mask)),
false,
Mutex::new(None));
let mut router = Router::new();
@@ -1833,7 +2020,8 @@ fn update_accounts(wallet: &Wallet, height: u64, spendable: Option<u64>) {
let r_inst = wallet.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut api = Owner::new(instance, None);
let _ = controller::owner_single_use(None, None, Some(&mut api), |api, m| {
let key_mask = wallet.keychain_mask();
let _ = controller::owner_single_use(None, key_mask.as_ref(), Some(&mut api), |api, m| {
let mut accounts = vec![];
for a in api.accounts(m)? {
api.set_active_account(m, a.label.as_str())?;
@@ -1886,7 +2074,7 @@ fn repair_wallet(wallet: &Wallet) {
let r_inst = wallet.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let api = Owner::new(instance, Some(info_tx));
match api.scan(None, Some(1), false) {
match api.scan(wallet.keychain_mask().as_ref(), Some(1), false) {
Ok(()) => {
// Set sync error if scanning was not complete and wallet is open.
if wallet.is_open() && wallet.repair_progress.load(Ordering::Relaxed) != 100 {