Skip to content

Commit d11fbfe

Browse files
authored
feat(zetaclient): add dedicated restricted addresses config file (#3600) (#3614)
* feat(zetaclient): add dedicated config file restricted addresses * use errors * use bg. errors should not be fatal to the main prodcess * changelog
1 parent 6f56f46 commit d11fbfe

File tree

11 files changed

+153
-28
lines changed

11 files changed

+153
-28
lines changed

changelog.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# CHANGELOG
22

3+
## v28.1.0
4+
5+
This is a zetaclient only release.
6+
7+
### Features
8+
* [3600](https://github.com/zeta-chain/node/pull/3600) - add dedicated zetaclient restricted addresses config. This file will be automatically reloaded when it changes without needing to restart zetaclient.
9+
310
## v28.0.0
411

512
v28 is based on the release/v27 branch rather than develop

cmd/zetaclientd/start.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/rs/zerolog/log"
1111
"github.com/spf13/cobra"
1212

13+
"github.com/zeta-chain/node/pkg/bg"
1314
"github.com/zeta-chain/node/pkg/chains"
1415
"github.com/zeta-chain/node/pkg/constant"
1516
"github.com/zeta-chain/node/pkg/graceful"
@@ -58,6 +59,15 @@ func Start(_ *cobra.Command, _ []string) error {
5859
appContext := zctx.New(cfg, passes.relayerKeys(), logger.Std)
5960
ctx := zctx.WithAppContext(context.Background(), appContext)
6061

62+
err = config.LoadRestrictedAddressesConfig(cfg, globalOpts.ZetacoreHome)
63+
if err != nil {
64+
logger.Std.Err(err).Msg("loading restricted addresses config")
65+
} else {
66+
bg.Work(ctx, func(ctx context.Context) error {
67+
return config.WatchRestrictedAddressesConfig(ctx, cfg, globalOpts.ZetacoreHome, logger.Std)
68+
}, bg.WithName("watch_restricted_addresses_config"), bg.WithLogger(logger.Std))
69+
}
70+
6171
telemetry, err := startTelemetry(ctx, cfg)
6272
if err != nil {
6373
return errors.Wrap(err, "unable to start telemetry")
@@ -77,7 +87,7 @@ func Start(_ *cobra.Command, _ []string) error {
7787
return errors.Wrap(err, "unable to update app context")
7888
}
7989

80-
log.Info().Msgf("Config is updated from zetacore\n %s", cfg.StringMasked())
90+
log.Debug().Msgf("Config is updated from zetacore\n %s", cfg.StringMasked())
8191

8292
granteePubKeyBech32, err := resolveObserverPubKeyBech32(cfg, passes.hotkey)
8393
if err != nil {

contrib/localnet/scripts/start-zetaclientd.sh

+3
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,7 @@ if [[ -f /root/zetaclient-config-overlay.json ]]; then
113113
mv /tmp/merged_config.json /root/.zetacored/config/zetaclient_config.json
114114
fi
115115

116+
# ensure restricted addresses config is initialized to avoid log spam
117+
echo "[]" > ~/.zetacored/config/zetaclient_restricted_addresses.json
118+
116119
zetaclientd-supervisor start < /root/password.file

zetaclient/chains/bitcoin/observer/event_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func Test_Category(t *testing.T) {
4646
cfg := config.Config{
4747
ComplianceConfig: sample.ComplianceConfig(),
4848
}
49-
config.LoadComplianceConfig(cfg)
49+
config.SetRestrictedAddressesFromConfig(cfg)
5050

5151
// test cases
5252
tests := []struct {
@@ -312,7 +312,7 @@ func Test_IsEventProcessable(t *testing.T) {
312312
cfg := config.Config{
313313
ComplianceConfig: sample.ComplianceConfig(),
314314
}
315-
config.LoadComplianceConfig(cfg)
315+
config.SetRestrictedAddressesFromConfig(cfg)
316316

317317
// test cases
318318
tests := []struct {

zetaclient/chains/evm/observer/inbound_test.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -296,21 +296,21 @@ func Test_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) {
296296
t.Run("should return nil msg if sender is restricted", func(t *testing.T) {
297297
sender := event.ZetaTxSenderAddress.Hex()
298298
cfg.ComplianceConfig.RestrictedAddresses = []string{sender}
299-
config.LoadComplianceConfig(cfg)
299+
config.SetRestrictedAddressesFromConfig(cfg)
300300
msg := ob.BuildInboundVoteMsgForZetaSentEvent(ob.appContext, event)
301301
require.Nil(t, msg)
302302
})
303303
t.Run("should return nil msg if receiver is restricted", func(t *testing.T) {
304304
receiver := clienttypes.BytesToEthHex(event.DestinationAddress)
305305
cfg.ComplianceConfig.RestrictedAddresses = []string{receiver}
306-
config.LoadComplianceConfig(cfg)
306+
config.SetRestrictedAddressesFromConfig(cfg)
307307
msg := ob.BuildInboundVoteMsgForZetaSentEvent(ob.appContext, event)
308308
require.Nil(t, msg)
309309
})
310310
t.Run("should return nil msg if txOrigin is restricted", func(t *testing.T) {
311311
txOrigin := event.SourceTxOriginAddress.Hex()
312312
cfg.ComplianceConfig.RestrictedAddresses = []string{txOrigin}
313-
config.LoadComplianceConfig(cfg)
313+
config.SetRestrictedAddressesFromConfig(cfg)
314314
msg := ob.BuildInboundVoteMsgForZetaSentEvent(ob.appContext, event)
315315
require.Nil(t, msg)
316316
})
@@ -343,14 +343,14 @@ func Test_BuildInboundVoteMsgForDepositedEvent(t *testing.T) {
343343
})
344344
t.Run("should return nil msg if sender is restricted", func(t *testing.T) {
345345
cfg.ComplianceConfig.RestrictedAddresses = []string{sender.Hex()}
346-
config.LoadComplianceConfig(cfg)
346+
config.SetRestrictedAddressesFromConfig(cfg)
347347
msg := ob.BuildInboundVoteMsgForDepositedEvent(event, sender)
348348
require.Nil(t, msg)
349349
})
350350
t.Run("should return nil msg if receiver is restricted", func(t *testing.T) {
351351
receiver := clienttypes.BytesToEthHex(event.Recipient)
352352
cfg.ComplianceConfig.RestrictedAddresses = []string{receiver}
353-
config.LoadComplianceConfig(cfg)
353+
config.SetRestrictedAddressesFromConfig(cfg)
354354
msg := ob.BuildInboundVoteMsgForDepositedEvent(event, sender)
355355
require.Nil(t, msg)
356356
})
@@ -400,7 +400,7 @@ func Test_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) {
400400
})
401401
t.Run("should return nil msg if sender is restricted", func(t *testing.T) {
402402
cfg.ComplianceConfig.RestrictedAddresses = []string{tx.From}
403-
config.LoadComplianceConfig(cfg)
403+
config.SetRestrictedAddressesFromConfig(cfg)
404404
msg := ob.BuildInboundVoteMsgForTokenSentToTSS(
405405
tx,
406406
ethcommon.HexToAddress(tx.From),
@@ -414,7 +414,7 @@ func Test_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) {
414414
message := hex.EncodeToString(ethcommon.HexToAddress(testutils.OtherAddress1).Bytes())
415415
txCopy.Input = message // use other address as receiver
416416
cfg.ComplianceConfig.RestrictedAddresses = []string{testutils.OtherAddress1}
417-
config.LoadComplianceConfig(cfg)
417+
config.SetRestrictedAddressesFromConfig(cfg)
418418
msg := ob.BuildInboundVoteMsgForTokenSentToTSS(
419419
txCopy,
420420
ethcommon.HexToAddress(txCopy.From),

zetaclient/chains/evm/observer/outbound_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func Test_IsOutboundProcessed(t *testing.T) {
7171
ComplianceConfig: config.ComplianceConfig{},
7272
}
7373
cfg.ComplianceConfig.RestrictedAddresses = []string{cctx.InboundParams.Sender}
74-
config.LoadComplianceConfig(cfg)
74+
config.SetRestrictedAddressesFromConfig(cfg)
7575

7676
// post outbound vote
7777
continueKeysign, err := ob.VoteOutboundIfConfirmed(ctx, cctx)

zetaclient/chains/solana/observer/inbound_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ func Test_BuildInboundVoteMsgFromEvent(t *testing.T) {
153153

154154
// restrict sender
155155
cfg.ComplianceConfig.RestrictedAddresses = []string{sender}
156-
config.LoadComplianceConfig(cfg)
156+
config.SetRestrictedAddressesFromConfig(cfg)
157157

158158
msg := ob.BuildInboundVoteMsgFromEvent(event)
159159
require.Nil(t, msg)
@@ -173,7 +173,7 @@ func Test_IsEventProcessable(t *testing.T) {
173173
cfg := config.Config{
174174
ComplianceConfig: sample.ComplianceConfig(),
175175
}
176-
config.LoadComplianceConfig(cfg)
176+
config.SetRestrictedAddressesFromConfig(cfg)
177177

178178
// test cases
179179
tests := []struct {

zetaclient/compliance/compliance_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,29 @@ func TestCctxRestricted(t *testing.T) {
2323

2424
t.Run("should return true if sender is restricted", func(t *testing.T) {
2525
cfg.ComplianceConfig.RestrictedAddresses = []string{cctx.InboundParams.Sender}
26-
config.LoadComplianceConfig(cfg)
26+
config.SetRestrictedAddressesFromConfig(cfg)
2727
require.True(t, IsCctxRestricted(cctx))
2828
})
2929
t.Run("should return true if receiver is restricted", func(t *testing.T) {
3030
cfg.ComplianceConfig.RestrictedAddresses = []string{cctx.GetCurrentOutboundParam().Receiver}
31-
config.LoadComplianceConfig(cfg)
31+
config.SetRestrictedAddressesFromConfig(cfg)
3232
require.True(t, IsCctxRestricted(cctx))
3333
})
3434
t.Run("should return false if sender and receiver are not restricted", func(t *testing.T) {
3535
// restrict other address
3636
cfg.ComplianceConfig.RestrictedAddresses = []string{"0x27104b8dB4aEdDb054fCed87c346C0758Ff5dFB1"}
37-
config.LoadComplianceConfig(cfg)
37+
config.SetRestrictedAddressesFromConfig(cfg)
3838
require.False(t, IsCctxRestricted(cctx))
3939
})
4040
t.Run("should be able to restrict coinbase address", func(t *testing.T) {
4141
cfg.ComplianceConfig.RestrictedAddresses = []string{ethcommon.Address{}.String()}
42-
config.LoadComplianceConfig(cfg)
42+
config.SetRestrictedAddressesFromConfig(cfg)
4343
cctx.InboundParams.Sender = ethcommon.Address{}.String()
4444
require.True(t, IsCctxRestricted(cctx))
4545
})
4646
t.Run("should ignore empty address", func(t *testing.T) {
4747
cfg.ComplianceConfig.RestrictedAddresses = []string{""}
48-
config.LoadComplianceConfig(cfg)
48+
config.SetRestrictedAddressesFromConfig(cfg)
4949
cctx.InboundParams.Sender = ""
5050
require.False(t, IsCctxRestricted(cctx))
5151
})

zetaclient/config/config.go

+112-8
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22
package config
33

44
import (
5+
"context"
56
"encoding/json"
67
"fmt"
78
"os"
89
"path/filepath"
910
"strings"
11+
"sync"
1012

13+
"github.com/fsnotify/fsnotify"
1114
"github.com/pkg/errors"
15+
"github.com/rs/zerolog"
1216
)
1317

1418
// restrictedAddressBook is a map of restricted addresses
1519
var restrictedAddressBook = map[string]bool{}
20+
var restrictedAddressBookLock sync.RWMutex
21+
22+
const restrictedAddressesPath string = "zetaclient_restricted_addresses.json"
1623

1724
// filename is config file name for ZetaClient
1825
const filename string = "zetaclient_config.json"
@@ -45,9 +52,9 @@ func Save(config *Config, path string) error {
4552
}
4653

4754
// Load loads ZetaClient config from a filepath
48-
func Load(path string) (Config, error) {
55+
func Load(basePath string) (Config, error) {
4956
// retrieve file
50-
file := filepath.Join(path, folder, filename)
57+
file := filepath.Join(basePath, folder, filename)
5158
file, err := filepath.Abs(file)
5259
if err != nil {
5360
return Config{}, err
@@ -76,19 +83,114 @@ func Load(path string) (Config, error) {
7683
// fields sanitization
7784
cfg.TssPath = GetPath(cfg.TssPath)
7885
cfg.PreParamsPath = GetPath(cfg.PreParamsPath)
79-
cfg.ZetaCoreHome = path
80-
81-
// load compliance config
82-
LoadComplianceConfig(cfg)
86+
cfg.ZetaCoreHome = basePath
8387

8488
return cfg, nil
8589
}
8690

87-
// LoadComplianceConfig loads compliance data (restricted addresses) from config
88-
func LoadComplianceConfig(cfg Config) {
91+
// SetRestrictedAddressesFromConfig loads compliance data (restricted addresses) from config.
92+
func SetRestrictedAddressesFromConfig(cfg Config) {
8993
restrictedAddressBook = cfg.GetRestrictedAddressBook()
9094
}
9195

96+
func getRestrictedAddressAbsPath(basePath string) (string, error) {
97+
file := filepath.Join(basePath, folder, restrictedAddressesPath)
98+
file, err := filepath.Abs(file)
99+
if err != nil {
100+
return "", errors.Wrapf(err, "absolute path conversion for %s", file)
101+
}
102+
return file, nil
103+
}
104+
105+
func loadRestrictedAddressesConfig(cfg Config, file string) error {
106+
input, err := os.ReadFile(file) // #nosec G304
107+
if err != nil {
108+
return errors.Wrapf(err, "reading file %s", file)
109+
}
110+
addresses := []string{}
111+
err = json.Unmarshal(input, &addresses)
112+
if err != nil {
113+
return errors.Wrap(err, "invalid json")
114+
}
115+
116+
restrictedAddressBookLock.Lock()
117+
defer restrictedAddressBookLock.Unlock()
118+
119+
// Clear the existing map, load addresses from main config, then load addresses
120+
// from dedicated config file
121+
SetRestrictedAddressesFromConfig(cfg)
122+
for _, addr := range cfg.ComplianceConfig.RestrictedAddresses {
123+
restrictedAddressBook[strings.ToLower(addr)] = true
124+
}
125+
return nil
126+
}
127+
128+
// LoadRestrictedAddressesConfig loads the restricted addresses from the config file
129+
func LoadRestrictedAddressesConfig(cfg Config, basePath string) error {
130+
file, err := getRestrictedAddressAbsPath(basePath)
131+
if err != nil {
132+
return errors.Wrap(err, "getting restricted address path")
133+
}
134+
return loadRestrictedAddressesConfig(cfg, file)
135+
}
136+
137+
// WatchRestrictedAddressesConfig monitors the restricted addresses config file
138+
// for changes and reloads it when necessary
139+
func WatchRestrictedAddressesConfig(ctx context.Context, cfg Config, basePath string, logger zerolog.Logger) error {
140+
file, err := getRestrictedAddressAbsPath(basePath)
141+
if err != nil {
142+
return errors.Wrap(err, "getting restricted address path")
143+
}
144+
watcher, err := fsnotify.NewWatcher()
145+
if err != nil {
146+
return errors.Wrap(err, "creating file watcher")
147+
}
148+
defer watcher.Close()
149+
150+
// Watch the config directory
151+
// If you only watch the file, the watch will be disconnected if/when
152+
// the config is recreated.
153+
dir := filepath.Dir(file)
154+
err = watcher.Add(dir)
155+
if err != nil {
156+
return errors.Wrapf(err, "watching directory %s", dir)
157+
}
158+
159+
for {
160+
select {
161+
case <-ctx.Done():
162+
return nil
163+
164+
case event, ok := <-watcher.Events:
165+
if !ok {
166+
return nil
167+
}
168+
169+
if event.Name != file {
170+
continue
171+
}
172+
173+
// only reload on create or write
174+
if event.Op&(fsnotify.Write|fsnotify.Create) == 0 {
175+
continue
176+
}
177+
178+
logger.Info().Msg("restricted addresses config updated")
179+
180+
err := loadRestrictedAddressesConfig(cfg, file)
181+
if err != nil {
182+
logger.Err(err).Msg("load restricted addresses config")
183+
}
184+
185+
case err, ok := <-watcher.Errors:
186+
if !ok {
187+
return nil
188+
}
189+
return errors.Wrap(err, "watcher error")
190+
}
191+
}
192+
}
193+
92194
// GetPath returns the absolute path of the input path
93195
func GetPath(inputPath string) string {
94196
path := strings.Split(inputPath, "/")
@@ -109,6 +211,8 @@ func GetPath(inputPath string) string {
109211
// ContainRestrictedAddress returns true if any one of the addresses is restricted
110212
// Note: the addrs can contains both ETH and BTC addresses
111213
func ContainRestrictedAddress(addrs ...string) bool {
214+
restrictedAddressBookLock.RLock()
215+
defer restrictedAddressBookLock.RUnlock()
112216
for _, addr := range addrs {
113217
if addr != "" && restrictedAddressBook[strings.ToLower(addr)] {
114218
return true

zetaclient/config/types.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ type TONConfig struct {
6363

6464
// ComplianceConfig is the config for compliance
6565
type ComplianceConfig struct {
66-
LogPath string `json:"LogPath"`
66+
LogPath string `json:"LogPath"`
67+
// Deprecated: use the separate restricted addresses config
6768
RestrictedAddresses []string `json:"RestrictedAddresses" mask:"zero"`
6869
}
6970

zetaclient/types/event_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func Test_Catetory(t *testing.T) {
6363
cfg := config.Config{
6464
ComplianceConfig: sample.ComplianceConfig(),
6565
}
66-
config.LoadComplianceConfig(cfg)
66+
config.SetRestrictedAddressesFromConfig(cfg)
6767

6868
// test cases
6969
tests := []struct {

0 commit comments

Comments
 (0)