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