Skip to content
Snippets Groups Projects
Commit c06b777c authored by Éloïs's avatar Éloïs
Browse files

[enh] #76 migrate sqlite to rustbreak

parent ee98333b
No related branches found
No related tags found
No related merge requests found
...@@ -59,7 +59,7 @@ pub fn dbex_tx(conf: &DuniterConf, query: &DBExTxQuery) { ...@@ -59,7 +59,7 @@ pub fn dbex_tx(conf: &DuniterConf, query: &DBExTxQuery) {
// Open databases // Open databases
let load_dbs_begin = SystemTime::now(); let load_dbs_begin = SystemTime::now();
//let blocks_databases = BlocksV10DBs::open(&db_path, false); //let blocks_databases = BlocksV10DBs::open(&db_path, false);
let currency_databases = CurrencyV10DBs::open(&db_path, false); let currency_databases = CurrencyV10DBs::<FileBackend>::open(&db_path);
let wot_databases = WotsV10DBs::open(&db_path, false); let wot_databases = WotsV10DBs::open(&db_path, false);
let load_dbs_duration = SystemTime::now() let load_dbs_duration = SystemTime::now()
.duration_since(load_dbs_begin) .duration_since(load_dbs_begin)
......
...@@ -34,6 +34,7 @@ extern crate duniter_message; ...@@ -34,6 +34,7 @@ extern crate duniter_message;
extern crate duniter_module; extern crate duniter_module;
extern crate duniter_network; extern crate duniter_network;
extern crate duniter_wotb; extern crate duniter_wotb;
extern crate rustbreak;
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate sqlite; extern crate sqlite;
...@@ -74,6 +75,7 @@ use duniter_network::{ ...@@ -74,6 +75,7 @@ use duniter_network::{
use duniter_wotb::data::rusty::RustyWebOfTrust; use duniter_wotb::data::rusty::RustyWebOfTrust;
use duniter_wotb::operations::file::BinaryFileFormater; use duniter_wotb::operations::file::BinaryFileFormater;
use duniter_wotb::{NodeId, WebOfTrust}; use duniter_wotb::{NodeId, WebOfTrust};
use rustbreak::backend::FileBackend;
/// The blocks are requested by packet groups. This constant sets the block packet size. /// The blocks are requested by packet groups. This constant sets the block packet size.
pub static CHUNK_SIZE: &'static u32 = &50; pub static CHUNK_SIZE: &'static u32 = &50;
...@@ -100,7 +102,7 @@ pub struct BlockchainModule { ...@@ -100,7 +102,7 @@ pub struct BlockchainModule {
/// Blocks Databases /// Blocks Databases
pub blocks_databases: BlocksV10DBs, pub blocks_databases: BlocksV10DBs,
/// Currency databases /// Currency databases
currency_databases: CurrencyV10DBs, currency_databases: CurrencyV10DBs<FileBackend>,
/// The block under construction /// The block under construction
pub pending_block: Option<Box<BlockDocument>>, pub pending_block: Option<Box<BlockDocument>>,
/// Current state of all forks /// Current state of all forks
...@@ -154,7 +156,7 @@ impl BlockchainModule { ...@@ -154,7 +156,7 @@ impl BlockchainModule {
// Open databases // Open databases
let blocks_databases = BlocksV10DBs::open(&db_path, false); let blocks_databases = BlocksV10DBs::open(&db_path, false);
let wot_databases = WotsV10DBs::open(&db_path, false); let wot_databases = WotsV10DBs::open(&db_path, false);
let currency_databases = CurrencyV10DBs::open(&db_path, false); let currency_databases = CurrencyV10DBs::<FileBackend>::open(&db_path);
// Get current blockstamp // Get current blockstamp
let current_blockstamp = duniter_dal::block::get_current_blockstamp(&blocks_databases) let current_blockstamp = duniter_dal::block::get_current_blockstamp(&blocks_databases)
......
...@@ -15,12 +15,10 @@ ...@@ -15,12 +15,10 @@
extern crate num_cpus; extern crate num_cpus;
extern crate pbr; extern crate pbr;
extern crate rustbreak;
extern crate sqlite; extern crate sqlite;
extern crate threadpool; extern crate threadpool;
use self::pbr::ProgressBar; use self::pbr::ProgressBar;
use self::rustbreak::{deser::Bincode, MemoryDatabase};
use self::threadpool::ThreadPool; use self::threadpool::ThreadPool;
use duniter_crypto::keys::*; use duniter_crypto::keys::*;
use duniter_dal::currency_params::CurrencyParameters; use duniter_dal::currency_params::CurrencyParameters;
...@@ -30,6 +28,7 @@ use duniter_documents::{BlockHash, BlockId, Hash}; ...@@ -30,6 +28,7 @@ use duniter_documents::{BlockHash, BlockId, Hash};
use duniter_network::NetworkBlock; use duniter_network::NetworkBlock;
use duniter_wotb::operations::file::FileFormater; use duniter_wotb::operations::file::FileFormater;
use duniter_wotb::{NodeId, WebOfTrust}; use duniter_wotb::{NodeId, WebOfTrust};
use rustbreak::{deser::Bincode, MemoryDatabase};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::fs; use std::fs;
use std::ops::Deref; use std::ops::Deref;
...@@ -437,7 +436,7 @@ pub fn sync_ts( ...@@ -437,7 +436,7 @@ pub fn sync_ts(
let tx_job_begin = SystemTime::now(); let tx_job_begin = SystemTime::now();
// Open databases // Open databases
let db_path = duniter_conf::get_blockchain_db_path(&profile_copy, &currency_copy); let db_path = duniter_conf::get_blockchain_db_path(&profile_copy, &currency_copy);
let databases = CurrencyV10DBs::open(&db_path, false); let databases = CurrencyV10DBs::<FileBackend>::open(&db_path);
// Listen db requets // Listen db requets
let mut all_wait_duration = Duration::from_millis(0); let mut all_wait_duration = Duration::from_millis(0);
......
...@@ -54,7 +54,7 @@ use duniter_documents::blockchain::v10::documents::transaction::*; ...@@ -54,7 +54,7 @@ use duniter_documents::blockchain::v10::documents::transaction::*;
use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash, PreviousBlockstamp}; use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash, PreviousBlockstamp};
use duniter_wotb::operations::file::FileFormater; use duniter_wotb::operations::file::FileFormater;
use duniter_wotb::{NodeId, WebOfTrust}; use duniter_wotb::{NodeId, WebOfTrust};
use rustbreak::backend::{FileBackend, MemoryBackend}; use rustbreak::backend::{Backend, FileBackend, MemoryBackend};
use rustbreak::error::{RustbreakError, RustbreakErrorKind}; use rustbreak::error::{RustbreakError, RustbreakErrorKind};
use rustbreak::{deser::Bincode, Database, FileDatabase, MemoryDatabase}; use rustbreak::{deser::Bincode, Database, FileDatabase, MemoryDatabase};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
...@@ -166,20 +166,31 @@ impl WotsV10DBs { ...@@ -166,20 +166,31 @@ impl WotsV10DBs {
#[derive(Debug)] #[derive(Debug)]
/// Set of databases storing currency information /// Set of databases storing currency information
pub struct CurrencyV10DBs { pub struct CurrencyV10DBs<B: Backend + Debug> {
/// Store all DU sources /// Store all DU sources
pub du_db: BinFileDB<DUsV10Datas>, pub du_db: BinDB<DUsV10Datas, B>,
/// Store all Transactions /// Store all Transactions
pub tx_db: BinFileDB<TxV10Datas>, pub tx_db: BinDB<TxV10Datas, B>,
/// Store all UTXOs /// Store all UTXOs
pub utxos_db: BinFileDB<UTXOsV10Datas>, pub utxos_db: BinDB<UTXOsV10Datas, B>,
/// Store balances of all address (and theirs UTXOs indexs) /// Store balances of all address (and theirs UTXOs indexs)
pub balances_db: BinFileDB<BalancesV10Datas>, pub balances_db: BinDB<BalancesV10Datas, B>,
} }
impl CurrencyV10DBs { impl CurrencyV10DBs<MemoryBackend> {
pub fn open_memory_mode() -> CurrencyV10DBs<MemoryBackend> {
CurrencyV10DBs {
du_db: open_memory_db::<DUsV10Datas>().expect("Fail to open DUsV10DB"),
tx_db: open_memory_db::<TxV10Datas>().expect("Fail to open TxV10DB"),
utxos_db: open_memory_db::<UTXOsV10Datas>().expect("Fail to open UTXOsV10DB"),
balances_db: open_memory_db::<BalancesV10Datas>().expect("Fail to open BalancesV10DB"),
}
}
}
impl CurrencyV10DBs<FileBackend> {
/// Open currency databases from their respective files /// Open currency databases from their respective files
pub fn open(db_path: &PathBuf, _memory_mode: bool) -> CurrencyV10DBs { pub fn open(db_path: &PathBuf) -> CurrencyV10DBs<FileBackend> {
CurrencyV10DBs { CurrencyV10DBs {
du_db: open_db::<DUsV10Datas>(&db_path, "du.db").expect("Fail to open DUsV10DB"), du_db: open_db::<DUsV10Datas>(&db_path, "du.db").expect("Fail to open DUsV10DB"),
tx_db: open_db::<TxV10Datas>(&db_path, "tx.db").expect("Fail to open TxV10DB"), tx_db: open_db::<TxV10Datas>(&db_path, "tx.db").expect("Fail to open TxV10DB"),
......
...@@ -16,13 +16,15 @@ ...@@ -16,13 +16,15 @@
use duniter_crypto::keys::PubKey; use duniter_crypto::keys::PubKey;
use duniter_documents::blockchain::v10::documents::transaction::*; use duniter_documents::blockchain::v10::documents::transaction::*;
use duniter_documents::BlockId; use duniter_documents::BlockId;
use rustbreak::backend::Backend;
use sources::SourceAmount; use sources::SourceAmount;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
use *; use *;
pub fn create_du( pub fn create_du<B: Backend + Debug>(
du_db: &BinFileDB<DUsV10Datas>, du_db: &BinDB<DUsV10Datas, B>,
balances_db: &BinFileDB<BalancesV10Datas>, balances_db: &BinDB<BalancesV10Datas, B>,
du_amount: &SourceAmount, du_amount: &SourceAmount,
du_block_id: &BlockId, du_block_id: &BlockId,
members: &[PubKey], members: &[PubKey],
......
...@@ -8,7 +8,9 @@ use duniter_documents::blockchain::v10::documents::identity::IdentityDocument; ...@@ -8,7 +8,9 @@ use duniter_documents::blockchain::v10::documents::identity::IdentityDocument;
use duniter_documents::Blockstamp; use duniter_documents::Blockstamp;
use duniter_wotb::NodeId; use duniter_wotb::NodeId;
use identity::DALIdentity; use identity::DALIdentity;
use rustbreak::backend::Backend;
use sources::SourceAmount; use sources::SourceAmount;
use std::fmt::Debug;
use std::ops::Deref; use std::ops::Deref;
use *; use *;
...@@ -181,10 +183,10 @@ pub enum CurrencyDBsWriteQuery { ...@@ -181,10 +183,10 @@ pub enum CurrencyDBsWriteQuery {
} }
impl CurrencyDBsWriteQuery { impl CurrencyDBsWriteQuery {
pub fn apply(&self, databases: &CurrencyV10DBs) -> Result<(), DALError> { pub fn apply<B: Backend + Debug>(&self, databases: &CurrencyV10DBs<B>) -> Result<(), DALError> {
match *self { match *self {
CurrencyDBsWriteQuery::WriteTx(ref tx_doc) => { CurrencyDBsWriteQuery::WriteTx(ref tx_doc) => {
super::transaction::apply_and_write_tx( super::transaction::apply_and_write_tx::<B>(
&databases.tx_db, &databases.tx_db,
&databases.utxos_db, &databases.utxos_db,
&databases.du_db, &databases.du_db,
...@@ -193,7 +195,7 @@ impl CurrencyDBsWriteQuery { ...@@ -193,7 +195,7 @@ impl CurrencyDBsWriteQuery {
)?; )?;
} }
CurrencyDBsWriteQuery::CreateDU(ref du_amount, ref block_id, ref members) => { CurrencyDBsWriteQuery::CreateDU(ref du_amount, ref block_id, ref members) => {
super::dividend::create_du( super::dividend::create_du::<B>(
&databases.du_db, &databases.du_db,
&databases.balances_db, &databases.balances_db,
du_amount, du_amount,
......
...@@ -14,7 +14,9 @@ ...@@ -14,7 +14,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use duniter_documents::blockchain::v10::documents::transaction::*; use duniter_documents::blockchain::v10::documents::transaction::*;
use rustbreak::backend::Backend;
use sources::{SourceAmount, SourceIndexV10, UTXOIndexV10, UTXOV10}; use sources::{SourceAmount, SourceIndexV10, UTXOIndexV10, UTXOV10};
use std::fmt::Debug;
use *; use *;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
...@@ -35,11 +37,11 @@ pub struct DALTxV10 { ...@@ -35,11 +37,11 @@ pub struct DALTxV10 {
sources_destroyed: HashSet<UTXOIndexV10>, sources_destroyed: HashSet<UTXOIndexV10>,
} }
pub fn apply_and_write_tx( pub fn apply_and_write_tx<B: Backend + Debug>(
tx_db: &BinFileDB<TxV10Datas>, tx_db: &BinDB<TxV10Datas, B>,
utxos_db: &BinFileDB<UTXOsV10Datas>, utxos_db: &BinDB<UTXOsV10Datas, B>,
dus_db: &BinFileDB<DUsV10Datas>, dus_db: &BinDB<DUsV10Datas, B>,
balances_db: &BinFileDB<BalancesV10Datas>, balances_db: &BinDB<BalancesV10Datas, B>,
tx_doc: &TransactionDocument, tx_doc: &TransactionDocument,
) -> Result<(), DALError> { ) -> Result<(), DALError> {
let mut tx_doc = tx_doc.clone(); let mut tx_doc = tx_doc.clone();
...@@ -112,11 +114,43 @@ pub fn apply_and_write_tx( ...@@ -112,11 +114,43 @@ pub fn apply_and_write_tx(
} }
new_balances_consumed_adress new_balances_consumed_adress
.push((conditions.clone(), (new_balance, new_sources_index))); .push((conditions.clone(), (new_balance, new_sources_index)));
} else {
panic!("Apply Tx : try to consume a source, but the owner address is not found in balances db : {:?}", conditions)
} }
} }
new_balances_consumed_adress new_balances_consumed_adress
})?; })?;
// Write new balance of consumed adress
balances_db.write(|db| {
for (conditions, (balance, sources_index)) in new_balances_consumed_adress {
db.insert(conditions, (balance, sources_index));
}
})?;
// Remove consumed sources
for source_index in consumed_sources.keys() {
if let SourceIndexV10::UTXO(utxo_index) = source_index {
utxos_db.write(|db| {
db.remove(utxo_index);
})?;
} else if let SourceIndexV10::DU(pubkey, block_id) = source_index {
let mut pubkey_dus: HashSet<BlockId> =
dus_db.read(|db| db.get(&pubkey).cloned().unwrap_or_default())?;
pubkey_dus.remove(block_id);
dus_db.write(|db| {
db.insert(*pubkey, pubkey_dus);
})?;
}
}
// Index created sources // Index created sources
/*let mut created_utxos: Vec<UTXOV10> = Vec::new();
let mut output_index = 0;
for output in tx_doc.get_outputs() {
created_utxos.push(UTXOV10(
UTXOIndexV10(tx_hash, TxIndex(output_index)),
output.clone(),
));
output_index += 1;
}*/
let created_utxos: Vec<UTXOV10> = tx_doc let created_utxos: Vec<UTXOV10> = tx_doc
.get_outputs() .get_outputs()
.iter() .iter()
...@@ -152,32 +186,14 @@ pub fn apply_and_write_tx( ...@@ -152,32 +186,14 @@ pub fn apply_and_write_tx(
} }
new_balances_supplied_adress new_balances_supplied_adress
})?; })?;
// Remove consumed sources
for source_index in consumed_sources.keys() {
if let SourceIndexV10::UTXO(utxo_index) = source_index {
utxos_db.write(|db| {
db.remove(utxo_index);
})?;
} else if let SourceIndexV10::DU(pubkey, block_id) = source_index {
let mut pubkey_dus: HashSet<BlockId> =
dus_db.read(|db| db.get(&pubkey).cloned().unwrap_or_default())?;
pubkey_dus.remove(block_id);
dus_db.write(|db| {
db.insert(*pubkey, pubkey_dus);
})?;
}
}
// Insert created UTXOs // Insert created UTXOs
utxos_db.write(|db| { utxos_db.write(|db| {
for utxo_v10 in created_utxos { for utxo_v10 in created_utxos {
db.insert(utxo_v10.0, utxo_v10.1); db.insert(utxo_v10.0, utxo_v10.1);
} }
})?; })?;
// Write new balance of consumed adress and supplied adress // Write new balance of supplied adress
balances_db.write(|db| { balances_db.write(|db| {
for (conditions, (balance, sources_index)) in new_balances_consumed_adress {
db.insert(conditions, (balance, sources_index));
}
for (conditions, (balance, sources_index)) in new_balances_supplied_adress { for (conditions, (balance, sources_index)) in new_balances_supplied_adress {
db.insert(conditions, (balance, sources_index)); db.insert(conditions, (balance, sources_index));
} }
...@@ -195,3 +211,127 @@ pub fn apply_and_write_tx( ...@@ -195,3 +211,127 @@ pub fn apply_and_write_tx(
})?; })?;
Ok(()) Ok(())
} }
#[cfg(test)]
mod tests {
use super::*;
use duniter_documents::blockchain::{Document, DocumentBuilder, VerificationResult};
fn build_first_tx_of_g1() -> TransactionDocument {
let pubkey = PubKey::Ed25519(
ed25519::PublicKey::from_base58("2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ")
.unwrap(),
);
let sig = Sig::Ed25519(ed25519::Signature::from_base64(
"fAH5Gor+8MtFzQZ++JaJO6U8JJ6+rkqKtPrRr/iufh3MYkoDGxmjzj6jCADQL+hkWBt8y8QzlgRkz0ixBcKHBw==",
).unwrap());
let block = Blockstamp::from_string(
"50-00001DAA4559FEDB8320D1040B0F22B631459F36F237A0D9BC1EB923C12A12E7",
).unwrap();
let builder = TransactionDocumentBuilder {
currency: "g1",
blockstamp: &block,
locktime: &0,
issuers: &vec![pubkey],
inputs: &vec![
TransactionInput::parse_from_str(
"1000:0:D:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ:1",
).expect("fail to parse input !"),
],
unlocks: &vec![
TransactionInputUnlocks::parse_from_str("0:SIG(0)")
.expect("fail to parse unlock !"),
],
outputs: &vec![
TransactionOutput::parse_from_str(
"1:0:SIG(Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm)",
).expect("fail to parse output !"),
TransactionOutput::parse_from_str(
"999:0:SIG(2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ)",
).expect("fail to parse output !"),
],
comment: "TEST",
};
builder.build_with_signature(vec![sig])
}
#[test]
fn apply_one_tx() {
// Get document of first g1 transaction
let tx_doc = build_first_tx_of_g1();
assert_eq!(tx_doc.verify_signatures(), VerificationResult::Valid());
// Get pubkey of receiver
let tortue_pubkey = PubKey::Ed25519(
ed25519::PublicKey::from_base58("Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm")
.unwrap(),
);
// Open currencys_db in memory mode
let currency_dbs = CurrencyV10DBs::open_memory_mode();
// Create first g1 DU for cgeek and tortue
writers::dividend::create_du(
&currency_dbs.du_db,
&currency_dbs.balances_db,
&SourceAmount(TxAmount(1000), TxBase(0)),
&BlockId(1),
&vec![tx_doc.issuers()[0], tortue_pubkey],
).expect("Fail to create first g1 DU !");
// Check members balance
let cgeek_new_balance = currency_dbs
.balances_db
.read(|db| {
db.get(&TransactionOutputConditionGroup::Single(
TransactionOutputCondition::Sig(tx_doc.issuers()[0]),
)).cloned()
})
.expect("Fail to read cgeek new balance")
.expect("Error : cgeek is not referenced in balances_db !");
assert_eq!(cgeek_new_balance.0, SourceAmount(TxAmount(1000), TxBase(0)));
let tortue_new_balance = currency_dbs
.balances_db
.read(|db| {
db.get(&TransactionOutputConditionGroup::Single(
TransactionOutputCondition::Sig(tortue_pubkey),
)).cloned()
})
.expect("Fail to read receiver new balance")
.expect("Error : receiver is not referenced in balances_db !");
assert_eq!(
tortue_new_balance.0,
SourceAmount(TxAmount(1000), TxBase(0))
);
// Apply first g1 transaction
apply_and_write_tx(
&currency_dbs.tx_db,
&currency_dbs.utxos_db,
&currency_dbs.du_db,
&currency_dbs.balances_db,
&tx_doc,
).expect("Fail to apply first g1 tx");
// Check issuer new balance
let cgeek_new_balance = currency_dbs
.balances_db
.read(|db| {
db.get(&TransactionOutputConditionGroup::Single(
TransactionOutputCondition::Sig(tx_doc.issuers()[0]),
)).cloned()
})
.expect("Fail to read cgeek new balance")
.expect("Error : cgeek is not referenced in balances_db !");
assert_eq!(cgeek_new_balance.0, SourceAmount(TxAmount(999), TxBase(0)));
// Check receiver new balance
let receiver_new_balance = currency_dbs
.balances_db
.read(|db| {
db.get(&TransactionOutputConditionGroup::Single(
TransactionOutputCondition::Sig(tortue_pubkey),
)).cloned()
})
.expect("Fail to read receiver new balance")
.expect("Error : receiver is not referenced in balances_db !");
assert_eq!(
receiver_new_balance.0,
SourceAmount(TxAmount(1001), TxBase(0))
);
}
}
...@@ -264,10 +264,14 @@ pub struct BlockDocument { ...@@ -264,10 +264,14 @@ pub struct BlockDocument {
impl BlockDocument { impl BlockDocument {
/// Return previous blockstamp /// Return previous blockstamp
pub fn previous_blockstamp(&self) -> Blockstamp { pub fn previous_blockstamp(&self) -> Blockstamp {
if self.number.0 > 0 {
Blockstamp { Blockstamp {
id: BlockId(self.number.0 - 1), id: BlockId(self.number.0 - 1),
hash: BlockHash(self.previous_hash), hash: BlockHash(self.previous_hash),
} }
} else {
Blockstamp::default()
}
} }
/// Compute inner hash /// Compute inner hash
pub fn compute_inner_hash(&mut self) { pub fn compute_inner_hash(&mut self) {
...@@ -537,7 +541,6 @@ mod tests { ...@@ -537,7 +541,6 @@ mod tests {
assert_eq!( assert_eq!(
block block
.inner_hash .inner_hash
.hash
.expect("Try to get inner_hash of an uncompleted or reduce block !") .expect("Try to get inner_hash of an uncompleted or reduce block !")
.to_hex(), .to_hex(),
"95948AC4D45E46DA07CE0713EDE1CE0295C227EE4CA5557F73F56B7DD46FE89C" "95948AC4D45E46DA07CE0713EDE1CE0295C227EE4CA5557F73F56B7DD46FE89C"
...@@ -578,7 +581,6 @@ Nonce: " ...@@ -578,7 +581,6 @@ Nonce: "
block.compute_hash(); block.compute_hash();
assert_eq!( assert_eq!(
block block
.hash
.hash .hash
.expect("Try to get hash of an uncompleted or reduce block !") .expect("Try to get hash of an uncompleted or reduce block !")
.0 .0
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment