Rand Protocol
文檔
核心概念

Mempool

節點在收到交易後、將其放入區塊前如何處理:接納次序、以衝突追蹤取代 nonce、四個驗證工作程序、對等節點計量與裁決快取、提議者如何挑選區塊,以及落敗的雙重支付為何會自行消失。

使用本頁,可了解一筆交易在 rand_sendTransaction 接受之後、被區塊打包之前會經歷甚麼:若被拒絕,原因為何;為何它可能從池中消失而從未報錯;區塊按甚麼次序填充;以及大量無效證明對驗證者能造成與不能造成的影響。內存池位於 crates/randprotocol-node/src/mempool.rs;其前方的防護位於 admission.rs。

沒有發送者,因此沒有 nonce

在賬戶型區塊鏈上,內存池按 nonce 為每個發送者的交易排序。屏蔽式區塊鏈沒有發送者:捆包不帶地址,也不帶簽名,兩個捆包唯有在觸及同一張票據時才有關聯。因此,已無按發送者排序可做。取代 nonce 簿記的是衝突追蹤:池會為每筆待處理交易的作廢符與承諾建立索引,並拒絕任何聲稱佔用其中之一的新交易。賬本對若干計數器亦只接受每個值對應一筆交易,處理方式相同:驗證者的註冊 nonce(Unbond 或 Withdraw)、代幣的 mint_nonce(由 TokenMint 與 SetAuthority 共用)、RegisterToken 所限定的註冊表索引,以及橋的 pause_nonce 與 list_nonce。以上每一項都是一個槽位,池中至多只能有一筆交易佔用。此類交易中的兩筆至多只有一筆可被納入;若兩者都攜帶,提議者會白白耗用區塊空間,而每個節點在每輪 gossip 中都要多做一次證明驗證。

池最多容納 10,000 筆交易(node.rs 中的 Mempool::new(10_000)),並且自 v0.6.8 起,最多容納八個區塊的位元組量:MAX_POOL_BLOCKS = 8 乘以該鏈的 max_block_bytes,在 chain 21 上為 152 MiB(八個 19 MiB 區塊;chain 20 上為 160 MiB)(審計 v6,CH-7)。在此之前只限制數量,而一筆攜帶證明的交易大小約為 1.3 MB 至逾 12 MB。每筆交易均依據共識樹的樹尖(而非最後一個已提交區塊)的賬本進行驗證:那才是提議者實際會在其上構建的狀態。

准入:先廉價,後昂貴

