Protocol
This release introduces protocol version 39
#13131: From protocol version 39 on devnet and testnet, random beacon DKG that misses its timeout round keeps running and can enable randomness again mid-epoch instead of disabling it for the rest of the epoch.
#13169: Protocol version 39 enables consensus_dedup_only_recorded_messages on all chains. A consensus message is skipped as a duplicate only of an earlier message with the same key that was recorded as processed or, in the same commit, passed verification, so all validators reach the same decision about a later copy of a dropped message.
#13300: Raise the minimum validator joining stake to 6M IOTA, the low stake threshold to 5.5M IOTA and the very low stake threshold to 5M IOTA.
#13285: From protocol version 39, a multisig whose committee mixes Ed25519 with other key schemes is only accepted when every committee member has signed.
#13308: groth16::prepare_verifying_key and groth16::verify_groth16_proof abort when the verifying key size does not match the number of public inputs.
#13297: Modules that use the deprecated global storage instructions, or a non-empty acquires list, fail to deserialize everywhere, including at execution.
Nodes (Validators and Full nodes)
#13084: max_fast_commit_sync_transaction_bytes now also caps what a node reads from one fast commit-sync response once its first commit is complete, and both ends handle these responses one chunk at a time instead of holding them whole.
#13164: Fix checkpoint execution stalling for several seconds when the synced checkpoint watermark moves past a checkpoint the executor is waiting for.
#13186: Bump rustls to 0.23.46 and quinn-proto to 0.11.19 to fix RUSTSEC-2026-0338 to RUSTSEC-2026-0343 and RUSTSEC-2026-0346 to RUSTSEC-2026-0349.
#13192: Fix checkpoint execution stalling for several seconds on a validator when a checkpoint from consensus arrives while state sync is still downloading the contents of the checkpoint before it.
#13184: Removed delegation of blocks to a remote firewall and the firewall-config node config key. A config that still sets it loads, but the section is ignored. The metrics blocks_delegated_to_firewall, firewall_delegation_request_fail, firewall_delegation_overflow and deadmans_switch_enabled are removed.
#13131: Added the epoch_random_beacon_dkg_completed_after_timeout metric; epoch_random_beacon_dkg_failed now reports whether DKG is unavailable after its timeout.
#13204: Fix a node with num-epochs-to-retain: 0 returning an internal error for a finalized transaction when the execution response asks for input or output objects.
#13190: The node logs client_blocked client=<ip> reason=<spam|error> ttl_secs=<n> at INFO under the target iota_node::client_blocked each time it starts to refuse a client. The format is stable for use with host firewall tools such as fail2ban. A blocklist TTL of 0 turns that blocklist off, and TTLs above 30 days (2592000 seconds) are rejected: the node does not start with one, and the admin API refuses it.
#13228: The traffic controller counts and blocks IPv6 clients by the first ipv6-prefix-len bits of their address (new policy-config field, 32 to 128, default 64), an assumed prefix length that applies to every IPv6 client. The block log line names such a client as client=<prefix>/<prefix length>.
#13252: In allowlist mode, the traffic control admin API now reports no blocklist TTLs and rejects a request that sets one, as it already does for the policy thresholds; the connection_blocklist_ttl_sec and proxy_blocklist_ttl_sec gauges are only set in blocklist mode.
#13248: Under deterministic validation, a transaction whose input was already consumed, deleted, or didn't match what the transaction declared at the consensus commit that sequenced it now fails at once with the new InputObjectNotAvailableAtCommit error instead of a retried ObjectNotFound.
Indexer
#13171: 🪛 Fixes an issue where the ingestion framework would download a historical checkpoint batch file multiple times in case the in-progress checkpoints reached the data limit checked by the application.
#13263: ingestion-core once its backoff max elapsed time is reached instead of a panic it now shuts down gracefully.
#12901: event queries are no longer limited by the retention of tables they do not read, so they return events that were previously incorrectly filtered out in case a different retention policy was used for different event_*/tx_senders tables.
JSON-RPC
#12901: indexer only. iotax_queryEvents responses return oldestAvailableCheckpoint, the oldest checkpoint from which the response is complete.
#12992: indexer only. iota_getCheckpoints responses return oldestAvailableCheckpoint, the oldest checkpoint the response can contain data from.
#13032: indexer only. iotax_queryTransactionBlocks responses return oldestAvailableCheckpoint, the oldest checkpoint the response can contain data from.
#13099: indexer only. Errors raised for pruned data return oldestAvailableCheckpoint in their data field.
iota_getTransactionBlock,iota_tryGetPastObjectandiota_tryGetObjectBeforeVersionnow fail withDataPrunedwhere they previously answered "not found" orObjectNotExists, when data has been pruned and no historical fallback is configured.iota_getEvents(when nothing has been pruned) andiota_getCheckpoint(when a configured historical fallback does not hold the checkpoint) now answer "not found" instead ofDataPruned.iota_getCheckpointby digest now serves pruned checkpoints from the historical fallback.- The
iota_tryGetPastObject,iota_tryGetObjectBeforeVersionandiota_tryMultiGetPastObjectsfallback now also covers the case where the object exists but the requested version was pruned.
#13176: the indexer only. DataPruned errors on iota_getEvents, iota_tryGetPastObject, iota_tryGetObjectBeforeVersion, iotax_queryTransactionBlocks and iotax_queryEvents report an oldestAvailableCheckpoint from which the same request would succeed, and a cursor digest that has been pruned fails with DataPruned instead of Invalid argument.
#12893: iota_multiGetTransactionBlocks reports unknown digests, and derived fields it could not compute, in the entry's errors field instead of returning empty entries.
GraphQL
#13165: every response now includes the x-iota-min-sdk-version header with the lowest iota-sdk-graphql-client version that can decode it.
#13225: address inputs are parsed with the SDK address parser, so the error messages for malformed addresses changed.
CLI
#13223: Fixed iota-localnet start --with-faucet serving the fullnode gRPC API on a free port; it now uses the documented default 0.0.0.0:50051.
#13285: iota keytool multi-sig-address and multi-sig-combine-partial-sig reject committees mixing Ed25519 with other key schemes; decode-multi-sig and decode-or-verify-tx verify with the newest protocol version's rules for the network given by the new --network option (default unknown).
Rust SDK
#12901: ⚠️ Breaking: Page gained an oldest_available_checkpoint field, so code that builds or exhaustively destructures a Page needs updating.
gRPC
#13237: A call whose grpc-timeout passes on the server ends with DEADLINE_EXCEEDED instead of CANCELLED. With TLS on, a TLS handshake must finish within 5 s; while 4,096 handshakes run, further connections wait.
#12893: requests that require disabled indexes fail with FailedPrecondition instead of Internal.
🚨 Breaking Changes Rollout
devnet
- iota-grpc-server: #12893: gRPC clients that match on
Internalfor requests to a node without indexes must match onFailedPreconditioninstead.
testnet
- iota-grpc-server: #12893: same as devnet, with the next testnet release.
mainnet
- iota-grpc-server: #12893: same as devnet, with the next mainnet release.