-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathconfig.go
More file actions
1112 lines (1014 loc) · 36.1 KB
/
Copy pathconfig.go
File metadata and controls
1112 lines (1014 loc) · 36.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2025 Blink Labs Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dingo
import (
"context"
"errors"
"fmt"
"io"
"log/slog"
"maps"
"net/http"
"runtime"
"slices"
"strconv"
"time"
"github.com/blinklabs-io/dingo/chainsync"
"github.com/blinklabs-io/dingo/config/cardano"
"github.com/blinklabs-io/dingo/connmanager"
internalconfig "github.com/blinklabs-io/dingo/internal/config"
"github.com/blinklabs-io/dingo/internal/version"
"github.com/blinklabs-io/dingo/ledger"
"github.com/blinklabs-io/dingo/ledger/leios"
"github.com/blinklabs-io/dingo/topology"
ouroboros "github.com/blinklabs-io/gouroboros"
ocommon "github.com/blinklabs-io/gouroboros/protocol/common"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
type ListenerConfig = connmanager.ListenerConfig
// runMode constants for operational mode configuration
const (
runModeServe = "serve"
runModeLoad = "load"
runModeDev = "dev"
runModeLeios = "leios"
)
// StorageMode controls how much data the metadata store persists.
type StorageMode string
const (
// StorageModeCore stores only consensus and chain state data.
// Witnesses, scripts, datums, redeemers, and tx metadata CBOR
// are skipped. Suitable for block producers with no APIs.
StorageModeCore StorageMode = "core"
// StorageModeAPI stores everything needed for API queries
// (blockfrost, utxorpc, mesh) in addition to core data.
StorageModeAPI StorageMode = "api"
)
// Valid returns true if the storage mode is a recognized value.
func (m StorageMode) Valid() bool {
switch m {
case StorageModeCore, StorageModeAPI:
return true
default:
return false
}
}
// IsAPI returns true if the storage mode includes API data.
func (m StorageMode) IsAPI() bool {
return m == StorageModeAPI
}
// HistoryExpiryConfig controls local expiry of immutable block history.
type HistoryExpiryConfig struct {
Enabled bool
Frequency time.Duration
}
// OffchainMetadataConfig controls API-mode off-chain metadata fetching.
// Zero values use the internal fetcher defaults.
type OffchainMetadataConfig struct {
HTTPClient *http.Client
Interval time.Duration
RequestTimeout time.Duration
UserAgent string
IPFSGatewayURL string
BatchSize int
MaxBytes int64
AllowPrivateAddresses bool
}
// MidnightConfig controls the Midnight indexer and optional gRPC listener.
// Indexing is only active in API storage mode. Port 0 disables the gRPC
// listener while leaving indexing eligible to run.
type MidnightConfig struct {
Port uint
Host string
CNightPolicyID string
CNightAssetName string
MappingValidatorAddress string
AuthTokenPolicyID string
AuthTokenAssetName string
CommitteeCandidateAddress string
TechnicalCommitteeAddress string
TechnicalCommitteePolicyID string
CouncilAddress string
CouncilPolicyID string
PermissionedCandidatePolicy string
}
type Config struct {
promRegistry prometheus.Registerer
topologyConfig *topology.TopologyConfig
logger *slog.Logger
cardanoNodeConfig *cardano.CardanoNodeConfig
dataDir string
bindAddr string
blobPlugin string
metadataPlugin string
network string
tlsCertFilePath string
tlsKeyFilePath string
intersectPoints []ocommon.Point
listeners []ListenerConfig
mempoolCapacity int64
evictionWatermark float64
rejectionWatermark float64
outboundSourcePort uint
utxorpcPort uint
barkBaseUrl string
barkBlockDownloadHosts []string
barkPort uint
historyExpiry HistoryExpiryConfig
corsAllowedOrigins []string
networkMagic uint32
intersectTip bool
peerSharing bool
validateHistorical bool
strictUtxoValidation bool
strictLeaderEligibility bool
strictSlotClock bool
tracing bool
tracingStdout bool
runMode string
startEra internalconfig.StartEra
shutdownTimeout time.Duration
DatabaseWorkerPoolConfig ledger.DatabaseWorkerPoolConfig
// Peer targets (0 = use default, -1 = unlimited)
targetNumberOfKnownPeers int
targetNumberOfEstablishedPeers int
targetNumberOfActivePeers int
// Per-source quotas for active peers (0 = use default)
activePeersTopologyQuota int
activePeersGossipQuota int
activePeersLedgerQuota int
// Ledger peer discovery (negative = disabled, 0 = use
// defaultLedgerPeerTarget, positive = target)
ledgerPeerTarget int
// Peer governor tuning (0 = use default)
minHotPeers int
reconcileInterval time.Duration
inactivityTimeout time.Duration
bootstrapPromotionMinDiversityGroups int
inboundWarmTarget int
inboundHotQuota int
inboundMinTenure time.Duration
inboundHotScoreThreshold float64
inboundPruneAfter time.Duration
inboundDuplexOnlyForHot bool
inboundCooldown time.Duration
maxConnectionsPerIP int
maxInboundConns int
genesisBootstrap bool
genesisWindowSlots uint64
// Block production configuration (SPO mode)
// Field names match cardano-node environment variable naming convention
blockProducer bool
shelleyVRFKey string
shelleyKESKey string
shelleyOperationalCertificate string
// Forging tolerances (0 = use defaults)
forgeSyncToleranceSlots uint64
forgeStaleGapThresholdSlots uint64
// validateForgedBlock enables self-validation of locally-forged blocks
// (VRF/KES header crypto, body-hash, per-tx ledger rules) before the
// block is adopted onto the chain and diffused to peers.
validateForgedBlock bool
// Leios voting configuration (experimental)
leiosVoteSigningKeyFile string
leiosVoterPublicKeys map[string]string
// Leios pipeline timing override (experimental, provisional). Nil
// uses leios.DefaultPipelineTiming.
leiosPipelineTiming *leios.PipelineTiming
// Blockfrost API port (0 = disabled)
blockfrostPort uint
// Off-chain metadata fetcher configuration
offchainMetadata OffchainMetadataConfig
// Midnight indexer and gRPC API configuration
midnight MidnightConfig
// Chainsync multi-client configuration
chainsyncMaxClients int
chainsyncStallTimeout time.Duration
chainsyncStrategy chainsync.HeaderSyncStrategy
// Mesh API port (0 = disabled)
meshPort uint
// Storage mode: "core" or "api"
storageMode StorageMode
// CBOR cache configuration
cacheBlockLRUEntries int
cacheHotUtxoEntries int
cacheHotTxEntries int
cacheHotTxMaxBytes int64
}
// configPopulateNetworkMagic uses the named network (if specified) to determine the network magic value (if not specified)
func (n *Node) configPopulateNetworkMagic() error {
if n.config.networkMagic == 0 && n.config.network != "" {
tmpCfg := n.config
tmpNetwork, ok := ouroboros.NetworkByName(n.config.network)
if !ok {
return fmt.Errorf("unknown network name: %s", n.config.network)
}
tmpCfg.networkMagic = tmpNetwork.NetworkMagic
n.config = tmpCfg
}
return nil
}
// configWrapPromRegistry wraps the prometheus registry with a "network" label
// so that all metrics registered through it carry the network name automatically.
func (n *Node) configWrapPromRegistry() {
if n.config.promRegistry == nil {
return
}
// Determine the network name: prefer the configured name, fall back to
// reverse-lookup by network magic.
networkName := n.config.network
if networkName == "" {
if net, ok := ouroboros.NetworkByNetworkMagic(n.config.networkMagic); ok {
networkName = net.String()
} else {
networkName = strconv.FormatUint(uint64(n.config.networkMagic), 10)
}
}
if networkName == "" {
return
}
n.config.promRegistry = prometheus.WrapRegistererWith(
prometheus.Labels{"network": networkName},
n.config.promRegistry,
)
}
// registerBuildInfo registers a dingo_build_info gauge with version and
// commit labels. The gauge is always set to 1; Grafana reads the labels.
func (n *Node) registerBuildInfo() {
if n.config.promRegistry == nil {
return
}
promauto.With(n.config.promRegistry).NewGaugeVec(
prometheus.GaugeOpts{
Name: "dingo_build_info",
Help: "dingo build information",
},
[]string{"version", "commit", "goversion"},
).WithLabelValues(
version.GetVersionString(),
version.CommitHash,
runtime.Version(),
).Set(1)
}
// rtsMetricsUpdateInterval is how often the background updater refreshes
// the RTS-style gauges from runtime.MemStats. Chosen to stay comfortably
// under the default Prometheus scrape interval (15s) so consecutive
// scrapes always see a fresh sample, without paying ReadMemStats
// stop-the-world cost more often than necessary.
const rtsMetricsUpdateInterval = 10 * time.Second
// rtsMetrics holds Haskell-cardano-node-style RTS memory gauges backed
// by Go runtime.MemStats. Matching the Haskell naming lets existing
// cardano-node dashboards and alert rules work against Dingo without
// rewriting queries. The gauges are approximate mappings from Go heap
// statistics to Haskell RTS GC concepts.
type rtsMetrics struct {
gcLiveBytes prometheus.Gauge
gcHeapBytes prometheus.Gauge
gcMajorNum prometheus.Gauge
gcMinorNum prometheus.Gauge
}
// registerRTSMetrics creates the four RTS gauges on the node's Prometheus
// registry. Safe to call when promRegistry is nil — in that case it
// returns early and leaves n.rtsMetrics nil, matching the registerBuildInfo
// nil-guard pattern.
func (n *Node) registerRTSMetrics() {
if n.config.promRegistry == nil {
return
}
factory := promauto.With(n.config.promRegistry)
n.rtsMetrics = &rtsMetrics{
gcLiveBytes: factory.NewGauge(prometheus.GaugeOpts{
Name: "cardano_node_metrics_RTS_gcLiveBytes_int",
Help: "live heap bytes currently in use (Go runtime.MemStats.HeapAlloc)",
}),
gcHeapBytes: factory.NewGauge(prometheus.GaugeOpts{
Name: "cardano_node_metrics_RTS_gcHeapBytes_int",
Help: "heap memory bytes obtained from the OS (Go runtime.MemStats.HeapSys)",
}),
gcMajorNum: factory.NewGauge(prometheus.GaugeOpts{
Name: "cardano_node_metrics_RTS_gcMajorNum_int",
Help: "count of forced GCs (Go runtime.MemStats.NumForcedGC)",
}),
gcMinorNum: factory.NewGauge(prometheus.GaugeOpts{
Name: "cardano_node_metrics_RTS_gcMinorNum_int",
Help: "count of automatic GCs (Go runtime.MemStats.NumGC - NumForcedGC)",
}),
}
}
// updateRTSMetrics writes the four gauge values from a runtime.MemStats
// snapshot. Kept as a pure function (no ReadMemStats call, no timer) so
// unit tests can drive it with crafted MemStats values. The Go runtime
// guarantees NumForcedGC <= NumGC, so the subtraction never underflows.
func updateRTSMetrics(m *rtsMetrics, stats *runtime.MemStats) {
m.gcLiveBytes.Set(float64(stats.HeapAlloc))
m.gcHeapBytes.Set(float64(stats.HeapSys))
m.gcMajorNum.Set(float64(stats.NumForcedGC))
m.gcMinorNum.Set(float64(stats.NumGC - stats.NumForcedGC))
}
// runRTSMetricsUpdater samples runtime.MemStats on a ticker and writes
// the values into the RTS gauges. Collection happens on this goroutine
// rather than at Prometheus scrape time so a scrape never pays the
// ReadMemStats stop-the-world cost. Exits when ctx is cancelled.
//
// The interval parameter is accepted explicitly so tests can drive the
// updater with a much shorter tick without changing production cadence;
// production callers pass rtsMetricsUpdateInterval.
func (n *Node) runRTSMetricsUpdater(
ctx context.Context,
interval time.Duration,
) {
if n.rtsMetrics == nil {
return
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
// Prime the gauges immediately so a scrape arriving before the
// first tick returns real values instead of zero.
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
updateRTSMetrics(n.rtsMetrics, &stats)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
runtime.ReadMemStats(&stats)
updateRTSMetrics(n.rtsMetrics, &stats)
}
}
}
// isDevMode returns true if running in development mode
func (c *Config) isDevMode() bool {
return c.runMode == runModeDev
}
// isMusashiNetwork reports whether the configured network is the experimental
// Musashi testnet (the IOG Leios prototype network), identified either by its
// network name or its network magic. The Musashi testnet hard-forks into the
// Dijkstra era, so a node syncing it must enable the Dijkstra era table
// regardless of the configured run mode. This lets `dingo -n musashi` follow
// the chain without also requiring `--run-mode leios`.
func (c *Config) isMusashiNetwork() bool {
return c.network == ouroboros.NetworkCardanoMusashi.Name ||
c.networkMagic == ouroboros.NetworkCardanoMusashi.NetworkMagic
}
// experimentalLeiosNetworkingEnabled reports whether the Leios node-to-node
// mini-protocols (leios-fetch / leios-notify) should be offered on outbound
// and inbound connections.
//
// This is enabled on the Musashi testnet (so `dingo -n musashi` fetches
// endorser blocks and follows the Leios overlay) as well as via explicit
// opt-in (leios run mode or a Dijkstra start era). Earlier prototype relays
// reset any connection that opened the standalone leios-votes mini-protocol
// (protocol 20), which the prototype does not run; that protocol is now gated
// off for the prototype network (see EnableLeiosVotes wiring in node.go), and
// the leios-notify / leios-fetch codecs decode the prototype's wire dialect,
// so the Leios protocols can stay active on the Musashi testnet.
func (c *Config) experimentalLeiosNetworkingEnabled() bool {
return c.isMusashiNetwork() ||
c.runMode == runModeLeios ||
c.startEra.IsDijkstra()
}
// experimentalDijkstraEnabled reports whether the Dijkstra ledger era is
// active. Beyond the explicit opt-ins, the Musashi testnet hard-forks into
// Dijkstra, so syncing it requires the Dijkstra era table even over base
// protocols.
func (c *Config) experimentalDijkstraEnabled() bool {
return c.experimentalLeiosNetworkingEnabled() || c.isMusashiNetwork()
}
func (n *Node) configValidate() error {
// Default storageMode to "core" when unset, and validate.
if n.config.storageMode == "" {
n.config.storageMode = StorageModeCore
}
if !n.config.storageMode.Valid() {
return fmt.Errorf(
"invalid storage mode %q: must be %q or %q",
n.config.storageMode,
StorageModeCore,
StorageModeAPI,
)
}
if !n.config.startEra.Valid() {
return fmt.Errorf(
"invalid start era %q: must be empty or %q",
n.config.startEra,
internalconfig.StartEraDijkstra,
)
}
// In core mode, ignore API ports — they are only used in API mode.
// This lets defaults stay non-zero without requiring core-mode users
// to explicitly disable each one.
if n.config.networkMagic == 0 {
return fmt.Errorf(
"invalid network magic value: %d",
n.config.networkMagic,
)
}
if len(n.config.listeners) == 0 {
return errors.New("no listeners defined")
}
for _, listener := range n.config.listeners {
if listener.Listener != nil {
continue
}
if listener.ListenNetwork != "" && listener.ListenAddress != "" {
continue
}
return errors.New(
"listener must provide net.Listener or listen network/address values",
)
}
if n.config.cardanoNodeConfig != nil {
shelleyGenesis := n.config.cardanoNodeConfig.ShelleyGenesis()
if shelleyGenesis == nil {
return errors.New("unable to get Shelley genesis information")
}
if n.config.networkMagic != shelleyGenesis.NetworkMagic {
return fmt.Errorf(
"network magic (%d) doesn't match value from Shelley genesis (%d)",
n.config.networkMagic,
shelleyGenesis.NetworkMagic,
)
}
}
return nil
}
// ConfigOptionFunc is a type that represents functions that modify the Connection config
type ConfigOptionFunc func(*Config)
// NewConfig creates a new dingo config with the specified options
func NewConfig(opts ...ConfigOptionFunc) Config {
c := Config{
// Default logger will throw away logs
// We do this so we don't have to add guards around every log operation
logger: slog.New(slog.NewJSONHandler(io.Discard, nil)),
genesisBootstrap: true,
historyExpiry: HistoryExpiryConfig{
Frequency: time.Hour,
},
midnight: MidnightConfig{
Port: 50051,
Host: "0.0.0.0",
},
corsAllowedOrigins: []string{
"*",
},
}
// Apply options
for _, opt := range opts {
opt(&c)
}
return c
}
// WithCardanoNodeConfig specifies the CardanoNodeConfig object to use. This is mostly used for loading genesis config files
// referenced by the dingo config
func WithCardanoNodeConfig(
cardanoNodeConfig *cardano.CardanoNodeConfig,
) ConfigOptionFunc {
return func(c *Config) {
c.cardanoNodeConfig = cardanoNodeConfig
}
}
// WithBindAddr specifies the IP address used for API listeners
// (Blockfrost, Mesh, UTxO RPC). The default is "0.0.0.0" (all interfaces).
func WithBindAddr(addr string) ConfigOptionFunc {
return func(c *Config) {
c.bindAddr = addr
}
}
// WithDatabasePath specifies the persistent data directory to use. The default is to store everything in memory
func WithDatabasePath(dataDir string) ConfigOptionFunc {
return func(c *Config) {
c.dataDir = dataDir
}
}
// WithBlobPlugin specifies the blob storage plugin to use.
func WithBlobPlugin(plugin string) ConfigOptionFunc {
return func(c *Config) {
c.blobPlugin = plugin
}
}
// WithMetadataPlugin specifies the metadata storage plugin to use.
func WithMetadataPlugin(plugin string) ConfigOptionFunc {
return func(c *Config) {
c.metadataPlugin = plugin
}
}
// WithIntersectPoints specifies intersect point(s) for the initial chainsync. The default is to start at chain genesis
func WithIntersectPoints(points []ocommon.Point) ConfigOptionFunc {
return func(c *Config) {
c.intersectPoints = points
}
}
// WithIntersectTip specifies whether to start the initial chainsync at the current tip. The default is to start at chain genesis
func WithIntersectTip(intersectTip bool) ConfigOptionFunc {
return func(c *Config) {
c.intersectTip = intersectTip
}
}
// WithLogger specifies the logger to use. This defaults to discarding log output
func WithLogger(logger *slog.Logger) ConfigOptionFunc {
return func(c *Config) {
c.logger = logger
}
}
// WithListeners specifies the listener config(s) to use
func WithListeners(listeners ...ListenerConfig) ConfigOptionFunc {
return func(c *Config) {
c.listeners = append(c.listeners, listeners...)
}
}
// WithNetwork specifies the named network to operate on. This will automatically set the appropriate network magic value
func WithNetwork(network string) ConfigOptionFunc {
return func(c *Config) {
c.network = network
}
}
// WithNetworkMagic specifies the network magic value to use. This will override any named network specified
func WithNetworkMagic(networkMagic uint32) ConfigOptionFunc {
return func(c *Config) {
c.networkMagic = networkMagic
}
}
// WithOutboundSourcePort specifies the source port to use for outbound connections. This defaults to dynamic source ports
func WithOutboundSourcePort(port uint) ConfigOptionFunc {
return func(c *Config) {
c.outboundSourcePort = port
}
}
// WithUtxorpcTlsCertFilePath specifies the path to the TLS certificate for the gRPC API listener. This defaults to empty
func WithUtxorpcTlsCertFilePath(path string) ConfigOptionFunc {
return func(c *Config) {
c.tlsCertFilePath = path
}
}
// WithUtxorpcTlsKeyFilePath specifies the path to the TLS key for the gRPC API listener. This defaults to empty
func WithUtxorpcTlsKeyFilePath(path string) ConfigOptionFunc {
return func(c *Config) {
c.tlsKeyFilePath = path
}
}
// WithUtxorpcPort specifies the port to use for the gRPC API listener. 0 disables the server (default)
func WithUtxorpcPort(port uint) ConfigOptionFunc {
return func(c *Config) {
c.utxorpcPort = port
}
}
// WithPeerSharing specifies whether to enable peer sharing. This is disabled by default
func WithPeerSharing(peerSharing bool) ConfigOptionFunc {
return func(c *Config) {
c.peerSharing = peerSharing
}
}
// WithPrometheusRegistry specifies a prometheus.Registerer instance to add metrics to. In most cases, prometheus.DefaultRegistry would be
// a good choice to get metrics working
func WithPrometheusRegistry(registry prometheus.Registerer) ConfigOptionFunc {
return func(c *Config) {
c.promRegistry = registry
}
}
// WithTopologyConfig specifies a topology.TopologyConfig to use for outbound peers
func WithTopologyConfig(
topologyConfig *topology.TopologyConfig,
) ConfigOptionFunc {
return func(c *Config) {
c.topologyConfig = topologyConfig
}
}
// WithTracing enables tracing. By default, spans are submitted to a HTTP(s) endpoint using OTLP. This can be configured
// using the OTEL_EXPORTER_OTLP_* env vars documented in the README for [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp]
func WithTracing(tracing bool) ConfigOptionFunc {
return func(c *Config) {
c.tracing = tracing
}
}
// WithTracingStdout enables tracing output to stdout. This also requires tracing to enabled separately. This is mostly useful for debugging
func WithTracingStdout(stdout bool) ConfigOptionFunc {
return func(c *Config) {
c.tracingStdout = stdout
}
}
// WithShutdownTimeout specifies the timeout for graceful shutdown. The default is 30 seconds
func WithShutdownTimeout(timeout time.Duration) ConfigOptionFunc {
return func(c *Config) {
c.shutdownTimeout = timeout
}
}
// WithMempoolCapacity sets the mempool capacity (in bytes)
func WithMempoolCapacity(capacity int64) ConfigOptionFunc {
return func(c *Config) {
c.mempoolCapacity = capacity
}
}
// WithEvictionWatermark sets the mempool eviction watermark
// as a fraction of capacity (0.0-1.0). When a new TX would
// push the mempool past this fraction, oldest TXs are evicted
// to make room. Default is 0.90 (90%).
func WithEvictionWatermark(
watermark float64,
) ConfigOptionFunc {
return func(c *Config) {
c.evictionWatermark = watermark
}
}
// WithRejectionWatermark sets the mempool rejection watermark
// as a fraction of capacity (0.0-1.0). New TXs are rejected
// when the mempool would exceed this fraction even after
// eviction. Default is 0.95 (95%).
func WithRejectionWatermark(
watermark float64,
) ConfigOptionFunc {
return func(c *Config) {
c.rejectionWatermark = watermark
}
}
// WithRunMode sets the operational mode ("serve", "load", or "dev").
// "dev" mode enables development behaviors (forge blocks, disable outbound).
func WithRunMode(mode string) ConfigOptionFunc {
return func(c *Config) {
c.runMode = mode
}
}
// WithStartEra sets the experimental direct startup era. Empty uses the
// genesis protocol version; "dijkstra" starts directly in the Dijkstra era.
func WithStartEra(startEra string) ConfigOptionFunc {
return func(c *Config) {
c.startEra = internalconfig.StartEra(startEra)
}
}
// WithValidateHistorical specifies whether to validate all historical blocks during ledger processing
func WithValidateHistorical(validate bool) ConfigOptionFunc {
return func(c *Config) {
c.validateHistorical = validate
}
}
// WithStrictUtxoValidation specifies whether an unrecoverable consumed UTxO
// past the recorded Mithril sync boundary is a hard error rather than a
// silently skipped condition. See database.Config.StrictUtxoValidation.
func WithStrictUtxoValidation(strict bool) ConfigOptionFunc {
return func(c *Config) {
c.strictUtxoValidation = strict
}
}
// WithStrictLeaderEligibility specifies whether a block is rejected (rather
// than warned about and skipped) when the total active stake is zero or the
// active slot coefficient is unavailable or non-positive during Praos leader
// eligibility checking. See ledger.LedgerStateConfig.StrictLeaderEligibility.
func WithStrictLeaderEligibility(strict bool) ConfigOptionFunc {
return func(c *Config) {
c.strictLeaderEligibility = strict
}
}
// WithStrictSlotClock specifies whether a transaction is rejected (rather than
// validated against the snapshot tip slot) when the slot clock cannot be read
// during validation. See ledger.LedgerStateConfig.StrictSlotClock.
func WithStrictSlotClock(strict bool) ConfigOptionFunc {
return func(c *Config) {
c.strictSlotClock = strict
}
}
// WithDatabaseWorkerPoolConfig specifies the database worker pool configuration
func WithDatabaseWorkerPoolConfig(
cfg ledger.DatabaseWorkerPoolConfig,
) ConfigOptionFunc {
return func(c *Config) {
c.DatabaseWorkerPoolConfig = cfg
}
}
// WithPeerTargets specifies the target number of peers in each state.
// Use 0 to use the default target, or -1 for unlimited.
// Default targets: known=150, established=50, active=20
func WithPeerTargets(
targetKnown, targetEstablished, targetActive int,
) ConfigOptionFunc {
return func(c *Config) {
c.targetNumberOfKnownPeers = targetKnown
c.targetNumberOfEstablishedPeers = targetEstablished
c.targetNumberOfActivePeers = targetActive
}
}
// WithActivePeersQuotas specifies the per-source quotas for active peers.
// Use 0 to use the default quota, or a negative value to disable enforcement.
// Default quotas: topology=20, gossip=20, ledger=20
func WithActivePeersQuotas(
topologyQuota, gossipQuota, ledgerQuota int,
) ConfigOptionFunc {
return func(c *Config) {
c.activePeersTopologyQuota = topologyQuota
c.activePeersGossipQuota = gossipQuota
c.activePeersLedgerQuota = ledgerQuota
}
}
// WithLedgerPeerTarget specifies the target number of known ledger peers.
// Discovery will add peers only until this target is reached.
// Negative values disable ledger peer discovery, 0 uses
// defaultLedgerPeerTarget, and positive values use that target. Default: 20.
func WithLedgerPeerTarget(n int) ConfigOptionFunc {
return func(c *Config) {
c.ledgerPeerTarget = n
}
}
// WithMinHotPeers specifies the minimum number of hot peers before aggressive
// promotion is triggered. Non-positive values are ignored. Default: 10.
func WithMinHotPeers(n int) ConfigOptionFunc {
return func(c *Config) {
if n > 0 {
c.minHotPeers = n
}
}
}
// WithBootstrapPromotionMinDiversityGroups sets the minimum number of
// bootstrap-time peer diversity groups to prefer before falling back to pure
// score ordering. Non-positive values use the peer-governor default.
func WithBootstrapPromotionMinDiversityGroups(n int) ConfigOptionFunc {
return func(c *Config) {
c.bootstrapPromotionMinDiversityGroups = n
}
}
// WithReconcileInterval specifies how often the peer governor runs its
// reconciliation loop. Non-positive values are ignored. Default: 5m.
func WithReconcileInterval(d time.Duration) ConfigOptionFunc {
return func(c *Config) {
if d > 0 {
c.reconcileInterval = d
}
}
}
// WithInactivityTimeout specifies how long a hot peer can be inactive
// before being demoted to warm. Non-positive values are ignored. Default: 10m.
func WithInactivityTimeout(d time.Duration) ConfigOptionFunc {
return func(c *Config) {
if d > 0 {
c.inactivityTimeout = d
}
}
}
// WithInboundPeerGovernance specifies explicit inbound peer governance budget
// and phase-1 policy fields. Non-positive values use peer governor defaults.
func WithInboundPeerGovernance(
warmTarget int,
hotQuota int,
minTenure time.Duration,
hotScoreThreshold float64,
pruneAfter time.Duration,
duplexOnlyForHot bool,
cooldown time.Duration,
) ConfigOptionFunc {
return func(c *Config) {
if warmTarget > 0 {
c.inboundWarmTarget = warmTarget
}
if hotQuota > 0 {
c.inboundHotQuota = hotQuota
}
if minTenure > 0 {
c.inboundMinTenure = minTenure
}
if hotScoreThreshold > 0 {
c.inboundHotScoreThreshold = hotScoreThreshold
}
if pruneAfter > 0 {
c.inboundPruneAfter = pruneAfter
}
c.inboundDuplexOnlyForHot = duplexOnlyForHot
if cooldown > 0 {
c.inboundCooldown = cooldown
}
}
}
// WithMaxConnectionsPerIP specifies the maximum number of concurrent
// inbound connections from a single IP. Non-positive values are ignored. Default: 5.
func WithMaxConnectionsPerIP(n int) ConfigOptionFunc {
return func(c *Config) {
if n > 0 {
c.maxConnectionsPerIP = n
}
}
}
// WithMaxInboundConns specifies the maximum number of inbound connections.
// Non-positive values are ignored. Default: 100.
func WithMaxInboundConns(n int) ConfigOptionFunc {
return func(c *Config) {
if n > 0 {
c.maxInboundConns = n
}
}
}
// WithGenesisBootstrap enables Genesis-mode chain selection during from-origin
// bootstrap. Genesis mode automatically exits once the local tip is within the
// configured Genesis window of the best known peer tip.
func WithGenesisBootstrap(enabled bool) ConfigOptionFunc {
return func(c *Config) {
c.genesisBootstrap = enabled
}
}
// WithGenesisWindowSlots overrides the Genesis density comparison window.
// A zero value lets the node derive the window from Shelley genesis parameters
// using 3k/f.
func WithGenesisWindowSlots(slots uint64) ConfigOptionFunc {
return func(c *Config) {
c.genesisWindowSlots = slots
}
}
// WithBlockProducer enables block production mode (CARDANO_BLOCK_PRODUCER).
// When enabled, the node will attempt to produce blocks using the configured credentials.
func WithBlockProducer(enabled bool) ConfigOptionFunc {
return func(c *Config) {
c.blockProducer = enabled
}
}
// WithShelleyVRFKey specifies the path to the VRF signing key file (CARDANO_SHELLEY_VRF_KEY).
// Required for block production.
func WithShelleyVRFKey(path string) ConfigOptionFunc {
return func(c *Config) {
c.shelleyVRFKey = path
}
}
// WithShelleyKESKey specifies the path to the KES signing key file (CARDANO_SHELLEY_KES_KEY).
// Required for block production.
func WithShelleyKESKey(path string) ConfigOptionFunc {
return func(c *Config) {
c.shelleyKESKey = path
}
}
// WithShelleyOperationalCertificate specifies the path to the operational certificate file
// (CARDANO_SHELLEY_OPERATIONAL_CERTIFICATE). Required for block production.
func WithShelleyOperationalCertificate(path string) ConfigOptionFunc {
return func(c *Config) {
c.shelleyOperationalCertificate = path
}
}
// WithLeiosVoteSigningKeyFile specifies the path to a hex-encoded BLS12-381
// Leios vote signing key (DINGO_LEIOS_VOTE_SIGNING_KEY_FILE). When set on a
// block producer whose pool is a Leios committee member, the node emits
// votes for endorser blocks. Experimental, leios runMode only.
func WithLeiosVoteSigningKeyFile(path string) ConfigOptionFunc {
return func(c *Config) {
c.leiosVoteSigningKeyFile = path
}
}
// WithLeiosVoterPublicKeys specifies the static Leios voter public key
// registry (DINGO_LEIOS_VOTER_PUBLIC_KEYS): hex pool key hash to
// hex-encoded BLS12-381 public key. Stands in for CIP-0164 key
// registration, which is not yet specified. Experimental, leios runMode
// only.
func WithLeiosVoterPublicKeys(keys map[string]string) ConfigOptionFunc {
return func(c *Config) {
// Copy so later caller mutations cannot change live config
c.leiosVoterPublicKeys = maps.Clone(keys)
}
}
// WithLeiosPipelineTiming overrides the provisional Leios pipeline stage
// timing windows. CIP-0164 has not finalized these parameters, so they are
// kept off-chain and overridable here rather than as protocol parameters.
// When unset, leios.DefaultPipelineTiming applies. Experimental, leios
// runMode only.
func WithLeiosPipelineTiming(timing leios.PipelineTiming) ConfigOptionFunc {
return func(c *Config) {
t := timing
c.leiosPipelineTiming = &t
}
}
// WithForgeSyncToleranceSlots sets the slot gap tolerated before forging is skipped.
// Use 0 to fall back to the built-in default.
func WithForgeSyncToleranceSlots(slots uint64) ConfigOptionFunc {
return func(c *Config) {
c.forgeSyncToleranceSlots = slots
}
}
// WithForgeStaleGapThresholdSlots sets the slot gap threshold for stale database warnings.
// Use 0 to fall back to the built-in default.
func WithForgeStaleGapThresholdSlots(slots uint64) ConfigOptionFunc {
return func(c *Config) {
c.forgeStaleGapThresholdSlots = slots
}
}
// WithValidateForgedBlock enables self-validation of locally-forged blocks
// before they are adopted onto the chain and diffused to peers. When enabled,
// the forger runs VRF/KES header crypto, body-hash consistency, and per-tx
// ledger validation on each forged block. A failing block is dropped without
// being adopted or diffused. Disabled by default.
func WithValidateForgedBlock(enabled bool) ConfigOptionFunc {
return func(c *Config) {
c.validateForgedBlock = enabled
}
}
// WithBlockfrostPort specifies the port for the
// Blockfrost-compatible REST API server. The server binds
// to the node's bindAddr on this port. 0 disables the
// server (default).
func WithBlockfrostPort(port uint) ConfigOptionFunc {
return func(c *Config) {
c.blockfrostPort = port
}
}