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

Do not re-sequence replayed transactions #1947

Merged
merged 11 commits into from
May 16, 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
43 changes: 41 additions & 2 deletions sui/tests/shared_objects_tests.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::sync::Arc;

use sui_config::ValidatorInfo;
use sui_core::{
authority_client::{AuthorityAPI, NetworkAuthorityClient},
gateway_state::{GatewayAPI, GatewayState},
};
use sui_types::object::OBJECT_START_VERSION;
use sui_types::{
base_types::ObjectRef,
error::{SuiError, SuiResult},
Expand Down Expand Up @@ -442,6 +441,46 @@ async fn shared_object_sync() {
}
}

/// Send a simple shared object transaction to Sui and ensures the client gets back a response.
#[tokio::test]
async fn replay_shared_object_transaction() {
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 package ref.
tokio::task::yield_now().await;
let package_ref =
publish_counter_package(gas_objects.pop().unwrap(), configs.validator_set()).await;

// Send a transaction to create a counter (only to one authority) -- twice.
tokio::task::yield_now().await;
let create_counter_transaction = move_transaction(
gas_objects.pop().unwrap(),
"Counter",
"create",
package_ref,
/* arguments */ Vec::default(),
);
for _ in 0..2 {
let mut replies = submit_single_owner_transaction(
create_counter_transaction.clone(),
&configs.validator_set()[0..1],
)
.await;
let reply = replies.pop().unwrap();
let effects = reply.signed_effects.unwrap().effects;
assert!(matches!(effects.status, ExecutionStatus::Success { .. }));

// Ensure the sequence number of the shared object did not change.
let ((_, seq, _), _) = effects.created[0];
assert_eq!(seq, OBJECT_START_VERSION);
}
}

#[tokio::test]
async fn shared_object_on_gateway() {
let mut gas_objects = test_gas_objects();
Expand Down
39 changes: 39 additions & 0 deletions sui_core/src/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,45 @@ impl AuthorityState {
.persist_certificate_and_lock_shared_objects(certificate, last_consensus_index)
}

/// Check if we need to submit this transaction to consensus. We usually do, unless (i) we already
/// processed the transaction and we can immediately return the effects, or (ii) we already locked
/// all shared-objects of the transaction and can (re-)attempt execution.
pub async fn try_skip_consensus(
&self,
certificate: CertifiedTransaction,
) -> Result<Option<TransactionInfoResponse>, SuiError> {
// Ensure it is a shared object certificate
fp_ensure!(
certificate.contains_shared_object(),
SuiError::NotASharedObjectTransaction
);

// If we already executed this transaction, return the sign effects.
let digest = certificate.digest();
if self._database.effects_exists(digest)? {
debug!("Shared-object transaction {digest:?} already executed");
return self.make_transaction_info(digest).await.map(Some);
}

// If we already assigned locks to this transaction, we can try to execute it immediately.
// This can happen to transaction previously submitted to consensus that failed execution
// due to missing dependencies.
match self
._database
.sequenced(digest, certificate.shared_input_objects())?[0]
{
Some(_) => {
// Attempt to execute the transaction. This will only succeed if the authority
// already executed all its dependencies.
let confirmation_transaction = ConfirmationTransaction { certificate };
self.handle_confirmation_transaction(confirmation_transaction.clone())
.await
.map(Some)
}
None => Ok(None),
}
}

pub async fn handle_transaction_info_request(
&self,
request: TransactionInfoRequest,
Expand Down
22 changes: 17 additions & 5 deletions sui_core/src/authority_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,24 @@ impl Validator for AuthorityServer {
) -> Result<tonic::Response<TransactionInfoResponse>, tonic::Status> {
let transaction = request.into_inner();

let info = self
.consensus_adapter
.submit(&transaction)
// In some cases we can skip consensus for shared-object transactions: (i) we already executed
// the transaction, (ii) we already assigned locks to the transaction but failed to execute it.
// The later scenario happens when the authority missed some of the transaction's dependencies;
// we can thus try to re-execute it now.
let ConsensusTransaction::UserTransaction(certificate) = transaction.clone();
let info = match self
.state
.try_skip_consensus(certificate)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;

.map_err(|e| tonic::Status::internal(e.to_string()))?
{
Some(info) => info,
None => self
.consensus_adapter
.submit(&transaction)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?,
};
Ok(tonic::Response::new(info))
}

Expand Down
8 changes: 5 additions & 3 deletions sui_core/tests/staged/sui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -585,15 +585,17 @@ SuiError:
ConsensusNarwhalSerializationError:
NEWTYPE: STR
101:
NotASharedObjectTransaction: UNIT
102:
SignatureSeedInvalidLength:
NEWTYPE: U64
102:
103:
HkdfError:
NEWTYPE: STR
103:
104:
SignatureKeyGenError:
NEWTYPE: STR
104:
105:
RpcError:
NEWTYPE: STR
TransactionDigest:
Expand Down
4 changes: 3 additions & 1 deletion sui_types/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,10 @@ pub enum SuiError {
SharedObjectLockingFailure(String),
#[error("Consensus listener is out of capacity")]
ListenerCapacityExceeded,
#[error("Failed to serialize/deserialize Narwhal message: {0}")]
#[error("Failed to serialize/deserialize Narwhal message: {0}")]
ConsensusNarwhalSerializationError(String),
#[error("Only shared object transactions need to be sequenced")]
NotASharedObjectTransaction,

// Cryptography errors.
#[error("Signature seed invalid length, input byte size was: {0}")]
Expand Down