Skip to content

feat:Deploy TON Core pool via stub-with-SETCODE init message#138

Merged
mrnkslv merged 7 commits into
release/nodectl/v0.5.0from
feature/sma-90-deploy-ton-core-pool-via-stub-with-setcode-init-message-so
May 15, 2026
Merged

feat:Deploy TON Core pool via stub-with-SETCODE init message#138
mrnkslv merged 7 commits into
release/nodectl/v0.5.0from
feature/sma-90-deploy-ton-core-pool-via-stub-with-setcode-init-message-so

Conversation

@mrnkslv
Copy link
Copy Markdown
Contributor

@mrnkslv mrnkslv commented May 12, 2026

Summary

Fix SMA-90 — TON Core nominator pools deployed by nodectl were rejected by Tonscan as invalid state init of contract. This PR introduces a per-slot deploy mode that produces a Tonscan-recognized StateInit (bootstrap stub + SETCODE on first run, mirroring the canonical new-pool.fif flow), while keeping legacy behavior available so already-deployed pool addresses remain stable.

Changes

  • Common / config (app_config): new TonCoreDeployMode enum with variants Legacy (full pool code in StateInit.code) and TonscanCompatible (bootstrap code + SETCODE). Per-slot deploy_mode field on TonCorePoolConfig (JSON key kept as deploy_layout for compatibility). #[non_exhaustive], FromStr driven by serde, snake_case + alias wire strings.
  • Defaults split so the bug is fixed for new pools without breaking existing ones:
    • Persisted config missing the field → Legacy (stable derived addresses for pools deployed by older nodectl).
    • POST /v1/pools/core / nodectl config pool add core omitting deploy mode → TonscanCompatible (default_new_pool_deploy_mode).
  • Contracts (ton_core_nominator): dual StateInit paths and layout-aware address derivation. Bootstrap stub is built from named TVM opcode constants (OP_SETCP0, OP_ACCEPT, OP_PUSHREF, OP_SETCODE). Unit test asserts stub references the full pool code.
  • Service / REST: PoolAddCoreRequest and TonCorePoolSlotDto carry deploy_layout; OpenAPI registers TonCoreDeployMode so $refs resolve. Handler tests cover (a) default-on-create is tonscan, (b) explicit tonscan round-trips, (c) persisted-without-field reads as legacy.
  • CLI: nodectl config pool add core --deploy-mode <legacy|tonscan> (alias --deploy-layout); value parser is clap::value_parser!(TonCoreDeployMode) (single source of truth with serde).
  • Docs: README.md and CHANGELOG.md document the field, the address-derivation impact, and the recommendation to use tonscan for new pools.

Copilot AI review requested due to automatic review settings May 12, 2026 13:31
@linear
Copy link
Copy Markdown

linear Bot commented May 12, 2026

SMA-90

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a configurable TONCore pool deployment layout (embedded_code vs activate_upgrade) and threads it through configuration, address/state-init derivation in the contracts layer, the node-control service HTTP API/OpenAPI, and nodectl CLI usage.

Changes:

  • Introduces TonCoreDeployLayout and persists it per TONCore pool slot in config/DTOs (defaulting to embedded_code).
  • Updates TONCore pool address derivation and StateInit construction to support both full embedded code and a stub + SETCODE activation flow.
  • Extends service/CLI plumbing and tests to pass/serialize/validate deploy_layout end-to-end.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/node-control/service/src/runtime_config.rs Passes deploy_layout into TONCore address derivation and wrapper initialization.
src/node-control/service/src/http/http_server_task.rs Registers TonCoreDeployLayout in OpenAPI schema components.
src/node-control/service/src/http/config_handlers.rs Adds deploy_layout to TONCore slot DTO/request and carries slot config through slot DTO fetch.
src/node-control/service/src/http/config_handlers_tests.rs Extends HTTP handler tests to cover default + persisted deploy_layout.
src/node-control/README.md Documents the new per-slot deploy_layout and its impact on derived addresses.
src/node-control/contracts/src/nominator/ton_core_nominator.rs Implements dual StateInit layouts and makes address derivation layout-aware (with unit tests).
src/node-control/contracts/src/nominator/ton_core_nominator_router.rs Makes router construction layout-aware when deriving per-slot pools from init params.
src/node-control/common/src/app_config.rs Adds TonCoreDeployLayout, stores it in TonCorePoolConfig, and adds serde roundtrip tests.
src/node-control/commands/src/commands/nodectl/utils.rs Uses layout-aware TONCore pool resolution when deriving addresses from config.
src/node-control/commands/src/commands/nodectl/deploy_cmd.rs Passes per-slot deploy_layout into TONCore deploy target resolution.
src/node-control/commands/src/commands/nodectl/config_pool_cmd.rs Adds --deploy-layout flag for TONCore pool config and parses/serializes it in API calls and views.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +638 to 639
slot_jobs.push((idx, cfg.clone(), cached));
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@Keshoid
Copy link
Copy Markdown
Contributor

