diff --git a/Cargo.lock b/Cargo.lock index 74723563578d4..d4341258ae74a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -849,7 +849,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "typed-store 0.1.0 (git+https://github.com/MystenLabs/mysten-infra?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", + "typed-store 0.1.0 (git+https://github.com/mystenlabs/mysten-infra.git?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", "types", ] @@ -1519,7 +1519,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "typed-store 0.1.0 (git+https://github.com/MystenLabs/mysten-infra?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", + "typed-store 0.1.0 (git+https://github.com/mystenlabs/mysten-infra.git?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", "types", "worker", ] @@ -3289,7 +3289,7 @@ dependencies = [ "tracing", "tracing-log", "tracing-subscriber 0.3.11", - "typed-store 0.1.0 (git+https://github.com/MystenLabs/mysten-infra?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", + "typed-store 0.1.0 (git+https://github.com/mystenlabs/mysten-infra.git?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", "types", "worker", ] @@ -3910,7 +3910,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "typed-store 0.1.0 (git+https://github.com/MystenLabs/mysten-infra?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", + "typed-store 0.1.0 (git+https://github.com/mystenlabs/mysten-infra.git?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", "types", ] @@ -5334,10 +5334,12 @@ dependencies = [ "config 0.1.0", "futures", "move-core-types", + "move-package", "rand 0.7.3", "rocksdb", "sui", "sui-adapter", + "sui-framework", "sui-network", "sui-types", "sui_core", @@ -5761,7 +5763,7 @@ checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" [[package]] name = "typed-store" version = "0.1.0" -source = "git+https://github.com/MystenLabs/mysten-infra?rev=808de09203d147b43d59114b8afd9e51cbcf5778#808de09203d147b43d59114b8afd9e51cbcf5778" +source = "git+https://github.com/mystenlabs/mysten-infra.git?rev=808de09203d147b43d59114b8afd9e51cbcf5778#808de09203d147b43d59114b8afd9e51cbcf5778" dependencies = [ "bincode", "collectable", @@ -5812,7 +5814,7 @@ dependencies = [ "thiserror", "tokio", "tokio-util", - "typed-store 0.1.0 (git+https://github.com/MystenLabs/mysten-infra?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", + "typed-store 0.1.0 (git+https://github.com/mystenlabs/mysten-infra.git?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", ] [[package]] @@ -6341,7 +6343,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", - "typed-store 0.1.0 (git+https://github.com/MystenLabs/mysten-infra?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", + "typed-store 0.1.0 (git+https://github.com/mystenlabs/mysten-infra.git?rev=808de09203d147b43d59114b8afd9e51cbcf5778)", "types", ] diff --git a/sui/tests/shared_objects_tests.rs b/sui/tests/shared_objects_tests.rs index b119abace8b8f..7a979c1cc80ce 100644 --- a/sui/tests/shared_objects_tests.rs +++ b/sui/tests/shared_objects_tests.rs @@ -4,21 +4,24 @@ use bytes::Bytes; use futures::{sink::SinkExt, stream::StreamExt}; use sui::config::AuthorityPrivateInfo; use sui_types::error::SuiError; -use sui_types::messages::ConsensusTransaction; -use sui_types::serialize::SerializedMessage; -use sui_types::serialize::{deserialize_message, serialize_consensus_transaction}; +use sui_types::messages::CallArg; +use sui_types::messages::Transaction; +use sui_types::messages::TransactionInfoResponse; +use sui_types::messages::{ConsensusTransaction, ExecutionStatus}; +use sui_types::serialize::{ + deserialize_message, deserialize_transaction_info, serialize_consensus_transaction, +}; +use sui_types::serialize::{serialize_cert, SerializedMessage}; use test_utils::authority::{spawn_test_authorities, test_authority_configs}; -use test_utils::messages::test_shared_object_certificates; +use test_utils::messages::{make_certificates, move_transaction, publish_move_package_transaction}; +use test_utils::messages::{parse_package_ref, test_shared_object_transactions}; use test_utils::objects::{test_gas_objects, test_shared_object}; use tokio::net::TcpStream; use tokio_util::codec::Framed; use tokio_util::codec::LengthDelimitedCodec; -/// Submits a transaction to a Sui authority. -async fn submit_transaction( - transaction: Bytes, - config: &AuthorityPrivateInfo, -) -> SerializedMessage { +/// Send bytes to a Sui authority. +async fn transmit(transaction: Bytes, config: &AuthorityPrivateInfo) -> SerializedMessage { let authority_address = format!("{}:{}", config.host, config.port); let stream = TcpStream::connect(authority_address).await.unwrap(); let mut connection = Framed::new(stream, LengthDelimitedCodec::new()); @@ -28,43 +31,157 @@ async fn submit_transaction( deserialize_message(&bytes[..]).unwrap() } -// TODO: Taking too long to run. Re-enable once it's fixed. -#[ignore] -#[tokio::test] -async fn shared_object_transaction() { - let mut objects = test_gas_objects(); - objects.push(test_shared_object()); +/// Submit a certificate containing only owned-objects to all authorities. +async fn submit_single_owner_transaction( + transaction: Transaction, + configs: &[AuthorityPrivateInfo], +) -> Vec { + let certificate = make_certificates(vec![transaction]).pop().unwrap(); + let serialized = Bytes::from(serialize_cert(&certificate)); - // Get the authority configs and spawn them. Note that it is important to not drop - // the handles (or the authorities will stop). - let configs = test_authority_configs(); - let _handles = spawn_test_authorities(objects, &configs).await; + let mut responses = Vec::new(); + for config in configs { + let bytes = transmit(serialized.clone(), config).await; + let reply = deserialize_transaction_info(bytes).unwrap(); + responses.push(reply); + } + responses +} - // Make a test shared object certificate. - let certificate = test_shared_object_certificates().await.pop().unwrap(); +// Keep submitting the certificate until it is sequenced by consensus. We use the loop +// since some consensus protocols (like Tusk) are not guaranteed to include the transaction +// (but it has high probability to do so). +// NOTE: This is good for testing but is not how a real client should submit transactions. +async fn submit_shared_object_transaction( + transaction: Transaction, + configs: &[AuthorityPrivateInfo], +) -> TransactionInfoResponse { + let certificate = make_certificates(vec![transaction]).pop().unwrap(); let message = ConsensusTransaction::UserTransaction(certificate); let serialized = Bytes::from(serialize_consensus_transaction(&message)); - // Keep submitting the certificate until it is sequenced by consensus. We use the loop - // since some consensus protocols (like Tusk) are not guaranteed to include the transaction - // (but it has high probability to do so). - tokio::task::yield_now().await; 'main: loop { - for config in &configs { - match submit_transaction(serialized.clone(), config).await { - SerializedMessage::TransactionResp(_) => { + for config in configs { + match transmit(serialized.clone(), config).await { + SerializedMessage::TransactionResp(reply) => { // We got a reply from the Sui authority. - break 'main; + break 'main *reply; } SerializedMessage::Error(error) => match *error { SuiError::ConsensusConnectionBroken(_) => { - // This is the (confusing) error message returned by the consensus adapter - // timed out and didn't hear back from consensus. + // This is the (confusing) error message returned by the consensus + // adapter timed out and didn't hear back from consensus. } - error => panic!("Unexpected error {error}"), + error => panic!("{error}"), }, message => panic!("Unexpected protocol message {message:?}"), } } } } + +#[tokio::test] +async fn shared_object_transaction() { + let mut objects = test_gas_objects(); + objects.push(test_shared_object()); + + // Get the authority configs and spawn them. Note that it is important to not drop + // the handles (or the authorities will stop). + let configs = test_authority_configs(); + let _handles = spawn_test_authorities(objects, &configs).await; + + // Make a test shared object certificate. + let transaction = test_shared_object_transactions().pop().unwrap(); + + // Submit the transaction. Note that this transaction is random and we do not expect + // it to be successfully executed by the Move execution engine. + tokio::task::yield_now().await; + let reply = submit_shared_object_transaction(transaction, &configs).await; + assert!(reply.signed_effects.is_some()); +} + +#[tokio::test] +async fn call_shared_object_contract() { + let mut gas_objects = test_gas_objects(); + + // Get the authority configs and spawn them. Note that it is important to not drop + // the handles (or the authorities will stop). + let configs = test_authority_configs(); + let _handles = spawn_test_authorities(gas_objects.clone(), &configs).await; + + // Publish the move package to all authorities and get the new pacakge ref. + tokio::task::yield_now().await; + let transaction = publish_move_package_transaction(gas_objects.pop().unwrap()); + let replies = submit_single_owner_transaction(transaction, &configs).await; + let mut package_refs = Vec::new(); + for reply in replies { + let effects = reply.signed_effects.unwrap().effects; + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); + package_refs.push(parse_package_ref(&effects).unwrap()); + } + let package_ref = package_refs.pop().unwrap(); + + // Make a transaction to create a counter. + tokio::task::yield_now().await; + let transaction = move_transaction( + gas_objects.pop().unwrap(), + "Counter", + "create", + package_ref, + /* arguments */ Vec::default(), + ); + let replies = submit_single_owner_transaction(transaction, &configs).await; + let mut counter_ids = Vec::new(); + for reply in replies { + let effects = reply.signed_effects.unwrap().effects; + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); + let ((shared_object_id, _, _), _) = effects.created[0]; + counter_ids.push(shared_object_id); + } + let counter_id = counter_ids.pop().unwrap(); + + // Ensure the value of the counter is `0`. + tokio::task::yield_now().await; + let transaction = move_transaction( + gas_objects.pop().unwrap(), + "Counter", + "assert_value", + package_ref, + vec![ + CallArg::SharedObject(counter_id), + CallArg::Pure(0u64.to_le_bytes().to_vec()), + ], + ); + let reply = submit_shared_object_transaction(transaction, &configs).await; + let effects = reply.signed_effects.unwrap().effects; + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); + + // Make a transaction to increment the counter. + tokio::task::yield_now().await; + let transaction = move_transaction( + gas_objects.pop().unwrap(), + "Counter", + "increment", + package_ref, + vec![CallArg::SharedObject(counter_id)], + ); + let reply = submit_shared_object_transaction(transaction, &configs).await; + let effects = reply.signed_effects.unwrap().effects; + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); + + // Ensure the value of the counter is `1`. + tokio::task::yield_now().await; + let transaction = move_transaction( + gas_objects.pop().unwrap(), + "Counter", + "assert_value", + package_ref, + vec![ + CallArg::SharedObject(counter_id), + CallArg::Pure(1u64.to_le_bytes().to_vec()), + ], + ); + let reply = submit_shared_object_transaction(transaction, &configs).await; + let effects = reply.signed_effects.unwrap().effects; + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); +} diff --git a/sui_core/src/authority/authority_store.rs b/sui_core/src/authority/authority_store.rs index 1c83b8a3aede7..b66d065bac40b 100644 --- a/sui_core/src/authority/authority_store.rs +++ b/sui_core/src/authority/authority_store.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use super::*; use crate::gateway_state::GatewayTxSeqNumber; - use narwhal_executor::ExecutionIndices; use rocksdb::Options; use serde::{Deserialize, Serialize}; @@ -12,6 +11,7 @@ use std::path::Path; use sui_types::base_types::SequenceNumber; use sui_types::batch::{SignedBatch, TxSequenceNumber}; use sui_types::crypto::{AuthoritySignInfo, EmptySignInfo}; +use sui_types::object::OBJECT_START_VERSION; use tracing::warn; use typed_store::rocks::{DBBatch, DBMap}; @@ -834,10 +834,13 @@ impl Deserialize<'de>> let (sequenced_to_write, schedule_to_write): (Vec<_>, Vec<_>) = ids .zip(versions.iter()) .map(|(id, v)| { - let version = v.unwrap_or_else(SequenceNumber::new); + // If it is the first time the shared object has been sequenced, assign it the default + // sequence number (`OBJECT_START_VERSION`). Otherwise use the `scheduled` map to + // to assign the next sequence number. + let version = v.unwrap_or_else(|| OBJECT_START_VERSION); let next_version = v .map(|v| v.increment()) - .unwrap_or_else(|| SequenceNumber::from(1)); + .unwrap_or_else(|| SequenceNumber::from(2)); let sequenced = ((transaction_digest, *id), version); let scheduled = (id, next_version); diff --git a/sui_core/src/unit_tests/authority_tests.rs b/sui_core/src/unit_tests/authority_tests.rs index 90f907d56f1f3..de753ec3167f8 100644 --- a/sui_core/src/unit_tests/authority_tests.rs +++ b/sui_core/src/unit_tests/authority_tests.rs @@ -1414,7 +1414,7 @@ async fn shared_object() { use sui_types::gas_coin::GasCoin; use sui_types::object::MoveObject; - let content = GasCoin::new(shared_object_id, SequenceNumber::new(), 10); + let content = GasCoin::new(shared_object_id, OBJECT_START_VERSION, 10); let obj = MoveObject::new(/* type */ GasCoin::type_(), content.to_bcs_bytes()); Object::new_move(obj, Owner::Shared, TransactionDigest::genesis()) }; @@ -1479,7 +1479,7 @@ async fn shared_object() { .sequenced(&transaction_digest, [shared_object_id].iter()) .unwrap()[0] .unwrap(); - assert_eq!(shared_object_version, SequenceNumber::new()); + assert_eq!(shared_object_version, OBJECT_START_VERSION); // Finally process the certificate and execute the contract. Ensure that the // shared object lock is cleaned up and that its sequence number increased. @@ -1500,5 +1500,5 @@ async fn shared_object() { .unwrap() .unwrap() .version(); - assert_eq!(shared_object_version, SequenceNumber::from(1)); + assert_eq!(shared_object_version, SequenceNumber::from(2)); } diff --git a/sui_programmability/adapter/src/adapter.rs b/sui_programmability/adapter/src/adapter.rs index e85231462e83f..03571f81fed7d 100644 --- a/sui_programmability/adapter/src/adapter.rs +++ b/sui_programmability/adapter/src/adapter.rs @@ -801,7 +801,7 @@ pub fn resolve_and_type_check( } fn is_primitive( - module: &CompiledModule, + _module: &CompiledModule, function_type_arguments: &[TypeTag], t: &SignatureToken, ) -> bool { @@ -812,7 +812,7 @@ fn is_primitive( | SignatureToken::U128 | SignatureToken::Address => true, - SignatureToken::Vector(inner) => is_primitive(module, function_type_arguments, inner), + SignatureToken::Vector(inner) => is_primitive(_module, function_type_arguments, inner), SignatureToken::TypeParameter(idx) => function_type_arguments .get(*idx as usize) diff --git a/sui_programmability/examples/basics/sources/Counter.move b/sui_programmability/examples/basics/sources/Counter.move index 7f23bedf3e596..48393b92c6c44 100644 --- a/sui_programmability/examples/basics/sources/Counter.move +++ b/sui_programmability/examples/basics/sources/Counter.move @@ -45,6 +45,11 @@ module Basics::Counter { assert!(counter.owner == TxContext::sender(ctx), 0); counter.value = value; } + + /// Assert a value for the counter. + public(script) fun assert_value(counter: &Counter, value: u64, _ctx: &mut TxContext) { + assert!(counter.value == value, 0) + } } #[test_only] diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index b16b106f8afe0..537c7bcaa353b 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -19,6 +19,8 @@ tempfile = "3.3.0" bcs = "0.1.3" sui-adapter = { path = "../sui_programmability/adapter" } +sui-framework = { path = "../sui_programmability/framework" } +move-package = { git = "https://github.com/move-language/move", rev = "6a80792ecbf16d74bf1d57e48a576377f0879646" } move-core-types = { git = "https://github.com/move-language/move", rev = "6a80792ecbf16d74bf1d57e48a576377f0879646", features = ["address20"] } typed-store = { git = "https://github.com/MystenLabs/mysten-infra", rev ="d2976a45420147ad821baae96e6fe4b12215f743"} narwhal-config = { git = "https://github.com/MystenLabs/narwhal", rev = "8ae2164f0510349cbac2770e50e853bce5ab0e02", package = "config" } diff --git a/test_utils/src/messages.rs b/test_utils/src/messages.rs index c3f3b17866f19..3b78f28eb662a 100644 --- a/test_utils/src/messages.rs +++ b/test_utils/src/messages.rs @@ -6,14 +6,19 @@ use crate::test_committee; use crate::test_keys; use move_core_types::account_address::AccountAddress; use move_core_types::ident_str; +use move_package::BuildConfig; +use std::path::PathBuf; use sui_adapter::genesis; use sui_types::base_types::ObjectRef; use sui_types::crypto::Signature; -use sui_types::messages::CallArg; +use sui_types::messages::{CallArg, TransactionEffects}; use sui_types::messages::{ CertifiedTransaction, SignatureAggregator, SignedTransaction, Transaction, TransactionData, }; -use sui_types::object::Object; +use sui_types::object::{Object, Owner}; + +/// The maximum gas per transaction. +pub const MAX_GAS: u64 = 10_000; /// Make a few different test transaction containing the same shared object. pub fn test_shared_object_transactions() -> Vec { @@ -59,7 +64,7 @@ pub fn test_shared_object_transactions() -> Vec { CallArg::Pure(16u64.to_le_bytes().to_vec()), CallArg::Pure(bcs::to_bytes(&AccountAddress::from(sender)).unwrap()), ], - /* max_gas */ 10_000, + MAX_GAS, ); let signature = Signature::new(&data, &keypair); transactions.push(Transaction::new(data, signature)); @@ -67,11 +72,60 @@ pub fn test_shared_object_transactions() -> Vec { transactions } -/// Make a test certificates for each shared-object transaction. -pub async fn test_shared_object_certificates() -> Vec { +/// Make a transaction to publish a test move contracts package. +pub fn publish_move_package_transaction(gas_object: Object) -> Transaction { + let build_config = BuildConfig::default(); + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("../sui_programmability/examples/basics"); + let modules = sui_framework::build_move_package(&path, build_config, false).unwrap(); + + let all_module_bytes = modules + .iter() + .map(|m| { + let mut module_bytes = Vec::new(); + m.serialize(&mut module_bytes).unwrap(); + module_bytes + }) + .collect(); + + let gas_object_ref = gas_object.compute_object_reference(); + let (sender, keypair) = test_keys().pop().unwrap(); + let data = TransactionData::new_module(sender, gas_object_ref, all_module_bytes, MAX_GAS); + let signature = Signature::new(&data, &keypair); + Transaction::new(data, signature) +} + +/// Make a transaction calling a specific move module & function. +pub fn move_transaction( + gas_object: Object, + module: &'static str, + function: &'static str, + package_ref: ObjectRef, + arguments: Vec, +) -> Transaction { + // The key pair of the sender of the transaction. + let (sender, keypair) = test_keys().pop().unwrap(); + + // Make the transaction. + let data = TransactionData::new_move_call( + sender, + package_ref, + ident_str!(module).to_owned(), + ident_str!(function).to_owned(), + /* type_args */ vec![], + gas_object.compute_object_reference(), + arguments, + MAX_GAS, + ); + let signature = Signature::new(&data, &keypair); + Transaction::new(data, signature) +} + +/// Make a test certificates for each input transaction. +pub fn make_certificates(transactions: Vec) -> Vec { let committee = test_committee(); let mut certificates = Vec::new(); - for tx in test_shared_object_transactions() { + for tx in transactions { let mut aggregator = SignatureAggregator::try_new(tx.clone(), &committee).unwrap(); for (_, key) in test_keys() { let vote = SignedTransaction::new( @@ -91,3 +145,13 @@ pub async fn test_shared_object_certificates() -> Vec { } certificates } + +/// Extract the package reference from a transaction effect. This is useful to deduce the +/// authority-created package reference after attempting to publish a new Move package. +pub fn parse_package_ref(effects: &TransactionEffects) -> Option { + effects + .created + .iter() + .find(|(_, owner)| matches!(owner, Owner::Immutable)) + .map(|(reference, _)| *reference) +} diff --git a/test_utils/src/objects.rs b/test_utils/src/objects.rs index 7812bc3754ed9..3b1b63478b3e2 100644 --- a/test_utils/src/objects.rs +++ b/test_utils/src/objects.rs @@ -1,14 +1,13 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 use crate::test_keys; -use sui_types::base_types::SequenceNumber; use sui_types::base_types::{ObjectID, TransactionDigest}; use sui_types::gas_coin::GasCoin; -use sui_types::object::{MoveObject, Object, Owner}; +use sui_types::object::{MoveObject, Object, Owner, OBJECT_START_VERSION}; /// Make a few test gas objects. pub fn test_gas_objects() -> Vec { - (0..4) + (0..9) .map(|i| { let seed = format!("0x555555555555555{i}"); let gas_object_id = ObjectID::from_hex_literal(&seed).unwrap(); @@ -22,7 +21,7 @@ pub fn test_gas_objects() -> Vec { pub fn test_shared_object() -> Object { let seed = "0x6666666666666660"; let shared_object_id = ObjectID::from_hex_literal(seed).unwrap(); - let content = GasCoin::new(shared_object_id, SequenceNumber::new(), 10); + let content = GasCoin::new(shared_object_id, OBJECT_START_VERSION, 10); let obj = MoveObject::new(/* type */ GasCoin::type_(), content.to_bcs_bytes()); Object::new_move(obj, Owner::Shared, TransactionDigest::genesis()) }