From 43cf7e6852da9d725182d49af2e0b6e854f10f6f Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Mon, 6 Feb 2023 21:01:45 -0300 Subject: [PATCH] implement `getdifficulty` rpc method (#6099) --- .../src/methods/get_block_template_rpcs.rs | 58 +++++++++++++ .../tests/snapshot/get_block_template_rpcs.rs | 33 +++++++ .../snapshots/get_difficulty@mainnet_10.snap | 5 ++ .../snapshots/get_difficulty@testnet_10.snap | 5 ++ zebra-rpc/src/methods/tests/vectors.rs | 85 +++++++++++++++++++ 5 files changed, 186 insertions(+) create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty@mainnet_10.snap create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty@testnet_10.snap diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 004eb21c5..26a0cc0c3 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -187,6 +187,12 @@ pub trait GetBlockTemplateRpc { /// zcashd reference: [`getblocksubsidy`](https://zcash.github.io/rpc/getblocksubsidy.html) #[rpc(name = "getblocksubsidy")] fn get_block_subsidy(&self, height: Option) -> BoxFuture>; + + /// Returns the proof-of-work difficulty as a multiple of the minimum difficulty. + /// + /// zcashd reference: [`getdifficulty`](https://zcash.github.io/rpc/getdifficulty.html) + #[rpc(name = "getdifficulty")] + fn get_difficulty(&self) -> BoxFuture>; } /// RPC method implementations. @@ -904,6 +910,58 @@ where } .boxed() } + + fn get_difficulty(&self) -> BoxFuture> { + let network = self.network; + let mut state = self.state.clone(); + + async move { + let request = ReadRequest::ChainInfo; + + let response = state + .ready() + .and_then(|service| service.call(request)) + .await + .map_err(|error| Error { + code: ErrorCode::ServerError(0), + message: error.to_string(), + data: None, + })?; + + let chain_info = match response { + ReadResponse::ChainInfo(info) => info, + _ => unreachable!("unmatched response to a chain info request"), + }; + + // The following code is ported from zcashd implementation. + // https://github.com/zcash/zcash/blob/v5.4.0-rc4/src/rpc/blockchain.cpp#L46-L73 + + let pow_limit = u32::from_be_bytes( + zebra_chain::work::difficulty::ExpandedDifficulty::target_difficulty_limit(network) + .to_compact() + .bytes_in_display_order(), + ); + let bits = u32::from_be_bytes(chain_info.expected_difficulty.bytes_in_display_order()); + + let mut n_shift = (bits >> 24) & 0xff; + let n_shift_amount = (pow_limit >> 24) & 0xff; + + let mut d_diff: f64 = (pow_limit & 0x00ffffff) as f64 / (bits & 0x00ffffff) as f64; + + while n_shift < n_shift_amount { + d_diff *= 256.0; + n_shift += 1; + } + + while n_shift > n_shift_amount { + d_diff /= 256.0; + n_shift -= 1; + } + + Ok(d_diff) + } + .boxed() + } } // Put support functions in a submodule, to keep this file small. diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index e27327096..f97df030b 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -390,6 +390,34 @@ pub async fn test_responses( .await .expect("We should have a validate_address::Response"); snapshot_rpc_validateaddress("invalid", validate_address, &settings); + + // getdifficulty + + // Fake the ChainInfo response + let response_read_state = new_read_state.clone(); + + tokio::spawn(async move { + response_read_state + .clone() + .expect_request_that(|req| matches!(req, ReadRequest::ChainInfo)) + .await + .respond(ReadResponse::ChainInfo(GetBlockTemplateChainInfo { + expected_difficulty: fake_difficulty, + tip_height: fake_tip_height, + tip_hash: fake_tip_hash, + cur_time: fake_cur_time, + min_time: fake_min_time, + max_time: fake_max_time, + history_tree: fake_history_tree(network), + })); + }); + + let get_difficulty = tokio::spawn(get_block_template_rpc.get_difficulty()) + .await + .expect("unexpected panic in getdifficulty RPC task") + .expect("unexpected error in getdifficulty RPC call"); + + snapshot_rpc_getdifficulty(get_difficulty, &settings); } /// Snapshot `getblockcount` response, using `cargo insta` and JSON serialization. @@ -466,3 +494,8 @@ fn snapshot_rpc_validateaddress( insta::assert_json_snapshot!(format!("validate_address_{variant}"), validate_address) }); } + +/// Snapshot `getdifficulty` response, using `cargo insta` and JSON serialization. +fn snapshot_rpc_getdifficulty(difficulty: f64, settings: &insta::Settings) { + settings.bind(|| insta::assert_json_snapshot!("get_difficulty", difficulty)); +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty@mainnet_10.snap new file mode 100644 index 000000000..48cf0a79f --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty@mainnet_10.snap @@ -0,0 +1,5 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: difficulty +--- +14134749558280407000000000000000000000000000000000000000000000000000000000.0 diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty@testnet_10.snap new file mode 100644 index 000000000..6fc586abc --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_difficulty@testnet_10.snap @@ -0,0 +1,5 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: difficulty +--- +3618495886919784300000000000000000000000000000000000000000000000000000000000.0 diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 7317aaf02..31294c7f3 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -1363,3 +1363,88 @@ async fn rpc_validateaddress() { "Testnet founder address should be invalid on Mainnet" ); } + +#[cfg(feature = "getblocktemplate-rpcs")] +#[tokio::test(flavor = "multi_thread")] +async fn rpc_getdifficulty() { + use zebra_chain::{ + block::Hash, + chain_sync_status::MockSyncStatus, + chain_tip::mock::MockChainTip, + serialization::DateTime32, + work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256}, + }; + + use zebra_network::address_book_peers::MockAddressBookPeers; + + use zebra_state::{GetBlockTemplateChainInfo, ReadRequest, ReadResponse}; + + use crate::methods::{ + get_block_template_rpcs::config::Config, tests::utils::fake_history_tree, + }; + + let _init_guard = zebra_test::init(); + + let mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); + + let mut read_state = MockService::build().for_unit_tests(); + let chain_verifier = MockService::build().for_unit_tests(); + + let mut mock_sync_status = MockSyncStatus::default(); + mock_sync_status.set_is_close_to_tip(true); + + let mining_config = Config { + miner_address: None, + }; + + // nu5 block height + let fake_tip_height = NetworkUpgrade::Nu5.activation_height(Mainnet).unwrap(); + // nu5 block hash + let fake_tip_hash = + Hash::from_hex("0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8").unwrap(); + // nu5 block time + 1 + let fake_min_time = DateTime32::from(1654008606); + // nu5 block time + 12 + let fake_cur_time = DateTime32::from(1654008617); + // nu5 block time + 123 + let fake_max_time = DateTime32::from(1654008728); + let fake_difficulty = CompactDifficulty::from(ExpandedDifficulty::from(U256::MAX)); + + let (mock_chain_tip, mock_chain_tip_sender) = MockChainTip::new(); + mock_chain_tip_sender.send_best_tip_height(fake_tip_height); + mock_chain_tip_sender.send_best_tip_hash(fake_tip_hash); + mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(0)); + + // Init RPC + let get_block_template_rpc = GetBlockTemplateRpcImpl::new( + Mainnet, + mining_config, + Buffer::new(mempool.clone(), 1), + read_state.clone(), + mock_chain_tip, + chain_verifier, + mock_sync_status.clone(), + MockAddressBookPeers::default(), + ); + + // Fake the ChainInfo response + let mock_read_state_request_handler = async move { + read_state + .expect_request_that(|req| matches!(req, ReadRequest::ChainInfo)) + .await + .respond(ReadResponse::ChainInfo(GetBlockTemplateChainInfo { + expected_difficulty: fake_difficulty, + tip_height: fake_tip_height, + tip_hash: fake_tip_hash, + cur_time: fake_cur_time, + min_time: fake_min_time, + max_time: fake_max_time, + history_tree: fake_history_tree(Mainnet), + })); + }; + + let get_difficulty_fut = get_block_template_rpc.get_difficulty(); + let (get_difficulty, ..) = tokio::join!(get_difficulty_fut, mock_read_state_request_handler,); + + assert_eq!(get_difficulty.unwrap(), 0.00012207194233937495); +}