diff --git a/changelog.md b/changelog.md index 5322f1a156..bef8ca7a71 100644 --- a/changelog.md +++ b/changelog.md @@ -29,6 +29,12 @@ Also EnableSolanaAddressLookupTable feature flag should be set. "MempoolCongestionThreshold": 3000, ``` +* Public DNS is introduced as an alternative to public IP. It will be used when public IP field is empty. +``` +"PublicIP": "", +"PublicDNS": "my.zetaclient.com", +``` + ### Features * [4274](https://github.com/zeta-chain/node/pull/4274) - multiple evm calls in single tx @@ -42,6 +48,7 @@ Also EnableSolanaAddressLookupTable feature flag should be set. * [4323](https://github.com/zeta-chain/node/pull/4323) - add dry-wrappers to zetacore client * [4328](https://github.com/zeta-chain/node/pull/4328) - missing fields in msg hash for solana outbounds * [4348](https://github.com/zeta-chain/node/pull/4348) - add mode option in ZetaClient configuration +* [4254](https://github.com/zeta-chain/node/pull/4254) - add additional support for zetaclient public DNS name * [4342](https://github.com/zeta-chain/node/pull/4342) - add metrics for monitoring inbound voting through blockscan and trackers ### Tests diff --git a/cmd/zetaclientd/initconfig.go b/cmd/zetaclientd/initconfig.go index 7a1d3ca2c9..00422a0285 100644 --- a/cmd/zetaclientd/initconfig.go +++ b/cmd/zetaclientd/initconfig.go @@ -17,6 +17,7 @@ type initializeConfigOptions struct { peer string publicIP string + publicDNS string logFormat string logSampler bool preParamsPath string @@ -57,10 +58,11 @@ func setupInitializeConfigOptions() { f.Uint8Var((*uint8)(&cfg.mode), "mode", uint8(mode.StandardMode), usageMode) f.StringVar(&cfg.peer, "peer", "", usagePeer) f.StringVar(&cfg.publicIP, "public-ip", "", "public ip address") + f.StringVar(&cfg.publicDNS, "public-dns", "", "public dns name (alternative to public-ip)") f.StringVar(&cfg.preParamsPath, "pre-params", "~/preParams.json", "pre-params file path") f.StringVar(&cfg.chainID, "chain-id", "athens_7001-1", "chain id") f.StringVar(&cfg.zetacoreURL, "zetacore-url", "127.0.0.1", "zetacore node URL") - f.StringVar(&cfg.authzGranter, "operator", "", "granter for the authorization , this should be operator address") + f.StringVar(&cfg.authzGranter, "operator", "", "granter for the authorization, this should be operator address") f.StringVar(&cfg.authzHotkey, "hotkey", "hotkey", usageHotKey) f.Int8Var(&cfg.level, "log-level", int8(zerolog.InfoLevel), usageLogLevel) f.StringVar(&cfg.logFormat, "log-format", "json", "log format (json, test)") @@ -99,6 +101,7 @@ func InitializeConfig(_ *cobra.Command, _ []string) error { configData.ClientMode = opts.mode configData.Peer = initializeConfigOpts.peer configData.PublicIP = opts.publicIP + configData.PublicDNS = opts.publicDNS configData.PreParamsPath = opts.preParamsPath configData.ChainID = opts.chainID configData.ZetaCoreURL = opts.zetacoreURL diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 5b6f411667..b82b01a2cd 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -225,6 +225,7 @@ func startTelemetry(ctx context.Context, cfg config.Config) (*metrics.TelemetryS // 3. Init telemetry server telemetry := metrics.NewTelemetryServer() telemetry.SetIPAddress(cfg.PublicIP) + telemetry.SetDNSName(cfg.PublicDNS) // 4. Add services to the process graceful.AddStarter(ctx, pprofServer) diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index 3254baf1c4..4e88cdf24b 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -106,6 +106,8 @@ services: networks: mynetwork: ipv4_address: 172.20.0.21 + extra_hosts: + - "zetaclient0.com:172.20.0.21" entrypoint: /root/start-zetaclientd.sh environment: - ETHDEV_ENDPOINT=http://eth:8545 @@ -127,6 +129,8 @@ services: networks: mynetwork: ipv4_address: 172.20.0.22 + extra_hosts: + - "zetaclient1.com:172.20.0.22" entrypoint: /root/start-zetaclientd.sh environment: - ETHDEV_ENDPOINT=http://eth:8545 @@ -151,6 +155,8 @@ services: networks: mynetwork: ipv4_address: 172.20.0.23 + extra_hosts: + - "zetaclient2.com:172.20.0.23" entrypoint: /root/start-zetaclientd.sh environment: - HOTKEY_BACKEND=file @@ -173,6 +179,8 @@ services: networks: mynetwork: ipv4_address: 172.20.0.24 + extra_hosts: + - "zetaclient3.com:172.20.0.24" entrypoint: /root/start-zetaclientd.sh environment: - HOTKEY_BACKEND=file diff --git a/contrib/localnet/scripts/start-zetaclientd.sh b/contrib/localnet/scripts/start-zetaclientd.sh index 6092ea722a..a8418e1f22 100755 --- a/contrib/localnet/scripts/start-zetaclientd.sh +++ b/contrib/localnet/scripts/start-zetaclientd.sh @@ -112,7 +112,11 @@ if [[ $HOSTNAME != "zetaclient0" && ! -f ~/.zetacored/config/zetaclient_config.j num=$(echo $HOSTNAME | tr -dc '0-9') node="zetacore$num" fi - zetaclientd init --zetacore-url "$node" --chain-id athens_101-1 --operator "$operatorAddress" --log-format=text --public-ip "$MYIP" --log-level 1 --keyring-backend "$BACKEND" --pre-params "$PREPARAMS_PATH" + + # use alternative DNS name (instead of IP) for other zetaclients (DNS should work as well) + zetaclientd init --zetacore-url "$node" --chain-id athens_101-1 \ + --operator "$operatorAddress" --log-format=text --public-dns "$HOSTNAME.com" \ + --keyring-backend "$BACKEND" --pre-params "$PREPARAMS_PATH" # import relayer private key for zetaclient{$num} import_relayer_key "${num}" diff --git a/go.mod b/go.mod index 78c1ef6a8a..55bc800213 100644 --- a/go.mod +++ b/go.mod @@ -306,7 +306,7 @@ require ( github.com/showa-93/go-mask v0.6.2 github.com/test-go/testify v1.1.4 github.com/tonkeeper/tongo v1.16.4 - github.com/zeta-chain/go-tss v0.6.3 + github.com/zeta-chain/go-tss v0.6.4 github.com/zeta-chain/protocol-contracts-solana/go-idl v0.0.0-20250409230544-d88f214f6f46 go.uber.org/mock v0.5.2 ) @@ -320,6 +320,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect github.com/allegro/bigcache v1.2.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/bytedance/sonic v1.13.2 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect diff --git a/go.sum b/go.sum index a279bf241b..cb5c7f03ea 100644 --- a/go.sum +++ b/go.sum @@ -719,6 +719,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= @@ -1961,8 +1963,8 @@ github.com/zeta-chain/evm v0.0.0-20250821154530-f0addce1e5ac h1:OQYitaQxJqWzyGvX github.com/zeta-chain/evm v0.0.0-20250821154530-f0addce1e5ac/go.mod h1:WfvV0raMAIEEa5MX6kp8VyELvkwBTNw8JNVlAXtKAXA= github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4 h1:FmO3HfVdZ7LzxBUfg6sVzV7ilKElQU2DZm8PxJ7KcYI= github.com/zeta-chain/go-libp2p v0.0.0-20240710192637-567fbaacc2b4/go.mod h1:TBv5NY/CqWYIfUstXO1fDWrt4bDoqgCw79yihqBspg8= -github.com/zeta-chain/go-tss v0.6.3 h1:c/zSEvI0h7MJ+xxu42M58uBdEWrlzfD3NI1kP/FlWBE= -github.com/zeta-chain/go-tss v0.6.3/go.mod h1:xLssidNiAP/fcdcw+cUPA2VS7Td2bnPMS/8x0jnde8w= +github.com/zeta-chain/go-tss v0.6.4 h1:GuzpCyCrxKqjCQCeUH/+MDGc5MJukJ64i/YrS49lx5s= +github.com/zeta-chain/go-tss v0.6.4/go.mod h1:xLssidNiAP/fcdcw+cUPA2VS7Td2bnPMS/8x0jnde8w= github.com/zeta-chain/protocol-contracts v0.0.0-20250909184950-6034c08e5870 h1:G0l52EwS5U7f5eOcQR0kD2tNE2BJtUsKlk9hFtYW7nc= github.com/zeta-chain/protocol-contracts v0.0.0-20250909184950-6034c08e5870/go.mod h1:SjT7QirtJE8stnAe1SlNOanxtfSfijJm3MGJ+Ax7w7w= github.com/zeta-chain/protocol-contracts-solana/go-idl v0.0.0-20250409230544-d88f214f6f46 h1:xbgrVDXWkZaWhMAuD3Q5A13jmRKYQbjZMPBaCkv3cns= diff --git a/zetaclient/config/config.go b/zetaclient/config/config.go index 0f945ef94e..8229477d2f 100644 --- a/zetaclient/config/config.go +++ b/zetaclient/config/config.go @@ -10,6 +10,7 @@ import ( "strings" "sync" + "github.com/asaskevich/govalidator" "github.com/fsnotify/fsnotify" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -29,6 +30,11 @@ const folder string = "config" // Save saves ZetaClient config func Save(config *Config, path string) error { + // validate config + if err := Validate(*config); err != nil { + return err + } + folderPath := filepath.Join(path, folder) err := os.MkdirAll(folderPath, 0o750) if err != nil { @@ -76,18 +82,40 @@ func Load(basePath string) (Config, error) { if cfg.KeyringBackend == KeyringBackendUndefined { cfg.KeyringBackend = KeyringBackendTest } - if cfg.KeyringBackend != KeyringBackendFile && cfg.KeyringBackend != KeyringBackendTest { - return Config{}, fmt.Errorf("invalid keyring backend %s", cfg.KeyringBackend) - } // fields sanitization cfg.TssPath = GetPath(cfg.TssPath) cfg.PreParamsPath = GetPath(cfg.PreParamsPath) cfg.ZetaCoreHome = basePath + // validate config + if err := Validate(cfg); err != nil { + return Config{}, err + } + return cfg, nil } +// Validate performs basic validation on the config fields +// TODO: add more validation for other fields +// https://github.com/zeta-chain/node/issues/4352 +func Validate(cfg Config) error { + // go-tss requires a valid IPv4 address + if cfg.PublicIP != "" && !govalidator.IsIPv4(cfg.PublicIP) { + return fmt.Errorf("invalid public IP %s", cfg.PublicIP) + } + + if cfg.PublicDNS != "" && !govalidator.IsDNSName(cfg.PublicDNS) { + return fmt.Errorf("invalid public DNS %s", cfg.PublicDNS) + } + + if cfg.KeyringBackend != KeyringBackendFile && cfg.KeyringBackend != KeyringBackendTest { + return fmt.Errorf("invalid keyring backend %s", cfg.KeyringBackend) + } + + return nil +} + // SetRestrictedAddressesFromConfig loads compliance data (restricted addresses) from config. func SetRestrictedAddressesFromConfig(cfg Config) { restrictedAddressBook = cfg.GetRestrictedAddressBook() diff --git a/zetaclient/config/config_test.go b/zetaclient/config/config_test.go index 9d594f4916..0fc31cb7c4 100644 --- a/zetaclient/config/config_test.go +++ b/zetaclient/config/config_test.go @@ -13,6 +13,63 @@ import ( "github.com/zeta-chain/node/zetaclient/config" ) +func TestValidate(t *testing.T) { + tests := []struct { + name string + config config.Config + expectError bool + errorMsg string + }{ + { + name: "valid config with default fields", + config: func() config.Config { + cfg := config.New(false) + cfg.KeyringBackend = "test" + return cfg + }(), + }, + { + name: "invalid public IP address", + config: func() config.Config { + cfg := config.New(false) + cfg.PublicIP = "192.168.1" + return cfg + }(), + errorMsg: "invalid public IP 192.168.1", + }, + { + name: "invalid DNS name", + config: func() config.Config { + cfg := config.New(false) + cfg.PublicDNS = "invalid..dns" + return cfg + }(), + errorMsg: "invalid public DNS invalid..dns", + }, + { + name: "invalid keyring backend", + config: func() config.Config { + cfg := config.New(false) + cfg.KeyringBackend = "invalid" + return cfg + }(), + errorMsg: "invalid keyring backend invalid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := config.Validate(tt.config) + + if tt.errorMsg != "" { + require.ErrorContains(t, err, tt.errorMsg) + return + } + require.NoError(t, err, "expected no error, got %v", err) + }) + } +} + func Test_LoadRestrictedAddressesConfig(t *testing.T) { // Create test addresses testAddresses := []string{ diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index 8d11f5542c..9bd6de28db 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -100,6 +100,7 @@ type Config struct { Peer string `json:"Peer"` PublicIP string `json:"PublicIP"` + PublicDNS string `json:"PublicDNS"` LogFormat string `json:"LogFormat"` LogLevel int8 `json:"LogLevel"` LogSampler bool `json:"LogSampler"` diff --git a/zetaclient/metrics/telemetry.go b/zetaclient/metrics/telemetry.go index 603a7e93bf..dad2c29063 100644 --- a/zetaclient/metrics/telemetry.go +++ b/zetaclient/metrics/telemetry.go @@ -30,6 +30,7 @@ type TelemetryServer struct { lastStartTimestamp time.Time status types.Status ipAddress string + dnsName string HotKeyBurnRate *BurnRate connectedPeers []peer.AddrInfo rtt map[peer.ID]int64 @@ -107,6 +108,20 @@ func (t *TelemetryServer) GetIPAddress() string { return t.ipAddress } +// SetDNSName sets p2p dns name +func (t *TelemetryServer) SetDNSName(dns string) { + t.mu.Lock() + defer t.mu.Unlock() + t.dnsName = dns +} + +// GetDNSName gets p2p dns name +func (t *TelemetryServer) GetDNSName() string { + t.mu.Lock() + defer t.mu.Unlock() + return t.dnsName +} + // GetLastStartTimestamp returns last start timestamp func (t *TelemetryServer) GetLastStartTimestamp() time.Time { t.mu.Lock() @@ -180,6 +195,7 @@ func (t *TelemetryServer) Handlers() http.Handler { router.Handle("/lastcoreblock", http.HandlerFunc(t.lastCoreBlockHandler)).Methods(http.MethodGet) router.Handle("/status", http.HandlerFunc(t.statusHandler)).Methods(http.MethodGet) router.Handle("/ip", http.HandlerFunc(t.ipHandler)).Methods(http.MethodGet) + router.Handle("/dns", http.HandlerFunc(t.dnsHandler)).Methods(http.MethodGet) router.Handle("/hotkeyburnrate", http.HandlerFunc(t.hotKeyFeeBurnRate)).Methods(http.MethodGet) router.Handle("/connectedpeers", http.HandlerFunc(t.connectedPeersHandler)).Methods(http.MethodGet) router.Handle("/pingrtt", http.HandlerFunc(t.pingRTTHandler)).Methods(http.MethodGet) @@ -232,6 +248,12 @@ func (t *TelemetryServer) ipHandler(w http.ResponseWriter, _ *http.Request) { fmt.Fprintf(w, "%s", t.GetIPAddress()) } +// dnsHandler returns the dns name +func (t *TelemetryServer) dnsHandler(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "%s", t.GetDNSName()) +} + func (t *TelemetryServer) lastScannedBlockHandler(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") diff --git a/zetaclient/tss/setup.go b/zetaclient/tss/setup.go index bc29c05cf7..3cedae2304 100644 --- a/zetaclient/tss/setup.go +++ b/zetaclient/tss/setup.go @@ -207,8 +207,8 @@ func NewServer( return nil, errors.New("tss password is empty") case privateKey == nil: return nil, errors.New("private key is nil") - case cfg.PublicIP == "": - logger.Warn().Msg("public IP is empty") + case cfg.PublicIP == "" && cfg.PublicDNS == "": + logger.Warn().Msg("no public IP or DNS is provided") } tssPath, err := resolveTSSPath(cfg.TssPath, logger) @@ -225,6 +225,7 @@ func NewServer( PreParamTimeout: 5 * time.Minute, }, ExternalIP: cfg.PublicIP, + ExternalDNS: cfg.PublicDNS, Port: Port, BootstrapPeers: bootstrapPeers, WhitelistedPeers: whitelistPeers,