Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,14 @@ mod tests {

mod contests_requests_query {
use super::*;
use crate::config::PlatformConfig;
use dapi_grpc::platform::v0::get_contested_resources_request::get_contested_resources_request_v0;
use dpp::dashcore::Network;
use dpp::data_contract::TokenConfiguration;
use dpp::prelude::CoreBlockHeight;
use drive::config::DEFAULT_QUERY_LIMIT;
use drive_proof_verifier::types::{ContestedResources, VotePollsGroupedByTimestamp};
use drive_proof_verifier::{ContextProvider, ContextProviderError, FromProof};

#[tokio::test]
async fn test_not_proved_contests_request() {
Expand Down Expand Up @@ -393,6 +400,234 @@ mod tests {
assert_eq!(contests.len(), 2);
}

/// Serves the DPNS contract and no quorum keys, so `FromProof` checks the Drive part
/// of a proof and then stops at the quorum key the signature check asks for
struct DpnsOnlyContextProvider(Arc<DataContract>);

impl ContextProvider for DpnsOnlyContextProvider {
fn get_data_contract(
&self,
id: &Identifier,
_platform_version: &PlatformVersion,
) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
Ok((*id == self.0.id()).then(|| Arc::clone(&self.0)))
}

fn get_token_configuration(
&self,
_token_id: &Identifier,
) -> Result<Option<TokenConfiguration>, ContextProviderError> {
Ok(None)
}

fn get_quorum_public_key(
&self,
_quorum_type: u32,
_quorum_hash: [u8; 32],
_core_chain_locked_height: u32,
) -> Result<[u8; 48], ContextProviderError> {
Err(ContextProviderError::Config(
"no quorum keys in this test".to_string(),
))
}

fn get_platform_activation_height(
&self,
) -> Result<CoreBlockHeight, ContextProviderError> {
Ok(1)
}
}

fn assert_drive_part_of_proof_verified(error: drive_proof_verifier::Error) {
assert!(
matches!(
error,
drive_proof_verifier::Error::QuorumKeyUnavailable { .. }
),
"expected the Drive part of the proof to verify and the signature check to \
stop at the quorum key, got {error:?}"
);
}

/// Drive answers a proved request naming no limit with a page of the node's
/// `default_query_limit` and proves that page. On a node keeping the default (100),
/// the proof of a full page verifies only as that page
#[tokio::test]
async fn should_verify_a_full_default_page_of_contests_requested_with_no_limit() {
verify_101_contests_requested_with_no_limit(DEFAULT_QUERY_LIMIT).await;
}

/// On a node configured with a larger default page, every contest fits it and the
/// proof verifies only as the whole range
#[tokio::test]
async fn should_verify_every_contest_requested_with_no_limit_from_a_node_with_a_larger_default(
) {
verify_101_contests_requested_with_no_limit(200).await;
}

/// Creates 101 contests on a node whose `default_query_limit` is
/// `node_default_query_limit`, and checks that `FromProof` verifies the Drive part of
/// the proofs of a contested resources request and a vote polls by end date request
/// naming no limit
async fn verify_101_contests_requested_with_no_limit(node_default_query_limit: u16) {
let platform_version = PlatformVersion::latest();
let mut config = PlatformConfig::default_testnet();
config.drive.default_query_limit = node_default_query_limit;
let mut platform = TestPlatformBuilder::new()
.with_config(config)
.with_latest_protocol_version()
.build_with_mock_rpc()
.set_genesis_state();

let platform_state = platform.state.load();

// One contest more than the standard default page (100). The suffix leaves out the letters
// DPNS normalizes to digits (i, l and o), which would merge two names into one contest
const SUFFIX_LETTERS: &[u8] = b"abcdefghjkmnpqrstuvwxyz";
let mut dpns_contract = None;
for n in 0..=DEFAULT_QUERY_LIMIT as u64 {
let letter = |i: u64| SUFFIX_LETTERS[i as usize % SUFFIX_LETTERS.len()] as char;
let name = format!(
"contested{}{}",
letter(n / SUFFIX_LETTERS.len() as u64),
letter(n)
);
let (_, _, contract) = create_dpns_identity_name_contest(
&mut platform,
&platform_state,
1_000 + n,
&name,
platform_version,
)
.await;
dpns_contract = Some(contract);
}
let dpns_contract = dpns_contract.expect("expected the dpns contract");
let provider = DpnsOnlyContextProvider(Arc::clone(&dpns_contract));

let domain = dpns_contract
.document_type_for_name("domain")
.expect("expected the domain document type");
let index_name = "parentNameAndLabel".to_string();
let dash_encoded = bincode::encode_to_vec(
Value::Text("dash".to_string()),
bincode::config::standard()
.with_big_endian()
.with_no_limit(),
)
.expect("expected to encode value");

let request = GetContestedResourcesRequest {
version: Some(get_contested_resources_request::Version::V0(
GetContestedResourcesRequestV0 {
contract_id: dpns_contract.id().to_vec(),
document_type_name: domain.name().clone(),
index_name: index_name.clone(),
start_index_values: vec![dash_encoded],
end_index_values: vec![],
start_at_value_info: None,
count: None,
order_ascending: true,
prove: true,
},
)),
};
let response = platform
.query_contested_resources(request.clone(), &platform_state, platform_version)
.expect("expected to execute query")
.into_data()
.expect("expected query to be valid");

let Some(get_contested_resources_response::Version::V0(
GetContestedResourcesResponseV0 {
result: Some(get_contested_resources_response_v0::Result::Proof(proof)),
..
},
)) = response.version.clone()
else {
panic!("expected a proof")
};

// Drive proved a page of the node's default: checked against the whole range and
// against a page of the standard default, only the check matching that page holds
let contests_count = DEFAULT_QUERY_LIMIT as usize + 1;
let mut query = ResolvedVotePollsByDocumentTypeQuery {
contract: DataContractResolvedInfo::BorrowedDataContract(
dpns_contract.as_ref(),
),
document_type_name: domain.name(),
index_name: &index_name,
start_index_values: &vec!["dash".into()],
end_index_values: &vec![],
limit: None,
order_ascending: true,
start_at_value: &None,
};
let whole_range =
query.verify_contests_proof(proof.grovedb_proof.as_ref(), platform_version);
query.limit = Some(DEFAULT_QUERY_LIMIT);
let standard_page =
query.verify_contests_proof(proof.grovedb_proof.as_ref(), platform_version);
if (node_default_query_limit as usize) < contests_count {
whole_range
.expect_err("expected the proof of a full page to fail with no limit");
let (_, contests) =
standard_page.expect("expected the proof to verify as a default page");
assert_eq!(contests.len(), DEFAULT_QUERY_LIMIT as usize);
} else {
let (_, contests) =
whole_range.expect("expected the proof to verify as the whole range");
assert_eq!(contests.len(), contests_count);
standard_page
.expect_err("expected the proof of every contest to fail as a 100 page");
}

let error = <ContestedResources as FromProof<GetContestedResourcesRequest>>::maybe_from_proof_with_metadata(
request,
response,
Network::Testnet,
platform_version,
&provider,
)
.expect_err("expected no quorum key");
assert_drive_part_of_proof_verified(error);

// The same contests read by end date: they all end at once
let request = GetVotePollsByEndDateRequest {
version: Some(get_vote_polls_by_end_date_request::Version::V0(
GetVotePollsByEndDateRequestV0 {
start_time_info: None,
end_time_info: None,
limit: None,
offset: None,
ascending: true,
prove: true,
},
)),
};
let response = platform
.query_vote_polls_by_end_date_query(
request.clone(),
&platform_state,
platform_version,
)
.expect("expected to execute query")
.into_data()
.expect("expected query to be valid");

let error = <VotePollsGroupedByTimestamp as FromProof<
GetVotePollsByEndDateRequest,
>>::maybe_from_proof_with_metadata(
request,
response,
Network::Testnet,
platform_version,
&provider,
)
.expect_err("expected no quorum key");
assert_drive_part_of_proof_verified(error);
}

#[tokio::test]
async fn test_empty_string_start_index_value() {
let platform_version = PlatformVersion::latest();
Expand Down
Loading
Loading