Skip to content

Commit 75c8fe9

Browse files
authored
Merge branch 'master' into accounts-sql-fix
2 parents 4519ef9 + 2b0c2fd commit 75c8fe9

File tree

17 files changed

+456
-154
lines changed

17 files changed

+456
-154
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 2.3.3 (2025-06-20)
2+
3+
### Fixes
4+
5+
- Prevent new v1 contracts from being formed post hardfork activation.
6+
- Prevent revising rejected contracts.
7+
18
## 2.3.2 (2025-06-17)
29

310
### Fixes

api/api.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ type (
5757
LastAnnouncement() (settings.Announcement, error)
5858

5959
UpdateDDNS(force bool) error
60+
}
6061

62+
// Connectivity tests the host's connectivity to the network
63+
Connectivity interface {
6164
TestConnection(context.Context) (explorer.TestResult, bool, error)
6265
}
6366

@@ -170,15 +173,16 @@ type (
170173

171174
sqlite3Store SQLite3Store
172175

173-
syncer Syncer
174-
chain ChainManager
175-
accounts AccountManager
176-
contracts ContractManager
177-
volumes VolumeManager
178-
wallet Wallet
179-
metrics MetricManager
180-
settings Settings
181-
index Index
176+
syncer Syncer
177+
chain ChainManager
178+
accounts AccountManager
179+
contracts ContractManager
180+
volumes VolumeManager
181+
wallet Wallet
182+
metrics MetricManager
183+
settings Settings
184+
connectivity Connectivity
185+
index Index
182186

183187
explorerDisabled bool
184188
explorer *explorer.Explorer

api/endpoints.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,11 @@ func (a *api) handleDELETEWebhooks(jc jape.Context) {
749749
}
750750

751751
func (a *api) handlePUTSystemConnectTest(jc jape.Context) {
752-
result, _, err := a.settings.TestConnection(jc.Request.Context())
752+
if a.connectivity == nil {
753+
jc.Error(errors.New("connectivity test not enabled"), http.StatusBadRequest)
754+
return
755+
}
756+
result, _, err := a.connectivity.TestConnection(jc.Request.Context())
753757
if err != nil {
754758
jc.Error(err, http.StatusInternalServerError)
755759
return

api/options.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ func WithAlerts(al Alerts) ServerOption {
1818
}
1919
}
2020

21+
// WithConnectivity sets the connectivity manager for the API server.
22+
func WithConnectivity(c Connectivity) ServerOption {
23+
return func(a *api) {
24+
a.connectivity = c
25+
}
26+
}
27+
2128
// WithSQLite3Store sets the SQLite3 store for the API server.
2229
//
2330
// This option is not required since it is only used for backups and there

cmd/hostd/run.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"go.sia.tech/hostd/v2/certificates/providers/nomad"
3030
"go.sia.tech/hostd/v2/config"
3131
"go.sia.tech/hostd/v2/explorer"
32+
"go.sia.tech/hostd/v2/explorer/connectivity"
3233
"go.sia.tech/hostd/v2/host/accounts"
3334
"go.sia.tech/hostd/v2/host/contracts"
3435
"go.sia.tech/hostd/v2/host/registry"
@@ -610,13 +611,22 @@ func runRootCmd(ctx context.Context, cfg config.Config, walletKey types.PrivateK
610611

611612
if !cfg.Explorer.Disable {
612613
apiOpts = append(apiOpts, api.WithExplorer(exp))
614+
615+
// enable price pinning
613616
pm, err := pin.NewManager(store, sm, exp, pin.WithLogger(log.Named("pin")))
614617
if err != nil {
615618
return fmt.Errorf("failed to create pin manager: %w", err)
616619
}
617620
defer pm.Close()
618621

619-
apiOpts = append(apiOpts, api.WithPinnedSettings(pm))
622+
// enable connectivity tests
623+
connectivity, err := connectivity.NewManager(hostKey.PublicKey(), sm, exp, connectivity.WithLog(log.Named("connectivity")), connectivity.WithAlerts(am))
624+
if err != nil {
625+
return fmt.Errorf("failed to create connectivity manager: %w", err)
626+
}
627+
defer connectivity.Close()
628+
629+
apiOpts = append(apiOpts, api.WithPinnedSettings(pm), api.WithConnectivity(connectivity))
620630
}
621631
go version.RunVersionCheck(ctx, am, log.Named("version"))
622632
web := http.Server{

explorer/connectivity/connectivity.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package connectivity
2+
3+
import (
4+
"context"
5+
"math"
6+
"time"
7+
8+
"go.sia.tech/core/types"
9+
"go.sia.tech/coreutils/chain"
10+
"go.sia.tech/hostd/v2/alerts"
11+
"go.sia.tech/hostd/v2/explorer"
12+
"go.sia.tech/hostd/v2/internal/threadgroup"
13+
"go.uber.org/zap"
14+
)
15+
16+
type (
17+
// Explorer is an interface to run connectivity tests on an external
18+
// service
19+
Explorer interface {
20+
TestConnection(ctx context.Context, host explorer.Host) (explorer.TestResult, error)
21+
}
22+
23+
// Settings is an interface to access the host's settings, specifically
24+
// the RHP4 net addresses.
25+
Settings interface {
26+
RHP4NetAddresses() []chain.NetAddress
27+
}
28+
29+
// Alerts is an interface to register and dismiss alerts related to
30+
// connectivity issues.
31+
Alerts interface {
32+
Register(alerts.Alert)
33+
DismissCategory(category string)
34+
}
35+
36+
// An Option is a functional option to configure the connectivity
37+
// manager.
38+
Option func(*Manager)
39+
40+
// Manager is a connectivity manager that periodically tests the host's
41+
// connectivity to renters and registers alerts if the host is not
42+
// reachable.
43+
Manager struct {
44+
hostKey types.PublicKey
45+
46+
tg *threadgroup.ThreadGroup
47+
alerts Alerts
48+
settings Settings
49+
explorer Explorer
50+
log *zap.Logger
51+
52+
maxCheckInterval time.Duration
53+
backoffFn func(int) time.Duration
54+
}
55+
)
56+
57+
// Close stops the connectivity manager and cleans up any resources
58+
// it is using.
59+
func (m *Manager) Close() error {
60+
m.tg.Stop()
61+
return nil
62+
}
63+
64+
// WithLog sets the logger for the connectivity manager.
65+
func WithLog(log *zap.Logger) Option {
66+
return func(m *Manager) {
67+
m.log = log
68+
}
69+
}
70+
71+
// WithAlerts sets the alerts manager for the connectivity manager.
72+
func WithAlerts(alerts Alerts) Option {
73+
return func(m *Manager) {
74+
m.alerts = alerts
75+
}
76+
}
77+
78+
// WithMaxCheckInterval sets the maximum interval between connectivity tests.
79+
// If the backoff function returns a longer duration, it will be clamped to
80+
// this value.
81+
//
82+
// The default value is 2 hours.
83+
func WithMaxCheckInterval(d time.Duration) Option {
84+
return func(m *Manager) {
85+
m.maxCheckInterval = d
86+
}
87+
}
88+
89+
// WithBackoff sets the backoff function for the connectivity manager.
90+
// The function should take the number of consecutive failures and return
91+
// the duration to wait before the next connectivity test.
92+
//
93+
// It will be clamped to the maximum check interval set by
94+
// WithMaxCheckInterval.
95+
func WithBackoff(fn func(failures int) time.Duration) Option {
96+
return func(m *Manager) {
97+
m.backoffFn = fn
98+
}
99+
}
100+
101+
// NewManager creates a new connectivity manager that periodically tests the
102+
// host's connectivity to renters.
103+
func NewManager(hostKey types.PublicKey, settings Settings, explorer Explorer, opts ...Option) (*Manager, error) {
104+
m := &Manager{
105+
hostKey: hostKey,
106+
107+
tg: threadgroup.New(),
108+
settings: settings,
109+
explorer: explorer,
110+
log: zap.NewNop(),
111+
112+
maxCheckInterval: 2 * time.Hour,
113+
backoffFn: func(failures int) time.Duration {
114+
return time.Minute * time.Duration(math.Pow(2, float64(failures)))
115+
},
116+
}
117+
for _, opt := range opts {
118+
opt(m)
119+
}
120+
121+
ctx, cancel, err := m.tg.AddContext(context.Background())
122+
if err != nil {
123+
return nil, err
124+
}
125+
go func() {
126+
defer cancel()
127+
128+
var consecutiveFailures int
129+
var nextTestTime time.Duration
130+
for {
131+
select {
132+
case <-time.After(nextTestTime):
133+
result, ok, err := m.TestConnection(context.Background())
134+
if err != nil {
135+
m.log.Error("failed to test connection", zap.Error(err))
136+
} else {
137+
m.log.Debug("connection test result", zap.Bool("ok", ok), zap.Any("result", result))
138+
}
139+
140+
if !ok {
141+
consecutiveFailures++
142+
nextTestTime = min(m.maxCheckInterval, m.backoffFn(consecutiveFailures))
143+
} else {
144+
consecutiveFailures = 0
145+
nextTestTime = m.maxCheckInterval
146+
}
147+
case <-ctx.Done():
148+
return
149+
}
150+
}
151+
}()
152+
153+
return m, nil
154+
}

0 commit comments

Comments
 (0)