From c00be96b9e5abb280cab3ce7c9a860ded7a1083c Mon Sep 17 00:00:00 2001 From: Evangel90 Date: Sun, 20 Sep 2026 20:08:58 +0100 Subject: [PATCH 1/3] Fill contracts, validator, quickstart pages; add troubleshooting and CODEOWNERS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the stubs with content taken from merged work rather than waiting on testnet-1. Everything not yet real (testnet-1 RPC URL, peers, genesis, release version, WKASH and vesting addresses) stays a marked TBD. - contracts.md: all 10 genesis preinstalls with addresses copied from contracts/preinstalls/*.json and cosmos/evm v0.7.3 DefaultPreinstalls (incl. EIP-2935 history storage from the pinned go-ethereum); why the SenderCreators must ship with the EntryPoints; precompile map with ICompliance at 0x...0900 (interface copied from x/compliance/precompile/ICompliance.sol), p256 at 0x...0100 and the werc20 WKASH precompile; WKASH.sol as a post-genesis Create2 deploy (address TBD); D12 vesting design from TOKENOMICS §7 with no addresses claimed; a cast-based verification recipe checked against a pinned codeHash. - run-a-validator.md: what `konstellationd init` actually writes (esp everywhere, evm-chain-id reconciled from genesis, mempool.type = "app"), the chain-id invariant with the exact refusal messages captured from the a9051f6 binary, ports as init writes them, cosmovisor layout and the infra systemd unit, never-build-on-a-validator / one-key-one-node, Horcrux, gentx parameters from networks/testnet-1/README.md, testnet-1 join table as placeholders linking to the networks repo. - troubleshooting.md (new, in the sidebar): the EVM-tx-invisible-to-eth_* case with the tx_search query, the module-account transfer now refused at submission and the residual internal-call case, "address is frozen", circuit-breaker refusals and what tripping MsgTransfer / MsgEthereumTx means, and the fixed PR #8 restart panic as an old-binary marker. - quickstart.md: MetaMask parameters for testnet-1 (56671 / KASH / 18, RPC TBD) and the local dev chain (56670, local_node.sh -y, :8545, dev0), first transaction with cast and the CLI. - CODEOWNERS copied from .github/CODEOWNERS.template as networks did. Hosting/domain is untouched: docusaurus.config.js `url` is still the placeholder. `npm ci && npm run build` passes with onBrokenLinks: 'throw'. Co-Authored-By: Claude Opus 5 (1M context) --- CODEOWNERS | 10 ++ README.md | 4 +- docs/contracts.md | 221 ++++++++++++++++++++++--- docs/quickstart.md | 135 ++++++++++++--- docs/run-a-validator.md | 352 +++++++++++++++++++++++++++++++++++----- docs/troubleshooting.md | 204 +++++++++++++++++++++++ sidebars.js | 1 + 7 files changed, 839 insertions(+), 88 deletions(-) create mode 100644 CODEOWNERS create mode 100644 docs/troubleshooting.md diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..bd195d2 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,10 @@ +# Copied from the org `.github` repo's CODEOWNERS.template (GitHub has no +# org-wide default CODEOWNERS; every repo carries its own — ENGINEERING.md §17). +# +# Ownership is shared across engineering, not assigned to individuals +# (ENGINEERING.md §17), so every path is owned by every current engineer. No +# GitHub team exists yet; swap this list for a team once one is created. +# Update the usernames if org membership changes +# (`gh api orgs/Konstellation-Network/members --jq '.[].login'`). + +* @defidevrel @Evangel90 @Levi-Ojukwu @OlolaJaco @Psalmuel01 diff --git a/README.md b/README.md index c8bace2..cdaf314 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,10 @@ docs/ │ ├── rpc-endpoints.md │ ├── contracts.md │ ├── run-a-validator.md -│ └── upgrades.md +│ ├── upgrades.md +│ └── troubleshooting.md ├── src/css/custom.css +├── CODEOWNERS ├── docusaurus.config.js └── sidebars.js ``` diff --git a/docs/contracts.md b/docs/contracts.md index e8fe958..169dc2c 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -8,43 +8,210 @@ sidebar_position: 3 Konstellation ships a set of contracts preinstalled in genesis at their canonical, deterministic addresses, so existing wallet SDKs and tooling that -hard-code these addresses work unmodified. The source of truth is -[`contracts/preinstalls/`](https://github.com/konstellation-network/contracts/tree/main/preinstalls); -this table must stay in sync with it and with the bytecode actually baked -into `networks//genesis.json`. +hard-code these addresses work unmodified. Every address on this page is +identical on `testnet-1`, `konstellation-1` and a local dev chain. + +Two sources of truth, in order: + +1. [`contracts/preinstalls/*.json`](https://github.com/konstellation-network/contracts/tree/main/preinstalls) + — the pinned deployed bytecode for Konstellation's own preinstalls, each + with a `codeHash` guard. `konstellation/app/preinstalls/` carries verbatim + copies (Go cannot import a Foundry repo) and re-verifies every `codeHash` + at `konstellationd init`, so an out-of-step copy fails before a genesis + is written. +2. `cosmos/evm`'s `x/vm/types/preinstall.go` (`DefaultPreinstalls`) for the + five upstream defaults, which the chain installs unchanged. + +If this table and those files ever disagree, the files win — file a docs bug. + +## Genesis preinstalls + +A preinstall is bytecode placed at an address in `genesis.json`. **Its +constructor never runs.** Anything a constructor would have deployed or +initialised has to be a preinstall too — which is why the ERC-4337 +`SenderCreator`s are in the list (see below). + +### From `cosmos/evm` `DefaultPreinstalls` + +| Contract | Address | Notes | +|---|---|---| +| Create2 (deterministic-deployment proxy) | `0x4e59b44847b379578588920ca78fbf26c0b4956c` | The minimal deterministic-deployment proxy (Arachnid's) that Foundry uses by default for CREATE2 deployments. Not the same contract as `Create2Deployer` below. | +| Multicall3 | `0xcA11bde05977b3631167028862bE2a173976CA11` | Canonical cross-chain address. Also pinned in `contracts/preinstalls/Multicall3.json`, byte-identical. | +| Permit2 | `0x000000000022D473030F116dDEE9F6B43aC78BA3` | Uniswap Permit2. Also pinned in `contracts/preinstalls/Permit2.json`, byte-identical. | +| Safe singleton factory | `0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7` | What Safe's deployment tooling uses to place its singletons at the same address on every chain. | +| EIP-2935 history storage | `0x0000F90827F1C53a10cb7A02335B175320002935` | Serves historical block hashes from state (Prague system contract). | + +### Konstellation additions (`contracts/preinstalls/`) | Contract | Address | Notes | |---|---|---| -| Multicall3 | `0xcA11bde05977b3631167028862bE2a173976CA11` | Canonical cross-chain address | -| Permit2 | _see `contracts/preinstalls/Permit2.json`_ | | -| EntryPoint v0.7 | _see `contracts/preinstalls/EntryPointV07.json`_ | ERC-4337 | -| EntryPoint v0.8 | _see `contracts/preinstalls/EntryPointV08.json`_ | ERC-4337 | -| Create2Deployer | _see `contracts/preinstalls/Create2Deployer.json`_ | | - -:::note -Addresses beyond Multicall3 are intentionally not hard-coded here yet — -copy them from `contracts/preinstalls/*.json` when this page is next -updated, rather than retyping them from memory, so a stale doc can't -silently diverge from the genesis blob it describes. -::: +| EntryPoint v0.7 | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | ERC-4337. Requires `SenderCreatorV07` below. | +| SenderCreator v0.7 | `0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C` | Companion of EntryPoint v0.7; see [Why the SenderCreators](#why-the-sendercreators-are-preinstalled). | +| EntryPoint v0.8 | `0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108` | ERC-4337. Requires `SenderCreatorV08` below. Shipped alongside v0.7 because wallets differ on which version they target. | +| SenderCreator v0.8 | `0x449ED7C3e6Fee6a97311d4b55475DF59C44AdD33` | Companion of EntryPoint v0.8. Its own `entryPoint` immutable is EntryPoint v0.8's address, so the two must ship together. | +| Create2Deployer | `0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2` | The hardhat-deploy / OpenZeppelin Defender CREATE2 deployer (`deploy(uint256,bytes32,bytes)`, `computeAddress(bytes32,bytes32)`, `computeAddressWithDeployer(bytes32,bytes32,address)`). Distinct from the minimal Create2 factory above; both are installed so tooling expecting either one works. | + +That is 10 preinstalls in total. The genesis loader refuses address +collisions between the two sets and checks that each EntryPoint's bytecode +literally contains its SenderCreator's address, so a half re-pin fails at +`konstellationd init`, not at block 1. + +### Why the SenderCreators are preinstalled + +On Ethereum each `EntryPoint`'s constructor does `new SenderCreator()` — a +`CREATE` from the EntryPoint at nonce 1 — and stores the resulting address as +an immutable in its runtime bytecode. On Konstellation the EntryPoint is +placed by genesis, so that constructor never runs and nothing exists at the +address the bytecode points to. Without the companion, every UserOperation +carrying `initCode` and every `getSenderAddress()` call reverts with empty +data, and the address (`CREATE(entryPoint, 1)`) cannot be recreated +post-genesis because the EntryPoint's nonce is never consumed that way +again. So both SenderCreators are pinned from mainnet and preinstalled next +to their EntryPoints. This was found in review before the first genesis; the +pairing is now enforced in code and covered by tests. + +## Precompiles + +Precompiles are not deployed bytecode: they are Go code in the node, exposed +at fixed addresses. `eth_getCode` on the static ones below returns empty +(the werc20 precompile is the exception — `x/erc20` registers placeholder +ERC-20 bytecode at its address so `extcodesize`-style "is this a contract" +checks pass). `cosmos/evm`'s own precompiles occupy `0x…0100`–`0x…0807`; +Konstellation's range starts at `0x…0900`. + +| Precompile | Address | Notes | +|---|---|---| +| p256 (EIP-7212 / RIP-7212) | `0x0000000000000000000000000000000000000100` | secp256r1 signature verification, from `cosmos/evm`. What passkey / WebAuthn smart accounts need. | +| bech32 | `0x…0400` | `cosmos/evm` | +| staking, distribution, ICS20, vesting, bank, gov, slashing, ICS02 | `0x…0800`–`0x…0807` | `cosmos/evm` | +| WKASH (werc20 native precompile) | `0xD4949664cD82660AaE99bEdc034a0deA8A0bd517` | The native token as an ERC-20 interface, registered with `x/erc20` at genesis. Upstream default address, kept so tooling assumptions carry over. This is a precompile, not deployed bytecode — distinct from the `WKASH.sol` contract below. | +| **ICompliance** | `0x0000000000000000000000000000000000000900` | Konstellation. Read-only view of the chain's compliance lists (below). | + +### `ICompliance` at `0x…0900` + +The chain itself refuses every transaction that *involves* a frozen address +— EVM and Cosmos, native KASH included — before execution (see +[Troubleshooting](/troubleshooting#address-is-frozen)). That check cannot +see internal calls, so a contract that forwards user-supplied addresses, or +that wants to serve only verified addresses, calls this precompile. All +functions are `view` and cost only the precompile's base read gas. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Konstellation compliance precompile +/// @notice Read-only view of the chain's compliance lists, at a fixed address +/// (COMPLIANCE_PRECOMPILE_ADDRESS). The chain itself refuses every +/// transaction that involves a frozen address; contracts that need to +/// refuse *internal* transfers to frozen addresses, or that want to +/// serve only verified addresses, call this. +/// @dev All functions are `view` and cost the precompile's base read gas. +interface ICompliance { + /// @notice True if `account` is on the allow ("verified") list. + function isVerified(address account) external view returns (bool); + + /// @notice True if `account` is on the block ("frozen") list and, for an + /// emergency freeze, the freeze has not yet expired. + function isFrozen(address account) external view returns (bool); + + /// @notice Both flags, plus the emergency-freeze expiry as a unix time. + /// `frozenUntil` is 0 when not frozen or when the freeze is + /// permanent. + function status(address account) + external + view + returns (bool verified, bool frozen, uint64 frozenUntil); +} +``` + +Source: `konstellation/x/compliance/precompile/ICompliance.sol` (the ABI is +`abi.json` next to it). Semantics worth knowing: + +- Two lists. The **blocklist** (`isFrozen`) is what the chain enforces; the + **allowlist** (`isVerified`) is opt-in — the chain never requires an + address to be verified to transact, only being frozen stops a tx. +- List changes come from the compliance authority through a 24-hour on-chain + timelock, or as an emergency freeze that takes effect immediately and + auto-expires after the timelock unless ratified; governance can override + any entry. Every action is an on-chain event. (Timelocks are 60 s on a + `local_node.sh` dev chain.) +- Freezing an EOA also clears any EIP-7702 delegation it carried, so a + smart-account wallet installed before the freeze cannot be driven by a + relayer afterwards. + +```solidity +ICompliance constant COMPLIANCE = + ICompliance(0x0000000000000000000000000000000000000900); + +function transfer(address to, uint256 amount) external { + require(!COMPLIANCE.isFrozen(to), "recipient is frozen"); + // ... +} +``` ## Account abstraction -- **ERC-4337:** EntryPoint v0.7 and v0.8 are preinstalled (table above). You - still need to run or use a bundler (Rundler, Alto, Skandha) and, if you - want gas sponsorship, a paymaster — neither ships with the chain. -- **Passkey / WebAuthn accounts:** supported via the **p256 precompile** - (EIP-7212), included in `cosmos/evm`. +- **ERC-4337:** EntryPoint v0.7 and v0.8 are preinstalled with their + SenderCreators (table above). You still need a bundler (Rundler, Alto, + Skandha) and, for gas sponsorship, a paymaster — neither ships with the + chain. +- **Passkey / WebAuthn accounts:** the p256 precompile at `0x…0100` is + active from genesis. +- **EIP-7702:** delegations are honoured by the EVM. Note the compliance + interaction above: an emergency or scheduled freeze resets the delegation + on the frozen EOA (only the 23-byte `0xef0100‖addr` code, never real + contract bytecode, and storage stays, so one new authorization restores + the wallet once lifted). + +## WKASH (post-genesis) + +`contracts/src/WKASH.sol` is the wrapped native token as ordinary +deployed bytecode (KASH / `esp`, 18 decimals). It is deliberately **not** a +genesis preinstall: it is deployed after genesis through `Create2Deployer` +with a fixed salt, which gives a predictable address without baking +not-yet-audited bytecode into `genesis.json` forever. + +| Contract | Address | +|---|---| +| WKASH | **TBD — see the `contracts` repo** once deployed on `testnet-1` | + +Solidity that only needs the native token as an ERC-20 can use the werc20 +precompile at `0xD4949664cD82660AaE99bEdc034a0deA8A0bd517` today. ## Vesting Vesting uses **Solidity vesting contracts** (`contracts/src/vesting/`), not -`x/auth` vesting accounts — a deliberate decision recorded in -`ENGINEERING.md §11 (D12)`. +`x/auth` vesting accounts — decision D12 in `ENGINEERING.md §11`, taken +because a peer chain attributed its exploit to a flaw touching vesting +accounts and balance handling. Keeping vesting in audited application code +keeps consensus-critical account logic stock. + +The design the contracts implement (`TOKENOMICS.md §7`): + +| Schedule | Shape | Revocable | +|---|---|---| +| Founding team & early contributors | 12-month cliff, then linear over 36 months (4 years total); one contract per person | **yes** — the foundation multisig can revoke a departing member's grant: unvested tokens return to the treasury, vested tokens stay with the beneficiary | +| Treasury (foundation) | 20 % liquid at genesis, remainder linear over 48 months | no | +| Community & developers | released programmatically over 5 years, front-loaded | no | + +The contracts are being built now. No addresses exist yet; on `testnet-1` +they are exercised as ordinary contract deployments, not genesis state. This +page will list the deployed instances once `networks//` records them. ## Verification -`contracts/test/GenesisBytecode.t.sol` asserts the genesis-embedded bytecode -for every preinstall matches the compiled artifact. If you're verifying a -contract against a block explorer, that test — not this page — is the -authority on what should be deployed. +`contracts/test/GenesisBytecode.t.sol` asserts that every +`preinstalls/*.json` is internally consistent (`keccak256(code) == +codeHash`); `contracts/script/VerifyPreinstalls.s.sol` checks the same pins +against a live Ethereum mainnet RPC. On a running Konstellation node, +`eth_getCode` at each preinstall address must byte-match the pin: + +```bash +# keccak256 of the live code must equal "codeHash" in the matching preinstalls/*.json +cast keccak "$(cast code 0x0000000071727De22E5E9d8BAf0edAc6f37da032 --rpc-url http://localhost:8545)" +jq -r .codeHash contracts/preinstalls/EntryPointV07.json +``` + +If you are verifying a contract against a block explorer, those tests — not +this page — are the authority on what should be deployed. diff --git a/docs/quickstart.md b/docs/quickstart.md index 5eac8fc..67a2ceb 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -8,10 +8,11 @@ sidebar_position: 1 # Quickstart :::caution Pre-testnet -Konstellation has not launched a public network yet. The values on this page -are placeholders until `testnet-1` genesis is finalized in the [`networks` -repo](https://github.com/konstellation-network/networks). Do not use them for -anything other than local development. +Konstellation has not launched a public network yet. The `testnet-1` RPC +and explorer URLs on this page are placeholders until the genesis is +published in the [`networks` +repo](https://github.com/konstellation-network/networks). Everything else — +chain ids, token, addresses, the local dev chain — is final. ::: ## Network parameters @@ -28,41 +29,129 @@ anything other than local development. | Base denom | `esp` (18 decimals; 1 KASH = 10^18 `esp`) | | Bech32 prefix | `kons` | +The EIP-155 ids are enforced by the node: a real network's id is used only by +that network, and a local chain can never be given one, so a transaction +signed for a dev chain cannot replay on `testnet-1` or mainnet. + ## Add Konstellation to MetaMask -TODO once `testnet-1` RPC is live — see [RPC Endpoints](/rpc-endpoints). +MetaMask → **Settings → Networks → Add a network → Add a network manually**. +Rabby and other EIP-3085 wallets take the same values. + +### testnet-1 + +| Field | Value | +|---|---| +| Network name | Konstellation Testnet | +| New RPC URL | **TBD** — published in `networks/testnet-1/chain.json` when the network launches; see [RPC Endpoints](/rpc-endpoints) | +| Chain ID | `56671` | +| Currency symbol | `KASH` | +| Currency decimals | 18 | +| Block explorer URL | **TBD** — Blockscout, see the [`explorer`](https://github.com/konstellation-network/explorer) repo | + +Test KASH comes from the faucet (**TBD** — [`faucet`](https://github.com/konstellation-network/faucet) repo). + +### Local dev chain -1. Open MetaMask → **Settings → Networks → Add network manually**. -2. Fill in: - - **Network name:** Konstellation Testnet - - **New RPC URL:** _TBD — testnet-1 has not launched_ - - **Chain ID:** `56671` - - **Currency symbol:** KASH - - **Block explorer URL:** _TBD — see [`explorer`](https://github.com/konstellation-network/explorer)_ +| Field | Value | +|---|---| +| Network name | Konstellation Local | +| New RPC URL | `http://localhost:8545` | +| Chain ID | `56670` | +| Currency symbol | `KASH` | +| Currency decimals | 18 | +| Block explorer URL | none | + +Programmatically (`wallet_addEthereumChain`), for the local chain: + +```json +{ + "chainId": "0xdd5e", + "chainName": "Konstellation Local", + "nativeCurrency": { "name": "KASH", "symbol": "KASH", "decimals": 18 }, + "rpcUrls": ["http://localhost:8545"] +} +``` + +`0xdd5e` is 56670; `testnet-1`'s `56671` is `0xdd5f`, mainnet's `5667` is `0x1623`. ## Run a local dev chain -The `konstellation` repo ships a single-node dev chain for testing against -before any public network exists: +The `konstellation` repo ships a single-node dev chain. It needs Go (see +`go.mod` for the version) and `jq`. ```bash -cd ~/src/konstellation +git clone https://github.com/Konstellation-Network/konstellation.git +cd konstellation ./local_node.sh -y ``` -This starts: +`-y` wipes any previous chain data in `~/.konstellationd` without asking. +The script builds and installs `konstellationd`, runs `konstellationd init` +with chain-id `konstellation-local-1`, funds the dev accounts in genesis, and +starts the node with every API enabled: -- JSON-RPC at `http://localhost:8545` (EIP-155 chain ID `56670`) -- Prometheus metrics at `:26660` - -A funded dev account (`dev0`) is pre-seeded from a public, well-known mnemonic -— never use it for anything beyond local testing: +| | | +|---|---| +| EVM JSON-RPC | `http://localhost:8545` (namespaces `eth,txpool,personal,net,debug,web3`) | +| EVM WebSocket | `ws://localhost:8546` | +| CometBFT RPC | `http://localhost:26657` | +| Cosmos REST / gRPC | `http://localhost:1317` / `localhost:9090` | +| Prometheus metrics | `:26660` | +| EIP-155 chain id | `56670` | +| Block time | ~1 s (`timeout_commit = "1s"`); node `minimum-gas-prices` and `evm.min-tip` are 0 | + +The dev chain is a real `konstellationd`: the same genesis preinstalls, +precompiles, compliance module and circuit breaker as `testnet-1`, with +governance and compliance timelocks shortened to seconds so they can be +exercised in a session (voting period 30 s, compliance timelocks 60 s, the +validator key as the compliance authority). + +### Dev accounts + +Funded accounts `dev0`–`dev3` are created from **public, well-known +mnemonics** written into `local_node.sh` — never use them for anything +beyond local testing. `dev0`: ``` 0xC6Fe5D33615a1C52c08018c47E8Bc53646A0E101 ``` +Its private key is in `local_node.sh` (search for `dev0`). The accounts are +also in the `konstellationd` test keyring, so the CLI can sign with them: + +```bash +konstellationd keys list --keyring-backend test +``` + ## Send your first transaction -TODO: walk through a bank send and an EVM transfer against the local dev -chain once the dev-chain tooling stabilizes. +Against the local chain, with [Foundry](https://getfoundry.sh)'s `cast`: + +```bash +# balance of dev0, in KASH +cast balance 0xC6Fe5D33615a1C52c08018c47E8Bc53646A0E101 --rpc-url http://localhost:8545 --ether + +# send 1 KASH from dev0 to another address (the dev0 key is in local_node.sh) +cast send --value 1ether \ + --rpc-url http://localhost:8545 \ + --private-key + +# the same asset, seen from the Cosmos side (1 KASH = 1000000000000000000esp) +konstellationd query bank balances $(konstellationd keys show dev0 -a --keyring-backend test) +``` + +`cast`'s `1ether` is 10^18 base units, which on Konstellation is 1 KASH. A +Cosmos-side bank send works the same way and burns the same EIP-1559 base +fee. The base fee starts at `1000000000esp` per gas (1 gwei-equivalent) in +genesis and adjusts from block 1, decaying toward 0 while the chain is idle; +a transaction priced below the current base fee is rejected, so price at the +genesis value to be safe: + +```bash +konstellationd tx bank send dev0 1000000000000000000esp \ + --keyring-backend test --gas-prices 1000000000esp -y +``` + +Next: the [preinstalled contracts](/contracts) every address on the chain +can rely on, or [run a node](/run-a-validator). diff --git a/docs/run-a-validator.md b/docs/run-a-validator.md index edb838c..7205652 100644 --- a/docs/run-a-validator.md +++ b/docs/run-a-validator.md @@ -7,40 +7,223 @@ sidebar_position: 4 # Run a Validator :::caution Pre-testnet -There is no validator set to join yet. This page collects the operational -requirements decided so far so external operators have something to review -before testnet-1 opens (`ENGINEERING.md §15`, phase 6). Expect gaps — the -plan explicitly relies on the first external operators finding them. +There is no validator set to join yet. Everything on this page about the +binary's behaviour is real and verified against the current `konstellationd`; +the `testnet-1` specifics (peers, genesis, release version) are placeholders +until `networks/testnet-1` publishes them. External operators are invited +onto `testnet-1` precisely so they can find the gaps in this page +(`ENGINEERING.md §15`, phase 6) — if something here does not work as +written, that is a docs bug, please report it. ::: +`konstellationd` is one binary for every network. It does not know which +network it is on beyond the chain-id checks described below: everything that +differs between `testnet-1` and `konstellation-1` lives in the network's +`genesis.json` (in the [`networks`](https://github.com/konstellation-network/networks) +repo) and in your own configuration. + ## Hardware -| Spec | Requirement | +Disk is the bottleneck: IAVL commit latency is disk-bound, so network block +storage costs block time. + +| Role | Spec | |---|---| -| vCPU | 8–16 | -| RAM | 32–64 GB | -| Storage | 2–4 TB **local NVMe** — network block storage costs block time; IAVL commit latency is disk-bound | -| Network | 1 Gbps | +| Validator / sentry | 8–16 vCPU, 32–64 GB RAM, 2–4 TB **local NVMe**, 1 Gbps | +| Archive node (`pruning = "nothing"`, tracing on) | same; disk grows without bound | + +## Two rules that are never relaxed + +:::danger Never build the binary on a validator +Every release binary comes from CI in the `konstellation` repo, built twice +on independent runners from a signed tag, published only when both checksums +agree, with GitHub build provenance attached. Download the release asset and +verify its SHA256 against the ledger in +[`networks/RELEASES.md`](https://github.com/konstellation-network/networks/blob/main/RELEASES.md) +— never against a chat message. Nothing runs a binary that is not in that +table. +::: -## Topology +:::danger Never run two nodes with the same `priv_validator_key.json` +Double-signing is unrecoverable: 5 % of stake slashed and a permanent +tombstone. This applies during migrations, failovers and testing — restoring +from a snapshot that includes the key, a warm standby, or "just for a +minute" all count. Testnet has no money at stake; the habit is the point. +::: -Sentry architecture is **mandatory** — validators do not take public -inbound connections: +## Ports -- Validator: `pex = false`, `persistent_peers` set only to your own - sentries, no public IP. -- Sentry: `private_peer_ids` set to the validator's node ID so its address - is never gossiped. +Defaults `konstellationd init` writes. Only 26656 needs to be reachable from +the internet, and on a validator behind sentries not even that. -## Key management +| Port | Protocol | File / key | Default bind | +|---|---|---|---| +| 26656 | CometBFT p2p | `config.toml` `[p2p] laddr` | `tcp://0.0.0.0:26656` | +| 26657 | CometBFT RPC | `config.toml` `[rpc] laddr` | `tcp://127.0.0.1:26657` | +| 26660 | Prometheus metrics | `config.toml` `[instrumentation]` | `:26660`, **off** (`prometheus = false`) | +| 9090 | Cosmos gRPC | `app.toml` `[grpc]` | `localhost:9090`, on | +| 1317 | Cosmos REST | `app.toml` `[api]` | `tcp://localhost:1317`, **off** | +| 8545 | EVM JSON-RPC (HTTP) | `app.toml` `[json-rpc] address` | `127.0.0.1:8545`, **off** (`enable = false`) | +| 8546 | EVM JSON-RPC (WebSocket) | `app.toml` `[json-rpc] ws-address` | `127.0.0.1:8546`, off with the above | +| 6060 | pprof | `config.toml` `pprof_laddr` | `localhost:6060` | -Recommended: **horcrux** threshold signing across 3+ regions, or `tmkms` + -YubiHSM2. Whichever you choose: +A validator leaves JSON-RPC and REST off. An RPC node enables `[json-rpc]` +(`enable = true`; default namespaces `eth,net,web3`) and puts a reverse +proxy in front of it — the JSON-RPC server itself has no auth. Never expose +`personal` or `debug` on a public endpoint. -:::danger Never run two nodes with the same `priv_validator_key.json` -Double-signing is unrecoverable and slashes 5% of stake. This applies during -migrations, failovers, and testing — not just steady-state operation. -::: +## Install the binary + +Cosmovisor with auto-download **off**. Upgrade binaries are staged by hand +after checksum verification (see [Upgrades](#upgrades)). + +```sh +VERSION= +curl -fsSLO "https://github.com/Konstellation-Network/konstellation/releases/download/${VERSION}/konstellationd-${VERSION}-linux-amd64" +sha256sum -c <<< " konstellationd-${VERSION}-linux-amd64" +# optional but recommended: GitHub build provenance +gh attestation verify "konstellationd-${VERSION}-linux-amd64" --owner Konstellation-Network + +export DAEMON_NAME=konstellationd +export DAEMON_HOME=$HOME/.konstellationd +export DAEMON_ALLOW_DOWNLOAD_BINARIES=false +export DAEMON_RESTART_AFTER_UPGRADE=true +mkdir -p "$DAEMON_HOME/cosmovisor/genesis/bin" +install -m 0755 "konstellationd-${VERSION}-linux-amd64" "$DAEMON_HOME/cosmovisor/genesis/bin/konstellationd" +ln -s "$DAEMON_HOME/cosmovisor/genesis" "$DAEMON_HOME/cosmovisor/current" +``` + +The default home directory is `~/.konstellationd`. Every command below +accepts `--home` if you keep it elsewhere. + +## `konstellationd init` + +```sh +"$DAEMON_HOME/cosmovisor/genesis/bin/konstellationd" init --chain-id testnet-1 --home "$DAEMON_HOME" +``` + +`init` writes a **complete, correct** `config.toml`, `app.toml`, `client.toml` +and a placeholder `genesis.json` for the chain-id you name. Unlike the +upstream `evmd` reference, there is no `jq`/`sed` patching step afterwards. +If a recipe tells you to patch `stake` → `esp`, set `evm-chain-id`, or set +`mempool.type`, it is wrong for this chain. Specifically: + +- **Every denom is `esp`.** The genesis defaults come from the chain's own + `app.DefaultGenesis()`, not the SDK module defaults, so nothing says + `stake`. `--default-denom` is accepted only as `esp`; anything else is + refused: + + ``` + Error: --default-denom must be "esp": the base denom is fixed at genesis + ``` + +- **`app.toml` `[evm] evm-chain-id` is derived from the genesis.** After + writing `genesis.json`, `init` reads the chain-id back out of it and + reconciles `app.toml`: a known network gets its required EIP-155 id + (`konstellation-1` → `5667`, `testnet-1` → `56671`); any other chain-id + gets the local id `56670`, and never a real network's id. A fresh + `app.toml` is written with the right value directly; one that already + existed (for example created by `konstellationd config set client + chain-id …`, the standard join recipe, or left over from another network) + is patched in place, and the change is printed — nothing is rewritten + silently: + + ``` + genesis profile: testnet/dev (chain-id "testnet-1") + app.toml: evm-chain-id 56670 -> 56671 for chain-id "testnet-1" + ``` + +- **`config.toml` `[mempool] type = "app"`** is written automatically. The + app-side EVM mempool ("Krakatoa") is on, and CometBFT v0.39 refuses to + start with the default `"flood"` when the application supplies a mempool: + + ``` + Error: EVM mempool enabled, but comet-bft has invalid config.toml:mempool.type (want 'app', got 'flood'): error in app.toml + ``` + +- The `genesis profile:` line tells you which governance timings the + genesis got. Only the exact chain-id `konstellation-1` selects the mainnet + profile (3-day voting, 1 000 / 5 000 KASH deposits); `testnet-1`, local and + unknown chain-ids get the fast testnet/dev profile (2 h / 30 min, 10 / 50 + KASH). Irrelevant when you replace the placeholder genesis with a + published one, but it is why a dev chain and mainnet look different. + +Other values `init` leaves at their defaults and you will want to change: +`prometheus = false` (set `true`), `pruning = "default"` (see below), +`minimum-gas-prices = "0esp"`. + +## The chain-id invariant + +**`genesis.json` decides the network. Every per-node value is checked +against it, in both directions.** Nothing you put in a flag or a config +file can steer the node onto a different network than the genesis it holds. + +At startup the node reads the chain-id from `genesis.json` first (honouring +`genesis_file` in `config.toml`). If `--chain-id` or `client.toml` disagree, +it refuses to start and names both files: + +``` +panic: --chain-id "konstellation-1" disagrees with /home/kons/.konstellationd/config/genesis.json ("testnet-1"); the genesis is authoritative +``` + +``` +panic: client.toml chain-id "konstellation-1" disagrees with /home/kons/.konstellationd/config/genesis.json ("testnet-1"); fix client.toml (`konstellationd config set client chain-id testnet-1`) +``` + +The fix is the one in the message: correct `client.toml` (or drop the flag), +never edit the genesis. The EVM id is checked the same way: + +``` +panic: genesis chain-id "testnet-1" requires evm-chain-id 56671, app.toml has 5667: set [evm] evm-chain-id = 56671 in app.toml before starting +``` + +and the check also runs the other way round — a real network's EIP-155 id +belongs only to that network, because a transaction signed under it would +replay there: + +``` +panic: genesis chain-id "my-devnet" is not testnet-1 but app.toml has evm-chain-id 56671, which belongs to testnet-1: a tx signed here would replay there; use 56670 (local) or another unreserved id +``` + +If you see any of these, a file in `config/` is stale (usually +`client.toml` left over from another network, or an `app.toml` copied +between machines). Fix the file the message names; do not re-run `init` on +a node that already has keys, and never edit the genesis to match. + +## Genesis + +Replace the placeholder genesis `init` wrote with the published one and +verify the hash **before** starting. `networks//genesis.sha256` is +checked by CI on every commit to that repo. + +```sh +# testnet-1 — placeholder paths; the files do not exist yet +curl -fsSL -o "$DAEMON_HOME/config/genesis.json" \ + https://raw.githubusercontent.com/Konstellation-Network/networks/main/testnet-1/genesis.json +curl -fsSL https://raw.githubusercontent.com/Konstellation-Network/networks/main/testnet-1/genesis.sha256 \ + | (cd "$DAEMON_HOME/config" && sha256sum -c -) +"$DAEMON_HOME/cosmovisor/genesis/bin/konstellationd" genesis validate --home "$DAEMON_HOME" +``` + +## Topology and keys + +Sentry architecture is **mandatory** — a validator takes no public inbound +connections: + +```toml +# config.toml on the validator +pex = false +persistent_peers = "@10.0.1.5:26656,@10.0.2.5:26656" + +# config.toml on each sentry +private_peer_ids = "" # never gossiped +``` + +Sign with [Horcrux](https://github.com/strangelove-ventures/horcrux) +(threshold signing across 3+ cosigners in different regions — the preferred +option for a chain holding user funds, and what the in-house fleet uses) or +`tmkms` + YubiHSM2. Either way the key never sits on the node itself — and the +one-key-one-node rule above still applies to the cosigner set as a whole. ## Pruning @@ -51,26 +234,121 @@ pruning-keep-recent = "100" pruning-interval = "10" ``` -Run an archive node (`pruning = "nothing"`, tracing enabled) separately if -you need historical queries or explorer support — not on a validator. +An archive node (`pruning = "nothing"`, tracing enabled) is a separate +machine; the explorer needs one because Blockscout requires +`debug_traceTransaction`. Do not run one on a validator. + +## Start + +```sh +cosmovisor run start --home "$DAEMON_HOME" +# elsewhere: +konstellationd status | jq .sync_info +``` + +Run it under systemd. This is the in-house unit +(`infra/ansible/roles/cosmovisor/templates/cosmovisor.service.j2`) with the +variables filled in: + +```ini +[Unit] +Description=Konstellation cosmovisor +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=kons +Group=kons +ExecStart=/usr/local/bin/cosmovisor run start --home /home/kons/.konstellationd +Restart=on-failure +RestartSec=3 +LimitNOFILE=65535 + +Environment=DAEMON_NAME=konstellationd +Environment=DAEMON_HOME=/home/kons/.konstellationd +# Never true: an upgrade binary reaching a validator without a human +# verifying its checksum is exactly what the release process exists to prevent. +Environment=DAEMON_ALLOW_DOWNLOAD_BINARIES=false +Environment=DAEMON_RESTART_AFTER_UPGRADE=true +Environment=UNSAFE_SKIP_BACKUP=false + +[Install] +WantedBy=multi-user.target +``` + +## Become a validator + +Staking parameters that bind: `min_commission_rate` **5 %**, `max_validators` +**30**, unbonding **21 days**, downtime slash **0.01 %** (miss more than 50 % +of a 100-block window; 10-minute jail, then `tx slashing unjail`), double-sign +slash **5 %** with permanent tombstone. + +- **At genesis:** you receive a `genesis.pre-gentx.json` with your bootstrap + allocation in it. Use it as `config/genesis.json`, create your key and + produce a gentx; the file goes back to the genesis coordinator. + + ```sh + konstellationd keys add validator --key-type eth_secp256k1 + konstellationd genesis gentx validator 24000000000000000000000000esp \ + --chain-id testnet-1 --moniker \ + --commission-rate 0.05 --commission-max-rate 0.20 --commission-max-change-rate 0.01 \ + --min-self-delegation 1000000000000000000 + ``` + + Amounts are in `esp` (18 decimals): `24000000000000000000000000esp` is + 24 M KASH, `1000000000000000000` is 1 KASH. `--min-self-delegation` is a + per-validator value that can be raised later, never lowered. + +- **After genesis:** sync a full node first, then + `konstellationd tx staking create-validator` with the same parameters. ## Upgrades -Binaries are staged by hand via Cosmovisor — **auto-download is off**. -Verify the SHA256 checksum published in -[`networks//upgrades/`](https://github.com/konstellation-network/networks) -before placing a binary in -`$DAEMON_HOME/cosmovisor/upgrades//bin/`. See -[Upgrades](/upgrades) for the log of what's shipped so far. +Auto-download is off. For each upgrade, `networks//upgrades/.md` +gives the upgrade name, halt height, binary URL, SHA256, any `config.toml` / +`app.toml` changes and a rollback note. Stage the binary by hand: + +```sh +mkdir -p "$DAEMON_HOME/cosmovisor/upgrades//bin" +sha256sum -c <<< " konstellationd--linux-amd64" +install -m 0755 konstellationd--linux-amd64 "$DAEMON_HOME/cosmovisor/upgrades//bin/konstellationd" +``` + +Cosmovisor swaps at the governance-set halt height; you do not need to be +awake. On mainnet the height comes from a `MsgSoftwareUpgrade` proposal; on +`testnet-1` the in-house fleet is upgraded with Ansible and external +operators get the same upgrade doc. See [Upgrades](/upgrades) for the log of +what has shipped. ## Monitoring -Enable `prometheus = true` in `config.toml` (port `26660`) and alert on: -missed blocks, falling peer count, block-time drift, disk headroom, and -**no new blocks** — the last is the exploit tripwire. +Set `prometheus = true` in `config.toml` (`init` leaves it off; metrics on +`:26660`). Alert on: missed blocks, falling peer count, block-time drift, +disk headroom, and **no new blocks** — the last is the exploit tripwire. +The in-house fleet pages through tenderduty. ## Joining testnet-1 -TODO once testnet-1 opens to external operators (`ENGINEERING.md §15`, -phase 6): seeds, persistent peers, genesis hash, minimum self-delegation, -and the gentx process live here. +Everything in this section is a placeholder until `networks/testnet-1` +publishes the genesis. The authoritative join page will be +[`networks/testnet-1/README.md`](https://github.com/konstellation-network/networks/blob/main/testnet-1/README.md). + +| | | +|---|---| +| Cosmos chain-id | `testnet-1` | +| EIP-155 chain id | `56671` | +| Binary version | **TBD** — `networks/RELEASES.md` | +| Genesis + sha256 | **TBD** — `networks/testnet-1/genesis.json`, `genesis.sha256` | +| Seeds | **TBD** — `networks/testnet-1/seeds.txt` | +| Persistent peers | **TBD** — `networks/testnet-1/persistent_peers.txt` | +| State sync / snapshots | **TBD** — `networks/testnet-1/snapshots.md` once archive nodes exist | +| Public RPC | **TBD** — see [RPC Endpoints](/rpc-endpoints) | + +At genesis the validator set is the in-house five; 3–5 external operators +are invited afterwards (phase 6). Governance on `testnet-1` is deliberately +fast (2-hour voting period) so upgrade drills take hours, not days; +everything economic is identical to mainnet. + +If something breaks, [Troubleshooting](/troubleshooting) has the errors we +know about. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..4165601 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,204 @@ +--- +id: troubleshooting +title: Troubleshooting +sidebar_position: 6 +--- + +# Troubleshooting + +Errors and behaviours that are deliberate, or known, and easy to mistake for +something else. Each entry says what you see, why, and what to do. Node +misconfiguration errors (chain-id, `evm-chain-id`, `mempool.type`) are on the +[Run a Validator](/run-a-validator#the-chain-id-invariant) page. + +## My EVM transaction has no receipt but I was charged gas + +**Symptom.** `eth_sendRawTransaction` returned a hash. `eth_getTransactionReceipt` +and `eth_getTransactionByHash` for it say **not found**, forever. The sender's +balance went down by the gas and the nonce advanced. `eth_call` and +`eth_estimateGas` for the same call succeeded beforehand. + +**Why.** Every EVM transaction on Konstellation is also a Cosmos SDK +transaction. When one passes the ante handler (so it gets into a block) but +then fails at the **SDK level** during execution — a non-zero ABCI result +code rather than an EVM revert — `cosmos/evm` does not index it as an +Ethereum transaction at all (`indexer/kv_indexer.go`, +`TxSucessOrExpectedFailure`). An EVM *revert* is indexed normally with +`status: 0x0`; an SDK-level failure simply disappears from the `eth_*` +namespace. Gas is charged because the transaction did execute in a block. +`eth_call` / `eth_estimateGas` never commit state, so they cannot warn about +a failure that happens at commit. + +Changing this would mean forking the indexer and RPC backend, which the +project does not do (upstream is consumed as a dependency only). + +**Where the reason is.** CometBFT still indexed it. Query `tx_search` on the +node's CometBFT RPC (port 26657) with the Ethereum hash: + +```bash +curl -s 'http://localhost:26657/tx_search?query="ethereum_tx.ethereumTxHash='"'"'0x'"'"'"' \ + | jq '.result.txs[0].tx_result | {code, codespace, log}' +``` + +or with the CLI: + +```bash +konstellationd query txs --query "ethereum_tx.ethereumTxHash='0x'" -o json \ + | jq '.txs[0] | {code, codespace, raw_log}' +``` + +`code` is non-zero and `log` / `raw_log` carries the reason. + +**The one case you could hit by hand is now refused up front.** Sending +**value** straight to a module account (`fee_collector`, `bonded_tokens_pool`, +…) or to any bank-blocked address (module accounts and precompiles) is +rejected by `x/vm`'s balance guard at state commit. Since 2026-09-19 the same +condition is checked in the ante handler and the mempool pre-check, so +`eth_sendRawTransaction` refuses it synchronously, nothing is charged and the +nonce is not consumed: + +``` +kons1... (0x...) is not allowed to receive funds: it is the "fee_collector" module account +``` + +``` +kons1... (0x...) is not allowed to receive funds: blocked address (module account or precompile) +``` + +**Residual case: internal calls.** The submission check only sees the +transaction's own `to`. If a *contract* forwards value to a blocked address +(contract → module account) the transfer still fails at commit, invisibly to +`eth_*`, with the symptom above. A contract that forwards user-supplied +addresses should not let them be module accounts or precompiles. If you are +building a router/multisend and hit this, `tx_search` will show +`codespace: sdk`, `code: 4` (unauthorized) and the guard's message. + +## "address is frozen" + +**Symptom.** A transaction is refused at submission — `eth_sendRawTransaction`, +`konstellationd tx …`, or the REST/gRPC broadcast — with: + +``` +kons1...: address is frozen +``` + +(`codespace: compliance`, `code: 6`; the address is always shown in bech32 +even when the transaction used its `0x` form.) Nothing was charged; the transaction +never entered the mempool. + +**What it means.** The chain has a compliance block list (`x/compliance`), +enforced for **every** transaction, EVM and Cosmos alike, native KASH +included. A transaction is rejected if *any* address it involves is frozen: +the sender, the recipient, an EVM `to`, a delegation authority in an +EIP-7702 authorization list, and so on. The check runs in the mempool +pre-check as well as the ante handler, so the refusal is immediate and +explained rather than discovered at block time. + +Freezes come from the compliance authority (a foundation multisig on +mainnet) through a 24-hour on-chain timelock, or as an **emergency freeze** +that takes effect immediately and auto-expires after the timelock unless +ratified; governance can override any entry. Every change is an on-chain +event. + +**Check an address's standing:** + +```bash +konstellationd query compliance status kons1... # or the 0x form +konstellationd query compliance entries # page through a list +konstellationd query compliance pending # scheduled updates +``` + +From Solidity, the same lists are readable through the +[`ICompliance` precompile](/contracts#icompliance-at-0x0900) at `0x…0900` +(`isFrozen`, `isVerified`, `status`). + +**Two consequences that surprise people.** + +- Incoming **IBC transfers** to a frozen address are refused too: the packet + is error-acknowledged and the sending chain refunds. +- Freezing an EOA that carried an **EIP-7702 delegation** clears the + delegation (only the `0xef0100‖addr` code; storage stays). Once the freeze + is lifted, one new authorization restores the smart-account wallet with + its state intact. This exists because a relayer could otherwise drive the + frozen account's wallet code by internal call. + +Being *verified* (on the allow list) is never required to transact; only the +block list stops a transaction. + +## "circuit breaker disables …" / "tx type not allowed" + +**Symptom.** A transaction is refused at submission with one of: + +``` +circuit breaker disables /ibc.applications.transfer.v1.MsgTransfer +``` + +``` +tx type not allowed +``` + +or, if it somehow reached a block, fails at execution with: + +``` +circuit breaker disallows execution of message /ibc.applications.transfer.v1.MsgTransfer +``` + +**What it means.** The chain runs the SDK circuit breaker (`x/circuit`). Its +admin — governance, and a 3-of-5 operations multisig granted in genesis — +can disable specific message types without halting the chain, as an +emergency control while a fix ships. It is checked at the router, in the +ante handler and in the mempool pre-check, walks into authz-nested `MsgExec` +messages, and also covers the EVM-side precompiles that execute the same +messages (so tripping `MsgTransfer` stops IBC sends from Solidity too). + +Two message families are worth knowing by name: + +| Disabled type | Effect | +|---|---| +| `/ibc.applications.transfer.v1.MsgTransfer` | all IBC sends stop, Cosmos and EVM (the ICS20 precompile included) | +| `/cosmos.evm.vm.v1.MsgEthereumTx` | the **entire EVM is paused** — every `eth_sendRawTransaction` is refused until reset | + +The breaker cannot disable its own messages or governance's, so a trip is +always reversible. + +**Check what is currently disabled:** + +```bash +konstellationd query circuit disabled-list +``` + +If something is on the list, it is there on purpose: watch the project's +announcement channel rather than retrying. + +## `panic: module account does not exist: unknown address` on the first EVM tx after a restart + +**Symptom.** A node that was restarted on existing state panics on the first +`eth_sendRawTransaction` it receives: + +``` +panic: module account does not exist: unknown address +``` + +from `x/vm` `DeductTxCostsFromUserBalance`, during the EVM mempool recheck. +A freshly initialised chain does not do this; only restarts do. + +**You are on an old binary.** This was root-caused and fixed in +`konstellation` PR #8 (merged 2026-09-16): `x/vm` charges EVM fees through +the SDK's `DeductFees`, whose fee-recipient module name is a package global +that upstream only populates lazily, on the first *Cosmos* transaction of +the process. A fresh chain gets that from the gentxs at InitChain; a restart +never does. The fix pins the value at startup. (Upstream issue: +[cosmos/evm#1288](https://github.com/cosmos/evm/issues/1288).) + +Every binary listed in `networks/RELEASES.md` contains the fix — no release +was ever cut without it. If you see this panic you built from source at a +commit before `642e7b3`, which you should not be doing on a validator in the +first place (see [Run a Validator](/run-a-validator#two-rules-that-are-never-relaxed)). +The startup log line `failed to initialize rechecker context … invalid +height` is a separate, self-healing race and is not the cause. + +## Node refuses to start: chain-id, `evm-chain-id` or `mempool.type` + +These are the chain-id invariant and the app-side mempool requirement, with +the exact messages and fixes, on the +[Run a Validator](/run-a-validator#the-chain-id-invariant) page. diff --git a/sidebars.js b/sidebars.js index 7fcf242..19e7b42 100644 --- a/sidebars.js +++ b/sidebars.js @@ -6,6 +6,7 @@ const sidebars = { 'contracts', 'run-a-validator', 'upgrades', + 'troubleshooting', ], }; From bb94830eeb535b0c66f22f1e7e53be6b58cc8176 Mon Sep 17 00:00:00 2001 From: Evangel90 Date: Mon, 21 Sep 2026 04:52:09 +0100 Subject: [PATCH 2/3] Docs review fixes: D7/D16 admission, D12 vesting, WKASH pin, error strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Findings from the docs PR #1 review, all reproduced against ENGINEERING.md §9.4/§11 (D7, D16, D17), TOKENOMICS.md §7 and STATUS.md §5a as of 2026-09-21: - run-a-validator: "Become a validator" rewritten for the re-decided D7 and D16 — 10 foundation-run genesis validators on both networks, MsgCreateValidator disabled in x/circuit genesis state (design; genesis tooling not yet writing it, noted), admission through the ops-multisig reset/submit/disable window per infra/runbooks/validator-admission.md, with the operator's own steps (show-validator, pre-signed create-validator file). Gentx bullet dropped for outside readers. Caution and testnet-1 join text no longer say "in-house five / 3-5 external". systemd unit uses infra's real user/home (konstellation, /home/konstellation/.konstellationd). mempool.type refusal attributed to cosmos/evm's server-config validation, not CometBFT. Prague fork / Osaka-not-enabled (D17) noted. - contracts: WKASH pinned at 0x34Ab8285C63b876717C2c56151700D02623559bE (Create2Deployer, salt keccak256("konstellation-network/contracts:WKASH:v1"), partial Blockscout match, compiled for prague). Vesting section reflects the built contracts (KonstellationVestingWallet, RevocableVestingWallet, VestingSchedules, DeployVesting.s.sol), CREATE2 wallets funded at block 0, team 10 % liquid + 90 % (start TGE+1y, cliff 0, duration 3y) revocable, community 50 M pool seed in distribution state + 280 M in 30/25/20/15/10 % yearly tranche wallets, ERC-20 release(token) disabled on purpose. - troubleshooting: "tx type not allowed" removed (this chain never emits it); real strings carry the ": unauthorized" suffix; eth_estimateGas's "execution reverted: failed to set account: …" added; protected-type refusal shown; MsgCreateValidator listed as disabled-by-design; `query compliance entries block|allow`; timelock stated as 24 h on mainnet / per-network genesis parameter. npm run build: green, no broken links or anchors. Co-Authored-By: Claude Opus 5 (1M context) --- docs/contracts.md | 75 ++++++++++++++++-------- docs/run-a-validator.md | 126 +++++++++++++++++++++++++++++----------- docs/troubleshooting.md | 38 ++++++++---- 3 files changed, 168 insertions(+), 71 deletions(-) diff --git a/docs/contracts.md b/docs/contracts.md index 169dc2c..927f821 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -131,11 +131,12 @@ Source: `konstellation/x/compliance/precompile/ICompliance.sol` (the ABI is - Two lists. The **blocklist** (`isFrozen`) is what the chain enforces; the **allowlist** (`isVerified`) is opt-in — the chain never requires an address to be verified to transact, only being frozen stops a tx. -- List changes come from the compliance authority through a 24-hour on-chain - timelock, or as an emergency freeze that takes effect immediately and - auto-expires after the timelock unless ratified; governance can override - any entry. Every action is an on-chain event. (Timelocks are 60 s on a - `local_node.sh` dev chain.) +- List changes come from the compliance authority through an on-chain + timelock — **24 h on mainnet**; it is a per-network genesis parameter, + short on `testnet-1` and 60 s on a `local_node.sh` dev chain — or as an + emergency freeze that takes effect immediately and auto-expires after the + timelock unless ratified; governance can override any entry. Every action + is an on-chain event. - Freezing an EOA also clears any EIP-7702 delegation it carried, so a smart-account wallet installed before the freeze cannot be driven by a relayer afterwards. @@ -166,18 +167,26 @@ function transfer(address to, uint256 amount) external { ## WKASH (post-genesis) -`contracts/src/WKASH.sol` is the wrapped native token as ordinary -deployed bytecode (KASH / `esp`, 18 decimals). It is deliberately **not** a -genesis preinstall: it is deployed after genesis through `Create2Deployer` -with a fixed salt, which gives a predictable address without baking +`contracts/src/WKASH.sol` is the wrapped native token as ordinary deployed +bytecode (KASH / `esp`, 18 decimals). It is deliberately **not** a genesis +preinstall: it is deployed after genesis through the preinstalled +`Create2Deployer`, which gives a fixed, predictable address without baking not-yet-audited bytecode into `genesis.json` forever. -| Contract | Address | -|---|---| -| WKASH | **TBD — see the `contracts` repo** once deployed on `testnet-1` | +| Contract | Address | Notes | +|---|---|---| +| WKASH | `0x34Ab8285C63b876717C2c56151700D02623559bE` | Same on every network. CREATE2 via `Create2Deployer` `0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2`, salt `keccak256("konstellation-network/contracts:WKASH:v1")`. Pinned by `contracts/test/DeployWKASH.t.sol`, which fails if a code, solc, optimizer or `evm_version` change moves it. | + +The bytecode is compiled metadata-free (`bytecode_hash = "none"`, +`cbor_metadata = false`) so a comment edit cannot move the address; the +trade-off is that Blockscout source verification shows a **partial match** +(metadata stripped), which is expected. Contracts are compiled for the +**Prague** EVM (`evm_version = "prague"`, D17) — the fork the chain runs from +genesis; Osaka is not enabled, so never compile for it (`CLZ` would be an +invalid opcode here). Cancun-targeted bytecode runs unchanged. -Solidity that only needs the native token as an ERC-20 can use the werc20 -precompile at `0xD4949664cD82660AaE99bEdc034a0deA8A0bd517` today. +Solidity that only needs the native token as an ERC-20 can also use the +werc20 precompile at `0xD4949664cD82660AaE99bEdc034a0deA8A0bd517`. ## Vesting @@ -187,17 +196,35 @@ because a peer chain attributed its exploit to a flaw touching vesting accounts and balance handling. Keeping vesting in audited application code keeps consensus-critical account logic stock. -The design the contracts implement (`TOKENOMICS.md §7`): +Built 2026-09-20 on an OpenZeppelin v5.7.0 base: -| Schedule | Shape | Revocable | -|---|---|---| -| Founding team & early contributors | 12-month cliff, then linear over 36 months (4 years total); one contract per person | **yes** — the foundation multisig can revoke a departing member's grant: unvested tokens return to the treasury, vested tokens stay with the beneficiary | -| Treasury (foundation) | 20 % liquid at genesis, remainder linear over 48 months | no | -| Community & developers | released programmatically over 5 years, front-loaded | no | - -The contracts are being built now. No addresses exist yet; on `testnet-1` -they are exercised as ordinary contract deployments, not genesis state. This -page will list the deployed instances once `networks//` records them. +| File | Role | +|---|---| +| `KonstellationVestingWallet.sol` | non-revocable wallet: treasury and community tranches | +| `RevocableVestingWallet.sol` | team wallet: one-shot `revoke()` by the foundation multisig; unvested returns to the treasury, vested stays with the beneficiary | +| `VestingSchedules.sol` | the one place `TOKENOMICS.md §7`'s numbers live (a vesting year is 365 days) | +| `script/DeployVesting.s.sol` | JSON config → CREATE2 wallets; `predict()` gives the addresses genesis funds | + +Every wallet is a CREATE2 deploy through `Create2Deployer`, so its address +is known before genesis and **`genesis.json` funds it directly at block 0** +— there is no post-genesis funding step. Vesting is **native KASH only**: +OpenZeppelin's ERC-20 `release(token)` path is disabled on purpose, because +the werc20 precompile presents the native balance as an ERC-20 and would let +a beneficiary withdraw the same KASH twice. + +The schedules (`TOKENOMICS.md §7`, on a 1 B KASH supply): + +| Bucket | Liquid at genesis | Locked part | Revocable | +|---|---|---|---| +| Founding team & early contributors (220 M) | **10 % of each grant** — a plain genesis balance, never in a wallet | 90 % in a `RevocableVestingWallet` per person: start = TGE + 1 year, cliff 0, duration 3 years (i.e. nothing until month 12, then linear over 36 months) | **yes** — foundation multisig | +| Community & developers (330 M) | **50 M** community-pool seed, written into genesis `distribution` state (a module account: no contract, no key, spendable only by governance proposal) | 280 M — grants 180 M and incentives 100 M — in non-revocable `KonstellationVestingWallet`s, five yearly tranches of 30 / 25 / 20 / 15 / 10 %, each linear within its year | no | +| Treasury (250 M) | 50 M (20 %) | 200 M linear over 48 months in a non-revocable wallet | no | + +Per-wallet addresses are a function of the beneficiary config +(`script/config/vesting.json`, from `vesting.example.json`; real team beneficiaries and TGE are not yet +filled in). `testnet-1` mirrors the same shape with test addresses. This +page will list the deployed instances once `networks//genesis.json` +funds them. ## Verification diff --git a/docs/run-a-validator.md b/docs/run-a-validator.md index 7205652..355096f 100644 --- a/docs/run-a-validator.md +++ b/docs/run-a-validator.md @@ -10,9 +10,12 @@ sidebar_position: 4 There is no validator set to join yet. Everything on this page about the binary's behaviour is real and verified against the current `konstellationd`; the `testnet-1` specifics (peers, genesis, release version) are placeholders -until `networks/testnet-1` publishes them. External operators are invited -onto `testnet-1` precisely so they can find the gaps in this page -(`ENGINEERING.md §15`, phase 6) — if something here does not work as +until `networks/testnet-1` publishes them. The genesis set on both networks +is 10 foundation-run validators; independent operators are admitted +afterwards through the permissioned procedure described under +[Become a validator](#become-a-validator) — and are admitted onto +`testnet-1` first precisely so they can find the gaps in this page +(`ENGINEERING.md §15`, phase 6). If something here does not work as written, that is a docs bug, please report it. ::: @@ -20,7 +23,10 @@ written, that is a docs bug, please report it. network it is on beyond the chain-id checks described below: everything that differs between `testnet-1` and `konstellation-1` lives in the network's `genesis.json` (in the [`networks`](https://github.com/konstellation-network/networks) -repo) and in your own configuration. +repo) and in your own configuration. The EVM runs the **Prague** (Pectra) +fork from genesis, which is what `cosmos/evm` v0.7.3 activates by default; +Osaka is deliberately not enabled (D17 in `ENGINEERING.md §11`) — nothing +for an operator to configure, but worth knowing when a wallet or tool asks. ## Hardware @@ -134,8 +140,9 @@ If a recipe tells you to patch `stake` → `esp`, set `evm-chain-id`, or set ``` - **`config.toml` `[mempool] type = "app"`** is written automatically. The - app-side EVM mempool ("Krakatoa") is on, and CometBFT v0.39 refuses to - start with the default `"flood"` when the application supplies a mempool: + app-side EVM mempool ("Krakatoa") is on, and cosmos/evm's server-config + validation (before CometBFT even starts) refuses the default `"flood"` + when the application supplies a mempool: ``` Error: EVM mempool enabled, but comet-bft has invalid config.toml:mempool.type (want 'app', got 'flood'): error in app.toml @@ -163,11 +170,11 @@ At startup the node reads the chain-id from `genesis.json` first (honouring it refuses to start and names both files: ``` -panic: --chain-id "konstellation-1" disagrees with /home/kons/.konstellationd/config/genesis.json ("testnet-1"); the genesis is authoritative +panic: --chain-id "konstellation-1" disagrees with /home/konstellation/.konstellationd/config/genesis.json ("testnet-1"); the genesis is authoritative ``` ``` -panic: client.toml chain-id "konstellation-1" disagrees with /home/kons/.konstellationd/config/genesis.json ("testnet-1"); fix client.toml (`konstellationd config set client chain-id testnet-1`) +panic: client.toml chain-id "konstellation-1" disagrees with /home/konstellation/.konstellationd/config/genesis.json ("testnet-1"); fix client.toml (`konstellationd config set client chain-id testnet-1`) ``` The fix is the one in the message: correct `client.toml` (or drop the flag), @@ -258,15 +265,15 @@ Wants=network-online.target [Service] Type=simple -User=kons -Group=kons -ExecStart=/usr/local/bin/cosmovisor run start --home /home/kons/.konstellationd +User=konstellation +Group=konstellation +ExecStart=/usr/local/bin/cosmovisor run start --home /home/konstellation/.konstellationd Restart=on-failure RestartSec=3 LimitNOFILE=65535 Environment=DAEMON_NAME=konstellationd -Environment=DAEMON_HOME=/home/kons/.konstellationd +Environment=DAEMON_HOME=/home/konstellation/.konstellationd # Never true: an upgrade binary reaching a validator without a human # verifying its checksum is exactly what the release process exists to prevent. Environment=DAEMON_ALLOW_DOWNLOAD_BINARIES=false @@ -279,29 +286,76 @@ WantedBy=multi-user.target ## Become a validator -Staking parameters that bind: `min_commission_rate` **5 %**, `max_validators` -**30**, unbonding **21 days**, downtime slash **0.01 %** (miss more than 50 % -of a 100-block window; 10-minute jail, then `tx slashing unjail`), double-sign -slash **5 %** with permanent tombstone. - -- **At genesis:** you receive a `genesis.pre-gentx.json` with your bootstrap - allocation in it. Use it as `config/genesis.json`, create your key and - produce a gentx; the file goes back to the genesis coordinator. - - ```sh - konstellationd keys add validator --key-type eth_secp256k1 - konstellationd genesis gentx validator 24000000000000000000000000esp \ - --chain-id testnet-1 --moniker \ - --commission-rate 0.05 --commission-max-rate 0.20 --commission-max-change-rate 0.01 \ - --min-self-delegation 1000000000000000000 - ``` +The genesis set is **10 foundation-run validators** created by gentx, on +`testnet-1` and `konstellation-1` alike (`max_validators` is 30, so 20 seats +are empty at genesis). Admission of further validators is **permissioned** +(decisions D7 and D16 in `ENGINEERING.md §11`), and it is enforced by the +chain, not by policy: `/cosmos.staking.v1beta1.MsgCreateValidator` is +disabled in `x/circuit` genesis state on both networks, so a plain +`tx staking create-validator` is refused at submission: - Amounts are in `esp` (18 decimals): `24000000000000000000000000esp` is - 24 M KASH, `1000000000000000000` is 1 KASH. `--min-self-delegation` is a - per-validator value that can be raised later, never lowered. +``` +circuit breaker disables /cosmos.staking.v1beta1.MsgCreateValidator: unauthorized +``` + +:::note +This is the decided design. As of 2026-09-21 the genesis tooling does not yet +write the entry (`konstellationd init` produces an empty +`disabled_type_urls`; it is added before the `testnet-1` genesis is cut), so +a local dev chain started with `local_node.sh` does not refuse the message. +::: -- **After genesis:** sync a full node first, then - `konstellationd tx staking create-validator` with the same parameters. +Gentx is not a path open to outside operators: it only exists at genesis and +bypasses the message router. "Permissioned" is about who may *validate*; +delegating to any validator is open from genesis. + +### The admission procedure + +The foundation runs each admission as an announced window, following +`infra/runbooks/validator-admission.md`. What you do: + +1. **Run a synced full node** on the network, behind a sentry, with the + topology above (`pex = false`, your node id in the sentry's + `private_peer_ids`), on hosts you control. +2. **Send the foundation** your consensus public key and your operator + address over the operator channel: + + ```sh + konstellationd comet show-validator # or the Horcrux cluster's key + konstellationd keys show -a # kons1… + konstellationd keys show --bech val # konsvaloper1… + ``` + +3. **Pre-build and pre-sign** the `MsgCreateValidator` and deliver it as a + file. The lead dry-runs it (`tx validate-signatures`) and checks the + pubkey, amounts and commission against what was agreed; nothing is + broadcast yet — broadcasting now is refused by the breaker anyway. + + ```sh + konstellationd tx staking create-validator validator.json \ + --from --chain-id --node \ + --generate-only > unsigned.json + konstellationd tx sign unsigned.json --from --chain-id --node > signed-create-validator.json + ``` + +4. **During the window** the 3-of-5 operations multisig resets the breaker + for that message type, your signed `create-validator` is broadcast, and + the multisig disables the type again — aimed at one block, at most a + window of seconds. A message that fails inside the window (wrong + commission, insufficient funds, bad pubkey) means a *new* window, not an + open gate, so get the parameters right first. + +Parameters that bind (D10): `commission_rate` ≥ `min_commission_rate` +**5 %**, `min_self_delegation` ≤ what you actually self-delegate (it can be +raised later, never lowered; amounts are in `esp`, `1000000000000000000` is +1 KASH), unbonding **21 days**, downtime slash **0.01 %** (miss more than +50 % of a 100-block window; 10-minute jail, then `tx slashing unjail`), +double-sign slash **5 %** with permanent tombstone. + +A validator admitted this way enters the active set immediately (≥ 1 KASH +self-delegated qualifies for an empty seat) and stays until it unbonds or is +jailed. Going permissionless is a governance proposal that removes the type +from the disabled list for good; the stages are in the whitepaper roadmap. ## Upgrades @@ -345,8 +399,10 @@ publishes the genesis. The authoritative join page will be | State sync / snapshots | **TBD** — `networks/testnet-1/snapshots.md` once archive nodes exist | | Public RPC | **TBD** — see [RPC Endpoints](/rpc-endpoints) | -At genesis the validator set is the in-house five; 3–5 external operators -are invited afterwards (phase 6). Governance on `testnet-1` is deliberately +At genesis the validator set is 10 foundation-run validators +(`max_validators` 30); independent operators are admitted afterwards through +the permissioned D16 procedure above (phase 6) — `testnet-1` rehearses +exactly what mainnet runs, admissions included. Governance on `testnet-1` is deliberately fast (2-hour voting period) so upgrade drills take hours, not days; everything economic is identical to mainnet. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 4165601..e02ee18 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -58,11 +58,19 @@ condition is checked in the ante handler and the mempool pre-check, so nonce is not consumed: ``` -kons1... (0x...) is not allowed to receive funds: it is the "fee_collector" module account +kons1... (0x...) is not allowed to receive funds: it is the "fee_collector" module account: unauthorized ``` ``` -kons1... (0x...) is not allowed to receive funds: blocked address (module account or precompile) +kons1... (0x...) is not allowed to receive funds: blocked address (module account or precompile): unauthorized +``` + +Most tooling never gets that far: without an explicit `--gas-limit`, `cast` +and wallets call `eth_estimateGas` first, and the simulation hits the same +guard at its (uncommitted) state write, so what you see is: + +``` +execution reverted: failed to set account: kons1... is not allowed to receive funds: unauthorized ``` **Residual case: internal calls.** The submission check only sees the @@ -95,16 +103,17 @@ pre-check as well as the ante handler, so the refusal is immediate and explained rather than discovered at block time. Freezes come from the compliance authority (a foundation multisig on -mainnet) through a 24-hour on-chain timelock, or as an **emergency freeze** -that takes effect immediately and auto-expires after the timelock unless -ratified; governance can override any entry. Every change is an on-chain +mainnet) through an on-chain timelock — 24 h on mainnet; a per-network +genesis parameter, short on `testnet-1` and 60 s on a local dev chain — or +as an **emergency freeze** that takes effect immediately and auto-expires +after the timelock unless ratified; governance can override any entry. Every change is an on-chain event. **Check an address's standing:** ```bash konstellationd query compliance status kons1... # or the 0x form -konstellationd query compliance entries # page through a list +konstellationd query compliance entries block # or `allow`; page through one list konstellationd query compliance pending # scheduled updates ``` @@ -125,22 +134,26 @@ From Solidity, the same lists are readable through the Being *verified* (on the allow list) is never required to transact; only the block list stops a transaction. -## "circuit breaker disables …" / "tx type not allowed" +## "circuit breaker disables …" -**Symptom.** A transaction is refused at submission with one of: +**Symptom.** A transaction is refused at submission (`codespace: sdk`, +`code: 4`) with: ``` -circuit breaker disables /ibc.applications.transfer.v1.MsgTransfer +circuit breaker disables /ibc.applications.transfer.v1.MsgTransfer: unauthorized ``` +or, if it somehow reached a block, fails at execution with: + ``` -tx type not allowed +circuit breaker disallows execution of message /ibc.applications.transfer.v1.MsgTransfer ``` -or, if it somehow reached a block, fails at execution with: +A related refusal, only ever seen by the breaker's admin, is an attempt to +trip a protected type: ``` -circuit breaker disallows execution of message /ibc.applications.transfer.v1.MsgTransfer +/cosmos.circuit.v1.MsgResetCircuitBreaker cannot be disabled: the breaker must stay resettable: unauthorized ``` **What it means.** The chain runs the SDK circuit breaker (`x/circuit`). Its @@ -157,6 +170,7 @@ Two message families are worth knowing by name: |---|---| | `/ibc.applications.transfer.v1.MsgTransfer` | all IBC sends stop, Cosmos and EVM (the ICS20 precompile included) | | `/cosmos.evm.vm.v1.MsgEthereumTx` | the **entire EVM is paused** — every `eth_sendRawTransaction` is refused until reset | +| `/cosmos.staking.v1beta1.MsgCreateValidator` | **disabled by design from genesis** on both networks: validator admission is permissioned (D16), see [Run a Validator](/run-a-validator#become-a-validator). Not a fault. | The breaker cannot disable its own messages or governance's, so a trip is always reversible. From b62ed62e51ae21db80ad2cc649cd2cf3eb8b2f1f Mon Sep 17 00:00:00 2001 From: Evangel90 Date: Mon, 21 Sep 2026 16:26:17 +0100 Subject: [PATCH 3/3] Docs adversarial-review fixes: honest freeze semantics, key handling, admission tx, trust roots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every item reproduced or checked against source before the wording changed (dev chain at f069ddb, cosmos/evm v0.7.3, infra templates and runbooks). - Freeze semantics (C1): reproduced on the dev chain — a frozen EOA's pre-freeze werc20 allowance still lets transferFrom(frozen, spender) succeed and transfer(to = frozen) succeeds, while its own sends and direct value/bank transfers to it are refused. contracts.md and troubleshooting.md now say a freeze stops signing and being the direct `to`, does not immobilise the balance, contracts must call isFrozen themselves, and a bank-level restriction is planned before mainnet. "Chain-wide … native KASH included" wording removed everywhere. - Keys (H1): init writes config/priv_validator_key.json; page now says to shard it offline, shred -u it on every host, set priv_validator_laddr, never restore config/ onto a second host, never restore or hand-edit priv_validator_state.json; notes that the start command regenerates a throwaway key file (LoadOrGenFilePV is unconditional). - Admission tx (H2): --gas 300000 --gas-prices 10000000000esp and --timeout-height on both generate-only and sign; explains the empty-fee, expiry and sequence failure modes; "send nothing from the operator account between signing and the window". - Genesis trust (M1): fetch by tag/commit SHA, compare the hash against the separately published value, .sha256 is consistency not authenticity. - Attestation (M2): --repo + --signer-workflow instead of --owner, at install and at upgrade staging (L2). - systemd unit (M3): UNSAFE_SKIP_BACKUP=true with the reason, RequiresMountsFor/ConditionPathIsMountPoint guard; Upgrades gained the rollback rules (own H-1 snapshot, keep current priv_validator_state.json, --unsafe-skip-upgrades for a failed handler, never a data restore). - eth_estimateGas mapping (M4): reproduced — module-account recipient estimates 21000 and is refused at send; precompile recipient fails estimation with "execution reverted: failed to set account …". - Provisional (M5): WKASH pin, Prague/metadata-free build and vesting marked as on unmerged contracts PR #2; quickstart no longer says "final". - cosmovisor (M6): pinned v1.7.0 tarball URL, sha256 from the release's SHA256SUMS, extract only the binary. - L1 dedicated `konstellation` user for every step; L3 timelock "24 h default, governance-changeable, min 1 min, testnet-1 per genesis"; L4 P12 caveat on MsgCreateValidator; L5 release pipeline under review; L6 every ENGINEERING/TOKENOMICS/STATUS/infra citation replaced by the fact inline (rpc-endpoints.md too); L8 the post-genesis DeployVesting step, who runs it and when. npm run build: green, no broken links or anchors. Co-Authored-By: Claude Opus 5 (1M context) --- docs/contracts.md | 55 ++++++++---- docs/quickstart.md | 6 +- docs/rpc-endpoints.md | 6 +- docs/run-a-validator.md | 191 ++++++++++++++++++++++++++++++++-------- docs/troubleshooting.md | 50 +++++++---- 5 files changed, 230 insertions(+), 78 deletions(-) diff --git a/docs/contracts.md b/docs/contracts.md index 927f821..81ebf02 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -89,11 +89,17 @@ Konstellation's range starts at `0x…0900`. ### `ICompliance` at `0x…0900` -The chain itself refuses every transaction that *involves* a frozen address -— EVM and Cosmos, native KASH included — before execution (see -[Troubleshooting](/troubleshooting#address-is-frozen)). That check cannot -see internal calls, so a contract that forwards user-supplied addresses, or -that wants to serve only verified addresses, calls this precompile. All +What a freeze does today, precisely: it stops the address from **signing** +transactions and from being the direct `to` of one — EVM and Cosmos alike, +checked before execution (see +[Troubleshooting](/troubleshooting#address-is-frozen)). It does **not** +immobilise the address's balance: KASH can still be pulled out through the +werc20 precompile or any contract by an allowance granted before the +freeze, and can still arrive by contract call, because the check runs on +signers and the transaction's `to`, not on bank transfers. A contract that +must not serve a frozen address therefore has to call `isFrozen` itself. +(A bank-level restriction is planned before mainnet; the chain's own +tracker records it as open.) That is what this precompile is for. All functions are `view` and cost only the precompile's base read gas. ```solidity @@ -128,12 +134,14 @@ interface ICompliance { Source: `konstellation/x/compliance/precompile/ICompliance.sol` (the ABI is `abi.json` next to it). Semantics worth knowing: -- Two lists. The **blocklist** (`isFrozen`) is what the chain enforces; the +- Two lists. The **blocklist** (`isFrozen`) is what the chain enforces — + on signers and direct recipients, as above, not on balances; the **allowlist** (`isVerified`) is opt-in — the chain never requires an address to be verified to transact, only being frozen stops a tx. - List changes come from the compliance authority through an on-chain - timelock — **24 h on mainnet**; it is a per-network genesis parameter, - short on `testnet-1` and 60 s on a `local_node.sh` dev chain — or as an + timelock — **24 h by default** (`konstellationd init` writes `86400s`; + governance-changeable, minimum 1 min; `testnet-1`'s value is whatever its + published genesis sets, and `local_node.sh` sets 60 s) — or as an emergency freeze that takes effect immediately and auto-expires after the timelock unless ratified; governance can override any entry. Every action is an on-chain event. @@ -175,13 +183,13 @@ not-yet-audited bytecode into `genesis.json` forever. | Contract | Address | Notes | |---|---|---| -| WKASH | `0x34Ab8285C63b876717C2c56151700D02623559bE` | Same on every network. CREATE2 via `Create2Deployer` `0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2`, salt `keccak256("konstellation-network/contracts:WKASH:v1")`. Pinned by `contracts/test/DeployWKASH.t.sol`, which fails if a code, solc, optimizer or `evm_version` change moves it. | +| WKASH | `0x34Ab8285C63b876717C2c56151700D02623559bE` — **provisional** until deployed on `testnet-1` and listed in `networks/testnet-1/chain.json` | Same on every network. CREATE2 via `Create2Deployer` `0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2`, salt `keccak256("konstellation-network/contracts:WKASH:v1")`. Pinned by `contracts/test/DeployWKASH.t.sol`, which fails if a code, solc, optimizer or `evm_version` change moves it. The pin, the Prague/metadata-free build settings and the vesting contracts below are on `contracts` pull request #2, not yet merged. | The bytecode is compiled metadata-free (`bytecode_hash = "none"`, `cbor_metadata = false`) so a comment edit cannot move the address; the trade-off is that Blockscout source verification shows a **partial match** (metadata stripped), which is expected. Contracts are compiled for the -**Prague** EVM (`evm_version = "prague"`, D17) — the fork the chain runs from +**Prague** EVM (`evm_version = "prague"`) — the fork the chain runs from genesis; Osaka is not enabled, so never compile for it (`CLZ` would be an invalid opcode here). Cancun-targeted bytecode runs unchanged. @@ -191,28 +199,37 @@ werc20 precompile at `0xD4949664cD82660AaE99bEdc034a0deA8A0bd517`. ## Vesting Vesting uses **Solidity vesting contracts** (`contracts/src/vesting/`), not -`x/auth` vesting accounts — decision D12 in `ENGINEERING.md §11`, taken -because a peer chain attributed its exploit to a flaw touching vesting -accounts and balance handling. Keeping vesting in audited application code -keeps consensus-critical account logic stock. +`x/auth` vesting accounts — a deliberate decision, taken because a peer +chain attributed its exploit to a flaw touching vesting accounts and +balance handling. Keeping vesting in audited application code keeps +consensus-critical account logic stock. -Built 2026-09-20 on an OpenZeppelin v5.7.0 base: +Built 2026-09-20 on an OpenZeppelin v5.7.0 base (`contracts` pull request +#2, under review): | File | Role | |---|---| | `KonstellationVestingWallet.sol` | non-revocable wallet: treasury and community tranches | | `RevocableVestingWallet.sol` | team wallet: one-shot `revoke()` by the foundation multisig; unvested returns to the treasury, vested stays with the beneficiary | -| `VestingSchedules.sol` | the one place `TOKENOMICS.md §7`'s numbers live (a vesting year is 365 days) | +| `VestingSchedules.sol` | the one place the schedule numbers below live (a vesting year is 365 days) | | `script/DeployVesting.s.sol` | JSON config → CREATE2 wallets; `predict()` gives the addresses genesis funds | Every wallet is a CREATE2 deploy through `Create2Deployer`, so its address -is known before genesis and **`genesis.json` funds it directly at block 0** -— there is no post-genesis funding step. Vesting is **native KASH only**: +is known before genesis and **`genesis.json` funds it directly at block 0**. +There is no post-genesis *funding* step, but there is a post-genesis +*deploy* step: until `DeployVesting.s.sol` is run, the KASH sits at +codeless addresses that no key can move (a plain balance at a +not-yet-existing contract). The deploy is permissionless and idempotent — +anyone can run it and the result is identical whoever does — and the +foundation runs it as part of the launch sequence, right after genesis and +before anything is due (the treasury wallet and the first community +tranche vest linearly from TGE, so `release()` on them is meaningful from +day one; nothing is lost while the code is absent). Vesting is **native KASH only**: OpenZeppelin's ERC-20 `release(token)` path is disabled on purpose, because the werc20 precompile presents the native balance as an ERC-20 and would let a beneficiary withdraw the same KASH twice. -The schedules (`TOKENOMICS.md §7`, on a 1 B KASH supply): +The schedules, on a 1 B KASH genesis supply: | Bucket | Liquid at genesis | Locked part | Revocable | |---|---|---|---| diff --git a/docs/quickstart.md b/docs/quickstart.md index 67a2ceb..8178edb 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -11,8 +11,10 @@ sidebar_position: 1 Konstellation has not launched a public network yet. The `testnet-1` RPC and explorer URLs on this page are placeholders until the genesis is published in the [`networks` -repo](https://github.com/konstellation-network/networks). Everything else — -chain ids, token, addresses, the local dev chain — is final. +repo](https://github.com/konstellation-network/networks). Chain ids, token +and the local dev chain are settled; contract addresses that are not +genesis preinstalls (WKASH, vesting) are provisional until deployed on +`testnet-1` and listed in `networks/testnet-1/chain.json`. ::: ## Network parameters diff --git a/docs/rpc-endpoints.md b/docs/rpc-endpoints.md index a506efe..7afb8da 100644 --- a/docs/rpc-endpoints.md +++ b/docs/rpc-endpoints.md @@ -23,8 +23,8 @@ exists). | Tendermint RPC | `http://localhost:26657` | Started via `./local_node.sh -y` in the `konstellation` repo. Chain ID -`56670` (local/unknown networks only — never a real network's ID, per the -chain-id invariant in `ENGINEERING.md`). +`56670` (local/unknown networks only — the node never lets a dev chain run +with a real network's ID; see [Run a Validator](/run-a-validator#the-chain-id-invariant)). ## testnet-1 @@ -51,4 +51,4 @@ Chain ID `5667`. Point clients at your own node instead of a shared endpoint. See [Run a Validator](/run-a-validator) for hardware and configuration — an RPC/archive node uses the same base configuration with pruning tuned for -its role (§9.2 of `ENGINEERING.md`). +its role. diff --git a/docs/run-a-validator.md b/docs/run-a-validator.md index 355096f..a4cd718 100644 --- a/docs/run-a-validator.md +++ b/docs/run-a-validator.md @@ -14,8 +14,8 @@ until `networks/testnet-1` publishes them. The genesis set on both networks is 10 foundation-run validators; independent operators are admitted afterwards through the permissioned procedure described under [Become a validator](#become-a-validator) — and are admitted onto -`testnet-1` first precisely so they can find the gaps in this page -(`ENGINEERING.md §15`, phase 6). If something here does not work as +`testnet-1` first precisely so they can find the gaps in this page. If +something here does not work as written, that is a docs bug, please report it. ::: @@ -25,7 +25,9 @@ differs between `testnet-1` and `konstellation-1` lives in the network's `genesis.json` (in the [`networks`](https://github.com/konstellation-network/networks) repo) and in your own configuration. The EVM runs the **Prague** (Pectra) fork from genesis, which is what `cosmos/evm` v0.7.3 activates by default; -Osaka is deliberately not enabled (D17 in `ENGINEERING.md §11`) — nothing +Osaka is deliberately not enabled (a recorded decision: cosmos/evm never +activates it, and its native P-256 precompile would collide with the one at +`0x…0100`) — nothing for an operator to configure, but worth knowing when a wallet or tool asks. ## Hardware @@ -41,10 +43,11 @@ storage costs block time. ## Two rules that are never relaxed :::danger Never build the binary on a validator -Every release binary comes from CI in the `konstellation` repo, built twice -on independent runners from a signed tag, published only when both checksums -agree, with GitHub build provenance attached. Download the release asset and -verify its SHA256 against the ledger in +Every release binary comes from CI in the `konstellation` repo (release +pipeline under review, not yet merged): built twice on independent runners +from a signed tag, published only when both checksums agree, with GitHub +build provenance attached. Download the release asset and verify its +SHA256 against the ledger in [`networks/RELEASES.md`](https://github.com/konstellation-network/networks/blob/main/RELEASES.md) — never against a chat message. Nothing runs a binary that is not in that table. @@ -80,18 +83,44 @@ proxy in front of it — the JSON-RPC server itself has no auth. Never expose ## Install the binary +Everything below runs as a dedicated system user, which is also the user +the systemd unit runs as. Create it once and do every step as it: + +```sh +sudo useradd -r -m -s /bin/bash konstellation +sudo -iu konstellation +``` + Cosmovisor with auto-download **off**. Upgrade binaries are staged by hand -after checksum verification (see [Upgrades](#upgrades)). +after checksum verification (see [Upgrades](#upgrades)). Cosmovisor itself +is a pinned release too: download the tarball, check it against the +`SHA256SUMS` published with that release, and extract only the binary. + +```sh +CV=v1.7.0 +curl -fsSLO "https://github.com/cosmos/cosmos-sdk/releases/download/cosmovisor%2F${CV}/cosmovisor-${CV}-linux-amd64.tar.gz" +sha256sum -c <<< "07f2824d924bd96029009047bffbbb0645769b90a9423a7872d3240880de88ba cosmovisor-${CV}-linux-amd64.tar.gz" +tar -xzf "cosmovisor-${CV}-linux-amd64.tar.gz" cosmovisor +sudo install -m 0755 cosmovisor /usr/local/bin/cosmovisor +``` + +(The checksum is from `SHA256SUMS-cosmovisor-v1.7.0.txt` on that release — +compare it yourself; this page is not the trust root for it either.) + +Then the chain binary. The provenance check must name the repository and +the workflow: `--owner` alone would accept a build from any repository in +the organisation. ```sh VERSION= curl -fsSLO "https://github.com/Konstellation-Network/konstellation/releases/download/${VERSION}/konstellationd-${VERSION}-linux-amd64" sha256sum -c <<< " konstellationd-${VERSION}-linux-amd64" -# optional but recommended: GitHub build provenance -gh attestation verify "konstellationd-${VERSION}-linux-amd64" --owner Konstellation-Network +gh attestation verify "konstellationd-${VERSION}-linux-amd64" \ + --repo Konstellation-Network/konstellation \ + --signer-workflow Konstellation-Network/konstellation/.github/workflows/release.yml export DAEMON_NAME=konstellationd -export DAEMON_HOME=$HOME/.konstellationd +export DAEMON_HOME=$HOME/.konstellationd # /home/konstellation/.konstellationd export DAEMON_ALLOW_DOWNLOAD_BINARIES=false export DAEMON_RESTART_AFTER_UPGRADE=true mkdir -p "$DAEMON_HOME/cosmovisor/genesis/bin" @@ -200,15 +229,24 @@ a node that already has keys, and never edit the genesis to match. ## Genesis Replace the placeholder genesis `init` wrote with the published one and -verify the hash **before** starting. `networks//genesis.sha256` is -checked by CI on every commit to that repo. +verify the hash **before** starting. Two things matter about *how*: + +- The `.sha256` file next to `genesis.json` in the `networks` repo is a + consistency check (CI keeps the pair in step), **not** a second trust + root — a hostile genesis carrying the real EIP-155 id would pass every + check on this page if both files came from the same place. Fetch by a + **tag or commit SHA**, never `main`, and compare the hash against the + value published *separately*: the release announcement and the join + table at the bottom of this page. +- `genesis validate` checks shape, not identity. ```sh -# testnet-1 — placeholder paths; the files do not exist yet +# testnet-1 — placeholder ref and hash; the files do not exist yet +REF= curl -fsSL -o "$DAEMON_HOME/config/genesis.json" \ - https://raw.githubusercontent.com/Konstellation-Network/networks/main/testnet-1/genesis.json -curl -fsSL https://raw.githubusercontent.com/Konstellation-Network/networks/main/testnet-1/genesis.sha256 \ - | (cd "$DAEMON_HOME/config" && sha256sum -c -) + "https://raw.githubusercontent.com/Konstellation-Network/networks/${REF}/testnet-1/genesis.json" +sha256sum "$DAEMON_HOME/config/genesis.json" +# must equal the hash in the announcement / the join table below — not merely the .sha256 file "$DAEMON_HOME/cosmovisor/genesis/bin/konstellationd" genesis validate --home "$DAEMON_HOME" ``` @@ -228,9 +266,35 @@ private_peer_ids = "" # never gossiped Sign with [Horcrux](https://github.com/strangelove-ventures/horcrux) (threshold signing across 3+ cosigners in different regions — the preferred -option for a chain holding user funds, and what the in-house fleet uses) or -`tmkms` + YubiHSM2. Either way the key never sits on the node itself — and the -one-key-one-node rule above still applies to the cosigner set as a whole. +option for a chain holding user funds, and what the foundation's own +validators use) or `tmkms` + YubiHSM2. Note that **`init` has already +written a consensus key** to `config/priv_validator_key.json` on the node; +a remote signer does not make that file go away, you do: + +1. Copy `config/priv_validator_key.json` to an **offline** machine and + shard it there (`horcrux create-shares …`, or import it into tmkms). + Distribute the shards to the cosigners. +2. On the validator host — and on every other host that ever held a copy — + destroy the file: `shred -u config/priv_validator_key.json`. A validator + host must never hold the key file once a remote signer exists. +3. Point the node at the signer in `config.toml`: + `priv_validator_laddr = "tcp://0.0.0.0:1234"` (the address the cosigners + connect to; firewall it to them). With it set the node signs through the + socket. It will still create a *new, random* `priv_validator_key.json` + on the next start if none exists (`LoadOrGenFilePV` runs unconditionally + in the start command) — that file is a throwaway, not your validator + key; do not mistake its reappearance for a restore, and do not reuse it. + +And two rules about state, both double-sign paths: + +- **Never restore `config/` from a backup onto a second host.** A backup + that contains the key (or the node key plus a state file) is a second + validator waiting to happen. +- **Never restore or hand-edit `data/priv_validator_state.json`.** It is + the node's record of the last height it signed; rewinding it is how a + node signs a second block at the same height. (Remote signers keep their + own copy of this state; the same rule applies to it.) The + one-key-one-node rule above applies to the cosigner set as a whole. ## Pruning @@ -253,15 +317,22 @@ cosmovisor run start --home "$DAEMON_HOME" konstellationd status | jq .sync_info ``` -Run it under systemd. This is the in-house unit -(`infra/ansible/roles/cosmovisor/templates/cosmovisor.service.j2`) with the -variables filled in: +Run it under systemd. This is the unit the foundation's own validators run, +with the variables filled in: ```ini [Unit] Description=Konstellation cosmovisor After=network-online.target Wants=network-online.target +# Only if data/ lives on a separate volume (local NVMe, a cloud data disk): +# never let the node start on the bare directory underneath. A host that +# comes back with an empty volume would otherwise start with its key and a +# priv_validator_state.json at height 0 — the double-sign path. With these +# two lines the unit stays inactive (a "node down" page) until the volume +# is mounted. +RequiresMountsFor=/home/konstellation/.konstellationd/data +ConditionPathIsMountPoint=/home/konstellation/.konstellationd/data [Service] Type=simple @@ -278,7 +349,10 @@ Environment=DAEMON_HOME=/home/konstellation/.konstellationd # verifying its checksum is exactly what the release process exists to prevent. Environment=DAEMON_ALLOW_DOWNLOAD_BINARIES=false Environment=DAEMON_RESTART_AFTER_UPGRADE=true -Environment=UNSAFE_SKIP_BACKUP=false +# cosmovisor's pre-upgrade backup copies the whole data directory into +# DAEMON_HOME — the boot disk, next to a multi-TB data volume — so the first +# upgrade would fill it. Rollback is your own H−1 snapshot (see Upgrades). +Environment=UNSAFE_SKIP_BACKUP=true [Install] WantedBy=multi-user.target @@ -289,8 +363,8 @@ WantedBy=multi-user.target The genesis set is **10 foundation-run validators** created by gentx, on `testnet-1` and `konstellation-1` alike (`max_validators` is 30, so 20 seats are empty at genesis). Admission of further validators is **permissioned** -(decisions D7 and D16 in `ENGINEERING.md §11`), and it is enforced by the -chain, not by policy: `/cosmos.staking.v1beta1.MsgCreateValidator` is +— a recorded decision, opening up in stages by governance — and it is +enforced by the chain, not by policy: `/cosmos.staking.v1beta1.MsgCreateValidator` is disabled in `x/circuit` genesis state on both networks, so a plain `tx staking create-validator` is refused at submission: @@ -311,8 +385,8 @@ delegating to any validator is open from genesis. ### The admission procedure -The foundation runs each admission as an announced window, following -`infra/runbooks/validator-admission.md`. What you do: +The foundation runs each admission as an announced window (a height +range), from a written runbook. What you do: 1. **Run a synced full node** on the network, behind a sentry, with the topology above (`pex = false`, your node id in the sentry's @@ -331,11 +405,31 @@ The foundation runs each admission as an announced window, following pubkey, amounts and commission against what was agreed; nothing is broadcast yet — broadcasting now is refused by the breaker anyway. + The transaction is signed *now* but executes *later*, so it has to be + correct for the window, not for the moment you sign it. Three things + are easy to get wrong and each one wastes the window: + + - **Fee.** `--generate-only` without gas flags produces a tx with an + empty fee (`fee.amount: []`), which fails inside the window with + `insufficient fee` whenever the base fee is above zero — it starts at + 1 gwei at genesis, and the public RPC floors at 1 gwei. Set an explicit + gas limit and a price with margin: `--gas 300000 --gas-prices + 10000000000esp` (10 gwei). + - **Expiry.** Set `--timeout-height` to the window's end plus a margin, + so a signed tx that misses the window cannot be replayed into a later + one you did not agree to. + - **Sequence.** The signature covers your account's sequence number. + **Send nothing from the operator account between signing and the + window**, or the tx is rejected for a sequence mismatch. + ```sh konstellationd tx staking create-validator validator.json \ --from --chain-id --node \ + --gas 300000 --gas-prices 10000000000esp --timeout-height \ --generate-only > unsigned.json - konstellationd tx sign unsigned.json --from --chain-id --node > signed-create-validator.json + konstellationd tx sign unsigned.json --from --chain-id --node \ + --gas 300000 --gas-prices 10000000000esp --timeout-height \ + > signed-create-validator.json ``` 4. **During the window** the 3-of-5 operations multisig resets the breaker @@ -345,7 +439,7 @@ The foundation runs each admission as an announced window, following commission, insufficient funds, bad pubkey) means a *new* window, not an open gate, so get the parameters right first. -Parameters that bind (D10): `commission_rate` ≥ `min_commission_rate` +Parameters that bind: `commission_rate` ≥ `min_commission_rate` **5 %**, `min_self_delegation` ≤ what you actually self-delegate (it can be raised later, never lowered; amounts are in `esp`, `1000000000000000000` is 1 KASH), unbonding **21 days**, downtime slash **0.01 %** (miss more than @@ -361,19 +455,40 @@ from the disabled list for good; the stages are in the whitepaper roadmap. Auto-download is off. For each upgrade, `networks//upgrades/.md` gives the upgrade name, halt height, binary URL, SHA256, any `config.toml` / -`app.toml` changes and a rollback note. Stage the binary by hand: +`app.toml` changes and a rollback note. Stage the binary by hand, verifying +it exactly as at install time: ```sh -mkdir -p "$DAEMON_HOME/cosmovisor/upgrades//bin" -sha256sum -c <<< " konstellationd--linux-amd64" -install -m 0755 konstellationd--linux-amd64 "$DAEMON_HOME/cosmovisor/upgrades//bin/konstellationd" +VERSION=; NAME= +curl -fsSLO "https://github.com/Konstellation-Network/konstellation/releases/download/${VERSION}/konstellationd-${VERSION}-linux-amd64" +sha256sum -c <<< " konstellationd-${VERSION}-linux-amd64" +gh attestation verify "konstellationd-${VERSION}-linux-amd64" \ + --repo Konstellation-Network/konstellation \ + --signer-workflow Konstellation-Network/konstellation/.github/workflows/release.yml +mkdir -p "$DAEMON_HOME/cosmovisor/upgrades/${NAME}/bin" +install -m 0755 "konstellationd-${VERSION}-linux-amd64" "$DAEMON_HOME/cosmovisor/upgrades/${NAME}/bin/konstellationd" ``` Cosmovisor swaps at the governance-set halt height; you do not need to be awake. On mainnet the height comes from a `MsgSoftwareUpgrade` proposal; on -`testnet-1` the in-house fleet is upgraded with Ansible and external -operators get the same upgrade doc. See [Upgrades](/upgrades) for the log of -what has shipped. +`testnet-1` the foundation's fleet is upgraded with Ansible and the same +proposal is submitted so the governance path is rehearsed. See +[Upgrades](/upgrades) for the log of what has shipped. + +**Rollback.** The unit above runs with `UNSAFE_SKIP_BACKUP=true`, so your +rollback is the snapshot **you** take of `data/` at H−1 with the node +stopped (or a filesystem snapshot of the data volume). Two rules: + +- If you ever restore `data/` from that snapshot, **keep the current + `data/priv_validator_state.json`** — the snapshot's copy is from a lower + height, and a node that re-signs at a height it already signed is + tombstoned. +- For a **failed upgrade handler** (the node panics at the upgrade height — + the common case) do **not** restore data at all: the validators have + already precommitted block H. Repoint `cosmovisor/current` at the + previous version, move `data/upgrade-info.json` aside, and start the old + binary with `--unsafe-skip-upgrades ` until the network has agreed + what happens next. Block H stands; nothing is re-signed. ## Monitoring @@ -401,7 +516,7 @@ publishes the genesis. The authoritative join page will be At genesis the validator set is 10 foundation-run validators (`max_validators` 30); independent operators are admitted afterwards through -the permissioned D16 procedure above (phase 6) — `testnet-1` rehearses +the permissioned procedure above — `testnet-1` rehearses exactly what mainnet runs, admissions included. Governance on `testnet-1` is deliberately fast (2-hour voting period) so upgrade drills take hours, not days; everything economic is identical to mainnet. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index e02ee18..891ffad 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -65,13 +65,17 @@ kons1... (0x...) is not allowed to receive funds: it is the "fee_collector" modu kons1... (0x...) is not allowed to receive funds: blocked address (module account or precompile): unauthorized ``` -Most tooling never gets that far: without an explicit `--gas-limit`, `cast` -and wallets call `eth_estimateGas` first, and the simulation hits the same -guard at its (uncommitted) state write, so what you see is: - -``` -execution reverted: failed to set account: kons1... is not allowed to receive funds: unauthorized -``` +What you see depends on the recipient (both reproduced on a dev chain, +2026-09-21): + +- **Module account** (`fee_collector`, `bonded_tokens_pool`, …): + `eth_estimateGas` *succeeds* and returns `21000` — the guard fires at + send, not in simulation — so `cast`, MetaMask and friends estimate fine + and then `eth_sendRawTransaction` refuses with the first string above. +- **Precompile** (`0x…0900`, and the others): `eth_estimateGas` fails first + with `execution reverted: failed to set account: kons1... is not allowed + to receive funds: unauthorized`; with an explicit gas limit the send is + refused with the second string above. **Residual case: internal calls.** The submission check only sees the transaction's own `to`. If a *contract* forwards value to a blocked address @@ -95,16 +99,30 @@ even when the transaction used its `0x` form.) Nothing was charged; the transact never entered the mempool. **What it means.** The chain has a compliance block list (`x/compliance`), -enforced for **every** transaction, EVM and Cosmos alike, native KASH -included. A transaction is rejected if *any* address it involves is frozen: -the sender, the recipient, an EVM `to`, a delegation authority in an -EIP-7702 authorization list, and so on. The check runs in the mempool -pre-check as well as the ante handler, so the refusal is immediate and -explained rather than discovered at block time. +checked on every transaction, EVM and Cosmos alike. A transaction is +rejected if a frozen address **signs** it (the sender, a delegation +authority in an EIP-7702 authorization list) or is its direct **`to`** (a +bank send recipient, an EVM `to`). The check runs in the mempool pre-check +as well as the ante handler, so the refusal is immediate and explained +rather than discovered at block time. + +**What it does not mean.** A freeze does **not** immobilise the address's +balance. The check looks at signers and the transaction's `to`, not at bank +transfers, and precompiles move balance directly: KASH can still be pulled +out of a frozen account through the werc20 precompile or any contract by an +allowance granted before the freeze (`transferFrom(frozen, spender)` +succeeds), and can still arrive by contract call (`transfer(frozen, …)` +succeeds — only a plain value transfer or bank send *to* it is refused). +Reproduced on a dev chain 2026-09-21. A contract that must not serve a +frozen address has to call +[`isFrozen`](/contracts#icompliance-at-0x0900) itself. A bank-level +restriction is planned before mainnet; until then, do not describe a freeze +as "the funds are locked". Freezes come from the compliance authority (a foundation multisig on -mainnet) through an on-chain timelock — 24 h on mainnet; a per-network -genesis parameter, short on `testnet-1` and 60 s on a local dev chain — or +mainnet) through an on-chain timelock — 24 h by default (governance-changeable, +minimum 1 min; `testnet-1`'s value is set in its genesis, `local_node.sh` +uses 60 s) — or as an **emergency freeze** that takes effect immediately and auto-expires after the timelock unless ratified; governance can override any entry. Every change is an on-chain event. @@ -170,7 +188,7 @@ Two message families are worth knowing by name: |---|---| | `/ibc.applications.transfer.v1.MsgTransfer` | all IBC sends stop, Cosmos and EVM (the ICS20 precompile included) | | `/cosmos.evm.vm.v1.MsgEthereumTx` | the **entire EVM is paused** — every `eth_sendRawTransaction` is refused until reset | -| `/cosmos.staking.v1beta1.MsgCreateValidator` | **disabled by design from genesis** on both networks: validator admission is permissioned (D16), see [Run a Validator](/run-a-validator#become-a-validator). Not a fault. | +| `/cosmos.staking.v1beta1.MsgCreateValidator` | **disabled by design from genesis** on both networks: validator admission is permissioned, see [Run a Validator](/run-a-validator#become-a-validator). Not a fault. (Decided; the genesis tooling does not write the entry yet, so a dev chain accepts the message.) | The breaker cannot disable its own messages or governance's, so a trip is always reversible.