Rand Protocol
Dokumentasi
Konsep inti

Mempool

Apa yang dilakukan sebuah node terhadap transaksi sejak diterima hingga dimasukkan ke dalam blok - urutan penerimaan, pelacakan konflik sebagai pengganti nonce, empat worker verifikasi, pengukur peer dan cache putusan, cara proposer memilih sebuah blok, dan alasan double-spend yang kalah menghilang dengan sendirinya.

Gunakan halaman ini untuk memahami apa yang terjadi pada sebuah transaksi setelah rand_sendTransaction menerimanya dan sebelum sebuah blok membawanya: mengapa transaksi itu ditolak jika memang ditolak, mengapa transaksi itu bisa lenyap dari pool tanpa pernah gagal, dalam urutan apa sebuah blok diisi, dan apa yang dapat serta tidak dapat dilakukan banjir bukti yang buruk terhadap sebuah validator. Mempool berada di crates/randprotocol-node/src/mempool.rs; penjaga di depannya berada di admission.rs.

Tanpa pengirim, maka tanpa nonce

Pada rantai berbasis akun, mempool mengurutkan transaksi setiap pengirim menurut nonce. Rantai shielded tidak memiliki pengirim: sebuah bundle tidak membawa alamat maupun tanda tangan, dan dua bundle hanya berkaitan bila keduanya menyentuh note yang sama. Jadi tidak ada lagi pengurutan per pengirim yang perlu dilakukan. Pengganti pembukuan nonce adalah pelacakan konflik: pool mengindeks setiap nullifier dan komitmen dari semua transaksi yang tertunda, dan menolak pendatang baru yang mengklaim salah satunya. Hal yang sama berlaku untuk beberapa penghitung yang hanya menerima tepat satu transaksi per nilainya oleh ledger: nonce register validator (sebuah Unbond atau Withdraw), mint_nonce sebuah token (dipakai bersama oleh TokenMint dan SetAuthority), indeks registri yang diikat pada sebuah RegisterToken, serta pause_nonce dan list_nonce milik bridge. Masing-masing adalah slot yang paling banyak dapat dipegang oleh satu transaksi dalam pool. Paling banyak satu dari dua transaksi semacam itu yang dapat dimasukkan ke blok, dan membawa yang lain akan membebani proposer dengan ruang blok dan setiap node dengan satu verifikasi bukti pada setiap putaran gossip.

Pool menampung paling banyak 10.000 transaksi (Mempool::new(10_000) di node.rs) dan, sejak v0.6.8, paling banyak byte setara delapan blok: MAX_POOL_BLOCKS = 8 kali max_block_bytes milik rantai, yaitu 160 MiB pada chain 20 (audit v6, CH-7). Sebelumnya hanya jumlahnya yang dibatasi, sementara satu transaksi pembawa bukti berukuran sekitar 1,3 MB hingga lebih dari 12 MB. Setiap transaksi divalidasi terhadap ledger pada ujung pohon konsensus, bukan blok terakhir yang di-commit: yaitu state yang sebenarnya akan dijadikan dasar oleh seorang proposer.

Admisi, yang murah lebih dulu daripada yang mahal

