Skip to content

Commit 2b0438a

Browse files
test: cover abci handler, drive contract/document/group, drive-abci config (#3516)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4aa73ca commit 2b0438a

21 files changed

Lines changed: 3082 additions & 1 deletion

File tree

  • packages
    • rs-drive-abci/src
      • abci/handler
      • execution/platform_events
        • core_chain_lock
          • verify_chain_lock_locally/v0
          • verify_chain_lock/v0
        • state_transition_processing/validate_fees_of_event/v0
    • rs-drive/src/drive
      • contract
      • document
        • index_uniqueness
        • insert_contested
        • query
          • query_contested_documents_storage/v0
          • query_contested_documents_vote_state/v0
          • query_documents_with_flags/v0
          • query_documents/v0
      • group/fetch
        • fetch_action_id_has_signer/v0
        • fetch_action_id_info_keep_serialized/v0
        • fetch_action_id_signers_power/v0
        • fetch_action_infos/v0
        • fetch_action_is_closed/v0
        • fetch_action_signers/v0
        • fetch_active_action_info/v0
        • fetch_group_infos/v0
        • fetch_group_info/v0

‎packages/rs-drive-abci/src/abci/handler/check_tx.rs‎

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,114 @@ where
175175
})
176176
})
177177
}
178+
179+
#[cfg(test)]
180+
mod tests {
181+
use super::*;
182+
use crate::rpc::core::MockCoreRPCLike;
183+
use crate::test::helpers::setup::TestPlatformBuilder;
184+
185+
/// Exercises the early-return path in `check_tx` where `r#type.try_into()?`
186+
/// propagates a `BadRequest` error before the validation result pipeline is
187+
/// reached. Unlike the `.or_else` branch which converts errors into responses,
188+
/// this error path propagates out of the handler entirely.
189+
#[test]
190+
fn check_tx_invalid_check_tx_type_propagates_bad_request_error() {
191+
let platform = TestPlatformBuilder::new()
192+
.with_latest_protocol_version()
193+
.build_with_mock_rpc()
194+
.set_initial_state_structure();
195+
let core_rpc = MockCoreRPCLike::new();
196+
197+
let request = proto::RequestCheckTx {
198+
tx: vec![1, 2, 3],
199+
// Only 0 (New) and 1 (Recheck) are valid. 2 is rejected by TryFrom.
200+
r#type: 2,
201+
};
202+
203+
let result = check_tx(&platform.platform, &core_rpc, request);
204+
205+
assert!(result.is_err());
206+
let err = result.unwrap_err();
207+
let err_str = err.to_string();
208+
assert!(
209+
err_str.contains("CheckTxLevel") || err_str.contains("2"),
210+
"expected BadRequest about CheckTxLevel, got: {}",
211+
err_str
212+
);
213+
}
214+
215+
/// Exercises the error-path of the main `and_then` branch where the
216+
/// `check_tx_v0` code produces a `ValidationResult` with a consensus error
217+
/// (`InvalidEncoding` from `decode_raw_state_transitions`) for garbage bytes.
218+
/// Covers the code/info propagation via `response_info_for_version`.
219+
#[test]
220+
fn check_tx_garbage_bytes_returns_nonzero_consensus_code() {
221+
let platform = TestPlatformBuilder::new()
222+
.with_latest_protocol_version()
223+
.build_with_mock_rpc()
224+
.set_initial_state_structure();
225+
let core_rpc = MockCoreRPCLike::new();
226+
227+
// Random garbage is guaranteed to fail state transition deserialization.
228+
let request = proto::RequestCheckTx {
229+
tx: vec![0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA],
230+
r#type: 0,
231+
};
232+
233+
let response = check_tx(&platform.platform, &core_rpc, request)
234+
.expect("handler should return Ok with a consensus error code in the response");
235+
236+
// Rejected, non-zero code and non-empty info (base64-encoded consensus info).
237+
assert_ne!(response.code, 0);
238+
assert!(
239+
!response.info.is_empty(),
240+
"expected non-empty response info for consensus error"
241+
);
242+
assert_eq!(response.gas_wanted, 0);
243+
}
244+
245+
/// Recheck mode (type = 1) should take the same code paths as new, but with
246+
/// the different label; here we confirm the handler also returns a response
247+
/// (not an error) for garbage bytes in Recheck mode.
248+
#[test]
249+
fn check_tx_garbage_bytes_recheck_returns_response() {
250+
let platform = TestPlatformBuilder::new()
251+
.with_latest_protocol_version()
252+
.build_with_mock_rpc()
253+
.set_initial_state_structure();
254+
let core_rpc = MockCoreRPCLike::new();
255+
256+
let request = proto::RequestCheckTx {
257+
tx: vec![0x00, 0x01, 0x02, 0x03],
258+
r#type: 1, // Recheck
259+
};
260+
261+
let response = check_tx(&platform.platform, &core_rpc, request)
262+
.expect("recheck with garbage bytes should not produce an Err");
263+
264+
assert_ne!(response.code, 0);
265+
}
266+
267+
/// An empty body is a boundary case: depending on decoding, it produces an
268+
/// `InvalidEncoding` error. The handler should still return Ok with a
269+
/// rejection code - not propagate an error.
270+
#[test]
271+
fn check_tx_empty_tx_body_returns_rejection_code() {
272+
let platform = TestPlatformBuilder::new()
273+
.with_latest_protocol_version()
274+
.build_with_mock_rpc()
275+
.set_initial_state_structure();
276+
let core_rpc = MockCoreRPCLike::new();
277+
278+
let request = proto::RequestCheckTx {
279+
tx: vec![],
280+
r#type: 0,
281+
};
282+
283+
let response = check_tx(&platform.platform, &core_rpc, request)
284+
.expect("empty tx should not propagate an Err");
285+
286+
assert_ne!(response.code, 0);
287+
}
288+
}

