Skip to content

Commit

Permalink
[fastx client] Check authority responses (Part 3) (MystenLabs#388)
Browse files Browse the repository at this point in the history
* Added checks to authority responses

Co-authored-by: George Danezis <[email protected]>
  • Loading branch information
gdanezis and George Danezis authored Feb 10, 2022
1 parent 9a597c8 commit 64a22e3
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 35 deletions.
46 changes: 11 additions & 35 deletions fastpay_core/src/authority_aggregator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// SPDX-License-Identifier: Apache-2.0

use crate::authority_client::AuthorityAPI;
use crate::safe_client::SafeClient;

use fastx_types::object::{Object, ObjectRead};
use fastx_types::{
base_types::*,
committee::Committee,
error::{FastPayError, FastPayResult},
fp_ensure,
messages::*,
};
use futures::{future, StreamExt};
Expand All @@ -33,7 +33,7 @@ pub struct AuthorityAggregator<AuthorityAPI> {
/// Our FastPay committee.
pub committee: Committee,
/// How to talk to this committee.
authority_clients: BTreeMap<AuthorityName, AuthorityAPI>,
authority_clients: BTreeMap<AuthorityName, SafeClient<AuthorityAPI>>,
}

impl<AuthorityAPI> AuthorityAggregator<AuthorityAPI> {
Expand All @@ -42,8 +42,11 @@ impl<AuthorityAPI> AuthorityAggregator<AuthorityAPI> {
authority_clients: BTreeMap<AuthorityName, AuthorityAPI>,
) -> Self {
Self {
committee,
authority_clients,
committee: committee.clone(),
authority_clients: authority_clients
.into_iter()
.map(|(name, api)| (name, SafeClient::new(api, committee.clone(), name)))
.collect(),
}
}
}
Expand Down Expand Up @@ -279,7 +282,7 @@ where
initial_timeout: Duration,
) -> Result<S, FastPayError>
where
FMap: FnOnce(AuthorityName, &'a A) -> AsyncResult<'a, V, FastPayError> + Clone,
FMap: FnOnce(AuthorityName, &'a SafeClient<A>) -> AsyncResult<'a, V, FastPayError> + Clone,
FReduce: Fn(
S,
AuthorityName,
Expand Down Expand Up @@ -685,7 +688,6 @@ where
.await?;

// Now broadcast the order to all authorities.
let digest = order.digest();
let threshold = self.committee.quorum_threshold();
let validity = self.committee.validity_threshold();

Expand All @@ -710,34 +712,8 @@ where
let state = self
.quorum_map_then_reduce_with_timeout(
state,
|name, client| {
Box::pin(async move {
let info_reponse = client.handle_order(order_ref.clone()).await?;

// NOTE: Move these and check all responses for validity.

if let Some(signed_order) = &info_reponse.signed_order {
signed_order.check(&self.committee)?;
fp_ensure!(
signed_order.authority == name,
FastPayError::ByzantineAuthoritySuspicion { authority: name }
);
fp_ensure!(
signed_order.order.digest() == digest,
FastPayError::ByzantineAuthoritySuspicion { authority: name }
);
}

if let Some(certificate) = &info_reponse.certified_order {
certificate.check(&self.committee)?;
fp_ensure!(
certificate.order.digest() == digest,
FastPayError::ByzantineAuthoritySuspicion { authority: name }
);
}

Ok(info_reponse)
})
|_name, client| {
Box::pin(async move { client.handle_order(order_ref.clone()).await })
},
|mut state, name, weight, result| {
Box::pin(async move {
Expand Down Expand Up @@ -1069,7 +1045,7 @@ where
/// The object ids are also returned so the caller can determine which fetches failed
/// NOTE: This function assumes all authorities are honest
async fn fetch_one_object(
authority_clients: BTreeMap<PublicKeyBytes, A>,
authority_clients: BTreeMap<PublicKeyBytes, SafeClient<A>>,
object_ref: ObjectRef,
timeout: Duration,
sender: tokio::sync::mpsc::Sender<Result<Object, FastPayError>>,
Expand Down
1 change: 1 addition & 0 deletions fastpay_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub mod authority_aggregator;
pub mod authority_client;
pub mod authority_server;
pub mod client;
pub mod safe_client;
191 changes: 191 additions & 0 deletions fastpay_core/src/safe_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// SPDX-License-Identifier: Apache-2.0

use crate::authority_client::AuthorityAPI;
use async_trait::async_trait;
use fastx_types::{base_types::*, committee::*, fp_ensure};

use fastx_types::{
error::{FastPayError, FastPayResult},
messages::*,
};

#[derive(Clone)]
pub struct SafeClient<C> {
authority_client: C,
committee: Committee,
address: FastPayAddress,
}

impl<C> SafeClient<C> {
pub fn new(authority_client: C, committee: Committee, address: FastPayAddress) -> Self {
Self {
authority_client,
committee,
address,
}
}

// Here we centralize all checks for order info responses
fn check_order_response(
&self,
digest: TransactionDigest,
response: &OrderInfoResponse,
) -> FastPayResult {
if let Some(signed_order) = &response.signed_order {
// Check the order signature
signed_order.check(&self.committee)?;
// Check it has the right signer
fp_ensure!(
signed_order.authority == self.address,
FastPayError::ByzantineAuthoritySuspicion {
authority: self.address
}
);
// Check it's the right order
fp_ensure!(
signed_order.order.digest() == digest,
FastPayError::ByzantineAuthoritySuspicion {
authority: self.address
}
);
}

if let Some(certificate) = &response.certified_order {
// Check signatures and quorum
certificate.check(&self.committee)?;
// Check it's the right order
fp_ensure!(
certificate.order.digest() == digest,
FastPayError::ByzantineAuthoritySuspicion {
authority: self.address
}
);
}

if let Some(signed_effects) = &response.signed_effects {
// Check signature
signed_effects
.signature
.check(&signed_effects.effects, self.address)?;
// Checks it concerns the right tx
fp_ensure!(
signed_effects.effects.transaction_digest == digest,
FastPayError::ByzantineAuthoritySuspicion {
authority: self.address
}
);
// Check it has the right signer
fp_ensure!(
signed_effects.authority == self.address,
FastPayError::ByzantineAuthoritySuspicion {
authority: self.address
}
);
}

Ok(())
}

fn check_object_response(
&self,
request: &ObjectInfoRequest,
response: &ObjectInfoResponse,
) -> FastPayResult {
// If we get a certificate make sure it is a valid certificate
if let Some(certificate) = &response.parent_certificate {
certificate.check(&self.committee)?;
}

// Check the right version is returned
if let Some(requested_version) = &request.request_sequence_number {
if let Some(object_ref) = &response.requested_object_reference {
fp_ensure!(
object_ref.1 == *requested_version,
FastPayError::ByzantineAuthoritySuspicion {
authority: self.address
}
);
}
}

// If an order lock is returned it is valid.
if let Some(object_and_lock) = &response.object_and_lock {
if let Some(signed_order) = &object_and_lock.lock {
signed_order.check(&self.committee)?;
// Check it has the right signer
fp_ensure!(
signed_order.authority == self.address,
FastPayError::ByzantineAuthoritySuspicion {
authority: self.address
}
);
}
}

Ok(())
}
}

#[async_trait]
impl<C> AuthorityAPI for SafeClient<C>
where
C: AuthorityAPI + Send + Sync + Clone + 'static,
{
/// Initiate a new transfer to a FastPay or Primary account.
async fn handle_order(&self, order: Order) -> Result<OrderInfoResponse, FastPayError> {
let digest = order.digest();
let order_info = self.authority_client.handle_order(order).await?;
self.check_order_response(digest, &order_info)?;
Ok(order_info)
}

/// Confirm a transfer to a FastPay or Primary account.
async fn handle_confirmation_order(
&self,
order: ConfirmationOrder,
) -> Result<OrderInfoResponse, FastPayError> {
let digest = order.certificate.order.digest();
let order_info = self
.authority_client
.handle_confirmation_order(order)
.await?;
self.check_order_response(digest, &order_info)?;
Ok(order_info)
}

async fn handle_account_info_request(
&self,
request: AccountInfoRequest,
) -> Result<AccountInfoResponse, FastPayError> {
self.authority_client
.handle_account_info_request(request)
.await
}

async fn handle_object_info_request(
&self,
request: ObjectInfoRequest,
) -> Result<ObjectInfoResponse, FastPayError> {
let response = self
.authority_client
.handle_object_info_request(request.clone())
.await?;
self.check_object_response(&request, &response)?;
Ok(response)
}

/// Handle Object information requests for this account.
async fn handle_order_info_request(
&self,
request: OrderInfoRequest,
) -> Result<OrderInfoResponse, FastPayError> {
let digest = request.transaction_digest;
let order_info = self
.authority_client
.handle_order_info_request(request)
.await?;
self.check_order_response(digest, &order_info)?;
Ok(order_info)
}
}

0 comments on commit 64a22e3

Please sign in to comment.