diff --git a/Cargo.lock b/Cargo.lock index e75dd997..fb6f845c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2108,6 +2108,7 @@ dependencies = [ "criterion", "futures", "hex", + "lazy_static", "log", "merkle", "mockall", diff --git a/VERSION b/VERSION index f5ab2b1d..860e0571 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.49 +0.2.50 diff --git a/saito-core/Cargo.toml b/saito-core/Cargo.toml index 2cf2698d..f3f102eb 100644 --- a/saito-core/Cargo.toml +++ b/saito-core/Cargo.toml @@ -6,8 +6,19 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tokio = { version = "1.37.0", features = ["sync", "tokio-macros", "test-util", "macros", "tracing"] } -secp256k1 = { version = "0.29.0", features = ["rand", "hashes", "global-context", "serde"] } +tokio = { version = "1.37.0", features = [ + "sync", + "tokio-macros", + "test-util", + "macros", + "tracing", +] } +secp256k1 = { version = "0.29.0", features = [ + "rand", + "hashes", + "global-context", + "serde", +] } rand = { version = "0.8.5", features = ["getrandom"] } pretty_env_logger = "0.5.0" byteorder = "1.5.0" @@ -35,6 +46,7 @@ tokio = { version = "1.37.0", features = ["full"] } criterion = { version = "0.5.1", features = ["default", "html_reports"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } mockall = "0.13.1" +lazy_static = "1.4.0" [features] default = [] diff --git a/saito-core/benches/benchmarks/misc.rs b/saito-core/benches/benchmarks/misc.rs index 557268cf..c73a032c 100644 --- a/saito-core/benches/benchmarks/misc.rs +++ b/saito-core/benches/benchmarks/misc.rs @@ -35,8 +35,8 @@ fn join() { assert!(buffer.len() > 0); } -fn gen_random_bytes(len: u64) { - let buf = generate_random_bytes(len); +async fn gen_random_bytes(len: u64) { + let buf = generate_random_bytes(len).await; assert_eq!(buf.len(), len as usize); } diff --git a/saito-core/src/core/consensus/block.rs b/saito-core/src/core/consensus/block.rs index cb4cd97c..fa2b3ab4 100644 --- a/saito-core/src/core/consensus/block.rs +++ b/saito-core/src/core/consensus/block.rs @@ -2178,6 +2178,7 @@ impl Block { utxoset: &UtxoSet, configs: &(dyn Configuration + Send + Sync), storage: &Storage, + validate_against_utxo: bool, ) -> bool { // // TODO SYNC : Add the code to check whether this is the genesis block and skip validations @@ -2228,7 +2229,7 @@ impl Block { // // total_fees // - if cv.total_fees != self.total_fees { + if validate_against_utxo && cv.total_fees != self.total_fees { error!( "total_fees error: {:?} expected : {:?}", self.total_fees, cv.total_fees @@ -2250,7 +2251,7 @@ impl Block { // // total_fees_atr // - if cv.total_fees_atr != self.total_fees_atr { + if validate_against_utxo && cv.total_fees_atr != self.total_fees_atr { error!( "total_fees_atr error: {:?} expected : {:?}", self.total_fees_atr, cv.total_fees_atr @@ -2261,7 +2262,7 @@ impl Block { // // avg_total_fees // - if cv.avg_total_fees != self.avg_total_fees { + if validate_against_utxo && cv.avg_total_fees != self.avg_total_fees { error!( "avg_total_fees error: {:?} expected : {:?}", self.avg_total_fees, cv.avg_total_fees @@ -2283,7 +2284,7 @@ impl Block { // // avg_total_fees_atr // - if cv.avg_total_fees_atr != self.avg_total_fees_atr { + if validate_against_utxo && cv.avg_total_fees_atr != self.avg_total_fees_atr { error!( "avg_total_fees_atr error: {:?} expected : {:?}", self.avg_total_fees_atr, cv.avg_total_fees_atr @@ -2426,7 +2427,9 @@ impl Block { // // consensus values -> difficulty (mining/payout unlock difficulty) // - if cv.avg_nolan_rebroadcast_per_block != self.avg_nolan_rebroadcast_per_block { + if validate_against_utxo + && cv.avg_nolan_rebroadcast_per_block != self.avg_nolan_rebroadcast_per_block + { error!( "ERROR 202392: avg_nolan_rebroadcast_per_block is invalid. expected: {:?} vs actual : {:?}", cv.avg_nolan_rebroadcast_per_block, self.avg_nolan_rebroadcast_per_block @@ -2620,7 +2623,7 @@ impl Block { // which we counted in the generate_metadata() function, with the // expected number given the consensus values we calculated earlier. // - if cv.total_rebroadcast_slips != self.total_rebroadcast_slips { + if validate_against_utxo && cv.total_rebroadcast_slips != self.total_rebroadcast_slips { error!( "ERROR 624442: rebroadcast slips total incorrect. expected : {:?} actual : {:?}", cv.total_rebroadcast_slips, self.total_rebroadcast_slips @@ -2635,7 +2638,7 @@ impl Block { // ); // return false; //} - if cv.rebroadcast_hash != self.rebroadcast_hash { + if validate_against_utxo && cv.rebroadcast_hash != self.rebroadcast_hash { error!("ERROR 123422: hash of rebroadcast transactions incorrect. expected : {:?} actual : {:?}",cv.rebroadcast_hash.to_hex(), self.rebroadcast_hash.to_hex()); return false; } @@ -2724,7 +2727,7 @@ impl Block { self.transactions.len() ); let transactions_valid = iterate!(self.transactions, 100) - .all(|tx: &Transaction| tx.validate(utxoset, blockchain)); + .all(|tx: &Transaction| tx.validate(utxoset, blockchain, validate_against_utxo)); if !transactions_valid { error!("ERROR 579128: Invalid transactions found, block validation failed"); diff --git a/saito-core/src/core/consensus/blockchain.rs b/saito-core/src/core/consensus/blockchain.rs index 46625292..a39eeecd 100644 --- a/saito-core/src/core/consensus/blockchain.rs +++ b/saito-core/src/core/consensus/blockchain.rs @@ -596,7 +596,7 @@ impl Blockchain { // TODO : what other types should be added back to the mempool if tx.transaction_type == TransactionType::Normal { // TODO : is there a way to not validate these again ? - return tx.validate(&self.utxoset, self); + return tx.validate(&self.utxoset, self, true); } false }) @@ -1222,7 +1222,28 @@ impl Blockchain { // || block // .validate(self, &self.utxoset, configs, storage, &wallet) // .await; - does_block_validate = block.validate(self, &self.utxoset, configs, storage).await; + let has_first_block = self + .blockring + .get_longest_chain_block_hash_at_block_id(1) + .is_some(); + let has_genesis_period_of_blocks = self.get_latest_block_id() + > configs.get_consensus_config().unwrap().genesis_period + self.genesis_block_id; + let validate_against_utxo = has_first_block || has_genesis_period_of_blocks; + + does_block_validate = block + .validate(self, &self.utxoset, configs, storage, validate_against_utxo) + .await; + + if !does_block_validate { + debug!("validate against utxo: {:?}, has_first_block : {:?}, has_genesis_period_of_blocks : {:?}", + validate_against_utxo,has_first_block,has_genesis_period_of_blocks); + debug!("latest_block_id = {:?}", self.get_latest_block_id()); + debug!("genesis_block_id = {:?}", self.genesis_block_id); + debug!( + "genesis_period = {:?}", + configs.get_consensus_config().unwrap().genesis_period + ); + } } let mut wallet_updated = WALLET_NOT_UPDATED; diff --git a/saito-core/src/core/consensus/golden_ticket.rs b/saito-core/src/core/consensus/golden_ticket.rs index e1eaa7dd..7aa9d93e 100644 --- a/saito-core/src/core/consensus/golden_ticket.rs +++ b/saito-core/src/core/consensus/golden_ticket.rs @@ -99,19 +99,19 @@ mod tests { assert_eq!(GoldenTicket::validate_hashing_difficulty(&hash2, 5), false); } - #[test] - fn golden_ticket_extremes_test() { + #[tokio::test] + async fn golden_ticket_extremes_test() { let keys = generate_keys(); let wallet = Wallet::new(keys.1, keys.0); - let random = hash(&generate_random_bytes(32)); - let target = hash(&random.to_vec()); + let random = hash(&generate_random_bytes(32).await); + let target = hash(random.as_ref()); let public_key = wallet.public_key; let gt = GoldenTicket::create(target, random, public_key); - assert_eq!(gt.validate(0), true); - assert_eq!(gt.validate(256), false); + assert!(gt.validate(0)); + assert!(!gt.validate(256)); } #[test] fn gt_against_slr() { diff --git a/saito-core/src/core/consensus/mempool.rs b/saito-core/src/core/consensus/mempool.rs index 834bc841..d184c653 100644 --- a/saito-core/src/core/consensus/mempool.rs +++ b/saito-core/src/core/consensus/mempool.rs @@ -113,7 +113,7 @@ impl Mempool { public_key = wallet.public_key; transaction.generate(&public_key, 0, 0); - tx_valid = transaction.validate(&blockchain.utxoset, blockchain); + tx_valid = transaction.validate(&blockchain.utxoset, blockchain, true); } // validate diff --git a/saito-core/src/core/consensus/peers/peer.rs b/saito-core/src/core/consensus/peers/peer.rs index 1ce32c1b..8aadbc91 100644 --- a/saito-core/src/core/consensus/peers/peer.rs +++ b/saito-core/src/core/consensus/peers/peer.rs @@ -147,7 +147,7 @@ impl Peer { debug!("initiating handshake : {:?}", self.index); let challenge = HandshakeChallenge { - challenge: generate_random_bytes(32).try_into().unwrap(), + challenge: generate_random_bytes(32).await.try_into().unwrap(), }; debug!( "generated challenge : {:?} for peer : {:?}", @@ -193,7 +193,7 @@ impl Peer { let response = HandshakeResponse { public_key: wallet.public_key, signature: sign(challenge.challenge.as_slice(), &wallet.private_key), - challenge: generate_random_bytes(32).try_into().unwrap(), + challenge: generate_random_bytes(32).await.try_into().unwrap(), is_lite, block_fetch_url, services: io_handler.get_my_services(), diff --git a/saito-core/src/core/consensus/transaction.rs b/saito-core/src/core/consensus/transaction.rs index 437ca599..9f068688 100644 --- a/saito-core/src/core/consensus/transaction.rs +++ b/saito-core/src/core/consensus/transaction.rs @@ -846,7 +846,12 @@ impl Transaction { self.signature = sign(&buffer, private_key); } - pub fn validate(&self, utxoset: &UtxoSet, blockchain: &Blockchain) -> bool { + pub fn validate( + &self, + utxoset: &UtxoSet, + blockchain: &Blockchain, + validate_against_utxo: bool, + ) -> bool { // Fee Transactions are validated in the block class. There can only // be one per block, and they are checked by ensuring the transaction hash // matches our self-generated safety check. We do not need to validate @@ -1049,10 +1054,14 @@ impl Transaction { return false; } - // trace!("validating transaction against utxo ..."); - let inputs_validate = self.validate_against_utxoset(utxoset); - // trace!("validated transaction against utxo !"); - inputs_validate + return if validate_against_utxo { + // trace!("validating transaction against utxo ..."); + let inputs_validate = self.validate_against_utxoset(utxoset); + // trace!("validated transaction against utxo !"); + inputs_validate + } else { + true + }; } pub fn validate_against_utxoset(&self, utxoset: &UtxoSet) -> bool { diff --git a/saito-core/src/core/mining_thread.rs b/saito-core/src/core/mining_thread.rs index 0cc0e9b3..1ee96de4 100644 --- a/saito-core/src/core/mining_thread.rs +++ b/saito-core/src/core/mining_thread.rs @@ -59,7 +59,7 @@ impl MiningThread { info!("node public key = {:?}", self.public_key.to_base58()); } - let random_bytes = hash(&generate_random_bytes(32)); + let random_bytes = hash(&generate_random_bytes(32).await); // The new way of validation will be wasting a GT instance if the validation fails // old way used a static method instead let gt = GoldenTicket::create(self.target, random_bytes, self.public_key); diff --git a/saito-core/src/core/msg/block_request.rs b/saito-core/src/core/msg/block_request.rs index e1188616..0f53ee3e 100644 --- a/saito-core/src/core/msg/block_request.rs +++ b/saito-core/src/core/msg/block_request.rs @@ -38,12 +38,12 @@ mod tests { use crate::core::util::crypto::generate_random_bytes; use crate::core::util::serialize::Serialize; - #[test] - fn test_serialize_with_full_fork_id() { + #[tokio::test] + async fn test_serialize_with_full_fork_id() { let request = BlockchainRequest { latest_block_id: 10, - latest_block_hash: generate_random_bytes(32).try_into().unwrap(), - fork_id: generate_random_bytes(32).try_into().unwrap(), + latest_block_hash: generate_random_bytes(32).await.try_into().unwrap(), + fork_id: generate_random_bytes(32).await.try_into().unwrap(), }; let buffer = request.serialize(); assert_eq!(buffer.len(), 72); diff --git a/saito-core/src/core/util/crypto.rs b/saito-core/src/core/util/crypto.rs index 3f5fe450..ed5e3c95 100644 --- a/saito-core/src/core/util/crypto.rs +++ b/saito-core/src/core/util/crypto.rs @@ -2,9 +2,10 @@ use crate::core::defs::{PrintForLog, SaitoHash, SaitoPrivateKey, SaitoPublicKey, use blake3::Hasher; use block_modes::BlockMode; pub use merkle::MerkleTree; -use rand::{thread_rng, Rng}; +use rand::{thread_rng, Rng, SeedableRng}; use secp256k1::ecdsa; pub use secp256k1::{Message, PublicKey, SecretKey, SECP256K1}; +use tokio::sync::Mutex; // type Aes128Cbc = Cbc; @@ -63,24 +64,38 @@ pub fn generate_keypair_from_private_key(slice: &[u8]) -> (SaitoPublicKey, Saito (public_key.serialize(), secret_bytes) } -pub fn sign_blob<'a, 'b>( - vbytes: &'a mut Vec, - private_key: &'b SaitoPrivateKey, -) -> &'a mut Vec { +pub fn sign_blob<'a>(vbytes: &'a mut Vec, private_key: &SaitoPrivateKey) -> &'a mut Vec { let sig = sign(&hash(vbytes.as_ref()), private_key); vbytes.extend(&sig); vbytes } +#[cfg(test)] +lazy_static::lazy_static! { + pub static ref TEST_RNG: Mutex = Mutex::new(create_test_rng()); +} + +fn create_test_rng() -> rand::rngs::StdRng { + rand::rngs::StdRng::from_seed([0; 32]) +} -pub fn generate_random_bytes(len: u64) -> Vec { +pub async fn generate_random_bytes(len: u64) -> Vec { if len == 0 { let x: Vec = vec![]; return x; } // Don't have to be cryptographically secure, since we only need a random hash and only check the signature of that in return - let mut rng = thread_rng(); - (0..len).map(|_| rng.gen::()).collect() + #[cfg(not(test))] + { + let mut rng = thread_rng(); + (0..len).map(|_| rng.gen::()).collect() + } + #[cfg(test)] + { + // let mut rng = TEST_RNG.clone(); + let mut rng = TEST_RNG.lock().await; + (0..len).map(|_| rng.gen::()).collect() + } } pub fn hash(data: &[u8]) -> SaitoHash { diff --git a/saito-core/src/core/util/test/node_tester.rs b/saito-core/src/core/util/test/node_tester.rs index 9c2b117b..b14d332d 100644 --- a/saito-core/src/core/util/test/node_tester.rs +++ b/saito-core/src/core/util/test/node_tester.rs @@ -607,10 +607,11 @@ pub mod test { .for_each(|(key, _)| { let slip = Slip::parse_slip_from_utxokey(key).unwrap(); info!( - "Utxo : {:?} : {} : {:?}", + "Utxo : {:?} : {} : {:?}, block : {:?}", slip.public_key.to_base58(), slip.amount, - slip.slip_type + slip.slip_type, + slip.block_id ); }); diff --git a/saito-core/src/core/util/test/test_manager.rs b/saito-core/src/core/util/test/test_manager.rs index 1579f4c1..53f79234 100644 --- a/saito-core/src/core/util/test/test_manager.rs +++ b/saito-core/src/core/util/test/test_manager.rs @@ -728,12 +728,12 @@ pub mod test { public_key = wallet.public_key; } - let mut random_bytes = hash(&generate_random_bytes(32)); + let mut random_bytes = hash(&generate_random_bytes(32).await); let mut gt = GoldenTicket::create(block_hash, random_bytes, public_key); while !gt.validate(block_difficulty) { - random_bytes = hash(&generate_random_bytes(32)); + random_bytes = hash(&generate_random_bytes(32).await); gt = GoldenTicket::create(block_hash, random_bytes, public_key); } diff --git a/saito-core/src/core/verification_thread.rs b/saito-core/src/core/verification_thread.rs index fd33228b..ab8a0712 100644 --- a/saito-core/src/core/verification_thread.rs +++ b/saito-core/src/core/verification_thread.rs @@ -49,7 +49,8 @@ impl VerificationThread { public_key = wallet.public_key; transaction.generate(&public_key, 0, 0); - if !transaction.validate(&blockchain.utxoset, &blockchain) { + // TODO : should we skip validation against utxo if we don't have the full utxo ? + if !transaction.validate(&blockchain.utxoset, &blockchain, true) { debug!( "transaction : {:?} not valid", transaction.signature.to_hex() @@ -82,7 +83,7 @@ impl VerificationThread { .filter_map(|mut transaction| { transaction.generate(&public_key, 0, 0); - if !transaction.validate(&blockchain.utxoset, &blockchain) { + if !transaction.validate(&blockchain.utxoset, &blockchain, true) { debug!( "transaction : {:?} not valid", transaction.signature.to_hex() diff --git a/saito-rust/src/network_controller.rs b/saito-rust/src/network_controller.rs index 0d570d6d..6058d425 100644 --- a/saito-rust/src/network_controller.rs +++ b/saito-rust/src/network_controller.rs @@ -956,7 +956,7 @@ mod tests { let mut socket = result.0; let challenge = HandshakeChallenge { - challenge: generate_random_bytes(32).try_into().unwrap(), + challenge: generate_random_bytes(32).await.try_into().unwrap(), }; // challenge_for_peer = Some(challenge.challenge); let message = Message::HandshakeChallenge(challenge); diff --git a/saito-spammer/src/transaction_generator.rs b/saito-spammer/src/transaction_generator.rs index 884af1d4..6989f1a6 100644 --- a/saito-spammer/src/transaction_generator.rs +++ b/saito-spammer/src/transaction_generator.rs @@ -225,7 +225,7 @@ impl TransactionGenerator { self.tx_size as i64 - (*total_output_slips_created + 1) as i64 * SLIP_SIZE as i64; if remaining_bytes > 0 { - transaction.data = generate_random_bytes(remaining_bytes as u64); + transaction.data = generate_random_bytes(remaining_bytes as u64).await; } transaction.timestamp = self.time_keeper.get_timestamp_in_ms();