nitro_da_proofs/
bank_hash.rs

1//! Proof of the bankhash, proving the state of the Solana bank (including the accounts_delta_hash)
2//! at a particular slot.
3
4use std::fmt::Debug;
5
6use serde::{Deserialize, Serialize};
7
8/// The proof for a bankhash is simply its components.
9///
10/// It's not very interesting on its own, but when combined with [`crate::accounts_delta_hash`]
11/// it can be used to prove the state of accounts at a particular slot.
12#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
13pub struct BankHashProof {
14    /// The bankhash of the parent block.
15    /// NOT the blockhash.
16    pub parent_bankhash: solana_sdk::hash::Hash,
17
18    /// The hash of all modified accounts in the block, see [`crate::accounts_delta_hash`].
19    pub(crate) accounts_delta_hash: solana_sdk::hash::Hash,
20
21    /// The number of signatures in the block.
22    pub(crate) signature_count: u64,
23
24    /// The Proof-of-History tick after interleaving all the transactions in the block.
25    /// NOT related to the bankhash.
26    pub blockhash: solana_sdk::hash::Hash,
27}
28
29impl Debug for BankHashProof {
30    #[cfg_attr(test, mutants::skip)]
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        f.debug_struct("Proof")
33            .field("parent_bankhash", &self.parent_bankhash)
34            .field("accounts_delta_hash", &self.accounts_delta_hash)
35            .field("signature_count", &self.signature_count)
36            .field("blockhash", &self.blockhash)
37            .field("bank_hash()", &self.hash())
38            .finish()
39    }
40}
41
42impl BankHashProof {
43    /// Creates a bank hash proof.
44    pub fn new(
45        parent_bankhash: solana_sdk::hash::Hash,
46        accounts_delta_hash: solana_sdk::hash::Hash,
47        signature_count: u64,
48        blockhash: solana_sdk::hash::Hash,
49    ) -> Self {
50        Self {
51            parent_bankhash,
52            accounts_delta_hash,
53            signature_count,
54            blockhash,
55        }
56    }
57
58    /// Verifies that the bankhash matches the expected value.
59    pub fn verify(&self, bank_hash: solana_sdk::hash::Hash) -> bool {
60        self.hash() == bank_hash
61    }
62
63    /// Hashes the components to create the bankhash.
64    pub fn hash(&self) -> solana_sdk::hash::Hash {
65        // https://github.com/anza-xyz/agave/blob/v1.18.22/runtime/src/bank.rs#L6951-L6956
66        solana_sdk::hash::hashv(&[
67            self.parent_bankhash.as_ref(),
68            self.accounts_delta_hash.as_ref(),
69            self.signature_count.to_le_bytes().as_ref(),
70            self.blockhash.as_ref(),
71        ])
72    }
73}
74
75#[cfg(test)]
76impl<'a> arbitrary::Arbitrary<'a> for BankHashProof {
77    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> Result<Self, arbitrary::Error> {
78        use crate::testing::arbitrary_hash;
79        Ok(Self {
80            parent_bankhash: arbitrary_hash(u)?,
81            accounts_delta_hash: arbitrary_hash(u)?,
82            signature_count: u.arbitrary()?,
83            blockhash: arbitrary_hash(u)?,
84        })
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use std::str::FromStr;
91
92    use arbitrary::Arbitrary;
93    use arbtest::arbtest;
94
95    use super::*;
96    use crate::testing::arbitrary_hash;
97
98    #[test]
99    fn known_values_from_solana() {
100        // These hardcoded values were taken from various tests in the Solana implementation.
101        let test_cases = vec![
102            (
103                "11111111111111111111111111111111",
104                "AAH4XpMn5FrdDoCwaTXKY8Cz3hmeQKbeZFt8S44XYuYi",
105                1,
106                "J4UmrMsC4pE4GKEgrbyegswSfMopxs38zg1xb7abVnfa",
107                "acuaeWZsRuYjSq8Z3t7BbRB2TnF538yrMZENofNQ3A9",
108            ),
109            (
110                "11111111111111111111111111111111",
111                "3e1WeddAcM6joht3K69aUzvYj4peMbWepYatDKYt9sNC",
112                0,
113                "2B7nfm7qU7z2n1LsXhYNsy1SgUrQytK2aW7HTRiKTSur",
114                "ARQDUtGsw7DPFmiw7AWvHgpxib1E3V15jp91U25wkFD",
115            ),
116            (
117                "11111111111111111111111111111111",
118                "9KMQVuq5rUG2ji36GfvoxZCTHrJEvuH9YJyfFjFZ6DDx",
119                0,
120                "2B7nfm7qU7z2n1LsXhYNsy1SgUrQytK2aW7HTRiKTSur",
121                "8uqjLNiXSkyg99dxRXMTJPN2Xz9xn6KvKkkNwMpPTLt4",
122            ),
123            (
124                "8uqjLNiXSkyg99dxRXMTJPN2Xz9xn6KvKkkNwMpPTLt4",
125                "EtDCV1MSqhvL8sfmwhxuVB3eC53KGJVT4NDVU5CBhaPD",
126                0,
127                "2B7nfm7qU7z2n1LsXhYNsy1SgUrQytK2aW7HTRiKTSur",
128                "2Pj5sRN85mVTqk4hiri9SgPQ7VZ7t8ki4AiXciKsdqzi",
129            ),
130            (
131                "8uqjLNiXSkyg99dxRXMTJPN2Xz9xn6KvKkkNwMpPTLt4",
132                "Fpi4tGcNqSmJxVc1F2A63v3gqTmahkjmF9D7qLxiKQAn",
133                1,
134                "2B7nfm7qU7z2n1LsXhYNsy1SgUrQytK2aW7HTRiKTSur",
135                "2WxpWuxFHLMKjVQDQSBznBbJNjZrxMiAEdPB4s6hTG2G",
136            ),
137            (
138                "11111111111111111111111111111111",
139                "3d8fqmw2aGfek4c5ZgTDnojF8UjQKxfmCS1sdRbUutZb",
140                0,
141                "GBXGZS579k5B7eqrGELiDkiP7vvQyB4bZ93u8cgaYFeJ",
142                "6ustQtaSzYzaMZbAVsNTdtj8RMgMeE83PDy1DdVV6N6U",
143            ),
144            (
145                "11111111111111111111111111111111",
146                "cLUFixMKDgFbNcWxLab3sJZsEaYPs41ZGTEoJbUPfX9",
147                2,
148                "GBXGZS579k5B7eqrGELiDkiP7vvQyB4bZ93u8cgaYFeJ",
149                "Chz6wPtQvcehwJemAvhP39xTPYB2BWPaa4F458Sohhjw",
150            ),
151            (
152                "FKv2WQG68Fj3MU5MVPTNzj4SaUq6heCgReMdMnvQW4Sy",
153                "CbYAUEJFYsCSF4ySAeUB71WzpTFW5bdu6Jp4zu3TLfMi",
154                2,
155                "JAu73Nm8wtCVEihHFitDMVQ2W3jRGuA19ZeshTp4gYvj",
156                "81CMWBfTbpRuX2zQbRyzgbfGBJoX7dMTUfMFkK6gs75v",
157            ),
158        ];
159
160        use solana_sdk::hash::Hash;
161
162        for (parent_bankhash, accounts_delta_hash, signature_count, blockhash, expected) in
163            test_cases
164        {
165            let proof = BankHashProof {
166                parent_bankhash: Hash::from_str(parent_bankhash).unwrap(),
167                accounts_delta_hash: Hash::from_str(accounts_delta_hash).unwrap(),
168                signature_count,
169                blockhash: Hash::from_str(blockhash).unwrap(),
170            };
171            assert!(proof.verify(Hash::from_str(expected).unwrap()));
172        }
173    }
174
175    #[test]
176    fn bank_hash_construction() {
177        arbtest(move |u| {
178            let mut proof = BankHashProof::arbitrary(u)?;
179            let hash_before = proof.hash();
180
181            let mut unmodified = true;
182            if u.ratio(1, 10)? {
183                let new_parent_bankhash = arbitrary_hash(u)?;
184                unmodified = new_parent_bankhash == proof.parent_bankhash;
185                proof.parent_bankhash = new_parent_bankhash;
186            } else if u.ratio(1, 10)? {
187                let new_accounts_delta_hash = arbitrary_hash(u)?;
188                unmodified = new_accounts_delta_hash == proof.accounts_delta_hash;
189                proof.accounts_delta_hash = new_accounts_delta_hash;
190            } else if u.ratio(1, 10)? {
191                let new_signature_count = u.arbitrary()?;
192                unmodified = new_signature_count == proof.signature_count;
193                proof.signature_count = new_signature_count;
194            } else if u.ratio(1, 10)? {
195                let new_blockhash = arbitrary_hash(u)?;
196                unmodified = new_blockhash == proof.blockhash;
197                proof.blockhash = new_blockhash;
198            }
199
200            if unmodified {
201                assert!(proof.verify(hash_before));
202            } else {
203                assert!(!proof.verify(hash_before));
204            }
205
206            Ok(())
207        });
208    }
209}