2
2
package config
3
3
4
4
import (
5
+ "context"
5
6
"encoding/json"
6
7
"fmt"
7
8
"os"
8
9
"path/filepath"
9
10
"strings"
11
+ "sync"
10
12
13
+ "github.com/fsnotify/fsnotify"
11
14
"github.com/pkg/errors"
15
+ "github.com/rs/zerolog"
12
16
)
13
17
14
18
// restrictedAddressBook is a map of restricted addresses
15
19
var restrictedAddressBook = map [string ]bool {}
20
+ var restrictedAddressBookLock sync.RWMutex
21
+
22
+ const restrictedAddressesPath string = "zetaclient_restricted_addresses.json"
16
23
17
24
// filename is config file name for ZetaClient
18
25
const filename string = "zetaclient_config.json"
@@ -45,9 +52,9 @@ func Save(config *Config, path string) error {
45
52
}
46
53
47
54
// Load loads ZetaClient config from a filepath
48
- func Load (path string ) (Config , error ) {
55
+ func Load (basePath string ) (Config , error ) {
49
56
// retrieve file
50
- file := filepath .Join (path , folder , filename )
57
+ file := filepath .Join (basePath , folder , filename )
51
58
file , err := filepath .Abs (file )
52
59
if err != nil {
53
60
return Config {}, err
@@ -76,19 +83,114 @@ func Load(path string) (Config, error) {
76
83
// fields sanitization
77
84
cfg .TssPath = GetPath (cfg .TssPath )
78
85
cfg .PreParamsPath = GetPath (cfg .PreParamsPath )
79
- cfg .ZetaCoreHome = path
80
-
81
- // load compliance config
82
- LoadComplianceConfig (cfg )
86
+ cfg .ZetaCoreHome = basePath
83
87
84
88
return cfg , nil
85
89
}
86
90
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 ) {
89
93
restrictedAddressBook = cfg .GetRestrictedAddressBook ()
90
94
}
91
95
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
+
92
194
// GetPath returns the absolute path of the input path
93
195
func GetPath (inputPath string ) string {
94
196
path := strings .Split (inputPath , "/" )
@@ -109,6 +211,8 @@ func GetPath(inputPath string) string {
109
211
// ContainRestrictedAddress returns true if any one of the addresses is restricted
110
212
// Note: the addrs can contains both ETH and BTC addresses
111
213
func ContainRestrictedAddress (addrs ... string ) bool {
214
+ restrictedAddressBookLock .RLock ()
215
+ defer restrictedAddressBookLock .RUnlock ()
112
216
for _ , addr := range addrs {
113
217
if addr != "" && restrictedAddressBook [strings .ToLower (addr )] {
114
218
return true
0 commit comments