Skip to content

Commit

Permalink
rpc: support IncludedFinal tx status
Browse files Browse the repository at this point in the history
  • Loading branch information
telezhnaya committed Apr 3, 2024
1 parent f37edc0 commit bf9a3e8
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 42 deletions.
110 changes: 82 additions & 28 deletions chain/chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3077,6 +3077,39 @@ 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 - &seen_receipt_ids).is_empty() {
result
} else {
FinalExecutionStatus::Started
};
}

/// Collect all the execution outcomes, fail if we have non executed receipts
fn get_recursive_transaction_results(
&self,
outcomes: &mut Vec<ExecutionOutcomeWithIdView>,
Expand All @@ -3091,39 +3124,31 @@ impl Chain {
Ok(())
}

/// Collect all the execution outcomes existing at the current moment
fn get_partial_recursive_transaction_results(
&self,
outcomes: &mut Vec<ExecutionOutcomeWithIdView>,
id: &CryptoHash,
) {
if let Ok(outcome) = self.get_execution_outcome(id) {
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_partial_recursive_transaction_results(outcomes, &id);
}
}
}

/// 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<FinalExecutionOutcomeView, Error> {
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");
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))
Expand All @@ -3133,6 +3158,34 @@ impl Chain {
Ok(FinalExecutionOutcomeView { status, transaction, transaction_outcome, receipts_outcome })
}

/// Returns FinalExecutionOutcomeView for the given transaction.
/// Does not wait for the end of the execution of all corresponding receipts
pub fn get_transaction_result_for_rpc(
&self,
transaction_hash: &CryptoHash,
) -> Result<FinalExecutionOutcomeView, Error> {
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_partial_recursive_transaction_results(&mut outcomes, transaction_hash);
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 })
}

pub fn get_final_transaction_result_with_receipt(
&self,
final_outcome: FinalExecutionOutcomeView,
Expand Down Expand Up @@ -4177,7 +4230,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,
Expand Down
19 changes: 8 additions & 11 deletions chain/client/src/view_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -507,7 +504,7 @@ impl ViewClientActor {
target_shard_id,
true,
) {
match self.chain.get_final_transaction_result(&tx_hash) {
match self.chain.get_transaction_result_for_rpc(&tx_hash) {
Ok(tx_result) => {
let status = self.get_tx_execution_status(&tx_result)?;
let res = if fetch_receipt {
Expand Down
3 changes: 1 addition & 2 deletions chain/jsonrpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion core/primitives/src/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions integration-tests/src/user/runtime_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ impl RuntimeUser {
transactions
}

// todo get rid of tons of copy pasted code, it's usually 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;
Expand Down

0 comments on commit bf9a3e8

Please sign in to comment.