execution-outcome
- Proposal Name: Execution Outcome
- Start Date: 2019-09-23
- NEP PR: nearprotocol/neps#0017
- Issue(s): https://github.com/nearprotocol/nearcore/issues/1307
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
toExecutionOutcome
since it's used for transactions and receipts - Rename
TransactionStatus
and merge it with result intoExecutionResult
. - 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 fromC
. Also in distinguish result in case of forks, e.g.A
callsB
and callsC
, but returns a result fromB
. Currently there is no way to know. - Rename
TransactionLog
toExecutionOutcomeWithId
which isExecutionOutcome
with receipt_id or transaction hash. Probably needs a better name. - Rename
FinalTransactionResult
toFinalExecutionOutcome
. - Update
FinalTransactionStatus
toFinalExecutionStatus
. - 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, theFinalExecutionOutcome
will haveFinalTransactionStatus::SuccessValue("")
after the execution of the 1st receipt, while the 2nd receipt execution outcome status is stillPending
. 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>,
}