Skip to content

Commit

Permalink
Ensure gateway gas metering is consistent with authority (MystenLabs#…
Browse files Browse the repository at this point in the history
…1764)

* Ensure gateway gas metering is consistent with authority

* Fix type
  • Loading branch information
lxfind authored May 4, 2022
1 parent 954bccb commit b2d12b3
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 131 deletions.
101 changes: 10 additions & 91 deletions sui_core/src/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use sui_types::{
crypto::AuthoritySignature,
error::{SuiError, SuiResult},
fp_bail, fp_ensure,
gas::{self, SuiGasStatus},
gas::SuiGasStatus,
messages::*,
object::{Data, Object},
storage::{BackingPackageStore, DeleteKind, Storage},
Expand Down Expand Up @@ -60,7 +60,7 @@ mod temporary_store;
pub use temporary_store::AuthorityTemporaryStore;

mod authority_store;
pub use authority_store::{AuthorityStore, GatewayStore};
pub use authority_store::{AuthorityStore, GatewayStore, SuiDataStore};

pub mod authority_notifier;

Expand Down Expand Up @@ -117,69 +117,6 @@ impl AuthorityState {
self.batch_channels.subscribe()
}

/// Checking gas budget by fetching the gas object only from the store,
/// and check whether the balance and budget satisfies the miminum requirement.
/// Returns the gas object (to be able to rese it latter) and a gas status
/// that will be used in the entire lifecycle of the transaction execution.
#[instrument(level = "trace", skip_all)]
async fn check_gas(
&self,
gas_payment_id: ObjectID,
gas_budget: u64,
) -> SuiResult<(Object, SuiGasStatus<'_>)> {
let gas_object = self.get_object(&gas_payment_id).await?;
let gas_object = gas_object.ok_or(SuiError::ObjectNotFound {
object_id: gas_payment_id,
})?;
gas::check_gas_balance(&gas_object, gas_budget)?;
// TODO: Pass in real computation gas unit price and storage gas unit price.
let gas_status = gas::start_gas_metering(gas_budget, 1, 1)?;
Ok((gas_object, gas_status))
}

#[instrument(level = "trace", skip_all)]
async fn check_locks(
&self,
transaction: &TransactionData,
gas_object: Object,
gas_status: &mut SuiGasStatus<'_>,
) -> Result<Vec<(InputObjectKind, Object)>, SuiError> {
let input_objects = transaction.input_objects()?;
// These IDs act as authenticators that can own other objects.
let objects = self.fetch_objects(&input_objects, Some(gas_object)).await?;
let all_objects =
transaction_input_checker::check_locks(transaction, input_objects, objects).await?;
// Charge gas for reading all objects from the DB.
// TODO: Some of the objects may be duplicate (for batch tx). We could save gas by
// fetching only unique objects.
let total_size = all_objects
.iter()
.map(|(_, obj)| obj.object_size_for_gas_metering())
.sum();
gas_status.charge_storage_read(total_size)?;

Ok(all_objects)
}

#[instrument(level = "trace", skip_all, fields(num_objects = input_objects.len()))]
async fn fetch_objects(
&self,
input_objects: &[InputObjectKind],
gas_object_opt: Option<Object>,
) -> Result<Vec<Option<Object>>, SuiError> {
let ids: Vec<_> = input_objects.iter().map(|kind| kind.object_id()).collect();
if let Some(gas_object) = gas_object_opt {
// If we already have the gas object, avoid fetching it again.
// Skip the last one since it's the gas object.
debug_assert_eq!(gas_object.id(), ids[ids.len() - 1]);
let mut result = self.get_objects(&ids[..ids.len() - 1]).await?;
result.push(Some(gas_object));
Ok(result)
} else {
self.get_objects(&ids[..]).await
}
}

async fn handle_transaction_impl(
&self,
transaction: Transaction,
Expand All @@ -191,21 +128,10 @@ impl AuthorityState {
return Ok(transaction_info);
}

let (gas_object, mut gas_status) = self
.check_gas(
transaction.gas_payment_object_ref().0,
transaction.data.gas_budget,
)
.await?;
let (_gas_status, all_objects) =
transaction_input_checker::check_transaction_input(&self._database, &transaction)
.await?;

let all_objects: Vec<_> = self
.check_locks(&transaction.data, gas_object, &mut gas_status)
.await?;
if transaction.contains_shared_object() {
// It's important that we do this here to make sure there is enough
// gas to cover shared objects, before we lock all objects.
gas_status.charge_consensus()?;
}
let owned_objects = transaction_input_checker::filter_owned_objects(&all_objects);

let signed_transaction =
Expand Down Expand Up @@ -332,16 +258,9 @@ impl AuthorityState {
let certificate = confirmation_transaction.certificate;
let transaction_digest = *certificate.digest();

let (gas_object, mut gas_status) = self
.check_gas(
certificate.gas_payment_object_ref().0,
certificate.data.gas_budget,
)
.await?;

let objects_by_kind = self
.check_locks(&certificate.data, gas_object, &mut gas_status)
.await?;
let (gas_status, objects_by_kind) =
transaction_input_checker::check_transaction_input(&self._database, &certificate)
.await?;

// At this point we need to check if any shared objects need locks,
// and whether they have them.
Expand All @@ -356,7 +275,6 @@ impl AuthorityState {
// for processing by the consensus protocol.
self.check_shared_locks(&transaction_digest, &shared_object_refs)
.await?;
gas_status.charge_consensus()?;
}

debug!(
Expand Down Expand Up @@ -693,7 +611,8 @@ impl AuthorityState {
modules: Vec<CompiledModule>,
) -> SuiResult {
let inputs = Transaction::input_objects_in_compiled_modules(&modules);
let input_objects = self.fetch_objects(&inputs, None).await?;
let ids: Vec<_> = inputs.iter().map(|kind| kind.object_id()).collect();
let input_objects = self.get_objects(&ids[..]).await?;
// When publishing genesis packages, since the std framework packages all have
// non-zero addresses, [`Transaction::input_objects_in_compiled_modules`] will consider
// them as dependencies even though they are not. Hence input_objects contain objects
Expand Down
57 changes: 24 additions & 33 deletions sui_core/src/gateway_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use move_core_types::language_storage::TypeTag;
use sui_adapter::adapter::resolve_and_type_check;
use tracing::{error, Instrument};

use sui_types::gas::{self, SuiGasStatus};
use sui_types::{
base_types::*,
coin,
Expand Down Expand Up @@ -255,36 +254,16 @@ where
.set_transaction_lock(mutable_input_objects, transaction)
}

async fn check_gas(
/// Make sure all objects in the input exist in the gateway store.
/// If any object does not exist in the store, give it a chance
/// to download from authorities.
async fn sync_input_objects_with_authorities(
&self,
gas_payment_id: ObjectID,
gas_budget: u64,
) -> SuiResult<(Object, SuiGasStatus<'_>)> {
let gas_object = self.get_object(&gas_payment_id).await?;
gas::check_gas_balance(&gas_object, gas_budget)?;
// TODO: Pass in real computation gas unit price and storage gas unit price.
let gas_status = gas::start_gas_metering(gas_budget, 1, 1)?;
Ok((gas_object, gas_status))
}

/// Execute (or retry) a transaction and execute the Confirmation Transaction.
/// Update local object states using newly created certificate and ObjectInfoResponse from the Confirmation step.
async fn execute_transaction_impl(
&self,
transaction: Transaction,
) -> Result<(CertifiedTransaction, TransactionEffects), anyhow::Error> {
transaction.verify_signature()?;
self.check_gas(
transaction.gas_payment_object_ref().0,
transaction.data.gas_budget,
)
.await?;

transaction: &Transaction,
) -> Result<(), anyhow::Error> {
let input_objects = transaction.data.input_objects()?;
let mut objects = self.read_objects_from_store(&input_objects).await?;
for (object_opt, kind) in objects.iter_mut().zip(&input_objects) {
// If any object does not exist in the store, give it a chance
// to download from authorities.
if object_opt.is_none() {
if let ObjectRead::Exists(_, object, _) =
self.get_object_info(kind.object_id()).await?
Expand All @@ -293,12 +272,24 @@ where
}
}
}
Ok(())
}

/// Execute (or retry) a transaction and execute the Confirmation Transaction.
/// Update local object states using newly created certificate and ObjectInfoResponse from the Confirmation step.
async fn execute_transaction_impl(
&self,
transaction: Transaction,
) -> Result<(CertifiedTransaction, TransactionEffects), anyhow::Error> {
transaction.verify_signature()?;

self.sync_input_objects_with_authorities(&transaction)
.await?;

let (_gas_status, all_objects) =
transaction_input_checker::check_transaction_input(&self.store, &transaction).await?;

let objects_by_kind =
transaction_input_checker::check_locks(&transaction.data, input_objects, objects)
.instrument(tracing::trace_span!("tx_check_locks"))
.await?;
let owned_objects = transaction_input_checker::filter_owned_objects(&objects_by_kind);
let owned_objects = transaction_input_checker::filter_owned_objects(&all_objects);
self.set_transaction_lock(&owned_objects, transaction.clone())
.instrument(tracing::trace_span!("db_set_transaction_lock"))
.await?;
Expand Down Expand Up @@ -336,7 +327,7 @@ where
.download_objects_from_authorities(mutated_object_refs)
.await?;
self.store.update_gateway_state(
objects_by_kind,
all_objects,
mutated_objects,
new_certificate.clone(),
effects.clone().to_unsigned_effects(),
Expand Down
109 changes: 102 additions & 7 deletions sui_core/src/transaction_input_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,107 @@

use std::collections::HashSet;

use serde::{Deserialize, Serialize};
use sui_types::{
base_types::{ObjectRef, SequenceNumber, SuiAddress},
base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress},
error::{SuiError, SuiResult},
fp_ensure,
messages::{InputObjectKind, SingleTransactionKind, TransactionData},
gas::{self, SuiGasStatus},
messages::{InputObjectKind, SingleTransactionKind, TransactionData, TransactionEnvelope},
object::{Object, Owner},
};
use tracing::debug;
use tracing::{debug, instrument};

use crate::authority::SuiDataStore;

#[instrument(level = "trace", skip_all)]
pub async fn check_transaction_input<const A: bool, S, T>(
store: &SuiDataStore<A, S>,
transaction: &TransactionEnvelope<T>,
) -> Result<(SuiGasStatus<'static>, Vec<(InputObjectKind, Object)>), SuiError>
where
S: Eq + Serialize + for<'de> Deserialize<'de>,
{
let (gas_object, mut gas_status) = check_gas(
store,
transaction.gas_payment_object_ref().0,
transaction.data.gas_budget,
)
.await?;

let objects_by_kind =
check_locks(store, &transaction.data, gas_object, &mut gas_status).await?;

if transaction.contains_shared_object() {
// It's important that we do this here to make sure there is enough
// gas to cover shared objects, before we lock all objects.
gas_status.charge_consensus()?;
}

Ok((gas_status, objects_by_kind))
}

/// Checking gas budget by fetching the gas object only from the store,
/// and check whether the balance and budget satisfies the miminum requirement.
/// Returns the gas object (to be able to reuse it latter) and a gas status
/// that will be used in the entire lifecycle of the transaction execution.
#[instrument(level = "trace", skip_all)]
async fn check_gas<const A: bool, S>(
store: &SuiDataStore<A, S>,
gas_payment_id: ObjectID,
gas_budget: u64,
) -> SuiResult<(Object, SuiGasStatus<'static>)>
where
S: Eq + Serialize + for<'de> Deserialize<'de>,
{
let gas_object = store.get_object(&gas_payment_id)?;
let gas_object = gas_object.ok_or(SuiError::ObjectNotFound {
object_id: gas_payment_id,
})?;
gas::check_gas_balance(&gas_object, gas_budget)?;
// TODO: Pass in real computation gas unit price and storage gas unit price.
let gas_status = gas::start_gas_metering(gas_budget, 1, 1)?;
Ok((gas_object, gas_status))
}

#[instrument(level = "trace", skip_all, fields(num_objects = input_objects.len()))]
async fn fetch_objects<const A: bool, S>(
store: &SuiDataStore<A, S>,
input_objects: &[InputObjectKind],
gas_object_opt: Option<Object>,
) -> Result<Vec<Option<Object>>, SuiError>
where
S: Eq + Serialize + for<'de> Deserialize<'de>,
{
let ids: Vec<_> = input_objects.iter().map(|kind| kind.object_id()).collect();
if let Some(gas_object) = gas_object_opt {
// If we already have the gas object, avoid fetching it again.
// Skip the last one since it's the gas object.
debug_assert_eq!(gas_object.id(), ids[ids.len() - 1]);
let mut result = store.get_objects(&ids[..ids.len() - 1])?;
result.push(Some(gas_object));
Ok(result)
} else {
store.get_objects(&ids[..])
}
}

/// Check all the objects used in the transaction against the database, and ensure
/// that they are all the correct version and number.
pub async fn check_locks(
#[instrument(level = "trace", skip_all)]
async fn check_locks<const A: bool, S>(
store: &SuiDataStore<A, S>,
transaction: &TransactionData,
input_objects: Vec<InputObjectKind>,
objects: Vec<Option<Object>>,
) -> Result<Vec<(InputObjectKind, Object)>, SuiError> {
gas_object: Object,
gas_status: &mut SuiGasStatus<'_>,
) -> Result<Vec<(InputObjectKind, Object)>, SuiError>
where
S: Eq + Serialize + for<'de> Deserialize<'de>,
{
let input_objects = transaction.input_objects()?;
// These IDs act as authenticators that can own other objects.
let objects = fetch_objects(store, &input_objects, Some(gas_object)).await?;

// Constructing the list of objects that could be used to authenticate other
// objects. Any mutable object (either shared or owned) can be used to
// authenticate other objects. Hence essentially we are building the list
Expand Down Expand Up @@ -89,6 +174,16 @@ pub async fn check_locks(
return Err(SuiError::LockErrors { errors });
}
fp_ensure!(!all_objects.is_empty(), SuiError::ObjectInputArityViolation);

// Charge gas for reading all objects from the DB.
// TODO: Some of the objects may be duplicate (for batch tx). We could save gas by
// fetching only unique objects.
let total_size = all_objects
.iter()
.map(|(_, obj)| obj.object_size_for_gas_metering())
.sum();
gas_status.charge_storage_read(total_size)?;

Ok(all_objects)
}

Expand Down
Loading

0 comments on commit b2d12b3

Please sign in to comment.