經 RPC 或 gossip 到達的交易會依次經過以下步驟,而你看到的錯誤就是第一個拒絕:

  1. **重複。**哈希已在池中:already in mempool。
  2. **衝突。**某個作廢符、某個承諾,或上述 nonce 與索引槽位之一,已被待處理交易佔用:conflicts with a pending transaction over <hex>。對於代幣槽位,hex 是一個顯示用的鍵:先是一個標籤字(mint_nonce 為 tokn,註冊索引為 tidx),然後是索引與 nonce。若兩個中繼者競相提交同一橋證明,則以其摘要區分:conflicts with a pending transaction over bridge attestation <hash>。
  3. 已滿。mempool full。容量檢查是這三項中的最後一項。在達到位元組上限時,新交易可取代池中排名嚴格低於它的條目——先比較其費用高於各條目當前每 KiB 下限的幅度,再比較費用——由最便宜者開始逐一驅逐,直至其能容納;若這些仍不足,或只是持平,則被拒絕的是新交易。治理操作不受兩項上限約束,且絕不會被驅逐:四項橋治理操作 PauseMints、UnpauseMints、RegisterBridgedToken、ListBacking,以及自 v0.6.8 起的 RotatePqGuardians、RotatePauseKey、其 …V2 形式、CancelRotation、AdmitValidator 與 SlashEquivocation。它們的位元組與條目仍計入上限。PauseMints 是應對守護者密鑰洩露的制動:無捆包、無費用,並且在攻擊者鑄幣期間必須進入區塊的唯一一筆交易,因此已滿的池不得將它拒之門外。此豁免受衝突步驟所限:pause_nonce 與 list_nonce 每次各只容許池中一筆交易,所以這四項橋操作至多使池超出上限兩個條目;其餘操作各自消耗一個 nonce 或一個法定人數。
  4. **Ledger::validate 的廉價部分。**凡是在准入與納入之間可能過期、且無需證明即可檢查的一切:錨點是 chain 20 與 chain 21 上最近 1,024 個根之一(創世區塊的 proof_window_blocks;沒有該設置時為 256),time 在窗口內,作廢符未被花費,承諾尚不存在,證明的摘要未被佔用,質押操作的註冊 nonce 為當前值,代幣操作的 mint_nonce 或註冊索引正是註冊表當前所在之值(鏈已越過該值時為 wrong mint nonce、wrong token index),暫停或上架的 nonce 為橋的當前值。交易所創建的承諾亦在此計算,包括賬本自行為 Withdraw、BridgeAttest 或聚合者派息所推導的票據,它不在傳輸數據中,但同樣不得衝突。大小上限會在解碼任何內容之前檢查:證明超過該鏈的 max_proof_bytes(默認 2 MiB,chain 20 上為 4 MiB)、證明超過 16 KiB,或交易大於一個區塊(max_block_bytes:默認 4 MiB,chain 20 上為 20 MiB),均被直接拒絕,因此過大的數據塊不會帶來任何工作量。處於封存(已修剪)標記形式的捆包證明,在封存形式同步之外的同一步驟被拒絕,錯誤為 PrunedFormOutsideSync。然後,在仍不驗證任何內容的情況下,池會讀取證明標頭。首先有三項策略:輸入、keccak 或 sha256 表低於 2^7 行的調用不會入池(CallRevealsPrivateInputs),沒有任何調用可容納的部署亦然(ProgramUncallable),捆包或調用證明的標頭欄位為誠實證明者不會寫出者的交易同樣不入池(NonCanonicalProof)。後兩項在 chain 20 上亦屬賬本規則,受 hardening_v6 管轄。自 v0.6.8 起,聲明的度數超過最高表上限的標頭,會在檢查遍歷它之前以 NonCanonicalProof 拒絕(審計 v6,ZKV-4),而此預篩選中任何位置的 panic 均視為對該交易的拒絕(VerifierPanicked),而非節點崩潰。接着是費用下限:在 chain 20 上,池會解碼調用或 invoke 證明所聲明的 gas 上限,並拒絕低於 BUNDLE_BASE + gas_price × limit + byte_price × ⌈bytes/1024⌉(按樹尖的價格,另加 invoke 的單元費用)的費用,錯誤為 FeeTooLow(費用)。這正是區塊自身的規則,所以池所要求的與區塊所要求的完全一致。
  5. 昂貴的部分,在共識循環之外。交易進入隊列,至多 64 筆深,交由四個驗證工作線程之一處理。工作線程在阻塞任務上運行 Ledger::validate 的其餘部分:捆包摘要、捆包證明所聲明的 gas 上限(自 chain 18 起恰為 20,479,否則為 BundleGasLimit)、捆包證明,以及自 chain 17 起針對交易綁定的授權證明(在 chain 21 上針對 hc_auth,或對多簽形態的證明針對 hc_auth_multisig;自 v0.8.0 起,每個 Ok 結論會保存在容量為 32,768 條目的驗證緩存中,因此區塊應用時不會再次驗證該證明),而對於調用,還包括其自身的證明與費用下限。它針對樹尖的快照進行驗證,該快照以惰性方式克隆,每次樹尖變動至多克隆一次,且僅在有交易等待時才克隆。經 gossip 到達而發現隊列已滿的交易會被丟棄,既不驗證也不轉發,而不是阻塞負責投票的循環;對端的下一輪 gossip 會再次攜帶它。
  6. 重新檢查並入池。insert_verified 會針對交易實際入池時的樹尖再次執行步驟 4,因為在驗證證明期間樹尖可能已經移動;然後接納該交易,並對經 gossip 到達的交易加以轉發。