Keshoid commented May 14, 2026

Naming: TonCoreDeployLayoutTonCoreDeployMode + variant rename

This field is operator-facing (lives in JSON config and a CLI flag), so the variant names should describe what the operator gets, not how the StateInit is built. EmbeddedCode / ActivateUpgrade describe the mechanism — an operator scanning the config can't tell which one fixes the tonscan parsing problem this PR is solving.

Proposal:

#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub enum TonCoreDeployMode {
    #[default]
    #[serde(rename = "legacy")]
    Legacy,
    #[serde(
        rename = "tonscan",
        alias = "tonscan_compatible",
        alias = "tonscan-compatible",
    )]
    TonscanCompatible,
}

Why these names:

  • Mode reads more naturally than Layout for a user-facing knob.
  • Legacy honestly signals "kept only for pools that were already deployed by older nodectl; don't pick this for new deployments" — which is the real semantic, since the default has to stay backwards-compatible with already-derived addresses.
  • TonscanCompatible tells the operator the one thing they need to know to pick correctly.
  • The serde tags (legacy / tonscan) keep typing short in CLI and JSON; the long form tonscan_compatible is still accepted as an alias for readers who copy from the Rust name.

Side benefit: with serde owning the string forms, parse_cli_deploy_layout in config_pool_cmd.rs can be removed and replaced with clap::value_parser!(TonCoreDeployMode) (assuming a FromStr impl driven by serde). One source of truth for accepted values, and the help text can list them programmatically.

Help text would then read something like:

--deploy-mode <MODE>
    Deploy mode for new TONCore pool slots.
    `tonscan` — deploy via stub + SETCODE; required for tonscan to recognise the pool (recommended for new deployments).
    `legacy`  — full pool code embedded directly in StateInit (kept for compatibility with pools already deployed by older nodectl).
    [default: legacy]

If the team decides to flip the default to tonscan for new pools, only that line and the #[default] attribute change.

@Keshoid
Copy link
Copy Markdown
Contributor

Keshoid commented May 14, 2026

Default for new TONCore pool creation should be the tonscan-compatible mode

Right now every entry point defaults to the embedded-code layout — #[default] on the enum, #[serde(default)] on PoolAddCoreRequest.deploy_layout, and self.deploy_layout.unwrap_or_default() in the CLI handler. Net effect: a fresh nodectl config pool add core … or POST /v1/pools/core without an explicit flag produces exactly the StateInit shape tonscan rejects — i.e. the bug SMA-90 is meant to fix is still the out-of-the-box behavior.

The enum's #[default] can't simply be flipped, though: it's also what fills the field in when an existing JSON config loaded by an older nodectl version is re-read with the new struct. Those pools were deployed with the legacy layout, and their derived addresses must keep matching, so a missing field has to read as legacy.

Proposal — split "default at deserialization" from "default at creation":

// Enum: #[default] stays Legacy so old configs keep deriving the same address.
#[derive(Default,)]
pub enum TonCoreDeployMode {
    #[default]
    Legacy,
    TonscanCompatible,
}

// CLI (config_pool_cmd.rs):
let deploy_mode = self.deploy_mode.unwrap_or(TonCoreDeployMode::TonscanCompatible);

// REST (config_handlers.rs):
fn default_new_pool_deploy_mode() -> TonCoreDeployMode { TonCoreDeployMode::TonscanCompatible }

#[derive(Deserialize,)]
pub struct PoolAddCoreRequest {#[serde(default = "default_new_pool_deploy_mode")]
    pub deploy_mode: TonCoreDeployMode,
}

Result:

  • Existing JSON configs without the field → reads as Legacy → addresses stable for already-deployed pools.
  • New pool via CLI or REST without an explicit value → TonscanCompatible → tonscan recognises it without the operator needing to know about the knob.

Worth calling out this split explicitly in the CHANGELOG entry too.

@Keshoid
Copy link
Copy Markdown
Contributor

Keshoid commented May 14, 2026

PR description advertises files that aren't in the diff

The PR summary's "Scripts / docs" section mentions testnet_toncore_activate_upgrade_deploy.sh and run_testnet_toncore_demo.py, but neither file appears in the diff. Could you either add them or trim the description? It's confusing for reviewers to figure out which list is authoritative.

