Skip to main content

execution-outcome

Summary

Refactor current TransactionResult/TransactionLog/FinalTransactionResult to improve naming, deduplicate results and provide results resolution by the front-end for async-calls.

Motivation

Right now the contract calls 2 promises and doesn't return a value, the front-end will return one of the promises results as an execution result. It's because we return the last result from final transaction result. With the current API, it's impossible to know what is the actual result of the contract execution.

Guide-level explanation

Here is the proposed Rust structures. Highlights:

  • Rename TransactionResult to ExecutionOutcome since it's used for transactions and receipts
  • Rename TransactionStatus and merge it with result into ExecutionResult.
  • In case of success ExecutionStatus can either be a value of a receipt_id. This helps to resolve the actual returned value by the transaction from async calls, e.g. A->B->A->C should return result from C. Also in distinguish result in case of forks, e.g. A calls B and calls C, but returns a result from B. Currently there is no way to know.
  • Rename TransactionLog to ExecutionOutcomeWithId which is ExecutionOutcome with receipt_id or transaction hash. Probably needs a better name.
  • Rename FinalTransactionResult to FinalExecutionOutcome.
  • Update FinalTransactionStatus to FinalExecutionStatus.
  • Provide final resolved returned result directly, so the front-end doesn't need to traverse the receipt tree. We may also expose the error directly in the execution result.
  • Split into final outcome into transaction and receipts.

NEW

  • The FinalExecutionStatus contains the early result even if some dependent receipts are not yet executed. Most function call transactions contain 2 receipts. The 1st receipt is execution, the 2nd is the refund. Before this change, the transaction was not resolved until the 2nd receipt was executed. After this change, the FinalExecutionOutcome will have FinalTransactionStatus::SuccessValue("") after the execution of the 1st receipt, while the 2nd receipt execution outcome status is still Pending. This helps to get the transaction result on the front-end faster without waiting for all refunds.
pub struct ExecutionOutcome {
/// Execution status. Contains the result in case of successful execution.
pub status: ExecutionStatus,
/// Logs from this transaction or receipt.
pub logs: Vec<LogEntry>,
/// Receipt IDs generated by this transaction or receipt.
pub receipt_ids: Vec<CryptoHash>,
/// The amount of the gas burnt by the given transaction or receipt.
pub gas_burnt: Gas,
}

/// The status of execution for a transaction or a receipt.
pub enum ExecutionStatus {
/// The execution is pending.
Pending,
/// The execution has failed.
Failure,
/// The final action succeeded and returned some value or an empty vec.
SuccessValue(Vec<u8>),
/// The final action of the receipt returned a promise or the signed transaction was converted
/// to a receipt. Contains the receipt_id of the generated receipt.
SuccessReceiptId(CryptoHash),
}

// TODO: Need a better name
pub struct ExecutionOutcomeWithId {
/// The transaction hash or the receipt ID.
pub id: CryptoHash,
pub outcome: ExecutionOutcome,
}

#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
pub enum FinalExecutionStatus {
/// The execution has not yet started.
NotStarted,
/// The execution has started and still going.
Started,
/// The execution has failed.
Failure,
/// The execution has succeeded and returned some value or an empty vec in base64.
SuccessValue(String),
}

pub struct FinalExecutionOutcome {
/// Execution status. Contains the result in case of successful execution.
pub status: FinalExecutionStatus,
/// The execution outcome of the signed transaction.
pub transaction: ExecutionOutcomeWithId,
/// The execution outcome of receipts.
pub receipts: Vec<ExecutionOutcomeWithId>,
}