Transaksi yang tiba melalui RPC atau gossip melewati langkah-langkah berikut secara berurutan, dan penolakan pertama adalah galat yang Anda lihat:

  1. Duplikat. Hash sudah ada di pool: already in mempool.
  2. Konflik. Sebuah nullifier, sebuah komitmen, atau salah satu slot nonce dan indeks di atas sudah diklaim oleh transaksi yang tertunda: conflicts with a pending transaction over <hex>. Untuk slot token, hex-nya adalah kunci tampilan: sebuah kata tag (tokn untuk mint_nonce, tidx untuk indeks registrasi), lalu indeks dan nonce. Atestasi bridge yang diperebutkan dua relayer dibedakan berdasarkan digest-nya: conflicts with a pending transaction over bridge attestation <hash>.
  3. Penuh. mempool full. Pemeriksaan kapasitas adalah yang terakhir dari ketiganya. Pada batas byte, pendatang baru dapat menggantikan entri di pool yang peringkatnya benar-benar di bawahnya — menurut fee di atas batas bawah saat ini per KiB milik masing-masing entri, lalu menurut fee — dengan mengeluarkan yang termurah lebih dulu hingga muat; jika itu tidak cukup, atau ia hanya setara, maka pendatang baru itulah yang ditolak. Aksi tata kelola berada di luar kedua batas dan tidak pernah dikeluarkan: empat aksi tata kelola bridge PauseMints, UnpauseMints, RegisterBridgedToken, ListBacking, dan sejak v0.6.8 RotatePqGuardians, RotatePauseKey, bentuk …V2 keduanya, CancelRotation, AdmitValidator dan SlashEquivocation. Byte dan entrinya tetap dihitung terhadap kedua batas. PauseMints adalah rem untuk kebocoran kunci guardian: tanpa bundle, tanpa fee, dan satu-satunya transaksi yang harus sampai ke sebuah blok selagi penyerang sedang melakukan minting, sehingga pool yang penuh tidak boleh menghalanginya. Pengecualian ini dibatasi oleh langkah konflik: pause_nonce dan list_nonce masing-masing hanya menerima satu transaksi dalam pool pada satu waktu, sehingga keempat aksi bridge itu paling banyak menambah dua entri melewati batas; aksi lainnya masing-masing menghabiskan nonce atau kuorum tersendiri.
  4. Separuh murah dari Ledger::validate. Segala sesuatu yang dapat menjadi usang antara admisi dan inklusi dan tidak membutuhkan bukti: anchor adalah salah satu dari 1.024 root terakhir pada chain 20 (proof_window_blocks genesis; 256 tanpanya), time berada di dalam jendela, nullifier belum dibelanjakan, komitmen belum ada, digest atestasi belum diklaim, nonce register aksi staking masih berlaku, mint_nonce atau indeks registrasi aksi token adalah yang sedang dipegang registri (wrong mint nonce, wrong token index setelah rantai melewatinya), nonce sebuah pause atau listing adalah nonce bridge saat ini. Komitmen yang dibuat sebuah transaksi juga dihitung di sini, termasuk note yang diturunkan ledger sendiri untuk sebuah Withdraw, sebuah BridgeAttest atau pembayaran aggregator, yang tidak ada di wire tetapi juga tidak boleh bertabrakan. Batas ukuran diperiksa sebelum apa pun didekode: bukti yang melebihi max_proof_bytes rantai (2 MiB secara bawaan, 4 MiB pada chain 20), atestasi di atas 16 KiB, atau transaksi yang lebih besar dari sebuah blok (max_block_bytes: 4 MiB secara bawaan, 20 MiB pada chain 20) langsung ditolak, sehingga blob yang terlalu besar tidak membeli pekerjaan apa pun. Bukti bundle dalam bentuk penanda tersegel (dipangkas) ditolak pada langkah yang sama di luar sinkronisasi bentuk tersegel, sebagai PrunedFormOutsideSync. Kemudian, masih tanpa memverifikasi apa pun, pool membaca header bukti. Tiga kebijakan didahulukan: sebuah call yang tabel input, keccak atau sha256-nya kurang dari 2^7 baris tidak dimasukkan ke pool (CallRevealsPrivateInputs), demikian pula deploy yang tidak dapat dipanggil oleh call mana pun (ProgramUncallable), maupun transaksi yang bukti bundle atau call-nya memiliki field header yang tidak akan ditulis prover yang jujur (NonCanonicalProof). Dua yang terakhir juga merupakan aturan ledger pada chain 20, di bawah hardening_v6. Sejak v0.6.8, header yang degree-nya yang dinyatakan melewati batas atas tabel tertinggi ditolak sebagai NonCanonicalProof sebelum pemeriksaan menelusurinya (audit v6, ZKV-4), dan panic di mana pun dalam pra-penyaringan ini diperlakukan sebagai penolakan atas transaksi tersebut (VerifierPanicked), bukan node yang mati. Kemudian batas bawah fee: pada chain 20 pool mendekode batas gas yang dinyatakan oleh bukti call atau invoke dan menolak fee di bawah BUNDLE_BASE + gas_price × limit + byte_price × ⌈bytes/1024⌉ pada harga di ujung rantai, ditambah cell fee sebuah invoke, sebagai FeeTooLow (Fees). Itu adalah aturan blok sendiri, sehingga pool menuntut persis apa yang dituntut sebuah blok.
  5. Separuh mahal, di luar loop konsensus. Transaksi diantrekan, paling dalam 64, untuk salah satu dari empat worker verifikasi. Worker menjalankan sisa Ledger::validate pada tugas pemblokiran: digest bundle, batas gas yang dinyatakan bukti bundle (pada chain 20 tepat 20.479, jika tidak BundleGasLimit), bukti bundle dan, sejak chain 17, bukti otorisasinya terhadap binding transaksi, dan untuk sebuah call, buktinya sendiri beserta batas bawah fee-nya. Validasi dilakukan terhadap snapshot ujung rantai yang dikloning secara lazy, paling banyak sekali per perubahan ujung dan hanya selama ada yang menunggu. Transaksi gossip yang mendapati antrean penuh akan dibuang, tidak diverifikasi maupun diteruskan, daripada memblokir loop yang melakukan voting; putaran gossip berikutnya dari peer akan membawanya lagi.
  6. Periksa ulang dan masukkan ke pool. insert_verified menjalankan langkah 4 sekali lagi terhadap ujung rantai tempat transaksi benar-benar dimasukkan ke pool, karena ujung rantai mungkin telah berpindah selagi bukti diverifikasi, lalu menerimanya dan, untuk lalu lintas gossip, meneruskannya.

