From daf4b6c906c0b9f6a6b22bff57500fde76ddd216 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 25 Jul 2024 13:59:45 +0200 Subject: [PATCH 01/24] Implement chain registry --- zetaclient/context/chain.go | 141 +++++++++++++++++++++++++++++++ zetaclient/context/chain_test.go | 79 +++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 zetaclient/context/chain.go create mode 100644 zetaclient/context/chain_test.go diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go new file mode 100644 index 0000000000..a2baa0389b --- /dev/null +++ b/zetaclient/context/chain.go @@ -0,0 +1,141 @@ +package context + +import ( + "fmt" + "sync" + + "github.com/pkg/errors" + "golang.org/x/exp/maps" + + "github.com/zeta-chain/zetacore/pkg/chains" + observer "github.com/zeta-chain/zetacore/x/observer/types" +) + +// ChainRegistry is a registry of supported chains +type ChainRegistry struct { + chains map[int64]Chain + additionalChains []chains.Chain + mu sync.Mutex +} + +// Chain represents chain with its parameters +type Chain struct { + id int64 + chain *chains.Chain + params *observer.ChainParams + registry *ChainRegistry +} + +var ( + ErrChainNotFound = errors.New("chain not found") + ErrChainNotSupported = errors.New("chain not supported") +) + +// NewChainRegistry constructs a new ChainRegistry +func NewChainRegistry() *ChainRegistry { + return &ChainRegistry{ + chains: make(map[int64]Chain), + additionalChains: []chains.Chain{}, + mu: sync.Mutex{}, + } +} + +func (cr *ChainRegistry) Get(chainID int64) (Chain, error) { + chain, ok := cr.chains[chainID] + if !ok { + return Chain{}, ErrChainNotFound + } + + return chain, nil +} + +// Set sets a chain in the registry. Note that chain must be SUPPORTED and NOT ZetaChain itself +// otherwise it will return ErrChainNotSupported +func (cr *ChainRegistry) Set(chainID int64, chain *chains.Chain, params *observer.ChainParams) error { + item, err := newChain(chainID, chain, params) + if err != nil { + return err + } + + item.registry = cr + + cr.mu.Lock() + defer cr.mu.Unlock() + + cr.chains[item.id] = item + + return nil +} + +// SetAdditionalChains sets additional chains +func (cr *ChainRegistry) SetAdditionalChains(chains []chains.Chain) { + cr.mu.Lock() + defer cr.mu.Unlock() + + cr.additionalChains = chains +} + +// Delete deletes one or more chains from the registry +func (cr *ChainRegistry) Delete(chainIDs ...int64) { + cr.mu.Lock() + defer cr.mu.Unlock() + + for _, id := range chainIDs { + delete(cr.chains, id) + } +} + +// Has checks if the chain is in the registry +func (cr *ChainRegistry) Has(chainID int64) bool { + _, ok := cr.chains[chainID] + return ok +} + +// ChainIDs returns a list of chain IDs in the registry +func (cr *ChainRegistry) ChainIDs() []int64 { + cr.mu.Lock() + defer cr.mu.Unlock() + + return maps.Keys(cr.chains) +} + +func newChain(chainID int64, chain *chains.Chain, params *observer.ChainParams) (Chain, error) { + switch { + case chainID < 1: + return Chain{}, fmt.Errorf("invalid chain id %d", chainID) + case chain == nil: + return Chain{}, fmt.Errorf("chain is nil") + case params == nil: + return Chain{}, fmt.Errorf("chain params is nil") + case chain.ChainId != chainID: + return Chain{}, fmt.Errorf("chain id %d does not match chain.ChainId %d", chainID, chain.ChainId) + case params.ChainId != chainID: + return Chain{}, fmt.Errorf("chain id %d does not match params.ChainId %d", chainID, params.ChainId) + case !params.IsSupported: + return Chain{}, ErrChainNotSupported + case chains.IsZetaChain(chainID, nil): + return Chain{}, errors.Wrap(ErrChainNotSupported, "ZetaChain itself cannot be in the registry") + } + + return Chain{ + id: chainID, + chain: chain, + params: params, + }, nil +} + +func (c Chain) Params() *observer.ChainParams { + return c.params +} + +func (c Chain) IsEVM() bool { + return chains.IsEVMChain(c.id, c.registry.additionalChains) +} + +func (c Chain) IsUTXO() bool { + return chains.IsBitcoinChain(c.id, c.registry.additionalChains) +} + +func (c Chain) IsSolana() bool { + return chains.IsSolanaChain(c.id, c.registry.additionalChains) +} diff --git a/zetaclient/context/chain_test.go b/zetaclient/context/chain_test.go new file mode 100644 index 0000000000..7e07b8187a --- /dev/null +++ b/zetaclient/context/chain_test.go @@ -0,0 +1,79 @@ +package context + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + observer "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestChainRegistry(t *testing.T) { + // Given chains & chainParams + var ( + btc = &chains.BitcoinMainnet + btcParams = makeParams(btc.ChainId, true) + + eth = &chains.Ethereum + ethParams = makeParams(eth.ChainId, true) + + matic = &chains.Polygon + maticParams = makeParams(matic.ChainId, true) + + // NOT supported! + opt = &chains.OptimismSepolia + optParams = makeParams(opt.ChainId, false) + + sol = &chains.SolanaMainnet + solParams = makeParams(sol.ChainId, true) + + // Zetachain itself + zeta = &chains.ZetaChainMainnet + zetaParams = makeParams(zeta.ChainId, true) + ) + + t.Run("Sample Flow", func(t *testing.T) { + // Given registry + r := NewChainRegistry() + + // With some chains added + require.NoError(t, r.Set(btc.ChainId, btc, btcParams)) + require.NoError(t, r.Set(eth.ChainId, eth, ethParams)) + require.NoError(t, r.Set(matic.ChainId, matic, maticParams)) + require.NoError(t, r.Set(sol.ChainId, sol, solParams)) + + // With failures on invalid data + require.Error(t, r.Set(0, btc, btcParams)) + require.Error(t, r.Set(btc.ChainId, btc, nil)) + require.Error(t, r.Set(btc.ChainId, nil, btcParams)) + require.Error(t, r.Set(123, btc, btcParams)) + + // With failure on adding unsupported chains + require.ErrorIs(t, r.Set(opt.ChainId, opt, optParams), ErrChainNotSupported) + + // With failure on adding ZetaChain itself + require.ErrorIs(t, r.Set(zeta.ChainId, zeta, zetaParams), ErrChainNotSupported) + + // Should return a proper chain list + require.ElementsMatch(t, []int64{btc.ChainId, eth.ChainId, matic.ChainId, sol.ChainId}, r.ChainIDs()) + + // Should return not found error + _, err := r.Get(123) + require.ErrorIs(t, err, ErrChainNotFound) + + // Let's check ETH + ethChain, err := r.Get(eth.ChainId) + require.NoError(t, err) + require.True(t, ethChain.IsEVM()) + require.False(t, ethChain.IsUTXO()) + require.False(t, ethChain.IsSolana()) + require.Equal(t, ethParams, ethChain.Params()) + }) +} + +func makeParams(id int64, supported bool) *observer.ChainParams { + return &observer.ChainParams{ + ChainId: id, + IsSupported: supported, + } +} From f24d05aade7688ca203eb4ef5b9492647f2bb09a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 26 Jul 2024 13:37:42 +0200 Subject: [PATCH 02/24] Rewrite test-cases for AppContext --- zetaclient/context/app.go | 365 +++++++---------- zetaclient/context/app_test.go | 716 ++++++++------------------------- zetaclient/context/chain.go | 26 +- 3 files changed, 328 insertions(+), 779 deletions(-) diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index 1c37c23a50..f57f3c436c 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -3,37 +3,28 @@ package context import ( "fmt" - "sort" "sync" + "github.com/pkg/errors" "github.com/rs/zerolog" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "github.com/zeta-chain/zetacore/pkg/chains" - lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" ) -// AppContext represents application context. +// AppContext represents application (zetaclient) context. type AppContext struct { config config.Config logger zerolog.Logger - keygen observertypes.Keygen - chainsEnabled []chains.Chain - evmChainParams map[int64]*observertypes.ChainParams - bitcoinChainParams *observertypes.ChainParams - solanaChainParams *observertypes.ChainParams - currentTssPubkey string - crosschainFlags observertypes.CrosschainFlags + chainRegistry *ChainRegistry - // additionalChains is a list of additional static chain information to use when searching from chain IDs - // it is stored in the protocol to dynamically support new chains without doing an upgrade - additionalChain []chains.Chain - - // blockHeaderEnabledChains is used to store the list of chains that have block header verification enabled - // All chains in this list will have Enabled flag set to true - blockHeaderEnabledChains []lightclienttypes.HeaderSupportedChain + currentTssPubKey string + crosschainFlags observertypes.CrosschainFlags + keygen observertypes.Keygen mu sync.RWMutex } @@ -44,16 +35,13 @@ func New(cfg config.Config, logger zerolog.Logger) *AppContext { config: cfg, logger: logger.With().Str("module", "appcontext").Logger(), - chainsEnabled: []chains.Chain{}, - evmChainParams: map[int64]*observertypes.ChainParams{}, - bitcoinChainParams: nil, - solanaChainParams: nil, - crosschainFlags: observertypes.CrosschainFlags{}, - blockHeaderEnabledChains: []lightclienttypes.HeaderSupportedChain{}, + chainRegistry: NewChainRegistry(), - currentTssPubkey: "", + crosschainFlags: observertypes.CrosschainFlags{}, + currentTssPubKey: "", keygen: observertypes.Keygen{}, - mu: sync.RWMutex{}, + + mu: sync.RWMutex{}, } } @@ -62,43 +50,24 @@ func (a *AppContext) Config() config.Config { return a.config } -// GetBTCChainAndConfig returns btc chain and config if enabled -func (a *AppContext) GetBTCChainAndConfig() (chains.Chain, config.BTCConfig, bool) { - cfg, configEnabled := a.Config().GetBTCConfig() - if !configEnabled { - return chains.Chain{}, config.BTCConfig{}, false - } - - chain, _, paramsEnabled := a.GetBTCChainParams() - if !paramsEnabled { - return chains.Chain{}, config.BTCConfig{}, false - } - - return chain, cfg, true +// GetChain returns the chain by ID. +func (a *AppContext) GetChain(chainID int64) (Chain, error) { + return a.chainRegistry.Get(chainID) } -// GetSolanaChainAndConfig returns solana chain and config if enabled -func (a *AppContext) GetSolanaChainAndConfig() (chains.Chain, config.SolanaConfig, bool) { - solConfig, configEnabled := a.Config().GetSolanaConfig() - solChain, _, paramsEnabled := a.GetSolanaChainParams() - - if !configEnabled || !paramsEnabled { - return chains.Chain{}, config.SolanaConfig{}, false - } - - return solChain, solConfig, true +// ListChainIDs returns the list of existing chain ids in the registry. +func (a *AppContext) ListChainIDs() []int64 { + return a.chainRegistry.ChainIDs() } -// IsOutboundObservationEnabled returns true if the chain is supported and outbound flag is enabled -func (a *AppContext) IsOutboundObservationEnabled(chainParams observertypes.ChainParams) bool { - flags := a.GetCrossChainFlags() - return chainParams.IsSupported && flags.IsOutboundEnabled +// IsOutboundObservationEnabled returns true if outbound flag is enabled +func (a *AppContext) IsOutboundObservationEnabled() bool { + return a.GetCrossChainFlags().IsOutboundEnabled } -// IsInboundObservationEnabled returns true if the chain is supported and inbound flag is enabled -func (a *AppContext) IsInboundObservationEnabled(chainParams observertypes.ChainParams) bool { - flags := a.GetCrossChainFlags() - return chainParams.IsSupported && flags.IsInboundEnabled +// IsInboundObservationEnabled returns true if inbound flag is enabled +func (a *AppContext) IsInboundObservationEnabled() bool { + return a.GetCrossChainFlags().IsInboundEnabled } // GetKeygen returns the current keygen @@ -106,240 +75,178 @@ func (a *AppContext) GetKeygen() observertypes.Keygen { a.mu.RLock() defer a.mu.RUnlock() - var copiedPubkeys []string + var copiedPubKeys []string if a.keygen.GranteePubkeys != nil { - copiedPubkeys = make([]string, len(a.keygen.GranteePubkeys)) - copy(copiedPubkeys, a.keygen.GranteePubkeys) + copiedPubKeys = make([]string, len(a.keygen.GranteePubkeys)) + copy(copiedPubKeys, a.keygen.GranteePubkeys) } return observertypes.Keygen{ Status: a.keygen.Status, - GranteePubkeys: copiedPubkeys, + GranteePubkeys: copiedPubKeys, BlockNumber: a.keygen.BlockNumber, } } -// GetCurrentTssPubKey returns the current tss pubkey +// GetCurrentTssPubKey returns the current tss pubKey. func (a *AppContext) GetCurrentTssPubKey() string { a.mu.RLock() defer a.mu.RUnlock() - return a.currentTssPubkey + return a.currentTssPubKey } -// GetEnabledChains returns all enabled chains including zetachain -func (a *AppContext) GetEnabledChains() []chains.Chain { +// GetCrossChainFlags returns crosschain flags +func (a *AppContext) GetCrossChainFlags() observertypes.CrosschainFlags { a.mu.RLock() defer a.mu.RUnlock() - copiedChains := make([]chains.Chain, len(a.chainsEnabled)) - copy(copiedChains, a.chainsEnabled) - - return copiedChains + return a.crosschainFlags } -// GetEnabledExternalChains returns all enabled external chains -func (a *AppContext) GetEnabledExternalChains() []chains.Chain { - a.mu.RLock() - defer a.mu.RUnlock() - - externalChains := make([]chains.Chain, 0) - for _, chain := range a.chainsEnabled { - if chain.IsExternal { - externalChains = append(externalChains, chain) +// Update updates AppContext and params for all chains +// this must be the ONLY function that writes to AppContext +func (a *AppContext) Update( + keygen observertypes.Keygen, + freshChains, additionalChains []chains.Chain, + freshChainParams map[int64]*observertypes.ChainParams, + tssPubKey string, + crosschainFlags observertypes.CrosschainFlags, +) error { + // some sanity checks + switch { + case len(freshChains) == 0: + return fmt.Errorf("no chains present") + case len(freshChainParams) == 0: + return fmt.Errorf("no chain params present") + case tssPubKey == "": + return fmt.Errorf("tss pubkey is empty") + case len(additionalChains) > 0: + for _, c := range additionalChains { + if !c.IsExternal { + return fmt.Errorf("additional chain %d is not external", c.ChainId) + } } } - return externalChains -} - -// GetEVMChainParams returns chain params for a specific EVM chain -func (a *AppContext) GetEVMChainParams(chainID int64) (*observertypes.ChainParams, bool) { - a.mu.RLock() - defer a.mu.RUnlock() - evmChainParams, found := a.evmChainParams[chainID] - return evmChainParams, found -} - -// GetAllEVMChainParams returns all chain params for EVM chains -func (a *AppContext) GetAllEVMChainParams() map[int64]*observertypes.ChainParams { - a.mu.RLock() - defer a.mu.RUnlock() - - // deep copy evm chain params - copied := make(map[int64]*observertypes.ChainParams, len(a.evmChainParams)) - for chainID, evmConfig := range a.evmChainParams { - copied[chainID] = &observertypes.ChainParams{} - *copied[chainID] = *evmConfig + err := a.updateChainRegistry(freshChains, additionalChains, freshChainParams) + if err != nil { + return errors.Wrap(err, "unable to update chain registry") } - return copied -} -// GetBTCChainParams returns (chain, chain params, found) for bitcoin chain -func (a *AppContext) GetBTCChainParams() (chains.Chain, *observertypes.ChainParams, bool) { - a.mu.RLock() - defer a.mu.RUnlock() - - // bitcoin is not enabled - if a.bitcoinChainParams == nil { - return chains.Chain{}, nil, false - } + a.mu.Lock() + defer a.mu.Unlock() - chain, found := chains.GetChainFromChainID(a.bitcoinChainParams.ChainId, a.additionalChain) - if !found { - return chains.Chain{}, nil, false - } + a.crosschainFlags = crosschainFlags + a.keygen = keygen + a.currentTssPubKey = tssPubKey - return chain, a.bitcoinChainParams, true + return nil } -// GetSolanaChainParams returns (chain, chain params, found) for solana chain -func (a *AppContext) GetSolanaChainParams() (chains.Chain, *observertypes.ChainParams, bool) { - a.mu.RLock() - defer a.mu.RUnlock() - - // solana is not enabled - if a.solanaChainParams == nil { - return chains.Chain{}, nil, false - } - - chain, found := chains.GetChainFromChainID(a.solanaChainParams.ChainId, a.additionalChain) - if !found { - fmt.Printf("solana Chain %d not found", a.solanaChainParams.ChainId) - return chains.Chain{}, nil, false +func (a *AppContext) updateChainRegistry( + freshChains, additionalChains []chains.Chain, + freshChainParams map[int64]*observertypes.ChainParams, +) error { + freshChainsByID := make(map[int64]chains.Chain, len(freshChains)+len(additionalChains)) + for _, c := range freshChains { + freshChainsByID[c.ChainId] = c } - return chain, a.solanaChainParams, true -} - -// GetCrossChainFlags returns crosschain flags -func (a *AppContext) GetCrossChainFlags() observertypes.CrosschainFlags { - a.mu.RLock() - defer a.mu.RUnlock() - - return a.crosschainFlags -} - -// GetAdditionalChains returns additional chains -func (a *AppContext) GetAdditionalChains() []chains.Chain { - a.mu.RLock() - defer a.mu.RUnlock() - return a.additionalChain -} - -// GetAllHeaderEnabledChains returns all verification flags -func (a *AppContext) GetAllHeaderEnabledChains() []lightclienttypes.HeaderSupportedChain { - a.mu.RLock() - defer a.mu.RUnlock() - - return a.blockHeaderEnabledChains -} - -// GetBlockHeaderEnabledChains checks if block header verification is enabled for a specific chain -func (a *AppContext) GetBlockHeaderEnabledChains(chainID int64) (lightclienttypes.HeaderSupportedChain, bool) { - a.mu.RLock() - defer a.mu.RUnlock() - - for _, flags := range a.blockHeaderEnabledChains { - if flags.ChainId == chainID { - return flags, true + for _, c := range additionalChains { + // shouldn't happen, but just in case + if _, found := freshChainsByID[c.ChainId]; found { + continue } - } - - return lightclienttypes.HeaderSupportedChain{}, false -} -// Update updates AppContext and params for all chains -// this must be the ONLY function that writes to AppContext -func (a *AppContext) Update( - keygen *observertypes.Keygen, - newChains []chains.Chain, - evmChainParams map[int64]*observertypes.ChainParams, - btcChainParams *observertypes.ChainParams, - solChainParams *observertypes.ChainParams, - tssPubKey string, - crosschainFlags observertypes.CrosschainFlags, - additionalChains []chains.Chain, - blockHeaderEnabledChains []lightclienttypes.HeaderSupportedChain, - init bool, -) { - if len(newChains) == 0 { - a.logger.Warn().Msg("UpdateChainParams: No chains enabled in ZeroCore") + freshChainsByID[c.ChainId] = c } - // Ignore whatever order zetacore organizes chain list in state - sort.SliceStable(newChains, func(i, j int) bool { - return newChains[i].ChainId < newChains[j].ChainId - }) - - a.mu.Lock() - defer a.mu.Unlock() + var ( + freshChainIDs = maps.Keys(freshChainsByID) + existingChainIDs = a.chainRegistry.ChainIDs() + ) - // Add some warnings if chain list changes at runtime - if !init && !chainsEqual(a.chainsEnabled, newChains) { + if len(existingChainIDs) > 0 && !elementsMatch(existingChainIDs, freshChainIDs) { a.logger.Warn(). - Interface("chains.current", a.chainsEnabled). - Interface("chains.new", newChains). - Msg("ChainsEnabled changed at runtime!") - } - - if keygen != nil { - a.keygen = *keygen + Ints64("chains.current", existingChainIDs). + Ints64("chains.new", freshChainIDs). + Msg("Chain list changed at the runtime!") } - a.chainsEnabled = newChains - a.crosschainFlags = crosschainFlags - a.additionalChain = additionalChains - a.blockHeaderEnabledChains = blockHeaderEnabledChains - - // update core params for evm chains we have configs in file - freshEvmChainParams := make(map[int64]*observertypes.ChainParams) - for _, cp := range evmChainParams { - _, found := a.config.EVMChainConfigs[cp.ChainId] - if !found { + // Log warn if somehow chain doesn't chainParam + for _, chainID := range freshChainIDs { + if _, ok := freshChainParams[chainID]; !ok { a.logger.Warn(). - Int64("chain.id", cp.ChainId). - Msg("Encountered EVM ChainParams that are not present in the config file") + Int64("chain.id", chainID). + Msg("Chain doesn't have according ChainParams present. Skipping.") + } + } - continue + // okay, let's update the chains. + // Set() ensures that chain, chainID, and params are consistent and chain is not zeta + chain is supported + for chainID, params := range freshChainParams { + if err := observertypes.ValidateChainParams(params); err != nil { + return errors.Wrapf(err, "invalid chain params for chain %d", chainID) } - if chains.IsZetaChain(cp.ChainId, nil) { - continue + chain, ok := freshChainsByID[chainID] + if !ok { + return fmt.Errorf("unable to locate fresh chain %d based on chain params", chainID) } - freshEvmChainParams[cp.ChainId] = cp + if err := a.chainRegistry.Set(chainID, &chain, params); err != nil { + return errors.Wrap(err, "unable to set chain in the registry") + } } - a.evmChainParams = freshEvmChainParams + a.chainRegistry.SetAdditionalChains(additionalChains) - // update chain params for bitcoin if it has config in file - if btcChainParams != nil { - a.bitcoinChainParams = btcChainParams - } + toBeDeleted := diff(existingChainIDs, freshChainIDs) + if len(toBeDeleted) > 0 { + a.logger.Warn(). + Ints64("chains.deleted", toBeDeleted). + Msg("Deleting chains that are no longer relevant") - // update chain params for solana if it has config in file - if solChainParams != nil { - a.solanaChainParams = solChainParams + a.chainRegistry.Delete(toBeDeleted...) } - if tssPubKey != "" { - a.currentTssPubkey = tssPubKey - } + return nil } -func chainsEqual(a []chains.Chain, b []chains.Chain) bool { +func elementsMatch(a, b []int64) bool { if len(a) != len(b) { return false } - for i, left := range a { - right := b[i] + slices.Sort(a) + slices.Sort(b) - if left.ChainId != right.ChainId || left.ChainName != right.ChainName { + for i, v := range a { + if v != b[i] { return false } } return true } + +// diff returns the elements in `a` that are not in `b` +func diff(a, b []int64) []int64 { + var ( + cache = map[int64]struct{}{} + result []int64 + ) + + for _, v := range b { + cache[v] = struct{}{} + } + + for _, v := range a { + if _, ok := cache[v]; !ok { + result = append(result, v) + } + } + + return result +} diff --git a/zetaclient/context/app_test.go b/zetaclient/context/app_test.go index 8927505f03..16a452d096 100644 --- a/zetaclient/context/app_test.go +++ b/zetaclient/context/app_test.go @@ -1,4 +1,4 @@ -package context_test +package context import ( "testing" @@ -6,578 +6,216 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/testutil/sample" - lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" - "github.com/zeta-chain/zetacore/zetaclient/context" + "golang.org/x/exp/maps" ) -func TestNew(t *testing.T) { +func TestAppContext(t *testing.T) { var ( testCfg = config.New(false) - logger = zerolog.Nop() - ) - - t.Run("should create new AppContext with empty config", func(t *testing.T) { - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) - - // assert keygen - keyGen := appContext.GetKeygen() - require.Equal(t, observertypes.Keygen{}, keyGen) - - // assert enabled chains - require.Empty(t, len(appContext.GetEnabledChains())) - - // assert external chains - require.Empty(t, len(appContext.GetEnabledExternalChains())) - - // assert current tss pubkey - require.Equal(t, "", appContext.GetCurrentTssPubKey()) + logger = zerolog.New(zerolog.NewTestWriter(t)) - // assert btc chain params - chain, btcChainParams, btcChainParamsFound := appContext.GetBTCChainParams() - require.Equal(t, chains.Chain{}, chain) - require.False(t, btcChainParamsFound) - require.Nil(t, btcChainParams) - - // assert evm chain params - allEVMChainParams := appContext.GetAllEVMChainParams() - require.Empty(t, allEVMChainParams) - }) - - t.Run("should return nil chain params if chain id is not found", func(t *testing.T) { - // create config with btc config - testCfg := config.New(false) - testCfg.BitcoinConfig = config.BTCConfig{ - RPCUsername: "test_user", - RPCPassword: "test_password", + keyGen = types.Keygen{ + Status: types.KeygenStatus_KeyGenSuccess, + GranteePubkeys: []string{"testPubKey1"}, + BlockNumber: 123, } + ccFlags = types.CrosschainFlags{ + IsInboundEnabled: true, + IsOutboundEnabled: true, + GasPriceIncreaseFlags: nil, + } + ttsPubKey = "tssPubKeyTest" + ) - // create AppContext with 0 chain id - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) + testCfg.BitcoinConfig.RPCUsername = "abc" - // assert btc chain params - chain, btcChainParams, btcChainParamsFound := appContext.GetBTCChainParams() - require.Equal(t, chains.Chain{}, chain) - require.False(t, btcChainParamsFound) - require.Nil(t, btcChainParams) - }) + ethParams := types.GetDefaultEthMainnetChainParams() + ethParams.IsSupported = true - t.Run("should create new AppContext with config containing evm chain params", func(t *testing.T) { - // ARRANGE - var ( - eth = chains.Ethereum.ChainId - matic = chains.Polygon.ChainId + btcParams := types.GetDefaultBtcMainnetChainParams() + btcParams.IsSupported = true - testCfg = config.New(false) + solParams := types.GetDefaultSolanaLocalnetChainParams() + solParams.IsSupported = true - ethChainParams = mocks.MockChainParams(eth, 200) - maticChainParams = mocks.MockChainParams(matic, 333) - ) + fancyL2 := chains.Chain{ + ChainId: 123, + Network: 0, + NetworkType: chains.NetworkType_mainnet, + Vm: chains.Vm_evm, + Consensus: chains.Consensus_ethereum, + IsExternal: true, + CctxGateway: 1, + } - // Given config with evm chain params (e.g. from a file) - testCfg.EVMChainConfigs = map[int64]config.EVMConfig{ - eth: {Chain: chains.Ethereum}, - matic: {Chain: chains.Polygon}, + fancyL2Params := types.GetDefaultEthMainnetChainParams() + fancyL2Params.ChainId = fancyL2.ChainId + fancyL2Params.IsSupported = true + + t.Run("Update", func(t *testing.T) { + // Given AppContext + appContext := New(testCfg, logger) + + // With expected default behavior + _, err := appContext.GetChain(123) + require.ErrorIs(t, err, ErrChainNotFound) + + require.Equal(t, testCfg, appContext.Config()) + require.Empty(t, appContext.GetKeygen()) + require.Empty(t, appContext.GetCurrentTssPubKey()) + require.Empty(t, appContext.GetCrossChainFlags()) + require.False(t, appContext.IsInboundObservationEnabled()) + require.False(t, appContext.IsOutboundObservationEnabled()) + + // Given some data that is supposed to come from ZetaCore RPC + newChains := []chains.Chain{ + chains.Ethereum, + chains.BitcoinMainnet, + chains.SolanaLocalnet, } - // And chain params from zetacore - chainParams := map[int64]*observertypes.ChainParams{ - eth: ðChainParams, - matic: &maticChainParams, + chainParams := map[int64]*types.ChainParams{ + chains.Ethereum.ChainId: ethParams, + chains.BitcoinMainnet.ChainId: btcParams, + chains.SolanaLocalnet.ChainId: solParams, + fancyL2.ChainId: fancyL2Params, } - // Given app context - appContext := context.New(testCfg, logger) - - // That was updated with chain params - appContext.Update(nil, nil, chainParams, nil, nil, "", observertypes.CrosschainFlags{}, nil, nil, false) - - // assert evm chain params - allEVMChainParams := appContext.GetAllEVMChainParams() - require.Equal(t, 2, len(allEVMChainParams)) - require.Equal(t, ðChainParams, allEVMChainParams[eth]) - require.Equal(t, &maticChainParams, allEVMChainParams[matic]) - - evmChainParams1, found := appContext.GetEVMChainParams(eth) - require.True(t, found) - require.Equal(t, ðChainParams, evmChainParams1) - - evmChainParams2, found := appContext.GetEVMChainParams(matic) - require.True(t, found) - require.Equal(t, &maticChainParams, evmChainParams2) - }) - - t.Run("should create new AppContext with config containing btc config", func(t *testing.T) { - testCfg := config.New(false) - testCfg.BitcoinConfig = config.BTCConfig{ - RPCUsername: "test username", - RPCPassword: "test password", - RPCHost: "test host", - RPCParams: "test params", + additionalChains := []chains.Chain{ + fancyL2, } - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) - }) -} -func TestAppContextUpdate(t *testing.T) { - var ( - testCfg = config.New(false) - logger = zerolog.Nop() - ) + // ACT + err = appContext.Update(keyGen, newChains, additionalChains, chainParams, ttsPubKey, ccFlags) + + // ASSERT + require.NoError(t, err) + + // Check getters + assert.Equal(t, testCfg, appContext.Config()) + assert.Equal(t, keyGen, appContext.GetKeygen()) + assert.Equal(t, ttsPubKey, appContext.GetCurrentTssPubKey()) + assert.Equal(t, ccFlags, appContext.GetCrossChainFlags()) + assert.True(t, appContext.IsInboundObservationEnabled()) + assert.True(t, appContext.IsOutboundObservationEnabled()) + + // Check ETH Chain + ethChain, err := appContext.GetChain(1) + assert.NoError(t, err) + assert.True(t, ethChain.IsEVM()) + assert.False(t, ethChain.IsUTXO()) + assert.False(t, ethChain.IsSolana()) + assert.Equal(t, ethParams, ethChain.Params()) + + // Check that fancyL2 chain is added as well + fancyL2Chain, err := appContext.GetChain(fancyL2.ChainId) + assert.NoError(t, err) + assert.True(t, fancyL2Chain.IsEVM()) + assert.Equal(t, fancyL2Params, fancyL2Chain.Params()) + + // Check chain IDs + expectedIDs := []int64{ethParams.ChainId, btcParams.ChainId, solParams.ChainId, fancyL2.ChainId} + assert.ElementsMatch(t, expectedIDs, appContext.ListChainIDs()) + + // Check config + assert.Equal(t, "abc", appContext.Config().BitcoinConfig.RPCUsername) + + t.Run("edge-cases", func(t *testing.T) { + for _, tt := range []struct { + name string + act func(*AppContext) error + assert func(*testing.T, *AppContext, error) + }{ + { + name: "trying to add non-supported chain results in an error", + act: func(a *AppContext) error { + // ASSERT + // GIven Optimism chain params from ZetaCore, but it's not supported YET + op := chains.OptimismMainnet + opParams := types.GetDefaultEthMainnetChainParams() + opParams.ChainId = op.ChainId + opParams.IsSupported = false - t.Run("should update AppContext after being created from empty config", func(t *testing.T) { - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) + chainsWithOpt := append(newChains, op) - keyGenToUpdate := observertypes.Keygen{ - Status: observertypes.KeygenStatus_KeyGenSuccess, - GranteePubkeys: []string{"testpubkey1"}, - } - enabledChainsToUpdate := []chains.Chain{ - { - ChainName: 1, - ChainId: 1, - IsExternal: true, - }, - { - ChainName: 2, - ChainId: 2, - IsExternal: true, - }, - chains.ZetaChainTestnet, - } - evmChainParamsToUpdate := map[int64]*observertypes.ChainParams{ - 1: { - ChainId: 1, - }, - 2: { - ChainId: 2, - }, - } - btcChainParamsToUpdate := &observertypes.ChainParams{ - ChainId: 3, - } - tssPubKeyToUpdate := "tsspubkeytest" - crosschainFlags := sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() - - require.NotNil(t, crosschainFlags) - appContext.Update( - &keyGenToUpdate, - enabledChainsToUpdate, - evmChainParamsToUpdate, - btcChainParamsToUpdate, - nil, - tssPubKeyToUpdate, - *crosschainFlags, - []chains.Chain{}, - verificationFlags, - false, - ) - - // assert keygen updated - keyGen := appContext.GetKeygen() - require.Equal(t, keyGenToUpdate, keyGen) - - // assert enabled chains updated - require.Equal(t, enabledChainsToUpdate, appContext.GetEnabledChains()) - - // assert enabled external chains - require.Equal(t, enabledChainsToUpdate[0:2], appContext.GetEnabledExternalChains()) - - // assert current tss pubkey updated - require.Equal(t, tssPubKeyToUpdate, appContext.GetCurrentTssPubKey()) - - // assert btc chain params still empty because they were not specified in config - chain, btcChainParams, btcChainParamsFound := appContext.GetBTCChainParams() - require.Equal(t, chains.Chain{}, chain) - require.False(t, btcChainParamsFound) - require.Nil(t, btcChainParams) - - // assert evm chain params still empty because they were not specified in config - allEVMChainParams := appContext.GetAllEVMChainParams() - require.Empty(t, allEVMChainParams) - - ccFlags := appContext.GetCrossChainFlags() - require.Equal(t, *crosschainFlags, ccFlags) - - verFlags := appContext.GetAllHeaderEnabledChains() - require.Equal(t, verificationFlags, verFlags) - }) + chainParamsWithOpt := maps.Clone(chainParams) + chainParamsWithOpt[opParams.ChainId] = opParams - t.Run( - "should update AppContext after being created from config with evm and btc chain params", - func(t *testing.T) { - testCfg := config.New(false) - testCfg.EVMChainConfigs = map[int64]config.EVMConfig{ - 1: { - Chain: chains.Chain{ - ChainName: 1, - ChainId: 1, + return a.Update(keyGen, chainsWithOpt, additionalChains, chainParamsWithOpt, ttsPubKey, ccFlags) }, - }, - 2: { - Chain: chains.Chain{ - ChainName: 2, - ChainId: 2, + assert: func(t *testing.T, a *AppContext, err error) { + assert.ErrorIs(t, err, ErrChainNotSupported) + mustBeNotFound(t, a, chains.OptimismMainnet.ChainId) }, }, - } - testCfg.BitcoinConfig = config.BTCConfig{ - RPCUsername: "test username", - RPCPassword: "test password", - RPCHost: "test host", - RPCParams: "test params", - } - - appContext := context.New(testCfg, logger) - require.NotNil(t, appContext) - - keyGenToUpdate := observertypes.Keygen{ - Status: observertypes.KeygenStatus_KeyGenSuccess, - GranteePubkeys: []string{"testpubkey1"}, - } - enabledChainsToUpdate := []chains.Chain{ { - ChainName: 1, - ChainId: 1, + name: "trying to add zetachain without chain params is allowed but skipped", + act: func(a *AppContext) error { + chainsWithZeta := append(newChains, chains.ZetaChainMainnet) + return a.Update(keyGen, chainsWithZeta, additionalChains, chainParams, ttsPubKey, ccFlags) + }, + assert: func(t *testing.T, a *AppContext, err error) { + assert.NoError(t, err) + mustBeNotFound(t, a, chains.ZetaChainMainnet.ChainId) + }, }, { - ChainName: 2, - ChainId: 2, - }, - } - evmChainParamsToUpdate := map[int64]*observertypes.ChainParams{ - 1: { - ChainId: 1, - }, - 2: { - ChainId: 2, - }, - } - - testBtcChain := chains.BitcoinTestnet - btcChainParamsToUpdate := &observertypes.ChainParams{ - ChainId: testBtcChain.ChainId, - } - tssPubKeyToUpdate := "tsspubkeytest" - crosschainFlags := sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() - require.NotNil(t, crosschainFlags) - appContext.Update( - &keyGenToUpdate, - enabledChainsToUpdate, - evmChainParamsToUpdate, - btcChainParamsToUpdate, - nil, - tssPubKeyToUpdate, - *crosschainFlags, - []chains.Chain{}, - verificationFlags, - false, - ) - - // assert keygen updated - keyGen := appContext.GetKeygen() - require.Equal(t, keyGenToUpdate, keyGen) - - // assert enabled chains updated - require.Equal(t, enabledChainsToUpdate, appContext.GetEnabledChains()) - - // assert current tss pubkey updated - require.Equal(t, tssPubKeyToUpdate, appContext.GetCurrentTssPubKey()) - - // assert btc chain params - chain, btcChainParams, btcChainParamsFound := appContext.GetBTCChainParams() - require.Equal(t, testBtcChain, chain) - require.True(t, btcChainParamsFound) - require.Equal(t, btcChainParamsToUpdate, btcChainParams) - - // assert evm chain params - allEVMChainParams := appContext.GetAllEVMChainParams() - require.Equal(t, evmChainParamsToUpdate, allEVMChainParams) - - evmChainParams1, found := appContext.GetEVMChainParams(1) - require.True(t, found) - require.Equal(t, evmChainParamsToUpdate[1], evmChainParams1) - - evmChainParams2, found := appContext.GetEVMChainParams(2) - require.True(t, found) - require.Equal(t, evmChainParamsToUpdate[2], evmChainParams2) - - ccFlags := appContext.GetCrossChainFlags() - require.Equal(t, ccFlags, *crosschainFlags) - - verFlags := appContext.GetAllHeaderEnabledChains() - require.Equal(t, verFlags, verificationFlags) - }, - ) -} - -func TestIsOutboundObservationEnabled(t *testing.T) { - // create test chain params and flags - evmChain := chains.Ethereum - ccFlags := *sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() - chainParams := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - IsSupported: true, - } - - t.Run("should return true if chain is supported and outbound flag is enabled", func(t *testing.T) { - appContext := makeAppContext(evmChain, chainParams, ccFlags, verificationFlags) + name: "trying to add zetachain with chain params results in an error", + act: func(a *AppContext) error { + zetaParams := types.GetDefaultZetaPrivnetChainParams() + zetaParams.IsSupported = true - require.True(t, appContext.IsOutboundObservationEnabled(*chainParams)) - }) - t.Run("should return false if chain is not supported yet", func(t *testing.T) { - paramsUnsupported := &observertypes.ChainParams{ChainId: evmChain.ChainId, IsSupported: false} - appContextUnsupported := makeAppContext(evmChain, paramsUnsupported, ccFlags, verificationFlags) - - require.False(t, appContextUnsupported.IsOutboundObservationEnabled(*paramsUnsupported)) - }) - t.Run("should return false if outbound flag is disabled", func(t *testing.T) { - flagsDisabled := ccFlags - flagsDisabled.IsOutboundEnabled = false - appContextDisabled := makeAppContext(evmChain, chainParams, flagsDisabled, verificationFlags) - - require.False(t, appContextDisabled.IsOutboundObservationEnabled(*chainParams)) - }) -} - -func TestIsInboundObservationEnabled(t *testing.T) { - // create test chain params and flags - evmChain := chains.Ethereum - ccFlags := *sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() - chainParams := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - IsSupported: true, - } - - t.Run("should return true if chain is supported and inbound flag is enabled", func(t *testing.T) { - appContext := makeAppContext(evmChain, chainParams, ccFlags, verificationFlags) - - require.True(t, appContext.IsInboundObservationEnabled(*chainParams)) - }) - - t.Run("should return false if chain is not supported yet", func(t *testing.T) { - paramsUnsupported := &observertypes.ChainParams{ChainId: evmChain.ChainId, IsSupported: false} - appContextUnsupported := makeAppContext(evmChain, paramsUnsupported, ccFlags, verificationFlags) - - require.False(t, appContextUnsupported.IsInboundObservationEnabled(*paramsUnsupported)) - }) - - t.Run("should return false if inbound flag is disabled", func(t *testing.T) { - flagsDisabled := ccFlags - flagsDisabled.IsInboundEnabled = false - appContextDisabled := makeAppContext(evmChain, chainParams, flagsDisabled, verificationFlags) + chainParamsWithZeta := maps.Clone(chainParams) + chainParamsWithZeta[zetaParams.ChainId] = zetaParams - require.False(t, appContextDisabled.IsInboundObservationEnabled(*chainParams)) - }) -} + chainsWithZeta := append(newChains, chains.ZetaChainPrivnet) -func TestGetBTCChainAndConfig(t *testing.T) { - logger := zerolog.Nop() - - emptyConfig := config.New(false) - nonEmptyConfig := config.New(true) - - assertEmpty := func(t *testing.T, chain chains.Chain, btcConfig config.BTCConfig, enabled bool) { - assert.Empty(t, chain) - assert.Empty(t, btcConfig) - assert.False(t, enabled) - } - - for _, tt := range []struct { - name string - cfg config.Config - setup func(app *context.AppContext) - assert func(t *testing.T, chain chains.Chain, btcConfig config.BTCConfig, enabled bool) - }{ - { - name: "no btc config", - cfg: emptyConfig, - setup: nil, - assert: assertEmpty, - }, - { - name: "btc config exists, but not chain params are set", - cfg: nonEmptyConfig, - setup: nil, - assert: assertEmpty, - }, - { - name: "btc config exists but chain is invalid", - cfg: nonEmptyConfig, - setup: func(app *context.AppContext) { - app.Update( - &observertypes.Keygen{}, - []chains.Chain{}, - nil, - &observertypes.ChainParams{ChainId: 123}, - nil, - "", - observertypes.CrosschainFlags{}, - []chains.Chain{}, - nil, - true, - ) - }, - assert: assertEmpty, - }, - { - name: "btc config exists and chain params are set", - cfg: nonEmptyConfig, - setup: func(app *context.AppContext) { - app.Update( - &observertypes.Keygen{}, - []chains.Chain{}, - nil, - &observertypes.ChainParams{ChainId: chains.BitcoinMainnet.ChainId}, - nil, - "", - observertypes.CrosschainFlags{}, - []chains.Chain{}, - nil, - true, - ) - }, - assert: func(t *testing.T, chain chains.Chain, btcConfig config.BTCConfig, enabled bool) { - assert.Equal(t, chains.BitcoinMainnet.ChainId, chain.ChainId) - assert.Equal(t, "smoketest", btcConfig.RPCUsername) - assert.True(t, enabled) - }, - }, - } { - t.Run(tt.name, func(t *testing.T) { - // ARRANGE - // Given app context - appContext := context.New(tt.cfg, logger) - - // And optional setup - if tt.setup != nil { - tt.setup(appContext) + return a.Update(keyGen, chainsWithZeta, additionalChains, chainParamsWithZeta, ttsPubKey, ccFlags) + }, + assert: func(t *testing.T, a *AppContext, err error) { + assert.ErrorIs(t, err, ErrChainNotSupported) + assert.ErrorContains(t, err, "ZetaChain itself cannot be in the registry") + mustBeNotFound(t, a, chains.ZetaChainMainnet.ChainId) + }, + }, + { + name: "trying to add new chainParams without chain results in an error", + act: func(a *AppContext) error { + // ASSERT + // Given polygon chain params WITHOUT the chain itself + maticParams := types.GetDefaultMumbaiTestnetChainParams() + maticParams.ChainId = chains.Polygon.ChainId + maticParams.IsSupported = true + + updatedChainParams := maps.Clone(chainParams) + updatedChainParams[maticParams.ChainId] = maticParams + + return a.Update(keyGen, newChains, additionalChains, updatedChainParams, ttsPubKey, ccFlags) + }, + assert: func(t *testing.T, a *AppContext, err error) { + assert.ErrorContains(t, err, "unable to locate fresh chain 137 based on chain params") + mustBeNotFound(t, a, chains.Polygon.ChainId) + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + // ACT + errAct := tt.act(appContext) + + // ASSERT + require.NotNil(t, tt.assert) + tt.assert(t, appContext, errAct) + }) } - - // ACT - chain, btcConfig, enabled := appContext.GetBTCChainAndConfig() - - // ASSERT - tt.assert(t, chain, btcConfig, enabled) }) - } -} - -func TestGetBlockHeaderEnabledChains(t *testing.T) { - // ARRANGE - // Given app config - appContext := context.New(config.New(false), zerolog.Nop()) - - // That was eventually updated - appContext.Update( - &observertypes.Keygen{}, - []chains.Chain{}, - nil, - &observertypes.ChainParams{ChainId: chains.BitcoinMainnet.ChainId}, - nil, - "", - observertypes.CrosschainFlags{}, - []chains.Chain{}, - []lightclienttypes.HeaderSupportedChain{ - {ChainId: 1, Enabled: true}, - }, - true, - ) - - // ACT #1 (found) - chain, found := appContext.GetBlockHeaderEnabledChains(1) - - // ASSERT #1 - assert.True(t, found) - assert.Equal(t, int64(1), chain.ChainId) - assert.True(t, chain.Enabled) - - // ACT #2 (not found) - chain, found = appContext.GetBlockHeaderEnabledChains(2) - - // ASSERT #2 - assert.False(t, found) - assert.Empty(t, chain) -} - -func TestGetAdditionalChains(t *testing.T) { - // ARRANGE - // Given app config - appContext := context.New(config.New(false), zerolog.Nop()) - - additionalChains := []chains.Chain{ - sample.Chain(1), - sample.Chain(2), - sample.Chain(3), - } - - // That was eventually updated - appContext.Update( - &observertypes.Keygen{}, - []chains.Chain{}, - nil, - &observertypes.ChainParams{}, - nil, - "", - observertypes.CrosschainFlags{}, - additionalChains, - []lightclienttypes.HeaderSupportedChain{ - {ChainId: 1, Enabled: true}, - }, - true, - ) - - // ACT - found := appContext.GetAdditionalChains() - - // ASSERT - assert.EqualValues(t, additionalChains, found) + }) } -func makeAppContext( - evmChain chains.Chain, - evmChainParams *observertypes.ChainParams, - ccFlags observertypes.CrosschainFlags, - headerSupportedChains []lightclienttypes.HeaderSupportedChain, -) *context.AppContext { - // create config - cfg := config.New(false) - logger := zerolog.Nop() - cfg.EVMChainConfigs[evmChain.ChainId] = config.EVMConfig{ - Chain: evmChain, - } - - // create AppContext - appContext := context.New(cfg, logger) - evmChainParamsMap := make(map[int64]*observertypes.ChainParams) - evmChainParamsMap[evmChain.ChainId] = evmChainParams - - // feed chain params - appContext.Update( - &observertypes.Keygen{}, - []chains.Chain{evmChain}, - evmChainParamsMap, - nil, - nil, - "", - ccFlags, - []chains.Chain{}, - headerSupportedChains, - true, - ) - - return appContext +func mustBeNotFound(t *testing.T, a *AppContext, chainID int64) { + t.Helper() + _, err := a.GetChain(chainID) + require.ErrorIs(t, err, ErrChainNotFound) } diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go index a2baa0389b..25f9925bd1 100644 --- a/zetaclient/context/chain.go +++ b/zetaclient/context/chain.go @@ -13,9 +13,13 @@ import ( // ChainRegistry is a registry of supported chains type ChainRegistry struct { - chains map[int64]Chain + chains map[int64]Chain + + // additionalChains is a list of additional static chain information to use when searching from + // chain IDs. It's stored in the protocol to dynamically support new chains without doing an upgrade additionalChains []chains.Chain - mu sync.Mutex + + mu sync.Mutex } // Chain represents chain with its parameters @@ -49,10 +53,10 @@ func (cr *ChainRegistry) Get(chainID int64) (Chain, error) { return chain, nil } -// Set sets a chain in the registry. Note that chain must be SUPPORTED and NOT ZetaChain itself -// otherwise it will return ErrChainNotSupported +// Set sets a chain in the registry. +// A chain must be SUPPORTED and NOT ZetaChain itself; otherwise returns ErrChainNotSupported func (cr *ChainRegistry) Set(chainID int64, chain *chains.Chain, params *observer.ChainParams) error { - item, err := newChain(chainID, chain, params) + item, err := newChain(cr, chainID, chain, params) if err != nil { return err } @@ -67,7 +71,6 @@ func (cr *ChainRegistry) Set(chainID int64, chain *chains.Chain, params *observe return nil } -// SetAdditionalChains sets additional chains func (cr *ChainRegistry) SetAdditionalChains(chains []chains.Chain) { cr.mu.Lock() defer cr.mu.Unlock() @@ -99,7 +102,7 @@ func (cr *ChainRegistry) ChainIDs() []int64 { return maps.Keys(cr.chains) } -func newChain(chainID int64, chain *chains.Chain, params *observer.ChainParams) (Chain, error) { +func newChain(cr *ChainRegistry, chainID int64, chain *chains.Chain, params *observer.ChainParams) (Chain, error) { switch { case chainID < 1: return Chain{}, fmt.Errorf("invalid chain id %d", chainID) @@ -113,14 +116,15 @@ func newChain(chainID int64, chain *chains.Chain, params *observer.ChainParams) return Chain{}, fmt.Errorf("chain id %d does not match params.ChainId %d", chainID, params.ChainId) case !params.IsSupported: return Chain{}, ErrChainNotSupported - case chains.IsZetaChain(chainID, nil): + case chains.IsZetaChain(chainID, nil) || !chain.IsExternal: return Chain{}, errors.Wrap(ErrChainNotSupported, "ZetaChain itself cannot be in the registry") } return Chain{ - id: chainID, - chain: chain, - params: params, + id: chainID, + chain: chain, + params: params, + registry: cr, }, nil } From 0474abd7689209bb32309a997d2c2655cbd75140 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 26 Jul 2024 19:12:23 +0200 Subject: [PATCH 03/24] Drop `supplychecker` --- zetaclient/supplychecker/logger.go | 31 -- zetaclient/supplychecker/validate.go | 34 --- .../supplychecker/zeta_supply_checker.go | 280 ------------------ .../supplychecker/zeta_supply_checker_test.go | 61 ---- 4 files changed, 406 deletions(-) delete mode 100644 zetaclient/supplychecker/logger.go delete mode 100644 zetaclient/supplychecker/validate.go delete mode 100644 zetaclient/supplychecker/zeta_supply_checker.go delete mode 100644 zetaclient/supplychecker/zeta_supply_checker_test.go diff --git a/zetaclient/supplychecker/logger.go b/zetaclient/supplychecker/logger.go deleted file mode 100644 index 89da0300d6..0000000000 --- a/zetaclient/supplychecker/logger.go +++ /dev/null @@ -1,31 +0,0 @@ -package supplychecker - -import ( - sdkmath "cosmossdk.io/math" - "github.com/rs/zerolog" - - "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" -) - -// ZetaSupplyCheckLogs is a struct to log the output of the ZetaSupplyChecker -type ZetaSupplyCheckLogs struct { - Logger zerolog.Logger - AbortedTxAmounts sdkmath.Int `json:"aborted_tx_amounts"` - ZetaInTransit sdkmath.Int `json:"zeta_in_transit"` - ExternalChainTotalSupply sdkmath.Int `json:"external_chain_total_supply"` - ZetaTokenSupplyOnNode sdkmath.Int `json:"zeta_token_supply_on_node"` - EthLockedAmount sdkmath.Int `json:"eth_locked_amount"` - NodeAmounts sdkmath.Int `json:"node_amounts"` - LHS sdkmath.Int `json:"LHS"` - RHS sdkmath.Int `json:"RHS"` - SupplyCheckSuccess bool `json:"supply_check_success"` -} - -// LogOutput logs the output of the ZetaSupplyChecker -func (z ZetaSupplyCheckLogs) LogOutput() { - output, err := bitcoin.PrettyPrintStruct(z) - if err != nil { - z.Logger.Error().Err(err).Msgf("error pretty printing struct") - } - z.Logger.Info().Msgf(output) -} diff --git a/zetaclient/supplychecker/validate.go b/zetaclient/supplychecker/validate.go deleted file mode 100644 index f9e4dbaf79..0000000000 --- a/zetaclient/supplychecker/validate.go +++ /dev/null @@ -1,34 +0,0 @@ -package supplychecker - -import ( - sdkmath "cosmossdk.io/math" - "github.com/rs/zerolog" -) - -// ValidateZetaSupply validates the zeta supply from the checked values -func ValidateZetaSupply( - logger zerolog.Logger, - abortedTxAmounts, zetaInTransit, genesisAmounts, externalChainTotalSupply, zetaTokenSupplyOnNode, ethLockedAmount sdkmath.Int, -) bool { - lhs := ethLockedAmount.Sub(abortedTxAmounts) - rhs := zetaTokenSupplyOnNode.Add(zetaInTransit).Add(externalChainTotalSupply).Sub(genesisAmounts) - - copyZetaTokenSupplyOnNode := zetaTokenSupplyOnNode - copyGenesisAmounts := genesisAmounts - nodeAmounts := copyZetaTokenSupplyOnNode.Sub(copyGenesisAmounts) - logs := ZetaSupplyCheckLogs{ - Logger: logger, - AbortedTxAmounts: abortedTxAmounts, - ZetaInTransit: zetaInTransit, - ExternalChainTotalSupply: externalChainTotalSupply, - NodeAmounts: nodeAmounts, - ZetaTokenSupplyOnNode: zetaTokenSupplyOnNode, - EthLockedAmount: ethLockedAmount, - LHS: lhs, - RHS: rhs, - } - defer logs.LogOutput() - - logs.SupplyCheckSuccess = lhs.Equal(rhs) - return logs.SupplyCheckSuccess -} diff --git a/zetaclient/supplychecker/zeta_supply_checker.go b/zetaclient/supplychecker/zeta_supply_checker.go deleted file mode 100644 index 53a61c707b..0000000000 --- a/zetaclient/supplychecker/zeta_supply_checker.go +++ /dev/null @@ -1,280 +0,0 @@ -// Package supplychecker provides functionalities to check the total supply of Zeta tokens -// Currently not used in the codebase -package supplychecker - -import ( - "context" - "fmt" - - sdkmath "cosmossdk.io/math" - ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/pkg/errors" - "github.com/rs/zerolog" - - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/coin" - "github.com/zeta-chain/zetacore/x/crosschain/types" - "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" - "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" - zctx "github.com/zeta-chain/zetacore/zetaclient/context" - clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" - "github.com/zeta-chain/zetacore/zetaclient/zetacore" -) - -// ZetaSupplyChecker is a utility to check the total supply of Zeta tokens -type ZetaSupplyChecker struct { - evmClient map[int64]*ethclient.Client - zetaClient *zetacore.Client - ticker *clienttypes.DynamicTicker - stop chan struct{} - logger zerolog.Logger - externalEvmChain []chains.Chain - ethereumChain chains.Chain - genesisSupply sdkmath.Int -} - -// NewZetaSupplyChecker creates a new ZetaSupplyChecker -func NewZetaSupplyChecker( - ctx context.Context, - zetaClient *zetacore.Client, - logger zerolog.Logger, -) (*ZetaSupplyChecker, error) { - dynamicTicker, err := clienttypes.NewDynamicTicker("ZETASupplyTicker", 15) - if err != nil { - return nil, err - } - - app, err := zctx.FromContext(ctx) - if err != nil { - return nil, err - } - - zetaSupplyChecker := &ZetaSupplyChecker{ - stop: make(chan struct{}), - ticker: dynamicTicker, - evmClient: make(map[int64]*ethclient.Client), - logger: logger.With(). - Str("module", "ZetaSupplyChecker"). - Logger(), - zetaClient: zetaClient, - } - - for _, evmConfig := range app.Config().GetAllEVMConfigs() { - if evmConfig.Chain.IsZetaChain() { - continue - } - client, err := ethclient.Dial(evmConfig.Endpoint) - if err != nil { - return nil, err - } - - zetaSupplyChecker.evmClient[evmConfig.Chain.ChainId] = client - } - - for chainID := range zetaSupplyChecker.evmClient { - chain, found := chains.GetChainFromChainID(chainID, app.GetAdditionalChains()) - if !found { - return zetaSupplyChecker, fmt.Errorf("chain not found for chain id %d", chainID) - } - if chain.IsExternalChain() && chain.IsEVMChain() && - chain.Network != chains.Network_eth { - zetaSupplyChecker.externalEvmChain = append(zetaSupplyChecker.externalEvmChain, chain) - } else { - zetaSupplyChecker.ethereumChain = chain - } - } - - balances, err := zetaSupplyChecker.zetaClient.GetGenesisSupply(ctx) - if err != nil { - return nil, err - } - - tokensMintedAtBeginBlock, ok := sdkmath.NewIntFromString("200000000000000000") - if !ok { - return nil, fmt.Errorf("error parsing tokens minted at begin block") - } - - zetaSupplyChecker.genesisSupply = balances.Add(tokensMintedAtBeginBlock) - - logger.Info(). - Msgf("zeta supply checker initialized , external chains : %v ,ethereum chain :%v", zetaSupplyChecker.externalEvmChain, zetaSupplyChecker.ethereumChain) - - return zetaSupplyChecker, nil -} - -// Start starts the ZetaSupplyChecker -func (zs *ZetaSupplyChecker) Start(ctx context.Context) { - defer zs.ticker.Stop() - for { - select { - case <-zs.ticker.C(): - err := zs.CheckZetaTokenSupply(ctx) - if err != nil { - zs.logger.Error().Err(err).Msgf("ZetaSupplyChecker error") - } - case <-zs.stop: - return - } - } -} - -// Stop stops the ZetaSupplyChecker -func (zs *ZetaSupplyChecker) Stop() { - zs.logger.Info().Msgf("ZetaSupplyChecker is stopping") - close(zs.stop) -} - -// CheckZetaTokenSupply checks the total supply of Zeta tokens -func (zs *ZetaSupplyChecker) CheckZetaTokenSupply(ctx context.Context) error { - app, err := zctx.FromContext(ctx) - if err != nil { - return err - } - - externalChainTotalSupply := sdkmath.ZeroInt() - for _, chain := range zs.externalEvmChain { - externalEvmChainParams, ok := app.GetEVMChainParams(chain.ChainId) - if !ok { - return fmt.Errorf("externalEvmChainParams not found for chain id %d", chain.ChainId) - } - - zetaTokenAddressString := externalEvmChainParams.ZetaTokenContractAddress - zetaTokenAddress := ethcommon.HexToAddress(zetaTokenAddressString) - zetatokenNonEth, err := observer.FetchZetaTokenContract(zetaTokenAddress, zs.evmClient[chain.ChainId]) - if err != nil { - return err - } - - totalSupply, err := zetatokenNonEth.TotalSupply(nil) - if err != nil { - return err - } - - totalSupplyInt, ok := sdkmath.NewIntFromString(totalSupply.String()) - if !ok { - zs.logger.Error().Msgf("error parsing total supply for chain %d", chain.ChainId) - continue - } - - externalChainTotalSupply = externalChainTotalSupply.Add(totalSupplyInt) - } - - evmChainParams, ok := app.GetEVMChainParams(zs.ethereumChain.ChainId) - if !ok { - return fmt.Errorf("eth config not found for chain id %d", zs.ethereumChain.ChainId) - } - - ethConnectorAddressString := evmChainParams.ConnectorContractAddress - ethConnectorAddress := ethcommon.HexToAddress(ethConnectorAddressString) - ethConnectorContract, err := observer.FetchConnectorContractEth( - ethConnectorAddress, - zs.evmClient[zs.ethereumChain.ChainId], - ) - if err != nil { - return err - } - - ethLockedAmount, err := ethConnectorContract.GetLockedAmount(nil) - if err != nil { - return err - } - - ethLockedAmountInt, ok := sdkmath.NewIntFromString(ethLockedAmount.String()) - if !ok { - return fmt.Errorf("error parsing eth locked amount") - } - - zetaInTransit, err := zs.GetAmountOfZetaInTransit(ctx) - if err != nil { - return err - } - zetaTokenSupplyOnNode, err := zs.zetaClient.GetZetaTokenSupplyOnNode(ctx) - if err != nil { - return err - } - - abortedAmount, err := zs.AbortedTxAmount(ctx) - if err != nil { - return err - } - - ValidateZetaSupply( - zs.logger, - abortedAmount, - zetaInTransit, - zs.genesisSupply, - externalChainTotalSupply, - zetaTokenSupplyOnNode, - ethLockedAmountInt, - ) - - return nil -} - -// AbortedTxAmount returns the amount of Zeta tokens in aborted transactions -func (zs *ZetaSupplyChecker) AbortedTxAmount(ctx context.Context) (sdkmath.Int, error) { - amount, err := zs.zetaClient.GetAbortedZetaAmount(ctx) - if err != nil { - return sdkmath.ZeroInt(), errors.Wrap(err, "error getting aborted zeta amount") - } - amountInt, ok := sdkmath.NewIntFromString(amount) - if !ok { - return sdkmath.ZeroInt(), errors.New("error parsing aborted zeta amount") - } - return amountInt, nil -} - -// GetAmountOfZetaInTransit returns the amount of Zeta tokens in transit -func (zs *ZetaSupplyChecker) GetAmountOfZetaInTransit(ctx context.Context) (sdkmath.Int, error) { - chainsToCheck := make([]chains.Chain, len(zs.externalEvmChain)+1) - chainsToCheck = append(append(chainsToCheck, zs.externalEvmChain...), zs.ethereumChain) - cctxs := zs.GetPendingCCTXInTransit(ctx, chainsToCheck) - amount := sdkmath.ZeroUint() - - for _, cctx := range cctxs { - amount = amount.Add(cctx.GetCurrentOutboundParam().Amount) - } - amountInt, ok := sdkmath.NewIntFromString(amount.String()) - if !ok { - return sdkmath.ZeroInt(), fmt.Errorf("error parsing amount %s", amount.String()) - } - - return amountInt, nil -} - -// GetPendingCCTXInTransit returns the pending CCTX in transit -func (zs *ZetaSupplyChecker) GetPendingCCTXInTransit( - ctx context.Context, - receivingChains []chains.Chain, -) []*types.CrossChainTx { - cctxInTransit := make([]*types.CrossChainTx, 0) - for _, chain := range receivingChains { - cctx, _, err := zs.zetaClient.ListPendingCCTX(ctx, chain.ChainId) - if err != nil { - continue - } - nonceToCctxMap := make(map[uint64]*types.CrossChainTx) - for _, c := range cctx { - if c.InboundParams.CoinType == coin.CoinType_Zeta { - nonceToCctxMap[c.GetCurrentOutboundParam().TssNonce] = c - } - } - - trackers, err := zs.zetaClient.GetAllOutboundTrackerByChain(ctx, chain.ChainId, interfaces.Ascending) - if err != nil { - continue - } - for _, tracker := range trackers { - zs.logger.Info().Msgf("tracker exists for nonce: %d , removing from supply checks", tracker.Nonce) - delete(nonceToCctxMap, tracker.Nonce) - } - for _, c := range nonceToCctxMap { - if c != nil { - cctxInTransit = append(cctxInTransit, c) - } - } - } - - return cctxInTransit -} diff --git a/zetaclient/supplychecker/zeta_supply_checker_test.go b/zetaclient/supplychecker/zeta_supply_checker_test.go deleted file mode 100644 index ed984de2d8..0000000000 --- a/zetaclient/supplychecker/zeta_supply_checker_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package supplychecker - -import ( - "os" - "testing" - - sdkmath "cosmossdk.io/math" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" -) - -func MustNewIntFromString(t *testing.T, val string) sdkmath.Int { - v, ok := sdkmath.NewIntFromString(val) - require.True(t, ok) - return v -} -func TestZetaSupplyChecker_ValidateZetaSupply(t *testing.T) { - tt := []struct { - name string - abortedTxAmount sdkmath.Int - zetaInTransit sdkmath.Int - genesisAmounts sdkmath.Int - externalChainTotalSupply sdkmath.Int - zetaTokenSupplyOnNode sdkmath.Int - ethLockedAmount sdkmath.Int - validate require.BoolAssertionFunc - }{ - { - name: "1 zeta cctx in progress", - abortedTxAmount: MustNewIntFromString(t, "0"), - zetaInTransit: MustNewIntFromString(t, "1000000000000000000"), - externalChainTotalSupply: MustNewIntFromString(t, "9000000000000000000"), - genesisAmounts: MustNewIntFromString(t, "1000000000000000000"), - zetaTokenSupplyOnNode: MustNewIntFromString(t, "1000000000000000000"), - ethLockedAmount: MustNewIntFromString(t, "10000000000000000000"), - validate: func(t require.TestingT, b bool, i ...interface{}) { - require.True(t, b, i...) - }, - }, - // Todo add more scenarios - //https://github.com/zeta-chain/node/issues/1375 - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - logger := zerolog.New(os.Stdout).With().Timestamp().Logger() - tc.validate( - t, - ValidateZetaSupply( - logger, - tc.abortedTxAmount, - tc.zetaInTransit, - tc.genesisAmounts, - tc.externalChainTotalSupply, - tc.zetaTokenSupplyOnNode, - tc.ethLockedAmount, - ), - ) - }) - } -} From 3d6e05f49aaabce51210c6b20bfe62a158a65b2a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 29 Jul 2024 15:21:54 +0200 Subject: [PATCH 04/24] Refactor app ctx Update worker --- cmd/zetaclientd/debug.go | 94 +++++++-------- cmd/zetaclientd/start.go | 17 ++- zetaclient/context/app.go | 22 ++++ zetaclient/context/chain.go | 21 ++++ zetaclient/zetacore/client.go | 115 +++++++++---------- zetaclient/zetacore/client_query_observer.go | 12 +- zetaclient/zetacore/client_worker.go | 2 +- 7 files changed, 160 insertions(+), 123 deletions(-) diff --git a/cmd/zetaclientd/debug.go b/cmd/zetaclientd/debug.go index d28f5cb898..dc0b9f868f 100644 --- a/cmd/zetaclientd/debug.go +++ b/cmd/zetaclientd/debug.go @@ -3,9 +3,11 @@ package main import ( "context" "fmt" + "os" "strconv" "strings" + "cosmossdk.io/errors" "github.com/btcsuite/btcd/rpcclient" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" @@ -14,10 +16,8 @@ import ( "github.com/rs/zerolog" "github.com/spf13/cobra" - "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/testutil/sample" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" btcobserver "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" evmobserver "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" "github.com/zeta-chain/zetacore/zetaclient/config" @@ -34,12 +34,16 @@ type debugArguments struct { zetaChainID string } +var homeDir = os.ExpandEnv("$HOME/.zetacored") + func init() { - RootCmd.AddCommand(DebugCmd()) - DebugCmd().Flags(). - StringVar(&debugArgs.zetaCoreHome, "core-home", "/Users/tanmay/.zetacored", "peer address, e.g. /dns/tss1/tcp/6668/ipfs/16Uiu2HAmACG5DtqmQsHtXg4G2sLS65ttv84e7MrL4kapkjfmhxAp") - DebugCmd().Flags().StringVar(&debugArgs.zetaNode, "node", "46.4.15.110", "public ip address") - DebugCmd().Flags().StringVar(&debugArgs.zetaChainID, "chain-id", "athens_7001-1", "pre-params file path") + cmd := DebugCmd() + + cmd.Flags().StringVar(&debugArgs.zetaCoreHome, "core-home", homeDir, "zetecore home directory") + cmd.Flags().StringVar(&debugArgs.zetaNode, "node", "46.4.15.110", "public ip address") + cmd.Flags().StringVar(&debugArgs.zetaChainID, "chain-id", "athens_7001-1", "pre-params file path") + + RootCmd.AddCommand(cmd) } func DebugCmd() *cobra.Command { @@ -54,20 +58,16 @@ func debugCmd(_ *cobra.Command, args []string) error { cobra.ExactArgs(2) cfg, err := config.Load(debugArgs.zetaCoreHome) if err != nil { - return err + return errors.Wrap(err, "failed to load config") } - appContext := zctx.New(cfg, zerolog.Nop()) - ctx := zctx.WithAppContext(context.Background(), appContext) + inboundHash := args[0] chainID, err := strconv.ParseInt(args[1], 10, 64) if err != nil { - return err + return errors.Wrap(err, "failed to parse chain id") } - inboundHash := args[0] - var ballotIdentifier string - // create a new zetacore client client, err := zetacore.NewClient( &keys.Keys{OperatorAddress: sdk.MustAccAddressFromBech32(sample.AccAddress())}, @@ -80,21 +80,30 @@ func debugCmd(_ *cobra.Command, args []string) error { if err != nil { return err } - chainParams, err := client.GetChainParams(ctx) - if err != nil { - return err + + appContext := zctx.New(cfg, zerolog.Nop()) + ctx := zctx.WithAppContext(context.Background(), appContext) + + if err := client.UpdateAppContext(ctx, appContext, zerolog.Nop()); err != nil { + return errors.Wrap(err, "failed to update app context") } + + var ballotIdentifier string + tssEthAddress, err := client.GetEVMTSSAddress(ctx) if err != nil { return err } - chain, found := chains.GetChainFromChainID(chainID, appContext.GetAdditionalChains()) - if !found { - return fmt.Errorf("invalid chain id") + + chain, err := appContext.GetChain(chainID) + if err != nil { + return err } + chainProto := chain.RawChain() + // get ballot identifier according to the chain type - if chains.IsEVMChain(chain.ChainId, appContext.GetAdditionalChains()) { + if chain.IsEVM() { evmObserver := evmobserver.Observer{} evmObserver.WithZetacoreClient(client) var ethRPC *ethrpc.EthRPC @@ -109,43 +118,34 @@ func debugCmd(_ *cobra.Command, args []string) error { } evmObserver.WithEvmClient(client) evmObserver.WithEvmJSONRPC(ethRPC) - evmObserver.WithChain(chain) + evmObserver.WithChain(*chainProto) } } hash := ethcommon.HexToHash(inboundHash) tx, isPending, err := evmObserver.TransactionByHash(inboundHash) if err != nil { - return fmt.Errorf("tx not found on chain %s , %d", err.Error(), chain.ChainId) + return fmt.Errorf("tx not found on chain %s, %d", err.Error(), chain.ID()) } + if isPending { return fmt.Errorf("tx is still pending") } + receipt, err := client.TransactionReceipt(context.Background(), hash) if err != nil { - return fmt.Errorf("tx receipt not found on chain %s, %d", err.Error(), chain.ChainId) + return fmt.Errorf("tx receipt not found on chain %s, %d", err.Error(), chain.ID()) } - for _, chainParams := range chainParams { - if chainParams.ChainId == chainID { - evmObserver.SetChainParams(observertypes.ChainParams{ - ChainId: chainID, - ConnectorContractAddress: chainParams.ConnectorContractAddress, - ZetaTokenContractAddress: chainParams.ZetaTokenContractAddress, - Erc20CustodyContractAddress: chainParams.Erc20CustodyContractAddress, - }) - evmChainParams, found := appContext.GetEVMChainParams(chainID) - if !found { - return fmt.Errorf("missing chain params for chain %d", chainID) - } - evmChainParams.ZetaTokenContractAddress = chainParams.ZetaTokenContractAddress - if strings.EqualFold(tx.To, chainParams.ConnectorContractAddress) { - coinType = coin.CoinType_Zeta - } else if strings.EqualFold(tx.To, chainParams.Erc20CustodyContractAddress) { - coinType = coin.CoinType_ERC20 - } else if strings.EqualFold(tx.To, tssEthAddress) { - coinType = coin.CoinType_Gas - } - } + params := chain.Params() + + evmObserver.SetChainParams(*params) + + if strings.EqualFold(tx.To, params.ConnectorContractAddress) { + coinType = coin.CoinType_Zeta + } else if strings.EqualFold(tx.To, params.Erc20CustodyContractAddress) { + coinType = coin.CoinType_ERC20 + } else if strings.EqualFold(tx.To, tssEthAddress) { + coinType = coin.CoinType_Gas } switch coinType { @@ -170,10 +170,10 @@ func debugCmd(_ *cobra.Command, args []string) error { fmt.Println("CoinType not detected") } fmt.Println("CoinType : ", coinType) - } else if chains.IsBitcoinChain(chain.ChainId, appContext.GetAdditionalChains()) { + } else if chain.IsUTXO() { btcObserver := btcobserver.Observer{} btcObserver.WithZetacoreClient(client) - btcObserver.WithChain(chain) + btcObserver.WithChain(*chainProto) connCfg := &rpcclient.ConnConfig{ Host: cfg.BitcoinConfig.RPCHost, User: cfg.BitcoinConfig.RPCUsername, diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index bbec0723f4..56a779141e 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -21,7 +21,6 @@ import ( "github.com/spf13/cobra" "github.com/zeta-chain/zetacore/pkg/authz" - "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/constant" observerTypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" @@ -143,11 +142,11 @@ func start(_ *cobra.Command, _ []string) error { startLogger.Debug().Msgf("CreateAuthzSigner is ready") // Initialize core parameters from zetacore - err = zetacoreClient.UpdateAppContext(ctx, appContext, true, startLogger) - if err != nil { + if err = zetacoreClient.UpdateAppContext(ctx, appContext, startLogger); err != nil { startLogger.Error().Err(err).Msg("Error getting core parameters") return err } + startLogger.Info().Msgf("Config is updated from zetacore %s", maskCfg(cfg)) go zetacoreClient.UpdateAppContextWorker(ctx, appContext) @@ -214,16 +213,16 @@ func start(_ *cobra.Command, _ []string) error { return err } - bitcoinChainID := chains.BitcoinRegtest.ChainId - btcChain, _, btcEnabled := appContext.GetBTCChainAndConfig() - if btcEnabled { - bitcoinChainID = btcChain.ChainId + btcChain, err := appContext.FirstChain(zctx.Chain.IsUTXO) + if err != nil { + return errors.Wrap(err, "unable to find BTC chain") } + tss, err := mc.NewTSS( ctx, zetacoreClient, tssHistoricalList, - bitcoinChainID, + btcChain.ID(), hotkeyPass, server, ) @@ -266,7 +265,7 @@ func start(_ *cobra.Command, _ []string) error { } startLogger.Info(). Msgf("Current TSS address \n ETH : %s \n BTC : %s \n PubKey : %s ", tss.EVMAddress(), tss.BTCAddress(), tss.CurrentPubkey) - if len(appContext.GetEnabledChains()) == 0 { + if len(appContext.ListChainIDs()) == 0 { startLogger.Error().Msgf("No chains enabled in updated config %s ", cfg.String()) } diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index f57f3c436c..14535f8a7c 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -60,6 +60,28 @@ func (a *AppContext) ListChainIDs() []int64 { return a.chainRegistry.ChainIDs() } +func (a *AppContext) ListChains() []Chain { + return a.chainRegistry.All() +} + +// FirstChain returns the first chain that satisfies the filter +func (a *AppContext) FirstChain(filter func(Chain) bool) (Chain, error) { + ids := a.ListChainIDs() + + for _, id := range ids { + chain, err := a.GetChain(id) + if err != nil { + return Chain{}, errors.Wrapf(err, "unable to get chain %d", id) + } + + if filter(chain) { + return chain, nil + } + } + + return Chain{}, errors.Wrap(ErrChainNotFound, "no chain satisfies the filter") +} + // IsOutboundObservationEnabled returns true if outbound flag is enabled func (a *AppContext) IsOutboundObservationEnabled() bool { return a.GetCrossChainFlags().IsOutboundEnabled diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go index 25f9925bd1..1b65e7b5c4 100644 --- a/zetaclient/context/chain.go +++ b/zetaclient/context/chain.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "github.com/zeta-chain/zetacore/pkg/chains" observer "github.com/zeta-chain/zetacore/x/observer/types" @@ -44,6 +45,7 @@ func NewChainRegistry() *ChainRegistry { } } +// Get returns a chain by ID. func (cr *ChainRegistry) Get(chainID int64) (Chain, error) { chain, ok := cr.chains[chainID] if !ok { @@ -53,6 +55,15 @@ func (cr *ChainRegistry) Get(chainID int64) (Chain, error) { return chain, nil } +// All returns all chains in the registry sorted by chain ID. +func (cr *ChainRegistry) All() []Chain { + items := maps.Values(cr.chains) + + slices.SortFunc(items, func(a, b Chain) bool { return a.id < b.id }) + + return items +} + // Set sets a chain in the registry. // A chain must be SUPPORTED and NOT ZetaChain itself; otherwise returns ErrChainNotSupported func (cr *ChainRegistry) Set(chainID int64, chain *chains.Chain, params *observer.ChainParams) error { @@ -71,6 +82,7 @@ func (cr *ChainRegistry) Set(chainID int64, chain *chains.Chain, params *observe return nil } +// SetAdditionalChains sets additional chains to the registry func (cr *ChainRegistry) SetAdditionalChains(chains []chains.Chain) { cr.mu.Lock() defer cr.mu.Unlock() @@ -128,10 +140,19 @@ func newChain(cr *ChainRegistry, chainID int64, chain *chains.Chain, params *obs }, nil } +func (c Chain) ID() int64 { + return c.id +} + func (c Chain) Params() *observer.ChainParams { return c.params } +// RawChain returns the underlying Chain object. Better not to use this method +func (c Chain) RawChain() *chains.Chain { + return c.chain +} + func (c Chain) IsEVM() bool { return chains.IsEVMChain(c.id, c.registry.additionalChains) } diff --git a/zetaclient/zetacore/client.go b/zetaclient/zetacore/client.go index 8789dce26b..6be07983dc 100644 --- a/zetaclient/zetacore/client.go +++ b/zetaclient/zetacore/client.go @@ -341,20 +341,15 @@ func (c *Client) WaitForZetacoreToCreateBlocks(ctx context.Context) error { // UpdateAppContext updates zctx.AppContext // zetacore stores AppContext for all clients -func (c *Client) UpdateAppContext( - ctx context.Context, - appContext *zctx.AppContext, - init bool, - sampledLogger zerolog.Logger, -) error { +func (c *Client) UpdateAppContext(ctx context.Context, appContext *zctx.AppContext, logger zerolog.Logger) error { bn, err := c.GetBlockHeight(ctx) if err != nil { - return fmt.Errorf("failed to get zetablock height: %w", err) + return errors.Wrap(err, "unable to get zetablock height") } plan, err := c.GetUpgradePlan(ctx) if err != nil { - return fmt.Errorf("failed to get upgrade plan: %w", err) + return errors.Wrap(err, "unable to get upgrade plan") } // Stop client and notify dependant services to stop (Orchestrator, Observers, and Signers) @@ -367,87 +362,83 @@ func (c *Client) UpdateAppContext( ) c.Stop() - } - additionalChains, err := c.GetAdditionalChains(ctx) - if err != nil { - return fmt.Errorf("failed to additional chains: %w", err) + return nil } - chainParams, err := c.GetChainParams(ctx) + supportedChains, err := c.GetSupportedChains(ctx) if err != nil { - return fmt.Errorf("failed to get chain params: %w", err) + return errors.Wrap(err, "unable to fetch supported chains") } - newEVMParams := make(map[int64]*observertypes.ChainParams) - var newBTCParams *observertypes.ChainParams - var newSolanaParams *observertypes.ChainParams - - // check and update chain params for each chain - for _, chainParam := range chainParams { - err := observertypes.ValidateChainParams(chainParam) - if err != nil { - sampledLogger.Warn().Err(err).Msgf("Invalid chain params for chain %d", chainParam.ChainId) - continue - } - if chains.IsBitcoinChain(chainParam.ChainId, additionalChains) { - newBTCParams = chainParam - } else if chains.IsSolanaChain(chainParam.ChainId, additionalChains) { - newSolanaParams = chainParam - } else if chains.IsEVMChain(chainParam.ChainId, additionalChains) { - newEVMParams[chainParam.ChainId] = chainParam - } + additionalChains, err := c.GetAdditionalChains(ctx) + if err != nil { + return errors.Wrap(err, "unable to fetch additional chains") } - supportedChains, err := c.GetSupportedChains(ctx) + chainParams, err := c.GetChainParams(ctx) if err != nil { - return fmt.Errorf("failed to get supported chains: %w", err) + return errors.Wrap(err, "unable to fetch chain params") } - newChains := make([]chains.Chain, len(supportedChains)) - for i, chain := range supportedChains { - newChains[i] = chain + keyGen, err := c.GetKeyGen(ctx) + if err != nil { + return errors.Wrap(err, "unable to fetch keygen from zetacore") } - keyGen, err := c.GetKeyGen(ctx) + crosschainFlags, err := c.GetCrosschainFlags(ctx) if err != nil { - c.logger.Info().Msg("Unable to fetch keygen from zetacore") - return fmt.Errorf("failed to get keygen: %w", err) + return errors.Wrap(err, "unable to fetch crosschain flags from zetacore") } tss, err := c.GetCurrentTSS(ctx) if err != nil { - c.logger.Info().Err(err).Msg("Unable to fetch TSS from zetacore") - return fmt.Errorf("failed to get current tss: %w", err) + return errors.Wrap(err, "unable to fetch current TSS") } - tssPubKey := tss.GetTssPubkey() - crosschainFlags, err := c.GetCrosschainFlags(ctx) - if err != nil { - c.logger.Info().Msg("Unable to fetch cross-chain flags from zetacore") - return fmt.Errorf("failed to get crosschain flags: %w", err) + var ( + freshChains = make([]chains.Chain, 0, len(supportedChains)) + freshParams = make(map[int64]*observertypes.ChainParams, len(chainParams)) + ) + + // check and update chain params for each chain + for chainID := range chainParams { + cp := chainParams[chainID] + if err := observertypes.ValidateChainParams(cp); err != nil { + logger.Warn().Err(err).Int64("chain", cp.ChainId).Msg("Skipping invalid chain params") + continue + } + + if !cp.IsSupported { + logger.Warn().Int64("chain.id", cp.ChainId).Msg("Skipping unsupported chain") + continue + } + + if chains.IsZetaChain(cp.ChainId, additionalChains) { + logger.Warn().Int64("chain.id", cp.ChainId).Msg("Skipping zeta chain itself") + continue + } + + freshParams[cp.ChainId] = cp } - blockHeaderEnabledChains, err := c.GetBlockHeaderEnabledChains(ctx) - if err != nil { - c.logger.Info().Msg("Unable to fetch block header enabled chains from zetacore") - return err + for _, chain := range supportedChains { + if !chain.IsExternal { + logger.Warn().Int64("chain.id", chain.ChainId).Msg("Skipping non-external chain") + continue + } + + freshChains = append(freshChains, chain) } - appContext.Update( + return appContext.Update( keyGen, - newChains, - newEVMParams, - newBTCParams, - newSolanaParams, - tssPubKey, - crosschainFlags, + freshChains, additionalChains, - blockHeaderEnabledChains, - init, + freshParams, + tss.GetTssPubkey(), + crosschainFlags, ) - - return nil } func cosmosREST(host string) string { diff --git a/zetaclient/zetacore/client_query_observer.go b/zetaclient/zetacore/client_query_observer.go index 45082aae16..4d98ca848e 100644 --- a/zetaclient/zetacore/client_query_observer.go +++ b/zetaclient/zetacore/client_query_observer.go @@ -2,6 +2,7 @@ package zetacore import ( "context" + "fmt" "cosmossdk.io/errors" @@ -95,18 +96,21 @@ func (c *Client) GetNonceByChain(ctx context.Context, chain chains.Chain) (types } // GetKeyGen returns the keygen -func (c *Client) GetKeyGen(ctx context.Context) (*types.Keygen, error) { +func (c *Client) GetKeyGen(ctx context.Context) (types.Keygen, error) { in := &types.QueryGetKeygenRequest{} resp, err := retry.DoTypedWithRetry(func() (*types.QueryGetKeygenResponse, error) { return c.client.observer.Keygen(ctx, in) }) - if err != nil { - return nil, errors.Wrap(err, "failed to get keygen") + switch { + case err != nil: + return types.Keygen{}, errors.Wrap(err, "failed to get keygen") + case resp.Keygen == nil: + return types.Keygen{}, fmt.Errorf("keygen is nil") } - return resp.GetKeygen(), nil + return *resp.Keygen, nil } // GetAllNodeAccounts returns all node accounts diff --git a/zetaclient/zetacore/client_worker.go b/zetaclient/zetacore/client_worker.go index 05029a9a22..fcf02766a0 100644 --- a/zetaclient/zetacore/client_worker.go +++ b/zetaclient/zetacore/client_worker.go @@ -32,7 +32,7 @@ func (c *Client) UpdateAppContextWorker(ctx context.Context, app *appcontext.App select { case <-ticker.C: c.logger.Debug().Msg("UpdateAppContextWorker invocation") - if err := c.UpdateAppContext(ctx, app, false, logger); err != nil { + if err := c.UpdateAppContext(ctx, app, logger); err != nil { c.logger.Err(err).Msg("UpdateAppContextWorker failed to update config") } case <-c.stop: From fc39f4eeb5301e73c6b8d9668d701bcb06602bc4 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 29 Jul 2024 15:22:16 +0200 Subject: [PATCH 05/24] Refactor orchestrator --- zetaclient/orchestrator/bootstap_test.go | 73 ++-- zetaclient/orchestrator/bootstrap.go | 390 ++++++++----------- zetaclient/orchestrator/orchestrator.go | 104 ++--- zetaclient/orchestrator/orchestrator_test.go | 21 +- 4 files changed, 263 insertions(+), 325 deletions(-) diff --git a/zetaclient/orchestrator/bootstap_test.go b/zetaclient/orchestrator/bootstap_test.go index 555c830df5..55b57ef1bc 100644 --- a/zetaclient/orchestrator/bootstap_test.go +++ b/zetaclient/orchestrator/bootstap_test.go @@ -6,6 +6,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/ptr" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -175,16 +176,16 @@ func TestCreateSignerMap(t *testing.T) { t.Run("Polygon is there but not supported, should be disabled", func(t *testing.T) { // ARRANGE // Given updated data from zetacore containing polygon chain - supportedChain, evmParams, btcParams, solParams := chainParams([]chains.Chain{ + supportedChain, params := chainParams([]chains.Chain{ chains.Ethereum, chains.Polygon, chains.BitcoinMainnet, }) // BUT (!) it's disabled via zetacore - evmParams[chains.Polygon.ChainId].IsSupported = false + params[chains.Polygon.ChainId].IsSupported = false - mustUpdateAppContext(t, app, supportedChain, evmParams, btcParams, solParams) + mustUpdateAppContext(t, app, supportedChain, nil, params) // Should have signer BEFORE disabling hasSigner(t, signers, chains.Polygon.ChainId) @@ -404,16 +405,16 @@ func TestCreateChainObserverMap(t *testing.T) { t.Run("Polygon is there but not supported, should be disabled", func(t *testing.T) { // ARRANGE // Given updated data from zetacore containing polygon chain - supportedChain, evmParams, btcParams, solParams := chainParams([]chains.Chain{ + supportedChain, params := chainParams([]chains.Chain{ chains.Ethereum, chains.Polygon, chains.BitcoinMainnet, }) // BUT (!) it's disabled via zetacore - evmParams[chains.Polygon.ChainId].IsSupported = false + params[chains.Polygon.ChainId].IsSupported = false - mustUpdateAppContext(t, app, supportedChain, evmParams, btcParams, solParams) + mustUpdateAppContext(t, app, supportedChain, nil, params) // Should have signer BEFORE disabling hasObserver(t, observers, chains.Polygon.ChainId) @@ -447,69 +448,59 @@ func TestCreateChainObserverMap(t *testing.T) { }) } -func chainParams(supportedChains []chains.Chain) ( - []chains.Chain, - map[int64]*observertypes.ChainParams, - *observertypes.ChainParams, - *observertypes.ChainParams, -) { - var ( - evmParams = make(map[int64]*observertypes.ChainParams) - btcParams = &observertypes.ChainParams{} - solParams = &observertypes.ChainParams{} - ) +func chainParams(supportedChains []chains.Chain) ([]chains.Chain, map[int64]*observertypes.ChainParams) { + params := make(map[int64]*observertypes.ChainParams) for _, chain := range supportedChains { - if chains.IsBitcoinChain(chain.ChainId, nil) { - btcParams = &observertypes.ChainParams{ - ChainId: chain.ChainId, + chainID := chain.ChainId + if chains.IsBitcoinChain(chainID, nil) { + params[chainID] = &observertypes.ChainParams{ + ChainId: chainID, IsSupported: true, } continue } - if chains.IsSolanaChain(chain.ChainId, nil) { - solParams = &observertypes.ChainParams{ - ChainId: chain.ChainId, + if chains.IsSolanaChain(chainID, nil) { + params[chainID] = &observertypes.ChainParams{ + ChainId: chainID, IsSupported: true, GatewayAddress: solanaGatewayAddress, } + continue } - if chains.IsEVMChain(chain.ChainId, nil) { - evmParams[chain.ChainId] = ptr.Ptr(mocks.MockChainParams(chain.ChainId, 100)) + if chains.IsEVMChain(chainID, nil) { + params[chainID] = ptr.Ptr(mocks.MockChainParams(chainID, 100)) + continue } } - return supportedChains, evmParams, btcParams, solParams + return supportedChains, params } func mustUpdateAppContextChainParams(t *testing.T, app *zctx.AppContext, chains []chains.Chain) { - supportedChain, evmParams, btcParams, solParams := chainParams(chains) - mustUpdateAppContext(t, app, supportedChain, evmParams, btcParams, solParams) + supportedChain, params := chainParams(chains) + mustUpdateAppContext(t, app, supportedChain, nil, params) } func mustUpdateAppContext( - _ *testing.T, + t *testing.T, app *zctx.AppContext, - chains []chains.Chain, - evmParams map[int64]*observertypes.ChainParams, - utxoParams *observertypes.ChainParams, - solParams *observertypes.ChainParams, + chains, additionalChains []chains.Chain, + chainParams map[int64]*observertypes.ChainParams, ) { - app.Update( - ptr.Ptr(app.GetKeygen()), + err := app.Update( + app.GetKeygen(), chains, - evmParams, - utxoParams, - solParams, + additionalChains, + chainParams, app.GetCurrentTssPubKey(), app.GetCrossChainFlags(), - app.GetAdditionalChains(), - nil, - false, ) + + require.NoError(t, err) } func hasSigner(t *testing.T, signers map[int64]interfaces.ChainSigner, chainId int64) { diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go index 5ce43a2ecd..fd29453e77 100644 --- a/zetaclient/orchestrator/bootstrap.go +++ b/zetaclient/orchestrator/bootstrap.go @@ -82,19 +82,8 @@ func syncSignerMap( } ) - // EVM signers - for _, evmConfig := range app.Config().GetAllEVMConfigs() { - chainID := evmConfig.Chain.ChainId - - evmChainParams, found := app.GetEVMChainParams(chainID) - switch { - case !found: - logger.Std.Warn().Msgf("Unable to find chain params for EVM chain %d", chainID) - continue - case !evmChainParams.IsSupported: - logger.Std.Warn().Msgf("EVM chain %d is not supported", chainID) - continue - } + for _, chain := range app.ListChains() { + chainID := chain.ID() presentChainIDs = append(presentChainIDs, chainID) @@ -103,62 +92,56 @@ func syncSignerMap( 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 - } - - addSigner(chainID, signer) - } + rawChain := chain.RawChain() - // BTC signer - // Emulate same loop semantics as for EVM chains - for i := 0; i < 1; i++ { - btcChain, btcChainParams, btcChainParamsFound := app.GetBTCChainParams() switch { - case !btcChainParamsFound: - logger.Std.Warn().Msgf("Unable to find chain params for BTC chain") - continue - case !btcChainParams.IsSupported: - logger.Std.Warn().Msgf("BTC chain is not supported") - continue - } - - chainID := btcChainParams.ChainId - - presentChainIDs = append(presentChainIDs, chainID) - - // noop - if mapHas(signers, chainID) { - continue + case chain.IsEVM(): + var ( + mpiAddress = ethcommon.HexToAddress(chain.Params().ConnectorContractAddress) + erc20CustodyAddress = ethcommon.HexToAddress(chain.Params().Erc20CustodyContractAddress) + ) + + cfg, found := app.Config().GetEVMConfig(chainID) + if !found || cfg.Empty() { + logger.Std.Warn().Msgf("Unable to find EVM config for chain %d", chainID) + continue + } + + signer, err := evmsigner.NewSigner( + ctx, + *rawChain, + tss, + ts, + logger, + cfg.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 + } + + addSigner(chainID, signer) + case chain.IsUTXO(): + cfg, found := app.Config().GetBTCConfig() + if !found { + logger.Std.Warn().Msgf("Unable to find UTXO config for chain %d", chainID) + continue + } + + signer, err := btcsigner.NewSigner(*rawChain, tss, ts, logger, cfg) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to construct signer for UTXO chain %d", chainID) + continue + } + + addSigner(chainID, signer) + default: + logger.Std.Warn().Msgf("Unable to create signer for chain %d", chainID) } - - cfg, _ := app.Config().GetBTCConfig() - - utxoSigner, err := btcsigner.NewSigner(btcChain, tss, ts, logger, cfg) - if err != nil { - logger.Std.Error().Err(err).Msgf("Unable to construct signer for UTXO chain %d", chainID) - continue - } - - addSigner(chainID, utxoSigner) } // Remove all disabled signers @@ -225,22 +208,8 @@ func syncObserverMap( } ) - // EVM observers - for _, evmConfig := range app.Config().GetAllEVMConfigs() { - var ( - chainID = evmConfig.Chain.ChainId - chainName = evmConfig.Chain.ChainName.String() - ) - - chainParams, found := app.GetEVMChainParams(evmConfig.Chain.ChainId) - switch { - case !found: - logger.Std.Error().Msgf("Unable to find chain params for EVM chain %d", chainID) - continue - case !chainParams.IsSupported: - logger.Std.Error().Msgf("EVM chain %d is not supported", chainID) - continue - } + for _, chain := range app.ListChains() { + chainID := chain.ID() presentChainIDs = append(presentChainIDs, chainID) @@ -249,155 +218,126 @@ func syncObserverMap( continue } - // create EVM client - evmClient, err := ethclient.DialContext(ctx, evmConfig.Endpoint) - if err != nil { - logger.Std.Error().Err(err).Str("rpc.endpoint", evmConfig.Endpoint).Msgf("Unable to dial EVM RPC") - continue - } - - database, err := db.NewFromSqlite(dbpath, chainName, true) - if err != nil { - logger.Std.Error().Err(err).Msgf("Unable to open a database for EVM chain %q", chainName) - continue - } - - // create EVM chain observer - observer, err := evmobserver.NewObserver( - ctx, - evmConfig, - evmClient, - *chainParams, - client, - tss, - database, - logger, - ts, - ) - if err != nil { - logger.Std.Error().Err(err).Msgf("NewObserver error for EVM chain %s", evmConfig.Chain.String()) - continue - } - - addObserver(chainID, observer) - } - - // Emulate same loop semantics as for EVM chains - // create BTC chain observer - for i := 0; i < 1; i++ { - btcChain, btcConfig, btcEnabled := app.GetBTCChainAndConfig() - if !btcEnabled { - continue - } - - chainID := btcChain.ChainId - - _, btcChainParams, found := app.GetBTCChainParams() - switch { - case !found: - logger.Std.Warn().Msgf("Unable to find chain params for BTC chain %d", chainID) - continue - case !btcChainParams.IsSupported: - logger.Std.Warn().Msgf("BTC chain %d is not supported", chainID) - continue - } - - presentChainIDs = append(presentChainIDs, chainID) - - // noop - if mapHas(observerMap, chainID) { - continue - } - - btcRPC, err := rpc.NewRPCClient(btcConfig) - if err != nil { - logger.Std.Error().Err(err).Msgf("unable to create rpc client for BTC chain %d", chainID) - continue - } - - database, err := db.NewFromSqlite(dbpath, btcDatabaseFilename, true) - if err != nil { - logger.Std.Error().Err(err).Msgf("unable to open database for BTC chain %d", chainID) - continue - } - - btcObserver, err := btcobserver.NewObserver( - btcChain, - btcRPC, - *btcChainParams, - client, - tss, - database, - logger, - ts, - ) - if err != nil { - logger.Std.Error().Err(err).Msgf("NewObserver error for BTC chain %d", chainID) - continue - } - - addObserver(chainID, btcObserver) - } - - // Emulate same loop semantics as for EVM chains - // create SOL chain observer - for i := 0; i < 1; i++ { - solChain, solConfig, solEnabled := app.GetSolanaChainAndConfig() - if !solEnabled { - continue - } - var ( - chainID = solChain.ChainId - chainName = solChain.ChainName.String() + params = chain.Params() + rawChain = chain.RawChain() + chainName = rawChain.ChainName.String() ) - _, solanaChainParams, found := app.GetSolanaChainParams() switch { - case !found: - logger.Std.Warn().Msgf("Unable to find chain params for SOL chain %d", chainID) - continue - case !solanaChainParams.IsSupported: - logger.Std.Warn().Msgf("SOL chain %d is not supported", chainID) - continue - } - - presentChainIDs = append(presentChainIDs, chainID) - - // noop - if mapHas(observerMap, chainID) { - continue - } - - rpcClient := solrpc.New(solConfig.Endpoint) - if rpcClient == nil { - // should never happen - logger.Std.Error().Msg("solana create Solana client error") - continue - } - - database, err := db.NewFromSqlite(dbpath, chainName, true) - if err != nil { - logger.Std.Error().Err(err).Msgf("unable to open database for SOL chain %d", chainID) - continue - } - - solObserver, err := solbserver.NewObserver( - solChain, - rpcClient, - *solanaChainParams, - client, - tss, - database, - logger, - ts, - ) - if err != nil { - logger.Std.Error().Err(err).Msgf("NewObserver error for SOL chain %d", chainID) + case chain.IsEVM(): + cfg, found := app.Config().GetEVMConfig(chainID) + if !found || cfg.Empty() { + logger.Std.Warn().Msgf("Unable to find EVM config for chain %d", chainID) + continue + } + + // create EVM client + evmClient, err := ethclient.DialContext(ctx, cfg.Endpoint) + if err != nil { + logger.Std.Error().Err(err).Str("rpc.endpoint", cfg.Endpoint).Msgf("Unable to dial EVM RPC") + continue + } + + database, err := db.NewFromSqlite(dbpath, chainName, true) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to open a database for EVM chain %q", chainName) + continue + } + + // create EVM chain observer + observer, err := evmobserver.NewObserver( + ctx, + cfg, + evmClient, + *params, + client, + tss, + database, + logger, + ts, + ) + if err != nil { + logger.Std.Error().Err(err).Msgf("NewObserver error for EVM chain %d", chainID) + continue + } + + addObserver(chainID, observer) + case chain.IsUTXO(): + cfg, found := app.Config().GetBTCConfig() + if !found { + logger.Std.Warn().Msgf("Unable to find chain params for BTC chain %d", chainID) + continue + } + + btcRPC, err := rpc.NewRPCClient(cfg) + if err != nil { + logger.Std.Error().Err(err).Msgf("unable to create rpc client for BTC chain %d", chainID) + continue + } + + database, err := db.NewFromSqlite(dbpath, btcDatabaseFilename, true) + if err != nil { + logger.Std.Error().Err(err).Msgf("unable to open database for BTC chain %d", chainID) + continue + } + + btcObserver, err := btcobserver.NewObserver( + *rawChain, + btcRPC, + *params, + client, + tss, + database, + logger, + ts, + ) + if err != nil { + logger.Std.Error().Err(err).Msgf("NewObserver error for BTC chain %d", chainID) + continue + } + + addObserver(chainID, btcObserver) + case chain.IsSolana(): + cfg, found := app.Config().GetSolanaConfig() + if !found { + logger.Std.Warn().Msgf("Unable to find chain params for SOL chain %d", chainID) + continue + } + + rpcClient := solrpc.New(cfg.Endpoint) + if rpcClient == nil { + // should never happen + logger.Std.Error().Msg("solana create Solana client error") + continue + } + + database, err := db.NewFromSqlite(dbpath, chainName, true) + if err != nil { + logger.Std.Error().Err(err).Msgf("unable to open database for SOL chain %d", chainID) + continue + } + + solObserver, err := solbserver.NewObserver( + *rawChain, + rpcClient, + *params, + client, + tss, + database, + logger, + ts, + ) + if err != nil { + logger.Std.Error().Err(err).Msgf("NewObserver error for SOL chain %d", chainID) + continue + } + + addObserver(chainID, solObserver) + default: + logger.Std.Warn().Msgf("Unable to create observer for chain %d", chainID) continue } - - addObserver(chainID, solObserver) } // Remove all disabled observers diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index 3f2d20915b..56ae914504 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -14,7 +14,6 @@ import ( "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/pkg/bg" - "github.com/zeta-chain/zetacore/pkg/chains" zetamath "github.com/zeta-chain/zetacore/pkg/math" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -153,18 +152,18 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte return nil, err } - // noop for non-EVM chains - if !chains.IsEVMChain(chainID, app.GetAdditionalChains()) { - return signer, nil + chain, err := app.GetChain(chainID) + if err != nil { + return nil, err } - evmParams, found := app.GetEVMChainParams(chainID) - if !found { + // noop for non-EVM chains + if !chain.IsEVM() { return signer, nil } // update zeta connector and ERC20 custody addresses - zetaConnectorAddress := ethcommon.HexToAddress(evmParams.GetConnectorContractAddress()) + zetaConnectorAddress := ethcommon.HexToAddress(chain.Params().GetConnectorContractAddress()) if zetaConnectorAddress != signer.GetZetaConnectorAddress() { signer.SetZetaConnectorAddress(zetaConnectorAddress) oc.logger.Info(). @@ -172,7 +171,7 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte Msgf("updated zeta connector address for chain %d", chainID) } - erc20CustodyAddress := ethcommon.HexToAddress(evmParams.GetErc20CustodyContractAddress()) + erc20CustodyAddress := ethcommon.HexToAddress(chain.Params().GetErc20CustodyContractAddress()) if erc20CustodyAddress != signer.GetERC20CustodyAddress() { signer.SetERC20CustodyAddress(erc20CustodyAddress) oc.logger.Info(). @@ -202,24 +201,22 @@ func (oc *Orchestrator) resolveObserver(app *zctx.AppContext, chainID int64) (in return nil, err } + chain, err := app.GetChain(chainID) + if err != nil { + return nil, errors.Wrapf(err, "unable to get chain %d", chainID) + } + // update chain observer chain parameters - curParams := observer.GetChainParams() - if chains.IsEVMChain(chainID, app.GetAdditionalChains()) { - evmParams, found := app.GetEVMChainParams(chainID) - if found && !observertypes.ChainParamsEqual(curParams, *evmParams) { - observer.SetChainParams(*evmParams) - oc.logger.Info(). - Interface("observer.chain_params", *evmParams). - Msgf("updated chain params for EVM chainID %d", chainID) - } - } else if chains.IsBitcoinChain(chainID, app.GetAdditionalChains()) { - _, btcParams, found := app.GetBTCChainParams() - if found && !observertypes.ChainParamsEqual(curParams, *btcParams) { - observer.SetChainParams(*btcParams) - oc.logger.Info(). - Interface("observer.chain_params", *btcParams). - Msgf("updated chain params for UTXO chainID %d", btcParams.ChainId) - } + var ( + curParams = observer.GetChainParams() + freshParams = chain.Params() + ) + + if !observertypes.ChainParamsEqual(curParams, *freshParams) { + observer.SetChainParams(*freshParams) + oc.logger.Info(). + Interface("observer.chain_params", *freshParams). + Msgf("updated chain params for chainID %d", chainID) } return observer, nil @@ -238,10 +235,10 @@ func (oc *Orchestrator) getObserver(chainID int64) (interfaces.ChainObserver, er } // GetPendingCctxsWithinRateLimit get pending cctxs across foreign chains within rate limit -func (oc *Orchestrator) GetPendingCctxsWithinRateLimit( - ctx context.Context, - foreignChains []chains.Chain, -) (map[int64][]*types.CrossChainTx, error) { +func (oc *Orchestrator) GetPendingCctxsWithinRateLimit(ctx context.Context, chainIDs []int64) ( + map[int64][]*types.CrossChainTx, + error, +) { // get rate limiter flags rateLimitFlags, err := oc.zetacoreClient.GetRateLimiterFlags(ctx) if err != nil { @@ -254,10 +251,10 @@ func (oc *Orchestrator) GetPendingCctxsWithinRateLimit( // fallback to non-rate-limited query if rate limiter is not usable cctxsMap := make(map[int64][]*types.CrossChainTx) if !rateLimiterUsable { - for _, chain := range foreignChains { - resp, _, err := oc.zetacoreClient.ListPendingCCTX(ctx, chain.ChainId) + for _, chainID := range chainIDs { + resp, _, err := oc.zetacoreClient.ListPendingCCTX(ctx, chainID) if err == nil && resp != nil { - cctxsMap[chain.ChainId] = resp + cctxsMap[chainID] = resp } } return cctxsMap, nil @@ -337,49 +334,58 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { // set current hot key burn rate metrics.HotKeyBurnRate.Set(float64(oc.ts.HotKeyBurnRate.GetBurnRate().Int64())) - // get supported external chains - externalChains := app.GetEnabledExternalChains() + chainIDs := app.ListChainIDs() // query pending cctxs across all external chains within rate limit - cctxMap, err := oc.GetPendingCctxsWithinRateLimit(ctx, externalChains) + cctxMap, err := oc.GetPendingCctxsWithinRateLimit(ctx, chainIDs) if err != nil { oc.logger.Error().Err(err).Msgf("runScheduler: GetPendingCctxsWithinRatelimit failed") } // schedule keysign for pending cctxs on each chain - for _, c := range externalChains { + for _, chain := range app.ListChains() { + chainID := chain.ID() + // get cctxs from map and set pending transactions prometheus gauge - cctxList := cctxMap[c.ChainId] - metrics.PendingTxsPerChain.WithLabelValues(c.ChainName.String()).Set(float64(len(cctxList))) + cctxList := cctxMap[chainID] + + metrics.PendingTxsPerChain. + WithLabelValues(fmt.Sprintf("%d", chainID)). + Set(float64(len(cctxList))) + if len(cctxList) == 0 { continue } // update chain parameters for signer and chain observer - signer, err := oc.resolveSigner(app, c.ChainId) + signer, err := oc.resolveSigner(app, chainID) if err != nil { oc.logger.Error().Err(err). - Msgf("runScheduler: unable to resolve signer for chain %d", c.ChainId) + Msgf("runScheduler: unable to resolve signer for chain %d", chainID) continue } - ob, err := oc.resolveObserver(app, c.ChainId) + + ob, err := oc.resolveObserver(app, chainID) if err != nil { oc.logger.Error().Err(err). - Msgf("runScheduler: resolveObserver failed for chain %d", c.ChainId) + Msgf("runScheduler: resolveObserver failed for chain %d", chainID) continue } - if !app.IsOutboundObservationEnabled(ob.GetChainParams()) { + + if !app.IsOutboundObservationEnabled() { continue } // #nosec G115 range is verified zetaHeight := uint64(bn) - if chains.IsEVMChain(c.ChainId, app.GetAdditionalChains()) { - oc.ScheduleCctxEVM(ctx, zetaHeight, c.ChainId, cctxList, ob, signer) - } else if chains.IsBitcoinChain(c.ChainId, app.GetAdditionalChains()) { - oc.ScheduleCctxBTC(ctx, zetaHeight, c.ChainId, cctxList, ob, signer) - } else { - oc.logger.Error().Msgf("StartCctxScheduler: unsupported chain %d", c.ChainId) + + switch { + case chain.IsEVM(): + oc.ScheduleCctxEVM(ctx, zetaHeight, chainID, cctxList, ob, signer) + case chain.IsUTXO(): + oc.ScheduleCctxBTC(ctx, zetaHeight, chainID, cctxList, ob, signer) + default: + oc.logger.Error().Msgf("runScheduler: no scheduler found chain %d", chainID) continue } } diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go index 94c134cad6..b1b227f7b2 100644 --- a/zetaclient/orchestrator/orchestrator_test.go +++ b/zetaclient/orchestrator/orchestrator_test.go @@ -9,6 +9,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/slices" zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/pkg/chains" @@ -69,23 +70,21 @@ func CreateAppContext( } // new AppContext appContext := zctx.New(cfg, zerolog.Nop()) - evmChainParamsMap := make(map[int64]*observertypes.ChainParams) - evmChainParamsMap[evmChain.ChainId] = evmChainParams + params := map[int64]*observertypes.ChainParams{ + evmChain.ChainId: evmChainParams, + btcChainParams.ChainId: btcChainParams, + } + ccFlags := sample.CrosschainFlags() - verificationFlags := sample.HeaderSupportedChains() // feed chain params appContext.Update( - &observertypes.Keygen{}, + observertypes.Keygen{}, []chains.Chain{evmChain, btcChain}, - evmChainParamsMap, - btcChainParams, nil, + params, "", *ccFlags, - []chains.Chain{}, - verificationFlags, - true, ) return appContext } @@ -369,8 +368,10 @@ func Test_GetPendingCctxsWithinRateLimit(t *testing.T) { // create orchestrator orchestrator := MockOrchestrator(t, client, ethChain, btcChain, ethChainParams, btcChainParams) + chainIDs := slices.Map(foreignChains, func(c chains.Chain) int64 { return c.ChainId }) + // run the test - cctxsMap, err := orchestrator.GetPendingCctxsWithinRateLimit(ctx, foreignChains) + cctxsMap, err := orchestrator.GetPendingCctxsWithinRateLimit(ctx, chainIDs) if tt.fail { assert.Error(t, err) assert.Empty(t, cctxsMap) From 782cef3eb71ab1d3cc0cfc578249ea52869b7c5f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 29 Jul 2024 15:23:35 +0200 Subject: [PATCH 06/24] Refactor observer&signer; DROP postBlockHeaders --- zetaclient/chains/bitcoin/observer/inbound.go | 12 +--- .../chains/bitcoin/observer/observer.go | 40 ------------- .../chains/bitcoin/observer/outbound.go | 2 +- zetaclient/chains/evm/observer/inbound.go | 59 ++++--------------- zetaclient/chains/evm/observer/observer.go | 42 ------------- zetaclient/chains/evm/observer/outbound.go | 2 +- zetaclient/chains/evm/signer/outbound_data.go | 12 ++-- zetaclient/chains/evm/signer/signer.go | 59 +++++++++---------- zetaclient/chains/interfaces/interfaces.go | 2 +- zetaclient/chains/solana/observer/inbound.go | 2 +- .../chains/solana/observer/inbound_tracker.go | 2 +- zetaclient/config/types.go | 4 ++ 12 files changed, 60 insertions(+), 178 deletions(-) diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 47a8177cff..d8532c5797 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -49,7 +49,7 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchInbound: inbound observation is disabled for chain %d", ob.Chain().ChainId) continue @@ -123,14 +123,6 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error { // https://github.com/zeta-chain/node/issues/1847 // TODO: move this logic in its own routine // https://github.com/zeta-chain/node/issues/2204 - blockHeaderVerification, found := app.GetBlockHeaderEnabledChains(ob.Chain().ChainId) - if found && blockHeaderVerification.Enabled { - // #nosec G115 always in range - err = ob.postBlockHeader(ctx, int64(blockNumber)) - if err != nil { - ob.logger.Inbound.Warn().Err(err).Msgf("observeInboundBTC: error posting block header %d", blockNumber) - } - } if len(res.Block.Tx) > 1 { // get depositor fee depositorFee := bitcoin.CalcDepositorFee(res.Block, ob.Chain().ChainId, ob.netParams, ob.logger.Inbound) @@ -206,7 +198,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { continue } err := ob.ProcessInboundTrackers(ctx) diff --git a/zetaclient/chains/bitcoin/observer/observer.go b/zetaclient/chains/bitcoin/observer/observer.go index 519b3b99d8..4bd759b3c4 100644 --- a/zetaclient/chains/bitcoin/observer/observer.go +++ b/zetaclient/chains/bitcoin/observer/observer.go @@ -2,7 +2,6 @@ package observer import ( - "bytes" "context" "encoding/hex" "fmt" @@ -21,7 +20,6 @@ import ( "github.com/zeta-chain/zetacore/pkg/bg" "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/proofs" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" @@ -648,41 +646,3 @@ func (ob *Observer) isTssTransaction(txid string) bool { _, found := ob.includedTxHashes[txid] return found } - -// postBlockHeader posts block header to zetacore -// TODO(revamp): move to block header file -func (ob *Observer) postBlockHeader(ctx context.Context, tip int64) error { - ob.logger.Inbound.Info().Msgf("postBlockHeader: tip %d", tip) - bn := tip - chainState, err := ob.ZetacoreClient().GetBlockHeaderChainState(ctx, ob.Chain().ChainId) - if err == nil && chainState != nil && chainState.EarliestHeight > 0 { - bn = chainState.LatestHeight + 1 - } - if bn > tip { - return fmt.Errorf("postBlockHeader: must post block confirmed block header: %d > %d", bn, tip) - } - res2, err := ob.GetBlockByNumberCached(bn) - if err != nil { - return fmt.Errorf("error getting bitcoin block %d: %s", bn, err) - } - - var headerBuf bytes.Buffer - err = res2.Header.Serialize(&headerBuf) - if err != nil { // should never happen - ob.logger.Inbound.Error().Err(err).Msgf("error serializing bitcoin block header: %d", bn) - return err - } - blockHash := res2.Header.BlockHash() - _, err = ob.ZetacoreClient().PostVoteBlockHeader( - ctx, - ob.Chain().ChainId, - blockHash[:], - res2.Block.Height, - proofs.NewBitcoinHeader(headerBuf.Bytes()), - ) - ob.logger.Inbound.Info().Msgf("posted block header %d: %s", bn, blockHash) - if err != nil { // error shouldn't block the process - ob.logger.Inbound.Error().Err(err).Msgf("error posting bitcoin block header: %d", bn) - } - return err -} diff --git a/zetaclient/chains/bitcoin/observer/outbound.go b/zetaclient/chains/bitcoin/observer/outbound.go index c7c1c649d7..1b15e00d8e 100644 --- a/zetaclient/chains/bitcoin/observer/outbound.go +++ b/zetaclient/chains/bitcoin/observer/outbound.go @@ -52,7 +52,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsOutboundObservationEnabled(ob.GetChainParams()) { + if !app.IsOutboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchOutbound: outbound observation is disabled for chain %d", chainID) continue diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index 3dba2aa62e..2abfbe89d7 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -57,7 +57,7 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchInbound: inbound observation is disabled for chain %d", ob.Chain().ChainId) continue @@ -97,7 +97,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { continue } err := ob.ProcessInboundTrackers(ctx) @@ -414,34 +414,10 @@ func (ob *Observer) ObserveERC20Deposited(ctx context.Context, startBlock, toBlo // ObserverTSSReceive queries the incoming gas asset to TSS address and posts to zetacore // returns the last block successfully scanned func (ob *Observer) ObserverTSSReceive(ctx context.Context, startBlock, toBlock uint64) (uint64, error) { - app, err := zctx.FromContext(ctx) - if err != nil { - return 0, err - } - - var ( - // post new block header (if any) to zetacore and ignore error - // TODO: consider having a independent ticker(from TSS scaning) for posting block headers - // https://github.com/zeta-chain/node/issues/1847 - chainID = ob.Chain().ChainId - blockHeaderVerification, found = app.GetBlockHeaderEnabledChains(chainID) - shouldPostBlockHeader = found && blockHeaderVerification.Enabled - ) + chainID := ob.Chain().ChainId // query incoming gas asset for bn := startBlock; bn <= toBlock; bn++ { - if shouldPostBlockHeader { - // post block header for supported chains - // TODO: move this logic in its own routine - // https://github.com/zeta-chain/node/issues/2204 - if err := ob.postBlockHeader(ctx, toBlock); err != nil { - ob.Logger().Inbound. - Error().Err(err). - Uint64("tss.to_block", toBlock). - Msg("error posting block header") - } - } - // observe TSS received gas token in block 'bn' err := ob.ObserveTSSReceiveInBlock(ctx, bn) if err != nil { @@ -671,14 +647,12 @@ func (ob *Observer) BuildInboundVoteMsgForZetaSentEvent( appContext *zctx.AppContext, event *zetaconnector.ZetaConnectorNonEthZetaSent, ) *types.MsgVoteInbound { - destChain, found := chains.GetChainFromChainID( - event.DestinationChainId.Int64(), - appContext.GetAdditionalChains(), - ) - if !found { - ob.Logger().Inbound.Warn().Msgf("chain id not supported %d", event.DestinationChainId.Int64()) + destChain, err := appContext.GetChain(event.DestinationChainId.Int64()) + if err != nil { + ob.Logger().Inbound.Warn().Err(err).Msgf("chain id %d not supported", event.DestinationChainId.Int64()) return nil } + destAddr := clienttypes.BytesToEthHex(event.DestinationAddress) // compliance check @@ -689,20 +663,13 @@ func (ob *Observer) BuildInboundVoteMsgForZetaSentEvent( return nil } - if !destChain.IsZetaChain() { - paramsDest, found := appContext.GetEVMChainParams(destChain.ChainId) - if !found { - ob.Logger().Inbound.Warn(). - Msgf("chain id not present in EVMChainParams %d", event.DestinationChainId.Int64()) - return nil - } + if strings.EqualFold(destAddr, destChain.Params().ZetaTokenContractAddress) { + ob.Logger().Inbound.Warn(). + Msgf("potential attack attempt: destination address is ZETA token contract address %s", destAddr) - if strings.EqualFold(destAddr, paramsDest.ZetaTokenContractAddress) { - ob.Logger().Inbound.Warn(). - Msgf("potential attack attempt: %s destination address is ZETA token contract address %s", destChain.String(), destAddr) - return nil - } + return nil } + message := base64.StdEncoding.EncodeToString(event.Message) ob.Logger().Inbound.Info().Msgf("ZetaSent inbound detected on chain %d tx %s block %d from %s value %s message %s", ob.Chain(). @@ -713,7 +680,7 @@ func (ob *Observer) BuildInboundVoteMsgForZetaSentEvent( ob.Chain().ChainId, event.SourceTxOriginAddress.Hex(), destAddr, - destChain.ChainId, + destChain.ID(), sdkmath.NewUintFromBigInt(event.ZetaValueAndGas), message, event.Raw.TxHash.Hex(), diff --git a/zetaclient/chains/evm/observer/observer.go b/zetaclient/chains/evm/observer/observer.go index d49e5a53a3..5a344d6eab 100644 --- a/zetaclient/chains/evm/observer/observer.go +++ b/zetaclient/chains/evm/observer/observer.go @@ -11,7 +11,6 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "github.com/onrik/ethrpc" "github.com/pkg/errors" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" @@ -20,7 +19,6 @@ import ( "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" "github.com/zeta-chain/zetacore/pkg/bg" - "github.com/zeta-chain/zetacore/pkg/proofs" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/evm" @@ -389,43 +387,3 @@ func (ob *Observer) LoadLastBlockScanned(ctx context.Context) error { return nil } - -// postBlockHeader posts the block header to zetacore -// TODO(revamp): move to a block header file -func (ob *Observer) postBlockHeader(ctx context.Context, tip uint64) error { - bn := tip - - chainState, err := ob.ZetacoreClient().GetBlockHeaderChainState(ctx, ob.Chain().ChainId) - if err == nil && chainState != nil && chainState.EarliestHeight > 0 { - // #nosec G115 always positive - bn = uint64(chainState.LatestHeight) + 1 // the next header to post - } - - if bn > tip { - return fmt.Errorf("postBlockHeader: must post block confirmed block header: %d > %d", bn, tip) - } - - header, err := ob.GetBlockHeaderCached(ctx, bn) - if err != nil { - ob.Logger().Inbound.Error().Err(err).Msgf("postBlockHeader: error getting block: %d", bn) - return err - } - headerRLP, err := rlp.EncodeToBytes(header) - if err != nil { - ob.Logger().Inbound.Error().Err(err).Msgf("postBlockHeader: error encoding block header: %d", bn) - return err - } - - _, err = ob.ZetacoreClient().PostVoteBlockHeader( - ctx, - ob.Chain().ChainId, - header.Hash().Bytes(), - header.Number.Int64(), - proofs.NewEthereumHeader(headerRLP), - ) - if err != nil { - ob.Logger().Inbound.Error().Err(err).Msgf("postBlockHeader: error posting block header: %d", bn) - return err - } - return nil -} diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go index 7b4f200d52..c895903112 100644 --- a/zetaclient/chains/evm/observer/outbound.go +++ b/zetaclient/chains/evm/observer/outbound.go @@ -58,7 +58,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsOutboundObservationEnabled(ob.GetChainParams()) { + if !app.IsOutboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchOutbound: outbound observation is disabled for chain %d", ob.Chain().ChainId) continue diff --git a/zetaclient/chains/evm/signer/outbound_data.go b/zetaclient/chains/evm/signer/outbound_data.go index 2509b79198..1f3d3d873d 100644 --- a/zetaclient/chains/evm/signer/outbound_data.go +++ b/zetaclient/chains/evm/signer/outbound_data.go @@ -139,11 +139,13 @@ func NewOutboundData( return nil, false, err } - toChain, found := chains.GetChainFromChainID(txData.toChainID.Int64(), app.GetAdditionalChains()) - if !found { - return nil, true, fmt.Errorf("unknown chain: %d", txData.toChainID.Int64()) + toChain, err := app.GetChain(txData.toChainID.Int64()) + if err != nil { + return nil, true, err } + rawChain := toChain.RawChain() + // Get nonce, Early return if the cctx is already processed nonce := cctx.GetCurrentOutboundParam().TssNonce included, confirmed, err := evmObserver.IsOutboundProcessed(ctx, cctx, logger) @@ -156,14 +158,14 @@ func NewOutboundData( } // Set up gas limit and gas price - err = txData.SetupGas(cctx, logger, evmRPC, toChain) + err = txData.SetupGas(cctx, logger, evmRPC, *rawChain) if err != nil { return nil, true, err } // Get sendHash logger.Info(). - Msgf("chain %s minting %d to %s, nonce %d, finalized zeta bn %d", toChain.String(), cctx.InboundParams.Amount, txData.to.Hex(), nonce, cctx.InboundParams.FinalizedZetaHeight) + Msgf("chain %d minting %d to %s, nonce %d, finalized zeta bn %d", toChain.ID(), cctx.InboundParams.Amount, txData.to.Hex(), nonce, cctx.InboundParams.FinalizedZetaHeight) cctxIndex, err := hex.DecodeString(cctx.Index[2:]) // remove the leading 0x if err != nil || len(cctxIndex) != 32 { return nil, true, fmt.Errorf("decode CCTX %s error", cctx.Index) diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index 7235aa4456..db9f0fb179 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -393,9 +393,9 @@ func (signer *Signer) TryProcessOutbound( return } - toChain, found := chains.GetChainFromChainID(txData.toChainID.Int64(), app.GetAdditionalChains()) - if !found { - logger.Warn().Msgf("unknown chain: %d", txData.toChainID.Int64()) + toChain, err := app.GetChain(txData.toChainID.Int64()) + if err != nil { + logger.Err(err).Msgf("error getting toChain %d", txData.toChainID.Int64()) return } @@ -447,27 +447,27 @@ func (signer *Signer) TryProcessOutbound( switch cctx.InboundParams.CoinType { case coin.CoinType_Gas: logger.Info().Msgf( - "SignWithdrawTx: %d => %s, nonce %d, gasPrice %d", + "SignWithdrawTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - toChain.String(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) tx, err = signer.SignWithdrawTx(ctx, txData) case coin.CoinType_ERC20: logger.Info().Msgf( - "SignERC20WithdrawTx: %d => %s, nonce %d, gasPrice %d", + "SignERC20WithdrawTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - toChain.String(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) tx, err = signer.SignERC20WithdrawTx(ctx, txData) case coin.CoinType_Zeta: logger.Info().Msgf( - "SignOutbound: %d => %s, nonce %d, gasPrice %d", + "SignOutbound: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - toChain.String(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -481,9 +481,9 @@ func (signer *Signer) TryProcessOutbound( switch cctx.InboundParams.CoinType { case coin.CoinType_Zeta: logger.Info().Msgf( - "SignRevertTx: %d => %s, nonce %d, gasPrice %d", + "SignRevertTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - toChain.String(), cctx.GetCurrentOutboundParam().TssNonce, + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) txData.srcChainID = big.NewInt(cctx.OutboundParams[0].ReceiverChainId) @@ -491,17 +491,17 @@ func (signer *Signer) TryProcessOutbound( tx, err = signer.SignRevertTx(ctx, txData) case coin.CoinType_Gas: logger.Info().Msgf( - "SignWithdrawTx: %d => %s, nonce %d, gasPrice %d", + "SignWithdrawTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - toChain.String(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) tx, err = signer.SignWithdrawTx(ctx, txData) case coin.CoinType_ERC20: - logger.Info().Msgf("SignERC20WithdrawTx: %d => %s, nonce %d, gasPrice %d", + logger.Info().Msgf("SignERC20WithdrawTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - toChain.String(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -513,9 +513,9 @@ func (signer *Signer) TryProcessOutbound( } } else if cctx.CctxStatus.Status == types.CctxStatus_PendingRevert { logger.Info().Msgf( - "SignRevertTx: %d => %s, nonce %d, gasPrice %d", + "SignRevertTx: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - toChain.String(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -529,9 +529,9 @@ func (signer *Signer) TryProcessOutbound( } } else if cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound { logger.Info().Msgf( - "SignOutbound: %d => %s, nonce %d, gasPrice %d", + "SignOutbound: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, - toChain.String(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, txData.gasPrice, ) @@ -543,9 +543,9 @@ func (signer *Signer) TryProcessOutbound( } logger.Info().Msgf( - "Key-sign success: %d => %s, nonce %d", + "Key-sign success: %d => %d, nonce %d", cctx.InboundParams.SenderChainId, - toChain.String(), + toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, ) @@ -569,10 +569,9 @@ func (signer *Signer) BroadcastOutbound( return } - // Get destination chain for logging - toChain, found := chains.GetChainFromChainID(txData.toChainID.Int64(), app.GetAdditionalChains()) - if !found { - logger.Warn().Msgf("BroadcastOutbound: unknown chain %d", txData.toChainID.Int64()) + toChain, err := app.GetChain(txData.toChainID.Int64()) + if err != nil { + logger.Err(err).Msgf("error getting toChain %d", txData.toChainID.Int64()) return } @@ -593,15 +592,15 @@ func (signer *Signer) BroadcastOutbound( log.Warn(). Err(err). Msgf("BroadcastOutbound: error broadcasting tx %s on chain %d nonce %d retry %d signer %s", - outboundHash, toChain.ChainId, cctx.GetCurrentOutboundParam().TssNonce, i, myID) + outboundHash, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, i, myID) retry, report := zetacore.HandleBroadcastError( err, strconv.FormatUint(cctx.GetCurrentOutboundParam().TssNonce, 10), - toChain.String(), + fmt.Sprintf("%d", toChain.ID()), outboundHash, ) if report { - signer.reportToOutboundTracker(ctx, zetacoreClient, toChain.ChainId, tx.Nonce(), outboundHash, logger) + signer.reportToOutboundTracker(ctx, zetacoreClient, toChain.ID(), tx.Nonce(), outboundHash, logger) } if !retry { break @@ -610,8 +609,8 @@ func (signer *Signer) BroadcastOutbound( continue } logger.Info().Msgf("BroadcastOutbound: broadcasted tx %s on chain %d nonce %d signer %s", - outboundHash, toChain.ChainId, cctx.GetCurrentOutboundParam().TssNonce, myID) - signer.reportToOutboundTracker(ctx, zetacoreClient, toChain.ChainId, tx.Nonce(), outboundHash, logger) + outboundHash, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, myID) + signer.reportToOutboundTracker(ctx, zetacoreClient, toChain.ID(), tx.Nonce(), outboundHash, logger) break // successful broadcast; no need to retry } } diff --git a/zetaclient/chains/interfaces/interfaces.go b/zetaclient/chains/interfaces/interfaces.go index 82125ca619..e06fb170bb 100644 --- a/zetaclient/chains/interfaces/interfaces.go +++ b/zetaclient/chains/interfaces/interfaces.go @@ -106,7 +106,7 @@ type ZetacoreClient interface { GetLogger() *zerolog.Logger GetKeys() keyinterfaces.ObserverKeys - GetKeyGen(ctx context.Context) (*observertypes.Keygen, error) + GetKeyGen(ctx context.Context) (observertypes.Keygen, error) GetBlockHeight(ctx context.Context) (int64, error) GetBlockHeaderChainState(ctx context.Context, chainID int64) (*lightclienttypes.ChainState, error) diff --git a/zetaclient/chains/solana/observer/inbound.go b/zetaclient/chains/solana/observer/inbound.go index 4a819ce9cb..95aadf797c 100644 --- a/zetaclient/chains/solana/observer/inbound.go +++ b/zetaclient/chains/solana/observer/inbound.go @@ -52,7 +52,7 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { sampledLogger.Info(). Msgf("WatchInbound: inbound observation is disabled for chain %d", ob.Chain().ChainId) continue diff --git a/zetaclient/chains/solana/observer/inbound_tracker.go b/zetaclient/chains/solana/observer/inbound_tracker.go index 7665359949..be5b6bf38d 100644 --- a/zetaclient/chains/solana/observer/inbound_tracker.go +++ b/zetaclient/chains/solana/observer/inbound_tracker.go @@ -33,7 +33,7 @@ func (ob *Observer) WatchInboundTracker(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsInboundObservationEnabled(ob.GetChainParams()) { + if !app.IsInboundObservationEnabled() { continue } err := ob.ProcessInboundTrackers(ctx) diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index b9535f3937..13d4b8d568 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -157,3 +157,7 @@ func (c Config) GetKeyringBackend() KeyringBackend { defer c.mu.RUnlock() return c.KeyringBackend } + +func (c EVMConfig) Empty() bool { + return c.Endpoint == "" && c.Chain == chains.Chain{} +} From 031e7e056bc452a8e0b1a000a3fb99cf617ce9a5 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 29 Jul 2024 20:52:59 +0200 Subject: [PATCH 07/24] Fix test cases [1] --- pkg/slices/slices.go | 12 +++ zetaclient/chains/bitcoin/observer/inbound.go | 5 -- zetaclient/chains/evm/observer/inbound.go | 4 +- .../chains/evm/observer/inbound_test.go | 11 +-- .../chains/evm/observer/observer_test.go | 27 ++++--- .../chains/evm/observer/outbound_test.go | 3 +- zetaclient/chains/evm/signer/outbound_data.go | 12 +-- .../chains/evm/signer/outbound_data_test.go | 38 ++++++--- zetaclient/chains/evm/signer/signer_test.go | 42 ++++++---- zetaclient/orchestrator/bootstap_test.go | 77 ++----------------- zetaclient/orchestrator/orchestrator_test.go | 61 +++++++-------- zetaclient/testutils/mocks/chain_params.go | 25 +++++- zetaclient/testutils/mocks/zetacore_client.go | 16 ++-- zetaclient/zetacore/client_query_test.go | 2 +- zetaclient/zetacore/tx_test.go | 24 ++---- 15 files changed, 168 insertions(+), 191 deletions(-) create mode 100644 pkg/slices/slices.go diff --git a/pkg/slices/slices.go b/pkg/slices/slices.go new file mode 100644 index 0000000000..d26c334c4d --- /dev/null +++ b/pkg/slices/slices.go @@ -0,0 +1,12 @@ +package slices + +// Map applies a function to each item in a slice and returns a new slice with the results. +func Map[T, V any](items []T, f func(T) V) []V { + result := make([]V, len(items)) + + for i, item := range items { + result[i] = f(item) + } + + return result +} diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index d8532c5797..5a419d9ab3 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -69,11 +69,6 @@ func (ob *Observer) WatchInbound(ctx context.Context) error { // ObserveInbound observes the Bitcoin chain for inbounds and post votes to zetacore // TODO(revamp): simplify this function into smaller functions func (ob *Observer) ObserveInbound(ctx context.Context) error { - app, err := zctx.FromContext(ctx) - if err != nil { - return err - } - zetaCoreClient := ob.ZetacoreClient() // get and update latest block height diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index 2abfbe89d7..a0c10f2cda 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -508,7 +508,7 @@ func (ob *Observer) CheckAndVoteInboundTokenERC20( } // get erc20 custody contract - addrCustory, custody, err := ob.GetERC20CustodyContract() + addrCustody, custody, err := ob.GetERC20CustodyContract() if err != nil { return "", err } @@ -520,7 +520,7 @@ func (ob *Observer) CheckAndVoteInboundTokenERC20( zetaDeposited, err := custody.ParseDeposited(*log) if err == nil && zetaDeposited != nil { // sanity check tx event - err = evm.ValidateEvmTxLog(&zetaDeposited.Raw, addrCustory, tx.Hash, evm.TopicsDeposited) + err = evm.ValidateEvmTxLog(&zetaDeposited.Raw, addrCustody, tx.Hash, evm.TopicsDeposited) if err == nil { msg = ob.BuildInboundVoteMsgForDepositedEvent(zetaDeposited, sender) } else { diff --git a/zetaclient/chains/evm/observer/inbound_test.go b/zetaclient/chains/evm/observer/inbound_test.go index 9e01c214b3..89fbb21db4 100644 --- a/zetaclient/chains/evm/observer/inbound_test.go +++ b/zetaclient/chains/evm/observer/inbound_test.go @@ -83,17 +83,17 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { require.Equal(t, "", ballot) }) t.Run("should not act if emitter is not ZetaConnector", func(t *testing.T) { - tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx( - t, + tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx(t, TestDataDir, - chainID, + 1, // ETH CHAIN inboundHash, coin.CoinType_Zeta, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - chainID = 56 // use BSC chain connector + params := mocks.MockChainParams(chain.ChainId, confirmation) + ob := MockEVMObserver( t, chain, @@ -102,8 +102,9 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { nil, nil, lastBlock, - mocks.MockChainParams(chainID, confirmation), + params, ) + _, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, true) require.ErrorContains(t, err, "emitter address mismatch") }) diff --git a/zetaclient/chains/evm/observer/observer_test.go b/zetaclient/chains/evm/observer/observer_test.go index f0b47044d5..5d6578e37d 100644 --- a/zetaclient/chains/evm/observer/observer_test.go +++ b/zetaclient/chains/evm/observer/observer_test.go @@ -36,6 +36,7 @@ var TestDataDir = "../../../" // getAppContext creates an AppContext for unit tests func getAppContext( + t *testing.T, evmChain chains.Chain, endpoint string, evmChainParams *observertypes.ChainParams, @@ -45,6 +46,8 @@ func getAppContext( endpoint = "http://localhost:8545" } + require.Equal(t, evmChain.ChainId, evmChainParams.ChainId, "chain id mismatch between chain and params") + // create config cfg := config.New(false) cfg.EVMChainConfigs[evmChain.ChainId] = config.EVMConfig{ @@ -52,24 +55,24 @@ func getAppContext( Endpoint: endpoint, } + logger := zerolog.New(zerolog.NewTestWriter(t)) + // create AppContext - appContext := zctx.New(cfg, zerolog.Nop()) - evmChainParamsMap := make(map[int64]*observertypes.ChainParams) - evmChainParamsMap[evmChain.ChainId] = evmChainParams + appContext := zctx.New(cfg, logger) + chainParams := make(map[int64]*observertypes.ChainParams) + chainParams[evmChain.ChainId] = evmChainParams // feed chain params - appContext.Update( - &observertypes.Keygen{}, + err := appContext.Update( + observertypes.Keygen{}, []chains.Chain{evmChain}, - evmChainParamsMap, nil, - nil, - "", + chainParams, + "tssPubKey", *sample.CrosschainFlags(), - []chains.Chain{}, - sample.HeaderSupportedChains(), - true, ) + require.NoError(t, err) + // create AppContext return appContext, cfg.EVMChainConfigs[evmChain.ChainId] } @@ -105,7 +108,7 @@ func MockEVMObserver( tss = mocks.NewTSSMainnet() } // create AppContext - _, evmCfg := getAppContext(chain, "", ¶ms) + _, evmCfg := getAppContext(t, chain, "", ¶ms) database, err := db.NewFromSqliteInMemory(true) require.NoError(t, err) diff --git a/zetaclient/chains/evm/observer/outbound_test.go b/zetaclient/chains/evm/observer/outbound_test.go index 8b0ad1573c..bd596eb4dc 100644 --- a/zetaclient/chains/evm/observer/outbound_test.go +++ b/zetaclient/chains/evm/observer/outbound_test.go @@ -12,7 +12,6 @@ import ( "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/testutil/sample" - observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/testutils" @@ -199,7 +198,7 @@ func Test_PostVoteOutbound(t *testing.T) { receiveStatus := chains.ReceiveStatus_success // create evm client using mock zetacore client and post outbound vote - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, observertypes.ChainParams{}) + ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(chain.ChainId, 100)) ob.PostVoteOutbound( ctx, cctx.Index, diff --git a/zetaclient/chains/evm/signer/outbound_data.go b/zetaclient/chains/evm/signer/outbound_data.go index 1f3d3d873d..59cc86cff4 100644 --- a/zetaclient/chains/evm/signer/outbound_data.go +++ b/zetaclient/chains/evm/signer/outbound_data.go @@ -4,11 +4,11 @@ import ( "context" "encoding/base64" "encoding/hex" - "errors" "fmt" "math/big" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/pkg/chains" @@ -93,7 +93,7 @@ func (txData *OutboundData) SetupGas( if chain.Network == chains.Network_eth { suggested, err := client.SuggestGasPrice(context.Background()) if err != nil { - return errors.Join(err, fmt.Errorf("cannot get gas price from chain %s ", chain.String())) + return errors.Wrapf(err, "cannot get gas price from chain %s ", chain.String()) } txData.gasPrice = roundUpToNearestGwei(suggested) } else { @@ -139,9 +139,11 @@ func NewOutboundData( return nil, false, err } - toChain, err := app.GetChain(txData.toChainID.Int64()) + chainID := txData.toChainID.Int64() + + toChain, err := app.GetChain(chainID) if err != nil { - return nil, true, err + return nil, true, errors.Wrapf(err, "unable to get chain %d from app context", chainID) } rawChain := toChain.RawChain() @@ -160,7 +162,7 @@ func NewOutboundData( // Set up gas limit and gas price err = txData.SetupGas(cctx, logger, evmRPC, *rawChain) if err != nil { - return nil, true, err + return nil, true, errors.Wrap(err, "unable to setup gas") } // Get sendHash diff --git a/zetaclient/chains/evm/signer/outbound_data_test.go b/zetaclient/chains/evm/signer/outbound_data_test.go index ac2b7061b5..dce5c2a888 100644 --- a/zetaclient/chains/evm/signer/outbound_data_test.go +++ b/zetaclient/chains/evm/signer/outbound_data_test.go @@ -7,9 +7,12 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/x/crosschain/types" @@ -72,6 +75,19 @@ func TestSigner_NewOutboundData(t *testing.T) { app := zctx.New(config.New(false), zerolog.Nop()) ctx := zctx.WithAppContext(context.Background(), app) + bscParams := mocks.MockChainParams(chains.BscMainnet.ChainId, 10) + + // Given app context + err := app.Update( + observertypes.Keygen{}, + []chains.Chain{chains.BscMainnet}, + nil, + map[int64]*observertypes.ChainParams{chains.BscMainnet.ChainId: &bscParams}, + "tssPubKey", + observertypes.CrosschainFlags{}, + ) + require.NoError(t, err) + // Setup evm signer evmSigner, err := getNewEvmSigner(nil) require.NoError(t, err) @@ -81,33 +97,35 @@ func TestSigner_NewOutboundData(t *testing.T) { t.Run("NewOutboundData success", func(t *testing.T) { cctx := getCCTX(t) + _, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.False(t, skip) - require.NoError(t, err) + assert.NoError(t, err) + assert.False(t, skip) }) t.Run("NewOutboundData skip", func(t *testing.T) { cctx := getCCTX(t) cctx.CctxStatus.Status = types.CctxStatus_Aborted + _, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.NoError(t, err) - require.True(t, skip) + assert.NoError(t, err) + assert.True(t, skip) }) t.Run("NewOutboundData unknown chain", func(t *testing.T) { cctx := getInvalidCCTX(t) - require.NoError(t, err) + _, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.ErrorContains(t, err, "unknown chain") - require.True(t, skip) + assert.ErrorContains(t, err, "unable to get chain 13378337 from app context: chain not found") + assert.True(t, skip) }) t.Run("NewOutboundData setup gas error", func(t *testing.T) { cctx := getCCTX(t) - require.NoError(t, err) cctx.GetCurrentOutboundParam().GasPrice = "invalidGasPrice" + _, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.True(t, skip) - require.ErrorContains(t, err, "cannot convert gas price") + assert.True(t, skip) + assert.ErrorContains(t, err, "cannot convert gas price") }) } diff --git a/zetaclient/chains/evm/signer/signer_test.go b/zetaclient/chains/evm/signer/signer_test.go index b0cf3e5504..95da02e648 100644 --- a/zetaclient/chains/evm/signer/signer_test.go +++ b/zetaclient/chains/evm/signer/signer_test.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog" "github.com/stretchr/testify/require" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/db" "github.com/zeta-chain/zetacore/zetaclient/keys" @@ -161,7 +162,7 @@ func TestSigner_SetGetERC20CustodyAddress(t *testing.T) { } func TestSigner_TryProcessOutbound(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) evmSigner, err := getNewEvmSigner(nil) require.NoError(t, err) @@ -184,7 +185,7 @@ func TestSigner_TryProcessOutbound(t *testing.T) { } func TestSigner_SignOutbound(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -221,7 +222,7 @@ func TestSigner_SignOutbound(t *testing.T) { } func TestSigner_SignRevertTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -261,7 +262,7 @@ func TestSigner_SignRevertTx(t *testing.T) { } func TestSigner_SignCancelTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -301,7 +302,7 @@ func TestSigner_SignCancelTx(t *testing.T) { } func TestSigner_SignWithdrawTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -340,7 +341,7 @@ func TestSigner_SignWithdrawTx(t *testing.T) { } func TestSigner_SignCommandTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer evmSigner, err := getNewEvmSigner(nil) @@ -386,7 +387,7 @@ func TestSigner_SignCommandTx(t *testing.T) { } func TestSigner_SignERC20WithdrawTx(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -427,7 +428,7 @@ func TestSigner_SignERC20WithdrawTx(t *testing.T) { } func TestSigner_BroadcastOutbound(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer evmSigner, err := getNewEvmSigner(nil) @@ -437,9 +438,10 @@ func TestSigner_BroadcastOutbound(t *testing.T) { cctx := getCCTX(t) mockObserver, err := getNewEvmChainObserver(t, nil) require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.False(t, skip) require.NoError(t, err) + require.False(t, skip) t.Run("BroadcastOutbound - should successfully broadcast", func(t *testing.T) { // Call SignERC20WithdrawTx @@ -481,7 +483,7 @@ func TestSigner_SignerErrorMsg(t *testing.T) { } func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -490,11 +492,13 @@ func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { // Setup txData struct cctx := getCCTX(t) + mockObserver, err := getNewEvmChainObserver(t, tss) require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - require.False(t, skip) require.NoError(t, err) + require.False(t, skip) t.Run("SignWhitelistERC20Cmd - should successfully sign", func(t *testing.T) { // Call SignWhitelistERC20Cmd @@ -526,7 +530,7 @@ func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { } func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { - ctx := makeCtx() + ctx := makeCtx(t) // Setup evm signer tss := mocks.NewTSSMainnet() @@ -565,8 +569,20 @@ func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { require.Nil(t, tx) }) } -func makeCtx() context.Context { +func makeCtx(t *testing.T) context.Context { app := zctx.New(config.New(false), zerolog.Nop()) + bscParams := mocks.MockChainParams(chains.BscMainnet.ChainId, 10) + + err := app.Update( + observertypes.Keygen{}, + []chains.Chain{chains.BscMainnet}, + nil, + map[int64]*observertypes.ChainParams{chains.BscMainnet.ChainId: &bscParams}, + "tssPubKey", + observertypes.CrosschainFlags{}, + ) + require.NoError(t, err, "unable to update app context") + return zctx.WithAppContext(context.Background(), app) } diff --git a/zetaclient/orchestrator/bootstap_test.go b/zetaclient/orchestrator/bootstap_test.go index 55b57ef1bc..55b6f47614 100644 --- a/zetaclient/orchestrator/bootstap_test.go +++ b/zetaclient/orchestrator/bootstap_test.go @@ -173,36 +173,6 @@ func TestCreateSignerMap(t *testing.T) { hasSigner(t, signers, chains.BitcoinMainnet.ChainId) }) - t.Run("Polygon is there but not supported, should be disabled", func(t *testing.T) { - // ARRANGE - // Given updated data from zetacore containing polygon chain - supportedChain, params := chainParams([]chains.Chain{ - chains.Ethereum, - chains.Polygon, - chains.BitcoinMainnet, - }) - - // BUT (!) it's disabled via zetacore - params[chains.Polygon.ChainId].IsSupported = false - - mustUpdateAppContext(t, app, supportedChain, nil, params) - - // Should have signer BEFORE disabling - hasSigner(t, signers, chains.Polygon.ChainId) - - // ACT - added, removed, err := syncSignerMap(ctx, tss, baseLogger, ts, &signers) - - // ASSERT - assert.NoError(t, err) - assert.Equal(t, 0, added) - assert.Equal(t, 1, removed) - - hasSigner(t, signers, chains.Ethereum.ChainId) - missesSigner(t, signers, chains.Polygon.ChainId) - hasSigner(t, signers, chains.BitcoinMainnet.ChainId) - }) - t.Run("No changes", func(t *testing.T) { // ARRANGE before := len(signers) @@ -402,36 +372,6 @@ func TestCreateChainObserverMap(t *testing.T) { hasObserver(t, observers, chains.BitcoinMainnet.ChainId) }) - t.Run("Polygon is there but not supported, should be disabled", func(t *testing.T) { - // ARRANGE - // Given updated data from zetacore containing polygon chain - supportedChain, params := chainParams([]chains.Chain{ - chains.Ethereum, - chains.Polygon, - chains.BitcoinMainnet, - }) - - // BUT (!) it's disabled via zetacore - params[chains.Polygon.ChainId].IsSupported = false - - mustUpdateAppContext(t, app, supportedChain, nil, params) - - // Should have signer BEFORE disabling - hasObserver(t, observers, chains.Polygon.ChainId) - - // ACT - added, removed, err := syncObserverMap(ctx, client, tss, dbPath, baseLogger, ts, &observers) - - // ASSERT - assert.NoError(t, err) - assert.Equal(t, 0, added) - assert.Equal(t, 1, removed) - - hasObserver(t, observers, chains.Ethereum.ChainId) - missesObserver(t, observers, chains.Polygon.ChainId) - hasObserver(t, observers, chains.BitcoinMainnet.ChainId) - }) - t.Run("No changes", func(t *testing.T) { // ARRANGE before := len(observers) @@ -454,20 +394,15 @@ func chainParams(supportedChains []chains.Chain) ([]chains.Chain, map[int64]*obs for _, chain := range supportedChains { chainID := chain.ChainId if chains.IsBitcoinChain(chainID, nil) { - params[chainID] = &observertypes.ChainParams{ - ChainId: chainID, - IsSupported: true, - } - + p := mocks.MockChainParams(chainID, 100) + params[chainID] = &p continue } if chains.IsSolanaChain(chainID, nil) { - params[chainID] = &observertypes.ChainParams{ - ChainId: chainID, - IsSupported: true, - GatewayAddress: solanaGatewayAddress, - } + p := mocks.MockChainParams(chainID, 100) + p.GatewayAddress = solanaGatewayAddress + params[chainID] = &p continue } @@ -496,7 +431,7 @@ func mustUpdateAppContext( chains, additionalChains, chainParams, - app.GetCurrentTssPubKey(), + "tssPubKey", app.GetCrossChainFlags(), ) diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go index b1b227f7b2..ec77783e31 100644 --- a/zetaclient/orchestrator/orchestrator_test.go +++ b/zetaclient/orchestrator/orchestrator_test.go @@ -57,6 +57,7 @@ func MockOrchestrator( } func CreateAppContext( + t *testing.T, evmChain, btcChain chains.Chain, evmChainParams, btcChainParams *observertypes.ChainParams, ) *zctx.AppContext { @@ -78,14 +79,16 @@ func CreateAppContext( ccFlags := sample.CrosschainFlags() // feed chain params - appContext.Update( + err := appContext.Update( observertypes.Keygen{}, []chains.Chain{evmChain, btcChain}, nil, params, - "", + "tssPubKey", *ccFlags, ) + require.NoError(t, err) + return appContext } @@ -93,32 +96,26 @@ func Test_GetUpdatedSigner(t *testing.T) { // initial parameters for orchestrator creation evmChain := chains.Ethereum btcChain := chains.BitcoinMainnet - evmChainParams := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - ConnectorContractAddress: testutils.ConnectorAddresses[evmChain.ChainId].Hex(), - Erc20CustodyContractAddress: testutils.CustodyAddresses[evmChain.ChainId].Hex(), - } - btcChainParams := &observertypes.ChainParams{} + evmChainParams := mocks.MockChainParams(evmChain.ChainId, 100) + btcChainParams := mocks.MockChainParams(btcChain.ChainId, 100) // new chain params in AppContext - evmChainParamsNew := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - ConnectorContractAddress: testutils.OtherAddress1, - Erc20CustodyContractAddress: testutils.OtherAddress2, - } + evmChainParamsNew := mocks.MockChainParams(evmChainParams.ChainId, 100) + evmChainParamsNew.ConnectorContractAddress = testutils.OtherAddress1 + evmChainParamsNew.Erc20CustodyContractAddress = testutils.OtherAddress2 t.Run("signer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) - context := CreateAppContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) + appContext := CreateAppContext(t, evmChain, btcChain, &evmChainParamsNew, &btcChainParams) // BSC signer should not be found - _, err := orchestrator.resolveSigner(context, chains.BscMainnet.ChainId) + _, err := orchestrator.resolveSigner(appContext, chains.BscMainnet.ChainId) require.ErrorContains(t, err, "signer not found") }) t.Run("should be able to update connector and erc20 custody address", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) - context := CreateAppContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) + appContext := CreateAppContext(t, evmChain, btcChain, &evmChainParamsNew, &btcChainParams) // update signer with new connector and erc20 custody address - signer, err := orchestrator.resolveSigner(context, evmChain.ChainId) + signer, err := orchestrator.resolveSigner(appContext, evmChain.ChainId) require.NoError(t, err) require.Equal(t, testutils.OtherAddress1, signer.GetZetaConnectorAddress().Hex()) require.Equal(t, testutils.OtherAddress2, signer.GetERC20CustodyAddress().Hex()) @@ -129,14 +126,8 @@ func Test_GetUpdatedChainObserver(t *testing.T) { // initial parameters for orchestrator creation evmChain := chains.Ethereum btcChain := chains.BitcoinMainnet - evmChainParams := &observertypes.ChainParams{ - ChainId: evmChain.ChainId, - ConnectorContractAddress: testutils.ConnectorAddresses[evmChain.ChainId].Hex(), - Erc20CustodyContractAddress: testutils.CustodyAddresses[evmChain.ChainId].Hex(), - } - btcChainParams := &observertypes.ChainParams{ - ChainId: btcChain.ChainId, - } + evmChainParams := mocks.MockChainParams(evmChain.ChainId, 100) + btcChainParams := mocks.MockChainParams(btcChain.ChainId, 100) // new chain params in AppContext evmChainParamsNew := &observertypes.ChainParams{ @@ -173,15 +164,15 @@ func Test_GetUpdatedChainObserver(t *testing.T) { } t.Run("evm chain observer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) - appContext := CreateAppContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) + appContext := CreateAppContext(t, evmChain, btcChain, evmChainParamsNew, &btcChainParams) // BSC chain observer should not be found _, err := orchestrator.resolveObserver(appContext, chains.BscMainnet.ChainId) require.ErrorContains(t, err, "observer not found") }) t.Run("chain params in evm chain observer should be updated successfully", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) - appContext := CreateAppContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) + orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) + appContext := CreateAppContext(t, evmChain, btcChain, evmChainParamsNew, &btcChainParams) // update evm chain observer with new chain params chainOb, err := orchestrator.resolveObserver(appContext, evmChain.ChainId) require.NoError(t, err) @@ -189,15 +180,15 @@ func Test_GetUpdatedChainObserver(t *testing.T) { require.True(t, observertypes.ChainParamsEqual(*evmChainParamsNew, chainOb.GetChainParams())) }) t.Run("btc chain observer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) - appContext := CreateAppContext(btcChain, btcChain, evmChainParams, btcChainParamsNew) + orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) + appContext := CreateAppContext(t, btcChain, btcChain, &evmChainParams, btcChainParamsNew) // BTC testnet chain observer should not be found _, err := orchestrator.resolveObserver(appContext, chains.BitcoinTestnet.ChainId) require.ErrorContains(t, err, "observer not found") }) t.Run("chain params in btc chain observer should be updated successfully", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) - appContext := CreateAppContext(btcChain, btcChain, evmChainParams, btcChainParamsNew) + orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) + appContext := CreateAppContext(t, btcChain, btcChain, &evmChainParams, btcChainParamsNew) // update btc chain observer with new chain params chainOb, err := orchestrator.resolveObserver(appContext, btcChain.ChainId) require.NoError(t, err) diff --git a/zetaclient/testutils/mocks/chain_params.go b/zetaclient/testutils/mocks/chain_params.go index 45c5df2497..19603eda34 100644 --- a/zetaclient/testutils/mocks/chain_params.go +++ b/zetaclient/testutils/mocks/chain_params.go @@ -13,11 +13,32 @@ import ( ) func MockChainParams(chainID int64, confirmation uint64) observertypes.ChainParams { + const zeroAddress = "0x0000000000000000000000000000000000000000" + + connectorAddr := zeroAddress + if a, ok := testutils.ConnectorAddresses[chainID]; ok { + connectorAddr = a.Hex() + } + + erc20CustodyAddr := zeroAddress + if a, ok := testutils.CustodyAddresses[chainID]; ok { + erc20CustodyAddr = a.Hex() + } + return observertypes.ChainParams{ ChainId: chainID, ConfirmationCount: confirmation, - ConnectorContractAddress: testutils.ConnectorAddresses[chainID].Hex(), - Erc20CustodyContractAddress: testutils.CustodyAddresses[chainID].Hex(), + ZetaTokenContractAddress: zeroAddress, + ConnectorContractAddress: connectorAddr, + Erc20CustodyContractAddress: erc20CustodyAddr, + InboundTicker: 12, + OutboundTicker: 15, + WatchUtxoTicker: 0, + GasPriceTicker: 30, + OutboundScheduleInterval: 30, + OutboundScheduleLookahead: 60, + BallotThreshold: observertypes.DefaultBallotThreshold, + MinObserverDelegation: observertypes.DefaultMinObserverDelegation, IsSupported: true, } } diff --git a/zetaclient/testutils/mocks/zetacore_client.go b/zetaclient/testutils/mocks/zetacore_client.go index b1dbd3f741..168b580ada 100644 --- a/zetaclient/testutils/mocks/zetacore_client.go +++ b/zetaclient/testutils/mocks/zetacore_client.go @@ -1,10 +1,10 @@ -// Code generated by mockery v2.38.0. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks import ( - blame "gitlab.com/thorchain/tss/go-tss/blame" chains "github.com/zeta-chain/zetacore/pkg/chains" + blame "gitlab.com/thorchain/tss/go-tss/blame" context "context" @@ -283,24 +283,22 @@ func (_m *ZetacoreClient) GetInboundTrackersForChain(ctx context.Context, chainI } // GetKeyGen provides a mock function with given fields: ctx -func (_m *ZetacoreClient) GetKeyGen(ctx context.Context) (*observertypes.Keygen, error) { +func (_m *ZetacoreClient) GetKeyGen(ctx context.Context) (observertypes.Keygen, error) { ret := _m.Called(ctx) if len(ret) == 0 { panic("no return value specified for GetKeyGen") } - var r0 *observertypes.Keygen + var r0 observertypes.Keygen var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*observertypes.Keygen, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context) (observertypes.Keygen, error)); ok { return rf(ctx) } - if rf, ok := ret.Get(0).(func(context.Context) *observertypes.Keygen); ok { + if rf, ok := ret.Get(0).(func(context.Context) observertypes.Keygen); ok { r0 = rf(ctx) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*observertypes.Keygen) - } + r0 = ret.Get(0).(observertypes.Keygen) } if rf, ok := ret.Get(1).(func(context.Context) error); ok { diff --git a/zetaclient/zetacore/client_query_test.go b/zetaclient/zetacore/client_query_test.go index 62560f21ec..cb2632b41b 100644 --- a/zetaclient/zetacore/client_query_test.go +++ b/zetaclient/zetacore/client_query_test.go @@ -626,7 +626,7 @@ func TestZetacore_GetKeyGen(t *testing.T) { resp, err := client.GetKeyGen(ctx) require.NoError(t, err) - require.Equal(t, expectedOutput.Keygen, resp) + require.Equal(t, *expectedOutput.Keygen, resp) } func TestZetacore_GetBallotByID(t *testing.T) { diff --git a/zetaclient/zetacore/tx_test.go b/zetaclient/zetacore/tx_test.go index bf78a7ab59..3c8a019f94 100644 --- a/zetaclient/zetacore/tx_test.go +++ b/zetaclient/zetacore/tx_test.go @@ -239,6 +239,8 @@ func TestZetacore_UpdateAppContext(t *testing.T) { listener, err := net.Listen("tcp", "127.0.0.1:9090") require.NoError(t, err) + ethChainParams := mocks.MockChainParams(chains.Ethereum.ChainId, 100) + server := grpcmock.MockUnstartedServer( grpcmock.RegisterService(crosschaintypes.RegisterQueryServer), grpcmock.RegisterService(upgradetypes.RegisterQueryServer), @@ -271,9 +273,8 @@ func TestZetacore_UpdateAppContext(t *testing.T) { WithPayload(observertypes.QueryGetChainParamsRequest{}). Return(observertypes.QueryGetChainParamsResponse{ChainParams: &observertypes.ChainParamsList{ ChainParams: []*observertypes.ChainParams{ - { - ChainId: 7000, - }, + {ChainId: 7000}, // ZetaChain + ðChainParams, }, }}) @@ -341,21 +342,6 @@ func TestZetacore_UpdateAppContext(t *testing.T) { GasPriceIncreaseFlags: nil, }}) - method = "/zetachain.zetacore.lightclient.Query/HeaderEnabledChains" - s.ExpectUnary(method). - UnlimitedTimes(). - WithPayload(lightclienttypes.QueryHeaderEnabledChainsRequest{}). - Return(lightclienttypes.QueryHeaderEnabledChainsResponse{HeaderEnabledChains: []lightclienttypes.HeaderSupportedChain{ - { - ChainId: chains.Ethereum.ChainId, - Enabled: true, - }, - { - ChainId: chains.BitcoinMainnet.ChainId, - Enabled: false, - }, - }}) - method = "/zetachain.zetacore.authority.Query/ChainInfo" s.ExpectUnary(method). UnlimitedTimes(). @@ -384,7 +370,7 @@ func TestZetacore_UpdateAppContext(t *testing.T) { t.Run("zetacore update success", func(t *testing.T) { cfg := config.New(false) appContext := zctx.New(cfg, zerolog.Nop()) - err := client.UpdateAppContext(ctx, appContext, false, zerolog.Logger{}) + err := client.UpdateAppContext(ctx, appContext, zerolog.New(zerolog.NewTestWriter(t))) require.NoError(t, err) }) } From 09478d6102bf0216ab27f250825cf26ee1a74183 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 29 Jul 2024 20:53:06 +0200 Subject: [PATCH 08/24] Update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 7840fffb4e..ae56535a5c 100644 --- a/changelog.md +++ b/changelog.md @@ -65,6 +65,7 @@ * [2428](https://github.com/zeta-chain/node/pull/2428) - propagate context across codebase & refactor zetacore client * [2464](https://github.com/zeta-chain/node/pull/2464) - move common voting logic to voting.go and add new function VoteOnBallot * [2515](https://github.com/zeta-chain/node/pull/2515) - replace chainName by chainID for ChainNonces indexing +* [2568](https://github.com/zeta-chain/node/pull/2568) - improve AppContext ### Tests From 2d6e3123e996155b088619478a74076bc14af928 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 30 Jul 2024 16:27:35 +0200 Subject: [PATCH 09/24] Allow Zeta Chain in appContext; address PR comments [1] --- pkg/slices/slices.go | 44 ++++++++++++++++++ x/observer/types/chain_params.go | 16 +------ zetaclient/context/app.go | 77 +++++++++++++++----------------- zetaclient/context/app_test.go | 29 +++++++++--- zetaclient/context/chain.go | 71 ++++++++++++++++------------- zetaclient/context/chain_test.go | 23 ++++++---- 6 files changed, 159 insertions(+), 101 deletions(-) diff --git a/pkg/slices/slices.go b/pkg/slices/slices.go index d26c334c4d..21f33e8279 100644 --- a/pkg/slices/slices.go +++ b/pkg/slices/slices.go @@ -1,5 +1,10 @@ package slices +import ( + "golang.org/x/exp/constraints" + "golang.org/x/exp/slices" +) + // Map applies a function to each item in a slice and returns a new slice with the results. func Map[T, V any](items []T, f func(T) V) []V { result := make([]V, len(items)) @@ -10,3 +15,42 @@ func Map[T, V any](items []T, f func(T) V) []V { return result } + +// ElementsMatch returns true if two slices have the same elements in the same order. +// Note that this function SORTS the slices before comparing them. +func ElementsMatch[T constraints.Ordered](a, b []T) bool { + if len(a) != len(b) { + return false + } + + slices.Sort(a) + slices.Sort(b) + + for i, v := range a { + if v != b[i] { + return false + } + } + + return true +} + +// Diff returns the elements in `a` that are not in `b` +func Diff[T comparable](a, b []T) []T { + var ( + cache = map[T]struct{}{} + result []T + ) + + for _, v := range b { + cache[v] = struct{}{} + } + + for _, v := range a { + if _, ok := cache[v]; !ok { + result = append(result, v) + } + } + + return result +} diff --git a/x/observer/types/chain_params.go b/x/observer/types/chain_params.go index b2cbb63c32..2e58eeb2de 100644 --- a/x/observer/types/chain_params.go +++ b/x/observer/types/chain_params.go @@ -8,7 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethchains "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" "github.com/zeta-chain/zetacore/pkg/chains" solanacontract "github.com/zeta-chain/zetacore/pkg/contract/solana" @@ -57,19 +56,8 @@ func ValidateChainParams(params *ChainParams) error { return fmt.Errorf("chain params cannot be nil") } - // TODO: ZetaChain chain params should be completely removed - // Once removed, this check is no longer necessary as all chasin params would need the same checks - // https://github.com/zeta-chain/node/issues/2419 - _, err := chains.ZetaChainFromChainID(params.ChainId) - if err == nil { - // zeta chain skips the rest of the checks for now - return nil - } - - // ignore error from ZetaChainFromChainID if reason is chain is not zeta chain - // return error otherwise - if !errors.Is(err, chains.ErrNotZetaChain) { - return err + if chains.IsZetaChain(params.ChainId, nil) { + return fmt.Errorf("zeta chain cannot have observer chain parameters") } if params.ConfirmationCount == 0 { diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index 14535f8a7c..c5aec61db0 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -8,9 +8,9 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog" "golang.org/x/exp/maps" - "golang.org/x/exp/slices" "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/slices" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" ) @@ -166,13 +166,24 @@ func (a *AppContext) Update( return nil } +// updateChainRegistry updates the chain registry with fresh chains and chain params. +// Note that there's an edge-case for ZetaChain itself because we WANT to have it in chains list, +// but it doesn't have chain params. func (a *AppContext) updateChainRegistry( - freshChains, additionalChains []chains.Chain, + freshChains []chains.Chain, + additionalChains []chains.Chain, freshChainParams map[int64]*observertypes.ChainParams, ) error { + var zetaChainID int64 + + // 1. build map[chainId]Chain freshChainsByID := make(map[int64]chains.Chain, len(freshChains)+len(additionalChains)) for _, c := range freshChains { freshChainsByID[c.ChainId] = c + + if isZeta(c.ChainId) && zetaChainID == 0 { + zetaChainID = c.ChainId + } } for _, c := range additionalChains { @@ -189,7 +200,8 @@ func (a *AppContext) updateChainRegistry( existingChainIDs = a.chainRegistry.ChainIDs() ) - if len(existingChainIDs) > 0 && !elementsMatch(existingChainIDs, freshChainIDs) { + // 2. Compare existing chains with fresh ones + if len(existingChainIDs) > 0 && !slices.ElementsMatch(existingChainIDs, freshChainIDs) { a.logger.Warn(). Ints64("chains.current", existingChainIDs). Ints64("chains.new", freshChainIDs). @@ -198,25 +210,33 @@ func (a *AppContext) updateChainRegistry( // Log warn if somehow chain doesn't chainParam for _, chainID := range freshChainIDs { - if _, ok := freshChainParams[chainID]; !ok { + if _, ok := freshChainParams[chainID]; !ok && !isZeta(chainID) { a.logger.Warn(). Int64("chain.id", chainID). Msg("Chain doesn't have according ChainParams present. Skipping.") } } + // 3. If we have zeta chain, we want to force "fake" chainParams for it + if zetaChainID != 0 { + freshChainParams[zetaChainID] = zetaObserverChainParams(zetaChainID) + } + + // 3. Update chain registry // okay, let's update the chains. // Set() ensures that chain, chainID, and params are consistent and chain is not zeta + chain is supported for chainID, params := range freshChainParams { - if err := observertypes.ValidateChainParams(params); err != nil { - return errors.Wrapf(err, "invalid chain params for chain %d", chainID) - } - chain, ok := freshChainsByID[chainID] if !ok { return fmt.Errorf("unable to locate fresh chain %d based on chain params", chainID) } + if !isZeta(chainID) { + if err := observertypes.ValidateChainParams(params); err != nil { + return errors.Wrapf(err, "invalid chain params for chain %d", chainID) + } + } + if err := a.chainRegistry.Set(chainID, &chain, params); err != nil { return errors.Wrap(err, "unable to set chain in the registry") } @@ -224,7 +244,7 @@ func (a *AppContext) updateChainRegistry( a.chainRegistry.SetAdditionalChains(additionalChains) - toBeDeleted := diff(existingChainIDs, freshChainIDs) + toBeDeleted := slices.Diff(existingChainIDs, freshChainIDs) if len(toBeDeleted) > 0 { a.logger.Warn(). Ints64("chains.deleted", toBeDeleted). @@ -236,39 +256,12 @@ func (a *AppContext) updateChainRegistry( return nil } -func elementsMatch(a, b []int64) bool { - if len(a) != len(b) { - return false - } - - slices.Sort(a) - slices.Sort(b) - - for i, v := range a { - if v != b[i] { - return false - } - } - - return true +func isZeta(chainID int64) bool { + return chains.IsZetaChain(chainID, nil) } -// diff returns the elements in `a` that are not in `b` -func diff(a, b []int64) []int64 { - var ( - cache = map[int64]struct{}{} - result []int64 - ) - - for _, v := range b { - cache[v] = struct{}{} - } - - for _, v := range a { - if _, ok := cache[v]; !ok { - result = append(result, v) - } - } - - return result +// zetaObserverChainParams returns "fake" chain params because +// actually chainParams is a concept of observer +func zetaObserverChainParams(chainID int64) *observertypes.ChainParams { + return &observertypes.ChainParams{ChainId: chainID, IsSupported: true} } diff --git a/zetaclient/context/app_test.go b/zetaclient/context/app_test.go index 16a452d096..23bb298495 100644 --- a/zetaclient/context/app_test.go +++ b/zetaclient/context/app_test.go @@ -152,33 +152,39 @@ func TestAppContext(t *testing.T) { }, }, { - name: "trying to add zetachain without chain params is allowed but skipped", + name: "trying to add zeta chain without chain params is allowed", act: func(a *AppContext) error { chainsWithZeta := append(newChains, chains.ZetaChainMainnet) return a.Update(keyGen, chainsWithZeta, additionalChains, chainParams, ttsPubKey, ccFlags) }, assert: func(t *testing.T, a *AppContext, err error) { assert.NoError(t, err) - mustBeNotFound(t, a, chains.ZetaChainMainnet.ChainId) + + zc := mustBePresent(t, a, chains.ZetaChainMainnet.ChainId) + assert.True(t, zc.IsZeta()) }, }, { - name: "trying to add zetachain with chain params results in an error", + name: "trying to add zetachain with chain params is allowed but forces fake params", act: func(a *AppContext) error { zetaParams := types.GetDefaultZetaPrivnetChainParams() + zetaParams.ChainId = chains.ZetaChainMainnet.ChainId zetaParams.IsSupported = true + zetaParams.GatewayAddress = "ABC123" chainParamsWithZeta := maps.Clone(chainParams) chainParamsWithZeta[zetaParams.ChainId] = zetaParams - chainsWithZeta := append(newChains, chains.ZetaChainPrivnet) + chainsWithZeta := append(newChains, chains.ZetaChainMainnet) return a.Update(keyGen, chainsWithZeta, additionalChains, chainParamsWithZeta, ttsPubKey, ccFlags) }, assert: func(t *testing.T, a *AppContext, err error) { - assert.ErrorIs(t, err, ErrChainNotSupported) - assert.ErrorContains(t, err, "ZetaChain itself cannot be in the registry") - mustBeNotFound(t, a, chains.ZetaChainMainnet.ChainId) + assert.NoError(t, err) + + zc := mustBePresent(t, a, chains.ZetaChainMainnet.ChainId) + assert.True(t, zc.IsZeta()) + assert.Equal(t, "", zc.Params().GatewayAddress) }, }, { @@ -192,6 +198,7 @@ func TestAppContext(t *testing.T) { updatedChainParams := maps.Clone(chainParams) updatedChainParams[maticParams.ChainId] = maticParams + delete(updatedChainParams, chains.ZetaChainMainnet.ChainId) return a.Update(keyGen, newChains, additionalChains, updatedChainParams, ttsPubKey, ccFlags) }, @@ -219,3 +226,11 @@ func mustBeNotFound(t *testing.T, a *AppContext, chainID int64) { _, err := a.GetChain(chainID) require.ErrorIs(t, err, ErrChainNotFound) } + +func mustBePresent(t *testing.T, a *AppContext, chainID int64) Chain { + t.Helper() + c, err := a.GetChain(chainID) + require.NoError(t, err) + + return c +} diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go index 1b65e7b5c4..e24539498e 100644 --- a/zetaclient/context/chain.go +++ b/zetaclient/context/chain.go @@ -25,9 +25,11 @@ type ChainRegistry struct { // Chain represents chain with its parameters type Chain struct { - id int64 - chain *chains.Chain - params *observer.ChainParams + chainInfo *chains.Chain + observerParams *observer.ChainParams + + // reference to the registry it necessary for some operations + // like checking if the chain is EVM or not because it uses some "global" context state registry *ChainRegistry } @@ -59,7 +61,7 @@ func (cr *ChainRegistry) Get(chainID int64) (Chain, error) { func (cr *ChainRegistry) All() []Chain { items := maps.Values(cr.chains) - slices.SortFunc(items, func(a, b Chain) bool { return a.id < b.id }) + slices.SortFunc(items, func(a, b Chain) bool { return a.ID() < b.ID() }) return items } @@ -77,7 +79,7 @@ func (cr *ChainRegistry) Set(chainID int64, chain *chains.Chain, params *observe cr.mu.Lock() defer cr.mu.Unlock() - cr.chains[item.id] = item + cr.chains[chainID] = item return nil } @@ -115,52 +117,61 @@ func (cr *ChainRegistry) ChainIDs() []int64 { } func newChain(cr *ChainRegistry, chainID int64, chain *chains.Chain, params *observer.ChainParams) (Chain, error) { - switch { - case chainID < 1: - return Chain{}, fmt.Errorf("invalid chain id %d", chainID) - case chain == nil: - return Chain{}, fmt.Errorf("chain is nil") - case params == nil: - return Chain{}, fmt.Errorf("chain params is nil") - case chain.ChainId != chainID: - return Chain{}, fmt.Errorf("chain id %d does not match chain.ChainId %d", chainID, chain.ChainId) - case params.ChainId != chainID: - return Chain{}, fmt.Errorf("chain id %d does not match params.ChainId %d", chainID, params.ChainId) - case !params.IsSupported: - return Chain{}, ErrChainNotSupported - case chains.IsZetaChain(chainID, nil) || !chain.IsExternal: - return Chain{}, errors.Wrap(ErrChainNotSupported, "ZetaChain itself cannot be in the registry") + if err := validateNewChain(chainID, chain, params); err != nil { + return Chain{}, err } return Chain{ - id: chainID, - chain: chain, - params: params, - registry: cr, + chainInfo: chain, + observerParams: params, + registry: cr, }, nil } func (c Chain) ID() int64 { - return c.id + return c.chainInfo.ChainId } func (c Chain) Params() *observer.ChainParams { - return c.params + return c.observerParams } // RawChain returns the underlying Chain object. Better not to use this method func (c Chain) RawChain() *chains.Chain { - return c.chain + return c.chainInfo } func (c Chain) IsEVM() bool { - return chains.IsEVMChain(c.id, c.registry.additionalChains) + return chains.IsEVMChain(c.ID(), c.registry.additionalChains) +} + +func (c Chain) IsZeta() bool { + return chains.IsZetaChain(c.ID(), c.registry.additionalChains) } func (c Chain) IsUTXO() bool { - return chains.IsBitcoinChain(c.id, c.registry.additionalChains) + return chains.IsBitcoinChain(c.ID(), c.registry.additionalChains) } func (c Chain) IsSolana() bool { - return chains.IsSolanaChain(c.id, c.registry.additionalChains) + return chains.IsSolanaChain(c.ID(), c.registry.additionalChains) +} + +func validateNewChain(chainID int64, chain *chains.Chain, params *observer.ChainParams) error { + switch { + case chainID < 1: + return fmt.Errorf("invalid chain id %d", chainID) + case chain == nil: + return fmt.Errorf("chain is nil") + case params == nil: + return fmt.Errorf("chain params is nil") + case chain.ChainId != chainID: + return fmt.Errorf("chain id %d does not match chain.ChainId %d", chainID, chain.ChainId) + case params.ChainId != chainID: + return fmt.Errorf("chain id %d does not match params.ChainId %d", chainID, params.ChainId) + case !params.IsSupported: + return ErrChainNotSupported + } + + return nil } diff --git a/zetaclient/context/chain_test.go b/zetaclient/context/chain_test.go index 7e07b8187a..365553303b 100644 --- a/zetaclient/context/chain_test.go +++ b/zetaclient/context/chain_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/pkg/chains" observer "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" ) func TestChainRegistry(t *testing.T) { @@ -41,6 +42,7 @@ func TestChainRegistry(t *testing.T) { require.NoError(t, r.Set(eth.ChainId, eth, ethParams)) require.NoError(t, r.Set(matic.ChainId, matic, maticParams)) require.NoError(t, r.Set(sol.ChainId, sol, solParams)) + require.NoError(t, r.Set(zeta.ChainId, zeta, zetaParams)) // With failures on invalid data require.Error(t, r.Set(0, btc, btcParams)) @@ -51,11 +53,16 @@ func TestChainRegistry(t *testing.T) { // With failure on adding unsupported chains require.ErrorIs(t, r.Set(opt.ChainId, opt, optParams), ErrChainNotSupported) - // With failure on adding ZetaChain itself - require.ErrorIs(t, r.Set(zeta.ChainId, zeta, zetaParams), ErrChainNotSupported) - // Should return a proper chain list - require.ElementsMatch(t, []int64{btc.ChainId, eth.ChainId, matic.ChainId, sol.ChainId}, r.ChainIDs()) + expectedChains := []int64{ + btc.ChainId, + eth.ChainId, + matic.ChainId, + sol.ChainId, + zeta.ChainId, + } + + require.ElementsMatch(t, expectedChains, r.ChainIDs()) // Should return not found error _, err := r.Get(123) @@ -72,8 +79,8 @@ func TestChainRegistry(t *testing.T) { } func makeParams(id int64, supported bool) *observer.ChainParams { - return &observer.ChainParams{ - ChainId: id, - IsSupported: supported, - } + cp := mocks.MockChainParams(id, 123) + cp.IsSupported = supported + + return &cp } From 487d7e13a7892551e5a11ae014889156825268d7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 30 Jul 2024 17:17:50 +0200 Subject: [PATCH 10/24] Fix app context update --- zetaclient/zetacore/client.go | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/zetaclient/zetacore/client.go b/zetaclient/zetacore/client.go index 6be07983dc..e035939bf7 100644 --- a/zetaclient/zetacore/client.go +++ b/zetaclient/zetacore/client.go @@ -396,16 +396,13 @@ func (c *Client) UpdateAppContext(ctx context.Context, appContext *zctx.AppConte return errors.Wrap(err, "unable to fetch current TSS") } - var ( - freshChains = make([]chains.Chain, 0, len(supportedChains)) - freshParams = make(map[int64]*observertypes.ChainParams, len(chainParams)) - ) + freshParams := make(map[int64]*observertypes.ChainParams, len(chainParams)) // check and update chain params for each chain for chainID := range chainParams { cp := chainParams[chainID] if err := observertypes.ValidateChainParams(cp); err != nil { - logger.Warn().Err(err).Int64("chain", cp.ChainId).Msg("Skipping invalid chain params") + logger.Warn().Err(err).Int64("chain.id", cp.ChainId).Msg("Skipping invalid chain params") continue } @@ -414,26 +411,12 @@ func (c *Client) UpdateAppContext(ctx context.Context, appContext *zctx.AppConte continue } - if chains.IsZetaChain(cp.ChainId, additionalChains) { - logger.Warn().Int64("chain.id", cp.ChainId).Msg("Skipping zeta chain itself") - continue - } - freshParams[cp.ChainId] = cp } - for _, chain := range supportedChains { - if !chain.IsExternal { - logger.Warn().Int64("chain.id", chain.ChainId).Msg("Skipping non-external chain") - continue - } - - freshChains = append(freshChains, chain) - } - return appContext.Update( keyGen, - freshChains, + supportedChains, additionalChains, freshParams, tss.GetTssPubkey(), From 5c63646f2593641c8d3791a72d477590d0b72498 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 30 Jul 2024 17:19:51 +0200 Subject: [PATCH 11/24] Check for `chain.IsZeta()` --- zetaclient/chains/evm/signer/outbound_data.go | 6 +++- zetaclient/chains/evm/signer/signer.go | 22 +++++++++----- zetaclient/orchestrator/bootstrap.go | 11 ++++++- zetaclient/orchestrator/orchestrator.go | 30 ++++++++++++++----- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/zetaclient/chains/evm/signer/outbound_data.go b/zetaclient/chains/evm/signer/outbound_data.go index 59cc86cff4..d29aa21fc4 100644 --- a/zetaclient/chains/evm/signer/outbound_data.go +++ b/zetaclient/chains/evm/signer/outbound_data.go @@ -142,8 +142,12 @@ func NewOutboundData( chainID := txData.toChainID.Int64() toChain, err := app.GetChain(chainID) - if err != nil { + switch { + case err != nil: return nil, true, errors.Wrapf(err, "unable to get chain %d from app context", chainID) + case toChain.IsZeta(): + // should not happen + return nil, true, errors.New("destination chain is Zeta") } rawChain := toChain.RawChain() diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index db9f0fb179..0a3b0736b5 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -394,8 +394,13 @@ func (signer *Signer) TryProcessOutbound( } toChain, err := app.GetChain(txData.toChainID.Int64()) - if err != nil { - logger.Err(err).Msgf("error getting toChain %d", txData.toChainID.Int64()) + switch { + case err != nil: + logger.Error().Err(err).Msgf("error getting toChain %d", txData.toChainID.Int64()) + return + case toChain.IsZeta(): + // should not happen + logger.Error().Msgf("unable to TryProcessOutbound when toChain is zetaChain (%d)", toChain.ID()) return } @@ -570,12 +575,15 @@ func (signer *Signer) BroadcastOutbound( } toChain, err := app.GetChain(txData.toChainID.Int64()) - if err != nil { - logger.Err(err).Msgf("error getting toChain %d", txData.toChainID.Int64()) + switch { + case err != nil: + logger.Error().Err(err).Msgf("error getting toChain %d", txData.toChainID.Int64()) return - } - - if tx == nil { + case toChain.IsZeta(): + // should not happen + logger.Error().Msgf("unable to broadcast when toChain is zetaChain (%d)", toChain.ID()) + return + case tx == nil: logger.Warn().Msgf("BroadcastOutbound: no tx to broadcast %s", cctx.Index) return } diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go index fd29453e77..71c4caae8f 100644 --- a/zetaclient/orchestrator/bootstrap.go +++ b/zetaclient/orchestrator/bootstrap.go @@ -83,6 +83,11 @@ func syncSignerMap( ) for _, chain := range app.ListChains() { + // skip ZetaChain + if chain.IsZeta() { + continue + } + chainID := chain.ID() presentChainIDs = append(presentChainIDs, chainID) @@ -209,8 +214,12 @@ func syncObserverMap( ) for _, chain := range app.ListChains() { - chainID := chain.ID() + // skip ZetaChain + if chain.IsZeta() { + continue + } + chainID := chain.ID() presentChainIDs = append(presentChainIDs, chainID) // noop diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index 56ae914504..40879dc496 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -15,6 +15,7 @@ import ( "github.com/zeta-chain/zetacore/pkg/bg" zetamath "github.com/zeta-chain/zetacore/pkg/math" + "github.com/zeta-chain/zetacore/pkg/slices" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" @@ -153,13 +154,15 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte } chain, err := app.GetChain(chainID) - if err != nil { + switch { + case err != nil: return nil, err - } - - // noop for non-EVM chains - if !chain.IsEVM() { + case chain.IsEVM(): + // noop for non-EVM chains return signer, nil + case chain.IsZeta(): + // should not happen + return nil, fmt.Errorf("unable to resolve signer for zeta chain %d", chainID) } // update zeta connector and ERC20 custody addresses @@ -202,8 +205,12 @@ func (oc *Orchestrator) resolveObserver(app *zctx.AppContext, chainID int64) (in } chain, err := app.GetChain(chainID) - if err != nil { + switch { + case err != nil: return nil, errors.Wrapf(err, "unable to get chain %d", chainID) + case chain.IsZeta(): + // should not happen + return nil, fmt.Errorf("unable to resolve observer for zeta chain %d", chainID) } // update chain observer chain parameters @@ -334,7 +341,11 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { // set current hot key burn rate metrics.HotKeyBurnRate.Set(float64(oc.ts.HotKeyBurnRate.GetBurnRate().Int64())) - chainIDs := app.ListChainIDs() + // get chain ids without zeta chain + chainIDs := slices.Map( + app.FilterChains(zctx.ChainIsNotZeta), + zctx.Chain.ID, + ) // query pending cctxs across all external chains within rate limit cctxMap, err := oc.GetPendingCctxsWithinRateLimit(ctx, chainIDs) @@ -344,6 +355,11 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { // schedule keysign for pending cctxs on each chain for _, chain := range app.ListChains() { + // skip zeta chain + if chain.IsZeta() { + continue + } + chainID := chain.ID() // get cctxs from map and set pending transactions prometheus gauge From f2e3facc2e68022c857b3b981dc2366917268029 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 30 Jul 2024 17:20:08 +0200 Subject: [PATCH 12/24] Add AppContext.FilterChains --- cmd/zetaclientd/start.go | 8 ++++---- zetaclient/context/app.go | 25 ++++++++++++++----------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 56a779141e..525f923d64 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -213,16 +213,16 @@ func start(_ *cobra.Command, _ []string) error { return err } - btcChain, err := appContext.FirstChain(zctx.Chain.IsUTXO) - if err != nil { - return errors.Wrap(err, "unable to find BTC chain") + btcChains := appContext.FilterChains(zctx.Chain.IsUTXO) + if len(btcChains) == 0 { + return errors.New("no BTC chains found") } tss, err := mc.NewTSS( ctx, zetacoreClient, tssHistoricalList, - btcChain.ID(), + btcChains[0].ID(), hotkeyPass, server, ) diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index c5aec61db0..4b65b149f8 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -60,26 +60,25 @@ func (a *AppContext) ListChainIDs() []int64 { return a.chainRegistry.ChainIDs() } +// ListChains returns the list of existing chains in the registry. func (a *AppContext) ListChains() []Chain { return a.chainRegistry.All() } -// FirstChain returns the first chain that satisfies the filter -func (a *AppContext) FirstChain(filter func(Chain) bool) (Chain, error) { - ids := a.ListChainIDs() - - for _, id := range ids { - chain, err := a.GetChain(id) - if err != nil { - return Chain{}, errors.Wrapf(err, "unable to get chain %d", id) - } +// FilterChains returns the list of chains that satisfy the filter +func (a *AppContext) FilterChains(filter func(Chain) bool) []Chain { + var ( + all = a.ListChains() + out = make([]Chain, 0, len(all)) + ) + for _, chain := range all { if filter(chain) { - return chain, nil + out = append(out, chain) } } - return Chain{}, errors.Wrap(ErrChainNotFound, "no chain satisfies the filter") + return out } // IsOutboundObservationEnabled returns true if outbound flag is enabled @@ -265,3 +264,7 @@ func isZeta(chainID int64) bool { func zetaObserverChainParams(chainID int64) *observertypes.ChainParams { return &observertypes.ChainParams{ChainId: chainID, IsSupported: true} } + +func ChainIsNotZeta(c Chain) bool { + return !c.IsZeta() +} From e6fe5d7bb1d1e64d88aea97be02b93d228e3635c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 30 Jul 2024 18:27:44 +0200 Subject: [PATCH 13/24] Fix test cases [2] --- zetaclient/chains/evm/observer/inbound.go | 32 ++++----- .../chains/evm/observer/inbound_test.go | 71 +++++++++++-------- .../chains/evm/observer/observer_gas_test.go | 4 +- .../chains/evm/observer/observer_test.go | 26 ++++--- .../chains/evm/observer/outbound_test.go | 12 ++-- zetaclient/orchestrator/orchestrator.go | 2 +- zetaclient/orchestrator/orchestrator_test.go | 2 +- 7 files changed, 86 insertions(+), 63 deletions(-) diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index a0c10f2cda..8d8634a29c 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -311,18 +311,17 @@ func (ob *Observer) ObserveZetaSent(ctx context.Context, startBlock, toBlock uin guard[event.Raw.TxHash.Hex()] = true msg := ob.BuildInboundVoteMsgForZetaSentEvent(app, event) - if msg != nil { - _, err = ob.PostVoteInbound( - ctx, - msg, - zetacore.PostVoteInboundMessagePassingExecutionGasLimit, - ) - if err != nil { - // we have to re-scan from this block next time - return beingScanned - 1, err - } + if msg == nil { + continue + } + + const gasLimit = zetacore.PostVoteInboundMessagePassingExecutionGasLimit + if _, err = ob.PostVoteInbound(ctx, msg, gasLimit); err != nil { + // we have to re-scan from this block next time + return beingScanned - 1, err } } + // successful processed all events in [startBlock, toBlock] return toBlock, nil } @@ -647,6 +646,7 @@ func (ob *Observer) BuildInboundVoteMsgForZetaSentEvent( appContext *zctx.AppContext, event *zetaconnector.ZetaConnectorNonEthZetaSent, ) *types.MsgVoteInbound { + // not that this is most likely zeta chain destChain, err := appContext.GetChain(event.DestinationChainId.Int64()) if err != nil { ob.Logger().Inbound.Warn().Err(err).Msgf("chain id %d not supported", event.DestinationChainId.Int64()) @@ -663,13 +663,13 @@ func (ob *Observer) BuildInboundVoteMsgForZetaSentEvent( return nil } - if strings.EqualFold(destAddr, destChain.Params().ZetaTokenContractAddress) { - ob.Logger().Inbound.Warn(). - Msgf("potential attack attempt: destination address is ZETA token contract address %s", destAddr) - - return nil + if !destChain.IsZeta() { + if strings.EqualFold(destAddr, destChain.Params().ZetaTokenContractAddress) { + ob.Logger().Inbound.Warn(). + Msgf("potential attack attempt: %s destination address is ZETA token contract address", destAddr) + return nil + } } - message := base64.StdEncoding.EncodeToString(event.Message) ob.Logger().Inbound.Info().Msgf("ZetaSent inbound detected on chain %d tx %s block %d from %s value %s message %s", ob.Chain(). diff --git a/zetaclient/chains/evm/observer/inbound_test.go b/zetaclient/chains/evm/observer/inbound_test.go index 89fbb21db4..231a3ae6c7 100644 --- a/zetaclient/chains/evm/observer/inbound_test.go +++ b/zetaclient/chains/evm/observer/inbound_test.go @@ -45,8 +45,10 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) - ballot, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, false) + ob, appContext := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + voteCtx := zctx.WithAppContext(context.Background(), appContext) + + ballot, err := ob.CheckAndVoteInboundTokenZeta(voteCtx, tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundParams.BallotIndex, ballot) }) @@ -61,7 +63,7 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) _, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) @@ -77,24 +79,27 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, true) require.NoError(t, err) require.Equal(t, "", ballot) }) t.Run("should not act if emitter is not ZetaConnector", func(t *testing.T) { + // Given tx from ETH tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx(t, TestDataDir, - 1, // ETH CHAIN + chains.Ethereum.ChainId, inboundHash, coin.CoinType_Zeta, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation + // Given BSC observer + chain := chains.BscMainnet params := mocks.MockChainParams(chain.ChainId, confirmation) - ob := MockEVMObserver( + ob, _ := MockEVMObserver( t, chain, nil, @@ -105,7 +110,10 @@ func Test_CheckAndVoteInboundTokenZeta(t *testing.T) { params, ) + // ACT _, err := ob.CheckAndVoteInboundTokenZeta(ctx, tx, receipt, true) + + // ASSERT require.ErrorContains(t, err, "emitter address mismatch") }) } @@ -132,7 +140,7 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundParams.BallotIndex, ballot) @@ -148,7 +156,7 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) _, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) @@ -164,24 +172,29 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, true) require.NoError(t, err) require.Equal(t, "", ballot) }) t.Run("should not act if emitter is not ERC20 Custody", func(t *testing.T) { + // ARRANGE + // Given tx from ETH tx, receipt, _ := testutils.LoadEVMInboundNReceiptNCctx( t, TestDataDir, - chainID, + chains.Ethereum.ChainId, inboundHash, coin.CoinType_ERC20, ) require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - chainID = 56 // use BSC chain ERC20 custody - ob := MockEVMObserver( + // Given BSC observer + chain := chains.BscMainnet + params := mocks.MockChainParams(chain.ChainId, confirmation) + + ob, _ := MockEVMObserver( t, chain, nil, @@ -189,9 +202,13 @@ func Test_CheckAndVoteInboundTokenERC20(t *testing.T) { nil, nil, lastBlock, - mocks.MockChainParams(chainID, confirmation), + params, ) + + // ACT _, err := ob.CheckAndVoteInboundTokenERC20(ctx, tx, receipt, true) + + // ASSERT require.ErrorContains(t, err, "emitter address mismatch") }) } @@ -218,7 +235,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, cctx.InboundParams.BallotIndex, ballot) @@ -228,7 +245,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) _, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.ErrorContains(t, err, "not been confirmed") }) @@ -238,7 +255,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.ErrorContains(t, err, "not TSS address") require.Equal(t, "", ballot) @@ -249,7 +266,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.ErrorContains(t, err, "not a successful tx") require.Equal(t, "", ballot) @@ -260,7 +277,7 @@ func Test_CheckAndVoteInboundTokenGas(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(tx)) lastBlock := receipt.BlockNumber.Uint64() + confirmation - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, lastBlock, chainParam) ballot, err := ob.CheckAndVoteInboundTokenGas(ctx, tx, receipt, false) require.NoError(t, err) require.Equal(t, "", ballot) @@ -277,7 +294,7 @@ func Test_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { cctx := testutils.LoadCctxByInbound(t, chainID, coin.CoinType_Zeta, inboundHash) // parse ZetaSent event - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) + ob, app := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) connector := mocks.MockConnectorNonEth(t, chainID) event := testutils.ParseReceiptZetaSent(receipt, connector) @@ -286,8 +303,6 @@ func Test_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { ComplianceConfig: config.ComplianceConfig{}, } - _, app := makeAppContext(t) - t.Run("should return vote msg for archived ZetaSent event", func(t *testing.T) { msg := ob.BuildInboundVoteMsgForZetaSentEvent(app, event) require.NotNil(t, msg) @@ -326,7 +341,7 @@ func Test_BuildInboundVoteMsgForDepositedEvent(t *testing.T) { cctx := testutils.LoadCctxByInbound(t, chainID, coin.CoinType_ERC20, inboundHash) // parse Deposited event - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) custody := mocks.MockERC20Custody(t, chainID) event := testutils.ParseReceiptERC20Deposited(receipt, custody) sender := ethcommon.HexToAddress(tx.From) @@ -384,7 +399,7 @@ func Test_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) { require.NoError(t, evm.ValidateEvmTransaction(txDonation)) // create test compliance config - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(1, 1)) cfg := config.Config{ ComplianceConfig: config.ComplianceConfig{}, } @@ -461,7 +476,7 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { ctx := context.Background() t.Run("should observe TSS receive in block", func(t *testing.T) { - ob := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) // feed archived block and receipt evmJSONRPC.WithBlock(block) @@ -470,13 +485,13 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { require.NoError(t, err) }) t.Run("should not observe on error getting block", func(t *testing.T) { - ob := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) err := ob.ObserveTSSReceiveInBlock(ctx, blockNumber) // error getting block is expected because the mock JSONRPC contains no block require.ErrorContains(t, err, "error getting block") }) t.Run("should not observe on error getting receipt", func(t *testing.T) { - ob := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) + ob, _ := MockEVMObserver(t, chain, evmClient, evmJSONRPC, zetacoreClient, tss, lastBlock, chainParam) evmJSONRPC.WithBlock(block) err := ob.ObserveTSSReceiveInBlock(ctx, blockNumber) // error getting block is expected because the mock evmClient contains no receipt @@ -484,9 +499,9 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { }) } -func makeAppContext(_ *testing.T) (context.Context, *zctx.AppContext) { +func makeAppContext(t *testing.T) (context.Context, *zctx.AppContext) { var ( - app = zctx.New(config.New(false), zerolog.Nop()) + app = zctx.New(config.New(false), zerolog.New(zerolog.NewTestWriter(t))) ctx = context.Background() ) diff --git a/zetaclient/chains/evm/observer/observer_gas_test.go b/zetaclient/chains/evm/observer/observer_gas_test.go index ce0b681d43..3a416f1733 100644 --- a/zetaclient/chains/evm/observer/observer_gas_test.go +++ b/zetaclient/chains/evm/observer/observer_gas_test.go @@ -34,7 +34,7 @@ func TestPostGasPrice(t *testing.T) { confirmation := uint64(10) chainParam := mocks.MockChainParams(chain.ChainId, confirmation) - observer := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) + observer, _ := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) // Given empty baseFee from RPC ethRPC.WithHeader(ðtypes.Header{BaseFee: nil}) @@ -79,7 +79,7 @@ func TestPostGasPrice(t *testing.T) { confirmation := uint64(10) chainParam := mocks.MockChainParams(chain.ChainId, confirmation) - observer := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) + observer, _ := MockEVMObserver(t, chain, ethRPC, nil, zetacoreClient, nil, blockNumber, chainParam) // Given 1 gwei baseFee from RPC ethRPC.WithHeader(ðtypes.Header{BaseFee: big.NewInt(gwei)}) diff --git a/zetaclient/chains/evm/observer/observer_test.go b/zetaclient/chains/evm/observer/observer_test.go index 5d6578e37d..95d2ed2140 100644 --- a/zetaclient/chains/evm/observer/observer_test.go +++ b/zetaclient/chains/evm/observer/observer_test.go @@ -13,6 +13,7 @@ import ( "github.com/onrik/ethrpc" "github.com/rs/zerolog" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/ptr" zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/db" "github.com/zeta-chain/zetacore/zetaclient/keys" @@ -59,13 +60,17 @@ func getAppContext( // create AppContext appContext := zctx.New(cfg, logger) - chainParams := make(map[int64]*observertypes.ChainParams) - chainParams[evmChain.ChainId] = evmChainParams + chainParams := map[int64]*observertypes.ChainParams{ + evmChain.ChainId: evmChainParams, + chains.ZetaChainMainnet.ChainId: ptr.Ptr( + mocks.MockChainParams(chains.ZetaChainMainnet.ChainId, 10), + ), + } // feed chain params err := appContext.Update( observertypes.Keygen{}, - []chains.Chain{evmChain}, + []chains.Chain{evmChain, chains.ZetaChainMainnet}, nil, chainParams, "tssPubKey", @@ -87,7 +92,7 @@ func MockEVMObserver( tss interfaces.TSSSigner, lastBlock uint64, params observertypes.ChainParams, -) *observer.Observer { +) (*observer.Observer, *zctx.AppContext) { ctx := context.Background() // use default mock evm client if not provided @@ -108,18 +113,21 @@ func MockEVMObserver( tss = mocks.NewTSSMainnet() } // create AppContext - _, evmCfg := getAppContext(t, chain, "", ¶ms) + appContext, evmCfg := getAppContext(t, chain, "", ¶ms) database, err := db.NewFromSqliteInMemory(true) require.NoError(t, err) + testLogger := zerolog.New(zerolog.NewTestWriter(t)) + logger := base.Logger{Std: testLogger, Compliance: testLogger} + // create observer - ob, err := observer.NewObserver(ctx, evmCfg, evmClient, params, zetacoreClient, tss, database, base.Logger{}, nil) + ob, err := observer.NewObserver(ctx, evmCfg, evmClient, params, zetacoreClient, tss, database, logger, nil) require.NoError(t, err) ob.WithEvmJSONRPC(evmJSONRPC) ob.WithLastBlock(lastBlock) - return ob + return ob, appContext } func Test_NewObserver(t *testing.T) { @@ -245,7 +253,7 @@ func Test_LoadLastBlockScanned(t *testing.T) { // create observer using mock evm client evmClient := mocks.NewMockEvmClient().WithBlockNumber(100) - ob := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) + ob, _ := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) t.Run("should load last block scanned", func(t *testing.T) { // create db and write 123 as last block scanned @@ -268,7 +276,7 @@ func Test_LoadLastBlockScanned(t *testing.T) { }) t.Run("should fail on RPC error", func(t *testing.T) { // create observer on separate path, as we need to reset last block scanned - obOther := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) + obOther, _ := MockEVMObserver(t, chain, evmClient, nil, nil, nil, 1, params) // reset last block scanned to 0 so that it will be loaded from RPC obOther.WithLastBlockScanned(0) diff --git a/zetaclient/chains/evm/observer/outbound_test.go b/zetaclient/chains/evm/observer/outbound_test.go index bd596eb4dc..6fb93a7e42 100644 --- a/zetaclient/chains/evm/observer/outbound_test.go +++ b/zetaclient/chains/evm/observer/outbound_test.go @@ -59,7 +59,7 @@ func Test_IsOutboundProcessed(t *testing.T) { t.Run("should post vote and return true if outbound is processed", func(t *testing.T) { // create evm observer and set outbound and receipt - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) ob.SetTxNReceipt(nonce, receipt, outbound) // post outbound vote @@ -76,7 +76,7 @@ func Test_IsOutboundProcessed(t *testing.T) { cctx.InboundParams.Sender = sample.EthAddress().Hex() // create evm observer and set outbound and receipt - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) ob.SetTxNReceipt(nonce, receipt, outbound) // modify compliance config to restrict sender address @@ -94,7 +94,7 @@ func Test_IsOutboundProcessed(t *testing.T) { }) t.Run("should return false if outbound is not confirmed", func(t *testing.T) { // create evm observer and DO NOT set outbound as confirmed - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) isIncluded, isConfirmed, err := ob.IsOutboundProcessed(ctx, cctx, zerolog.Nop()) require.NoError(t, err) require.False(t, isIncluded) @@ -102,7 +102,7 @@ func Test_IsOutboundProcessed(t *testing.T) { }) t.Run("should fail if unable to parse ZetaReceived event", func(t *testing.T) { // create evm observer and set outbound and receipt - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) ob.SetTxNReceipt(nonce, receipt, outbound) // set connector contract address to an arbitrary address to make event parsing fail @@ -152,7 +152,7 @@ func Test_IsOutboundProcessed_ContractError(t *testing.T) { t.Run("should fail if unable to get connector/custody contract", func(t *testing.T) { // create evm observer and set outbound and receipt - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, chainParam) ob.SetTxNReceipt(nonce, receipt, outbound) abiConnector := zetaconnector.ZetaConnectorNonEthMetaData.ABI abiCustody := erc20custody.ERC20CustodyMetaData.ABI @@ -198,7 +198,7 @@ func Test_PostVoteOutbound(t *testing.T) { receiveStatus := chains.ReceiveStatus_success // create evm client using mock zetacore client and post outbound vote - ob := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(chain.ChainId, 100)) + ob, _ := MockEVMObserver(t, chain, nil, nil, nil, nil, 1, mocks.MockChainParams(chain.ChainId, 100)) ob.PostVoteOutbound( ctx, cctx.Index, diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index 40879dc496..a2e3a941ed 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -157,7 +157,7 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte switch { case err != nil: return nil, err - case chain.IsEVM(): + case !chain.IsEVM(): // noop for non-EVM chains return signer, nil case chain.IsZeta(): diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go index ec77783e31..04e2a7018e 100644 --- a/zetaclient/orchestrator/orchestrator_test.go +++ b/zetaclient/orchestrator/orchestrator_test.go @@ -70,7 +70,7 @@ func CreateAppContext( RPCHost: "localhost", } // new AppContext - appContext := zctx.New(cfg, zerolog.Nop()) + appContext := zctx.New(cfg, zerolog.New(zerolog.NewTestWriter(t))) params := map[int64]*observertypes.ChainParams{ evmChain.ChainId: evmChainParams, btcChainParams.ChainId: btcChainParams, From ca9826e41e6d2f8c5fbef9ba4c2cef6a578a0562 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 30 Jul 2024 18:30:37 +0200 Subject: [PATCH 14/24] Fix test cases [3] --- x/observer/types/chain_params.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/observer/types/chain_params.go b/x/observer/types/chain_params.go index 2e58eeb2de..31db97ec82 100644 --- a/x/observer/types/chain_params.go +++ b/x/observer/types/chain_params.go @@ -152,7 +152,6 @@ func GetDefaultChainParams() ChainParamsList { GetDefaultBtcTestnetChainParams(), GetDefaultBtcRegtestChainParams(), GetDefaultGoerliLocalnetChainParams(), - GetDefaultZetaPrivnetChainParams(), }, } } From c642109f6ddc5fd3d2ffc2bb18c3f7cc0053e62d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 30 Jul 2024 19:02:58 +0200 Subject: [PATCH 15/24] Address PR comments [1] --- zetaclient/chains/evm/signer/outbound_data_test.go | 2 +- zetaclient/config/types.go | 2 +- zetaclient/context/app_test.go | 9 +++++++++ zetaclient/context/chain.go | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/zetaclient/chains/evm/signer/outbound_data_test.go b/zetaclient/chains/evm/signer/outbound_data_test.go index dce5c2a888..0d44fd3dbb 100644 --- a/zetaclient/chains/evm/signer/outbound_data_test.go +++ b/zetaclient/chains/evm/signer/outbound_data_test.go @@ -116,7 +116,7 @@ func TestSigner_NewOutboundData(t *testing.T) { cctx := getInvalidCCTX(t) _, skip, err := NewOutboundData(ctx, cctx, mockObserver, evmSigner.EvmClient(), zerolog.Logger{}, 123) - assert.ErrorContains(t, err, "unable to get chain 13378337 from app context: chain not found") + assert.ErrorContains(t, err, "unable to get chain 13378337 from app context: id=13378337: chain not found") assert.True(t, skip) }) diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index 13d4b8d568..0edf06171b 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -159,5 +159,5 @@ func (c Config) GetKeyringBackend() KeyringBackend { } func (c EVMConfig) Empty() bool { - return c.Endpoint == "" && c.Chain == chains.Chain{} + return c.Endpoint == "" && c.Chain.IsEmpty() } diff --git a/zetaclient/context/app_test.go b/zetaclient/context/app_test.go index 23bb298495..0dd8e2daed 100644 --- a/zetaclient/context/app_test.go +++ b/zetaclient/context/app_test.go @@ -129,6 +129,15 @@ func TestAppContext(t *testing.T) { act func(*AppContext) error assert func(*testing.T, *AppContext, error) }{ + { + name: "update with empty chains results in an error", + act: func(a *AppContext) error { + return appContext.Update(keyGen, newChains, nil, nil, ttsPubKey, ccFlags) + }, + assert: func(t *testing.T, a *AppContext, err error) { + assert.ErrorContains(t, err, "no chain params present") + }, + }, { name: "trying to add non-supported chain results in an error", act: func(a *AppContext) error { diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go index e24539498e..977716f7f9 100644 --- a/zetaclient/context/chain.go +++ b/zetaclient/context/chain.go @@ -51,7 +51,7 @@ func NewChainRegistry() *ChainRegistry { func (cr *ChainRegistry) Get(chainID int64) (Chain, error) { chain, ok := cr.chains[chainID] if !ok { - return Chain{}, ErrChainNotFound + return Chain{}, errors.Wrapf(ErrChainNotFound, "id=%d", chainID) } return chain, nil @@ -118,7 +118,7 @@ func (cr *ChainRegistry) ChainIDs() []int64 { func newChain(cr *ChainRegistry, chainID int64, chain *chains.Chain, params *observer.ChainParams) (Chain, error) { if err := validateNewChain(chainID, chain, params); err != nil { - return Chain{}, err + return Chain{}, errors.Wrap(err, "invalid input") } return Chain{ From 38cb3cb68a8127c81c1baa4e0412320142dbaa43 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 30 Jul 2024 19:07:32 +0200 Subject: [PATCH 16/24] Address PR comments [2] --- zetaclient/chains/bitcoin/observer/inbound.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 5e33d0e146..a7dc5afe3d 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -114,10 +114,6 @@ func (ob *Observer) ObserveInbound(ctx context.Context) error { blockNumber, len(res.Block.Tx), cnt, lastScanned) // add block header to zetacore - // TODO: consider having a separate ticker(from TSS scaning) for posting block headers - // https://github.com/zeta-chain/node/issues/1847 - // TODO: move this logic in its own routine - // https://github.com/zeta-chain/node/issues/2204 if len(res.Block.Tx) > 1 { // get depositor fee depositorFee := bitcoin.CalcDepositorFee(res.Block, ob.Chain().ChainId, ob.netParams, ob.logger.Inbound) From 65a3830b1f7a6e6237dc88f5587615d0a6e3d9a7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 30 Jul 2024 19:13:03 +0200 Subject: [PATCH 17/24] Add tests for `slices` --- pkg/slices/slices_test.go | 128 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 pkg/slices/slices_test.go diff --git a/pkg/slices/slices_test.go b/pkg/slices/slices_test.go new file mode 100644 index 0000000000..6513de610d --- /dev/null +++ b/pkg/slices/slices_test.go @@ -0,0 +1,128 @@ +package slices + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMap(t *testing.T) { + tests := []struct { + name string + input []int + function func(int) int + expected []int + }{ + { + name: "double", + input: []int{1, 2, 3, 4}, + function: func(x int) int { return x * 2 }, + expected: []int{2, 4, 6, 8}, + }, + { + name: "square", + input: []int{1, 2, 3, 4}, + function: func(x int) int { return x * x }, + expected: []int{1, 4, 9, 16}, + }, + { + name: "increment", + input: []int{1, 2, 3, 4}, + function: func(x int) int { return x + 1 }, + expected: []int{2, 3, 4, 5}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Map(tt.input, tt.function) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestElementsMatch(t *testing.T) { + tests := []struct { + name string + a, b []int + expected bool + }{ + { + name: "same elements in same order", + a: []int{1, 2, 3, 4}, + b: []int{1, 2, 3, 4}, + expected: true, + }, + { + name: "same elements in different order", + a: []int{4, 3, 2, 1}, + b: []int{1, 2, 3, 4}, + expected: true, + }, + { + name: "different elements", + a: []int{1, 2, 3, 5}, + b: []int{1, 2, 3, 4}, + expected: false, + }, + { + name: "different lengths", + a: []int{1, 2, 3}, + b: []int{1, 2, 3, 4}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ElementsMatch(tt.a, tt.b) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestDiff(t *testing.T) { + tests := []struct { + name string + a, b []int + expected []int + }{ + { + name: "elements in a not in b", + a: []int{1, 2, 3, 4}, + b: []int{3, 4, 5, 6}, + expected: []int{1, 2}, + }, + { + name: "no elements in a not in b", + a: []int{3, 4}, + b: []int{3, 4, 5, 6}, + expected: nil, + }, + { + name: "all elements in a not in b", + a: []int{1, 2}, + b: []int{3, 4}, + expected: []int{1, 2}, + }, + { + name: "empty a", + a: []int{}, + b: []int{1, 2, 3, 4}, + expected: nil, + }, + { + name: "empty b", + a: []int{1, 2, 3, 4}, + b: []int{}, + expected: []int{1, 2, 3, 4}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Diff(tt.a, tt.b) + require.Equal(t, tt.expected, result) + }) + } +} From e3831257527777ae7a368b21fd71defbbbba4423 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 31 Jul 2024 15:09:34 +0200 Subject: [PATCH 18/24] Fix e2e tests [1] --- x/observer/types/chain_params.go | 2 +- zetaclient/context/app.go | 3 ++- zetaclient/orchestrator/bootstrap.go | 13 +++++++++---- zetaclient/zetacore/client.go | 18 ++++++++++++------ 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/x/observer/types/chain_params.go b/x/observer/types/chain_params.go index 31db97ec82..072b336572 100644 --- a/x/observer/types/chain_params.go +++ b/x/observer/types/chain_params.go @@ -57,7 +57,7 @@ func ValidateChainParams(params *ChainParams) error { } if chains.IsZetaChain(params.ChainId, nil) { - return fmt.Errorf("zeta chain cannot have observer chain parameters") + return errorsmod.Wrap(sdkerrors.ErrInvalidChainID, "zeta chain cannot have observer chain parameters") } if params.ConfirmationCount == 0 { diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index 4b65b149f8..4f937c4d10 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -140,7 +140,8 @@ func (a *AppContext) Update( return fmt.Errorf("no chains present") case len(freshChainParams) == 0: return fmt.Errorf("no chain params present") - case tssPubKey == "": + case tssPubKey == "" && a.currentTssPubKey != "": + // note that if we're doing a fresh start, we ALLOW an empty tssPubKey return fmt.Errorf("tss pubkey is empty") case len(additionalChains) > 0: for _, c := range additionalChains { diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go index 71c4caae8f..2dad1a704a 100644 --- a/zetaclient/orchestrator/bootstrap.go +++ b/zetaclient/orchestrator/bootstrap.go @@ -145,7 +145,10 @@ func syncSignerMap( addSigner(chainID, signer) default: - logger.Std.Warn().Msgf("Unable to create signer for chain %d", chainID) + logger.Std.Warn(). + Int64("signer.chain_id", chain.ID()). + Str("signer.chain_name", chain.RawChain().Name). + Msgf("Unable to create a signer") } } @@ -230,7 +233,7 @@ func syncObserverMap( var ( params = chain.Params() rawChain = chain.RawChain() - chainName = rawChain.ChainName.String() + chainName = rawChain.Name ) switch { @@ -344,8 +347,10 @@ func syncObserverMap( addObserver(chainID, solObserver) default: - logger.Std.Warn().Msgf("Unable to create observer for chain %d", chainID) - continue + logger.Std.Warn(). + Int64("observer.chain_id", chain.ID()). + Str("observer.chain_name", chain.RawChain().Name). + Msgf("Unable to create an observer") } } diff --git a/zetaclient/zetacore/client.go b/zetaclient/zetacore/client.go index e035939bf7..0806c709fe 100644 --- a/zetaclient/zetacore/client.go +++ b/zetaclient/zetacore/client.go @@ -399,18 +399,24 @@ func (c *Client) UpdateAppContext(ctx context.Context, appContext *zctx.AppConte freshParams := make(map[int64]*observertypes.ChainParams, len(chainParams)) // check and update chain params for each chain - for chainID := range chainParams { - cp := chainParams[chainID] - if err := observertypes.ValidateChainParams(cp); err != nil { - logger.Warn().Err(err).Int64("chain.id", cp.ChainId).Msg("Skipping invalid chain params") - continue - } + // Note that we are EXCLUDING ZetaChain from the chainParams if it's present + for i := range chainParams { + cp := chainParams[i] if !cp.IsSupported { logger.Warn().Int64("chain.id", cp.ChainId).Msg("Skipping unsupported chain") continue } + if chains.IsZetaChain(cp.ChainId, nil) { + continue + } + + if err := observertypes.ValidateChainParams(cp); err != nil { + logger.Warn().Err(err).Int64("chain.id", cp.ChainId).Msg("Skipping invalid chain params") + continue + } + freshParams[cp.ChainId] = cp } From ac0ea1e582bc99048403bf05c4b2e7efa298c8a0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 31 Jul 2024 15:31:36 +0200 Subject: [PATCH 19/24] Fix e2e tests [2] --- cmd/zetae2e/local/admin.go | 2 +- cmd/zetae2e/local/bitcoin.go | 5 +- cmd/zetae2e/local/erc20.go | 2 +- cmd/zetae2e/local/ethereum.go | 3 +- cmd/zetae2e/local/local.go | 7 +- cmd/zetae2e/local/performance.go | 2 +- cmd/zetae2e/local/zeta.go | 2 +- cmd/zetae2e/local/zevm_mp.go | 2 +- cmd/zetae2e/stress.go | 2 +- e2e/e2etests/test_eth_deposit.go | 2 +- e2e/e2etests/test_migrate_chain_support.go | 2 +- e2e/e2etests/test_stress_eth_deposit.go | 2 +- e2e/runner/bitcoin.go | 97 +--------------------- e2e/runner/evm.go | 76 +---------------- 14 files changed, 19 insertions(+), 187 deletions(-) diff --git a/cmd/zetae2e/local/admin.go b/cmd/zetae2e/local/admin.go index bc76aeeedc..6aaf386496 100644 --- a/cmd/zetae2e/local/admin.go +++ b/cmd/zetae2e/local/admin.go @@ -45,7 +45,7 @@ func adminTestRoutine( // depositing the necessary tokens on ZetaChain txZetaDeposit := adminRunner.DepositZeta() - txEtherDeposit := adminRunner.DepositEther(false) + txEtherDeposit := adminRunner.DepositEther() txERC20Deposit := adminRunner.DepositERC20() adminRunner.WaitForMinedCCTX(txZetaDeposit) adminRunner.WaitForMinedCCTX(txEtherDeposit) diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index 05098fd5a9..184277d0cc 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -17,7 +17,6 @@ func bitcoinTestRoutine( deployerRunner *runner.E2ERunner, verbose bool, initBitcoinNetwork bool, - testHeader bool, testNames ...string, ) func() error { return func() (err error) { @@ -42,14 +41,14 @@ func bitcoinTestRoutine( bitcoinRunner.WaitForTxReceiptOnEvm(txERC20Send) // depositing the necessary tokens on ZetaChain - txEtherDeposit := bitcoinRunner.DepositEther(false) + txEtherDeposit := bitcoinRunner.DepositEther() txERC20Deposit := bitcoinRunner.DepositERC20() bitcoinRunner.WaitForMinedCCTX(txEtherDeposit) bitcoinRunner.WaitForMinedCCTX(txERC20Deposit) bitcoinRunner.SetupBitcoinAccount(initBitcoinNetwork) - bitcoinRunner.DepositBTC(testHeader) + bitcoinRunner.DepositBTC() // run bitcoin test // Note: due to the extensive block generation in Bitcoin localnet, block header test is run first diff --git a/cmd/zetae2e/local/erc20.go b/cmd/zetae2e/local/erc20.go index 8b0d21e564..94c3cbfc29 100644 --- a/cmd/zetae2e/local/erc20.go +++ b/cmd/zetae2e/local/erc20.go @@ -41,7 +41,7 @@ func erc20TestRoutine( erc20Runner.WaitForTxReceiptOnEvm(txERC20Send) // depositing the necessary tokens on ZetaChain - txEtherDeposit := erc20Runner.DepositEther(false) + txEtherDeposit := erc20Runner.DepositEther() txERC20Deposit := erc20Runner.DepositERC20() erc20Runner.WaitForMinedCCTX(txEtherDeposit) erc20Runner.WaitForMinedCCTX(txERC20Deposit) diff --git a/cmd/zetae2e/local/ethereum.go b/cmd/zetae2e/local/ethereum.go index ae2eebc268..84b68608c8 100644 --- a/cmd/zetae2e/local/ethereum.go +++ b/cmd/zetae2e/local/ethereum.go @@ -16,7 +16,6 @@ func ethereumTestRoutine( conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, - testHeader bool, testNames ...string, ) func() error { return func() (err error) { @@ -36,7 +35,7 @@ func ethereumTestRoutine( startTime := time.Now() // depositing the necessary tokens on ZetaChain - txEtherDeposit := ethereumRunner.DepositEther(testHeader) + txEtherDeposit := ethereumRunner.DepositEther() ethereumRunner.WaitForMinedCCTX(txEtherDeposit) // run ethereum test diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 72d01e4584..5b2fc35ad6 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -283,14 +283,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { ethereumTests = append(ethereumTests, ethereumAdvancedTests...) } - // skip the header proof test if we run light test or skipHeaderProof is enabled - testHeader := !light && !skipHeaderProof - eg.Go(erc20TestRoutine(conf, deployerRunner, verbose, erc20Tests...)) eg.Go(zetaTestRoutine(conf, deployerRunner, verbose, zetaTests...)) eg.Go(zevmMPTestRoutine(conf, deployerRunner, verbose, zevmMPTests...)) - eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, testHeader, bitcoinTests...)) - eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, testHeader, ethereumTests...)) + eg.Go(bitcoinTestRoutine(conf, deployerRunner, verbose, !skipBitcoinSetup, bitcoinTests...)) + eg.Go(ethereumTestRoutine(conf, deployerRunner, verbose, ethereumTests...)) } if testAdmin { diff --git a/cmd/zetae2e/local/performance.go b/cmd/zetae2e/local/performance.go index 4039084550..3fc6b8b52c 100644 --- a/cmd/zetae2e/local/performance.go +++ b/cmd/zetae2e/local/performance.go @@ -79,7 +79,7 @@ func ethereumWithdrawPerformanceRoutine( startTime := time.Now() // depositing the necessary tokens on ZetaChain - txEtherDeposit := r.DepositEther(false) + txEtherDeposit := r.DepositEther() r.WaitForMinedCCTX(txEtherDeposit) tests, err := r.GetE2ETestsToRunByName( diff --git a/cmd/zetae2e/local/zeta.go b/cmd/zetae2e/local/zeta.go index 3fdb4f48cc..a0f6d49a09 100644 --- a/cmd/zetae2e/local/zeta.go +++ b/cmd/zetae2e/local/zeta.go @@ -41,7 +41,7 @@ func zetaTestRoutine( // depositing the necessary tokens on ZetaChain txZetaDeposit := zetaRunner.DepositZeta() - txEtherDeposit := zetaRunner.DepositEther(false) + txEtherDeposit := zetaRunner.DepositEther() zetaRunner.WaitForMinedCCTX(txZetaDeposit) zetaRunner.WaitForMinedCCTX(txEtherDeposit) diff --git a/cmd/zetae2e/local/zevm_mp.go b/cmd/zetae2e/local/zevm_mp.go index bc97c45e29..b8d6126ae0 100644 --- a/cmd/zetae2e/local/zevm_mp.go +++ b/cmd/zetae2e/local/zevm_mp.go @@ -41,7 +41,7 @@ func zevmMPTestRoutine( // depositing the necessary tokens on ZetaChain txZetaDeposit := zevmMPRunner.DepositZeta() - txEtherDeposit := zevmMPRunner.DepositEther(false) + txEtherDeposit := zevmMPRunner.DepositEther() zevmMPRunner.WaitForMinedCCTX(txZetaDeposit) zevmMPRunner.WaitForMinedCCTX(txEtherDeposit) diff --git a/cmd/zetae2e/stress.go b/cmd/zetae2e/stress.go index 51e4762635..b1d3a41bfc 100644 --- a/cmd/zetae2e/stress.go +++ b/cmd/zetae2e/stress.go @@ -144,7 +144,7 @@ func StressTest(cmd *cobra.Command, _ []string) { e2eTest.SetZEVMContracts() // deposit on ZetaChain - e2eTest.DepositEther(false) + e2eTest.DepositEther() e2eTest.DepositZeta() case "TESTNET": ethZRC20Addr := must(e2eTest.SystemContract.GasCoinZRC20ByChainId(&bind.CallOpts{}, big.NewInt(5))) diff --git a/e2e/e2etests/test_eth_deposit.go b/e2e/e2etests/test_eth_deposit.go index 03da8f6da4..c5f0701516 100644 --- a/e2e/e2etests/test_eth_deposit.go +++ b/e2e/e2etests/test_eth_deposit.go @@ -16,7 +16,7 @@ func TestEtherDeposit(r *runner.E2ERunner, args []string) { amount, ok := big.NewInt(0).SetString(args[0], 10) require.True(r, ok, "Invalid amount specified for TestEtherDeposit.") - hash := r.DepositEtherWithAmount(false, amount) // in wei + hash := r.DepositEtherWithAmount(amount) // in wei // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, hash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "deposit") diff --git a/e2e/e2etests/test_migrate_chain_support.go b/e2e/e2etests/test_migrate_chain_support.go index 0fd2574c85..b8a92cd472 100644 --- a/e2e/e2etests/test_migrate_chain_support.go +++ b/e2e/e2etests/test_migrate_chain_support.go @@ -136,7 +136,7 @@ func TestMigrateChainSupport(r *runner.E2ERunner, _ []string) { // deposit Ethers and ERC20 on ZetaChain etherAmount := big.NewInt(1e18) etherAmount = etherAmount.Mul(etherAmount, big.NewInt(10)) - txEtherDeposit := newRunner.DepositEtherWithAmount(false, etherAmount) + txEtherDeposit := newRunner.DepositEtherWithAmount(etherAmount) newRunner.WaitForMinedCCTX(txEtherDeposit) // perform withdrawals on the new chain diff --git a/e2e/e2etests/test_stress_eth_deposit.go b/e2e/e2etests/test_stress_eth_deposit.go index 9e0208f7e3..04ef846889 100644 --- a/e2e/e2etests/test_stress_eth_deposit.go +++ b/e2e/e2etests/test_stress_eth_deposit.go @@ -31,7 +31,7 @@ func TestStressEtherDeposit(r *runner.E2ERunner, args []string) { // send the deposits for i := 0; i < numDeposits; i++ { i := i - hash := r.DepositEtherWithAmount(false, depositAmount) + hash := r.DepositEtherWithAmount(depositAmount) r.Logger.Print("index %d: starting deposit, tx hash: %s", i, hash.Hex()) eg.Go(func() error { return monitorEtherDeposit(r, hash, i, time.Now()) }) diff --git a/e2e/runner/bitcoin.go b/e2e/runner/bitcoin.go index 868c344766..3a4dad583e 100644 --- a/e2e/runner/bitcoin.go +++ b/e2e/runner/bitcoin.go @@ -2,7 +2,6 @@ package runner import ( "bytes" - "encoding/hex" "fmt" "sort" "time" @@ -19,17 +18,12 @@ import ( "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/constant" - "github.com/zeta-chain/zetacore/pkg/proofs" - "github.com/zeta-chain/zetacore/pkg/proofs/bitcoin" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" - lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" zetabitcoin "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" btcobserver "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/signer" ) -var blockHeaderBTCTimeout = 5 * time.Minute - // ListDeployerUTXOs list the deployer's UTXOs func (r *E2ERunner) ListDeployerUTXOs() ([]btcjson.ListUnspentResult, error) { // query UTXOs from node @@ -113,7 +107,7 @@ func (r *E2ERunner) DepositBTCWithAmount(amount float64) *chainhash.Hash { } // DepositBTC deposits BTC on ZetaChain -func (r *E2ERunner) DepositBTC(testHeader bool) { +func (r *E2ERunner) DepositBTC() { r.Logger.Print("⏳ depositing BTC into ZEVM") startTime := time.Now() defer func() { @@ -143,7 +137,7 @@ func (r *E2ERunner) DepositBTC(testHeader bool) { // send two transactions to the TSS address amount1 := 1.1 + zetabitcoin.DefaultDepositorFee - txHash1, err := r.SendToTSSFromDeployerToDeposit(amount1, utxos[:2]) + _, err = r.SendToTSSFromDeployerToDeposit(amount1, utxos[:2]) require.NoError(r, err) amount2 := 0.05 + zetabitcoin.DefaultDepositorFee @@ -169,12 +163,6 @@ func (r *E2ERunner) DepositBTC(testHeader bool) { balance, err := r.BTCZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) require.NoError(r, err) require.Equal(r, 1, balance.Sign(), "balance should be positive") - - // due to the high block throughput in localnet, ZetaClient might catch up slowly with the blocks - // to optimize block header proof test, this test is directly executed here on the first deposit instead of having a separate test - if testHeader { - r.ProveBTCTransaction(txHash1) - } } func (r *E2ERunner) SendToTSSFromDeployerToDeposit(amount float64, inputUTXOs []btcjson.ListUnspentResult) ( @@ -346,84 +334,3 @@ func (r *E2ERunner) MineBlocksIfLocalBitcoin() func() { close(stopChan) } } - -// ProveBTCTransaction proves that a BTC transaction is in a block header and that the block header is in ZetaChain -func (r *E2ERunner) ProveBTCTransaction(txHash *chainhash.Hash) { - // get tx result - btc := r.BtcRPCClient - txResult, err := btc.GetTransaction(txHash) - require.NoError(r, err, "should get tx result") - require.True(r, txResult.Confirmations > 0, "tx should have already confirmed") - - txBytes, err := hex.DecodeString(txResult.Hex) - require.NoError(r, err) - - // get the block with verbose transactions - blockHash, err := chainhash.NewHashFromStr(txResult.BlockHash) - require.NoError(r, err) - - blockVerbose, err := btc.GetBlockVerboseTx(blockHash) - require.NoError(r, err, "should get block verbose tx") - - // get the block header - header, err := btc.GetBlockHeader(blockHash) - require.NoError(r, err, "should get block header") - - // collect all the txs in the block - txns := []*btcutil.Tx{} - for _, res := range blockVerbose.Tx { - txBytes, err := hex.DecodeString(res.Hex) - require.NoError(r, err) - - tx, err := btcutil.NewTxFromBytes(txBytes) - require.NoError(r, err) - - txns = append(txns, tx) - } - - // build merkle proof - mk := bitcoin.NewMerkle(txns) - path, index, err := mk.BuildMerkleProof(int(txResult.BlockIndex)) - require.NoError(r, err, "should build merkle proof") - - // verify merkle proof statically - pass := bitcoin.Prove(*txHash, header.MerkleRoot, path, index) - require.True(r, pass, "should verify merkle proof") - - // wait for block header to show up in ZetaChain - startTime := time.Now() - hash := header.BlockHash() - for { - // timeout - reachedTimeout := time.Since(startTime) > blockHeaderBTCTimeout - require.False(r, reachedTimeout, "timed out waiting for block header to show up in observer") - - _, err := r.LightclientClient.BlockHeader(r.Ctx, &lightclienttypes.QueryGetBlockHeaderRequest{ - BlockHash: hash.CloneBytes(), - }) - if err != nil { - r.Logger.Info( - "waiting for block header to show up in observer... current hash %s; err %s", - hash.String(), - err.Error(), - ) - } - if err == nil { - break - } - time.Sleep(2 * time.Second) - } - - // verify merkle proof through RPC - res, err := r.LightclientClient.Prove(r.Ctx, &lightclienttypes.QueryProveRequest{ - ChainId: chains.BitcoinRegtest.ChainId, - TxHash: txHash.String(), - BlockHash: blockHash.String(), - Proof: proofs.NewBitcoinProof(txBytes, path, index), - TxIndex: 0, // bitcoin doesn't use txIndex - }) - require.NoError(r, err) - require.True(r, res.Valid, "txProof should be valid") - - r.Logger.Info("OK: txProof verified for inTx: %s", txHash.String()) -} diff --git a/e2e/runner/evm.go b/e2e/runner/evm.go index 5fd4bdd565..10fd599d63 100644 --- a/e2e/runner/evm.go +++ b/e2e/runner/evm.go @@ -11,14 +11,8 @@ import ( "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/utils" - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/proofs" - "github.com/zeta-chain/zetacore/pkg/proofs/ethereum" - lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" ) -var blockHeaderETHTimeout = 5 * time.Minute - // WaitForTxReceiptOnEvm waits for a tx receipt on EVM func (r *E2ERunner) WaitForTxReceiptOnEvm(tx *ethtypes.Transaction) { r.Lock() @@ -110,13 +104,13 @@ func (r *E2ERunner) DepositERC20WithAmountAndMessage(to ethcommon.Address, amoun } // DepositEther sends Ethers into ZEVM -func (r *E2ERunner) DepositEther(testHeader bool) ethcommon.Hash { +func (r *E2ERunner) DepositEther() ethcommon.Hash { amount := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(100)) // 100 eth - return r.DepositEtherWithAmount(testHeader, amount) + return r.DepositEtherWithAmount(amount) } // DepositEtherWithAmount sends Ethers into ZEVM -func (r *E2ERunner) DepositEtherWithAmount(testHeader bool, amount *big.Int) ethcommon.Hash { +func (r *E2ERunner) DepositEtherWithAmount(amount *big.Int) ethcommon.Hash { r.Logger.Print("⏳ depositing Ethers into ZEVM") signedTx, err := r.SendEther(r.TSSAddress, amount, nil) @@ -129,12 +123,6 @@ func (r *E2ERunner) DepositEtherWithAmount(testHeader bool, amount *big.Int) eth r.Logger.EVMReceipt(*receipt, "send to TSS") - // due to the high block throughput in localnet, ZetaClient might catch up slowly with the blocks - // to optimize block header proof test, this test is directly executed here on the first deposit instead of having a separate test - if testHeader { - r.ProveEthTransaction(receipt) - } - return signedTx.Hash() } @@ -176,64 +164,6 @@ func (r *E2ERunner) SendEther(_ ethcommon.Address, value *big.Int, data []byte) return signedTx, nil } -// ProveEthTransaction proves an ETH transaction on ZetaChain -func (r *E2ERunner) ProveEthTransaction(receipt *ethtypes.Receipt) { - startTime := time.Now() - - txHash := receipt.TxHash - blockHash := receipt.BlockHash - - // #nosec G115 test - always in range - txIndex := int(receipt.TransactionIndex) - - block, err := r.EVMClient.BlockByHash(r.Ctx, blockHash) - require.NoError(r, err) - - for { - // check timeout - reachedTimeout := time.Since(startTime) > blockHeaderETHTimeout - require.False(r, reachedTimeout, "timeout waiting for block header") - - _, err := r.LightclientClient.BlockHeader(r.Ctx, &lightclienttypes.QueryGetBlockHeaderRequest{ - BlockHash: blockHash.Bytes(), - }) - if err != nil { - r.Logger.Info("WARN: block header not found; retrying... error: %s", err.Error()) - } else { - r.Logger.Info("OK: block header found") - break - } - - time.Sleep(2 * time.Second) - } - - trie := ethereum.NewTrie(block.Transactions()) - require.Equal(r, trie.Hash(), block.Header().TxHash, "tx root hash & block tx root mismatch") - - txProof, err := trie.GenerateProof(txIndex) - require.NoError(r, err, "error generating txProof") - - val, err := txProof.Verify(block.TxHash(), txIndex) - require.NoError(r, err, "error verifying txProof") - - var txx ethtypes.Transaction - require.NoError(r, txx.UnmarshalBinary(val)) - - res, err := r.LightclientClient.Prove(r.Ctx, &lightclienttypes.QueryProveRequest{ - BlockHash: blockHash.Hex(), - TxIndex: int64(txIndex), - TxHash: txHash.Hex(), - Proof: proofs.NewEthereumProof(txProof), - ChainId: chains.GoerliLocalnet.ChainId, - }) - - // FIXME: @lumtis: don't do this in production - require.NoError(r, err) - require.True(r, res.Valid, "txProof invalid") - - r.Logger.Info("OK: txProof verified") -} - // AnvilMineBlocks mines blocks on Anvil localnet // the block time is provided in seconds // the method returns a function to stop the mining From 05b3d971e3ca74e8ba76f987d5162793bdd9e812 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 2 Aug 2024 14:40:48 +0200 Subject: [PATCH 20/24] Resolve conflicts, converge codebase between PRs --- zetaclient/chains/evm/signer/outbound_data.go | 2 + zetaclient/chains/evm/signer/signer.go | 8 +- zetaclient/chains/solana/observer/outbound.go | 2 +- zetaclient/orchestrator/bootstrap.go | 91 ++--- zetaclient/orchestrator/orchestrator.go | 47 ++- zetaclient/orchestrator/orchestrator_test.go | 375 +++++++++++------- 6 files changed, 297 insertions(+), 228 deletions(-) diff --git a/zetaclient/chains/evm/signer/outbound_data.go b/zetaclient/chains/evm/signer/outbound_data.go index ddee5bc15a..ebeef1c5b1 100644 --- a/zetaclient/chains/evm/signer/outbound_data.go +++ b/zetaclient/chains/evm/signer/outbound_data.go @@ -157,6 +157,8 @@ func NewOutboundData( return nil, true, errors.Wrap(err, "unable to setup gas") } + nonce := cctx.GetCurrentOutboundParam().TssNonce + // Get sendHash logger.Info(). Msgf("chain %d minting %d to %s, nonce %d, finalized zeta bn %d", toChain.ID(), cctx.InboundParams.Amount, txData.to.Hex(), nonce, cctx.InboundParams.FinalizedZetaHeight) diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index 60bf2d8feb..9c928face1 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -362,6 +362,12 @@ func (signer *Signer) TryProcessOutbound( zetacoreClient interfaces.ZetacoreClient, height uint64, ) { + app, err := zctx.FromContext(ctx) + if err != nil { + signer.Logger().Std.Error().Err(err).Msg("error getting app context") + return + } + // end outbound process on panic defer func() { outboundProc.EndTryProcess(outboundID) @@ -410,8 +416,6 @@ func (signer *Signer) TryProcessOutbound( return } - // Get cross-chain flags - crossChainflags := app.GetCrossChainFlags() // https://github.com/zeta-chain/node/issues/2050 var tx *ethtypes.Transaction // compliance check goes first diff --git a/zetaclient/chains/solana/observer/outbound.go b/zetaclient/chains/solana/observer/outbound.go index 59d93d2efb..6eca0b437e 100644 --- a/zetaclient/chains/solana/observer/outbound.go +++ b/zetaclient/chains/solana/observer/outbound.go @@ -47,7 +47,7 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { for { select { case <-ticker.C(): - if !app.IsOutboundObservationEnabled(ob.GetChainParams()) { + if !app.IsOutboundObservationEnabled() { sampledLogger.Info().Msgf("WatchOutbound: outbound observation is disabled for chain %d", chainID) continue } diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go index 805726a6f9..cd4d2a223c 100644 --- a/zetaclient/orchestrator/bootstrap.go +++ b/zetaclient/orchestrator/bootstrap.go @@ -144,69 +144,46 @@ func syncSignerMap( continue } + addSigner(chainID, signer) + case chain.IsSolana(): + cfg, found := app.Config().GetSolanaConfig() + if !found { + logger.Std.Warn().Msgf("Unable to find SOL config for chain %d", chainID) + continue + } + + // create Solana client + rpcClient := solrpc.New(cfg.Endpoint) + if rpcClient == nil { + // should never happen + logger.Std.Error().Msgf("Unable to create SOL client from endpoint %s", cfg.Endpoint) + continue + } + + // load the Solana private key + solanaKey, err := app.Config().LoadSolanaPrivateKey() + if err != nil { + logger.Std.Error().Err(err).Msg("Unable to get Solana private key") + } + + var ( + chainRaw = chain.RawChain() + paramsRaw = chain.Params() + ) + + // create Solana signer + signer, err := solanasigner.NewSigner(*chainRaw, *paramsRaw, rpcClient, tss, solanaKey, ts, logger) + if err != nil { + logger.Std.Error().Err(err).Msgf("Unable to construct signer for SOL chain %d", chainID) + continue + } + addSigner(chainID, signer) default: logger.Std.Warn(). Int64("signer.chain_id", chain.ID()). Str("signer.chain_name", chain.RawChain().Name). Msgf("Unable to create a signer") - - /* - TODO FIX AFTER MERGE - - // Solana signer - // Emulate same loop semantics as for EVM chains - for i := 0; i < 1; i++ { - solChain, solChainParams, solChainParamsFound := app.GetSolanaChainParams() - switch { - case !solChainParamsFound: - logger.Std.Warn().Msgf("Unable to find chain params for Solana chain") - continue - case !solChainParams.IsSupported: - logger.Std.Warn().Msgf("Solana chain is not supported") - continue - } - - chainID := solChainParams.ChainId - presentChainIDs = append(presentChainIDs, chainID) - - // noop - if mapHas(signers, chainID) { - continue - } - - // get Solana config - cfg, found := app.Config().GetSolanaConfig() - if !found { - logger.Std.Error().Msgf("Unable to find Solana config for chain %d", chainID) - continue - } - - // create Solana client - rpcClient := solrpc.New(cfg.Endpoint) - if rpcClient == nil { - // should never happen - logger.Std.Error().Msgf("Unable to create Solana client from endpoint %s", cfg.Endpoint) - continue - } - - // load the Solana private key - solanaKey, err := app.Config().LoadSolanaPrivateKey() - if err != nil { - logger.Std.Error().Err(err).Msg("Unable to get Solana private key") - } - - // create Solana signer - signer, err := solanasigner.NewSigner(solChain, *solChainParams, rpcClient, tss, solanaKey, ts, logger) - if err != nil { - logger.Std.Error().Err(err).Msgf("Unable to construct signer for Solana chain %d", chainID) - continue - } - - addSigner(chainID, signer) - } - - */ } } diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index aa730f3c24..d284c0d713 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -158,29 +158,38 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte switch { case err != nil: return nil, err - case !chain.IsEVM(): - // noop for non-EVM chains - return signer, nil case chain.IsZeta(): // should not happen return nil, fmt.Errorf("unable to resolve signer for zeta chain %d", chainID) - } + case chain.IsEVM(): + params := chain.Params() - // update zeta connector and ERC20 custody addresses - zetaConnectorAddress := ethcommon.HexToAddress(chain.Params().GetConnectorContractAddress()) - if zetaConnectorAddress != signer.GetZetaConnectorAddress() { - signer.SetZetaConnectorAddress(zetaConnectorAddress) - oc.logger.Info(). - Str("signer.connector_address", zetaConnectorAddress.String()). - Msgf("updated zeta connector address for chain %d", chainID) - } + // update zeta connector and ERC20 custody addresses + zetaConnectorAddress := ethcommon.HexToAddress(params.GetConnectorContractAddress()) + if zetaConnectorAddress != signer.GetZetaConnectorAddress() { + signer.SetZetaConnectorAddress(zetaConnectorAddress) + oc.logger.Info(). + Str("signer.connector_address", zetaConnectorAddress.String()). + Msgf("updated zeta connector address for chain %d", chainID) + } - erc20CustodyAddress := ethcommon.HexToAddress(chain.Params().GetErc20CustodyContractAddress()) - if erc20CustodyAddress != signer.GetERC20CustodyAddress() { - signer.SetERC20CustodyAddress(erc20CustodyAddress) - oc.logger.Info(). - Str("signer.erc20_custody", erc20CustodyAddress.String()). - Msgf("updated zeta connector address for chain %d", chainID) + erc20CustodyAddress := ethcommon.HexToAddress(params.GetErc20CustodyContractAddress()) + if erc20CustodyAddress != signer.GetERC20CustodyAddress() { + signer.SetERC20CustodyAddress(erc20CustodyAddress) + oc.logger.Info(). + Str("signer.erc20_custody", erc20CustodyAddress.String()). + Msgf("updated zeta connector address for chain %d", chainID) + } + case chain.IsSolana(): + params := chain.Params() + + // update solana gateway address + if params.GatewayAddress != signer.GetGatewayAddress() { + signer.SetGatewayAddress(params.GatewayAddress) + oc.logger.Info(). + Str("signer.gateway_address", params.GatewayAddress). + Msgf("updated gateway address for chain %d", chainID) + } } return signer, nil @@ -401,6 +410,8 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { oc.ScheduleCctxEVM(ctx, zetaHeight, chainID, cctxList, ob, signer) case chain.IsUTXO(): oc.ScheduleCctxBTC(ctx, zetaHeight, chainID, cctxList, ob, signer) + case chain.IsSolana(): + oc.ScheduleCctxSolana(ctx, zetaHeight, chainID, cctxList, ob, signer) default: oc.logger.Error().Msgf("runScheduler: no scheduler found chain %d", chainID) continue diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go index 913658b284..417d30c3f9 100644 --- a/zetaclient/orchestrator/orchestrator_test.go +++ b/zetaclient/orchestrator/orchestrator_test.go @@ -25,127 +25,64 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" ) -// TODO FIX AFTER MERGING - -// MockOrchestrator creates a mock orchestrator for testing -func MockOrchestrator( - t *testing.T, - zetacoreClient interfaces.ZetacoreClient, - evmChain, btcChain, solChain *chains.Chain, - evmChainParams, btcChainParams, solChainParams *observertypes.ChainParams, -) *Orchestrator { - // create mock signers and clients - evmSigner := mocks.NewEVMSigner( - evmChain, - ethcommon.HexToAddress(evmChainParams.ConnectorContractAddress), - ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress), +func Test_GetUpdatedSigner(t *testing.T) { + // initial parameters for orchestrator creation + var ( + evmChain = chains.Ethereum + btcChain = chains.BitcoinMainnet + solChain = chains.SolanaMainnet ) - btcSigner := mocks.NewBTCSigner() - evmObserver := mocks.NewEVMObserver(evmChainParams) - btcObserver := mocks.NewBTCObserver(btcChainParams) - - // create orchestrator - orchestrator := &Orchestrator{ - zetacoreClient: zetacoreClient, - signerMap: map[int64]interfaces.ChainSigner{ - evmChain.ChainId: evmSigner, - btcChain.ChainId: btcSigner, - }, - observerMap: map[int64]interfaces.ChainObserver{ - evmChain.ChainId: evmObserver, - btcChain.ChainId: btcObserver, - }, - } - return orchestrator -} - -// evmChain, btcChain, solChain chains.Chain, -// evmChainParams, btcChainParams, solChainParams *observertypes.ChainParams, -func CreateAppContext( - t *testing.T, - evmChain, btcChain chains.Chain, - evmChainParams, btcChainParams *observertypes.ChainParams, -) *zctx.AppContext { - // new config - cfg := config.New(false) - cfg.EVMChainConfigs[evmChain.ChainId] = config.EVMConfig{ - Chain: evmChain, - } - cfg.BitcoinConfig = config.BTCConfig{ - RPCHost: "localhost", - } - // new AppContext - appContext := zctx.New(cfg, zerolog.New(zerolog.NewTestWriter(t))) - params := map[int64]*observertypes.ChainParams{ - evmChain.ChainId: evmChainParams, - btcChainParams.ChainId: btcChainParams, - } - ccFlags := sample.CrosschainFlags() - - // feed chain params - err := appContext.Update( - observertypes.Keygen{}, - []chains.Chain{evmChain, btcChain}, - nil, - params, - "tssPubKey", - *ccFlags, + var ( + evmChainParams = mocks.MockChainParams(evmChain.ChainId, 100) + btcChainParams = mocks.MockChainParams(btcChain.ChainId, 100) + solChainParams = mocks.MockChainParams(solChain.ChainId, 100) ) - require.NoError(t, err) - return appContext -} - -func Test_GetUpdatedSigner(t *testing.T) { - // initial parameters for orchestrator creation - evmChain := chains.Ethereum - btcChain := chains.BitcoinMainnet - solChain := chains.SolanaMainnet - - evmChainParams := mocks.MockChainParams(evmChain.ChainId, 100) - btcChainParams := mocks.MockChainParams(btcChain.ChainId, 100) - solChainParams := &observertypes.ChainParams{ - ChainId: solChain.ChainId, - GatewayAddress: solanacontracts.SolanaGatewayProgramID, - } + solChainParams.GatewayAddress = solanacontracts.SolanaGatewayProgramID // new chain params in AppContext evmChainParamsNew := mocks.MockChainParams(evmChainParams.ChainId, 100) evmChainParamsNew.ConnectorContractAddress = testutils.OtherAddress1 evmChainParamsNew.Erc20CustodyContractAddress = testutils.OtherAddress2 + // new solana chain params in AppContext + solChainParamsNew := mocks.MockChainParams(solChain.ChainId, 100) + solChainParamsNew.GatewayAddress = sample.SolanaAddress(t) + t.Run("signer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) - appContext := CreateAppContext(t, evmChain, btcChain, &evmChainParamsNew, &btcChainParams) + orchestrator := mockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) + appContext := createAppContext(t, evmChain, btcChain, evmChainParamsNew, btcChainParams) // BSC signer should not be found _, err := orchestrator.resolveSigner(appContext, chains.BscMainnet.ChainId) require.ErrorContains(t, err, "signer not found") }) + t.Run("should be able to update connector and erc20 custody address", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) - appContext := CreateAppContext(t, evmChain, btcChain, &evmChainParamsNew, &btcChainParams) + orchestrator := mockOrchestrator(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) + appContext := createAppContext(t, evmChain, btcChain, evmChainParamsNew, btcChainParams) + // update signer with new connector and erc20 custody address signer, err := orchestrator.resolveSigner(appContext, evmChain.ChainId) require.NoError(t, err) + require.Equal(t, testutils.OtherAddress1, signer.GetZetaConnectorAddress().Hex()) require.Equal(t, testutils.OtherAddress2, signer.GetERC20CustodyAddress().Hex()) }) + t.Run("should be able to update solana gateway address", func(t *testing.T) { - orchestrator := MockOrchestrator( - t, - nil, - &evmChain, - &btcChain, - &solChain, - evmChainParams, - btcChainParams, - solChainParams, + orchestrator := mockOrchestrator(t, nil, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParams, + ) + + appContext := createAppContext(t, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParamsNew, ) - context := CreateAppContext(evmChain, btcChain, solChain, evmChainParams, btcChainParams, solChainParamsNew) // update signer with new gateway address - signer, err := orchestrator.resolveSigner(context, solChain.ChainId) + signer, err := orchestrator.resolveSigner(appContext, solChain.ChainId) require.NoError(t, err) require.Equal(t, solChainParamsNew.GatewayAddress, signer.GetGatewayAddress()) }) @@ -153,16 +90,19 @@ func Test_GetUpdatedSigner(t *testing.T) { func Test_GetUpdatedChainObserver(t *testing.T) { // initial parameters for orchestrator creation - evmChain := chains.Ethereum - btcChain := chains.BitcoinMainnet - solChain := chains.SolanaMainnet - evmChainParams := mocks.MockChainParams(evmChain.ChainId, 100) - btcChainParams := mocks.MockChainParams(btcChain.ChainId, 100) + var ( + evmChain = chains.Ethereum + btcChain = chains.BitcoinMainnet + solChain = chains.SolanaMainnet + ) - // solChainParams := &observertypes.ChainParams{ - // ChainId: solChain.ChainId, - // GatewayAddress: solanacontracts.SolanaGatewayProgramID, - // } + var ( + evmChainParams = mocks.MockChainParams(evmChain.ChainId, 100) + btcChainParams = mocks.MockChainParams(btcChain.ChainId, 100) + solChainParams = mocks.MockChainParams(solChain.ChainId, 100) + ) + + solChainParams.GatewayAddress = solanacontracts.SolanaGatewayProgramID // new chain params in AppContext evmChainParamsNew := &observertypes.ChainParams{ @@ -215,75 +155,91 @@ func Test_GetUpdatedChainObserver(t *testing.T) { } t.Run("evm chain observer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) - appContext := CreateAppContext(t, evmChain, btcChain, evmChainParamsNew, &btcChainParams) - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, + btcChain, + solChain, evmChainParams, btcChainParams, solChainParams, ) - appContext := CreateAppContext(evmChain, btcChain, solChain, evmChainParamsNew, btcChainParams, solChainParams) + appContext := createAppContext(t, evmChain, btcChain, evmChainParamsNew, btcChainParams) + // BSC chain observer should not be found _, err := orchestrator.resolveObserver(appContext, chains.BscMainnet.ChainId) require.ErrorContains(t, err, "observer not found") }) t.Run("chain params in evm chain observer should be updated successfully", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) - appContext := CreateAppContext(t, evmChain, btcChain, evmChainParamsNew, &btcChainParams) - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, + btcChain, + solChain, evmChainParams, btcChainParams, solChainParams, ) - appContext := CreateAppContext(evmChain, btcChain, solChain, evmChainParamsNew, btcChainParams, solChainParams) + appContext := createAppContext( + t, + evmChain, + btcChain, + solChain, + evmChainParamsNew, + btcChainParams, + solChainParams, + ) + // update evm chain observer with new chain params chainOb, err := orchestrator.resolveObserver(appContext, evmChain.ChainId) require.NoError(t, err) require.NotNil(t, chainOb) require.True(t, observertypes.ChainParamsEqual(*evmChainParamsNew, chainOb.GetChainParams())) }) + t.Run("btc chain observer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) - appContext := CreateAppContext(t, btcChain, btcChain, &evmChainParams, btcChainParamsNew) - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, + btcChain, + solChain, evmChainParams, btcChainParams, solChainParams, ) - appContext := CreateAppContext(btcChain, btcChain, solChain, evmChainParams, btcChainParamsNew, solChainParams) + appContext := createAppContext( + t, + evmChain, + btcChain, + solChain, + evmChainParams, + btcChainParamsNew, + solChainParams, + ) + // BTC testnet chain observer should not be found _, err := orchestrator.resolveObserver(appContext, chains.BitcoinTestnet.ChainId) require.ErrorContains(t, err, "observer not found") }) t.Run("chain params in btc chain observer should be updated successfully", func(t *testing.T) { - orchestrator := MockOrchestrator(t, nil, evmChain, btcChain, &evmChainParams, &btcChainParams) - appContext := CreateAppContext(t, btcChain, btcChain, &evmChainParams, btcChainParamsNew) - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParams, + ) + appContext := createAppContext( + t, + evmChain, + btcChain, + solChain, evmChainParams, - btcChainParams, + btcChainParamsNew, solChainParams, ) - appContext := CreateAppContext(btcChain, btcChain, solChain, evmChainParams, btcChainParamsNew, solChainParams) // update btc chain observer with new chain params chainOb, err := orchestrator.resolveObserver(appContext, btcChain.ChainId) require.NoError(t, err) @@ -291,33 +247,37 @@ func Test_GetUpdatedChainObserver(t *testing.T) { require.True(t, observertypes.ChainParamsEqual(*btcChainParamsNew, chainOb.GetChainParams())) }) t.Run("solana chain observer should not be found", func(t *testing.T) { - orchestrator := MockOrchestrator( + orchestrator := mockOrchestrator( t, nil, - &evmChain, - &btcChain, - &solChain, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParams, + ) + + appContext := createAppContext( + t, + evmChain, + btcChain, + solChain, evmChainParams, btcChainParams, - solChainParams, + solChainParamsNew, ) - appContext := CreateAppContext(solChain, btcChain, solChain, evmChainParams, btcChainParams, solChainParamsNew) + // Solana Devnet chain observer should not be found _, err := orchestrator.resolveObserver(appContext, chains.SolanaDevnet.ChainId) require.ErrorContains(t, err, "observer not found") }) t.Run("chain params in solana chain observer should be updated successfully", func(t *testing.T) { - orchestrator := MockOrchestrator( - t, - nil, - &evmChain, - &btcChain, - &solChain, - evmChainParams, - btcChainParams, - solChainParams, + orchestrator := mockOrchestrator(t, nil, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParams, ) - appContext := CreateAppContext(solChain, btcChain, solChain, evmChainParams, btcChainParams, solChainParamsNew) + appContext := createAppContext(t, + evmChain, btcChain, solChain, + evmChainParams, btcChainParams, solChainParamsNew, + ) + // update solana chain observer with new chain params chainOb, err := orchestrator.resolveObserver(appContext, solChain.ChainId) require.NoError(t, err) @@ -486,7 +446,7 @@ func Test_GetPendingCctxsWithinRateLimit(t *testing.T) { client.WithPendingCctx(btcChain.ChainId, tt.btcCctxsFallback) // create orchestrator - orchestrator := MockOrchestrator(t, client, ðChain, &btcChain, nil, ethChainParams, btcChainParams, nil) + orchestrator := mockOrchestrator(t, client, ethChain, btcChain, ethChainParams, btcChainParams) chainIDs := slices.Map(foreignChains, func(c chains.Chain) int64 { return c.ChainId }) @@ -502,3 +462,118 @@ func Test_GetPendingCctxsWithinRateLimit(t *testing.T) { }) } } + +func mockOrchestrator(t *testing.T, zetaClient interfaces.ZetacoreClient, chainsOrParams ...any) *Orchestrator { + supportedChains, obsParams := parseChainsWithParams(t, chainsOrParams...) + + var ( + signers = make(map[int64]interfaces.ChainSigner) + observers = make(map[int64]interfaces.ChainObserver) + ) + + mustFindChain := func(chainID int64) chains.Chain { + for _, c := range supportedChains { + if c.ChainId == chainID { + return c + } + } + + t.Fatalf("mock orchestrator: must find chain: chain %d not found", chainID) + + return chains.Chain{} + } + + for i := range obsParams { + cp := obsParams[i] + + switch { + case chains.IsEVMChain(cp.ChainId, nil): + observers[cp.ChainId] = mocks.NewEVMObserver(cp) + signers[cp.ChainId] = mocks.NewEVMSigner( + mustFindChain(cp.ChainId), + ethcommon.HexToAddress(cp.ConnectorContractAddress), + ethcommon.HexToAddress(cp.Erc20CustodyContractAddress), + ) + case chains.IsBitcoinChain(cp.ChainId, nil): + observers[cp.ChainId] = mocks.NewBTCObserver(cp) + signers[cp.ChainId] = mocks.NewBTCSigner() + case chains.IsSolanaChain(cp.ChainId, nil): + observers[cp.ChainId] = mocks.NewSolanaObserver(cp) + signers[cp.ChainId] = mocks.NewSolanaSigner() + default: + t.Fatalf("mock orcestrator: unsupported chain %d", cp.ChainId) + } + } + + return &Orchestrator{ + zetacoreClient: zetaClient, + signerMap: signers, + observerMap: observers, + } +} + +func createAppContext(t *testing.T, chainsOrParams ...any) *zctx.AppContext { + supportedChains, obsParams := parseChainsWithParams(t, chainsOrParams...) + + cfg := config.New(false) + + // Mock config + cfg.BitcoinConfig = config.BTCConfig{ + RPCHost: "localhost", + } + + for _, c := range supportedChains { + if chains.IsEVMChain(c.ChainId, nil) { + cfg.EVMChainConfigs[c.ChainId] = config.EVMConfig{Chain: c} + } + } + + params := map[int64]*observertypes.ChainParams{} + for i := range obsParams { + cp := obsParams[i] + params[cp.ChainId] = cp + } + + // new AppContext + appContext := zctx.New(cfg, zerolog.New(zerolog.NewTestWriter(t))) + + ccFlags := sample.CrosschainFlags() + + // feed chain params + err := appContext.Update( + observertypes.Keygen{}, + supportedChains, + nil, + params, + "tssPubKey", + *ccFlags, + ) + require.NoError(t, err, "failed to update app context") + + return appContext +} + +// handy helper for testing +func parseChainsWithParams(t *testing.T, chainsOrParams ...any) ([]chains.Chain, []*observertypes.ChainParams) { + var ( + supportedChains = make([]chains.Chain, 0, len(chainsOrParams)) + obsParams = make([]*observertypes.ChainParams, 0, len(chainsOrParams)) + ) + + for _, something := range chainsOrParams { + switch tt := something.(type) { + case *chains.Chain: + supportedChains = append(supportedChains, *tt) + case chains.Chain: + supportedChains = append(supportedChains, tt) + case *observertypes.ChainParams: + obsParams = append(obsParams, tt) + case observertypes.ChainParams: + obsParams = append(obsParams, &tt) + default: + t.Fatalf("parse chains and params: unsupported type %T (%+v)", tt, tt) + } + } + + return supportedChains, obsParams +} From ac9fcf2c55a547b27fcb58101e724a94320a03d2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 2 Aug 2024 14:59:53 +0200 Subject: [PATCH 21/24] Add lodash; remove slices pkg --- go.mod | 17 +-- go.sum | 34 ++--- pkg/slices/slices.go | 56 -------- pkg/slices/slices_test.go | 128 ------------------- zetaclient/context/app.go | 27 +++- zetaclient/orchestrator/orchestrator.go | 9 +- zetaclient/orchestrator/orchestrator_test.go | 4 +- 7 files changed, 55 insertions(+), 220 deletions(-) delete mode 100644 pkg/slices/slices.go delete mode 100644 pkg/slices/slices_test.go diff --git a/go.mod b/go.mod index bd46b60e9b..7f952f66a6 100644 --- a/go.mod +++ b/go.mod @@ -68,6 +68,7 @@ require ( github.com/nanmu42/etherscan-api v1.10.0 github.com/near/borsh-go v0.3.1 github.com/onrik/ethrpc v1.2.0 + github.com/samber/lo v1.46.0 gitlab.com/thorchain/tss/tss-lib v0.2.0 go.nhat.io/grpcmock v0.25.0 ) @@ -326,16 +327,16 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.17.0 + golang.org/x/crypto v0.23.0 golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb - golang.org/x/mod v0.11.0 // indirect - golang.org/x/net v0.19.0 + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 golang.org/x/oauth2 v0.15.0 // indirect - golang.org/x/sync v0.6.0 - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.152.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.32.0 diff --git a/go.sum b/go.sum index 9579b0702f..36e3a288c5 100644 --- a/go.sum +++ b/go.sum @@ -1456,6 +1456,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= +github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= @@ -1782,8 +1784,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1830,8 +1832,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1902,8 +1904,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1946,8 +1948,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2070,14 +2072,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2089,8 +2091,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2166,8 +2168,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/slices/slices.go b/pkg/slices/slices.go deleted file mode 100644 index 21f33e8279..0000000000 --- a/pkg/slices/slices.go +++ /dev/null @@ -1,56 +0,0 @@ -package slices - -import ( - "golang.org/x/exp/constraints" - "golang.org/x/exp/slices" -) - -// Map applies a function to each item in a slice and returns a new slice with the results. -func Map[T, V any](items []T, f func(T) V) []V { - result := make([]V, len(items)) - - for i, item := range items { - result[i] = f(item) - } - - return result -} - -// ElementsMatch returns true if two slices have the same elements in the same order. -// Note that this function SORTS the slices before comparing them. -func ElementsMatch[T constraints.Ordered](a, b []T) bool { - if len(a) != len(b) { - return false - } - - slices.Sort(a) - slices.Sort(b) - - for i, v := range a { - if v != b[i] { - return false - } - } - - return true -} - -// Diff returns the elements in `a` that are not in `b` -func Diff[T comparable](a, b []T) []T { - var ( - cache = map[T]struct{}{} - result []T - ) - - for _, v := range b { - cache[v] = struct{}{} - } - - for _, v := range a { - if _, ok := cache[v]; !ok { - result = append(result, v) - } - } - - return result -} diff --git a/pkg/slices/slices_test.go b/pkg/slices/slices_test.go deleted file mode 100644 index 6513de610d..0000000000 --- a/pkg/slices/slices_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package slices - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestMap(t *testing.T) { - tests := []struct { - name string - input []int - function func(int) int - expected []int - }{ - { - name: "double", - input: []int{1, 2, 3, 4}, - function: func(x int) int { return x * 2 }, - expected: []int{2, 4, 6, 8}, - }, - { - name: "square", - input: []int{1, 2, 3, 4}, - function: func(x int) int { return x * x }, - expected: []int{1, 4, 9, 16}, - }, - { - name: "increment", - input: []int{1, 2, 3, 4}, - function: func(x int) int { return x + 1 }, - expected: []int{2, 3, 4, 5}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := Map(tt.input, tt.function) - require.Equal(t, tt.expected, result) - }) - } -} - -func TestElementsMatch(t *testing.T) { - tests := []struct { - name string - a, b []int - expected bool - }{ - { - name: "same elements in same order", - a: []int{1, 2, 3, 4}, - b: []int{1, 2, 3, 4}, - expected: true, - }, - { - name: "same elements in different order", - a: []int{4, 3, 2, 1}, - b: []int{1, 2, 3, 4}, - expected: true, - }, - { - name: "different elements", - a: []int{1, 2, 3, 5}, - b: []int{1, 2, 3, 4}, - expected: false, - }, - { - name: "different lengths", - a: []int{1, 2, 3}, - b: []int{1, 2, 3, 4}, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := ElementsMatch(tt.a, tt.b) - require.Equal(t, tt.expected, result) - }) - } -} - -func TestDiff(t *testing.T) { - tests := []struct { - name string - a, b []int - expected []int - }{ - { - name: "elements in a not in b", - a: []int{1, 2, 3, 4}, - b: []int{3, 4, 5, 6}, - expected: []int{1, 2}, - }, - { - name: "no elements in a not in b", - a: []int{3, 4}, - b: []int{3, 4, 5, 6}, - expected: nil, - }, - { - name: "all elements in a not in b", - a: []int{1, 2}, - b: []int{3, 4}, - expected: []int{1, 2}, - }, - { - name: "empty a", - a: []int{}, - b: []int{1, 2, 3, 4}, - expected: nil, - }, - { - name: "empty b", - a: []int{1, 2, 3, 4}, - b: []int{}, - expected: []int{1, 2, 3, 4}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := Diff(tt.a, tt.b) - require.Equal(t, tt.expected, result) - }) - } -} diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index 4f937c4d10..032d0b759c 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -7,10 +7,12 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog" + "github.com/samber/lo" + "golang.org/x/exp/constraints" "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/slices" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" ) @@ -201,7 +203,7 @@ func (a *AppContext) updateChainRegistry( ) // 2. Compare existing chains with fresh ones - if len(existingChainIDs) > 0 && !slices.ElementsMatch(existingChainIDs, freshChainIDs) { + if len(existingChainIDs) > 0 && !elementsMatch(existingChainIDs, freshChainIDs) { a.logger.Warn(). Ints64("chains.current", existingChainIDs). Ints64("chains.new", freshChainIDs). @@ -244,7 +246,7 @@ func (a *AppContext) updateChainRegistry( a.chainRegistry.SetAdditionalChains(additionalChains) - toBeDeleted := slices.Diff(existingChainIDs, freshChainIDs) + toBeDeleted, _ := lo.Difference(existingChainIDs, freshChainIDs) if len(toBeDeleted) > 0 { a.logger.Warn(). Ints64("chains.deleted", toBeDeleted). @@ -266,6 +268,21 @@ func zetaObserverChainParams(chainID int64) *observertypes.ChainParams { return &observertypes.ChainParams{ChainId: chainID, IsSupported: true} } -func ChainIsNotZeta(c Chain) bool { - return !c.IsZeta() +// elementsMatch returns true if two slices are equal. +// SORTS the slices before comparison. +func elementsMatch[T constraints.Ordered](a, b []T) bool { + if len(a) != len(b) { + return false + } + + slices.Sort(a) + slices.Sort(b) + + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true } diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index d284c0d713..8bff065099 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -12,10 +12,10 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/rs/zerolog" + "github.com/samber/lo" "github.com/zeta-chain/zetacore/pkg/bg" zetamath "github.com/zeta-chain/zetacore/pkg/math" - "github.com/zeta-chain/zetacore/pkg/slices" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" @@ -352,10 +352,9 @@ func (oc *Orchestrator) runScheduler(ctx context.Context) error { metrics.HotKeyBurnRate.Set(float64(oc.ts.HotKeyBurnRate.GetBurnRate().Int64())) // get chain ids without zeta chain - chainIDs := slices.Map( - app.FilterChains(zctx.ChainIsNotZeta), - zctx.Chain.ID, - ) + chainIDs := lo.FilterMap(app.ListChains(), func(c zctx.Chain, _ int) (int64, bool) { + return c.ID(), !c.IsZeta() + }) // query pending cctxs across all external chains within rate limit cctxMap, err := oc.GetPendingCctxsWithinRateLimit(ctx, chainIDs) diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go index 417d30c3f9..3594accc2a 100644 --- a/zetaclient/orchestrator/orchestrator_test.go +++ b/zetaclient/orchestrator/orchestrator_test.go @@ -7,9 +7,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" + "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/zeta-chain/zetacore/pkg/slices" zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/pkg/chains" @@ -448,7 +448,7 @@ func Test_GetPendingCctxsWithinRateLimit(t *testing.T) { // create orchestrator orchestrator := mockOrchestrator(t, client, ethChain, btcChain, ethChainParams, btcChainParams) - chainIDs := slices.Map(foreignChains, func(c chains.Chain) int64 { return c.ChainId }) + chainIDs := lo.Map(foreignChains, func(c chains.Chain, _ int) int64 { return c.ChainId }) // run the test cctxsMap, err := orchestrator.GetPendingCctxsWithinRateLimit(ctx, chainIDs) From aa57ba6c106012f6ced71571e53221246efec890 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 2 Aug 2024 15:14:25 +0200 Subject: [PATCH 22/24] Address PR comments --- changelog.md | 2 +- cmd/zetaclientd/debug.go | 7 +++---- cmd/zetaclientd/start.go | 7 ++++++- zetaclient/chains/evm/observer/inbound.go | 2 +- zetaclient/context/chain.go | 2 +- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 5dc495e464..822cefe360 100644 --- a/changelog.md +++ b/changelog.md @@ -80,7 +80,7 @@ * [2542](https://github.com/zeta-chain/node/pull/2542) - adjust permissions to be more restrictive * [2572](https://github.com/zeta-chain/node/pull/2572) - turn off IBC modules * [2556](https://github.com/zeta-chain/node/pull/2556) - refactor migrator length check to use consensus type -* [2568](https://github.com/zeta-chain/node/pull/2568) - improve AppContext +* [2568](https://github.com/zeta-chain/node/pull/2568) - improve AppContext by converging chains, chainParams, enabledChains, and additionalChains into a single zctx.Chain ### Tests diff --git a/cmd/zetaclientd/debug.go b/cmd/zetaclientd/debug.go index dc0b9f868f..6fc46f71f9 100644 --- a/cmd/zetaclientd/debug.go +++ b/cmd/zetaclientd/debug.go @@ -34,12 +34,11 @@ type debugArguments struct { zetaChainID string } -var homeDir = os.ExpandEnv("$HOME/.zetacored") - func init() { - cmd := DebugCmd() + defaultHomeDir := os.ExpandEnv("$HOME/.zetacored") - cmd.Flags().StringVar(&debugArgs.zetaCoreHome, "core-home", homeDir, "zetecore home directory") + cmd := DebugCmd() + cmd.Flags().StringVar(&debugArgs.zetaCoreHome, "core-home", defaultHomeDir, "zetacore home directory") cmd.Flags().StringVar(&debugArgs.zetaNode, "node", "46.4.15.110", "public ip address") cmd.Flags().StringVar(&debugArgs.zetaChainID, "chain-id", "athens_7001-1", "pre-params file path") diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 525f923d64..e7ec280e9e 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -214,8 +214,13 @@ func start(_ *cobra.Command, _ []string) error { } btcChains := appContext.FilterChains(zctx.Chain.IsUTXO) - if len(btcChains) == 0 { + switch { + case len(btcChains) == 0: return errors.New("no BTC chains found") + case len(btcChains) > 1: + // In the future we might support multiple UTXO chains; + // right now we only support BTC. Let's make sure there are no surprises. + return errors.New("more than one BTC chain found") } tss, err := mc.NewTSS( diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index 7804356730..19ad1f14d5 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -646,7 +646,7 @@ func (ob *Observer) BuildInboundVoteMsgForZetaSentEvent( appContext *zctx.AppContext, event *zetaconnector.ZetaConnectorNonEthZetaSent, ) *types.MsgVoteInbound { - // not that this is most likely zeta chain + // note that this is most likely zeta chain destChain, err := appContext.GetChain(event.DestinationChainId.Int64()) if err != nil { ob.Logger().Inbound.Warn().Err(err).Msgf("chain id %d not supported", event.DestinationChainId.Int64()) diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go index 977716f7f9..b3ec993766 100644 --- a/zetaclient/context/chain.go +++ b/zetaclient/context/chain.go @@ -67,7 +67,7 @@ func (cr *ChainRegistry) All() []Chain { } // Set sets a chain in the registry. -// A chain must be SUPPORTED and NOT ZetaChain itself; otherwise returns ErrChainNotSupported +// A chain must be SUPPORTED; otherwise returns ErrChainNotSupported func (cr *ChainRegistry) Set(chainID int64, chain *chains.Chain, params *observer.ChainParams) error { item, err := newChain(cr, chainID, chain, params) if err != nil { From 18b445dc2459d067aa91bd75d46e2ea2a235e218 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 2 Aug 2024 15:21:37 +0200 Subject: [PATCH 23/24] Minor logging fix --- cmd/zetaclientd/start.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index e7ec280e9e..281043cb27 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -267,11 +267,16 @@ func start(_ *cobra.Command, _ []string) error { tss.CurrentPubkey = currentTss.TssPubkey if tss.EVMAddress() == (ethcommon.Address{}) || tss.BTCAddress() == "" { startLogger.Error().Msg("TSS address is not set in zetacore") + } else { + startLogger.Info(). + Str("tss.eth", tss.EVMAddress().String()). + Str("tss.btc", tss.BTCAddress()). + Str("tss.pub_key", tss.CurrentPubkey). + Msg("Current TSS") } - startLogger.Info(). - Msgf("Current TSS address \n ETH : %s \n BTC : %s \n PubKey : %s ", tss.EVMAddress(), tss.BTCAddress(), tss.CurrentPubkey) + if len(appContext.ListChainIDs()) == 0 { - startLogger.Error().Msgf("No chains enabled in updated config %s ", cfg.String()) + startLogger.Error().Interface("config", cfg).Msgf("No chains in updated config") } isObserver, err := isObserverNode(ctx, zetacoreClient) From 0f48ce3db374635dc406198b3545026899cb8d38 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 2 Aug 2024 15:25:33 +0200 Subject: [PATCH 24/24] Address PR comments --- zetaclient/context/chain_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/zetaclient/context/chain_test.go b/zetaclient/context/chain_test.go index 365553303b..a679ed020a 100644 --- a/zetaclient/context/chain_test.go +++ b/zetaclient/context/chain_test.go @@ -49,6 +49,7 @@ func TestChainRegistry(t *testing.T) { require.Error(t, r.Set(btc.ChainId, btc, nil)) require.Error(t, r.Set(btc.ChainId, nil, btcParams)) require.Error(t, r.Set(123, btc, btcParams)) + require.Error(t, r.Set(btc.ChainId, btc, ethParams)) // With failure on adding unsupported chains require.ErrorIs(t, r.Set(opt.ChainId, opt, optParams), ErrChainNotSupported)