Merge branch 'refs/heads/master' into master-jasper

# Conflicts:
#	locales/zh-CN.yml
This commit is contained in:
ardocrat
2026-07-08 23:59:20 +03:00
21 changed files with 892 additions and 677 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- name: Checkout submodules
run: |
sed -i 's#https://code\.gri\.mw#${{ secrets.REPO_HOST }}#g' .gitmodules
git submodule update --init
git submodule update --init --recursive --remote
- name: Check commit
id: check
run: |
+6 -6
View File
@@ -21,7 +21,7 @@ jobs:
- name: Checkout submodules
run: |
sed -i 's#https://code\.gri\.mw#${{ secrets.REPO_HOST }}#g' .gitmodules
git submodule update --init
git submodule update --init --recursive --remote
- name: Get version
id: version
run: |
@@ -86,7 +86,7 @@ jobs:
- name: Checkout submodules
run: |
sed -i -- 's#https://code\.gri\.mw#${{ secrets.REPO_HOST }}#g' .gitmodules
git submodule update --init
git submodule update --init --recursive --remote
- name: Restore cargo cache
id: cache-cargo-restore
uses: actions/cache/restore@v5
@@ -188,7 +188,7 @@ jobs:
- name: Checkout submodules
run: |
sed -i 's#https://code\.gri\.mw#${{ secrets.REPO_HOST }}#g' .gitmodules
git submodule update --init
git submodule update --init --recursive --remote
- name: Restore cargo cache
id: cache-cargo-restore
uses: actions/cache/restore@v5
@@ -255,7 +255,7 @@ jobs:
- name: Checkout submodules
run: |
sed -i 's#https://code\.gri\.mw#${{ secrets.REPO_HOST }}#g' .gitmodules
git submodule update --init
git submodule update --init --recursive --remote
- name: Restore cargo cache
id: cache-cargo-restore
uses: actions/cache/restore@v5
@@ -307,7 +307,7 @@ jobs:
- name: Checkout submodules
run: |
sed -i -- 's#https://code\.gri\.mw#${{ secrets.REPO_HOST }}#g' .gitmodules
git submodule update --init
git submodule update --init --recursive --remote
- run: mkdir release
- name: Restore cargo cache
id: cache-cargo-restore
@@ -360,7 +360,7 @@ jobs:
- name: Checkout submodules
run: |
(Get-content .gitmodules) | Foreach-Object {$_ -replace "https://code.gri.mw", "${{ secrets.REPO_HOST }}"} | Set-Content .gitmodules
git submodule update --init
git submodule update --init --recursive --remote
- name: Update UpgradeCode
shell: powershell
run: |
+1 -4
View File
@@ -1,10 +1,7 @@
[submodule "node"]
path = node
url = https://code.gri.mw/ardocrat/node
[submodule "wallet"]
path = wallet
url = https://code.gri.mw/ardocrat/wallet
branch = grim
branch = grim-staging
[submodule "tor/webtunnel"]
path = tor/webtunnel
url = https://code.gri.mw/WEB/webtunnel
Generated
+509 -415
View File
File diff suppressed because it is too large Load Diff
+8 -14
View File
@@ -29,14 +29,14 @@ panic = "abort"
log = "0.4.27"
# node
grin_api = { path = "node/api" }
grin_chain = { path = "node/chain" }
grin_config = { path = "node/config" }
grin_core = { path = "node/core" }
grin_p2p = { path = "node/p2p" }
grin_servers = { path = "node/servers" }
grin_keychain = { path = "node/keychain" }
grin_util = { path = "node/util" }
grin_api = { path = "wallet/grin/api" }
grin_chain = { path = "wallet/grin/chain" }
grin_config = { path = "wallet/grin/config" }
grin_core = { path = "wallet/grin/core" }
grin_p2p = { path = "wallet/grin/p2p" }
grin_servers = { path = "wallet/grin/servers" }
grin_keychain = { path = "wallet/grin/keychain" }
grin_util = { path = "wallet/grin/util" }
# wallet
grin_wallet_impls = { path = "wallet/impls" }
@@ -53,10 +53,7 @@ rust-i18n = "3.1.5"
## other
log4rs = "1.4.0"
anyhow = "1.0.97"
pin-project = "1.1.10"
backtrace = "0.3.76"
thiserror = "2.0.18"
futures = "0.3.31"
dirs = "6.0.0"
sys-locale = "0.3.2"
@@ -101,12 +98,9 @@ tor-hsrproxy = "0.43.0"
tor-keymgr = "0.43.0"
tor-llcrypto = "0.43.0"
tor-hscrypto = "0.43.0"
tor-error = "0.43.0"
sha2 = "0.10.8"
ed25519-dalek = "2.1.1"
curve25519-dalek = "4.1.3"
tls-api = "0.12.0"
tls-api-native-tls = "0.12.1"
safelog = "0.8.1"
## stratum server
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Verbindungsproblem
disconnected: Verbindung getrennt
receiver_address: 'Empfängeraddresse:'
sender_address: 'Absenderadresse:'
incorrect_addr_err: 'Eingegebene Addresse ist inkorrekt:'
tor_send_error: Beim Senden über Tor ist ein Fehler aufgetreten. Stellen Sie sicher, dass der Empfänger online ist. Die Transaktion wurde abgebrochen.
tor_autorun_desc: Gibt an, ob beim Öffnen des Wallets der Tor-Dienst gestartet werden soll, um Transaktionen synchron zu empfangen.
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Connection error
disconnected: Disconnected
receiver_address: 'Address of the receiver:'
sender_address: 'Address of the sender:'
incorrect_addr_err: 'Entered address is incorrect:'
tor_send_error: An error occurred during sending over Tor, make sure receiver is online, transaction was canceled.
tor_autorun_desc: Whether to launch Tor service on wallet opening to receive transactions synchronously.
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Erreur de connexion
disconnected: Déconnecté
receiver_address: 'Adresse du destinataire:'
sender_address: "Adresse de l'expéditeur:"
incorrect_addr_err: 'Adresse entrée incorrecte:'
tor_send_error: "Une erreur s'est produite lors de l'envoi via Tor. Assurez-vous que le destinataire est en ligne, la transaction a été annulée."
tor_autorun_desc: "Lancer automatiquement le service Tor à l'ouverture du portefeuille pour recevoir les transactions de manière synchronisée."
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Ошибка подключения
disconnected: Отключено
receiver_address: 'Адрес получателя:'
sender_address: 'Адрес отправителя:'
incorrect_addr_err: 'Введённый адрес неверен:'
tor_send_error: Во время отправки через Tor произошла ошибка, убедитесь, что получатель находится онлайн, транзакция была отменена.
tor_autorun_desc: Запускать ли Tor сервис при открытии кошелька для синхронного получения транзакций.
+1
View File
@@ -152,6 +152,7 @@ transport:
conn_error: Bagalanti hatasi
disconnected: Baglanti yok
receiver_address: 'Alicinin adresi:'
sender_address: 'Gönderici adresi:'
incorrect_addr_err: 'Girilen adres hatali:'
tor_send_error: Tor adresi uzerinden gonderimde aksaklik olustu, alici online olmasi gerek, islem iptal edildi.
tor_autorun_desc: Islemleri Tor adresi olarak AL,bunun için cuzdan acilisinda Tor hizmetinin baslatilip baslatilmayacagi.
+27 -26
View File
@@ -152,6 +152,7 @@ transport:
conn_error: 连接错误
disconnected: 已断开
receiver_address: '接收方地址:'
sender_address: '发送方地址:'
incorrect_addr_err: '输入的地址不正确:'
tor_send_error: 通过 Tor 发送时发生错误,请确认接收方在线,交易已取消。
tor_autorun_desc: 是否在打开钱包时启动 Tor 服务以同步接收交易。
@@ -325,35 +326,35 @@ keyboard:
9: 9
0: 0
01: '-'
q: q
w: w
e: e
r: r
t: t
y: y
u: u
i: i
o: o
p: p
q:
w:
e:
r:
t: 廿
y:
u:
i:
o:
p:
p1: '"'
a: a
s: s
d: d
f: f
g: g
h: h
j: j
k: k
l: l
a:
s:
d:
f:
g:
h:
j:
k:
l:
l1: \
l2: ':'
z: z
x: x
c: c
v: v
b: b
n: n
m: m
z:
x:
c:
v:
b:
n:
m:
m1: ','
m2: .
m3: /
Submodule node deleted from bce5a7144b
+37 -1
View File
@@ -25,8 +25,8 @@ use std::thread;
use crate::gui::Colors;
use crate::gui::icons::CAMERA_ROTATE;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::View;
use crate::gui::views::types::{QrScanResult, QrScanState};
use crate::gui::views::{Modal, View};
use crate::wallet::WalletUtils;
use crate::wallet::types::PhraseSize;
@@ -88,6 +88,42 @@ impl CameraContent {
ui.ctx().request_repaint();
}
/// Draw modal camera content.
pub fn modal_ui(
&mut self,
ui: &mut egui::Ui,
cb: &dyn PlatformCallbacks,
mut on_result: impl FnMut(Option<QrScanResult>),
) {
if let Some(result) = self.qr_scan_result() {
on_result(Some(result));
} else {
ui.add_space(6.0);
self.ui(ui, cb);
ui.add_space(6.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
// Show buttons to close modal or come back to sending input.
ui.columns(2, |cols| {
cols[0].vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
cb.stop_camera();
on_result(None);
Modal::close();
});
});
cols[1].vertical_centered_justified(|ui| {
View::button(ui, t!("back"), Colors::white_or_black(false), || {
on_result(None);
});
});
});
ui.add_space(6.0);
}
}
/// Draw camera image.
fn image_ui(&mut self, ui: &mut egui::Ui, mut img: DynamicImage, rotation: u32) -> Rect {
// Setup image rotation.
+88 -11
View File
@@ -12,25 +12,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::{Id, RichText};
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
use crate::gui::Colors;
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, TextEdit, View};
use crate::gui::views::{CameraContent, Modal, TextEdit, View};
use crate::wallet::Wallet;
use crate::wallet::types::WalletTask;
use egui::{Id, RichText};
use grin_core::core::{amount_from_hr_string, amount_to_hr_string};
use grin_wallet_libwallet::SlatepackAddress;
/// Invoice request creation content.
pub struct InvoiceRequestContent {
/// Amount to receive.
amount_edit: String,
/// Sender address.
address_edit: String,
/// Flag to check if entered address is incorrect.
address_error: bool,
/// Address QR code scanner content.
address_scan_content: Option<CameraContent>,
}
impl Default for InvoiceRequestContent {
fn default() -> Self {
Self {
amount_edit: "".to_string(),
address_edit: "".to_string(),
address_error: false,
address_scan_content: None,
}
}
}
@@ -50,14 +61,36 @@ impl InvoiceRequestContent {
return;
}
if let Ok(a) = amount_from_hr_string(m.amount_edit.as_str()) {
m.amount_edit = "".to_string();
wallet.task(WalletTask::Receive(a));
let addr_str = m.address_edit.as_str();
let addr = if let Ok(r) = SlatepackAddress::try_from(addr_str.trim()) {
Some(r)
} else {
None
};
wallet.task(WalletTask::Receive(a, addr));
Modal::close();
}
};
ui.add_space(6.0);
// Draw QR code scanner content if requested.
if let Some(content) = self.address_scan_content.as_mut() {
let mut close_scan = true;
content.modal_ui(ui, cb, |result| {
if let Some(result) = result {
self.address_edit = result.text();
} else {
modal.enable_closing();
close_scan = true;
}
});
if close_scan {
self.address_scan_content = None;
}
return;
}
// Draw amount input content.
ui.vertical_centered(|ui| {
ui.label(
@@ -72,11 +105,9 @@ impl InvoiceRequestContent {
let amount_edit_before = self.amount_edit.clone();
let mut amount_edit = TextEdit::new(Id::from(modal.id).with(wallet.get_config().id))
.h_center()
.numeric();
.numeric()
.focus(Modal::first_draw());
amount_edit.ui(ui, &mut self.amount_edit, cb);
if amount_edit.enter_pressed {
on_continue(self);
}
// Check value if input was changed.
if amount_edit_before != self.amount_edit {
@@ -109,7 +140,54 @@ impl InvoiceRequestContent {
}
}
ui.add_space(8.0);
// Show address error or input description.
ui.vertical_centered(|ui| {
if self.address_error {
ui.label(
RichText::new(t!("transport.incorrect_addr_err"))
.size(17.0)
.color(Colors::red()),
);
} else {
ui.label(
RichText::new(t!("transport.sender_address"))
.size(17.0)
.color(Colors::gray()),
);
}
});
ui.add_space(6.0);
// Show address text edit.
let addr_edit_before = self.address_edit.clone();
let address_edit_id = Id::from(modal.id)
.with("_address")
.with(wallet.get_config().id);
let mut address_edit = TextEdit::new(address_edit_id)
.paste()
.focus(false)
.scan_qr();
if amount_edit.enter_pressed {
address_edit.focus_request();
}
address_edit.ui(ui, &mut self.address_edit, cb);
// Check if scan button was pressed.
if address_edit.scan_pressed {
modal.disable_closing();
self.address_scan_content = Some(CameraContent::default());
}
ui.add_space(12.0);
// Check value if input was changed.
if addr_edit_before != self.address_edit {
self.address_error = false;
}
// Continue on Enter press.
if address_edit.enter_pressed {
on_continue(self);
}
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
@@ -121,7 +199,6 @@ impl InvoiceRequestContent {
t!("modal.cancel"),
Colors::white_or_black(false),
|| {
self.amount_edit = "".to_string();
Modal::close();
},
);
+11 -33
View File
@@ -90,40 +90,18 @@ impl SendRequestContent {
ui.add_space(6.0);
// Draw QR code scanner content if requested.
if let Some(scanner) = self.address_scan_content.as_mut() {
let on_stop = || {
cb.stop_camera();
modal.enable_closing();
};
if let Some(result) = scanner.qr_scan_result() {
self.address_edit = result.text();
on_stop();
if let Some(content) = self.address_scan_content.as_mut() {
let mut close_scan = true;
content.modal_ui(ui, cb, |result| {
if let Some(result) = result {
self.address_edit = result.text();
} else {
modal.enable_closing();
close_scan = true;
}
});
if close_scan {
self.address_scan_content = None;
} else {
ui.add_space(6.0);
scanner.ui(ui, cb);
ui.add_space(6.0);
// Setup spacing between buttons.
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
// Show buttons to close modal or come back to sending input.
ui.columns(2, |cols| {
cols[0].vertical_centered_justified(|ui| {
View::button(ui, t!("close"), Colors::white_or_black(false), || {
on_stop();
self.close();
});
});
cols[1].vertical_centered_justified(|ui| {
View::button(ui, t!("back"), Colors::white_or_black(false), || {
on_stop();
self.address_scan_content = None;
});
});
});
ui.add_space(6.0);
}
return;
}
+11 -15
View File
@@ -633,24 +633,20 @@ impl WalletTransactionsContent {
View::item_button(ui, rounding, icon, color, || {
if repost {
wallet.task(WalletTask::Post(tx.data.id));
return;
} else if let Some(action) = tx.action.as_ref() {
match action {
WalletTxAction::Finalizing => {
wallet.task(WalletTask::Finalize(tx.data.id));
}
WalletTxAction::Posting => {
wallet.task(WalletTask::Post(tx.data.id));
}
_ => {
if let Some(a) = &tx.receiver {
wallet.task(WalletTask::SendTor(tx.data.clone(), a.clone()));
}
}
if action == &WalletTxAction::Finalizing {
wallet.task(WalletTask::Finalize(tx.data.id));
return;
} else if action == &WalletTxAction::Posting {
wallet.task(WalletTask::Post(tx.data.id));
return;
}
}
if let Some(a) = &tx.receiver {
wallet.task(WalletTask::SendTor(tx.data.clone(), a.clone()));
} else {
if let Some(a) = &tx.receiver {
wallet.task(WalletTask::SendTor(tx.data.clone(), a.clone()));
}
wallet.task(WalletTask::FinalizeTor(tx.data.clone()));
}
});
}
+42 -38
View File
@@ -12,13 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use egui::scroll_area::ScrollBarVisibility;
use egui::{Align, CornerRadius, Id, Layout, RichText, ScrollArea, StrokeKind};
use grin_core::core::amount_to_hr_string;
use grin_util::ToHex;
use grin_wallet_libwallet::TxLogEntryType;
use std::fs;
use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{
@@ -34,6 +27,13 @@ use crate::gui::views::{Modal, QrCodeContent, View};
use crate::wallet::Wallet;
use crate::wallet::types::{WalletTask, WalletTx};
use egui::scroll_area::ScrollBarVisibility;
use egui::{Align, CornerRadius, Id, Layout, RichText, ScrollArea, StrokeKind};
use grin_core::core::amount_to_hr_string;
use grin_util::ToHex;
use grin_wallet_libwallet::TxLogEntryType;
use std::fs;
/// Transaction information [`Modal`] content.
pub struct WalletTransactionContent {
/// Transaction identifier.
@@ -168,10 +168,12 @@ impl WalletTransactionContent {
cb: &dyn PlatformCallbacks,
) {
if self.message.is_none() {
let slatepack_path = wallet
.get_config()
.get_slate_path(tx.data.tx_slate_id.unwrap(), &tx.state);
self.message = Some(fs::read_to_string(slatepack_path).unwrap_or("".to_string()));
if let Some(slate_state) = tx.data.tx_slate_state.as_ref() {
let slatepack_path = wallet
.get_config()
.get_slate_path(tx.data.tx_slate_id.unwrap(), slate_state);
self.message = Some(fs::read_to_string(slatepack_path).unwrap_or("".to_string()));
}
}
if let Some(m) = &self.message {
if m.is_empty() {
@@ -260,29 +262,31 @@ impl WalletTransactionContent {
});
// Draw button to share response as file.
ui.add_space(8.0);
ui.vertical_centered(|ui| {
let share_text = format!("{} {}", FILE_TEXT, t!("share"));
View::colored_text_button(
ui,
share_text,
Colors::blue(),
Colors::white_or_black(false),
|| {
if let Some(slate_id) = tx.data.tx_slate_id {
let name = format!("{}.{}.slatepack", slate_id, tx.state);
let data = m.as_bytes().to_vec();
cb.share_data(name, data).unwrap_or_default();
// Show message input or close modal.
if tx.can_finalize() {
finalization_needed = true;
} else {
Modal::close();
}
}
},
);
});
if let Some(slate_id) = tx.data.tx_slate_id {
if let Some(slate_state) = tx.data.tx_slate_state.as_ref() {
ui.add_space(8.0);
ui.vertical_centered(|ui| {
let share_text = format!("{} {}", FILE_TEXT, t!("share"));
View::colored_text_button(
ui,
share_text,
Colors::blue(),
Colors::white_or_black(false),
|| {
let name = format!("{}.{}.slatepack", slate_id, slate_state);
let data = m.as_bytes().to_vec();
cb.share_data(name, data).unwrap_or_default();
// Show message input or close modal.
if tx.can_finalize() {
finalization_needed = true;
} else {
Modal::close();
}
},
);
});
}
}
if finalization_needed {
Modal::new(MessageInputContent::MODAL_ID)
@@ -370,13 +374,13 @@ impl WalletTransactionContent {
info_item_ui(ui, kernel.0.to_hex(), label, true, cb);
}
// Show receiver or sender address.
let addr = if tx.data.tx_type == TxLogEntryType::TxSent {
&tx.receiver
let (addr, label) = if tx.data.tx_type == TxLogEntryType::TxSent {
(&tx.receiver, t!("transport.receiver_address"))
} else {
&tx.sender
(&tx.sender, t!("transport.sender_address"))
};
if let Some(addr) = addr {
let label = format!("{} {}", CIRCLE_HALF, t!("network_mining.address"));
let label = format!("{} {}", CIRCLE_HALF, label.replace(":", ""));
info_item_ui(ui, addr.to_string(), label, true, cb);
}
}
+1 -1
View File
@@ -323,7 +323,7 @@ impl Tor {
.block_on(async move {
let res = c
.runtime()
.timeout(Duration::from_millis(600000), async {
.timeout(Duration::from_millis(60000), async {
if let Ok(stream) = c
.connect((uri.host().unwrap(), uri.port_u16().unwrap_or(80)))
.await
+9 -18
View File
@@ -213,8 +213,6 @@ pub enum WalletTxAction {
pub struct WalletTx {
/// Information from database.
pub data: TxLogEntry,
/// State of transaction Slate.
pub state: SlateState,
/// Payment proof.
pub(crate) proof: Option<PaymentProof>,
@@ -240,7 +238,6 @@ impl WalletTx {
pub fn new(
tx: TxLogEntry,
proof: Option<PaymentProof>,
wallet: &Wallet,
height: Option<u64>,
broadcasting_height: Option<u64>,
action: Option<WalletTxAction>,
@@ -263,9 +260,8 @@ impl WalletTx {
sender = Some(addr);
}
}
let mut t = Self {
let t = Self {
data: tx,
state: SlateState::Unknown,
proof,
amount,
receiver,
@@ -275,15 +271,6 @@ impl WalletTx {
action,
action_error,
};
// Update Slate state for unconfirmed.
if !t.data.confirmed
&& (t.data.tx_type == TxLogEntryType::TxSent
|| t.data.tx_type == TxLogEntryType::TxReceived)
{
if let Some(slate_id) = t.data.tx_slate_id {
t.state = wallet.get_slate_state(slate_id, &t.data.tx_type)
}
}
t
}
@@ -294,15 +281,16 @@ impl WalletTx {
&& (!self.sending_tor() || self.action_error.is_some())
&& (self.data.tx_type == TxLogEntryType::TxSent
|| self.data.tx_type == TxLogEntryType::TxReceived)
&& (self.state == SlateState::Invoice1 || self.state == SlateState::Standard1)
&& (self.data.tx_slate_state == Some(SlateState::Invoice1)
|| self.data.tx_slate_state == Some(SlateState::Standard1))
}
/// Check if transaction was finalized.
pub fn finalized(&self) -> bool {
(self.data.tx_type == TxLogEntryType::TxSent
|| self.data.tx_type == TxLogEntryType::TxReceived)
&& self.state == SlateState::Invoice3
|| self.state == SlateState::Standard3
&& self.data.tx_slate_state == Some(SlateState::Invoice3)
|| self.data.tx_slate_state == Some(SlateState::Standard3)
}
/// Check if transaction is sending over Tor.
@@ -418,9 +406,12 @@ pub enum WalletTask {
/// * tx
/// * receiver
SendTor(TxLogEntry, SlatepackAddress),
/// Finalize over Tor.
/// * tx
FinalizeTor(TxLogEntry),
/// Invoice creation.
/// * amount
Receive(u64),
Receive(u64, Option<SlatepackAddress>),
/// Transaction finalization.
/// * tx id
Finalize(u32),
+135 -92
View File
@@ -39,6 +39,7 @@ use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
use grin_wallet_libwallet::api_impl::owner::{
cancel_tx, init_send_tx, retrieve_summary_info, retrieve_txs, verify_payment_proof,
};
use grin_wallet_libwallet::api_impl::types::update_tx_slate_state;
use grin_wallet_libwallet::{
Error, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState,
SlateVersion, SlatepackAddress, StatusMessage, StoredProofInfo, TxLogEntry, TxLogEntryType,
@@ -936,7 +937,7 @@ impl Wallet {
fn create_slatepack_message(
&self,
slate: &Slate,
_: Option<SlatepackAddress>,
address: Option<SlatepackAddress>,
) -> Result<String, Error> {
let mut message = "".to_string();
let r_inst = self.instance.as_ref().read();
@@ -947,11 +948,11 @@ impl Wallet {
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), vec![])?;
let addrs = match address {
Some(a) => vec![a],
None => vec![],
};
message = api.create_slatepack_message(m, &slate, Some(0), addrs)?;
Ok(())
},
)?;
@@ -970,40 +971,6 @@ impl Wallet {
fs::exists(slatepack_path).unwrap_or(false)
}
/// Get possible state from tx type.
pub fn get_slate_state(&self, slate_id: Uuid, tx_type: &TxLogEntryType) -> SlateState {
let mut slate = Slate::blank(1, false);
slate.id = slate_id;
slate.state = match tx_type {
TxLogEntryType::TxReceived => SlateState::Invoice3,
_ => SlateState::Standard3,
};
// Transaction was finalized.
if self.slatepack_exists(&slate) {
slate.state
} else {
slate.state = match tx_type {
TxLogEntryType::TxReceived => SlateState::Standard2,
_ => SlateState::Invoice2,
};
// Transaction signed to be finalized.
if self.slatepack_exists(&slate) {
slate.state
} else {
// Transaction just was created.
slate.state = match tx_type {
TxLogEntryType::TxReceived => SlateState::Invoice1,
_ => SlateState::Standard1,
};
if self.slatepack_exists(&slate) {
slate.state
} else {
SlateState::Unknown
}
}
}
}
/// Calculate transaction fee for provided amount.
fn calculate_fee(&self, a: u64) -> Result<u64, Error> {
let r_inst = self.instance.as_ref().read();
@@ -1057,7 +1024,7 @@ impl Wallet {
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)?;
let _ = self.create_slatepack_message(&s, None)?;
// Lock outputs to for this transaction.
api.tx_lock_outputs(m, &s)?;
slate = Some(s);
@@ -1070,27 +1037,47 @@ impl Wallet {
}
}
/// Send slate to Tor address.
async fn send_tor(&self, id: u32, s: &Slate, addr: &SlatepackAddress) -> Result<Slate, Error> {
/// Send slate to Tor address. When `finalize` is true, posts the slate to the
/// peer's foreign-api `finalize_tx` (used by the invoice-flow payer to push the
/// signed Invoice2 back to the merchant for broadcast); otherwise posts to
/// `receive_tx` (standard send flow).
async fn send_tor(
&self,
id: u32,
s: &Slate,
addr: &SlatepackAddress,
finalize: bool,
) -> Result<Slate, Error> {
self.on_tx_action(id, Some(WalletTxAction::SendingTor));
let tor_addr = OnionV3Address::try_from(addr).unwrap().to_http_str();
let url = format!("{}/v2/foreign", tor_addr);
let slate_send = VersionedSlate::into_version(s.clone(), SlateVersion::V4)?;
let body = json!({
"jsonrpc": "2.0",
"method": "receive_tx",
"id": 1,
"params": [
slate_send,
null,
null
]
})
let body = if finalize {
json!({
"jsonrpc": "2.0",
"method": "finalize_tx",
"id": 1,
"params": [slate_send]
})
} else {
json!({
"jsonrpc": "2.0",
"method": "receive_tx",
"id": 1,
"params": [
slate_send,
null,
null
]
})
}
.to_string();
// Wait Tor service to launch.
while Tor::is_service_starting(&self.identifier()) {
tokio::time::sleep(Duration::from_secs(1)).await;
while !Tor::is_service_running(&self.identifier()) {
return Err(Error::GenericError(
"Tor service is not running".to_string(),
));
}
// Send request to receiver.
let req_res = Tor::post(body, url).await;
@@ -1118,7 +1105,11 @@ impl Wallet {
}
/// Initialize an invoice transaction to receive amount, return request for funds sender.
fn issue_invoice(&self, amount: u64) -> Result<Slate, Error> {
fn issue_invoice(
&self,
amount: u64,
address: Option<SlatepackAddress>,
) -> Result<Slate, Error> {
let args = IssueInvoiceTxArgs {
dest_acct_name: None,
amount,
@@ -1130,13 +1121,13 @@ impl Wallet {
let slate = api.issue_invoice_tx(self.keychain_mask().as_ref(), args)?;
// Create Slatepack message response.
let _ = self.create_slatepack_message(&slate, None)?;
let _ = self.create_slatepack_message(&slate, address)?;
Ok(slate)
}
/// Handle message from the invoice issuer to send founds, return response for funds receiver.
fn pay(&self, slate: &Slate) -> Result<Slate, Error> {
fn pay(&self, slate: &Slate, dest: Option<SlatepackAddress>) -> Result<Slate, Error> {
let config = self.get_config();
let args = InitTxArgs {
src_acct_name: None,
@@ -1151,8 +1142,9 @@ impl Wallet {
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, None)?;
// Create Slatepack message response (kept as on-disk paste-back fallback even
// when an auto-Tor reply is attempted by the caller).
let _ = self.create_slatepack_message(&slate, dest)?;
Ok(slate)
}
@@ -1310,20 +1302,33 @@ impl Wallet {
}
/// Get stored transaction Slate.
fn get_tx_slate(&self, tx_id: u32) -> Option<Slate> {
fn get_tx_slate(&self, tx_id: u32) -> Option<(Slate, Option<SlatepackAddress>)> {
if let Some(tx) = self.retrieve_tx_by_id(Some(tx_id), None) {
if let Some(slate_id) = tx.tx_slate_id {
let slate_state = self.get_slate_state(slate_id, &tx.tx_type);
let slatepack_path = self.get_config().get_slate_path(slate_id, &slate_state);
let msg = fs::read_to_string(slatepack_path).unwrap_or("".to_string());
if let Ok((slate, _)) = self.parse_slatepack(&msg) {
return Some(slate);
if let Some(slate_state) = tx.tx_slate_state {
let slatepack_path = self.get_config().get_slate_path(slate_id, &slate_state);
let msg = fs::read_to_string(slatepack_path).unwrap_or("".to_string());
if let Ok((slate, dest)) = self.parse_slatepack(&msg) {
return Some((slate, dest));
}
}
}
}
None
}
/// Update transaction slate state.
fn update_slate_state(&self, slate: &Slate) -> Result<(), Error> {
let r_inst = self.instance.as_ref().read();
let instance = r_inst.clone().unwrap();
let mut w_lock = instance.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
let keychain_mask = self.keychain_mask();
let parent_key = w.parent_key_id();
update_tx_slate_state(w, keychain_mask.as_ref(), &parent_key, slate)?;
Ok(())
}
/// Delete transaction from database.
fn delete_tx(&self, id: u32) -> Result<(), Error> {
self.on_tx_action(id, Some(WalletTxAction::Deleting));
@@ -1341,7 +1346,7 @@ impl Wallet {
batch.commit()?;
// Delete transaction files.
if let Some(s) = slate {
if let Some((s, _)) = slate {
let slatepack_path = self.get_config().get_slate_path(s.id, &s.state);
fs::remove_file(&slatepack_path).unwrap_or_default();
let path = path::Path::new(&self.get_config().get_data_path())
@@ -1743,8 +1748,9 @@ fn start_sync(wallet: Wallet) -> Thread {
/// Handle wallet task.
async fn handle_task(w: &Wallet, t: WalletTask) {
// Send amount over Tor.
let send_tor = async |tx: TxLogEntry, s: &Slate, r: &SlatepackAddress| match w
.send_tor(tx.id, &s, r)
.send_tor(tx.id, &s, r, false)
.await
{
Ok(s) => match w.finalize(&s, tx.id) {
@@ -1754,17 +1760,35 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
w.on_task_result(Some(tx), &t);
}
Err(e) => {
error!("send tor post error: {:?}", e);
error!("Send Tor post error: {:?}", e);
w.on_tx_error(tx.id, Some(e));
}
},
Err(e) => {
error!("send tor finalize error: {:?}", e);
error!("Send Tor finalize error: {:?}", e);
w.task(WalletTask::Cancel(tx.id));
}
},
Err(e) => {
error!("send tor error: {:?}", e);
error!("Send Tor error: {:?}", e);
w.on_tx_error(tx.id, Some(e));
w.on_task_result(Some(tx), &t);
}
};
// Finalize tx over Tor.
let finalize_tor = async |tx: TxLogEntry, s: &Slate, r: &SlatepackAddress| match w
.send_tor(tx.id, &s, r, true)
.await
{
Ok(s) => {
w.on_tx_action(tx.id, None);
let _ = w.update_slate_state(&s);
let _ = w.create_slatepack_message(&s, None);
sync_wallet_data(&w, false);
w.on_task_result(Some(tx), &t);
}
Err(e) => {
error!("Finalize Tor error: {:?}", e);
w.on_tx_error(tx.id, Some(e));
w.on_task_result(Some(tx), &t);
}
@@ -1800,21 +1824,34 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
w.message_opening.store(false, Ordering::Relaxed);
return;
}
// Finalize over Tor if service is running.
let maybe_finalize_tor = async |s: Slate| {
sync_wallet_data(&w, false);
let tx = w.retrieve_tx_by_id(None, Some(s.id));
let id = w.identifier();
if Tor::is_service_running(&id) {
if let Some(tx) = tx.as_ref() {
if let Some(addr) = dest {
w.message_opening.store(false, Ordering::Relaxed);
finalize_tor(tx.clone(), &s, &addr).await;
return;
}
}
}
};
// Create response or finalize.
match s.state {
SlateState::Standard1 | SlateState::Invoice1 => {
if s.state != SlateState::Standard1 {
if let Ok(_) = w.pay(&s) {
sync_wallet_data(&w, false);
let tx = w.retrieve_tx_by_id(None, Some(s.id));
w.on_task_result(tx, &t);
}
} else {
if let Ok(_) = w.receive(&s, dest) {
sync_wallet_data(&w, false);
let tx = w.retrieve_tx_by_id(None, Some(s.id));
w.on_task_result(tx, &t);
}
SlateState::Standard1 => {
if let Ok(s) = w.receive(&s, None) {
maybe_finalize_tor(s).await;
w.on_task_result(tx, &t);
}
}
SlateState::Invoice1 => {
if let Ok(s) = w.pay(&s, None) {
sync_wallet_data(&w, false);
maybe_finalize_tor(s).await;
w.on_task_result(tx, &t);
}
}
SlateState::Standard2 | SlateState::Invoice2 => {
@@ -1870,7 +1907,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
if let Some(tx) = tx {
if let Some(addr) = r {
let id = w.identifier();
if Tor::is_service_running(&id) || Tor::is_service_starting(&id) {
if Tor::is_service_running(&id) {
w.send_creating.store(false, Ordering::Relaxed);
send_tor(tx, &s, addr).await;
return;
@@ -1885,13 +1922,20 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
w.send_creating.store(false, Ordering::Relaxed);
}
WalletTask::SendTor(tx, r) => {
if let Some(s) = w.get_tx_slate(tx.id) {
send_tor(tx.clone(), &s, r).await;
if let Some((slate, _)) = w.get_tx_slate(tx.id) {
send_tor(tx.clone(), &slate, r).await;
}
}
WalletTask::Receive(a) => {
WalletTask::FinalizeTor(tx) => {
if let Some((slate, dest)) = w.get_tx_slate(tx.id) {
if let Some(dest) = dest {
finalize_tor(tx.clone(), &slate, &dest).await;
}
}
}
WalletTask::Receive(amount, address) => {
w.invoice_creating.store(true, Ordering::Relaxed);
if let Ok(s) = w.issue_invoice(*a) {
if let Ok(s) = w.issue_invoice(*amount, address.clone()) {
sync_wallet_data(&w, false);
let tx = w.retrieve_tx_by_id(None, Some(s.id));
if let Some(tx) = tx {
@@ -1901,7 +1945,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
w.invoice_creating.store(false, Ordering::Relaxed);
}
WalletTask::Finalize(id) => {
if let Some(s) = w.get_tx_slate(*id) {
if let Some((s, _)) = w.get_tx_slate(*id) {
w.on_tx_error(*id, None);
match w.finalize(&s, *id) {
Ok(s) => match w.post(&s, Some(*id)) {
@@ -1924,7 +1968,7 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
}
}
WalletTask::Post(id) => {
if let Some(s) = w.get_tx_slate(*id) {
if let Some((s, _)) = w.get_tx_slate(*id) {
w.on_tx_error(*id, None);
// Cleanup broadcasting tx height.
let tx_height_store = TxHeightStore::new(w.get_config().get_extra_db_path());
@@ -2143,7 +2187,6 @@ fn update_txs(wallet: &Wallet, mut txs_limit: u32) -> Result<(), Error> {
let mut new = WalletTx::new(
tx.clone(),
proof.clone(),
wallet,
height,
broadcasting_height,
action,
+1 -1
Submodule wallet updated: c2db754552...5c54e7cf8d