Langkah 1 sampai 4 adalah Mempool::precheck; langkah 5 adalah satu-satunya yang memverifikasi sebuah STARK. Karena biaya itu dibayar pada sebuah worker, banjir bukti yang buruk hanya menyibukkan empat thread dan tidak pernah loop konsensus. Transactions and actions mencantumkan urutan admisi ledger sendiri, yang dijalankan oleh langkah 4 dan 5 bersama-sama.

Dua penjaga dan satu cache untuk gossip

Di depan admisi, dua hal berlaku hanya untuk transaksi yang datang dari peer; kiriman RPC melewatinya, karena port itu milik operator sendiri dan sudah dibatasi oleh batas ukuran body permintaan. Satu-satunya jalur RPC yang diukur adalah faucet, di bawah.

  • Meter byte per peer. Sejak v0.6.8, setiap frame gossip dibebankan ke anggaran byte peer yang meneruskannya sebelum frame itu didekode: lonjakan awal delapan frame dan pengisian ulang dua frame berukuran transmisi maksimum per detik. Di atas batas itu, frame dibuang tanpa didekode (audit v6, GOSSIP-1). Id transaksi, yang melakukan hash atas seluruh proof, dihitung hanya dalam batas jatah peer peneruslah.
  • Meter transaksi per peer. Sebuah token bucket untuk setiap peer peneruus, burst 16, diisi ulang 4 per detik (PEER_TX_BURST, PEER_TX_PER_SEC). Meter ini mengukur peer yang meneruskan transaksi, bukan pembuatnya, yang tidak dapat disebutkan oleh chain shielded.
  • Cache putusan. Transaksi yang sudah ditolak node ini karena alasan yang semata-mata merupakan fungsi dari byte-nya, seperti proof yang buruk, tanda tangan yang buruk, atau bagian yang terlalu besar, ditolak kembali dari cache FIFO berisi 8.192 entri (REFUSED_CACHE_ENTRIES) tanpa diverifikasi untuk kedua kalinya. Putusan yang menyangkut state node ini, seperti anchor yang tidak dikenal atau nullifier yang sudah dibelanjakan, tidak pernah di-cache: node yang tertinggal satu blok akan meracuni dirinya sendiri terhadap transaksi yang sebentar lagi valid. Daftar ini bersifat allowlist, sehingga error baru dianggap state sampai ada yang memutuskan sebaliknya. Entri-entri berikut menunjukkan di mana garisnya ditarik:
    • BadPauseSignature (the pause signature does not verify) tidak permanen sejak v0.6.8 (audit v6, BRG-18). Error ini memeriksa tanda tangan terhadap kunci jeda chain yang berlaku saat ini, yang dapat dirotasi oleh bridge rules v2, sehingga ia adalah state: node yang tertinggal satu blok dari sebuah rotasi akan menolak PauseMints dari pemegang baru hingga di-restart. Indeks ko-tanda tangan PQ di luar rentang meninggalkan cache karena alasan yang sama. Hingga v0.6.7, pada chain 18 dan 19, keduanya di-cache sebagai permanen. Jeda yang buruk dan berulang kini menelan satu verifikasi Dilithium2 per pengiriman, dibatasi oleh meter per peer.
    • PrunedFormOutsideSync sengaja tidak permanen. Bentuk penanda dari bundle tersegel di-hash menjadi id transaksi mentahnya sendiri, dan cache menggunakan id itu sebagai kunci, sehingga meng-cache penolakan ini akan memungkinkan peer gossip mana pun menyensor transaksi yang jujur di seluruh jaringan.
    • Batas gas yang dideklarasikan sebuah proof adalah byte-nya sendiri yang dibandingkan dengan konstanta genesis, sehingga BundleGasLimit dan AuthGasLimit permanen. FeeTooLow tidak: pada chain 20 batas bawahnya bergerak mengikuti harga. Tiga kebijakan header pada langkah 4 juga tidak pernah di-cache: masing-masing merupakan pengetahuan build ini, yang dapat digeser oleh build berikutnya.
    • Pada chain dengan allowlist faucet (chain 20), FaucetRecipientNotAllowed permanen: ia membandingkan penerima mint itu sendiri dengan konstanta genesis. FaucetBudgetExhausted tidak: ia adalah state, dan mint yang sama diterima kembali pada epoch berikutnya.
    • BadSignature pada aksi token juga tidak permanen, berbeda dengan aksi staking: otoritas mint sebuah token adalah state, SetAuthority dapat menyerahkannya kepada kunci lain, dan byte yang sama dapat ditolak sebelum rotasi itu di-commit dan diterima setelahnya. Penolakan token tingkat byte (nama atau simbol yang buruk, desimal yang terlalu banyak, jumlah nol, indeks token 0) di-cache; demikian pula field burn yang tidak boleh diatur oleh aksi tersebut, mint yang commitment-nya tidak terbuka ke jumlahnya, dan deposit bridge yang blinding-nya bukan yang diturunkan.

