Skip to content

Commit

Permalink
Add RPC endpoint to call tx without commit (MystenLabs#4514)
Browse files Browse the repository at this point in the history
  • Loading branch information
gegaowp authored Sep 13, 2022
1 parent 5c4c30d commit c55adde
Show file tree
Hide file tree
Showing 8 changed files with 885 additions and 740 deletions.
30 changes: 29 additions & 1 deletion crates/sui-core/src/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ use std::{
use sui_adapter::adapter;
use sui_adapter::temporary_store::InnerTemporaryStore;
use sui_config::genesis::Genesis;
use sui_json_rpc_types::SuiEventEnvelope;
use sui_json_rpc_types::{SuiEventEnvelope, SuiTransactionEffects};
use sui_storage::{
event_store::{EventStore, EventStoreType, StoredEvent},
write_ahead_log::{DBTxGuard, TxGuard, WriteAheadLog},
Expand Down Expand Up @@ -792,6 +792,34 @@ impl AuthorityState {
Ok((inner_temp_store, signed_effects))
}

pub async fn dry_run_transaction(
&self,
transaction: &Transaction,
transaction_digest: TransactionDigest,
) -> Result<SuiTransactionEffects, anyhow::Error> {
transaction.verify()?;
let (gas_status, input_objects) =
transaction_input_checker::check_transaction_input(&self.database, transaction).await?;
let shared_object_refs = input_objects.filter_shared_objects();

let transaction_dependencies = input_objects.transaction_dependencies();
let temporary_store =
TemporaryStore::new(self.database.clone(), input_objects, transaction_digest);
let (_inner_temp_store, effects, _execution_error) =
execution_engine::execute_transaction_to_effects(
shared_object_refs,
temporary_store,
transaction.signed_data.data.clone(),
transaction_digest,
transaction_dependencies,
&self.move_vm,
&self._native_functions,
gas_status,
self.epoch(),
);
SuiTransactionEffects::try_from(effects, self.module_cache.as_ref())
}

pub async fn check_tx_already_executed(
&self,
digest: &TransactionDigest,
Expand Down
121 changes: 81 additions & 40 deletions crates/sui-core/src/unit_tests/authority_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,85 @@ fn compare_transaction_info_responses(o1: &TransactionInfoResponse, o2: &Transac
}
}

async fn construct_shared_object_transaction_with_sequence_number(
sequence_number: SequenceNumber,
) -> (AuthorityState, Transaction, ObjectID, ObjectID) {
let (sender, keypair): (_, AccountKeyPair) = get_key_pair();

// Initialize an authority with a (owned) gas object and a shared object.
let gas_object_id = ObjectID::random();
let gas_object = Object::with_id_owner_for_testing(gas_object_id, sender);
let gas_object_ref = gas_object.compute_object_reference();

let shared_object_id = ObjectID::random();
let shared_object = {
use sui_types::gas_coin::GasCoin;
use sui_types::object::MoveObject;

let content = GasCoin::new(shared_object_id, 10);
let obj = MoveObject::new_gas_coin(sequence_number, content.to_bcs_bytes());
Object::new_move(obj, Owner::Shared, TransactionDigest::genesis())
};
let authority = init_state_with_objects(vec![gas_object, shared_object]).await;

// Make a sample transaction.
let module = "object_basics";
let function = "create";
let package_object_ref = authority.get_framework_object_ref().await.unwrap();

let data = TransactionData::new_move_call(
sender,
package_object_ref,
ident_str!(module).to_owned(),
ident_str!(function).to_owned(),
/* type_args */ vec![],
gas_object_ref,
/* args */
vec![
CallArg::Object(ObjectArg::SharedObject(shared_object_id)),
CallArg::Pure(16u64.to_le_bytes().to_vec()),
CallArg::Pure(bcs::to_bytes(&AccountAddress::from(sender)).unwrap()),
],
MAX_GAS,
);
let signature = Signature::new(&data, &keypair);
(
authority,
Transaction::new(data, signature),
gas_object_id,
shared_object_id,
)
}

#[tokio::test]
async fn test_dry_run_transaction() {
let (authority, transaction, gas_object_id, shared_object_id) =
construct_shared_object_transaction_with_sequence_number(SequenceNumber::MIN).await;

let transaction_digest = *transaction.digest();

let response = authority
.dry_run_transaction(&transaction, transaction_digest)
.await;
assert!(response.is_ok());

// Make sure that objects are not mutated after dry run.
let gas_object_version = authority
.get_object(&gas_object_id)
.await
.unwrap()
.unwrap()
.version();
assert_eq!(gas_object_version, SequenceNumber::new());
let shared_object_version = authority
.get_object(&shared_object_id)
.await
.unwrap()
.unwrap()
.version();
assert_eq!(shared_object_version, SequenceNumber::MIN);
}

#[tokio::test]
async fn test_handle_transfer_transaction_bad_signature() {
let (sender, sender_key): (_, AccountKeyPair) = get_key_pair();
Expand Down Expand Up @@ -213,46 +292,8 @@ async fn test_handle_transfer_transaction_with_max_sequence_number() {

#[tokio::test]
async fn test_handle_shared_object_with_max_sequence_number() {
let (sender, keypair): (_, AccountKeyPair) = get_key_pair();

// Initialize an authority with a (owned) gas object and a shared object.
let gas_object_id = ObjectID::random();
let gas_object = Object::with_id_owner_for_testing(gas_object_id, sender);
let gas_object_ref = gas_object.compute_object_reference();

let shared_object_id = ObjectID::random();
let shared_object = {
use sui_types::gas_coin::GasCoin;
use sui_types::object::MoveObject;

let content = GasCoin::new(shared_object_id, 10);
let obj = MoveObject::new_gas_coin(SequenceNumber::MAX, content.to_bcs_bytes());
Object::new_move(obj, Owner::Shared, TransactionDigest::genesis())
};
let authority = init_state_with_objects(vec![gas_object, shared_object]).await;

// Make a sample transaction.
let module = "object_basics";
let function = "create";
let package_object_ref = authority.get_framework_object_ref().await.unwrap();

let data = TransactionData::new_move_call(
sender,
package_object_ref,
ident_str!(module).to_owned(),
ident_str!(function).to_owned(),
/* type_args */ vec![],
gas_object_ref,
/* args */
vec![
CallArg::Object(ObjectArg::SharedObject(shared_object_id)),
CallArg::Pure(16u64.to_le_bytes().to_vec()),
CallArg::Pure(bcs::to_bytes(&AccountAddress::from(sender)).unwrap()),
],
MAX_GAS,
);
let signature = Signature::new(&data, &keypair);
let transaction = Transaction::new(data, signature);
let (authority, transaction, _, _) =
construct_shared_object_transaction_with_sequence_number(SequenceNumber::MAX).await;
// Submit the transaction and assemble a certificate.
let response = authority.handle_transaction(transaction.clone()).await;
assert!(response.is_err());
Expand Down
13 changes: 11 additions & 2 deletions crates/sui-json-rpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use sui_json_rpc_types::{
GatewayTxSeqNumber, GetObjectDataResponse, GetPastObjectDataResponse, GetRawObjectDataResponse,
MoveFunctionArgType, RPCTransactionRequestParams, SuiEventEnvelope, SuiEventFilter,
SuiExecuteTransactionResponse, SuiGasCostSummary, SuiMoveNormalizedFunction,
SuiMoveNormalizedModule, SuiMoveNormalizedStruct, SuiObjectInfo, SuiTransactionFilter,
SuiTransactionResponse, SuiTypeTag, TransactionBytes,
SuiMoveNormalizedModule, SuiMoveNormalizedStruct, SuiObjectInfo, SuiTransactionEffects,
SuiTransactionFilter, SuiTransactionResponse, SuiTypeTag, TransactionBytes,
};
use sui_open_rpc_macros::open_rpc;
use sui_types::base_types::{ObjectID, SequenceNumber, SuiAddress, TransactionDigest};
Expand Down Expand Up @@ -117,6 +117,15 @@ pub trait RpcReadApi {
#[open_rpc(namespace = "sui", tag = "Full Node API")]
#[rpc(server, client, namespace = "sui")]
pub trait RpcFullNodeReadApi {
#[method(name = "dryRunTransaction")]
async fn dry_run_transaction(
&self,
tx_bytes: Base64,
sig_scheme: SignatureScheme,
signature: Base64,
pub_key: Base64,
) -> RpcResult<SuiTransactionEffects>;

/// Return the argument types of a Move function,
/// based on normalized Type.
#[method(name = "getMoveFunctionArgTypes")]
Expand Down
22 changes: 22 additions & 0 deletions crates/sui-json-rpc/src/read_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use jsonrpsee::core::RpcResult;
use jsonrpsee_core::server::rpc_module::RpcModule;
use move_binary_format::normalized::{Module as NormalizedModule, Type};
use move_core_types::identifier::Identifier;
use signature::Signature;
use std::collections::BTreeMap;
use std::sync::Arc;
use sui_core::authority::AuthorityState;
Expand All @@ -22,8 +23,11 @@ use sui_json_rpc_types::{
use sui_open_rpc::Module;
use sui_types::base_types::SequenceNumber;
use sui_types::base_types::{ObjectID, SuiAddress, TransactionDigest};
use sui_types::crypto::{SignableBytes, SignatureScheme};
use sui_types::messages::{Transaction, TransactionData};
use sui_types::move_package::normalize_modules;
use sui_types::object::{Data, ObjectRead, Owner};
use sui_types::sui_serde::Base64;

// An implementation of the read portion of the Gateway JSON-RPC interface intended for use in
// Fullnodes.
Expand Down Expand Up @@ -129,6 +133,24 @@ impl SuiRpcModule for ReadApi {

#[async_trait]
impl RpcFullNodeReadApiServer for FullNodeApi {
async fn dry_run_transaction(
&self,
tx_bytes: Base64,
sig_scheme: SignatureScheme,
signature: Base64,
pub_key: Base64,
) -> RpcResult<SuiTransactionEffects> {
let data = TransactionData::from_signable_bytes(&tx_bytes.to_vec()?)?;
let flag = vec![sig_scheme.flag()];
let signature =
Signature::from_bytes(&[&*flag, &*signature.to_vec()?, &pub_key.to_vec()?].concat())
.map_err(|e| anyhow!(e))?;
let txn = Transaction::new(data, signature);
let txn_digest = *txn.digest();

Ok(self.state.dry_run_transaction(&txn, txn_digest).await?)
}

async fn get_normalized_move_modules_by_package(
&self,
package: ObjectID,
Expand Down
46 changes: 23 additions & 23 deletions crates/sui-open-rpc/samples/objects.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
"fields": {
"description": "An NFT created by the Sui Command Line Tool",
"id": {
"id": "0xe55c184478d987f20d1f1d28dab88c1f915c3dcb"
"id": "0x295c8f098a7be0d00d5d8392276013c11ee9fae3"
},
"name": "Example NFT",
"url": "ipfs://bafkreibngqhl3gaa7daob4i2vccziay2jjlp435cf66vhono7nrvww53ty"
}
},
"owner": {
"AddressOwner": "0x5b134916a82cf98ee682d4bf0a9ec8fa30f25f4f"
"AddressOwner": "0x0fcbee87e98de1e49ef1f7b6d49b21106b14f221"
},
"previousTransaction": "9IlGkZ6HfvTvkdT78gZrjyuZEHOvj5dSxR/eiZz8hJQ=",
"previousTransaction": "x/F470Cwigc+c1qLfv3CbU/6OIjJu2RyOTbJ6MM1eFI=",
"storageRebate": 25,
"reference": {
"objectId": "0xe55c184478d987f20d1f1d28dab88c1f915c3dcb",
"objectId": "0x295c8f098a7be0d00d5d8392276013c11ee9fae3",
"version": 1,
"digest": "OkEXoj0CvpnwaB+IuMQHa1qY8+y0EjMCtWhPDbTr2hM="
"digest": "8NfGrY2Hv5sVF8kSaTusaDu+AnOAvmM2zb12zF31fkM="
}
}
},
Expand All @@ -37,19 +37,19 @@
"fields": {
"balance": 100000000,
"id": {
"id": "0x011b1ef0df4c3e2c3d2f6710f9ae024d973e8d74"
"id": "0x072709ca4bcd3c4dc539ddc74bba48bbdc4e02c2"
}
}
},
"owner": {
"AddressOwner": "0x5b134916a82cf98ee682d4bf0a9ec8fa30f25f4f"
"AddressOwner": "0x0fcbee87e98de1e49ef1f7b6d49b21106b14f221"
},
"previousTransaction": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"storageRebate": 0,
"reference": {
"objectId": "0x011b1ef0df4c3e2c3d2f6710f9ae024d973e8d74",
"objectId": "0x072709ca4bcd3c4dc539ddc74bba48bbdc4e02c2",
"version": 0,
"digest": "2j+uhveHx9zUP2LGn2p3L8AUodlKZcIiY4d7a4J0Uhc="
"digest": "JbC1lGGopUt/nhCLSdVKj4g94F0Q7YpmUFtP/DDeXgU="
}
}
},
Expand All @@ -59,16 +59,16 @@
"data": {
"dataType": "package",
"disassembled": {
"m1": "// Move bytecode v5\nmodule 1d5a2d02d4640974c5c4fbcdb837efae87951ed3.m1 {\nstruct Forge has store, key {\n\tid: UID,\n\tswords_created: u64\n}\nstruct Sword has store, key {\n\tid: UID,\n\tmagic: u64,\n\tstrength: u64\n}\n\ninit(Arg0: &mut TxContext) {\nB0:\n\t0: CopyLoc[0](Arg0: &mut TxContext)\n\t1: Call[6](new(&mut TxContext): UID)\n\t2: LdU64(0)\n\t3: Pack[0](Forge)\n\t4: StLoc[1](loc0: Forge)\n\t5: MoveLoc[1](loc0: Forge)\n\t6: MoveLoc[0](Arg0: &mut TxContext)\n\t7: FreezeRef\n\t8: Call[7](sender(&TxContext): address)\n\t9: Call[0](transfer<Forge>(Forge, address))\n\t10: Ret\n}\npublic magic(Arg0: &Sword): u64 {\nB0:\n\t0: MoveLoc[0](Arg0: &Sword)\n\t1: ImmBorrowField[0](Sword.magic: u64)\n\t2: ReadRef\n\t3: Ret\n}\npublic strength(Arg0: &Sword): u64 {\nB0:\n\t0: MoveLoc[0](Arg0: &Sword)\n\t1: ImmBorrowField[1](Sword.strength: u64)\n\t2: ReadRef\n\t3: Ret\n}\nentry public sword_create(Arg0: &mut Forge, Arg1: u64, Arg2: u64, Arg3: address, Arg4: &mut TxContext) {\nB0:\n\t0: MoveLoc[4](Arg4: &mut TxContext)\n\t1: Call[6](new(&mut TxContext): UID)\n\t2: MoveLoc[1](Arg1: u64)\n\t3: MoveLoc[2](Arg2: u64)\n\t4: Pack[1](Sword)\n\t5: StLoc[5](loc0: Sword)\n\t6: MoveLoc[5](loc0: Sword)\n\t7: MoveLoc[3](Arg3: address)\n\t8: Call[1](transfer<Sword>(Sword, address))\n\t9: CopyLoc[0](Arg0: &mut Forge)\n\t10: ImmBorrowField[2](Forge.swords_created: u64)\n\t11: ReadRef\n\t12: LdU64(1)\n\t13: Add\n\t14: MoveLoc[0](Arg0: &mut Forge)\n\t15: MutBorrowField[2](Forge.swords_created: u64)\n\t16: WriteRef\n\t17: Ret\n}\nentry public sword_transfer(Arg0: Sword, Arg1: address) {\nB0:\n\t0: MoveLoc[0](Arg0: Sword)\n\t1: MoveLoc[1](Arg1: address)\n\t2: Call[1](transfer<Sword>(Sword, address))\n\t3: Ret\n}\npublic swords_created(Arg0: &Forge): u64 {\nB0:\n\t0: MoveLoc[0](Arg0: &Forge)\n\t1: ImmBorrowField[2](Forge.swords_created: u64)\n\t2: ReadRef\n\t3: Ret\n}\n}"
"my_module": "// Move bytecode v5\nmodule ed5320d61269e518f809f050e259a47ef906c1cb.my_module {\nstruct Forge has key {\n\tid: UID,\n\tswords_created: u64\n}\nstruct Sword has store, key {\n\tid: UID,\n\tmagic: u64,\n\tstrength: u64\n}\n\ninit(Arg0: &mut TxContext) {\nB0:\n\t0: CopyLoc[0](Arg0: &mut TxContext)\n\t1: Call[5](new(&mut TxContext): UID)\n\t2: LdU64(0)\n\t3: Pack[0](Forge)\n\t4: StLoc[1](loc0: Forge)\n\t5: MoveLoc[1](loc0: Forge)\n\t6: MoveLoc[0](Arg0: &mut TxContext)\n\t7: FreezeRef\n\t8: Call[6](sender(&TxContext): address)\n\t9: Call[0](transfer<Forge>(Forge, address))\n\t10: Ret\n}\npublic magic(Arg0: &Sword): u64 {\nB0:\n\t0: MoveLoc[0](Arg0: &Sword)\n\t1: ImmBorrowField[0](Sword.magic: u64)\n\t2: ReadRef\n\t3: Ret\n}\npublic strength(Arg0: &Sword): u64 {\nB0:\n\t0: MoveLoc[0](Arg0: &Sword)\n\t1: ImmBorrowField[1](Sword.strength: u64)\n\t2: ReadRef\n\t3: Ret\n}\nentry public sword_create(Arg0: &mut Forge, Arg1: u64, Arg2: u64, Arg3: address, Arg4: &mut TxContext) {\nB0:\n\t0: MoveLoc[4](Arg4: &mut TxContext)\n\t1: Call[5](new(&mut TxContext): UID)\n\t2: MoveLoc[1](Arg1: u64)\n\t3: MoveLoc[2](Arg2: u64)\n\t4: Pack[1](Sword)\n\t5: StLoc[5](loc0: Sword)\n\t6: MoveLoc[5](loc0: Sword)\n\t7: MoveLoc[3](Arg3: address)\n\t8: Call[1](transfer<Sword>(Sword, address))\n\t9: CopyLoc[0](Arg0: &mut Forge)\n\t10: ImmBorrowField[2](Forge.swords_created: u64)\n\t11: ReadRef\n\t12: LdU64(1)\n\t13: Add\n\t14: MoveLoc[0](Arg0: &mut Forge)\n\t15: MutBorrowField[2](Forge.swords_created: u64)\n\t16: WriteRef\n\t17: Ret\n}\npublic swords_created(Arg0: &Forge): u64 {\nB0:\n\t0: MoveLoc[0](Arg0: &Forge)\n\t1: ImmBorrowField[2](Forge.swords_created: u64)\n\t2: ReadRef\n\t3: Ret\n}\n}"
}
},
"owner": "Immutable",
"previousTransaction": "9AfwcAlpAjhj0UquPXb6VIC+GPObT+OWyiw4B2lVUIs=",
"previousTransaction": "fVcK3PEQFNcVly2Nnz+E6Gs4lNNQ2ETBWU9lrQom+Vk=",
"storageRebate": 0,
"reference": {
"objectId": "0x1d5a2d02d4640974c5c4fbcdb837efae87951ed3",
"objectId": "0xed5320d61269e518f809f050e259a47ef906c1cb",
"version": 1,
"digest": "2XXcvzFPdnXVSst4kKUWFkLltvXxS3paG/jW0jZijcs="
"digest": "rgq6fesi7EWzPvVWHUcSx/K9FzLrxoATl0P6iwhXKjE="
}
}
},
Expand All @@ -77,21 +77,21 @@
"details": {
"data": {
"dataType": "moveObject",
"type": "0xaecb5cab6b9ecfadb7306f011820950e3abcec6a::hero::Hero",
"type": "0x177bf159477e823c16b34533ef7e77454653d20d::hero::Hero",
"has_public_transfer": true,
"fields": {
"experience": 0,
"game_id": "0xf15819e721de7916c4c6ce26452bec75e41b2c57",
"game_id": "0xda127000c34d76c4d16af6e70308806438274d81",
"hp": 100,
"id": {
"id": "0xb70edef88ed2802e404ef9edba0f5d01731d589c"
"id": "0x8b57dbe7e3843f86c9d82dc1b01a483f9ead1a85"
},
"sword": {
"type": "0xaecb5cab6b9ecfadb7306f011820950e3abcec6a::hero::Sword",
"type": "0x177bf159477e823c16b34533ef7e77454653d20d::hero::Sword",
"fields": {
"game_id": "0xf15819e721de7916c4c6ce26452bec75e41b2c57",
"game_id": "0xda127000c34d76c4d16af6e70308806438274d81",
"id": {
"id": "0x070930dd4d88992acea68bfc41c0ac8e4cffb93a"
"id": "0xcdb92717ccac9aaf4a66b465c72c91f3b47897bc"
},
"magic": 10,
"strength": 1
Expand All @@ -100,14 +100,14 @@
}
},
"owner": {
"AddressOwner": "0x5b134916a82cf98ee682d4bf0a9ec8fa30f25f4f"
"AddressOwner": "0x0fcbee87e98de1e49ef1f7b6d49b21106b14f221"
},
"previousTransaction": "go/9qf497GracECE+SzyVpzM+BxZGMWrZ0O75/yxt4k=",
"previousTransaction": "w9/40vdXNJjxXB9xGkvSX0FBKwAMwkLUxhgEzSXszi4=",
"storageRebate": 21,
"reference": {
"objectId": "0xb70edef88ed2802e404ef9edba0f5d01731d589c",
"objectId": "0x8b57dbe7e3843f86c9d82dc1b01a483f9ead1a85",
"version": 1,
"digest": "3tOglj1ubshz1Xf6ElWsDdCQ6dfcadKfSAbQ6is1lwA="
"digest": "7UTmt+rNLOPnwpAH8CsiKhN8WNjT3670c+J3lEcaUQ0="
}
}
}
Expand Down
Loading

0 comments on commit c55adde

Please sign in to comment.