Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RPC endpoint to call tx without commit #4514

Merged
merged 1 commit into from
Sep 13, 2022
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
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