diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index fba4eeb2bc..a2973e82fd 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -266,10 +266,11 @@ func start(_ *cobra.Command, _ []string) error { } } - // CreateSignerMap: This creates a map of all signers for each chain . Each signer is responsible for signing transactions for a particular chain - signerMap, err := CreateSignerMap(ctx, appContext, tss, logger, telemetryServer) + // CreateSignerMap: This creates a map of all signers for each chain. + // Each signer is responsible for signing transactions for a particular chain + signerMap, err := orchestrator.CreateSignerMap(ctx, tss, logger, telemetryServer) if err != nil { - log.Error().Err(err).Msg("CreateSignerMap") + log.Error().Err(err).Msg("Unable to create signer map") return err } diff --git a/cmd/zetaclientd/utils.go b/cmd/zetaclientd/utils.go index 01021d4e74..f55edd306a 100644 --- a/cmd/zetaclientd/utils.go +++ b/cmd/zetaclientd/utils.go @@ -5,7 +5,6 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/rs/zerolog" @@ -13,9 +12,7 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/chains/base" btcobserver "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" btcrpc "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/rpc" - btcsigner "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/signer" evmobserver "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" - evmsigner "github.com/zeta-chain/zetacore/zetaclient/chains/evm/signer" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/context" @@ -56,69 +53,6 @@ func CreateZetacoreClient(cfg config.Config, hotkeyPassword string, logger zerol return client, nil } -// CreateSignerMap creates a map of ChainSigners for all chains in the config -func CreateSignerMap( - ctx gocontext.Context, - appContext *context.AppContext, - tss interfaces.TSSSigner, - logger base.Logger, - ts *metrics.TelemetryServer, -) (map[int64]interfaces.ChainSigner, error) { - signerMap := make(map[int64]interfaces.ChainSigner) - - // EVM signers - for _, evmConfig := range appContext.Config().GetAllEVMConfigs() { - if evmConfig.Chain.IsZetaChain() { - continue - } - evmChainParams, found := appContext.GetEVMChainParams(evmConfig.Chain.ChainId) - if !found { - logger.Std.Error().Msgf("ChainParam not found for chain %s", evmConfig.Chain.String()) - continue - } - - chainName := evmConfig.Chain.ChainName.String() - mpiAddress := ethcommon.HexToAddress(evmChainParams.ConnectorContractAddress) - erc20CustodyAddress := ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress) - - signer, err := evmsigner.NewSigner( - ctx, - evmConfig.Chain, - tss, - ts, - logger, - evmConfig.Endpoint, - config.GetConnectorABI(), - config.GetERC20CustodyABI(), - mpiAddress, - erc20CustodyAddress, - ) - if err != nil { - logger.Std.Error().Err(err).Msgf("NewSigner error for EVM chain %q", chainName) - continue - } - - signerMap[evmConfig.Chain.ChainId] = signer - logger.Std.Info().Msgf("NewSigner succeeded for EVM chain %q", chainName) - } - - // BTC signer - btcChain, btcConfig, btcEnabled := appContext.GetBTCChainAndConfig() - if btcEnabled { - chainName := btcChain.ChainName.String() - - signer, err := btcsigner.NewSigner(btcChain, tss, ts, logger, btcConfig) - if err != nil { - logger.Std.Error().Err(err).Msgf("NewSigner error for BTC chain %q", chainName) - } else { - signerMap[btcChain.ChainId] = signer - logger.Std.Info().Msgf("NewSigner succeeded for BTC chain %q", chainName) - } - } - - return signerMap, nil -} - // CreateChainObserverMap creates a map of ChainObservers for all chains in the config func CreateChainObserverMap( ctx gocontext.Context, diff --git a/pkg/ptr/ptr.go b/pkg/ptr/ptr.go new file mode 100644 index 0000000000..baab694c81 --- /dev/null +++ b/pkg/ptr/ptr.go @@ -0,0 +1,17 @@ +// Package ptr provides helper functions for working with pointers. +package ptr + +// Ptr returns a pointer to the value passed in. +func Ptr[T any](value T) *T { + return &value +} + +// Deref returns the value of the pointer passed in, or the zero value of the type if the pointer is nil. +func Deref[T any](value *T) T { + var out T + if value != nil { + out = *value + } + + return out +} diff --git a/zetaclient/chains/bitcoin/signer/signer.go b/zetaclient/chains/bitcoin/signer/signer.go index 93b2fac800..9fe53fb870 100644 --- a/zetaclient/chains/bitcoin/signer/signer.go +++ b/zetaclient/chains/bitcoin/signer/signer.go @@ -16,6 +16,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" @@ -77,7 +78,7 @@ func NewSigner( } client, err := rpcclient.New(connCfg, nil) if err != nil { - return nil, fmt.Errorf("error creating bitcoin rpc client: %s", err) + return nil, errors.Wrap(err, "unable to create bitcoin rpc client") } return &Signer{ diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index 4888443ea9..26983b7059 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -36,26 +36,15 @@ type AppContext struct { mu sync.RWMutex } -// New creates and returns new AppContext +// New creates and returns new empty AppContext func New(cfg config.Config, logger zerolog.Logger) *AppContext { - evmChainParams := make(map[int64]*observertypes.ChainParams) - for _, e := range cfg.EVMChainConfigs { - evmChainParams[e.Chain.ChainId] = &observertypes.ChainParams{} - } - - var bitcoinChainParams *observertypes.ChainParams - _, found := cfg.GetBTCConfig() - if found { - bitcoinChainParams = &observertypes.ChainParams{} - } - return &AppContext{ config: cfg, logger: logger.With().Str("module", "appcontext").Logger(), chainsEnabled: []chains.Chain{}, - evmChainParams: evmChainParams, - bitcoinChainParams: bitcoinChainParams, + evmChainParams: map[int64]*observertypes.ChainParams{}, + bitcoinChainParams: nil, crosschainFlags: observertypes.CrosschainFlags{}, blockHeaderEnabledChains: []lightclienttypes.HeaderSupportedChain{}, @@ -72,14 +61,17 @@ func (a *AppContext) Config() config.Config { // GetBTCChainAndConfig returns btc chain and config if enabled func (a *AppContext) GetBTCChainAndConfig() (chains.Chain, config.BTCConfig, bool) { - btcConfig, configEnabled := a.Config().GetBTCConfig() - btcChain, _, paramsEnabled := a.GetBTCChainParams() + cfg, configEnabled := a.Config().GetBTCConfig() + if !configEnabled { + return chains.Chain{}, config.BTCConfig{}, false + } - if !configEnabled || !paramsEnabled { + chain, _, paramsEnabled := a.GetBTCChainParams() + if !paramsEnabled { return chains.Chain{}, config.BTCConfig{}, false } - return btcChain, btcConfig, true + return chain, cfg, true } // IsOutboundObservationEnabled returns true if the chain is supported and outbound flag is enabled @@ -173,7 +165,8 @@ func (a *AppContext) GetBTCChainParams() (chains.Chain, *observertypes.ChainPara a.mu.RLock() defer a.mu.RUnlock() - if a.bitcoinChainParams == nil { // bitcoin is not enabled + // bitcoin is not enabled + if a.bitcoinChainParams == nil { return chains.Chain{}, nil, false } @@ -235,15 +228,15 @@ func (a *AppContext) Update( blockHeaderEnabledChains []lightclienttypes.HeaderSupportedChain, init bool, ) { + if len(newChains) == 0 { + a.logger.Warn().Msg("UpdateChainParams: No chains enabled in ZeroCore") + } + // Ignore whatever order zetacore organizes chain list in state sort.SliceStable(newChains, func(i, j int) bool { return newChains[i].ChainId < newChains[j].ChainId }) - if len(newChains) == 0 { - a.logger.Warn().Msg("UpdateChainParams: No chains enabled in ZeroCore") - } - a.mu.Lock() defer a.mu.Unlock() @@ -252,7 +245,7 @@ func (a *AppContext) Update( a.logger.Warn(). Interface("chains.current", a.chainsEnabled). Interface("chains.new", newChains). - Msg("UpdateChainParams: ChainsEnabled changed at runtime!") + Msg("ChainsEnabled changed at runtime!") } if keygen != nil { @@ -264,18 +257,26 @@ func (a *AppContext) Update( a.additionalChain = additionalChains a.blockHeaderEnabledChains = blockHeaderEnabledChains - // update chain params for bitcoin if it has config in file - if a.bitcoinChainParams != nil && btcChainParams != nil { - a.bitcoinChainParams = btcChainParams - } - // update core params for evm chains we have configs in file - for _, params := range evmChainParams { - _, found := a.evmChainParams[params.ChainId] + freshEvmChainParams := make(map[int64]*observertypes.ChainParams) + for _, cp := range evmChainParams { + _, found := a.config.EVMChainConfigs[cp.ChainId] if !found { + a.logger.Warn(). + Int64("chain.id", cp.ChainId). + Msg("Encountered EVM ChainParams that are not present in the config file") + continue } - a.evmChainParams[params.ChainId] = params + + freshEvmChainParams[cp.ChainId] = cp + } + + a.evmChainParams = freshEvmChainParams + + // update chain params for bitcoin if it has config in file + if btcChainParams != nil { + a.bitcoinChainParams = btcChainParams } if tssPubKey != "" { diff --git a/zetaclient/orchestrator/bootstap_test.go b/zetaclient/orchestrator/bootstap_test.go new file mode 100644 index 0000000000..9468b2e5fc --- /dev/null +++ b/zetaclient/orchestrator/bootstap_test.go @@ -0,0 +1,286 @@ +package orchestrator + +import ( + "context" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/ptr" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/zetaclient/chains/base" + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/config" + zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/metrics" + "github.com/zeta-chain/zetacore/zetaclient/testutils" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" +) + +func TestSigners(t *testing.T) { + var ( + ts = metrics.NewTelemetryServer() + tss = mocks.NewTSSMainnet() + log = zerolog.New(zerolog.NewTestWriter(t)) + baseLogger = base.Logger{Std: log, Compliance: log} + ) + + t.Run("CreateSignerMap", func(t *testing.T) { + // ARRANGE + // Given a BTC server + _, btcConfig := testutils.NewBtcServer(t) + + // Given a zetaclient config with ETH, MATIC, and BTC chains + cfg := config.New(false) + + cfg.EVMChainConfigs[chains.Ethereum.ChainId] = config.EVMConfig{ + Chain: chains.Ethereum, + Endpoint: mocks.EVMRPCEnabled, + } + + cfg.EVMChainConfigs[chains.Polygon.ChainId] = config.EVMConfig{ + Chain: chains.Polygon, + Endpoint: mocks.EVMRPCEnabled, + } + + cfg.BitcoinConfig = btcConfig + + // Given AppContext + app := zctx.New(cfg, log) + ctx := zctx.WithAppContext(context.Background(), app) + + // Given chain & chainParams "fetched" from zetacore + // (note that slice LACKS polygon chain on purpose) + supportedChains := []chains.Chain{ + chains.Ethereum, + chains.BitcoinMainnet, + } + + evmParams := map[int64]*observertypes.ChainParams{ + chains.Ethereum.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Ethereum.ChainId, 10)), + } + + btcParams := &observertypes.ChainParams{ + ChainId: chains.BitcoinMainnet.ChainId, + } + + mustUpdateAppContext(t, app, supportedChains, evmParams, btcParams) + + // ACT + signers, err := CreateSignerMap(ctx, tss, baseLogger, ts) + + // ASSERT + assert.NoError(t, err) + assert.NotEmpty(t, signers) + + // Okay, now we want to check that signers for EVM and BTC were created + assert.Equal(t, 2, len(signers)) + hasSigner(t, signers, chains.Ethereum.ChainId) + hasSigner(t, signers, chains.BitcoinMainnet.ChainId) + + t.Run("Add polygon in the runtime", func(t *testing.T) { + // ARRANGE + supportedChains = []chains.Chain{ + chains.Ethereum, + chains.Polygon, + chains.BitcoinMainnet, + } + + evmParams = map[int64]*observertypes.ChainParams{ + chains.Ethereum.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Ethereum.ChainId, 10)), + chains.Polygon.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Polygon.ChainId, 10)), + } + + btcParams = &observertypes.ChainParams{ + ChainId: chains.BitcoinMainnet.ChainId, + } + + mustUpdateAppContext(t, app, supportedChains, evmParams, btcParams) + + // ACT + sm := signerMap(signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &sm) + + // ASSERT + assert.NoError(t, err) + assert.Equal(t, 1, added) + assert.Equal(t, 0, removed) + + hasSigner(t, signers, chains.Ethereum.ChainId) + hasSigner(t, signers, chains.Polygon.ChainId) + hasSigner(t, signers, chains.BitcoinMainnet.ChainId) + }) + + t.Run("Disable ethereum in the runtime", func(t *testing.T) { + // ARRANGE + supportedChains = []chains.Chain{ + chains.Polygon, + chains.BitcoinMainnet, + } + + evmParams = map[int64]*observertypes.ChainParams{ + chains.Polygon.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Polygon.ChainId, 10)), + } + + btcParams = &observertypes.ChainParams{ + ChainId: chains.BitcoinMainnet.ChainId, + } + + mustUpdateAppContext(t, app, supportedChains, evmParams, btcParams) + + // ACT + sm := signerMap(signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &sm) + + // ASSERT + assert.NoError(t, err) + assert.Equal(t, 0, added) + assert.Equal(t, 1, removed) + + missesSigner(t, signers, chains.Ethereum.ChainId) + hasSigner(t, signers, chains.Polygon.ChainId) + hasSigner(t, signers, chains.BitcoinMainnet.ChainId) + }) + + t.Run("Re-enable ethereum in the runtime", func(t *testing.T) { + // ARRANGE + supportedChains = []chains.Chain{ + chains.Ethereum, + chains.Polygon, + chains.BitcoinMainnet, + } + + evmParams = map[int64]*observertypes.ChainParams{ + chains.Ethereum.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Ethereum.ChainId, 10)), + chains.Polygon.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Polygon.ChainId, 10)), + } + + btcParams = &observertypes.ChainParams{ + ChainId: chains.BitcoinMainnet.ChainId, + } + + mustUpdateAppContext(t, app, supportedChains, evmParams, btcParams) + + // ACT + sm := signerMap(signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &sm) + + // ASSERT + assert.NoError(t, err) + assert.Equal(t, 1, added) + assert.Equal(t, 0, removed) + + hasSigner(t, signers, chains.Ethereum.ChainId) + hasSigner(t, signers, chains.Polygon.ChainId) + hasSigner(t, signers, chains.BitcoinMainnet.ChainId) + }) + + t.Run("Disable btc in the runtime", func(t *testing.T) { + // ARRANGE + // Given updated data from zetacore containing polygon chain + supportedChains = []chains.Chain{ + chains.Ethereum, + chains.Polygon, + } + + evmParams = map[int64]*observertypes.ChainParams{ + chains.Ethereum.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Ethereum.ChainId, 10)), + chains.Polygon.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Polygon.ChainId, 10)), + } + + btcParams = &observertypes.ChainParams{} + + mustUpdateAppContext(t, app, supportedChains, evmParams, btcParams) + + // ACT + sm := signerMap(signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &sm) + + // ASSERT + assert.NoError(t, err) + assert.Equal(t, 0, added) + assert.Equal(t, 1, removed) + + hasSigner(t, signers, chains.Ethereum.ChainId) + hasSigner(t, signers, chains.Polygon.ChainId) + missesSigner(t, signers, chains.BitcoinMainnet.ChainId) + }) + + t.Run("Re-enable btc in the runtime", func(t *testing.T) { + // ARRANGE + // Given updated data from zetacore containing polygon chain + supportedChains = []chains.Chain{ + chains.Ethereum, + chains.Polygon, + chains.BitcoinMainnet, + } + + evmParams = map[int64]*observertypes.ChainParams{ + chains.Ethereum.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Ethereum.ChainId, 10)), + chains.Polygon.ChainId: ptr.Ptr(mocks.MockChainParams(chains.Polygon.ChainId, 10)), + } + + btcParams = &observertypes.ChainParams{ + ChainId: chains.BitcoinMainnet.ChainId, + } + + mustUpdateAppContext(t, app, supportedChains, evmParams, btcParams) + + // ACT + sm := signerMap(signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &sm) + + // ASSERT + assert.NoError(t, err) + assert.Equal(t, 1, added) + assert.Equal(t, 0, removed) + + hasSigner(t, signers, chains.Ethereum.ChainId) + hasSigner(t, signers, chains.Polygon.ChainId) + hasSigner(t, signers, chains.BitcoinMainnet.ChainId) + }) + + t.Run("No changes", func(t *testing.T) { + // ACT + sm := signerMap(signers) + added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &sm) + + // ASSERT + assert.NoError(t, err) + assert.Equal(t, 0, added) + assert.Equal(t, 0, removed) + }) + }) +} + +func mustUpdateAppContext( + _ *testing.T, + app *zctx.AppContext, + chains []chains.Chain, + evmParams map[int64]*observertypes.ChainParams, + utxoParams *observertypes.ChainParams, +) { + app.Update( + ptr.Ptr(app.GetKeygen()), + chains, + evmParams, + utxoParams, + app.GetCurrentTssPubKey(), + app.GetCrossChainFlags(), + app.GetAdditionalChains(), + nil, + false, + ) +} + +func hasSigner(t *testing.T, signers map[int64]interfaces.ChainSigner, chainId int64) { + signer, ok := signers[chainId] + assert.True(t, ok, "missing signer for chain %d", chainId) + assert.NotEmpty(t, signer) +} + +func missesSigner(t *testing.T, signers map[int64]interfaces.ChainSigner, chainId int64) { + _, ok := signers[chainId] + assert.False(t, ok, "unexpected signer for chain %d", chainId) +} diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go new file mode 100644 index 0000000000..d739492f0a --- /dev/null +++ b/zetaclient/orchestrator/bootstrap.go @@ -0,0 +1,170 @@ +package orchestrator + +import ( + "context" + + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/rs/zerolog" + + "github.com/zeta-chain/zetacore/zetaclient/chains/base" + btcsigner "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/signer" + evmsigner "github.com/zeta-chain/zetacore/zetaclient/chains/evm/signer" + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/config" + zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/metrics" +) + +// CreateSignerMap creates a map of interfaces.ChainSigner (by chainID) for all chains in the config. +// Note that signer construction failure for a chain does not prevent the creation of signers for other chains. +func CreateSignerMap( + ctx context.Context, + tss interfaces.TSSSigner, + logger base.Logger, + ts *metrics.TelemetryServer, +) (map[int64]interfaces.ChainSigner, error) { + signers := make(signerMap) + _, _, err := syncSignerMap(ctx, tss, logger, ts, &signers) + if err != nil { + return nil, err + } + + return signers, nil +} + +// syncSignerMap synchronizes the given signers map with the signers for all chains in the config. +// This semantic is used to allow dynamic updates to the signers map. +func syncSignerMap( + ctx context.Context, + tss interfaces.TSSSigner, + logger base.Logger, + ts *metrics.TelemetryServer, + signers *signerMap, +) (int, int, error) { + if signers == nil { + return 0, 0, errors.New("signers map is nil") + } + + app, err := zctx.FromContext(ctx) + if err != nil { + return 0, 0, errors.Wrapf(err, "failed to get app context") + } + + var added int + + presentChainIDs := make([]int64, 0) + + // EVM signers + for _, evmConfig := range app.Config().GetAllEVMConfigs() { + chainID := evmConfig.Chain.ChainId + + if evmConfig.Chain.IsZetaChain() { + continue + } + + evmChainParams, found := app.GetEVMChainParams(chainID) + if !found { + logger.Std.Warn().Msgf("Unable to find chain params for EVM chain %d", chainID) + continue + } + + presentChainIDs = append(presentChainIDs, chainID) + + // noop for existing signers + if signers.has(chainID) { + continue + } + + var ( + mpiAddress = ethcommon.HexToAddress(evmChainParams.ConnectorContractAddress) + erc20CustodyAddress = ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress) + ) + + signer, err := evmsigner.NewSigner( + ctx, + evmConfig.Chain, + tss, + ts, + logger, + evmConfig.Endpoint, + config.GetConnectorABI(), + config.GetERC20CustodyABI(), + mpiAddress, + erc20CustodyAddress, + ) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to construct signer for EVM chain %d", chainID) + continue + } + + signers.set(chainID, signer) + logger.Std.Info().Msgf("Added signer for EVM chain %d", chainID) + added++ + } + + // BTC signer + btcChain, btcConfig, btcEnabled := app.GetBTCChainAndConfig() + if btcEnabled { + chainID := btcChain.ChainId + + presentChainIDs = append(presentChainIDs, chainID) + + if !signers.has(chainID) { + utxoSigner, err := btcsigner.NewSigner(btcChain, tss, ts, logger, btcConfig) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to construct signer for UTXO chain %d", chainID) + } else { + signers.set(chainID, utxoSigner) + logger.Std.Info().Msgf("Added signer for UTXO chain %d", chainID) + added++ + } + } + } + + // Remove all disabled signers + removed := signers.unsetMissing(presentChainIDs, logger.Std) + + return added, removed, nil +} + +type signerMap map[int64]interfaces.ChainSigner + +func (m *signerMap) has(chainID int64) bool { + _, ok := (*m)[chainID] + return ok +} + +func (m *signerMap) set(chainID int64, signer interfaces.ChainSigner) { + (*m)[chainID] = signer +} + +func (m *signerMap) unset(chainID int64, logger zerolog.Logger) bool { + if _, ok := (*m)[chainID]; !ok { + return false + } + + logger.Info().Msgf("Removing signer for chain %d", chainID) + delete(*m, chainID) + + return true +} + +// unsetMissing removes signers from the map IF they are not in the enabledChains list. +func (m *signerMap) unsetMissing(enabledChains []int64, logger zerolog.Logger) int { + enabledMap := make(map[int64]struct{}, len(enabledChains)) + for _, id := range enabledChains { + enabledMap[id] = struct{}{} + } + + var removed int + + for id := range *m { + if _, ok := enabledMap[id]; !ok { + m.unset(id, logger) + removed++ + } + } + + return removed +} diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index a4bef22c64..d355c41249 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -57,8 +57,8 @@ type Orchestrator struct { // misc logger multiLogger - stop chan struct{} ts *metrics.TelemetryServer + stop chan struct{} } type multiLogger struct { diff --git a/zetaclient/testutils/btc_rpc_server.go b/zetaclient/testutils/btc_rpc_server.go new file mode 100644 index 0000000000..3c1c49ad72 --- /dev/null +++ b/zetaclient/testutils/btc_rpc_server.go @@ -0,0 +1,39 @@ +package testutils + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/zeta-chain/zetacore/zetaclient/config" +) + +// BtcServer represents a BTC RPC mock with a "real" HTTP server allocated. +type BtcServer struct { + t *testing.T +} + +// NewBtcServer constructs BtcServer. +func NewBtcServer(t *testing.T) (*BtcServer, config.BTCConfig) { + var ( + btcServer = &BtcServer{t: t} + server = httptest.NewUnstartedServer(http.HandlerFunc(btcServer.handler)) + cfg = config.BTCConfig{ + RPCUsername: "btc-user", + RPCPassword: "btc-password", + RPCHost: server.URL, + RPCParams: "", + } + ) + + server.Start() + t.Cleanup(server.Close) + + return btcServer, cfg +} + +// handler is a simple HTTP handler that returns 200 OK. +// Later we can add any logic here. +func (s BtcServer) handler(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) +}