Перейти к основному содержимому

Редакционная доработка ещё намеренно не завершена

Этот раздел скрыт из основной навигации. Перевод и редакторская полировка намеренно не считаются обязательными, пока раздел не станет публичным.

Финальность

Every execution in NEAR produces an outcome that records what happened. Understanding outcomes is crucial for knowing when your транзакции truly succeeded.

ExecutionOutcome Structure

Source: core/primitives/src/transaction.rs

pub struct ExecutionOutcome {
/// Logs emitted during execution
pub logs: Vec<LogEntry>,

/// Receipt IDs generated by this execution
pub receipt_ids: Vec<CryptoHash>,

/// Gas consumed
pub gas_burnt: Gas,

/// Compute time (internal metric, not persisted)
pub compute_usage: Option<Compute>,

/// NEAR burned (gas_burnt × gas_price)
pub tokens_burnt: Balance,

/// Who executed (signer for tx, receiver for receipt)
pub executor_id: AccountId,

/// Final status
pub status: ExecutionStatus,

/// Detailed profiling (V1, V2, V3 versions)
pub metadata: ExecutionMetadata,
}

ExecutionStatus

pub enum ExecutionStatus {
/// Execution hasn't started or status unknown
Unknown,

/// Execution failed with error
Failure(TxExecutionError),

/// Success with return value
SuccessValue(Vec<u8>),

/// Success, spawned a receipt (for async calls)
SuccessReceiptId(CryptoHash),
}

Key distinctions:

  • SuccessValue: Execution completed and returned data
  • SuccessReceiptId: Execution succeeded but created a promise - final result is in that квитанция
  • Failure: Execution failed - check error for details

Транзакция Outcome vs Квитанция Outcome

A single транзакции can produce many outcomes:

Example outcome tree:

Transaction Hash: TX1
├── TransactionOutcome: Initial execution
│ ├── status: SuccessReceiptId(R1)
│ └── receipt_ids: [R1, R2]

├── ReceiptOutcome R1: First receipt execution
│ ├── status: SuccessValue(...)
│ └── receipt_ids: [R3]

├── ReceiptOutcome R2: Second receipt (callback)
│ ├── status: SuccessValue(...)
│ └── receipt_ids: []

└── ReceiptOutcome R3: Refund
├── status: SuccessValue(...)
└── receipt_ids: []

FinalExecutionOutcome

The aggregated просмотр of all outcomes:

pub struct FinalExecutionOutcomeView {
/// Status of the whole transaction
pub status: FinalExecutionStatus,

/// The original signed transaction
pub transaction: SignedTransactionView,

/// Transaction outcome
pub transaction_outcome: ExecutionOutcomeWithIdView,

/// All receipt outcomes
pub receipts_outcome: Vec<ExecutionOutcomeWithIdView>,
}

pub enum FinalExecutionStatus {
/// Still processing
NotStarted = 0,
Started = 1,

/// All done but the final receipt failed
Failure(TxExecutionError) = 2,

/// All done with final value
SuccessValue(Vec<u8>) = 3,
}

Querying Outcomes

The tx RPC Method

curl -X POST https://rpc.mainnet.fastnear.com \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "tx",
"params": {
"tx_hash": "6zgh2u9DqHHiXzdy9ouTP7oGky2T4nugqzqt9wJZwNFm",
"sender_account_id": "sender.near",
"wait_until": "FINAL"
}
}'

wait_until Options

ValueDescriptionUse Case
NONEReturn immediately with whatever статус existsPolling pattern
INCLUDEDWait until транзакции is in a блокаQuick confirmation
INCLUDED_FINALWait until блока is finalFork protection
EXECUTEDWait until initial квитанция executedBasic execution
EXECUTED_OPTIMISTICWait for execution with optimistic финальностьFaster feedback
FINALWait until ALL квитанции executed and finalizedRecommended

Understanding Финальность

Блок Финальность

NEAR uses Doomslug consensus with ~2 second финальность:

  1. Optimistic: Блок produced but not yet endorsed
  2. Near-final: Блок has endorsements but could theoretically revert
  3. Final: Блок has BFT финальность - cannot revert

Транзакция Финальность

A транзакции is truly final when:

  1. The блока containing it is final
  2. All квитанции have executed
  3. All квитанция блоки are final
Common Mistake

Checking only INCLUDED means the транзакции is in a блока, but:

  • The блока might not be final
  • The квитанции haven't executed yet
  • Cross-shard квитанции take additional блоки

Practical Examples

Simple Transfer (Fast)

Block N: Transaction included → immediate execution
Block N+1: Block N is final
Total: ~2 seconds to finality

Cross-Контракт Вызов (Slower)

Block N: Transaction creates receipt R1
Block N+1: R1 executes, creates R2 (callback)
Block N+2: R2 executes
Block N+3: Block N+2 is final
Total: ~4-6 seconds to finality

Multi-Shard DeFi (Slowest)

Complex operations spanning multiple shards:

Block N through N+5: 6 blocks of receipts
Block N+6: Final block is final
Total: ~12+ seconds to finality

Error Handling

Check All Outcomes

const result = await near.connection.provider.txStatus(txHash, accountId);

// Check transaction outcome
if (result.status.Failure) {
console.error('Transaction failed:', result.status.Failure);
return;
}

// Check ALL receipt outcomes
for (const receipt of result.receipts_outcome) {
if (receipt.outcome.status.Failure) {
console.error('Receipt failed:', receipt.id, receipt.outcome.status.Failure);
return;
}
}

// Success!
const finalValue = result.status.SuccessValue;

Common Errors

ErrorDescription
ActionError::FunctionCallErrorКонтракт execution failed
ActionError::AccountDoesNotExistTarget аккаунта missing
ActionError::InsufficientStakeNot enough staked
ActionError::ActorNoPermissionдоступ ключ lacks permission

Parsing Return Values

When a контракта function returns data:

// The SuccessValue is base64-encoded
const result = await provider.txStatus(txHash, accountId);

if (result.status.SuccessValue) {
// Decode base64 to bytes
const bytes = Buffer.from(result.status.SuccessValue, 'base64');

// If the contract returns JSON (common pattern)
const value = JSON.parse(bytes.toString());
console.log('Return value:', value);
}

Best Practices

1. Always Use wait_until: FINAL

Unless you have specific requirements for faster feedback, use FINAL to ensure complete execution.

2. Check All Квитанция Outcomes

A транзакции can succeed but a квитанция can fail. Always iterate through all outcomes.

3. Handle Partial Success

In cross-контракта calls, some квитанции may succeed while others fail. Your application should handle this gracefully.

4. Implement Retry Logic

For TIMEOUT errors, the транзакции may still execute. Запрос the статус before retrying.

async function submitWithRetry(signedTx, maxAttempts = 3) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await provider.sendTransaction(signedTx);
} catch (error) {
if (error.type === 'TimeoutError') {
// Check if tx was actually processed
const status = await provider.txStatus(
signedTx.transaction.hash,
signedTx.transaction.signer_id
);
if (status) return status;
}
if (attempt === maxAttempts - 1) throw error;
}
}
}