步驟 1 至 4 即 Mempool::precheck;步驟 5 是唯一驗證 STARK 的一步。由於該成本由工作線程承擔,大量無效證明只會佔用四個線程,而不會影響共識循環。交易與操作列出了賬本自身的准入次序,步驟 4 與 5 合起來即執行該次序。

gossip 的兩道防護與一個緩存

在准入之前,有兩項措施僅適用於來自對等節點的交易;RPC 提交會跳過它們,因為該端口屬於運營者自己,並已受請求體大小限制所約束。唯一受計量的 RPC 路徑是下文的水龍頭。

  • 按對等節點計量的位元組額度。 自 v0.6.8 起,每個流言幀在解碼之前,已先計入其轉發者的位元組額度:突發容量為八個幀,每秒補充兩個幀,每幀以最大傳輸大小計;超出額度的幀不經解碼即被丟棄(審計 v6,GOSSIP-1)。交易 id 是對整個證明取哈希而得,僅在轉發者的額度之內才會計算。
  • 按對等節點計量的交易額度。 每個轉發節點各有一個令牌桶,突發容量 16,每秒補充 4 個(PEER_TX_BURST、PEER_TX_PER_SEC)。計量對象是轉發該交易的節點,而非其作者,因為屏蔽鏈無法指認作者。
  • 裁決緩存。 若本節點已因僅取決於交易自身位元組的原因拒絕某筆交易,例如證明無效、簽名無效或某部分超出大小上限,則再次收到時會由一個容量為 8,192 項的先進先出緩存(REFUSED_CACHE_ENTRIES)直接拒絕,而不會再驗證一次。若裁決反映的是本節點的狀態,例如錨點未知或作廢符已被花費,則絕不緩存:否則落後一個區塊的節點會使自己拒絕即將生效的交易。該清單屬允許清單,因此新增的錯誤在有人另作決定之前一律視為狀態。以下各項說明界線所在:
    • 自 v0.6.8 起,BadPauseSignature(the pause signature does not verify)不是永久性拒絕(審計 v6,BRG-18)。它依鏈上當前的暫停密鑰檢驗簽名,而橋接規則 v2 可輪換該密鑰,因此它屬於狀態:落後一次輪換的節點,否則會在重啟之前一直拒絕新持有者的 PauseMints。後量子聯合簽名索引超出範圍,亦因同一原因移出緩存。直至 v0.6.7,在鏈 18 和鏈 19 上,兩者均被當作永久性拒絕而緩存。如今重複出現的無效暫停,每次送達都會耗費一次 Dilithium2 驗證,並受按對等節點計量的額度所限制。
    • PrunedFormOutsideSync 刻意不列為永久性拒絕。已封存捆綁包的標記形式,其哈希與原始交易自身的 id 相同,而緩存正是以該 id 為鍵,因此若緩存此項拒絕,任何一個流言對等節點都能在全網審查這筆誠實交易。
    • 證明所宣告的 gas 上限,只是其自身位元組與創世常數之比較,因此 BundleGasLimit 和 AuthGasLimit 是永久性拒絕。FeeTooLow 則不是:在按 gas 計量的鏈上,下限會隨價格而變動。第 4 步的三項區塊頭政策同樣絕不緩存:每一項都是當前這個版本的認知,後續版本可能會改動。
    • 在設有水龍頭許可名單的鏈上(鏈 20 和鏈 21),FaucetRecipientNotAllowed 是永久性拒絕:它把該筆鑄造的收款方與一個創世常數作比較。FaucetBudgetExhausted 則不是:它屬於狀態,同一筆鑄造在下一個紀元會再獲接納。
    • 代幣操作的 BadSignature 同樣不是永久性拒絕,這與質押操作不同:代幣的鑄造授權屬於狀態,SetAuthority 可把它交給另一個密鑰,同一串位元組可能在該輪換提交前被拒絕、提交後被接受。位元組層面的代幣拒絕(名稱或符號無效、小數位過多、數額為零、代幣索引為 0)會被緩存;操作不得設定的銷毀欄位、承諾無法開啟為其數額的鑄造,以及盲化因子並非推導所得的橋接存款,亦然。

