Skip to content

Commit ff2941a

Browse files
pugachAGclaude
andauthored
fix(runtime): clamp protocol version for view calls on archival nodes (#15603)
- Archival nodes can serve view call queries against blocks from protocol versions older than `MIN_SUPPORTED_PROTOCOL_VERSION`, where the runtime config references VM backends (Wasmer0, Wasmer2) that have been removed from the binary, causing a panic - Clamp the protocol version to `MIN_SUPPORTED_PROTOCOL_VERSION` in `NightshadeRuntime` before passing it to the state viewer - Add `clamp_to_supported_protocol_version` and `assert_supported_protocol_version` helpers in `core/primitives-core/src/version.rs` - Add precondition asserts in `TrieViewer::call_function` and `TrieViewer::view_account_contract_code` See [zulip](https://near.zulipchat.com/#narrow/channel/533300-releases.2F2.2E11/topic/Mainnet.20archival.20node.20crashes.20on.20some.20RPC.20query/with/585060345) for more context. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 897be93 commit ff2941a

4 files changed

Lines changed: 35 additions & 3 deletions

File tree

chain/chain/src/runtime/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ use near_primitives::types::{
3434
AccountId, Balance, BlockHeight, EpochHeight, EpochId, EpochInfoProvider, Gas, MerkleHash,
3535
Nonce, NonceIndex, NumShards, ShardId, StateRoot, StateRootNode,
3636
};
37-
use near_primitives::version::{ProtocolFeature, ProtocolVersion};
37+
use near_primitives::version::{
38+
ProtocolFeature, ProtocolVersion, clamp_to_supported_protocol_version,
39+
};
3840
use near_primitives::views::{
3941
AccessKeyInfoView, CallResult, ContractCodeView, GasKeyNoncesView, QueryRequest, QueryResponse,
4042
QueryResponseKind, ViewStateResult,
@@ -1696,7 +1698,7 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime {
16961698
self.trie_viewer.view_account_contract_code(
16971699
&state_update,
16981700
account_id,
1699-
current_protocol_version,
1701+
clamp_to_supported_protocol_version(current_protocol_version),
17001702
&self.genesis_config.chain_id,
17011703
)
17021704
}
@@ -1725,7 +1727,7 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime {
17251727
epoch_id: *epoch_id,
17261728
epoch_height,
17271729
block_timestamp,
1728-
current_protocol_version,
1730+
current_protocol_version: clamp_to_supported_protocol_version(current_protocol_version),
17291731
cache: Some(self.compiled_contract_cache.handle()),
17301732
};
17311733
self.trie_viewer.call_function(

core/primitives-core/src/version.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,31 @@ pub const PROD_GENESIS_PROTOCOL_VERSION: ProtocolVersion = 29;
519519
/// Minimum supported protocol version for the current binary
520520
pub const MIN_SUPPORTED_PROTOCOL_VERSION: ProtocolVersion = 80;
521521

522+
/// Returns the effective protocol version to use for processing a request.
523+
///
524+
/// Archival nodes can serve requests for blocks from protocol versions older than
525+
/// `MIN_SUPPORTED_PROTOCOL_VERSION`. Some features from those old versions may no longer be
526+
/// available in the current binary (e.g. the Wasmer0/Wasmer2 VM backends have been removed).
527+
/// For read-only view calls that don't produce on-chain state, it is safe to clamp the protocol
528+
/// version to `MIN_SUPPORTED_PROTOCOL_VERSION` so the request is processed with the config of
529+
/// the oldest fully-supported version.
530+
pub fn clamp_to_supported_protocol_version(
531+
current_protocol_version: ProtocolVersion,
532+
) -> ProtocolVersion {
533+
current_protocol_version.max(MIN_SUPPORTED_PROTOCOL_VERSION)
534+
}
535+
536+
/// Panics if `current_protocol_version` is below `MIN_SUPPORTED_PROTOCOL_VERSION`.
537+
///
538+
/// Use this at callee boundaries to enforce that the caller has already clamped the version
539+
/// via [`clamp_to_supported_protocol_version`].
540+
pub fn assert_supported_protocol_version(current_protocol_version: ProtocolVersion) {
541+
assert!(
542+
current_protocol_version >= MIN_SUPPORTED_PROTOCOL_VERSION,
543+
"protocol version {current_protocol_version} is below minimum supported {MIN_SUPPORTED_PROTOCOL_VERSION}"
544+
);
545+
}
546+
522547
/// Current protocol version used on the mainnet with all stable features.
523548
const STABLE_PROTOCOL_VERSION: ProtocolVersion = 85;
524549

core/primitives/src/version.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub use near_primitives_core::version::MIN_SUPPORTED_PROTOCOL_VERSION;
1919
pub use near_primitives_core::version::PROD_GENESIS_PROTOCOL_VERSION;
2020
pub use near_primitives_core::version::PROTOCOL_VERSION;
2121
pub use near_primitives_core::version::ProtocolFeature;
22+
pub use near_primitives_core::version::assert_supported_protocol_version;
23+
pub use near_primitives_core::version::clamp_to_supported_protocol_version;
2224

2325
/// Minimum gas price proposed in NEP 92 and the associated protocol version
2426
pub const MIN_GAS_PRICE_NEP_92: Balance = Balance::from_yoctonear(1_000_000_000);

runtime/runtime/src/state_viewer/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use near_primitives::trie_key::trie_key_parsers::{
2222
use near_primitives::types::{
2323
AccountId, Balance, BlockHeight, EpochHeight, EpochId, EpochInfoProvider, Gas, Nonce, ShardId,
2424
};
25+
use near_primitives::version::assert_supported_protocol_version;
2526
use near_primitives::views::{StateItem, ViewStateResult};
2627
use near_primitives_core::config::ViewConfig;
2728
use near_store::trie::AccessOptions;
@@ -97,6 +98,7 @@ impl TrieViewer {
9798
current_protocol_version: ProtocolVersion,
9899
chain_id: &str,
99100
) -> Result<ContractCode, errors::ViewContractCodeError> {
101+
assert_supported_protocol_version(current_protocol_version);
100102
let account = self.view_account(state_update, account_id)?;
101103
let wasm_config =
102104
&self.runtime_config_store.get_config(current_protocol_version).wasm_config;
@@ -271,6 +273,7 @@ impl TrieViewer {
271273
logs: &mut Vec<String>,
272274
epoch_info_provider: &dyn EpochInfoProvider,
273275
) -> Result<Vec<u8>, errors::CallFunctionError> {
276+
assert_supported_protocol_version(view_state.current_protocol_version);
274277
let now = Instant::now();
275278
let root = *state_update.get_root();
276279
let account = get_account(&state_update, contract_id)?.ok_or_else(|| {

0 commit comments

Comments
 (0)