Batas faucet

rand_mint bebas biaya dan memakan satu transaksi di pool per panggilan, dan pada chain 20 faucet menyala dengan bridge yang menahan nilai di belakangnya. Genesis membatasi apa yang dapat dilakukannya: sebuah mint hanya membayar 16 wallet dalam allowlist dan paling banyak 10.000 RAND per epoch 1.000 blok (Staking), dan hanya satu dari 18 kunci dalam daftar staking.faucet_minters di genesis yang boleh menandatanganinya (MinterNotAllowed, tidak pernah di-cache: aturan ledger sejak chain 16, kebijakan node pada chain 15, ketika daftarnya adalah validator genesis). Selain itu, setiap node meter faucet-nya sendiri: delapan mint berturut-turut, diisi ulang satu per detik (FAUCET_MINT_BURST, FAUCET_MINT_PER_SEC). Di atas itu, panggilan dijawab dengan faucet is rate limited on this node (8 mints back to back, refilling at 1/s); try again shortly. Batas ini berlaku per proses node, bukan per pemanggil, karena port RPC tidak memiliki identitas peer untuk diukur dan sumber daya yang dilindungi adalah pool node ini. Bersama dengan pengecualian kapasitas di atas, banjir faucet tidak dapat memenuhi pool mendahului sebuah PauseMints.

