1use std::fmt::Display;
2
3use borsh::{BorshDeserialize, BorshSerialize};
4use serde::{Deserialize, Serialize};
5use sha3::{Digest, Sha3_256};
6use utils::TimestampMs;
7use utoipa::ToSchema;
8
9use crate::{staking::*, *};
10
11pub type Slot = u64;
12pub type View = u64;
13
14#[derive(Clone, Serialize, Deserialize, ToSchema, Debug)]
15pub struct ConsensusInfo {
16 pub slot: Slot,
17 pub view: View,
18 pub round_leader: ValidatorPublicKey,
19 pub last_timestamp: TimestampMs,
20 pub validators: Vec<ValidatorPublicKey>,
21}
22
23#[derive(
28 Debug,
29 Serialize,
30 Deserialize,
31 Clone,
32 BorshSerialize,
33 BorshDeserialize,
34 PartialEq,
35 Eq,
36 Hash,
37 Ord,
38 PartialOrd,
39)]
40pub struct ValidatorCandidacy {
41 pub peer_address: String,
43}
44
45impl Display for SignedByValidator<ValidatorCandidacy> {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 write!(f, "Pubkey: {}", self.signature.validator)
48 }
49}
50
51#[derive(
52 Debug,
53 Clone,
54 Default,
55 Serialize,
56 Deserialize,
57 BorshSerialize,
58 BorshDeserialize,
59 PartialEq,
60 Eq,
61 Ord,
62 PartialOrd,
63)]
64pub struct ConsensusProposal {
65 pub slot: Slot,
66 pub parent_hash: ConsensusProposalHash,
67 pub cut: Cut,
68 pub staking_actions: Vec<ConsensusStakingAction>,
69 pub timestamp: TimestampMs,
70}
71
72impl Hashed<ConsensusProposalHash> for ConsensusProposal {
75 fn hashed(&self) -> ConsensusProposalHash {
76 let mut hasher = Sha3_256::new();
77 hasher.update(self.slot.to_le_bytes());
78 self.cut.iter().for_each(|(lane_id, hash, _, _)| {
79 lane_id.update_hasher(&mut hasher);
80 hasher.update(&hash.0);
81 });
82 self.staking_actions.iter().for_each(|val| match val {
83 ConsensusStakingAction::Bond { candidate } => {
84 hasher.update(&candidate.signature.validator.0)
85 }
86 ConsensusStakingAction::PayFeesForDaDi {
87 lane_id,
88 cumul_size,
89 } => {
90 lane_id.update_hasher(&mut hasher);
91 hasher.update(cumul_size.0.to_le_bytes())
92 }
93 });
94 hasher.update(self.timestamp.0.to_le_bytes());
95 hasher.update(&self.parent_hash.0);
96 ConsensusProposalHash(hasher.finalize().to_vec())
97 }
98}
99
100impl std::hash::Hash for ConsensusProposal {
101 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
102 Hashed::<ConsensusProposalHash>::hashed(self).hash(state);
103 }
104}
105
106impl Display for ConsensusProposal {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 write!(
109 f,
110 "Hash: {}, Parent Hash: {}, Slot: {}, Cut: {}, staking_actions: {:?}",
111 self.hashed(),
112 self.parent_hash,
113 self.slot,
114 CutDisplay(&self.cut),
115 self.staking_actions,
116 )
117 }
118}
119
120impl Display for ConsensusProposalHash {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 write!(f, "{}", hex::encode(&self.0))
123 }
124}
125
126#[derive(
128 BorshSerialize,
129 BorshDeserialize,
130 Debug,
131 Clone,
132 Deserialize,
133 Serialize,
134 PartialEq,
135 Eq,
136 Ord,
137 PartialOrd,
138)]
139pub enum ConsensusStakingAction {
140 Bond {
142 candidate: Box<SignedByValidator<ValidatorCandidacy>>,
145 },
146
147 PayFeesForDaDi {
149 lane_id: LaneId,
150 cumul_size: LaneBytesSize,
151 },
152}
153
154impl From<SignedByValidator<ValidatorCandidacy>> for ConsensusStakingAction {
155 fn from(val: SignedByValidator<ValidatorCandidacy>) -> Self {
156 ConsensusStakingAction::Bond {
157 candidate: Box::new(val),
158 }
159 }
160}
161
162#[cfg(test)]
163mod tests {
164
165 #[test]
166 fn test_consensus_proposal_hash() {
167 use super::*;
168 let proposal = ConsensusProposal {
169 slot: 1,
170 cut: Cut::default(),
171 staking_actions: vec![],
172 timestamp: TimestampMs(1),
173 parent_hash: b"".into(),
174 };
175 let hash = proposal.hashed();
176 assert_eq!(hash.0.len(), 32);
177 }
178 #[test]
179 fn test_consensus_proposal_hash_all_items() {
180 use super::*;
181 let mut a = ConsensusProposal {
182 slot: 1,
183 cut: vec![(
184 LaneId::new(ValidatorPublicKey(vec![1])),
185 b"propA".into(),
186 LaneBytesSize(1),
187 AggregateSignature::default(),
188 )],
189 staking_actions: vec![SignedByValidator::<ValidatorCandidacy> {
190 msg: ValidatorCandidacy {
191 peer_address: "peer".to_string(),
192 },
193 signature: ValidatorSignature {
194 signature: Signature(vec![1, 2, 3]),
195 validator: ValidatorPublicKey(vec![1, 2, 3]),
196 },
197 }
198 .into()],
199 timestamp: TimestampMs(1),
200 parent_hash: b"parent".into(),
201 };
202 let mut b = ConsensusProposal {
203 slot: 1,
204 cut: vec![(
205 LaneId::new(ValidatorPublicKey(vec![1])),
206 b"propA".into(),
207 LaneBytesSize(1),
208 AggregateSignature {
209 signature: Signature(vec![1, 2, 3]),
210 validators: vec![ValidatorPublicKey(vec![1, 2, 3])],
211 },
212 )],
213 staking_actions: vec![SignedByValidator::<ValidatorCandidacy> {
214 msg: ValidatorCandidacy {
215 peer_address: "different".to_string(),
216 },
217 signature: ValidatorSignature {
218 signature: Signature(vec![3, 4, 5]),
219 validator: ValidatorPublicKey(vec![3, 4, 5]),
220 },
221 }
222 .into()],
223 timestamp: TimestampMs(1),
224 parent_hash: b"parent".into(),
225 };
226 assert_ne!(a.hashed(), b.hashed());
227 if let ConsensusStakingAction::Bond { candidate: a } =
228 b.staking_actions.first_mut().unwrap()
229 {
230 a.msg.peer_address = "peer".to_string()
231 }
232 assert_ne!(a.hashed(), b.hashed());
233 if let ConsensusStakingAction::Bond { candidate: a } =
234 b.staking_actions.first_mut().unwrap()
235 {
236 a.signature = ValidatorSignature {
237 signature: Signature(vec![1, 2, 3]),
238 validator: ValidatorPublicKey(vec![1, 2, 3]),
239 };
240 }
241 assert_eq!(a.hashed(), b.hashed());
242 a.timestamp = TimestampMs(2);
243 assert_ne!(a.hashed(), b.hashed());
244 b.timestamp = TimestampMs(2);
245 assert_eq!(a.hashed(), b.hashed());
246
247 a.slot = 2;
248 assert_ne!(a.hashed(), b.hashed());
249 b.slot = 2;
250 assert_eq!(a.hashed(), b.hashed());
251
252 a.parent_hash = b"different".into();
253 assert_ne!(a.hashed(), b.hashed());
254 b.parent_hash = b"different".into();
255 assert_eq!(a.hashed(), b.hashed());
256 }
257}