Protocol
This release introduces protocol version 40
On devnet, this protocol version can be enabled no earlier than 2026-11-07 18:29 UTC (start of epoch 15). Please ensure that you update your validators before this epoch change. Otherwise, if the upgrade threshold is reached, the protocol will remove you from the committee, and both you and your delegators will lose rewards.
#13362: Adds the meter_instantiation_loops feature flag and the max_instantiation_loop_graph_edges parameter for verifying published packages after consensus, set to the values already in use.
#13452: Packages named by a Move authenticator's type arguments are now inputs of the authenticator: nodes wait for them before executing the transaction, and they are charged as input reads unless they are system packages.
#13306: stake whose activation epoch change ran in safe mode is withdrawn as pending stake.
#13490: enhanced loading of the struct, enum and vector values in Move-call arguments, with the new limits max_move_call_argument_nodes (2,097,152 per Move call) and max_move_call_argument_nodes_total (16,777,216 per transaction).
#13453: new feature flag disallow_self_identifier_in_transactions (protocol 40): a transaction whose type arguments, Move call target or Move authenticator type arguments name <SELF> is rejected at signing and in consensus.
#13497: Updated the gas model version to 3.
#13483: Validator attestation, behind enable_validator_attestation: the proposing validator dry-runs each user transaction, attaches an attestation with the computation units it estimates, and consensus requires one on every user transaction while the flag is on; certificates in blocks are refused. The attested units can serve as the execution cost for shared-object congestion control. Post-consensus validation runs execution's input checks on attested transactions; a failed Move authentication of an attested transaction is judged against the attestor's recorded object versions, and the verdict decides whether the gas owner pays. Verdicts other than valid are certified in checkpoint summary data version 2. With attestation_max_refuted_units_squared set, an attestor's epoch-end score is cut by the squared units its refuted attestations wasted, and one whose total reaches the threshold is cut off for the rest of the epoch. On devnet, protocol version 40 enables the flag with summary data version 2 and the threshold unset; mainnet and testnet are unchanged.
Nodes (Validators and Full nodes)
#13205: max_headers_per_commit_sync_fetch, max_transactions_per_transaction_sync_fetch and max_transactions_per_commit_sync_fetch above 1024 are now rejected at startup.
#13232: commit_sync_batch_size and fast_commit_sync_batch_size above 1024 are now rejected at startup.
#13331: The gRPC request count, latency and in-flight metrics (node_grpc_requests, node_grpc_request_latency, node_grpc_inflight_requests, node_grpc_inflight_checkpoint_stream_subscribers) are now exported with the default metrics settings.
#13299: new connection metrics of the gRPC API listener (node_grpc_*): accepted connections, open connections and their peak, connection lifetime, and with TLS the TLS handshake time and result and the peak of pending handshakes. Exported by default. The connection metrics of the JSON-RPC listener (json_rpc_*) are now exported by default too.
#13334: Dev-inspect now rejects direct calls to non-public native functions with NonEntryFunctionInvoked.
#13351: Full nodes now reject signed transaction responses from a validator whose signature names a different validator.
#13295: When the server ends a gRPC API call at its grpc-timeout, node_grpc_requests now counts it as DeadlineExceeded (before: canceled), and traffic control counts it as a request. Most gRPC clients also end the call at their deadline, so either side can end it first; a call that the client ends first is still canceled.
#13350: Nodes no longer load and re-execute the migration data (migration.blob) on startup once the genesis checkpoint has been executed.
#13339: the jsonrpc-server-type and indexer-max-subscriptions settings are removed; the JSON-RPC server only serves HTTP.
#12645: One rpc_indexes database replaces the JSON-RPC and gRPC index stores. Fullnodes rebuild it once on first start, and history is backfilled only from the checkpoints the node still holds. num-epochs-to-retain-for-indexes governs all index history. OWNER_INDEX_BLOCK_CACHE_MB (default 2 GB) replaces COIN_INDEX_BLOCK_CACHE_MB (default 5 GB). New gauges: rpc_index_rebuild_in_progress, rpc_index_history_backfill_running, rpc_index_history_backfill_lowest_replayed_checkpoint.
#12652: enable-index-processing is replaced by enable-jsonrpc-api (default true), which turns the JSON-RPC API and its index on or off together. A full node still setting enable-index-processing: false refuses to start. num-epochs-to-retain-for-indexes no longer has a 7-epoch minimum and counts epochs on top of the current one (0 keeps only the current epoch). With enable-jsonrpc-api: false, a full node serves only /health on its JSON-RPC address.
#12695: Superseded object versions are moved into per-epoch history buckets in the perpetual database instead of being deleted.
#12737: Object pruning now drops whole epochs at the epoch boundary, so 0 keeps the current epoch's superseded versions until the next boundary. The first start after the upgrade runs a one-time migration. A validator's finite non-zero num-epochs-to-retain is honoured. The objects pruner metrics and iota-tool db-tool prune-objects are removed.
#12762: All three pruning settings now mean N past epochs on top of the current one (u64::MAX for off), so num-epochs-to-retain-for-checkpoints keeps one more epoch than before for values of 2 and up, and 0 is refused. An unset value now keeps 30 epochs instead of everything; set 18446744073709551615 before upgrading to keep the full history. An unset num-epochs-to-retain-for-indexes now follows the checkpoint retention. A node refuses to start when num-epochs-to-retain-for-indexes is larger than num-epochs-to-retain-for-checkpoints. On the first start after the upgrade the node moves its history into per-epoch buckets before serving; no older release must be started on the database after that. The pruner metrics last_pruned_effects_checkpoint, last_pruned_effects_checkpoint_timestamp_ms, num_epochs_to_retain_for_objects, num_epochs_to_retain_for_checkpoints and pruning_chain_time_lag_ms are removed.
#13114: the per-epoch history (superseded object versions, transactions and effects, checkpoint contents, RPC index history) is now kept in <db-path>/live/historic, one directory per epoch.
#13231: Historic buckets expire in the background instead of during the epoch change, and once at every start, so a lowered retention takes effect at the next start. The new gauge historic_bucket_earliest_retained_epoch and the new histogram historic_bucket_expiry_duration_seconds, recorded for expiries that drop a bucket, replace last_pruned_indexes_transaction, which is removed.
#13235: Ended epochs' historic data is compacted once in the background, which makes the first start after the upgrade take extra disk I/O on a node with a long history. The first start refuses to run, naming the space needed, when the disk holding the historic data is too small for the one-time ledger migration (space freed by removing the old index databases counts only on the same file system), or when num-epochs-to-retain-for-checkpoints is unset and the node holds transactions older than 30 epochs. A first start refused for either reason leaves the database usable by the older release. A node refuses to start when an older release has written to its migrated database. rocksdb_total_sst_files_size reports the disk use of each history store under the same cf_name as its other metrics (hist_obj, hist_ledger, hist_ckpt, hist_rpc, marker).
#12332: Fullnodes rebuild the JSON-RPC index once on the first start after this upgrade; startup waits only for the live-object scan, and the transaction and event history fills in the background. iota-tool download-formal-snapshot now builds the JSON-RPC index during the restore; pass --skip-jsonrpc-indexes to skip it. The metrics post_processing_total_tx_indexed and post_processing_total_failures are removed.
#13424: With the P-COOL flow on, a fullnode's latency ping to a validator counts as a successful submit interaction when the validator answers, instead of a validator error, and gives a latency sample.
#13392: validators pass each validator's score to advance_epoch at its committee member index, so on networks that pass calculated scores, a validator's score-adjusted reward reaches that validator after a join or with a capped committee.
#13434: With the P-COOL flow on, a fullnode rejects a transaction whose input object a quorum of validators does not have right away and names the object, instead of timing out after 30 s. A finality timeout after failed submission attempts now names the last attempt's errors in its message.
#13452: The object deny list also rejects a transaction whose Move authenticator's type arguments name a denied package.
#13438: A validator's get_tx_status answers the new TimedOut status when its wait ends without a final status, and Expired only when the epoch ended.
#13312: new request metrics of the JSON-RPC, validator gRPC and gRPC API listeners (connection_inflight_requests with its peak, connection_idle_seconds, request_completion_latency, response_delivery_latency, under json_rpc_, authority_grpc_ and node_grpc_). Exported by default.
#13353: new client metrics of the JSON-RPC, validator gRPC and gRPC API listeners, grouped by client (IPv4 address or IPv6 /64 prefix): clients, and client_connections with its peak. Exported by default.
#13466: With the P-COOL flow on, a transaction rejected at submission is reported with the validators' reasons in the same wording as with the flow off, instead of "Invalid transaction: unknown error: ...". The error code and data.reason are unchanged.
#13326: with the P-COOL flow, transactions rejected by submit_tx now count toward the validator traffic control error policy.
#13490: new metrics move_call_argument_nodes (values in the arguments of each Move call) and excessive_move_call_argument_nodes (Move calls over a limit), enhancing the observability of argument loading.
#13468: The latest-object lookup returns a storage read or decode error instead of reporting the object as missing.
#13483: With the flag on, a validator rejects submissions while it is cut off (AttestorCutOff) and fullnodes route around cut-off validators. New errors AttestationAuthorMismatch, AttestationUnitsBelowMinimum, AttestationUnitsAboveBudget, AttestorCutOff; new metrics consensus_handler_validation_attested_transactions and consensus_handler_validation_attestor_cut_off_transactions.
Indexer
#13311: Responses from the historical fallback KV store are now capped by --fallback-kv-max-response-size-bytes (env FALLBACK_KV_MAX_RESPONSE_SIZE_BYTES). Larger responses are rejected.
#12841: getOwnedObjects, getCoins and getAllCoins refuse a cursor that came from a full node with an invalid-params error, instead of paging from the wrong place. Pass the indexer only the cursors it returned.
JSON-RPC
#13333: on full nodes, iotax_getOwnedObjects refuses filters with more than 10 nested levels or 64 filters and reads at most 10,000 objects per page, so a page can hold fewer results than the limit with hasNextPage set; iotax_subscribeEvent and iotax_subscribeTransaction are marked deprecated in the OpenRPC spec.
#13352: On the Indexer JSON-RPC, iotax_getOwnedObjects rejects filters with more that 10 nested levels or 64 filters eagerly.
#13339: iotax_subscribeEvent, iotax_subscribeTransaction and the WebSocket endpoint are removed; use iotax_queryEvents and iotax_queryTransactionBlocks.
#12645: getOwnedObjects pages are grouped by object type (coins largest balance first), and getCoins pages are sorted by balance, largest first. A coin whose balance changes between two getCoins pages can be repeated or skipped. iotax_queryEvents with a TimeRange filter no longer returns events of earlier pages again, and a cursor naming an event its transaction does not have is refused instead of shifting the page. With a Transaction filter, events have no timestamp until the transaction is in a checkpoint, and a cursor from another transaction is refused.
#12652: A node that serves the JSON-RPC API answers all index-backed methods (iotax_getOwnedObjects, iotax_getCoins, balances, iotax_queryTransactionBlocks, iotax_queryEvents, ...) instead of returning IndexStoreNotAvailable.
#12695: iota_tryGetPastObject and the other exact-version reads now return superseded versions from the history buckets.
#12737: iota_tryGetObjectBeforeVersion and withdrawn stakes in iotax_getStakes/getStakesByIds reach back as far as the configured object retention, counted in whole epochs.
#12762: Once epoch 0 falls out of the checkpoint retention, iota_getCheckpoint for checkpoint 0 and iota_getTransactionBlock for the genesis transaction return not found, like for any other expired epoch; before, checkpoint 0 and the genesis transaction were never pruned.
#12837: WaitForLocalExecution now returns only once the transaction's checkpoint is indexed (or with confirmedLocalExecution: false after 30 s). A transaction's checkpoint is reported only once that checkpoint is indexed.
#12841: on a full node, the nextCursor from iotax_getOwnedObjects, iotax_getCoins, iotax_getAllCoins and iotax_iotaNamesFindAllRegistrationNFTs is now an opaque string, not an object id. Pass back the cursor a page returned. If that cursor's object has since been spent, the next page still loads. getCoins and getAllCoins return a cursor only when a next page exists. Older clients cannot parse a response that has a next page. Indexer cursors are unchanged.
#13234: iotax_getCoinMetadata and iotax_getTotalSupply find a coin's metadata and treasury cap through the index, so they keep working, also for IOTA itself, after the publish transaction has been pruned.
#12332: Index-backed queries now update at checkpoint commit instead of execution commit, indexed event timestamps are now checkpoint timestamps, and iota_getDynamicFields reads field info from the live objects at query time.
#13384: WaitForLocalExecution waits up to 30 s in total for local execution and the checkpoint's indexes, instead of reporting confirmedLocalExecution: false once local execution takes longer than 10 s.
#13467: iotax_getStakesByIds and iotax_getTimelockedStakesByIds on a fullnode reject requests with more than QUERY_MAX_RESULT_LIMIT IDs, as the indexer already does.
#13502: The system state summary reports the validator count limits and stake thresholds enforced by the protocol config instead of the deprecated on-chain values.
#13483: iotax_getProtocolConfig reports enable_validator_attestation, attestation_max_refuted_units_squared and attestor_accuracy_tolerance_percentage.
GraphQL
#13311: The GraphQL service accepts --fallback-kv-max-response-size-bytes to bound the size of a single fallback KV store response.
#13390: subscriptions now reject a startAfter digest more than max-subscription-recovery-checkpoints checkpoints behind the latest indexed one, and cap concurrent subscriptions across all connections at max-active-subscriptions. Both can be changed in the [limits] section of the TOML file passed with --config. A startAfter digest that is too old or not found is reported with the BAD_USER_INPUT error code. To keep query latency stable, subscriptions replay from a dedicated Postgres pool of db-pool-size / 2 connections, so the service opens one and a half times as many connections as before.
#13502: The system parameters report the validator count limits and stake thresholds enforced by the protocol config instead of the deprecated on-chain values.
#13423: transactionBlocks and events return DATA_PRUNED for a cursor issued at a pruned checkpoint, instead of an empty page or an internal error
CLI
#13339: the --ws option of iota client new-env is removed.
#12645: iota-tool download-formal-snapshot replaces --skip-jsonrpc-indexes/--skip-grpc-indexes with --skip-rpc-indexes; iota client gas lists coins largest balance first. iota client balance and iota client objects count a coin repeated across pages once, with its highest version.
#12762: iota-tool db-tool prune-checkpoints is removed, since the node now expires checkpoint history at the epoch change. iota-tool db-tool print-checkpoint-watermarks is added.
#12478: iota client transaction commands and iota client ptb accept --local to run --dry-run through the local Move VM, resolving objects from the active env's grpc URL. A dry run without --gas-budget now reports the gas used as its budget.
#13010: iota client transaction commands and iota client ptb accept --local with --dev-inspect to run the dev-inspect through the local Move VM.
#13426: iota client publish --with-unpublished-dependencies keeps the #[authenticator] and #[view] attributes of the bundled dependency modules and verifies those modules at build time. iota client verify-source of a package published with bundled dependencies by an earlier CLI reports a bytecode mismatch for dependency modules with such attributes, unless the build uses the original toolchain.
#13404: Signing with a Ledger key now fails with an error when the transaction's objects cannot be loaded from the RPC or do not match the transaction, instead of silently sending the device an incomplete set.
Rust SDK
#13333: paged streams and the WalletContext gas object helpers no longer stop at an empty page that has a next page.
#13339: the WebSocket client, subscribe_event and subscribe_transaction are removed.
#12645: CoinReadApi::select_coins picks the largest coins first. CoinReadApi::select_coins and WalletContext::gas_objects keep the highest version of a coin repeated across pages; CoinReadApi::get_coins_stream yields it once, as first seen.
#12841: CoinPage and ObjectsPage page with the new OwnedObjectCursor, which replaces ObjectId as the cursor parameter of get_coins, get_all_coins, get_owned_objects and iota_names_find_all_registration_nfts.
#13380: QuorumDriverApi::execute_transaction_block with WaitForLocalExecution, when the node does not confirm local execution, polls until the node reports the transaction's checkpoint before reporting it as confirmed, so a read after the call sees the transaction's changes.
gRPC
#13333: transaction and event filters are limited to 256 nodes instead of 1000, and each transaction kind of a TransactionKind filter counts as a node.
#12737: lowest_available_checkpoint_objects and the x-iota-lowest-available-checkpoint-objects header come from the oldest historic bucket the node still holds.
#12762: Once epoch 0 falls out of the checkpoint retention, GetCheckpoint for checkpoint 0 and GetTransactions for the genesis transaction return not found. GetEpoch, including epoch 0's committee and close proof, is unaffected.
#12837: ExecuteTransactions with checkpoint_inclusion_timeout_ms now waits, within that timeout, until the transaction is indexed, so a query sent right after the response sees it. If the indexes are slower, the response still carries the checkpoint.
🚨 Breaking Changes Rollout
devnet
- iota-json-rpc-types: #12841: Rust SDK callers pass back the
next_cursora page returned instead of anObjectId. To page through coins or owned objects on an upgraded full node, clients need the SDK or CLI from this release. - iota-sdk: #12841: Rust SDK callers pass back the
next_cursora page returned instead of anObjectId. To page through coins or owned objects on an upgraded full node, clients need the SDK or CLI from this release. - iota-config: #12652: Rename
enable-index-processingtoenable-jsonrpc-apiin the node config. Full nodes that run without the index, such as State Sync Full Nodes withenable-index-processing: false, must setenable-jsonrpc-api: falseinstead, or they refuse to start. Operators who setnum-epochs-to-retain-for-indexesbelow 7 now keep fewer epochs of index history. Lowering the value deletes the older history on the next restart, and it cannot be brought back. - iota-config: #12737: Expect a longer first start while the one-time object migration runs (it resumes if interrupted). Move alerting off the removed objects pruner metrics, and stop using
iota-tool db-tool prune-objects. Validators that set a finite non-zeronum-epochs-to-retainnow keep that many epochs and need disk for them. - iota-config: #12762: Back up the database before the first start of this release; to go back to an older release afterwards, restore the backup or a snapshot. Set
num-epochs-to-retain-for-checkpointsbefore upgrading:18446744073709551615keeps the whole transaction and checkpoint history. Lowernum-epochs-to-retain-for-indexesto at mostnum-epochs-to-retain-for-checkpoints, or remove it so that it follows that retention. - iota-core: #12645: Expect a one-time index rebuild on the first start after the upgrade, which adds start-up time and disk use. Index history older than the node's checkpoint retention is not brought back. Clients paging
getOwnedObjects/getCoinsmust not rely on object-id order. Scripts using--skip-jsonrpc-indexes/--skip-grpc-indexesmust switch to--skip-rpc-indexes. - iota-core: #12652: Rename
enable-index-processingtoenable-jsonrpc-apiin the node config. Full nodes that run without the index, such as State Sync Full Nodes withenable-index-processing: false, must setenable-jsonrpc-api: falseinstead, or they refuse to start. Operators who setnum-epochs-to-retain-for-indexesbelow 7 now keep fewer epochs of index history. Lowering the value deletes the older history on the next restart, and it cannot be brought back. - iota-core: #12737: Expect a longer first start while the one-time object migration runs (it resumes if interrupted). Move alerting off the removed objects pruner metrics, and stop using
iota-tool db-tool prune-objects. Validators that set a finite non-zeronum-epochs-to-retainnow keep that many epochs and need disk for them. - iota-node: #12652: Rename
enable-index-processingtoenable-jsonrpc-apiin the node config. Full nodes that run without the index, such as State Sync Full Nodes withenable-index-processing: false, must setenable-jsonrpc-api: falseinstead, or they refuse to start. Operators who setnum-epochs-to-retain-for-indexesbelow 7 now keep fewer epochs of index history. Lowering the value deletes the older history on the next restart, and it cannot be brought back. - iota-node: #12737: Expect a longer first start while the one-time object migration runs (it resumes if interrupted). Move alerting off the removed objects pruner metrics, and stop using
iota-tool db-tool prune-objects. Validators that set a finite non-zeronum-epochs-to-retainnow keep that many epochs and need disk for them. - iota-snapshot: #12645: Expect a one-time index rebuild on the first start after the upgrade, which adds start-up time and disk use. Index history older than the node's checkpoint retention is not brought back. Clients paging
getOwnedObjects/getCoinsmust not rely on object-id order. Scripts using--skip-jsonrpc-indexes/--skip-grpc-indexesmust switch to--skip-rpc-indexes. - iota-snapshot: #12737: Expect a longer first start while the one-time object migration runs (it resumes if interrupted). Move alerting off the removed objects pruner metrics, and stop using
iota-tool db-tool prune-objects. Validators that set a finite non-zeronum-epochs-to-retainnow keep that many epochs and need disk for them. - iota-tool: #12645: Expect a one-time index rebuild on the first start after the upgrade, which adds start-up time and disk use. Index history older than the node's checkpoint retention is not brought back. Clients paging
getOwnedObjects/getCoinsmust not rely on object-id order. Scripts using--skip-jsonrpc-indexes/--skip-grpc-indexesmust switch to--skip-rpc-indexes. - iota-tool: #12737: Expect a longer first start while the one-time object migration runs (it resumes if interrupted). Move alerting off the removed objects pruner metrics, and stop using
iota-tool db-tool prune-objects. Validators that set a finite non-zeronum-epochs-to-retainnow keep that many epochs and need disk for them. - iota-json-rpc: #12645: Expect a one-time index rebuild on the first start after the upgrade, which adds start-up time and disk use. Index history older than the node's checkpoint retention is not brought back. Clients paging
getOwnedObjects/getCoinsmust not rely on object-id order. Scripts using--skip-jsonrpc-indexes/--skip-grpc-indexesmust switch to--skip-rpc-indexes. - iota-grpc-server: #12645: Expect a one-time index rebuild on the first start after the upgrade, which adds start-up time and disk use. Index history older than the node's checkpoint retention is not brought back. Clients paging
getOwnedObjects/getCoinsmust not rely on object-id order. Scripts using--skip-jsonrpc-indexes/--skip-grpc-indexesmust switch to--skip-rpc-indexes.
testnet
- iota-json-rpc-types: #12841: Same as devnet.
- iota-sdk: #12841: Same as devnet.
- iota-config: #12652: Same actions as devnet.
- iota-config: #12737: Same actions as devnet.
- iota-config: #12762: Same as devnet.
- iota-core: #12645: Same actions as devnet.
- iota-core: #12652: Same actions as devnet.
- iota-core: #12737: Same actions as devnet.
- iota-node: #12652: Same actions as devnet.
- iota-node: #12737: Same actions as devnet.
- iota-snapshot: #12645: Same actions as devnet.
- iota-snapshot: #12737: Same actions as devnet.
- iota-tool: #12645: Same actions as devnet.
- iota-tool: #12737: Same actions as devnet.
- iota-json-rpc: #12645: Same actions as devnet.
- iota-grpc-server: #12645: Same actions as devnet.
mainnet
- iota-json-rpc-types: #12841: Same as devnet.
- iota-sdk: #12841: Same as devnet.
- iota-config: #12652: Same actions as devnet.
- iota-config: #12737: Same actions as devnet.
- iota-config: #12762: Same as devnet.
- iota-core: #12645: Same actions as devnet.
- iota-core: #12652: Same actions as devnet.
- iota-core: #12737: Same actions as devnet.
- iota-node: #12652: Same actions as devnet.
- iota-node: #12737: Same actions as devnet.
- iota-snapshot: #12645: Same actions as devnet.
- iota-snapshot: #12737: Same actions as devnet.
- iota-tool: #12645: Same actions as devnet.
- iota-tool: #12737: Same actions as devnet.
- iota-json-rpc: #12645: Same actions as devnet.
- iota-grpc-server: #12645: Same actions as devnet.