Skip to content

Commit 7e52c59

Browse files
committed
fix: switch from time.Time to int64 for state configuration timestamps
- add String method to StateConfiguration to log timestamps in Unix format - persist state configuration after cold wallet transfer
1 parent 40b9c47 commit 7e52c59

File tree

10 files changed

+330
-106
lines changed

10 files changed

+330
-106
lines changed

cmd/application/lps/application.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ func (app *Application) prepareWatchers() ([]watcher.Watcher, error) {
176176
app.watcherRegistry.BtcReleaseWatcher,
177177
app.watcherRegistry.QuoteMetricsWatcher,
178178
app.watcherRegistry.AssetReportWatcher,
179+
app.watcherRegistry.TransferColdWalletWatcher,
180+
app.watcherRegistry.ColdWalletMetricsWatcher,
179181
}
180182

181183
if app.env.Eclipse.Enabled {

internal/adapters/dataproviders/database/mongo/liquidity_provider_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,14 @@ var testCredentials = &entities.Signed[liquidity_provider.HashedCredentials]{
8585
}
8686

8787
var (
88-
lastBtcToColdWalletTransfer = time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
89-
lastRbtcToColdWalletTransfer = time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC)
88+
lastBtcToColdWalletTransferUnix = time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC).Unix()
89+
lastRbtcToColdWalletTransferUnix = time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC).Unix()
9090
)
9191

9292
var testStateConfig = &entities.Signed[liquidity_provider.StateConfiguration]{
9393
Value: liquidity_provider.StateConfiguration{
94-
LastBtcToColdWalletTransfer: &lastBtcToColdWalletTransfer,
95-
LastRbtcToColdWalletTransfer: &lastRbtcToColdWalletTransfer,
94+
LastBtcToColdWalletTransfer: &lastBtcToColdWalletTransferUnix,
95+
LastRbtcToColdWalletTransfer: &lastRbtcToColdWalletTransferUnix,
9696
},
9797
Signature: "state signature",
9898
Hash: "state hash",

internal/adapters/dataproviders/liquidity_provider_test.go

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -724,11 +724,11 @@ func TestLocalLiquidityProvider_StateConfiguration(t *testing.T) {
724724

725725
t.Run("Return signed state configuration from db", func(t *testing.T) {
726726
// Create properly signed config using the test wallet
727-
btcTime := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
728-
rbtcTime := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC)
727+
btcUnix := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC).Unix()
728+
rbtcUnix := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC).Unix()
729729
stateConfigValue := liquidity_provider.StateConfiguration{
730-
LastBtcToColdWalletTransfer: &btcTime,
731-
LastRbtcToColdWalletTransfer: &rbtcTime,
730+
LastBtcToColdWalletTransfer: &btcUnix,
731+
LastRbtcToColdWalletTransfer: &rbtcUnix,
732732
}
733733

734734
signedConfig, err := usecases.SignConfiguration(usecases.InitializeStateConfigurationId, wallet, crypto.Keccak256, stateConfigValue)
@@ -768,19 +768,19 @@ func TestLocalLiquidityProvider_StateConfiguration(t *testing.T) {
768768
})
769769

770770
t.Run("Return empty state configuration when db configuration is tampered", func(t *testing.T) {
771-
btcTime := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
772-
rbtcTime := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC)
771+
btcUnix := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC).Unix()
772+
rbtcUnix := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC).Unix()
773773
stateConfigValue := liquidity_provider.StateConfiguration{
774-
LastBtcToColdWalletTransfer: &btcTime,
775-
LastRbtcToColdWalletTransfer: &rbtcTime,
774+
LastBtcToColdWalletTransfer: &btcUnix,
775+
LastRbtcToColdWalletTransfer: &rbtcUnix,
776776
}
777777

778778
tamperedConfig, err := usecases.SignConfiguration(usecases.InitializeStateConfigurationId, wallet, crypto.Keccak256, stateConfigValue)
779779
require.NoError(t, err)
780780

781781
// Tamper with the data after signing
782-
newTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
783-
tamperedConfig.Value.LastBtcToColdWalletTransfer = &newTime
782+
newUnix := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
783+
tamperedConfig.Value.LastBtcToColdWalletTransfer = &newUnix
784784

785785
lpRepository.On("GetStateConfiguration", test.AnyCtx).Return(&tamperedConfig, nil).Once()
786786
lp := dataproviders.NewLocalLiquidityProvider(nil, nil, lpRepository, blockchain.Rpc{}, wallet, nil, blockchain.RskContracts{})
@@ -793,11 +793,11 @@ func TestLocalLiquidityProvider_StateConfiguration(t *testing.T) {
793793
})
794794

795795
t.Run("Return empty state configuration when db configuration doesn't have valid signature", func(t *testing.T) {
796-
btcTime := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
797-
rbtcTime := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC)
796+
btcUnix := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC).Unix()
797+
rbtcUnix := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC).Unix()
798798
stateConfigValue := liquidity_provider.StateConfiguration{
799-
LastBtcToColdWalletTransfer: &btcTime,
800-
LastRbtcToColdWalletTransfer: &rbtcTime,
799+
LastBtcToColdWalletTransfer: &btcUnix,
800+
LastRbtcToColdWalletTransfer: &rbtcUnix,
801801
}
802802

803803
// Create config with correct hash but wrong signature
@@ -818,11 +818,11 @@ func TestLocalLiquidityProvider_StateConfiguration(t *testing.T) {
818818
})
819819

820820
t.Run("Return empty state configuration when integrity check fails", func(t *testing.T) {
821-
btcTime := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
822-
rbtcTime := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC)
821+
btcUnix := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC).Unix()
822+
rbtcUnix := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC).Unix()
823823
stateConfigValue := liquidity_provider.StateConfiguration{
824-
LastBtcToColdWalletTransfer: &btcTime,
825-
LastRbtcToColdWalletTransfer: &rbtcTime,
824+
LastBtcToColdWalletTransfer: &btcUnix,
825+
LastRbtcToColdWalletTransfer: &rbtcUnix,
826826
}
827827

828828
integrityFailConfig, err := usecases.SignConfiguration(usecases.InitializeStateConfigurationId, wallet, crypto.Keccak256, stateConfigValue)
@@ -843,11 +843,11 @@ func TestLocalLiquidityProvider_StateConfiguration(t *testing.T) {
843843
})
844844

845845
t.Run("Return empty state configuration when there is an unexpected error", func(t *testing.T) {
846-
btcTime := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
847-
rbtcTime := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC)
846+
btcUnix := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC).Unix()
847+
rbtcUnix := time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC).Unix()
848848
stateConfigValue := liquidity_provider.StateConfiguration{
849-
LastBtcToColdWalletTransfer: &btcTime,
850-
LastRbtcToColdWalletTransfer: &rbtcTime,
849+
LastBtcToColdWalletTransfer: &btcUnix,
850+
LastRbtcToColdWalletTransfer: &rbtcUnix,
851851
}
852852

853853
integrityFailConfig, err := usecases.SignConfiguration(usecases.InitializeStateConfigurationId, wallet, crypto.Keccak256, stateConfigValue)

internal/adapters/entrypoints/rest/routes/routes_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ func setupRegistryMock(registryMock *mocks.UseCaseRegistryMock) {
256256
registryMock.EXPECT().DeleteTrustedAccountUseCase().Return(&liquidity_provider.DeleteTrustedAccountUseCase{})
257257
registryMock.EXPECT().RecommendedPegoutUseCase().Return(&pegout.RecommendedPegoutUseCase{})
258258
registryMock.EXPECT().RecommendedPeginUseCase().Return(&pegin.RecommendedPeginUseCase{})
259+
registryMock.EXPECT().TransferExcessToColdWalletUseCase().Return(&liquidity_provider.TransferExcessToColdWalletUseCase{}).Maybe()
259260
}
260261

261262
func assertHasCorsHeaders(t *testing.T, recorder *httptest.ResponseRecorder) {

internal/configuration/registry/usecase.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,8 @@ func NewUseCaseRegistry(
384384
env.ColdWallet.RbtcMinTransferFeeMultiplier,
385385
env.ColdWallet.ForceTransferAfterSeconds,
386386
messaging.EventBus,
387+
rskRegistry.Wallet,
388+
signingHashFunction,
387389
),
388390
}
389391
}

