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)
|
/// zcashd reference: [`getblocksubsidy`](https://zcash.github.io/rpc/getblocksubsidy.html)
|
||||||
#[rpc(name = "getblocksubsidy")]
|
#[rpc(name = "getblocksubsidy")]
|
||||||
fn get_block_subsidy(&self, height: Option<u32>) -> BoxFuture<Result<BlockSubsidy>>;
|
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.
|
/// RPC method implementations.
|
||||||
|
@ -904,6 +910,58 @@ where
|
||||||
}
|
}
|
||||||
.boxed()
|
.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.
|
// Put support functions in a submodule, to keep this file small.
|
||||||
|
|
|
@ -390,6 +390,34 @@ pub async fn test_responses<State, ReadState>(
|
||||||
.await
|
.await
|
||||||
.expect("We should have a validate_address::Response");
|
.expect("We should have a validate_address::Response");
|
||||||
snapshot_rpc_validateaddress("invalid", validate_address, &settings);
|
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.
|
/// 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)
|
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"
|
"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