diff --git a/docs/api/public-stats.md b/docs/api/public-stats.md index d41fb08ae..d7b393551 100644 --- a/docs/api/public-stats.md +++ b/docs/api/public-stats.md @@ -106,7 +106,7 @@ reflects a one-minute window): | Field | Meaning | |---|---| -| `unit` | IGN; the coinbase pays in 8-decimal units (open item O-2.6), the EVM shows 18 | +| `unit` | IGN; the devnet's coinbase pays in 8-decimal units and the EVM shows 18; the testnet and mainnet unit is 18 on both sides (O-2.6, proposed Decided 6 October 2026, `docs/design/base-unit.md`), so `decimals_consensus` reads the network's genesis parameter | | `daa` | The newest DAA score the observer stored | | `max_supply_ign` | 4,000,000,000, the hard cap (spec 2.5, no tail emission: spec 5.10) | | `circulating_ign`, `circulating_sompi` | Minted so far by the rule: `E(t)` summed over every DAA second from 0 to `daa`, exact (floor sum, `mintedByRule`) | diff --git a/docs/bench-log.md b/docs/bench-log.md index c37889aef..24834bfb5 100644 --- a/docs/bench-log.md +++ b/docs/bench-log.md @@ -2597,3 +2597,25 @@ USD 20 an hour on community pods, against a devnet of 1.16 GH/s. Consequence: the devnet's hash is rentable for the price of a dinner, so nothing on it is a security result; the counter-ASIC and finality work is tested there for correctness, not for cost. The cost argument only starts at the TH/s scale, where the rental market's supply (not its price) is the limit, and that number belongs in the litepaper with this caveat. + +## Base unit widening cost, 6 October 2026, 22:05Z (branch `decimals`, fork eec34ac3 + the O-2.6 commits; igneum-build-1) + +What moving every amount from u64 (8 decimals) to u128 (18 decimals, `unit::Amount`) costs, on 1,000,000 synthetic UTXOs of the +devnet's shape (34-byte pay-to-pubkey script, DAA score, coinbase flag, no covenant). `cargo test -p kaspa-consensus-core --release +--lib unit_measure -- --ignored --nocapture` on igneum-build-1 (AX162, 128 GB), 0.84 s; `consensus/core/src/unit_measure.rs`. + +| | 8 decimals (u64) | 18 decimals (u128) | Difference | +|---|---|---|---| +| UTXO set, bincode value plus the 35-byte RocksDB key | 97 B per entry, 97,000,000 B | 105 B per entry, 105,000,000 B | +8 B per entry, +8.25 percent | +| `size_of` the entry struct | 112 B | 128 B | +16 B (the u128 aligns the struct to 16) | +| Resident memory, 1,000,000 `Arc` entries | 151,945,216 B (151 B per entry) | 168,005,632 B (168 B per entry) | +17 B per entry, +10.6 percent | +| Wire, per output, protobuf overhead included, 1,000 schedule amounts | 6.0 B (`uint64` varint) | 12.4 B (two `uint64` varints, low and high) or 18 B (16-byte `bytes` field) | +6.4 B or +12 B | +| Wire, a 2-output transaction | | | +12.8 B or +24 B | + +Consequences by tier: a node at 10,000,000 UTXOs (approximate, Kaspa's order) holds 80 MB more on disk and 170 MB more in memory, +against the 1 GiB dataset and a 2 to 4 GB node, so no home miner (8, 12, 16, 24 or 32 GB card), rig or pool node changes class on +Windows, Linux or macOS; hash rate, power and deadlines are untouched. At 1 BPS and 100 transactions a block the wire grows 1.3 KB +a second (110 MB a day) with two varints and twice that with the fixed field; at the 10 BPS step 1.1 GB against 2.1 GB a day, so the +design picks two varints and keeps the existing field number for the low word (a message under 2^64 is byte-identical to today's). +The fork's suites after the change: `kaspa-consensus-core` 129 passed (the 18 new tests among them), the rest in the O-2.6 report. + diff --git a/docs/design/base-unit.md b/docs/design/base-unit.md new file mode 100644 index 000000000..6958b1df6 --- /dev/null +++ b/docs/design/base-unit.md @@ -0,0 +1,146 @@ +# The base unit of IGN (O-2.6) + +Decision proposed for the project lead's word (6 October 2026, 22:xx UK; his answer in `docs/plans/ledger-decisions.md` row 5 was +"18"): one IGN is 10^18 base units on the testnet and on mainnet, the EVM's wei, so a consensus amount and an EVM +balance are the same number and the bridge is the identity. The devnet keeps 10^8 (Kaspa's sompi, what it mints +today). The unit is a genesis parameter, `Params::base_unit_decimals`, per network, in the consensus digest once it +leaves 8. Every amount in the node becomes a `u128` behind one type, `kaspa_consensus_core::unit::Amount`, end to +end, so nothing depends on the cap fitting any type: the emission lane may add a tail or remove the cap and the +type does not care (headroom 3.4 x 10^38, which is 10^19 seconds of the 18-decimal full rate). + +Why 18: every EVM wallet, bridge, explorer and rollup assumes it (`nativeCurrency.decimals: 18` is already what +`tools/evm-smoke/smoke.mjs` tells viem; MetaMask shows 18). Why not keep 8 and bridge at 10^10, as the devnet does: the +public text then carries two unit systems ("the coinbase pays 8-decimal units, the EVM shows 18", `docs/api/public-stats.md`), +every exchange integration has to know which API returns which, and a UTXO-side amount with 8 decimals cannot express a +wei-level EVM balance, so the two ledgers can never be reconciled to the unit. Why not U256: consensus-core has no alloy +dependency, u128 holds every schedule by a factor of 10^11, and u128 to U256 is lossless on the exec side. + +## 1. Where an amount lives today (the fork at `ca3-v4-0316`, eec34ac3) + +| Place | File | Type and byte form today | At 18 decimals | +|---|---|---|---| +| UTXO output value | `consensus/core/src/tx.rs:175` `TransactionOutput.value` | `u64`; borsh 8 LE; the tx hash and id write 8 LE bytes (`hashing/tx.rs:125`); the sighash covers it through `outputs_hash` | `Amount` (u128); 16 LE bytes under a new tx version (section 3) | +| UTXO set entry | `consensus/core/src/utxo/utxo_entry.rs:21` `UtxoEntry.amount` | `u64`; bincode in the store (`consensus/src/model/stores/utxo_set.rs`, `CachedDbAccess`, `database/src/access.rs:134`); muhash over the bincode | `Amount`; store schema version with a u64 legacy decode (the pre-Toccata `TLegacy` path, `access.rs:214`, is the pattern) | +| Mass view of a UTXO | `consensus/core/src/mass/mod.rs:141` `UtxoCell.amount`; `calc_storage_mass` | `u64`; C = `storage_mass_parameter` = 10^12 (`constants.rs` `STORAGE_MASS_PARAMETER = SOMPI_PER_KASPA x 10,000`), in Params and the digest | `UtxoCellUnits` and `calc_storage_mass_units` (in the tree now); C = `Params::storage_mass_parameter_units()` = 10^22, which no u64 holds | +| Coinbase subsidy | `consensus/core/src/igneum.rs` (`block_subsidy`, u64 table); `consensus/src/processes/coinbase.rs` (`CoinbaseData.subsidy` u64, payload `LENGTH_OF_SUBSIDY` = 8 LE bytes at `:14`, `:139`; `BlockRewardData.subsidy`) | `u64` everywhere | `block_subsidy_units` (in the tree now, u128); the payload carries 16 LE bytes under the new tx version | +| Coinbase outputs | `coinbase.rs:84 to 118` (producer, pool, red reward sums) | `u64` adds | `Amount` with checked adds | +| Tx validation caps | `consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs:155, 165` `MAX_SOMPI` | `u64` = 4 x 10^9 x 10^8 | `Params::supply_cap_units()` or no cap check at all (a tail removes the meaning of MAX) | +| Fees (UTXO side) | `mining/src/mempool/model/frontier/feerate_key.rs:9` `fee: u64`; `rpc/core/src/model/mempool.rs:8`; `DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE = 100_000` sompi per kilogram (`mining/src/mempool/config.rs:20`) | `u64` | `Amount`; the relay floor and the dust rule scale with the unit (section 7) | +| Script introspection | `crypto/txscript/src/opcodes/mod.rs:1100` `OpTxInputAmount`, `:1151` `OpTxOutputAmount` (KIP-10) | amounts pushed as script numbers, i64 | 16-byte script numbers for these two opcodes under the new tx version, or the opcodes refused above 2^63 (section 7) | +| P2P wire | `protocol/p2p/proto/p2p.proto:62` `uint64 value = 1` (output), `:190` `uint64 amount = 1` (UTXO entry) | varint, 6.0 B per schedule amount measured | low word stays field 1, `uint64 value_hi` added (section 3); 12.4 B measured | +| gRPC | `rpc/grpc/core/proto/rpc.proto:103, 122` `uint64 amount`, `:310` `uint64 fee` | varint | the same low and high pair | +| RPC model, wRPC borsh | `rpc/core/src/model/tx.rs:26, 245` | `u64`; borsh 8 LE | `Amount`; borsh 16 LE; the RPC serializer version steps (3 today for the checkpoints report) | +| JSON and wasm clients | `consensus/client/src/serializable/numeric.rs:34, 236`, `consensus/client/src/output.rs:56`, `utxo.rs:69` | JSON numbers (lossy past 2^53 in JavaScript) | `Amount` serialises as a decimal string in JSON (in the tree now); wasm hands out `BigInt` | +| Wallet | `wallet/core/src/utils.rs:34 to 70` (`sompi_to_kaspa` as f64), `wallet/pskt/src/output.rs:15`, `wallet/core/src/storage/transaction/*.rs`, `cli/src/utils.rs` | `u64` and `f64` formatting | `Amount::format_ign` and `parse_ign` (in the tree now, exact, never a float); the Igneum wallet lives on the `testnet-wallet` branch and is not in master yet | +| UTXO index | `indexes/core/src/indexed_utxos.rs:25` and the circulating supply counter | `u64` | `Amount`; the supply counter checked | +| Exec bridge | `igneum/exec/src/config.rs:43` `WEI_PER_SOMPI = 10^10`; `executor.rs:145` `execute_segment(.., subsidy_sompi: u64, ..)`, `:167` `producer as u128 x WEI_PER_SOMPI` | u64 in, U256 out | `Params::wei_per_unit()` (in the tree now, 1 at 18); `execute_segment` takes `Amount`; the SP1 guest reads the rewards in wei already (the fixture's `rewards` and `payouts` are U256), so its input type changes once and the shard program id is re-pinned at the testnet genesis, never on the devnet | +| Proving pool accounting | `igneum/exec/src/service.rs:1029` (`block_subsidy` u64 into the segment), `proving.rs` `split_pool_credit` (wei) | u64 subsidy, wei credits | `Amount` subsidy; credits unchanged (already wei) | +| Pool software | `pool/src/state.rs:9 to 11` `WEI_PER_IGN`, `WEI_PER_SOMPI`; `pool/src/node.rs:251, 366` | u64 sompi from RPC, wei out | reads the unit from the node (`igneum_getProvingStatus` or the params RPC) and drops `WEI_PER_SOMPI` | +| App display | `app/igneum-app/src/prover.rs:501, 548` (`wei as f64 / 1e18` in event lines) | f64 for log lines | `format_ign` for anything a user reads (section 6) | +| Observer and public stats | `tools/observer/observer.mjs:110` `subsidy_sompi bigint`, `paid_sompi bigint`; `docs/api/public-stats.md` `decimals_consensus: 8` | Postgres `bigint` is i64: 9.2 x 10^18, below one 18-decimal block | `numeric` columns; `decimals_consensus` reads the network's unit | +| Signed manifest, tuning kits | `app/igneum-app/src/config.rs` (update manifest), `infra/fleet` tuning files | no amount fields (checked 6 October 2026: the manifest carries URLs, versions and hashes; the tuning kits carry miner settings) | nothing | + +## 2. The type + +`kaspa_consensus_core::unit::Amount(pub u128)`: `Copy`, `Ord`, `Hash`, `Default`, borsh (16 LE bytes), serde (a decimal +string in human-readable formats, the u128 in binary ones, reading a JSON number too), checked and saturating +arithmetic, `percent` without an intermediate product, `to_le_bytes` / `from_le_bytes`, `to_wire` / `from_wire` +(16 bytes, or the 8-byte u64 form every rusty-kaspa format carries today), `rescale(from, to)` (exact, refuses a +remainder), `to_wei(decimals)`, `format_ign` / `parse_ign` (section 6). Helpers: `unit(decimals)`, `wei_per_unit`, +`decimals_valid` (0 to 18), `IMPLEMENTED_DECIMALS` (section 8). A NewType, not a bare `u128`, so a unit is never +added to a count and every serialiser sees one type. + +## 3. Serialisation and versioning + +| Format | Change | Versioned by | +|---|---|---| +| Transaction hash and id (`hashing/tx.rs`), sighash | `value` as 16 LE bytes | `TX_VERSION` 2 (new); versions 0 and 1 keep 8 bytes, so every devnet hash is unchanged; a network at 18 refuses versions 0 and 1 at genesis | +| Coinbase payload | `LENGTH_OF_SUBSIDY` 16 | the coinbase transaction's version | +| UTXO set store, UTXO diffs, muhash | bincode of `UtxoEntry` with a 16-byte amount | a store schema version with the u64 legacy decode (`CachedDbAccess` already carries one for pre-Toccata entries); a fresh network has no legacy rows | +| P2P (`p2p.proto`) | `uint64 value = 1` stays as the low word, `uint64 value_hi` added (same for `amount`); a wide amount sets both | `PROTOCOL_VERSION` 16 to 17; an amount under 2^64 encodes exactly as today, so the digest, not the wire, is what separates networks | +| gRPC (`rpc.proto`) | the same low and high pair | the gRPC message is additive; old clients read the low word, which is exact under 2^64 | +| wRPC borsh, RPC model | `Amount` (16 LE) | the RPC serializer version | +| JSON RPC, wasm | string / `BigInt` | the JSON shape accepts numbers on the way in | +| Coinbase payload in the observer, `/api/supply` | `numeric`, strings | the observer's table migration | + +Two varints were measured against a 16-byte `bytes` field: 12.4 B against 18 B per output at the schedule's amounts +(section 5), and the low word reuses the field number so a message under 2^64 is byte-identical to today's. + +## 4. The EVM side + +1 IGN = 10^18 wei, the base unit. `Params::wei_per_unit()` is 1 at 18 decimals and 10^10 at 8 (today's `WEI_PER_SOMPI`, +which the widening replaces). The executor credits `Amount::to_wei(decimals)` of the producer and pool shares; the pool +escrow, the payouts, the fee splits and the aggregator share are already in wei and do not change. The SP1 guest's +statement carries rewards and payouts in wei and changes only at the input type, so the shard program id moves once, +at the testnet genesis, where it is pinned fresh anyway; the devnet's pinned id is untouched. + +## 5. What it costs (measured on igneum-build-1, 6 October 2026, `consensus/core/src/unit_measure.rs`, 1,000,000 synthetic UTXOs of the devnet's shape) + +| | 8 decimals (u64) | 18 decimals (u128) | Difference | +|---|---|---|---| +| UTXO set, serialised + 35-byte key | 97 B per entry, 97.0 MB | 105 B per entry, 105.0 MB | +8 B, +8.25 percent | +| `size_of` the entry | 112 B | 128 B | +16 B (alignment to 16) | +| Resident memory, 1,000,000 `Arc` entries | 151 B per entry, 152 MB | 168 B per entry, 168 MB | +17 B, +10.6 percent | +| Wire, per output (protobuf overhead included) | 6.0 B (varint) | 12.4 B (two varints) or 18 B (16-byte bytes) | +6.4 B or +12 B | +| Wire, a 2-output transaction | | | +12.8 B or +24 B | + +Consequences by tier. A home miner's node at 10,000,000 UTXOs (approximate; Kaspa's set is of that order) holds +80 MB more on disk and 170 MB more in memory, against a 1 GiB dataset and a 2 to 4 GB node: no tier changes class, on +8 GB cards or on 32 GB, on Windows, Linux or macOS. A pool or rig node is the same number. At 1 block a second and +100 transactions a block the wire grows 1.3 KB a second with two varints, 110 MB a day; at the 10 BPS step 1.1 GB a +day, which is why two varints are the recommendation and not the fixed 16 bytes. Nothing changes for a miner's hash +rate, power or deadline. For a wallet or exchange the number is the same as Ethereum's, which is the point. + +## 6. Display + +The app and the wallet show IGN with up to 8 digits after the point (`unit::VISIBLE_DECIMALS`), truncated toward +zero, trailing zeros trimmed, no point on a whole number; a user never sees 18 digits (`Amount::format_ign`: +31,688,087,814,028,950,237 wei reads "31.68808781", the same as the 8-decimal 3,168,808,781 sompi). An amount under +10^-8 IGN reads "0"; a tooltip or a detail view may use `format_ign_visible(decimals, 18)`. Input is `parse_ign`: +digits, one point, at most `decimals` fraction digits, no float anywhere (the wallet's `sompi_to_kaspa` f64 helpers are +retired at the widening; `pool/src/payout.rs` and `prover.rs` keep `as f64 / 1e18` for log lines only). + +## 7. Hostile review + +| Attack or slip | Finding | Answer | +|---|---|---| +| Overflow at a multiplication | `proving_pool_share` (u64) multiplies by 20 before dividing: overflows above 9.2 x 10^17 sompi, unreachable at 8 decimals (above the cap) and reached by the first full-rate block at 18 | `Amount::percent` splits quotient and remainder; the u128 family is exact to `u128::MAX` (tested) | +| The launch ramp | `launch_ramp` goes through u128 and back to u64; at 18 the result itself is above u64 | `launch_ramp_units` with a checked product and an overflow-free fallback (tested at `u128::MAX`) | +| The subsidy table | `SUBSIDY_PERIODS = 33` is a property of the 8-decimal base (31 bits); at 18 the base is 65 bits and the table is 66 long | `subsidy_periods_units(decimals)`, `per_second_subsidy_units` with a shift that is zero at 128 and above, never a wrap (tested) | +| The floor at the finer unit | 18 decimals mints 4,028,950,237 wei a second more than 10^10 x the 8-decimal rate (the 8-decimal floor drops them); the display agrees to 8 digits | stated; the total stays under the cap by under 100 IGN at both units (tested) | +| Storage mass constants assume sompi | C = 10,000 IGN = 10^12 sompi; at 18 it is 10^22, above u64, and `C x p^2` at the script-length plurality bound (100) is 10^26 | `storage_mass_parameter_units()`, `calc_storage_mass_units` in u128; the formula is scale-free, so a transaction weighs the same at both units (tested on the KIP-9 vectors and the three paths); the one floor at the unit (the mean input) can move the answer by at most the input plurality | +| Dust | KIP-9 refuses a one-unit output by mass (C x p^2 / 1 = 10^22 at 18, saturated to `u64::MAX`, over every limit), the same answer as 10^12 at 8; the relay floor `DEFAULT_MINIMUM_RELAY_TRANSACTION_FEE` = 100,000 sompi per kilogram is a unit-bearing constant | the mass rule holds at both units (tested); the relay floor becomes 10^-3 IGN per kilogram at the network's unit, a mempool constant, not consensus | +| `MAX_SOMPI` | 4 x 10^9 x 10^8 as a transaction cap; at 18 it is 4 x 10^27 and a tail removes its meaning | `supply_cap_units()` for the check, or the check goes at the widening; nothing in the type depends on it | +| Script numbers | KIP-10 introspection pushes amounts as i64 script numbers; a wei amount above 2^63 does not fit | at the widening: 16-byte script numbers for the two opcodes under tx version 2, or the opcodes fail above 2^63; the decision belongs to the covenant lane, flagged here | +| JavaScript precision | a JSON number above 2^53 rounds | `Amount` is a string in JSON (tested) | +| A peer on the other unit | its coinbases differ by 10^10 | the unit is in the digest once it leaves 8; the handshake refuses it (tested) | +| A half-widened binary starts the testnet | the coinbase, the UTXO value and the wire still carry u64 | `IMPLEMENTED_DECIMALS` = [8]; `igneumd` refuses to start a network whose unit it cannot mint (section 8) | +| Postgres `bigint` in the observer | i64 overflows at one 18-decimal block | `numeric` at the widening | + +## 8. What is in the tree tonight and what is left + +In the tree (fork branch `decimals` from eec34ac3 on the box mirror: 3158571c the type, fa61e035 the parameter and the schedule, fcd6b6e9 the mass rule, 6d5f2488 the measurement; each commit tested on its own on igneum-build-1, the first two by a stash-and-run: 120 and 126 consensus-core tests; the final tree: kaspa-consensus-core 129 passed, igneum-exec 21, igneum-miner 18, kaspa-p2p-flows 33, kaspa-pow 15, kaspad 2 + 7; kaspa-consensus 102 passed and 1 failed, `igneum_m20_tests::witnesses_are_checked_in_epoch_order_under_their_own_seeds` under the `igneum-pow` feature, which fails identically on the untouched eec34ac3 and is not this lane's): +the parameter in `Params`, `OverrideParams`, the digest (once not 8), all four networks (devnet and simnet 8, testnet and +mainnet 18); `unit::Amount` with every byte form and the round-trip test above `u64::MAX` first; the schedule at any +unit (`block_subsidy_units` and family) asserted bit for bit against the u64 schedule at 8 and under the cap at 18; +`calc_storage_mass_units` with the same-answer test; `Params::unit`, `wei_per_unit`, `storage_mass_parameter_units`, +`supply_cap_units`; the daemon's base-unit line and its refusal to start on a unit the binary cannot mint; the +measurement. The devnet's digest is unchanged (tested: an explicit 8 in the override file gives the same digest). + +Left (phase B, the widening proper; every row is a commit that compiles and passes on its own, in this order): + +| Step | Files | Hours | +|---|---|---| +| B1 `TransactionOutput.value`, `UtxoEntry.amount`, `UtxoCell` to `Amount`; tx version 2 in `hashing/tx.rs` and the sighash; `MAX_SOMPI` to the params | consensus-core (tx, utxo, hashing, mass, errors, sign) | 4 | +| B2 coinbase: `CoinbaseManager` on `block_subsidy_units`, payload 16 bytes under version 2, `BlockRewardData`, the validators | consensus (coinbase, body validation, tx validation, utxo validation, test consensus) | 3 | +| B3 the UTXO store schema version with the legacy decode, utxo diffs, muhash, the UTXO index | consensus stores, database, indexes | 2 | +| B4 p2p: `value_hi` and `amount_hi`, protocol version 17, converters | protocol/p2p, flows | 2 | +| B5 RPC: gRPC pair, rpc-core model, wRPC serializer version, JSON strings, wasm BigInt | rpc, consensus/client, wasm | 3 | +| B6 mempool and mining: fee rate keys, relay floor at the unit, dust, templates | mining | 2 | +| B7 exec bridge: `execute_segment(Amount)`, `wei_per_unit`, the guest input type, the shard program id re-pinned for the testnet genesis only | igneum/exec, proving/igneum-prove | 2 | +| B8 txscript: the two introspection opcodes at 16-byte numbers or refused above 2^63 | crypto/txscript | 2 | +| B9 wallet, cli, pool, observer, public stats, the app's display on `format_ign` | wallet, cli, pool, tools/observer, app | 3 | +| B10 flip `IMPLEMENTED_DECIMALS` to [8, 18]; the six-crate suite and a Devnet 2 crossing at 8 (nothing moves); a two-node testnet-params chain at 18 mining for ten minutes with the coinbase, the explorer and viem reading the same number | tools/fleet, the gate | 3 | + +26 hours of lane work. The devnet needs none of it to keep running; the testnet genesis needs all of it, and B10 is +the gate. diff --git a/docs/fork-divergence.md b/docs/fork-divergence.md index 64b26fc28..cbb55b456 100644 --- a/docs/fork-divergence.md +++ b/docs/fork-divergence.md @@ -129,6 +129,7 @@ Implements `docs/design/execution-layer.md` D1 to D10 and section 8.2 on a 3-nod | `igneum/evm-types/` (new crate `igneum-evm-types`) | Decoding through alloy (`TxEnvelope::decode_2718`), sender recovery, Cancun intrinsic gas, the per-transaction and per-body state-free rules; depends on alloy only so consensus and the executor share one decoder | Design 1.5 state-free class in one place | None to consensus beyond its use above | New crate. Pins `alloy-consensus 2.5`, `alloy-primitives 1.7`, `alloy-eips 2.5`. | | `igneum/exec/` (new crate `igneum-exec`) | The execution layer: `service.rs` chain follower (polls `get_virtual_chain_from_block`, builds segment(C) from `get_block_acceptance_data` which is written in `consensus_ordered_mergeset` order, unwinds reorgs from a 64-deep snapshot ring), `executor.rs` (revm 43 over the segment, rewards by rule, two-dimensional gas, fee flows, skip rule), `pgas.rs` (the prototype pgas table and the per-frame attribution inspector, CREATE rule for the registry), `state.rs` (in-memory revm `CacheDB`, MPT state root through alloy-trie as an output), `pool.rs` (EVM mempool), `rpc.rs` (`eth_*` and `igneum_*` JSON-RPC on axum), `registry.rs` and `contracts/DeveloperRegistry.sol` (system contract at `0x...0210`, runtime bytecode embedded), `bin/diff.rs` (`igneum-exec-diff`, the plain-revm differential harness) | Design sections 1 to 4 and 8.2 | None to consensus (the node runs without it under `--evm-disable`) | New crate. Pins `revm 43`, `alloy-trie 0.9`, `axum 0.8`. The in-memory state and the full-recompute state root are devnet scope (see "what is missing"). | | `igneum/miner/src/main.rs` | `--vote-key-hash ` (so a miner's EVM address, the low 20 bytes, is a key it holds), `--hold-ms ` (paces the stub miner on a network without proof of work), `--network simnet` (simnet address prefix for label addresses) | Test-network tooling | None | Internal tool. | +| `consensus/core/src/unit.rs` (new), `consensus/core/src/igneum.rs`, `consensus/core/src/mass/mod.rs`, `consensus/core/src/config/params.rs`, `kaspad/src/daemon.rs`, `consensus/core/src/unit_measure.rs` (new, an ignored measurement) | O-2.6 (6 October 2026, branch `decimals`): `Params::base_unit_decimals` (8 devnet and simnet, 18 testnet and mainnet; `OverrideParams`; in the digest once not 8); `unit::Amount(u128)` with borsh 16 LE, serde (string in JSON, u128 in bincode), the 16-byte and legacy 8-byte wire forms, `rescale`, `to_wei`, `format_ign` (8 visible digits) and `parse_ign`; the schedule at any unit (`block_subsidy_units`, `base_subsidy_per_second_units`, `subsidy_periods_units`, `launch_ramp_units`, the 80/20 split on `Amount`); `calc_storage_mass_units` and `UtxoCellUnits`; `Params::unit`, `wei_per_unit`, `storage_mass_parameter_units`, `supply_cap_units`, `base_unit_valid`, `base_unit_implemented`; the daemon prints the unit and refuses a unit outside `unit::IMPLEMENTED_DECIMALS` ([8] until the widening) | One IGN is 10^18 base units on every public network so the EVM bridge is the identity; the devnet keeps 8 and its digest does not move; nothing in the type depends on the cap | Low tonight (additive; the u64 schedule and mass rule are untouched and asserted equal at 8). High at phase B (`docs/design/base-unit.md` section 8: the UTXO value, the coinbase payload, the stores, p2p, RPC, mempool, exec bridge, txscript, wallet) | Pure addition beside Kaspa's `SOMPI_PER_KASPA`; upstream's u64 amount stays until phase B replaces it with `Amount`. | ## Decisions recorded as open diff --git a/docs/fud-ledger.md b/docs/fud-ledger.md index e8e808d52..53ad72b10 100644 --- a/docs/fud-ledger.md +++ b/docs/fud-ledger.md @@ -574,6 +574,15 @@ Answer: Correct. The fee block carries the dev address in the producer output on Evidence: `docs/analysis/horizon/economy-and-utility.md` section 4.4 (`devfee_out.md`) and proposal 7, 6 October 2026; the fee measured on a test network, 4 October 2026 (bench-log: 9 fee blocks in 785). +### E22. Eight decimals on a chain that calls itself EVM-native +"Your coinbase pays sompi, your EVM shows wei, and your own stats API has to carry two `decimals` fields to explain it. Every wallet and bridge assumes 18. Either the UTXO side is a second ledger nobody reconciles to the unit, or you widen every u64 in a Kaspa fork the week before a testnet." + +Status: Proposed Decided (6 October 2026, 22:xx UK; the project lead's word "18" in `docs/plans/ledger-decisions.md` row 5, the implementation lane's gate pending): 10^18 base units per IGN on the testnet and mainnet, 10^8 on the devnet; a genesis parameter, per network, in the consensus digest once it leaves 8; every amount a `u128` behind one type. Spec 2.5 carries the sentence; `docs/design/base-unit.md` is the design. Was: Open (O-2.6), 8 decimals in the code. + +Answer: Correct on both counts, and the second is the work. The unit is 18 on every public network, so a consensus amount and an EVM balance are the same number and the bridge is the identity (`Params::wei_per_unit()` = 1; the devnet's 10^10 is the same function at 8). The widening is real: at 18 decimals the first full-rate block is 31,688,087,814,028,950,237 base units, above `u64::MAX` (18,446,744,073,709,551,615), so no u64 in the fork can hold one coinbase, and the type is `u128` with 10^19 seconds of headroom at that rate, so the emission lane's cap or tail never meets the type again. What is in the tree tonight (fork branch `decimals`): the parameter in all four networks (devnet and simnet 8, testnet and mainnet 18), the type with every byte form and a round-trip test above `u64::MAX` as the first test, the schedule at any unit asserted bit for bit against the u64 schedule at 8 and under the cap at 18, the u128 storage mass rule with the same answer at both units, and a daemon that refuses to start a network at a unit the binary cannot mint (`unit::IMPLEMENTED_DECIMALS` = [8] until the widening lands). What the widening costs (measured on igneum-build-1, 1,000,000 synthetic UTXOs): +8 B per entry on disk (97 to 105 B, 8.25 percent), +17 B per entry in memory (151 to 168 B), +6.4 B per output on the wire with two varints (6.0 to 12.4 B). For a node at 10,000,000 UTXOs that is 80 MB of disk and 170 MB of memory, which changes no tier's class on any card or OS. What is left: 26 hours of lane work in ten commits (the design's section 8), gated by a two-node chain at 18 whose coinbase, explorer and an EVM wallet read one number. + +Evidence: `docs/design/base-unit.md` (the inventory of every amount, the serialisation table, the hostile review); the fork's `consensus/core/src/unit.rs`, `igneum.rs` (`schedule_units_tests`), `mass/mod.rs` (`units_tests`), `config/params.rs` (`base_unit_is_per_network_and_in_the_digest_once_not_eight`); `docs/bench-log.md`, "Base unit widening cost". + ## 5. Governance and the founders ### G1. No cryptography team diff --git a/docs/plans/ledger-decisions.md b/docs/plans/ledger-decisions.md index 1ac6842c5..b99bc5cf0 100644 --- a/docs/plans/ledger-decisions.md +++ b/docs/plans/ledger-decisions.md @@ -97,7 +97,7 @@ Still owed from the project lead after the close: the cryptanalysis spend (fundi | 2 | Latency-shadow ladder | Explanation requested | Answer pending; the six-step ladder, every step by 90 percent signal, never unconditional, verifier-bounded at 10 ms, is being implemented behind its switch meanwhile. | | 3 | Proof verification in consensus | On | `proving_consensus_verify_daa` = 0 in the testnet genesis. Devnet stays off. | | 4 | Finality leave item | On | `finality_leave_activation_daa` = 0 in the testnet genesis. Devnet stays never until the 95 percent signal. | -| 5 | Base unit | 18 | 18 decimals on the testnet (O-2.6 Decided, pending the implementation lane's gate); the devnet keeps 8. | +| 5 | Base unit | 18 | 18 decimals on the testnet (O-2.6 Decided, pending the implementation lane's gate); the devnet keeps 8. Lane state 6 October 2026, 22:xx UK: design `docs/design/base-unit.md`; the genesis parameter `base_unit_decimals`, the `u128` `Amount` type, the schedule at any unit and the u128 storage mass rule are in the fork (branch `decimals`, consensus-core 129 tests green on the box); the widening of the UTXO value, coinbase payload, stores, wire, RPC, mempool, exec bridge, txscript and wallet is phase B (26 hours, ten commits), and `igneumd` refuses to start a network at 18 until it lands. | | 6 | Cryptanalysis spend | Yes | An outside team attacks the hash class after class v4 has run a week of real hash; bounded (lane 2's USD 80,000 to 160,000); the procurement plan is owed. | | 7 | Testnet date | Leave open | No month anywhere; the go checklist is the date. | diff --git a/docs/spec/02-consensus.md b/docs/spec/02-consensus.md index 0abfa8e53..311079820 100644 --- a/docs/spec/02-consensus.md +++ b/docs/spec/02-consensus.md @@ -121,7 +121,7 @@ Designed (design document, "The token" and "Difficulty, block timing and proving | Halving interval | 63,115,200 DAA s (2 years of 365.25 days), for ever | Decided (ledger E9), `HALVING_INTERVAL_SECONDS` | | Launch ramp | linear from 10% at genesis to 100% at DAA second 2,592,000 (30 days) | Designed | | Split | 80% block producer, 20% proving pool. A red block inside the DAA window pays its 80% to the miner of the block that merges it and its 20% to the pool | Designed; the red rule Decided 3 October 2026 (ledger E10, round 3), `coinbase.rs:102 to 109` | -| Base unit | Open (O-2.6): the code keeps Kaspa's 8 decimals (`SOMPI_PER_KASPA`, devnet v0), under which the cap is 4 x 10^17 units and fits a u64; 18 decimals (the EVM convention) puts the cap at 4 x 10^27 and needs a wider type. `docs/fork-map.md` b2 wrote `cap_sompi = 4e9 x 1e8`. Decided before the first testnet genesis | +| Base unit | 10^18 base units per IGN on the testnet and mainnet (the EVM's wei, so a consensus amount and an EVM balance are one number and the exec bridge is the identity); the devnet keeps 10^8 (Kaspa's sompi, what it mints). A genesis parameter, `Params::base_unit_decimals`, per network, in the consensus digest once it leaves 8. Every amount is a `u128` behind `unit::Amount`, so nothing depends on the cap fitting any type. Proposed Decided 6 October 2026 (O-2.6; the project lead's word "18" in `docs/plans/ledger-decisions.md` row 5; design `docs/design/base-unit.md`; the parameter, the type and the schedule at any unit are in the fork at branch `decimals`, the widening of the UTXO value and the wire is phase B, gated by `unit::IMPLEMENTED_DECIMALS`, which the daemon enforces). Was: Open, 8 decimals in the code, `docs/fork-map.md` b2 `cap_sompi = 4e9 x 1e8` | Emission per DAA second at DAA score `t`: @@ -130,7 +130,7 @@ E(t) = ramp(t) * floor(10^9 * UNIT / 31,557,600) >> floor(t / 63,115,200) ramp(t) = min(1, 1/10 + 9/10 * t / 2,592,000) evaluated in integers as a rational with denominator 25,920,000 ``` -The pre-ramp rate is 31.68808781 IGN per DAA second at 8 decimals (`BASE_SUBSIDY_PER_SECOND_SOMPI` = 3,168,808,781, asserted by the code's own test) and about 31.688 IGN at any unit (Decided, ledger E9). The geometric series sums to 4 x 10^9 IGN; the ramp withholds 0.45 x 2,592,000 / 31,557,600 x 10^9 = about 37 million IGN that are never minted, and integer floors withhold a negligible further amount, so the cap is a strict bound. +The pre-ramp rate is 31.68808781 IGN per DAA second at 8 decimals (`BASE_SUBSIDY_PER_SECOND_SOMPI` = 3,168,808,781, asserted by the code's own test) and 31,688,087,814,028,950,237 base units at 18 (`igneum::base_subsidy_per_second_units(18)`, asserted; the floor is taken at the finer unit, so 18 decimals mints 4,028,950,237 wei a second more than 10^10 times the 8-decimal rate, and the display agrees to 8 digits); about 31.688 IGN at any unit (Decided, ledger E9). The geometric series sums to 4 x 10^9 IGN; the ramp withholds 0.45 x 2,592,000 / 31,557,600 x 10^9 = about 37 million IGN that are never minted, and integer floors withhold a negligible further amount, so the cap is a strict bound. Emission is keyed to DAA score, not to timestamps: `E` is a function of DAA score and miner-chosen timestamps cannot mint (hostile review table, "Emission per wall-clock second invites timestamp games"). `E` is paid per block, so coins are blocks times `E`: when the controller lets the block rate run above target, short-run emission runs above schedule by the same factor, as on every proof-of-work chain, and the cap is unaffected because the halving schedule and the ramp are in DAA seconds (Decided 3 October 2026, ledger E10, round 3; the earlier sentence "more blocks never means more coins" is withdrawn, and the devnet of 3 October 2026 minted 4.7x the schedule for eight minutes during a retarget lag, `docs/review/round-3-2026-10-03.md`, "Tonight's devnet"). Payment is through the merging block's coinbase as in Kaspa (`coinbase.rs:97 to 142`): the coinbase of block B pays, for each blue block M in B's mergeset, `E(daa_score(B))` split 80% to M's miner and 20% to the proving pool; for each red block in B's mergeset that is inside the DAA window, the same `E` split 80% to B's own miner and 20% to the pool (`coinbase.rs:102 to 109`, Kaspa's rule, Decided, ledger E10); a red outside the DAA window earns only its coinbase-level fees, of which Igneum has none (fees are in section 5). How the DAA-score increment is apportioned among the blues of one mergeset at higher block rates is Open (O-2.7); at 1 BPS the mergeset is usually one block. diff --git a/docs/spec/06-open-items.md b/docs/spec/06-open-items.md index 0da101da1..1450156b9 100644 --- a/docs/spec/06-open-items.md +++ b/docs/spec/06-open-items.md @@ -40,7 +40,7 @@ An item closes when its measurement is in `docs/bench-log.md` or its decision is | O-2.3 | Pruning depth must stay above the longest checkpoint gap and never pass the latest lock (section 2.1, F3) | Define the bound once the first-month rule (O-3.1) and the stall behaviour (section 3.3.1) fix the longest gap; implement in `block_depth.rs` and the virtual processor | 2, with 3 | | O-2.4 | Mapping of the 64-bit hash into the 256-bit target and the block level for pruning proofs (fork map a3) | Decision: `target64 = target256 >> 192`, `level = leading_zeros(hash64)` capped at 64, or widen the output; check `calc_level_from_pow` callers | 2 | | O-2.5 | Header validation gains a chain-state dependency (the epoch seed) and must stay deterministic from headers plus certificates during IBD and pruning-proof validation (fork map a4, risk High) | Implement `seed_source` validation; sync a node from genesis and from a pruning point on the devnet | 2 | -| O-2.6 | Base unit: 10^18 (EVM) or 10^8 (fork map b2) | Decision, owner execution engineer with consensus engineer | 2 | +| O-2.6 (decided, proposed) | **Proposed Decided 6 October 2026 (the project lead's answer "18", `docs/plans/ledger-decisions.md` row 5): 10^18 base units per IGN on the testnet and mainnet, 10^8 on the devnet; a genesis parameter `base_unit_decimals`, per network, in the digest once not 8; every amount a `u128` behind `unit::Amount`, so no cap or tail has to fit any type.** Design `docs/design/base-unit.md`. In the fork (branch `decimals` on eec34ac3): the parameter in all four networks, the type with every byte form (an amount above u64::MAX round-trips borsh, bincode, JSON, the wire; the known-failed case first), the schedule at any unit (bit for bit the u64 schedule at 8; the first full-rate block at 18 is 3.17 x 10^19, above u64::MAX; the full schedule under the cap in checked u128), the u128 storage mass rule (same answer at both units), the daemon's refusal to start a unit the binary cannot mint. Measured: +8 B per UTXO on disk, +17 B in memory, +6.4 B per output on the wire with two varints | Closes at phase B of the design (26 hours: the UTXO value, the coinbase, the stores, p2p, RPC, mempool, the exec bridge, txscript, the wallet and tools) and its gate B10: a two-node testnet-params chain at 18 mining ten minutes with the coinbase, the explorer and viem reading one number; then `IMPLEMENTED_DECIMALS` gains 18 and spec 2.5 drops "proposed" | 2, before the testnet genesis | | O-2.7 | Apportioning the DAA-score increment among several blue blocks in one mergeset at higher block rates (section 2.5) | Define before the first block-rate step; at 1 BPS the mergeset is usually one block | 2, before the 4 BPS step | | O-2.9 | `docs/fork-divergence.md` does not exist | Write it as the fork is made | 2 | | O-2.10 | Block-rate steps to 4 and 10 BPS each re-derive k, parents and mergeset limit and are hard forks (hostile review table) | Each step has its own test campaign, as Kaspa's Crescendo | later, consensus engineer |