Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support multiple btc chain config #2870

Merged
merged 9 commits into from
Sep 18, 2024
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
* [2644](https://github.com/zeta-chain/node/pull/2644) - add created_timestamp to cctx status
* [2673](https://github.com/zeta-chain/node/pull/2673) - add relayer key importer, encryption and decryption
* [2633](https://github.com/zeta-chain/node/pull/2633) - support for stateful precompiled contracts
* [2788](https://github.com/zeta-chain/node/pull/2788) - add common importable zetacored rpc package
* [2784](https://github.com/zeta-chain/node/pull/2784) - staking precompiled contract
* [2795](https://github.com/zeta-chain/node/pull/2795) - support restricted address in Solana
* [2861](https://github.com/zeta-chain/node/pull/2861) - emit events from staking precompile
* [2870](https://github.com/zeta-chain/node/pull/2870) - support for multiple Bitcoin chains in the zetaclient
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
* [2751](https://github.com/zeta-chain/node/pull/2751) - add RPC status check for Solana chain
* [2788](https://github.com/zeta-chain/node/pull/2788) - add common importable zetacored rpc package
* [2784](https://github.com/zeta-chain/node/pull/2784) - staking precompiled contract
Expand Down
14 changes: 9 additions & 5 deletions cmd/zetaclientd/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,17 +169,21 @@ func debugCmd(_ *cobra.Command, args []string) error {
fmt.Println("CoinType not detected")
}
fmt.Println("CoinType : ", coinType)
} else if chain.IsUTXO() {
} else if chain.IsBitcoin() {
btcObserver := btcobserver.Observer{}
btcObserver.WithZetacoreClient(client)
btcObserver.WithChain(*chainProto)
btcConfig, found := cfg.GetBTCConfig(chainID)
if !found {
return fmt.Errorf("unable to find config for BTC chain %d", chainID)
}
connCfg := &rpcclient.ConnConfig{
Host: cfg.BitcoinConfig.RPCHost,
User: cfg.BitcoinConfig.RPCUsername,
Pass: cfg.BitcoinConfig.RPCPassword,
Host: btcConfig.RPCHost,
User: btcConfig.RPCUsername,
Pass: btcConfig.RPCPassword,
HTTPPostMode: true,
DisableTLS: true,
Params: cfg.BitcoinConfig.RPCParams,
Params: btcConfig.RPCParams,
}

btcClient, err := rpcclient.New(connCfg, nil)
Expand Down
2 changes: 1 addition & 1 deletion cmd/zetaclientd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func start(_ *cobra.Command, _ []string) error {
return err
}

btcChains := appContext.FilterChains(zctx.Chain.IsUTXO)
btcChains := appContext.FilterChains(zctx.Chain.IsBitcoin)
switch {
case len(btcChains) == 0:
return errors.New("no BTC chains found")
Expand Down
34 changes: 13 additions & 21 deletions cmd/zetaclientd/start_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"fmt"
"net"
"net/url"
"strings"
"time"

Expand Down Expand Up @@ -53,41 +52,34 @@ func validatePeer(seedPeer string) error {
return nil
}

// maskCfg sensitive fields are masked, currently only the EVM endpoints and bitcoin credentials,
// maskCfg sensitive fields are masked, currently only the endpoints and bitcoin credentials,
//
// other fields can be added.
func maskCfg(cfg config.Config) string {
// Make a copy of the config
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
maskedCfg := cfg

maskedCfg.BitcoinConfig = config.BTCConfig{
RPCUsername: cfg.BitcoinConfig.RPCUsername,
RPCPassword: cfg.BitcoinConfig.RPCPassword,
RPCHost: cfg.BitcoinConfig.RPCHost,
RPCParams: cfg.BitcoinConfig.RPCParams,
}
// Mask EVM endpoints
maskedCfg.EVMChainConfigs = map[int64]config.EVMConfig{}
for key, val := range cfg.EVMChainConfigs {
maskedCfg.EVMChainConfigs[key] = config.EVMConfig{
Chain: val.Chain,
Endpoint: val.Endpoint,
Endpoint: "",
}
}

// Mask Sensitive data
for _, chain := range maskedCfg.EVMChainConfigs {
if chain.Endpoint == "" {
continue
}
endpointURL, err := url.Parse(chain.Endpoint)
if err != nil {
continue
// Mask BTC endpoints and credentials
maskedCfg.BTCChainConfigs = map[int64]config.BTCConfig{}
for key, val := range cfg.BTCChainConfigs {
maskedCfg.BTCChainConfigs[key] = config.BTCConfig{
RPCParams: val.RPCParams,
}
chain.Endpoint = endpointURL.Hostname()
}
maskedCfg.BitcoinConfig = config.BTCConfig{
RPCParams: cfg.BitcoinConfig.RPCParams,
}

// mask endpoints
maskedCfg.BitcoinConfig.RPCUsername = ""
maskedCfg.BitcoinConfig.RPCPassword = ""
// Mask Solana endpoint
maskedCfg.SolanaConfig.Endpoint = ""

return maskedCfg.String()
Expand Down
15 changes: 11 additions & 4 deletions zetaclient/config/config_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ const (
func New(setDefaults bool) Config {
cfg := Config{
EVMChainConfigs: make(map[int64]EVMConfig),
BitcoinConfig: BTCConfig{},
BTCChainConfigs: make(map[int64]BTCConfig),

mu: &sync.RWMutex{},
}

if setDefaults {
cfg.BitcoinConfig = bitcoinConfigRegnet()
cfg.SolanaConfig = solanaConfigLocalnet()
cfg.EVMChainConfigs = evmChainsConfigs()
cfg.BTCChainConfigs = btcChainsConfigs()
cfg.SolanaConfig = solanaConfigLocalnet()
}

return cfg
Expand All @@ -31,7 +31,7 @@ func New(setDefaults bool) Config {
// bitcoinConfigRegnet contains Bitcoin config for regnet
func bitcoinConfigRegnet() BTCConfig {
return BTCConfig{
RPCUsername: "smoketest", // smoketest is the previous name for E2E test, we keep this name for compatibility between client versions in upgrade test
RPCUsername: "e2etest",
RPCPassword: "123",
RPCHost: "bitcoin:18443",
RPCParams: "regtest",
Expand Down Expand Up @@ -80,3 +80,10 @@ func evmChainsConfigs() map[int64]EVMConfig {
},
}
}

// btcChainsConfigs contains BTC chain configs
func btcChainsConfigs() map[int64]BTCConfig {
return map[int64]BTCConfig{
chains.BitcoinRegtest.ChainId: bitcoinConfigRegnet(),
}
}
30 changes: 22 additions & 8 deletions zetaclient/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,10 @@ type Config struct {

// chain configs
EVMChainConfigs map[int64]EVMConfig `json:"EVMChainConfigs"`
BitcoinConfig BTCConfig `json:"BitcoinConfig"`
SolanaConfig SolanaConfig `json:"SolanaConfig"`
BTCChainConfigs map[int64]BTCConfig `json:"BTCChainConfigs"`
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
// Deprecated: the 'BitcoinConfig' will be removed once the 'BTCChainConfigs' is fully adopted
BitcoinConfig BTCConfig `json:"BitcoinConfig"`
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
SolanaConfig SolanaConfig `json:"SolanaConfig"`

// compliance config
ComplianceConfig ComplianceConfig `json:"ComplianceConfig"`
Expand All @@ -104,8 +106,9 @@ type Config struct {
func (c Config) GetEVMConfig(chainID int64) (EVMConfig, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
evmCfg, found := c.EVMChainConfigs[chainID]
return evmCfg, found

evmCfg := c.EVMChainConfigs[chainID]
return evmCfg, !evmCfg.Empty()
}

// GetAllEVMConfigs returns a map of all EVM configs
Expand All @@ -121,12 +124,19 @@ func (c Config) GetAllEVMConfigs() map[int64]EVMConfig {
return copied
}

// GetBTCConfig returns the BTC config
func (c Config) GetBTCConfig() (BTCConfig, bool) {
// GetBTCConfig returns the BTC config for the given chain ID
func (c Config) GetBTCConfig(chainID int64) (BTCConfig, bool) {
c.mu.RLock()
defer c.mu.RUnlock()

return c.BitcoinConfig, c.BitcoinConfig != (BTCConfig{})
// we prefer 'BTCChainConfigs' over 'BitcoinConfig' but still fallback to be backward compatible
// this will allow new 'zetaclientd' binary to work with old config file
btcCfg, found := c.BTCChainConfigs[chainID]
if !found || btcCfg.Empty() {
btcCfg = c.BitcoinConfig
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
}

return btcCfg, !btcCfg.Empty()
}

// GetSolanaConfig returns the Solana config
Expand Down Expand Up @@ -178,5 +188,9 @@ func (c Config) GetRelayerKeyPath() string {
}

func (c EVMConfig) Empty() bool {
return c.Endpoint == "" && c.Chain.IsEmpty()
return c.Endpoint == "" || c.Chain.IsEmpty()
}

func (c BTCConfig) Empty() bool {
return c.RPCHost == ""
}
109 changes: 109 additions & 0 deletions zetaclient/config/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/require"
"github.com/zeta-chain/node/pkg/chains"
"github.com/zeta-chain/node/zetaclient/config"
)

Expand All @@ -14,3 +15,111 @@ func Test_GetRelayerKeyPath(t *testing.T) {
// should return default relayer key path
require.Equal(t, config.DefaultRelayerKeyPath, cfg.GetRelayerKeyPath())
}

func Test_GetEVMConfig(t *testing.T) {
chain := chains.Sepolia
chainID := chains.Sepolia.ChainId

t.Run("should find non-empty evm config", func(t *testing.T) {
// create config with defaults
cfg := config.New(true)

// set valid evm endpoint
cfg.EVMChainConfigs[chainID] = config.EVMConfig{
Chain: chain,
Endpoint: "localhost",
}

// should return non-empty evm config
evmCfg, found := cfg.GetEVMConfig(chainID)
require.True(t, found)
require.False(t, evmCfg.Empty())
})

t.Run("should not find evm config if endpoint is empty", func(t *testing.T) {
// create config with defaults
cfg := config.New(true)

// should not find evm config because endpoint is empty
_, found := cfg.GetEVMConfig(chainID)
require.False(t, found)
})

t.Run("should not find evm config if chain is empty", func(t *testing.T) {
// create config with defaults
cfg := config.New(true)

// set empty chain
cfg.EVMChainConfigs[chainID] = config.EVMConfig{
Chain: chains.Chain{},
Endpoint: "localhost",
}

// should not find evm config because chain is empty
_, found := cfg.GetEVMConfig(chainID)
require.False(t, found)
})
}

func Test_GetBTCConfig(t *testing.T) {
tests := []struct {
name string
chainID int64
oldCfg config.BTCConfig
btcCfg *config.BTCConfig
want bool
}{
{
name: "should find non-empty btc config",
chainID: chains.BitcoinRegtest.ChainId,
btcCfg: &config.BTCConfig{
RPCHost: "localhost",
},
want: true,
},
{
name: "should fallback to old 'BitcoinConfig' if new config is not set",
chainID: chains.BitcoinRegtest.ChainId,
oldCfg: config.BTCConfig{
RPCHost: "old_host",
},
btcCfg: nil, // new config is not set
want: true,
},
{
name: "should fallback to old config but still can't find btc config as it's empty",
chainID: chains.BitcoinRegtest.ChainId,
oldCfg: config.BTCConfig{
RPCUsername: "user",
RPCPassword: "pass",
RPCHost: "", // empty config
RPCParams: "regtest",
},
btcCfg: &config.BTCConfig{
RPCUsername: "user",
RPCPassword: "pass",
RPCHost: "", // empty config
RPCParams: "regtest",
},
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// create config with defaults
cfg := config.New(true)

// set both new and old btc config
cfg.BitcoinConfig = tt.oldCfg
if tt.btcCfg != nil {
cfg.BTCChainConfigs[tt.chainID] = *tt.btcCfg
}

// should return btc config
btcCfg, found := cfg.GetBTCConfig(tt.chainID)
require.Equal(t, tt.want, found)
require.Equal(t, tt.want, !btcCfg.Empty())
})
}
}
6 changes: 3 additions & 3 deletions zetaclient/context/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestAppContext(t *testing.T) {
ttsPubKey = "tssPubKeyTest"
)

testCfg.BitcoinConfig.RPCUsername = "abc"
testCfg.BTCChainConfigs[111] = config.BTCConfig{RPCUsername: "satoshi"}

ethParams := types.GetDefaultEthMainnetChainParams()
ethParams.IsSupported = true
Expand Down Expand Up @@ -106,7 +106,7 @@ func TestAppContext(t *testing.T) {
ethChain, err := appContext.GetChain(1)
assert.NoError(t, err)
assert.True(t, ethChain.IsEVM())
assert.False(t, ethChain.IsUTXO())
assert.False(t, ethChain.IsBitcoin())
assert.False(t, ethChain.IsSolana())
assert.Equal(t, ethParams, ethChain.Params())

Expand All @@ -121,7 +121,7 @@ func TestAppContext(t *testing.T) {
assert.ElementsMatch(t, expectedIDs, appContext.ListChainIDs())

// Check config
assert.Equal(t, "abc", appContext.Config().BitcoinConfig.RPCUsername)
assert.Equal(t, "satoshi", appContext.Config().BTCChainConfigs[111].RPCUsername)

t.Run("edge-cases", func(t *testing.T) {
for _, tt := range []struct {
Expand Down
2 changes: 1 addition & 1 deletion zetaclient/context/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func (c Chain) IsZeta() bool {
return chains.IsZetaChain(c.ID(), c.registry.additionalChains)
}

func (c Chain) IsUTXO() bool {
func (c Chain) IsBitcoin() bool {
return chains.IsBitcoinChain(c.ID(), c.registry.additionalChains)
}

Expand Down
2 changes: 1 addition & 1 deletion zetaclient/context/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestChainRegistry(t *testing.T) {
ethChain, err := r.Get(eth.ChainId)
require.NoError(t, err)
require.True(t, ethChain.IsEVM())
require.False(t, ethChain.IsUTXO())
require.False(t, ethChain.IsBitcoin())
require.False(t, ethChain.IsSolana())
require.Equal(t, ethParams, ethChain.Params())
})
Expand Down
4 changes: 2 additions & 2 deletions zetaclient/orchestrator/bootstap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestCreateSignerMap(t *testing.T) {
Endpoint: testutils.MockEVMRPCEndpoint,
}

cfg.BitcoinConfig = btcConfig
cfg.BTCChainConfigs[chains.BitcoinMainnet.ChainId] = btcConfig

// Given AppContext
app := zctx.New(cfg, nil, log)
Expand Down Expand Up @@ -227,7 +227,7 @@ func TestCreateChainObserverMap(t *testing.T) {
Endpoint: evmServer.Endpoint,
}

cfg.BitcoinConfig = btcConfig
cfg.BTCChainConfigs[chains.BitcoinMainnet.ChainId] = btcConfig
cfg.SolanaConfig = solConfig

// Given AppContext
Expand Down
Loading
Loading