Редакционная доработка ещё намеренно не завершена
Этот раздел скрыт из основной навигации. Перевод и редакторская полировка намеренно не считаются обязательными, пока раздел не станет публичным.
Асинхронная Model
The promise system is the foundation of NEAR's asynchronous execution model. When a smart контракта needs to вызов another контракта, it creates promises - abstract representations of future computations that will be converted to квитанции for actual execution.
Understanding Promises
In NEAR, a Promise is a placeholder for work that will happen in a future блока. Unlike JavaScript promises or Rust futures, NEAR promises are not in-memory objects that you await - they're instructions that get converted to квитанции when the текущий function returns.
The Key Principle
Promises are declarative, not imperative. You're not calling another контракта - you're declaring that you want to вызов it. The actual execution happens in future блоки, potentially on different shards.
This is why:
- You can't get a return value immediately
- You must use callbacks to process results
- The callback might execute in a completely different блока/shard
- NEAR is fundamentally asynchronous
The Promise DAG
Promises form a Directed Acyclic Graph (DAG) of dependencies:
Promise Creation Functions
promise_create()
Creates a new promise for calling another аккаунта's function.
pub fn promise_create(
&mut self,
account_id_len: u64,
account_id_ptr: u64,
function_name_len: u64,
function_name_ptr: u64,
arguments_len: u64,
arguments_ptr: u64,
amount: u128,
gas: u64,
) -> Result<u64>
What it does:
- Reads target
account_idandfunction_namefrom WASM memory - Reads
arguments(the вызов payload) - Creates a new квитанция destined for the target аккаунта
- Attaches the specified
amountof NEAR and prepaysgas - Returns a
PromiseIndexthat can be used in subsequent calls
Usage pattern:
// In contract code
let promise_id = env::promise_create(
"other-contract.near",
"some_method",
b'{"arg": "value"}',
0, // attached NEAR
5_000_000_000_000, // attached gas (5 TGas)
);
promise_then()
Creates a callback that executes after another promise completes.
pub fn promise_then(
&mut self,
promise_idx: u64,
// ... target account, method, args ...
amount: u128,
gas: u64,
) -> Result<u64>
What it does:
- Takes an existing
promise_idxas a dependency - Creates a new квитанция that waits for the first promise to complete
- When the first promise finishes, its return value becomes available as
PromiseResultin the callback - Returns a new
PromiseIndexfor the callback
The Key Insight: The callback doesn't execute immediately. NEAR creates two квитанции:
- The original promise (вызов to other контракта)
- A callback квитанция with an
input_data_iddependency
When the first квитанция executes and produces a result, that result becomes a DataReceipt that satisfies the callback's dependency.
promise_and()
Joins multiple promises - waits for ALL of them to complete.
pub fn promise_and(
&mut self,
promise_idx_ptr: u64,
promise_idx_count: u64,
) -> Result<u64>
Use case: When you need to вызов multiple контракты and wait for all results:
let p1 = env::promise_create("contract_a", "get_data", ...);
let p2 = env::promise_create("contract_b", "get_data", ...);
let p3 = env::promise_create("contract_c", "get_data", ...);
let all = env::promise_and(&[p1, p2, p3]);
let callback = env::promise_then(all, "self", "process_all_results", ...);
promise_batch_create() and promise_batch_then()
Low-level functions for creating квитанции with multiple actions:
// Create an account with initial balance
let batch = env::promise_batch_create("new_account.near");
env::promise_batch_action_create_account(batch);
env::promise_batch_action_transfer(batch, deposit_amount);
env::promise_batch_action_add_full_access_key(batch, public_key);
Promise Results
When a callback executes, it can read the results of the promises it depends on.
pub enum PromiseResult {
/// The promise has not yet been resolved
Pending,
/// The promise succeeded with a value
Successful(Vec<u8>),
/// The promise failed (no value)
Failed,
}
In callback код:
fn callback() {
let count = env::promise_results_count();
for i in 0..count {
match env::promise_result(i, 0) {
PromiseResult::Successful(value) => {
// Process successful result
}
PromiseResult::Failed => {
// Handle failure
}
PromiseResult::Pending => unreachable!(),
}
}
}
Квитанция Types
Receipts are the fundamental unit of execution in NEAR. While транзакции are what users submit, квитанции are what the runtime actually processes.
Квитанция Hierarchy
pub enum ReceiptEnum {
Action(ActionReceipt) = 0, // Execute actions
Data(DataReceipt) = 1, // Deliver return data
PromiseYield(ActionReceipt) = 2, // Suspend execution (NEP-519)
PromiseResume(DataReceipt) = 3, // Resume suspended execution
GlobalContractDistribution(...) = 4, // Distribute shared contracts
ActionV2(ActionReceiptV2) = 5, // Enhanced action receipt
PromiseYieldV2(ActionReceiptV2) = 6, // Yield with enhanced receipt
}
ActionReceipt: The Workhorse
ActionReceipts contain the actual work to be done.
pub struct ActionReceipt {
/// Account that originally signed the transaction
pub signer_id: AccountId,
/// Public key used for signing (for gas refunds)
pub signer_public_key: PublicKey,
/// Gas price when this receipt was created
pub gas_price: Balance,
/// Where to send the result of this execution
pub output_data_receivers: Vec<DataReceiver>,
/// What this receipt is waiting for (callback dependencies)
pub input_data_ids: Vec<CryptoHash>,
/// The actual operations to perform
pub actions: Vec<Action>,
}
Key Fields:
| Field | Purpose |
|---|---|
signer_id | Track back to original tx signer for газ refunds |
gas_price | Газ price at creation time (for deterministic refunds) |
output_data_receivers | Receipts waiting for this execution's result |
input_data_ids | Dependencies this квитанция must wait for |
actions | Операцияs to perform: FunctionCall, Transfer, etc. |
DataReceipt: The Messenger
DataReceipts carry return values between квитанции.
pub struct DataReceipt {
/// Unique identifier matching an input_data_id somewhere
pub data_id: CryptoHash,
/// The actual return value (None means execution failed)
pub data: Option<Vec<u8>>,
}
The Data Flow Dance
Квитанция Lifecycle
Stage 1: Creation
Receipts are created in two ways:
- From транзакции: The first квитанция for any транзакции
- From execution: When a контракта creates promises
Stage 2: Routing
Each квитанция routes to its receiver's shard:
pub fn receiver_shard_id(&self, shard_layout: &ShardLayout) -> ShardId {
shard_layout.account_id_to_shard_id(self.receiver_id())
}
Stage 3: Waiting (if needed)
If a квитанция has input_data_ids, it cannot execute until ALL dependencies are satisfied. These become delayed квитанции.
Stage 4: Execution
When all dependencies are satisfied:
- Load input data from состояние as
PromiseResults - Delete input data from состояние (one-time consumption)
- Execute actions sequentially
- Produce outputs (DataReceipts, new ActionReceipts, refunds)
Cross-Shard Communication
NEAR's sharding model means different аккаунтов live on different shards. Cross-shard communication uses asynchronous квитанция passing.
Аккаунт-to-Shard Mapping
Every аккаунта belongs to exactly one shard, determined by alphabetical ordering against boundary аккаунтов:
boundary_accounts: ["aurora", "aurora-0", "kkuuue2akv_1630967379.near"]
Shard 0: accounts < "aurora"
Shard 1: "aurora" <= accounts < "aurora-0"
Shard 2: "aurora-0" <= accounts < "kkuuue2akv_1630967379.near"
Shard 3: accounts >= "kkuuue2akv_1630967379.near"
Alphabetical ordering uses the full аккаунта name. So a.near and z.a.near may be on different shards!
Cross-Shard Квитанция Propagation
Execution Order Guarantees
Within a Shard:
- Транзакции execute in chunk order
- Receipts execute deterministically (local first, then by receipt_id)
Across Shards:
- No global ordering guarantee across shards
- Causal ordering: If квитанция A creates квитанция B, A always executes before B
- Data dependencies: A квитанция waits until ALL its
input_data_idsare satisfied - No double execution: Each квитанция executes exactly once
Delayed Receipts
When a квитанция arrives but its dependencies aren't ready, it becomes a delayed квитанция.
Why Delayed?
- Cross-shard calls take time (at least 1 блока per hop)
- Callback can't execute until source вызов completes
- Сеть latency and shard processing order affect arrival time
Processing (each блока):
- Check delayed квитанции queue
- For each, check if dependencies arrived
- If satisfied, promote to execution
- If not, leave in queue
Congestion Control
Too many cross-shard квитанции can overwhelm a shard. NEAR implements backpressure (NEP-539):
- Memory-based limiting: Reject new транзакции when shard memory exceeds threshold (~500MB)
- Receiver-specific backpressure: Stop accepting транзакции to congested аккаунтов
- Deadlock prevention: Always allow minimum throughput to drain queues
Practical Example: Cross-Контракт Вызов with Callback
Step 1: Контракт A Creates Promises
Contract A executes transaction:
├── promise_create("B", "getData", ...) → Promise 0
└── promise_then(0, "A", "callback", ...) → Promise 1
Step 2: ReceiptManager Generates Receipts
Receipt R1 (for Promise 0):
├── receiver_id: "B"
├── actions: [FunctionCall("getData")]
├── input_data_ids: [] (no dependencies)
└── output_data_receivers: [{data_id: D1, receiver_id: "A"}]
Receipt R2 (for Promise 1):
├── receiver_id: "A"
├── actions: [FunctionCall("callback")]
├── input_data_ids: [D1] (waits for R1's output)
└── output_data_receivers: []
Step 3: Execution Timeline
Block N (Shard A):
└── Transaction executes → creates R1, R2
└── R1 sent to Shard B, R2 stored as delayed (waiting for D1)
Block N+1 (Shard B):
└── R1 executes → produces DataReceipt D1 with result
└── D1 sent to Shard A
Block N+2 (Shard A):
└── D1 arrives → satisfies R2's dependency
└── R2 executes with D1's data as PromiseResult::Successful
Газ Weight Distribution
When you don't know exactly how much газ to allocate, use газ weights:
env::promise_batch_action_function_call_weight(
promise_id,
"method",
args,
deposit,
Gas::ZERO, // minimum gas
GasWeight(1), // weight for distributing remaining gas
);
The ReceiptManager distributes unused газ proportionally based on weights.