From 77acc5bc75273b08ec6091f050c38e74e78e4ddc Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Fri, 3 May 2024 15:22:19 -0500 Subject: [PATCH] moved state irrelevant methods to types; renaming and cleaning --- changelog.md | 3 +- cmd/zetae2e/local/local.go | 1 - testutil/sample/crosschain.go | 26 ++- .../keeper/grpc_query_cctx_rate_limit.go | 105 ++------- .../keeper/grpc_query_cctx_rate_limit_test.go | 185 ++-------------- x/crosschain/keeper/rate_limiter_flags.go | 40 +--- .../keeper/rate_limiter_flags_test.go | 78 ++----- x/crosschain/types/rate_limiter_flags.go | 95 +++++++++ x/crosschain/types/rate_limiter_flags_test.go | 200 ++++++++++++++++++ zetaclient/core_context/zeta_core_context.go | 7 +- .../core_context/zeta_core_context_test.go | 8 +- zetaclient/ratelimiter/rate_limiter_test.go | 4 +- zetaclient/zetacore_observer.go | 104 ++++----- zetaclient/zetacore_observer_test.go | 2 +- 14 files changed, 446 insertions(+), 412 deletions(-) diff --git a/changelog.md b/changelog.md index d60f66427a..8b301fc1c7 100644 --- a/changelog.md +++ b/changelog.md @@ -9,7 +9,7 @@ ### Refactor * [2032](https://github.com/zeta-chain/node/pull/2032) - improve some general structure of the ZetaClient codebase - +* [2110](https://github.com/zeta-chain/node/pull/2110) - move non-query rate limiter logic to zetaclient side and code refactor. ## v16.0.0 @@ -57,7 +57,6 @@ * [2013](https://github.com/zeta-chain/node/pull/2013) - rename `GasPriceVoter` message to `VoteGasPrice` * [2059](https://github.com/zeta-chain/node/pull/2059) - Remove unused params from all functions in zetanode * [2076](https://github.com/zeta-chain/node/pull/2076) - automatically deposit native zeta to an address if it doesn't exist on ZEVM. -* [2110](https://github.com/zeta-chain/node/pull/2110) - move non-query rate limiter logic to zetaclient side and code refactor. * [2071](https://github.com/zeta-chain/node/pull/2071) - Modify chains struct to add all chain related information ### Features diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 39f3be0013..3103810305 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -313,7 +313,6 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestUpdateBytecodeZRC20Name, e2etests.TestUpdateBytecodeConnectorName, e2etests.TestDepositEtherLiquidityCapName, - e2etests.TestRateLimiterName, // TestMigrateChainSupportName tests EVM chain migration. Currently this test doesn't work with Anvil because pre-EIP1559 txs are not supported // See issue below for details diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index 72c93f3fad..9142d6f532 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math/rand" + "strings" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -50,10 +51,20 @@ func RateLimiterFlags() types.RateLimiterFlags { } } -func AssetRate() *types.AssetRate { +// CustomRateLimiterFlags creates a custom rate limiter flags with the given parameters +func CustomRateLimiterFlags(enabled bool, window int64, rate math.Uint, conversions []types.Conversion) types.RateLimiterFlags { + return types.RateLimiterFlags{ + Enabled: enabled, + Window: window, + Rate: rate, + Conversions: conversions, + } +} + +func AssetRate() types.AssetRate { r := Rand() - return &types.AssetRate{ + return types.AssetRate{ ChainId: r.Int63(), Asset: EthAddress().Hex(), Decimals: uint32(r.Uint64()), @@ -62,6 +73,17 @@ func AssetRate() *types.AssetRate { } } +// CustomAssetRate creates a custom asset rate with the given parameters +func CustomAssetRate(chainID int64, asset string, decimals uint32, coinType coin.CoinType, rate sdk.Dec) types.AssetRate { + return types.AssetRate{ + ChainId: chainID, + Asset: strings.ToLower(asset), + Decimals: decimals, + CoinType: coinType, + Rate: rate, + } +} + func OutTxTracker(t *testing.T, index string) types.OutTxTracker { r := newRandFromStringSeed(t, index) diff --git a/x/crosschain/keeper/grpc_query_cctx_rate_limit.go b/x/crosschain/keeper/grpc_query_cctx_rate_limit.go index 75ef53df58..695c4029b2 100644 --- a/x/crosschain/keeper/grpc_query_cctx_rate_limit.go +++ b/x/crosschain/keeper/grpc_query_cctx_rate_limit.go @@ -3,11 +3,9 @@ package keeper import ( "context" "sort" - "strings" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "google.golang.org/grpc/codes" @@ -66,8 +64,11 @@ func (k Keeper) RateLimiterInput(c context.Context, req *types.QueryRateLimiterI // get foreign chains and conversion rates of foreign coins chains := k.zetaObserverKeeper.GetSupportedForeignChains(ctx) - assetRates := k.GetRateLimiterAssetRateList(ctx) - gasAssetRateMap, erc20AssetRateMap := BuildAssetRateMapFromList(assetRates) + _, assetRates, found := k.GetRateLimiterAssetRateList(ctx) + if !found { + return nil, status.Error(codes.Internal, "asset rates not found") + } + gasAssetRateMap, erc20AssetRateMap := types.BuildAssetRateMapFromList(assetRates) // query pending nonces of each foreign chain and get the lowest height of the pending cctxs lowestPendingCctxHeight := int64(0) @@ -129,7 +130,7 @@ func (k Keeper) RateLimiterInput(c context.Context, req *types.QueryRateLimiterI // sum up past cctxs' value within window if inWindow && isPast { - pastCctxsValue = pastCctxsValue.Add(ConvertCctxValue(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap)) + pastCctxsValue = pastCctxsValue.Add(types.ConvertCctxValue(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap)) } // add cctx to corresponding list @@ -140,7 +141,7 @@ func (k Keeper) RateLimiterInput(c context.Context, req *types.QueryRateLimiterI } else { cctxsPending = append(cctxsPending, cctx) // sum up non-past pending cctxs' value - pendingCctxsValue = pendingCctxsValue.Add(ConvertCctxValue(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap)) + pendingCctxsValue = pendingCctxsValue.Add(types.ConvertCctxValue(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap)) } } } @@ -191,7 +192,7 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que // check rate limit flags to decide if we should apply rate limit applyLimit := true - rateLimitFlags, found := k.GetRateLimiterFlags(ctx) + rateLimitFlags, assetRates, found := k.GetRateLimiterAssetRateList(ctx) if !found || !rateLimitFlags.Enabled { applyLimit = false } @@ -231,19 +232,10 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que leftWindowBoundary = 0 } - // get the conversion rates for all foreign coins - var gasAssetRateMap map[int64]*types.AssetRate - var erc20AssetRateMap map[int64]map[string]*types.AssetRate - var blockLimitInAzeta sdkmath.Int - var windowLimitInAzeta sdkmath.Int - if applyLimit { - assetRates := k.GetRateLimiterAssetRateList(ctx) - gasAssetRateMap, erc20AssetRateMap = BuildAssetRateMapFromList(assetRates) - - // initiate block limit and window limit in azeta - blockLimitInAzeta = sdkmath.NewIntFromBigInt(rateLimitFlags.Rate.BigInt()) - windowLimitInAzeta = blockLimitInAzeta.Mul(sdkmath.NewInt(rateLimitFlags.Window)) - } + // initiate block limit and window limit in azeta; build asset rate maps + blockLimitInAzeta := sdkmath.NewIntFromBigInt(rateLimitFlags.Rate.BigInt()) + windowLimitInAzeta := blockLimitInAzeta.Mul(sdkmath.NewInt(rateLimitFlags.Window)) + gasAssetRateMap, erc20AssetRateMap := types.BuildAssetRateMapFromList(assetRates) // the criteria to stop adding cctxs to the rpc response maxCCTXsReached := func(cctxs []*types.CrossChainTx) bool { @@ -322,7 +314,7 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que break } // skip the cctx if rate limit is exceeded but still accumulate the total withdraw value - if inWindow && rateLimitExceeded(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { + if inWindow && types.RateLimitExceeded(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { limitExceeded = true continue } @@ -355,7 +347,7 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que } // skip the cctx if rate limit is exceeded but still accumulate the total withdraw value - if rateLimitExceeded(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { + if types.RateLimitExceeded(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { limitExceeded = true continue } @@ -388,72 +380,3 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que RateLimitExceeded: limitExceeded, }, nil } - -// ConvertCctxValue converts the value of the cctx to azeta using given conversion rates -func ConvertCctxValue( - chainID int64, - cctx *types.CrossChainTx, - gasAssetRateMap map[int64]*types.AssetRate, - erc20AssetRateMap map[int64]map[string]*types.AssetRate, -) sdkmath.Int { - var rate sdk.Dec - var decimals uint32 - switch cctx.InboundTxParams.CoinType { - case coin.CoinType_Zeta: - // no conversion needed for ZETA - return sdk.NewIntFromBigInt(cctx.GetCurrentOutTxParam().Amount.BigInt()) - case coin.CoinType_Gas: - assetRate, found := gasAssetRateMap[chainID] - if !found { - // skip if no rate found for gas coin on this chainID - return sdk.NewInt(0) - } - rate = assetRate.Rate - decimals = assetRate.Decimals - case coin.CoinType_ERC20: - // get the ERC20 coin rate - _, found := erc20AssetRateMap[chainID] - if !found { - // skip if no rate found for this chainID - return sdk.NewInt(0) - } - assetRate := erc20AssetRateMap[chainID][strings.ToLower(cctx.InboundTxParams.Asset)] - rate = assetRate.Rate - decimals = assetRate.Decimals - default: - // skip CoinType_Cmd - return sdk.NewInt(0) - } - // should not happen, return 0 to skip if it happens - if rate.IsNil() || rate.LTE(sdk.NewDec(0)) { - return sdkmath.NewInt(0) - } - - // the whole coin amounts of zeta and zrc20 - // given decimals = 6, the amount will be 10^6 = 1000000 - oneZeta := coin.AzetaPerZeta() - oneZrc20 := sdk.NewDec(10).Power(uint64(decimals)) - - // convert cctx asset amount into azeta amount - // given amountCctx = 2000000, rate = 0.8, decimals = 6 - // amountCctxDec: 2000000 * 0.8 = 1600000.0 - // amountAzetaDec: 1600000.0 * 10e18 / 10e6 = 1600000000000000000.0 - amountCctxDec := sdk.NewDecFromBigInt(cctx.GetCurrentOutTxParam().Amount.BigInt()) - amountAzetaDec := amountCctxDec.Mul(rate).Mul(oneZeta).Quo(oneZrc20) - return amountAzetaDec.TruncateInt() -} - -// rateLimitExceeded accumulates the cctx value and then checks if the rate limit is exceeded -// returns true if the rate limit is exceeded -func rateLimitExceeded( - chainID int64, - cctx *types.CrossChainTx, - gasAssetRateMap map[int64]*types.AssetRate, - erc20AssetRateMap map[int64]map[string]*types.AssetRate, - currentCctxValue *sdkmath.Int, - withdrawLimitInAzeta sdkmath.Int, -) bool { - amountZeta := ConvertCctxValue(chainID, cctx, gasAssetRateMap, erc20AssetRateMap) - *currentCctxValue = currentCctxValue.Add(amountZeta) - return currentCctxValue.GT(withdrawLimitInAzeta) -} diff --git a/x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go b/x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go index ea44c05cbb..fc5c96cf09 100644 --- a/x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go +++ b/x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go @@ -1,12 +1,9 @@ package keeper_test import ( - "fmt" - "strings" "testing" "cosmossdk.io/math" - sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/pkg/coin" @@ -94,168 +91,6 @@ func setupForeignCoins( } } -func Test_ConvertCctxValue(t *testing.T) { - // chain IDs - ethChainID := getValidEthChainID() - btcChainID := getValidBtcChainID() - - // zrc20 addresses for ETH, BTC, USDT and asset for USDT - zrc20ETH := sample.EthAddress().Hex() - zrc20BTC := sample.EthAddress().Hex() - zrc20USDT := sample.EthAddress().Hex() - assetUSDT := sample.EthAddress().Hex() - - k, ctx, _, zk := keepertest.CrosschainKeeper(t) - - // Set TSS - tss := sample.Tss() - zk.ObserverKeeper.SetTSS(ctx, tss) - - // Set foreign coins - setupForeignCoins(t, ctx, zk, zrc20ETH, zrc20BTC, zrc20USDT, assetUSDT) - - // Set rate limiter flags - rateLimiterFlags := createTestRateLimiterFlags(500, math.NewUint(10), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8") - k.SetRateLimiterFlags(ctx, *rateLimiterFlags) - - // get rate limiter rates - assetRates := k.GetRateLimiterAssetRateList(ctx) - gasAssetRateMap, erc20AssetRateMap := keeper.BuildAssetRateMapFromList(assetRates) - - // test cases - tests := []struct { - name string - - // input - chainID int64 - coinType coin.CoinType - asset string - amount math.Uint - gasAssetRates map[int64]*types.AssetRate - erc20AssetRates map[int64]map[string]*types.AssetRate - - // output - expectedValue sdkmath.Int - }{ - { - name: "should convert cctx ZETA value correctly", - chainID: ethChainID, - coinType: coin.CoinType_Zeta, - asset: "", - amount: sdk.NewUint(3e17), // 0.3 ZETA - gasAssetRates: gasAssetRateMap, - erc20AssetRates: erc20AssetRateMap, - expectedValue: sdk.NewInt(3e17), - }, - { - name: "should convert cctx ETH value correctly", - chainID: ethChainID, - coinType: coin.CoinType_Gas, - asset: "", - amount: sdk.NewUint(3e15), // 0.003 ETH - gasAssetRates: gasAssetRateMap, - erc20AssetRates: erc20AssetRateMap, - expectedValue: sdk.NewInt(75e17), // 0.003 ETH * 2500 = 7.5 ZETA - }, - { - name: "should convert cctx BTC value correctly", - chainID: btcChainID, - coinType: coin.CoinType_Gas, - asset: "", - amount: sdk.NewUint(70000), // 0.0007 BTC - gasAssetRates: gasAssetRateMap, - erc20AssetRates: erc20AssetRateMap, - expectedValue: sdk.NewInt(35).Mul(sdk.NewInt(1e18)), // 0.0007 BTC * 50000 = 35.0 ZETA - }, - { - name: "should convert cctx USDT value correctly", - chainID: ethChainID, - coinType: coin.CoinType_ERC20, - asset: assetUSDT, - amount: sdk.NewUint(3e6), // 3 USDT - gasAssetRates: gasAssetRateMap, - erc20AssetRates: erc20AssetRateMap, - expectedValue: sdk.NewInt(24e17), // 3 USDT * 0.8 = 2.4 ZETA - }, - { - name: "should return 0 if no gas asset rate found for chainID", - chainID: ethChainID, - coinType: coin.CoinType_Gas, - asset: "", - amount: sdk.NewUint(100), - gasAssetRates: nil, - erc20AssetRates: erc20AssetRateMap, - expectedValue: sdk.NewInt(0), - }, - { - name: "should return 0 if no erc20 asset rate found for chainID", - chainID: ethChainID, - coinType: coin.CoinType_ERC20, - asset: assetUSDT, - amount: sdk.NewUint(100), - gasAssetRates: gasAssetRateMap, - erc20AssetRates: nil, - expectedValue: sdk.NewInt(0), - }, - { - name: "should return 0 if coinType is CoinType_Cmd", - chainID: ethChainID, - coinType: coin.CoinType_Cmd, - asset: "", - amount: sdk.NewUint(100), - gasAssetRates: gasAssetRateMap, - erc20AssetRates: erc20AssetRateMap, - expectedValue: sdk.NewInt(0), - }, - { - name: "should return 0 on nil rate", - chainID: ethChainID, - coinType: coin.CoinType_Gas, - asset: "", - amount: sdk.NewUint(100), - gasAssetRates: func() map[int64]*types.AssetRate { - // set rate to nil - tempRates := k.GetRateLimiterAssetRateList(ctx) - nilAssetRateMap, _ := keeper.BuildAssetRateMapFromList(tempRates) - nilAssetRateMap[ethChainID].Rate = sdk.Dec{} - return nilAssetRateMap - }(), - erc20AssetRates: erc20AssetRateMap, - expectedValue: sdk.NewInt(0), - }, - { - name: "should return 0 on rate <= 0", - chainID: ethChainID, - coinType: coin.CoinType_ERC20, - asset: assetUSDT, - amount: sdk.NewUint(100), - gasAssetRates: gasAssetRateMap, - erc20AssetRates: func() map[int64]map[string]*types.AssetRate { - // set rate to 0 - tempRates := k.GetRateLimiterAssetRateList(ctx) - _, zeroAssetRateMap := keeper.BuildAssetRateMapFromList(tempRates) - zeroAssetRateMap[ethChainID][strings.ToLower(assetUSDT)].Rate = sdk.NewDec(0) - return zeroAssetRateMap - }(), - expectedValue: sdk.NewInt(0), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // create cctx with given input - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", tt.chainID, 1)) - cctx.InboundTxParams.CoinType = tt.coinType - cctx.InboundTxParams.Asset = tt.asset - cctx.GetCurrentOutTxParam().Amount = tt.amount - - // convert cctx value - value := keeper.ConvertCctxValue(tt.chainID, cctx, tt.gasAssetRates, tt.erc20AssetRates) - require.Equal(t, tt.expectedValue, value) - }) - } -} - func TestKeeper_RateLimiterInput(t *testing.T) { // create sample TSS tss := sample.Tss() @@ -512,17 +347,29 @@ func TestKeeper_RateLimiterInput_Errors(t *testing.T) { }) require.ErrorContains(t, err, "tss not found") }) - t.Run("pending nonces not found", func(t *testing.T) { + t.Run("asset rates not found", func(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) - // Set rate limiter flags as disabled - rFlags := sample.RateLimiterFlags() - k.SetRateLimiterFlags(ctx, rFlags) + // Set TSS but no rate limiter flags + tss := sample.Tss() + zk.ObserverKeeper.SetTSS(ctx, tss) + + _, err := k.RateLimiterInput(ctx, &types.QueryRateLimiterInputRequest{ + Window: 100, + }) + require.ErrorContains(t, err, "asset rates not found") + }) + t.Run("pending nonces not found", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeper(t) // Set TSS tss := sample.Tss() zk.ObserverKeeper.SetTSS(ctx, tss) + // Set rate limiter flags as disabled + rFlags := sample.RateLimiterFlags() + k.SetRateLimiterFlags(ctx, rFlags) + _, err := k.RateLimiterInput(ctx, &types.QueryRateLimiterInputRequest{ Window: 100, }) diff --git a/x/crosschain/keeper/rate_limiter_flags.go b/x/crosschain/keeper/rate_limiter_flags.go index e8287e17f0..973a6a2554 100644 --- a/x/crosschain/keeper/rate_limiter_flags.go +++ b/x/crosschain/keeper/rate_limiter_flags.go @@ -5,7 +5,6 @@ import ( "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/crosschain/types" ) @@ -30,21 +29,21 @@ func (k Keeper) GetRateLimiterFlags(ctx sdk.Context) (val types.RateLimiterFlags } // GetRateLimiterAssetRateList returns a list of all foreign asset rate -func (k Keeper) GetRateLimiterAssetRateList(ctx sdk.Context) []*types.AssetRate { - rateLimiterFlags, _ := k.GetRateLimiterFlags(ctx) - - // the result list - assetRateList := make([]*types.AssetRate, 0) +func (k Keeper) GetRateLimiterAssetRateList(ctx sdk.Context) (flags types.RateLimiterFlags, assetRates []types.AssetRate, found bool) { + flags, found = k.GetRateLimiterFlags(ctx) + if !found { + return flags, nil, false + } // loop through the rate limiter flags to get the rate - for _, conversion := range rateLimiterFlags.Conversions { + for _, conversion := range flags.Conversions { fCoin, found := k.fungibleKeeper.GetForeignCoins(ctx, conversion.Zrc20) if !found { continue } // add asset rate to list - assetRateList = append(assetRateList, &types.AssetRate{ + assetRates = append(assetRates, types.AssetRate{ ChainId: fCoin.ForeignChainId, Asset: strings.ToLower(fCoin.Asset), Decimals: fCoin.Decimals, @@ -52,28 +51,5 @@ func (k Keeper) GetRateLimiterAssetRateList(ctx sdk.Context) []*types.AssetRate Rate: conversion.Rate, }) } - return assetRateList -} - -// BuildAssetRateMapFromList builds maps (foreign chain id -> asset -> rate) from a list of gas and erc20 asset rates -// The 1st map: foreign chain id -> gas coin asset rate -// The 2nd map: foreign chain id -> erc20 asset -> erc20 coin asset rate -func BuildAssetRateMapFromList(assetRates []*types.AssetRate) (map[int64]*types.AssetRate, map[int64]map[string]*types.AssetRate) { - // the result maps - gasAssetRateMap := make(map[int64]*types.AssetRate) - erc20AssetRateMap := make(map[int64]map[string]*types.AssetRate) - - // loop through the asset rates to build the maps - for _, assetRate := range assetRates { - chainID := assetRate.ChainId - if assetRate.CoinType == coin.CoinType_Gas { - gasAssetRateMap[chainID] = assetRate - } else { - if _, found := erc20AssetRateMap[chainID]; !found { - erc20AssetRateMap[chainID] = make(map[string]*types.AssetRate) - } - erc20AssetRateMap[chainID][strings.ToLower(assetRate.Asset)] = assetRate - } - } - return gasAssetRateMap, erc20AssetRateMap + return flags, assetRates, true } diff --git a/x/crosschain/keeper/rate_limiter_flags_test.go b/x/crosschain/keeper/rate_limiter_flags_test.go index 320085a1d1..6e0180bc88 100644 --- a/x/crosschain/keeper/rate_limiter_flags_test.go +++ b/x/crosschain/keeper/rate_limiter_flags_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "strings" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -10,7 +9,6 @@ import ( "github.com/zeta-chain/zetacore/pkg/coin" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" - "github.com/zeta-chain/zetacore/x/crosschain/keeper" "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" ) @@ -24,7 +22,7 @@ func createForeignCoinAndAssetRate( decimals uint32, coinType coin.CoinType, rate sdk.Dec, -) (fungibletypes.ForeignCoins, *types.AssetRate) { +) (fungibletypes.ForeignCoins, types.AssetRate) { // create foreign coin foreignCoin := sample.ForeignCoins(t, zrc20Addr) foreignCoin.Asset = asset @@ -33,13 +31,13 @@ func createForeignCoinAndAssetRate( foreignCoin.CoinType = coinType // create corresponding asset rate - assetRate := &types.AssetRate{ - ChainId: foreignCoin.ForeignChainId, - Asset: strings.ToLower(foreignCoin.Asset), - Decimals: foreignCoin.Decimals, - CoinType: foreignCoin.CoinType, - Rate: rate, - } + assetRate := sample.CustomAssetRate( + foreignCoin.ForeignChainId, + foreignCoin.Asset, + foreignCoin.Decimals, + foreignCoin.CoinType, + rate, + ) return foreignCoin, assetRate } @@ -59,7 +57,7 @@ func TestKeeper_GetRateLimiterFlags(t *testing.T) { require.Equal(t, flags, r) } -func TestKeeper_GetRateLimiterRateList(t *testing.T) { +func TestKeeper_GetRateLimiterAssetRateList(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) // create test flags @@ -67,7 +65,7 @@ func TestKeeper_GetRateLimiterRateList(t *testing.T) { zrc20GasAddr := sample.EthAddress().Hex() zrc20ERC20Addr1 := sample.EthAddress().Hex() zrc20ERC20Addr2 := sample.EthAddress().Hex() - flags := types.RateLimiterFlags{ + testflags := types.RateLimiterFlags{ Rate: sdk.NewUint(100), Conversions: []types.Conversion{ { @@ -85,8 +83,14 @@ func TestKeeper_GetRateLimiterRateList(t *testing.T) { }, } + // asset rates not found before setting flags + flags, assetRates, found := k.GetRateLimiterAssetRateList(ctx) + require.False(t, found) + require.Equal(t, types.RateLimiterFlags{}, flags) + require.Nil(t, assetRates) + // set flags - k.SetRateLimiterFlags(ctx, flags) + k.SetRateLimiterFlags(ctx, testflags) // add gas coin gasCoin, gasAssetRate := createForeignCoinAndAssetRate(t, zrc20GasAddr, "", chainID, 18, coin.CoinType_Gas, sdk.NewDec(1)) @@ -101,48 +105,8 @@ func TestKeeper_GetRateLimiterRateList(t *testing.T) { zk.FungibleKeeper.SetForeignCoins(ctx, erc20Coin2) // get rates - assetRates := k.GetRateLimiterAssetRateList(ctx) - require.EqualValues(t, []*types.AssetRate{gasAssetRate, erc20AssetRate1, erc20AssetRate2}, assetRates) -} - -func TestBuildAssetRateMapFromList(t *testing.T) { - // define asset rate list - assetRates := []*types.AssetRate{ - { - ChainId: 1, - Asset: "eth", - Decimals: 18, - CoinType: coin.CoinType_Gas, - Rate: sdk.NewDec(1), - }, - { - ChainId: 1, - Asset: "usdt", - Decimals: 6, - CoinType: coin.CoinType_ERC20, - Rate: sdk.NewDec(2), - }, - { - ChainId: 2, - Asset: "btc", - Decimals: 8, - CoinType: coin.CoinType_Gas, - Rate: sdk.NewDec(3), - }, - } - - // build asset rate map - gasAssetRateMap, erc20AssetRateMap := keeper.BuildAssetRateMapFromList(assetRates) - - // check length - require.Equal(t, 2, len(gasAssetRateMap)) - require.Equal(t, 1, len(erc20AssetRateMap)) - require.Equal(t, 1, len(erc20AssetRateMap[1])) - - // check gas asset rate map - require.EqualValues(t, assetRates[0], gasAssetRateMap[1]) - require.EqualValues(t, assetRates[2], gasAssetRateMap[2]) - - // check erc20 asset rate map - require.EqualValues(t, assetRates[1], erc20AssetRateMap[1]["usdt"]) + flags, assetRates, found = k.GetRateLimiterAssetRateList(ctx) + require.True(t, found) + require.Equal(t, testflags, flags) + require.EqualValues(t, []types.AssetRate{gasAssetRate, erc20AssetRate1, erc20AssetRate2}, assetRates) } diff --git a/x/crosschain/types/rate_limiter_flags.go b/x/crosschain/types/rate_limiter_flags.go index 93a9617961..59c98111f2 100644 --- a/x/crosschain/types/rate_limiter_flags.go +++ b/x/crosschain/types/rate_limiter_flags.go @@ -2,9 +2,12 @@ package types import ( "fmt" + "strings" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" + coin "github.com/zeta-chain/zetacore/pkg/coin" ) // Validate checks that the RateLimiterFlags is valid @@ -45,3 +48,95 @@ func (r RateLimiterFlags) GetConversionRate(zrc20 string) (sdk.Dec, bool) { } return sdk.NewDec(0), false } + +// BuildAssetRateMapFromList builds maps (foreign chain id -> asset -> rate) from a list of gas and erc20 asset rates +// The 1st map: foreign chain id -> gas coin asset rate +// The 2nd map: foreign chain id -> erc20 asset -> erc20 coin asset rate +func BuildAssetRateMapFromList(assetRates []AssetRate) (map[int64]AssetRate, map[int64]map[string]AssetRate) { + // the result maps + gasAssetRateMap := make(map[int64]AssetRate) + erc20AssetRateMap := make(map[int64]map[string]AssetRate) + + // loop through the asset rates to build the maps + for _, assetRate := range assetRates { + chainID := assetRate.ChainId + if assetRate.CoinType == coin.CoinType_Gas { + gasAssetRateMap[chainID] = assetRate + } else { + if _, found := erc20AssetRateMap[chainID]; !found { + erc20AssetRateMap[chainID] = make(map[string]AssetRate) + } + erc20AssetRateMap[chainID][strings.ToLower(assetRate.Asset)] = assetRate + } + } + return gasAssetRateMap, erc20AssetRateMap +} + +// ConvertCctxValue converts the value of the cctx to azeta using given conversion rates +func ConvertCctxValue( + chainID int64, + cctx *CrossChainTx, + gasAssetRateMap map[int64]AssetRate, + erc20AssetRateMap map[int64]map[string]AssetRate, +) sdkmath.Int { + var rate sdk.Dec + var decimals uint32 + switch cctx.InboundTxParams.CoinType { + case coin.CoinType_Zeta: + // no conversion needed for ZETA + return sdk.NewIntFromBigInt(cctx.GetCurrentOutTxParam().Amount.BigInt()) + case coin.CoinType_Gas: + assetRate, found := gasAssetRateMap[chainID] + if !found { + // skip if no rate found for gas coin on this chainID + return sdk.NewInt(0) + } + rate = assetRate.Rate + decimals = assetRate.Decimals + case coin.CoinType_ERC20: + // get the ERC20 coin rate + _, found := erc20AssetRateMap[chainID] + if !found { + // skip if no rate found for this chainID + return sdk.NewInt(0) + } + assetRate := erc20AssetRateMap[chainID][strings.ToLower(cctx.InboundTxParams.Asset)] + rate = assetRate.Rate + decimals = assetRate.Decimals + default: + // skip CoinType_Cmd + return sdk.NewInt(0) + } + // should not happen, return 0 to skip if it happens + if rate.IsNil() || rate.LTE(sdk.NewDec(0)) { + return sdkmath.NewInt(0) + } + + // the whole coin amounts of zeta and zrc20 + // given decimals = 6, the amount will be 10^6 = 1000000 + oneZeta := coin.AzetaPerZeta() + oneZrc20 := sdk.NewDec(10).Power(uint64(decimals)) + + // convert cctx asset amount into azeta amount + // given amountCctx = 2000000, rate = 0.8, decimals = 6 + // amountCctxDec: 2000000 * 0.8 = 1600000.0 + // amountAzetaDec: 1600000.0 * 10e18 / 10e6 = 1600000000000000000.0 + amountCctxDec := sdk.NewDecFromBigInt(cctx.GetCurrentOutTxParam().Amount.BigInt()) + amountAzetaDec := amountCctxDec.Mul(rate).Mul(oneZeta).Quo(oneZrc20) + return amountAzetaDec.TruncateInt() +} + +// RateLimitExceeded accumulates the cctx value and then checks if the rate limit is exceeded +// returns true if the rate limit is exceeded +func RateLimitExceeded( + chainID int64, + cctx *CrossChainTx, + gasAssetRateMap map[int64]AssetRate, + erc20AssetRateMap map[int64]map[string]AssetRate, + currentCctxValue *sdkmath.Int, + withdrawLimitInAzeta sdkmath.Int, +) bool { + amountZeta := ConvertCctxValue(chainID, cctx, gasAssetRateMap, erc20AssetRateMap) + *currentCctxValue = currentCctxValue.Add(amountZeta) + return currentCctxValue.GT(withdrawLimitInAzeta) +} diff --git a/x/crosschain/types/rate_limiter_flags_test.go b/x/crosschain/types/rate_limiter_flags_test.go index d837fabc51..f8376d546a 100644 --- a/x/crosschain/types/rate_limiter_flags_test.go +++ b/x/crosschain/types/rate_limiter_flags_test.go @@ -1,10 +1,16 @@ package types_test import ( + "fmt" + "strings" "testing" + "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/crosschain/types" ) @@ -184,3 +190,197 @@ func TestRateLimiterFlags_GetConversionRate(t *testing.T) { }) } } + +func TestBuildAssetRateMapFromList(t *testing.T) { + // define asset rate list + assetRates := []types.AssetRate{ + { + ChainId: 1, + Asset: "eth", + Decimals: 18, + CoinType: coin.CoinType_Gas, + Rate: sdk.NewDec(1), + }, + { + ChainId: 1, + Asset: "usdt", + Decimals: 6, + CoinType: coin.CoinType_ERC20, + Rate: sdk.NewDec(2), + }, + { + ChainId: 2, + Asset: "btc", + Decimals: 8, + CoinType: coin.CoinType_Gas, + Rate: sdk.NewDec(3), + }, + } + + // build asset rate map + gasAssetRateMap, erc20AssetRateMap := types.BuildAssetRateMapFromList(assetRates) + + // check length + require.Equal(t, 2, len(gasAssetRateMap)) + require.Equal(t, 1, len(erc20AssetRateMap)) + require.Equal(t, 1, len(erc20AssetRateMap[1])) + + // check gas asset rate map + require.EqualValues(t, assetRates[0], gasAssetRateMap[1]) + require.EqualValues(t, assetRates[2], gasAssetRateMap[2]) + + // check erc20 asset rate map + require.EqualValues(t, assetRates[1], erc20AssetRateMap[1]["usdt"]) +} + +func TestConvertCctxValue(t *testing.T) { + // chain IDs + ethChainID := chains.GoerliLocalnetChain.ChainId + btcChainID := chains.BtcRegtestChain.ChainId + + // setup test asset rates + assetETH := sample.EthAddress().Hex() + assetBTC := sample.EthAddress().Hex() + assetUSDT := sample.EthAddress().Hex() + assetRateList := []types.AssetRate{ + sample.CustomAssetRate(ethChainID, assetETH, 18, coin.CoinType_Gas, sdk.NewDec(2500)), + sample.CustomAssetRate(btcChainID, assetBTC, 8, coin.CoinType_Gas, sdk.NewDec(50000)), + sample.CustomAssetRate(ethChainID, assetUSDT, 6, coin.CoinType_ERC20, sdk.MustNewDecFromStr("0.8")), + } + gasAssetRateMap, erc20AssetRateMap := types.BuildAssetRateMapFromList(assetRateList) + + // test cases + tests := []struct { + name string + + // input + chainID int64 + coinType coin.CoinType + asset string + amount math.Uint + gasAssetRates map[int64]types.AssetRate + erc20AssetRates map[int64]map[string]types.AssetRate + + // output + expectedValue sdkmath.Int + }{ + { + name: "should convert cctx ZETA value correctly", + chainID: ethChainID, + coinType: coin.CoinType_Zeta, + asset: "", + amount: sdk.NewUint(3e17), // 0.3 ZETA + gasAssetRates: gasAssetRateMap, + erc20AssetRates: erc20AssetRateMap, + expectedValue: sdk.NewInt(3e17), + }, + { + name: "should convert cctx ETH value correctly", + chainID: ethChainID, + coinType: coin.CoinType_Gas, + asset: "", + amount: sdk.NewUint(3e15), // 0.003 ETH + gasAssetRates: gasAssetRateMap, + erc20AssetRates: erc20AssetRateMap, + expectedValue: sdk.NewInt(75e17), // 0.003 ETH * 2500 = 7.5 ZETA + }, + { + name: "should convert cctx BTC value correctly", + chainID: btcChainID, + coinType: coin.CoinType_Gas, + asset: "", + amount: sdk.NewUint(70000), // 0.0007 BTC + gasAssetRates: gasAssetRateMap, + erc20AssetRates: erc20AssetRateMap, + expectedValue: sdk.NewInt(35).Mul(sdk.NewInt(1e18)), // 0.0007 BTC * 50000 = 35.0 ZETA + }, + { + name: "should convert cctx USDT value correctly", + chainID: ethChainID, + coinType: coin.CoinType_ERC20, + asset: assetUSDT, + amount: sdk.NewUint(3e6), // 3 USDT + gasAssetRates: gasAssetRateMap, + erc20AssetRates: erc20AssetRateMap, + expectedValue: sdk.NewInt(24e17), // 3 USDT * 0.8 = 2.4 ZETA + }, + { + name: "should return 0 if no gas asset rate found for chainID", + chainID: ethChainID, + coinType: coin.CoinType_Gas, + asset: "", + amount: sdk.NewUint(100), + gasAssetRates: nil, + erc20AssetRates: erc20AssetRateMap, + expectedValue: sdk.NewInt(0), + }, + { + name: "should return 0 if no erc20 asset rate found for chainID", + chainID: ethChainID, + coinType: coin.CoinType_ERC20, + asset: assetUSDT, + amount: sdk.NewUint(100), + gasAssetRates: gasAssetRateMap, + erc20AssetRates: nil, + expectedValue: sdk.NewInt(0), + }, + { + name: "should return 0 if coinType is CoinType_Cmd", + chainID: ethChainID, + coinType: coin.CoinType_Cmd, + asset: "", + amount: sdk.NewUint(100), + gasAssetRates: gasAssetRateMap, + erc20AssetRates: erc20AssetRateMap, + expectedValue: sdk.NewInt(0), + }, + { + name: "should return 0 on nil rate", + chainID: ethChainID, + coinType: coin.CoinType_Gas, + asset: "", + amount: sdk.NewUint(100), + gasAssetRates: func() map[int64]types.AssetRate { + // set rate to nil + nilAssetRateMap, _ := types.BuildAssetRateMapFromList(assetRateList) + nilRate := nilAssetRateMap[ethChainID] + nilRate.Rate = sdk.Dec{} + nilAssetRateMap[ethChainID] = nilRate + return nilAssetRateMap + }(), + erc20AssetRates: erc20AssetRateMap, + expectedValue: sdk.NewInt(0), + }, + { + name: "should return 0 on rate <= 0", + chainID: ethChainID, + coinType: coin.CoinType_ERC20, + asset: assetUSDT, + amount: sdk.NewUint(100), + gasAssetRates: gasAssetRateMap, + erc20AssetRates: func() map[int64]map[string]types.AssetRate { + // set rate to 0 + _, zeroAssetRateMap := types.BuildAssetRateMapFromList(assetRateList) + zeroRate := zeroAssetRateMap[ethChainID][strings.ToLower(assetUSDT)] + zeroRate.Rate = sdk.NewDec(0) + zeroAssetRateMap[ethChainID][strings.ToLower(assetUSDT)] = zeroRate + return zeroAssetRateMap + }(), + expectedValue: sdk.NewInt(0), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // create cctx with given input + cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", tt.chainID, 1)) + cctx.InboundTxParams.CoinType = tt.coinType + cctx.InboundTxParams.Asset = tt.asset + cctx.GetCurrentOutTxParam().Amount = tt.amount + + // convert cctx value + value := types.ConvertCctxValue(tt.chainID, cctx, tt.gasAssetRates, tt.erc20AssetRates) + require.Equal(t, tt.expectedValue, value) + }) + } +} diff --git a/zetaclient/core_context/zeta_core_context.go b/zetaclient/core_context/zeta_core_context.go index 256684c895..8390775959 100644 --- a/zetaclient/core_context/zeta_core_context.go +++ b/zetaclient/core_context/zeta_core_context.go @@ -75,17 +75,21 @@ func (c *ZetaCoreContext) GetCurrentTssPubkey() string { return c.currentTssPubkey } +// GetEnabledChains returns all enabled chains including zetachain func (c *ZetaCoreContext) GetEnabledChains() []chains.Chain { c.coreContextLock.RLock() defer c.coreContextLock.RUnlock() + copiedChains := make([]chains.Chain, len(c.chainsEnabled)) copy(copiedChains, c.chainsEnabled) return copiedChains } -func (c *ZetaCoreContext) GetEnabledForeignChains() []chains.Chain { +// GetEnabledExternalChains returns all enabled external chains +func (c *ZetaCoreContext) GetEnabledExternalChains() []chains.Chain { c.coreContextLock.RLock() defer c.coreContextLock.RUnlock() + foreignChains := make([]chains.Chain, 0) for _, chain := range c.chainsEnabled { if !chain.IsZetaChain() { @@ -98,6 +102,7 @@ func (c *ZetaCoreContext) GetEnabledForeignChains() []chains.Chain { func (c *ZetaCoreContext) GetEVMChainParams(chainID int64) (*observertypes.ChainParams, bool) { c.coreContextLock.RLock() defer c.coreContextLock.RUnlock() + evmChainParams, found := c.evmChainParams[chainID] return evmChainParams, found } diff --git a/zetaclient/core_context/zeta_core_context_test.go b/zetaclient/core_context/zeta_core_context_test.go index 771ff6f920..625fb40269 100644 --- a/zetaclient/core_context/zeta_core_context_test.go +++ b/zetaclient/core_context/zeta_core_context_test.go @@ -69,8 +69,8 @@ func TestNewZetaCoreContext(t *testing.T) { // assert enabled chains require.Empty(t, len(zetaContext.GetEnabledChains())) - // assert foreign chains - require.Empty(t, len(zetaContext.GetEnabledForeignChains())) + // assert external chains + require.Empty(t, len(zetaContext.GetEnabledExternalChains())) // assert current tss pubkey require.Equal(t, "", zetaContext.GetCurrentTssPubkey()) @@ -196,8 +196,8 @@ func TestUpdateZetaCoreContext(t *testing.T) { // assert enabled chains updated require.Equal(t, enabledChainsToUpdate, zetaContext.GetEnabledChains()) - // assert enabled foreign chains - require.Equal(t, enabledChainsToUpdate[0:2], zetaContext.GetEnabledForeignChains()) + // assert enabled external chains + require.Equal(t, enabledChainsToUpdate[0:2], zetaContext.GetEnabledExternalChains()) // assert current tss pubkey updated require.Equal(t, tssPubKeyToUpdate, zetaContext.GetCurrentTssPubkey()) diff --git a/zetaclient/ratelimiter/rate_limiter_test.go b/zetaclient/ratelimiter/rate_limiter_test.go index 2012d19d60..0f6c3a0e73 100644 --- a/zetaclient/ratelimiter/rate_limiter_test.go +++ b/zetaclient/ratelimiter/rate_limiter_test.go @@ -14,7 +14,7 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/ratelimiter" ) -func Test_NewFilterInput(t *testing.T) { +func Test_NewInput(t *testing.T) { // sample response response := crosschaintypes.QueryRateLimiterInputResponse{ Height: 10, @@ -26,7 +26,7 @@ func Test_NewFilterInput(t *testing.T) { LowestPendingCctxHeight: 2, } - t.Run("should create a filter input from gRPC response", func(t *testing.T) { + t.Run("should create a input from gRPC response", func(t *testing.T) { filterInput, ok := ratelimiter.NewInput(response) require.True(t, ok) require.Equal(t, response.Height, filterInput.Height) diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index da45c4fb31..de2f9707f4 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -22,16 +22,20 @@ import ( ) const ( - // EVMOutboundTxLookbackFactor is the factor to determine how many nonces to look back for pending cctxs + // evmOutboundTxLookbackFactor is the factor to determine how many nonces to look back for pending cctxs // For example, give OutboundTxScheduleLookahead of 120, pending NonceLow of 1000 and factor of 1.0, // the scheduler need to be able to pick up and schedule any pending cctx with nonce < 880 (1000 - 120 * 1.0) // NOTE: 1.0 means look back the same number of cctxs as we look ahead - EVMOutboundTxLookbackFactor = 1.0 + evmOutboundTxLookbackFactor = 1.0 + + // sampling rate for sampled observer logger + loggerSamplingRate = 10 ) type ZetaCoreLog struct { - ChainLogger zerolog.Logger - ZetaChainWatcher zerolog.Logger + Observer zerolog.Logger + ObserverSampled zerolog.Logger + OutTxProcessor zerolog.Logger } // CoreObserver wraps the zetabridge, chain clients and signers. This is the high level object used for CCTX scheduling @@ -57,22 +61,23 @@ func NewCoreObserver( ts: ts, stop: make(chan struct{}), } - chainLogger := logger.With(). - Str("chain", "ZetaChain"). - Logger() + + // create loggers + chainLogger := logger.With().Str("chain", "ZetaChain").Logger() co.logger = ZetaCoreLog{ - ChainLogger: chainLogger, - ZetaChainWatcher: chainLogger.With().Str("module", "ZetaChainWatcher").Logger(), + Observer: chainLogger.With().Str("module", "Observer").Logger(), + OutTxProcessor: chainLogger.With().Str("module", "OutTxProcessor").Logger(), } + co.logger.ObserverSampled = co.logger.Observer.Sample(&zerolog.BasicSampler{N: loggerSamplingRate}) + // set bridge, signers and clients co.bridge = bridge co.signerMap = signerMap - co.clientMap = clientMap - co.logger.ChainLogger.Info().Msg("starting core observer") + co.logger.Observer.Info().Msg("starting core observer") balance, err := bridge.GetZetaHotKeyBalance() if err != nil { - co.logger.ChainLogger.Error().Err(err).Msg("error getting last balance of the hot key") + co.logger.Observer.Error().Err(err).Msg("error getting last balance of the hot key") } co.lastOperatorBalance = balance @@ -81,7 +86,7 @@ func NewCoreObserver( func (co *CoreObserver) MonitorCore(appContext *appcontext.AppContext) { myid := co.bridge.GetKeys().GetAddress() - co.logger.ZetaChainWatcher.Info().Msgf("Starting Send Scheduler for %s", myid) + co.logger.Observer.Info().Msgf("Starting cctx scheduler for %s", myid) go co.StartCctxScheduler(appContext) go func() { @@ -110,12 +115,12 @@ func (co *CoreObserver) GetUpdatedSigner(coreContext *corecontext.ZetaCoreContex erc20CustodyAddress := ethcommon.HexToAddress(evmParams.GetErc20CustodyContractAddress()) if zetaConnectorAddress != signer.GetZetaConnectorAddress() { signer.SetZetaConnectorAddress(zetaConnectorAddress) - co.logger.ZetaChainWatcher.Info().Msgf( + co.logger.Observer.Info().Msgf( "updated zeta connector address for chainID %d, new address: %s", chainID, zetaConnectorAddress) } if erc20CustodyAddress != signer.GetERC20CustodyAddress() { signer.SetERC20CustodyAddress(erc20CustodyAddress) - co.logger.ZetaChainWatcher.Info().Msgf( + co.logger.Observer.Info().Msgf( "updated ERC20 custody address for chainID %d, new address: %s", chainID, erc20CustodyAddress) } } @@ -135,7 +140,7 @@ func (co *CoreObserver) GetUpdatedChainClient(coreContext *corecontext.ZetaCoreC evmParams, found := coreContext.GetEVMChainParams(chainID) if found && !observertypes.ChainParamsEqual(curParams, *evmParams) { chainOb.SetChainParams(*evmParams) - co.logger.ZetaChainWatcher.Info().Msgf( + co.logger.Observer.Info().Msgf( "updated chain params for chainID %d, new params: %v", chainID, *evmParams) } } else if chains.IsBitcoinChain(chainID) { @@ -143,7 +148,7 @@ func (co *CoreObserver) GetUpdatedChainClient(coreContext *corecontext.ZetaCoreC if found && !observertypes.ChainParamsEqual(curParams, *btcParams) { chainOb.SetChainParams(*btcParams) - co.logger.ZetaChainWatcher.Info().Msgf( + co.logger.Observer.Info().Msgf( "updated chain params for Bitcoin, new params: %v", *btcParams) } } @@ -151,7 +156,7 @@ func (co *CoreObserver) GetUpdatedChainClient(coreContext *corecontext.ZetaCoreC } // GetPendingCctxsWithinRatelimit get pending cctxs across foreign chains within rate limit -func (co *CoreObserver) GetPendingCctxsWithinRatelimit(foreignChains []chains.Chain, logger zerolog.Logger) (map[int64][]*types.CrossChainTx, error) { +func (co *CoreObserver) GetPendingCctxsWithinRatelimit(foreignChains []chains.Chain) (map[int64][]*types.CrossChainTx, error) { // get rate limiter flags rateLimitFlags, err := co.bridge.GetRateLimiterFlags() if err != nil { @@ -191,7 +196,7 @@ func (co *CoreObserver) GetPendingCctxsWithinRatelimit(foreignChains []chains.Ch if percentage != nil { percentageFloat, _ := percentage.Float64() metrics.PercentageOfRateReached.Set(percentageFloat) - logger.Info().Msgf("current rate limiter window: %d rate: %s, percentage: %f", + co.logger.ObserverSampled.Info().Msgf("current rate limiter window: %d rate: %s, percentage: %f", output.CurrentWithdrawWindow, output.CurrentWithdrawRate.String(), percentageFloat) } @@ -200,24 +205,23 @@ func (co *CoreObserver) GetPendingCctxsWithinRatelimit(foreignChains []chains.Ch // StartCctxScheduler schedules keysigns for cctxs on each ZetaChain block (the ticker) func (co *CoreObserver) StartCctxScheduler(appContext *appcontext.AppContext) { - outTxMan := outtxprocessor.NewOutTxProcessorManager(co.logger.ChainLogger) + outTxMan := outtxprocessor.NewOutTxProcessorManager(co.logger.OutTxProcessor) observeTicker := time.NewTicker(3 * time.Second) - sampledLogger := co.logger.ZetaChainWatcher.Sample(&zerolog.BasicSampler{N: 10}) var lastBlockNum int64 for { select { case <-co.stop: - co.logger.ZetaChainWatcher.Warn().Msg("StartCctxScheduler: stopped") + co.logger.Observer.Warn().Msg("StartCctxScheduler: stopped") return case <-observeTicker.C: { bn, err := co.bridge.GetZetaBlockHeight() if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msg("StartCctxScheduler: GetZetaBlockHeight fail") + co.logger.Observer.Error().Err(err).Msg("StartCctxScheduler: GetZetaBlockHeight fail") continue } if bn < 0 { - co.logger.ZetaChainWatcher.Error().Msg("StartCctxScheduler: GetZetaBlockHeight returned negative height") + co.logger.Observer.Error().Msg("StartCctxScheduler: GetZetaBlockHeight returned negative height") continue } if lastBlockNum == 0 { @@ -226,12 +230,12 @@ func (co *CoreObserver) StartCctxScheduler(appContext *appcontext.AppContext) { if bn > lastBlockNum { // we have a new block bn = lastBlockNum + 1 if bn%10 == 0 { - co.logger.ZetaChainWatcher.Debug().Msgf("StartCctxScheduler: ZetaCore heart beat: %d", bn) + co.logger.Observer.Debug().Msgf("StartCctxScheduler: ZetaCore heart beat: %d", bn) } balance, err := co.bridge.GetZetaHotKeyBalance() if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("couldn't get operator balance") + co.logger.Observer.Error().Err(err).Msgf("couldn't get operator balance") } else { diff := co.lastOperatorBalance.Sub(balance) if diff.GT(sdkmath.NewInt(0)) && diff.LT(sdkmath.NewInt(math.MaxInt64)) { @@ -243,18 +247,18 @@ func (co *CoreObserver) StartCctxScheduler(appContext *appcontext.AppContext) { // set current hot key burn rate metrics.HotKeyBurnRate.Set(float64(co.ts.HotKeyBurnRate.GetBurnRate().Int64())) - // get supported foreign chains + // get supported external chains coreContext := appContext.ZetaCoreContext() - foreignChains := coreContext.GetEnabledForeignChains() + externalChains := coreContext.GetEnabledExternalChains() - // query pending cctxs across all foreign chains within rate limit - cctxMap, err := co.GetPendingCctxsWithinRatelimit(foreignChains, sampledLogger) + // query pending cctxs across all external chains within rate limit + cctxMap, err := co.GetPendingCctxsWithinRatelimit(externalChains) if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("StartCctxScheduler: GetPendingCctxsWithinRatelimit failed") + co.logger.Observer.Error().Err(err).Msgf("StartCctxScheduler: GetPendingCctxsWithinRatelimit failed") } // schedule keysign for pending cctxs on each chain - for _, c := range foreignChains { + for _, c := range externalChains { // get cctxs from map and set pending transactions prometheus gauge cctxList := cctxMap[c.ChainId] metrics.PendingTxsPerChain.WithLabelValues(c.ChainName.String()).Set(float64(len(cctxList))) @@ -265,12 +269,12 @@ func (co *CoreObserver) StartCctxScheduler(appContext *appcontext.AppContext) { // update chain parameters for signer and chain client signer, err := co.GetUpdatedSigner(coreContext, c.ChainId) if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("StartCctxScheduler: GetUpdatedSigner failed for chain %d", c.ChainId) + co.logger.Observer.Error().Err(err).Msgf("StartCctxScheduler: GetUpdatedSigner failed for chain %d", c.ChainId) continue } ob, err := co.GetUpdatedChainClient(coreContext, c.ChainId) if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("StartCctxScheduler: GetUpdatedChainClient failed for chain %d", c.ChainId) + co.logger.Observer.Error().Err(err).Msgf("StartCctxScheduler: GetUpdatedChainClient failed for chain %d", c.ChainId) continue } if !corecontext.IsOutboundObservationEnabled(coreContext, ob.GetChainParams()) { @@ -284,7 +288,7 @@ func (co *CoreObserver) StartCctxScheduler(appContext *appcontext.AppContext) { } else if chains.IsBitcoinChain(c.ChainId) { co.ScheduleCctxBTC(outTxMan, zetaHeight, c.ChainId, cctxList, ob, signer) } else { - co.logger.ZetaChainWatcher.Error().Msgf("StartCctxScheduler: unsupported chain %d", c.ChainId) + co.logger.Observer.Error().Msgf("StartCctxScheduler: unsupported chain %d", c.ChainId) continue } } @@ -308,7 +312,7 @@ func (co *CoreObserver) ScheduleCctxEVM( signer interfaces.ChainSigner) { res, err := co.bridge.GetAllOutTxTrackerByChain(chainID, interfaces.Ascending) if err != nil { - co.logger.ZetaChainWatcher.Warn().Err(err).Msgf("ScheduleCctxEVM: GetAllOutTxTrackerByChain failed for chain %d", chainID) + co.logger.Observer.Warn().Err(err).Msgf("ScheduleCctxEVM: GetAllOutTxTrackerByChain failed for chain %d", chainID) return } trackerMap := make(map[uint64]bool) @@ -317,7 +321,7 @@ func (co *CoreObserver) ScheduleCctxEVM( } outboundScheduleLookahead := ob.GetChainParams().OutboundTxScheduleLookahead // #nosec G701 always in range - outboundScheduleLookback := uint64(float64(outboundScheduleLookahead) * EVMOutboundTxLookbackFactor) + outboundScheduleLookback := uint64(float64(outboundScheduleLookahead) * evmOutboundTxLookbackFactor) // #nosec G701 positive outboundScheduleInterval := uint64(ob.GetChainParams().OutboundTxScheduleInterval) @@ -327,23 +331,23 @@ func (co *CoreObserver) ScheduleCctxEVM( outTxID := outtxprocessor.ToOutTxID(cctx.Index, params.ReceiverChainId, nonce) if params.ReceiverChainId != chainID { - co.logger.ZetaChainWatcher.Error().Msgf("ScheduleCctxEVM: outtx %s chainid mismatch: want %d, got %d", outTxID, chainID, params.ReceiverChainId) + co.logger.Observer.Error().Msgf("ScheduleCctxEVM: outtx %s chainid mismatch: want %d, got %d", outTxID, chainID, params.ReceiverChainId) continue } if params.OutboundTxTssNonce > cctxList[0].GetCurrentOutTxParam().OutboundTxTssNonce+outboundScheduleLookback { - co.logger.ZetaChainWatcher.Error().Msgf("ScheduleCctxEVM: nonce too high: signing %d, earliest pending %d, chain %d", + co.logger.Observer.Error().Msgf("ScheduleCctxEVM: nonce too high: signing %d, earliest pending %d, chain %d", params.OutboundTxTssNonce, cctxList[0].GetCurrentOutTxParam().OutboundTxTssNonce, chainID) break } // try confirming the outtx - included, _, err := ob.IsOutboundProcessed(cctx, co.logger.ZetaChainWatcher) + included, _, err := ob.IsOutboundProcessed(cctx, co.logger.Observer) if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("ScheduleCctxEVM: IsOutboundProcessed faild for chain %d nonce %d", chainID, nonce) + co.logger.Observer.Error().Err(err).Msgf("ScheduleCctxEVM: IsOutboundProcessed faild for chain %d nonce %d", chainID, nonce) continue } if included { - co.logger.ZetaChainWatcher.Info().Msgf("ScheduleCctxEVM: outtx %s already included; do not schedule keysign", outTxID) + co.logger.Observer.Info().Msgf("ScheduleCctxEVM: outtx %s already included; do not schedule keysign", outTxID) continue } @@ -371,7 +375,7 @@ func (co *CoreObserver) ScheduleCctxEVM( // otherwise, the normal interval is used if nonce%outboundScheduleInterval == zetaHeight%outboundScheduleInterval && !outTxMan.IsOutTxActive(outTxID) { outTxMan.StartTryProcess(outTxID) - co.logger.ZetaChainWatcher.Debug().Msgf("ScheduleCctxEVM: sign outtx %s with value %d\n", outTxID, cctx.GetCurrentOutTxParam().Amount) + co.logger.Observer.Debug().Msgf("ScheduleCctxEVM: sign outtx %s with value %d\n", outTxID, cctx.GetCurrentOutTxParam().Amount) go signer.TryProcessOutTx(cctx, outTxMan, outTxID, ob, co.bridge, zetaHeight) } @@ -395,7 +399,7 @@ func (co *CoreObserver) ScheduleCctxBTC( signer interfaces.ChainSigner) { btcClient, ok := ob.(*bitcoin.BTCChainClient) if !ok { // should never happen - co.logger.ZetaChainWatcher.Error().Msgf("scheduleCctxBTC: chain client is not a bitcoin client") + co.logger.Observer.Error().Msgf("ScheduleCctxBTC: chain client is not a bitcoin client") return } // #nosec G701 positive @@ -409,17 +413,17 @@ func (co *CoreObserver) ScheduleCctxBTC( outTxID := outtxprocessor.ToOutTxID(cctx.Index, params.ReceiverChainId, nonce) if params.ReceiverChainId != chainID { - co.logger.ZetaChainWatcher.Error().Msgf("scheduleCctxBTC: outtx %s chainid mismatch: want %d, got %d", outTxID, chainID, params.ReceiverChainId) + co.logger.Observer.Error().Msgf("ScheduleCctxBTC: outtx %s chainid mismatch: want %d, got %d", outTxID, chainID, params.ReceiverChainId) continue } // try confirming the outtx - included, confirmed, err := btcClient.IsOutboundProcessed(cctx, co.logger.ZetaChainWatcher) + included, confirmed, err := btcClient.IsOutboundProcessed(cctx, co.logger.Observer) if err != nil { - co.logger.ZetaChainWatcher.Error().Err(err).Msgf("scheduleCctxBTC: IsOutboundProcessed faild for chain %d nonce %d", chainID, nonce) + co.logger.Observer.Error().Err(err).Msgf("ScheduleCctxBTC: IsOutboundProcessed faild for chain %d nonce %d", chainID, nonce) continue } if included || confirmed { - co.logger.ZetaChainWatcher.Info().Msgf("scheduleCctxBTC: outtx %s already included; do not schedule keysign", outTxID) + co.logger.Observer.Info().Msgf("ScheduleCctxBTC: outtx %s already included; do not schedule keysign", outTxID) continue } @@ -429,13 +433,13 @@ func (co *CoreObserver) ScheduleCctxBTC( } // stop if lookahead is reached if int64(idx) >= lookahead { // 2 bitcoin confirmations span is 20 minutes on average. We look ahead up to 100 pending cctx to target TPM of 5. - co.logger.ZetaChainWatcher.Warn().Msgf("scheduleCctxBTC: lookahead reached, signing %d, earliest pending %d", nonce, cctxList[0].GetCurrentOutTxParam().OutboundTxTssNonce) + co.logger.Observer.Warn().Msgf("ScheduleCctxBTC: lookahead reached, signing %d, earliest pending %d", nonce, cctxList[0].GetCurrentOutTxParam().OutboundTxTssNonce) break } // try confirming the outtx or scheduling a keysign if nonce%interval == zetaHeight%interval && !outTxMan.IsOutTxActive(outTxID) { outTxMan.StartTryProcess(outTxID) - co.logger.ZetaChainWatcher.Debug().Msgf("scheduleCctxBTC: sign outtx %s with value %d\n", outTxID, params.Amount) + co.logger.Observer.Debug().Msgf("ScheduleCctxBTC: sign outtx %s with value %d\n", outTxID, params.Amount) go signer.TryProcessOutTx(cctx, outTxMan, outTxID, ob, co.bridge, zetaHeight) } } diff --git a/zetaclient/zetacore_observer_test.go b/zetaclient/zetacore_observer_test.go index e5a439c2e2..33ec1c01e7 100644 --- a/zetaclient/zetacore_observer_test.go +++ b/zetaclient/zetacore_observer_test.go @@ -320,7 +320,7 @@ func Test_GetPendingCctxsWithinRatelimit(t *testing.T) { observer := MockCoreObserver(t, bridge, ethChain, btcChain, ethChainParams, btcChainParams) // run the test - cctxsMap, err := observer.GetPendingCctxsWithinRatelimit(foreignChains, zerolog.Logger{}) + cctxsMap, err := observer.GetPendingCctxsWithinRatelimit(foreignChains) if tt.fail { require.Error(t, err) require.Nil(t, cctxsMap)