‎packages/rs-drive-abci/src/config.rs‎

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,10 @@ impl Default for PlatformTestConfig {
917917
#[cfg(test)]
918918
mod tests {
919919
use super::FromEnv;
920+
use crate::config::{
921+
ChainLockConfig, ConsensusCoreRpcConfig, CoreConfig, InstantLockConfig, PlatformConfig,
922+
QuorumLikeConfig, ValidatorSetConfig,
923+
};
920924
use crate::logging::LogDestination;
921925
use dpp::dashcore::Network;
922926
use dpp::dashcore_rpc::dashcore_rpc_json::QuorumType;
@@ -969,4 +973,265 @@ mod tests {
969973
assert_eq!(config.network, config.drive.network);
970974
assert_eq!(config.network, Network::Testnet);
971975
}
976+
977+
// --- `from_str_to_network_with_aliases`: valid aliases and error path ---
978+
979+
/// Ensures every network alias accepted by the deserializer maps to the
980+
/// expected `Network`, case-insensitively. This exercises each match arm in
981+
/// `from_str_to_network_with_aliases`, which is otherwise only called at
982+
/// most once per integration-test setup.
983+
#[test]
984+
fn network_aliases_deserialize_all_variants_case_insensitively() {
985+
let cases = &[
986+
("\"dash\"", Network::Mainnet),
987+
("\"DASH\"", Network::Mainnet),
988+
("\"MainNet\"", Network::Mainnet),
989+
("\"main\"", Network::Mainnet),
990+
("\"local\"", Network::Regtest),
991+
("\"regtest\"", Network::Regtest),
992+
("\"testnet\"", Network::Testnet),
993+
("\"TEST\"", Network::Testnet),
994+
("\"devnet\"", Network::Devnet),
995+
("\"Dev\"", Network::Devnet),
996+
];
997+
998+
for (input, expected) in cases {
999+
let mut de = serde_json::Deserializer::from_str(input);
1000+
let network = super::from_str_to_network_with_aliases(&mut de)
1001+
.expect("alias should parse successfully");
1002+
assert_eq!(
1003+
&network, expected,
1004+
"input {} should map to {:?}",
1005+
input, expected
1006+
);
1007+
}
1008+
}
1009+
1010+
/// Verifies that an unknown network alias produces a clear deserializer error.
1011+
/// This covers the final arm of `from_str_to_network_with_aliases`.
1012+
#[test]
1013+
fn network_alias_unknown_value_returns_custom_error() {
1014+
let mut de = serde_json::Deserializer::from_str("\"nonsense_network\"");
1015+
let err = super::from_str_to_network_with_aliases(&mut de)
1016+
.expect_err("unknown network should fail");
1017+
let msg = err.to_string();
1018+
assert!(
1019+
msg.contains("unknown network") && msg.contains("nonsense_network"),
1020+
"error message should name the offending value, got: {}",
1021+
msg
1022+
);
1023+
}
1024+
1025+
// --- `deserialize_quorum_type`: numeric/string/UNKNOWN error path ---
1026+
1027+
/// Covers the string-name branch of `deserialize_quorum_type`.
1028+
#[test]
1029+
fn deserialize_quorum_type_accepts_string_names() {
1030+
// All numeric fields use `from_str_or_number`, which requires a JSON
1031+
// string (even for numbers).
1032+
let json = r#"{
1033+
"validator_set_quorum_type": "llmq_25_67",
1034+
"validator_set_quorum_size": "25",
1035+
"validator_set_quorum_window": "24",
1036+
"validator_set_quorum_active_signers": "24",
1037+
"validator_set_quorum_rotation": "false"
1038+
}"#;
1039+
let cfg: ValidatorSetConfig =
1040+
serde_json::from_str(json).expect("quorum type string should deserialize");
1041+
assert_eq!(cfg.quorum_type, QuorumType::Llmq25_67);
1042+
}
1043+
1044+
/// Covers the numeric (u32) branch of `deserialize_quorum_type` (a number
1045+
/// serialized as a JSON string still goes through the `parse::<u32>()` path).
1046+
#[test]
1047+
fn deserialize_quorum_type_accepts_numeric_string() {
1048+
let json = r#"{
1049+
"validator_set_quorum_type": "6",
1050+
"validator_set_quorum_size": "25",
1051+
"validator_set_quorum_window": "24",
1052+
"validator_set_quorum_active_signers": "24",
1053+
"validator_set_quorum_rotation": "false"
1054+
}"#;
1055+
let cfg: ValidatorSetConfig =
1056+
serde_json::from_str(json).expect("numeric quorum type should deserialize");
1057+
// QuorumType::from(6) should be a known variant; we only assert it is not UNKNOWN.
1058+
assert_ne!(cfg.quorum_type, QuorumType::UNKNOWN);
1059+
}
1060+
1061+
/// Covers the `UNKNOWN` rejection branch of `deserialize_quorum_type`.
1062+
#[test]
1063+
fn deserialize_quorum_type_rejects_unknown_names() {
1064+
let json = r#"{
1065+
"validator_set_quorum_type": "this_is_not_a_quorum",
1066+
"validator_set_quorum_size": "25",
1067+
"validator_set_quorum_window": "24",
1068+
"validator_set_quorum_active_signers": "24",
1069+
"validator_set_quorum_rotation": "false"
1070+
}"#;
1071+
let err = serde_json::from_str::<ValidatorSetConfig>(json)
1072+
.expect_err("unknown quorum type name should fail");
1073+
let msg = err.to_string();
1074+
assert!(
1075+
msg.contains("unsupported") && msg.contains("QUORUM_TYPE"),
1076+
"expected unsupported QUORUM_TYPE error, got: {}",
1077+
msg
1078+
);
1079+
}
1080+
1081+
// --- Core RPC URL composition and defaults ---
1082+
1083+
/// Exercises `ConsensusCoreRpcConfig::url` formatting.
1084+
#[test]
1085+
fn consensus_core_rpc_url_composes_host_and_port() {
1086+
let cfg = ConsensusCoreRpcConfig {
1087+
host: "node.example".to_string(),
1088+
port: 9998,
1089+
username: "u".to_string(),
1090+
password: "p".to_string(),
1091+
};
1092+
assert_eq!(cfg.url(), "node.example:9998");
1093+
}
1094+
1095+
/// Exercises `CheckTxCoreRpcConfig::url` formatting.
1096+
#[test]
1097+
fn check_tx_core_rpc_url_composes_host_and_port() {
1098+
let cfg = super::CheckTxCoreRpcConfig {
1099+
host: "127.0.0.1".to_string(),
1100+
port: 1,
1101+
username: String::new(),
1102+
password: String::new(),
1103+
};
1104+
assert_eq!(cfg.url(), "127.0.0.1:1");
1105+
}
1106+
1107+
/// `CoreConfig::default()` should be empty strings and zero port (covers
1108+
/// the auto-derived `Default` with both flattened members).
1109+
#[test]
1110+
fn core_config_default_is_empty() {
1111+
let cfg = CoreConfig::default();
1112+
assert_eq!(cfg.consensus_rpc.host, "");
1113+
assert_eq!(cfg.consensus_rpc.port, 0);
1114+
assert_eq!(cfg.check_tx_rpc.host, "");
1115+
assert_eq!(cfg.check_tx_rpc.port, 0);
1116+
}
1117+
1118+
// --- `default_for_network` dispatch, including the catch-all ---
1119+
1120+
/// Exercises the `Network::Mainnet`, `Network::Testnet`, `Network::Devnet`,
1121+
/// `Network::Regtest` arms of `default_for_network`.
1122+
#[test]
1123+
fn default_for_network_dispatches_all_known_networks() {
1124+
let mainnet = PlatformConfig::default_for_network(Network::Mainnet);
1125+
assert_eq!(mainnet.network, Network::Mainnet);
1126+
assert_eq!(mainnet.validator_set.quorum_type, QuorumType::Llmq100_67);
1127+
1128+
let testnet = PlatformConfig::default_for_network(Network::Testnet);
1129+
assert_eq!(testnet.network, Network::Testnet);
1130+
assert_eq!(testnet.validator_set.quorum_type, QuorumType::Llmq25_67);
1131+
1132+
let devnet = PlatformConfig::default_for_network(Network::Devnet);
1133+
// default_devnet currently sets network to Regtest (see impl).
1134+
assert_eq!(devnet.network, Network::Regtest);
1135+
1136+
let regtest = PlatformConfig::default_for_network(Network::Regtest);
1137+
assert_eq!(regtest.network, Network::Regtest);
1138+
}
1139+
1140+
// --- `ValidatorSetConfig::default_100_67` and QuorumLikeConfig getters ---
1141+
1142+
/// Exercises `default_100_67` + every getter in `QuorumLikeConfig` for
1143+
/// `ValidatorSetConfig` - these getters are pure accessors that would
1144+
/// otherwise be uncovered outside the actual consensus path.
1145+
#[test]
1146+
fn validator_set_default_100_67_accessors() {
1147+
let cfg = ValidatorSetConfig::default_100_67();
1148+
assert_eq!(cfg.quorum_type(), QuorumType::Llmq100_67);
1149+
assert_eq!(cfg.quorum_size(), 100);
1150+
assert_eq!(cfg.quorum_window(), 24);
1151+
assert_eq!(cfg.quorum_active_signers(), 24);
1152+
assert!(!cfg.quorum_rotation());
1153+
}
1154+
1155+
/// Same but for `ChainLockConfig::default_100_67`.
1156+
#[test]
1157+
fn chain_lock_default_100_67_accessors() {
1158+
let cfg = ChainLockConfig::default_100_67();
1159+
assert_eq!(cfg.quorum_type(), QuorumType::Llmq100_67);
1160+
assert_eq!(cfg.quorum_size(), 100);
1161+
assert_eq!(cfg.quorum_window(), 24);
1162+
assert_eq!(cfg.quorum_active_signers(), 24);
1163+
assert!(!cfg.quorum_rotation());
1164+
}
1165+
1166+
/// `ChainLockConfig::default()` is Mainnet LLMQ400_60 - cover all getters.
1167+
#[test]
1168+
fn chain_lock_default_is_llmq_400_60() {
1169+
let cfg = ChainLockConfig::default();
1170+
assert_eq!(cfg.quorum_type(), QuorumType::Llmq400_60);
1171+
assert_eq!(cfg.quorum_size(), 400);
1172+
assert_eq!(cfg.quorum_window(), 24 * 12);
1173+
assert_eq!(cfg.quorum_active_signers(), 4);
1174+
assert!(!cfg.quorum_rotation());
1175+
}
1176+
1177+
/// `InstantLockConfig::default()` is DIP24 rotated LLMQ60_75.
1178+
#[test]
1179+
fn instant_lock_default_is_llmq_60_75_rotated() {
1180+
let cfg = InstantLockConfig::default();
1181+
assert_eq!(cfg.quorum_type(), QuorumType::Llmq60_75);
1182+
assert_eq!(cfg.quorum_size(), 60);
1183+
assert_eq!(cfg.quorum_window(), 24 * 12);
1184+
assert_eq!(cfg.quorum_active_signers(), 32);
1185+
assert!(cfg.quorum_rotation());
1186+
}
1187+
1188+
/// `InstantLockConfig::default_100_67` is the classic LLMQ variant.
1189+
#[test]
1190+
fn instant_lock_default_100_67_accessors() {
1191+
let cfg = InstantLockConfig::default_100_67();
1192+
assert_eq!(cfg.quorum_type(), QuorumType::Llmq100_67);
1193+
assert_eq!(cfg.quorum_size(), 100);
1194+
assert_eq!(cfg.quorum_window(), 24);
1195+
assert_eq!(cfg.quorum_active_signers(), 24);
1196+
assert!(!cfg.quorum_rotation());
1197+
}
1198+
1199+
/// Exercises `PlatformConfig::default()` which delegates to `default_mainnet`.
1200+
#[test]
1201+
fn platform_config_default_is_mainnet() {
1202+
let cfg = PlatformConfig::default();
1203+
assert_eq!(cfg.network, Network::Mainnet);
1204+
assert_eq!(cfg.validator_set.quorum_type, QuorumType::Llmq100_67);
1205+
assert_eq!(cfg.chain_lock.quorum_type, QuorumType::Llmq400_60);
1206+
assert_eq!(cfg.instant_lock.quorum_type, QuorumType::Llmq60_75);
1207+
assert_eq!(cfg.block_spacing_ms, 5000);
1208+
}
1209+
1210+
// --- `ExecutionConfig` default values ---
1211+
1212+
/// Exercises all four default-provider functions on `ExecutionConfig`.
1213+
#[test]
1214+
fn execution_config_defaults() {
1215+
let cfg = super::ExecutionConfig::default();
1216+
assert!(cfg.verify_sum_trees);
1217+
assert!(cfg.verify_token_sum_trees);
1218+
assert!(cfg.use_document_triggers);
1219+
assert_eq!(cfg.epoch_time_length_s, 788400);
1220+
}
1221+
1222+
// --- `serialize_quorum_type` round-trip via Serialize ---
1223+
1224+
/// Covers the `serialize_quorum_type` codepath (symmetric pair of the
1225+
/// deserialize tests above).
1226+
#[test]
1227+
fn validator_set_config_serializes_quorum_type_as_string() {
1228+
let cfg = ValidatorSetConfig::default_100_67();
1229+
let json = serde_json::to_string(&cfg).expect("valid serialize");
1230+
// The serialized representation must use the textual form.
1231+
assert!(
1232+
json.contains("llmq_100_67") || json.contains("Llmq100_67"),
1233+
"expected textual quorum type in JSON, got: {}",
1234+
json
1235+
);
1236+
}
9721237
}

0 commit comments

Comments
 (0)