penumbra_sdk_mock_consensus/
block.rs

1//! [`Builder`] facilities for constructing [`Block`]s.
2//!
3//! Builders are acquired by calling [`TestNode::block()`], see [`TestNode`] for more information.
4
5use {
6    crate::TestNode,
7    prost::Message,
8    sha2::{Digest, Sha256},
9    std::ops::Deref,
10    tap::Tap,
11    tendermint::{
12        abci::Event,
13        account,
14        block::{self, header::Version, Block, Commit, Header, Round},
15        evidence,
16        merkle::simple_hash_from_byte_vectors,
17        v0_37::abci::{ConsensusRequest, ConsensusResponse},
18        Hash, Time,
19    },
20    tower::{BoxError, Service},
21    tracing::{instrument, trace},
22};
23
24/// Interfaces for generating commit signatures.
25mod signature;
26
27/// A block builder.
28///
29/// A block builder can be used to prepare and instantiate a new [`Block`]. A block builder is
30/// acquired by calling [`TestNode::block()`]. This builder holds an exclusive reference to a
31/// [`TestNode`], so only one block may be built at once.
32///
33/// This builder can be consumed, executing the block against the [`TestNode`]'s consensus service,
34/// by calling [`Builder::execute()`].
35pub struct Builder<'e, C> {
36    /// A unique reference to the test node.
37    test_node: &'e mut TestNode<C>,
38    /// Transaction data.
39    data: Vec<Vec<u8>>,
40    /// Evidence of malfeasance.
41    evidence: evidence::List,
42    /// The timestamp of the block.
43    timestamp: Time,
44    /// Disable producing signatures. Defaults to produce signatures.
45    disable_signatures: bool,
46}
47
48// === impl TestNode ===
49
50impl<C> TestNode<C> {
51    /// Returns a new [`Builder`].
52    ///
53    pub fn block(&mut self) -> Builder<'_, C> {
54        let ts = self.timestamp.clone();
55        Builder {
56            test_node: self,
57            data: Default::default(),
58            evidence: Default::default(),
59            timestamp: ts,
60            disable_signatures: false,
61        }
62    }
63}
64
65// === impl Builder ===
66
67impl<'e, C> Builder<'e, C> {
68    /// Sets the data for this block.
69    pub fn with_data(self, data: Vec<Vec<u8>>) -> Self {
70        let Self { data: prev, .. } = self;
71
72        if !prev.is_empty() {
73            tracing::warn!(
74                count = %prev.len(),
75                "block builder overwriting transaction data, this may be a bug!"
76            );
77        }
78
79        Self { data, ..self }
80    }
81
82    /// Appends the given tx to this block's data.
83    pub fn add_tx(mut self, tx: Vec<u8>) -> Self {
84        self.data.push(tx);
85        self
86    }
87
88    /// Sets the evidence [`List`][evidence::List] for this block.
89    pub fn with_evidence(self, evidence: evidence::List) -> Self {
90        Self { evidence, ..self }
91    }
92
93    /// Disables producing commit signatures for this block.
94    pub fn without_signatures(self) -> Self {
95        Self {
96            disable_signatures: true,
97            ..self
98        }
99    }
100}
101
102impl<'e, C> Builder<'e, C>
103where
104    C: Service<ConsensusRequest, Response = ConsensusResponse, Error = BoxError>
105        + Send
106        + Clone
107        + 'static,
108    C::Future: Send + 'static,
109    C::Error: Sized,
110{
111    /// Consumes this builder, executing the [`Block`] using the consensus service.
112    ///
113    /// Use [`TestNode::block()`] to build a new block.
114    ///
115    /// By default, signatures for all of the validators currently within the keyring will be
116    /// included in the block. Use [`Builder::without_signatures()`] to disable producing
117    /// validator signatures.
118    #[instrument(level = "info", skip_all, fields(height, time))]
119    pub async fn execute(self) -> Result<(EndBlockEvents, DeliverTxEvents), anyhow::Error> {
120        // Calling `finish` finishes the previous block
121        // and prepares the current block.
122        let (test_node, block) = self.finish()?;
123
124        let Block {
125            // The header for the current block
126            header,
127            data,
128            evidence: _,
129            // Votes for the previous block
130            last_commit,
131            ..
132        } = block.clone().tap(|block| {
133            tracing::span::Span::current()
134                .record("height", block.header.height.value())
135                .record("time", block.header.time.unix_timestamp());
136        });
137        let last_commit_info = Self::last_commit_info(last_commit);
138
139        trace!("sending block");
140        test_node.begin_block(header, last_commit_info).await?;
141        let mut deliver_tx_responses = Vec::new();
142        for tx in data {
143            let tx = tx.into();
144            // The caller may want to access the DeliverTx responses
145            deliver_tx_responses.push(test_node.deliver_tx(tx).await?);
146        }
147
148        // The CheckTx, BeginBlock, DeliverTx, EndBlock methods include an Events field.
149        // The mock consensus code only handles EndBlock and DeliverTx events.
150        // Extract the events emitted during end_block.
151        let events = test_node.end_block().await?.events;
152        let deliver_tx_events = deliver_tx_responses
153            .iter()
154            .flat_map(|response| response.events.clone())
155            .collect::<Vec<_>>();
156
157        // the commit call will set test_node.last_app_hash, preparing
158        // for the next block to begin execution
159        let commit_response = test_node.commit().await?;
160
161        // NOTE: after calling .commit(), the internal status of the pd node's storage is going to be updated
162        // to the next block
163        // therefore we need to update the height within our mock now now
164
165        // Set the last app hash to the new block's app hash.
166        test_node.last_app_hash = commit_response.data.to_vec();
167        trace!(
168            last_app_hash = ?hex::encode(commit_response.data.to_vec()),
169            "test node has committed block, setting last_app_hash"
170        );
171
172        // If an `on_block` callback was set, call it now.
173        test_node.on_block.as_mut().map(move |f| f(block));
174
175        Ok((EndBlockEvents(events), DeliverTxEvents(deliver_tx_events)))
176    }
177
178    /// Consumes this builder, returning its [`TestNode`] reference and a [`Block`].
179    #[instrument(
180        level = "info"
181        skip(self),
182        fields(height),
183    )]
184    fn finish(self) -> Result<(&'e mut TestNode<C>, Block), anyhow::Error> {
185        tracing::trace!("building block");
186        let Self {
187            data,
188            evidence,
189            test_node,
190            timestamp,
191            disable_signatures,
192        } = self;
193
194        // Call the timestamp callback to increment the node's current timestamp.
195        test_node.timestamp = (test_node.ts_callback)(test_node.timestamp.clone());
196
197        // The first (non-genesis) block has height 1.
198        let height = {
199            let height = test_node.height.increment();
200            test_node.height = height;
201            tracing::Span::current().record("height", height.value());
202            height
203        };
204
205        // Pull the current last_commit out of the node, since it will
206        // be discarded after we build the block.
207        let last_commit = test_node.last_commit.clone();
208
209        // Set the validator set based on the current configuration.
210        let pk = test_node
211            .keyring
212            .iter()
213            .next()
214            .expect("validator key in keyring")
215            .0;
216        let proposer_address = account::Id::new(
217            <Sha256 as sha2::Digest>::digest(pk).as_slice()[0..20]
218                .try_into()
219                .expect(""),
220        );
221
222        let validators_hash = test_node.last_validator_set_hash.clone().unwrap();
223
224        // The data hash is the sha256 hash of all the transactions
225        // I think as long as we are consistent here it's fine.
226        let data_hash = sha2::Sha256::digest(&data.concat()).to_vec();
227        let consensus_hash = test_node.consensus_params_hash.clone().try_into().unwrap();
228        let header = Header {
229            // Protocol version. Block version 11 matches cometbft when tests were written.
230            version: Version { block: 11, app: 0 },
231            chain_id: tendermint::chain::Id::try_from(test_node.chain_id.clone())?,
232            // Height is the height for this header.
233            height,
234            time: timestamp,
235            // MerkleRoot of the lastCommit’s signatures. The signatures represent the validators that committed to the last block. The first block has an empty slices of bytes for the hash.
236            last_commit_hash: last_commit
237                .as_ref()
238                .map(|c| c.hash().unwrap())
239                .unwrap_or(Some(
240                    // empty hash value
241                    hex::decode(
242                        "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
243                    )?
244                    .try_into()?,
245                )),
246            last_block_id: test_node.last_commit.as_ref().map(|c| c.block_id.clone()),
247            // MerkleRoot of the hash of transactions. Note: The transactions are hashed before being included in the merkle tree, the leaves of the Merkle tree are the hashes, not the transactions themselves.
248            data_hash: Some(tendermint::Hash::Sha256(data_hash.try_into().unwrap())),
249            // force the header to have the hash of the validator set to pass
250            // the validation
251            // MerkleRoot of the current validator set
252            validators_hash: validators_hash.into(),
253            // MerkleRoot of the next validator set
254            next_validators_hash: validators_hash.into(),
255            // Hash of the protobuf encoded consensus parameters.
256            consensus_hash,
257            // Arbitrary byte array returned by the application after executing and committing the previous block.
258            app_hash: tendermint::AppHash::try_from(test_node.last_app_hash().to_vec())?,
259            // TODO: we should probably have a way to set this
260            // root hash of a Merkle tree built from DeliverTxResponse responses(Log,Info, Codespace and Events fields are ignored).The first block has block.Header.ResultsHash == MerkleRoot(nil), i.e. the hash of an empty input, for RFC-6962 conformance.
261            // the go version will shasum empty bytes and produce "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
262            last_results_hash: Some(
263                hex::decode("E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855")?
264                    .try_into()?,
265            ),
266            // MerkleRoot of the evidence of Byzantine behavior included in this block.
267            evidence_hash: Some(
268                hex::decode("E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855")?
269                    .try_into()?,
270            ),
271            // Address of the original proposer of the block. Validator must be in the current validatorSet.
272            proposer_address,
273        };
274        tracing::trace!(?header, "built block header");
275
276        // The next block will use the signatures of this block's header.
277        let signatures: Vec<block::CommitSig> = if !disable_signatures {
278            test_node.generate_signatures(&header).collect()
279        } else {
280            vec![]
281        };
282
283        tracing::trace!(
284            height=?height.value(),
285            app_hash=?hex::encode(header.app_hash.clone()),
286            block_id=?hex::encode(header.hash()),
287            last_commit_height=?last_commit.as_ref().map(|c| c.height.value()),
288            "made block"
289        );
290        // pass the current value of last_commit with this header
291        let block = Block::new(header.clone(), data, evidence, last_commit);
292
293        // Now that the block is finalized, we can transition to the next block.
294        // Generate a commit for the header we just made, that will be
295        // included in the next header.
296        // Update the last_commit.
297        test_node.last_commit = Some(Commit {
298            height: block.header.height,
299            round: Round::default(),
300            block_id: block::Id {
301                hash: block.header.hash().into(),
302                // The part_set_header seems to be used internally by cometbft
303                // and the pd node doesn't care about it
304                part_set_header: block::parts::Header::new(0, Hash::None)?,
305            },
306            // Signatures of the last block
307            signatures: signatures.clone(),
308        });
309
310        Ok((test_node, block))
311    }
312}
313
314// Allows hashing of commits
315pub trait CommitHashingExt: Sized {
316    fn hash(&self) -> anyhow::Result<Option<Hash>>;
317}
318
319impl CommitHashingExt for Commit {
320    // https://github.com/tendermint/tendermint/blob/51dc810d041eaac78320adc6d53ad8b160b06601/types/block.go#L672
321    fn hash(&self) -> anyhow::Result<Option<Hash>> {
322        // make a vec of the precommit protobuf encodings
323        // then merkle hash them
324        // https://github.com/tendermint/tendermint/blob/35581cf54ec436b8c37fabb43fdaa3f48339a170/crypto/merkle/tree.go#L9
325        let bs = self
326            .signatures
327            .iter()
328            .map(|precommit| {
329                tendermint_proto::types::CommitSig::from(precommit.clone()).encode_to_vec()
330            })
331            .collect::<Vec<_>>();
332
333        match bs.len() {
334            0 =>
335            // empty hash
336            {
337                Ok(Some(
338                    hex::decode(
339                        "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
340                    )?
341                    .try_into()?,
342                ))
343            }
344            _ => Ok(Some(
345                simple_hash_from_byte_vectors::<Sha256>(&bs)
346                    .to_vec()
347                    .try_into()?,
348            )),
349        }
350    }
351}
352
353#[derive(Debug, Clone)]
354pub struct EndBlockEvents(pub Vec<Event>);
355
356#[derive(Debug, Clone)]
357pub struct DeliverTxEvents(pub Vec<Event>);
358
359impl Deref for DeliverTxEvents {
360    type Target = Vec<Event>;
361
362    fn deref(&self) -> &Self::Target {
363        &self.0
364    }
365}
366
367impl Deref for EndBlockEvents {
368    type Target = Vec<Event>;
369
370    fn deref(&self) -> &Self::Target {
371        &self.0
372    }
373}