use anyhow::Result;
use chaincraft::{
crypto::ecdsa::{ECDSASigner, ECDSAVerifier},
network::PeerId,
shared::{MessageType, SharedMessage},
storage::MemoryStorage,
ChaincraftNode,
};
use chrono::Utc;
use serde_json::json;
use std::sync::Arc;
use tokio::time::{sleep, Duration};
async fn create_consensus_node() -> Result<ChaincraftNode> {
let id = PeerId::new();
let storage = Arc::new(MemoryStorage::new());
let mut node = ChaincraftNode::new(id, storage);
node.set_port(0); node.start().await?;
Ok(node)
}
#[tokio::test]
async fn test_tendermint_initialization() -> Result<()> {
let mut node = create_consensus_node().await?;
assert!(node.is_running_async().await);
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_validator_setup() -> Result<()> {
let mut node = create_consensus_node().await?;
let validator_msg = json!({
"type": "VALIDATOR_SETUP",
"validator_id": "validator_1",
"public_key": "validator_public_key",
"stake": 1000
});
let msg = SharedMessage::custom("VALIDATOR_SETUP", validator_msg)?;
node.create_shared_message(msg.to_json()?).await?;
sleep(Duration::from_millis(100)).await;
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_block_proposal() -> Result<()> {
let mut node = create_consensus_node().await?;
let proposal = json!({
"type": "PROPOSAL",
"height": 1,
"round": 0,
"block_hash": "proposed_block_hash",
"proposer": "validator_1",
"timestamp": Utc::now().to_rfc3339()
});
let msg = SharedMessage::custom("PROPOSAL", proposal)?;
node.create_shared_message(msg.to_json()?).await?;
sleep(Duration::from_millis(100)).await;
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_prevote_phase() -> Result<()> {
let mut node = create_consensus_node().await?;
let prevote = json!({
"type": "PREVOTE",
"height": 1,
"round": 0,
"block_hash": "voted_block_hash",
"validator": "validator_1"
});
let msg = SharedMessage::custom("PREVOTE", prevote)?;
node.create_shared_message(msg.to_json()?).await?;
sleep(Duration::from_millis(100)).await;
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_precommit_phase() -> Result<()> {
let mut node = create_consensus_node().await?;
let precommit = json!({
"type": "PRECOMMIT",
"height": 1,
"round": 0,
"block_hash": "committed_block_hash",
"validator": "validator_1"
});
let msg = SharedMessage::custom("PRECOMMIT", precommit)?;
node.create_shared_message(msg.to_json()?).await?;
sleep(Duration::from_millis(100)).await;
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_consensus_round_completion() -> Result<()> {
let mut node = create_consensus_node().await?;
let height = 1;
let round = 0;
let proposal = json!({
"type": "PROPOSAL",
"height": height,
"round": round,
"block_hash": "test_block_hash",
"proposer": "validator_1",
"timestamp": Utc::now().to_rfc3339()
});
let msg = SharedMessage::custom("PROPOSAL", proposal)?;
node.create_shared_message(msg.to_json()?).await?;
sleep(Duration::from_millis(50)).await;
let prevote = json!({
"type": "PREVOTE",
"height": height,
"round": round,
"block_hash": "test_block_hash",
"validator": "validator_1"
});
let msg = SharedMessage::custom("PREVOTE", prevote)?;
node.create_shared_message(msg.to_json()?).await?;
sleep(Duration::from_millis(50)).await;
let precommit = json!({
"type": "PRECOMMIT",
"height": height,
"round": round,
"block_hash": "test_block_hash",
"validator": "validator_1"
});
let msg = SharedMessage::custom("PRECOMMIT", precommit)?;
node.create_shared_message(msg.to_json()?).await?;
sleep(Duration::from_millis(100)).await;
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_byzantine_fault_tolerance() -> Result<()> {
let mut nodes = Vec::new();
for _ in 0..4 {
let node = create_consensus_node().await?;
nodes.push(node);
}
let byzantine_msg = json!({
"type": "PREVOTE",
"height": 1,
"round": 0,
"block_hash": "block_hash_1",
"validator": "byzantine_validator"
});
let msg = SharedMessage::custom("PREVOTE", byzantine_msg)?;
nodes[0].create_shared_message(msg.to_json()?).await?;
let conflicting_msg = json!({
"type": "PREVOTE",
"height": 1,
"round": 0,
"block_hash": "block_hash_2",
"validator": "byzantine_validator"
});
let msg = SharedMessage::custom("PREVOTE", conflicting_msg)?;
nodes[0].create_shared_message(msg.to_json()?).await?;
sleep(Duration::from_millis(100)).await;
for (i, node) in nodes.iter_mut().enumerate().skip(1) {
let honest_msg = json!({
"type": "PREVOTE",
"height": 1,
"round": 0,
"block_hash": "legitimate_block_hash",
"validator": format!("honest_validator_{}", i)
});
let msg = SharedMessage::custom("PREVOTE", honest_msg)?;
node.create_shared_message(msg.to_json()?).await?;
}
for mut node in nodes {
node.close().await?;
}
Ok(())
}
#[tokio::test]
async fn test_validator_signature_verification() {
let signer = ECDSASigner::new().unwrap();
let verifier = ECDSAVerifier::new();
let consensus_data = b"height:1,round:0,block_hash:test_hash";
let signature = signer.sign(consensus_data).unwrap();
let public_key_pem = signer.get_public_key_pem().unwrap();
let is_valid = verifier
.verify(consensus_data, &signature, &public_key_pem)
.unwrap();
assert!(is_valid);
let tampered_data = b"height:1,round:0,block_hash:tampered_hash";
let is_invalid = verifier
.verify(tampered_data, &signature, &public_key_pem)
.unwrap();
assert!(!is_invalid);
}
#[tokio::test]
async fn test_network_partition_recovery() -> Result<()> {
let mut node1 = create_consensus_node().await?;
let mut node2 = create_consensus_node().await?;
let partition_msg = json!({
"message_type": "NETWORK_PARTITION",
"node": "node1",
"status": "isolated"
});
node1.create_shared_message_with_data(partition_msg).await?;
sleep(Duration::from_millis(100)).await;
let recovery_msg = json!({
"message_type": "NETWORK_RECOVERY",
"node": "node1",
"status": "reconnected",
"sync_height": 5
});
node1
.create_shared_message_with_data(recovery_msg.clone())
.await?;
node2.create_shared_message_with_data(recovery_msg).await?;
sleep(Duration::from_millis(200)).await;
node1.close().await?;
node2.close().await?;
Ok(())
}
#[tokio::test]
async fn test_consensus_timeout_handling() -> Result<()> {
let mut node = create_consensus_node().await?;
let timeout_msg = json!({
"message_type": "CONSENSUS_TIMEOUT",
"height": 1,
"round": 0,
"timeout_type": "propose_timeout",
"validator": "validator_1"
});
node.create_shared_message_with_data(timeout_msg).await?;
sleep(Duration::from_millis(100)).await;
let round_increment = json!({
"message_type": "ROUND_INCREMENT",
"height": 1,
"new_round": 1,
"reason": "propose_timeout"
});
node.create_shared_message_with_data(round_increment)
.await?;
sleep(Duration::from_millis(100)).await;
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_validator_set_changes() -> Result<()> {
let mut node = create_consensus_node().await?;
let add_validator = json!({
"message_type": "ADD_VALIDATOR",
"validator_id": "new_validator",
"public_key": "new_validator_pubkey",
"stake": 500
});
node.create_shared_message_with_data(add_validator).await?;
sleep(Duration::from_millis(100)).await;
let remove_validator = json!({
"message_type": "REMOVE_VALIDATOR",
"validator_id": "old_validator",
"reason": "insufficient_stake"
});
node.create_shared_message_with_data(remove_validator)
.await?;
sleep(Duration::from_millis(100)).await;
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_block_finality() -> Result<()> {
let mut node = create_consensus_node().await?;
let finalized_block = json!({
"message_type": "BLOCK_FINALIZED",
"height": 1,
"block_hash": "finalized_block_hash",
"commit_signatures": ["sig1", "sig2", "sig3"],
"timestamp": chrono::Utc::now().to_rfc3339()
});
node.create_shared_message_with_data(finalized_block)
.await?;
sleep(Duration::from_millis(100)).await;
let revert_attempt = json!({
"message_type": "REVERT_BLOCK",
"height": 1,
"new_block_hash": "different_block_hash"
});
let _result = node.create_shared_message_with_data(revert_attempt).await;
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_state_machine_replication() -> Result<()> {
let mut node = create_consensus_node().await?;
for i in 0..5 {
let tx = json!({
"message_type": "STATE_MACHINE_TX",
"height": 1,
"tx_id": i,
"operation": "transfer",
"from": "account_a",
"to": "account_b",
"amount": 100
});
node.create_shared_message_with_data(tx).await?;
sleep(Duration::from_millis(20)).await;
}
let state_query = json!({
"message_type": "QUERY_STATE",
"height": 1,
"account": "account_a"
});
node.create_shared_message_with_data(state_query).await?;
sleep(Duration::from_millis(100)).await;
node.close().await?;
Ok(())
}
#[tokio::test]
async fn test_message_type_handling() -> Result<()> {
let mut node = create_consensus_node().await?;
let message_types = vec![
MessageType::PeerDiscovery,
MessageType::RequestLocalPeers,
MessageType::LocalPeers,
MessageType::Custom("test".to_string()),
];
for msg_type in message_types {
let data = json!({ "type": msg_type.to_string() });
let hash = node.create_shared_message_with_data(data.clone()).await?;
let stored = node.get_object(&hash).await?;
let value: serde_json::Value = serde_json::from_str(&stored)?;
let msg_type_json = &value["message_type"];
match msg_type {
MessageType::Custom(ref s) => {
assert!(msg_type_json.is_object());
assert_eq!(msg_type_json.get("Custom").and_then(|v| v.as_str()), Some(s.as_str()));
},
_ => {
assert_eq!(msg_type_json, &serde_json::Value::String(msg_type.to_string()));
},
}
}
node.close().await?;
Ok(())
}