Skip to content

Commit 23edd77

Browse files
Merge pull request #920 from rsksmart/feat/fly2166/cold_wallet_address_change_alert
Feat / FLY-2166/ Add alerting for cold wallet address change
2 parents 0914bd8 + 4f02230 commit 23edd77

File tree

9 files changed

+522
-26
lines changed

9 files changed

+522
-26
lines changed

cmd/application/lps/application.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ func (app *Application) Run(env environment.Environment, logLevel log.Level) {
144144
log.Fatal("Error initializing state configuration: ", err)
145145
}
146146

147+
if err = app.useCaseRegistry.CheckColdWalletAddressChangeUseCase().Run(context.Background()); err != nil {
148+
log.Error("Error checking cold wallet address change: ", err)
149+
}
150+
147151
watchers, err := app.prepareWatchers()
148152
if err != nil {
149153
log.Fatal("Error initializing watchers: ", err)

internal/configuration/registry/usecase.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ type UseCaseRegistry struct {
8181
recommendedPegoutUseCase *pegout.RecommendedPegoutUseCase
8282
recommendedPeginUseCase *pegin.RecommendedPeginUseCase
8383
transferExcessToColdWalletUseCase *liquidity_provider.TransferExcessToColdWalletUseCase
84+
checkColdWalletAddressChangeUseCase *liquidity_provider.CheckColdWalletAddressChangeUseCase
8485
}
8586

8687
// NewUseCaseRegistry
@@ -257,6 +258,7 @@ func NewUseCaseRegistry(
257258
initializeStateConfigurationUseCase: liquidity_provider.NewInitializeStateConfigurationUseCase(
258259
lpRegistry.LiquidityProvider,
259260
databaseRegistry.LiquidityProviderRepository,
261+
lpRegistry.ColdWallet,
260262
rskRegistry.Wallet,
261263
signingHashFunction,
262264
),
@@ -388,6 +390,15 @@ func NewUseCaseRegistry(
388390
rskRegistry.Wallet,
389391
signingHashFunction,
390392
),
393+
checkColdWalletAddressChangeUseCase: liquidity_provider.NewCheckColdWalletAddressChangeUseCase(
394+
databaseRegistry.LiquidityProviderRepository,
395+
lpRegistry.LiquidityProvider,
396+
lpRegistry.ColdWallet,
397+
messaging.AlertSender,
398+
env.Provider.AlertRecipientEmail,
399+
rskRegistry.Wallet,
400+
signingHashFunction,
401+
),
391402
}
392403
}
393404

@@ -562,3 +573,7 @@ func (registry *UseCaseRegistry) RecommendedPeginUseCase() *pegin.RecommendedPeg
562573
func (registry *UseCaseRegistry) TransferExcessToColdWalletUseCase() *liquidity_provider.TransferExcessToColdWalletUseCase {
563574
return registry.transferExcessToColdWalletUseCase
564575
}
576+
577+
func (registry *UseCaseRegistry) CheckColdWalletAddressChangeUseCase() *liquidity_provider.CheckColdWalletAddressChangeUseCase {
578+
return registry.checkColdWalletAddressChangeUseCase
579+
}

internal/entities/alerts/alerts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const (
1010
AlertSubjectPeginOutOfLiquidity = "PegIn: Out of liquidity"
1111
AlertSubjectPegoutOutOfLiquidity = "PegOut: Out of liquidity"
1212
AlertSubjectEclipseAttack = "Node Eclipse Detected"
13+
AlertSubjectColdWalletChange = "Cold wallet address changed"
1314
)
1415

