diem_types/
ledger_info.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    account_address::AccountAddress,
6    block_info::{BlockInfo, Round},
7    epoch_state::EpochState,
8    on_chain_config::ValidatorSet,
9    transaction::Version,
10    validator_verifier::{ValidatorVerifier, VerifyError},
11};
12use diem_crypto::{ed25519::Ed25519Signature, hash::HashValue};
13use diem_crypto_derive::{BCSCryptoHash, CryptoHasher};
14#[cfg(any(test, feature = "fuzzing"))]
15use proptest_derive::Arbitrary;
16use serde::{Deserialize, Serialize};
17use std::{
18    collections::BTreeMap,
19    fmt::{Display, Formatter},
20    ops::{Deref, DerefMut},
21};
22
23/// This structure serves a dual purpose.
24///
25/// First, if this structure is signed by 2f+1 validators it signifies the state of the ledger at
26/// version `version` -- it contains the transaction accumulator at that version which commits to
27/// all historical transactions. This structure may be expanded to include other information that
28/// is derived from that accumulator (e.g. the current time according to the time contract) to
29/// reduce the number of proofs a client must get.
30///
31/// Second, the structure contains a `consensus_data_hash` value. This is the hash of an internal
32/// data structure that represents a block that is voted on in HotStuff. If 2f+1 signatures are
33/// gathered on the same ledger info that represents a Quorum Certificate (QC) on the consensus
34/// data.
35///
36/// Combining these two concepts, when a validator votes on a block, B it votes for a
37/// LedgerInfo with the `version` being the latest version that will be committed if B gets 2f+1
38/// votes. It sets `consensus_data_hash` to represent B so that if those 2f+1 votes are gathered a
39/// QC is formed on B.
40#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, CryptoHasher, BCSCryptoHash)]
41#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
42pub struct LedgerInfo {
43    commit_info: BlockInfo,
44
45    /// Hash of consensus specific data that is opaque to all parts of the system other than
46    /// consensus.
47    consensus_data_hash: HashValue,
48}
49
50impl Display for LedgerInfo {
51    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
52        write!(f, "LedgerInfo: [commit_info: {}]", self.commit_info())
53    }
54}
55
56impl LedgerInfo {
57    /// Constructs a `LedgerInfo` object based on the given commit info and vote data hash.
58    pub fn new(commit_info: BlockInfo, consensus_data_hash: HashValue) -> Self {
59        Self {
60            commit_info,
61            consensus_data_hash,
62        }
63    }
64
65    /// Create a new LedgerInfo at genesis with the given genesis state and
66    /// initial validator set.
67    pub fn genesis(genesis_state_root_hash: HashValue, validator_set: ValidatorSet) -> Self {
68        Self::new(
69            BlockInfo::genesis(genesis_state_root_hash, validator_set),
70            HashValue::zero(),
71        )
72    }
73
74    #[cfg(any(test, feature = "fuzzing"))]
75    pub fn mock_genesis(validator_set: Option<ValidatorSet>) -> Self {
76        Self::new(BlockInfo::mock_genesis(validator_set), HashValue::zero())
77    }
78
79    /// The `BlockInfo` of a committed block.
80    pub fn commit_info(&self) -> &BlockInfo {
81        &self.commit_info
82    }
83
84    /// A series of wrapper functions for the data stored in the commit info. For the detailed
85    /// information, please refer to `BlockInfo`
86    pub fn epoch(&self) -> u64 {
87        self.commit_info.epoch()
88    }
89
90    pub fn next_block_epoch(&self) -> u64 {
91        self.commit_info.next_block_epoch()
92    }
93
94    pub fn round(&self) -> Round {
95        self.commit_info.round()
96    }
97
98    pub fn consensus_block_id(&self) -> HashValue {
99        self.commit_info.id()
100    }
101
102    pub fn transaction_accumulator_hash(&self) -> HashValue {
103        self.commit_info.executed_state_id()
104    }
105
106    pub fn version(&self) -> Version {
107        self.commit_info.version()
108    }
109
110    pub fn timestamp_usecs(&self) -> u64 {
111        self.commit_info.timestamp_usecs()
112    }
113
114    pub fn next_epoch_state(&self) -> Option<&EpochState> {
115        self.commit_info.next_epoch_state()
116    }
117
118    pub fn ends_epoch(&self) -> bool {
119        self.next_epoch_state().is_some()
120    }
121
122    /// Returns hash of consensus voting data in this `LedgerInfo`.
123    pub fn consensus_data_hash(&self) -> HashValue {
124        self.consensus_data_hash
125    }
126
127    pub fn set_consensus_data_hash(&mut self, consensus_data_hash: HashValue) {
128        self.consensus_data_hash = consensus_data_hash;
129    }
130}
131
132/// Wrapper around LedgerInfoWithScheme to support future upgrades, this is the data being persisted.
133#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
134pub enum LedgerInfoWithSignatures {
135    V0(LedgerInfoWithV0),
136}
137
138impl Display for LedgerInfoWithSignatures {
139    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
140        match self {
141            LedgerInfoWithSignatures::V0(ledger) => write!(f, "{}", ledger),
142        }
143    }
144}
145
146// proxy to create LedgerInfoWithEd25519
147impl LedgerInfoWithSignatures {
148    pub fn new(
149        ledger_info: LedgerInfo,
150        signatures: BTreeMap<AccountAddress, Ed25519Signature>,
151    ) -> Self {
152        LedgerInfoWithSignatures::V0(LedgerInfoWithV0::new(ledger_info, signatures))
153    }
154
155    pub fn genesis(genesis_state_root_hash: HashValue, validator_set: ValidatorSet) -> Self {
156        LedgerInfoWithSignatures::V0(LedgerInfoWithV0::genesis(
157            genesis_state_root_hash,
158            validator_set,
159        ))
160    }
161}
162
163// Temporary hack to avoid massive changes, it won't work when new variant comes and needs proper
164// dispatch at that time.
165impl Deref for LedgerInfoWithSignatures {
166    type Target = LedgerInfoWithV0;
167
168    fn deref(&self) -> &LedgerInfoWithV0 {
169        match &self {
170            LedgerInfoWithSignatures::V0(ledger) => ledger,
171        }
172    }
173}
174
175impl DerefMut for LedgerInfoWithSignatures {
176    fn deref_mut(&mut self) -> &mut LedgerInfoWithV0 {
177        match self {
178            LedgerInfoWithSignatures::V0(ref mut ledger) => ledger,
179        }
180    }
181}
182
183/// The validator node returns this structure which includes signatures
184/// from validators that confirm the state.  The client needs to only pass back
185/// the LedgerInfo element since the validator node doesn't need to know the signatures
186/// again when the client performs a query, those are only there for the client
187/// to be able to verify the state
188#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
189pub struct LedgerInfoWithV0 {
190    ledger_info: LedgerInfo,
191    /// The validator is identified by its account address: in order to verify a signature
192    /// one needs to retrieve the public key of the validator for the given epoch.
193    signatures: BTreeMap<AccountAddress, Ed25519Signature>,
194}
195
196impl Display for LedgerInfoWithV0 {
197    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
198        write!(f, "{}", self.ledger_info)
199    }
200}
201
202impl LedgerInfoWithV0 {
203    pub fn new(
204        ledger_info: LedgerInfo,
205        signatures: BTreeMap<AccountAddress, Ed25519Signature>,
206    ) -> Self {
207        LedgerInfoWithV0 {
208            ledger_info,
209            signatures,
210        }
211    }
212
213    /// Create a new `LedgerInfoWithEd25519` at genesis with the given genesis
214    /// state and initial validator set.
215    ///
216    /// Note that the genesis `LedgerInfoWithEd25519` is unsigned. Validators
217    /// and FullNodes are configured with the same genesis transaction and generate
218    /// an identical genesis `LedgerInfoWithEd25519` independently. In contrast,
219    /// Clients will likely use a waypoint generated from the genesis `LedgerInfo`.
220    pub fn genesis(genesis_state_root_hash: HashValue, validator_set: ValidatorSet) -> Self {
221        Self::new(
222            LedgerInfo::genesis(genesis_state_root_hash, validator_set),
223            BTreeMap::new(),
224        )
225    }
226
227    pub fn ledger_info(&self) -> &LedgerInfo {
228        &self.ledger_info
229    }
230
231    pub fn commit_info(&self) -> &BlockInfo {
232        self.ledger_info.commit_info()
233    }
234
235    pub fn add_signature(&mut self, validator: AccountAddress, signature: Ed25519Signature) {
236        self.signatures.entry(validator).or_insert(signature);
237    }
238
239    pub fn remove_signature(&mut self, validator: AccountAddress) {
240        self.signatures.remove(&validator);
241    }
242
243    pub fn signatures(&self) -> &BTreeMap<AccountAddress, Ed25519Signature> {
244        &self.signatures
245    }
246
247    pub fn verify_signatures(
248        &self,
249        validator: &ValidatorVerifier,
250    ) -> ::std::result::Result<(), VerifyError> {
251        validator.batch_verify_aggregated_signatures(self.ledger_info(), self.signatures())
252    }
253
254    pub fn check_voting_power(
255        &self,
256        validator: &ValidatorVerifier,
257    ) -> ::std::result::Result<(), VerifyError> {
258        validator.check_voting_power(self.signatures.keys())
259    }
260}
261
262//
263// Arbitrary implementation of LedgerInfoWithV0 (for fuzzing)
264//
265
266#[cfg(any(test, feature = "fuzzing"))]
267use ::proptest::prelude::*;
268
269#[cfg(any(test, feature = "fuzzing"))]
270impl Arbitrary for LedgerInfoWithV0 {
271    type Parameters = ();
272    type Strategy = BoxedStrategy<Self>;
273
274    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
275        let dummy_signature = Ed25519Signature::dummy_signature();
276        (
277            proptest::arbitrary::any::<LedgerInfo>(),
278            proptest::collection::vec(proptest::arbitrary::any::<AccountAddress>(), 0..100),
279        )
280            .prop_map(move |(ledger_info, addresses)| {
281                let mut signatures = BTreeMap::new();
282                for address in addresses {
283                    let signature = dummy_signature.clone();
284                    signatures.insert(address, signature);
285                }
286                Self {
287                    ledger_info,
288                    signatures,
289                }
290            })
291            .boxed()
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use crate::validator_signer::ValidatorSigner;
299
300    #[test]
301    fn test_signatures_hash() {
302        let ledger_info = LedgerInfo::new(BlockInfo::empty(), HashValue::random());
303
304        const NUM_SIGNERS: u8 = 7;
305        // Generate NUM_SIGNERS random signers.
306        let validator_signers: Vec<ValidatorSigner> = (0..NUM_SIGNERS)
307            .map(|i| ValidatorSigner::random([i; 32]))
308            .collect();
309        let mut author_to_signature_map = BTreeMap::new();
310        for validator in validator_signers.iter() {
311            author_to_signature_map.insert(validator.author(), validator.sign(&ledger_info));
312        }
313
314        let ledger_info_with_signatures =
315            LedgerInfoWithV0::new(ledger_info.clone(), author_to_signature_map);
316
317        // Add the signatures in reverse order and ensure the serialization matches
318        let mut author_to_signature_map = BTreeMap::new();
319        for validator in validator_signers.iter().rev() {
320            author_to_signature_map.insert(validator.author(), validator.sign(&ledger_info));
321        }
322
323        let ledger_info_with_signatures_reversed =
324            LedgerInfoWithV0::new(ledger_info, author_to_signature_map);
325
326        let ledger_info_with_signatures_bytes =
327            bcs::to_bytes(&ledger_info_with_signatures).expect("block serialization failed");
328        let ledger_info_with_signatures_reversed_bytes =
329            bcs::to_bytes(&ledger_info_with_signatures_reversed)
330                .expect("block serialization failed");
331
332        assert_eq!(
333            ledger_info_with_signatures_bytes,
334            ledger_info_with_signatures_reversed_bytes
335        );
336    }
337}