Mainnet KES-period calculation ignores Byron slot duration
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 30/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Domain
- blockchain
Research direction
The calculation is getCurrentKESPeriodAt in src/crypto/Cardano/KESAgent/KES/Evolution.hs, which ignores the post-Shelley absolute-slot offset. Start by reproducing the numbers in the issue's GHCi session with cabal repl lib:crypto. Done means the agent and default config both return the absolute KES period for the fixed timestamp, and the issue's unchanged expected values are confirmed by a regression test. The maintainers still need to confirm the intended timing origin before any change.
Written by the indexing model from the issue text.
Description
Summary
At kes-agent-1.4.0.0 / commit 44fdcec892e2868053f964cad87fc32c590c81e4, getCurrentKESPeriodAt appears to calculate an incorrect absolute KES period for mainnet when using defEvolutionConfig or the unmodified mainnet Shelley genesis.
It treats all elapsed time since the 2017 network start as one-second slots, ignoring the Byron-to-Shelley slot-duration transition. At the fixed timestamp below, the agent formula produces period 2216 whereas mainnet's absolute-slot calculation gives 1557.
The native Haskell reproduction below calls the actual upstream implementation with both the shipped genesis and the default configuration. Its expected output follows from the inspected source. Premature key expiry and reconnect consequences are source-derived, not a live agent/node reproduction.
Affected calculation
getCurrentKESPeriodAt computes:
let diffSecs = floor (nominalDiffTimeToSeconds $ diffUTCTime now (systemStart ec))
kesPeriodDuration = fromIntegral (slotLength ec) * fromIntegral (slotsPerKESPeriod ec)
in KESPeriod (diffSecs `div` kesPeriodDuration)
Both the default configuration and the shipped mainnet genesis use:
systemStart = 2017-09-23T21:44:51Z
slotLength = 1
slotsPerKESPeriod = 129600
The genesis loader decodes these fields directly; nmoToAgentOptions passes the result through without a historical adjustment.
Why Shelley-only KES still needs the absolute-slot offset
KES was introduced with Shelley, but the chain's absolute slot number did not reset then. The Shelley transition announcement identifies 2020-07-29T21:44:51Z as absolute slot 4492800. Subsequent mainnet slots are one second long.
Thus, for post-Shelley mainnet with the current slot duration:
absoluteSlot = 4492800 + (unixTime - 1596059091)
absoluteKESPeriod = absoluteSlot div 129600
This agrees with the agent's own OpCert-issuance instructions, which divide the node tip's absolute slot by slotsPerKESPeriod. The KESPeriod definition also distinguishes the chain's absolute period from a key's relative evolution.
Haskell reproduction
Use the pinned source revision with its normal Haskell/native build prerequisites from the build guide. The public crypto sublibrary exposes the timing implementation. No node or signing keys are needed.
In a disposable checkout:
git clone https://github.com/IntersectMBO/kes-agent.git
cd kes-agent
git checkout --detach 44fdcec892e2868053f964cad87fc32c590c81e4
cabal repl lib:crypto --repl-options=-ignore-dot-ghci
At the GHCi prompt, run:
import Cardano.KESAgent.KES.Evolution
import Cardano.KESAgent.KES.OCert (unKESPeriod)
import Control.Monad (unless)
import Data.Time (UTCTime(..), fromGregorian, secondsToDiffTime, diffUTCTime)
config <- evolutionConfigFromGenesisFile "fixtures/mainnet-shelley-genesis.json" >>= either fail pure
let now = UTCTime (fromGregorian 2026 10 31) 0
let shelleyStart = UTCTime (fromGregorian 2020 7 29) (secondsToDiffTime (21 * 3600 + 44 * 60 + 51))
let absoluteSlot = 4492800 + floor (diffUTCTime now shelleyStart) :: Integer
let chainPeriod = absoluteSlot `div` 129600
let fixturePeriod = toInteger (unKESPeriod (getCurrentKESPeriodAt now config))
let defaultPeriod = toInteger (unKESPeriod (getCurrentKESPeriodAt now defEvolutionConfig))
putStrLn ("absolute_slot=" ++ show absoluteSlot)
putStrLn ("chain_period=" ++ show chainPeriod)
putStrLn ("fixture_agent_period=" ++ show fixturePeriod)
putStrLn ("default_agent_period=" ++ show defaultPeriod)
putStrLn ("relative_target_for_current_opcert=" ++ show (fixturePeriod - chainPeriod))
unless (fixturePeriod == chainPeriod && defaultPeriod == chainPeriod) (fail "mainnet KES period mismatch")
Expected output at the affected revision, excluding GHCi prompts:
absolute_slot=201838509
chain_period=1557
fixture_agent_period=2216
default_agent_period=2216
relative_target_for_current_opcert=659
*** Exception: user error (mainnet KES period mismatch)
The final assertion is deliberately the desired invariant: both upstream calculations should equal the absolute-slot KES period. It fails on the affected calculation. This is a call into the library, not a reimplementation of its formula.
Expected behavior
For this timestamp and network, the agent should identify absolute KES period 1557. A fresh key with an OpCert starting at period 1557 should remain at relative evolution 0, not be caught up toward relative evolution 659.
The next evolution deadline should likewise correspond to the next absolute-slot KES boundary, using the same corrected time/slot mapping.
Why shifting the certificate period does not cancel the problem
A common period offset could cancel inside the agent's relative-evolution subtraction, but the certificate must also pass independent consensus validation. The OpCert contains a cold-signed start period, not separate signed start/end periods; its expiry is derived using the network's permitted KES lifetime.
The published consensus doValidateKESSignature' derives kp from the block header's absolute slot and enforces:
certificate_start <= kp
kp < certificate_start + maxKESEvolutions
Using 2216 as the certificate start would make the agent's relative evolution 0 at the example timestamp, but the actual chain period is 1557: consensus rejects the certificate as not yet valid (KESBeforeStartOCERT), and the forging check also rejects a future start (PraosCannotForgeKeyNotUsableYet). Using the correct certificate start 1557 passes those period bounds but exposes the agent's wrong catch-up target. Changing a signed certificate's period during delivery is not a valid conversion.
Source-derived operational impact
The following has not been reproduced with a running agent or node:
installKeymoves the staged key into a bundle and callspushKey.pushKeyinstalls/broadcasts the bundle and then invokescheckEvolution.updateKESTosubtracts the certificate start period from the calculated absolute period. A target of659exceeds the 64-periodSum6KESprimitive used by StandardCrypto.- On exhaustion,
checkEvolutionlogsAgentKeyExpired, releases the key, and removes the active bundle. It intentionally does not send a Drop, assuming peers will independently expire their copies. - A node that already obtained a valid key can continue evolving its own copy, so this must not be described as an unconditional immediate forging outage. Publication occurs before catch-up; whether a particular newly connected receiver successfully obtains a usable bundle is not established by this report.
- Once the agent has no active bundle, the service connection's
currentKeywaits using STMretry. A restarting/new node cannot retrieve that key from this agent unless another key is installed. This undermines the agent's restart/reconnect role even if an existing producer appears healthy.
This particular historical-offset defect does not apply to a network that began with Shelley timing at slot zero and has retained that slot duration. No claim is made that every deployment is affected.
Related issues and requested confirmation
Related: #7 (RTC-based evolution), #50 (sleeping until the next KES boundary), and #39 (future-start key validity). This report concerns a deterministic mainnet time-to-absolute-slot mismatch even when the host clock is correct, not ordinary clock skew or intentionally future-dated certificates.
Could you confirm the expected mainnet handling and whether a timing-origin adjustment is documented elsewhere? A regression using getCurrentKESPeriodAt with the fixed timestamp and actual mainnet genesis parameters should distinguish the current arithmetic from the expected period. The correction need not introduce node polling: an authoritative post-Shelley time/absolute-slot anchor is enough for the current mainnet slot schedule.
This report provides a native Haskell reproduction against the pinned timing implementation. No production keys are needed, and no live forging outage or full-agent/node integration result is claimed.
- Dominant language
- Haskell
- Stars
- 2
- Forks
- 13
- PR merge metrics
- No merged PRs in 30d
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from IntersectMBO/kes-agent
-
Difficulty 2/5 1-3 hours Newbie friendliness 52/100
IntersectMBO/kes-agent#102 ·
-
Difficulty 4/5 3-5 days Newbie friendliness 38/100
IntersectMBO/kes-agent#79 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 45/100
IntersectMBO/kes-agent#78 · 1 reaction ·
-
Difficulty 4/5 3-5 days Newbie friendliness 35/100
IntersectMBO/kes-agent#70 ·
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
IntersectMBO/kes-agent#69 ·
All issues in IntersectMBO/kes-agent
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 75/100
-
15-minutes 5-minutes algorithm any-language beginner-friendly c cpp csharp easy go good first issue hacktoberfest help wanted java javascript kotlin python rust swift typescript
Difficulty 1/5 Under an hour Newbie friendliness 68/100
Huerte/algo-solutions-python#260 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
IntersectMBO/cardano-ledger#6276 ·
Maintainers usually reply within 1 day