Gossipsub berjalan dengan validasi tingkat aplikasi, sehingga sebuah transaksi diteruskan ke peer lain hanya setelah ia terverifikasi di sini. Peer yang membanjiri jaringan dengan transaksi tidak valid akan dibatasi oleh meter, dijawab dari cache, dan tidak membebani loop konsensus sama sekali.

Membangun blok

Ketika seorang validator memimpin sebuah view, ia meminta kandidat dari pool. candidates_within hanya menyimpan transaksi yang masih berlaku pada ledger yang sedang dibangun, dengan menjalankan ulang separuh admisi yang murah: anchor masih dalam jendela, time masih di dalamnya, nullifier belum dibelanjakan, commitment belum ada. Fungsi ini juga membuang aksi token yang mint_nonce atau indeks registrasinya sudah dilewati chain, serta jeda atau listing dengan nonce yang basi. Sejak v0.6.8, ia menghitung ulang harga setiap call dan invoke pada harga yang berlaku di tip tempat ia membangun, dari batas gas yang diingat saat admisi, dan mengeluarkan transaksi yang biayanya kini di bawah batas bawah; transaksi itu tetap berada di pool, dan ditawarkan kembali setelah harga turun (audit v6, CH-9). Pada chain 20, sisanya kemudian diurutkan governance terlebih dahulu, lalu berdasarkan biaya di atas batas bawah saat ini, per KiB transaksi, lalu berdasarkan biaya, dari yang tertinggi, dengan hash sebagai pemutus seri, sehingga setiap proposer jujur yang membangun di atas pool yang sama memilih blok yang sama, dan mengisi hingga anggaran 2.000 transaksi dan batas blok chain (MAX_BLOCK_TXS; max_block_bytes, 4 MiB secara default dan 20 MiB pada chain 20). Byte adalah hal yang paling kurang dalam sebuah blok, itulah sebabnya kelebihan biaya diambil per KiB. Chain tanpa bagian gas dan node tanpa kebijakan gas melewati kunci itu dan mengurutkan berdasarkan governance, biaya, dan hash, seperti pada chain 15. Governance terlebih dahulu adalah separuh lain dari jaminan jeda: PauseMints tidak membayar biaya by design, karena ia harus dapat berfungsi dari wallet yang tidak memegang RAND, sehingga urutan biaya biasa akan menempatkan rem darurat di belakang setiap transaksi yang membayar.

Satu aturan lagi berlaku selama ada call atau invoke yang siap (audit v6, POOL-2; kebijakan node pada setiap chain, bukan aturan validitas). Hanya keduanya yang membayar harga byte, dan transfer yang membayar sedikit di atas biaya dasarnya yang flat mengungguli keduanya per KiB, sehingga arus transfer dapat menahan sebuah call yang siap keluar dari setiap blok. Karena itu transaksi berbiaya flat mengambil paling banyak tiga perempat byte blok pada putaran pertama; setelah call ditempatkan, ruang yang tersisa diisi dengan transfer yang tertahan, sesuai urutannya. Tidak ada ruang blok yang hilang, dan bila tidak ada call yang siap, tidak ada yang berubah (Fees).

Transaksi yang terlalu besar untuk ruang yang tersisa dilewati, bukan menjadi akhir blok: transaksi yang lebih kecil di belakangnya tetap ikut. Sebuah transfer chain 18 berukuran sekitar 2,85 MB, terdiri dari proof bundle 1,49 MB dan proof otorisasi 1,36 MB, sehingga blok 20 MiB-nya memuat sekitar tujuh dan blok default 4 MiB memuat satu. Pada chain 15, dengan sekitar 1,43 MB per transfer, 20 MiB yang sama memuat empat belas. Di bawah beban, biayalah yang menentukan yang mana (Fees).

Pruning

