chaincraft_rust/examples/
tendermint.rs

1use crate::{
2    crypto::{
3        ecdsa::{ECDSASigner, ECDSAVerifier},
4        KeyType, PrivateKey, PublicKey, Signature,
5    },
6    error::{ChaincraftError, Result},
7    shared::{MessageType, SharedMessage, SharedObjectId},
8    shared_object::ApplicationObject,
9};
10use async_trait::async_trait;
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use sha2::{Digest, Sha256};
14use std::collections::{HashMap, HashSet};
15
16/// Tendermint consensus message types
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
18pub enum TendermintMessageType {
19    Proposal {
20        height: u64,
21        round: u32,
22        block_hash: String,
23        proposer: String,
24        timestamp: DateTime<Utc>,
25        signature: String,
26    },
27    Prevote {
28        height: u64,
29        round: u32,
30        block_hash: Option<String>, // None for nil vote
31        validator: String,
32        signature: String,
33    },
34    Precommit {
35        height: u64,
36        round: u32,
37        block_hash: Option<String>, // None for nil vote
38        validator: String,
39        signature: String,
40    },
41    ValidatorSet {
42        validators: Vec<ValidatorInfo>,
43        height: u64,
44    },
45    BlockCommit {
46        height: u64,
47        block_hash: String,
48        commit_signatures: Vec<String>,
49        timestamp: DateTime<Utc>,
50    },
51}
52
53/// Validator information
54#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
55pub struct ValidatorInfo {
56    pub address: String,
57    pub public_key: String,
58    pub voting_power: u64,
59    pub active: bool,
60}
61
62/// Block data structure
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Block {
65    pub height: u64,
66    pub hash: String,
67    pub previous_hash: String,
68    pub timestamp: DateTime<Utc>,
69    pub proposer: String,
70    pub transactions: Vec<serde_json::Value>,
71    pub commit_signatures: Vec<String>,
72}
73
74/// Tendermint consensus state
75#[derive(Debug, Clone, PartialEq)]
76pub enum ConsensusState {
77    Propose,
78    Prevote,
79    Precommit,
80    Commit,
81}
82
83/// Vote information
84#[derive(Debug, Clone)]
85pub struct Vote {
86    pub validator: String,
87    pub block_hash: Option<String>,
88    pub signature: String,
89    pub timestamp: DateTime<Utc>,
90}
91
92/// Tendermint BFT consensus object
93#[derive(Debug)]
94pub struct TendermintObject {
95    pub id: SharedObjectId,
96    pub validators: HashMap<String, ValidatorInfo>,
97    pub blocks: Vec<Block>,
98    pub current_height: u64,
99    pub current_round: u32,
100    pub state: ConsensusState,
101    pub proposals: HashMap<(u64, u32), TendermintMessageType>,
102    pub prevotes: HashMap<(u64, u32), HashMap<String, Vote>>,
103    pub precommits: HashMap<(u64, u32), HashMap<String, Vote>>,
104    pub locked_block: Option<String>,
105    pub locked_round: Option<u32>,
106    pub my_validator_address: String,
107    pub signer: ECDSASigner,
108    pub verifier: ECDSAVerifier,
109    pub messages: Vec<TendermintMessageType>,
110}
111
112impl TendermintObject {
113    pub fn new() -> Result<Self> {
114        let signer = ECDSASigner::new()?;
115        let my_validator_address = signer.get_public_key_pem()?;
116
117        // Create genesis block
118        let genesis_block = Block {
119            height: 0,
120            hash: "genesis_hash".to_string(),
121            previous_hash: "".to_string(),
122            timestamp: Utc::now(),
123            proposer: "genesis".to_string(),
124            transactions: vec![],
125            commit_signatures: vec![],
126        };
127
128        Ok(Self {
129            id: SharedObjectId::new(),
130            validators: HashMap::new(),
131            blocks: vec![genesis_block],
132            current_height: 1,
133            current_round: 0,
134            state: ConsensusState::Propose,
135            proposals: HashMap::new(),
136            prevotes: HashMap::new(),
137            precommits: HashMap::new(),
138            locked_block: None,
139            locked_round: None,
140            my_validator_address,
141            signer,
142            verifier: ECDSAVerifier::new(),
143            messages: Vec::new(),
144        })
145    }
146
147    /// Add a validator to the set
148    pub fn add_validator(&mut self, address: String, public_key: String, voting_power: u64) {
149        let validator = ValidatorInfo {
150            address: address.clone(),
151            public_key,
152            voting_power,
153            active: true,
154        };
155        self.validators.insert(address, validator);
156    }
157
158    /// Get total voting power of active validators
159    pub fn total_voting_power(&self) -> u64 {
160        self.validators
161            .values()
162            .filter(|v| v.active)
163            .map(|v| v.voting_power)
164            .sum()
165    }
166
167    /// Check if we have +2/3 majority
168    pub fn has_majority(&self, voting_power: u64) -> bool {
169        voting_power * 3 > self.total_voting_power() * 2
170    }
171
172    /// Process a proposal message
173    pub fn process_proposal(&mut self, proposal: TendermintMessageType) -> Result<bool> {
174        if let TendermintMessageType::Proposal { height, round, .. } = &proposal {
175            if *height == self.current_height && *round == self.current_round {
176                self.proposals.insert((*height, *round), proposal.clone());
177                self.messages.push(proposal);
178                return Ok(true);
179            }
180        }
181        Ok(false)
182    }
183
184    /// Process a prevote message
185    pub fn process_prevote(&mut self, prevote: TendermintMessageType) -> Result<bool> {
186        if let TendermintMessageType::Prevote {
187            height,
188            round,
189            block_hash,
190            validator,
191            signature,
192        } = &prevote
193        {
194            if *height == self.current_height && *round == self.current_round {
195                let vote = Vote {
196                    validator: validator.clone(),
197                    block_hash: block_hash.clone(),
198                    signature: signature.clone(),
199                    timestamp: Utc::now(),
200                };
201
202                self.prevotes
203                    .entry((*height, *round))
204                    .or_default()
205                    .insert(validator.clone(), vote);
206
207                self.messages.push(prevote);
208                return Ok(true);
209            }
210        }
211        Ok(false)
212    }
213
214    /// Process a precommit message
215    pub fn process_precommit(&mut self, precommit: TendermintMessageType) -> Result<bool> {
216        if let TendermintMessageType::Precommit {
217            height,
218            round,
219            block_hash,
220            validator,
221            signature,
222        } = &precommit
223        {
224            if *height == self.current_height && *round == self.current_round {
225                let vote = Vote {
226                    validator: validator.clone(),
227                    block_hash: block_hash.clone(),
228                    signature: signature.clone(),
229                    timestamp: Utc::now(),
230                };
231
232                self.precommits
233                    .entry((*height, *round))
234                    .or_default()
235                    .insert(validator.clone(), vote);
236
237                self.messages.push(precommit);
238                return Ok(true);
239            }
240        }
241        Ok(false)
242    }
243
244    /// Check if we can commit a block
245    pub fn can_commit(&self) -> Option<String> {
246        if let Some(precommits) = self
247            .precommits
248            .get(&(self.current_height, self.current_round))
249        {
250            let mut vote_counts: HashMap<Option<String>, u64> = HashMap::new();
251
252            for vote in precommits.values() {
253                if let Some(validator) = self.validators.get(&vote.validator) {
254                    *vote_counts.entry(vote.block_hash.clone()).or_insert(0) +=
255                        validator.voting_power;
256                }
257            }
258
259            for (block_hash, voting_power) in vote_counts {
260                if let Some(hash) = block_hash {
261                    if self.has_majority(voting_power) {
262                        return Some(hash);
263                    }
264                }
265            }
266        }
267        None
268    }
269
270    /// Commit a block
271    pub fn commit_block(&mut self, block_hash: String) -> Result<()> {
272        let block = Block {
273            height: self.current_height,
274            hash: block_hash.clone(),
275            previous_hash: self.blocks.last().unwrap().hash.clone(),
276            timestamp: Utc::now(),
277            proposer: self.my_validator_address.clone(),
278            transactions: vec![], // Would contain actual transactions
279            commit_signatures: self
280                .precommits
281                .get(&(self.current_height, self.current_round))
282                .map(|votes| votes.values().map(|v| v.signature.clone()).collect())
283                .unwrap_or_default(),
284        };
285
286        self.blocks.push(block);
287        self.current_height += 1;
288        self.current_round = 0;
289        self.state = ConsensusState::Propose;
290        self.locked_block = None;
291        self.locked_round = None;
292
293        // Clean up old votes
294        self.prevotes.retain(|&(h, _), _| h >= self.current_height);
295        self.precommits
296            .retain(|&(h, _), _| h >= self.current_height);
297        self.proposals.retain(|&(h, _), _| h >= self.current_height);
298
299        Ok(())
300    }
301
302    /// Create a proposal for the current height/round
303    pub fn create_proposal(
304        &self,
305        transactions: Vec<serde_json::Value>,
306    ) -> Result<TendermintMessageType> {
307        let block_data = serde_json::json!({
308            "height": self.current_height,
309            "round": self.current_round,
310            "previous_hash": self.blocks.last().unwrap().hash,
311            "transactions": transactions,
312            "timestamp": Utc::now().to_rfc3339()
313        });
314
315        let block_hash = format!("{:x}", sha2::Sha256::digest(block_data.to_string().as_bytes()));
316        let signature_data =
317            format!("proposal:{}:{}:{}", self.current_height, self.current_round, block_hash);
318        let signature = self.signer.sign(signature_data.as_bytes())?;
319
320        Ok(TendermintMessageType::Proposal {
321            height: self.current_height,
322            round: self.current_round,
323            block_hash,
324            proposer: self.my_validator_address.clone(),
325            timestamp: Utc::now(),
326            signature: hex::encode(signature.to_bytes()),
327        })
328    }
329
330    /// Get current consensus state info
331    pub fn get_consensus_info(&self) -> serde_json::Value {
332        serde_json::json!({
333            "height": self.current_height,
334            "round": self.current_round,
335            "state": format!("{:?}", self.state),
336            "validators_count": self.validators.len(),
337            "blocks_count": self.blocks.len(),
338            "locked_block": self.locked_block,
339            "locked_round": self.locked_round,
340            "total_voting_power": self.total_voting_power()
341        })
342    }
343
344    /// Get voting statistics for current round
345    pub fn get_voting_stats(&self) -> serde_json::Value {
346        let prevote_count = self
347            .prevotes
348            .get(&(self.current_height, self.current_round))
349            .map(|votes| votes.len())
350            .unwrap_or(0);
351        let precommit_count = self
352            .precommits
353            .get(&(self.current_height, self.current_round))
354            .map(|votes| votes.len())
355            .unwrap_or(0);
356
357        serde_json::json!({
358            "height": self.current_height,
359            "round": self.current_round,
360            "prevotes": prevote_count,
361            "precommits": precommit_count,
362            "has_proposal": self.proposals.contains_key(&(self.current_height, self.current_round))
363        })
364    }
365}
366
367#[async_trait]
368impl ApplicationObject for TendermintObject {
369    fn id(&self) -> &SharedObjectId {
370        &self.id
371    }
372
373    fn type_name(&self) -> &'static str {
374        "TendermintBFT"
375    }
376
377    async fn is_valid(&self, message: &SharedMessage) -> Result<bool> {
378        let msg_result: std::result::Result<TendermintMessageType, _> =
379            serde_json::from_value(message.data.clone());
380        Ok(msg_result.is_ok())
381    }
382
383    async fn add_message(&mut self, message: SharedMessage) -> Result<()> {
384        let tendermint_msg: TendermintMessageType = serde_json::from_value(message.data.clone())
385            .map_err(|e| {
386                ChaincraftError::Serialization(crate::error::SerializationError::Json(e))
387            })?;
388
389        let processed = match &tendermint_msg {
390            TendermintMessageType::Proposal { .. } => {
391                self.process_proposal(tendermint_msg.clone())?
392            },
393            TendermintMessageType::Prevote { .. } => {
394                self.process_prevote(tendermint_msg.clone())?
395            },
396            TendermintMessageType::Precommit { .. } => {
397                self.process_precommit(tendermint_msg.clone())?
398            },
399            TendermintMessageType::ValidatorSet { validators, .. } => {
400                for validator in validators {
401                    self.add_validator(
402                        validator.address.clone(),
403                        validator.public_key.clone(),
404                        validator.voting_power,
405                    );
406                }
407                true
408            },
409            TendermintMessageType::BlockCommit { block_hash, .. } => {
410                self.commit_block(block_hash.clone())?;
411                true
412            },
413        };
414
415        if processed {
416            tracing::debug!("Successfully processed Tendermint message: {:?}", tendermint_msg);
417
418            // Check if we can advance consensus
419            if let Some(commit_hash) = self.can_commit() {
420                self.commit_block(commit_hash)?;
421            }
422        }
423
424        Ok(())
425    }
426
427    fn is_merkleized(&self) -> bool {
428        false
429    }
430
431    async fn get_latest_digest(&self) -> Result<String> {
432        Ok(format!("{}:{}", self.current_height, self.current_round))
433    }
434
435    async fn has_digest(&self, digest: &str) -> Result<bool> {
436        let current_digest = format!("{}:{}", self.current_height, self.current_round);
437        Ok(digest == current_digest)
438    }
439
440    async fn is_valid_digest(&self, _digest: &str) -> Result<bool> {
441        Ok(true)
442    }
443
444    async fn add_digest(&mut self, _digest: String) -> Result<bool> {
445        Ok(true)
446    }
447
448    async fn gossip_messages(&self, _digest: Option<&str>) -> Result<Vec<SharedMessage>> {
449        Ok(Vec::new())
450    }
451
452    async fn get_messages_since_digest(&self, _digest: &str) -> Result<Vec<SharedMessage>> {
453        Ok(Vec::new())
454    }
455
456    async fn get_state(&self) -> Result<serde_json::Value> {
457        Ok(serde_json::json!({
458            "type": "TendermintBFT",
459            "height": self.current_height,
460            "round": self.current_round,
461            "state": format!("{:?}", self.state),
462            "validators": self.validators.len(),
463            "blocks": self.blocks.len(),
464            "messages": self.messages.len(),
465            "consensus_info": self.get_consensus_info(),
466            "voting_stats": self.get_voting_stats()
467        }))
468    }
469
470    async fn reset(&mut self) -> Result<()> {
471        self.current_height = 1;
472        self.current_round = 0;
473        self.state = ConsensusState::Propose;
474        self.proposals.clear();
475        self.prevotes.clear();
476        self.precommits.clear();
477        self.locked_block = None;
478        self.locked_round = None;
479        self.messages.clear();
480        Ok(())
481    }
482
483    fn clone_box(&self) -> Box<dyn ApplicationObject> {
484        // Create a new instance with same configuration
485        let new_obj = TendermintObject::new().unwrap_or_else(|_| {
486            // Fallback if creation fails
487            let signer = ECDSASigner::new().unwrap();
488            let my_validator_address = signer.get_public_key_pem().unwrap();
489            TendermintObject {
490                id: SharedObjectId::new(),
491                validators: HashMap::new(),
492                blocks: vec![],
493                current_height: 1,
494                current_round: 0,
495                state: ConsensusState::Propose,
496                proposals: HashMap::new(),
497                prevotes: HashMap::new(),
498                precommits: HashMap::new(),
499                locked_block: None,
500                locked_round: None,
501                my_validator_address,
502                signer,
503                verifier: ECDSAVerifier::new(),
504                messages: Vec::new(),
505            }
506        });
507        Box::new(new_obj)
508    }
509
510    fn as_any(&self) -> &dyn std::any::Any {
511        self
512    }
513
514    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
515        self
516    }
517}
518
519/// Helper functions for creating Tendermint messages
520pub mod helpers {
521    use super::*;
522
523    pub fn create_validator_set_message(
524        validators: Vec<ValidatorInfo>,
525        height: u64,
526    ) -> Result<serde_json::Value> {
527        let validator_msg = TendermintMessageType::ValidatorSet { validators, height };
528        serde_json::to_value(validator_msg)
529            .map_err(|e| ChaincraftError::Serialization(crate::error::SerializationError::Json(e)))
530    }
531
532    pub fn create_proposal_message(
533        height: u64,
534        round: u32,
535        block_hash: String,
536        proposer: String,
537        signer: &ECDSASigner,
538    ) -> Result<serde_json::Value> {
539        let signature_data = format!("proposal:{}:{}:{}", height, round, block_hash);
540        let signature = signer.sign(signature_data.as_bytes())?;
541
542        let proposal = TendermintMessageType::Proposal {
543            height,
544            round,
545            block_hash,
546            proposer,
547            timestamp: Utc::now(),
548            signature: hex::encode(signature.to_bytes()),
549        };
550
551        serde_json::to_value(proposal)
552            .map_err(|e| ChaincraftError::Serialization(crate::error::SerializationError::Json(e)))
553    }
554
555    pub fn create_prevote_message(
556        height: u64,
557        round: u32,
558        block_hash: Option<String>,
559        validator: String,
560        signer: &ECDSASigner,
561    ) -> Result<serde_json::Value> {
562        let signature_data = format!("prevote:{}:{}:{:?}", height, round, block_hash);
563        let signature = signer.sign(signature_data.as_bytes())?;
564
565        let prevote = TendermintMessageType::Prevote {
566            height,
567            round,
568            block_hash,
569            validator,
570            signature: hex::encode(signature.to_bytes()),
571        };
572
573        serde_json::to_value(prevote)
574            .map_err(|e| ChaincraftError::Serialization(crate::error::SerializationError::Json(e)))
575    }
576
577    pub fn create_precommit_message(
578        height: u64,
579        round: u32,
580        block_hash: Option<String>,
581        validator: String,
582        signer: &ECDSASigner,
583    ) -> Result<serde_json::Value> {
584        let signature_data = format!("precommit:{}:{}:{:?}", height, round, block_hash);
585        let signature = signer.sign(signature_data.as_bytes())?;
586
587        let precommit = TendermintMessageType::Precommit {
588            height,
589            round,
590            block_hash,
591            validator,
592            signature: hex::encode(signature.to_bytes()),
593        };
594
595        serde_json::to_value(precommit)
596            .map_err(|e| ChaincraftError::Serialization(crate::error::SerializationError::Json(e)))
597    }
598}