Slatepack address index on send (#821)

* Store address_index into tx log entry to get address and decrypt slatepack message, increment over all values

* Provide address index on send and issue_invoice command

* Keep 0 index for receive

* Use sender address index at payment proof

Rust API users need to update to SlatepackAddressIndex and the Slatepack methods
Keep a wallet database backup for pending transactions and saved address indices. Seed recovery alone does not restore these, and the address counter restarts at 1.
This commit is contained in:
ardocrat
2026-10-13 16:41:29 +03:00
committed by GitHub
parent dce4e5eecc
commit b8ddf13001
31 changed files with 1171 additions and 735 deletions
+70 -68
View File
@@ -19,19 +19,20 @@ use crate::apiwallet::{try_slatepack_sync_workflow, Owner};
use crate::config::{TorConfig, WalletConfig, WALLET_CONFIG_FILE_NAME};
use crate::core::{core, global};
use crate::error::Error;
use crate::impls::PathToSlatepack;
use crate::impls::SlateGetter as _;
use crate::keychain;
use crate::libwallet::api_impl::owner::{self, decrypt_slatepack, deserialize_slatepack};
use crate::libwallet::api_impl::types::update_tx_slate_state;
use crate::libwallet::slatepack::get_slatepack_file_contents;
use crate::libwallet::slatepack::SlatepackAddressIndex;
use crate::libwallet::{
self, InitTxArgs, IssueInvoiceTxArgs, NodeClient, PaymentProof, Slate, SlateState, Slatepack,
SlatepackAddress, Slatepacker, SlatepackerArgs, WalletLCProvider,
SlatepackAddress, WalletLCProvider,
};
use crate::libwallet::{wallet_lock, WalletHandle};
use crate::util::secp::key::SecretKey;
use crate::util::{Mutex, ZeroingString};
use crate::{controller, display};
use grin_wallet_libwallet::wallet_lock;
use qr_code::QrCode;
use serde_json as json;
use std::fs::File;
@@ -341,6 +342,7 @@ pub struct SendArgs {
pub outfile: Option<String>,
pub bridge: Option<String>,
pub slatepack_qr: bool,
pub address_index: Option<SlatepackAddressIndex>,
}
fn max_retry_args(mut init_args: InitTxArgs, amount: u64, max_inputs: u32) -> InitTxArgs {
@@ -412,6 +414,7 @@ where
selection_strategy_is_use_all: strategy == "all",
refresh_outputs_from_node: !info_updated,
estimate_only: Some(true),
address_index: args.address_index,
..Default::default()
};
let result = owner_api.init_send_tx(keychain_mask, init_args.clone());
@@ -456,6 +459,7 @@ where
payment_proof_recipient_address,
ttl_blocks: args.ttl_blocks,
late_lock: Some(args.late_lock),
address_index: args.address_index,
..Default::default()
};
let init_send_tx = |init_args: InitTxArgs| -> Result<Slate, libwallet::Error> {
@@ -505,6 +509,11 @@ where
}
let output_sp = |owner_api: &mut Owner<L, C, K>, lock_outputs: bool| -> Result<(), Error> {
let sender_index = {
wallet_lock!(owner_api.wallet_inst, w);
let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?;
context.payment_proof_derivation_index
};
Ok(output_slatepack(
owner_api,
keychain_mask,
@@ -514,6 +523,7 @@ where
lock_outputs,
false,
args.slatepack_qr,
sender_index,
)?)
};
@@ -567,6 +577,7 @@ pub fn output_slatepack<L, C, K>(
lock: bool,
finalizing: bool,
show_qr: bool,
sender_index: Option<SlatepackAddressIndex>,
) -> Result<(), libwallet::Error>
where
L: WalletLCProvider<'static, C, K> + 'static,
@@ -579,7 +590,8 @@ where
Some(a) => vec![a],
None => vec![],
};
let message = owner_api.create_slatepack_message(keychain_mask, slate, Some(0), recipients)?;
let message =
owner_api.create_slatepack_message(keychain_mask, slate, sender_index, recipients)?;
let tld = owner_api.get_top_level_directory()?;
// create a directory to which files will be output
@@ -653,42 +665,30 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let mut ret_address = None;
let slate = match filename {
let (slate, sender_address) = match filename {
Some(f) => {
// otherwise, get slate from slatepack
let dec_key = owner_api.get_slatepack_secret_key(keychain_mask, 0)?;
let packer = Slatepacker::new(SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key: Some(&dec_key),
});
let pts = PathToSlatepack::new(f.into(), &packer, true);
let sl = Some(pts.get_tx()?.0);
ret_address = pts.get_slatepack(true)?.sender;
sl
let data = get_slatepack_file_contents(&f.into())?;
let sp = deserialize_slatepack(
&data,
owner_api.wallet_inst.clone(),
keychain_mask,
vec![SlatepackAddressIndex(0)],
)?;
let sl = sp.get_slate()?;
(sl, sp.sender)
}
None => None,
};
let slate = match slate {
Some(s) => s,
None => {
// try and parse directly from input_slatepack_message
match message {
Some(message) => {
let slate = owner_api.slate_from_slatepack_message(
let sp = owner_api.decode_slatepack_message(
keychain_mask,
message.clone(),
vec![0],
vec![SlatepackAddressIndex(0)],
)?;
let slatepack = owner_api.decode_slatepack_message(
keychain_mask,
message.clone(),
vec![0],
)?;
ret_address = slatepack.sender;
slate
let sl = sp.get_slate()?;
(sl, sp.sender)
}
None => {
let msg = "No slate provided via file or direct input";
@@ -697,7 +697,7 @@ where
}
}
};
Ok((slate, ret_address))
Ok((slate, sender_address))
}
/// Receive command argument
@@ -775,6 +775,7 @@ where
false,
false,
args.slatepack_qr,
None,
)?)
};
@@ -790,8 +791,7 @@ where
Ok(s) => {
// Update slate state.
{
let mut w_lock = owner_api.wallet_inst.lock();
let w = w_lock.lc_provider()?.wallet_inst()?;
wallet_lock!(owner_api.wallet_inst, w);
let parent_key_id = w.parent_key_id();
match update_tx_slate_state(w, keychain_mask, &parent_key_id, &s) {
Ok(_) => {}
@@ -814,25 +814,18 @@ where
}
pub fn read_slatepack(args: ReceiveArgs) -> Result<Slatepack, Error> {
let packer = Slatepacker::new(SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key: None,
});
let slatepack = match args.input_file {
Some(f) => PathToSlatepack::new(f.into(), &packer, true).get_slatepack(false)?,
let data = match args.input_file {
Some(f) => get_slatepack_file_contents(&f.into())?,
None => match args.input_slatepack_message {
Some(message) => packer.deser_slatepack(message.as_bytes(), false)?,
None => {
return Err(Error::ArgumentError("Invalid Slatepack Input".into()));
}
Some(message) => message.into_bytes(),
None => return Err(Error::ArgumentError("Invalid Slatepack Input".into())),
},
};
Ok(slatepack)
Ok(owner::parse_slatepack(&data)?)
}
pub fn unpack<L, C, K>(
owner_api: &mut Owner<L, C, K>,
wallet_inst: WalletHandle<'static, L, C, K>,
keychain_mask: Option<&SecretKey>,
mut slatepack: Slatepack,
) -> Result<(), Error>
@@ -847,33 +840,32 @@ where
println!("{}", slatepack);
println!("------------------");
let packer = Slatepacker::new(SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key: None,
});
if slatepack.mode == 1 {
let dec_key = owner_api.get_slatepack_secret_key(keychain_mask, 0)?;
match slatepack.try_decrypt_payload(Some(&dec_key)) {
Ok(_) => {
match decrypt_slatepack(
&mut slatepack,
wallet_inst,
keychain_mask,
vec![SlatepackAddressIndex(0)],
)
.and_then(|_| slatepack.get_slate())
{
Ok(s) => {
println!("Slatepack is encrypted for this wallet");
println!();
println!("DECRYPTED SLATEPACK");
println!("-------------------");
println!("{}", slatepack);
let slate = packer.get_slate(&slatepack)?;
println!();
println!("DECRYPTED SLATE");
println!("---------------");
println!("{}", slate);
println!("{}", s);
}
Err(_) => {
println!("Slatepack payload cannot be decrypted by this wallet");
}
}
} else {
let slate = packer.get_slate(&slatepack)?;
let slate = slatepack.get_slate()?;
println!("Slatepack is not encrypted");
println!();
println!("SLATE");
@@ -904,7 +896,7 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let (mut slate, _ret_address) = parse_slatepack(
let (mut slate, _) = parse_slatepack(
owner_api,
keychain_mask,
args.input_file.clone(),
@@ -975,6 +967,7 @@ where
false,
true,
args.slatepack_qr,
None,
)?;
Ok(())
@@ -984,11 +977,11 @@ where
pub struct IssueInvoiceArgs {
/// Slatepack address
pub dest: Option<String>,
/// issue invoice tx args
/// Issue invoice tx args
pub issue_args: IssueInvoiceTxArgs,
/// output file override
/// Output file override
pub outfile: Option<String>,
/// show slatepack as QR code
/// Show slatepack as QR code
pub slatepack_qr: bool,
}
@@ -1009,8 +1002,13 @@ where
.transpose()?;
let issue_args = args.issue_args.clone();
let slate = owner_api.issue_invoice_tx(keychain_mask, issue_args)?;
let slate = owner_api.issue_invoice_tx(keychain_mask, issue_args.clone())?;
let sender_index = {
wallet_lock!(owner_api.wallet_inst, w);
let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?;
context.payment_proof_derivation_index
};
output_slatepack(
owner_api,
keychain_mask,
@@ -1020,6 +1018,7 @@ where
false,
false,
args.slatepack_qr,
sender_index,
)?;
Ok(())
}
@@ -1133,6 +1132,7 @@ where
true,
false,
args.slatepack_qr,
None,
)?)
};
@@ -1314,7 +1314,7 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
let (slate, _ret_address) = parse_slatepack(
let (slate, _) = parse_slatepack(
owner_api,
keychain_mask,
args.input_file,
@@ -1462,7 +1462,7 @@ where
}
}
/// Payment Proof Address
/// Show wallet's slatepack address.
pub fn address<L, C, K>(
owner_api: &mut Owner<L, C, K>,
keychain_mask: Option<&SecretKey>,
@@ -1472,8 +1472,10 @@ where
C: NodeClient + 'static,
K: keychain::Keychain + 'static,
{
// Just address at derivation index 0 for now
let address = owner_api.get_slatepack_address(keychain_mask, 0)?;
// Just address at derivation index 0 for now,
// cause sender encrypts message for address,
// and we don't know its derivation path index.
let address = owner_api.get_slatepack_address(keychain_mask, SlatepackAddressIndex(0))?;
let account = account_label(owner_api)?;
println!();
println!("Address for account - {}", account);
+4 -2
View File
@@ -55,6 +55,7 @@ use grin_wallet_config::config::{
};
use grin_wallet_impls::tor::arti::{start_tor_service, stop_tor_service};
use grin_wallet_impls::tor::process::TorProcess;
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
use http_body_util::{BodyExt, Full};
use hyper::body::Incoming;
use tokio::sync::mpsc;
@@ -255,8 +256,9 @@ where
let w_inst = lc.wallet_inst()?;
let k = w_inst.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)))?;
let sec_key =
address::address_from_derivation_path(&k, &parent_key_id, SlatepackAddressIndex(0))
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
let tor_dir = format!("{}/tor/listener", lc.get_top_level_directory()?);
let onion_address = OnionV3Address::from_private(&sec_key.0)
.map_err(|e| Error::TorConfig(format!("{:?}", e)))?;
+3 -2
View File
@@ -20,7 +20,7 @@ use crate::libwallet::{
TxLogEntry, TxLogEntryType, ViewWallet, WalletInfo,
};
use crate::util::ToHex;
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
use prettytable;
use prettytable::format::{FormatBuilder, LinePosition, LineSeparator};
use std::io::prelude::Write;
@@ -541,7 +541,8 @@ pub fn accounts(acct_mappings: Vec<AcctPathMapping>) {
bMG->"Spendable balance",
]);
for m in acct_mappings {
let slatepack_path = address::address_derivation_path(&m.path, 0).to_bip_32_string();
let slatepack_path =
address::address_derivation_path(&m.path, SlatepackAddressIndex(0)).to_bip_32_string();
let spendable = if let Some(info) = m.info {
amount_to_hr_string(info.amount_currently_spendable, true)
} else {
+2 -1
View File
@@ -32,6 +32,7 @@ use grin_wallet_libwallet::{InitTxArgs, IssueInvoiceTxArgs};
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
/// self send impl
fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(), libwallet::Error> {
@@ -235,7 +236,7 @@ fn file_exchange_test_impl(test_dir: &'static str, use_bin: bool) -> Result<(),
format!("{}/standard_pp_S3.txbin", test_dir),
),
};
let address = Some(api2.get_slatepack_address(mask2, 0)?);
let address = Some(api2.get_slatepack_address(mask2, SlatepackAddressIndex(0))?);
// send to send
let args = InitTxArgs {
+3 -2
View File
@@ -28,6 +28,7 @@ use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
/// Various tests on accounts within the same wallet
fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
@@ -78,7 +79,7 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
let address = Some(api2.get_slatepack_address(mask2, 0)?);
let address = Some(api2.get_slatepack_address(mask2, SlatepackAddressIndex(0))?);
println!("Public address is: {:?}", address);
let amount = 60_000_000_000;
@@ -121,7 +122,7 @@ fn payment_proofs_test_impl(test_dir: &'static str) -> Result<(), libwallet::Err
slate_i.payment_proof.as_ref().unwrap().receiver_address
);
assert!(pp.receiver_signature.is_some());
assert_eq!(pp.sender_address_path, 0);
assert_eq!(pp.sender_address_path, SlatepackAddressIndex(1));
assert_eq!(pp.sender_signature, None);
// check we should get an error at this point since proof is not complete
+4 -1
View File
@@ -30,6 +30,7 @@ use std::time::Duration;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
use grin_wallet_libwallet::slatepack::SlatepackAddressIndex;
/// self send impl
fn self_send_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
@@ -171,7 +172,9 @@ fn late_lock_self_send_proof() -> Result<(), libwallet::Error> {
amount: core::consensus::REWARD,
minimum_confirmations: 2,
late_lock: Some(true),
payment_proof_recipient_address: Some(api1.get_slatepack_address(mask1, 0)?),
payment_proof_recipient_address: Some(
api1.get_slatepack_address(mask1, SlatepackAddressIndex(0))?,
),
..Default::default()
},
)?;
+298 -93
View File
@@ -20,25 +20,30 @@ extern crate grin_wallet_impls as impls;
use grin_core as core;
use grin_wallet_config::GlobalWalletConfig;
use grin_wallet_libwallet as libwallet;
use std::path::PathBuf;
use impls::test_framework::{self, LocalWalletClient};
use impls::{PathToSlatepack, SlatePutter as _};
use impls::DefaultLCProvider;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
use std::thread;
use std::time::Duration;
use std::{fs, thread};
use grin_wallet_libwallet::{
InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, Slate, Slatepack, SlatepackAddress,
Slatepacker, SlatepackerArgs,
slatepack, Error, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, Slate, Slatepack,
SlatepackAddress, SlatepackBin, WalletHandle,
};
use ed25519_dalek::SigningKey as edDalekSecretKey;
use ed25519_dalek::VerifyingKey as edDalekPublicKey;
#[macro_use]
mod common;
use common::{clean_output_dir, create_wallet_proxy, setup};
use grin_keychain::ExtKeychain;
use grin_util::secp::SecretKey;
use grin_wallet_libwallet::api_impl::owner::{decrypt_slatepack, deserialize_slatepack};
use grin_wallet_libwallet::slatepack::{get_slatepack_file_contents, SlatepackAddressIndex};
use grin_wallet_util::byte_ser;
fn output_slatepack(
slate: &Slate,
@@ -47,35 +52,55 @@ fn output_slatepack(
use_bin: bool,
sender: Option<SlatepackAddress>,
recipients: Vec<SlatepackAddress>,
) -> Result<(), libwallet::Error> {
let packer = Slatepacker::new(SlatepackerArgs {
sender,
recipients,
dec_key: None,
});
) -> Result<(), Error> {
let mut file = file.into();
if armored {
file = format!("{}.armored", file);
}
PathToSlatepack::new(file.into(), &packer, armored).put_tx(slate, use_bin)
let slatepack = slate.create_slatepack(sender, recipients)?;
let mut pub_tx = File::create(&file)?;
if use_bin {
if armored {
let armored = slatepack.armor()?;
pub_tx.write_all(armored.as_bytes())?;
} else {
pub_tx.write_all(
&byte_ser::to_bytes(&SlatepackBin(slatepack)).map_err(|_| Error::SlatepackSer)?,
)?;
}
} else {
pub_tx.write_all(
serde_json::to_string_pretty(&slatepack)
.map_err(|_| Error::SlateSer)?
.as_bytes(),
)?;
}
pub_tx.sync_all()?;
Ok(())
}
fn slate_from_packed(
wallet_inst: WalletHandle<
DefaultLCProvider<LocalWalletClient, ExtKeychain>,
LocalWalletClient,
ExtKeychain,
>,
keychain_mask: Option<&SecretKey>,
file: &str,
armored: bool,
dec_key: Option<&edDalekSecretKey>,
) -> Result<(Slatepack, Slate), libwallet::Error> {
let packer = Slatepacker::new(SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key,
});
) -> Result<(Slatepack, Slate), Error> {
let mut file = file.into();
if armored {
file = format!("{}.armored", file);
}
let slatepack = PathToSlatepack::new(file.into(), &packer, armored).get_slatepack(true)?;
Ok((slatepack.clone(), packer.get_slate(&slatepack)?))
let data = get_slatepack_file_contents(&file.into())?;
let slatepack = deserialize_slatepack(
&data,
wallet_inst,
keychain_mask,
vec![SlatepackAddressIndex(0)],
)?;
Ok((slatepack.clone(), slatepack.get_slate()?))
}
/// self send impl
@@ -84,7 +109,7 @@ fn slatepack_exchange_test_impl(
use_bin: bool,
use_armored: bool,
use_encryption: bool,
) -> Result<(), libwallet::Error> {
) -> Result<(), Error> {
// Create a new proxy to simulate server and wallet responses
let mut wallet_proxy = create_wallet_proxy(test_dir);
let chain = wallet_proxy.chain.clone();
@@ -134,41 +159,49 @@ fn slatepack_exchange_test_impl(
api2.create_account_path(mask2, "account1")?;
api2.create_account_path(mask2, "account2")?;
// Get some mining done
{
wallet_inst!(wallet1, w);
w.set_account_by_name("mining")?;
}
{
wallet_inst!(wallet2, w);
w.set_account_by_name("account1")?;
}
// Get some mining done
let mut bh = 10u64;
let _ =
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
let (recipients_1, dec_key_1, sender_1) = match use_encryption {
let (recipients_1, sender_1) = match use_encryption {
true => {
let sec_key = api1.get_slatepack_secret_key(mask1, 0)?;
let sec_key = api1.get_slatepack_secret_key(mask1, SlatepackAddressIndex(0))?;
let pub_key = edDalekPublicKey::from(&sec_key);
let rec_address = SlatepackAddress::new(&pub_key);
(
vec![rec_address.clone()],
Some(sec_key),
Some(rec_address.clone()),
)
let sen_index = SlatepackAddressIndex(7);
let sec_key = api1.get_slatepack_secret_key(mask1, sen_index)?;
let pub_key = edDalekPublicKey::from(&sec_key);
let sen_address = SlatepackAddress::new(&pub_key);
(vec![rec_address.clone()], Some(sen_address.clone()))
}
false => (vec![], None, None),
false => (vec![], None),
};
let (recipients_2, dec_key_2, sender_2) = match use_encryption {
let (recipients_2, sender_2) = match use_encryption {
true => {
let sec_key = api2.get_slatepack_secret_key(mask2, 0)?;
let sec_key = api2.get_slatepack_secret_key(mask2, SlatepackAddressIndex(0))?;
let pub_key = edDalekPublicKey::from(&sec_key);
let rec_address = SlatepackAddress::new(&pub_key);
(
vec![rec_address.clone()],
Some(sec_key),
Some(rec_address.clone()),
)
let sen_index = SlatepackAddressIndex(11);
let sec_key = api2.get_slatepack_secret_key(mask2, sen_index)?;
let pub_key = edDalekPublicKey::from(&sec_key);
let sen_address = SlatepackAddress::new(&pub_key);
(vec![rec_address.clone()], Some(sen_address.clone()))
}
false => (vec![], None, None),
false => (vec![], None),
};
let (send_file, receive_file, final_file) = match use_bin {
@@ -190,7 +223,7 @@ fn slatepack_exchange_test_impl(
assert_eq!(wallet1_info.total, bh * reward);
// send to send
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
address_index: Some(SlatepackAddressIndex(7)),
amount: reward * 2,
minimum_confirmations: 2,
max_outputs: 500,
@@ -210,14 +243,8 @@ fn slatepack_exchange_test_impl(
)?;
api1.tx_lock_outputs(mask1, &slate)?;
// Get some mining done
{
wallet_inst!(wallet2, w);
w.set_account_by_name("account1")?;
}
let (mut slatepack, mut slate) =
slate_from_packed(&send_file, use_armored, dec_key_2.as_ref())?;
let (slatepack, mut slate) =
slate_from_packed(wallet2.clone(), mask2, &send_file, use_armored)?;
// wallet 2 receives file, completes, sends file back
wallet::controller::foreign_single_use(
@@ -231,8 +258,8 @@ fn slatepack_exchange_test_impl(
&receive_file,
use_armored,
use_bin,
// re-encrypt for sender!
sender_2.clone(),
// re-encrypt for sender!
match slatepack.sender.clone() {
Some(s) => vec![s.clone()],
None => vec![],
@@ -243,23 +270,34 @@ fn slatepack_exchange_test_impl(
)?;
// wallet 1 finalizes and posts
let (_, mut slate) = slate_from_packed(&receive_file, use_armored, dec_key_1.as_ref())?;
let (slatepack, mut slate) =
slate_from_packed(wallet1.clone(), mask1, &receive_file, use_armored)?;
slate = api1.finalize_tx(mask1, &slate)?;
// Output final file for reference
output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?;
output_slatepack(
&slate,
&final_file,
use_armored,
use_bin,
sender_1.clone(),
match slatepack.sender {
Some(s) => vec![s.clone()],
None => vec![],
},
)?;
api1.post_tx(mask1, &slate, false)?;
bh += 1;
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
bh += 3;
// Check total in mining account
// Check total in wallet 1 account
let (wallet1_refreshed, wallet1_info) = api1.retrieve_summary_info(mask1, true, 1)?;
assert!(wallet1_refreshed);
assert_eq!(wallet1_info.last_confirmed_height, bh);
assert_eq!(wallet1_info.total, bh * reward - reward * 2);
// Check total in 'wallet 2' account
// Check total in wallet 2 account
let (wallet2_refreshed, wallet2_info) = api2.retrieve_summary_info(mask2, true, 1)?;
assert!(wallet2_refreshed);
assert_eq!(wallet2_info.last_confirmed_height, bh);
@@ -281,10 +319,11 @@ fn slatepack_exchange_test_impl(
};
let args = IssueInvoiceTxArgs {
address_index: Some(SlatepackAddressIndex(11)),
amount: 1000000000,
..Default::default()
};
let mut slate = api2.issue_invoice_tx(mask2, args)?;
let slate = api2.issue_invoice_tx(mask2, args)?;
output_slatepack(
&slate,
&send_file,
@@ -303,9 +342,8 @@ fn slatepack_exchange_test_impl(
selection_strategy_is_use_all: true,
..Default::default()
};
let res = slate_from_packed(&send_file, use_armored, dec_key_1.as_ref())?;
slatepack = res.0;
slate = res.1;
let (slatepack, mut slate) =
slate_from_packed(wallet1.clone(), mask1, &send_file, use_armored)?;
slate = api1.process_invoice_tx(mask1, &slate, args)?;
api1.tx_lock_outputs(mask1, &slate)?;
output_slatepack(
@@ -314,25 +352,16 @@ fn slatepack_exchange_test_impl(
use_armored,
use_bin,
sender_1.clone(),
match slatepack.sender.clone() {
match slatepack.sender {
Some(s) => vec![s.clone()],
None => vec![],
},
)?;
wallet::controller::foreign_single_use(
wallet2.clone(),
PathBuf::from(test_dir),
mask2_i.clone(),
|api| {
// Wallet 2 receives the invoice transaction
let res = slate_from_packed(&receive_file, use_armored, dec_key_2.as_ref())?;
slate = res.1;
slate = api.finalize_tx(&slate, false)?;
output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?;
Ok(())
},
)?;
api1.post_tx(mask1, &slate, false)?;
// Wallet 2 receives the invoice transaction
let (_, mut slate) = slate_from_packed(wallet2.clone(), mask2, &receive_file, use_armored)?;
slate = api2.finalize_tx(mask2, &slate)?;
output_slatepack(&slate, &final_file, use_armored, use_bin, None, vec![])?;
api2.post_tx(mask2, &slate, false)?;
// Standard, with payment proof
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 3, false);
@@ -349,17 +378,17 @@ fn slatepack_exchange_test_impl(
),
};
let address = Some(api2.get_slatepack_address(mask2, 0)?);
// send to send
// send from 1st wallet
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
address_index: Some(SlatepackAddressIndex(7)),
amount: reward,
minimum_confirmations: 2,
max_outputs: 500,
num_change_outputs: 1,
selection_strategy_is_use_all: true,
payment_proof_recipient_address: address.clone(),
payment_proof_recipient_address: Some(
api2.get_slatepack_address(mask2, SlatepackAddressIndex(0))?,
),
..Default::default()
};
let mut slate = api1.init_send_tx(mask1, args)?;
@@ -373,21 +402,21 @@ fn slatepack_exchange_test_impl(
)?;
api1.tx_lock_outputs(mask1, &slate)?;
// receive at 2nd wallet
wallet::controller::foreign_single_use(
wallet2.clone(),
PathBuf::from(test_dir),
mask2_i.clone(),
|api| {
let res = slate_from_packed(&send_file, use_armored, dec_key_2.as_ref())?;
let slatepack = res.0;
slate = res.1;
let (slatepack, mut slate) =
slate_from_packed(wallet2.clone(), mask2, &send_file, use_armored)?;
slate = api.receive_tx(&slate, None, None)?;
output_slatepack(
&slate,
&receive_file,
use_armored,
use_bin,
sender_2,
sender_2.clone(),
match slatepack.sender {
Some(s) => vec![s.clone()],
None => vec![],
@@ -398,7 +427,7 @@ fn slatepack_exchange_test_impl(
)?;
// wallet 1 finalizes and posts
let res = slate_from_packed(&receive_file, use_armored, dec_key_1.as_ref())?;
let res = slate_from_packed(wallet1.clone(), mask1, &receive_file, use_armored)?;
slate = res.1;
slate = api1.finalize_tx(mask1, &slate)?;
// Output final file for reference
@@ -450,7 +479,6 @@ fn slatepack_api_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, bh as usize, false);
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward * 2,
minimum_confirmations: 2,
max_outputs: 500,
@@ -458,15 +486,141 @@ fn slatepack_api_impl(test_dir: &'static str) -> Result<(), libwallet::Error> {
selection_strategy_is_use_all: true,
..Default::default()
};
for index in [SlatepackAddressIndex::MAX + 1, u32::MAX] {
assert!(matches!(
api1.init_send_tx(
mask1,
InitTxArgs {
address_index: Some(SlatepackAddressIndex(index)),
..args.clone()
}
),
Err(Error::SlatepackAddress(_))
));
assert!(matches!(
api1.issue_invoice_tx(
mask1,
IssueInvoiceTxArgs {
amount: reward,
address_index: Some(SlatepackAddressIndex(index)),
..Default::default()
}
),
Err(Error::SlatepackAddress(_))
));
}
api1.init_send_tx(
mask1,
InitTxArgs {
estimate_only: Some(true),
..args.clone()
},
)?;
let slate = api1.init_send_tx(mask1, args)?;
// create an encrypted slatepack (just encrypted for self)
let enc_addr = api1.get_slatepack_address(mask1, 0)?;
let slatepack = api1.create_slatepack_message(mask1, &slate, Some(0), vec![enc_addr])?;
println!("{}", slatepack);
let slatepack_raw = api1.decode_slatepack_message(mask1, slatepack.clone(), vec![0])?;
println!("{}", slatepack_raw);
let decoded_slate = api1.slate_from_slatepack_message(mask1, slatepack, vec![0])?;
println!("{}", decoded_slate);
let address_index = SlatepackAddressIndex(0);
let enc_addr = api1.get_slatepack_address(mask1, address_index)?;
let slatepack =
api1.create_slatepack_message(mask1, &slate, Some(address_index), vec![enc_addr])?;
let mut encrypted = api1.decode_slatepack_message(mask1, slatepack.clone(), vec![])?;
assert_eq!(encrypted.mode, 1);
decrypt_slatepack(&mut encrypted, wallet1.clone(), mask1, vec![address_index])?;
assert_eq!(encrypted.get_slate()?.id, slate.id);
let slatepack_raw =
api1.decode_slatepack_message(mask1, slatepack.clone(), vec![address_index])?;
assert_eq!(slatepack_raw.mode, 0);
assert_eq!(slatepack_raw.get_slate()?.id, slate.id);
let decoded_slate = api1.slate_from_slatepack_message(mask1, slatepack, vec![address_index])?;
assert_eq!(decoded_slate.id, slate.id);
let unknown_index = SlatepackAddressIndex(77);
let unknown_address = api1.get_slatepack_address(mask1, unknown_index)?;
let message = api1.create_slatepack_message(mask1, &slate, None, vec![unknown_address])?;
let encrypted = api1.decode_slatepack_message(mask1, message.clone(), vec![])?;
let decoded = api1.decode_slatepack_message(mask1, message.clone(), vec![address_index])?;
assert_eq!(decoded.mode, 1);
assert_eq!(decoded.payload, encrypted.payload);
assert!(matches!(
api1.slate_from_slatepack_message(mask1, message.clone(), vec![address_index]),
Err(Error::SlatepackDecryption(_))
));
let mut api1 = api1;
assert!(matches!(
wallet::command::parse_slatepack(&mut api1, mask1, None, Some(message)),
Err(wallet::Error::LibWallet(Error::SlatepackDecryption(_)))
));
let index = {
wallet_inst!(wallet1, w);
w.get_private_context(mask1, slate.id.as_bytes())?
.payment_proof_derivation_index
.unwrap()
};
assert_eq!(index, SlatepackAddressIndex(1));
let address = api1.get_slatepack_address(mask1, index)?;
let message = api1.create_slatepack_message(mask1, &slate, None, vec![address])?;
let decoded = api1.decode_slatepack_message(mask1, message.clone(), vec![address_index])?;
assert_eq!(decoded.mode, 0);
assert_eq!(decoded.get_slate()?.id, slate.id);
api1.create_account_path(mask1, "other")?;
api1.set_active_account(mask1, "other")?;
test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), mask1, 6, false)?;
api1.set_active_account(mask1, "default")?;
let invoice = api1.issue_invoice_tx(
mask1,
IssueInvoiceTxArgs {
amount: reward,
dest_acct_name: Some("other".into()),
..Default::default()
},
)?;
let send = api1.init_send_tx(
mask1,
InitTxArgs {
amount: reward,
src_acct_name: Some("other".into()),
minimum_confirmations: 2,
..Default::default()
},
)?;
api1.set_active_account(mask1, "other")?;
let next = api1.issue_invoice_tx(
mask1,
IssueInvoiceTxArgs {
amount: reward,
..Default::default()
},
)?;
for (slate, expected) in [(&invoice, 1), (&send, 2), (&next, 3)] {
wallet_inst!(wallet1, w);
let context = w.get_private_context(mask1, slate.id.as_bytes())?;
assert_eq!(context.parent_key_id, w.parent_key_id());
assert_eq!(
context.payment_proof_derivation_index,
Some(SlatepackAddressIndex(expected))
);
}
api1.set_active_account(mask1, "default")?;
let invoice = api1.issue_invoice_tx(
mask1,
IssueInvoiceTxArgs {
amount: reward,
..Default::default()
},
)?;
{
wallet_inst!(wallet1, w);
assert_eq!(
w.get_private_context(mask1, invoice.id.as_bytes())?
.payment_proof_derivation_index,
Some(SlatepackAddressIndex(2))
);
}
api1.close_wallet(None)?;
let encrypted = api1.decode_slatepack_message(None, message, vec![])?;
assert_eq!(encrypted.mode, 1);
// let logging finish
stopper.store(false, Ordering::Relaxed);
@@ -526,7 +680,6 @@ fn slatepack_address_validation(test_dir: &'static str) -> Result<(), libwallet:
)?;
let args = InitTxArgs {
src_acct_name: Some("mining".to_owned()),
amount: reward,
minimum_confirmations: 2,
max_outputs: 500,
@@ -566,6 +719,15 @@ fn slatepack_address_validation(test_dir: &'static str) -> Result<(), libwallet:
fluff: false,
skip_tor: Some(true),
});
let missing_config = PathBuf::from(test_dir).join("missing.toml");
let api = grin_wallet_api::Owner::new(wallet1.clone(), None, missing_config);
assert!(api
.process_invoice_tx(mask1, &slate, valid_args.clone())
.is_err());
{
wallet_inst!(wallet1, w);
assert!(w.get_private_context(mask1, slate.id.as_bytes()).is_err());
}
api1.process_invoice_tx(mask1, &slate, valid_args.clone())?;
api1.init_send_tx(mask1, valid_args)?;
@@ -660,3 +822,46 @@ fn slatepack_address() {
}
clean_output_dir(test_dir);
}
#[test]
fn slatepack_file_contents() {
let test_dir = "test_output/slatepack_file_contents";
setup(test_dir);
fs::create_dir_all(test_dir).unwrap();
let sp_path = PathBuf::from(test_dir).join("pack_file");
// set Slatepack file to minimum allowable size
{
let f = File::create(sp_path.clone()).unwrap();
f.set_len(slatepack::min_size()).unwrap();
}
let mut pack_path = get_slatepack_file_contents(&sp_path);
assert!(pack_path.is_ok());
// set Slatepack file to maximum allowable size
{
let f = File::create(sp_path.clone()).unwrap();
f.set_len(slatepack::max_size()).unwrap();
}
pack_path = get_slatepack_file_contents(&sp_path);
assert!(pack_path.is_ok());
// set Slatepack file below minimum allowable size
{
let f = File::create(sp_path.clone()).unwrap();
f.set_len(slatepack::min_size() - 1).unwrap();
}
pack_path = get_slatepack_file_contents(&sp_path);
assert!(pack_path.is_err());
// set Slatepack file above maximum allowable size
{
let f = File::create(sp_path.clone()).unwrap();
f.set_len(slatepack::max_size() + 1).unwrap();
}
pack_path = get_slatepack_file_contents(&sp_path);
assert!(pack_path.is_err());
clean_output_dir(test_dir);
}