水龍頭限額

rand_mint 不收費,每次調用佔用交易池中的一筆交易;在鏈 21 上,一如鏈 20,水龍頭處於開啟狀態,且其背後有橋接持有價值。創世配置限定了它能做的事:一筆鑄造只能向 16 個列入許可名單的錢包付款,且每個 1,000 區塊的紀元最多 10,000 RAND(質押),並且只有創世 staking.faucet_minters 清單上 18 個密鑰之一才能簽署(MinterNotAllowed,從不緩存:自鏈 16 起為賬本規則,在鏈 15 上則為節點政策,當時該清單即創世驗證者)。在此之上,每個節點還各自計量其水龍頭:連續八次鑄造,每秒補充一次(FAUCET_MINT_BURST、FAUCET_MINT_PER_SEC)。超出後,該調用回應 faucet is rate limited on this node (8 mints back to back, refilling at 1/s); try again shortly。限額按節點進程而非按調用方計算,因為 RPC 埠沒有可供計量的對等節點身份,而所保護的資源是本節點的交易池。連同上文的容量豁免,水龍頭洪泛無法在 PauseMints 之前填滿交易池。

Gossipsub 以應用層驗證運行,因此一筆交易須先在本節點通過驗證,才會轉發給其他對等節點。持續發送無效交易的對等節點會被額度所節流,由緩存直接回應,並且不會在共識循環上耗費任何資源。

構建區塊

驗證者在某個視圖中擔任領導者時,會向交易池索取候選交易。candidates_within 只保留仍然適用於其所構建賬本的交易,並重新執行准入中成本較低的一半:錨點仍在窗口內、time 仍在窗口內、作廢符尚未被花費、承諾尚未存在。它還會剔除 mint_nonce 或登記索引已被鏈越過的代幣操作,以及 nonce 已過時的暫停或上架。自 v0.6.8 起,它會依所構建之鏈頂當前有效的價格,根據准入時記下的 gas 上限,為每一次 call 和 invoke 重新定價,並略去費用已低於下限的交易;這類交易仍留在池中,待價格回落後再度獲提供(審計 v6,CH-9)。自鏈 20 起,它再把其餘交易排序為先治理,其次按高於當前下限的費用、每 KiB 交易計,再按費用由高至低,同值者以哈希決定先後,使每個在同一交易池上構建區塊的誠實提議者選出相同的區塊,並填至2,000 筆交易及該鏈區塊上限的預算為止(MAX_BLOCK_TXS;max_block_bytes,默認 4 MiB,鏈 20 為 20 MiB,鏈 21 為 19 MiB)。區塊所缺的是位元組,因此盈餘按每 KiB 計算。沒有 gas 部分的鏈,以及沒有 gas 政策的節點,會略過該鍵,改按治理、費用和哈希排序,一如鏈 15。治理優先,是暫停保證的另一半:PauseMints 設計上不收費,因為它必須能由一個不持有 RAND 的錢包發出,所以若純按費用排序,緊急煞車就會被排在每一筆付費交易之後。

另有一條規則,適用於 call 或 invoke 處於就緒狀態之時(審計 v6,POOL-2;屬每條鏈上的節點政策,而非有效性規則)。只有這兩類交易須支付位元組價格,而一筆只比固定基礎費用略高的轉賬,按每 KiB 計會排在它們之前,因此源源不斷的轉賬可能使一個就緒的 call 在每個區塊中都落選。所以,固定費用的交易在第一輪中至多佔用區塊位元組的四分之三;待各個 call 就位後,剩餘空間再按原有次序,用被擱置的轉賬填滿。區塊空間不會浪費,而在沒有 call 就緒時,一切如常(費用)。

