feat:Deploy TON Core pool via stub-with-SETCODE init message#138
Conversation
There was a problem hiding this comment.
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
TonCoreDeployLayoutand persists it per TONCore pool slot in config/DTOs (defaulting toembedded_code). - Updates TONCore pool address derivation and
StateInitconstruction to support both full embedded code and a stub +SETCODEactivation flow. - Extends service/CLI plumbing and tests to pass/serialize/validate
deploy_layoutend-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.
| slot_jobs.push((idx, cfg.clone(), cached)); | ||
| } |
…core-pool-via-stub-with-setcode-init-message-so
|
Naming: 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. 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:
Side benefit: with serde owning the string forms, Help text would then read something like: If the team decides to flip the default to |
|
Default for new TONCore pool creation should be the tonscan-compatible mode Right now every entry point defaults to the embedded-code layout — The enum's 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:
Worth calling out this split explicitly in the CHANGELOG entry too. |
|
PR description advertises files that aren't in the diff The PR summary's "Scripts / docs" section mentions |
| provider: Arc<dyn ContractProvider>, | ||
| pools: [Option<TonCoreInitParams>; 2], | ||
| validator_address: &MsgAddressInt, | ||
| deploy_layouts: [TonCoreDeployLayout; 2], |
There was a problem hiding this comment.
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.
| - `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”. |
There was a problem hiding this comment.
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.
|
|
||
| ### 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 …`). |
There was a problem hiding this comment.
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_upgradefor new pools — tonscan rejects pools deployed withembedded_codeasinvalid state init of contract, whileactivate_upgradematches the canonicalnew-pool.fifflow that tonscan recognises. Pools already deployed withembedded_codemust 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.
| } | ||
|
|
||
| impl Default for TonCorePoolConfig { | ||
| fn default() -> Self { |
There was a problem hiding this comment.
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.
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
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 +SETCODEon first run, mirroring the canonicalnew-pool.fifflow), while keeping legacy behavior available so already-deployed pool addresses remain stable.Changes
app_config): newTonCoreDeployModeenum with variantsLegacy(full pool code inStateInit.code) andTonscanCompatible(bootstrap code +SETCODE). Per-slotdeploy_modefield onTonCorePoolConfig(JSON key kept asdeploy_layoutfor compatibility).#[non_exhaustive],FromStrdriven by serde, snake_case + alias wire strings.Legacy(stable derived addresses for pools deployed by older nodectl).POST /v1/pools/core/nodectl config pool add coreomitting deploy mode →TonscanCompatible(default_new_pool_deploy_mode).ton_core_nominator): dualStateInitpaths 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.PoolAddCoreRequestandTonCorePoolSlotDtocarrydeploy_layout; OpenAPI registersTonCoreDeployModeso$refs resolve. Handler tests cover (a) default-on-create istonscan, (b) explicittonscanround-trips, (c) persisted-without-field reads aslegacy.nodectl config pool add core --deploy-mode <legacy|tonscan>(alias--deploy-layout); value parser isclap::value_parser!(TonCoreDeployMode)(single source of truth with serde).README.mdandCHANGELOG.mddocument the field, the address-derivation impact, and the recommendation to usetonscanfor new pools.