Parcourir la source

validator/pow: use randomx factory to manage VMs and keys rotation optimization added

skoupidi il y a 1 an
Parent
commit
d819f257da

+ 19 - 4
bin/darkfid/src/task/miner.rs

@@ -24,6 +24,7 @@ use darkfi::{
     util::{encoding::base64, time::Timestamp},
     validator::{
         consensus::{Fork, Proposal},
+        pow::{RANDOMX_KEY_CHANGE_DELAY, RANDOMX_KEY_CHANGING_HEIGHT},
         utils::best_fork_index,
         verification::apply_producer_transaction,
     },
@@ -308,16 +309,30 @@ async fn mine_next_block(
 
     // Execute request to minerd and parse response
     let target = JsonValue::String(next_target.to_string());
-    let block = JsonValue::String(base64::encode(&serialize_async(&next_block).await));
-    let response =
-        node.miner_daemon_request_with_retry("mine", &JsonValue::Array(vec![target, block])).await;
+    // Grab the RandomX key to use.
+    // We only use the next key when the next block is the
+    // height changing one.
+    let randomx_key = if next_block.header.height > RANDOMX_KEY_CHANGING_HEIGHT &&
+        next_block.header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
+    {
+        JsonValue::String(base64::encode(&extended_fork.module.darkfi_rx_keys.1))
+    } else {
+        JsonValue::String(base64::encode(&extended_fork.module.darkfi_rx_keys.0))
+    };
+    let header = JsonValue::String(base64::encode(&serialize_async(&next_block.header).await));
+    let response = node
+        .miner_daemon_request_with_retry(
+            "mine",
+            &JsonValue::Array(vec![target, randomx_key, header]),
+        )
+        .await;
     next_block.header.nonce = *response.get::<f64>().unwrap() as u64;
 
     // Sign the mined block
     next_block.sign(&block_signing_secret);
 
     // Verify it
-    extended_fork.module.verify_current_block(&next_block)?;
+    extended_fork.module.verify_current_block(&next_block.header)?;
 
     // Check if we are connected to the network
     if !skip_sync && !node.p2p_handler.p2p.is_connected() {

+ 4 - 3
bin/darkfid/src/task/unknown_proposal.rs

@@ -333,8 +333,6 @@ async fn handle_reorg(
         validator.consensus.module.read().await.target,
         validator.consensus.module.read().await.fixed_difficulty.clone(),
         Some(last_common_height + 1),
-        validator.consensus.darkfi_rx_factory.clone(),
-        validator.consensus.monero_rx_factory.clone(),
     ) {
         Ok(m) => m,
         Err(e) => {
@@ -430,7 +428,10 @@ async fn handle_reorg(
             hashes_rank += hash_distance_sq.clone();
 
             // Update PoW headers module
-            headers_module.append(peer_header.timestamp, &next_difficulty);
+            if let Err(e) = headers_module.append(peer_header, &next_difficulty) {
+                debug!(target: "darkfid::task::handle_reorg", "Error while appending header to module: {e}");
+                return true
+            };
 
             // Set previous header
             previous_height = peer_header.height;

+ 2 - 11
bin/darkfid/src/tests/forks.rs

@@ -18,7 +18,7 @@
 
 use darkfi::{
     blockchain::{BlockInfo, Blockchain, HeaderHash},
-    validator::{consensus::Fork, pow::PoWModule, RandomXFactory},
+    validator::{consensus::Fork, pow::PoWModule},
     Result,
 };
 use sled_overlay::sled;
@@ -39,16 +39,7 @@ fn forks() -> Result<()> {
         let genesis_block_hash = genesis_block.hash();
 
         // Generate the PoW module
-        let darkfi_rx_factory = RandomXFactory::default();
-        let monero_rx_factory = RandomXFactory::default();
-        let module = PoWModule::new(
-            blockchain.clone(),
-            90,
-            None,
-            None,
-            darkfi_rx_factory,
-            monero_rx_factory,
-        )?;
+        let module = PoWModule::new(blockchain.clone(), 90, None, None)?;
 
         // Create a fork
         let fork = Fork::new(blockchain.clone(), module).await?;

+ 35 - 18
bin/minerd/src/rpc.rs

@@ -23,7 +23,7 @@ use smol::lock::MutexGuard;
 use tracing::{debug, error, info};
 
 use darkfi::{
-    blockchain::BlockInfo,
+    blockchain::header_store::{Header, HeaderHash},
     rpc::{
         jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
         server::RequestHandler,
@@ -74,9 +74,11 @@ impl MinerNode {
     }
 
     // RPCAPI:
-    // Mine provided block for requested mine target, and return the corresponding nonce value.
+    // Mine provided block header for requested mine target, using
+    // provided RandomX VM key, and return the corresponding nonce
+    // value.
     //
-    // --> {"jsonrpc": "2.0", "method": "mine", "params": ["target", "block"], "id": 42}
+    // --> {"jsonrpc": "2.0", "method": "mine", "params": ["target", "randomx_key", "header"], "id": 42}
     // --> {"jsonrpc": "2.0", "result": "nonce", "id": 42}
     async fn mine(&self, id: u16, params: JsonValue) -> JsonResult {
         // Verify parameters
@@ -84,7 +86,11 @@ impl MinerNode {
             return JsonError::new(ErrorCode::InvalidParams, None, id).into()
         }
         let params = params.get::<Vec<JsonValue>>().unwrap();
-        if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
+        if params.len() != 3 ||
+            !params[0].is_string() ||
+            !params[1].is_string() ||
+            !params[2].is_string()
+        {
             return JsonError::new(ErrorCode::InvalidParams, None, id).into()
         }
 
@@ -93,19 +99,28 @@ impl MinerNode {
             error!(target: "minerd::rpc", "Failed to parse target");
             return server_error(RpcError::TargetParseError, id, None)
         };
-        let Some(block_bytes) = base64::decode(params[1].get::<String>().unwrap()) else {
-            error!(target: "minerd::rpc", "Failed to parse block bytes");
+        let Some(randomx_key_bytes) = base64::decode(params[1].get::<String>().unwrap()) else {
+            error!(target: "minerd::rpc", "Failed to parse RandomX key bytes");
             return server_error(RpcError::BlockParseError, id, None)
         };
-        let Ok(mut block) = deserialize_async::<BlockInfo>(&block_bytes).await else {
-            error!(target: "minerd::rpc", "Failed to parse block");
+        let Ok(randomx_key) = deserialize_async::<[u8; 32]>(&randomx_key_bytes).await else {
+            error!(target: "minerd::rpc", "Failed to parse RandomX key");
             return server_error(RpcError::BlockParseError, id, None)
         };
-        let block_hash = block.hash();
-        info!(target: "minerd::rpc", "Received request to mine block {block_hash} for target: {target}");
+        let Some(header_bytes) = base64::decode(params[2].get::<String>().unwrap()) else {
+            error!(target: "minerd::rpc", "Failed to parse header bytes");
+            return server_error(RpcError::BlockParseError, id, None)
+        };
+        let Ok(mut header) = deserialize_async::<Header>(&header_bytes).await else {
+            error!(target: "minerd::rpc", "Failed to parse header");
+            return server_error(RpcError::BlockParseError, id, None)
+        };
+        let header_hash = header.hash();
+        let randomx_key_hash = HeaderHash::new(randomx_key);
+        info!(target: "minerd::rpc", "Received request to mine block header {header_hash} with key {randomx_key_hash} for target: {target}");
 
         // If we have a requested mining height, we'll keep dropping here.
-        if self.stop_at_height > 0 && block.header.height >= self.stop_at_height {
+        if self.stop_at_height > 0 && header.height >= self.stop_at_height {
             info!(target: "minerd::rpc", "Reached requested mining height {}", self.stop_at_height);
             return server_error(RpcError::MiningFailed, id, None)
         }
@@ -115,16 +130,18 @@ impl MinerNode {
             return e
         };
 
-        // Mine provided block
-        info!(target: "minerd::rpc", "Mining block {block_hash} for target: {target}");
-        if let Err(e) = mine_block(&target, &mut block, self.threads, &self.stop_signal.clone()) {
-            error!(target: "minerd::rpc", "Failed mining block {block_hash} with error: {e}");
+        // Mine provided block header
+        info!(target: "minerd::rpc", "Mining block header {header_hash} with key {randomx_key_hash} for target: {target}");
+        if let Err(e) =
+            mine_block(&target, &randomx_key, &mut header, self.threads, &self.stop_signal.clone())
+        {
+            error!(target: "minerd::rpc", "Failed mining block header {header_hash} with error: {e}");
             return server_error(RpcError::MiningFailed, id, None)
         }
-        info!(target: "minerd::rpc", "Mined block {block_hash} with nonce: {}", block.header.nonce);
+        info!(target: "minerd::rpc", "Mined block header {header_hash} with nonce: {}", header.nonce);
 
-        // Return block nonce
-        JsonResponse::new(JsonValue::Number(block.header.nonce as f64), id).into()
+        // Return block header nonce
+        JsonResponse::new(JsonValue::Number(header.nonce as f64), id).into()
     }
 
     /// Auxiliary function to abort pending request.

+ 4 - 4
src/blockchain/header_store.rs

@@ -39,8 +39,8 @@ use super::{monero::MoneroPowData, SledDbOverlayPtr};
 #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
 #[allow(clippy::large_enum_variant)]
 pub enum PowData {
-    /// Native Darkfi PoW
-    Darkfi,
+    /// Native DarkFi PoW
+    DarkFi,
     /// Monero merge mining PoW
     Monero(MoneroPowData),
 }
@@ -105,12 +105,12 @@ pub struct Header {
 
 impl Header {
     /// Generates a new header with default transactions and state root,
-    /// using Darkfi native Proof of Work data.
+    /// using DarkFi native Proof of Work data.
     pub fn new(previous: HeaderHash, height: u32, timestamp: Timestamp, nonce: u64) -> Self {
         let version = block_version(height);
         let transactions_root = MerkleTree::new(1).root(0).unwrap();
         let state_root = *EMPTY_HASH;
-        let pow_data = PowData::Darkfi;
+        let pow_data = PowData::DarkFi;
         Self {
             version,
             previous,

+ 136 - 0
src/blockchain/mod.rs

@@ -410,6 +410,65 @@ impl Blockchain {
     pub fn get_state_monotree(&self) -> Result<Monotree<monotree::MemoryDb>> {
         self.contracts.get_state_monotree(&self.sled_db)
     }
+
+    /// Grab the RandomX VM current and next key, based on provided key
+    /// changing height and delay.
+    ///
+    /// NOTE: the height calculation logic is verified using test:
+    //        test_randomx_keys_retrieval_logic
+    pub fn get_randomx_vm_keys(
+        &self,
+        key_change_height: &u32,
+        key_change_delay: &u32,
+    ) -> Result<([u8; 32], [u8; 32])> {
+        // Grab last known block header
+        let last = self.last_header()?;
+
+        // Check if we passed the first key change height
+        if &last.height <= key_change_height {
+            // Genesis is our current
+            let current = *self.genesis()?.1.inner();
+
+            // Check if last known block header is the next key
+            let next =
+                if &last.height == key_change_height { *last.hash().inner() } else { current };
+
+            return Ok((current, next))
+        }
+
+        // Find the current and next key based on distance of last
+        // known block header height from the key change height.
+        let distance = last.height % key_change_height;
+
+        // When distance is 0, current key is the block header
+        // located at last_height - key_change_height height, while
+        // last known block header is the next key.
+        if distance == 0 {
+            return Ok((
+                *self.get_blocks_by_heights(&[last.height - key_change_height])?[0].hash().inner(),
+                *last.hash().inner(),
+            ))
+        }
+
+        // When distance is less than key change delay, current key
+        // is the block header located at last_height - (distance + key_change_height)
+        // height, while the block header located at last_height - distance
+        // height is the next key.
+        if &distance < key_change_delay {
+            return Ok((
+                *self.get_blocks_by_heights(&[last.height - (distance + key_change_height)])?[0]
+                    .hash()
+                    .inner(),
+                *self.get_blocks_by_heights(&[last.height - distance])?[0].hash().inner(),
+            ))
+        }
+
+        // When distance is greater or equal to key change delay,
+        // current key is the block header located at last_height - distance
+        // height and we don't know the next key.
+        let current = *self.get_blocks_by_heights(&[last.height - distance])?[0].hash().inner();
+        Ok((current, current))
+    }
 }
 
 /// Atomic pointer to sled db overlay.
@@ -617,3 +676,80 @@ impl BlockchainOverlay {
         self.full_clone()?.lock().unwrap().contracts.get_state_monotree()
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::validator::pow::{RANDOMX_KEY_CHANGE_DELAY, RANDOMX_KEY_CHANGING_HEIGHT};
+
+    /// Compute the RandomX VM current and next key heights, based on
+    /// provided key changing height and delay.
+    fn get_randomx_vm_keys_heights(last: u32) -> (u32, u32) {
+        // Check if we passed the first key change height
+        if last <= RANDOMX_KEY_CHANGING_HEIGHT {
+            // Genesis is our current
+            let current = 0;
+
+            // Check if last height is the next key height
+            let next = if last == RANDOMX_KEY_CHANGING_HEIGHT { last } else { current };
+
+            return (current, next)
+        }
+
+        // Find the current and next key based on distance of last
+        // height from the key change height.
+        let distance = last % RANDOMX_KEY_CHANGING_HEIGHT;
+
+        // When distance is 0, current key is the last_height - RANDOMX_KEY_CHANGING_HEIGHT
+        // height, while last is the next key.
+        if distance == 0 {
+            return (last - RANDOMX_KEY_CHANGING_HEIGHT, last)
+        }
+
+        // When distance is less than key change delay, current key
+        // is the last_height - (distance + RANDOMX_KEY_CHANGING_HEIGHT) height,
+        // while the last_height - distance height is the next key.
+        if distance < RANDOMX_KEY_CHANGE_DELAY {
+            return (last - (distance + RANDOMX_KEY_CHANGING_HEIGHT), last - distance)
+        }
+
+        // When distance is greater or equal to key change delay,
+        // current key is the last_height - distance height and we
+        // don't know the next key height.
+        let current = last - distance;
+        (current, current)
+    }
+
+    #[test]
+    fn test_randomx_keys_retrieval_logic() {
+        // last < RANDOMX_KEY_CHANGING_HEIGHT(2048)
+        let (current, next) = get_randomx_vm_keys_heights(2047);
+        assert_eq!(current, 0);
+        assert_eq!(next, 0);
+
+        // last == RANDOMX_KEY_CHANGING_HEIGHT(2048)
+        let (current, next) = get_randomx_vm_keys_heights(2048);
+        assert_eq!(current, 0);
+        assert_eq!(next, 2048);
+
+        // last > RANDOMX_KEY_CHANGING_HEIGHT(2048)
+        // last % RANDOMX_KEY_CHANGING_HEIGHT(2048) == 0
+        let (current, next) = get_randomx_vm_keys_heights(4096);
+        assert_eq!(current, 2048);
+        assert_eq!(next, 4096);
+
+        // last % RANDOMX_KEY_CHANGING_HEIGHT(2048) < RANDOMX_KEY_CHANGE_DELAY(64)
+        let (current, next) = get_randomx_vm_keys_heights(4097);
+        assert_eq!(current, 2048);
+        assert_eq!(next, 4096);
+
+        // last % RANDOMX_KEY_CHANGING_HEIGHT(2048) == RANDOMX_KEY_CHANGE_DELAY(64)
+        let (current, next) = get_randomx_vm_keys_heights(4160);
+        assert_eq!(current, 4096);
+        assert_eq!(next, 4096);
+
+        // last % RANDOMX_KEY_CHANGING_HEIGHT(2048) > RANDOMX_KEY_CHANGE_DELAY(64)
+        let (current, next) = get_randomx_vm_keys_heights(4161);
+        assert_eq!(current, 4096);
+        assert_eq!(next, 4096);
+    }
+}

+ 5 - 24
src/validator/consensus.rs

@@ -40,7 +40,6 @@ use crate::{
         pow::PoWModule,
         utils::{best_fork_index, block_rank, find_extended_fork_index},
         verification::{verify_proposal, verify_transaction},
-        RandomXFactory,
     },
     zk::VerifyingKey,
     Error, Result,
@@ -55,10 +54,6 @@ pub struct Consensus {
     pub blockchain: Blockchain,
     /// Fork size(length) after which it can be confirmed
     pub confirmation_threshold: usize,
-    /// RandomXFactory for native PoW
-    pub darkfi_rx_factory: RandomXFactory,
-    /// RandomXFactory for Monero PoW
-    pub monero_rx_factory: RandomXFactory,
     /// Fork chains containing block proposals
     pub forks: RwLock<Vec<Fork>>,
     /// Canonical blockchain PoW module state
@@ -77,29 +72,16 @@ impl Consensus {
     ) -> Result<Self> {
         let forks = RwLock::new(vec![]);
 
-        let darkfi_rx_factory = RandomXFactory::default();
-        let monero_rx_factory = RandomXFactory::default();
-
         let module = RwLock::new(PoWModule::new(
             blockchain.clone(),
             pow_target,
             pow_fixed_difficulty,
             None,
-            darkfi_rx_factory.clone(),
-            monero_rx_factory.clone(),
         )?);
 
         let append_lock = RwLock::new(());
 
-        Ok(Self {
-            blockchain,
-            confirmation_threshold,
-            darkfi_rx_factory,
-            monero_rx_factory,
-            forks,
-            module,
-            append_lock,
-        })
+        Ok(Self { blockchain, confirmation_threshold, forks, module, append_lock })
     }
 
     /// Generate a new empty fork.
@@ -242,7 +224,7 @@ impl Consensus {
             let (target_distance_sq, hash_distance_sq) = block_rank(block, &next_target)?;
 
             // Update PoW module
-            fork.module.append(block.header.timestamp, &next_difficulty);
+            fork.module.append(&block.header, &next_difficulty)?;
 
             // Update fork ranks
             fork.targets_rank += target_distance_sq;
@@ -656,8 +638,6 @@ impl Consensus {
             module.target,
             module.fixed_difficulty.clone(),
             None,
-            self.darkfi_rx_factory.clone(),
-            self.monero_rx_factory.clone(),
         )?;
         drop(module);
         debug!(target: "validator::consensus::reset_pow_module", "PoW module reset successfully!");
@@ -794,7 +774,7 @@ impl Fork {
             cumulative_difficulty,
             ranks,
         );
-        self.module.append_difficulty(&self.overlay, block_difficulty)?;
+        self.module.append_difficulty(&self.overlay, &proposal.block.header, block_difficulty)?;
 
         // Push proposal's hash
         self.proposals.push(proposal.hash);
@@ -961,7 +941,8 @@ impl Fork {
     //        proposal.
     pub fn healthcheck(&self) -> Result<()> {
         // Rebuild current contract states checksums monotree
-        let state_monotree = self.overlay.lock().unwrap().get_state_monotree()?;
+        let mut state_monotree = self.overlay.lock().unwrap().get_state_monotree()?;
+        self.overlay.lock().unwrap().contracts.update_state_monotree(&mut state_monotree)?;
 
         // Check that it matches forks' tree
         let Some(state_root) = state_monotree.get_headroot()? else {

+ 8 - 21
src/validator/mod.rs

@@ -393,7 +393,7 @@ impl Validator {
             info!(target: "validator::confirmation", "\t{proposal} - {}", confirmed_blocks[index].header.height);
             fork.overlay.lock().unwrap().overlay.lock().unwrap().apply_diff(&diffs[index])?;
             let next_difficulty = module.next_difficulty()?;
-            module.append(confirmed_blocks[index].header.timestamp, &next_difficulty);
+            module.append(&confirmed_blocks[index].header, &next_difficulty)?;
             confirmed_txs.extend_from_slice(&confirmed_blocks[index].txs);
             state_inverse_diffs_heights.push(confirmed_blocks[index].header.height);
             state_inverse_diffs.push(diffs[index].inverse());
@@ -503,7 +503,7 @@ impl Validator {
                 cumulative_difficulty,
                 ranks,
             );
-            module.append_difficulty(&overlay, block_difficulty)?;
+            module.append_difficulty(&overlay, &block.header, block_difficulty)?;
 
             // Store block transactions
             for tx in &block.txs {
@@ -617,7 +617,7 @@ impl Validator {
                 cumulative_difficulty,
                 ranks,
             );
-            module.append_difficulty(&overlay, block_difficulty)?;
+            module.append_difficulty(&overlay, &block.header, block_difficulty)?;
 
             // Store block transactions
             for tx in &block.txs {
@@ -785,14 +785,7 @@ impl Validator {
         overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
 
         // Create a PoW module to validate each block
-        let mut module = PoWModule::new(
-            blockchain,
-            pow_target,
-            pow_fixed_difficulty,
-            Some(0),
-            self.consensus.darkfi_rx_factory.clone(),
-            self.consensus.monero_rx_factory.clone(),
-        )?;
+        let mut module = PoWModule::new(blockchain, pow_target, pow_fixed_difficulty, Some(0))?;
 
         // Grab current contracts states monotree to validate each block
         let mut state_monotree = overlay.lock().unwrap().get_state_monotree()?;
@@ -823,7 +816,7 @@ impl Validator {
             };
 
             // Update PoW module
-            module.append(block.header.timestamp, &module.next_difficulty()?);
+            module.append(&block.header, &module.next_difficulty()?)?;
 
             // Use last inserted block as next iteration previous
             previous = block;
@@ -895,14 +888,8 @@ impl Validator {
 
         // Create a PoW module and an in memory overlay to compute each
         // block difficulty.
-        let mut module = PoWModule::new(
-            self.blockchain.clone(),
-            pow_target,
-            pow_fixed_difficulty,
-            Some(0),
-            self.consensus.darkfi_rx_factory.clone(),
-            self.consensus.monero_rx_factory.clone(),
-        )?;
+        let mut module =
+            PoWModule::new(self.blockchain.clone(), pow_target, pow_fixed_difficulty, Some(0))?;
 
         // Grab genesis block difficulty to access current ranks
         let genesis_block = self.blockchain.genesis_block()?;
@@ -943,7 +930,7 @@ impl Validator {
                 cumulative_difficulty,
                 ranks,
             );
-            module.append(block_difficulty.timestamp, &block_difficulty.difficulty);
+            module.append(&block.header, &block_difficulty.difficulty)?;
 
             // Add difficulty to database
             self.blockchain.blocks.insert_difficulty(&[block_difficulty])?;

+ 119 - 64
src/validator/pow.rs

@@ -33,7 +33,11 @@ use tracing::debug;
 
 use crate::{
     blockchain::{
-        block_store::{BlockDifficulty, BlockInfo},
+        block_store::BlockDifficulty,
+        header_store::{
+            Header,
+            PowData::{DarkFi, Monero},
+        },
         Blockchain, BlockchainOverlayPtr,
     },
     system::thread_priority::ThreadPriority,
@@ -71,6 +75,10 @@ const CUT_END: usize = 660;
 const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: usize = 60;
 /// Time limit in the future of what blocks can be
 const BLOCK_FUTURE_TIME_LIMIT: Timestamp = Timestamp::from_u64(60 * 60 * 2);
+/// RandomX VM key changing height
+pub const RANDOMX_KEY_CHANGING_HEIGHT: u32 = 2048;
+/// RandomX VM key change delay
+pub const RANDOMX_KEY_CHANGE_DELAY: u32 = 64;
 
 /// This struct represents the information required by the PoW algorithm
 #[derive(Clone)]
@@ -90,6 +98,8 @@ pub struct PoWModule {
     /// access(optimization), since its always same as
     /// difficulties buffer last.
     pub cumulative_difficulty: BigUint,
+    /// Native PoW RandomX VMs current and next keys pair
+    pub darkfi_rx_keys: ([u8; 32], [u8; 32]),
     /// RandomXFactory for native PoW (Arc from parent)
     pub darkfi_rx_factory: RandomXFactory,
     /// RandomXFactory for Monero PoW (Arc from parent)
@@ -104,8 +114,6 @@ impl PoWModule {
         target: u32,
         fixed_difficulty: Option<BigUint>,
         height: Option<u32>,
-        darkfi_rx_factory: RandomXFactory,
-        monero_rx_factory: RandomXFactory,
     ) -> Result<Self> {
         // Retrieve genesis block timestamp
         let genesis = blockchain.genesis_block()?.header.timestamp;
@@ -129,6 +137,13 @@ impl PoWModule {
             assert!(diff > &BigUint::zero());
         }
 
+        // Retrieve current and next native PoW RandomX VM current and
+        // next keys pair, and generate the RandomX factories.
+        let darkfi_rx_keys = blockchain
+            .get_randomx_vm_keys(&RANDOMX_KEY_CHANGING_HEIGHT, &RANDOMX_KEY_CHANGE_DELAY)?;
+        let darkfi_rx_factory = RandomXFactory::default();
+        let monero_rx_factory = RandomXFactory::default();
+
         Ok(Self {
             genesis,
             target,
@@ -136,6 +151,7 @@ impl PoWModule {
             timestamps,
             difficulties,
             cumulative_difficulty,
+            darkfi_rx_keys,
             darkfi_rx_factory,
             monero_rx_factory,
         })
@@ -264,34 +280,51 @@ impl PoWModule {
     }
 
     /// Verify provided block timestamp and hash.
-    pub fn verify_current_block(&self, block: &BlockInfo) -> Result<()> {
+    pub fn verify_current_block(&self, header: &Header) -> Result<()> {
         // First we verify the block's timestamp
-        if !self.verify_current_timestamp(block.header.timestamp)? {
+        if !self.verify_current_timestamp(header.timestamp)? {
             return Err(Error::PoWInvalidTimestamp)
         }
 
         // Then we verify the block's hash
-        self.verify_block_hash(block)
+        self.verify_block_hash(header)
     }
 
     /// Verify provided block corresponds to next mine target.
-    // TODO: Verify depending on block Proof of Work data
-    pub fn verify_block_hash(&self, block: &BlockInfo) -> Result<()> {
+    pub fn verify_block_hash(&self, header: &Header) -> Result<()> {
         let verifier_setup = Instant::now();
 
         // Grab the next mine target
         let target = self.next_mine_target()?;
 
-        // Setup verifier
-        let randomx_key = block.header.previous.inner();
+        // Setup verifier based on block PoW data
         let flags = RandomXFlags::get_recommended_flags();
-        let cache = RandomXCache::new(flags, &randomx_key[..])?;
-        let vm = self.darkfi_rx_factory.create(&randomx_key[..], Some(cache), None)?;
+        let vm = match &header.pow_data {
+            DarkFi => {
+                // Check which VM key should be used.
+                // We only use the next key when the next block is the
+                // height changing one.
+                let randomx_key = if header.height > RANDOMX_KEY_CHANGING_HEIGHT &&
+                    header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
+                {
+                    &self.darkfi_rx_keys.1[..]
+                } else {
+                    &self.darkfi_rx_keys.0[..]
+                };
+                let cache = RandomXCache::new(flags, randomx_key)?;
+                self.darkfi_rx_factory.create(randomx_key, Some(cache), None)?
+            }
+            Monero(monero_pow_data) => {
+                let randomx_key = &monero_pow_data.randomx_key[..];
+                let cache = RandomXCache::new(flags, randomx_key)?;
+                self.monero_rx_factory.create(randomx_key, Some(cache), None)?
+            }
+        };
         debug!(target: "validator::pow::verify_block", "[VERIFIER] Setup time: {:?}", verifier_setup.elapsed());
 
         // Compute the output hash
         let verification_time = Instant::now();
-        let out_hash = vm.calculate_hash(block.header.hash().inner())?;
+        let out_hash = vm.calculate_hash(header.hash().inner())?;
         let out_hash = BigUint::from_bytes_be(&out_hash);
 
         // Verify hash is less than the expected mine target
@@ -303,11 +336,34 @@ impl PoWModule {
         Ok(())
     }
 
-    /// Append provided timestamp and difficulty to the ring buffers.
-    pub fn append(&mut self, timestamp: Timestamp, difficulty: &BigUint) {
-        self.timestamps.push(timestamp);
+    /// Append provided header timestamp and difficulty to the ring
+    /// buffers, and check if we need to rotate and/or create the next
+    /// key RandomX VM in the native PoW factory.
+    pub fn append(&mut self, header: &Header, difficulty: &BigUint) -> Result<()> {
+        self.timestamps.push(header.timestamp);
         self.cumulative_difficulty += difficulty;
         self.difficulties.push(self.cumulative_difficulty.clone());
+
+        if header.height < RANDOMX_KEY_CHANGING_HEIGHT {
+            return Ok(())
+        }
+
+        // Check if need to set the new key
+        if header.height % RANDOMX_KEY_CHANGING_HEIGHT == 0 {
+            let next_key = *header.hash().inner();
+            let flags = RandomXFlags::get_recommended_flags();
+            let cache = RandomXCache::new(flags, &next_key[..])?;
+            let _ = self.darkfi_rx_factory.create(&next_key[..], Some(cache), None)?;
+            self.darkfi_rx_keys.1 = next_key;
+            return Ok(())
+        }
+
+        // Check if need to rotate keys
+        if header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY {
+            self.darkfi_rx_keys.0 = self.darkfi_rx_keys.1;
+        }
+
+        Ok(())
     }
 
     /// Append provided block difficulty to the ring buffers and insert
@@ -315,23 +371,35 @@ impl PoWModule {
     pub fn append_difficulty(
         &mut self,
         overlay: &BlockchainOverlayPtr,
+        header: &Header,
         difficulty: BlockDifficulty,
     ) -> Result<()> {
-        self.append(difficulty.timestamp, &difficulty.difficulty);
+        self.append(header, &difficulty.difficulty)?;
         overlay.lock().unwrap().blocks.insert_difficulty(&[difficulty])
     }
 
     /// Mine provided block, based on next mine target.
     pub fn mine_block(
         &self,
-        miner_block: &mut BlockInfo,
+        header: &mut Header,
         threads: usize,
         stop_signal: &Receiver<()>,
     ) -> Result<()> {
         // Grab the next mine target
         let target = self.next_mine_target()?;
 
-        mine_block(&target, miner_block, threads, stop_signal)
+        // Grab the RandomX key to use.
+        // We only use the next key when the next block is the
+        // height changing one.
+        let randomx_key = if header.height > RANDOMX_KEY_CHANGING_HEIGHT &&
+            header.height % RANDOMX_KEY_CHANGING_HEIGHT == RANDOMX_KEY_CHANGE_DELAY
+        {
+            &self.darkfi_rx_keys.1
+        } else {
+            &self.darkfi_rx_keys.0
+        };
+
+        mine_block(&target, randomx_key, header, threads, stop_signal)
     }
 }
 
@@ -348,16 +416,15 @@ impl std::fmt::Display for PoWModule {
 /// Mine provided block, based on provided PoW module next mine target.
 pub fn mine_block(
     target: &BigUint,
-    miner_block: &mut BlockInfo,
+    input: &[u8; 32],
+    miner_header: &mut Header,
     threads: usize,
     stop_signal: &Receiver<()>,
 ) -> Result<()> {
     let miner_setup = Instant::now();
 
-    debug!(target: "validator::pow::mine_block", "[MINER] Mine target: 0x{:064x}", target);
-    // Get the PoW input. The key changes with every mined block.
-    let input = miner_block.header.previous;
-    debug!(target: "validator::pow::mine_block", "[MINER] PoW input: {}", input);
+    debug!(target: "validator::pow::mine_block", "[MINER] Mine target: 0x{target:064x}");
+    debug!(target: "validator::pow::mine_block", "[MINER] PoW input: {}", blake3::hash(input));
 
     let mut flags = RandomXFlags::get_recommended_flags() | RandomXFlags::FULLMEM;
     #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@@ -368,25 +435,24 @@ pub fn mine_block(
     }
 
     debug!(target: "validator::pow::mine_block", "[MINER] Initializing RandomX cache...");
-    let cache = RandomXCache::new(flags, input.inner())?;
+    let cache = RandomXCache::new(flags, &input[..])?;
 
     debug!(target: "validator::pow::mine_block", "[MINER] Setup time: {:?}", miner_setup.elapsed());
 
     // Multithreaded mining setup
     let mining_time = Instant::now();
     let mut handles = vec![];
-    let found_block = Arc::new(AtomicBool::new(false));
+    let found_header = Arc::new(AtomicBool::new(false));
     let found_nonce = Arc::new(AtomicU64::new(0));
     let threads = threads as u64;
     let dataset_item_count = RandomXDataset::count()?;
 
     for t in 0..threads {
         let target = target.clone();
-        let mut block = miner_block.clone();
-        let found_block = Arc::clone(&found_block);
+        let mut header = miner_header.clone();
+        let found_header = Arc::clone(&found_header);
         let found_nonce = Arc::clone(&found_nonce);
 
-        // TODO: Clean up using RandomXFactory and add wrapper for AVX2
         let dataset = if threads > 1 {
             let a = (dataset_item_count * (t as u32)) / (threads as u32);
             let b = (dataset_item_count * (t as u32 + 1)) / (threads as u32);
@@ -414,19 +480,19 @@ pub fn mine_block(
                     break
                 }
 
-                block.header.nonce = miner_nonce;
-                if found_block.load(Ordering::SeqCst) {
-                    debug!(target: "validator::pow::mine_block", "[MINER] Block found, thread #{t} exiting");
+                header.nonce = miner_nonce;
+                if found_header.load(Ordering::SeqCst) {
+                    debug!(target: "validator::pow::mine_block", "[MINER] Block header found, thread #{t} exiting");
                     break
                 }
 
-                let out_hash = vm.calculate_hash(block.hash().inner()).unwrap();
+                let out_hash = vm.calculate_hash(header.hash().inner()).unwrap();
                 let out_hash = BigUint::from_bytes_be(&out_hash);
                 if out_hash <= target {
-                    found_block.store(true, Ordering::SeqCst);
+                    found_header.store(true, Ordering::SeqCst);
                     found_nonce.store(miner_nonce, Ordering::SeqCst);
-                    debug!(target: "validator::pow::mine_block", "[MINER] Thread #{t} found block using nonce {miner_nonce}");
-                    debug!(target: "validator::pow::mine_block", "[MINER] Block hash {}", block.hash());
+                    debug!(target: "validator::pow::mine_block", "[MINER] Thread #{t} found block header using nonce {miner_nonce}");
+                    debug!(target: "validator::pow::mine_block", "[MINER] Block header hash {}", header.hash());
                     debug!(target: "validator::pow::mine_block", "[MINER] RandomX output: 0x{out_hash:064x}");
                     break
                 }
@@ -448,8 +514,8 @@ pub fn mine_block(
 
     debug!(target: "validator::pow::mine_block", "[MINER] Mining time: {:?}", mining_time.elapsed());
 
-    // Set the valid mined nonce in the block
-    miner_block.header.nonce = found_nonce.load(Ordering::SeqCst);
+    // Set the valid mined nonce in the block header
+    miner_header.nonce = found_nonce.load(Ordering::SeqCst);
 
     Ok(())
 }
@@ -466,11 +532,11 @@ mod tests {
     use sled_overlay::sled;
 
     use crate::{
-        blockchain::{BlockInfo, Blockchain},
+        blockchain::{header_store::Header, BlockInfo, Blockchain},
         Result,
     };
 
-    use super::{super::RandomXFactory, PoWModule};
+    use super::PoWModule;
 
     const DEFAULT_TEST_THREADS: usize = 2;
     const DEFAULT_TEST_DIFFICULTY_TARGET: u32 = 120;
@@ -482,26 +548,23 @@ mod tests {
         let genesis_block = BlockInfo::default();
         blockchain.add_block(&genesis_block)?;
 
-        let darkfi_rx_factory = RandomXFactory::default();
-        let monero_rx_factory = RandomXFactory::default();
-        let mut module = PoWModule::new(
-            blockchain,
-            DEFAULT_TEST_DIFFICULTY_TARGET,
-            None,
-            None,
-            darkfi_rx_factory,
-            monero_rx_factory,
-        )?;
+        let mut module = PoWModule::new(blockchain, DEFAULT_TEST_DIFFICULTY_TARGET, None, None)?;
 
         let output = Command::new("./script/research/pow/gen_wide_data.py").output().unwrap();
         let reader = Cursor::new(output.stdout);
 
+        let mut previous = genesis_block.header;
         for (n, line) in reader.lines().enumerate() {
             let line = line.unwrap();
             let parts: Vec<String> = line.split(' ').map(|x| x.to_string()).collect();
             assert!(parts.len() == 2);
 
-            let timestamp = parts[0].parse::<u64>().unwrap().into();
+            let header = Header::new(
+                previous.hash(),
+                previous.height + 1,
+                parts[0].parse::<u64>().unwrap().into(),
+                0,
+            );
             let difficulty = BigUint::from_str_radix(&parts[1], 10).unwrap();
 
             let res = module.next_difficulty()?;
@@ -513,7 +576,8 @@ mod tests {
                 assert!(res == difficulty);
             }
 
-            module.append(timestamp, &difficulty);
+            module.append(&header, &difficulty)?;
+            previous = header;
         }
 
         Ok(())
@@ -528,26 +592,17 @@ mod tests {
         genesis_block.header.timestamp = 0.into();
         blockchain.add_block(&genesis_block)?;
 
-        let darkfi_rx_factory = RandomXFactory::default();
-        let monero_rx_factory = RandomXFactory::default();
-        let module = PoWModule::new(
-            blockchain,
-            DEFAULT_TEST_DIFFICULTY_TARGET,
-            None,
-            None,
-            darkfi_rx_factory,
-            monero_rx_factory,
-        )?;
+        let module = PoWModule::new(blockchain, DEFAULT_TEST_DIFFICULTY_TARGET, None, None)?;
 
         let (_, recvr) = smol::channel::bounded(1);
 
         // Mine next block
         let mut next_block = BlockInfo::default();
         next_block.header.previous = genesis_block.hash();
-        module.mine_block(&mut next_block, DEFAULT_TEST_THREADS, &recvr)?;
+        module.mine_block(&mut next_block.header, DEFAULT_TEST_THREADS, &recvr)?;
 
         // Verify it
-        module.verify_current_block(&next_block)?;
+        module.verify_current_block(&next_block.header)?;
 
         Ok(())
     }

+ 6 - 16
src/validator/verification.rs

@@ -36,8 +36,8 @@ use tracing::{debug, error, warn};
 
 use crate::{
     blockchain::{
-        block_store::append_tx_to_merkle_tree, header_store::PowData, BlockInfo, Blockchain,
-        BlockchainOverlayPtr, HeaderHash,
+        block_store::append_tx_to_merkle_tree, header_store::PowData::DarkFi, BlockInfo,
+        Blockchain, BlockchainOverlayPtr, HeaderHash,
     },
     error::TxVerifyFailed,
     runtime::vm_runtime::Runtime,
@@ -46,7 +46,6 @@ use crate::{
         consensus::{Consensus, Fork, Proposal, BLOCK_GAS_LIMIT},
         fees::{circuit_gas_use, compute_fee, GasData, PALLAS_SCHNORR_SIGNATURE_FEE},
         pow::PoWModule,
-        RandomXFactory,
     },
     zk::VerifyingKey,
     Error, Result,
@@ -78,7 +77,7 @@ pub async fn verify_genesis_block(
 
     // Block must use Darkfi native Proof of Work data
     match block.header.pow_data {
-        PowData::Darkfi => { /* do nothing */ }
+        DarkFi => { /* do nothing */ }
         _ => return Err(Error::BlockIsInvalid(block_hash)),
     }
 
@@ -172,7 +171,7 @@ pub fn validate_block(block: &BlockInfo, previous: &BlockInfo, module: &PoWModul
     }
 
     // Check block hash corresponds to next one (5)
-    module.verify_block_hash(block)?;
+    module.verify_block_hash(&block.header)?;
 
     Ok(())
 }
@@ -186,16 +185,7 @@ pub fn validate_blockchain(
     pow_fixed_difficulty: Option<BigUint>,
 ) -> Result<()> {
     // Generate a PoW module
-    let darkfi_rx_factory = RandomXFactory::default();
-    let monero_rx_factory = RandomXFactory::default();
-    let mut module = PoWModule::new(
-        blockchain.clone(),
-        pow_target,
-        pow_fixed_difficulty,
-        Some(0),
-        darkfi_rx_factory,
-        monero_rx_factory,
-    )?;
+    let mut module = PoWModule::new(blockchain.clone(), pow_target, pow_fixed_difficulty, Some(0))?;
 
     // We use block order store here so we have all blocks in order
     let blocks = blockchain.blocks.get_all_order()?;
@@ -204,7 +194,7 @@ pub fn validate_blockchain(
         let full_block = &full_blocks[1];
         validate_block(full_block, &full_blocks[0], &module)?;
         // Update PoW module
-        module.append(full_block.header.timestamp, &module.next_difficulty()?);
+        module.append(&full_block.header, &module.next_difficulty()?)?;
     }
 
     Ok(())