diff --git a/chain/chain/src/chain.rs b/chain/chain/src/chain.rs index 46f9fdec6cc..dbe484d435f 100644 --- a/chain/chain/src/chain.rs +++ b/chain/chain/src/chain.rs @@ -3077,53 +3077,68 @@ impl Chain { Ok(self.chain_store.get_outcomes_by_id(id)?.into_iter().map(Into::into).collect()) } + /// Returns execution status based on the list of currently existing outcomes + fn get_execution_status( + &self, + outcomes: &[ExecutionOutcomeWithIdView], + ) -> FinalExecutionStatus { + let mut awaiting_receipt_ids = HashSet::new(); + let mut seen_receipt_ids = HashSet::new(); + let mut result: FinalExecutionStatus = FinalExecutionStatus::NotStarted; + for outcome in outcomes { + seen_receipt_ids.insert(&outcome.id); + match &outcome.outcome.status { + ExecutionStatusView::Unknown => return FinalExecutionStatus::Started, + ExecutionStatusView::Failure(e) => return FinalExecutionStatus::Failure(e.clone()), + ExecutionStatusView::SuccessValue(v) => { + if result == FinalExecutionStatus::NotStarted { + // historically, we used the first SuccessValue we have seen + // let's continue sticking to it + result = FinalExecutionStatus::SuccessValue(v.clone()); + } + } + ExecutionStatusView::SuccessReceiptId(_) => { + awaiting_receipt_ids.extend(&outcome.outcome.receipt_ids); + } + } + } + return if awaiting_receipt_ids.is_subset(&seen_receipt_ids) { + result + } else { + FinalExecutionStatus::Started + }; + } + + /// Collect all the execution outcomes existing at the current moment + /// Fails if there are non executed receipts, and require_all_outcomes == true fn get_recursive_transaction_results( &self, outcomes: &mut Vec, id: &CryptoHash, + require_all_outcomes: bool, ) -> Result<(), Error> { - outcomes.push(ExecutionOutcomeWithIdView::from(self.get_execution_outcome(id)?)); + let outcome = match self.get_execution_outcome(id) { + Ok(outcome) => outcome, + Err(err) => return if require_all_outcomes { Err(err) } else { Ok(()) }, + }; + outcomes.push(ExecutionOutcomeWithIdView::from(outcome)); let outcome_idx = outcomes.len() - 1; for idx in 0..outcomes[outcome_idx].outcome.receipt_ids.len() { let id = outcomes[outcome_idx].outcome.receipt_ids[idx]; - self.get_recursive_transaction_results(outcomes, &id)?; + self.get_recursive_transaction_results(outcomes, &id, require_all_outcomes)?; } Ok(()) } + /// Returns FinalExecutionOutcomeView for the given transaction. + /// Waits for the end of the execution of all corresponding receipts pub fn get_final_transaction_result( &self, transaction_hash: &CryptoHash, ) -> Result { let mut outcomes = Vec::new(); - self.get_recursive_transaction_results(&mut outcomes, transaction_hash)?; - let mut looking_for_id = *transaction_hash; - let num_outcomes = outcomes.len(); - let status = outcomes - .iter() - .find_map(|outcome_with_id| { - if outcome_with_id.id == looking_for_id { - match &outcome_with_id.outcome.status { - ExecutionStatusView::Unknown if num_outcomes == 1 => { - Some(FinalExecutionStatus::NotStarted) - } - ExecutionStatusView::Unknown => Some(FinalExecutionStatus::Started), - ExecutionStatusView::Failure(e) => { - Some(FinalExecutionStatus::Failure(e.clone())) - } - ExecutionStatusView::SuccessValue(v) => { - Some(FinalExecutionStatus::SuccessValue(v.clone())) - } - ExecutionStatusView::SuccessReceiptId(id) => { - looking_for_id = *id; - None - } - } - } else { - None - } - }) - .expect("results should resolve to a final outcome"); + self.get_recursive_transaction_results(&mut outcomes, transaction_hash, true)?; + let status = self.get_execution_status(&outcomes); let receipts_outcome = outcomes.split_off(1); let transaction = self.chain_store.get_transaction(transaction_hash)?.ok_or_else(|| { Error::DBNotFoundErr(format!("Transaction {} is not found", transaction_hash)) @@ -3133,16 +3148,45 @@ impl Chain { Ok(FinalExecutionOutcomeView { status, transaction, transaction_outcome, receipts_outcome }) } - pub fn get_final_transaction_result_with_receipt( + /// Returns FinalExecutionOutcomeView for the given transaction. + /// Does not wait for the end of the execution of all corresponding receipts + pub fn get_partial_transaction_result( + &self, + transaction_hash: &CryptoHash, + ) -> Result { + let transaction = self.chain_store.get_transaction(transaction_hash)?.ok_or_else(|| { + Error::DBNotFoundErr(format!("Transaction {} is not found", transaction_hash)) + })?; + let transaction: SignedTransactionView = SignedTransaction::clone(&transaction).into(); + + let mut outcomes = Vec::new(); + self.get_recursive_transaction_results(&mut outcomes, transaction_hash, false)?; + if outcomes.is_empty() { + // It can't be, we would fail with tx not found error earlier in this case + // But if so, let's return meaningful error instead of panic on split_off + return Err(Error::DBNotFoundErr(format!( + "Transaction {} is not found", + transaction_hash + ))); + } + + let status = self.get_execution_status(&outcomes); + let receipts_outcome = outcomes.split_off(1); + let transaction_outcome = outcomes.pop().unwrap(); + Ok(FinalExecutionOutcomeView { status, transaction, transaction_outcome, receipts_outcome }) + } + + /// Returns corresponding receipts for provided outcome + /// The incoming list in receipts_outcome may be partial + pub fn get_transaction_result_with_receipt( &self, - final_outcome: FinalExecutionOutcomeView, + outcome: FinalExecutionOutcomeView, ) -> Result { let receipt_id_from_transaction = - final_outcome.transaction_outcome.outcome.receipt_ids.get(0).cloned(); - let is_local_receipt = - final_outcome.transaction.signer_id == final_outcome.transaction.receiver_id; + outcome.transaction_outcome.outcome.receipt_ids.get(0).cloned(); + let is_local_receipt = outcome.transaction.signer_id == outcome.transaction.receiver_id; - let receipts = final_outcome + let receipts = outcome .receipts_outcome .iter() .filter_map(|outcome| { @@ -3158,7 +3202,7 @@ impl Chain { }) .collect::, _>>()?; - Ok(FinalExecutionOutcomeWithReceiptView { final_outcome, receipts }) + Ok(FinalExecutionOutcomeWithReceiptView { final_outcome: outcome, receipts }) } pub fn check_blocks_final_and_canonical( @@ -4177,7 +4221,8 @@ impl Chain { Ok(is_first_block_of_epoch?) } - /// Get transaction result for given hash of transaction or receipt id on the canonical chain + /// Get transaction result for given hash of transaction or receipt id + /// Chain may not be canonical yet pub fn get_execution_outcome( &self, id: &CryptoHash, diff --git a/chain/client/src/view_client.rs b/chain/client/src/view_client.rs index e3e76b16369..9fdeec0fb77 100644 --- a/chain/client/src/view_client.rs +++ b/chain/client/src/view_client.rs @@ -436,26 +436,23 @@ impl ViewClientActor { return Ok(TxExecutionStatus::None); } + let is_execution_finished = match execution_outcome.status { + FinalExecutionStatus::Failure(_) | FinalExecutionStatus::SuccessValue(_) => true, + FinalExecutionStatus::NotStarted | FinalExecutionStatus::Started => false, + }; + if let Err(_) = self.chain.check_blocks_final_and_canonical(&[self .chain .get_block_header(&execution_outcome.transaction_outcome.block_hash)?]) { - return if execution_outcome - .receipts_outcome - .iter() - .all(|e| e.outcome.status != ExecutionStatusView::Unknown) - { + return if is_execution_finished { Ok(TxExecutionStatus::ExecutedOptimistic) } else { Ok(TxExecutionStatus::Included) }; } - if execution_outcome - .receipts_outcome - .iter() - .any(|e| e.outcome.status == ExecutionStatusView::Unknown) - { + if !is_execution_finished { return Ok(TxExecutionStatus::IncludedFinal); } @@ -507,12 +504,12 @@ impl ViewClientActor { target_shard_id, true, ) { - match self.chain.get_final_transaction_result(&tx_hash) { + match self.chain.get_partial_transaction_result(&tx_hash) { Ok(tx_result) => { let status = self.get_tx_execution_status(&tx_result)?; let res = if fetch_receipt { let final_result = - self.chain.get_final_transaction_result_with_receipt(tx_result)?; + self.chain.get_transaction_result_with_receipt(tx_result)?; FinalExecutionOutcomeViewEnum::FinalExecutionOutcomeWithReceipt( final_result, ) diff --git a/chain/jsonrpc/src/lib.rs b/chain/jsonrpc/src/lib.rs index ddedbbd3f84..3dc262911dd 100644 --- a/chain/jsonrpc/src/lib.rs +++ b/chain/jsonrpc/src/lib.rs @@ -711,8 +711,7 @@ impl JsonRpcHandler { > { self.send_tx(RpcSendTransactionRequest { signed_transaction: request_data.signed_transaction, - // Will be ignored, broadcast_tx_commit is not aligned with existing enum - wait_until: Default::default(), + wait_until: TxExecutionStatus::ExecutedOptimistic, }) .await } diff --git a/core/primitives/src/views.rs b/core/primitives/src/views.rs index 900f2445bdb..3c9d55f63f8 100644 --- a/core/primitives/src/views.rs +++ b/core/primitives/src/views.rs @@ -1756,7 +1756,7 @@ impl TxStatusView { } } -/// Execution outcome of the transaction and all of subsequent the receipts. +/// Execution outcome of the transaction and all the subsequent receipts. /// Could be not finalised yet #[derive( BorshSerialize, BorshDeserialize, serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone, diff --git a/integration-tests/src/user/runtime_user.rs b/integration-tests/src/user/runtime_user.rs index 316a759caa0..c9d43a68ad3 100644 --- a/integration-tests/src/user/runtime_user.rs +++ b/integration-tests/src/user/runtime_user.rs @@ -194,6 +194,7 @@ impl RuntimeUser { transactions } + // TODO(#10942) get rid of copy pasted code, it's outdated comparing to the original fn get_final_transaction_result(&self, hash: &CryptoHash) -> FinalExecutionOutcomeView { let mut outcomes = self.get_recursive_transaction_results(hash); let mut looking_for_id = *hash;