Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions packages/rs-drive-abci/src/abci/error.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
use super::signature_verifier::SignatureError;
use crate::validator_set::ValidatorSetError;
use dpp::bls_signatures::Error as BLSError;

/// Error returned within ABCI server
#[derive(Debug, thiserror::Error)]
pub enum AbciError {
/// Invalid system state
#[error("invalid state: {0}")]
InvalidState(String),
/// Request does not match currently processed block
#[error("request does not match current block: {0}")]
RequestForWrongBlockReceived(String),
/// Withdrawal transactions mismatch
#[error("vote extensions mismatch: got {got:?}, expected {expected:?}")]
#[allow(missing_docs)]
VoteExtensionMismatchReceived { got: String, expected: String },
/// Vote extensions signature is invalid
#[error("one of vote extension signatures is invalid")]
VoteExtensionsSignatureInvalid,
/// Cannot load withdrawal transactions
#[error("cannot load withdrawal transactions: {0}")]
WithdrawalTransactionsDBLoadError(String),
/// Wrong finalize block received
#[error("finalize block received before processing from Tenderdash: {0}")]
FinalizeBlockReceivedBeforeProcessing(String),
Expand All @@ -26,9 +39,9 @@ pub enum AbciError {
#[error("tenderdash: {0}")]
Tenderdash(#[from] tenderdash_abci::Error),

/// Error occurred during bls signature verification
#[error("bls error set: {0}")]
BLSError(#[from] BLSError),
/// Error occurred during signature verification
#[error("signature error: {0}")]
SignatureError(#[from] SignatureError),

/// Error occurred during validator set creation
#[error("validator set: {0}")]
Expand All @@ -38,3 +51,10 @@ pub enum AbciError {
#[error("generic with code: {0}")]
GenericWithCode(u32),
}

// used by `?` operator
impl From<AbciError> for String {
fn from(value: AbciError) -> Self {
value.to_string()
}
}
77 changes: 77 additions & 0 deletions packages/rs-drive-abci/src/abci/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use crate::abci::server::AbciApplication;
use crate::rpc::core::CoreRPCLike;
use drive::fee::credits::SignedCredits;
use tenderdash_abci::proto::abci::response_verify_vote_extension::VerifyStatus;
use tenderdash_abci::proto::abci::tx_record::TxAction;
use tenderdash_abci::proto::abci::{
self as proto, RequestExtendVote, ResponseException, ResponseExtendVote,
Expand All @@ -49,6 +50,9 @@ use crate::error::execution::ExecutionError;
use crate::error::Error;
use crate::execution::engine::BlockExecutionOutcome;

use super::withdrawal::WithdrawalTxs;
use super::AbciError;

impl<'a, C> tenderdash_abci::Application for AbciApplication<'a, C>
where
C: CoreRPCLike,
Expand Down Expand Up @@ -181,12 +185,59 @@ where
let response = ResponseProcessProposal {
app_hash: app_hash.to_vec(),
tx_results,
status: proto::response_process_proposal::ProposalStatus::Accept.into(),
..Default::default()
};

Ok(response)
}

fn extend_vote(
&self,
request: proto::RequestExtendVote,
) -> Result<proto::ResponseExtendVote, proto::ResponseException> {
let transaction_guard = self.transaction.read().unwrap();
let transaction = transaction_guard.as_ref().unwrap();

self.must_match(request.height, request.round, request.hash)?;

let withdrawals = WithdrawalTxs::load(Some(transaction), &self.platform.drive)?;

Ok(proto::ResponseExtendVote {
vote_extensions: withdrawals.to_vec(),
})
}

fn verify_vote_extension(
&self,
request: proto::RequestVerifyVoteExtension,
) -> Result<proto::ResponseVerifyVoteExtension, proto::ResponseException> {
let transaction_guard = self.transaction.read().unwrap();
let transaction = transaction_guard.as_ref().unwrap();

self.must_match(request.height, request.round, request.hash)?;

let got: WithdrawalTxs = request.vote_extensions.into();
let expected = WithdrawalTxs::load(Some(transaction), &self.platform.drive)?;

if let Err(err) = self.platform.check_withdrawals(&got, &expected, None) {
tracing::error!(
method = "verify_vote_extension",
?got,
?expected,
?err,
"vote extension mismatch"
);
Ok(proto::ResponseVerifyVoteExtension {
status: VerifyStatus::Reject.into(),
})
} else {
Ok(proto::ResponseVerifyVoteExtension {
status: VerifyStatus::Accept.into(),
})
}
}

fn finalize_block(
&self,
request: RequestFinalizeBlock,
Expand All @@ -198,6 +249,7 @@ where
"trying to finalize block without a current transaction",
),
))?;

self.platform
.finalize_block_proposal(request, transaction)?;

Expand Down Expand Up @@ -798,3 +850,28 @@ where
// }
// }
// }
impl<'a, C> AbciApplication<'a, C> {
/// Check if current state (round/height/hash) matches received message.
fn must_match(&self, height: i64, round: i32, hash: Vec<u8>) -> Result<(), Error> {
let guarded_block_execution_context = self.platform.block_execution_context.read().unwrap();
let block_execution_context =
guarded_block_execution_context
.as_ref()
.ok_or(Error::Execution(ExecutionError::CorruptedCodeExecution(
"block execution context must be set in block begin handler",
)))?;

let block_state_info = &block_execution_context.block_state_info;

if !block_state_info.matches(height as u64, round as u32, hash)? {
Err(Error::from(AbciError::RequestForWrongBlockReceived(
format!(
"received request for height: {} rount: {}, expected height: {} round: {}",
height, round, block_state_info.height, block_state_info.round
),
)))
} else {
Ok(())
}
}
}
3 changes: 3 additions & 0 deletions packages/rs-drive-abci/src/abci/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub mod mimic;
#[cfg(any(feature = "server", test))]
mod server;

pub mod signature_verifier;
pub mod withdrawal;

pub use error::AbciError;
#[cfg(feature = "server")]
pub use server::start;
Expand Down
60 changes: 60 additions & 0 deletions packages/rs-drive-abci/src/abci/signature_verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Signature verification

use dpp::bls_signatures::Serialize;
use tenderdash_abci::proto::types::VoteExtension;

/// Errors occured during signature verification
#[derive(Debug, thiserror::Error)]
pub enum SignatureError {
/// Error occurred during bls signature verification
#[error("bls error set: {0}")]
BLSError(#[from] dpp::bls_signatures::Error),
}
/// SignatureVerifier can be used to verify a BLS signature.
pub trait SignatureVerifier {
/// Verify all signatures using provided public key.
///
/// ## Return value
///
/// * Ok(true) when all signatures are correct
/// * Ok(false) when at least one signature is invalid
/// * Err(e) on error
fn verify_signature(
&self,
quorum_public_key: dpp::bls_signatures::PublicKey,
) -> Result<bool, SignatureError>;
}

impl<T: SignatureVerifier> SignatureVerifier for Vec<T> {
fn verify_signature(
&self,
quorum_public_key: dpp::bls_signatures::PublicKey,
) -> Result<bool, SignatureError> {
for tx in self {
if !tx.verify_signature(quorum_public_key)? {
return Ok(false);
}
}

Ok(true)
}
}

impl SignatureVerifier for VoteExtension {
fn verify_signature(
&self,
_quorum_public_key: dpp::bls_signatures::PublicKey,
) -> Result<bool, SignatureError> {
let signature = &self.signature;

// We could have received a fake commit, so signature validation needs to be returned if error as a simple validation result
let _signature = match dpp::bls_signatures::Signature::from_bytes(signature.as_slice()) {
Ok(signature) => signature,
Err(e) => return Err(SignatureError::from(e)),
};
// TODO: implement correct signature verification for VoteExtension. It uses CanonicalVoteExtension.
// For now, we just return `true`
// Ok(quorum_public_key.verify(signature, &self.extension))
Ok(true)
}
}
151 changes: 151 additions & 0 deletions packages/rs-drive-abci/src/abci/withdrawal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//! Withdrawal transactions definitions and processing

use dpp::bls_signatures::{self};
use drive::{
drive::{batch::DriveOperation, block_info::BlockInfo, Drive},
fee::result::FeeResult,
query::TransactionArg,
};
use std::fmt::Display;
use tenderdash_abci::proto::{
abci::ExtendVoteExtension,
types::{VoteExtension, VoteExtensionType},
};

use super::{
signature_verifier::{SignatureError, SignatureVerifier},
AbciError,
};

const MAX_WITHDRAWAL_TXS: u16 = 16;

/// Collection of withdrawal transactions processed at some height/round
#[derive(Debug)]
pub struct WithdrawalTxs<'a> {
inner: Vec<VoteExtension>,
drive_operations: Vec<DriveOperation<'a>>,
}

impl<'a> WithdrawalTxs<'a> {
/// Load pending withdrawal transactions from database
pub fn load(transaction: TransactionArg, drive: &Drive) -> Result<Self, AbciError> {
let mut drive_operations = Vec::<DriveOperation>::new();

let inner = drive
.dequeue_withdrawal_transactions(MAX_WITHDRAWAL_TXS, transaction, &mut drive_operations)
.map_err(|e| AbciError::WithdrawalTransactionsDBLoadError(e.to_string()))?
.into_iter()
.map(|(_k, v)| VoteExtension {
r#type: VoteExtensionType::ThresholdRecover.into(),
extension: v,
signature: Default::default(),
})
.collect::<Vec<VoteExtension>>();

Ok(Self {
drive_operations,
inner,
})
}

/// Basic validation of withdrawals.
///
/// TODO: validate signature, etc.
pub fn validate(&self) -> Result<(), AbciError> {
if self.drive_operations.len() != self.inner.len() {
return Err(AbciError::InvalidState(format!(
"num of drive operations {} must match num of withdrawal transactions {}",
self.drive_operations.len(),
self.inner.len(),
)));
}

Ok(())
}

/// Finalize operations related to this withdrawal, as part of FinalizeBlock logic.
///
/// Deletes withdrawal transactions that were executed.
pub fn finalize(
&self,
transaction: TransactionArg,
drive: &Drive,
block_info: &BlockInfo,
) -> Result<FeeResult, AbciError> {
self.validate()?;
// TODO: Do we need to do sth with withdrawal txs to actually execute them?
// FIXME: check if this is correct, esp. "apply" arg
drive
.apply_drive_operations(self.drive_operations.clone(), true, block_info, transaction)
.map_err(|e| AbciError::WithdrawalTransactionsDBLoadError(e.to_string()))
}
}

impl<'a> WithdrawalTxs<'a> {
/// Convert withdrawal transactions to vector of ExtendVoteExtension
pub fn to_vec(self) -> Vec<ExtendVoteExtension> {
self.inner
.into_iter()
.map(|v| ExtendVoteExtension {
r#type: v.r#type,
extension: v.extension,
})
.collect::<Vec<ExtendVoteExtension>>()
}
}

impl<'a> Display for WithdrawalTxs<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("txs:["))?;
for item in &self.inner {
f.write_fmt(format_args!(
"tx:{},sig:{}\n",
hex::encode(&item.extension),
hex::encode(&item.signature)
))?;
}
f.write_str("]\n")?;
Ok(())
}
}
impl<'a> From<Vec<ExtendVoteExtension>> for WithdrawalTxs<'a> {
fn from(value: Vec<ExtendVoteExtension>) -> Self {
WithdrawalTxs {
inner: value
.into_iter()
.map(|v| VoteExtension {
r#type: v.r#type,
extension: v.extension,
signature: Default::default(),
})
.collect::<Vec<VoteExtension>>(),
drive_operations: Vec::<DriveOperation>::new(),
}
}
}

impl<'a> From<&Vec<VoteExtension>> for WithdrawalTxs<'a> {
fn from(value: &Vec<VoteExtension>) -> Self {
WithdrawalTxs {
inner: value.clone(),
drive_operations: Vec::<DriveOperation>::new(),
}
}
}

impl<'a> PartialEq for WithdrawalTxs<'a> {
/// Two sets of withdrawal transactions are equal if all their inner raw transactions are equal.
/// Note we don't compare `drive_operations`.
fn eq(&self, other: &Self) -> bool {
self.inner.eq(&other.inner)
}
}

impl<'a> SignatureVerifier for WithdrawalTxs<'a> {
fn verify_signature(
&self,
quorum_public_key: bls_signatures::PublicKey,
) -> Result<bool, SignatureError> {
self.inner.verify_signature(quorum_public_key)
}
}
Loading