diem_types/
account_state_blob.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8use crate::{
9    account_address::{AccountAddress, HashAccountAddress},
10    account_config::{AccountResource, BalanceResource},
11    account_state::AccountState,
12    ledger_info::LedgerInfo,
13    proof::AccountStateProof,
14    transaction::Version,
15};
16use anyhow::{anyhow, ensure, Error, Result};
17use diem_crypto::{
18    hash::{CryptoHash, CryptoHasher},
19    HashValue,
20};
21use diem_crypto_derive::CryptoHasher;
22#[cfg(any(test, feature = "fuzzing"))]
23use proptest::{arbitrary::Arbitrary, prelude::*};
24#[cfg(any(test, feature = "fuzzing"))]
25use proptest_derive::Arbitrary;
26use serde::{Deserialize, Serialize};
27use std::{convert::TryFrom, fmt};
28
29#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, CryptoHasher)]
30pub struct AccountStateBlob {
31    blob: Vec<u8>,
32}
33
34impl fmt::Debug for AccountStateBlob {
35    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36        let decoded = bcs::from_bytes(&self.blob)
37            .map(|account_state: AccountState| format!("{:#?}", account_state))
38            .unwrap_or_else(|_| String::from("[fail]"));
39
40        write!(
41            f,
42            "AccountStateBlob {{ \n \
43             Raw: 0x{} \n \
44             Decoded: {} \n \
45             }}",
46            hex::encode(&self.blob),
47            decoded,
48        )
49    }
50}
51
52impl AsRef<[u8]> for AccountStateBlob {
53    fn as_ref(&self) -> &[u8] { &self.blob }
54}
55
56impl From<&AccountStateBlob> for Vec<u8> {
57    fn from(account_state_blob: &AccountStateBlob) -> Vec<u8> {
58        account_state_blob.blob.clone()
59    }
60}
61
62impl From<AccountStateBlob> for Vec<u8> {
63    fn from(account_state_blob: AccountStateBlob) -> Vec<u8> {
64        Self::from(&account_state_blob)
65    }
66}
67
68impl From<Vec<u8>> for AccountStateBlob {
69    fn from(blob: Vec<u8>) -> AccountStateBlob { AccountStateBlob { blob } }
70}
71
72impl TryFrom<&AccountState> for AccountStateBlob {
73    type Error = Error;
74
75    fn try_from(account_state: &AccountState) -> Result<Self> {
76        Ok(Self {
77            blob: bcs::to_bytes(account_state)?,
78        })
79    }
80}
81
82impl TryFrom<&AccountStateBlob> for AccountState {
83    type Error = Error;
84
85    fn try_from(account_state_blob: &AccountStateBlob) -> Result<Self> {
86        bcs::from_bytes(&account_state_blob.blob).map_err(Into::into)
87    }
88}
89
90impl TryFrom<(&AccountResource, &BalanceResource)> for AccountStateBlob {
91    type Error = Error;
92
93    fn try_from(
94        (account_resource, balance_resource): (
95            &AccountResource,
96            &BalanceResource,
97        ),
98    ) -> Result<Self> {
99        Self::try_from(&AccountState::try_from((
100            account_resource,
101            balance_resource,
102        ))?)
103    }
104}
105
106impl TryFrom<&AccountStateBlob> for AccountResource {
107    type Error = Error;
108
109    fn try_from(account_state_blob: &AccountStateBlob) -> Result<Self> {
110        AccountState::try_from(account_state_blob)?
111            .get_account_resource()?
112            .ok_or_else(|| anyhow!("AccountResource not found."))
113    }
114}
115
116impl CryptoHash for AccountStateBlob {
117    type Hasher = AccountStateBlobHasher;
118
119    fn hash(&self) -> HashValue {
120        let mut hasher = Self::Hasher::default();
121        hasher.update(&self.blob);
122        hasher.finish()
123    }
124}
125
126#[cfg(any(test, feature = "fuzzing"))]
127prop_compose! {
128    fn account_state_blob_strategy()(account_resource in any::<AccountResource>(), balance_resource in any::<BalanceResource>()) -> AccountStateBlob {
129        AccountStateBlob::try_from((&account_resource, &balance_resource)).unwrap()
130    }
131}
132
133#[cfg(any(test, feature = "fuzzing"))]
134impl Arbitrary for AccountStateBlob {
135    type Parameters = ();
136    type Strategy = BoxedStrategy<Self>;
137
138    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
139        account_state_blob_strategy().boxed()
140    }
141}
142
143#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
144#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
145pub struct AccountStateWithProof {
146    /// The transaction version at which this account state is seen.
147    pub version: Version,
148    /// Blob value representing the account state. If this field is not set, it
149    /// means the account does not exist.
150    pub blob: Option<AccountStateBlob>,
151    /// The proof the client can use to authenticate the value.
152    pub proof: AccountStateProof,
153}
154
155impl AccountStateWithProof {
156    /// Constructor.
157    pub fn new(
158        version: Version, blob: Option<AccountStateBlob>,
159        proof: AccountStateProof,
160    ) -> Self {
161        Self {
162            version,
163            blob,
164            proof,
165        }
166    }
167
168    /// Verifies the account state blob with the proof, both carried by
169    /// `self`.
170    ///
171    /// Two things are ensured if no error is raised:
172    ///   1. This account state exists in the ledger represented by
173    /// `ledger_info`.   2. It belongs to account of `address` and is seen
174    /// at the time the transaction at version `state_version` is just
175    /// committed. To make sure this is the latest state, pass in
176    /// `ledger_info.version()` as `state_version`.
177    pub fn verify(
178        &self, ledger_info: &LedgerInfo, version: Version,
179        address: AccountAddress,
180    ) -> Result<()> {
181        ensure!(
182            self.version == version,
183            "State version ({}) is not expected ({}).",
184            self.version,
185            version,
186        );
187
188        self.proof.verify(
189            ledger_info,
190            version,
191            address.hash(),
192            self.blob.as_ref(),
193        )
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use bcs::test_helpers::assert_canonical_encode_decode;
201    use proptest::collection::vec;
202
203    fn hash_blob(blob: &[u8]) -> HashValue {
204        let mut hasher = AccountStateBlobHasher::default();
205        hasher.update(blob);
206        hasher.finish()
207    }
208
209    proptest! {
210        #[test]
211        fn account_state_blob_hash(blob in vec(any::<u8>(), 1..100)) {
212            prop_assert_eq!(hash_blob(&blob), AccountStateBlob::from(blob).hash());
213        }
214
215        #[test]
216        fn account_state_blob_bcs_roundtrip(account_state_blob in any::<AccountStateBlob>()) {
217            assert_canonical_encode_decode(account_state_blob);
218        }
219
220        #[test]
221        fn account_state_with_proof_bcs_roundtrip(account_state_with_proof in any::<AccountStateWithProof>()) {
222            assert_canonical_encode_decode(account_state_with_proof);
223        }
224    }
225
226    #[test]
227    fn test_debug_does_not_panic() {
228        let _ = format!("{:#?}", AccountStateBlob::from(vec![1u8, 2u8, 3u8]));
229    }
230}