randomized reorg check timer & swap_tx_not_found test

This commit is contained in:
scilio 2024-04-08 16:50:46 -04:00
parent d1ae6863f8
commit df2cb31258
5 changed files with 355 additions and 326 deletions

View file

@ -47,6 +47,7 @@ In case of errors, the API will return a `SwapError` type with one of the follow
- `FeeTooLow`: The provided fee is too low. - `FeeTooLow`: The provided fee is too low.
- `StoreError`: An error occurred when saving swap to the data store. - `StoreError`: An error occurred when saving swap to the data store.
- `ClientError`: An error occurred during client communication. - `ClientError`: An error occurred during client communication.
- `SwapTxNotFound`: The previous swap transaction was not found in data store.
- `UnknownError`: An unknown error occurred. - `UnknownError`: An unknown error occurred.
### Example ### Example

View file

@ -14,12 +14,12 @@ use grin_util::{StopState, ZeroingString};
use mwixnet::client::{MixClient, MixClientImpl}; use mwixnet::client::{MixClient, MixClientImpl};
use mwixnet::node::GrinNode; use mwixnet::node::GrinNode;
use mwixnet::store::StoreError; use mwixnet::store::StoreError;
use rand::{thread_rng, Rng};
use rpassword; use rpassword;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread::{sleep, spawn}; use std::thread::{sleep, spawn};
use std::time::Duration; use std::time::Duration;
use grin_core::core::Transaction;
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
@ -262,7 +262,10 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
let close_handle = http_server.close_handle(); let close_handle = http_server.close_handle();
let round_handle = spawn(move || { let round_handle = spawn(move || {
let mut secs = 0; let mut rng = thread_rng();
let mut secs = 0u32;
let mut reorg_secs = 0u32;
let mut reorg_window = rng.gen_range(900u32, 3600u32);
let prev_tx = Arc::new(Mutex::new(None)); let prev_tx = Arc::new(Mutex::new(None));
let server = swap_server.clone(); let server = swap_server.clone();
@ -274,6 +277,7 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
sleep(Duration::from_secs(1)); sleep(Duration::from_secs(1));
secs = (secs + 1) % server_config.interval_s; secs = (secs + 1) % server_config.interval_s;
reorg_secs = (reorg_secs + 1) % reorg_window;
if secs == 0 { if secs == 0 {
let prev_tx_clone = prev_tx.clone(); let prev_tx_clone = prev_tx.clone();
@ -286,7 +290,9 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
_ => None, _ => None,
}; };
}); });
} else if secs % 30 == 0 { reorg_secs = 0;
reorg_window = rng.gen_range(900u32, 3600u32);
} else if reorg_secs == 0 {
let prev_tx_clone = prev_tx.clone(); let prev_tx_clone = prev_tx.clone();
let server_clone = server.clone(); let server_clone = server.clone();
rt.spawn(async move { rt.spawn(async move {
@ -304,6 +310,7 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
}; };
} }
}); });
reorg_window = rng.gen_range(900u32, 3600u32);
} }
} }
}); });

View file