internal/entities/liquidity_provider/configuration.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ package liquidity_provider
33
import (
44
"errors"
55
"fmt"
6-
"slices"
7-
"time"
8-
96
"math/big"
7+
"slices"
8+
"strconv"
109

1110
"github.com/rsksmart/liquidity-provider-server/internal/entities"
1211
"github.com/rsksmart/liquidity-provider-server/internal/entities/utils"
@@ -106,8 +105,21 @@ type HashedCredentials struct {
106105
}
107106

108107
type StateConfiguration struct {
109-
LastBtcToColdWalletTransfer *time.Time `json:"lastBtcToColdWalletTransfer" bson:"last_btc_to_cold_wallet_transfer"`
110-
LastRbtcToColdWalletTransfer *time.Time `json:"lastRbtcToColdWalletTransfer" bson:"last_rbtc_to_cold_wallet_transfer"`
108+
LastBtcToColdWalletTransfer *int64 `json:"lastBtcToColdWalletTransfer" bson:"last_btc_to_cold_wallet_transfer"`
109+
LastRbtcToColdWalletTransfer *int64 `json:"lastRbtcToColdWalletTransfer" bson:"last_rbtc_to_cold_wallet_transfer"`
110+
}
111+
112+
// Implementing this so that we can log the state configuration in a readable format
113+
func (s StateConfiguration) String() string {
114+
btc := "nil"
115+
if s.LastBtcToColdWalletTransfer != nil {
116+
btc = strconv.FormatInt(*s.LastBtcToColdWalletTransfer, 10)
117+
}
118+
rbtc := "nil"
119+
if s.LastRbtcToColdWalletTransfer != nil {
120+
rbtc = strconv.FormatInt(*s.LastRbtcToColdWalletTransfer, 10)
121+
}
122+
return fmt.Sprintf("{LastBtcToColdWalletTransfer:%s LastRbtcToColdWalletTransfer:%s}", btc, rbtc)
111123
}
112124

113125
type ConfigurationType interface {

internal/usecases/liquidity_provider/initialize_state_configuration.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (useCase *InitializeStateConfigurationUseCase) Run(ctx context.Context) err
4040
}
4141

4242
modified := false
43-
now := time.Now()
43+
now := time.Now().UTC().Unix()
4444

4545
// BTC field
4646
if stateConfig.LastBtcToColdWalletTransfer == nil {

internal/usecases/liquidity_provider/initialize_state_configuration_test.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ func TestInitializeStateConfigurationUseCase_Run_StateConfigAlreadyExists(t *tes
2121
hashMock := &mocks.HashMock{}
2222

2323
// Mock existing state config with all fields initialized
24-
existingTime := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
24+
existingUnix := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC).Unix()
2525
existingStateConfig := &entities.Signed[lpEntity.StateConfiguration]{
2626
Value: lpEntity.StateConfiguration{
27-
LastBtcToColdWalletTransfer: &existingTime,
28-
LastRbtcToColdWalletTransfer: &existingTime,
27+
LastBtcToColdWalletTransfer: &existingUnix,
28+
LastRbtcToColdWalletTransfer: &existingUnix,
2929
},
3030
Signature: "existing signature",
3131
Hash: "existing hash",
@@ -59,15 +59,15 @@ func TestInitializeStateConfigurationUseCase_Run_CreateNewStateConfig(t *testing
5959
var capturedConfig entities.Signed[lpEntity.StateConfiguration]
6060
lpRepository.On("UpsertStateConfiguration", test.AnyCtx, mock.MatchedBy(func(config entities.Signed[lpEntity.StateConfiguration]) bool {
6161
capturedConfig = config
62-
now := time.Now()
62+
nowUnix := time.Now().Unix()
6363
if config.Value.LastBtcToColdWalletTransfer == nil || config.Value.LastRbtcToColdWalletTransfer == nil {
6464
return false
6565
}
66-
btcDiff := now.Sub(*config.Value.LastBtcToColdWalletTransfer)
67-
rbtcDiff := now.Sub(*config.Value.LastRbtcToColdWalletTransfer)
66+
btcDiff := nowUnix - *config.Value.LastBtcToColdWalletTransfer
67+
rbtcDiff := nowUnix - *config.Value.LastRbtcToColdWalletTransfer
6868

69-
return btcDiff >= 0 && btcDiff < 5*time.Second &&
70-
rbtcDiff >= 0 && rbtcDiff < 5*time.Second &&
69+
return btcDiff >= 0 && btcDiff < 5 &&
70+
rbtcDiff >= 0 && rbtcDiff < 5 &&
7171
config.Signature != ""
7272
})).Return(nil).Once()
7373

@@ -133,10 +133,10 @@ func TestInitializeStateConfigurationUseCase_Run_PartialInitialization(t *testin
133133
hashMock := &mocks.HashMock{}
134134

135135
// Mock existing state config with only BTC field initialized
136-
existingTime := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)
136+
existingUnix := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC).Unix()
137137
existingStateConfig := &entities.Signed[lpEntity.StateConfiguration]{
138138
Value: lpEntity.StateConfiguration{
139-
LastBtcToColdWalletTransfer: &existingTime,
139+
LastBtcToColdWalletTransfer: &existingUnix,
140140
LastRbtcToColdWalletTransfer: nil,
141141
},
142142
Signature: "old signature",
@@ -152,22 +152,22 @@ func TestInitializeStateConfigurationUseCase_Run_PartialInitialization(t *testin
152152
var capturedConfig entities.Signed[lpEntity.StateConfiguration]
153153
lpRepository.On("UpsertStateConfiguration", test.AnyCtx, mock.MatchedBy(func(config entities.Signed[lpEntity.StateConfiguration]) bool {
154154
capturedConfig = config
155-
now := time.Now()
155+
nowUnix := time.Now().Unix()
156156

157157
// BTC field should remain unchanged
158158
if config.Value.LastBtcToColdWalletTransfer == nil {
159159
return false
160160
}
161-
if !config.Value.LastBtcToColdWalletTransfer.Equal(existingTime) {
161+
if *config.Value.LastBtcToColdWalletTransfer != existingUnix {
162162
return false
163163
}
164164

165165
// RBTC field should be newly initialized
166166
if config.Value.LastRbtcToColdWalletTransfer == nil {
167167
return false
168168
}
169-
rbtcDiff := now.Sub(*config.Value.LastRbtcToColdWalletTransfer)
170-
if rbtcDiff < 0 || rbtcDiff >= 5*time.Second {
169+
rbtcDiff := nowUnix - *config.Value.LastRbtcToColdWalletTransfer
170+
if rbtcDiff < 0 || rbtcDiff >= 5 {
171171
return false
172172
}
173173

@@ -186,7 +186,7 @@ func TestInitializeStateConfigurationUseCase_Run_PartialInitialization(t *testin
186186
// Verify captured config
187187
assert.NotNil(t, capturedConfig.Value.LastBtcToColdWalletTransfer)
188188
assert.NotNil(t, capturedConfig.Value.LastRbtcToColdWalletTransfer)
189-
assert.Equal(t, existingTime, *capturedConfig.Value.LastBtcToColdWalletTransfer, "BTC timestamp should remain unchanged")
189+
assert.Equal(t, existingUnix, *capturedConfig.Value.LastBtcToColdWalletTransfer, "BTC timestamp should remain unchanged")
190190
assert.NotEqual(t, "old signature", capturedConfig.Signature, "New signature should be generated")
191191
assert.Equal(t, "070809", capturedConfig.Signature)
192192
assert.Equal(t, "0a0b0c", capturedConfig.Hash)

internal/usecases/liquidity_provider/transfer_excess_to_cold_wallet.go

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/rsksmart/liquidity-provider-server/internal/entities/liquidity_provider"
1313
"github.com/rsksmart/liquidity-provider-server/internal/entities/utils"
1414
"github.com/rsksmart/liquidity-provider-server/internal/usecases"
15+
log "github.com/sirupsen/logrus"
1516
)
1617

1718
const (
@@ -83,6 +84,8 @@ type TransferExcessToColdWalletUseCase struct {
8384
rbtcMinTransferFeeMultiplier uint64
8485
forceTransferAfterSeconds uint64
8586
eventBus entities.EventBus
87+
signer entities.Signer
88+
hashFunc entities.HashFunction
8689
}
8790

8891
func NewTransferExcessToColdWalletUseCase(
@@ -100,6 +103,8 @@ func NewTransferExcessToColdWalletUseCase(
100103
rbtcMinTransferFeeMultiplier uint64,
101104
forceTransferAfterSeconds uint64,
102105
eventBus entities.EventBus,
106+
signer entities.Signer,
107+
hashFunc entities.HashFunction,
103108
) *TransferExcessToColdWalletUseCase {
104109
return &TransferExcessToColdWalletUseCase{
105110
peginProvider: peginProvider,
@@ -116,6 +121,8 @@ func NewTransferExcessToColdWalletUseCase(
116121
rbtcMinTransferFeeMultiplier: rbtcMinTransferFeeMultiplier,
117122
forceTransferAfterSeconds: forceTransferAfterSeconds,
118123
eventBus: eventBus,
124+
signer: signer,
125+
hashFunc: hashFunc,
119126
}
120127
}
121128

@@ -158,7 +165,31 @@ func (useCase *TransferExcessToColdWalletUseCase) Run(ctx context.Context) (*Tra
158165
return result, usecases.WrapUseCaseError(usecases.TransferExcessToColdWalletId, rskResult.Error)
159166
}
160167

161-
return NewTransferToColdWalletResult(btcResult, rskResult), nil
168+
result := NewTransferToColdWalletResult(btcResult, rskResult)
169+
if btcResult.Status == TransferStatusSuccess || rskResult.Status == TransferStatusSuccess {
170+
useCase.persistStateConfigAfterTransfers(ctx, stateConfig, btcResult, rskResult)
171+
}
172+
return result, nil
173+
}
174+
175+
func (useCase *TransferExcessToColdWalletUseCase) persistStateConfigAfterTransfers(ctx context.Context, stateConfig *liquidity_provider.StateConfiguration, btcResult, rskResult NetworkTransferResult) {
176+
updatedStateConfig := *stateConfig
177+
now := time.Now().UTC().Unix()
178+
if btcResult.Status == TransferStatusSuccess {
179+
updatedStateConfig.LastBtcToColdWalletTransfer = &now
180+
}
181+
if rskResult.Status == TransferStatusSuccess {
182+
updatedStateConfig.LastRbtcToColdWalletTransfer = &now
183+
}
184+
newSigned, err := usecases.SignConfiguration(usecases.TransferExcessToColdWalletId, useCase.signer, useCase.hashFunc, updatedStateConfig)
185+
if err != nil {
186+
log.Errorf("TransferExcessToColdWallet: failed to sign state configuration: %v", err)
187+
return
188+
}
189+
if err := useCase.lpRepository.UpsertStateConfiguration(ctx, newSigned); err != nil {
190+
log.Errorf("TransferExcessToColdWallet: failed to persist state configuration: %v", err)
191+
return
192+
}
162193
}
163194

164195
func (useCase *TransferExcessToColdWalletUseCase) getAndValidateConfiguration(ctx context.Context) (*liquidity_provider.GeneralConfiguration, *liquidity_provider.StateConfiguration, error) {
@@ -459,7 +490,7 @@ func (useCase *TransferExcessToColdWalletUseCase) calculateExcessWithTimeForcing
459490
target *entities.Wei,
460491
threshold *entities.Wei,
461492
currentLiquidity *entities.Wei,
462-
lastTransferTime *time.Time,
493+
lastTransferUnix *int64,
463494
) (*entities.Wei, bool) {
464495
// First, calculate normal excess (respecting threshold)
465496
excess := useCase.calculateExcess(target, currentLiquidity, threshold)
@@ -468,7 +499,7 @@ func (useCase *TransferExcessToColdWalletUseCase) calculateExcessWithTimeForcing
468499
return excess, false // Transfer due to threshold, not time forcing
469500
}
470501

471-
if useCase.shouldForceTransferDueToTime(lastTransferTime) {
502+
if useCase.shouldForceTransferDueToTime(lastTransferUnix) {
472503
// Calculate excess respecting target
473504
forcedExcess := useCase.calculateExcess(target, currentLiquidity, target)
474505
return forcedExcess, true
@@ -477,8 +508,7 @@ func (useCase *TransferExcessToColdWalletUseCase) calculateExcessWithTimeForcing
477508
return entities.NewWei(0), false
478509
}
479510

480-
func (useCase *TransferExcessToColdWalletUseCase) shouldForceTransferDueToTime(lastTransferTime *time.Time) bool {
481-
timeSinceLastTransfer := time.Since(*lastTransferTime)
482-
timeRequiredToForceTransfer := time.Duration(useCase.forceTransferAfterSeconds) * time.Second
483-
return timeSinceLastTransfer >= timeRequiredToForceTransfer
511+
func (useCase *TransferExcessToColdWalletUseCase) shouldForceTransferDueToTime(lastTransferUnix *int64) bool {
512+
secondsSinceLastTransfer := time.Now().Unix() - *lastTransferUnix
513+
return secondsSinceLastTransfer >= int64(useCase.forceTransferAfterSeconds)
484514
}

0 commit comments

Comments
 (0)