Skip to content

Commit 5a3c7c6

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

File tree

9 files changed

+181
-42
lines changed

9 files changed

+181
-42
lines changed

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,32 @@ variable. For example, the `-input` option has the `INPUT` environment variable,
128128
the `-input-chainsync-address` option has the `INPUT_CHAINSYNC_ADDRESS`
129129
environment variable, and `-output` has `OUTPUT`.
130130

131+
### Environment Variables
132+
133+
Core configuration options can be set using environment variables:
134+
135+
- `INPUT` - Input plugin to use (default: "chainsync")
136+
- `OUTPUT` - Output plugin to use (default: "log")
137+
- `KUPO_URL` - URL for Kupo service integration
138+
- `LOGGING_LEVEL` - Log level (default: "info")
139+
- `API_ADDRESS` - API server listen address (default: "0.0.0.0")
140+
- `API_PORT` - API server port (default: 8080)
141+
- `DEBUG_ADDRESS` - Debug server address (default: "localhost")
142+
- `DEBUG_PORT` - Debug server port (default: 0)
143+
144+
Genesis configuration can also be controlled via environment variables:
145+
146+
**Network Transition:**
147+
- `SHELLEY_TRANS_EPOCH` - Epoch number when Shelley era begins (default: 208 for mainnet)
148+
149+
**Byron Genesis:**
150+
- `BYRON_GENESIS_END_SLOT` - End slot for Byron era
151+
- `BYRON_GENESIS_EPOCH_LENGTH` - Slot length of Byron epochs (default: 21600)
152+
- `BYRON_GENESIS_BYRON_SLOTS_PER_EPOCH` - Byron slots per epoch
153+
154+
**Shelley Genesis:**
155+
- `SHELLEY_GENESIS_EPOCH_LENGTH` - Slot length of Shelley epochs (default: 432000)
156+
131157
You can also specify each option in the config file.
132158

