Skip to content

Commit ab7b00e

Browse files
authored
feat: embed preview network configs in binary (#1009)
Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
1 parent d591b4a commit ab7b00e

File tree

8 files changed

+403
-13
lines changed

8 files changed

+403
-13
lines changed

config/cardano/embed.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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.
14+
15+
package cardano
16+
17+
import "embed"
18+
19+
// EmbeddedConfigPreviewNetworkFS contains the embedded Cardano configuration files
20+
// for the preview network. This includes config.json and all genesis files required
21+
// for preview network operation when no external config files are available.
22+
//
23+
//go:embed preview
24+
var EmbeddedConfigPreviewNetworkFS embed.FS

config/cardano/embed_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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.
14+
15+
package cardano
16+
17+
import (
18+
"testing"
19+
)
20+
21+
func TestNewCardanoNodeConfigFromEmbedFS(t *testing.T) {
22+
// Test loading config from embedded filesystem
23+
cfg, err := NewCardanoNodeConfigFromEmbedFS(
24+
EmbeddedConfigPreviewNetworkFS,
25+
"preview/config.json",
26+
)
27+
if err != nil {
28+
t.Fatalf("failed to load cardano config from embedded FS: %v", err)
29+
}
30+
31+
if cfg == nil {
32+
t.Fatal("expected non-nil config")
33+
}
34+
35+
// Verify that the config has expected values
36+
if cfg.ShelleyGenesisFile == "" {
37+
t.Error("expected ShelleyGenesisFile to be set")
38+
}
39+
40+
if cfg.ByronGenesisFile == "" {
41+
t.Error("expected ByronGenesisFile to be set")
42+
}
43+
44+
// Verify genesis configs were actually loaded
45+
if cfg.ShelleyGenesis() == nil {
46+
t.Error("expected ShelleyGenesis to be loaded")
47+
}
48+
49+
if cfg.ByronGenesis() == nil {
50+
t.Error("expected ByronGenesis to be loaded")
51+
}
52+
}
53+
54+
func TestNewCardanoNodeConfigFromEmbedFS_InvalidPath(t *testing.T) {
55+
// Test loading config from embedded filesystem with invalid path
56+
_, err := NewCardanoNodeConfigFromEmbedFS(
57+
EmbeddedConfigPreviewNetworkFS,
58+
"nonexistent/config.json",
59+
)
60+
if err == nil {
61+
t.Fatal("expected error for invalid path")
62+
}
63+
}
64+
65+
func TestEmbedFS_ListFiles(t *testing.T) {
66+
// Test that embedded FS contains expected files in preview directory
67+
previewEntries, err := EmbeddedConfigPreviewNetworkFS.ReadDir("preview")
68+
if err != nil {
69+
t.Fatalf("failed to read preview directory: %v", err)
70+
}
71+
72+
if len(previewEntries) == 0 {
73+
t.Fatal("expected preview directory to contain files")
74+
}
75+
76+
expectedFiles := []string{
77+
"config.json",
78+
"byron-genesis.json",
79+
"shelley-genesis.json",
80+
"alonzo-genesis.json",
81+
"conway-genesis.json",
82+
}
83+
foundFiles := make(map[string]bool)
84+
85+
for _, entry := range previewEntries {
86+
foundFiles[entry.Name()] = true
87+
}
88+
89+
for _, expectedFile := range expectedFiles {
90+
if !foundFiles[expectedFile] {
91+
t.Errorf("expected to find %s in preview directory", expectedFile)
92+
}
93+
}
94+
}

config/cardano/loadhelper.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.
14+
15+
package cardano
16+
17+
import (
18+
"embed"
19+
"fmt"
20+
"os"
21+
)
22+
23+
// LoadCardanoNodeConfigWithFallback tries to load config from file, then falls back to embed FS for preview network.
24+
func LoadCardanoNodeConfigWithFallback(
25+
cfgPath, network string,
26+
embedFS embed.FS,
27+
) (*CardanoNodeConfig, error) {
28+
_, err := os.Stat(cfgPath)
29+
if err == nil {
30+
return NewCardanoNodeConfigFromFile(cfgPath)
31+
}
32+
if !os.IsNotExist(err) {
33+
return nil, fmt.Errorf(
34+
"failed to check config file %q: %w",
35+
cfgPath,
36+
err,
37+
)
38+
}
39+
40+
// File doesn't exist, try embedded config for preview network
41+
if network == "preview" {
42+
return NewCardanoNodeConfigFromEmbedFS(embedFS, cfgPath)
43+
}
44+
return nil, fmt.Errorf(
45+
"config file %q not found and no embedded config available for network %q",
46+
cfgPath,
47+
network,
48+
)
49+
}

config/cardano/node.go

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package cardano
1616

1717
import (
18+
"embed"
1819
"io"
1920
"os"
2021
"path"
@@ -27,19 +28,21 @@ import (
2728
"gopkg.in/yaml.v3"
2829
)
2930

30-
// CardanoNodeConfig represents the config.json/yaml file used by cardano-node
31+
// CardanoNodeConfig represents the config.json/yaml file used by cardano-node.
3132
type CardanoNodeConfig struct {
32-
path string
33+
// Embedded filesystem for loading genesis files
34+
embedFS embed.FS
3335
alonzoGenesis *alonzo.AlonzoGenesis
36+
byronGenesis *byron.ByronGenesis
37+
conwayGenesis *conway.ConwayGenesis
38+
shelleyGenesis *shelley.ShelleyGenesis
39+
path string
3440
AlonzoGenesisFile string `yaml:"AlonzoGenesisFile"`
3541
AlonzoGenesisHash string `yaml:"AlonzoGenesisHash"`
36-
byronGenesis *byron.ByronGenesis
3742
ByronGenesisFile string `yaml:"ByronGenesisFile"`
3843
ByronGenesisHash string `yaml:"ByronGenesisHash"`
39-
conwayGenesis *conway.ConwayGenesis
4044
ConwayGenesisFile string `yaml:"ConwayGenesisFile"`
4145
ConwayGenesisHash string `yaml:"ConwayGenesisHash"`
42-
shelleyGenesis *shelley.ShelleyGenesis
4346
ShelleyGenesisFile string `yaml:"ShelleyGenesisFile"`
4447
ShelleyGenesisHash string `yaml:"ShelleyGenesisHash"`
4548
}
@@ -69,6 +72,31 @@ func NewCardanoNodeConfigFromFile(file string) (*CardanoNodeConfig, error) {
6972
return c, nil
7073
}
7174

75+
// NewCardanoNodeConfigFromEmbedFS creates a CardanoNodeConfig from an embedded filesystem.
76+
// It loads the main config file and all referenced genesis files from the embedded FS.
77+
// The file parameter should be a path relative to the root of the embedded filesystem.
78+
func NewCardanoNodeConfigFromEmbedFS(
79+
fs embed.FS,
80+
file string,
81+
) (*CardanoNodeConfig, error) {
82+
f, err := fs.Open(file)
83+
if err != nil {
84+
return nil, err
85+
}
86+
defer f.Close()
87+
88+
c, err := NewCardanoNodeConfigFromReader(f)
89+
if err != nil {
90+
return nil, err
91+
}
92+
c.path = path.Dir(file)
93+
c.embedFS = fs // Store reference to embedded FS
94+
if err := c.loadGenesisConfigsFromEmbed(); err != nil {
95+
return nil, err
96+
}
97+
return c, nil
98+
}
99+
72100
func (c *CardanoNodeConfig) loadGenesisConfigs() error {
73101
// Load Byron genesis
74102
if c.ByronGenesisFile != "" {
@@ -127,6 +155,79 @@ func (c *CardanoNodeConfig) loadGenesisConfigs() error {
127155
return nil
128156
}
129157

158+
// loadGenesisFromEmbedFS loads a single genesis file from the embedded filesystem.
159+
// It handles path resolution and file opening/closing automatically.
160+
func (c *CardanoNodeConfig) loadGenesisFromEmbedFS(
161+
filename string,
162+
) (io.ReadCloser, error) {
163+
if filename == "" {
164+
return nil, nil
165+
}
166+
167+
genesisPath := filename
168+
genesisPath = path.Join(c.path, genesisPath)
169+
170+
f, err := c.embedFS.Open(genesisPath)
171+
if err != nil {
172+
return nil, err
173+
}
174+
return f, nil
175+
}
176+
177+
// loadGenesisConfigsFromEmbed loads all genesis configuration files from the embedded filesystem.
178+
// This method mirrors loadGenesisConfigs but reads from embed.FS instead of the regular filesystem.
179+
func (c *CardanoNodeConfig) loadGenesisConfigsFromEmbed() error {
180+
// Load Byron genesis
181+
if f, err := c.loadGenesisFromEmbedFS(c.ByronGenesisFile); err != nil {
182+
return err
183+
} else if f != nil {
184+
defer f.Close()
185+
byronGenesis, err := byron.NewByronGenesisFromReader(f)
186+
if err != nil {
187+
return err
188+
}
189+
c.byronGenesis = &byronGenesis
190+
}
191+
192+
// Load Shelley genesis
193+
if f, err := c.loadGenesisFromEmbedFS(c.ShelleyGenesisFile); err != nil {
194+
return err
195+
} else if f != nil {
196+
defer f.Close()
197+
shelleyGenesis, err := shelley.NewShelleyGenesisFromReader(f)
198+
if err != nil {
199+
return err
200+
}
201+
c.shelleyGenesis = &shelleyGenesis
202+
}
203+
204+
// Load Alonzo genesis
205+
if f, err := c.loadGenesisFromEmbedFS(c.AlonzoGenesisFile); err != nil {
206+
return err
207+
} else if f != nil {
208+
defer f.Close()
209+
alonzoGenesis, err := alonzo.NewAlonzoGenesisFromReader(f)
210+
if err != nil {
211+
return err
212+
}
213+
c.alonzoGenesis = &alonzoGenesis
214+
}
215+
216+
// Load Conway genesis
217+
if f, err := c.loadGenesisFromEmbedFS(c.ConwayGenesisFile); err != nil {
218+
return err
219+
} else if f != nil {
220+
defer f.Close()
221+
conwayGenesis, err := conway.NewConwayGenesisFromReader(f)
222+
if err != nil {
223+
return err
224+
}
225+
c.conwayGenesis = &conwayGenesis
226+
}
227+
228+
return nil
229+
}
230+
130231
// ByronGenesis returns the Byron genesis config specified in the cardano-node config
131232
func (c *CardanoNodeConfig) ByronGenesis() *byron.ByronGenesis {
132233
return c.byronGenesis

internal/config/config.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ var globalConfig = &Config{
6767
BadgerCacheSize: 1073741824,
6868
MempoolCapacity: 1048576,
6969
BindAddr: "0.0.0.0",
70-
CardanoConfig: "./config/cardano/preview/config.json",
70+
CardanoConfig: "", // Will be set dynamically based on network
7171
DatabasePath: ".dingo",
7272
SocketPath: "dingo.socket",
7373
IntersectTip: false,
@@ -103,6 +103,7 @@ func LoadConfig(configFile string) (*Config, error) {
103103
}
104104
}
105105
}
106+
106107
if configFile != "" {
107108
buf, err := os.ReadFile(configFile)
108109
if err != nil {
@@ -113,11 +114,21 @@ func LoadConfig(configFile string) (*Config, error) {
113114
return nil, fmt.Errorf("error parsing config file: %w", err)
114115
}
115116
}
117+
// Process environment variables
116118
err := envconfig.Process("cardano", globalConfig)
117119
if err != nil {
118120
return nil, fmt.Errorf("error processing environment: %+w", err)
119121
}
120122

123+
// Set default CardanoConfig path based on network if not provided by user
124+
if globalConfig.CardanoConfig == "" {
125+
if globalConfig.Network == "preview" {
126+
globalConfig.CardanoConfig = "preview/config.json"
127+
} else {
128+
globalConfig.CardanoConfig = "/opt/cardano/" + globalConfig.Network + "/config.json"
129+
}
130+
}
131+
121132
_, err = LoadTopologyConfig()
122133
if err != nil {
123134
return nil, fmt.Errorf("error loading topology: %+w", err)

0 commit comments

Comments
 (0)