Skip to content

Commit

Permalink
Add batch transaction support to Gateway (MystenLabs#2555)
Browse files Browse the repository at this point in the history
* Add batch_transaction to gateway

* Address comments

* regen json rpc
  • Loading branch information
lxfind authored Jun 16, 2022
1 parent 655252e commit 87ea17d
Show file tree
Hide file tree
Showing 11 changed files with 1,072 additions and 787 deletions.
254 changes: 177 additions & 77 deletions crates/sui-core/src/gateway_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use async_trait::async_trait;
use futures::future;
use move_bytecode_utils::module_cache::SyncModuleCache;
use move_core_types::identifier::Identifier;
use move_core_types::language_storage::TypeTag;
use once_cell::sync::Lazy;
use prometheus_exporter::prometheus::{
register_histogram, register_int_counter, Histogram, IntCounter,
Expand Down Expand Up @@ -44,9 +43,10 @@ use crate::{
};
use sui_json::{resolve_move_function_args, SuiJsonCallArg, SuiJsonValue};
use sui_json_rpc_api::rpc_types::{
GetObjectDataResponse, GetRawObjectDataResponse, MergeCoinResponse, PublishResponse,
SplitCoinResponse, SuiMoveObject, SuiObject, SuiObjectInfo, SuiTransactionEffects,
TransactionEffectsResponse, TransactionResponse,
GetObjectDataResponse, GetRawObjectDataResponse, MergeCoinResponse, MoveCallParams,
PublishResponse, RPCTransactionRequestParams, SplitCoinResponse, SuiMoveObject, SuiObject,
SuiObjectInfo, SuiTransactionEffects, SuiTypeTag, TransactionEffectsResponse,
TransactionResponse, TransferCoinParams,
};

use crate::transaction_input_checker::InputObjects;
Expand Down Expand Up @@ -274,7 +274,7 @@ pub trait GatewayAPI {
package_object_id: ObjectID,
module: String,
function: String,
type_arguments: Vec<TypeTag>,
type_arguments: Vec<SuiTypeTag>,
arguments: Vec<SuiJsonValue>,
gas: Option<ObjectID>,
gas_budget: u64,
Expand Down Expand Up @@ -321,6 +321,17 @@ pub trait GatewayAPI {
gas_budget: u64,
) -> Result<TransactionData, anyhow::Error>;

/// Create a Batch Transaction that contains a vector of parameters needed to construct
/// all the single transactions in it.
/// Supported single transactions are TransferCoin and MoveCall.
async fn batch_transaction(
&self,
signer: SuiAddress,
single_transaction_params: Vec<RPCTransactionRequestParams>,
gas: Option<ObjectID>,
gas_budget: u64,
) -> Result<TransactionData, anyhow::Error>;

/// Get the object data
async fn get_object(&self, object_id: ObjectID)
-> Result<GetObjectDataResponse, anyhow::Error>;
Expand Down Expand Up @@ -794,17 +805,16 @@ where
address: SuiAddress,
budget: u64,
gas: Option<ObjectID>,
used_coins: Vec<ObjectID>,
used_object_ids: BTreeSet<ObjectID>,
) -> Result<ObjectRef, anyhow::Error> {
if let Some(id) = gas {
Ok(self
.get_object_internal(&id)
.await?
.compute_object_reference())
} else {
let used_coins = used_coins.into_iter().collect::<BTreeSet<_>>();
for (id, balance) in self.get_owned_coins(address).await.unwrap() {
if balance >= budget && !used_coins.contains(&id.0) {
if balance >= budget && !used_object_ids.contains(&id.0) {
return Ok(id);
}
}
Expand All @@ -830,6 +840,93 @@ where
Ok(coins)
}

async fn create_transfer_coin_transaction_kind(
&self,
params: TransferCoinParams,
used_object_ids: &mut BTreeSet<ObjectID>,
) -> Result<SingleTransactionKind, anyhow::Error> {
used_object_ids.insert(params.object_id);
let object = self.get_object_internal(&params.object_id).await?;
let object_ref = object.compute_object_reference();
Ok(SingleTransactionKind::TransferCoin(TransferCoin {
recipient: params.recipient,
object_ref,
}))
}

async fn create_move_call_transaction_kind(
&self,
params: MoveCallParams,
used_object_ids: &mut BTreeSet<ObjectID>,
) -> Result<SingleTransactionKind, anyhow::Error> {
let MoveCallParams {
module,
function,
package_object_id,
type_arguments,
arguments,
} = params;
let module = Identifier::new(module)?;
let function = Identifier::new(function)?;
let package_obj = self.get_object_internal(&package_object_id).await?;
let package_obj_ref = package_obj.compute_object_reference();
let json_args = resolve_move_function_args(
package_obj.data.try_as_package().unwrap(),
module.clone(),
function.clone(),
arguments,
)?;

// Fetch all the objects needed for this call
let mut objects = BTreeMap::new();
let mut args = Vec::with_capacity(json_args.len());

for json_arg in json_args {
args.push(match json_arg {
SuiJsonCallArg::Object(id) => {
let obj = self.get_object_internal(&id).await?;
let arg = if obj.is_shared() {
CallArg::SharedObject(id)
} else {
CallArg::ImmOrOwnedObject(obj.compute_object_reference())
};
objects.insert(id, obj);
arg
}
SuiJsonCallArg::Pure(bytes) => CallArg::Pure(bytes),
})
}

// Pass in the objects for a deeper check
let is_genesis = false;
let type_arguments = type_arguments
.into_iter()
.map(|arg| arg.try_into())
.collect::<Result<Vec<_>, _>>()?;
let compiled_module = package_obj
.data
.try_as_package()
.ok_or_else(|| anyhow!("Cannot get package from object"))?
.deserialize_module(&module)?;
resolve_and_type_check(
&objects,
&compiled_module,
&function,
&type_arguments,
args.clone(),
is_genesis,
)?;
used_object_ids.extend(objects.keys());

Ok(SingleTransactionKind::Call(MoveCall {
package: package_obj_ref,
module,
function,
type_arguments,
arguments: args,
}))
}

#[cfg(test)]
pub fn highest_known_version(&self, object_id: &ObjectID) -> Result<SequenceNumber, SuiError> {
self.latest_object_ref(object_id)
Expand Down Expand Up @@ -950,14 +1047,19 @@ where
gas_budget: u64,
recipient: SuiAddress,
) -> Result<TransactionData, anyhow::Error> {
let mut used_object_ids = BTreeSet::new();
let params = TransferCoinParams {
recipient,
object_id,
};
let kind = TransactionKind::Single(
self.create_transfer_coin_transaction_kind(params, &mut used_object_ids)
.await?,
);
let gas_payment = self
.choose_gas_for_address(signer, gas_budget, gas, vec![object_id])
.choose_gas_for_address(signer, gas_budget, gas, used_object_ids)
.await?;
let object = self.get_object_internal(&object_id).await?;
let object_ref = object.compute_object_reference();
let data =
TransactionData::new_transfer(recipient, object_ref, signer, gas_payment, gas_budget);
Ok(data)
Ok(TransactionData::new(kind, signer, gas_payment, gas_budget))
}

async fn transfer_sui(
Expand All @@ -975,6 +1077,46 @@ where
Ok(data)
}

async fn batch_transaction(
&self,
signer: SuiAddress,
single_transaction_params: Vec<RPCTransactionRequestParams>,
gas: Option<ObjectID>,
gas_budget: u64,
) -> Result<TransactionData, anyhow::Error> {
fp_ensure!(
!single_transaction_params.is_empty(),
SuiError::InvalidBatchTransaction {
error: "Batch Transaction cannot be empty".to_owned(),
}
.into()
);
let mut all_tx_kind = vec![];
let mut used_object_ids = BTreeSet::new();
for param in single_transaction_params {
let kind = match param {
RPCTransactionRequestParams::TransferCoinRequestParams(t) => {
self.create_transfer_coin_transaction_kind(t, &mut used_object_ids)
.await?
}
RPCTransactionRequestParams::MoveCallRequestParams(m) => {
self.create_move_call_transaction_kind(m, &mut used_object_ids)
.await?
}
};
all_tx_kind.push(kind);
}
let gas = self
.choose_gas_for_address(signer, gas_budget, gas, used_object_ids)
.await?;
Ok(TransactionData::new(
TransactionKind::Batch(all_tx_kind),
signer,
gas,
gas_budget,
))
}

// TODO: Get rid of the sync API.
// https://github.com/MystenLabs/sui/issues/1045
async fn sync_account_state(&self, account_addr: SuiAddress) -> Result<(), anyhow::Error> {
Expand Down Expand Up @@ -1005,74 +1147,27 @@ where
package_object_id: ObjectID,
module: String,
function: String,
type_arguments: Vec<TypeTag>,
type_arguments: Vec<SuiTypeTag>,
arguments: Vec<SuiJsonValue>,
gas: Option<ObjectID>,
gas_budget: u64,
) -> Result<TransactionData, anyhow::Error> {
let module = Identifier::new(module)?;
let function = Identifier::new(function)?;
let package_obj = self.get_object_internal(&package_object_id).await?;
let package_obj_ref = package_obj.compute_object_reference();
let json_args = resolve_move_function_args(
package_obj.data.try_as_package().unwrap(),
module.clone(),
function.clone(),
arguments,
)?;

// Fetch all the objects needed for this call
let mut objects = BTreeMap::new();
let mut args = Vec::with_capacity(json_args.len());

for json_arg in json_args {
args.push(match json_arg {
SuiJsonCallArg::Object(id) => {
let obj = self.get_object_internal(&id).await?;
let arg = if obj.is_shared() {
CallArg::SharedObject(id)
} else {
CallArg::ImmOrOwnedObject(obj.compute_object_reference())
};
objects.insert(id, obj);
arg
}
SuiJsonCallArg::Pure(bytes) => CallArg::Pure(bytes),
})
}

let forbidden_gas_objects = objects.keys().copied().collect();
let gas = self
.choose_gas_for_address(signer, gas_budget, gas, forbidden_gas_objects)
.await?;

// Pass in the objects for a deeper check
let is_genesis = false;
let compiled_module = package_obj
.data
.try_as_package()
.ok_or_else(|| anyhow!("Cannot get package from object"))?
.deserialize_module(&module)?;
resolve_and_type_check(
&objects,
&compiled_module,
&function,
&type_arguments,
args.clone(),
is_genesis,
)?;

let data = TransactionData::new_move_call(
signer,
package_obj_ref,
let params = MoveCallParams {
package_object_id,
module,
function,
type_arguments,
gas,
args,
gas_budget,
arguments,
};
let mut used_object_ids = BTreeSet::new();
let kind = TransactionKind::Single(
self.create_move_call_transaction_kind(params, &mut used_object_ids)
.await?,
);

let gas = self
.choose_gas_for_address(signer, gas_budget, gas, used_object_ids)
.await?;
let data = TransactionData::new(kind, signer, gas, gas_budget);
debug!(?data, "Created Move Call transaction data");
Ok(data)
}
Expand All @@ -1085,7 +1180,7 @@ where
gas_budget: u64,
) -> Result<TransactionData, anyhow::Error> {
let gas = self
.choose_gas_for_address(signer, gas_budget, gas, vec![])
.choose_gas_for_address(signer, gas_budget, gas, BTreeSet::new())
.await?;
let data = TransactionData::new_module(signer, gas, package_bytes, gas_budget);
Ok(data)
Expand All @@ -1100,7 +1195,7 @@ where
gas_budget: u64,
) -> Result<TransactionData, anyhow::Error> {
let gas = self
.choose_gas_for_address(signer, gas_budget, gas, vec![coin_object_id])
.choose_gas_for_address(signer, gas_budget, gas, BTreeSet::from([coin_object_id]))
.await?;
let coin_object = self.get_object_internal(&coin_object_id).await?;
let coin_object_ref = coin_object.compute_object_reference();
Expand Down Expand Up @@ -1131,7 +1226,12 @@ where
gas_budget: u64,
) -> Result<TransactionData, anyhow::Error> {
let gas = self
.choose_gas_for_address(signer, gas_budget, gas, vec![coin_to_merge, primary_coin])
.choose_gas_for_address(
signer,
gas_budget,
gas,
BTreeSet::from([coin_to_merge, primary_coin]),
)
.await?;
let primary_coin_ref = self.get_object_ref(&primary_coin).await?;
let coin_to_merge = self.get_object_internal(&coin_to_merge).await?;
Expand Down
Loading

0 comments on commit 87ea17d

Please sign in to comment.