133159
```yaml

api/api_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import (
55
"net/http/httptest"
66
"testing"
77

8+
"github.com/stretchr/testify/assert"
9+
810
"github.com/blinklabs-io/adder/api"
911
"github.com/blinklabs-io/adder/output/push"
10-
"github.com/stretchr/testify/assert"
1112
)
1213

1314
func TestRouteRegistration(t *testing.T) {

filter/chainsync/chainsync_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ import (
1919
"testing"
2020
"time"
2121

22-
"github.com/blinklabs-io/adder/event"
2322
"github.com/blinklabs-io/gouroboros/cbor"
2423
"github.com/blinklabs-io/gouroboros/ledger"
2524
"github.com/blinklabs-io/gouroboros/ledger/common"
2625
"github.com/blinklabs-io/plutigo/data"
2726
"github.com/btcsuite/btcd/btcutil/bech32"
2827
"github.com/stretchr/testify/assert"
2928
"github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano"
29+
30+
"github.com/blinklabs-io/adder/event"
3031
)
3132

3233
// MockLogger is a mock implementation of the plugin.Logger interface
@@ -92,10 +93,10 @@ func (m *MockAddress) UnmarshalCBOR(data []byte) error {
9293
// MockOutput is a mock implementation of the TransactionOutput interface
9394
type MockOutput struct {
9495
address ledger.Address
95-
amount uint64
96+
scriptRef common.Script
9697
assets *common.MultiAsset[common.MultiAssetTypeOutput]
9798
datum *common.Datum
98-
scriptRef common.Script
99+
amount uint64
99100
}
100101

101102
func (m MockOutput) Address() ledger.Address {
@@ -207,15 +208,15 @@ func TestChainSync_OutputChan(t *testing.T) {
207208

208209
// Mock certificate implementations
209210
type mockStakeDelegationCert struct {
210-
common.StakeDelegationCertificate
211211
cborData []byte
212+
common.StakeDelegationCertificate
212213
}
213214

214215
func (m *mockStakeDelegationCert) Cbor() []byte { return m.cborData }
215216

216217
type mockStakeDeregistrationCert struct {
217-
common.StakeDeregistrationCertificate
218218
cborData []byte
219+
common.StakeDeregistrationCertificate
219220
}
220221

221222
func (m *mockStakeDeregistrationCert) Cbor() []byte { return m.cborData }
@@ -251,10 +252,10 @@ func TestFilterByAddress(t *testing.T) {
251252
testStakeAddress, _ := bech32.Encode("stake", convData)
252253

253254
tests := []struct {
254-
name string
255-
filterAddress string
256255
outputAddr common.Address
257256
cert ledger.Certificate
257+
name string
258+
filterAddress string
258259
shouldMatch bool
259260
}{
260261
{

filter/chainsync/plugin_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import (
44
"testing"
55
"time"
66

7+
"github.com/stretchr/testify/assert"
8+
79
"github.com/blinklabs-io/adder/event"
810
"github.com/blinklabs-io/adder/plugin"
9-
"github.com/stretchr/testify/assert"
1011
)
1112

1213
func TestPluginRegistration(t *testing.T) {

input/chainsync/block_test.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package chainsync
33
import (
44
"testing"
55

6-
"github.com/blinklabs-io/adder/event"
76
"github.com/blinklabs-io/gouroboros/ledger/common"
87
"github.com/stretchr/testify/assert"
98
utxorpc "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano"
9+
10+
"github.com/blinklabs-io/adder/event"
1011
)
1112

1213
// MockIssuerVkey to implement IssuerVkey interface
@@ -22,14 +23,14 @@ func (m MockIssuerVkey) Hash() []byte {
2223

2324
// MockBlockHeader implements BlockHeader interface
2425
type MockBlockHeader struct {
25-
hash common.Blake2b256
26-
prevHash common.Blake2b256
26+
era common.Era
27+
cborBytes []byte
2728
blockNumber uint64
2829
slotNumber uint64
29-
issuerVkey common.IssuerVkey
3030
blockBodySize uint64
31-
era common.Era
32-
cborBytes []byte
31+
hash common.Blake2b256
32+
prevHash common.Blake2b256
33+
issuerVkey common.IssuerVkey
3334
}
3435

3536
func (m MockBlockHeader) Hash() common.Blake2b256 {
@@ -113,11 +114,11 @@ func (m MockBlock) IsConway() bool {
113114
func TestNewBlockContext(t *testing.T) {
114115
testCases := []struct {
115116
name string
116-
block MockBlock
117-
networkMagic uint32
118117
expectedEra string
118+
block MockBlock
119119
expectedBlock uint64
120120
expectedSlot uint64
121+
networkMagic uint32
121122
}{
122123
{
123124
name: "Shelley Era Block",
@@ -230,9 +231,9 @@ func TestNewBlockContext(t *testing.T) {
230231
func TestNewBlockContextEdgeCases(t *testing.T) {
231232
testCases := []struct {
232233
name string
234+
expectedEra string
233235
block MockBlock
234236
networkMagic uint32
235-
expectedEra string
236237
}{
237238
{
238239
name: "Zero Values",

input/chainsync/chainsync.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,52 @@ import (
2828
"time"
2929

3030
"github.com/SundaeSwap-finance/kugo"
31-
"github.com/SundaeSwap-finance/ogmigo/v6/ouroboros/chainsync"
3231
"github.com/blinklabs-io/adder/event"
3332
"github.com/blinklabs-io/adder/internal/config"
3433
"github.com/blinklabs-io/adder/internal/logging"
3534
"github.com/blinklabs-io/adder/plugin"
3635
ouroboros "github.com/blinklabs-io/gouroboros"
3736
"github.com/blinklabs-io/gouroboros/ledger"
38-
"github.com/blinklabs-io/gouroboros/protocol/blockfetch"
37+
blockfetch "github.com/blinklabs-io/gouroboros/protocol/blockfetch"
3938
ochainsync "github.com/blinklabs-io/gouroboros/protocol/chainsync"
4039
ocommon "github.com/blinklabs-io/gouroboros/protocol/common"
4140
)
4241

42+
// EpochFromSlot derives an epoch from a slot using Byron/Shelley genesis params.
43+
// Byron slots: 0..EndSlot inclusive. Explicit zero EndSlot/ShelleyTransEpoch means no Byron era.
44+
// Zero epoch length in either era yields a safe fallback (0 Byron / starting Shelley epoch).
45+
func EpochFromSlot(slot uint64) uint64 {
46+
cfg := config.GetConfig()
47+
byron := cfg.ByronGenesis
48+
shelley := cfg.ShelleyGenesis
49+
50+
endSlot := func() uint64 {
51+
if byron.EndSlot != nil {
52+
return *byron.EndSlot
53+
}
54+
return 0
55+
}()
56+
shelleyTransEpoch := func() uint64 {
57+
if cfg.ShelleyTransEpoch >= 0 {
58+
//nolint:gosec // ShelleyTransEpoch is controlled config, safe conversion
59+
return uint64(cfg.ShelleyTransEpoch)
60+
}
61+
return 0
62+
}()
63+
if slot <= endSlot {
64+
if byron.EpochLength == 0 {
65+
return 0 // avoid div by zero
66+
}
67+
return slot / byron.EpochLength
68+
}
69+
shelleyStartEpoch := shelleyTransEpoch
70+
shelleyStartSlot := endSlot + 1
71+
if shelley.EpochLength == 0 {
72+
return shelleyStartEpoch // avoid div by zero
73+
}
74+
return shelleyStartEpoch + (slot-shelleyStartSlot)/shelley.EpochLength
75+
}
76+
4377
const (
4478
// Size of cache for recent chainsync cursors
4579
cursorCacheSize = 20
@@ -84,6 +118,7 @@ type ChainSyncStatus struct {
84118
TipBlockHash string
85119
SlotNumber uint64
86120
BlockNumber uint64
121+
EpochNumber uint64
87122
TipSlotNumber uint64
88123
TipReached bool
89124
}
@@ -522,6 +557,7 @@ func (c *ChainSync) updateStatus(
522557
c.status.SlotNumber = slotNumber
523558
c.status.BlockNumber = blockNumber
524559
c.status.BlockHash = blockHash
560+
c.status.EpochNumber = EpochFromSlot(slotNumber)
525561
c.status.TipSlotNumber = tipSlotNumber
526562
c.status.TipBlockHash = tipBlockHash
527563
if c.statusUpdateFunc != nil {
@@ -621,9 +657,9 @@ func resolveTransactionInputs(
621657
)
622658
defer cancel()
623659

624-
matches, err := k.Matches(ctx,
625-
kugo.TxOut(chainsync.NewTxID(txId, txIndex)),
626-
)
660+
// Create a simple transaction identifier
661+
txID := fmt.Sprintf("%d@%s", txIndex, txId)
662+
matches, err := k.Matches(ctx, kugo.Transaction(txID))
627663
if err != nil {
628664
if errors.Is(err, context.DeadlineExceeded) {
629665
return nil, fmt.Errorf(

input/chainsync/chainsync_test.go

Lines changed: 19 additions & 3 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 (
@@ -10,11 +23,12 @@ import (
1023
"time"
1124

1225
"github.com/SundaeSwap-finance/kugo"
13-
"github.com/blinklabs-io/adder/event"
1426
"github.com/blinklabs-io/gouroboros/protocol/chainsync"
1527
ocommon "github.com/blinklabs-io/gouroboros/protocol/common"
1628
"github.com/stretchr/testify/assert"
1729
"github.com/stretchr/testify/require"
30+
31+
"github.com/blinklabs-io/adder/event"
1832
)
1933

2034
func TestHandleRollBackward(t *testing.T) {
@@ -26,7 +40,7 @@ func TestHandleRollBackward(t *testing.T) {
2640

2741
// Define test data
2842
point := ocommon.Point{
29-
Slot: 12345,
43+
Slot: 123456,
3044
Hash: []byte{0x01, 0x02, 0x03, 0x04, 0x05},
3145
}
3246
tip := chainsync.Tip{
@@ -64,7 +78,7 @@ func TestHandleRollBackward(t *testing.T) {
6478
}
6579

6680
// Verify that the status was updated correctly
67-
assert.Equal(t, uint64(12345), c.status.SlotNumber)
81+
assert.Equal(t, uint64(123456), c.status.SlotNumber)
6882
assert.Equal(
6983
t,
7084
uint64(0),
@@ -73,6 +87,8 @@ func TestHandleRollBackward(t *testing.T) {
7387
assert.Equal(t, "0102030405", c.status.BlockHash)
7488
assert.Equal(t, uint64(67890), c.status.TipSlotNumber)
7589
assert.Equal(t, "060708090a", c.status.TipBlockHash)
90+
// New: Check EpochNumber (Byron era: 123456/21600 = 5)
91+
assert.Equal(t, uint64(5), c.status.EpochNumber)
7692
}
7793

7894
func TestGetKupoClient(t *testing.T) {

0 commit comments

Comments
 (0)