Setelah setiap commit, prune membuang dari pool persis apa yang akan dilewati candidates_within: transaksi yang note-nya dibelanjakan oleh pihak lain, yang commitment-nya kini sudah ada, yang nonce atau indeks registrasinya sudah dilewati chain, atau yang anchor atau time-nya telah bergulir keluar dari jendela (1.024 blok pada chain 20, 256 hingga chain 19). Bundle yang kalah dalam perlombaan double-spend karenanya hilang dengan sendirinya, tanpa pernah menghasilkan error bagi siapa pun. Penerimaan oleh rand_sendTransaction bukanlah commitment: lakukan polling rand_getTransaction hingga ia mengembalikan sebuah blok, dan jika jendela anchor berlalu tanpa blok, bangun ulang bundle pada anchor yang baru dan kirim kembali.

Memantaunya

rand_status melaporkan mempool_size, verify_queue (transaksi yang menunggu worker) dan refused_cache (entri dalam cache putusan). verify_queue yang bertahan di 64 berarti proof datang lebih cepat daripada yang dapat diverifikasi empat worker, sekitar 16 ms masing-masing dengan kunci yang hangat; mempool_size yang terus tumbuh sementara blok tetap penuh berarti biayalah, bukan urutan kedatangan, yang menentukan inklusi.

Yang belum tersedia

  • Tanpa penggantian. Harga gas dan harga byte pada chain 20 menggeser batas bawah (floor) sebuah panggilan di setiap blok, hingga batas atasnya; floor sebuah transfer bersifat tetap. Tidak ada penggantian transaksi di dalam pool oleh transaksi berbiaya lebih tinggi atas note yang sama, karena aturan konflik menolaknya. Panggilan yang harganya terlampaui menunggu di dalam pool tanpa ditawarkan, sampai harga turun atau jendela anchor-nya lewat. Pada v0.6.7 (chain 18 dan 19), panggilan di dalam pool mempertahankan floor saat diterima, tetap ditawarkan setelah harga naik, dan gagal pada penerapan percobaan oleh proposer dengan galat FeeTooLow. Dokumen block-space pada fullnode mencantumkan mempool yang diurutkan berdasarkan biaya sebagai langkah sebelum blok yang lebih besar, dan agregasi tingkat blok sebagai hal yang mengangkat batas atas per blok.
  • Tanpa kedaluwarsa berdasarkan usia. Transaksi di dalam pool hanya keluar melalui inklusi, melalui pengusiran pada batas byte, atau melalui jendela anchor dan jendela waktu: 1.024 blok pada chain 20, sekitar 23 menit pada 1,34 detik per blok yang dilaporkan explorer di sana. Hingga chain 19, jendelanya 256 blok, sekitar lima menit.
  • Tanpa RPC mempool. Tidak ada metode yang mendaftar transaksi yang tertunda; mempool_size pada rand_status serta jumlah, byte, dan usia tertua pada rand_getMempoolInfo adalah satu-satunya tampilan, dan explorer hanya mengindeks blok yang sudah dikomit.

Terakhir diverifikasi terhadap commit fullnode c0878c3f (chain 21) pada 2026-10-12, dan fullnode v0.6.8 `c9c9bd3c` (chain 20): `crates/randprotocol-node/src/mempool.rs` (`MAX_POOL_BLOCKS`, `make_room`, `is_governance`, the call reservation in `candidates_within`), the commits for CH-7, CH-9, POOL-2, BRG-18, GOSSIP-1 and ZKV-4, and `rand_getLimits` on chain 20, fullnode v0.6.7 `86941a1`: `crates/randprotocol-node/src/mempool.rs`, `admission.rs`, `crates/randprotocol-core/src/ledger/mod.rs` (the gas floor), fullnode `90e7716`: `docs/architecture.md` §8, `docs/rpc.md` (changelog, the 2026-09-28 gas entries), fullnode `ed88241`: `docs/confidential.md` (transaction binding). Jika kode dan halaman ini tidak sesuai, kodelah yang benar; beri tahu kami.