mirror of
https://github.com/mimblewimble/mwixnet.git
synced 2025-01-20 19:11:09 +03:00
randomized reorg check timer & swap_tx_not_found test
This commit is contained in:
parent
d1ae6863f8
commit
df2cb31258
5 changed files with 355 additions and 326 deletions
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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", ¶ms)
|
.async_perform_request("retrieve_summary_info", ¶ms)
|
||||||
.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", ¶ms)
|
.async_perform_request("init_send_tx", ¶ms)
|
||||||
.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", ¶ms)
|
.async_perform_request("tx_lock_outputs", ¶ms)
|
||||||
.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", ¶ms)
|
.async_perform_request("finalize_tx", ¶ms)
|
||||||
.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", ¶ms)
|
.async_perform_request("post_tx", ¶ms)
|
||||||
.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue