Skip to content

Commit e2706bf

Browse files
Support partial updates in UpdateSettings
1 parent dd48374 commit e2706bf

4 files changed

Lines changed: 116 additions & 3 deletions

File tree

internal/core/application/admin.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -623,13 +623,25 @@ func (a *adminService) GetSettings(ctx context.Context) (*domain.Settings, error
623623
}
624624

625625
func (a *adminService) UpdateSettings(ctx context.Context, settings domain.Settings) error {
626+
a.settingsMu.Lock()
627+
defer a.settingsMu.Unlock()
628+
629+
// Merge with current settings so callers can send only the fields they
630+
// want to change (zero-valued fields inherit the existing value).
631+
current, err := a.repoManager.Settings().Get(ctx)
632+
if err != nil {
633+
return fmt.Errorf("failed to get current settings: %w", err)
634+
}
635+
// nil means no settings exist yet (first boot) so then skip merge
636+
// so caller's full settings are used as-is.
637+
if current != nil {
638+
settings = settings.Merge(*current)
639+
}
640+
626641
if err := settings.Validate(); err != nil {
627642
return err
628643
}
629644

630-
a.settingsMu.Lock()
631-
defer a.settingsMu.Unlock()
632-
633645
// Apply to the running service before persisting so that if live-apply
634646
// fails we don't leave invalid settings in the DB.
635647
if a.onSettingsUpdated != nil {

internal/core/application/admin_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,28 @@ func TestAdminService_Settings(t *testing.T) {
132132
assert.Equal(t, int64(7200), got.SettlementMinExpiryGap)
133133
})
134134

135+
t.Run("partial update only changes provided fields", func(t *testing.T) {
136+
// Get current state after the full update above.
137+
before, err := svc.GetSettings(ctx)
138+
require.NoError(t, err)
139+
140+
// Send only BanThreshold — everything else is zero.
141+
partial := domain.Settings{BanThreshold: 99}
142+
err = svc.UpdateSettings(ctx, partial)
143+
require.NoError(t, err)
144+
145+
got, err := svc.GetSettings(ctx)
146+
require.NoError(t, err)
147+
require.NotNil(t, got)
148+
assert.Equal(t, int64(99), got.BanThreshold)
149+
// Other fields unchanged.
150+
assert.Equal(t, before.BanDuration, got.BanDuration)
151+
assert.Equal(t, before.UnilateralExitDelay, got.UnilateralExitDelay)
152+
assert.Equal(t, before.BoardingExitDelay, got.BoardingExitDelay)
153+
assert.Equal(t, before.VtxoTreeExpiry, got.VtxoTreeExpiry)
154+
assert.Equal(t, before.MaxTxWeight, got.MaxTxWeight)
155+
})
156+
135157
t.Run("clear resets settings to defaults", func(t *testing.T) {
136158
err := svc.ClearSettings(ctx)
137159
require.NoError(t, err)

internal/core/domain/settings.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,61 @@ func (s Settings) Validate() error {
128128
return nil
129129
}
130130

131+
// Merge returns a copy of s where any zero-valued field is replaced by the
132+
// corresponding value from other. This allows callers to send only the fields
133+
// they want to change.
134+
func (s Settings) Merge(other Settings) Settings {
135+
if s.BanThreshold == 0 {
136+
s.BanThreshold = other.BanThreshold
137+
}
138+
if s.BanDuration == 0 {
139+
s.BanDuration = other.BanDuration
140+
}
141+
if s.UnilateralExitDelay == 0 {
142+
s.UnilateralExitDelay = other.UnilateralExitDelay
143+
}
144+
if s.PublicUnilateralExitDelay == 0 {
145+
s.PublicUnilateralExitDelay = other.PublicUnilateralExitDelay
146+
}
147+
if s.CheckpointExitDelay == 0 {
148+
s.CheckpointExitDelay = other.CheckpointExitDelay
149+
}
150+
if s.BoardingExitDelay == 0 {
151+
s.BoardingExitDelay = other.BoardingExitDelay
152+
}
153+
if s.VtxoTreeExpiry == 0 {
154+
s.VtxoTreeExpiry = other.VtxoTreeExpiry
155+
}
156+
if s.RoundMinParticipantsCount == 0 {
157+
s.RoundMinParticipantsCount = other.RoundMinParticipantsCount
158+
}
159+
if s.RoundMaxParticipantsCount == 0 {
160+
s.RoundMaxParticipantsCount = other.RoundMaxParticipantsCount
161+
}
162+
if s.VtxoMinAmount == 0 {
163+
s.VtxoMinAmount = other.VtxoMinAmount
164+
}
165+
if s.VtxoMaxAmount == 0 {
166+
s.VtxoMaxAmount = other.VtxoMaxAmount
167+
}
168+
if s.UtxoMinAmount == 0 {
169+
s.UtxoMinAmount = other.UtxoMinAmount
170+
}
171+
if s.UtxoMaxAmount == 0 {
172+
s.UtxoMaxAmount = other.UtxoMaxAmount
173+
}
174+
if s.SettlementMinExpiryGap == 0 {
175+
s.SettlementMinExpiryGap = other.SettlementMinExpiryGap
176+
}
177+
if s.VtxoNoCsvValidationCutoffDate == 0 {
178+
s.VtxoNoCsvValidationCutoffDate = other.VtxoNoCsvValidationCutoffDate
179+
}
180+
if s.MaxTxWeight == 0 {
181+
s.MaxTxWeight = other.MaxTxWeight
182+
}
183+
return s
184+
}
185+
131186
func NewSettings(
132187
banThreshold, banDuration,
133188
unilateralExitDelay, publicUnilateralExitDelay,

internal/core/domain/settings_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,30 @@ func TestSettings_Validate(t *testing.T) {
144144
}
145145
})
146146

147+
t.Run("merge fills zero fields from other", func(t *testing.T) {
148+
current := validSettings()
149+
partial := Settings{BanThreshold: 10}
150+
merged := partial.Merge(current)
151+
152+
assert.Equal(t, int64(10), merged.BanThreshold)
153+
assert.Equal(t, current.BanDuration, merged.BanDuration)
154+
assert.Equal(t, current.UnilateralExitDelay, merged.UnilateralExitDelay)
155+
assert.Equal(t, current.BoardingExitDelay, merged.BoardingExitDelay)
156+
assert.Equal(t, current.VtxoTreeExpiry, merged.VtxoTreeExpiry)
157+
assert.Equal(t, current.MaxTxWeight, merged.MaxTxWeight)
158+
require.NoError(t, merged.Validate())
159+
})
160+
161+
t.Run("merge preserves all set fields", func(t *testing.T) {
162+
current := validSettings()
163+
full := validSettings()
164+
full.BanThreshold = 99
165+
merged := full.Merge(current)
166+
167+
assert.Equal(t, int64(99), merged.BanThreshold)
168+
assert.Equal(t, full.BanDuration, merged.BanDuration)
169+
})
170+
147171
t.Run("min exceeds max", func(t *testing.T) {
148172
t.Run("vtxo", func(t *testing.T) {
149173
s := validSettings()

0 commit comments

Comments
 (0)