Skip to content

Commit c7bbe8b

Browse files
Merge pull request #925 from rsksmart/feat_fly2164_cold_wallet_funding_alert
Feat fly2164 cold wallet funding alert
2 parents 723cbfc + ec4a68d commit c7bbe8b

File tree

16 files changed

+406
-32
lines changed

16 files changed

+406
-32
lines changed

docker-compose/docker-compose.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ services:
157157
- BTC_RELEASE_WATCHER_START_BLOCK
158158
- BTC_RELEASE_WATCHER_PAGE_SIZE
159159
- BTC_RELEASE_CHECK_TIMEOUT
160+
- BTC_MIN_TRANSFER_FEE_MULTIPLIER
161+
- RBTC_MIN_TRANSFER_FEE_MULTIPLIER
162+
- COLD_WALLET_FORCE_TRANSFER_AFTER_SECONDS
163+
- HOT_WALLET_LOW_LIQUIDITY_WARNING
164+
- HOT_WALLET_LOW_LIQUIDITY_CRITICAL
160165
ports:
161166
- "8080:8080"
162167
volumes:

docker-compose/local/docker-compose.lps.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ services:
8585
- BTC_RELEASE_WATCHER_START_BLOCK
8686
- BTC_RELEASE_WATCHER_PAGE_SIZE
8787
- BTC_RELEASE_CHECK_TIMEOUT
88+
- BTC_MIN_TRANSFER_FEE_MULTIPLIER
89+
- RBTC_MIN_TRANSFER_FEE_MULTIPLIER
90+
- COLD_WALLET_FORCE_TRANSFER_AFTER_SECONDS
91+
- HOT_WALLET_LOW_LIQUIDITY_WARNING
92+
- HOT_WALLET_LOW_LIQUIDITY_CRITICAL
8893
ports:
8994
- "8080:8080"
9095
volumes:

docker-compose/mainnet/docker-compose.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ services:
101101
- BTC_RELEASE_WATCHER_START_BLOCK
102102
- BTC_RELEASE_WATCHER_PAGE_SIZE
103103
- BTC_RELEASE_CHECK_TIMEOUT
104+
- BTC_MIN_TRANSFER_FEE_MULTIPLIER
105+
- RBTC_MIN_TRANSFER_FEE_MULTIPLIER
106+
- COLD_WALLET_FORCE_TRANSFER_AFTER_SECONDS
107+
- HOT_WALLET_LOW_LIQUIDITY_WARNING
108+
- HOT_WALLET_LOW_LIQUIDITY_CRITICAL
104109
ports:
105110
- "8080:8080"
106111
volumes:

