implement `getdifficulty` rpc method (#6099)
This commit is contained in:
parent
311daaa19e
commit
43cf7e6852
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||
expression: difficulty
|
||||
---
|
||||
14134749558280407000000000000000000000000000000000000000000000000000000000.0
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||
expression: difficulty
|
||||
---
|
||||
3618495886919784300000000000000000000000000000000000000000000000000000000000.0
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue