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

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

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

Сеть & Блоки

This page covers how транзакции propagate through NEAR's peer-to-peer сеть, wait in the транзакции pool, get selected for inclusion in chunks, and ultimately execute in a deterministic order.

P2P Architecture Обзор

NEAR uses a two-tier P2P сеть:

TierNameParticipantsPurpose
TIER 2 (T2)General сетьAll nodesMost протокола messages, gossip routing
TIER 1 (T1)Validator сетьВалидаторы and proxiesConsensus-critical messages, low latency

Транзакция forwarding uses TIER 2 routing.

Сеть Message Types

Source: chain/network/src/network_protocol/mod.rs

#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub enum T2MessageBody {
    ForwardTx(SignedTransaction),
    TxStatusRequest(AccountId, CryptoHash),
    TxStatusResponse(Box<FinalExecutionOutcomeView>),
    // ... more variants
}

Транзакция Forwarding

When a non-producing node receives a транзакции, it must forward it to validators who can include it in a блока.

Source: chain/client/src/rpc_handler.rs

fn forward_tx(
    &self,
    epoch_id: &EpochId,
    tx: &SignedTransaction,
) -> Result<(), Error> {
    // Determine which shard the transaction belongs to
    let shard_id = account_id_to_shard_id(
        self.epoch_manager.as_ref(),
        tx.transaction.signer_id(),
        epoch_id,
    )?;

    let head = self.chain_store.header_head()?;
    let mut validators = HashSet::new();

    // Find validators for upcoming block heights
    for horizon in (2..=self.config.tx_routing_height_horizon)
        .chain(vec![self.config.tx_routing_height_horizon * 2])
    {
        let target_height = head.height + horizon - 1;

        // Get chunk producer for this shard at target height
        let validator = self.epoch_manager
            .get_chunk_producer_info(&ChunkProductionKey {
                epoch_id: *epoch_id,
                height_created: target_height,
                shard_id,
            })?
            .take_account_id();
        validators.insert(validator);
    }

    // Send to all selected validators
    for validator in validators {
        self.network_adapter.send(PeerManagerMessageRequest::NetworkRequests(
            NetworkRequests::ForwardTx(validator, tx.clone()),
        ));
    }

    Ok(())
}

Why Forward to Multiple Валидаторы?

The код forwards to validators at heights +2, +3, +4, and +8 (configurable via TX_ROUTING_HEIGHT_HORIZON). This redundancy ensures:

ReasonBenefit
ReliabilityIf one валидатора misses the message, others can include the tx
LatencyCloser validators can include it sooner
Epoch boundariesNext-epoch validators are included when near epoch end

Validator Discovery

Валидаторы periodically broadcast their сеть address via AnnounceAccount:

pub struct AnnounceAccount {
    pub account_id: AccountId,
    pub peer_id: PeerId,
    pub epoch_id: EpochId,
    pub signature: Signature,
}

These announcements propagate through the сеть, building a routing table: account_id -> peer_id -> network_address.

Route Not Found

If a node doesn't have a route to the target валидатора:

  • The транзакции is dropped
  • No error is returned to the original sender
  • The клиент should retry or use a different node

This is why транзакции can occasionally get "lost" - all selected validators might be unreachable.

Транзакция Pool Design

The транзакции pool (mempool) is sharded to match NEAR's sharded architecture. Each shard maintains its own pool.

Source: chain/pool/src/lib.rs

pub struct TransactionPool {
    /// Transactions grouped by (AccountId, PublicKey) pairs
    transactions: BTreeMap<PoolKey, Vec<ValidatedTransaction>>,

    /// O(1) duplicate detection
    unique_transactions: HashSet<CryptoHash>,

    /// Randomized seed for anti-gaming
    key_seed: RngSeed,

    /// Tracks iteration position for round-robin
    last_used_key: PoolKey,

    /// Per-shard size limit (prevents memory exhaustion)
    total_transaction_size_limit: Option<u64>,
    total_transaction_size: u64,
}

Pool Visualization

┌─────────────────────────────────────────────────────────────┐
│           Transaction Pool (Shard 0)                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Group: (alice.near, ed25519:ABC...)                       │
│  ├─ tx_nonce=105 (waiting)                                 │
│  ├─ tx_nonce=106 (waiting)                                 │
│  └─ tx_nonce=107 (waiting)                                 │
│                                                             │
│  Group: (bob.near, ed25519:DEF...)                         │
│  └─ tx_nonce=42 (waiting)                                  │
│                                                             │
│  Group: (contract.near, ed25519:GHI...)                    │
│  ├─ tx_nonce=1001 (waiting)                                │
│  └─ tx_nonce=1002 (waiting)                                │
│                                                             │
│  Pool Size: 2.3 MB / 100 MB limit                          │
│  Transaction Count: 1,247                                   │
└─────────────────────────────────────────────────────────────┘

Key Design Decisions

  1. Per-shard pools: Транзакции targeting different shards never compete for the same pool space
  2. Grouped by аккаунта+ключ: Enables fair selection across different users
  3. Randomized ключи: The key_seed randomizes group ordering, preventing attackers from gaming транзакции order
  4. Size limits: Pool has a configurable maximum size (default 100MB). When full, new транзакции are rejected

Insertion Results

pub enum InsertTransactionResult {
    /// Transaction added successfully
    Success,

    /// Transaction already in pool
    Duplicate,

    /// Pool is full
    NoSpaceLeft,
}

Important: Pool is rejection, not eviction based. NEAR doesn't kick out existing транзакции to make room for new ones. First-come, first-served.

Round-Robin Fair Selection

When a chunk producer builds a chunk, they use a round-robin algorithm that gives each аккаунта a fair chance:

Selection Properties

PropertyDescription
One транзакции per group per passPrevents any single user from monopolizing a chunk
Nonce ordering within groupsCan't include nonce=107 before nonce=106
Randomized start positionPrevents predictable ordering
Multi-pass iterationIf capacity remains, continue through groups with more транзакции

Selection Limits

Chunk production stops when any limit is reached:

Limit TypeDescriptionTypical Value
ГазTotal транзакции газShard's газ limit (~1.3 PGas)
SizeCombined транзакции bytesWitness size limit
TimePreparation duration~2 seconds
Storage ДоказательствоTrie доступа доказательство sizeSoft limit

The Ethereum Contrast

NEAR's транзакции selection is fundamentally different from Ethereum's EIP-1559 fee market:

AspectEthereum (EIP-1559)NEAR
Fee structureBase fee (burned) + priority fee (to валидатора)Flat газ price (adjusted by протокола)
Транзакция orderingPriority fee bidding determines orderRound-robin FIFO, no bidding
Speed boostPay higher priority fee = faster inclusionCan't pay for priority
MEV exposureHigh - validators can reorder for profitLow - fair queue with randomization
User experienceMust estimate/bid fees correctlyPredictable, deterministic costs
During congestionFee spikes, bidding warsТранзакции wait in queue, costs stable

Why No Fee Bidding?

NEAR's design is intentional:

  1. Predictable costs: Users know exactly what a транзакции will cost
  2. Fairness over efficiency: Everyone waits their turn
  3. MEV resistance: Without reordering capability, many MEV strategies are impossible
  4. Simpler mental model: "Submit and wait" is easier than "estimate fees, potentially rebid"
Future Development

TransactionV1 includes a priority_fee field, but this is reserved for future use and not currently enabled. Setting a priority fee currently has no effect on транзакции ordering.

Chunk Production

Chunks are the units of execution in NEAR's sharded architecture.

Chunk Structure

A chunk contains:

  • Транзакции assigned to this shard
  • Receipts to process (from previous chunks)
  • Состояние root after execution
  • Газ used and limits

Production Flow

Chunk Header

pub struct ShardChunkHeaderInner {
    pub prev_block_hash: CryptoHash,
    pub prev_state_root: StateRoot,
    pub outcome_root: CryptoHash,
    pub encoded_merkle_root: CryptoHash,
    pub encoded_length: u64,
    pub height_created: BlockHeight,
    pub shard_id: ShardId,
    pub gas_used: Gas,
    pub gas_limit: Gas,
    pub balance_burnt: Balance,
    pub outgoing_receipts_root: CryptoHash,
    pub tx_root: CryptoHash,
    pub validator_proposals: Vec<ValidatorStakeV1>,
}

Квитанция Execution Ordering

Once транзакции become квитанции, they're executed in a strict three-phase ordering. This is deterministic across all validators.

Source: runtime/runtime/src/lib.rs, function process_receipts()

Phase Details

PhaseSourceOrderingWhy This Order
LocalThis блока's транзакцииТранзакция inclusion orderMinimize latency for same-shard ops
DelayedPrevious блоки' overflowStrict FIFOFairness - oldest first
IncomingCross-shard квитанцииChunk orderNew work waits for existing
Yield TimeoutsNEP-519 expirationsTimeout orderClean up suspended promises

What Happens When Газ Runs Out?

When газ is exhausted during any phase, remaining квитанции move to the delayed queue:

if processing_state.total.compute >= compute_limit {
    processing_state.delayed_receipts.push(
        &mut processing_state.state_update,
        &receipt,
    )?;
    continue;  // Skip to next receipt (which will also be delayed)
}

Receipts maintain their relative order when moved to the delayed queue.

The Delayed Квитанция Queue

The delayed квитанция queue is ключ to NEAR's ordering guarantees - implemented as a strict FIFO queue.

Source: core/store/src/trie/receipts_column_helper.rs

pub struct DelayedReceiptQueue {
    indices: TrieQueueIndices,
}

pub struct TrieQueueIndices {
    first_index: u64,          // Oldest receipt
    next_available_index: u64, // Where to add new ones
}

Queue Visualization

Indices: first=1000, next=1007

┌───────┬───────┬───────┬───────┬───────┬───────┬───────┐
│ 1000  │ 1001  │ 1002  │ 1003  │ 1004  │ 1005  │ 1006  │
│Receipt│Receipt│Receipt│Receipt│Receipt│Receipt│Receipt│
│   A   │   B   │   C   │   D   │   E   │   F   │   G   │
└───────┴───────┴───────┴───────┴───────┴───────┴───────┘
    ↑                                               ↑
    │ pop_front() always takes from here            │
    │ (oldest first)                         push() adds here

Processing Order: A → B → C → D → E → F → G
(strictly FIFO - no reordering possible)

Sequential Indices Guarantees

  1. No gaps: Every index from first_index to next_available_index-1 contains a квитанция
  2. Deterministic ordering: All validators agree on which квитанция is at each index
  3. No reordering: You can only add to the back and remove from the front
  4. Audit trail: The indices tell you exactly how many квитанции have been delayed

Cross-Shard Ordering

What's Deterministic

ScenarioOrdering Guarantee
Same shard, same блокаТранзакция order = local квитанция order
Same shard, different блокиFIFO via delayed queue
Different shards, same original txSequential within квитанция chain
Different shards, different txsNo ordering guarantee

The Causal Ordering Guarantee

NEAR guarantees causal ordering: if квитанция B was caused by квитанция A, then A's effects are visible when B executes.

Transaction on Shard 0:
  call contract.near::transfer(bob.near, 100)

Generates:
  Receipt A (Shard 0): Execute transfer
  Receipt B (Shard 1): Credit bob.near

Guarantee: When B executes, A has already executed.
           Bob sees the 100 tokens.

MEV Resistance

NEAR's ordering model provides significant MEV resistance compared to blockchains with reorderable mempools.

MEV Vectors That Are Blocked

AttackWhy Blocked
Транзакция reorderingRound-robin fair selection
Delayed квитанция reorderingFIFO queue cannot be reordered
Priority fee manipulationNo priority fees to bribe with
Sandwich attacks on delayed квитанцииQueue is strict FIFO

MEV Vectors That Still Exist

AttackWhy Possible
Same-блока front-runningAttacker might get транзакции in same блока (round-robin makes unreliable)
Cross-shard timingCross-shard квитанция timing has more uncertainty
Off-chain coordinationPrivate submission or валидатора collusion

Comparison with Ethereum

Ethereum:
├── Mempool visible → Attackers see pending txs
├── Fee bidding → Attackers can outbid
├── Validator reordering → Attackers collude with validators
└── Result: Sandwich attacks, front-running common

NEAR:
├── Mempool visible → Attackers see pending txs (same)
├── No fee bidding → Can't outbid for position
├── Round-robin selection → Harder to guarantee position
├── FIFO delayed queue → Can't manipulate delayed order
└── Result: MEV significantly constrained

Implications for Application Design

DEXs

Single-shard DEX (common for popular DEXs):

  • Транзакция ordering: Round-robin fair, no priority fees
  • Квитанция ordering: Deterministic within блока, FIFO across блоки
  • Design pattern: Accept that large swaps may be delayed. Design slippage tolerance accordingly.

Cross-shard DEX:

  • Less predictable: Cross-shard квитанция timing varies
  • More latency: Each shard hop is at least one блока
  • Design pattern: Use single-shard execution where possible

Auctions

  • Same-блока bids: Order within блока is deterministic but based on chunk inclusion order
  • Different-блока bids: Later блоки always beat earlier блоки for "последний bid wins"
  • Design pattern: Use блока height + position within блока for bid ordering

Oracle Integration

  • Single-shard: Oracle updates and dependent транзакции have predictable ordering
  • Cross-shard: Oracle on different shard means update visibility depends on квитанция delivery timing
  • Design pattern: Place oracle контракта on same shard as consumers

Key Takeaways

  1. No fee bidding: You can't pay more to get processed faster. Fairness over economic efficiency.

  2. Round-robin selection: Each аккаунта gets a fair chance. No monopolization by high-volume senders.

  3. Three-phase ordering is deterministic: Local → Delayed → Incoming, always in that order.

  4. FIFO delayed queue: Receipts that have waited longest are processed first. No cutting in line.

  5. MEV resistance is structural: No fee bidding + FIFO queues + round-robin selection = hard to manipulate.

  6. Cross-shard is eventually consistent: Within a shard, strict ordering. Across shards, causal ordering only.

This design reflects NEAR's philosophy: a blockchain should be usable by everyone, not just those who can afford to outbid others.