對於剩餘空間而言過大的交易會被跳過,而非區塊就此結束:排在其後的較小交易仍可入塊。鏈 18 上一筆轉賬約 2.85 MB,由 1.49 MB 的捆綁包證明和 1.36 MB 的授權證明組成,因此鏈 20 的 20 MiB 區塊可容納約七筆,鏈 21 的 19 MiB 區塊以此大小計可容納六筆(約束集 9 的證明大小在此尚未量度),而默認的 4 MiB 區塊則可容納一筆。在鏈 15 上,每筆轉賬約 1.43 MB,20 MiB 可容納十四筆。負載之下,由費用決定哪些交易入塊(費用)。自 v0.8.0 起,交易池也是副本節點重建區塊的依據:領導者以流言方式傳播區塊頭和交易哈希(緊湊區塊,共識),副本節點只按哈希獲取其交易池和近期交易緩存所缺的交易主體;若某筆流言交易被按轉發者計量的額度拒絕,則會以未驗證狀態保存在一個為期一個區塊的溢出緩存中供該次重建之用,絕不入池。

修剪

每次提交之後,prune 會從池中剔除 candidates_within 本來會跳過的交易:其票據已被他人花費的交易、其承諾現已存在的交易、其 nonce 或登記索引已被鏈越過的交易,以及其錨點或 time 已移出窗口的交易(鏈 20 和鏈 21 為 1,024 個區塊,至鏈 19 為 256 個)。因此,在雙重花費競爭中落敗的捆綁包會自行消失,不會向任何人產生錯誤。rand_sendTransaction 的接受並不等於提交:請輪詢 rand_getTransaction,直至它返回一個區塊;若錨點窗口過去仍無結果,請在新的錨點上重建捆綁包,然後再次提交。

監察

rand_status 會報告 mempool_size、verify_queue(等待工作線程處理的交易)和 refused_cache(裁決緩存中的項數)。verify_queue 停在 64,表示證明到達的速度超過四個工作線程的驗證速度,在密鑰已預熱時每個約 16 ms;mempool_size 在區塊持續滿載之際不斷增長,則表示決定納入與否的是費用,而非到達次序。

尚未實現的部分

  • 不設替換。 自第 18 鏈起,gas 與位元組價格每個區塊都會移動一次調用的底價,直至其上限;轉賬的底價則固定不變。同一批 note 之上,不存在以較高費用的交易替換已入池交易的做法,衝突規則會拒絕此類交易。定價過高而無法入選的調用會留在交易池中,不予提供,直至價格回落,或其錨定窗口屆滿。在 v0.6.7(第 18 鏈及第 19 鏈)上,已入池的調用沿用其被接納時的底價,價格上升後仍會被提供,並在出塊者的試應用中以 FeeTooLow 失敗。fullnode 的區塊空間文件把按費用排序的 mempool 列為擴大區塊之前的一步,並把區塊層級的聚合列為提升每個區塊上限的做法。
  • 不按存留時間過期。 已入池的交易只會因被納入區塊、在位元組上限處被逐出,或超出錨定與時間窗口而離開:第 20 鏈及第 21 鏈為 1,024 個區塊,按 explorer 在第 20 鏈上報告的每個區塊 1.34 秒計,約為 23 分鐘(第 21 鏈首 2,300 個區塊為 1.17 秒)。直至第 19 鏈,窗口為 256 個區塊,約五分鐘。
  • 沒有 mempool RPC。 沒有可列出待處理交易的方法;rand_status 中的 mempool_size,以及 rand_getMempoolInfo 中的數量、位元組數與最舊存留時間,是僅有的檢視途徑,而 explorer 只索引已提交的區塊。

最後一次對照 fullnode 提交 c0878c3f(chain 21)核實的日期為 2026-10-12,並已核對 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)。 如代碼與本頁內容有出入,以代碼為準;請告知我們。