@ -45,6 +45,8 @@ pub enum SwapError {
NodeError(String), NodeError(String),
#[error("Client communication error: {0:?}")] #[error("Client communication error: {0:?}")]
ClientError(String), ClientError(String),
#[error("Swap transaction not found: {0:?}")]
SwapTxNotFound(Commitment),
#[error("{0}")] #[error("{0}")]
UnknownError(String), UnknownError(String),
} }
@ -327,7 +329,8 @@ impl SwapServer for SwapServerImpl {
async fn check_reorg(&self, tx: Arc<Transaction>) -> Result<Option<Arc<Transaction>>, SwapError> { async fn check_reorg(&self, tx: Arc<Transaction>) -> Result<Option<Arc<Transaction>>, SwapError> {
let excess = tx.kernels().first().unwrap().excess; let excess = tx.kernels().first().unwrap().excess;
if let Ok(swap_tx) = self.store.lock().await.get_swap_tx(&excess) { let locked_store = self.store.lock().await;
if let Ok(swap_tx) = locked_store.get_swap_tx(&excess) {
// If kernel is in active chain, return tx // If kernel is in active chain, return tx
if self.node.async_get_kernel(&excess, Some(swap_tx.chain_tip.0), None).await?.is_some() { if self.node.async_get_kernel(&excess, Some(swap_tx.chain_tip.0), None).await?.is_some() {
return Ok(Some(tx)); return Ok(Some(tx));
@ -341,7 +344,6 @@ impl SwapServer for SwapServerImpl {
// Collect all swaps based on tx's inputs, and execute_round with those swaps // Collect all swaps based on tx's inputs, and execute_round with those swaps
let next_block_height = self.node.async_get_chain_tip().await?.0 + 1; let next_block_height = self.node.async_get_chain_tip().await?.0 + 1;
let locked_store = self.store.lock().await;
let mut swaps = Vec::new(); let mut swaps = Vec::new();
for input_commit in &tx.inputs_committed() { for input_commit in &tx.inputs_committed() {
if let Ok(swap) = locked_store.get_swap(&input_commit) { if let Ok(swap) = locked_store.get_swap(&input_commit) {
@ -353,7 +355,7 @@ impl SwapServer for SwapServerImpl {
self.async_execute_round(&locked_store, swaps).await self.async_execute_round(&locked_store, swaps).await
} else { } else {
Err(SwapError::UnknownError("Swap transaction not found".to_string())) // TODO: Create SwapError enum value Err(SwapError::SwapTxNotFound(excess))
} }
} }
} }
@ -453,7 +455,7 @@ mod tests {
use crate::tx::TxComponents; use crate::tx::TxComponents;
use ::function_name::named; use ::function_name::named;
use grin_core::core::{Committed, Input, Output, OutputFeatures, Transaction, Weighting}; use grin_core::core::{Committed, Input, Inputs, Output, OutputFeatures, Transaction, Weighting};
use grin_onion::crypto::comsig::ComSignature; use grin_onion::crypto::comsig::ComSignature;
use grin_onion::crypto::secp; use grin_onion::crypto::secp;
use grin_onion::onion::Onion; use grin_onion::onion::Onion;
@ -833,6 +835,26 @@ mod tests {
Ok(()) Ok(())
} }
/// Returns SwapTxNotFound when trying to check_reorg with a transaction not found in the store.
#[tokio::test]
#[named]
async fn swap_tx_not_found() -> Result<(), Box<dyn std::error::Error>> {
let test_dir = init_test!();
let server_key = secp::random_secret();
let node: Arc<MockGrinNode> = Arc::new(MockGrinNode::new());
let (server, _) = super::test_util::new_swapper(&test_dir, &server_key, None, node.clone());
let kern = tx::build_kernel(&secp::random_secret(), 1000u64)?;
let tx: Arc<Transaction> = Arc::new(Transaction::new(Inputs::default(), &[], &[kern.clone()]));
let result = server.check_reorg(tx).await;
assert_eq!(
Err(SwapError::SwapTxNotFound(kern.excess())),
result
);
Ok(())
}
/// Returns PeelOnionFailure when a failure occurs trying to decrypt the onion payload. /// Returns PeelOnionFailure when a failure occurs trying to decrypt the onion payload.
#[tokio::test] #[tokio::test]
#[named] #[named]

View file

@ -236,7 +236,7 @@ impl SwapStore {
/// Reads a single value by key /// Reads a single value by key
fn read<K: AsRef<[u8]> + Copy, V: Readable>(&self, prefix: u8, k: K) -> Result<V, StoreError> { fn read<K: AsRef<[u8]> + Copy, V: Readable>(&self, prefix: u8, k: K) -> Result<V, StoreError> {
store::option_to_not_found(self.db.get_ser(&store::to_key(prefix, k)[..], None), || { store::option_to_not_found(self.db.get_ser(&store::to_key(prefix, k), None), || {
format!("{}:{}", prefix, k.to_hex()) format!("{}:{}", prefix, k.to_hex())
}) })
.map_err(StoreError::ReadError) .map_err(StoreError::ReadError)
@ -279,7 +279,6 @@ impl SwapStore {
} }
/// Reads a swap from the database /// Reads a swap from the database
#[allow(dead_code)]
pub fn get_swap(&self, input_commit: &Commitment) -> Result<SwapData, StoreError> { pub fn get_swap(&self, input_commit: &Commitment) -> Result<SwapData, StoreError> {
self.read(SWAP_PREFIX, input_commit) self.read(SWAP_PREFIX, input_commit)
} }

View file

@ -8,7 +8,7 @@ use grin_keychain::{BlindingFactor, ExtKeychain, Identifier, Keychain, SwitchCom
use grin_onion::crypto::comsig::ComSignature; use grin_onion::crypto::comsig::ComSignature;
use grin_onion::onion::Onion; use grin_onion::onion::Onion;
use grin_onion::Hop; use grin_onion::Hop;
use grin_util::{Mutex, ToHex, ZeroingString}; use grin_util::{Mutex, ZeroingString};
use grin_wallet_api::Owner; use grin_wallet_api::Owner;
use grin_wallet_config::WalletConfig; use grin_wallet_config::WalletConfig;
use grin_wallet_controller::controller; use grin_wallet_controller::controller;
@ -28,243 +28,243 @@ use x25519_dalek::PublicKey as xPublicKey;
/// Response to build a coinbase output. /// Response to build a coinbase output.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CbData { pub struct CbData {
/// Output /// Output
pub output: Output, pub output: Output,
/// Kernel /// Kernel
pub kernel: TxKernel, pub kernel: TxKernel,
/// Key Id /// Key Id
pub key_id: Option<Identifier>, pub key_id: Option<Identifier>,
} }
pub struct IntegrationGrinWallet { pub struct IntegrationGrinWallet {
wallet: Arc< wallet: Arc<
Mutex< Mutex<
Box< Box<
dyn WalletInst< dyn WalletInst<
'static, 'static,
DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>,
HTTPNodeClient, HTTPNodeClient,
ExtKeychain, ExtKeychain,
>, >,
>, >,
>, >,
>, >,
api_listen_port: u16, api_listen_port: u16,
owner_api: Arc< owner_api: Arc<
Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>, Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
>, >,
http_client: Arc<HttpWallet>, http_client: Arc<HttpWallet>,
} }
impl IntegrationGrinWallet { impl IntegrationGrinWallet {
pub async fn async_new_wallet( pub async fn async_new_wallet(
wallet_dir: String, wallet_dir: String,
api_listen_port: u16, api_listen_port: u16,
node_api: String, node_api: String,
) -> IntegrationGrinWallet { ) -> IntegrationGrinWallet {
let node_client = HTTPNodeClient::new(&node_api, None).unwrap(); let node_client = HTTPNodeClient::new(&node_api, None).unwrap();
let mut wallet = Box::new( let mut wallet = Box::new(
DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(), DefaultWalletImpl::<'static, HTTPNodeClient>::new(node_client.clone()).unwrap(),
) )
as Box< as Box<
dyn WalletInst< dyn WalletInst<
'static, 'static,
DefaultLCProvider<HTTPNodeClient, ExtKeychain>, DefaultLCProvider<HTTPNodeClient, ExtKeychain>,
HTTPNodeClient, HTTPNodeClient,
ExtKeychain, ExtKeychain,
>, >,
>; >;
// Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc... // Wallet LifeCycle Provider provides all functions init wallet and work with seeds, etc...
let lc = wallet.lc_provider().unwrap(); let lc = wallet.lc_provider().unwrap();
let mut wallet_config = WalletConfig::default(); let mut wallet_config = WalletConfig::default();
wallet_config.check_node_api_http_addr = node_api.clone(); wallet_config.check_node_api_http_addr = node_api.clone();
wallet_config.owner_api_listen_port = Some(api_listen_port); wallet_config.owner_api_listen_port = Some(api_listen_port);
wallet_config.api_secret_path = None; wallet_config.api_secret_path = None;
wallet_config.data_file_dir = wallet_dir.clone(); wallet_config.data_file_dir = wallet_dir.clone();
// The top level wallet directory should be set manually (in the reference implementation, // The top level wallet directory should be set manually (in the reference implementation,
// this is provided in the WalletConfig) // this is provided in the WalletConfig)
let _ = lc.set_top_level_directory(&wallet_config.data_file_dir); let _ = lc.set_top_level_directory(&wallet_config.data_file_dir);
lc.create_config( lc.create_config(
&ChainTypes::AutomatedTesting, &ChainTypes::AutomatedTesting,
"grin-wallet.toml", "grin-wallet.toml",
Some(wallet_config.clone()), Some(wallet_config.clone()),
None, None,
None, None,
) )
.unwrap(); .unwrap();
lc.create_wallet(None, None, 12, ZeroingString::from("pass"), false) lc.create_wallet(None, None, 12, ZeroingString::from("pass"), false)
.unwrap(); .unwrap();
// Start owner API // Start owner API
let km = Arc::new(Mutex::new(None)); let km = Arc::new(Mutex::new(None));
let wallet = Arc::new(Mutex::new(wallet)); let wallet = Arc::new(Mutex::new(wallet));
let owner_api = Arc::new(Owner::new(wallet.clone(), None)); let owner_api = Arc::new(Owner::new(wallet.clone(), None));
let address_str = format!("127.0.0.1:{}", api_listen_port); let address_str = format!("127.0.0.1:{}", api_listen_port);
let owner_addr: SocketAddr = address_str.parse().unwrap(); let owner_addr: SocketAddr = address_str.parse().unwrap();
let thr_wallet = wallet.clone(); let thr_wallet = wallet.clone();
let _thread_handle = thread::spawn(move || { let _thread_handle = thread::spawn(move || {
controller::owner_listener( controller::owner_listener(
thr_wallet, thr_wallet,
km, km,
address_str.as_str(), address_str.as_str(),
None, None,
None, None,
Some(true), Some(true),
None, None,
false, false,
) )
.unwrap() .unwrap()
}); });
let http_client = Arc::new( let http_client = Arc::new(
HttpWallet::async_open_wallet(&owner_addr, &None, &ZeroingString::from("pass")) HttpWallet::async_open_wallet(&owner_addr, &None, &ZeroingString::from("pass"))
.await .await
.unwrap(), .unwrap(),
); );
IntegrationGrinWallet { IntegrationGrinWallet {
wallet, wallet,
api_listen_port, api_listen_port,
owner_api, owner_api,
http_client, http_client,
} }
} }
pub async fn async_retrieve_summary_info(&self) -> Result<WalletInfo, mwixnet::WalletError> { pub async fn async_retrieve_summary_info(&self) -> Result<WalletInfo, mwixnet::WalletError> {
let params = json!({ let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"refresh_from_node": true, "refresh_from_node": true,
"minimum_confirmations": 1 "minimum_confirmations": 1
}); });
let (_, wallet_info): (bool, WalletInfo) = self let (_, wallet_info): (bool, WalletInfo) = self
.http_client .http_client
.clone() .clone()
.async_perform_request("retrieve_summary_info", &params) .async_perform_request("retrieve_summary_info", &params)
.await?; .await?;
Ok(wallet_info) Ok(wallet_info)
} }
pub async fn async_send( pub async fn async_send(
&self, &self,
receiving_wallet: &IntegrationGrinWallet, receiving_wallet: &IntegrationGrinWallet,
amount: u64, amount: u64,
) -> Result<Transaction, mwixnet::WalletError> { ) -> Result<Transaction, mwixnet::WalletError> {
let slate = self.async_init_send_tx(amount).await.unwrap(); let slate = self.async_init_send_tx(amount).await.unwrap();
let slate = receiving_wallet.async_receive_tx(&slate).await.unwrap(); let slate = receiving_wallet.async_receive_tx(&slate).await.unwrap();
let slate = self.async_finalize_tx(&slate).await.unwrap(); let slate = self.async_finalize_tx(&slate).await.unwrap();
let tx = Slate::from(slate).tx_or_err().unwrap().clone(); let tx = Slate::from(slate).tx_or_err().unwrap().clone();
Ok(tx) Ok(tx)
} }
async fn async_init_send_tx( async fn async_init_send_tx(
&self, &self,
amount: u64, amount: u64,
) -> Result<VersionedSlate, mwixnet::WalletError> { ) -> Result<VersionedSlate, mwixnet::WalletError> {
let args = InitTxArgs { let args = InitTxArgs {
src_acct_name: None, src_acct_name: None,
amount, amount,
minimum_confirmations: 0, minimum_confirmations: 0,
max_outputs: 10, max_outputs: 10,
num_change_outputs: 1, num_change_outputs: 1,
selection_strategy_is_use_all: false, selection_strategy_is_use_all: false,
..Default::default() ..Default::default()
}; };
let params = json!({ let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"args": args "args": args
}); });
let slate: VersionedSlate = self let slate: VersionedSlate = self
.http_client .http_client
.clone() .clone()
.async_perform_request("init_send_tx", &params) .async_perform_request("init_send_tx", &params)
.await?; .await?;
let params = json!({ let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"slate": &slate "slate": &slate
}); });
self.http_client self.http_client
.clone() .clone()
.async_perform_request("tx_lock_outputs", &params) .async_perform_request("tx_lock_outputs", &params)
.await?; .await?;
Ok(slate) Ok(slate)
} }
pub async fn async_receive_tx( pub async fn async_receive_tx(
&self, &self,
slate: &VersionedSlate, slate: &VersionedSlate,
) -> Result<VersionedSlate, grin_servers::common::types::Error> { ) -> Result<VersionedSlate, grin_servers::common::types::Error> {
let req_body = json!({ let req_body = json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "receive_tx", "method": "receive_tx",
"id": 1, "id": 1,
"params": [slate, null, null] "params": [slate, null, null]
}); });
let res: Response = client::post_async(self.foreign_api().as_str(), &req_body, None) let res: Response = client::post_async(self.foreign_api().as_str(), &req_body, None)
.await .await
.map_err(|e| { .map_err(|e| {
let report = format!("Failed to receive tx. Is the wallet listening? {}", e); let report = format!("Failed to receive tx. Is the wallet listening? {}", e);
error!("{}", report); error!("{}", report);
grin_servers::common::types::Error::WalletComm(report) grin_servers::common::types::Error::WalletComm(report)
})?; })?;
let parsed: VersionedSlate = res.clone().into_result().map_err(|e| { let parsed: VersionedSlate = res.clone().into_result().map_err(|e| {
let report = format!("Error parsing result: {}", e); let report = format!("Error parsing result: {}", e);
error!("{}", report); error!("{}", report);
grin_servers::common::types::Error::WalletComm(report) grin_servers::common::types::Error::WalletComm(report)
})?; })?;
Ok(parsed) Ok(parsed)
} }
async fn async_finalize_tx( async fn async_finalize_tx(
&self, &self,
slate: &VersionedSlate, slate: &VersionedSlate,
) -> Result<VersionedSlate, mwixnet::WalletError> { ) -> Result<VersionedSlate, mwixnet::WalletError> {
let params = json!({ let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"slate": slate "slate": slate
}); });
self.http_client self.http_client
.clone() .clone()
.async_perform_request("finalize_tx", &params) .async_perform_request("finalize_tx", &params)
.await .await
} }
async fn async_post_tx( async fn async_post_tx(
&self, &self,
finalized_slate: &VersionedSlate, finalized_slate: &VersionedSlate,
fluff: bool, fluff: bool,
) -> Result<VersionedSlate, mwixnet::WalletError> { ) -> Result<VersionedSlate, mwixnet::WalletError> {
let params = json!({ let params = json!({
"token": self.http_client.clone().get_token(), "token": self.http_client.clone().get_token(),
"slate": finalized_slate, "slate": finalized_slate,
"fluff": fluff "fluff": fluff
}); });
self.http_client self.http_client
.clone() .clone()
.async_perform_request("post_tx", &params) .async_perform_request("post_tx", &params)
.await .await
} }
/// Call the wallet API to create a coinbase output for the given block_fees. /// Call the wallet API to create a coinbase output for the given block_fees.
/// Will retry based on default "retry forever with backoff" behavior. /// Will retry based on default "retry forever with backoff" behavior.
pub async fn async_create_coinbase( pub async fn async_create_coinbase(
&self, &self,
block_fees: &BlockFees, block_fees: &BlockFees,
) -> Result<CbData, grin_servers::common::types::Error> { ) -> Result<CbData, grin_servers::common::types::Error> {
let req_body = json!({ let req_body = json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "build_coinbase", "method": "build_coinbase",
"id": 1, "id": 1,
@ -273,159 +273,159 @@ impl IntegrationGrinWallet {
} }
}); });
let res: Response = client::post_async(self.foreign_api().as_str(), &req_body, None) let res: Response = client::post_async(self.foreign_api().as_str(), &req_body, None)
.await .await
.map_err(|e| { .map_err(|e| {
let report = format!("Failed to get coinbase. Is the wallet listening? {}", e); let report = format!("Failed to get coinbase. Is the wallet listening? {}", e);
error!("{}", report); error!("{}", report);
grin_servers::common::types::Error::WalletComm(report) grin_servers::common::types::Error::WalletComm(report)
})?; })?;
let parsed: CbData = res.clone().into_result().map_err(|e| { let parsed: CbData = res.clone().into_result().map_err(|e| {
let report = format!("Error parsing result: {}", e); let report = format!("Error parsing result: {}", e);
error!("{}", report); error!("{}", report);
grin_servers::common::types::Error::WalletComm(report) grin_servers::common::types::Error::WalletComm(report)
})?; })?;
Ok(parsed) Ok(parsed)
} }
pub fn build_onion( pub fn build_onion(
&self, &self,
commitment: &Commitment, commitment: &Commitment,
server_pubkeys: &Vec<xPublicKey>, server_pubkeys: &Vec<xPublicKey>,
) -> Result<(Onion, ComSignature), grin_wallet_libwallet::Error> { ) -> Result<(Onion, ComSignature), grin_wallet_libwallet::Error> {
let keychain = self let keychain = self
.wallet .wallet
.lock() .lock()
.lc_provider()? .lc_provider()?
.wallet_inst()? .wallet_inst()?
.keychain(self.keychain_mask().as_ref())?; .keychain(self.keychain_mask().as_ref())?;
let (_, outputs) = let (_, outputs) =
self.owner_api self.owner_api
.retrieve_outputs(self.keychain_mask().as_ref(), false, false, None)?; .retrieve_outputs(self.keychain_mask().as_ref(), false, false, None)?;
let mut output = None; let mut output = None;
for o in &outputs { for o in &outputs {
if o.commit == *commitment { if o.commit == *commitment {
output = Some(o.output.clone()); output = Some(o.output.clone());
break; break;
} }
} }
if output.is_none() { if output.is_none() {
return Err(grin_wallet_libwallet::Error::GenericError(String::from( return Err(grin_wallet_libwallet::Error::GenericError(String::from(
"output not found", "output not found",
))); )));
} }
let amount = output.clone().unwrap().value; let amount = output.clone().unwrap().value;
let input_blind = keychain.derive_key( let input_blind = keychain.derive_key(
amount, amount,
&output.clone().unwrap().key_id, &output.clone().unwrap().key_id,
SwitchCommitmentType::Regular, SwitchCommitmentType::Regular,
)?; )?;
let fee = tx_fee(1, 1, 1); let fee = tx_fee(1, 1, 1);
let new_amount = amount - (fee * server_pubkeys.len() as u64); let new_amount = amount - (fee * server_pubkeys.len() as u64);
let new_output = self.owner_api.build_output( let new_output = self.owner_api.build_output(
self.keychain_mask().as_ref(), self.keychain_mask().as_ref(),
OutputFeatures::Plain, OutputFeatures::Plain,
new_amount, new_amount,
)?; )?;
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let mut blind_sum = new_output let mut blind_sum = new_output
.blind .blind
.split(&BlindingFactor::from_secret_key(input_blind.clone()), &secp)?; .split(&BlindingFactor::from_secret_key(input_blind.clone()), &secp)?;
let hops = server_pubkeys let hops = server_pubkeys
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, &p)| { .map(|(i, &p)| {
if (i + 1) == server_pubkeys.len() { if (i + 1) == server_pubkeys.len() {
Hop { Hop {
server_pubkey: p.clone(), server_pubkey: p.clone(),
excess: blind_sum.secret_key(&secp).unwrap(), excess: blind_sum.secret_key(&secp).unwrap(),
fee: FeeFields::from(fee as u32), fee: FeeFields::from(fee as u32),
rangeproof: Some(new_output.output.proof.clone()), rangeproof: Some(new_output.output.proof.clone()),
} }
} else { } else {
let hop_excess = BlindingFactor::rand(&secp); let hop_excess = BlindingFactor::rand(&secp);
blind_sum = blind_sum.split(&hop_excess, &secp).unwrap(); blind_sum = blind_sum.split(&hop_excess, &secp).unwrap();
Hop { Hop {
server_pubkey: p.clone(), server_pubkey: p.clone(),
excess: hop_excess.secret_key(&secp).unwrap(), excess: hop_excess.secret_key(&secp).unwrap(),
fee: FeeFields::from(fee as u32), fee: FeeFields::from(fee as u32),
rangeproof: None, rangeproof: None,
} }
} }
}) })
.collect(); .collect();
let onion = grin_onion::create_onion(&commitment, &hops).unwrap(); let onion = grin_onion::create_onion(&commitment, &hops).unwrap();
let comsig = ComSignature::sign(amount, &input_blind, &onion.serialize().unwrap()).unwrap(); let comsig = ComSignature::sign(amount, &input_blind, &onion.serialize().unwrap()).unwrap();
Ok((onion, comsig)) Ok((onion, comsig))
} }
pub fn owner_api( pub fn owner_api(
&self, &self,
) -> Arc< ) -> Arc<
Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>, Owner<DefaultLCProvider<'static, HTTPNodeClient, ExtKeychain>, HTTPNodeClient, ExtKeychain>,
> { > {
self.owner_api.clone() self.owner_api.clone()
} }
pub fn foreign_api(&self) -> String { pub fn foreign_api(&self) -> String {
format!("http://127.0.0.1:{}/v2/foreign", self.api_listen_port) format!("http://127.0.0.1:{}/v2/foreign", self.api_listen_port)
} }
pub fn owner_address(&self) -> SocketAddr { pub fn owner_address(&self) -> SocketAddr {
SocketAddr::new( SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
self.api_listen_port, self.api_listen_port,
) )
} }
pub fn keychain_mask(&self) -> Option<SecretKey> { pub fn keychain_mask(&self) -> Option<SecretKey> {
self.http_client.as_ref().get_token().keychain_mask.clone() self.http_client.as_ref().get_token().keychain_mask.clone()
} }
pub fn get_client(&self) -> Arc<HttpWallet> { pub fn get_client(&self) -> Arc<HttpWallet> {
self.http_client.clone() self.http_client.clone()
} }
} }
#[allow(dead_code)] #[allow(dead_code)]
pub struct GrinWalletManager { pub struct GrinWalletManager {
// base directory for the server instance // base directory for the server instance
working_dir: String, working_dir: String,
wallets: Vec<Arc<Mutex<IntegrationGrinWallet>>>, wallets: Vec<Arc<Mutex<IntegrationGrinWallet>>>,
} }
impl GrinWalletManager { impl GrinWalletManager {
pub fn new(test_dir: &str) -> GrinWalletManager { pub fn new(test_dir: &str) -> GrinWalletManager {
GrinWalletManager { GrinWalletManager {
working_dir: String::from(test_dir), working_dir: String::from(test_dir),
wallets: vec![], wallets: vec![],
} }
} }
pub async fn async_new_wallet( pub async fn async_new_wallet(
&mut self, &mut self,
node_api_addr: &SocketAddr, node_api_addr: &SocketAddr,
) -> Arc<Mutex<IntegrationGrinWallet>> { ) -> Arc<Mutex<IntegrationGrinWallet>> {
let wallet_dir = format!("{}/wallets/{}", self.working_dir, self.wallets.len()); let wallet_dir = format!("{}/wallets/{}", self.working_dir, self.wallets.len());
let wallet = Arc::new(Mutex::new( let wallet = Arc::new(Mutex::new(
IntegrationGrinWallet::async_new_wallet( IntegrationGrinWallet::async_new_wallet(
wallet_dir, wallet_dir,
21000 + self.wallets.len() as u16, 21000 + self.wallets.len() as u16,
format!("http://{}", node_api_addr), format!("http://{}", node_api_addr),
) )
.await, .await,
)); ));
self.wallets.push(wallet.clone()); self.wallets.push(wallet.clone());
wallet wallet
} }
} }