implement `getdifficulty` rpc method (#6099)

This commit is contained in:
Alfredo Garcia 2023-02-06 21:01:45 -03:00 committed by GitHub
parent 311daaa19e
commit 43cf7e6852
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 186 additions and 0 deletions

View File

@ -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<u32>) -> BoxFuture<Result<BlockSubsidy>>;
/// 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<Result<f64>>;
}
/// RPC method implementations.
@ -904,6 +910,58 @@ where
}
.boxed()
}
fn get_difficulty(&self) -> BoxFuture<Result<f64>> {
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.

View File

@ -390,6 +390,34 @@ pub async fn test_responses<State, ReadState>(
.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));
}

View File

@ -0,0 +1,5 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: difficulty
---
14134749558280407000000000000000000000000000000000000000000000000000000000.0

View File

@ -0,0 +1,5 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: difficulty
---
3618495886919784300000000000000000000000000000000000000000000000000000000000.0

View File

@ -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);
}