Skip to content

Commit 373d1ff

Browse files
committed
feat(chainsync): add EpochNumber to ChainSyncStatus
Closes #187 Signed-off-by: Chris Gianelloni <[email protected]>
1 parent f4fecb1 commit 373d1ff

File tree

3 files changed

+127
-16
lines changed

3 files changed

+127
-16
lines changed

input/chainsync/chainsync.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,26 @@ import (
4040
ocommon "github.com/blinklabs-io/gouroboros/protocol/common"
4141
)
4242

43+
// EpochFromSlot returns the epoch number for a given slot, using Cardano era rules.
44+
func EpochFromSlot(slot uint64) uint64 {
45+
cfg := config.GetConfig()
46+
byron := cfg.ByronGenesis
47+
shelley := cfg.ShelleyGenesis
48+
49+
if slot <= byron.EndSlot {
50+
if byron.EpochLength == 0 {
51+
return 0 // avoid div by zero
52+
}
53+
return slot / byron.EpochLength
54+
}
55+
shelleyStartEpoch := byron.Epochs
56+
shelleyStartSlot := byron.EndSlot + 1
57+
if shelley.EpochLength == 0 {
58+
return shelleyStartEpoch // avoid div by zero
59+
}
60+
return shelleyStartEpoch + (slot-shelleyStartSlot)/shelley.EpochLength
61+
}
62+
4363
const (
4464
// Size of cache for recent chainsync cursors
4565
cursorCacheSize = 20
@@ -84,6 +104,7 @@ type ChainSyncStatus struct {
84104
TipBlockHash string
85105
SlotNumber uint64
86106
BlockNumber uint64
107+
EpochNumber uint64
87108
TipSlotNumber uint64
88109
TipReached bool
89110
}
@@ -522,6 +543,7 @@ func (c *ChainSync) updateStatus(
522543
c.status.SlotNumber = slotNumber
523544
c.status.BlockNumber = blockNumber
524545
c.status.BlockHash = blockHash
546+
c.status.EpochNumber = EpochFromSlot(slotNumber)
525547
c.status.TipSlotNumber = tipSlotNumber
526548
c.status.TipBlockHash = tipBlockHash
527549
if c.statusUpdateFunc != nil {

input/chainsync/chainsync_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
// Copyright 2025 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
114
package chainsync
215

316
import (
@@ -65,14 +78,12 @@ func TestHandleRollBackward(t *testing.T) {
6578

6679
// Verify that the status was updated correctly
6780
assert.Equal(t, uint64(12345), c.status.SlotNumber)
68-
assert.Equal(
69-
t,
70-
uint64(0),
71-
c.status.BlockNumber,
72-
) // BlockNumber should be 0 after rollback
81+
assert.Equal(t, uint64(0), c.status.BlockNumber) // BlockNumber should be 0 after rollback
7382
assert.Equal(t, "0102030405", c.status.BlockHash)
7483
assert.Equal(t, uint64(67890), c.status.TipSlotNumber)
7584
assert.Equal(t, "060708090a", c.status.TipBlockHash)
85+
// New: Check EpochNumber
86+
assert.Equal(t, uint64(0), c.status.EpochNumber) // 12345/432000 = 0
7687
}
7788

7889
func TestGetKupoClient(t *testing.T) {

internal/config/config.go

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package config
1616

1717
import (
18+
"errors"
1819
"flag"
1920
"fmt"
2021
"os"
@@ -24,21 +25,85 @@ import (
2425
"gopkg.in/yaml.v2"
2526
)
2627

28+
// ByronGenesisConfig holds Byron era genesis parameters
29+
// ShelleyGenesisConfig holds Shelley era genesis parameters
30+
// ShelleyTransEpoch holds transition slot/epoch info
31+
32+
// ByronGenesisConfig holds Byron era genesis parameters
33+
type ByronGenesisConfig struct {
34+
EpochLength uint64 `yaml:"epoch_length"`
35+
ByronSlotsPerEpoch uint64 `yaml:"byron_slots_per_epoch"`
36+
EndSlot uint64 `yaml:"end_slot"`
37+
Epochs uint64 `yaml:"epochs"`
38+
}
39+
40+
// ShelleyGenesisConfig holds Shelley era genesis parameters
41+
type ShelleyGenesisConfig struct {
42+
EpochLength uint64 `yaml:"epoch_length"`
43+
}
44+
45+
// ShelleyTransEpoch holds transition slot/epoch info
46+
type ShelleyTransEpoch struct {
47+
FirstShelleySlot uint64 `yaml:"first_shelley_slot"`
48+
FirstShelleyEpoch uint64 `yaml:"first_shelley_epoch"`
49+
}
50+
51+
// populateByronGenesis sets defaults and validates ByronGenesisConfig
52+
func (c *Config) populateByronGenesis() error {
53+
cfg := &c.ByronGenesis
54+
if cfg.EpochLength == 0 {
55+
cfg.EpochLength = 21600
56+
}
57+
if cfg.ByronSlotsPerEpoch == 0 {
58+
cfg.ByronSlotsPerEpoch = cfg.EpochLength
59+
}
60+
if cfg.EndSlot == 0 {
61+
cfg.EndSlot = 4492799
62+
}
63+
if cfg.Epochs == 0 {
64+
cfg.Epochs = 208
65+
}
66+
if cfg.EpochLength == 0 {
67+
return errors.New("ByronGenesisConfig: EpochLength must be nonzero")
68+
}
69+
if cfg.EndSlot == 0 {
70+
return errors.New("ByronGenesisConfig: EndSlot must be nonzero")
71+
}
72+
if cfg.Epochs == 0 {
73+
return errors.New("ByronGenesisConfig: Epochs must be nonzero")
74+
}
75+
return nil
76+
}
77+
78+
// populateShelleyGenesis sets defaults and validates ShelleyGenesisConfig
79+
func (c *Config) populateShelleyGenesis() error {
80+
cfg := &c.ShelleyGenesis
81+
if cfg.EpochLength == 0 {
82+
cfg.EpochLength = 432000
83+
}
84+
if cfg.EpochLength == 0 {
85+
return errors.New("ShelleyGenesisConfig: EpochLength must be nonzero")
86+
}
87+
return nil
88+
}
89+
2790
const (
2891
DefaultInputPlugin = "chainsync"
2992
DefaultOutputPlugin = "log"
3093
)
3194

3295
type Config struct {
33-
Plugin map[string]map[string]map[any]any `yaml:"plugins"`
34-
Logging LoggingConfig `yaml:"logging"`
35-
ConfigFile string `yaml:"-"`
36-
Input string `yaml:"input" envconfig:"INPUT"`
37-
Output string `yaml:"output" envconfig:"OUTPUT"`
38-
KupoUrl string `yaml:"kupo_url" envconfig:"KUPO_URL"`
39-
Api ApiConfig `yaml:"api"`
40-
Debug DebugConfig `yaml:"debug"`
41-
Version bool `yaml:"-"`
96+
Plugin map[string]map[string]map[any]any `yaml:"plugins"`
97+
Logging LoggingConfig `yaml:"logging"`
98+
ConfigFile string `yaml:"-"`
99+
Input string `yaml:"input" envconfig:"INPUT"`
100+
Output string `yaml:"output" envconfig:"OUTPUT"`
101+
KupoUrl string `yaml:"kupo_url" envconfig:"KUPO_URL"`
102+
Api ApiConfig `yaml:"api"`
103+
Debug DebugConfig `yaml:"debug"`
104+
ByronGenesis ByronGenesisConfig `yaml:"byron_genesis" envconfig:"BYRON_GENESIS"`
105+
ShelleyGenesis ShelleyGenesisConfig `yaml:"shelley_genesis" envconfig:"SHELLEY_GENESIS"`
106+
Version bool `yaml:"-"`
42107
}
43108

44109
type ApiConfig struct {
@@ -71,6 +136,14 @@ var globalConfig = &Config{
71136
Input: DefaultInputPlugin,
72137
Output: DefaultOutputPlugin,
73138
KupoUrl: "",
139+
ByronGenesis: ByronGenesisConfig{
140+
EpochLength: 21600,
141+
EndSlot: 4492799,
142+
Epochs: 208,
143+
},
144+
ShelleyGenesis: ShelleyGenesisConfig{
145+
EpochLength: 432000,
146+
},
74147
}
75148

76149
func (c *Config) Load(configFile string) error {
@@ -86,12 +159,17 @@ func (c *Config) Load(configFile string) error {
86159
}
87160
}
88161
// Load config values from environment variables
89-
// We use "dummy" as the app name here to (mostly) prevent picking up env
90-
// vars that we hadn't explicitly specified in annotations above
91162
err := envconfig.Process("dummy", c)
92163
if err != nil {
93164
return fmt.Errorf("error processing environment: %w", err)
94165
}
166+
// Populate Byron and Shelley genesis configs (from nview)
167+
if err := c.populateByronGenesis(); err != nil {
168+
return fmt.Errorf("error populating Byron genesis: %w", err)
169+
}
170+
if err := c.populateShelleyGenesis(); err != nil {
171+
return fmt.Errorf("error populating Shelley genesis: %w", err)
172+
}
95173
return nil
96174
}
97175

0 commit comments

Comments
 (0)