provider: Arc<dyn ContractProvider>,
pools: [Option<TonCoreInitParams>; 2],
validator_address: &MsgAddressInt,
deploy_layouts: [TonCoreDeployLayout; 2],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TonCoreNominatorRouter::from_state_init has no callers in the tree — runtime_config.rs:678 builds the router via from_wrappers([w0, w1]). The new [TonCoreDeployLayout; 2] parameter is dead code.

Either delete the function (the from_wrappers path covers the production use case) or add a test that exercises it so the per-slot layout wiring is verified end-to-end. Leaving it as-is means future refactors have to keep updating an unused signature.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread src/node-control/README.md Outdated
- `pools` — JSON array of **exactly two** elements: `pools[0]` and `pools[1]`. Each element is either `null` (slot unused) or an object:
- `address` — optional deployed pool contract address (raw / base64url string). When omitted but `params` is set, the address is derived from the validator and `params` (see `resolve_toncore_pool` / `toncore_pool_address_and_state` in the contracts crate). If `address` is set, it must match the derived address when `params` is present.
- `address` — optional deployed pool contract address (raw / base64url string). When omitted but `params` is set, the address is derived from the validator and `params` (see `resolve_toncore_pool` / `toncore_pool_address_and_state` in the contracts crate). If `address` is set, it must match the derived address when `params` is present. Derivation depends on `deploy_layout`: `embedded_code` and `activate_upgrade` yield different addresses for the same parameters.
- `deploy_layout` — optional string per slot: `embedded_code` (default — entire pool bytecode in `StateInit.code`) or `activate_upgrade` (minimal bootstrap in `code`, full pool installed via `SETCODE` on first execution — often described as “upgrade on activation”.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small typo — the parenthetical that opens with (minimal bootstrap in code, … never closes. Add the trailing ) after "upgrade on activation".

Also worth adding one sentence here recommending the tonscan-compatible mode for new pools — otherwise an operator reading the config docs has no way to tell which value to pick.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread src/node-control/commands/src/commands/nodectl/config_pool_cmd.rs Outdated
Comment thread src/node-control/CHANGELOG.md Outdated

### Added

- **TONCore pool deploy layout** — per-slot config field `deploy_layout` (`embedded_code` default, or `activate_upgrade` for minimal bootstrap `StateInit.code` with full pool code applied via `SETCODE` on activation). Wired through JSON config, REST (`POST /v1/pools/core`, pool views), and CLI (`config pool add core --deploy-layout …`).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entry describes the new field mechanically but never mentions tonscan — which is the entire reason this feature exists. An operator skimming the changelog has no way to know they should switch modes for new deployments.

Suggestion: add one short sentence explaining the motivation and the recommendation. Something like:

Use activate_upgrade for new pools — tonscan rejects pools deployed with embedded_code as invalid state init of contract, while activate_upgrade matches the canonical new-pool.fif flow that tonscan recognises. Pools already deployed with embedded_code must keep that value (derivation depends on the layout, so changing it would change the pool address).

If the renaming proposal in the PR comment lands, swap the variant names accordingly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread src/node-control/common/src/app_config.rs Outdated
}

impl Default for TonCorePoolConfig {
fn default() -> Self {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manual impl Default is redundant here — every field of TonCorePoolConfig (Option<String>, Option<TonCoreInitParams>, TonCoreDeployLayout) already implements Default. Just derive it:

#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Debug, Default)]
pub struct TonCorePoolConfig {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub address: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub params: Option<TonCoreInitParams>,
    #[serde(default, skip_serializing_if = "skip_serializing_toncore_deploy_layout")]
    pub deploy_layout: TonCoreDeployLayout,
}

…and delete the impl Default for TonCorePoolConfig block. Less code, and the default stays in sync automatically if a new field is added later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread src/node-control/contracts/src/nominator/ton_core_nominator.rs Outdated
Comment thread src/node-control/service/src/http/config_handlers.rs Outdated
Comment thread src/node-control/common/src/app_config.rs Outdated
Comment thread src/node-control/common/src/app_config.rs Outdated
Comment thread src/node-control/commands/src/commands/nodectl/config_pool_cmd.rs
mrnkslv and others added 3 commits May 15, 2026 14:12
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	src/node-control/service/src/http/config_handlers.rs
#	src/node-control/service/src/http/config_handlers_tests.rs
#	src/node-control/service/src/http/http_server_task.rs
@mrnkslv mrnkslv merged commit b591451 into release/nodectl/v0.5.0 May 15, 2026
6 checks passed
@mrnkslv mrnkslv deleted the feature/sma-90-deploy-ton-core-pool-via-stub-with-setcode-init-message-so branch May 15, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants