mirror of
https://github.com/mimblewimble/mwixnet.git
synced 2025-01-20 19:11:09 +03:00
basic reorg protection
(cherry picked from commit e3696ed73c2012f20205b98099f397ad799fcff4)
This commit is contained in:
parent
389581d759
commit
d1ae6863f8
4 changed files with 1422 additions and 1272 deletions
|
@ -16,9 +16,10 @@ use mwixnet::node::GrinNode;
|
|||
use mwixnet::store::StoreError;
|
||||
use rpassword;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::{sleep, spawn};
|
||||
use std::time::Duration;
|
||||
use grin_core::core::Transaction;
|
||||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
@ -26,311 +27,339 @@ extern crate clap;
|
|||
const DEFAULT_INTERVAL: u32 = 12 * 60 * 60;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
real_main()?;
|
||||
std::process::exit(0);
|
||||
real_main()?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
fn real_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let yml = load_yaml!("mwixnet.yml");
|
||||
let args = App::from_yaml(yml).get_matches();
|
||||
let chain_type = if args.is_present("testnet") {
|
||||
ChainTypes::Testnet
|
||||
} else {
|
||||
ChainTypes::Mainnet
|
||||
};
|
||||
global::set_local_chain_type(chain_type);
|
||||
let yml = load_yaml!("mwixnet.yml");
|
||||
let args = App::from_yaml(yml).get_matches();
|
||||
let chain_type = if args.is_present("testnet") {
|
||||
ChainTypes::Testnet
|
||||
} else {
|
||||
ChainTypes::Mainnet
|
||||
};
|
||||
global::set_local_chain_type(chain_type);
|
||||
|
||||
let config_path = match args.value_of("config_file") {
|
||||
Some(path) => PathBuf::from(path),
|
||||
None => {
|
||||
let mut grin_path = config::get_grin_path(&chain_type);
|
||||
grin_path.push("mwixnet-config.toml");
|
||||
grin_path
|
||||
}
|
||||
};
|
||||
let config_path = match args.value_of("config_file") {
|
||||
Some(path) => PathBuf::from(path),
|
||||
None => {
|
||||
let mut grin_path = config::get_grin_path(&chain_type);
|
||||
grin_path.push("mwixnet-config.toml");
|
||||
grin_path
|
||||
}
|
||||
};
|
||||
|
||||
let round_time = args
|
||||
.value_of("round_time")
|
||||
.map(|t| t.parse::<u32>().unwrap());
|
||||
let bind_addr = args.value_of("bind_addr");
|
||||
let socks_addr = args.value_of("socks_addr");
|
||||
let grin_node_url = args.value_of("grin_node_url");
|
||||
let grin_node_secret_path = args.value_of("grin_node_secret_path");
|
||||
let wallet_owner_url = args.value_of("wallet_owner_url");
|
||||
let wallet_owner_secret_path = args.value_of("wallet_owner_secret_path");
|
||||
let prev_server = args
|
||||
.value_of("prev_server")
|
||||
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
|
||||
let next_server = args
|
||||
.value_of("next_server")
|
||||
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
|
||||
let round_time = args
|
||||
.value_of("round_time")
|
||||
.map(|t| t.parse::<u32>().unwrap());
|
||||
let bind_addr = args.value_of("bind_addr");
|
||||
let socks_addr = args.value_of("socks_addr");
|
||||
let grin_node_url = args.value_of("grin_node_url");
|
||||
let grin_node_secret_path = args.value_of("grin_node_secret_path");
|
||||
let wallet_owner_url = args.value_of("wallet_owner_url");
|
||||
let wallet_owner_secret_path = args.value_of("wallet_owner_secret_path");
|
||||
let prev_server = args
|
||||
.value_of("prev_server")
|
||||
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
|
||||
let next_server = args
|
||||
.value_of("next_server")
|
||||
.map(|p| DalekPublicKey::from_hex(&p).unwrap());
|
||||
|
||||
// Write a new config file if init-config command is supplied
|
||||
if let ("init-config", Some(_)) = args.subcommand() {
|
||||
if config_path.exists() {
|
||||
panic!(
|
||||
"Config file already exists at {}",
|
||||
config_path.to_string_lossy()
|
||||
);
|
||||
}
|
||||
// Write a new config file if init-config command is supplied
|
||||
if let ("init-config", Some(_)) = args.subcommand() {
|
||||
if config_path.exists() {
|
||||
panic!(
|
||||
"Config file already exists at {}",
|
||||
config_path.to_string_lossy()
|
||||
);
|
||||
}
|
||||
|
||||
let server_config = ServerConfig {
|
||||
key: crypto::secp::random_secret(),
|
||||
interval_s: round_time.unwrap_or(DEFAULT_INTERVAL),
|
||||
addr: bind_addr.unwrap_or("127.0.0.1:3000").parse()?,
|
||||
socks_proxy_addr: socks_addr.unwrap_or("127.0.0.1:3001").parse()?,
|
||||
grin_node_url: match grin_node_url {
|
||||
Some(u) => u.parse()?,
|
||||
None => config::grin_node_url(&chain_type),
|
||||
},
|
||||
grin_node_secret_path: match grin_node_secret_path {
|
||||
Some(p) => Some(p.to_owned()),
|
||||
None => config::node_secret_path(&chain_type)
|
||||
.to_str()
|
||||
.map(|p| p.to_owned()),
|
||||
},
|
||||
wallet_owner_url: match wallet_owner_url {
|
||||
Some(u) => u.parse()?,
|
||||
None => config::wallet_owner_url(&chain_type),
|
||||
},
|
||||
wallet_owner_secret_path: match wallet_owner_secret_path {
|
||||
Some(p) => Some(p.to_owned()),
|
||||
None => config::wallet_owner_secret_path(&chain_type)
|
||||
.to_str()
|
||||
.map(|p| p.to_owned()),
|
||||
},
|
||||
prev_server,
|
||||
next_server,
|
||||
};
|
||||
let server_config = ServerConfig {
|
||||
key: crypto::secp::random_secret(),
|
||||
interval_s: round_time.unwrap_or(DEFAULT_INTERVAL),
|
||||
addr: bind_addr.unwrap_or("127.0.0.1:3000").parse()?,
|
||||
socks_proxy_addr: socks_addr.unwrap_or("127.0.0.1:3001").parse()?,
|
||||
grin_node_url: match grin_node_url {
|
||||
Some(u) => u.parse()?,
|
||||
None => config::grin_node_url(&chain_type),
|
||||
},
|
||||
grin_node_secret_path: match grin_node_secret_path {
|
||||
Some(p) => Some(p.to_owned()),
|
||||
None => config::node_secret_path(&chain_type)
|
||||
.to_str()
|
||||
.map(|p| p.to_owned()),
|
||||
},
|
||||
wallet_owner_url: match wallet_owner_url {
|
||||
Some(u) => u.parse()?,
|
||||
None => config::wallet_owner_url(&chain_type),
|
||||
},
|
||||
wallet_owner_secret_path: match wallet_owner_secret_path {
|
||||
Some(p) => Some(p.to_owned()),
|
||||
None => config::wallet_owner_secret_path(&chain_type)
|
||||
.to_str()
|
||||
.map(|p| p.to_owned()),
|
||||
},
|
||||
prev_server,
|
||||
next_server,
|
||||
};
|
||||
|
||||
let password = prompt_password_confirm();
|
||||
config::write_config(&config_path, &server_config, &password)?;
|
||||
println!(
|
||||
"Config file written to {:?}. Please back this file up in a safe place.",
|
||||
config_path
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let password = prompt_password_confirm();
|
||||
config::write_config(&config_path, &server_config, &password)?;
|
||||
println!(
|
||||
"Config file written to {:?}. Please back this file up in a safe place.",
|
||||
config_path
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let password = prompt_password();
|
||||
let mut server_config = config::load_config(&config_path, &password)?;
|
||||
let password = prompt_password();
|
||||
let mut server_config = config::load_config(&config_path, &password)?;
|
||||
|
||||
// Override grin_node_url, if supplied
|
||||
if let Some(grin_node_url) = grin_node_url {
|
||||
server_config.grin_node_url = grin_node_url.parse()?;
|
||||
}
|
||||
// Override grin_node_url, if supplied
|
||||
if let Some(grin_node_url) = grin_node_url {
|
||||
server_config.grin_node_url = grin_node_url.parse()?;
|
||||
}
|
||||
|
||||
// Override grin_node_secret_path, if supplied
|
||||
if let Some(grin_node_secret_path) = grin_node_secret_path {
|
||||
server_config.grin_node_secret_path = Some(grin_node_secret_path.to_owned());
|
||||
}
|
||||
// Override grin_node_secret_path, if supplied
|
||||
if let Some(grin_node_secret_path) = grin_node_secret_path {
|
||||
server_config.grin_node_secret_path = Some(grin_node_secret_path.to_owned());
|
||||
}
|
||||
|
||||
// Override wallet_owner_url, if supplied
|
||||
if let Some(wallet_owner_url) = wallet_owner_url {
|
||||
server_config.wallet_owner_url = wallet_owner_url.parse()?;
|
||||
}
|
||||
// Override wallet_owner_url, if supplied
|
||||
if let Some(wallet_owner_url) = wallet_owner_url {
|
||||
server_config.wallet_owner_url = wallet_owner_url.parse()?;
|
||||
}
|
||||
|
||||
// Override wallet_owner_secret_path, if supplied
|
||||
if let Some(wallet_owner_secret_path) = wallet_owner_secret_path {
|
||||
server_config.wallet_owner_secret_path = Some(wallet_owner_secret_path.to_owned());
|
||||
}
|
||||
// Override wallet_owner_secret_path, if supplied
|
||||
if let Some(wallet_owner_secret_path) = wallet_owner_secret_path {
|
||||
server_config.wallet_owner_secret_path = Some(wallet_owner_secret_path.to_owned());
|
||||
}
|
||||
|
||||
// Override bind_addr, if supplied
|
||||
if let Some(bind_addr) = bind_addr {
|
||||
server_config.addr = bind_addr.parse()?;
|
||||
}
|
||||
// Override bind_addr, if supplied
|
||||
if let Some(bind_addr) = bind_addr {
|
||||
server_config.addr = bind_addr.parse()?;
|
||||
}
|
||||
|
||||
// Override socks_addr, if supplied
|
||||
if let Some(socks_addr) = socks_addr {
|
||||
server_config.socks_proxy_addr = socks_addr.parse()?;
|
||||
}
|
||||
// Override socks_addr, if supplied
|
||||
if let Some(socks_addr) = socks_addr {
|
||||
server_config.socks_proxy_addr = socks_addr.parse()?;
|
||||
}
|
||||
|
||||
// Override prev_server, if supplied
|
||||
if let Some(prev_server) = prev_server {
|
||||
server_config.prev_server = Some(prev_server);
|
||||
}
|
||||
// Override prev_server, if supplied
|
||||
if let Some(prev_server) = prev_server {
|
||||
server_config.prev_server = Some(prev_server);
|
||||
}
|
||||
|
||||
// Override next_server, if supplied
|
||||
if let Some(next_server) = next_server {
|
||||
server_config.next_server = Some(next_server);
|
||||
}
|
||||
// Override next_server, if supplied
|
||||
if let Some(next_server) = next_server {
|
||||
server_config.next_server = Some(next_server);
|
||||
}
|
||||
|
||||
// Create GrinNode
|
||||
let node = HttpGrinNode::new(
|
||||
&server_config.grin_node_url,
|
||||
&server_config.node_api_secret(),
|
||||
);
|
||||
// Create GrinNode
|
||||
let node = HttpGrinNode::new(
|
||||
&server_config.grin_node_url,
|
||||
&server_config.node_api_secret(),
|
||||
);
|
||||
|
||||
// Node API health check
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
if let Err(e) = rt.block_on(node.async_get_chain_height()) {
|
||||
eprintln!("Node communication failure. Is node listening?");
|
||||
return Err(e.into());
|
||||
};
|
||||
// Node API health check
|
||||
let mut rt = tokio::runtime::Builder::new()
|
||||
.threaded_scheduler()
|
||||
.enable_all()
|
||||
.build()?;
|
||||
if let Err(e) = rt.block_on(node.async_get_chain_tip()) {
|
||||
eprintln!("Node communication failure. Is node listening?");
|
||||
return Err(e.into());
|
||||
};
|
||||
|
||||
// Open wallet
|
||||
let wallet_pass = prompt_wallet_password(&args.value_of("wallet_pass"));
|
||||
let wallet = rt.block_on(HttpWallet::async_open_wallet(
|
||||
&server_config.wallet_owner_url,
|
||||
&server_config.wallet_owner_api_secret(),
|
||||
&wallet_pass,
|
||||
));
|
||||
let wallet = match wallet {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
eprintln!("Wallet communication failure. Is wallet listening?");
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
// Open wallet
|
||||
let wallet_pass = prompt_wallet_password(&args.value_of("wallet_pass"));
|
||||
let wallet = rt.block_on(HttpWallet::async_open_wallet(
|
||||
&server_config.wallet_owner_url,
|
||||
&server_config.wallet_owner_api_secret(),
|
||||
&wallet_pass,
|
||||
));
|
||||
let wallet = match wallet {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
eprintln!("Wallet communication failure. Is wallet listening?");
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let mut tor_process = tor::init_tor_listener(
|
||||
&config::get_grin_path(&chain_type).to_str().unwrap(),
|
||||
&server_config,
|
||||
)?;
|
||||
let mut tor_process = tor::init_tor_listener(
|
||||
&config::get_grin_path(&chain_type).to_str().unwrap(),
|
||||
&server_config,
|
||||
)?;
|
||||
|
||||
let stop_state = Arc::new(StopState::new());
|
||||
let stop_state_clone = stop_state.clone();
|
||||
let stop_state = Arc::new(StopState::new());
|
||||
let stop_state_clone = stop_state.clone();
|
||||
|
||||
rt.spawn(async move {
|
||||
futures::executor::block_on(build_signals_fut());
|
||||
let _ = tor_process.kill();
|
||||
stop_state_clone.stop();
|
||||
});
|
||||
rt.spawn(async move {
|
||||
futures::executor::block_on(build_signals_fut());
|
||||
let _ = tor_process.kill();
|
||||
stop_state_clone.stop();
|
||||
});
|
||||
|
||||
let next_mixer: Option<Arc<dyn MixClient>> = server_config.next_server.clone().map(|pk| {
|
||||
let client: Arc<dyn MixClient> =
|
||||
Arc::new(MixClientImpl::new(server_config.clone(), pk.clone()));
|
||||
client
|
||||
});
|
||||
let next_mixer: Option<Arc<dyn MixClient>> = server_config.next_server.clone().map(|pk| {
|
||||
let client: Arc<dyn MixClient> =
|
||||
Arc::new(MixClientImpl::new(server_config.clone(), pk.clone()));
|
||||
client
|
||||
});
|
||||
|
||||
if server_config.prev_server.is_some() {
|
||||
// Start the JSON-RPC HTTP 'mix' server
|
||||
println!(
|
||||
"Starting MIX server with public key {:?}",
|
||||
server_config.server_pubkey().to_hex()
|
||||
);
|
||||
if server_config.prev_server.is_some() {
|
||||
// Start the JSON-RPC HTTP 'mix' server
|
||||
println!(
|
||||
"Starting MIX server with public key {:?}",
|
||||
server_config.server_pubkey().to_hex()
|
||||
);
|
||||
|
||||
let (_, http_server) = servers::mix_rpc::listen(
|
||||
rt.handle(),
|
||||
server_config,
|
||||
next_mixer,
|
||||
Arc::new(wallet),
|
||||
Arc::new(node),
|
||||
)?;
|
||||
let (_, http_server) = servers::mix_rpc::listen(
|
||||
rt.handle(),
|
||||
server_config,
|
||||
next_mixer,
|
||||
Arc::new(wallet),
|
||||
Arc::new(node),
|
||||
)?;
|
||||
|
||||
let close_handle = http_server.close_handle();
|
||||
let round_handle = spawn(move || loop {
|
||||
if stop_state.is_stopped() {
|
||||
close_handle.close();
|
||||
break;
|
||||
}
|
||||
let close_handle = http_server.close_handle();
|
||||
let round_handle = spawn(move || loop {
|
||||
if stop_state.is_stopped() {
|
||||
close_handle.close();
|
||||
break;
|
||||
}
|
||||
|
||||
sleep(Duration::from_millis(100));
|
||||
});
|
||||
sleep(Duration::from_millis(100));
|
||||
});
|
||||
|
||||
http_server.wait();
|
||||
round_handle.join().unwrap();
|
||||
} else {
|
||||
println!(
|
||||
"Starting SWAP server with public key {:?}",
|
||||
server_config.server_pubkey().to_hex()
|
||||
);
|
||||
http_server.wait();
|
||||
round_handle.join().unwrap();
|
||||
} else {
|
||||
println!(
|
||||
"Starting SWAP server with public key {:?}",
|
||||
server_config.server_pubkey().to_hex()
|
||||
);
|
||||
|
||||
// Open SwapStore
|
||||
let store = SwapStore::new(
|
||||
config::get_grin_path(&chain_type)
|
||||
.join("db")
|
||||
.to_str()
|
||||
.ok_or(StoreError::OpenError(grin_store::lmdb::Error::FileErr(
|
||||
"db_root path error".to_string(),
|
||||
)))?,
|
||||
)?;
|
||||
// Open SwapStore
|
||||
let store = SwapStore::new(
|
||||
config::get_grin_path(&chain_type)
|
||||
.join("db")
|
||||
.to_str()
|
||||
.ok_or(StoreError::OpenError(grin_store::lmdb::Error::FileErr(
|
||||
"db_root path error".to_string(),
|
||||
)))?,
|
||||
)?;
|
||||
|
||||
// Start the mwixnet JSON-RPC HTTP 'swap' server
|
||||
let (swap_server, http_server) = servers::swap_rpc::listen(
|
||||
rt.handle(),
|
||||
&server_config,
|
||||
next_mixer,
|
||||
Arc::new(wallet),
|
||||
Arc::new(node),
|
||||
store,
|
||||
)?;
|
||||
// Start the mwixnet JSON-RPC HTTP 'swap' server
|
||||
let (swap_server, http_server) = servers::swap_rpc::listen(
|
||||
rt.handle(),
|
||||
&server_config,
|
||||
next_mixer,
|
||||
Arc::new(wallet),
|
||||
Arc::new(node),
|
||||
store,
|
||||
)?;
|
||||
|
||||
let close_handle = http_server.close_handle();
|
||||
let round_handle = spawn(move || {
|
||||
let mut secs = 0;
|
||||
loop {
|
||||
if stop_state.is_stopped() {
|
||||
close_handle.close();
|
||||
break;
|
||||
}
|
||||
let close_handle = http_server.close_handle();
|
||||
let round_handle = spawn(move || {
|
||||
let mut secs = 0;
|
||||
let prev_tx = Arc::new(Mutex::new(None));
|
||||
let server = swap_server.clone();
|
||||
|
||||
sleep(Duration::from_secs(1));
|
||||
secs = (secs + 1) % server_config.interval_s;
|
||||
loop {
|
||||
if stop_state.is_stopped() {
|
||||
close_handle.close();
|
||||
break;
|
||||
}
|
||||
|
||||
if secs == 0 {
|
||||
let server = swap_server.clone();
|
||||
rt.spawn(async move { server.lock().await.execute_round().await });
|
||||
//let _ = swap_server.lock().unwrap().execute_round();
|
||||
}
|
||||
}
|
||||
});
|
||||
sleep(Duration::from_secs(1));
|
||||
secs = (secs + 1) % server_config.interval_s;
|
||||
|
||||
http_server.wait();
|
||||
round_handle.join().unwrap();
|
||||
}
|
||||
if secs == 0 {
|
||||
let prev_tx_clone = prev_tx.clone();
|
||||
let server_clone = server.clone();
|
||||
rt.spawn(async move {
|
||||
let result = server_clone.lock().await.execute_round().await;
|
||||
let mut prev_tx_lock = prev_tx_clone.lock().unwrap();
|
||||
*prev_tx_lock = match result {
|
||||
Ok(Some(tx)) => Some(tx),
|
||||
_ => None,
|
||||
};
|
||||
});
|
||||
} else if secs % 30 == 0 {
|
||||
let prev_tx_clone = prev_tx.clone();
|
||||
let server_clone = server.clone();
|
||||
rt.spawn(async move {
|
||||
let tx_option = {
|
||||
let prev_tx_lock = prev_tx_clone.lock().unwrap();
|
||||
prev_tx_lock.clone()
|
||||
}; // Lock is dropped here
|
||||
|
||||
Ok(())
|
||||
if let Some(tx) = tx_option {
|
||||
let result = server_clone.lock().await.check_reorg(tx).await;
|
||||
let mut prev_tx_lock = prev_tx_clone.lock().unwrap();
|
||||
*prev_tx_lock = match result {
|
||||
Ok(Some(tx)) => Some(tx),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
http_server.wait();
|
||||
round_handle.join().unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn build_signals_fut() {
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
|
||||
// Listen for SIGINT, SIGQUIT, and SIGTERM
|
||||
let mut terminate_signal =
|
||||
signal(SignalKind::terminate()).expect("failed to create terminate signal");
|
||||
let mut quit_signal = signal(SignalKind::quit()).expect("failed to create quit signal");
|
||||
let mut interrupt_signal =
|
||||
signal(SignalKind::interrupt()).expect("failed to create interrupt signal");
|
||||
// Listen for SIGINT, SIGQUIT, and SIGTERM
|
||||
let mut terminate_signal =
|
||||
signal(SignalKind::terminate()).expect("failed to create terminate signal");
|
||||
let mut quit_signal = signal(SignalKind::quit()).expect("failed to create quit signal");
|
||||
let mut interrupt_signal =
|
||||
signal(SignalKind::interrupt()).expect("failed to create interrupt signal");
|
||||
|
||||
futures::future::select_all(vec![
|
||||
Box::pin(terminate_signal.recv()),
|
||||
Box::pin(quit_signal.recv()),
|
||||
Box::pin(interrupt_signal.recv()),
|
||||
])
|
||||
.await;
|
||||
futures::future::select_all(vec![
|
||||
Box::pin(terminate_signal.recv()),
|
||||
Box::pin(quit_signal.recv()),
|
||||
Box::pin(interrupt_signal.recv()),
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
async fn build_signals_fut() {
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("failed to install CTRL+C signal handler");
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("failed to install CTRL+C signal handler");
|
||||
}
|
||||
|
||||
fn prompt_password() -> ZeroingString {
|
||||
ZeroingString::from(rpassword::prompt_password_stdout("Server password: ").unwrap())
|
||||
ZeroingString::from(rpassword::prompt_password_stdout("Server password: ").unwrap())
|
||||
}
|
||||
|
||||
fn prompt_password_confirm() -> ZeroingString {
|
||||
let mut first = "first".to_string();
|
||||
let mut second = "second".to_string();
|
||||
while first != second {
|
||||
first = rpassword::prompt_password_stdout("Server password: ").unwrap();
|
||||
second = rpassword::prompt_password_stdout("Confirm server password: ").unwrap();
|
||||
}
|
||||
ZeroingString::from(first)
|
||||
let mut first = "first".to_string();
|
||||
let mut second = "second".to_string();
|
||||
while first != second {
|
||||
first = rpassword::prompt_password_stdout("Server password: ").unwrap();
|
||||
second = rpassword::prompt_password_stdout("Confirm server password: ").unwrap();
|
||||
}
|
||||
ZeroingString::from(first)
|
||||
}
|
||||
|
||||
fn prompt_wallet_password(wallet_pass: &Option<&str>) -> ZeroingString {
|
||||
match *wallet_pass {
|
||||
Some(wallet_pass) => ZeroingString::from(wallet_pass),
|
||||
None => {
|
||||
ZeroingString::from(rpassword::prompt_password_stdout("Wallet password: ").unwrap())
|
||||
}
|
||||
}
|
||||
match *wallet_pass {
|
||||
Some(wallet_pass) => ZeroingString::from(wallet_pass),
|
||||
None => {
|
||||
ZeroingString::from(rpassword::prompt_password_stdout("Wallet password: ").unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
33
src/node.rs
33
src/node.rs
|
@ -4,13 +4,14 @@ use grin_api::json_rpc::{build_request, Request, Response};
|
|||
use grin_api::{client, LocatedTxKernel};
|
||||
use grin_api::{OutputPrintable, OutputType, Tip};
|
||||
use grin_core::consensus::COINBASE_MATURITY;
|
||||
use grin_core::core::{Input, OutputFeatures, Transaction};
|
||||
use grin_core::core::{Committed, Input, OutputFeatures, Transaction};
|
||||
use grin_util::ToHex;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde_json::json;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use grin_core::core::hash::Hash;
|
||||
use thiserror::Error;
|
||||
|
||||
#[async_trait]
|
||||
|
@ -21,8 +22,8 @@ pub trait GrinNode: Send + Sync {
|
|||
output_commit: &Commitment,
|
||||
) -> Result<Option<OutputPrintable>, NodeError>;
|
||||
|
||||
/// Gets the height of the chain tip
|
||||
async fn async_get_chain_height(&self) -> Result<u64, NodeError>;
|
||||
/// Gets the height and hash of the chain tip
|
||||
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError>;
|
||||
|
||||
/// Posts a transaction to the grin node
|
||||
async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError>;
|
||||
|
@ -108,6 +109,23 @@ pub async fn async_build_input(
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn async_is_tx_valid(node: &Arc<dyn GrinNode>, tx: &Transaction) -> Result<bool, NodeError> {
|
||||
let next_block_height = node.async_get_chain_tip().await?.0 + 1;
|
||||
for input_commit in &tx.inputs_committed() {
|
||||
if !async_is_spendable(&node, &input_commit, next_block_height).await? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
for output_commit in &tx.outputs_committed() {
|
||||
if async_is_unspent(&node, &output_commit).await? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// HTTP (JSON-RPC) implementation of the 'GrinNode' trait
|
||||
#[derive(Clone)]
|
||||
pub struct HttpGrinNode {
|
||||
|
@ -176,7 +194,7 @@ impl GrinNode for HttpGrinNode {
|
|||
Ok(Some(outputs[0].clone()))
|
||||
}
|
||||
|
||||
async fn async_get_chain_height(&self) -> Result<u64, NodeError> {
|
||||
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError> {
|
||||
let params = json!([]);
|
||||
let tip_json = self
|
||||
.async_send_request::<serde_json::Value>("get_tip", ¶ms)
|
||||
|
@ -184,7 +202,7 @@ impl GrinNode for HttpGrinNode {
|
|||
let tip =
|
||||
serde_json::from_value::<Tip>(tip_json).map_err(NodeError::DecodeResponseError)?;
|
||||
|
||||
Ok(tip.height)
|
||||
Ok((tip.height, Hash::from_hex(tip.last_block_pushed.as_str()).unwrap()))
|
||||
}
|
||||
|
||||
async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError> {
|
||||
|
@ -226,6 +244,7 @@ pub mod mock {
|
|||
use grin_onion::crypto::secp::Commitment;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
use grin_core::core::hash::Hash;
|
||||
|
||||
/// Implementation of 'GrinNode' trait that mocks a grin node instance.
|
||||
/// Use only for testing purposes.
|
||||
|
@ -299,8 +318,8 @@ pub mod mock {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
async fn async_get_chain_height(&self) -> Result<u64, NodeError> {
|
||||
Ok(100)
|
||||
async fn async_get_chain_tip(&self) -> Result<(u64, Hash), NodeError> {
|
||||
Ok((100, Hash::default()))
|
||||
}
|
||||
|
||||
async fn async_post_tx(&self, tx: &Transaction) -> Result<(), NodeError> {
|
||||
|
|
1529
src/servers/swap.rs
1529
src/servers/swap.rs
File diff suppressed because it is too large
Load diff
597
src/store.rs
597
src/store.rs
|
@ -3,9 +3,9 @@ use grin_onion::crypto::secp::{self, Commitment, RangeProof, SecretKey};
|
|||
use grin_onion::onion::Onion;
|
||||
use grin_onion::util::{read_optional, write_optional};
|
||||
|
||||
use grin_core::core::Input;
|
||||
use grin_core::core::{Input, Transaction};
|
||||
use grin_core::ser::{
|
||||
self, DeserializationMode, ProtocolVersion, Readable, Reader, Writeable, Writer,
|
||||
self, DeserializationMode, ProtocolVersion, Readable, Reader, Writeable, Writer,
|
||||
};
|
||||
use grin_store::{self as store, Store};
|
||||
use grin_util::ToHex;
|
||||
|
@ -14,337 +14,390 @@ use thiserror::Error;
|
|||
const DB_NAME: &str = "swap";
|
||||
const STORE_SUBPATH: &str = "swaps";
|
||||
|
||||
const CURRENT_VERSION: u8 = 0;
|
||||
const CURRENT_SWAP_VERSION: u8 = 0;
|
||||
const SWAP_PREFIX: u8 = b'S';
|
||||
|
||||
const CURRENT_TX_VERSION: u8 = 0;
|
||||
const TX_PREFIX: u8 = b'T';
|
||||
|
||||
/// Swap statuses
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum SwapStatus {
|
||||
Unprocessed,
|
||||
InProcess {
|
||||
kernel_commit: Commitment,
|
||||
},
|
||||
Completed {
|
||||
kernel_commit: Commitment,
|
||||
block_hash: Hash,
|
||||
},
|
||||
Failed,
|
||||
Unprocessed,
|
||||
InProcess {
|
||||
kernel_commit: Commitment,
|
||||
},
|
||||
Completed {
|
||||
kernel_commit: Commitment,
|
||||
block_hash: Hash,
|
||||
},
|
||||
Failed,
|
||||
}
|
||||
|
||||
impl Writeable for SwapStatus {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
match self {
|
||||
SwapStatus::Unprocessed => {
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
SwapStatus::InProcess { kernel_commit } => {
|
||||
writer.write_u8(1)?;
|
||||
kernel_commit.write(writer)?;
|
||||
}
|
||||
SwapStatus::Completed {
|
||||
kernel_commit,
|
||||
block_hash,
|
||||
} => {
|
||||
writer.write_u8(2)?;
|
||||
kernel_commit.write(writer)?;
|
||||
block_hash.write(writer)?;
|
||||
}
|
||||
SwapStatus::Failed => {
|
||||
writer.write_u8(3)?;
|
||||
}
|
||||
};
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
match self {
|
||||
SwapStatus::Unprocessed => {
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
SwapStatus::InProcess { kernel_commit } => {
|
||||
writer.write_u8(1)?;
|
||||
kernel_commit.write(writer)?;
|
||||
}
|
||||
SwapStatus::Completed {
|
||||
kernel_commit,
|
||||
block_hash,
|
||||
} => {
|
||||
writer.write_u8(2)?;
|
||||
kernel_commit.write(writer)?;
|
||||
block_hash.write(writer)?;
|
||||
}
|
||||
SwapStatus::Failed => {
|
||||
writer.write_u8(3)?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SwapStatus {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapStatus, ser::Error> {
|
||||
let status = match reader.read_u8()? {
|
||||
0 => SwapStatus::Unprocessed,
|
||||
1 => {
|
||||
let kernel_commit = Commitment::read(reader)?;
|
||||
SwapStatus::InProcess { kernel_commit }
|
||||
}
|
||||
2 => {
|
||||
let kernel_commit = Commitment::read(reader)?;
|
||||
let block_hash = Hash::read(reader)?;
|
||||
SwapStatus::Completed {
|
||||
kernel_commit,
|
||||
block_hash,
|
||||
}
|
||||
}
|
||||
3 => SwapStatus::Failed,
|
||||
_ => {
|
||||
return Err(ser::Error::CorruptedData);
|
||||
}
|
||||
};
|
||||
Ok(status)
|
||||
}
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapStatus, ser::Error> {
|
||||
let status = match reader.read_u8()? {
|
||||
0 => SwapStatus::Unprocessed,
|
||||
1 => {
|
||||
let kernel_commit = Commitment::read(reader)?;
|
||||
SwapStatus::InProcess { kernel_commit }
|
||||
}
|
||||
2 => {
|
||||
let kernel_commit = Commitment::read(reader)?;
|
||||
let block_hash = Hash::read(reader)?;
|
||||
SwapStatus::Completed {
|
||||
kernel_commit,
|
||||
block_hash,
|
||||
}
|
||||
}
|
||||
3 => SwapStatus::Failed,
|
||||
_ => {
|
||||
return Err(ser::Error::CorruptedData);
|
||||
}
|
||||
};
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
|
||||
/// Data needed to swap a single output.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SwapData {
|
||||
/// The total excess for the output commitment
|
||||
pub excess: SecretKey,
|
||||
/// The derived output commitment after applying excess and fee
|
||||
pub output_commit: Commitment,
|
||||
/// The rangeproof, included only for the final hop (node N)
|
||||
pub rangeproof: Option<RangeProof>,
|
||||
/// Transaction input being spent
|
||||
pub input: Input,
|
||||
/// Transaction fee
|
||||
pub fee: u64,
|
||||
/// The remaining onion after peeling off our layer
|
||||
pub onion: Onion,
|
||||
/// The status of the swap
|
||||
pub status: SwapStatus,
|
||||
/// The total excess for the output commitment
|
||||
pub excess: SecretKey,
|
||||
/// The derived output commitment after applying excess and fee
|
||||
pub output_commit: Commitment,
|
||||
/// The rangeproof, included only for the final hop (node N)
|
||||
pub rangeproof: Option<RangeProof>,
|
||||
/// Transaction input being spent
|
||||
pub input: Input,
|
||||
/// Transaction fee
|
||||
pub fee: u64,
|
||||
/// The remaining onion after peeling off our layer
|
||||
pub onion: Onion,
|
||||
/// The status of the swap
|
||||
pub status: SwapStatus,
|
||||
}
|
||||
|
||||
impl Writeable for SwapData {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(CURRENT_VERSION)?;
|
||||
writer.write_fixed_bytes(&self.excess)?;
|
||||
writer.write_fixed_bytes(&self.output_commit)?;
|
||||
write_optional(writer, &self.rangeproof)?;
|
||||
self.input.write(writer)?;
|
||||
writer.write_u64(self.fee.into())?;
|
||||
self.onion.write(writer)?;
|
||||
self.status.write(writer)?;
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(CURRENT_SWAP_VERSION)?;
|
||||
writer.write_fixed_bytes(&self.excess)?;
|
||||
writer.write_fixed_bytes(&self.output_commit)?;
|
||||
write_optional(writer, &self.rangeproof)?;
|
||||
self.input.write(writer)?;
|
||||
writer.write_u64(self.fee.into())?;
|
||||
self.onion.write(writer)?;
|
||||
self.status.write(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SwapData {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapData, ser::Error> {
|
||||
let version = reader.read_u8()?;
|
||||
if version != CURRENT_VERSION {
|
||||
return Err(ser::Error::UnsupportedProtocolVersion);
|
||||
}
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapData, ser::Error> {
|
||||
let version = reader.read_u8()?;
|
||||
if version != CURRENT_SWAP_VERSION {
|
||||
return Err(ser::Error::UnsupportedProtocolVersion);
|
||||
}
|
||||
|
||||
let excess = secp::read_secret_key(reader)?;
|
||||
let output_commit = Commitment::read(reader)?;
|
||||
let rangeproof = read_optional(reader)?;
|
||||
let input = Input::read(reader)?;
|
||||
let fee = reader.read_u64()?;
|
||||
let onion = Onion::read(reader)?;
|
||||
let status = SwapStatus::read(reader)?;
|
||||
Ok(SwapData {
|
||||
excess,
|
||||
output_commit,
|
||||
rangeproof,
|
||||
input,
|
||||
fee,
|
||||
onion,
|
||||
status,
|
||||
})
|
||||
}
|
||||
let excess = secp::read_secret_key(reader)?;
|
||||
let output_commit = Commitment::read(reader)?;
|
||||
let rangeproof = read_optional(reader)?;
|
||||
let input = Input::read(reader)?;
|
||||
let fee = reader.read_u64()?;
|
||||
let onion = Onion::read(reader)?;
|
||||
let status = SwapStatus::read(reader)?;
|
||||
Ok(SwapData {
|
||||
excess,
|
||||
output_commit,
|
||||
rangeproof,
|
||||
input,
|
||||
fee,
|
||||
onion,
|
||||
status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A transaction created as part of a swap round.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct SwapTx {
|
||||
pub tx: Transaction,
|
||||
pub chain_tip: (u64, Hash),
|
||||
// TODO: Include status
|
||||
}
|
||||
|
||||
impl Writeable for SwapTx {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(CURRENT_TX_VERSION)?;
|
||||
self.tx.write(writer)?;
|
||||
writer.write_u64(self.chain_tip.0)?;
|
||||
self.chain_tip.1.write(writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SwapTx {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SwapTx, ser::Error> {
|
||||
let version = reader.read_u8()?;
|
||||
if version != CURRENT_TX_VERSION {
|
||||
return Err(ser::Error::UnsupportedProtocolVersion);
|
||||
}
|
||||
|
||||
let tx = Transaction::read(reader)?;
|
||||
let height = reader.read_u64()?;
|
||||
let block_hash = Hash::read(reader)?;
|
||||
Ok(SwapTx {
|
||||
tx,
|
||||
chain_tip: (height, block_hash),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage facility for swap data.
|
||||
pub struct SwapStore {
|
||||
db: Store,
|
||||
db: Store,
|
||||
}
|
||||
|
||||
/// Store error types
|
||||
#[derive(Clone, Error, Debug, PartialEq)]
|
||||
pub enum StoreError {
|
||||
#[error("Swap entry already exists for '{0:?}'")]
|
||||
AlreadyExists(Commitment),
|
||||
#[error("Error occurred while attempting to open db: {0}")]
|
||||
OpenError(store::lmdb::Error),
|
||||
#[error("Serialization error occurred: {0}")]
|
||||
SerializationError(ser::Error),
|
||||
#[error("Error occurred while attempting to read from db: {0}")]
|
||||
ReadError(store::lmdb::Error),
|
||||
#[error("Error occurred while attempting to write to db: {0}")]
|
||||
WriteError(store::lmdb::Error),
|
||||
#[error("Swap entry already exists for '{0:?}'")]
|
||||
AlreadyExists(Commitment),
|
||||
#[error("Error occurred while attempting to open db: {0}")]
|
||||
OpenError(store::lmdb::Error),
|
||||
#[error("Serialization error occurred: {0}")]
|
||||
SerializationError(ser::Error),
|
||||
#[error("Error occurred while attempting to read from db: {0}")]
|
||||
ReadError(store::lmdb::Error),
|
||||
#[error("Error occurred while attempting to write to db: {0}")]
|
||||
WriteError(store::lmdb::Error),
|
||||
}
|
||||
|
||||
impl From<ser::Error> for StoreError {
|
||||
fn from(e: ser::Error) -> StoreError {
|
||||
StoreError::SerializationError(e)
|
||||
}
|
||||
fn from(e: ser::Error) -> StoreError {
|
||||
StoreError::SerializationError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl SwapStore {
|
||||
/// Create new chain store
|
||||
pub fn new(db_root: &str) -> Result<SwapStore, StoreError> {
|
||||
let db = Store::new(db_root, Some(DB_NAME), Some(STORE_SUBPATH), None)
|
||||
.map_err(StoreError::OpenError)?;
|
||||
Ok(SwapStore { db })
|
||||
}
|
||||
/// Create new chain store
|
||||
pub fn new(db_root: &str) -> Result<SwapStore, StoreError> {
|
||||
let db = Store::new(db_root, Some(DB_NAME), Some(STORE_SUBPATH), None)
|
||||
.map_err(StoreError::OpenError)?;
|
||||
Ok(SwapStore { db })
|
||||
}
|
||||
|
||||
/// Writes a single key-value pair to the database
|
||||
fn write<K: AsRef<[u8]>>(
|
||||
&self,
|
||||
prefix: u8,
|
||||
k: K,
|
||||
value: &Vec<u8>,
|
||||
overwrite: bool,
|
||||
) -> Result<bool, store::lmdb::Error> {
|
||||
let batch = self.db.batch()?;
|
||||
let key = store::to_key(prefix, k);
|
||||
if !overwrite && batch.exists(&key[..])? {
|
||||
Ok(false)
|
||||
} else {
|
||||
batch.put(&key[..], &value[..])?;
|
||||
batch.commit()?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
/// Writes a single key-value pair to the database
|
||||
fn write<K: AsRef<[u8]>>(
|
||||
&self,
|
||||
prefix: u8,
|
||||
k: K,
|
||||
value: &Vec<u8>,
|
||||
overwrite: bool,
|
||||
) -> Result<bool, store::lmdb::Error> {
|
||||
let batch = self.db.batch()?;
|
||||
let key = store::to_key(prefix, k);
|
||||
if !overwrite && batch.exists(&key[..])? {
|
||||
Ok(false)
|
||||
} else {
|
||||
batch.put(&key[..], &value[..])?;
|
||||
batch.commit()?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a single value by key
|
||||
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), || {
|
||||
format!("{}:{}", prefix, k.to_hex())
|
||||
})
|
||||
.map_err(StoreError::ReadError)
|
||||
}
|
||||
/// Reads a single value by key
|
||||
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), || {
|
||||
format!("{}:{}", prefix, k.to_hex())
|
||||
})
|
||||
.map_err(StoreError::ReadError)
|
||||
}
|
||||
|
||||
/// Saves a swap to the database
|
||||
pub fn save_swap(&self, s: &SwapData, overwrite: bool) -> Result<(), StoreError> {
|
||||
let data = ser::ser_vec(&s, ProtocolVersion::local())?;
|
||||
let saved = self
|
||||
.write(SWAP_PREFIX, &s.input.commit, &data, overwrite)
|
||||
.map_err(StoreError::WriteError)?;
|
||||
if !saved {
|
||||
Err(StoreError::AlreadyExists(s.input.commit.clone()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
/// Saves a swap to the database
|
||||
pub fn save_swap(&self, s: &SwapData, overwrite: bool) -> Result<(), StoreError> {
|
||||
let data = ser::ser_vec(&s, ProtocolVersion::local())?;
|
||||
let saved = self
|
||||
.write(SWAP_PREFIX, &s.input.commit, &data, overwrite)
|
||||
.map_err(StoreError::WriteError)?;
|
||||
if !saved {
|
||||
Err(StoreError::AlreadyExists(s.input.commit.clone()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over all swaps.
|
||||
pub fn swaps_iter(&self) -> Result<impl Iterator<Item = SwapData>, StoreError> {
|
||||
let key = store::to_key(SWAP_PREFIX, "");
|
||||
let protocol_version = self.db.protocol_version();
|
||||
self.db
|
||||
.iter(&key[..], move |_, mut v| {
|
||||
ser::deserialize(&mut v, protocol_version, DeserializationMode::default())
|
||||
.map_err(From::from)
|
||||
})
|
||||
.map_err(|e| StoreError::ReadError(e))
|
||||
}
|
||||
/// Iterator over all swaps.
|
||||
pub fn swaps_iter(&self) -> Result<impl Iterator<Item=SwapData>, StoreError> {
|
||||
let key = store::to_key(SWAP_PREFIX, "");
|
||||
let protocol_version = self.db.protocol_version();
|
||||
self.db
|
||||
.iter(&key[..], move |_, mut v| {
|
||||
ser::deserialize(&mut v, protocol_version, DeserializationMode::default())
|
||||
.map_err(From::from)
|
||||
})
|
||||
.map_err(|e| StoreError::ReadError(e))
|
||||
}
|
||||
|
||||
/// Checks if a matching swap exists in the database
|
||||
#[allow(dead_code)]
|
||||
pub fn swap_exists(&self, input_commit: &Commitment) -> Result<bool, StoreError> {
|
||||
let key = store::to_key(SWAP_PREFIX, input_commit);
|
||||
self.db
|
||||
.batch()
|
||||
.map_err(StoreError::ReadError)?
|
||||
.exists(&key[..])
|
||||
.map_err(StoreError::ReadError)
|
||||
}
|
||||
/// Checks if a matching swap exists in the database
|
||||
#[allow(dead_code)]
|
||||
pub fn swap_exists(&self, input_commit: &Commitment) -> Result<bool, StoreError> {
|
||||
let key = store::to_key(SWAP_PREFIX, input_commit);
|
||||
self.db
|
||||
.batch()
|
||||
.map_err(StoreError::ReadError)?
|
||||
.exists(&key[..])
|
||||
.map_err(StoreError::ReadError)
|
||||
}
|
||||
|
||||
/// Reads a swap from the database
|
||||
#[allow(dead_code)]
|
||||
pub fn get_swap(&self, input_commit: &Commitment) -> Result<SwapData, StoreError> {
|
||||
self.read(SWAP_PREFIX, input_commit)
|
||||
}
|
||||
/// Reads a swap from the database
|
||||
#[allow(dead_code)]
|
||||
pub fn get_swap(&self, input_commit: &Commitment) -> Result<SwapData, StoreError> {
|
||||
self.read(SWAP_PREFIX, input_commit)
|
||||
}
|
||||
|
||||
/// Saves a swap transaction to the database
|
||||
pub fn save_swap_tx(&self, s: &SwapTx) -> Result<(), StoreError> {
|
||||
let data = ser::ser_vec(&s, ProtocolVersion::local())?;
|
||||
self
|
||||
.write(TX_PREFIX, &s.tx.kernels().first().unwrap().excess, &data, true)
|
||||
.map_err(StoreError::WriteError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads a swap tx from the database
|
||||
pub fn get_swap_tx(&self, kernel_excess: &Commitment) -> Result<SwapTx, StoreError> {
|
||||
self.read(TX_PREFIX, kernel_excess)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::store::{StoreError, SwapData, SwapStatus, SwapStore};
|
||||
use grin_core::core::{Input, OutputFeatures};
|
||||
use grin_core::global::{self, ChainTypes};
|
||||
use grin_onion::crypto::secp;
|
||||
use grin_onion::test_util as onion_test_util;
|
||||
use rand::RngCore;
|
||||
use std::cmp::Ordering;
|
||||
use crate::store::{StoreError, SwapData, SwapStatus, SwapStore};
|
||||
use grin_core::core::{Input, OutputFeatures};
|
||||
use grin_core::global::{self, ChainTypes};
|
||||
use grin_onion::crypto::secp;
|
||||
use grin_onion::test_util as onion_test_util;
|
||||
use rand::RngCore;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
fn new_store(test_name: &str) -> SwapStore {
|
||||
global::set_local_chain_type(ChainTypes::AutomatedTesting);
|
||||
let db_root = format!("./target/tmp/.{}", test_name);
|
||||
let _ = std::fs::remove_dir_all(db_root.as_str());
|
||||
SwapStore::new(db_root.as_str()).unwrap()
|
||||
}
|
||||
fn new_store(test_name: &str) -> SwapStore {
|
||||
global::set_local_chain_type(ChainTypes::AutomatedTesting);
|
||||
let db_root = format!("./target/tmp/.{}", test_name);
|
||||
let _ = std::fs::remove_dir_all(db_root.as_str());
|
||||
SwapStore::new(db_root.as_str()).unwrap()
|
||||
}
|
||||
|
||||
fn rand_swap_with_status(status: SwapStatus) -> SwapData {
|
||||
SwapData {
|
||||
excess: secp::random_secret(),
|
||||
output_commit: onion_test_util::rand_commit(),
|
||||
rangeproof: Some(onion_test_util::rand_proof()),
|
||||
input: Input::new(OutputFeatures::Plain, onion_test_util::rand_commit()),
|
||||
fee: rand::thread_rng().next_u64(),
|
||||
onion: onion_test_util::rand_onion(),
|
||||
status,
|
||||
}
|
||||
}
|
||||
fn rand_swap_with_status(status: SwapStatus) -> SwapData {
|
||||
SwapData {
|
||||
excess: secp::random_secret(),
|
||||
output_commit: onion_test_util::rand_commit(),
|
||||
rangeproof: Some(onion_test_util::rand_proof()),
|
||||
input: Input::new(OutputFeatures::Plain, onion_test_util::rand_commit()),
|
||||
fee: rand::thread_rng().next_u64(),
|
||||
onion: onion_test_util::rand_onion(),
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
fn rand_swap() -> SwapData {
|
||||
let s = rand::thread_rng().next_u64() % 3;
|
||||
let status = if s == 0 {
|
||||
SwapStatus::Unprocessed
|
||||
} else if s == 1 {
|
||||
SwapStatus::InProcess {
|
||||
kernel_commit: onion_test_util::rand_commit(),
|
||||
}
|
||||
} else {
|
||||
SwapStatus::Completed {
|
||||
kernel_commit: onion_test_util::rand_commit(),
|
||||
block_hash: onion_test_util::rand_hash(),
|
||||
}
|
||||
};
|
||||
rand_swap_with_status(status)
|
||||
}
|
||||
fn rand_swap() -> SwapData {
|
||||
let s = rand::thread_rng().next_u64() % 3;
|
||||
let status = if s == 0 {
|
||||
SwapStatus::Unprocessed
|
||||
} else if s == 1 {
|
||||
SwapStatus::InProcess {
|
||||
kernel_commit: onion_test_util::rand_commit(),
|
||||
}
|
||||
} else {
|
||||
SwapStatus::Completed {
|
||||
kernel_commit: onion_test_util::rand_commit(),
|
||||
block_hash: onion_test_util::rand_hash(),
|
||||
}
|
||||
};
|
||||
rand_swap_with_status(status)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_iter() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let store = new_store("swap_iter");
|
||||
let mut swaps: Vec<SwapData> = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let swap = rand_swap();
|
||||
store.save_swap(&swap, false)?;
|
||||
swaps.push(swap);
|
||||
}
|
||||
#[test]
|
||||
fn swap_iter() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let store = new_store("swap_iter");
|
||||
let mut swaps: Vec<SwapData> = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let swap = rand_swap();
|
||||
store.save_swap(&swap, false)?;
|
||||
swaps.push(swap);
|
||||
}
|
||||
|
||||
swaps.sort_by(|a, b| {
|
||||
if a.input.commit < b.input.commit {
|
||||
Ordering::Less
|
||||
} else if a.input.commit == b.input.commit {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
swaps.sort_by(|a, b| {
|
||||
if a.input.commit < b.input.commit {
|
||||
Ordering::Less
|
||||
} else if a.input.commit == b.input.commit {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
|
||||
let mut i: usize = 0;
|
||||
for swap in store.swaps_iter()? {
|
||||
assert_eq!(swap, *swaps.get(i).unwrap());
|
||||
i += 1;
|
||||
}
|
||||
let mut i: usize = 0;
|
||||
for swap in store.swaps_iter()? {
|
||||
assert_eq!(swap, *swaps.get(i).unwrap());
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_swap() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let store = new_store("save_swap");
|
||||
#[test]
|
||||
fn save_swap() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let store = new_store("save_swap");
|
||||
|
||||
let mut swap = rand_swap_with_status(SwapStatus::Unprocessed);
|
||||
assert!(!store.swap_exists(&swap.input.commit)?);
|
||||
let mut swap = rand_swap_with_status(SwapStatus::Unprocessed);
|
||||
assert!(!store.swap_exists(&swap.input.commit)?);
|
||||
|
||||
store.save_swap(&swap, false)?;
|
||||
assert_eq!(swap, store.get_swap(&swap.input.commit)?);
|
||||
assert!(store.swap_exists(&swap.input.commit)?);
|
||||
store.save_swap(&swap, false)?;
|
||||
assert_eq!(swap, store.get_swap(&swap.input.commit)?);
|
||||
assert!(store.swap_exists(&swap.input.commit)?);
|
||||
|
||||
swap.status = SwapStatus::InProcess {
|
||||
kernel_commit: onion_test_util::rand_commit(),
|
||||
};
|
||||
let result = store.save_swap(&swap, false);
|
||||
assert_eq!(
|
||||
Err(StoreError::AlreadyExists(swap.input.commit.clone())),
|
||||
result
|
||||
);
|
||||
swap.status = SwapStatus::InProcess {
|
||||
kernel_commit: onion_test_util::rand_commit(),
|
||||
};
|
||||
let result = store.save_swap(&swap, false);
|
||||
assert_eq!(
|
||||
Err(StoreError::AlreadyExists(swap.input.commit.clone())),
|
||||
result
|
||||
);
|
||||
|
||||
store.save_swap(&swap, true)?;
|
||||
assert_eq!(swap, store.get_swap(&swap.input.commit)?);
|
||||
store.save_swap(&swap, true)?;
|
||||
assert_eq!(swap, store.get_swap(&swap.input.commit)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue