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:
Generated
+4
-29
@@ -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
@@ -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"] }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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), || {
|
||||
|
||||
@@ -26,4 +26,5 @@ pub use content::WalletContent;
|
||||
mod account;
|
||||
mod transport;
|
||||
mod request;
|
||||
mod message;
|
||||
mod message;
|
||||
mod proof;
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user