Base unit (O-2.6): the design, the proposed Decided sentence and the ledger row for 18 decimals
docs/design/base-unit.md: where every amount lives (UTXO value, entry, mass view, coinbase and its payload, fees, script introspection, p2p, gRPC, RPC model, JSON and wasm, wallet, UTXO index, the exec bridge, pool accounting, pool software, app display, observer and public stats, the manifest), the u128 Amount type, the serialisation and versioning table (tx version 2 for the 16-byte value, a store schema version with the u64 legacy decode, two uint64 varints on the wire with the low word on the existing field number), the EVM side (1 IGN = 10^18 wei, the bridge the identity), the devnet unchanged at 8 with its digest unmoved, the display rule (8 visible digits), the hostile review (the u64 pool share overflow, the ramp, the 66-period table, the floor at the finer unit, the storage mass constant at 10^22, dust and the relay floor, MAX_SOMPI, KIP-10 script numbers, JavaScript precision, the wrong-unit peer, the half-widened binary, Postgres bigint), the measurement (+8 B per UTXO on disk, +17 B in memory, +6.4 B per output on the wire) with the consequences by tier, and phase B in ten commits with hours (26). Spec 2.5 carries the proposed Decided sentence and the 18-decimal rate; 06-open-items O-2.6 proposed decided with its gate B10; fud-ledger E22; fork-divergence row for the fork commits (3158571c, fa61e035, fcd6b6e9, 6d5f2488 on the box mirror, branch decimals); bench-log "Base unit widening cost"; public-stats unit note; ledger-decisions row 5 lane state. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
437ca52ca8
commit
50363a64f8
8 changed files with 183 additions and 5 deletions
|
|
@ -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`) |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
146
docs/design/base-unit.md
Normal file
146
docs/design/base-unit.md
Normal file
|
|
@ -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.
|
||||
|
|
@ -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 <hex32>` (so a miner's EVM address, the low 20 bytes, is a key it holds), `--hold-ms <n>` (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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
Loading…
Reference in a new issue