Skip to main content

thruput_native_exec/
try_commit_result.rs

1use thruput_sys::FFIDataBuffer;
2use thruput_xdr::exec_result::ExecResult;
3
4// Re-exported so callers matching on `TxNonDeterministicError` have the
5// conflict types in scope without depending on thruput-trace directly.
6pub use thruput_trace::db_conflict::{
7    DbConflictCode, DbConflictDiagnostic, DbConflictResult,
8};
9
10/// Outcome of a two-phase commit (`try_commit_transaction`).
11///
12/// Mirrors the codes of [`ExecResult`], one variant per code, so a commit
13/// outcome is matched exhaustively. Only `ExecResultTxNonDeterministicError`
14/// carries a two-phase-commit conflict diagnostic, so the [`DbConflictResult`]
15/// rides on that variant alone — there is no way to reach a conflict after a
16/// successful (or otherwise-failed) commit.
17///
18/// [`ExecResult`]: thruput_xdr::exec_result::ExecResult
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum TryCommitResult {
21    /// `ExecResultOK` — the commit succeeded.
22    Ok,
23    /// `ExecResultTxDeterministicError` — the root frame returned a
24    /// deterministic error.
25    TxDeterministicError,
26    /// `ExecResultTxRootFrameReverted` — the root frame chose to revert.
27    TxRootFrameReverted,
28    /// `ExecResultTxNonDeterministicError` — nondeterminism during the
29    /// post-execution two-phase commit (a storage conflict). Carries the
30    /// conflict diagnostic; `DbConflictResult::kDbDiagnosticNone` when the
31    /// database reported no per-conflict detail for this execution mode.
32    TxNonDeterministicError(DbConflictResult),
33    /// `ExecResultFailedGasSponsorship` — no longer produced; retained to
34    /// mirror [`ExecResult`].
35    FailedGasSponsorship,
36    /// `ExecResultTxValidationFailure` — attached validation data was bad.
37    TxValidationFailure,
38    /// `ExecResultSystemError` — recoverable system error (e.g. broken
39    /// connection).
40    SystemError,
41    /// `ExecResultUnrecoverable` — critical, unrecoverable executor failure.
42    Unrecoverable,
43}
44
45impl TryCommitResult {
46    /// Build from the raw `try_commit_transaction` FFI result. The conflict
47    /// buffer is only decoded for the nondeterministic arm; every other code
48    /// ignores it.
49    pub(crate) fn from_ffi(
50        result_code: i32,
51        serialized_conflict: &FFIDataBuffer,
52    ) -> Self {
53        match result_code {
54            x if x == ExecResult::ExecResultOK.0 => Self::Ok,
55            x if x == ExecResult::ExecResultTxDeterministicError.0 => {
56                Self::TxDeterministicError
57            }
58            x if x == ExecResult::ExecResultTxRootFrameReverted.0 => {
59                Self::TxRootFrameReverted
60            }
61            x if x == ExecResult::ExecResultTxNonDeterministicError.0 => {
62                Self::TxNonDeterministicError(decode_conflict(
63                    serialized_conflict,
64                ))
65            }
66            x if x == ExecResult::ExecResultFailedGasSponsorship.0 => {
67                Self::FailedGasSponsorship
68            }
69            x if x == ExecResult::ExecResultTxValidationFailure.0 => {
70                Self::TxValidationFailure
71            }
72            x if x == ExecResult::ExecResultSystemError.0 => Self::SystemError,
73            x if x == ExecResult::ExecResultUnrecoverable.0 => {
74                Self::Unrecoverable
75            }
76            other => panic!(
77                "try_commit_transaction returned unknown ExecResult code: {other}"
78            ),
79        }
80    }
81
82    /// The [`ExecResult`] code this outcome corresponds to, discarding any
83    /// conflict diagnostic.
84    pub fn exec_result(&self) -> ExecResult {
85        match self {
86            Self::Ok => ExecResult::ExecResultOK,
87            Self::TxDeterministicError => {
88                ExecResult::ExecResultTxDeterministicError
89            }
90            Self::TxRootFrameReverted => {
91                ExecResult::ExecResultTxRootFrameReverted
92            }
93            Self::TxNonDeterministicError(_) => {
94                ExecResult::ExecResultTxNonDeterministicError
95            }
96            Self::FailedGasSponsorship => {
97                ExecResult::ExecResultFailedGasSponsorship
98            }
99            Self::TxValidationFailure => {
100                ExecResult::ExecResultTxValidationFailure
101            }
102            Self::SystemError => ExecResult::ExecResultSystemError,
103            Self::Unrecoverable => ExecResult::ExecResultUnrecoverable,
104        }
105    }
106}
107
108/// Decode the XDR-serialized [`DbConflictResult`] attached to a
109/// nondeterministic commit failure.
110///
111/// Per the test_ffi contract (`ForeignTryCommitResult::serialized_conflict`),
112/// the buffer is *always* populated with the whole union — the
113/// `kDbDiagnosticNone` arm when the execution mode reports no per-slot detail —
114/// and is null *only* when the host's own serialization failed. So a null
115/// buffer is a genuine FFI fault, not the no-detail case, and we panic rather
116/// than silently substituting an empty diagnostic.
117fn decode_conflict(serialized_conflict: &FFIDataBuffer) -> DbConflictResult {
118    match serialized_conflict.as_slice() {
119        Some(bytes) => xdrs::txs::decode(bytes).unwrap_or_else(|e| {
120            panic!(
121                "failed to decode DbConflictResult ({} bytes): {e}",
122                bytes.len()
123            )
124        }),
125        None => panic!(
126            "try_commit_transaction reported a nondeterministic error but the \
127             serialized conflict buffer was null (host-side serialization failed)"
128        ),
129    }
130}