-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathconfig.go
More file actions
1818 lines (1552 loc) · 69.4 KB
/
Copy pathconfig.go
File metadata and controls
1818 lines (1552 loc) · 69.4 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
package waved
import (
"context"
"fmt"
"io"
"net"
"os"
"path/filepath"
"strings"
"time"
"github.com/btcsuite/btcd/chaincfg/v2"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightninglabs/wavelength/baselib/actor"
"github.com/lightninglabs/wavelength/btcwbackend"
"github.com/lightninglabs/wavelength/chainbackends"
"github.com/lightninglabs/wavelength/credit"
"github.com/lightninglabs/wavelength/db"
"github.com/lightninglabs/wavelength/db/sqlc"
"github.com/lightninglabs/wavelength/lwwallet"
mailboxpb "github.com/lightninglabs/wavelength/mailbox/pb"
"github.com/lightninglabs/wavelength/metrics"
"github.com/lightninglabs/wavelength/oor"
"github.com/lightninglabs/wavelength/rpc/roundpb"
"google.golang.org/grpc"
)
const (
// DefaultDataDir is the default root data directory for waved. It
// lives under the user's home directory.
DefaultDataDir = "~/.waved"
// DefaultNetwork is the default bitcoin network the daemon operates
// on.
DefaultNetwork = "mainnet"
// DefaultRPCHost is the default listen address for the daemon's own
// gRPC server.
DefaultRPCHost = "localhost:10029"
// DefaultRPCGatewayHost is the default listen address for the
// daemon's HTTP/JSON gateway.
DefaultRPCGatewayHost = "localhost:10031"
// DefaultLndHost is the default address for connecting to the local
// lnd instance.
DefaultLndHost = "localhost:10009"
// DefaultServerHost is the default address for the ark operator's
// mailbox edge server.
DefaultServerHost = "localhost:10010"
// defaultSwapServerHost is the default address for the swap server.
defaultSwapServerHost = "localhost:10030"
// defaultMainnetServerGRPCHost is the public mainnet Ark operator gRPC
// endpoint.
defaultMainnetServerGRPCHost = "wavelength." +
"lightning.finance:443"
// defaultMainnetServerRESTHost is the public mainnet Ark operator REST
// endpoint. The external NLB and dual-SAN certificates from
// lightning-infra#3592 only cover the gRPC names. The prod REST
// ingress still serves the raw cluster hostname on its own, so this
// name stays dark until the ingress host and matching certificate SAN
// work in lightning-infra#3749 lands.
defaultMainnetServerRESTHost = "wavelength-rest." +
"lightning.finance"
// defaultMainnetSwapServerGRPCHost is the public mainnet swap server
// gRPC endpoint.
defaultMainnetSwapServerGRPCHost = "swap.wavelength." +
"lightning.finance:443"
// defaultMainnetSwapServerRESTHost is the public mainnet swap server
// REST endpoint. Not routable yet either; see
// defaultMainnetServerRESTHost.
defaultMainnetSwapServerRESTHost = "swapd-rest." +
"lightning.finance"
// defaultTestnet3ServerGRPCHost is the public testnet3 Ark operator
// gRPC endpoint.
defaultTestnet3ServerGRPCHost = "test.wavelength." +
"lightning.finance:443"
// defaultTestnet3ServerRESTHost is the public testnet3 Ark operator
// REST endpoint.
defaultTestnet3ServerRESTHost = "test.wavelength-rest." +
"lightning.finance"
// defaultTestnet3SwapServerGRPCHost is the public testnet3 swap server
// gRPC endpoint.
defaultTestnet3SwapServerGRPCHost = "swap.test.wavelength." +
"lightning.finance:443"
// defaultTestnet3SwapServerRESTHost is the public testnet3 swap server
// REST endpoint.
defaultTestnet3SwapServerRESTHost = "test.swapd-rest." +
"lightning.finance"
// defaultTestnet4ServerGRPCHost is the public testnet4 Ark operator
// gRPC endpoint. No friendly-domain CNAME exists yet; the public NLB
// stays behind the raw cluster hostname until the certificate work
// in lightning-infra#3517 lands.
defaultTestnet4ServerGRPCHost = "lumosd-testnet4.testnet." +
"lightningcluster.com:443"
// defaultTestnet4ServerRESTHost is the public testnet4 Ark operator
// REST endpoint.
defaultTestnet4ServerRESTHost = "test4.wavelength-rest." +
"lightning.finance"
// defaultTestnet4SwapServerGRPCHost is the public testnet4 swap server
// gRPC endpoint. No friendly-domain CNAME exists yet; see
// defaultTestnet4ServerGRPCHost.
defaultTestnet4SwapServerGRPCHost = "swapd-testnet4.testnet." +
"lightningcluster.com:443"
// defaultTestnet4SwapServerRESTHost is the public testnet4 swap server
// REST endpoint.
defaultTestnet4SwapServerRESTHost = "test4.swapd-rest." +
"lightning.finance"
// defaultSignetServerGRPCHost is the public signet Ark operator gRPC
// endpoint.
defaultSignetServerGRPCHost = "signet.wavelength." +
"lightning.finance:443"
// defaultSignetServerRESTHost is the public signet Ark operator REST
// endpoint. The REST client adds the HTTPS scheme when the configured
// host is bare.
defaultSignetServerRESTHost = "signet.wavelength-rest." +
"lightning.finance"
// defaultSignetSwapServerGRPCHost is the public signet swap server gRPC
// endpoint.
defaultSignetSwapServerGRPCHost = "swap.signet.wavelength." +
"lightning.finance:443"
// defaultSignetSwapServerRESTHost is the public signet swap server REST
// endpoint. The REST client adds the HTTPS scheme when the configured
// host is bare.
defaultSignetSwapServerRESTHost = "signet.swapd-rest." +
"lightning.finance"
// DefaultIndexerServerID is the canonical operator identifier used
// in signed indexer proofs.
DefaultIndexerServerID = "lumosd"
// DefaultRPCTimeout is the default timeout for RPC calls to lnd.
DefaultRPCTimeout = 30 * time.Second
// DefaultDebugLevel is the default logging verbosity.
DefaultDebugLevel = "info"
// DefaultShutdownTimeout is the maximum duration to wait for
// graceful shutdown of the actor system and subsystems.
DefaultShutdownTimeout = 10 * time.Second
// DefaultForfeitCollectionTimeout is the default wall-clock
// deadline for collecting forfeit signatures from VTXO actors
// during a round.
DefaultForfeitCollectionTimeout = 2 * time.Minute
// DefaultSigningWorkers selects backend-aware MuSig2 concurrency. The
// profiled lwwallet backend uses a small bounded pool, while other
// signer backends remain serial unless explicitly configured otherwise.
DefaultSigningWorkers = 0
// DefaultLwwalletSigningWorkers is the bounded MuSig2 concurrency used
// by the profiled in-process lwwallet signer in automatic mode.
DefaultLwwalletSigningWorkers = 4
// MaxSigningWorkers prevents a local configuration mistake from
// creating an excessive number of simultaneous cryptographic jobs.
MaxSigningWorkers = 64
// DefaultWalletType is the default wallet backend. The "lwwallet"
// backend uses an in-process lightweight wallet backed by
// btcwallet and Esplora, requiring no external lnd node.
DefaultWalletType = "lwwallet"
// WalletTypeLnd selects lnd as the wallet backend.
WalletTypeLnd = "lnd"
// WalletTypeLwwallet selects the lightweight in-process wallet
// backed by btcwallet and Esplora.
WalletTypeLwwallet = "lwwallet"
// WalletTypeBtcwallet selects the in-process wallet backed by
// btcwallet and neutrino (BIP 157/158 compact block filters).
WalletTypeBtcwallet = "btcwallet"
// DefaultEsploraPollInterval is the default interval at which the
// lwwallet polls the Esplora API for new blocks and transactions.
// Mainnet blocks land roughly every 10 minutes, so a 30 s cadence
// stays comfortably within one block's worth of latency while
// keeping the request volume well under the public mempool.space
// rate limits. Tests / regtest environments that mine blocks on
// demand should override this to a sub-second value via the
// `wallet.pollinterval` config knob.
DefaultEsploraPollInterval = 30 * time.Second
// DefaultRecoveryWindow is the default address look-ahead window
// used during lwwallet recovery.
DefaultRecoveryWindow = 100
// DefaultMaxOperatorFeeSat is the default client-side cap on
// the per-round operator fee under the seal-time fee
// handshake. 0.01 BTC — generous for regtest/testnet and well
// below any reasonable mainnet abuse threshold. Operators that
// need a stricter cap override via the `maxoperatorfeesat`
// config knob.
DefaultMaxOperatorFeeSat int64 = 1_000_000
// RPCTransportGRPC selects native gRPC for daemon-owned outbound RPCs.
RPCTransportGRPC = "grpc"
// RPCTransportREST selects grpc-gateway HTTP/JSON for daemon-owned
// outbound RPCs.
RPCTransportREST = "rest"
// DefaultSwapRecoveryCooperativeFailureGracePeriod is how long the
// daemon-owned swap runtime keeps retrying cooperative vHTLC settlement
// after the first observed cooperative send failure before automatic
// on-chain recovery may start.
DefaultSwapRecoveryCooperativeFailureGracePeriod = time.Hour
// DefaultSwapRecoveryMinMarginBlocks is the block-height safety margin
// that lets receive-side claim recovery override the wall-clock grace
// period before the sender refund locktime can make waiting unsafe.
DefaultSwapRecoveryMinMarginBlocks = uint32(12)
// DefaultSwapRecoveryMaxFeeRateSatPerKW caps swapruntime-armed vHTLC
// exit spends at 100 sat/vbyte. The cap is copied into the recovery row
// at arm time so later config changes cannot silently loosen an
// existing job.
DefaultSwapRecoveryMaxFeeRateSatPerKW int32 = 25_000
)
// Config holds all configuration for the waved daemon.
type Config struct {
// DataDir is the root data directory for all daemon state. Database
// files, logs, and TLS material are stored under this directory.
DataDir string `mapstructure:"datadir"`
// Network selects the bitcoin network: mainnet, testnet, testnet4,
// regtest, simnet, or signet.
Network string `mapstructure:"network"`
// DebugLevel controls the verbosity of daemon logging. A single value
// sets the global level for all subsystems (e.g. "info"). A
// comma-separated list of subsystem=level pairs sets per-subsystem
// levels (e.g. "ROND=debug,OORC=trace,info"). The last bare level in
// the list (without a '=') sets the default for unlisted subsystems.
// Valid levels: trace, debug, info, warn, error, critical, off.
DebugLevel string `mapstructure:"debuglevel"`
// LogDirPath overrides the network-scoped directory used by the CLI
// for persistent daemon log files. When empty, logs are written under
// DataDir/logs/<network>.
LogDirPath string `mapstructure:"logdir"`
// LogWriter is the sink for daemon log output. When nil, waved
// writes logs to stdout.
LogWriter io.Writer
// MailboxEdgeFactory optionally wraps the mailbox transport edge used
// by the serverconn runtime. Test harnesses use this to intercept
// durable transport traffic without changing production config files.
MailboxEdgeFactory MailboxEdgeFactory
// PackageSubmitter optionally provides atomic parent+child
// package submission for the unroll subsystem. Typically backed
// by a direct bitcoind RPC client. Set programmatically by the
// test harness; not serialized to config files.
PackageSubmitter chainbackends.PackageSubmitter
// FailUnrollBroadcastReason is a TEST-ONLY hook. When non-empty, the
// unroll subsystem's tx-confirmation requests are rejected with this
// reason before any broadcast, simulating a proof tx that cannot enter
// the mempool (e.g. "min relay fee not met" on a sub-dust exit). It
// lets integration tests reproduce the wavelength#602 failure mode —
// a unilateral exit that fails terminally with no on-chain footprint —
// and verify the VTXO is recovered to live. Empty in production; not
// serialized to config files.
FailUnrollBroadcastReason string
// Lnd configures the connection to the backing lnd node.
Lnd *LndConfig `mapstructure:"lnd"`
// Server configures the connection to the ark operator's mailbox
// edge server.
Server *ServerConfig `mapstructure:"server"`
// RPC configures the daemon's own gRPC server that external tools
// (CLI, GUI) connect to.
RPC *RPCConfig `mapstructure:"rpc"`
// RPCServiceRegistrars are programmatic hooks that may register
// optional subservers on the daemon gRPC server after DaemonService is
// registered. They are not loaded from config files because they wire
// compiled-in runtime capabilities, such as swapruntime, rather than
// user-provided daemon settings.
RPCServiceRegistrars []RPCServiceRegistrar
// UnaryServerInterceptors wrap every unary RPC handler on the daemon
// gRPC server. Like RPCServiceRegistrars they wire compiled-in runtime
// capabilities (such as mapping wavewalletrpc sentinel errors to
// machine-readable status codes), not user-provided daemon settings.
UnaryServerInterceptors []grpc.UnaryServerInterceptor
// RPCGatewayRegistrars are programmatic hooks that may register
// optional subservers on the daemon HTTP/JSON gateway after
// DaemonService is registered.
RPCGatewayRegistrars []RPCGatewayRegistrar
// WalletReadyHooks are programmatic hooks that run after the
// wallet-derived mailbox transport and wallet-dependent actors are
// online, but before daemon readiness is marked. They are used by
// optional subservers that must register RPC surfaces while locked
// but defer background work until the wallet can sign.
WalletReadyHooks []WalletReadyHook
// Wallet configures the wallet backend used for signing, key
// derivation, and chain access.
Wallet *WalletConfig `mapstructure:"wallet"`
// ForfeitCollectionTimeout is the maximum wall-clock
// duration to wait for forfeit signatures during a round.
// If zero, the default of 2 minutes is used.
//nolint:ll
ForfeitCollectionTimeout time.Duration `mapstructure:"forfeitcollectiontimeout"`
// SigningWorkers bounds the number of VTXO signer sessions processed in
// parallel. Zero selects a backend-aware default and one preserves the
// original serial behavior.
SigningWorkers int `mapstructure:"signingworkers"`
// RegistrationTimeout is the maximum wall-clock duration to
// wait for the server's RoundJoined admission watermark after
// sending a JoinRoundRequest. If zero, the round package
// default is used; a negative value disables the timeout.
RegistrationTimeout time.Duration `mapstructure:"registrationtimeout"`
// AllowMainnet must be set to true explicitly to run the daemon
// on mainnet. This guard prevents accidentally operating on
// mainnet during development, since DefaultNetwork is "mainnet".
AllowMainnet bool `mapstructure:"allow-mainnet"`
// AllowInsecureMainnet permits the rpc.notls and rpc.no-macaroons
// options on mainnet TCP listeners. Both are refused on mainnet by
// default so a stray flag can't silently expose an unauthenticated,
// plaintext RPC surface. Deployments that terminate TLS and enforce
// authentication at an external proxy must set this explicitly to
// acknowledge that the daemon's own RPC listener runs without
// transport security. See the lightning-infra tracking issue for the
// deployment rationale.
AllowInsecureMainnet bool `mapstructure:"allow-insecure-mainnet"`
// Unroll configures the unilateral-exit subsystem.
Unroll *UnrollConfig `mapstructure:"unroll"`
// Swap configures the optional swapruntime subserver. The fields are
// inert in default builds because the SwapClientService is not
// registered unless the daemon is compiled with the swapruntime tag.
Swap *SwapConfig `mapstructure:"swap"`
// SwapWallet configures the optional wavewalletrpc subserver (the
// simplified high-level wallet facade composed over the swap
// runtime and the cooperative-leave subsystem). The fields are
// inert in default builds because the WalletService is not
// registered unless the daemon is compiled with both the
// wavewalletrpc and swapruntime tags.
SwapWallet *SwapWalletConfig `mapstructure:"swapwallet"`
// ActivityStore is the canonical activity-log projector the
// wavewalletrpc subserver writes to as wallet state advances. It is
// injected programmatically by the server, never deserialized, and is a
// top-level field (not under SwapWallet) because the subserver is
// registered by build tag regardless of whether the operator supplied a
// [swapwallet] config section. A nil value disables projection.
ActivityStore ActivityStore `mapstructure:"-"`
// MaxOperatorFeeSat caps the per-round operator fee the client
// is willing to pay under the #270 seal-time fee handshake.
// Every JoinRoundQuote is compared against this value before
// the client accepts; a quote above the cap is rejected with
// JoinRoundRejectOutbox and the FSM transitions to
// ClientFailedState without signing. A zero / negative value
// fails closed (every quote rejected) so an unset cap cannot
// silently disable the protection. Defaults to 1_000_000 sats
// (0.01 BTC), generous enough for regtest/testnet but well
// below any reasonable mainnet abuse threshold.
MaxOperatorFeeSat int64 `mapstructure:"maxoperatorfeesat"`
// AutoRefreshFeeFloorSat is the optional fixed allowance in the
// automatic maintenance budget curve. The effective budget is the
// larger of this floor and AutoRefreshFeeRatePPM applied to
// automatically refreshed value, always clamped by MaxOperatorFeeSat.
// Zero disables the floor.
AutoRefreshFeeFloorSat int64 `mapstructure:"autorefreshfeefloorsat"`
// AutoRefreshFeeRatePPM is the optional proportional allowance in the
// automatic maintenance budget curve. Zero disables this component.
// When both components are zero, only MaxOperatorFeeSat applies.
AutoRefreshFeeRatePPM uint32 `mapstructure:"autorefreshfeerateppm"`
// OOR configures off-band receive/send actor behavior.
OOR *OORConfig `mapstructure:"oor"`
// FeeEstimation configures optional external chain fee providers for
// the lnd wallet backend (e.g. mempool.space). Disabled by default.
FeeEstimation *FeeEstimationConfig `mapstructure:"feeestimation"`
// EagerRoundJoin makes the wallet actor drive round-joining
// without waiting for a follow-up Board / LeaveVTXOs RPC. With
// the flag on, every freshly confirmed boarding UTXO runs the
// existing Board path inline, and cooperative-leave intents are
// forwarded with TriggerRegistration=true so the round FSM
// leaves PendingRoundAssembly immediately. Off keeps the
// batched semantics that operator-driven hosts rely on
// (wavecli, server deployments); wallet-shaped SDK hosts
// (sdk/wavewalletdk) get the eager behavior by default so
// user-visible "deposit" and "exit" actions translate into a
// full round join end-to-end.
//
// DefaultConfig seeds this from defaultEagerRoundJoin(), which
// is build-tag-aware: false on the standalone non-wavewalletrpc
// build and true when waved is compiled with the wavewalletrpc
// build tag (both the standalone cmd/waved binary and the
// sdk/wavewalletdk embedded path).
EagerRoundJoin bool `mapstructure:"eagerroundjoin"`
// DB groups the per-backend database tuning knobs under the db.sqlite.*
// and db.postgres.* namespaces. A value type so a zero-value Config can
// never carry a nil sub-config into the start path.
DB DBConfig `mapstructure:"db"`
// Pprof configures the optional net/http/pprof debug server. It is
// disabled by default and must be explicitly opted into via a
// non-empty listen address. A value type so a zero-value Config can
// never carry a nil pprof config into the start path.
Pprof PprofConfig `mapstructure:"pprof"`
// Metrics configures the optional Prometheus /metrics HTTP server.
// It is disabled by default and must be explicitly opted into via a
// non-empty listen address. A value type so a zero-value Config can
// never carry a nil metrics config into the start path.
Metrics metrics.ServerConfig `mapstructure:"metrics"`
}
// DBConfig groups the per-backend database tuning knobs. Only the SQLite
// knobs are wired today; the daemon always opens SQLite, so the Postgres
// namespace is reserved for a future Postgres-tuning change.
type DBConfig struct {
// Sqlite holds the SQLite-backend durability knobs, exposed on the
// daemon under the db.sqlite.* namespace.
Sqlite DBSqliteConfig `mapstructure:"sqlite"`
// Postgres is reserved for future Postgres-backend tuning knobs. It is
// intentionally empty today: the daemon always opens SQLite, and
// Postgres durability tuning is deferred to a separate change.
Postgres DBPostgresConfig `mapstructure:"postgres"`
}
// DBSqliteConfig holds the SQLite-backend durability knobs exposed on the
// daemon under the db.sqlite.* namespace.
type DBSqliteConfig struct {
// Synchronous selects the SQLite synchronous (commit durability)
// level. One of "full", "normal", or "off"; an empty value resolves to
// the safe default ("normal"). Under WAL mode "normal" omits the
// per-commit WAL fsync of "full" for substantially higher write
// throughput, replaying any tail dropped on power loss via the
// idempotent persistence stack. See db.SqliteConfig.Synchronous.
Synchronous string `mapstructure:"synchronous"`
// NoFullfsync disables the SQLite fullfsync pragma. The pragma only
// matters on macOS, where it makes flushes wait on a full hardware
// cache flush; with the default synchronous=normal level it governs
// the WAL checkpoint sync, which recurs continuously under sustained
// write load. Write-heavy macOS deployments that accept the weaker
// flush guarantee can disable it for substantially higher throughput.
// No effect on other platforms. See db.SqliteConfig.NoFullfsync.
NoFullfsync bool `mapstructure:"nofullfsync"`
}
// DBPostgresConfig is reserved for future Postgres-backend tuning knobs. It
// is intentionally empty: the daemon always opens SQLite, and Postgres
// durability tuning is deferred to a separate change.
type DBPostgresConfig struct{}
// RPCServiceRegistrar registers one optional daemon gRPC subserver on the
// daemon's existing listener.
//
// Registrars are invoked after the core DaemonService is registered but before
// the server begins accepting requests. A registrar may return a cleanup
// function for any resources it owns, such as background workers, stores, or
// upstream gRPC connections; that cleanup is called during daemon shutdown.
type RPCServiceRegistrar func(
ctx context.Context, grpcServer *grpc.Server, rpcServer *RPCServer,
cfg *Config,
) (func(), error)
// RPCGatewayRegistrar registers one optional daemon HTTP/JSON subserver on
// the daemon gateway.
type RPCGatewayRegistrar func(
ctx context.Context, mux *runtime.ServeMux, endpoint string,
opts []grpc.DialOption, rpcServer *RPCServer, cfg *Config,
) error
// WalletReadyHook runs once after the daemon wallet is unlocked and all
// wallet-dependent daemon services have started.
type WalletReadyHook func(ctx context.Context) error
// UnrollConfig configures the unilateral-exit subsystem.
type UnrollConfig struct {
// BumpAfterBlocks is the number of blocks after which unroll
// will attempt a fee-bump rebroadcast. Zero uses the default
// of 6.
BumpAfterBlocks int32 `mapstructure:"bumpafterblocks"`
// MaxFeeRateSatPerVByte caps fee estimates to prevent runaway
// fees. Zero uses the default of 100 sat/vB.
MaxFeeRateSatPerVByte int64 `mapstructure:"maxfeeratesatpervbyte"`
}
// FeeEstimationConfig groups optional external chain fee providers used by the
// lnd wallet backend's fee estimator. It is namespaced separately from the
// wallet config so its mempool.space provider is not confused with the
// Esplora/mempool.space chain backend that powers the lwwallet.
type FeeEstimationConfig struct {
// MempoolSpace configures the optional mempool.space fee provider.
MempoolSpace *MempoolSpaceFeeConfig `mapstructure:"mempoolspace"`
}
// MempoolSpaceFeeConfig configures the optional mempool.space fee provider.
// When enabled, the lnd chain backend composes a minimum-selecting fee
// estimator over the local WalletKit estimator and a mempool.space estimator,
// choosing the lower of the two live estimates.
type MempoolSpaceFeeConfig struct {
// Enabled turns on the mempool.space fee provider. It applies only to
// the lnd wallet backend; the lwwallet and btcwallet backends own their
// own fee sources.
Enabled bool `mapstructure:"enabled"`
// URL optionally overrides the network-default mempool.space
// recommended-fee endpoint. It must be an absolute https URL (plaintext
// http is rejected except for a loopback host). When empty, the
// network-default endpoint is used.
URL string `mapstructure:"url"`
}
// MempoolSpaceFeeEnabled reports whether the mempool.space fee provider is
// enabled. It is nil-safe so callers do not need to probe the nested config.
func (c *Config) MempoolSpaceFeeEnabled() bool {
return c.FeeEstimation != nil &&
c.FeeEstimation.MempoolSpace != nil &&
c.FeeEstimation.MempoolSpace.Enabled
}
// MempoolSpaceFeeURL returns the configured mempool.space endpoint override, or
// the empty string when none is set (the network default is then used).
func (c *Config) MempoolSpaceFeeURL() string {
if c.FeeEstimation == nil || c.FeeEstimation.MempoolSpace == nil {
return ""
}
return c.FeeEstimation.MempoolSpace.URL
}
// OORConfig configures off-band transfer actor behavior.
type OORConfig struct {
// Limits configures advanced incoming OOR receive safety caps.
Limits *OORLimitsConfig `mapstructure:"limits"`
// MaxTransientSubmitRetry bounds the cumulative wall-clock time the OOR
// FSM keeps re-driving a transient submit rejection
// (OOR_REJECT_INPUT_NOT_SPENDABLE or OOR_REJECT_USER_BALANCE) while
// awaiting submit acceptance before failing the session terminally. A
// zero value selects defaultMaxTransientSubmitRetry; a negative value
// is rejected by Config.Validate. The config key is shortened to
// "maxsubmitretry" so the tagged field line stays within 80 columns.
MaxTransientSubmitRetry time.Duration `mapstructure:"maxsubmitretry"`
}
// defaultMaxTransientSubmitRetry is the default cap on how long waved keeps
// re-driving a transient OOR submit rejection before giving up. It must outlast
// a several-block operator confirmation catch-up at the
// oorTransientRejectRetryDelay (15s) retry cadence: ~6 mainnet blocks at
// ~10 min/block is ~60 min, so a normal INPUT_NOT_SPENDABLE (the operator's
// chain view simply lagging a block this client already saw) always clears well
// within the window, while a genuinely-stuck input or a never-draining
// USER_BALANCE recipient gives up after the cap instead of retrying forever.
const defaultMaxTransientSubmitRetry = time.Hour
// OORLimitsConfig configures advanced incoming OOR receive safety caps.
type OORLimitsConfig struct {
// MaxCheckpoints caps checkpoint transactions allowed in one incoming
// OOR transfer.
MaxCheckpoints uint32 `mapstructure:"maxcheckpoints"`
// MaxVTXOMatches caps VTXOs returned by one indexer lookup during
// incoming OOR receive.
MaxVTXOMatches uint32 `mapstructure:"maxvtxomatches"`
// MaxMailboxItems caps items decoded from one stored mailbox message.
MaxMailboxItems uint32 `mapstructure:"maxmailboxitems"`
// MaxMailboxScriptBytes caps address-script bytes decoded from one
// stored mailbox message.
MaxMailboxScriptBytes uint32 `mapstructure:"maxmailboxscriptbytes"`
}
// minOORMailboxScriptBytes is the smallest standard script cap accepted by
// daemon config validation. It covers a v1 P2TR output script.
const minOORMailboxScriptBytes uint32 = 34
// defaultOORConfig returns daemon defaults for OOR actor settings.
func defaultOORConfig() *OORConfig {
limits := oor.DefaultReceiveLimits()
return &OORConfig{
Limits: &OORLimitsConfig{
MaxCheckpoints: limits.MaxCheckpoints,
MaxVTXOMatches: limits.MaxVTXOMatches,
MaxMailboxItems: limits.MaxMailboxItems,
MaxMailboxScriptBytes: limits.MaxMailboxScriptBytes,
},
MaxTransientSubmitRetry: defaultMaxTransientSubmitRetry,
}
}
// OORMaxTransientSubmitRetry returns the cumulative retry-window cap for
// transient OOR submit rejections, falling back to the default when the OOR
// config is absent or left unset.
func (c *Config) OORMaxTransientSubmitRetry() time.Duration {
if c == nil || c.OOR == nil || c.OOR.MaxTransientSubmitRetry <= 0 {
return defaultMaxTransientSubmitRetry
}
return c.OOR.MaxTransientSubmitRetry
}
// OORReceiveLimits returns the incoming OOR receive limits configured for this
// daemon.
func (c *Config) OORReceiveLimits() oor.ReceiveLimits {
if c == nil || c.OOR == nil || c.OOR.Limits == nil {
return oor.DefaultReceiveLimits()
}
return oor.ReceiveLimits{
MaxCheckpoints: c.OOR.Limits.MaxCheckpoints,
MaxVTXOMatches: c.OOR.Limits.MaxVTXOMatches,
MaxMailboxItems: c.OOR.Limits.MaxMailboxItems,
MaxMailboxScriptBytes: c.OOR.Limits.MaxMailboxScriptBytes,
}
}
// SwapConfig configures the optional daemon-owned swap executor.
//
// The struct is present in all builds so configuration files can be stable, but
// the fields are only consumed when the daemon is compiled with swapruntime and
// registers SwapClientService.
type SwapConfig struct {
// ServerAddress is the swapdk-server endpoint used by the daemon
// executor. Its meaning follows ServerTransport: host:port for gRPC,
// or an HTTP gateway base URL for REST. Empty selects the configured
// network+transport default.
ServerAddress string `mapstructure:"serveraddress"`
// ServerTransport selects the daemon-owned swapdk-server transport.
// Empty values default to gRPC.
ServerTransport string `mapstructure:"servertransport"`
// ServerTLSCertPath is an optional TLS certificate path for the
// swapdk-server connection. When set, the daemon uses the certificate
// instead of system roots or insecure local credentials.
ServerTLSCertPath string `mapstructure:"servertlscertpath"`
// ServerInsecure disables TLS for the swapdk-server connection. This
// should only be used for explicit regtest/dev deployments.
ServerInsecure bool `mapstructure:"serverinsecure"`
// DatabaseFileName is the daemon-owned swap SQLite database path. When
// empty, the daemon stores swaps under NetworkDir()/swaps.db so a
// network DB reset clears persisted swap activity with the main daemon
// DB.
DatabaseFileName string `mapstructure:"databasefilename"`
// VHTLCRecovery controls when the daemon-owned swap runtime escalates
// an already-armed vHTLC recovery row from cooperative retry into
// on-chain unroll.
VHTLCRecovery SwapVHTLCRecoveryConfig `mapstructure:"vhtlcrecovery"`
// Credit configures the daemon-owned credit subsystem, chiefly the
// wallet-owned auto-redeem policy.
Credit CreditConfig `mapstructure:"credit"`
// SuppressResume disables swapclientserver's own synchronous
// resume-on-startup sweep so a higher layer (wavewalletrpc subserver)
// can own the unified resume policy. Default false preserves identical
// behavior for swapruntime-only builds: the swap subserver continues to
// resume its pending sessions before Register returns. The flag is set
// programmatically by the wavewalletrpc registrar; it is not loaded
// from config files.
SuppressResume bool `mapstructure:"-"`
// Backend is populated by swapclientserver.Register after the swap
// subserver is fully wired. Higher layers (the wavewalletrpc subserver)
// read this handle to drive in-Go calls into the swap runtime without
// going through the gRPC stub. The field is set programmatically by
// the registrar; it is never loaded from config files.
Backend SwapBackend `mapstructure:"-"`
// CreditServer and CreditDaemon are populated by
// swapclientserver.Register (only under the swapruntime build tag) so
// the daemon can construct the credit durable-actor subsystem with the
// swap-server credit surface and the wallet/daemon surface. Both are
// nil in builds without the swap runtime, in which case the credit
// subsystem is not started. They are set programmatically by the
// registrar; never loaded from config files.
CreditServer credit.CreditServer `mapstructure:"-"`
CreditDaemon credit.CreditDaemon `mapstructure:"-"`
// CreditRegistry is a lazy service-key reference to the credit registry
// actor, published by the daemon before the swap registrars run so the
// wavewalletrpc subserver can route credit-backed Send/Recv through the
// credit subsystem. It resolves at Tell/Ask time, after the registry
// has registered under the credit service key. Nil until the daemon
// publishes it; set programmatically, never loaded from config files.
CreditRegistry actor.ActorRef[credit.CreditMsg,
credit.CreditResp] `mapstructure:"-"`
// CreditEarmarkSetter wires the wallet's credit-earmark provider into
// the auto-redeem policy. The daemon populates it when it builds the
// credit registry; the wavewalletrpc subserver calls it once its
// prepared-send store exists, so the sweep never redeems credits a
// pending credit-backed send is about to spend. Nil in builds without
// the credit subsystem; set programmatically, never from config files.
CreditEarmarkSetter func(credit.EarmarkFunc) `mapstructure:"-"`
}
// CreditConfig configures the daemon-owned credit subsystem.
type CreditConfig struct {
// AutoRedeemDisabled turns off the wallet-owned auto-redeem that
// materializes idle available credits back into a vTXO. Auto-redeem is
// on by default; operators who prefer to manage credit redemption
// manually set this to true.
AutoRedeemDisabled bool `mapstructure:"autoredeemdisabled"`
// AutoRedeemMinSat optionally delays redemption above the live operator
// VTXO floor. A settled receive triggers at or above the greater of
// this value and the live floor; zero uses the live floor alone.
AutoRedeemMinSat uint64 `mapstructure:"autoredeemminsat"`
// MaxAwaitingPolls caps how many reconciliation polls a single
// credit-operation awaiting state (top-up funding or credit-pay
// settlement) may take before the operation terminal-fails. It is the
// backstop that stops a credit-backed send from parking forever when
// the server never reports a terminal state (wavelength#880). Zero
// applies credit.DefaultMaxAwaitingPolls: the fail-fast bound cannot be
// disabled from config because an unbounded wait is the hang this
// guards against; operators who need a longer ceiling set an explicit
// larger value.
MaxAwaitingPolls uint32 `mapstructure:"maxawaitingpolls"`
}
// MaxAwaitingPollsOrDefault resolves the awaiting-state poll cap the credit
// registry runs with. A zero value (the default) is coerced to
// credit.DefaultMaxAwaitingPolls so the production daemon never runs with the
// unbounded wait that lets a credit-backed send hang forever
// (wavelength#880); a non-zero value is an explicit operator override.
func (c CreditConfig) MaxAwaitingPollsOrDefault() uint32 {
if c.MaxAwaitingPolls == 0 {
return credit.DefaultMaxAwaitingPolls
}
return c.MaxAwaitingPolls
}
// SwapVHTLCRecoveryConfig controls automatic escalation from cooperative vHTLC
// retry to daemon-owned on-chain recovery. Arming is still immediate and cheap;
// this policy only gates the expensive unroll transition.
type SwapVHTLCRecoveryConfig struct {
// AutoEscalate allows the daemon-owned swap runtime to start on-chain
// recovery without a manual command once the grace/deadline policy says
// cooperative retry is no longer safe or useful.
AutoEscalate bool `mapstructure:"autoescalate"`
// CooperativeFailureGracePeriod is measured from the first cooperative
// vHTLC send failure. While the period is open, the swap runtime keeps
// retrying cooperative settlement unless deadline pressure overrides
// the wait.
CooperativeFailureGracePeriod time.Duration `mapstructure:"cooperativefailuregraceperiod"` //nolint:ll
// MinRecoveryMarginBlocks is the minimum block margin preserved before
// a refund locktime. Receive-side claim recovery may override the grace
// period when the current height plus this margin reaches the refund
// locktime.
MinRecoveryMarginBlocks uint32 `mapstructure:"minrecoverymarginblocks"`
// MaxFeeRateSatPerKW caps the final vHTLC recovery exit-spend fee rate.
MaxFeeRateSatPerKW int32 `mapstructure:"maxfeeratesatperkw"`
}
// WithDefaults fills unset numeric vHTLC recovery fields with production
// defaults while preserving AutoEscalate. This lets operators set
// autoescalate=false without that explicit manual-recovery mode being rewritten
// back to the default automatic policy.
func (c SwapVHTLCRecoveryConfig) WithDefaults() SwapVHTLCRecoveryConfig {
if c.MinRecoveryMarginBlocks == 0 {
c.MinRecoveryMarginBlocks = DefaultSwapRecoveryMinMarginBlocks
}
if c.MaxFeeRateSatPerKW == 0 {
c.MaxFeeRateSatPerKW = DefaultSwapRecoveryMaxFeeRateSatPerKW
}
return c
}
// SwapBackend is the in-Go handle exposed by swapclientserver after Register
// completes. It lets higher-level subservers (such as the wavewalletrpc
// subserver) drive the swap runtime without dialing the daemon's gRPC server
// from inside the same process. The interface is intentionally small and grows
// only as new wallet-layer needs arise.
type SwapBackend interface {
// ResumePending re-arms background workers for every persisted
// pending swap session. It is idempotent: payment hashes already
// owned by an active worker are skipped. Callers invoke it once
// when the active resume policy is allowed to start background
// workers.
ResumePending(ctx context.Context)
}
// ActivityStore is the wavewalletrpc subserver's handle on the canonical
// activity log. *db.ActivityPersistenceStore satisfies it; the projector
// writes through ProjectEntry from the emit sites and the startup backfill,
// and the List read path pages current-state rows through ListEntries. The
// interface keeps the daemon-side store out of the swapwallet build-tag
// boundary and lets tests pass nil.
type ActivityStore interface {
// ProjectEntry advances the activity row to the projected state and
// records the transition, atomically. It returns the event_seq assigned
// to the appended transition, or 0 when the projection was
// change-suppressed (no transition, so nothing to emit).
ProjectEntry(ctx context.Context,
p db.ActivityProjection) (int64, error)
// GetEntry returns the current durable projection for one canonical id.
// The live projector uses it to retain immutable request context in a
// later sparse lifecycle event.
GetEntry(ctx context.Context,
canonicalID string) (sqlc.ActivityEntry, error)
// RemoveEntry deletes a projection and its transition events after the
// history merger proves that the row is internal accounting rather than
// user activity. The underlying accounting ledger is not changed.
RemoveEntry(ctx context.Context, canonicalID string) error
// ListEntries returns up to limit current-state rows newest-first,
// starting after the (cursorCreated, cursorID) keyset. A cursorCreated
// of 0 starts from the newest row.
ListEntries(ctx context.Context, cursorCreated int64, cursorID string,
limit int32) ([]sqlc.ActivityEntry, error)
// ListEntriesByKindStatus returns up to limit rows of the given kind
// and status, paged by canonical_id ascending after cursorID. It backs
// the startup rehydration of the wallet-local pending map, scanning
// only the matching rows rather than decoding the whole feed.
ListEntriesByKindStatus(ctx context.Context, kind, status int64,
cursorID string, limit int32) ([]sqlc.ActivityEntry, error)
// PullEvents returns up to limit append-only transition rows with
// event_seq strictly greater than cursor, oldest-first. It is the
// resumable-subscribe replay primitive: a reconnecting client passes
// the last event_seq it processed and receives everything after it.
PullEvents(ctx context.Context, cursor int64,
limit int32) ([]sqlc.ActivityEvent, error)
// CountByStatus returns the number of current-state rows in the given
// status. It backs the wallet status summary's full-feed pending count,
// which the paginated List path cannot report.
CountByStatus(ctx context.Context, status int64) (int64, error)
}
// SwapWalletConfig configures the optional wavewalletrpc subserver. The struct
// is present in all builds so configuration files stay stable, but the
// fields are only consumed when the daemon is compiled with both the
// wavewalletrpc and swapruntime build tags.
type SwapWalletConfig struct {
// Deadline is the wallet-level timeout applied to every PENDING
// entry. When an entry is older than this duration without
// transitioning to a terminal state, the runtime overlays its
// status as FAILED with failure_reason="timed_out" so the user
// surface never hangs on a stuck swap. Zero means use the
// package default (30 minutes). The wallet deadline lives ABOVE
// the swap FSM's own deadline; it never mutates underlying swap
// state.
Deadline time.Duration `mapstructure:"deadline"`
// DefaultListLimit is the page size used when a List or
// SubscribeWallet snapshot request omits a limit. Zero means use
// the package default (100). The configured value is also clamped
// to MaxListLimit so a misconfiguration cannot silently fan out
// unbounded DB work.
DefaultListLimit uint32 `mapstructure:"defaultlistlimit"`
// MaxListLimit caps the per-call list page size. Larger callers
// are clamped to this maximum. Zero means use the package default
// (1000).
MaxListLimit uint32 `mapstructure:"maxlistlimit"`
// SubscribeBuffer is the per-subscriber channel buffer used by
// SubscribeWallet. A slow consumer drops updates when its buffer
// saturates; it can reconcile via List on reconnect. Zero means
// use the package default (32).
SubscribeBuffer uint32 `mapstructure:"subscribebuffer"`
}
// MailboxEdgeFactory constructs the mailbox edge client used by the
// serverconn runtime. The base client already carries the daemon's configured
// operator RPC auth, so wrappers must delegate to it rather than rebuilding a
// client from the raw connection.
type MailboxEdgeFactory func(
conn grpc.ClientConnInterface, base mailboxpb.MailboxServiceClient,
) mailboxpb.MailboxServiceClient
// LndConfig holds connection parameters for the backing lnd node.
type LndConfig struct {
// Host is the network address of the lnd gRPC interface.
Host string `mapstructure:"host"`
// TLSPath is the path to lnd's TLS certificate file. If empty, the
// default lnd TLS cert location is used.
TLSPath string `mapstructure:"tlspath"`
// MacaroonPath is the full path to the lnd admin macaroon. If empty,
// the default lnd macaroon location for the active network is used.
MacaroonPath string `mapstructure:"macaroonpath"`
// RPCTimeout is the maximum duration for individual RPC calls to
// lnd. If zero, DefaultRPCTimeout is used.
RPCTimeout time.Duration `mapstructure:"rpctimeout"`
// Account is the lnd wallet account this daemon may spend from. It
// bounds coin selection, change, address derivation and signing, but
// not what the daemon can observe: imported boarding scripts live in
// lnd's watch-only account and stay visible either way.
//
// Empty selects lndbackend.DefaultWalletAccount, lnd's built-in
// "default" account, which is where a freshly funded node keeps its
// coins. Set this when several daemons share one lnd node, so that one
// daemon's spending cannot drain another's funds. The named account
// must already exist on the node (lnd's `wallet accounts create`) and
// must be taproot-scoped: every lnd address this daemon derives for
// itself asks for a taproot address, and lnd resolves a custom account
// name within the key scope implied by the requested address type, so
// an account created under any other scope resolves as "not found".
Account string `mapstructure:"account"`
}
// ServerConfig holds connection parameters for the ark operator's mailbox
// edge server.
type ServerConfig struct {
// Host is the ark operator endpoint. Its meaning follows Transport:
// host:port for gRPC, or an HTTP gateway base URL for REST. Empty
// selects the configured network+transport default.
Host string `mapstructure:"host"`
// Transport selects the daemon-owned outbound transport for ArkService
// and MailboxService clients. OOR traffic uses the mailbox edge, so it
// follows this selector as well. Empty values default to gRPC.
Transport string `mapstructure:"transport"`
// TLSCertPath is the path to the operator's TLS certificate for
// verifying the server connection. If empty, the system cert pool
// is used.
TLSCertPath string `mapstructure:"tlscertpath"`
// Insecure disables TLS for the server connection. This should only
// be used in regtest or development environments.
Insecure bool `mapstructure:"insecure"`
// MacaroonPath is the path to the operator macaroon used for
// outbound ArkService and MailboxService requests.
MacaroonPath string `mapstructure:"macaroonpath"`
// MaxTreeNodes caps the number of nodes accepted in a VTXO tree
// received from the server. This prevents memory exhaustion from
// oversized tree payloads. If zero, the default of
// roundpb.DefaultMaxTreeNodes (50,000) is used.
MaxTreeNodes int `mapstructure:"maxtreenodes"`
}
// RPCConfig holds configuration for the daemon's own gRPC server.
type RPCConfig struct {
// ListenAddr is the network address the gRPC server binds to when the
// daemon opens its own TCP listener. Valid RPC configurations either