-
Notifications
You must be signed in to change notification settings - Fork 316
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
mock-consensus: 🚀 test node can send an empty block
fixes #3792. see #3588. * `block` module defining interfaces to build a tendermint block, holding a unique reference to the `TestNode`. * `abci` module defining `TestNode` interfaces that will send consensus requests to the application. this includes: * BeginBlock * DeliverTx * EndBlock * Commit * `send_block` module defining a `TestNode` interface to send the requisite abci requests, given a `tendermint::Block`. * documentation is added for assorted public interfaces. this represents a huge, exciting step for work on the mock engine! we can now initialize and send an (empty) block to the consensus service. ✨ 🎊 ✨ 🎊 ✨ what next? this isn't a _comprehensive_ set of interfaces. `penumbra_mock_consensus::block::Builder` will certainly grow more methods as it is iterated upon. we'll use work in porting tests (see #3788) to drive which other fields are needed. forthcoming work will build upon this to: * introduce more builder methods to set other `Header` fields (e.g. timestamp, see #3759) * use the reference to the test node to set other `Header` fields (e.g. height) todo comments are left to that effect. --- * #3588 * #3792
- Loading branch information
Showing
5 changed files
with
374 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
//! [`TestNode`] interfaces for sending consensus requests to an ABCI application. | ||
use { | ||
super::TestNode, | ||
anyhow::anyhow, | ||
bytes::Bytes, | ||
tap::{Tap, TapFallible}, | ||
tendermint::{ | ||
abci::types::CommitInfo, | ||
block::{Header, Round}, | ||
v0_37::abci::{request, response, ConsensusRequest, ConsensusResponse}, | ||
}, | ||
tower::{BoxError, Service}, | ||
tracing::{error, instrument, trace}, | ||
}; | ||
|
||
/// ABCI-related interfaces. | ||
impl<C> TestNode<C> | ||
where | ||
C: Service<ConsensusRequest, Response = ConsensusResponse, Error = BoxError> | ||
+ Send | ||
+ Clone | ||
+ 'static, | ||
C::Future: Send + 'static, | ||
C::Error: Sized, | ||
{ | ||
/// Yields a mutable reference to the consensus service when it is ready to accept a request. | ||
async fn service(&mut self) -> Result<&mut C, anyhow::Error> { | ||
use tower::ServiceExt; | ||
self.consensus | ||
.ready() | ||
.tap(|_| trace!("waiting for consensus service")) | ||
.await | ||
.tap_err(|error| error!(?error, "failed waiting for consensus service")) | ||
.map_err(|_| anyhow!("failed waiting for consensus service")) | ||
.tap_ok(|_| trace!("consensus service is now ready")) | ||
} | ||
|
||
/// Sends a [`ConsensusRequest::BeginBlock`] request to the ABCI application. | ||
#[instrument(level = "debug", skip_all)] | ||
pub async fn begin_block( | ||
&mut self, | ||
header: Header, | ||
) -> Result<response::BeginBlock, anyhow::Error> { | ||
let request = ConsensusRequest::BeginBlock(request::BeginBlock { | ||
hash: tendermint::Hash::None, | ||
header, | ||
last_commit_info: CommitInfo { | ||
round: Round::from(1_u8), | ||
votes: Default::default(), | ||
}, | ||
byzantine_validators: Default::default(), | ||
}); | ||
let service = self.service().await?; | ||
match service | ||
.tap(|_| trace!("sending BeginBlock request")) | ||
.call(request) | ||
.await | ||
.tap_err(|error| error!(?error, "consensus service returned error")) | ||
.map_err(|_| anyhow!("consensus service returned error"))? | ||
{ | ||
ConsensusResponse::BeginBlock(response) => { | ||
let response::BeginBlock { events } = &response; | ||
trace!(?events, "received BeginBlock events"); | ||
Ok(response) | ||
} | ||
response => { | ||
error!(?response, "unexpected InitChain response"); | ||
Err(anyhow!("unexpected InitChain response")) | ||
} | ||
} | ||
} | ||
|
||
/// Sends a [`ConsensusRequest::DeliverTx`] request to the ABCI application. | ||
#[instrument(level = "debug", skip_all)] | ||
pub async fn deliver_tx(&mut self, tx: Bytes) -> Result<response::DeliverTx, anyhow::Error> { | ||
let request = ConsensusRequest::DeliverTx(request::DeliverTx { tx }); | ||
let service = self.service().await?; | ||
match service | ||
.tap(|_| trace!("sending DeliverTx request")) | ||
.call(request) | ||
.await | ||
.tap_err(|error| error!(?error, "consensus service returned error")) | ||
.map_err(|_| anyhow!("consensus service returned error"))? | ||
{ | ||
ConsensusResponse::DeliverTx(response) => { | ||
let response::DeliverTx { | ||
code, | ||
gas_used, | ||
gas_wanted, | ||
events, | ||
.. | ||
} = &response; | ||
trace!( | ||
?code, | ||
?gas_used, | ||
?gas_wanted, | ||
?events, | ||
"received DeliverTx response" | ||
); | ||
Ok(response) | ||
} | ||
response => { | ||
error!(?response, "unexpected DeliverTx response"); | ||
Err(anyhow!("unexpected DeliverTx response")) | ||
} | ||
} | ||
} | ||
|
||
/// Sends a [`ConsensusRequest::EndBlock`] request to the ABCI application. | ||
#[instrument(level = "debug", skip_all)] | ||
pub async fn end_block(&mut self) -> Result<response::EndBlock, anyhow::Error> { | ||
let request = ConsensusRequest::EndBlock(request::EndBlock { height: 1 }); | ||
let service = self.service().await?; | ||
match service | ||
.call(request) | ||
.await | ||
.tap_err(|error| error!(?error, "consensus service returned error")) | ||
.map_err(|_| anyhow!("consensus service returned error"))? | ||
{ | ||
ConsensusResponse::EndBlock(response) => { | ||
let response::EndBlock { | ||
validator_updates, | ||
consensus_param_updates, | ||
events, | ||
} = &response; | ||
trace!( | ||
?validator_updates, | ||
?consensus_param_updates, | ||
?events, | ||
"received EndBlock response" | ||
); | ||
Ok(response) | ||
} | ||
response => { | ||
error!(?response, "unexpected EndBlock response"); | ||
Err(anyhow!("unexpected EndBlock response")) | ||
} | ||
} | ||
} | ||
|
||
/// Sends a [`ConsensusRequest::Commit`] request to the ABCI application. | ||
#[instrument(level = "debug", skip_all)] | ||
pub async fn commit(&mut self) -> Result<response::Commit, anyhow::Error> { | ||
let request = ConsensusRequest::Commit; | ||
let service = self.service().await?; | ||
match service | ||
.call(request) | ||
.await | ||
.tap_err(|error| error!(?error, "consensus service returned error")) | ||
.map_err(|_| anyhow!("consensus service returned error"))? | ||
{ | ||
ConsensusResponse::Commit(response) => { | ||
let response::Commit { | ||
data, | ||
retain_height, | ||
} = &response; | ||
trace!(?data, ?retain_height, "received Commit response"); | ||
Ok(response) | ||
} | ||
response => { | ||
error!(?response, "unexpected Commit response"); | ||
Err(anyhow!("unexpected Commit response")) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,107 @@ | ||
// TODO: see #3792. | ||
//! [`Builder`] facilities for constructing [`Block`]s. | ||
//! | ||
/// Builders are acquired by calling [`TestNode::block()`]. | ||
use { | ||
crate::TestNode, | ||
anyhow::bail, | ||
tendermint::{ | ||
account, | ||
block::{header::Version, Block, Commit, Header, Height}, | ||
chain, evidence, AppHash, Hash, | ||
}, | ||
}; | ||
|
||
use crate::TestNode; | ||
/// A builder, used to prepare and instantiate a new [`Block`]. | ||
/// | ||
/// These are acquired by calling [`TestNode::block()`]. | ||
pub struct Builder<'e, C> { | ||
/// A unique reference to the test node. | ||
// | ||
// NB: this is currently unused, but will eventually be used to fill in header fields, etc. | ||
#[allow(dead_code)] | ||
test_node: &'e mut TestNode<C>, | ||
|
||
struct _Builder<'e, C> { | ||
engine: &'e mut TestNode<C>, | ||
/// Transaction data. | ||
data: Option<Vec<Vec<u8>>>, | ||
|
||
/// Evidence of malfeasance. | ||
evidence: Option<evidence::List>, | ||
|
||
/// Last commit. | ||
last_commit: Option<Commit>, | ||
} | ||
|
||
impl<C> TestNode<C> { | ||
/// Returns a new [`Builder`]. | ||
pub fn block<'e>(&'e mut self) -> Builder<'e, C> { | ||
Builder { | ||
test_node: self, | ||
data: Default::default(), | ||
evidence: Default::default(), | ||
last_commit: Default::default(), | ||
} | ||
} | ||
} | ||
|
||
impl<'e, C> Builder<'e, C> { | ||
/// Sets the data for this block. | ||
pub fn with_data(self, data: Vec<Vec<u8>>) -> Self { | ||
Self { | ||
data: Some(data), | ||
..self | ||
} | ||
} | ||
|
||
/// Sets the evidence [`List`][evidence::List] for this block. | ||
pub fn with_evidence(self, evidence: evidence::List) -> Self { | ||
Self { | ||
evidence: Some(evidence), | ||
..self | ||
} | ||
} | ||
|
||
/// Sets the last [`Commit`] for this block. | ||
pub fn with_last_commit(self, last_commit: Commit) -> Self { | ||
Self { | ||
last_commit: Some(last_commit), | ||
..self | ||
} | ||
} | ||
|
||
// TODO(kate): add more `with_` setters for fields in the header. | ||
// TODO(kate): set some fields using state in the test node. | ||
|
||
/// Consumes this builder, returning a [`Block`]. | ||
pub fn finish(self) -> Result<Block, anyhow::Error> { | ||
let Self { | ||
data: Some(data), | ||
evidence: Some(evidence), | ||
last_commit, | ||
test_node: _, | ||
} = self | ||
else { | ||
bail!("builder was not fully initialized") | ||
}; | ||
|
||
let header = Header { | ||
version: Version { block: 1, app: 1 }, | ||
chain_id: chain::Id::try_from("test".to_owned())?, | ||
height: Height::try_from(1_u8)?, | ||
time: tendermint::Time::now(), | ||
last_block_id: None, | ||
last_commit_hash: None, | ||
data_hash: None, | ||
validators_hash: Hash::None, | ||
next_validators_hash: Hash::None, | ||
consensus_hash: Hash::None, | ||
app_hash: AppHash::try_from(Vec::default())?, | ||
last_results_hash: None, | ||
evidence_hash: None, | ||
proposer_address: account::Id::new([ | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
]), | ||
}; | ||
|
||
Block::new(header, data, evidence, last_commit).map_err(Into::into) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.