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}