1516
type AlertSender interface {

internal/entities/liquidity_provider/configuration.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,10 @@ type HashedCredentials struct {
106106
}
107107

108108
type StateConfiguration struct {
109-
LastBtcToColdWalletTransfer int64 `json:"lastBtcToColdWalletTransfer" bson:"last_btc_to_cold_wallet_transfer"`
110-
LastRbtcToColdWalletTransfer int64 `json:"lastRbtcToColdWalletTransfer" bson:"last_rbtc_to_cold_wallet_transfer"`
109+
LastBtcToColdWalletTransfer int64 `json:"lastBtcToColdWalletTransfer" bson:"last_btc_to_cold_wallet_transfer"`
110+
LastRbtcToColdWalletTransfer int64 `json:"lastRbtcToColdWalletTransfer" bson:"last_rbtc_to_cold_wallet_transfer"`
111+
BtcColdWalletAddressHash string `json:"btcColdWalletAddressHash" bson:"btc_cold_wallet_address_hash"`
112+
RskColdWalletAddressHash string `json:"rskColdWalletAddressHash" bson:"rsk_cold_wallet_address_hash"`
111113
}
112114

113115
type ConfigurationType interface {

internal/usecases/common.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const (
8181
RecommendedPegoutId UseCaseId = "RecommendedPegout"
8282
RecommendedPeginId UseCaseId = "RecommendedPegin"
8383
TransferExcessToColdWalletId UseCaseId = "TransferExcessToColdWallet"
84+
CheckColdWalletAddressChangeId UseCaseId = "CheckColdWalletAddressChange"
8485
)
8586

8687
var (
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package liquidity_provider
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"fmt"
7+
8+
"github.com/rsksmart/liquidity-provider-server/internal/entities"
9+
"github.com/rsksmart/liquidity-provider-server/internal/entities/alerts"
10+
"github.com/rsksmart/liquidity-provider-server/internal/entities/cold_wallet"
11+
"github.com/rsksmart/liquidity-provider-server/internal/entities/liquidity_provider"
12+
"github.com/rsksmart/liquidity-provider-server/internal/usecases"
13+
log "github.com/sirupsen/logrus"
14+
)
15+
16+
// stateAndCurrentHashes holds the loaded state config and current cold wallet address hashes.
17+
type stateAndCurrentHashes struct {
18+
StateConfig liquidity_provider.StateConfiguration
19+
NewBtcHash string
20+
NewRskHash string
21+
}
22+
23+
type CheckColdWalletAddressChangeUseCase struct {
24+
lpRepository liquidity_provider.LiquidityProviderRepository
25+
generalProvider liquidity_provider.LiquidityProvider
26+
coldWallet cold_wallet.ColdWallet
27+
alertSender alerts.AlertSender
28+
alertRecipient string
29+
signer entities.Signer
30+
hashFunc entities.HashFunction
31+
}
32+
33+
func NewCheckColdWalletAddressChangeUseCase(
34+
lpRepository liquidity_provider.LiquidityProviderRepository,
35+
generalProvider liquidity_provider.LiquidityProvider,
36+
coldWallet cold_wallet.ColdWallet,
37+
alertSender alerts.AlertSender,
38+
alertRecipient string,
39+
signer entities.Signer,
40+
hashFunc entities.HashFunction,
41+
) *CheckColdWalletAddressChangeUseCase {
42+
return &CheckColdWalletAddressChangeUseCase{
43+
lpRepository: lpRepository,
44+
generalProvider: generalProvider,
45+
coldWallet: coldWallet,
46+
alertSender: alertSender,
47+
alertRecipient: alertRecipient,
48+
signer: signer,
49+
hashFunc: hashFunc,
50+
}
51+
}
52+
53+
func (useCase *CheckColdWalletAddressChangeUseCase) hashAddress(address string) string {
54+
return hex.EncodeToString(useCase.hashFunc([]byte(address)))
55+
}
56+
57+
func (useCase *CheckColdWalletAddressChangeUseCase) Run(ctx context.Context) error {
58+
hashes, err := useCase.loadStateAndCurrentHashes(ctx)
59+
if err != nil {
60+
return err
61+
}
62+
63+
btcChanged := hashes.StateConfig.BtcColdWalletAddressHash != hashes.NewBtcHash
64+
rskChanged := hashes.StateConfig.RskColdWalletAddressHash != hashes.NewRskHash
65+
66+
if !btcChanged && !rskChanged {
67+
return nil
68+
}
69+
return useCase.handleAddressChange(ctx, hashes, btcChanged, rskChanged)
70+
}
71+
72+
// loadStateAndCurrentHashes reads the state config through the LiquidityProvider interface
73+
// (which validates signatures) and computes current cold wallet address hashes, or returns an error.
74+
func (useCase *CheckColdWalletAddressChangeUseCase) loadStateAndCurrentHashes(ctx context.Context) (stateAndCurrentHashes, error) {
75+
stateConfig, err := useCase.generalProvider.StateConfiguration(ctx)
76+
if err != nil {
77+
return stateAndCurrentHashes{}, usecases.WrapUseCaseError(usecases.CheckColdWalletAddressChangeId, err)
78+
}
79+
80+
return stateAndCurrentHashes{
81+
StateConfig: stateConfig,
82+
NewBtcHash: useCase.hashAddress(useCase.coldWallet.GetBtcAddress()),
83+
NewRskHash: useCase.hashAddress(useCase.coldWallet.GetRskAddress()),
84+
}, nil
85+
}
86+
87+
func (useCase *CheckColdWalletAddressChangeUseCase) handleAddressChange(ctx context.Context, input stateAndCurrentHashes, btcChanged, rskChanged bool) error {
88+
if err := useCase.sendAddressChangeAlerts(ctx, btcChanged, rskChanged); err != nil {
89+
return err
90+
}
91+
input.StateConfig.BtcColdWalletAddressHash = input.NewBtcHash
92+
input.StateConfig.RskColdWalletAddressHash = input.NewRskHash
93+
return useCase.persistStateConfig(ctx, input.StateConfig)
94+
}
95+
96+
func (useCase *CheckColdWalletAddressChangeUseCase) sendAddressChangeAlerts(ctx context.Context, btcChanged, rskChanged bool) error {
97+
const bodyPrefix = "Cold wallet address change detected at startup"
98+
if btcChanged {
99+
body := bodyPrefix + " | Network: BTC"
100+
log.Info("CheckColdWalletAddressChange: cold wallet address change detected at startup | Network: BTC")
101+
if err := useCase.alertSender.SendAlert(ctx, alerts.AlertSubjectColdWalletChange, body, []string{useCase.alertRecipient}); err != nil {
102+
log.Errorf("CheckColdWalletAddressChange: failed to send alert | Network: BTC | error: %v", err)
103+
return usecases.WrapUseCaseError(usecases.CheckColdWalletAddressChangeId, fmt.Errorf("send alert: %w", err))
104+
}
105+
}
106+
if rskChanged {
107+
body := bodyPrefix + " | Network: RSK"
108+
log.Info("CheckColdWalletAddressChange: cold wallet address change detected at startup | Network: RSK")
109+
if err := useCase.alertSender.SendAlert(ctx, alerts.AlertSubjectColdWalletChange, body, []string{useCase.alertRecipient}); err != nil {
110+
log.Errorf("CheckColdWalletAddressChange: failed to send alert | Network: RSK | error: %v", err)
111+
return usecases.WrapUseCaseError(usecases.CheckColdWalletAddressChangeId, fmt.Errorf("send alert: %w", err))
112+
}
113+
}
114+
return nil
115+
}
116+
117+
func (useCase *CheckColdWalletAddressChangeUseCase) persistStateConfig(ctx context.Context, stateConfig liquidity_provider.StateConfiguration) error {
118+
signedConfig, err := usecases.SignConfiguration(usecases.CheckColdWalletAddressChangeId, useCase.signer, useCase.hashFunc, stateConfig)
119+
if err != nil {
120+
return err
121+
}
122+
if err := useCase.lpRepository.UpsertStateConfiguration(ctx, signedConfig); err != nil {
123+
return usecases.WrapUseCaseError(usecases.CheckColdWalletAddressChangeId, err)
124+
}
125+
return nil
126+
}

0 commit comments

Comments
 (0)