docs/Environment.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ These are the environment variables required by the liquidity provider server (L
7272
| `BTC_MIN_TRANSFER_FEE_MULTIPLIER` | It is a plain number meaning the minimum multiplier for BTC transfers to cold wallet to be considered economical (transfer amount must be >= fee * multiplier) | `5` | No |
7373
| `RBTC_MIN_TRANSFER_FEE_MULTIPLIER` | It is a plain number meaning the minimum multiplier for RBTC transfers to cold wallet to be considered economical (transfer amount must be >= fee * multiplier) | `100` | No |
7474
| `COLD_WALLET_FORCE_TRANSFER_AFTER_SECONDS` | Number of seconds after which excess liquidity will be transferred to cold wallet even if below threshold | `1209600` (2 weeks) | No |
75+
| `HOT_WALLET_LOW_LIQUIDITY_WARNING` | Hot wallet liquidity threshold in whole coins (BTC/RBTC) below which a warning alert is emitted every check cycle | `3` | No |
76+
| `HOT_WALLET_LOW_LIQUIDITY_CRITICAL` | Hot wallet liquidity threshold in whole coins (BTC/RBTC) below which a critical alert is emitted every check cycle. Must be less than `HOT_WALLET_LOW_LIQUIDITY_WARNING` | `1` | No |
7577

7678
## AWS variables
7779
You may notice that in [`sample-config.env`](https://github.com/rsksmart/liquidity-provider-server/blob/master/sample-config.env) there are some environment variables that are related to AWS. These variables are required to use AWS services, however, they are not listed in the table as the AWS SDK has the functionality to load them from multiple sources. For that reason, they are not accessed directly from the code and are not listed in the table above.

internal/adapters/entrypoints/watcher/liquidity_check.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,34 @@ package watcher
22

33
import (
44
"context"
5+
"time"
6+
57
"github.com/rsksmart/liquidity-provider-server/internal/entities/utils"
68
"github.com/rsksmart/liquidity-provider-server/internal/usecases/liquidity_provider"
79
log "github.com/sirupsen/logrus"
8-
"time"
910
)
1011

1112
type LiquidityCheckWatcher struct {
12-
checkLiquidityUseCase *liquidity_provider.CheckLiquidityUseCase
13-
watcherStopChannel chan bool
14-
ticker utils.Ticker
15-
validationTimeout time.Duration
13+
checkLiquidityUseCase *liquidity_provider.CheckLiquidityUseCase
14+
lowLiquidityAlertUseCase *liquidity_provider.LowLiquidityAlertUseCase
15+
watcherStopChannel chan bool
16+
ticker utils.Ticker
17+
validationTimeout time.Duration
1618
}
1719

1820
func NewLiquidityCheckWatcher(
1921
checkLiquidityUseCase *liquidity_provider.CheckLiquidityUseCase,
22+
lowLiquidityAlertUseCase *liquidity_provider.LowLiquidityAlertUseCase,
2023
ticker utils.Ticker,
2124
validationTimeout time.Duration,
2225
) *LiquidityCheckWatcher {
2326
watcherStopChannel := make(chan bool, 1)
2427
return &LiquidityCheckWatcher{
25-
checkLiquidityUseCase: checkLiquidityUseCase,
26-
watcherStopChannel: watcherStopChannel,
27-
ticker: ticker,
28-
validationTimeout: validationTimeout,
28+
checkLiquidityUseCase: checkLiquidityUseCase,
29+
lowLiquidityAlertUseCase: lowLiquidityAlertUseCase,
30+
watcherStopChannel: watcherStopChannel,
31+
ticker: ticker,
32+
validationTimeout: validationTimeout,
2933
}
3034
}
3135

@@ -46,6 +50,9 @@ watcherLoop:
4650
if err := watcher.checkLiquidityUseCase.Run(ctx); err != nil {
4751
log.Error("Error checking liquidity inside watcher: ", err)
4852
}
53+
if err := watcher.lowLiquidityAlertUseCase.Run(ctx); err != nil {
54+
log.Error("Error checking low liquidity inside watcher: ", err)
55+
}
4956
cancel()
5057
case <-watcher.watcherStopChannel:
5158
watcher.ticker.Stop()

internal/adapters/entrypoints/watcher/liquidity_check_test.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package watcher_test
22

33
import (
44
"context"
5+
"sync"
6+
"testing"
7+
"time"
8+
59
"github.com/rsksmart/liquidity-provider-server/internal/adapters/entrypoints/watcher"
610
"github.com/rsksmart/liquidity-provider-server/internal/entities"
711
"github.com/rsksmart/liquidity-provider-server/internal/entities/blockchain"
@@ -12,22 +16,20 @@ import (
1216
"github.com/stretchr/testify/assert"
1317
"github.com/stretchr/testify/mock"
1418
"github.com/stretchr/testify/require"
15-
"sync"
16-
"testing"
17-
"time"
1819
)
1920

2021
func TestLiquidityCheckWatcher_Shutdown(t *testing.T) {
2122
createWatcherShutdownTest(t, func(ticker utils.Ticker) watcher.Watcher {
22-
return watcher.NewLiquidityCheckWatcher(nil, ticker, time.Duration(1))
23+
return watcher.NewLiquidityCheckWatcher(nil, nil, ticker, time.Duration(1))
2324
})
2425
}
2526

2627
func TestNewLiquidityCheckWatcher(t *testing.T) {
2728
ticker := &mocks.TickerMock{}
2829
providerMock := &mocks.ProviderMock{}
29-
useCase := liquidity_provider.NewCheckLiquidityUseCase(providerMock, providerMock, blockchain.RskContracts{}, &mocks.AlertSenderMock{}, test.AnyString)
30-
test.AssertNonZeroValues(t, watcher.NewLiquidityCheckWatcher(useCase, ticker, time.Duration(1)))
30+
checkLiquidityUseCase := liquidity_provider.NewCheckLiquidityUseCase(providerMock, providerMock, blockchain.RskContracts{}, &mocks.AlertSenderMock{}, test.AnyString)
31+
lowLiquidityUseCase := liquidity_provider.NewLowLiquidityAlertUseCase(providerMock, providerMock, &mocks.AlertSenderMock{}, test.AnyString, 3, 1)
32+
test.AssertNonZeroValues(t, watcher.NewLiquidityCheckWatcher(checkLiquidityUseCase, lowLiquidityUseCase, ticker, time.Duration(1)))
3133
}
3234

3335
func TestLiquidityCheckWatcher_Start(t *testing.T) {
@@ -38,10 +40,15 @@ func TestLiquidityCheckWatcher_Start(t *testing.T) {
3840
providerMock := &mocks.ProviderMock{}
3941
providerMock.On("HasPeginLiquidity", mock.Anything, mock.Anything).Return(nil)
4042
providerMock.On("HasPegoutLiquidity", mock.Anything, mock.Anything).Return(nil)
43+
providerMock.On("AvailablePeginLiquidity", mock.Anything).Return(entities.NewWei(0), nil)
44+
providerMock.On("AvailablePegoutLiquidity", mock.Anything).Return(entities.NewWei(0), nil)
4145
bridgeMock := &mocks.BridgeMock{}
4246
bridgeMock.On("GetMinimumLockTxValue").Return(entities.NewWei(5), nil)
43-
useCase := liquidity_provider.NewCheckLiquidityUseCase(providerMock, providerMock, blockchain.RskContracts{Bridge: bridgeMock}, &mocks.AlertSenderMock{}, test.AnyString)
44-
w := watcher.NewLiquidityCheckWatcher(useCase, ticker, time.Duration(1))
47+
alertSenderMock := &mocks.AlertSenderMock{}
48+
alertSenderMock.On("SendAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
49+
checkLiquidityUseCase := liquidity_provider.NewCheckLiquidityUseCase(providerMock, providerMock, blockchain.RskContracts{Bridge: bridgeMock}, alertSenderMock, test.AnyString)
50+
lowLiquidityUseCase := liquidity_provider.NewLowLiquidityAlertUseCase(providerMock, providerMock, alertSenderMock, test.AnyString, 3, 1)
51+
w := watcher.NewLiquidityCheckWatcher(checkLiquidityUseCase, lowLiquidityUseCase, ticker, time.Duration(1))
4552
wg := sync.WaitGroup{}
4653
wg.Add(2)
4754
closeChannel := make(chan bool)
@@ -68,10 +75,15 @@ func TestLiquidityCheckWatcher_Start_ErrorHandling(t *testing.T) {
6875
ticker.EXPECT().C().Return(tickerChannel)
6976
ticker.EXPECT().Stop().Return()
7077
providerMock := &mocks.ProviderMock{}
78+
providerMock.On("AvailablePeginLiquidity", mock.Anything).Return(entities.NewWei(0), nil)
79+
providerMock.On("AvailablePegoutLiquidity", mock.Anything).Return(entities.NewWei(0), nil)
7180
bridgeMock := &mocks.BridgeMock{}
7281
bridgeMock.On("GetMinimumLockTxValue").Return(nil, assert.AnError)
73-
useCase := liquidity_provider.NewCheckLiquidityUseCase(providerMock, providerMock, blockchain.RskContracts{Bridge: bridgeMock}, &mocks.AlertSenderMock{}, test.AnyString)
74-
w := watcher.NewLiquidityCheckWatcher(useCase, ticker, time.Duration(1))
82+
alertSenderMock := &mocks.AlertSenderMock{}
83+
alertSenderMock.On("SendAlert", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
84+
checkLiquidityUseCase := liquidity_provider.NewCheckLiquidityUseCase(providerMock, providerMock, blockchain.RskContracts{Bridge: bridgeMock}, alertSenderMock, test.AnyString)
85+
lowLiquidityUseCase := liquidity_provider.NewLowLiquidityAlertUseCase(providerMock, providerMock, alertSenderMock, test.AnyString, 3, 1)
86+
w := watcher.NewLiquidityCheckWatcher(checkLiquidityUseCase, lowLiquidityUseCase, ticker, time.Duration(1))
7587
wg := sync.WaitGroup{}
7688
wg.Add(2)
7789
defer test.AssertLogContains(t, assert.AnError.Error())
@@ -90,6 +102,6 @@ func TestLiquidityCheckWatcher_Start_ErrorHandling(t *testing.T) {
90102
}
91103

92104
func TestLiquidityCheckWatcher_Prepare(t *testing.T) {
93-
w := watcher.NewLiquidityCheckWatcher(nil, &mocks.TickerMock{}, time.Duration(1))
105+
w := watcher.NewLiquidityCheckWatcher(nil, nil, &mocks.TickerMock{}, time.Duration(1))
94106
require.NoError(t, w.Prepare(context.Background()))
95107
}

internal/configuration/environment/environment.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,20 +190,29 @@ type ManagementEnv struct {
190190
}
191191

192192
type ColdWalletEnv struct {
193-
BtcMinTransferFeeMultiplier uint64 `env:"BTC_MIN_TRANSFER_FEE_MULTIPLIER"`
194-
RbtcMinTransferFeeMultiplier uint64 `env:"RBTC_MIN_TRANSFER_FEE_MULTIPLIER"`
195-
ForceTransferAfterSeconds uint64 `env:"COLD_WALLET_FORCE_TRANSFER_AFTER_SECONDS"`
193+
BtcMinTransferFeeMultiplier uint64 `env:"BTC_MIN_TRANSFER_FEE_MULTIPLIER"`
194+
RbtcMinTransferFeeMultiplier uint64 `env:"RBTC_MIN_TRANSFER_FEE_MULTIPLIER"`
195+
ForceTransferAfterSeconds uint64 `env:"COLD_WALLET_FORCE_TRANSFER_AFTER_SECONDS"`
196+
HotWalletLowLiquidityWarning uint64 `env:"HOT_WALLET_LOW_LIQUIDITY_WARNING"`
197+
HotWalletLowLiquidityCritical uint64 `env:"HOT_WALLET_LOW_LIQUIDITY_CRITICAL"`
196198
}
197199

198200
func (env *ColdWalletEnv) FillWithDefaults() *ColdWalletEnv {
199201
defaults := ColdWalletEnv{
200-
BtcMinTransferFeeMultiplier: 5,
201-
RbtcMinTransferFeeMultiplier: 100,
202-
ForceTransferAfterSeconds: 1209600, // 2 weeks (14 days * 24 hours * 60 minutes * 60 seconds)
202+
BtcMinTransferFeeMultiplier: 5,
203+
RbtcMinTransferFeeMultiplier: 100,
204+
ForceTransferAfterSeconds: 1209600, // 2 weeks (14 days * 24 hours * 60 minutes * 60 seconds)
205+
HotWalletLowLiquidityWarning: 3,
206+
HotWalletLowLiquidityCritical: 1,
203207
}
204208
env.BtcMinTransferFeeMultiplier = utils.FirstNonZero(env.BtcMinTransferFeeMultiplier, defaults.BtcMinTransferFeeMultiplier)
205209
env.RbtcMinTransferFeeMultiplier = utils.FirstNonZero(env.RbtcMinTransferFeeMultiplier, defaults.RbtcMinTransferFeeMultiplier)
206210
env.ForceTransferAfterSeconds = utils.FirstNonZero(env.ForceTransferAfterSeconds, defaults.ForceTransferAfterSeconds)
211+
env.HotWalletLowLiquidityWarning = utils.FirstNonZero(env.HotWalletLowLiquidityWarning, defaults.HotWalletLowLiquidityWarning)
212+
env.HotWalletLowLiquidityCritical = utils.FirstNonZero(env.HotWalletLowLiquidityCritical, defaults.HotWalletLowLiquidityCritical)
213+
if env.HotWalletLowLiquidityCritical >= env.HotWalletLowLiquidityWarning {
214+
log.Fatal("HOT_WALLET_LOW_LIQUIDITY_CRITICAL must be less than HOT_WALLET_LOW_LIQUIDITY_WARNING")
215+
}
207216
return env
208217
}
209218

internal/configuration/registry/usecase.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ type UseCaseRegistry struct {
8282
recommendedPeginUseCase *pegin.RecommendedPeginUseCase
8383
transferExcessToColdWalletUseCase *liquidity_provider.TransferExcessToColdWalletUseCase
8484
checkColdWalletAddressChangeUseCase *liquidity_provider.CheckColdWalletAddressChangeUseCase
85+
lowLiquidityAlertUseCase *liquidity_provider.LowLiquidityAlertUseCase
8586
}
8687

8788
// NewUseCaseRegistry
@@ -95,6 +96,8 @@ func NewUseCaseRegistry(
9596
messaging *Messaging,
9697
mutexes entities.ApplicationMutexes,
9798
) *UseCaseRegistry {
99+
env.ColdWallet.FillWithDefaults()
100+
98101
return &UseCaseRegistry{
99102
summariesUseCase: reports.NewSummariesUseCase(
100103
databaseRegistry.PeginRepository,
@@ -392,6 +395,14 @@ func NewUseCaseRegistry(
392395
messaging.AlertSender,
393396
env.Provider.AlertRecipientEmail,
394397
),
398+
lowLiquidityAlertUseCase: liquidity_provider.NewLowLiquidityAlertUseCase(
399+
lpRegistry.LiquidityProvider,
400+
lpRegistry.LiquidityProvider,
401+
messaging.AlertSender,
402+
env.Provider.AlertRecipientEmail,
403+
env.ColdWallet.HotWalletLowLiquidityWarning,
404+
env.ColdWallet.HotWalletLowLiquidityCritical,
405+
),
395406
checkColdWalletAddressChangeUseCase: liquidity_provider.NewCheckColdWalletAddressChangeUseCase(
396407
databaseRegistry.LiquidityProviderRepository,
397408
lpRegistry.LiquidityProvider,
@@ -579,3 +590,7 @@ func (registry *UseCaseRegistry) TransferExcessToColdWalletUseCase() *liquidity_
579590
func (registry *UseCaseRegistry) CheckColdWalletAddressChangeUseCase() *liquidity_provider.CheckColdWalletAddressChangeUseCase {
580591
return registry.checkColdWalletAddressChangeUseCase
581592
}
593+
594+
func (registry *UseCaseRegistry) LowLiquidityAlertUseCase() *liquidity_provider.LowLiquidityAlertUseCase {
595+
return registry.lowLiquidityAlertUseCase
596+
}

internal/configuration/registry/watcher.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func NewWatcherRegistry(
8989
),
9090
LiquidityCheckWatcher: watcher.NewLiquidityCheckWatcher(
9191
useCaseRegistry.liquidityCheckUseCase,
92+
useCaseRegistry.lowLiquidityAlertUseCase,
9293
tickers.LiquidityCheckTicker,
9394
timeouts.WatcherValidation.Seconds(),
9495
),

internal/entities/alerts/alerts.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import "context"
66
// Changing one of these constants impacts the external alerting system and there is not an automatic way
77
// to identify that error.
88
const (
9-
AlertSubjectPenalization = "LPS has been penalized"
10-
AlertSubjectPeginOutOfLiquidity = "PegIn: Out of liquidity"
11-
AlertSubjectPegoutOutOfLiquidity = "PegOut: Out of liquidity"
12-
AlertSubjectEclipseAttack = "Node Eclipse Detected"
13-
AlertSubjectColdWalletChange = "Cold wallet address changed"
14-
AlertSubjectHotToColdTransfer = "Hot to cold wallet transfer executed"
9+
AlertSubjectPenalization = "LPS has been penalized"
10+
AlertSubjectPeginOutOfLiquidity = "PegIn: Out of liquidity"
11+
AlertSubjectPegoutOutOfLiquidity = "PegOut: Out of liquidity"
12+
AlertSubjectEclipseAttack = "Node Eclipse Detected"
13+
AlertSubjectColdWalletChange = "Cold wallet address changed"
14+
AlertSubjectHotToColdTransfer = "Hot to cold wallet transfer executed"
15+
AlertSubjectHotWalletLowLiquidityWarning = "Hot wallet: Low liquidity, refill recommended"
16+
AlertSubjectHotWalletLowLiquidityCritical = "Hot wallet: Critical low liquidity, refill required"
1517
)
1618

1719
type AlertSender interface {

0 commit comments

Comments
 (0)