From 7c98376996987cae0338ed53847340c4a994a4e7 Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Tue, 7 May 2024 12:43:40 -0500 Subject: [PATCH] refactor: move rate limiter to zetaclient and add metrics (#2110) * initial commit of grpc pending cctx query with rate limiter * replace big.Float with sdk.Dec and update mock rate limiter flags * split big loop into backwards loop and forwards loop to be more accurate * adjust zetaclient code to query pending cctx with rate limit * update change log and add one more rate limiter flag test * use outboun amount for calculation * some minimum code refactor * created separate file for cctx query with rate limit * improved a few error handlling * use old cctx query as fallback when rate limiter is disabled; some renaming * fixed unit test compile * added unit test for fallback query * added unit tests for cctx value conversion * add changelog entry * added unit tests for query pending cctxs within rate limit * added total value in rate limiter window for monitoring purpose * Update x/crosschain/keeper/grpc_query_cctx_rate_limit.go Co-authored-by: Lucas Bertrand * change variable name fCoin to foreignCoin * Update x/fungible/keeper/foreign_coins.go Co-authored-by: Lucas Bertrand * Update x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go Co-authored-by: Lucas Bertrand * converted rate limiter query unit tests to table test * handle edge case when pending cctxs span wider block range than sliding window * added zero rate check; added comment to make unit test clearer * added unit test and note for method GetAllForeignCoinMap * treat Rate as average block rate; stop outbound when current rate limit exceeds Rate; updated metrics * refactor: allow zeta deposits to new zevm address (#2076) * allow zevm coin deposit to unknow addresses * add e2e tests * add changelog * add comments * add commented unit tests back * replace sdk.Dec with sdkmath.Int to represent cctx value in azeta * test(e2e): add rate limiter admin E2E test (#2063) * refactor and create Withdraw ZETA general function * new rate limiter test * use rate limiter for admin test * fix the test: single approval and add liquidity * make generate * fix liquidity * fix uniswap pool * change localnet chain params * fix lint * add cli query * add nil check * fix nil point * modify tests * eliminate nil pending nonce issue * fix query * set flags * Update e2e/runner/evm.go Co-authored-by: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> * add back other advanced tests * make generate * add comment * fix eth liquidity cap test * fix withdraw count --------- Co-authored-by: Charlie Chen Co-authored-by: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> * removed incorrect Note * initiated rate limiter refactor and added metrics * added more unit tests and updated changelog * print more details from rate limiter output * reorder observer methods declaration * fix unit test * moved state irrelevant methods to types; renaming and cleaning * code indentation and update variable * replace with * fix unit test * added extra unit tests and improved func name --------- Co-authored-by: Lucas Bertrand Co-authored-by: Tanmay --- changelog.md | 4 +- docs/openapi/openapi.swagger.yaml | 52 + pkg/math/float.go | 28 + pkg/math/float_test.go | 68 ++ .../zetachain/zetacore/crosschain/query.proto | 21 + .../crosschain/rate_limiter_flags.proto | 12 + testutil/sample/crosschain.go | 62 ++ .../zetacore/crosschain/query_pb.d.ts | 83 ++ .../crosschain/rate_limiter_flags_pb.d.ts | 45 + x/crosschain/keeper/cctx_utils.go | 12 + x/crosschain/keeper/cctx_utils_test.go | 34 + .../keeper/grpc_query_cctx_rate_limit.go | 260 +++-- .../keeper/grpc_query_cctx_rate_limit_test.go | 431 +++++--- x/crosschain/keeper/rate_limiter_flags.go | 38 +- .../keeper/rate_limiter_flags_test.go | 87 +- x/crosschain/types/query.pb.go | 955 +++++++++++++++--- x/crosschain/types/query.pb.gw.go | 83 ++ x/crosschain/types/rate_limiter_flags.go | 95 ++ x/crosschain/types/rate_limiter_flags.pb.go | 374 ++++++- x/crosschain/types/rate_limiter_flags_test.go | 200 ++++ zetaclient/core_context/zeta_core_context.go | 17 + .../core_context/zeta_core_context_test.go | 17 +- zetaclient/interfaces/interfaces.go | 2 + zetaclient/metrics/metrics.go | 6 + zetaclient/ratelimiter/rate_limiter.go | 139 +++ zetaclient/ratelimiter/rate_limiter_test.go | 265 +++++ zetaclient/testutils/stub/core_bridge.go | 87 +- zetaclient/zetabridge/query.go | 98 +- zetaclient/zetabridge/query_test.go | 48 + zetaclient/zetacore_observer.go | 200 ++-- zetaclient/zetacore_observer_test.go | 156 ++- 31 files changed, 3359 insertions(+), 620 deletions(-) create mode 100644 pkg/math/float.go create mode 100644 pkg/math/float_test.go create mode 100644 zetaclient/ratelimiter/rate_limiter.go create mode 100644 zetaclient/ratelimiter/rate_limiter_test.go diff --git a/changelog.md b/changelog.md index abe4efaf75..1c4190aab9 100644 --- a/changelog.md +++ b/changelog.md @@ -14,9 +14,7 @@ ### Refactor * [2094](https://github.com/zeta-chain/node/pull/2094) - upgrade go-tss to use cosmos v0.47 - -### Refactor - +* [2110](https://github.com/zeta-chain/node/pull/2110) - move non-query rate limiter logic to zetaclient side and code refactor. * [2032](https://github.com/zeta-chain/node/pull/2032) - improve some general structure of the ZetaClient codebase * [2071](https://github.com/zeta-chain/node/pull/2071) - Modify chains struct to add all chain related information * [2124](https://github.com/zeta-chain/node/pull/2124) - removed unused variables and method diff --git a/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml index 17a447b6dc..fa0855454a 100644 --- a/docs/openapi/openapi.swagger.yaml +++ b/docs/openapi/openapi.swagger.yaml @@ -29015,6 +29015,32 @@ paths: $ref: '#/definitions/googlerpcStatus' tags: - Query + /zeta-chain/crosschain/rateLimiterInput: + get: + summary: Queries the input data of rate limiter. + operationId: Query_RateLimiterInput + responses: + "200": + description: A successful response. + schema: + $ref: '#/definitions/crosschainQueryRateLimiterInputResponse' + default: + description: An unexpected error response. + schema: + $ref: '#/definitions/googlerpcStatus' + parameters: + - name: limit + in: query + required: false + type: integer + format: int64 + - name: window + in: query + required: false + type: string + format: int64 + tags: + - Query /zeta-chain/crosschain/zetaAccounting: get: operationId: Query_ZetaAccounting @@ -56829,6 +56855,32 @@ definitions: properties: rateLimiterFlags: $ref: '#/definitions/crosschainRateLimiterFlags' + crosschainQueryRateLimiterInputResponse: + type: object + properties: + height: + type: string + format: int64 + cctxs_missed: + type: array + items: + type: object + $ref: '#/definitions/crosschainCrossChainTx' + cctxs_pending: + type: array + items: + type: object + $ref: '#/definitions/crosschainCrossChainTx' + total_pending: + type: string + format: uint64 + past_cctxs_value: + type: string + pending_cctxs_value: + type: string + lowest_pending_cctx_height: + type: string + format: int64 crosschainQueryZetaAccountingResponse: type: object properties: diff --git a/pkg/math/float.go b/pkg/math/float.go new file mode 100644 index 0000000000..b2fa2a7036 --- /dev/null +++ b/pkg/math/float.go @@ -0,0 +1,28 @@ +package math + +import ( + "math/big" +) + +// Percentage calculates the percentage of A over B. +func Percentage(a, b *big.Int) *big.Float { + // cannot calculate if either a or b is nil + if a == nil || b == nil { + return nil + } + + // if a is zero, return nil to avoid division by zero + if b.Cmp(big.NewInt(0)) == 0 { + return nil + } + + // convert a and b to big.Float + floatA := new(big.Float).SetInt(a) + floatB := new(big.Float).SetInt(b) + + // calculate the percentage of a over b + percentage := new(big.Float).Quo(floatA, floatB) + percentage.Mul(percentage, big.NewFloat(100)) + + return percentage +} diff --git a/pkg/math/float_test.go b/pkg/math/float_test.go new file mode 100644 index 0000000000..171645beb8 --- /dev/null +++ b/pkg/math/float_test.go @@ -0,0 +1,68 @@ +package math + +import ( + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPercentage(t *testing.T) { + testCases := []struct { + name string + numerator *big.Int + denominator *big.Int + percentage *big.Float + fail bool + }{ + { + name: "positive percentage", + numerator: big.NewInt(165), + denominator: big.NewInt(1000), + percentage: big.NewFloat(16.5), + fail: false, + }, + { + name: "negative percentage", + numerator: big.NewInt(-165), + denominator: big.NewInt(1000), + percentage: big.NewFloat(-16.5), + fail: false, + }, + { + name: "zero denominator", + numerator: big.NewInt(1), + denominator: big.NewInt(0), + percentage: nil, + fail: true, + }, + { + name: "nil numerator", + numerator: nil, + denominator: big.NewInt(1000), + percentage: nil, + fail: true, + }, + { + name: "nil denominator", + numerator: big.NewInt(165), + denominator: nil, + percentage: nil, + fail: true, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + percentage := Percentage(tc.numerator, tc.denominator) + fmt.Printf("percentage: %v\n", percentage) + if tc.fail { + require.Nil(t, percentage) + } else { + require.True(t, percentage.Cmp(tc.percentage) == 0) + } + }) + } +} diff --git a/proto/zetachain/zetacore/crosschain/query.proto b/proto/zetachain/zetacore/crosschain/query.proto index d0e4ff09e0..a30cb5a6ff 100644 --- a/proto/zetachain/zetacore/crosschain/query.proto +++ b/proto/zetachain/zetacore/crosschain/query.proto @@ -142,6 +142,12 @@ service Query { returns (QueryRateLimiterFlagsResponse) { option (google.api.http).get = "/zeta-chain/crosschain/rateLimiterFlags"; } + + // Queries the input data of rate limiter. + rpc RateLimiterInput(QueryRateLimiterInputRequest) + returns (QueryRateLimiterInputResponse) { + option (google.api.http).get = "/zeta-chain/crosschain/rateLimiterInput"; + } } message QueryZetaAccountingRequest {} @@ -270,6 +276,21 @@ message QueryListPendingCctxResponse { uint64 totalPending = 2; } +message QueryRateLimiterInputRequest { + uint32 limit = 1; + int64 window = 2; +} + +message QueryRateLimiterInputResponse { + int64 height = 1; + repeated CrossChainTx cctxs_missed = 2; + repeated CrossChainTx cctxs_pending = 3; + uint64 total_pending = 4; + string past_cctxs_value = 5; + string pending_cctxs_value = 6; + int64 lowest_pending_cctx_height = 7; +} + message QueryListPendingCctxWithinRateLimitRequest { uint32 limit = 1; } message QueryListPendingCctxWithinRateLimitResponse { diff --git a/proto/zetachain/zetacore/crosschain/rate_limiter_flags.proto b/proto/zetachain/zetacore/crosschain/rate_limiter_flags.proto index f1402cb138..b073aa46b4 100644 --- a/proto/zetachain/zetacore/crosschain/rate_limiter_flags.proto +++ b/proto/zetachain/zetacore/crosschain/rate_limiter_flags.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package zetachain.zetacore.crosschain; import "gogoproto/gogo.proto"; +import "zetachain/zetacore/pkg/coin/coin.proto"; option go_package = "github.com/zeta-chain/zetacore/x/crosschain/types"; @@ -28,3 +29,14 @@ message Conversion { (gogoproto.nullable) = false ]; } + +message AssetRate { + int64 chainId = 1; + string asset = 2; + uint32 decimals = 3; + pkg.coin.CoinType coin_type = 4; + string rate = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index 03727a1b03..9142d6f532 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -3,7 +3,9 @@ package sample import ( "encoding/base64" "encoding/json" + "fmt" "math/rand" + "strings" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -49,6 +51,39 @@ func RateLimiterFlags() types.RateLimiterFlags { } } +// 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{ + ChainId: r.Int63(), + Asset: EthAddress().Hex(), + Decimals: uint32(r.Uint64()), + CoinType: coin.CoinType_ERC20, + Rate: sdk.NewDec(r.Int63()), + } +} + +// 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) @@ -173,6 +208,33 @@ func CrossChainTx(t *testing.T, index string) *types.CrossChainTx { } } +// CustomCctxsInBlockRange create 1 cctx per block in block range [lowBlock, highBlock] (inclusive) +func CustomCctxsInBlockRange( + t *testing.T, + lowBlock uint64, + highBlock uint64, + chainID int64, + coinType coin.CoinType, + asset string, + amount uint64, + status types.CctxStatus, +) (cctxs []*types.CrossChainTx) { + // create 1 cctx per block + for i := lowBlock; i <= highBlock; i++ { + nonce := i - 1 + cctx := CrossChainTx(t, fmt.Sprintf("%d-%d", chainID, nonce)) + cctx.CctxStatus.Status = status + cctx.InboundTxParams.CoinType = coinType + cctx.InboundTxParams.Asset = asset + cctx.InboundTxParams.InboundTxObservedExternalHeight = i + cctx.GetCurrentOutTxParam().ReceiverChainId = chainID + cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(amount) + cctx.GetCurrentOutTxParam().OutboundTxTssNonce = nonce + cctxs = append(cctxs, cctx) + } + return cctxs +} + func LastBlockHeight(t *testing.T, index string) *types.LastBlockHeight { r := newRandFromStringSeed(t, index) diff --git a/typescript/zetachain/zetacore/crosschain/query_pb.d.ts b/typescript/zetachain/zetacore/crosschain/query_pb.d.ts index 3ed91cc08c..250a753ec3 100644 --- a/typescript/zetachain/zetacore/crosschain/query_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/query_pb.d.ts @@ -871,6 +871,89 @@ export declare class QueryListPendingCctxResponse extends Message | undefined, b: QueryListPendingCctxResponse | PlainMessage | undefined): boolean; } +/** + * @generated from message zetachain.zetacore.crosschain.QueryRateLimiterInputRequest + */ +export declare class QueryRateLimiterInputRequest extends Message { + /** + * @generated from field: uint32 limit = 1; + */ + limit: number; + + /** + * @generated from field: int64 window = 2; + */ + window: bigint; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.QueryRateLimiterInputRequest"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): QueryRateLimiterInputRequest; + + static fromJson(jsonValue: JsonValue, options?: Partial): QueryRateLimiterInputRequest; + + static fromJsonString(jsonString: string, options?: Partial): QueryRateLimiterInputRequest; + + static equals(a: QueryRateLimiterInputRequest | PlainMessage | undefined, b: QueryRateLimiterInputRequest | PlainMessage | undefined): boolean; +} + +/** + * @generated from message zetachain.zetacore.crosschain.QueryRateLimiterInputResponse + */ +export declare class QueryRateLimiterInputResponse extends Message { + /** + * @generated from field: int64 height = 1; + */ + height: bigint; + + /** + * @generated from field: repeated zetachain.zetacore.crosschain.CrossChainTx cctxs_missed = 2; + */ + cctxsMissed: CrossChainTx[]; + + /** + * @generated from field: repeated zetachain.zetacore.crosschain.CrossChainTx cctxs_pending = 3; + */ + cctxsPending: CrossChainTx[]; + + /** + * @generated from field: uint64 total_pending = 4; + */ + totalPending: bigint; + + /** + * @generated from field: string past_cctxs_value = 5; + */ + pastCctxsValue: string; + + /** + * @generated from field: string pending_cctxs_value = 6; + */ + pendingCctxsValue: string; + + /** + * @generated from field: int64 lowest_pending_cctx_height = 7; + */ + lowestPendingCctxHeight: bigint; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.QueryRateLimiterInputResponse"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): QueryRateLimiterInputResponse; + + static fromJson(jsonValue: JsonValue, options?: Partial): QueryRateLimiterInputResponse; + + static fromJsonString(jsonString: string, options?: Partial): QueryRateLimiterInputResponse; + + static equals(a: QueryRateLimiterInputResponse | PlainMessage | undefined, b: QueryRateLimiterInputResponse | PlainMessage | undefined): boolean; +} + /** * @generated from message zetachain.zetacore.crosschain.QueryListPendingCctxWithinRateLimitRequest */ diff --git a/typescript/zetachain/zetacore/crosschain/rate_limiter_flags_pb.d.ts b/typescript/zetachain/zetacore/crosschain/rate_limiter_flags_pb.d.ts index fb593e85b4..0f09937335 100644 --- a/typescript/zetachain/zetacore/crosschain/rate_limiter_flags_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/rate_limiter_flags_pb.d.ts @@ -5,6 +5,7 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; import { Message, proto3 } from "@bufbuild/protobuf"; +import type { CoinType } from "../pkg/coin/coin_pb.js"; /** * @generated from message zetachain.zetacore.crosschain.RateLimiterFlags @@ -80,3 +81,47 @@ export declare class Conversion extends Message { static equals(a: Conversion | PlainMessage | undefined, b: Conversion | PlainMessage | undefined): boolean; } +/** + * @generated from message zetachain.zetacore.crosschain.AssetRate + */ +export declare class AssetRate extends Message { + /** + * @generated from field: int64 chainId = 1; + */ + chainId: bigint; + + /** + * @generated from field: string asset = 2; + */ + asset: string; + + /** + * @generated from field: uint32 decimals = 3; + */ + decimals: number; + + /** + * @generated from field: zetachain.zetacore.pkg.coin.CoinType coin_type = 4; + */ + coinType: CoinType; + + /** + * @generated from field: string rate = 5; + */ + rate: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.AssetRate"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): AssetRate; + + static fromJson(jsonValue: JsonValue, options?: Partial): AssetRate; + + static fromJsonString(jsonString: string, options?: Partial): AssetRate; + + static equals(a: AssetRate | PlainMessage | undefined, b: AssetRate | PlainMessage | undefined): boolean; +} + diff --git a/x/crosschain/keeper/cctx_utils.go b/x/crosschain/keeper/cctx_utils.go index 6f9651967a..591f131c16 100644 --- a/x/crosschain/keeper/cctx_utils.go +++ b/x/crosschain/keeper/cctx_utils.go @@ -2,6 +2,7 @@ package keeper import ( "fmt" + "sort" cosmoserrors "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" @@ -105,3 +106,14 @@ func GetAbortedAmount(cctx types.CrossChainTx) sdkmath.Uint { return sdkmath.ZeroUint() } + +// SortCctxsByHeightAndChainID sorts the cctxs by height (first come first serve), the chain ID doesn't really matter +func SortCctxsByHeightAndChainID(cctxs []*types.CrossChainTx) []*types.CrossChainTx { + sort.Slice(cctxs, func(i, j int) bool { + if cctxs[i].InboundTxParams.InboundTxObservedExternalHeight == cctxs[j].InboundTxParams.InboundTxObservedExternalHeight { + return cctxs[i].GetCurrentOutTxParam().ReceiverChainId < cctxs[j].GetCurrentOutTxParam().ReceiverChainId + } + return cctxs[i].InboundTxParams.InboundTxObservedExternalHeight < cctxs[j].InboundTxParams.InboundTxObservedExternalHeight + }) + return cctxs +} diff --git a/x/crosschain/keeper/cctx_utils_test.go b/x/crosschain/keeper/cctx_utils_test.go index af6445744c..a268c20b78 100644 --- a/x/crosschain/keeper/cctx_utils_test.go +++ b/x/crosschain/keeper/cctx_utils_test.go @@ -12,6 +12,7 @@ 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" crosschainkeeper "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" @@ -374,3 +375,36 @@ func TestKeeper_UpdateNonce(t *testing.T) { require.NoError(t, err) }) } + +func TestKeeper_SortCctxsByHeightAndChainId(t *testing.T) { + // cctx1 + cctx1 := sample.CrossChainTx(t, "1-1") + cctx1.GetCurrentOutTxParam().ReceiverChainId = 1 + cctx1.InboundTxParams.InboundTxObservedExternalHeight = 10 + + // cctx2 + cctx2 := sample.CrossChainTx(t, "1-2") + cctx2.GetCurrentOutTxParam().ReceiverChainId = 1 + cctx2.InboundTxParams.InboundTxObservedExternalHeight = 13 + + // cctx3 + cctx3 := sample.CrossChainTx(t, "56-1") + cctx3.GetCurrentOutTxParam().ReceiverChainId = 56 + cctx3.InboundTxParams.InboundTxObservedExternalHeight = 13 + + // cctx4 + cctx4 := sample.CrossChainTx(t, "56-2") + cctx4.GetCurrentOutTxParam().ReceiverChainId = 56 + cctx4.InboundTxParams.InboundTxObservedExternalHeight = 16 + + // sort by height + cctxs := []*types.CrossChainTx{cctx1, cctx2, cctx3, cctx4} + keeper.SortCctxsByHeightAndChainID(cctxs) + + // check order + require.Len(t, cctxs, 4) + require.Equal(t, cctx1, cctxs[0]) + require.Equal(t, cctx2, cctxs[1]) + require.Equal(t, cctx3, cctxs[2]) + require.Equal(t, cctx4, cctxs[3]) +} diff --git a/x/crosschain/keeper/grpc_query_cctx_rate_limit.go b/x/crosschain/keeper/grpc_query_cctx_rate_limit.go index b1aa54c46a..1bb3c552a7 100644 --- a/x/crosschain/keeper/grpc_query_cctx_rate_limit.go +++ b/x/crosschain/keeper/grpc_query_cctx_rate_limit.go @@ -3,18 +3,172 @@ 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" - fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) +// RateLimiterInput collects the input data for the rate limiter +func (k Keeper) RateLimiterInput(c context.Context, req *types.QueryRateLimiterInputRequest) (res *types.QueryRateLimiterInputResponse, err error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + if req.Window <= 0 { + return nil, status.Error(codes.InvalidArgument, "window must be positive") + } + + // use default MaxPendingCctxs if not specified or too high + limit := req.Limit + if limit == 0 || limit > MaxPendingCctxs { + limit = MaxPendingCctxs + } + ctx := sdk.UnwrapSDKContext(c) + + // get current height and tss + height := ctx.BlockHeight() + if height <= 0 { + return nil, status.Error(codes.OutOfRange, "height out of range") + } + tss, found := k.zetaObserverKeeper.GetTSS(ctx) + if !found { + return nil, observertypes.ErrTssNotFound + } + + // calculate the rate limiter sliding window left boundary (inclusive) + leftWindowBoundary := height - req.Window + 1 + if leftWindowBoundary < 1 { + leftWindowBoundary = 1 + } + + // the `limit` of pending result is reached or not + maxCCTXsReached := func(cctxs []*types.CrossChainTx) bool { + // #nosec G701 len always positive + return uint32(len(cctxs)) > limit + } + + // if a cctx falls within the rate limiter window + isCctxInWindow := func(cctx *types.CrossChainTx) bool { + // #nosec G701 checked positive + return cctx.InboundTxParams.InboundTxObservedExternalHeight >= uint64(leftWindowBoundary) + } + + // it is a past cctx if its nonce < `nonceLow`, + isPastCctx := func(cctx *types.CrossChainTx, nonceLow int64) bool { + // #nosec G701 always positive + return cctx.GetCurrentOutTxParam().OutboundTxTssNonce < uint64(nonceLow) + } + + // get foreign chains and conversion rates of foreign coins + chains := k.zetaObserverKeeper.GetSupportedForeignChains(ctx) + _, 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) + pendingNoncesMap := make(map[int64]observertypes.PendingNonces) + for _, chain := range chains { + pendingNonces, found := k.GetObserverKeeper().GetPendingNonces(ctx, tss.TssPubkey, chain.ChainId) + if !found { + return nil, status.Error(codes.Internal, "pending nonces not found") + } + pendingNoncesMap[chain.ChainId] = pendingNonces + + // update lowest pending cctx height + if pendingNonces.NonceLow < pendingNonces.NonceHigh { + cctx, err := getCctxByChainIDAndNonce(k, ctx, tss.TssPubkey, chain.ChainId, pendingNonces.NonceLow) + if err != nil { + return nil, err + } + // #nosec G701 len always in range + cctxHeight := int64(cctx.InboundTxParams.InboundTxObservedExternalHeight) + if lowestPendingCctxHeight == 0 || cctxHeight < lowestPendingCctxHeight { + lowestPendingCctxHeight = cctxHeight + } + } + } + + // define a few variables to be used in the query loops + totalPending := uint64(0) + pastCctxsValue := sdk.NewInt(0) + pendingCctxsValue := sdk.NewInt(0) + cctxsMissed := make([]*types.CrossChainTx, 0) + cctxsPending := make([]*types.CrossChainTx, 0) + + // query backwards for pending cctxs of each foreign chain + for _, chain := range chains { + // we should at least query 1000 prior to find any pending cctx that we might have missed + // this logic is needed because a confirmation of higher nonce will automatically update the p.NonceLow + // therefore might mask some lower nonce cctx that is still pending. + pendingNonces := pendingNoncesMap[chain.ChainId] + startNonce := pendingNonces.NonceHigh - 1 + endNonce := pendingNonces.NonceLow - MaxLookbackNonce + if endNonce < 0 { + endNonce = 0 + } + + // go all the way back to the left window boundary or `NonceLow - 1000`, depending on which on arrives first + for nonce := startNonce; nonce >= 0; nonce-- { + cctx, err := getCctxByChainIDAndNonce(k, ctx, tss.TssPubkey, chain.ChainId, nonce) + if err != nil { + return nil, err + } + inWindow := isCctxInWindow(cctx) + isPast := isPastCctx(cctx, pendingNonces.NonceLow) + + // we should at least go backwards by 1000 nonces to pick up missed pending cctxs + // we might go even further back if the endNonce hasn't hit the left window boundary yet + if nonce < endNonce && !inWindow { + break + } + + // sum up past cctxs' value within window + if inWindow && isPast { + pastCctxsValue = pastCctxsValue.Add(types.ConvertCctxValueToAzeta(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap)) + } + + // add cctx to corresponding list + if IsPending(cctx) { + totalPending++ + if isPast { + cctxsMissed = append(cctxsMissed, cctx) + } else { + cctxsPending = append(cctxsPending, cctx) + // sum up non-past pending cctxs' value + pendingCctxsValue = pendingCctxsValue.Add(types.ConvertCctxValueToAzeta(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap)) + } + } + } + } + + // sort the missed cctxs order by height (can sort by other criteria, for unit testability) + SortCctxsByHeightAndChainID(cctxsMissed) + + // sort the pending cctxs order by height (first come first serve) + SortCctxsByHeightAndChainID(cctxsPending) + + // we take all the missed cctxs (won't be a lot) for simplicity of the query, but we only take a `limit` number of pending cctxs + if maxCCTXsReached(cctxsPending) { + cctxsPending = cctxsPending[:limit] + } + + return &types.QueryRateLimiterInputResponse{ + Height: height, + CctxsMissed: cctxsMissed, + CctxsPending: cctxsPending, + TotalPending: totalPending, + PastCctxsValue: pastCctxsValue.String(), + PendingCctxsValue: pendingCctxsValue.String(), + LowestPendingCctxHeight: lowestPendingCctxHeight, + }, nil +} + // ListPendingCctxWithinRateLimit returns a list of pending cctxs that do not exceed the outbound rate limit // a limit for the number of cctxs to return can be specified or the default is MaxPendingCctxs func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.QueryListPendingCctxWithinRateLimitRequest) (res *types.QueryListPendingCctxWithinRateLimitResponse, err error) { @@ -38,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 } @@ -78,20 +232,10 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que leftWindowBoundary = 0 } - // get the conversion rates for all foreign coins - var gasCoinRates map[int64]sdk.Dec - var erc20CoinRates map[int64]map[string]sdk.Dec - var foreignCoinMap map[int64]map[string]fungibletypes.ForeignCoins - var blockLimitInAzeta sdkmath.Int - var windowLimitInAzeta sdkmath.Int - if applyLimit { - gasCoinRates, erc20CoinRates = k.GetRateLimiterRates(ctx) - foreignCoinMap = k.fungibleKeeper.GetAllForeignCoinMap(ctx) - - // 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 { @@ -170,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, gasCoinRates, erc20CoinRates, foreignCoinMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { + if inWindow && types.RateLimitExceeded(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { limitExceeded = true continue } @@ -203,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, gasCoinRates, erc20CoinRates, foreignCoinMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { + if types.RateLimitExceeded(chain.ChainId, cctx, gasAssetRateMap, erc20AssetRateMap, &totalWithdrawInAzeta, withdrawLimitInAzeta) { limitExceeded = true continue } @@ -236,79 +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, - gasCoinRates map[int64]sdk.Dec, - erc20CoinRates map[int64]map[string]sdk.Dec, - foreignCoinMap map[int64]map[string]fungibletypes.ForeignCoins, -) sdkmath.Int { - var rate sdk.Dec - var decimals uint64 - switch cctx.InboundTxParams.CoinType { - case coin.CoinType_Zeta: - // no conversion needed for ZETA - return sdkmath.NewIntFromBigInt(cctx.GetCurrentOutTxParam().Amount.BigInt()) - case coin.CoinType_Gas: - rate = gasCoinRates[chainID] - case coin.CoinType_ERC20: - // get the ERC20 coin rate - _, found := erc20CoinRates[chainID] - if !found { - // skip if no rate found for this chainID - return sdkmath.NewInt(0) - } - rate = erc20CoinRates[chainID][strings.ToLower(cctx.InboundTxParams.Asset)] - default: - // skip CoinType_Cmd - return sdkmath.NewInt(0) - } - // should not happen, return 0 to skip if it happens - if rate.IsNil() || rate.LTE(sdk.NewDec(0)) { - return sdkmath.NewInt(0) - } - - // get foreign coin decimals - foreignCoinFromChainMap, found := foreignCoinMap[chainID] - if !found { - // skip if no coin found for this chainID - return sdkmath.NewInt(0) - } - foreignCoin, found := foreignCoinFromChainMap[strings.ToLower(cctx.InboundTxParams.Asset)] - if !found { - // skip if no coin found for this Asset - return sdkmath.NewInt(0) - } - decimals = uint64(foreignCoin.Decimals) - - // 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(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, - gasCoinRates map[int64]sdk.Dec, - erc20CoinRates map[int64]map[string]sdk.Dec, - foreignCoinMap map[int64]map[string]fungibletypes.ForeignCoins, - currentCctxValue *sdkmath.Int, - withdrawLimitInAzeta sdkmath.Int, -) bool { - cctxValueAzeta := ConvertCctxValue(chainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) - *currentCctxValue = currentCctxValue.Add(cctxValueAzeta) - 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 6418e57c03..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,8 +1,6 @@ package keeper_test import ( - "fmt" - "strings" "testing" "cosmossdk.io/math" @@ -59,37 +57,6 @@ func createTestRateLimiterFlags( } } -// createCctxsWithCoinTypeAndHeightRange -// - create 1 cctx per block from lowBlock to highBlock (inclusive) -// -// return created cctxs -func createCctxsWithCoinTypeAndHeightRange( - t *testing.T, - lowBlock uint64, - highBlock uint64, - chainID int64, - coinType coin.CoinType, - asset string, - amount uint64, - status types.CctxStatus, -) (cctxs []*types.CrossChainTx) { - // create 1 pending cctxs per block - for i := lowBlock; i <= highBlock; i++ { - nonce := i - 1 - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", chainID, nonce)) - cctx.CctxStatus.Status = status - cctx.InboundTxParams.SenderChainId = chainID - cctx.InboundTxParams.CoinType = coinType - cctx.InboundTxParams.Asset = asset - cctx.InboundTxParams.InboundTxObservedExternalHeight = i - cctx.GetCurrentOutTxParam().ReceiverChainId = chainID - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(amount) - cctx.GetCurrentOutTxParam().OutboundTxTssNonce = nonce - cctxs = append(cctxs, cctx) - } - return cctxs -} - // setCctxsInKeeper sets the given cctxs to the keeper func setCctxsInKeeper( ctx sdk.Context, @@ -101,7 +68,7 @@ func setCctxsInKeeper( for _, cctx := range cctxs { k.SetCrossChainTx(ctx, *cctx) zk.ObserverKeeper.SetNonceToCctx(ctx, observertypes.NonceToCctx{ - ChainId: cctx.InboundTxParams.SenderChainId, + ChainId: cctx.GetCurrentOutTxParam().ReceiverChainId, // #nosec G701 always in range for tests Nonce: int64(cctx.GetCurrentOutTxParam().OutboundTxTssNonce), CctxIndex: cctx.Index, @@ -124,143 +91,289 @@ func setupForeignCoins( } } -func Test_ConvertCctxValue(t *testing.T) { - // chain IDs - ethChainID := getValidEthChainID() - btcChainID := getValidBtcChainID() +func TestKeeper_RateLimiterInput(t *testing.T) { + // create sample TSS + tss := sample.Tss() - // zrc20 addresses for ETH, BTC, USDT and asset for USDT + // create sample zrc20 addresses for ETH, BTC, USDT zrc20ETH := sample.EthAddress().Hex() zrc20BTC := sample.EthAddress().Hex() zrc20USDT := sample.EthAddress().Hex() - assetUSDT := sample.EthAddress().Hex() - k, ctx, _, zk := keepertest.CrosschainKeeper(t) + // create Eth chain 999 mined and 200 pending cctxs for rate limiter test + // the number 999 is to make it less than `MaxLookbackNonce` so the LoopBackwards gets the chance to hit nonce 0 + ethMinedCctxs := sample.CustomCctxsInBlockRange(t, 1, 999, ethChainID, coin.CoinType_Gas, "", uint64(1e15), types.CctxStatus_OutboundMined) + ethPendingCctxs := sample.CustomCctxsInBlockRange(t, 1000, 1199, ethChainID, coin.CoinType_Gas, "", uint64(1e15), types.CctxStatus_PendingOutbound) - // Set TSS - tss := sample.Tss() - zk.ObserverKeeper.SetTSS(ctx, tss) + // create Btc chain 999 mined and 200 pending cctxs for rate limiter test + // the number 999 is to make it less than `MaxLookbackNonce` so the LoopBackwards gets the chance to hit nonce 0 + btcMinedCctxs := sample.CustomCctxsInBlockRange(t, 1, 999, btcChainID, coin.CoinType_Gas, "", 1000, types.CctxStatus_OutboundMined) + btcPendingCctxs := sample.CustomCctxsInBlockRange(t, 1000, 1199, btcChainID, coin.CoinType_Gas, "", 1000, types.CctxStatus_PendingOutbound) - // Set foreign coins - setupForeignCoins(t, ctx, zk, zrc20ETH, zrc20BTC, zrc20USDT, assetUSDT) + // define test cases + tests := []struct { + name string + rateLimitFlags *types.RateLimiterFlags - // Set rate limiter flags - rateLimiterFlags := createTestRateLimiterFlags(500, math.NewUint(10), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8") - k.SetRateLimiterFlags(ctx, *rateLimiterFlags) + // Eth chain cctxs setup + ethMinedCctxs []*types.CrossChainTx + ethPendingCctxs []*types.CrossChainTx + ethPendingNonces observertypes.PendingNonces - // get rate limiter rates - gasCoinRates, erc20CoinRates := k.GetRateLimiterRates(ctx) - foreignCoinMap := zk.FungibleKeeper.GetAllForeignCoinMap(ctx) + // Btc chain cctxs setup + btcMinedCctxs []*types.CrossChainTx + btcPendingCctxs []*types.CrossChainTx + btcPendingNonces observertypes.PendingNonces - t.Run("should convert cctx ZETA value correctly", func(t *testing.T) { - // create cctx with 0.3 ZETA - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) - cctx.InboundTxParams.CoinType = coin.CoinType_Zeta - cctx.InboundTxParams.Asset = "" - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(3e17) // 0.3 ZETA + // block height and limit of cctxs to retrieve + currentHeight int64 + queryLimit uint32 - // convert cctx value - value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) - require.Equal(t, sdk.NewInt(3e17), value) - }) - t.Run("should convert cctx ETH value correctly", func(t *testing.T) { - // create cctx with 0.003 ETH - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) - cctx.InboundTxParams.CoinType = coin.CoinType_Gas - cctx.InboundTxParams.Asset = "" - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(3e15) // 0.003 ETH - - // convert cctx value: 0.003 ETH * 2500 = 7.5 ZETA - value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) - require.Equal(t, sdk.NewInt(75e17), value) - }) - t.Run("should convert cctx BTC value correctly", func(t *testing.T) { - // create cctx with 0.0007 BTC - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", btcChainID, 1)) - cctx.InboundTxParams.CoinType = coin.CoinType_Gas - cctx.InboundTxParams.Asset = "" - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(70000) // 0.0007 BTC - - // convert cctx value: 0.0007 BTC * 50000 = 35.0 ZETA - value := keeper.ConvertCctxValue(btcChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) - require.Equal(t, sdk.NewInt(35).Mul(sdk.NewInt(1e18)), value) - }) - t.Run("should convert cctx USDT value correctly", func(t *testing.T) { - // create cctx with 3 USDT - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) - cctx.InboundTxParams.CoinType = coin.CoinType_ERC20 - cctx.InboundTxParams.Asset = assetUSDT - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(3e6) // 3 USDT - - // convert cctx value: 3 USDT * 0.8 = 2.4 ZETA - value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) - require.Equal(t, sdk.NewInt(24e17), value) + // expected results + expectedHeight int64 + expectedCctxsMissed []*types.CrossChainTx + expectedCctxsPending []*types.CrossChainTx + expectedTotalPending uint64 + expectedPastCctxsValue string + expectedPendingCctxsValue string + expectedLowestPendingCctxHeight int64 + }{ + { + name: "can retrieve all pending cctxs", + rateLimitFlags: createTestRateLimiterFlags(500, math.NewUint(10*1e18), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: 0, // use default MaxPendingCctxs + + // expected results + expectedHeight: 1199, + expectedCctxsMissed: keeper.SortCctxsByHeightAndChainID(append(append([]*types.CrossChainTx{}, ethPendingCctxs[0:100]...), btcPendingCctxs[0:100]...)), + expectedCctxsPending: keeper.SortCctxsByHeightAndChainID(append(append([]*types.CrossChainTx{}, ethPendingCctxs[100:200]...), btcPendingCctxs[100:200]...)), + expectedTotalPending: 400, + expectedPastCctxsValue: sdk.NewInt(1200).Mul(sdk.NewInt(1e18)).String(), // 400 * (2.5 + 0.5) ZETA + expectedPendingCctxsValue: sdk.NewInt(300).Mul(sdk.NewInt(1e18)).String(), // 100 * 1e15 ZETA + expectedLowestPendingCctxHeight: 1100, + }, + { + name: "should use left window boundary 1 if window > currentHeight", + rateLimitFlags: createTestRateLimiterFlags(1200, math.NewUint(10*1e18), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, // window 1200 > 1199 + queryLimit: keeper.MaxPendingCctxs + 1, // should use default MaxPendingCctxs + + // expected results + expectedHeight: 1199, + expectedCctxsMissed: keeper.SortCctxsByHeightAndChainID(append(append([]*types.CrossChainTx{}, ethPendingCctxs[0:100]...), btcPendingCctxs[0:100]...)), + expectedCctxsPending: keeper.SortCctxsByHeightAndChainID(append(append([]*types.CrossChainTx{}, ethPendingCctxs[100:200]...), btcPendingCctxs[100:200]...)), + expectedTotalPending: 400, + expectedPastCctxsValue: sdk.NewInt(3297).Mul(sdk.NewInt(1e18)).String(), // 1099 * (2.5 + 0.5) ZETA + expectedPendingCctxsValue: sdk.NewInt(300).Mul(sdk.NewInt(1e18)).String(), // 100 * 1e15 ZETA + expectedLowestPendingCctxHeight: 1100, + }, + { + name: "should loop from nonce 0", + rateLimitFlags: createTestRateLimiterFlags(500, math.NewUint(10*1e18), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 999, // startNonce will be set to 0 (NonceLow - 1000 < 0) + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 999, // startNonce will be set to 0 (NonceLow - 1000 < 0) + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: keeper.MaxPendingCctxs, + + // expected results + expectedHeight: 1199, + expectedCctxsMissed: []*types.CrossChainTx{}, // no missed cctxs + expectedCctxsPending: keeper.SortCctxsByHeightAndChainID(append(append([]*types.CrossChainTx{}, ethPendingCctxs...), btcPendingCctxs...)), + expectedTotalPending: 400, + expectedPastCctxsValue: sdk.NewInt(900).Mul(sdk.NewInt(1e18)).String(), // 300 * (2.5 + 0.5) ZETA + expectedPendingCctxsValue: sdk.NewInt(600).Mul(sdk.NewInt(1e18)).String(), // 200 * (2.5 + 0.5) ZETA + expectedLowestPendingCctxHeight: 1000, + }, + { + name: "set a lower gRPC request limit < len(pending_cctxs)", + rateLimitFlags: createTestRateLimiterFlags(500, math.NewUint(10*1e18), zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8"), + ethMinedCctxs: ethMinedCctxs, + ethPendingCctxs: ethPendingCctxs, + ethPendingNonces: observertypes.PendingNonces{ + ChainId: ethChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + btcMinedCctxs: btcMinedCctxs, + btcPendingCctxs: btcPendingCctxs, + btcPendingNonces: observertypes.PendingNonces{ + ChainId: btcChainID, + NonceLow: 1099, + NonceHigh: 1199, + Tss: tss.TssPubkey, + }, + currentHeight: 1199, + queryLimit: 100, // 100 < keeper.MaxPendingCctxs + + // expected results + expectedHeight: 1199, + // should include all missed and 50 pending cctxs for each chain + expectedCctxsMissed: keeper.SortCctxsByHeightAndChainID(append(append([]*types.CrossChainTx{}, ethPendingCctxs[0:100]...), btcPendingCctxs[0:100]...)), + expectedCctxsPending: keeper.SortCctxsByHeightAndChainID(append(append([]*types.CrossChainTx{}, ethPendingCctxs[100:150]...), btcPendingCctxs[100:150]...)), + expectedTotalPending: 400, + expectedPastCctxsValue: sdk.NewInt(1200).Mul(sdk.NewInt(1e18)).String(), // 400 * (2.5 + 0.5) ZETA + expectedPendingCctxsValue: sdk.NewInt(300).Mul(sdk.NewInt(1e18)).String(), // 100 * (2.5 + 0.5) ZETA + expectedLowestPendingCctxHeight: 1100, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // create test keepers + k, ctx, _, zk := keepertest.CrosschainKeeper(t) + + // Set TSS + zk.ObserverKeeper.SetTSS(ctx, tss) + + // Set foreign coins + assetUSDT := sample.EthAddress().Hex() + setupForeignCoins(t, ctx, zk, zrc20ETH, zrc20BTC, zrc20USDT, assetUSDT) + + // Set rate limiter flags + if tt.rateLimitFlags != nil { + k.SetRateLimiterFlags(ctx, *tt.rateLimitFlags) + } + + // Set Eth chain mined cctxs, pending ccxts and pending nonces + setCctxsInKeeper(ctx, *k, zk, tss, tt.ethMinedCctxs) + setCctxsInKeeper(ctx, *k, zk, tss, tt.ethPendingCctxs) + zk.ObserverKeeper.SetPendingNonces(ctx, tt.ethPendingNonces) + + // Set Btc chain mined cctxs, pending ccxts and pending nonces + setCctxsInKeeper(ctx, *k, zk, tss, tt.btcMinedCctxs) + setCctxsInKeeper(ctx, *k, zk, tss, tt.btcPendingCctxs) + zk.ObserverKeeper.SetPendingNonces(ctx, tt.btcPendingNonces) + + // Set current block height + ctx = ctx.WithBlockHeight(tt.currentHeight) + + // Query input data for the rate limiter + request := &types.QueryRateLimiterInputRequest{ + Limit: tt.queryLimit, + Window: tt.rateLimitFlags.Window, + } + res, err := k.RateLimiterInput(ctx, request) + + // check results + require.NoError(t, err) + require.Equal(t, tt.expectedHeight, res.Height) + require.Equal(t, tt.expectedCctxsMissed, res.CctxsMissed) + require.Equal(t, tt.expectedCctxsPending, res.CctxsPending) + require.Equal(t, tt.expectedTotalPending, res.TotalPending) + require.Equal(t, tt.expectedPastCctxsValue, res.PastCctxsValue) + require.Equal(t, tt.expectedPendingCctxsValue, res.PendingCctxsValue) + require.Equal(t, tt.expectedLowestPendingCctxHeight, res.LowestPendingCctxHeight) + }) + } +} + +func TestKeeper_RateLimiterInput_Errors(t *testing.T) { + t.Run("should fail for empty req", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeper(t) + _, err := k.RateLimiterInput(ctx, nil) + require.ErrorContains(t, err, "invalid request") }) - t.Run("should return 0 if no rate found for chainID", func(t *testing.T) { - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) - cctx.InboundTxParams.CoinType = coin.CoinType_ERC20 - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) - - // use nil erc20CoinRates map to convert cctx value - value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, nil, foreignCoinMap) - require.Equal(t, sdk.NewInt(0), value) + t.Run("window must be positive", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeper(t) + _, err := k.RateLimiterInput(ctx, &types.QueryRateLimiterInputRequest{ + Window: 0, // 0 window + }) + require.ErrorContains(t, err, "window must be positive") }) - t.Run("should return 0 if coinType is CoinType_Cmd", func(t *testing.T) { - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) - cctx.InboundTxParams.CoinType = coin.CoinType_Cmd - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) - - // convert cctx value - value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap) - require.Equal(t, sdk.NewInt(0), value) + t.Run("height out of range", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeper(t) + + // set current height to 0 + ctx = ctx.WithBlockHeight(0) + _, err := k.RateLimiterInput(ctx, &types.QueryRateLimiterInputRequest{ + Window: 100, + }) + require.ErrorContains(t, err, "height out of range") }) - t.Run("should return 0 on nil rate or rate <= 0", func(t *testing.T) { - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) - cctx.InboundTxParams.CoinType = coin.CoinType_Gas - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) - - // use nil gasCoinRates map to convert cctx value - value := keeper.ConvertCctxValue(ethChainID, cctx, nil, erc20CoinRates, foreignCoinMap) - require.Equal(t, sdk.NewInt(0), value) - - // set rate to 0 - zeroCoinRates, _ := k.GetRateLimiterRates(ctx) - zeroCoinRates[ethChainID] = sdk.NewDec(0) - - // convert cctx value - value = keeper.ConvertCctxValue(ethChainID, cctx, zeroCoinRates, erc20CoinRates, foreignCoinMap) - require.Equal(t, sdk.NewInt(0), value) - - // set rate to -1 - negativeCoinRates, _ := k.GetRateLimiterRates(ctx) - negativeCoinRates[ethChainID] = sdk.NewDec(-1) - - // convert cctx value - value = keeper.ConvertCctxValue(ethChainID, cctx, negativeCoinRates, erc20CoinRates, foreignCoinMap) - require.Equal(t, sdk.NewInt(0), value) + t.Run("tss not found", func(t *testing.T) { + k, ctx, _, _ := keepertest.CrosschainKeeper(t) + + // no TSS set + _, err := k.RateLimiterInput(ctx, &types.QueryRateLimiterInputRequest{ + Window: 100, + }) + require.ErrorContains(t, err, "tss not found") }) - t.Run("should return 0 if no coin found for chainID", func(t *testing.T) { - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) - cctx.InboundTxParams.CoinType = coin.CoinType_Gas - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) - - // use empty foreignCoinMap to convert cctx value - value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, nil) - require.Equal(t, sdk.NewInt(0), value) + t.Run("asset rates not found", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeper(t) + + // 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("should return 0 if no coin found for asset", func(t *testing.T) { - cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1)) - cctx.InboundTxParams.CoinType = coin.CoinType_ERC20 - cctx.InboundTxParams.Asset = assetUSDT - cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100) - - // delete assetUSDT from foreignCoinMap for ethChainID - tempCoinMap := zk.FungibleKeeper.GetAllForeignCoinMap(ctx) - delete(tempCoinMap[ethChainID], strings.ToLower(assetUSDT)) - - // convert cctx value - value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, tempCoinMap) - require.Equal(t, sdk.NewInt(0), value) + 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, + }) + require.ErrorContains(t, err, "pending nonces not found") }) } @@ -275,13 +388,13 @@ func TestKeeper_ListPendingCctxWithinRateLimit(t *testing.T) { // create Eth chain 999 mined and 200 pending cctxs for rate limiter test // the number 999 is to make it less than `MaxLookbackNonce` so the LoopBackwards gets the chance to hit nonce 0 - ethMinedCctxs := createCctxsWithCoinTypeAndHeightRange(t, 1, 999, ethChainID, coin.CoinType_Gas, "", uint64(1e15), types.CctxStatus_OutboundMined) - ethPendingCctxs := createCctxsWithCoinTypeAndHeightRange(t, 1000, 1199, ethChainID, coin.CoinType_Gas, "", uint64(1e15), types.CctxStatus_PendingOutbound) + ethMinedCctxs := sample.CustomCctxsInBlockRange(t, 1, 999, ethChainID, coin.CoinType_Gas, "", uint64(1e15), types.CctxStatus_OutboundMined) + ethPendingCctxs := sample.CustomCctxsInBlockRange(t, 1000, 1199, ethChainID, coin.CoinType_Gas, "", uint64(1e15), types.CctxStatus_PendingOutbound) // create Btc chain 999 mined and 200 pending cctxs for rate limiter test // the number 999 is to make it less than `MaxLookbackNonce` so the LoopBackwards gets the chance to hit nonce 0 - btcMinedCctxs := createCctxsWithCoinTypeAndHeightRange(t, 1, 999, btcChainID, coin.CoinType_Gas, "", 1000, types.CctxStatus_OutboundMined) - btcPendingCctxs := createCctxsWithCoinTypeAndHeightRange(t, 1000, 1199, btcChainID, coin.CoinType_Gas, "", 1000, types.CctxStatus_PendingOutbound) + btcMinedCctxs := sample.CustomCctxsInBlockRange(t, 1, 999, btcChainID, coin.CoinType_Gas, "", 1000, types.CctxStatus_OutboundMined) + btcPendingCctxs := sample.CustomCctxsInBlockRange(t, 1000, 1199, btcChainID, coin.CoinType_Gas, "", 1000, types.CctxStatus_PendingOutbound) // define test cases tests := []struct { diff --git a/x/crosschain/keeper/rate_limiter_flags.go b/x/crosschain/keeper/rate_limiter_flags.go index 324c636f93..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" ) @@ -29,33 +28,28 @@ func (k Keeper) GetRateLimiterFlags(ctx sdk.Context) (val types.RateLimiterFlags return val, true } -// GetRateLimiterRates returns two maps of foreign coins and their rates -// The 1st map: foreign chain id -> gas coin rate -// The 2nd map: foreign chain id -> erc20 asset -> erc20 coin rate -func (k Keeper) GetRateLimiterRates(ctx sdk.Context) (map[int64]sdk.Dec, map[int64]map[string]sdk.Dec) { - rateLimiterFlags, _ := k.GetRateLimiterFlags(ctx) - - // the result maps - gasCoinRates := make(map[int64]sdk.Dec) - erc20CoinRates := make(map[int64]map[string]sdk.Dec) +// GetRateLimiterAssetRateList returns a list of all foreign asset rate +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 } - chainID := fCoin.ForeignChainId - switch fCoin.CoinType { - case coin.CoinType_Gas: - gasCoinRates[chainID] = conversion.Rate - case coin.CoinType_ERC20: - if _, found := erc20CoinRates[chainID]; !found { - erc20CoinRates[chainID] = make(map[string]sdk.Dec) - } - erc20CoinRates[chainID][strings.ToLower(fCoin.Asset)] = conversion.Rate - } + // add asset rate to list + assetRates = append(assetRates, types.AssetRate{ + ChainId: fCoin.ForeignChainId, + Asset: strings.ToLower(fCoin.Asset), + Decimals: fCoin.Decimals, + CoinType: fCoin.CoinType, + Rate: conversion.Rate, + }) } - return gasCoinRates, erc20CoinRates + 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 de134ee4f7..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" @@ -11,8 +10,38 @@ import ( keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/crosschain/types" + fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" ) +// createForeignCoinAndAssetRate creates foreign coin and corresponding asset rate +func createForeignCoinAndAssetRate( + t *testing.T, + zrc20Addr string, + asset string, + chainID int64, + decimals uint32, + coinType coin.CoinType, + rate sdk.Dec, +) (fungibletypes.ForeignCoins, types.AssetRate) { + // create foreign coin + foreignCoin := sample.ForeignCoins(t, zrc20Addr) + foreignCoin.Asset = asset + foreignCoin.ForeignChainId = chainID + foreignCoin.Decimals = decimals + foreignCoin.CoinType = coinType + + // create corresponding asset rate + assetRate := sample.CustomAssetRate( + foreignCoin.ForeignChainId, + foreignCoin.Asset, + foreignCoin.Decimals, + foreignCoin.CoinType, + rate, + ) + + return foreignCoin, assetRate +} + func TestKeeper_GetRateLimiterFlags(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeper(t) @@ -28,14 +57,15 @@ func TestKeeper_GetRateLimiterFlags(t *testing.T) { require.Equal(t, flags, r) } -func TestKeeper_GetRateLimiterRates(t *testing.T) { +func TestKeeper_GetRateLimiterAssetRateList(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) // create test flags + chainID := chains.GoerliLocalnetChain.ChainId 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{ { @@ -53,39 +83,30 @@ func TestKeeper_GetRateLimiterRates(t *testing.T) { }, } - chainID := chains.GoerliLocalnetChain.ChainId + // 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, testflags) // add gas coin - fcGas := sample.ForeignCoins(t, zrc20GasAddr) - fcGas.CoinType = coin.CoinType_Gas - fcGas.ForeignChainId = chainID - zk.FungibleKeeper.SetForeignCoins(ctx, fcGas) - - // add two erc20 coins - asset1 := sample.EthAddress().Hex() - fcERC20 := sample.ForeignCoins(t, zrc20ERC20Addr1) - fcERC20.Asset = asset1 - fcERC20.ForeignChainId = chainID - zk.FungibleKeeper.SetForeignCoins(ctx, fcERC20) - - asset2 := sample.EthAddress().Hex() - fcERC20 = sample.ForeignCoins(t, zrc20ERC20Addr2) - fcERC20.Asset = asset2 - fcERC20.ForeignChainId = chainID - zk.FungibleKeeper.SetForeignCoins(ctx, fcERC20) + gasCoin, gasAssetRate := createForeignCoinAndAssetRate(t, zrc20GasAddr, "", chainID, 18, coin.CoinType_Gas, sdk.NewDec(1)) + zk.FungibleKeeper.SetForeignCoins(ctx, gasCoin) - // set flags - k.SetRateLimiterFlags(ctx, flags) - r, found := k.GetRateLimiterFlags(ctx) - require.True(t, found) - require.Equal(t, flags, r) + // add 1st erc20 coin + erc20Coin1, erc20AssetRate1 := createForeignCoinAndAssetRate(t, zrc20ERC20Addr1, sample.EthAddress().Hex(), chainID, 8, coin.CoinType_ERC20, sdk.NewDec(2)) + zk.FungibleKeeper.SetForeignCoins(ctx, erc20Coin1) + + // add 2nd erc20 coin + erc20Coin2, erc20AssetRate2 := createForeignCoinAndAssetRate(t, zrc20ERC20Addr2, sample.EthAddress().Hex(), chainID, 6, coin.CoinType_ERC20, sdk.NewDec(3)) + zk.FungibleKeeper.SetForeignCoins(ctx, erc20Coin2) // get rates - gasRates, erc20Rates := k.GetRateLimiterRates(ctx) - require.Equal(t, 1, len(gasRates)) - require.Equal(t, 1, len(erc20Rates)) - require.Equal(t, sdk.NewDec(1), gasRates[chainID]) - require.Equal(t, 2, len(erc20Rates[chainID])) - require.Equal(t, sdk.NewDec(2), erc20Rates[chainID][strings.ToLower(asset1)]) - require.Equal(t, sdk.NewDec(3), erc20Rates[chainID][strings.ToLower(asset2)]) + 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/query.pb.go b/x/crosschain/types/query.pb.go index 9feff25575..195846bf92 100644 --- a/x/crosschain/types/query.pb.go +++ b/x/crosschain/types/query.pb.go @@ -1586,6 +1586,150 @@ func (m *QueryListPendingCctxResponse) GetTotalPending() uint64 { return 0 } +type QueryRateLimiterInputRequest struct { + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Window int64 `protobuf:"varint,2,opt,name=window,proto3" json:"window,omitempty"` +} + +func (m *QueryRateLimiterInputRequest) Reset() { *m = QueryRateLimiterInputRequest{} } +func (m *QueryRateLimiterInputRequest) String() string { return proto.CompactTextString(m) } +func (*QueryRateLimiterInputRequest) ProtoMessage() {} +func (*QueryRateLimiterInputRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_d00cb546ea76908b, []int{33} +} +func (m *QueryRateLimiterInputRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryRateLimiterInputRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryRateLimiterInputRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryRateLimiterInputRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRateLimiterInputRequest.Merge(m, src) +} +func (m *QueryRateLimiterInputRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryRateLimiterInputRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRateLimiterInputRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRateLimiterInputRequest proto.InternalMessageInfo + +func (m *QueryRateLimiterInputRequest) GetLimit() uint32 { + if m != nil { + return m.Limit + } + return 0 +} + +func (m *QueryRateLimiterInputRequest) GetWindow() int64 { + if m != nil { + return m.Window + } + return 0 +} + +type QueryRateLimiterInputResponse struct { + Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + CctxsMissed []*CrossChainTx `protobuf:"bytes,2,rep,name=cctxs_missed,json=cctxsMissed,proto3" json:"cctxs_missed,omitempty"` + CctxsPending []*CrossChainTx `protobuf:"bytes,3,rep,name=cctxs_pending,json=cctxsPending,proto3" json:"cctxs_pending,omitempty"` + TotalPending uint64 `protobuf:"varint,4,opt,name=total_pending,json=totalPending,proto3" json:"total_pending,omitempty"` + PastCctxsValue string `protobuf:"bytes,5,opt,name=past_cctxs_value,json=pastCctxsValue,proto3" json:"past_cctxs_value,omitempty"` + PendingCctxsValue string `protobuf:"bytes,6,opt,name=pending_cctxs_value,json=pendingCctxsValue,proto3" json:"pending_cctxs_value,omitempty"` + LowestPendingCctxHeight int64 `protobuf:"varint,7,opt,name=lowest_pending_cctx_height,json=lowestPendingCctxHeight,proto3" json:"lowest_pending_cctx_height,omitempty"` +} + +func (m *QueryRateLimiterInputResponse) Reset() { *m = QueryRateLimiterInputResponse{} } +func (m *QueryRateLimiterInputResponse) String() string { return proto.CompactTextString(m) } +func (*QueryRateLimiterInputResponse) ProtoMessage() {} +func (*QueryRateLimiterInputResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d00cb546ea76908b, []int{34} +} +func (m *QueryRateLimiterInputResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryRateLimiterInputResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryRateLimiterInputResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryRateLimiterInputResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRateLimiterInputResponse.Merge(m, src) +} +func (m *QueryRateLimiterInputResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryRateLimiterInputResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRateLimiterInputResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRateLimiterInputResponse proto.InternalMessageInfo + +func (m *QueryRateLimiterInputResponse) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *QueryRateLimiterInputResponse) GetCctxsMissed() []*CrossChainTx { + if m != nil { + return m.CctxsMissed + } + return nil +} + +func (m *QueryRateLimiterInputResponse) GetCctxsPending() []*CrossChainTx { + if m != nil { + return m.CctxsPending + } + return nil +} + +func (m *QueryRateLimiterInputResponse) GetTotalPending() uint64 { + if m != nil { + return m.TotalPending + } + return 0 +} + +func (m *QueryRateLimiterInputResponse) GetPastCctxsValue() string { + if m != nil { + return m.PastCctxsValue + } + return "" +} + +func (m *QueryRateLimiterInputResponse) GetPendingCctxsValue() string { + if m != nil { + return m.PendingCctxsValue + } + return "" +} + +func (m *QueryRateLimiterInputResponse) GetLowestPendingCctxHeight() int64 { + if m != nil { + return m.LowestPendingCctxHeight + } + return 0 +} + type QueryListPendingCctxWithinRateLimitRequest struct { Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` } @@ -1598,7 +1742,7 @@ func (m *QueryListPendingCctxWithinRateLimitRequest) String() string { } func (*QueryListPendingCctxWithinRateLimitRequest) ProtoMessage() {} func (*QueryListPendingCctxWithinRateLimitRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{33} + return fileDescriptor_d00cb546ea76908b, []int{35} } func (m *QueryListPendingCctxWithinRateLimitRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1650,7 +1794,7 @@ func (m *QueryListPendingCctxWithinRateLimitResponse) String() string { } func (*QueryListPendingCctxWithinRateLimitResponse) ProtoMessage() {} func (*QueryListPendingCctxWithinRateLimitResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{34} + return fileDescriptor_d00cb546ea76908b, []int{36} } func (m *QueryListPendingCctxWithinRateLimitResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1721,7 +1865,7 @@ func (m *QueryLastZetaHeightRequest) Reset() { *m = QueryLastZetaHeightR func (m *QueryLastZetaHeightRequest) String() string { return proto.CompactTextString(m) } func (*QueryLastZetaHeightRequest) ProtoMessage() {} func (*QueryLastZetaHeightRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{35} + return fileDescriptor_d00cb546ea76908b, []int{37} } func (m *QueryLastZetaHeightRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1758,7 +1902,7 @@ func (m *QueryLastZetaHeightResponse) Reset() { *m = QueryLastZetaHeight func (m *QueryLastZetaHeightResponse) String() string { return proto.CompactTextString(m) } func (*QueryLastZetaHeightResponse) ProtoMessage() {} func (*QueryLastZetaHeightResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{36} + return fileDescriptor_d00cb546ea76908b, []int{38} } func (m *QueryLastZetaHeightResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1803,7 +1947,7 @@ func (m *QueryConvertGasToZetaRequest) Reset() { *m = QueryConvertGasToZ func (m *QueryConvertGasToZetaRequest) String() string { return proto.CompactTextString(m) } func (*QueryConvertGasToZetaRequest) ProtoMessage() {} func (*QueryConvertGasToZetaRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{37} + return fileDescriptor_d00cb546ea76908b, []int{39} } func (m *QueryConvertGasToZetaRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1856,7 +2000,7 @@ func (m *QueryConvertGasToZetaResponse) Reset() { *m = QueryConvertGasTo func (m *QueryConvertGasToZetaResponse) String() string { return proto.CompactTextString(m) } func (*QueryConvertGasToZetaResponse) ProtoMessage() {} func (*QueryConvertGasToZetaResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{38} + return fileDescriptor_d00cb546ea76908b, []int{40} } func (m *QueryConvertGasToZetaResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1913,7 +2057,7 @@ func (m *QueryMessagePassingProtocolFeeRequest) Reset() { *m = QueryMess func (m *QueryMessagePassingProtocolFeeRequest) String() string { return proto.CompactTextString(m) } func (*QueryMessagePassingProtocolFeeRequest) ProtoMessage() {} func (*QueryMessagePassingProtocolFeeRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{39} + return fileDescriptor_d00cb546ea76908b, []int{41} } func (m *QueryMessagePassingProtocolFeeRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1952,7 +2096,7 @@ func (m *QueryMessagePassingProtocolFeeResponse) Reset() { func (m *QueryMessagePassingProtocolFeeResponse) String() string { return proto.CompactTextString(m) } func (*QueryMessagePassingProtocolFeeResponse) ProtoMessage() {} func (*QueryMessagePassingProtocolFeeResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{40} + return fileDescriptor_d00cb546ea76908b, []int{42} } func (m *QueryMessagePassingProtocolFeeResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1995,7 +2139,7 @@ func (m *QueryRateLimiterFlagsRequest) Reset() { *m = QueryRateLimiterFl func (m *QueryRateLimiterFlagsRequest) String() string { return proto.CompactTextString(m) } func (*QueryRateLimiterFlagsRequest) ProtoMessage() {} func (*QueryRateLimiterFlagsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{41} + return fileDescriptor_d00cb546ea76908b, []int{43} } func (m *QueryRateLimiterFlagsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2032,7 +2176,7 @@ func (m *QueryRateLimiterFlagsResponse) Reset() { *m = QueryRateLimiterF func (m *QueryRateLimiterFlagsResponse) String() string { return proto.CompactTextString(m) } func (*QueryRateLimiterFlagsResponse) ProtoMessage() {} func (*QueryRateLimiterFlagsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d00cb546ea76908b, []int{42} + return fileDescriptor_d00cb546ea76908b, []int{44} } func (m *QueryRateLimiterFlagsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2102,6 +2246,8 @@ func init() { proto.RegisterType((*QueryAllCctxResponse)(nil), "zetachain.zetacore.crosschain.QueryAllCctxResponse") proto.RegisterType((*QueryListPendingCctxRequest)(nil), "zetachain.zetacore.crosschain.QueryListPendingCctxRequest") proto.RegisterType((*QueryListPendingCctxResponse)(nil), "zetachain.zetacore.crosschain.QueryListPendingCctxResponse") + proto.RegisterType((*QueryRateLimiterInputRequest)(nil), "zetachain.zetacore.crosschain.QueryRateLimiterInputRequest") + proto.RegisterType((*QueryRateLimiterInputResponse)(nil), "zetachain.zetacore.crosschain.QueryRateLimiterInputResponse") proto.RegisterType((*QueryListPendingCctxWithinRateLimitRequest)(nil), "zetachain.zetacore.crosschain.QueryListPendingCctxWithinRateLimitRequest") proto.RegisterType((*QueryListPendingCctxWithinRateLimitResponse)(nil), "zetachain.zetacore.crosschain.QueryListPendingCctxWithinRateLimitResponse") proto.RegisterType((*QueryLastZetaHeightRequest)(nil), "zetachain.zetacore.crosschain.QueryLastZetaHeightRequest") @@ -2119,130 +2265,140 @@ func init() { } var fileDescriptor_d00cb546ea76908b = []byte{ - // 1966 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x5a, 0xcf, 0x6f, 0xdc, 0xc6, - 0x15, 0xf6, 0x68, 0xad, 0x44, 0x1e, 0xf9, 0x47, 0x3c, 0x96, 0x13, 0x75, 0x63, 0xaf, 0x6d, 0xba, - 0xb6, 0x14, 0xbb, 0xda, 0x8d, 0xd6, 0xb1, 0x52, 0xdb, 0x4a, 0xd1, 0x95, 0x1c, 0x2b, 0x6a, 0x95, - 0x44, 0x59, 0xa8, 0x50, 0xe1, 0xa2, 0x20, 0x46, 0xdc, 0x09, 0x97, 0x08, 0x45, 0x2a, 0xe4, 0x6c, - 0xb4, 0xb2, 0xa0, 0x8b, 0x0f, 0x3d, 0x17, 0xc8, 0xa1, 0x97, 0x5e, 0x8b, 0xe6, 0xd0, 0x43, 0x0f, - 0x45, 0x7b, 0x28, 0x90, 0x22, 0x68, 0xeb, 0xfa, 0x18, 0xa0, 0x40, 0x51, 0xb4, 0x40, 0x51, 0xd8, - 0xfd, 0x0b, 0xfa, 0x17, 0x04, 0x1c, 0x3e, 0x72, 0x87, 0xbf, 0x76, 0x47, 0xab, 0xcd, 0x21, 0x27, - 0x91, 0x9c, 0x79, 0xef, 0x7d, 0xdf, 0x7b, 0x33, 0xc3, 0xc7, 0x4f, 0x8b, 0x5f, 0x7b, 0xc4, 0x38, - 0x35, 0xda, 0xd4, 0x72, 0x6a, 0xe2, 0xca, 0xf5, 0x58, 0xcd, 0xf0, 0x5c, 0xdf, 0x0f, 0x9f, 0x7d, - 0xdc, 0x61, 0xde, 0x5e, 0x75, 0xc7, 0x73, 0xb9, 0x4b, 0x2e, 0xc6, 0x53, 0xab, 0xd1, 0xd4, 0x6a, - 0x6f, 0x6a, 0xf9, 0x86, 0xe1, 0xfa, 0xdb, 0xae, 0x5f, 0xdb, 0xa2, 0x3e, 0x0b, 0xed, 0x6a, 0x9f, - 0xcc, 0x6f, 0x31, 0x4e, 0xe7, 0x6b, 0x3b, 0xd4, 0xb4, 0x1c, 0xca, 0x2d, 0xd7, 0x09, 0x5d, 0x95, - 0xeb, 0xfd, 0xa3, 0x8a, 0x4b, 0x5d, 0x5c, 0xeb, 0xbc, 0x0b, 0x36, 0x73, 0xfd, 0x6d, 0x4c, 0xea, - 0xeb, 0x3b, 0x9e, 0x65, 0x30, 0x98, 0xbe, 0xd0, 0x7f, 0xba, 0xf0, 0xac, 0xb7, 0xa9, 0xdf, 0xd6, - 0xb9, 0xab, 0x1b, 0x46, 0x1c, 0x66, 0x5e, 0xc5, 0x8e, 0x7b, 0xd4, 0xf8, 0x88, 0x79, 0x60, 0x72, - 0xbb, 0xbf, 0x89, 0x4d, 0x7d, 0xae, 0x6f, 0xd9, 0xae, 0xf1, 0x91, 0xde, 0x66, 0x96, 0xd9, 0xe6, - 0x6a, 0x49, 0x70, 0x3b, 0x3c, 0x1b, 0x6a, 0x00, 0x2b, 0x8f, 0x72, 0xa6, 0xdb, 0xd6, 0xb6, 0xc5, - 0x99, 0xa7, 0x7f, 0x68, 0x53, 0xd3, 0x07, 0xbb, 0x29, 0xd3, 0x35, 0x5d, 0x71, 0x59, 0x0b, 0xae, - 0xe0, 0xe9, 0x05, 0xd3, 0x75, 0x4d, 0x9b, 0xd5, 0xe8, 0x8e, 0x55, 0xa3, 0x8e, 0xe3, 0x72, 0x51, - 0x23, 0xb0, 0xd1, 0x2e, 0xe0, 0xf2, 0x07, 0x41, 0x19, 0x1f, 0x32, 0x4e, 0x1b, 0x86, 0xe1, 0x76, - 0x1c, 0x6e, 0x39, 0x66, 0x93, 0x7d, 0xdc, 0x61, 0x3e, 0xd7, 0xde, 0xc5, 0xaf, 0xe6, 0x8e, 0xfa, - 0x3b, 0xae, 0xe3, 0x33, 0x52, 0xc5, 0xe7, 0xe8, 0x96, 0xeb, 0x71, 0xd6, 0xd2, 0x03, 0xa0, 0x3a, - 0xdd, 0x0e, 0x66, 0x4c, 0xa3, 0xcb, 0x68, 0xf6, 0x44, 0xf3, 0x2c, 0x0c, 0x09, 0x5b, 0x31, 0x10, - 0xbb, 0x5b, 0x61, 0xfc, 0xfd, 0x0e, 0xdf, 0xe8, 0x6e, 0x84, 0xb4, 0x21, 0x1a, 0x99, 0xc6, 0x2f, - 0x0a, 0x86, 0xab, 0xf7, 0x85, 0x8b, 0x52, 0x33, 0xba, 0x25, 0x53, 0x78, 0xdc, 0x71, 0x1d, 0x83, - 0x4d, 0x8f, 0x5d, 0x46, 0xb3, 0xc7, 0x9b, 0xe1, 0x8d, 0xd6, 0xc1, 0x17, 0xf2, 0xdd, 0x01, 0xbc, - 0x1f, 0xe1, 0x93, 0xae, 0xf4, 0x5c, 0x38, 0x9d, 0xac, 0xdf, 0xac, 0xf6, 0x5d, 0xe2, 0x55, 0xd9, - 0xd5, 0xd2, 0xf1, 0xa7, 0xff, 0xb9, 0x74, 0xac, 0x99, 0x70, 0xa3, 0x31, 0x60, 0xd1, 0xb0, 0xed, - 0x3c, 0x16, 0x0f, 0x30, 0xee, 0x6d, 0x05, 0x88, 0x79, 0xbd, 0x1a, 0xee, 0x9b, 0x6a, 0xb0, 0x6f, - 0xaa, 0xe1, 0x7e, 0x83, 0x7d, 0x53, 0x5d, 0xa7, 0x26, 0x03, 0xdb, 0xa6, 0x64, 0xa9, 0x7d, 0x8e, - 0x80, 0x5e, 0x26, 0x4e, 0x21, 0xbd, 0xd2, 0x08, 0xe8, 0x91, 0x95, 0x04, 0xfe, 0x31, 0x81, 0x7f, - 0x66, 0x20, 0xfe, 0x10, 0x53, 0x82, 0xc0, 0x63, 0x84, 0xb5, 0x3c, 0x02, 0x4b, 0x7b, 0xcb, 0x01, - 0x92, 0x28, 0x5f, 0x53, 0x78, 0x5c, 0x20, 0x83, 0x9a, 0x87, 0x37, 0xa9, 0x2c, 0x8e, 0x0d, 0x9d, - 0xc5, 0xbf, 0x22, 0x7c, 0xb5, 0x2f, 0x88, 0x6f, 0x48, 0x32, 0x7f, 0x86, 0xf0, 0x95, 0x88, 0xc7, - 0xaa, 0x53, 0x94, 0xcb, 0x6f, 0xe1, 0x89, 0xf0, 0x40, 0xb5, 0x5a, 0xc9, 0x2d, 0xd4, 0x1a, 0x59, - 0x42, 0xbf, 0x90, 0xaa, 0x9a, 0x07, 0x04, 0xf2, 0xd9, 0xc4, 0x93, 0x96, 0x93, 0x4e, 0xe7, 0x8d, - 0x01, 0xe9, 0x94, 0xfd, 0x85, 0xd9, 0x94, 0x9d, 0x8c, 0x2e, 0x99, 0xd2, 0x0e, 0x96, 0x42, 0xfa, - 0xa3, 0xde, 0xc1, 0x7f, 0x94, 0x76, 0x70, 0x32, 0xce, 0x37, 0x21, 0x49, 0xf7, 0xf0, 0xc5, 0xe8, - 0x74, 0x0d, 0x42, 0xbe, 0x43, 0xfd, 0xf6, 0x86, 0xbb, 0x6c, 0xf0, 0x6e, 0x94, 0xa6, 0x32, 0x9e, - 0xb0, 0x60, 0x00, 0x8e, 0xfc, 0xf8, 0x5e, 0x3b, 0xc0, 0x95, 0x22, 0x63, 0xe0, 0xfe, 0x13, 0x7c, - 0xda, 0x4a, 0x8c, 0x40, 0xa2, 0xe7, 0x14, 0xe8, 0xf7, 0x8c, 0x20, 0x03, 0x29, 0x57, 0xda, 0x22, - 0x84, 0x4f, 0x4e, 0xbe, 0x4f, 0x39, 0x55, 0x01, 0xff, 0x08, 0x5f, 0x2a, 0xb4, 0x06, 0xf4, 0x9b, - 0xf8, 0xd4, 0x72, 0x80, 0x49, 0x2c, 0xfa, 0x8d, 0xae, 0xaf, 0x78, 0x5e, 0xc8, 0x36, 0x00, 0x3d, - 0xe9, 0x47, 0x33, 0x21, 0xeb, 0xb0, 0x64, 0xb2, 0x59, 0x1f, 0xd5, 0xe2, 0x7c, 0x82, 0x20, 0x47, - 0x39, 0x91, 0xfa, 0x94, 0xa8, 0x34, 0xa2, 0x12, 0x8d, 0x6e, 0x9d, 0xd6, 0xf0, 0x2b, 0xd1, 0x52, - 0x5b, 0xa1, 0xfe, 0x7a, 0xd0, 0x1d, 0x4a, 0xaf, 0x16, 0xcb, 0x69, 0xb1, 0x2e, 0x54, 0x38, 0xbc, - 0xd1, 0x74, 0x3c, 0x9d, 0x35, 0x00, 0xca, 0xcb, 0x78, 0x22, 0x7a, 0x06, 0xb9, 0x9d, 0x19, 0x40, - 0x36, 0x76, 0x11, 0x1b, 0x6a, 0x14, 0x10, 0x35, 0x6c, 0x3b, 0x8d, 0x68, 0x54, 0xd5, 0xfb, 0x0c, - 0x01, 0x89, 0x44, 0x8c, 0x5c, 0x12, 0xa5, 0xa1, 0x48, 0x8c, 0xae, 0x3e, 0x0b, 0xbd, 0xa3, 0x60, - 0x8d, 0xfa, 0x7c, 0x29, 0xe8, 0x91, 0xdf, 0x11, 0x2d, 0x72, 0xff, 0x32, 0xed, 0xc3, 0x2e, 0xcc, - 0xb3, 0x03, 0xa2, 0x3f, 0xc6, 0x67, 0x52, 0x43, 0x90, 0xd2, 0xea, 0x00, 0xbe, 0x69, 0x87, 0x69, - 0x37, 0x5a, 0xbb, 0xb7, 0x39, 0x0a, 0x40, 0x8f, 0xaa, 0x92, 0x7f, 0x41, 0xc0, 0x33, 0x2f, 0x54, - 0x3f, 0x9e, 0xa5, 0x11, 0xf0, 0x1c, 0x5d, 0x95, 0x6f, 0xe2, 0x73, 0x51, 0xb5, 0xe4, 0xd3, 0x2a, - 0xbf, 0xb4, 0x6b, 0xf0, 0xd1, 0x01, 0x93, 0x97, 0xf6, 0xde, 0x0b, 0xfa, 0xf9, 0x61, 0x3f, 0x03, - 0x4c, 0x3c, 0x95, 0x0c, 0x0d, 0x59, 0x7b, 0x1f, 0x9f, 0x94, 0xcf, 0x56, 0xc5, 0xf6, 0x5f, 0x36, - 0x69, 0x26, 0x1c, 0x68, 0x3f, 0x05, 0x8e, 0x0d, 0xdb, 0xfe, 0x3a, 0x4e, 0xe4, 0xdf, 0x22, 0x20, - 0x12, 0xfb, 0x2f, 0x24, 0x52, 0x3a, 0x12, 0x91, 0xd1, 0x55, 0xfd, 0x3d, 0x68, 0xa4, 0xd6, 0x2c, - 0x9f, 0xaf, 0x33, 0xa7, 0x65, 0x39, 0xa6, 0x9c, 0x99, 0x3e, 0xed, 0xe8, 0x14, 0x1e, 0x17, 0x9f, - 0xb0, 0x22, 0xfa, 0xa9, 0x66, 0x78, 0xa3, 0x7d, 0x1a, 0x75, 0x4c, 0x19, 0x87, 0x5f, 0x57, 0x2a, - 0x34, 0x7c, 0x92, 0xbb, 0x9c, 0xda, 0x10, 0x0c, 0x56, 0x56, 0xe2, 0x99, 0xb6, 0x84, 0x6f, 0xe4, - 0x81, 0xda, 0xb4, 0x78, 0xdb, 0x72, 0x9a, 0x94, 0xb3, 0xb5, 0x00, 0xbc, 0xb4, 0xe4, 0x43, 0x66, - 0x48, 0x66, 0xf6, 0xc5, 0x18, 0xbe, 0xa9, 0xe4, 0x04, 0x88, 0x7e, 0x80, 0x4f, 0x27, 0x05, 0x92, - 0xa1, 0xa8, 0x1a, 0x32, 0xd5, 0xab, 0xf8, 0x94, 0xa0, 0xa5, 0xef, 0x14, 0x73, 0x25, 0x0b, 0xf8, - 0x15, 0xa3, 0xe3, 0x79, 0xcc, 0xe1, 0xfa, 0xae, 0xc5, 0xdb, 0x2d, 0x8f, 0xee, 0xea, 0xbb, 0x96, - 0xd3, 0x72, 0x77, 0xa7, 0x4b, 0xa2, 0x82, 0xe7, 0x61, 0x78, 0x13, 0x46, 0x37, 0xc5, 0x20, 0xa9, - 0xe3, 0xf3, 0x19, 0x3b, 0x8f, 0x72, 0x36, 0x7d, 0x5c, 0x6c, 0xfc, 0x73, 0x29, 0xab, 0x80, 0x30, - 0xa9, 0xe2, 0x73, 0x3d, 0x2d, 0x43, 0x67, 0x5d, 0x83, 0xb1, 0x16, 0x6b, 0x4d, 0x8f, 0x5f, 0x46, - 0xb3, 0x13, 0xcd, 0xb3, 0x5e, 0x94, 0x93, 0xb7, 0x61, 0x20, 0xd6, 0x2a, 0x82, 0x43, 0xec, 0x21, - 0xe3, 0x34, 0x71, 0x20, 0x6b, 0xb7, 0xa3, 0xb5, 0x98, 0x1a, 0x85, 0x84, 0xbe, 0x8c, 0x5f, 0x90, - 0x5e, 0x11, 0xa5, 0x26, 0xdc, 0x69, 0x1b, 0xb0, 0xe2, 0x96, 0x5d, 0xe7, 0x13, 0xe6, 0x05, 0x1d, - 0xc1, 0x86, 0x1b, 0x98, 0x67, 0x4e, 0xa3, 0xcc, 0x12, 0x2e, 0xe3, 0x09, 0x93, 0xfa, 0x6b, 0xf1, - 0x2a, 0x3e, 0xd1, 0x8c, 0xef, 0xb5, 0x5f, 0x21, 0xe8, 0xe3, 0xb2, 0x6e, 0x01, 0xcf, 0x77, 0xf0, - 0x59, 0xb7, 0xc3, 0xb7, 0xdc, 0x8e, 0xd3, 0x5a, 0xa1, 0xfe, 0xaa, 0x13, 0x0c, 0x46, 0xca, 0x49, - 0x66, 0x20, 0x98, 0x2d, 0xf4, 0x1a, 0xc3, 0xb5, 0x1f, 0x30, 0x06, 0xb3, 0xc3, 0xa0, 0xd9, 0x01, - 0x32, 0x8b, 0xcf, 0x04, 0x7f, 0xe5, 0xf7, 0x45, 0x49, 0xd4, 0x3a, 0xfd, 0x58, 0x9b, 0xc1, 0xd7, - 0x04, 0xcc, 0x77, 0x99, 0xef, 0x53, 0x93, 0xad, 0x53, 0xdf, 0xb7, 0x1c, 0x73, 0xbd, 0xe7, 0x31, - 0xca, 0xee, 0x03, 0x7c, 0x7d, 0xd0, 0x44, 0x20, 0x76, 0x01, 0x9f, 0xf8, 0x30, 0x86, 0x18, 0x12, - 0xea, 0x3d, 0xd0, 0x2a, 0x90, 0xee, 0x78, 0xc5, 0x33, 0xef, 0x81, 0x4d, 0xcd, 0xe8, 0xdb, 0x4b, - 0x7b, 0x1c, 0x25, 0x2e, 0x3b, 0x01, 0xfc, 0x53, 0xfc, 0x92, 0x97, 0x1a, 0x83, 0x43, 0xb7, 0x36, - 0x60, 0x6f, 0xa4, 0x5d, 0x42, 0x67, 0x9a, 0x71, 0x57, 0xff, 0xec, 0x0a, 0x1e, 0x17, 0x20, 0xc8, - 0x13, 0x84, 0x4f, 0xca, 0x1f, 0xf9, 0xe4, 0xee, 0x80, 0x18, 0x7d, 0xf4, 0xad, 0xf2, 0xbd, 0xa1, - 0x6c, 0x43, 0xda, 0xda, 0x5b, 0x8f, 0xff, 0xfe, 0xbf, 0x4f, 0xc7, 0xde, 0x24, 0xb7, 0x85, 0x26, - 0x38, 0x27, 0x69, 0xa8, 0xb1, 0x92, 0x18, 0x1b, 0xd5, 0xf6, 0xe1, 0x8d, 0x79, 0x50, 0xdb, 0x17, - 0xef, 0xc8, 0x03, 0xf2, 0x07, 0x84, 0xcf, 0xc8, 0x7e, 0x1b, 0xb6, 0xad, 0xc6, 0x25, 0x5f, 0xe5, - 0x52, 0xe3, 0x52, 0xa0, 0x5c, 0x69, 0x37, 0x05, 0x97, 0x6b, 0xe4, 0xaa, 0x02, 0x17, 0xf2, 0x6f, - 0x84, 0x5f, 0x4e, 0x21, 0x07, 0xb1, 0x81, 0x34, 0x86, 0x00, 0x91, 0x54, 0x4c, 0xca, 0x4b, 0x47, - 0x71, 0x01, 0x74, 0xee, 0x0a, 0x3a, 0x6f, 0x90, 0xba, 0x02, 0x1d, 0xb0, 0x85, 0x0a, 0x1d, 0x90, - 0x7f, 0x21, 0x7c, 0x5e, 0xfa, 0xa2, 0x97, 0xc8, 0x7d, 0x5f, 0x11, 0x59, 0xa1, 0x1a, 0x54, 0x6e, - 0x1c, 0xc1, 0x03, 0x50, 0x5b, 0x14, 0xd4, 0x16, 0xc8, 0x1b, 0x05, 0xd4, 0x2c, 0xa7, 0x80, 0x99, - 0x6e, 0xb5, 0x0e, 0xc8, 0xef, 0x11, 0x3e, 0x9d, 0x24, 0xa7, 0xbc, 0xe6, 0x72, 0x74, 0x19, 0xe5, - 0x35, 0x97, 0xa7, 0xb5, 0x0c, 0x5c, 0x73, 0x12, 0x13, 0x9f, 0xfc, 0x0d, 0x80, 0x4b, 0xdf, 0xab, - 0x8b, 0x8a, 0x9b, 0x37, 0xf7, 0xab, 0xbd, 0xfc, 0xd6, 0x90, 0xd6, 0x00, 0xfe, 0xbb, 0x02, 0x7c, - 0x9d, 0xbc, 0xde, 0x07, 0x7c, 0xcf, 0xac, 0xb6, 0x1f, 0xdd, 0x1f, 0x90, 0x7f, 0x20, 0x4c, 0xb2, - 0x3a, 0x06, 0x51, 0xc2, 0x53, 0xa8, 0x9e, 0x94, 0xbf, 0x37, 0xac, 0x39, 0xf0, 0x69, 0x08, 0x3e, - 0xf7, 0xc8, 0x9d, 0x42, 0x3e, 0xe9, 0x7f, 0xdc, 0xe8, 0x2d, 0xca, 0xa9, 0x4c, 0xec, 0x4f, 0x08, - 0x9f, 0x4d, 0x46, 0x08, 0x96, 0xd7, 0xe2, 0x21, 0x96, 0xc8, 0x90, 0x55, 0x2a, 0xd4, 0x4b, 0xb4, - 0x39, 0xc1, 0x6a, 0x86, 0x5c, 0x53, 0xaa, 0x12, 0xf9, 0x0d, 0xea, 0x7d, 0xa7, 0x93, 0x05, 0xc5, - 0x05, 0x92, 0x12, 0x14, 0xca, 0x6f, 0x1e, 0xda, 0x0e, 0xc0, 0xd6, 0x04, 0xd8, 0xd7, 0xc8, 0x4c, - 0x01, 0x58, 0x13, 0x0c, 0x82, 0x9c, 0xb7, 0x58, 0xf7, 0x80, 0xfc, 0x1a, 0xe1, 0xc9, 0xc8, 0x4b, - 0x90, 0xea, 0x05, 0xc5, 0x64, 0x0d, 0x85, 0x38, 0x47, 0xd6, 0xd0, 0x66, 0x04, 0xe2, 0x2b, 0xe4, - 0xd2, 0x00, 0xc4, 0xe4, 0x73, 0x84, 0x5f, 0x4a, 0xf7, 0x5d, 0x44, 0xe9, 0xf0, 0x28, 0x68, 0x02, - 0xcb, 0x8b, 0xc3, 0x19, 0x2b, 0xa6, 0xda, 0x48, 0x63, 0x7d, 0x82, 0xf0, 0xa4, 0xd4, 0x5a, 0x91, - 0xfb, 0x2a, 0xe1, 0x07, 0xb5, 0x70, 0xe5, 0xb7, 0x8f, 0xe8, 0x05, 0xd8, 0xdc, 0x10, 0x6c, 0xbe, - 0x4d, 0xb4, 0x02, 0x36, 0x52, 0x3b, 0x4a, 0x9e, 0xa2, 0x8c, 0x72, 0x41, 0x54, 0x8f, 0xc2, 0x7c, - 0xdd, 0x45, 0xed, 0xe8, 0x29, 0xd6, 0x8c, 0xb4, 0x05, 0x01, 0xff, 0x75, 0x52, 0x2d, 0x80, 0x6f, - 0x27, 0xed, 0xe2, 0xe5, 0xff, 0x67, 0x84, 0x49, 0xca, 0x67, 0xb0, 0x0b, 0x54, 0x8f, 0x8c, 0xa3, - 0xb0, 0x29, 0x56, 0x86, 0xb4, 0xaa, 0x60, 0x33, 0x4b, 0xae, 0xab, 0xb1, 0x21, 0xbf, 0x44, 0xf8, - 0xb8, 0x38, 0x7c, 0xea, 0x8a, 0x69, 0x94, 0x8f, 0xc7, 0x5b, 0x87, 0xb2, 0x51, 0x7c, 0xef, 0x1a, - 0xf0, 0xc2, 0x12, 0x49, 0xfe, 0x1d, 0xc2, 0x93, 0x92, 0x22, 0x44, 0xee, 0x1c, 0x22, 0x62, 0x52, - 0x45, 0x1a, 0x0e, 0xec, 0x6d, 0x01, 0xb6, 0x46, 0xe6, 0xfa, 0x82, 0xcd, 0x34, 0xd7, 0xbf, 0x40, - 0xf8, 0xc5, 0xe8, 0x0d, 0x54, 0x57, 0xac, 0xe8, 0xa1, 0x13, 0x9b, 0x52, 0x85, 0xb4, 0xab, 0x02, - 0xeb, 0x45, 0xf2, 0x6a, 0x1f, 0xac, 0x41, 0x07, 0x76, 0x26, 0xa5, 0x38, 0xa8, 0xb5, 0x60, 0xf9, - 0x8a, 0x8e, 0x5a, 0x0b, 0x56, 0x20, 0xde, 0x0c, 0x3e, 0x39, 0x24, 0x90, 0xff, 0x47, 0xb8, 0xd2, - 0x5f, 0x2a, 0x21, 0xab, 0x43, 0x60, 0xc9, 0xd7, 0x6c, 0xca, 0x3f, 0x18, 0x85, 0x2b, 0x60, 0x79, - 0x47, 0xb0, 0xbc, 0x45, 0xe6, 0x07, 0xb3, 0x4c, 0x33, 0x0a, 0xfa, 0xe5, 0xe4, 0x4f, 0x2d, 0xd4, - 0x76, 0x40, 0xee, 0x8f, 0x37, 0xca, 0x77, 0x87, 0x31, 0x55, 0x6c, 0x65, 0x1e, 0x25, 0x51, 0x06, - 0xc0, 0x93, 0xba, 0x8b, 0x1a, 0xf0, 0x5c, 0x25, 0x47, 0x0d, 0x78, 0xbe, 0xcc, 0x33, 0x10, 0xb8, - 0x9d, 0x44, 0x19, 0xb4, 0x0a, 0x69, 0x59, 0x40, 0xad, 0x55, 0x28, 0x10, 0x30, 0xd4, 0x5a, 0x85, - 0x22, 0x71, 0x63, 0x60, 0xab, 0x90, 0x96, 0x2a, 0x96, 0x7e, 0xf8, 0xf4, 0x59, 0x05, 0x7d, 0xf9, - 0xac, 0x82, 0xfe, 0xfb, 0xac, 0x82, 0x7e, 0xfe, 0xbc, 0x72, 0xec, 0xcb, 0xe7, 0x95, 0x63, 0xff, - 0x7c, 0x5e, 0x39, 0xf6, 0x70, 0xde, 0xb4, 0x78, 0xbb, 0xb3, 0x55, 0x35, 0xdc, 0x6d, 0xd9, 0x59, - 0xfc, 0x8b, 0xa2, 0xae, 0xec, 0x97, 0xef, 0xed, 0x30, 0x7f, 0xeb, 0x05, 0xf1, 0xee, 0xbe, 0xf5, - 0x55, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x0b, 0xd7, 0xd1, 0x30, 0x26, 0x00, 0x00, + // 2127 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x5a, 0xcd, 0x6f, 0x1c, 0x49, + 0x15, 0x4f, 0x79, 0xf2, 0xe1, 0x94, 0x9d, 0xaf, 0x8a, 0x93, 0x98, 0xd9, 0x64, 0xb2, 0xdb, 0x21, + 0xb1, 0x37, 0xc1, 0x33, 0x6b, 0x67, 0xe3, 0x25, 0x89, 0x17, 0x61, 0x3b, 0x9b, 0xc4, 0xe0, 0x64, + 0xbd, 0x23, 0x43, 0x50, 0x10, 0x6a, 0x95, 0x7b, 0x6a, 0x7b, 0x5a, 0xdb, 0xee, 0x9e, 0xed, 0xae, + 0x89, 0x27, 0x89, 0x7c, 0xc9, 0x81, 0x33, 0xd2, 0x0a, 0x71, 0xe1, 0x8a, 0xe0, 0xc0, 0x81, 0x03, + 0x82, 0x03, 0xd2, 0xa2, 0x15, 0x10, 0x72, 0x5c, 0x09, 0x09, 0x21, 0x90, 0x10, 0x4a, 0xb8, 0x70, + 0xe5, 0x2f, 0x58, 0x75, 0xf5, 0xeb, 0x9e, 0xea, 0xaf, 0x99, 0x9a, 0xf1, 0xec, 0x61, 0x4f, 0x99, + 0xea, 0xaa, 0xf7, 0xea, 0xf7, 0x7b, 0xaf, 0xea, 0xf5, 0xeb, 0x5f, 0x8c, 0xdf, 0x7c, 0xc2, 0x38, + 0x35, 0x9a, 0xd4, 0x72, 0x6a, 0xe2, 0x97, 0xeb, 0xb1, 0x9a, 0xe1, 0xb9, 0xbe, 0x1f, 0x3e, 0xfb, + 0xb8, 0xcd, 0xbc, 0xc7, 0xd5, 0x96, 0xe7, 0x72, 0x97, 0x9c, 0x8b, 0x97, 0x56, 0xa3, 0xa5, 0xd5, + 0xee, 0xd2, 0xf2, 0x65, 0xc3, 0xf5, 0xb7, 0x5d, 0xbf, 0xb6, 0x45, 0x7d, 0x16, 0xda, 0xd5, 0x1e, + 0xcd, 0x6f, 0x31, 0x4e, 0xe7, 0x6b, 0x2d, 0x6a, 0x5a, 0x0e, 0xe5, 0x96, 0xeb, 0x84, 0xae, 0xca, + 0x0b, 0xbd, 0x77, 0x15, 0x3f, 0x75, 0xf1, 0x5b, 0xe7, 0x1d, 0xb0, 0x99, 0xeb, 0x6d, 0x63, 0x52, + 0x5f, 0x6f, 0x79, 0x96, 0xc1, 0x60, 0xf9, 0x62, 0xef, 0xe5, 0xc2, 0xb3, 0xde, 0xa4, 0x7e, 0x53, + 0xe7, 0xae, 0x6e, 0x18, 0xf1, 0x36, 0xf3, 0x2a, 0x76, 0xdc, 0xa3, 0xc6, 0x47, 0xcc, 0x03, 0x93, + 0x6b, 0xbd, 0x4d, 0x6c, 0xea, 0x73, 0x7d, 0xcb, 0x76, 0x8d, 0x8f, 0xf4, 0x26, 0xb3, 0xcc, 0x26, + 0x57, 0x0b, 0x82, 0xdb, 0xe6, 0xd9, 0xad, 0xfa, 0xb0, 0xf2, 0x28, 0x67, 0xba, 0x6d, 0x6d, 0x5b, + 0x9c, 0x79, 0xfa, 0x87, 0x36, 0x35, 0x7d, 0xb0, 0x9b, 0x32, 0x5d, 0xd3, 0x15, 0x3f, 0x6b, 0xc1, + 0x2f, 0x78, 0x7a, 0xd6, 0x74, 0x5d, 0xd3, 0x66, 0x35, 0xda, 0xb2, 0x6a, 0xd4, 0x71, 0x5c, 0x2e, + 0x72, 0x04, 0x36, 0xda, 0x59, 0x5c, 0xfe, 0x20, 0x48, 0xe3, 0x43, 0xc6, 0xe9, 0xb2, 0x61, 0xb8, + 0x6d, 0x87, 0x5b, 0x8e, 0x59, 0x67, 0x1f, 0xb7, 0x99, 0xcf, 0xb5, 0x7b, 0xf8, 0xb5, 0xdc, 0x59, + 0xbf, 0xe5, 0x3a, 0x3e, 0x23, 0x55, 0x7c, 0x92, 0x6e, 0xb9, 0x1e, 0x67, 0x0d, 0x3d, 0x00, 0xaa, + 0xd3, 0xed, 0x60, 0xc5, 0x34, 0x7a, 0x1d, 0xcd, 0x1e, 0xae, 0x9f, 0x80, 0x29, 0x61, 0x2b, 0x26, + 0x62, 0x77, 0x77, 0x18, 0x7f, 0xbf, 0xcd, 0x37, 0x3b, 0x9b, 0x21, 0x6d, 0xd8, 0x8d, 0x4c, 0xe3, + 0x43, 0x82, 0xe1, 0xda, 0x2d, 0xe1, 0xa2, 0x54, 0x8f, 0x86, 0x64, 0x0a, 0x1f, 0x70, 0x5c, 0xc7, + 0x60, 0xd3, 0x63, 0xaf, 0xa3, 0xd9, 0xfd, 0xf5, 0x70, 0xa0, 0xb5, 0xf1, 0xd9, 0x7c, 0x77, 0x00, + 0xef, 0x7b, 0x78, 0xd2, 0x95, 0x9e, 0x0b, 0xa7, 0x13, 0x0b, 0x57, 0xaa, 0x3d, 0x8f, 0x78, 0x55, + 0x76, 0xb5, 0xb2, 0xff, 0xc5, 0xbf, 0xcf, 0xef, 0xab, 0x27, 0xdc, 0x68, 0x0c, 0x58, 0x2c, 0xdb, + 0x76, 0x1e, 0x8b, 0xdb, 0x18, 0x77, 0xaf, 0x02, 0xec, 0x79, 0xa9, 0x1a, 0xde, 0x9b, 0x6a, 0x70, + 0x6f, 0xaa, 0xe1, 0x7d, 0x83, 0x7b, 0x53, 0xdd, 0xa0, 0x26, 0x03, 0xdb, 0xba, 0x64, 0xa9, 0x7d, + 0x8a, 0x80, 0x5e, 0x66, 0x9f, 0x42, 0x7a, 0xa5, 0x11, 0xd0, 0x23, 0x77, 0x12, 0xf8, 0xc7, 0x04, + 0xfe, 0x99, 0xbe, 0xf8, 0x43, 0x4c, 0x09, 0x02, 0xcf, 0x10, 0xd6, 0xf2, 0x08, 0xac, 0x3c, 0x5e, + 0x0d, 0x90, 0x44, 0xf1, 0x9a, 0xc2, 0x07, 0x04, 0x32, 0xc8, 0x79, 0x38, 0x48, 0x45, 0x71, 0x6c, + 0xe8, 0x28, 0xfe, 0x05, 0xe1, 0x0b, 0x3d, 0x41, 0x7c, 0x45, 0x82, 0xf9, 0x63, 0x84, 0xdf, 0x88, + 0x78, 0xac, 0x39, 0x45, 0xb1, 0xfc, 0x1a, 0x1e, 0x0f, 0x0b, 0xaa, 0xd5, 0x48, 0x5e, 0xa1, 0xc6, + 0xc8, 0x02, 0xfa, 0x99, 0x94, 0xd5, 0x3c, 0x20, 0x10, 0xcf, 0x3a, 0x9e, 0xb0, 0x9c, 0x74, 0x38, + 0x2f, 0xf7, 0x09, 0xa7, 0xec, 0x2f, 0x8c, 0xa6, 0xec, 0x64, 0x74, 0xc1, 0x94, 0x6e, 0xb0, 0xb4, + 0xa5, 0x3f, 0xea, 0x1b, 0xfc, 0x07, 0xe9, 0x06, 0x27, 0xf7, 0xf9, 0x2a, 0x04, 0xe9, 0x26, 0x3e, + 0x17, 0x55, 0xd7, 0x60, 0xcb, 0xbb, 0xd4, 0x6f, 0x6e, 0xba, 0xab, 0x06, 0xef, 0x44, 0x61, 0x2a, + 0xe3, 0x71, 0x0b, 0x26, 0xa0, 0xe4, 0xc7, 0x63, 0x6d, 0x17, 0x57, 0x8a, 0x8c, 0x81, 0xfb, 0x0f, + 0xf1, 0x51, 0x2b, 0x31, 0x03, 0x81, 0x9e, 0x53, 0xa0, 0xdf, 0x35, 0x82, 0x08, 0xa4, 0x5c, 0x69, + 0x4b, 0xb0, 0x7d, 0x72, 0xf1, 0x2d, 0xca, 0xa9, 0x0a, 0xf8, 0x27, 0xf8, 0x7c, 0xa1, 0x35, 0xa0, + 0x7f, 0x80, 0x8f, 0xac, 0x06, 0x98, 0xc4, 0xa1, 0xdf, 0xec, 0xf8, 0x8a, 0xf5, 0x42, 0xb6, 0x01, + 0xe8, 0x49, 0x3f, 0x9a, 0x09, 0x51, 0x87, 0x23, 0x93, 0x8d, 0xfa, 0xa8, 0x0e, 0xe7, 0x73, 0x04, + 0x31, 0xca, 0xd9, 0xa9, 0x47, 0x8a, 0x4a, 0x23, 0x4a, 0xd1, 0xe8, 0xce, 0x69, 0x0d, 0x9f, 0x89, + 0x8e, 0xda, 0x1d, 0xea, 0x6f, 0x04, 0xdd, 0xa1, 0xf4, 0x6a, 0xb1, 0x9c, 0x06, 0xeb, 0x40, 0x86, + 0xc3, 0x81, 0xa6, 0xe3, 0xe9, 0xac, 0x01, 0x50, 0x5e, 0xc5, 0xe3, 0xd1, 0x33, 0x88, 0xed, 0x4c, + 0x1f, 0xb2, 0xb1, 0x8b, 0xd8, 0x50, 0xa3, 0x80, 0x68, 0xd9, 0xb6, 0xd3, 0x88, 0x46, 0x95, 0xbd, + 0x5f, 0x21, 0x20, 0x91, 0xd8, 0x23, 0x97, 0x44, 0x69, 0x28, 0x12, 0xa3, 0xcb, 0xcf, 0x62, 0xb7, + 0x14, 0xac, 0x53, 0x9f, 0xaf, 0x04, 0x3d, 0xf2, 0x5d, 0xd1, 0x22, 0xf7, 0x4e, 0xd3, 0x53, 0xb8, + 0x85, 0x79, 0x76, 0x40, 0xf4, 0x07, 0xf8, 0x58, 0x6a, 0x0a, 0x42, 0x5a, 0xed, 0xc3, 0x37, 0xed, + 0x30, 0xed, 0x46, 0x6b, 0x76, 0x2f, 0x47, 0x01, 0xe8, 0x51, 0x65, 0xf2, 0xcf, 0x08, 0x78, 0xe6, + 0x6d, 0xd5, 0x8b, 0x67, 0x69, 0x04, 0x3c, 0x47, 0x97, 0xe5, 0x2b, 0xf8, 0x64, 0x94, 0x2d, 0xb9, + 0x5a, 0xe5, 0xa7, 0x76, 0x1d, 0x3e, 0x3a, 0x60, 0xf1, 0xca, 0xe3, 0xfb, 0x41, 0x3f, 0x3f, 0xec, + 0x67, 0x80, 0x89, 0xa7, 0x92, 0x5b, 0x43, 0xd4, 0xde, 0xc7, 0x93, 0x72, 0x6d, 0x55, 0x6c, 0xff, + 0x65, 0x93, 0x7a, 0xc2, 0x81, 0xf6, 0x23, 0xe0, 0xb8, 0x6c, 0xdb, 0x5f, 0x46, 0x45, 0xfe, 0x0d, + 0x02, 0x22, 0xb1, 0xff, 0x42, 0x22, 0xa5, 0x3d, 0x11, 0x19, 0x5d, 0xd6, 0xef, 0x43, 0x23, 0xb5, + 0x6e, 0xf9, 0x7c, 0x83, 0x39, 0x0d, 0xcb, 0x31, 0xe5, 0xc8, 0xf4, 0x68, 0x47, 0xa7, 0xf0, 0x01, + 0xf1, 0x09, 0x2b, 0x76, 0x3f, 0x52, 0x0f, 0x07, 0xda, 0x27, 0x51, 0xc7, 0x94, 0x71, 0xf8, 0x65, + 0x85, 0x42, 0xc3, 0x93, 0xdc, 0xe5, 0xd4, 0x86, 0xcd, 0xe0, 0x64, 0x25, 0x9e, 0x69, 0xeb, 0x00, + 0xaa, 0x4e, 0x39, 0x5b, 0x0f, 0xbf, 0xbb, 0xd7, 0x9c, 0x56, 0x5b, 0xae, 0x5f, 0x21, 0x17, 0x24, + 0x71, 0x21, 0xa7, 0xf1, 0xc1, 0x1d, 0xcb, 0x69, 0xb8, 0x3b, 0xc2, 0x67, 0xa9, 0x0e, 0x23, 0xed, + 0xa7, 0x25, 0x78, 0xc5, 0x67, 0xdd, 0x01, 0xc9, 0xd3, 0xf8, 0x60, 0xb3, 0x5b, 0xcd, 0x4a, 0x75, + 0x18, 0x91, 0xfb, 0x78, 0xd2, 0x30, 0x78, 0xc7, 0xd7, 0xb7, 0x2d, 0xdf, 0x67, 0x8d, 0xe9, 0xb1, + 0xc1, 0xc9, 0x4f, 0x08, 0x07, 0xf7, 0x84, 0x3d, 0xd9, 0xc0, 0x47, 0x42, 0x7f, 0x2d, 0x20, 0x5f, + 0x1a, 0x22, 0x9a, 0xc2, 0x03, 0x44, 0x8a, 0x5c, 0xc0, 0x47, 0x44, 0xe4, 0x62, 0x8f, 0xfb, 0xb3, + 0xe1, 0x24, 0xb3, 0xf8, 0x78, 0x8b, 0xfa, 0x5c, 0x0f, 0xf7, 0x7e, 0x44, 0xed, 0x36, 0x9b, 0x3e, + 0x20, 0xca, 0xc3, 0xd1, 0xe0, 0x79, 0x90, 0x6f, 0xff, 0xfb, 0xc1, 0x53, 0x52, 0xc5, 0x27, 0xc1, + 0x51, 0x62, 0xf1, 0xc1, 0x50, 0x5f, 0x68, 0x75, 0xcf, 0x07, 0xac, 0xbf, 0x89, 0xcb, 0xb6, 0xbb, + 0xc3, 0x7c, 0xae, 0xcb, 0x66, 0x20, 0xc8, 0x4c, 0x1f, 0x12, 0xc1, 0x3c, 0x13, 0xae, 0x90, 0x0e, + 0x17, 0x94, 0xfc, 0x15, 0x7c, 0x39, 0xef, 0xe8, 0x3d, 0xb0, 0x78, 0xd3, 0x72, 0xe2, 0x5c, 0xf5, + 0xcc, 0xb9, 0xf6, 0xd9, 0x18, 0xbe, 0xa2, 0xe4, 0x04, 0x32, 0xfd, 0x01, 0x3e, 0x9a, 0x94, 0xc1, + 0x86, 0x3a, 0xd0, 0x86, 0x7c, 0xa0, 0x33, 0x29, 0xc8, 0x39, 0xd1, 0x64, 0x11, 0x9f, 0x31, 0xda, + 0x9e, 0xc7, 0x1c, 0xae, 0xef, 0x58, 0xbc, 0xd9, 0xf0, 0xe8, 0x8e, 0x0e, 0x87, 0xb5, 0x24, 0xa2, + 0x74, 0x0a, 0xa6, 0x1f, 0xc0, 0xec, 0x03, 0x31, 0x49, 0x16, 0xf0, 0xa9, 0x8c, 0x9d, 0x47, 0x39, + 0x13, 0x79, 0x3e, 0x5c, 0x3f, 0x99, 0xb2, 0x0a, 0x08, 0x07, 0x49, 0xec, 0x2a, 0x56, 0x3a, 0xeb, + 0x18, 0x8c, 0x35, 0x58, 0x43, 0x64, 0x7c, 0xbc, 0x7e, 0xc2, 0x8b, 0x62, 0xf2, 0x1e, 0x4c, 0xc4, + 0x8a, 0x54, 0xf0, 0xaa, 0x7a, 0xc8, 0x38, 0x4d, 0xbc, 0x76, 0xb5, 0x6b, 0x51, 0xc5, 0x49, 0xcd, + 0x76, 0xaf, 0xce, 0xdd, 0xc4, 0xd5, 0x81, 0xe4, 0x6e, 0xc2, 0x15, 0x5e, 0x75, 0x9d, 0x47, 0xcc, + 0x0b, 0xfa, 0xbe, 0x4d, 0x37, 0x30, 0xcf, 0xbc, 0x73, 0x32, 0x85, 0xaa, 0x8c, 0xc7, 0x4d, 0xea, + 0xaf, 0xc7, 0xb5, 0xea, 0x70, 0x3d, 0x1e, 0x6b, 0xbf, 0x40, 0x70, 0x95, 0xb3, 0x6e, 0x01, 0xcf, + 0x37, 0xf0, 0x09, 0xb7, 0xcd, 0xb7, 0xdc, 0xb6, 0xd3, 0xb8, 0x43, 0xfd, 0x35, 0x27, 0x98, 0x8c, + 0xf4, 0xb1, 0xcc, 0x44, 0xb0, 0x5a, 0xa8, 0x72, 0x86, 0x6b, 0xdf, 0x66, 0x0c, 0x56, 0x8f, 0xc1, + 0x69, 0x4f, 0x4f, 0x90, 0x59, 0x7c, 0x2c, 0xf8, 0x57, 0xee, 0x0a, 0x4a, 0x22, 0xd7, 0xe9, 0xc7, + 0xda, 0x0c, 0xbe, 0x28, 0x60, 0xde, 0x63, 0xbe, 0x4f, 0x4d, 0xb6, 0x41, 0x7d, 0xdf, 0x72, 0xcc, + 0x8d, 0xae, 0xc7, 0x28, 0xba, 0xb7, 0xf1, 0xa5, 0x7e, 0x0b, 0x81, 0xd8, 0x59, 0x7c, 0xf8, 0xc3, + 0x18, 0x62, 0x48, 0xa8, 0xfb, 0x40, 0xab, 0x64, 0x2b, 0xe6, 0x6d, 0x9b, 0x9a, 0xd1, 0x17, 0xb6, + 0xf6, 0x0c, 0x65, 0x6b, 0x20, 0x2c, 0x00, 0xff, 0x14, 0x1f, 0xf7, 0x52, 0x73, 0xf0, 0x6a, 0xad, + 0xf5, 0xb9, 0x1b, 0x69, 0x97, 0xf0, 0xfd, 0x91, 0x71, 0xb7, 0xf0, 0x3f, 0x0d, 0x1f, 0x10, 0x20, + 0xc8, 0x73, 0x84, 0x27, 0x65, 0x29, 0x87, 0xdc, 0xe8, 0xb3, 0x47, 0x0f, 0x15, 0xb3, 0x7c, 0x73, + 0x28, 0xdb, 0x90, 0xb6, 0xf6, 0xee, 0xb3, 0xbf, 0xfd, 0xf7, 0x93, 0xb1, 0x77, 0xc8, 0x35, 0xa1, + 0xfc, 0xce, 0x49, 0x4a, 0x79, 0xac, 0x17, 0xc7, 0x46, 0xb5, 0xa7, 0xd0, 0x17, 0xed, 0xd6, 0x9e, + 0x8a, 0x4e, 0x68, 0x97, 0xfc, 0x1e, 0xe1, 0x63, 0xb2, 0xdf, 0x65, 0xdb, 0x56, 0xe3, 0x92, 0xaf, + 0x65, 0xaa, 0x71, 0x29, 0xd0, 0x27, 0xb5, 0x2b, 0x82, 0xcb, 0x45, 0x72, 0x41, 0x81, 0x0b, 0xf9, + 0x17, 0xc2, 0xa7, 0x53, 0xc8, 0x41, 0x52, 0x22, 0xcb, 0x43, 0x80, 0x48, 0xea, 0x62, 0xe5, 0x95, + 0xbd, 0xb8, 0x00, 0x3a, 0x37, 0x04, 0x9d, 0xb7, 0xc9, 0x82, 0x02, 0x1d, 0xb0, 0x85, 0x0c, 0xed, + 0x92, 0x7f, 0x22, 0x7c, 0x4a, 0xd2, 0x6d, 0x24, 0x72, 0xdf, 0x56, 0x44, 0x56, 0xa8, 0xf9, 0x95, + 0x97, 0xf7, 0xe0, 0x01, 0xa8, 0x2d, 0x09, 0x6a, 0x8b, 0xe4, 0xed, 0x02, 0x6a, 0x96, 0x53, 0xc0, + 0x4c, 0xb7, 0x1a, 0xbb, 0xe4, 0x77, 0x08, 0x1f, 0x4d, 0x92, 0x53, 0x3e, 0x73, 0x39, 0xea, 0x9b, + 0xf2, 0x99, 0xcb, 0x53, 0xd4, 0xfa, 0x9e, 0x39, 0x89, 0x89, 0x4f, 0xfe, 0x0a, 0xc0, 0x25, 0x55, + 0x62, 0x49, 0xf1, 0xf2, 0xe6, 0x6a, 0x33, 0xe5, 0x77, 0x87, 0xb4, 0x06, 0xf0, 0xdf, 0x14, 0xe0, + 0x17, 0xc8, 0x5b, 0x3d, 0xc0, 0x77, 0xcd, 0x6a, 0x4f, 0xa3, 0xf1, 0x2e, 0xf9, 0x3b, 0xc2, 0x24, + 0xab, 0x56, 0x11, 0x25, 0x3c, 0x85, 0x1a, 0x59, 0xf9, 0x5b, 0xc3, 0x9a, 0x03, 0x9f, 0x65, 0xc1, + 0xe7, 0x26, 0xb9, 0x5e, 0xc8, 0x27, 0xfd, 0xdf, 0x73, 0x7a, 0x83, 0x72, 0x2a, 0x13, 0xfb, 0x23, + 0xc2, 0x27, 0x92, 0x3b, 0x04, 0xc7, 0x6b, 0x69, 0x80, 0x23, 0x32, 0x64, 0x96, 0x0a, 0x55, 0x31, + 0x6d, 0x4e, 0xb0, 0x9a, 0x21, 0x17, 0x95, 0xb2, 0x44, 0x7e, 0x8d, 0xba, 0x6a, 0x0c, 0x59, 0x54, + 0x3c, 0x20, 0x29, 0xd9, 0xa8, 0xfc, 0xce, 0xc0, 0x76, 0x00, 0xb6, 0x26, 0xc0, 0xbe, 0x49, 0x66, + 0x0a, 0xc0, 0x9a, 0x60, 0x10, 0xc4, 0xbc, 0xc1, 0x3a, 0xbb, 0xe4, 0x97, 0x08, 0x4f, 0x44, 0x5e, + 0x82, 0x50, 0x2f, 0x2a, 0x06, 0x6b, 0x28, 0xc4, 0x39, 0xe2, 0x95, 0x36, 0x23, 0x10, 0xbf, 0x41, + 0xce, 0xf7, 0x41, 0x4c, 0x3e, 0x45, 0xf8, 0x78, 0xba, 0xef, 0x22, 0x4a, 0xc5, 0xa3, 0xa0, 0x09, + 0x2c, 0x2f, 0x0d, 0x67, 0xac, 0x18, 0x6a, 0x23, 0x8d, 0xf5, 0x39, 0xc2, 0x13, 0x52, 0x6b, 0x45, + 0x6e, 0xa9, 0x6c, 0xdf, 0xaf, 0x85, 0x2b, 0xbf, 0xb7, 0x47, 0x2f, 0xc0, 0xe6, 0xb2, 0x60, 0xf3, + 0x75, 0xa2, 0x15, 0xb0, 0x91, 0xda, 0x51, 0xf2, 0x02, 0x65, 0xf4, 0x29, 0xa2, 0x5a, 0x0a, 0xf3, + 0xd5, 0x35, 0xb5, 0xd2, 0x53, 0xac, 0x0c, 0x6a, 0x8b, 0x02, 0xfe, 0x5b, 0xa4, 0x5a, 0x00, 0xdf, + 0x4e, 0xda, 0xc5, 0xc7, 0xff, 0x4f, 0x08, 0x93, 0x94, 0xcf, 0xe0, 0x16, 0xa8, 0x96, 0x8c, 0xbd, + 0xb0, 0x29, 0xd6, 0xff, 0xb4, 0xaa, 0x60, 0x33, 0x4b, 0x2e, 0xa9, 0xb1, 0x21, 0x3f, 0x47, 0x78, + 0xbf, 0x28, 0x3e, 0x0b, 0x8a, 0x61, 0x94, 0xcb, 0xe3, 0xd5, 0x81, 0x6c, 0x14, 0xdf, 0xbb, 0x06, + 0xbc, 0xb0, 0x44, 0x90, 0x7f, 0x8b, 0xf0, 0x84, 0xa4, 0xfb, 0x91, 0xeb, 0x03, 0xec, 0x98, 0xd4, + 0x0a, 0x87, 0x03, 0x7b, 0x4d, 0x80, 0xad, 0x91, 0xb9, 0x9e, 0x60, 0x33, 0xcd, 0xf5, 0xcf, 0x10, + 0x3e, 0x14, 0xbd, 0x81, 0x16, 0x14, 0x33, 0x3a, 0x70, 0x60, 0x53, 0xda, 0x9f, 0x76, 0x41, 0x60, + 0x3d, 0x47, 0x5e, 0xeb, 0x81, 0x35, 0xe8, 0xc0, 0x8e, 0xa5, 0x14, 0x07, 0xb5, 0x16, 0x2c, 0x5f, + 0xb7, 0x53, 0x6b, 0xc1, 0x0a, 0x24, 0xba, 0xfe, 0x95, 0x43, 0x02, 0xf9, 0x7f, 0x84, 0x2b, 0xbd, + 0xa5, 0x12, 0xb2, 0x36, 0x04, 0x96, 0x7c, 0xcd, 0xa6, 0xfc, 0x9d, 0x51, 0xb8, 0x02, 0x96, 0xd7, + 0x05, 0xcb, 0xab, 0x64, 0xbe, 0x3f, 0xcb, 0x34, 0xa3, 0xa0, 0x5f, 0x4e, 0xfe, 0x41, 0x8d, 0xda, + 0x0d, 0xc8, 0xfd, 0x13, 0x9d, 0xf2, 0x8d, 0x61, 0x4c, 0x15, 0x5b, 0x99, 0x27, 0x49, 0x94, 0x01, + 0xf0, 0xa4, 0xee, 0xa2, 0x06, 0x3c, 0x57, 0xc9, 0x51, 0x03, 0x9e, 0x2f, 0xf3, 0xf4, 0x05, 0x6e, + 0x27, 0x51, 0x06, 0xad, 0x42, 0x5a, 0x16, 0x50, 0x6b, 0x15, 0x0a, 0x04, 0x0c, 0xb5, 0x56, 0xa1, + 0x48, 0xdc, 0xe8, 0xdb, 0x2a, 0xa4, 0xa5, 0x8a, 0x34, 0x01, 0x21, 0x17, 0x0f, 0x4c, 0x40, 0xd6, + 0xac, 0x07, 0x26, 0x90, 0x50, 0xa8, 0x07, 0x21, 0x20, 0x0c, 0x57, 0xbe, 0xfb, 0xe2, 0x65, 0x05, + 0x7d, 0xfe, 0xb2, 0x82, 0xfe, 0xf3, 0xb2, 0x82, 0x7e, 0xf2, 0xaa, 0xb2, 0xef, 0xf3, 0x57, 0x95, + 0x7d, 0xff, 0x78, 0x55, 0xd9, 0xf7, 0x70, 0xde, 0xb4, 0x78, 0xb3, 0xbd, 0x55, 0x35, 0xdc, 0x6d, + 0xd9, 0x59, 0xfc, 0x87, 0x6f, 0x1d, 0xd9, 0x2f, 0x7f, 0xdc, 0x62, 0xfe, 0xd6, 0x41, 0xd1, 0x7c, + 0x5c, 0xfd, 0x22, 0x00, 0x00, 0xff, 0xff, 0xad, 0xd1, 0x6d, 0x51, 0xd7, 0x28, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -2295,6 +2451,8 @@ type QueryClient interface { LastZetaHeight(ctx context.Context, in *QueryLastZetaHeightRequest, opts ...grpc.CallOption) (*QueryLastZetaHeightResponse, error) // Queries the rate limiter flags RateLimiterFlags(ctx context.Context, in *QueryRateLimiterFlagsRequest, opts ...grpc.CallOption) (*QueryRateLimiterFlagsResponse, error) + // Queries the input data of rate limiter. + RateLimiterInput(ctx context.Context, in *QueryRateLimiterInputRequest, opts ...grpc.CallOption) (*QueryRateLimiterInputResponse, error) } type queryClient struct { @@ -2503,6 +2661,15 @@ func (c *queryClient) RateLimiterFlags(ctx context.Context, in *QueryRateLimiter return out, nil } +func (c *queryClient) RateLimiterInput(ctx context.Context, in *QueryRateLimiterInputRequest, opts ...grpc.CallOption) (*QueryRateLimiterInputResponse, error) { + out := new(QueryRateLimiterInputResponse) + err := c.cc.Invoke(ctx, "/zetachain.zetacore.crosschain.Query/RateLimiterInput", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Queries a OutTxTracker by index. @@ -2543,6 +2710,8 @@ type QueryServer interface { LastZetaHeight(context.Context, *QueryLastZetaHeightRequest) (*QueryLastZetaHeightResponse, error) // Queries the rate limiter flags RateLimiterFlags(context.Context, *QueryRateLimiterFlagsRequest) (*QueryRateLimiterFlagsResponse, error) + // Queries the input data of rate limiter. + RateLimiterInput(context.Context, *QueryRateLimiterInputRequest) (*QueryRateLimiterInputResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -2615,6 +2784,9 @@ func (*UnimplementedQueryServer) LastZetaHeight(ctx context.Context, req *QueryL func (*UnimplementedQueryServer) RateLimiterFlags(ctx context.Context, req *QueryRateLimiterFlagsRequest) (*QueryRateLimiterFlagsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RateLimiterFlags not implemented") } +func (*UnimplementedQueryServer) RateLimiterInput(ctx context.Context, req *QueryRateLimiterInputRequest) (*QueryRateLimiterInputResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RateLimiterInput not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -3016,6 +3188,24 @@ func _Query_RateLimiterFlags_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _Query_RateLimiterInput_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryRateLimiterInputRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).RateLimiterInput(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/zetachain.zetacore.crosschain.Query/RateLimiterInput", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).RateLimiterInput(ctx, req.(*QueryRateLimiterInputRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "zetachain.zetacore.crosschain.Query", HandlerType: (*QueryServer)(nil), @@ -3108,6 +3298,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "RateLimiterFlags", Handler: _Query_RateLimiterFlags_Handler, }, + { + MethodName: "RateLimiterInput", + Handler: _Query_RateLimiterInput_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "zetachain/zetacore/crosschain/query.proto", @@ -4347,6 +4541,119 @@ func (m *QueryListPendingCctxResponse) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } +func (m *QueryRateLimiterInputRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryRateLimiterInputRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryRateLimiterInputRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Window != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Window)) + i-- + dAtA[i] = 0x10 + } + if m.Limit != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Limit)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryRateLimiterInputResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryRateLimiterInputResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryRateLimiterInputResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.LowestPendingCctxHeight != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.LowestPendingCctxHeight)) + i-- + dAtA[i] = 0x38 + } + if len(m.PendingCctxsValue) > 0 { + i -= len(m.PendingCctxsValue) + copy(dAtA[i:], m.PendingCctxsValue) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PendingCctxsValue))) + i-- + dAtA[i] = 0x32 + } + if len(m.PastCctxsValue) > 0 { + i -= len(m.PastCctxsValue) + copy(dAtA[i:], m.PastCctxsValue) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PastCctxsValue))) + i-- + dAtA[i] = 0x2a + } + if m.TotalPending != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.TotalPending)) + i-- + dAtA[i] = 0x20 + } + if len(m.CctxsPending) > 0 { + for iNdEx := len(m.CctxsPending) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CctxsPending[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.CctxsMissed) > 0 { + for iNdEx := len(m.CctxsMissed) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CctxsMissed[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if m.Height != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *QueryListPendingCctxWithinRateLimitRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -5175,6 +5482,59 @@ func (m *QueryListPendingCctxResponse) Size() (n int) { return n } +func (m *QueryRateLimiterInputRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Limit != 0 { + n += 1 + sovQuery(uint64(m.Limit)) + } + if m.Window != 0 { + n += 1 + sovQuery(uint64(m.Window)) + } + return n +} + +func (m *QueryRateLimiterInputResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovQuery(uint64(m.Height)) + } + if len(m.CctxsMissed) > 0 { + for _, e := range m.CctxsMissed { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if len(m.CctxsPending) > 0 { + for _, e := range m.CctxsPending { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.TotalPending != 0 { + n += 1 + sovQuery(uint64(m.TotalPending)) + } + l = len(m.PastCctxsValue) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.PendingCctxsValue) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.LowestPendingCctxHeight != 0 { + n += 1 + sovQuery(uint64(m.LowestPendingCctxHeight)) + } + return n +} + func (m *QueryListPendingCctxWithinRateLimitRequest) Size() (n int) { if m == nil { return 0 @@ -8423,6 +8783,333 @@ func (m *QueryListPendingCctxResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryRateLimiterInputRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryRateLimiterInputRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryRateLimiterInputRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Limit", wireType) + } + m.Limit = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Limit |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Window", wireType) + } + m.Window = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Window |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryRateLimiterInputResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryRateLimiterInputResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryRateLimiterInputResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CctxsMissed", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CctxsMissed = append(m.CctxsMissed, &CrossChainTx{}) + if err := m.CctxsMissed[len(m.CctxsMissed)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CctxsPending", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CctxsPending = append(m.CctxsPending, &CrossChainTx{}) + if err := m.CctxsPending[len(m.CctxsPending)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalPending", wireType) + } + m.TotalPending = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TotalPending |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PastCctxsValue", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PastCctxsValue = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingCctxsValue", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PendingCctxsValue = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LowestPendingCctxHeight", wireType) + } + m.LowestPendingCctxHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LowestPendingCctxHeight |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *QueryListPendingCctxWithinRateLimitRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/crosschain/types/query.pb.gw.go b/x/crosschain/types/query.pb.gw.go index ac9d4ef555..7e6dba6226 100644 --- a/x/crosschain/types/query.pb.gw.go +++ b/x/crosschain/types/query.pb.gw.go @@ -995,6 +995,42 @@ func local_request_Query_RateLimiterFlags_0(ctx context.Context, marshaler runti } +var ( + filter_Query_RateLimiterInput_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_RateLimiterInput_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRateLimiterInputRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_RateLimiterInput_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.RateLimiterInput(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_RateLimiterInput_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRateLimiterInputRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_RateLimiterInput_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.RateLimiterInput(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1507,6 +1543,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_RateLimiterInput_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_RateLimiterInput_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_RateLimiterInput_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1988,6 +2047,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_RateLimiterInput_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_RateLimiterInput_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_RateLimiterInput_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -2035,6 +2114,8 @@ var ( pattern_Query_LastZetaHeight_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"zeta-chain", "crosschain", "lastZetaHeight"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_RateLimiterFlags_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"zeta-chain", "crosschain", "rateLimiterFlags"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_RateLimiterInput_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"zeta-chain", "crosschain", "rateLimiterInput"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -2081,4 +2162,6 @@ var ( forward_Query_LastZetaHeight_0 = runtime.ForwardResponseMessage forward_Query_RateLimiterFlags_0 = runtime.ForwardResponseMessage + + forward_Query_RateLimiterInput_0 = runtime.ForwardResponseMessage ) diff --git a/x/crosschain/types/rate_limiter_flags.go b/x/crosschain/types/rate_limiter_flags.go index 93a9617961..27d7614623 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 +} + +// ConvertCctxValueToAzeta converts the value of the cctx to azeta using given conversion rates +func ConvertCctxValueToAzeta( + 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 := ConvertCctxValueToAzeta(chainID, cctx, gasAssetRateMap, erc20AssetRateMap) + *currentCctxValue = currentCctxValue.Add(amountZeta) + return currentCctxValue.GT(withdrawLimitInAzeta) +} diff --git a/x/crosschain/types/rate_limiter_flags.pb.go b/x/crosschain/types/rate_limiter_flags.pb.go index 86f9c34059..f98d6575d4 100644 --- a/x/crosschain/types/rate_limiter_flags.pb.go +++ b/x/crosschain/types/rate_limiter_flags.pb.go @@ -8,6 +8,7 @@ import ( github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" + coin "github.com/zeta-chain/zetacore/pkg/coin" io "io" math "math" math_bits "math/bits" @@ -133,9 +134,79 @@ func (m *Conversion) GetZrc20() string { return "" } +type AssetRate struct { + ChainId int64 `protobuf:"varint,1,opt,name=chainId,proto3" json:"chainId,omitempty"` + Asset string `protobuf:"bytes,2,opt,name=asset,proto3" json:"asset,omitempty"` + Decimals uint32 `protobuf:"varint,3,opt,name=decimals,proto3" json:"decimals,omitempty"` + CoinType coin.CoinType `protobuf:"varint,4,opt,name=coin_type,json=coinType,proto3,enum=zetachain.zetacore.pkg.coin.CoinType" json:"coin_type,omitempty"` + Rate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=rate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"rate"` +} + +func (m *AssetRate) Reset() { *m = AssetRate{} } +func (m *AssetRate) String() string { return proto.CompactTextString(m) } +func (*AssetRate) ProtoMessage() {} +func (*AssetRate) Descriptor() ([]byte, []int) { + return fileDescriptor_9c435f4c2dabc0eb, []int{2} +} +func (m *AssetRate) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AssetRate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AssetRate.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AssetRate) XXX_Merge(src proto.Message) { + xxx_messageInfo_AssetRate.Merge(m, src) +} +func (m *AssetRate) XXX_Size() int { + return m.Size() +} +func (m *AssetRate) XXX_DiscardUnknown() { + xxx_messageInfo_AssetRate.DiscardUnknown(m) +} + +var xxx_messageInfo_AssetRate proto.InternalMessageInfo + +func (m *AssetRate) GetChainId() int64 { + if m != nil { + return m.ChainId + } + return 0 +} + +func (m *AssetRate) GetAsset() string { + if m != nil { + return m.Asset + } + return "" +} + +func (m *AssetRate) GetDecimals() uint32 { + if m != nil { + return m.Decimals + } + return 0 +} + +func (m *AssetRate) GetCoinType() coin.CoinType { + if m != nil { + return m.CoinType + } + return coin.CoinType_Zeta +} + func init() { proto.RegisterType((*RateLimiterFlags)(nil), "zetachain.zetacore.crosschain.RateLimiterFlags") proto.RegisterType((*Conversion)(nil), "zetachain.zetacore.crosschain.Conversion") + proto.RegisterType((*AssetRate)(nil), "zetachain.zetacore.crosschain.AssetRate") } func init() { @@ -143,28 +214,35 @@ func init() { } var fileDescriptor_9c435f4c2dabc0eb = []byte{ - // 335 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x91, 0xcf, 0x4e, 0xfa, 0x40, - 0x10, 0xc7, 0xbb, 0xc0, 0x8f, 0x9f, 0x2c, 0x17, 0xb3, 0x21, 0xa6, 0x31, 0xb1, 0x34, 0x1c, 0xb4, - 0x1e, 0xd8, 0x2a, 0x26, 0x3e, 0x40, 0x31, 0x5e, 0xf4, 0x62, 0x13, 0x2f, 0x5e, 0x48, 0x59, 0x96, - 0xb2, 0x11, 0x3a, 0x64, 0x77, 0x15, 0xe5, 0x29, 0x7c, 0x2c, 0x8e, 0x1c, 0x8d, 0x31, 0xc4, 0xc0, - 0x8b, 0x98, 0x6e, 0xcb, 0x9f, 0x78, 0x30, 0x9e, 0x3a, 0xd3, 0xcc, 0xe7, 0xbb, 0x9f, 0xc9, 0xe0, - 0xcb, 0x29, 0xd7, 0x11, 0x1b, 0x44, 0x22, 0xf1, 0x4d, 0x05, 0x92, 0xfb, 0x4c, 0x82, 0x52, 0xd9, - 0x3f, 0x19, 0x69, 0xde, 0x19, 0x8a, 0x91, 0xd0, 0x5c, 0x76, 0xfa, 0xc3, 0x28, 0x56, 0x74, 0x2c, - 0x41, 0x03, 0x39, 0xda, 0x70, 0x74, 0xcd, 0xd1, 0x2d, 0x77, 0x58, 0x8b, 0x21, 0x06, 0x33, 0xe9, - 0xa7, 0x55, 0x06, 0x35, 0x3e, 0x11, 0xde, 0x0f, 0x23, 0xcd, 0x6f, 0xb3, 0xc0, 0xeb, 0x34, 0x8f, - 0xd8, 0xf8, 0x3f, 0x4f, 0xa2, 0xee, 0x90, 0xf7, 0x6c, 0xe4, 0x22, 0x6f, 0x2f, 0x5c, 0xb7, 0xe4, - 0x00, 0x97, 0x27, 0x22, 0xe9, 0xc1, 0xc4, 0x2e, 0xb8, 0xc8, 0x2b, 0x86, 0x79, 0x47, 0xda, 0xb8, - 0x94, 0x7a, 0xd9, 0x45, 0x17, 0x79, 0x95, 0xc0, 0x9f, 0x2d, 0xea, 0xd6, 0xc7, 0xa2, 0x7e, 0x12, - 0x0b, 0x3d, 0x78, 0xea, 0x52, 0x06, 0x23, 0x9f, 0x81, 0x1a, 0x81, 0xca, 0x3f, 0x4d, 0xd5, 0x7b, - 0xf4, 0xf5, 0xeb, 0x98, 0x2b, 0x7a, 0x2f, 0x12, 0x1d, 0x1a, 0x98, 0xdc, 0xe1, 0x2a, 0x83, 0xe4, - 0x99, 0x4b, 0x25, 0x20, 0x51, 0x76, 0xc9, 0x2d, 0x7a, 0xd5, 0xd6, 0x29, 0xfd, 0x75, 0x2d, 0xda, - 0xde, 0x10, 0x41, 0x29, 0x7d, 0x36, 0xdc, 0xcd, 0x68, 0xf4, 0x31, 0xde, 0x0e, 0x90, 0x1a, 0xfe, - 0x37, 0x95, 0xac, 0x75, 0x66, 0xb6, 0xaa, 0x84, 0x59, 0x43, 0x82, 0xdc, 0xbd, 0x60, 0xdc, 0x69, - 0xee, 0x7e, 0xfc, 0x07, 0xf7, 0x2b, 0xce, 0x32, 0xf5, 0xe0, 0x66, 0xb6, 0x74, 0xd0, 0x7c, 0xe9, - 0xa0, 0xaf, 0xa5, 0x83, 0xde, 0x56, 0x8e, 0x35, 0x5f, 0x39, 0xd6, 0xfb, 0xca, 0xb1, 0x1e, 0xce, - 0x77, 0x72, 0x52, 0xff, 0xe6, 0x8f, 0xcb, 0xbe, 0xec, 0xde, 0xd6, 0xc4, 0x76, 0xcb, 0xe6, 0x34, - 0x17, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x16, 0x2b, 0x9e, 0xbb, 0x09, 0x02, 0x00, 0x00, + // 435 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x52, 0x41, 0x6b, 0x13, 0x41, + 0x14, 0xce, 0x34, 0x69, 0xcd, 0x4e, 0x51, 0x64, 0x28, 0xb2, 0x04, 0xdc, 0x2e, 0x01, 0xeb, 0x7a, + 0xe8, 0xac, 0x46, 0xf0, 0xee, 0x46, 0x04, 0xd1, 0x8b, 0x83, 0x5e, 0xbc, 0x84, 0xc9, 0xec, 0x74, + 0x3b, 0x64, 0x77, 0x66, 0xd9, 0x19, 0xad, 0xed, 0xaf, 0xf0, 0x67, 0xf5, 0xd8, 0xa3, 0x88, 0x54, + 0x49, 0xfe, 0x88, 0xcc, 0xcc, 0x66, 0x1b, 0x24, 0x88, 0xf4, 0x92, 0xbc, 0x6f, 0x79, 0xdf, 0xfb, + 0xbe, 0xf7, 0xcd, 0x83, 0x2f, 0x2e, 0xb8, 0xa1, 0xec, 0x94, 0x0a, 0x99, 0xba, 0x4a, 0x35, 0x3c, + 0x65, 0x8d, 0xd2, 0xda, 0x7f, 0x6b, 0xa8, 0xe1, 0xb3, 0x52, 0x54, 0xc2, 0xf0, 0x66, 0x76, 0x52, + 0xd2, 0x42, 0xe3, 0xba, 0x51, 0x46, 0xa1, 0x87, 0x1d, 0x0f, 0xaf, 0x79, 0xf8, 0x86, 0x37, 0x3a, + 0x28, 0x54, 0xa1, 0x5c, 0x67, 0x6a, 0x2b, 0x4f, 0x1a, 0x1d, 0x6d, 0x11, 0xab, 0x17, 0x45, 0xca, + 0x94, 0x90, 0xee, 0xc7, 0xf7, 0x8d, 0x7f, 0x02, 0x78, 0x9f, 0x50, 0xc3, 0xdf, 0x79, 0xe1, 0xd7, + 0x56, 0x17, 0x85, 0xf0, 0x0e, 0x97, 0x74, 0x5e, 0xf2, 0x3c, 0x04, 0x31, 0x48, 0x86, 0x64, 0x0d, + 0xd1, 0x03, 0xb8, 0x77, 0x26, 0x64, 0xae, 0xce, 0xc2, 0x9d, 0x18, 0x24, 0x7d, 0xd2, 0x22, 0x34, + 0x85, 0x03, 0xeb, 0x3f, 0xec, 0xc7, 0x20, 0x09, 0xb2, 0xf4, 0xf2, 0xfa, 0xb0, 0xf7, 0xe3, 0xfa, + 0xf0, 0x71, 0x21, 0xcc, 0xe9, 0xe7, 0x39, 0x66, 0xaa, 0x4a, 0x99, 0xd2, 0x95, 0xd2, 0xed, 0xdf, + 0xb1, 0xce, 0x17, 0xa9, 0x39, 0xaf, 0xb9, 0xc6, 0x1f, 0x85, 0x34, 0xc4, 0x91, 0xd1, 0x7b, 0xb8, + 0xcf, 0x94, 0xfc, 0xc2, 0x1b, 0x2d, 0x94, 0xd4, 0xe1, 0x20, 0xee, 0x27, 0xfb, 0x93, 0x27, 0xf8, + 0x9f, 0xeb, 0xe3, 0x69, 0xc7, 0xc8, 0x06, 0x56, 0x96, 0x6c, 0xce, 0x18, 0x9f, 0x40, 0x78, 0xd3, + 0x80, 0x0e, 0xe0, 0xee, 0x45, 0xc3, 0x26, 0x4f, 0xdd, 0x56, 0x01, 0xf1, 0x00, 0x65, 0xad, 0xf7, + 0x1d, 0xe7, 0x1d, 0xb7, 0xde, 0x8f, 0xfe, 0xc3, 0xfb, 0x2b, 0xce, 0xbc, 0xf5, 0xf1, 0x2f, 0x00, + 0x83, 0x97, 0x5a, 0x73, 0x63, 0xb3, 0xb4, 0xf9, 0x39, 0x73, 0x6f, 0x7c, 0x7e, 0x7d, 0xb2, 0x86, + 0xd6, 0x01, 0xb5, 0x6d, 0x5e, 0x8c, 0x78, 0x80, 0x46, 0x70, 0x98, 0x73, 0x26, 0x2a, 0x5a, 0x6a, + 0x97, 0xe0, 0x5d, 0xd2, 0x61, 0x94, 0xc1, 0xc0, 0x3e, 0xd7, 0xcc, 0x2a, 0x86, 0x83, 0x18, 0x24, + 0xf7, 0x26, 0x8f, 0xb6, 0x45, 0x52, 0x2f, 0x0a, 0xec, 0xde, 0x75, 0xaa, 0x84, 0xfc, 0x70, 0x5e, + 0x73, 0x32, 0x64, 0x6d, 0xd5, 0x6d, 0xb8, 0x7b, 0xfb, 0x0d, 0xb3, 0xb7, 0x97, 0xcb, 0x08, 0x5c, + 0x2d, 0x23, 0xf0, 0x7b, 0x19, 0x81, 0x6f, 0xab, 0xa8, 0x77, 0xb5, 0x8a, 0x7a, 0xdf, 0x57, 0x51, + 0xef, 0xd3, 0xb3, 0x8d, 0x39, 0xd6, 0xce, 0xf1, 0x5f, 0x67, 0xf7, 0x75, 0xf3, 0xca, 0xdd, 0xd8, + 0xf9, 0x9e, 0x3b, 0xbe, 0xe7, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x20, 0xbc, 0x25, 0x58, 0x13, + 0x03, 0x00, 0x00, } func (m *RateLimiterFlags) Marshal() (dAtA []byte, err error) { @@ -269,6 +347,61 @@ func (m *Conversion) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *AssetRate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AssetRate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AssetRate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.Rate.Size() + i -= size + if _, err := m.Rate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintRateLimiterFlags(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if m.CoinType != 0 { + i = encodeVarintRateLimiterFlags(dAtA, i, uint64(m.CoinType)) + i-- + dAtA[i] = 0x20 + } + if m.Decimals != 0 { + i = encodeVarintRateLimiterFlags(dAtA, i, uint64(m.Decimals)) + i-- + dAtA[i] = 0x18 + } + if len(m.Asset) > 0 { + i -= len(m.Asset) + copy(dAtA[i:], m.Asset) + i = encodeVarintRateLimiterFlags(dAtA, i, uint64(len(m.Asset))) + i-- + dAtA[i] = 0x12 + } + if m.ChainId != 0 { + i = encodeVarintRateLimiterFlags(dAtA, i, uint64(m.ChainId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func encodeVarintRateLimiterFlags(dAtA []byte, offset int, v uint64) int { offset -= sovRateLimiterFlags(v) base := offset @@ -318,6 +451,30 @@ func (m *Conversion) Size() (n int) { return n } +func (m *AssetRate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ChainId != 0 { + n += 1 + sovRateLimiterFlags(uint64(m.ChainId)) + } + l = len(m.Asset) + if l > 0 { + n += 1 + l + sovRateLimiterFlags(uint64(l)) + } + if m.Decimals != 0 { + n += 1 + sovRateLimiterFlags(uint64(m.Decimals)) + } + if m.CoinType != 0 { + n += 1 + sovRateLimiterFlags(uint64(m.CoinType)) + } + l = m.Rate.Size() + n += 1 + l + sovRateLimiterFlags(uint64(l)) + return n +} + func sovRateLimiterFlags(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -597,6 +754,179 @@ func (m *Conversion) Unmarshal(dAtA []byte) error { } return nil } +func (m *AssetRate) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRateLimiterFlags + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AssetRate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AssetRate: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + m.ChainId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRateLimiterFlags + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ChainId |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Asset", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRateLimiterFlags + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRateLimiterFlags + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRateLimiterFlags + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Asset = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Decimals", wireType) + } + m.Decimals = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRateLimiterFlags + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Decimals |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CoinType", wireType) + } + m.CoinType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRateLimiterFlags + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CoinType |= coin.CoinType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRateLimiterFlags + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRateLimiterFlags + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRateLimiterFlags + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Rate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRateLimiterFlags(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthRateLimiterFlags + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipRateLimiterFlags(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/crosschain/types/rate_limiter_flags_test.go b/x/crosschain/types/rate_limiter_flags_test.go index d837fabc51..e13a22d78b 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.ConvertCctxValueToAzeta(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 e602512fd6..0eb0341755 100644 --- a/zetaclient/core_context/zeta_core_context.go +++ b/zetaclient/core_context/zeta_core_context.go @@ -75,17 +75,34 @@ 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 } +// GetEnabledExternalChains returns all enabled external chains +func (c *ZetaCoreContext) GetEnabledExternalChains() []chains.Chain { + c.coreContextLock.RLock() + defer c.coreContextLock.RUnlock() + + externalChains := make([]chains.Chain, 0) + for _, chain := range c.chainsEnabled { + if chain.IsExternal { + externalChains = append(externalChains, chain) + } + } + return externalChains +} + 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 e9fb129023..c2517f66da 100644 --- a/zetaclient/core_context/zeta_core_context_test.go +++ b/zetaclient/core_context/zeta_core_context_test.go @@ -69,6 +69,9 @@ func TestNewZetaCoreContext(t *testing.T) { // assert enabled chains require.Empty(t, len(zetaContext.GetEnabledChains())) + // assert external chains + require.Empty(t, len(zetaContext.GetEnabledExternalChains())) + // assert current tss pubkey require.Equal(t, "", zetaContext.GetCurrentTssPubkey()) @@ -148,13 +151,16 @@ func TestUpdateZetaCoreContext(t *testing.T) { } enabledChainsToUpdate := []chains.Chain{ { - ChainName: 1, - ChainId: 1, + ChainName: 1, + ChainId: 1, + IsExternal: true, }, { - ChainName: 2, - ChainId: 2, + ChainName: 2, + ChainId: 2, + IsExternal: true, }, + chains.ZetaTestnetChain, } evmChainParamsToUpdate := map[int64]*observertypes.ChainParams{ 1: { @@ -192,6 +198,9 @@ func TestUpdateZetaCoreContext(t *testing.T) { // assert enabled chains updated require.Equal(t, enabledChainsToUpdate, zetaContext.GetEnabledChains()) + // 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/interfaces/interfaces.go b/zetaclient/interfaces/interfaces.go index bffff1ec83..e219de135f 100644 --- a/zetaclient/interfaces/interfaces.go +++ b/zetaclient/interfaces/interfaces.go @@ -98,11 +98,13 @@ type ZetaCoreBridger interface { GetLastBlockHeightByChain(chain chains.Chain) (*crosschaintypes.LastBlockHeight, error) ListPendingCctx(chainID int64) ([]*crosschaintypes.CrossChainTx, uint64, error) ListPendingCctxWithinRatelimit() ([]*crosschaintypes.CrossChainTx, uint64, int64, string, bool, error) + GetRateLimiterInput(window int64) (crosschaintypes.QueryRateLimiterInputResponse, error) GetPendingNoncesByChain(chainID int64) (observertypes.PendingNonces, error) GetCctxByNonce(chainID int64, nonce uint64) (*crosschaintypes.CrossChainTx, error) GetOutTxTracker(chain chains.Chain, nonce uint64) (*crosschaintypes.OutTxTracker, error) GetAllOutTxTrackerByChain(chainID int64, order Order) ([]crosschaintypes.OutTxTracker, error) GetCrosschainFlags() (observertypes.CrosschainFlags, error) + GetRateLimiterFlags() (crosschaintypes.RateLimiterFlags, error) GetObserverList() ([]string, error) GetKeyGen() (*observertypes.Keygen, error) GetBtcTssAddress(chainID int64) (string, error) diff --git a/zetaclient/metrics/metrics.go b/zetaclient/metrics/metrics.go index d6525a70ba..d466524970 100644 --- a/zetaclient/metrics/metrics.go +++ b/zetaclient/metrics/metrics.go @@ -84,6 +84,12 @@ var ( Name: "num_active_message_signs", Help: "Number of concurrent key signs", }) + + PercentageOfRateReached = promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: ZetaClientNamespace, + Name: "percentage_of_rate_reached", + Help: "Percentage of the rate limiter rate reached", + }) ) func NewMetrics() (*Metrics, error) { diff --git a/zetaclient/ratelimiter/rate_limiter.go b/zetaclient/ratelimiter/rate_limiter.go new file mode 100644 index 0000000000..ed54831849 --- /dev/null +++ b/zetaclient/ratelimiter/rate_limiter.go @@ -0,0 +1,139 @@ +package ratelimiter + +import ( + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// Input is the input data for the rate limiter +type Input struct { + // zeta chain height + Height int64 + + // the missed cctxs in range [?, NonceLow) across all chains + CctxsMissed []*crosschaintypes.CrossChainTx + + // the pending cctxs in range [NonceLow, NonceHigh) across all chains + CctxsPending []*crosschaintypes.CrossChainTx + + // the total value of the past cctxs within window across all chains + PastCctxsValue sdkmath.Int + + // the total value of the pending cctxs across all chains + PendingCctxsValue sdkmath.Int + + // the lowest height of the pending (not missed) cctxs across all chains + LowestPendingCctxHeight int64 +} + +// Output is the output data for the rate limiter +type Output struct { + // the cctxs to be scheduled after rate limit check + CctxsMap map[int64][]*crosschaintypes.CrossChainTx + + // the current sliding window within which the withdrawals are considered by the rate limiter + CurrentWithdrawWindow int64 + + // the current withdraw rate (azeta/block) within the current sliding window + CurrentWithdrawRate sdkmath.Int + + // wehther the current withdraw rate exceeds the given rate limit or not + RateLimitExceeded bool +} + +// NewInput creates a rate limiter input from gRPC response +func NewInput(resp crosschaintypes.QueryRateLimiterInputResponse) (*Input, bool) { + // parse the past cctxs value from string + pastCctxsValue, ok := sdk.NewIntFromString(resp.PastCctxsValue) + if !ok { + return nil, false + } + + // parse the pending cctxs value from string + pendingCctxsValue, ok := sdk.NewIntFromString(resp.PendingCctxsValue) + if !ok { + return nil, false + } + + return &Input{ + Height: resp.Height, + CctxsMissed: resp.CctxsMissed, + CctxsPending: resp.CctxsPending, + PastCctxsValue: pastCctxsValue, + PendingCctxsValue: pendingCctxsValue, + LowestPendingCctxHeight: resp.LowestPendingCctxHeight, + }, true +} + +// IsRateLimiterUsable checks if the rate limiter is usable or not +func IsRateLimiterUsable(rateLimiterFlags crosschaintypes.RateLimiterFlags) bool { + if !rateLimiterFlags.Enabled { + return false + } + if rateLimiterFlags.Window <= 0 { + return false + } + if rateLimiterFlags.Rate.IsNil() { + return false + } + if rateLimiterFlags.Rate.IsZero() { + return false + } + return true +} + +// ApplyRateLimiter applies the rate limiter to the input and produces output +func ApplyRateLimiter(input *Input, window int64, rate sdkmath.Uint) *Output { + // block limit and the window limit in azeta + blockLimitInAzeta := sdkmath.NewIntFromBigInt(rate.BigInt()) + windowLimitInAzeta := blockLimitInAzeta.Mul(sdkmath.NewInt(window)) + + // invariant: for period of time >= `window`, the zetaclient-side average withdraw rate should be <= `blockLimitInZeta` + // otherwise, zetaclient should wait for the average rate to drop below `blockLimitInZeta` + withdrawWindow := window + withdrawLimitInAzeta := windowLimitInAzeta + if input.LowestPendingCctxHeight != 0 { + // If [input.LowestPendingCctxHeight, input.Height] is wider than the given `window`, we should: + // 1. use the wider window to calculate the average withdraw rate + // 2. adjust the limit proportionally to fit the wider window + pendingCctxWindow := input.Height - input.LowestPendingCctxHeight + 1 + if pendingCctxWindow > window { + withdrawWindow = pendingCctxWindow + withdrawLimitInAzeta = blockLimitInAzeta.Mul(sdk.NewInt(pendingCctxWindow)) + } + } + + // limit exceeded or not + totalWithdrawInAzeta := input.PastCctxsValue.Add(input.PendingCctxsValue) + limitExceeded := totalWithdrawInAzeta.GT(withdrawLimitInAzeta) + + // define the result cctx map to be scheduled + cctxMap := make(map[int64][]*crosschaintypes.CrossChainTx) + + // addCctxsToMap adds the given cctxs to the cctx map + addCctxsToMap := func(cctxs []*crosschaintypes.CrossChainTx) { + for _, cctx := range cctxs { + chainID := cctx.GetCurrentOutTxParam().ReceiverChainId + if _, found := cctxMap[chainID]; !found { + cctxMap[chainID] = make([]*crosschaintypes.CrossChainTx, 0) + } + cctxMap[chainID] = append(cctxMap[chainID], cctx) + } + } + + // schedule missed cctxs regardless of the `limitExceeded` flag + addCctxsToMap(input.CctxsMissed) + + // schedule pending cctxs only if `limitExceeded == false` + if !limitExceeded { + addCctxsToMap(input.CctxsPending) + } + + return &Output{ + CctxsMap: cctxMap, + CurrentWithdrawWindow: withdrawWindow, + CurrentWithdrawRate: totalWithdrawInAzeta.Quo(sdk.NewInt(withdrawWindow)), + RateLimitExceeded: limitExceeded, + } +} diff --git a/zetaclient/ratelimiter/rate_limiter_test.go b/zetaclient/ratelimiter/rate_limiter_test.go new file mode 100644 index 0000000000..0f6c3a0e73 --- /dev/null +++ b/zetaclient/ratelimiter/rate_limiter_test.go @@ -0,0 +1,265 @@ +package ratelimiter_test + +import ( + "testing" + + 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" + crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/ratelimiter" +) + +func Test_NewInput(t *testing.T) { + // sample response + response := crosschaintypes.QueryRateLimiterInputResponse{ + Height: 10, + CctxsMissed: []*crosschaintypes.CrossChainTx{sample.CrossChainTx(t, "1-1")}, + CctxsPending: []*crosschaintypes.CrossChainTx{sample.CrossChainTx(t, "1-2")}, + TotalPending: 7, + PastCctxsValue: sdk.NewInt(12345678).Mul(sdk.NewInt(1e18)).String(), + PendingCctxsValue: sdk.NewInt(4321).Mul(sdk.NewInt(1e18)).String(), + LowestPendingCctxHeight: 2, + } + + 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) + require.Equal(t, response.CctxsMissed, filterInput.CctxsMissed) + require.Equal(t, response.CctxsPending, filterInput.CctxsPending) + require.Equal(t, response.PastCctxsValue, filterInput.PastCctxsValue.String()) + require.Equal(t, response.PendingCctxsValue, filterInput.PendingCctxsValue.String()) + require.Equal(t, response.LowestPendingCctxHeight, filterInput.LowestPendingCctxHeight) + }) + t.Run("should return false if past cctxs value is invalid", func(t *testing.T) { + invalidResp := response + invalidResp.PastCctxsValue = "invalid" + filterInput, ok := ratelimiter.NewInput(invalidResp) + require.False(t, ok) + require.Nil(t, filterInput) + }) + t.Run("should return false if pending cctxs value is invalid", func(t *testing.T) { + invalidResp := response + invalidResp.PendingCctxsValue = "invalid" + filterInput, ok := ratelimiter.NewInput(invalidResp) + require.False(t, ok) + require.Nil(t, filterInput) + }) +} + +func Test_IsRateLimiterUsable(t *testing.T) { + tests := []struct { + name string + flags crosschaintypes.RateLimiterFlags + expected bool + }{ + { + name: "rate limiter is enabled", + flags: crosschaintypes.RateLimiterFlags{ + Enabled: true, + Window: 100, + Rate: sdkmath.NewUint(1e18), // 1 ZETA/block + }, + expected: true, + }, + { + name: "rate limiter is disabled", + flags: crosschaintypes.RateLimiterFlags{ + Enabled: false, + }, + expected: false, + }, + { + name: "rate limiter is enabled with 0 window", + flags: crosschaintypes.RateLimiterFlags{ + Enabled: true, + Window: 0, + }, + expected: false, + }, + { + name: "rate limiter is enabled with nil rate", + flags: crosschaintypes.RateLimiterFlags{ + Enabled: true, + Window: 100, + Rate: sdkmath.Uint{}, + }, + expected: false, + }, + { + name: "rate limiter is enabled with zero rate", + flags: crosschaintypes.RateLimiterFlags{ + Enabled: true, + Window: 100, + Rate: sdkmath.NewUint(0), + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + usable := ratelimiter.IsRateLimiterUsable(tt.flags) + require.Equal(t, tt.expected, usable) + }) + } +} + +func Test_ApplyRateLimiter(t *testing.T) { + // define test chain ids + ethChainID := chains.EthChain.ChainId + btcChainID := chains.BtcMainnetChain.ChainId + + // create 10 missed and 90 pending cctxs for eth chain, the coinType/amount does not matter for this test + // but we still use a proper cctx value (0.5 ZETA) to make the test more realistic + ethCctxsMissed := sample.CustomCctxsInBlockRange(t, 1, 10, ethChainID, coin.CoinType_Gas, "", uint64(2e14), crosschaintypes.CctxStatus_PendingOutbound) + ethCctxsPending := sample.CustomCctxsInBlockRange(t, 11, 100, ethChainID, coin.CoinType_Gas, "", uint64(2e14), crosschaintypes.CctxStatus_PendingOutbound) + ethCctxsAll := append(append([]*crosschaintypes.CrossChainTx{}, ethCctxsMissed...), ethCctxsPending...) + + // create 10 missed and 90 pending cctxs for btc chain, the coinType/amount does not matter for this test + // but we still use a proper cctx value (0.5 ZETA) to make the test more realistic + btcCctxsMissed := sample.CustomCctxsInBlockRange(t, 1, 10, btcChainID, coin.CoinType_Gas, "", 2000, crosschaintypes.CctxStatus_PendingOutbound) + btcCctxsPending := sample.CustomCctxsInBlockRange(t, 11, 100, btcChainID, coin.CoinType_Gas, "", 2000, crosschaintypes.CctxStatus_PendingOutbound) + btcCctxsAll := append(append([]*crosschaintypes.CrossChainTx{}, btcCctxsMissed...), btcCctxsPending...) + + // all missed cctxs and all pending cctxs across all chains + allCctxsMissed := crosschainkeeper.SortCctxsByHeightAndChainID( + append(append([]*crosschaintypes.CrossChainTx{}, ethCctxsMissed...), btcCctxsMissed...)) + allCctxsPending := crosschainkeeper.SortCctxsByHeightAndChainID( + append(append([]*crosschaintypes.CrossChainTx{}, ethCctxsPending...), btcCctxsPending...)) + + // define test cases + tests := []struct { + name string + window int64 + rate sdkmath.Uint + input ratelimiter.Input + output ratelimiter.Output + }{ + { + name: "should return all missed and pending cctxs", + window: 100, + rate: sdk.NewUint(1e18), // 1 ZETA/block + input: ratelimiter.Input{ + Height: 100, + CctxsMissed: allCctxsMissed, + CctxsPending: allCctxsPending, + PastCctxsValue: sdk.NewInt(10).Mul(sdk.NewInt(1e18)), // 10 * 1 ZETA + PendingCctxsValue: sdk.NewInt(90).Mul(sdk.NewInt(1e18)), // 90 * 1 ZETA + LowestPendingCctxHeight: 11, + }, + output: ratelimiter.Output{ + CctxsMap: map[int64][]*crosschaintypes.CrossChainTx{ + ethChainID: ethCctxsAll, + btcChainID: btcCctxsAll, + }, + CurrentWithdrawWindow: 100, // height [1, 100] + CurrentWithdrawRate: sdk.NewInt(1e18), // (10 + 90) / 100 + RateLimitExceeded: false, + }, + }, + { + name: "should monitor a wider window and adjust the total limit", + window: 50, + rate: sdk.NewUint(1e18), // 1 ZETA/block + input: ratelimiter.Input{ + Height: 100, + CctxsMissed: allCctxsMissed, + CctxsPending: allCctxsPending, + PastCctxsValue: sdk.NewInt(0), // no past cctx in height range [51, 100] + PendingCctxsValue: sdk.NewInt(90).Mul(sdk.NewInt(1e18)), // 90 * 1 ZETA + LowestPendingCctxHeight: 11, + }, + output: ratelimiter.Output{ + CctxsMap: map[int64][]*crosschaintypes.CrossChainTx{ + ethChainID: ethCctxsAll, + btcChainID: btcCctxsAll, + }, + CurrentWithdrawWindow: 90, // [LowestPendingCctxHeight, Height] = [11, 100] + CurrentWithdrawRate: sdk.NewInt(1e18), // 90 / 90 = 1 ZETA/block + RateLimitExceeded: false, + }, + }, + { + name: "rate limit is exceeded in given sliding window 100", + window: 100, + rate: sdk.NewUint(1e18), // 1 ZETA/block + input: ratelimiter.Input{ + Height: 100, + CctxsMissed: allCctxsMissed, + CctxsPending: allCctxsPending, + PastCctxsValue: sdk.NewInt(11).Mul(sdk.NewInt(1e18)), // 11 ZETA, increased value by 1 ZETA + PendingCctxsValue: sdk.NewInt(90).Mul(sdk.NewInt(1e18)), // 90 * 1 ZETA + LowestPendingCctxHeight: 11, + }, + output: ratelimiter.Output{ // should return missed cctxs only + CctxsMap: map[int64][]*crosschaintypes.CrossChainTx{ + ethChainID: ethCctxsMissed, + btcChainID: btcCctxsMissed, + }, + CurrentWithdrawWindow: 100, // height [1, 100] + CurrentWithdrawRate: sdk.NewInt(101e16), // (11 + 90) / 100 = 1.01 ZETA/block (exceeds 0.99 ZETA/block) + RateLimitExceeded: true, + }, + }, + { + name: "rate limit is exceeded in wider window then the given sliding window 50", + window: 50, + rate: sdk.NewUint(1e18), // 1 ZETA/block + input: ratelimiter.Input{ + Height: 100, + CctxsMissed: allCctxsMissed, + CctxsPending: allCctxsPending, + PastCctxsValue: sdk.NewInt(0), // no past cctx in height range [51, 100] + PendingCctxsValue: sdk.NewInt(91).Mul(sdk.NewInt(1e18)), // 91 ZETA, increased value by 1 ZETA + LowestPendingCctxHeight: 11, + }, + output: ratelimiter.Output{ + CctxsMap: map[int64][]*crosschaintypes.CrossChainTx{ + ethChainID: ethCctxsMissed, + btcChainID: btcCctxsMissed, + }, + CurrentWithdrawWindow: 90, // [LowestPendingCctxHeight, Height] = [11, 100] + CurrentWithdrawRate: sdk.NewInt(91).Mul(sdk.NewInt(1e18)).Quo(sdk.NewInt(90)), // 91 / 90 = 1.011111111111111111 ZETA/block + RateLimitExceeded: true, + }, + }, + { + name: "should not exceed rate limit if we wait for 1 more block", + window: 50, + rate: sdk.NewUint(1e18), // 1 ZETA/block + input: ratelimiter.Input{ + Height: 101, + CctxsMissed: allCctxsMissed, + CctxsPending: allCctxsPending, + PastCctxsValue: sdk.NewInt(0), // no past cctx in height range [52, 101] + PendingCctxsValue: sdk.NewInt(91).Mul(sdk.NewInt(1e18)), // 91 ZETA, increased value by 1 ZETA + LowestPendingCctxHeight: 11, + }, + output: ratelimiter.Output{ + CctxsMap: map[int64][]*crosschaintypes.CrossChainTx{ + ethChainID: ethCctxsAll, + btcChainID: btcCctxsAll, + }, + CurrentWithdrawWindow: 91, // [LowestPendingCctxHeight, Height] = [11, 101] + CurrentWithdrawRate: sdk.NewInt(91).Mul(sdk.NewInt(1e18)).Quo(sdk.NewInt(91)), // 91 / 91 = 1.011 ZETA/block + RateLimitExceeded: false, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output := ratelimiter.ApplyRateLimiter(&tt.input, tt.window, tt.rate) + require.Equal(t, tt.output.CctxsMap, output.CctxsMap) + require.Equal(t, tt.output.CurrentWithdrawWindow, output.CurrentWithdrawWindow) + require.Equal(t, tt.output.CurrentWithdrawRate, output.CurrentWithdrawRate) + require.Equal(t, tt.output.RateLimitExceeded, output.RateLimitExceeded) + }) + } +} diff --git a/zetaclient/testutils/stub/core_bridge.go b/zetaclient/testutils/stub/core_bridge.go index ec72eae7d8..becc5eb98c 100644 --- a/zetaclient/testutils/stub/core_bridge.go +++ b/zetaclient/testutils/stub/core_bridge.go @@ -13,7 +13,7 @@ import ( "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/pkg/proofs" "github.com/zeta-chain/zetacore/testutil/sample" - cctxtypes "github.com/zeta-chain/zetacore/x/crosschain/types" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" observerTypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/keys" @@ -21,12 +21,23 @@ import ( ) const ErrMsgPaused = "zeta core bridge is paused" +const ErrMsgRPCFailed = "rpc failed" var _ interfaces.ZetaCoreBridger = &MockZetaCoreBridge{} type MockZetaCoreBridge struct { paused bool zetaChain chains.Chain + + // the mock data for testing + // pending cctxs + pendingCctxs map[int64][]*crosschaintypes.CrossChainTx + + // rate limiter flags + rateLimiterFlags *crosschaintypes.RateLimiterFlags + + // rate limiter input + input *crosschaintypes.QueryRateLimiterInputResponse } func NewMockZetaCoreBridge() *MockZetaCoreBridge { @@ -35,12 +46,13 @@ func NewMockZetaCoreBridge() *MockZetaCoreBridge { panic(err) } return &MockZetaCoreBridge{ - paused: false, - zetaChain: zetaChain, + paused: false, + zetaChain: zetaChain, + pendingCctxs: map[int64][]*crosschaintypes.CrossChainTx{}, } } -func (z *MockZetaCoreBridge) PostVoteInbound(_, _ uint64, _ *cctxtypes.MsgVoteOnObservedInboundTx) (string, string, error) { +func (z *MockZetaCoreBridge) PostVoteInbound(_, _ uint64, _ *crosschaintypes.MsgVoteOnObservedInboundTx) (string, string, error) { if z.paused { return "", "", errors.New(ErrMsgPaused) } @@ -107,25 +119,35 @@ func (z *MockZetaCoreBridge) GetZetaBlockHeight() (int64, error) { return 0, nil } -func (z *MockZetaCoreBridge) GetLastBlockHeightByChain(_ chains.Chain) (*cctxtypes.LastBlockHeight, error) { +func (z *MockZetaCoreBridge) GetLastBlockHeightByChain(_ chains.Chain) (*crosschaintypes.LastBlockHeight, error) { if z.paused { return nil, errors.New(ErrMsgPaused) } - return &cctxtypes.LastBlockHeight{}, nil + return &crosschaintypes.LastBlockHeight{}, nil +} + +func (z *MockZetaCoreBridge) GetRateLimiterInput(_ int64) (crosschaintypes.QueryRateLimiterInputResponse, error) { + if z.paused { + return crosschaintypes.QueryRateLimiterInputResponse{}, errors.New(ErrMsgPaused) + } + if z.input == nil { + return crosschaintypes.QueryRateLimiterInputResponse{}, errors.New(ErrMsgRPCFailed) + } + return *z.input, nil } -func (z *MockZetaCoreBridge) ListPendingCctx(_ int64) ([]*cctxtypes.CrossChainTx, uint64, error) { +func (z *MockZetaCoreBridge) ListPendingCctx(chainID int64) ([]*crosschaintypes.CrossChainTx, uint64, error) { if z.paused { return nil, 0, errors.New(ErrMsgPaused) } - return []*cctxtypes.CrossChainTx{}, 0, nil + return z.pendingCctxs[chainID], 0, nil } -func (z *MockZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*cctxtypes.CrossChainTx, uint64, int64, string, bool, error) { +func (z *MockZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*crosschaintypes.CrossChainTx, uint64, int64, string, bool, error) { if z.paused { return nil, 0, 0, "", false, errors.New(ErrMsgPaused) } - return []*cctxtypes.CrossChainTx{}, 0, 0, "", false, nil + return []*crosschaintypes.CrossChainTx{}, 0, 0, "", false, nil } func (z *MockZetaCoreBridge) GetPendingNoncesByChain(_ int64) (observerTypes.PendingNonces, error) { @@ -135,25 +157,25 @@ func (z *MockZetaCoreBridge) GetPendingNoncesByChain(_ int64) (observerTypes.Pen return observerTypes.PendingNonces{}, nil } -func (z *MockZetaCoreBridge) GetCctxByNonce(_ int64, _ uint64) (*cctxtypes.CrossChainTx, error) { +func (z *MockZetaCoreBridge) GetCctxByNonce(_ int64, _ uint64) (*crosschaintypes.CrossChainTx, error) { if z.paused { return nil, errors.New(ErrMsgPaused) } - return &cctxtypes.CrossChainTx{}, nil + return &crosschaintypes.CrossChainTx{}, nil } -func (z *MockZetaCoreBridge) GetOutTxTracker(_ chains.Chain, _ uint64) (*cctxtypes.OutTxTracker, error) { +func (z *MockZetaCoreBridge) GetOutTxTracker(_ chains.Chain, _ uint64) (*crosschaintypes.OutTxTracker, error) { if z.paused { return nil, errors.New(ErrMsgPaused) } - return &cctxtypes.OutTxTracker{}, nil + return &crosschaintypes.OutTxTracker{}, nil } -func (z *MockZetaCoreBridge) GetAllOutTxTrackerByChain(_ int64, _ interfaces.Order) ([]cctxtypes.OutTxTracker, error) { +func (z *MockZetaCoreBridge) GetAllOutTxTrackerByChain(_ int64, _ interfaces.Order) ([]crosschaintypes.OutTxTracker, error) { if z.paused { return nil, errors.New(ErrMsgPaused) } - return []cctxtypes.OutTxTracker{}, nil + return []crosschaintypes.OutTxTracker{}, nil } func (z *MockZetaCoreBridge) GetCrosschainFlags() (observerTypes.CrosschainFlags, error) { @@ -163,6 +185,16 @@ func (z *MockZetaCoreBridge) GetCrosschainFlags() (observerTypes.CrosschainFlags return observerTypes.CrosschainFlags{}, nil } +func (z *MockZetaCoreBridge) GetRateLimiterFlags() (crosschaintypes.RateLimiterFlags, error) { + if z.paused { + return crosschaintypes.RateLimiterFlags{}, errors.New(ErrMsgPaused) + } + if z.rateLimiterFlags == nil { + return crosschaintypes.RateLimiterFlags{}, errors.New(ErrMsgRPCFailed) + } + return *z.rateLimiterFlags, nil +} + func (z *MockZetaCoreBridge) GetObserverList() ([]string, error) { if z.paused { return nil, errors.New(ErrMsgPaused) @@ -184,11 +216,11 @@ func (z *MockZetaCoreBridge) GetBtcTssAddress(_ int64) (string, error) { return testutils.TSSAddressBTCMainnet, nil } -func (z *MockZetaCoreBridge) GetInboundTrackersForChain(_ int64) ([]cctxtypes.InTxTracker, error) { +func (z *MockZetaCoreBridge) GetInboundTrackersForChain(_ int64) ([]crosschaintypes.InTxTracker, error) { if z.paused { return nil, errors.New(ErrMsgPaused) } - return []cctxtypes.InTxTracker{}, nil + return []crosschaintypes.InTxTracker{}, nil } func (z *MockZetaCoreBridge) GetLogger() *zerolog.Logger { @@ -213,3 +245,22 @@ func (z *MockZetaCoreBridge) GetZetaHotKeyBalance() (math.Int, error) { } return math.NewInt(0), nil } + +// ---------------------------------------------------------------------------- +// Feed data to the mock zeta bridge for testing +// ---------------------------------------------------------------------------- + +func (z *MockZetaCoreBridge) WithPendingCctx(chainID int64, cctxs []*crosschaintypes.CrossChainTx) *MockZetaCoreBridge { + z.pendingCctxs[chainID] = cctxs + return z +} + +func (z *MockZetaCoreBridge) WithRateLimiterFlags(flags *crosschaintypes.RateLimiterFlags) *MockZetaCoreBridge { + z.rateLimiterFlags = flags + return z +} + +func (z *MockZetaCoreBridge) WithRateLimiterInput(input *crosschaintypes.QueryRateLimiterInputResponse) *MockZetaCoreBridge { + z.input = input + return z +} diff --git a/zetaclient/zetabridge/query.go b/zetaclient/zetabridge/query.go index dd396605f4..25cdca1934 100644 --- a/zetaclient/zetabridge/query.go +++ b/zetaclient/zetabridge/query.go @@ -18,7 +18,7 @@ import ( "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/proofs" - "github.com/zeta-chain/zetacore/x/crosschain/types" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/interfaces" @@ -34,6 +34,15 @@ func (b *ZetaCoreBridge) GetCrosschainFlags() (observertypes.CrosschainFlags, er return resp.CrosschainFlags, nil } +func (b *ZetaCoreBridge) GetRateLimiterFlags() (crosschaintypes.RateLimiterFlags, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.RateLimiterFlags(context.Background(), &crosschaintypes.QueryRateLimiterFlagsRequest{}) + if err != nil { + return crosschaintypes.RateLimiterFlags{}, err + } + return resp.RateLimiterFlags, nil +} + func (b *ZetaCoreBridge) GetVerificationFlags() (lightclienttypes.VerificationFlags, error) { client := lightclienttypes.NewQueryClient(b.grpcConn) resp, err := client.VerificationFlags(context.Background(), &lightclienttypes.QueryVerificationFlagsRequest{}) @@ -77,27 +86,27 @@ func (b *ZetaCoreBridge) GetUpgradePlan() (*upgradetypes.Plan, error) { return resp.Plan, nil } -func (b *ZetaCoreBridge) GetAllCctx() ([]*types.CrossChainTx, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.CctxAll(context.Background(), &types.QueryAllCctxRequest{}) +func (b *ZetaCoreBridge) GetAllCctx() ([]*crosschaintypes.CrossChainTx, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.CctxAll(context.Background(), &crosschaintypes.QueryAllCctxRequest{}) if err != nil { return nil, err } return resp.CrossChainTx, nil } -func (b *ZetaCoreBridge) GetCctxByHash(sendHash string) (*types.CrossChainTx, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.Cctx(context.Background(), &types.QueryGetCctxRequest{Index: sendHash}) +func (b *ZetaCoreBridge) GetCctxByHash(sendHash string) (*crosschaintypes.CrossChainTx, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.Cctx(context.Background(), &crosschaintypes.QueryGetCctxRequest{Index: sendHash}) if err != nil { return nil, err } return resp.CrossChainTx, nil } -func (b *ZetaCoreBridge) GetCctxByNonce(chainID int64, nonce uint64) (*types.CrossChainTx, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.CctxByNonce(context.Background(), &types.QueryGetCctxByNonceRequest{ +func (b *ZetaCoreBridge) GetCctxByNonce(chainID int64, nonce uint64) (*crosschaintypes.CrossChainTx, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.CctxByNonce(context.Background(), &crosschaintypes.QueryGetCctxByNonceRequest{ ChainID: chainID, Nonce: nonce, }) @@ -121,14 +130,33 @@ func (b *ZetaCoreBridge) GetObserverList() ([]string, error) { return nil, err } +// GetRateLimiterInput returns input data for the rate limit checker +func (b *ZetaCoreBridge) GetRateLimiterInput(window int64) (crosschaintypes.QueryRateLimiterInputResponse, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + maxSizeOption := grpc.MaxCallRecvMsgSize(32 * 1024 * 1024) + resp, err := client.RateLimiterInput( + context.Background(), + &crosschaintypes.QueryRateLimiterInputRequest{ + Window: window, + }, + maxSizeOption, + ) + if err != nil { + return crosschaintypes.QueryRateLimiterInputResponse{}, err + } + return *resp, nil +} + // ListPendingCctx returns a list of pending cctxs for a given chainID // - The max size of the list is crosschainkeeper.MaxPendingCctxs -func (b *ZetaCoreBridge) ListPendingCctx(chainID int64) ([]*types.CrossChainTx, uint64, error) { - client := types.NewQueryClient(b.grpcConn) +func (b *ZetaCoreBridge) ListPendingCctx(chainID int64) ([]*crosschaintypes.CrossChainTx, uint64, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) maxSizeOption := grpc.MaxCallRecvMsgSize(32 * 1024 * 1024) resp, err := client.ListPendingCctx( context.Background(), - &types.QueryListPendingCctxRequest{ChainId: chainID}, + &crosschaintypes.QueryListPendingCctxRequest{ + ChainId: chainID, + }, maxSizeOption, ) if err != nil { @@ -140,12 +168,12 @@ func (b *ZetaCoreBridge) ListPendingCctx(chainID int64) ([]*types.CrossChainTx, // ListPendingCctxWithinRatelimit returns a list of pending cctxs that do not exceed the outbound rate limit // - The max size of the list is crosschainkeeper.MaxPendingCctxs // - The returned `rateLimitExceeded` flag indicates if the rate limit is exceeded or not -func (b *ZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*types.CrossChainTx, uint64, int64, string, bool, error) { - client := types.NewQueryClient(b.grpcConn) +func (b *ZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*crosschaintypes.CrossChainTx, uint64, int64, string, bool, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) maxSizeOption := grpc.MaxCallRecvMsgSize(32 * 1024 * 1024) resp, err := client.ListPendingCctxWithinRateLimit( context.Background(), - &types.QueryListPendingCctxWithinRateLimitRequest{}, + &crosschaintypes.QueryListPendingCctxWithinRateLimitRequest{}, maxSizeOption, ) if err != nil { @@ -155,8 +183,8 @@ func (b *ZetaCoreBridge) ListPendingCctxWithinRatelimit() ([]*types.CrossChainTx } func (b *ZetaCoreBridge) GetAbortedZetaAmount() (string, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.ZetaAccounting(context.Background(), &types.QueryZetaAccountingRequest{}) + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.ZetaAccounting(context.Background(), &crosschaintypes.QueryZetaAccountingRequest{}) if err != nil { return "", err } @@ -190,9 +218,9 @@ func (b *ZetaCoreBridge) GetZetaTokenSupplyOnNode() (sdkmath.Int, error) { return resp.GetAmount().Amount, nil } -func (b *ZetaCoreBridge) GetLastBlockHeight() ([]*types.LastBlockHeight, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.LastBlockHeightAll(context.Background(), &types.QueryAllLastBlockHeightRequest{}) +func (b *ZetaCoreBridge) GetLastBlockHeight() ([]*crosschaintypes.LastBlockHeight, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.LastBlockHeightAll(context.Background(), &crosschaintypes.QueryAllLastBlockHeightRequest{}) if err != nil { b.logger.Error().Err(err).Msg("query GetBlockHeight error") return nil, err @@ -223,9 +251,9 @@ func (b *ZetaCoreBridge) GetNodeInfo() (*tmservice.GetNodeInfoResponse, error) { return nil, err } -func (b *ZetaCoreBridge) GetLastBlockHeightByChain(chain chains.Chain) (*types.LastBlockHeight, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.LastBlockHeight(context.Background(), &types.QueryGetLastBlockHeightRequest{Index: chain.ChainName.String()}) +func (b *ZetaCoreBridge) GetLastBlockHeightByChain(chain chains.Chain) (*crosschaintypes.LastBlockHeight, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.LastBlockHeight(context.Background(), &crosschaintypes.QueryGetLastBlockHeightRequest{Index: chain.ChainName.String()}) if err != nil { return nil, err } @@ -233,8 +261,8 @@ func (b *ZetaCoreBridge) GetLastBlockHeightByChain(chain chains.Chain) (*types.L } func (b *ZetaCoreBridge) GetZetaBlockHeight() (int64, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.LastZetaHeight(context.Background(), &types.QueryLastZetaHeightRequest{}) + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.LastZetaHeight(context.Background(), &crosschaintypes.QueryLastZetaHeightRequest{}) if err != nil { return 0, err } @@ -305,9 +333,9 @@ func (b *ZetaCoreBridge) GetBallot(ballotIdentifier string) (*observertypes.Quer return resp, nil } -func (b *ZetaCoreBridge) GetInboundTrackersForChain(chainID int64) ([]types.InTxTracker, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.InTxTrackerAllByChain(context.Background(), &types.QueryAllInTxTrackerByChainRequest{ChainId: chainID}) +func (b *ZetaCoreBridge) GetInboundTrackersForChain(chainID int64) ([]crosschaintypes.InTxTracker, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.InTxTrackerAllByChain(context.Background(), &crosschaintypes.QueryAllInTxTrackerByChainRequest{ChainId: chainID}) if err != nil { return nil, err } @@ -352,9 +380,9 @@ func (b *ZetaCoreBridge) GetTssHistory() ([]observertypes.TSS, error) { return resp.TssList, nil } -func (b *ZetaCoreBridge) GetOutTxTracker(chain chains.Chain, nonce uint64) (*types.OutTxTracker, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.OutTxTracker(context.Background(), &types.QueryGetOutTxTrackerRequest{ +func (b *ZetaCoreBridge) GetOutTxTracker(chain chains.Chain, nonce uint64) (*crosschaintypes.OutTxTracker, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.OutTxTracker(context.Background(), &crosschaintypes.QueryGetOutTxTrackerRequest{ ChainID: chain.ChainId, Nonce: nonce, }) @@ -364,9 +392,9 @@ func (b *ZetaCoreBridge) GetOutTxTracker(chain chains.Chain, nonce uint64) (*typ return &resp.OutTxTracker, nil } -func (b *ZetaCoreBridge) GetAllOutTxTrackerByChain(chainID int64, order interfaces.Order) ([]types.OutTxTracker, error) { - client := types.NewQueryClient(b.grpcConn) - resp, err := client.OutTxTrackerAllByChain(context.Background(), &types.QueryAllOutTxTrackerByChainRequest{ +func (b *ZetaCoreBridge) GetAllOutTxTrackerByChain(chainID int64, order interfaces.Order) ([]crosschaintypes.OutTxTracker, error) { + client := crosschaintypes.NewQueryClient(b.grpcConn) + resp, err := client.OutTxTrackerAllByChain(context.Background(), &crosschaintypes.QueryAllOutTxTrackerByChainRequest{ Chain: chainID, Pagination: &query.PageRequest{ Key: nil, diff --git a/zetaclient/zetabridge/query_test.go b/zetaclient/zetabridge/query_test.go index 65993b9c58..90f868b89e 100644 --- a/zetaclient/zetabridge/query_test.go +++ b/zetaclient/zetabridge/query_test.go @@ -15,6 +15,7 @@ import ( "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/testutil/sample" crosschainTypes "github.com/zeta-chain/zetacore/x/crosschain/types" lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -102,6 +103,29 @@ func TestZetaCoreBridge_GetCrosschainFlags(t *testing.T) { require.Equal(t, expectedOutput.CrosschainFlags, resp) } +func TestZetaCoreBridge_GetRateLimiterFlags(t *testing.T) { + // create sample flags + rateLimiterFlags := sample.RateLimiterFlags() + expectedOutput := crosschainTypes.QueryRateLimiterFlagsResponse{ + RateLimiterFlags: rateLimiterFlags, + } + + // setup mock server + input := crosschainTypes.QueryRateLimiterFlagsRequest{} + method := "/zetachain.zetacore.crosschain.Query/RateLimiterFlags" + server := setupMockServer(t, crosschainTypes.RegisterQueryServer, method, input, expectedOutput) + server.Serve() + defer closeMockServer(t, server) + + zetabridge, err := setupCoreBridge() + require.NoError(t, err) + + // query + resp, err := zetabridge.GetRateLimiterFlags() + require.NoError(t, err) + require.Equal(t, expectedOutput.RateLimiterFlags, resp) +} + func TestZetaCoreBridge_GetVerificationFlags(t *testing.T) { expectedOutput := lightclienttypes.QueryVerificationFlagsResponse{VerificationFlags: lightclienttypes.VerificationFlags{ EthTypeChainEnabled: true, @@ -270,6 +294,30 @@ func TestZetaCoreBridge_GetObserverList(t *testing.T) { require.Equal(t, expectedOutput.Observers, resp) } +func TestZetaCoreBridge_GetRateLimiterInput(t *testing.T) { + expectedOutput := crosschainTypes.QueryRateLimiterInputResponse{ + Height: 10, + CctxsMissed: []*crosschainTypes.CrossChainTx{sample.CrossChainTx(t, "1-1")}, + CctxsPending: []*crosschainTypes.CrossChainTx{sample.CrossChainTx(t, "1-2")}, + TotalPending: 1, + PastCctxsValue: "123456", + PendingCctxsValue: "1234", + LowestPendingCctxHeight: 2, + } + input := crosschainTypes.QueryRateLimiterInputRequest{Window: 10} + method := "/zetachain.zetacore.crosschain.Query/RateLimiterInput" + server := setupMockServer(t, crosschainTypes.RegisterQueryServer, method, input, expectedOutput) + server.Serve() + defer closeMockServer(t, server) + + zetabridge, err := setupCoreBridge() + require.NoError(t, err) + + resp, err := zetabridge.GetRateLimiterInput(10) + require.NoError(t, err) + require.Equal(t, expectedOutput, resp) +} + func TestZetaCoreBridge_ListPendingCctx(t *testing.T) { expectedOutput := crosschainTypes.QueryListPendingCctxResponse{ CrossChainTx: []*crosschainTypes.CrossChainTx{ diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index 807b0a8c66..474eb99ac5 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -9,6 +9,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" "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" appcontext "github.com/zeta-chain/zetacore/zetaclient/app_context" @@ -17,19 +18,24 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outtxprocessor" + "github.com/zeta-chain/zetacore/zetaclient/ratelimiter" ) 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 @@ -55,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 @@ -79,8 +86,8 @@ 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) - go co.startCctxScheduler(appContext) + co.logger.Observer.Info().Msgf("Starting cctx scheduler for %s", myid) + go co.StartCctxScheduler(appContext) go func() { // bridge queries UpgradePlan from zetabridge and send to its pause channel if upgrade height is reached @@ -108,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) } } @@ -133,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) { @@ -141,32 +148,80 @@ 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) } } return chainOb, nil } -// startCctxScheduler schedules keysigns for cctxs on each ZetaChain block (the ticker) -func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { - outTxMan := outtxprocessor.NewOutTxProcessorManager(co.logger.ChainLogger) +// GetPendingCctxsWithinRatelimit get pending cctxs across foreign chains within rate limit +func (co *CoreObserver) GetPendingCctxsWithinRatelimit(foreignChains []chains.Chain) (map[int64][]*types.CrossChainTx, error) { + // get rate limiter flags + rateLimitFlags, err := co.bridge.GetRateLimiterFlags() + if err != nil { + return nil, err + } + + // apply rate limiter or not according to the flags + rateLimiterUsable := ratelimiter.IsRateLimiterUsable(rateLimitFlags) + + // 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 := co.bridge.ListPendingCctx(chain.ChainId) + if err == nil && resp != nil { + cctxsMap[chain.ChainId] = resp + } + } + return cctxsMap, nil + } + + // query rate limiter input + resp, err := co.bridge.GetRateLimiterInput(rateLimitFlags.Window) + if err != nil { + return nil, err + } + input, ok := ratelimiter.NewInput(resp) + if !ok { + return nil, fmt.Errorf("failed to create rate limiter input") + } + + // apply rate limiter + output := ratelimiter.ApplyRateLimiter(input, rateLimitFlags.Window, rateLimitFlags.Rate) + + // set metrics + percentage := zetamath.Percentage(output.CurrentWithdrawRate.BigInt(), rateLimitFlags.Rate.BigInt()) + if percentage != nil { + percentageFloat, _ := percentage.Float64() + metrics.PercentageOfRateReached.Set(percentageFloat) + co.logger.ObserverSampled.Info().Msgf("current rate limiter window: %d rate: %s, percentage: %f", + output.CurrentWithdrawWindow, output.CurrentWithdrawRate.String(), percentageFloat) + } + + return output.CctxsMap, nil +} + +// StartCctxScheduler schedules keysigns for cctxs on each ZetaChain block (the ticker) +func (co *CoreObserver) StartCctxScheduler(appContext *appcontext.AppContext) { + outTxMan := outtxprocessor.NewOutTxProcessorManager(co.logger.OutTxProcessor) observeTicker := time.NewTicker(3 * time.Second) 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 { @@ -175,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)) { @@ -192,24 +247,18 @@ func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { // set current hot key burn rate metrics.HotKeyBurnRate.Set(float64(co.ts.HotKeyBurnRate.GetBurnRate().Int64())) - // query pending cctxs across all foreign chains with rate limit - cctxMap, withdrawWindow, withdrawRate, err := co.getAllPendingCctxWithRatelimit() + // get supported external chains + coreContext := appContext.ZetaCoreContext() + externalChains := coreContext.GetEnabledExternalChains() + + // 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: queryPendingCctxWithRatelimit failed") - } - // print value within rate limiter window every minute - if bn%10 == 0 { - co.logger.ZetaChainWatcher.Debug().Msgf( - "startCctxScheduler: withdraw window is %d, withdraw rate is %s", withdrawWindow, withdrawRate) + co.logger.Observer.Error().Err(err).Msgf("StartCctxScheduler: GetPendingCctxsWithinRatelimit failed") } // schedule keysign for pending cctxs on each chain - coreContext := appContext.ZetaCoreContext() - supportedChains := coreContext.GetEnabledChains() - for _, c := range supportedChains { - if c.IsZetaChain() { - continue - } + 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))) @@ -220,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: getTargetChainOb 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()) { @@ -235,11 +284,11 @@ func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { // #nosec G701 range is verified zetaHeight := uint64(bn) if chains.IsEVMChain(c.ChainId) { - co.scheduleCctxEVM(outTxMan, zetaHeight, c.ChainId, cctxList, ob, signer) + co.ScheduleCctxEVM(outTxMan, zetaHeight, c.ChainId, cctxList, ob, signer) } else if chains.IsBitcoinChain(c.ChainId) { - co.scheduleCctxBTC(outTxMan, zetaHeight, c.ChainId, cctxList, ob, signer) + 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 } } @@ -253,31 +302,8 @@ func (co *CoreObserver) startCctxScheduler(appContext *appcontext.AppContext) { } } -// getAllPendingCctxWithRatelimit get pending cctxs across all foreign chains with rate limit -func (co *CoreObserver) getAllPendingCctxWithRatelimit() (map[int64][]*types.CrossChainTx, int64, string, error) { - cctxList, totalPending, withdrawWindow, withdrawRate, rateLimitExceeded, err := co.bridge.ListPendingCctxWithinRatelimit() - if err != nil { - return nil, 0, "", err - } - if rateLimitExceeded { - co.logger.ZetaChainWatcher.Warn().Msgf("rate limit exceeded, fetched %d cctxs out of %d", len(cctxList), totalPending) - } - - // classify pending cctxs by chain id - cctxMap := make(map[int64][]*types.CrossChainTx) - for _, cctx := range cctxList { - chainID := cctx.GetCurrentOutTxParam().ReceiverChainId - if _, found := cctxMap[chainID]; !found { - cctxMap[chainID] = make([]*types.CrossChainTx, 0) - } - cctxMap[chainID] = append(cctxMap[chainID], cctx) - } - - return cctxMap, withdrawWindow, withdrawRate, nil -} - -// scheduleCctxEVM schedules evm outtx keysign on each ZetaChain block (the ticker) -func (co *CoreObserver) scheduleCctxEVM( +// ScheduleCctxEVM schedules evm outtx keysign on each ZetaChain block (the ticker) +func (co *CoreObserver) ScheduleCctxEVM( outTxMan *outtxprocessor.Processor, zetaHeight uint64, chainID int64, @@ -287,7 +313,7 @@ func (co *CoreObserver) scheduleCctxEVM( ) { 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) @@ -296,7 +322,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) @@ -306,23 +332,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 } @@ -350,7 +376,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) } @@ -361,11 +387,11 @@ func (co *CoreObserver) scheduleCctxEVM( } } -// scheduleCctxBTC schedules bitcoin outtx keysign on each ZetaChain block (the ticker) +// ScheduleCctxBTC schedules bitcoin outtx keysign on each ZetaChain block (the ticker) // 1. schedule at most one keysign per ticker // 2. schedule keysign only when nonce-mark UTXO is available // 3. stop keysign when lookahead is reached -func (co *CoreObserver) scheduleCctxBTC( +func (co *CoreObserver) ScheduleCctxBTC( outTxMan *outtxprocessor.Processor, zetaHeight uint64, chainID int64, @@ -375,7 +401,7 @@ func (co *CoreObserver) scheduleCctxBTC( ) { 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 @@ -389,17 +415,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 } @@ -409,13 +435,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 a02263fb0f..33ec1c01e7 100644 --- a/zetaclient/zetacore_observer_test.go +++ b/zetaclient/zetacore_observer_test.go @@ -8,7 +8,10 @@ import ( "github.com/rs/zerolog" "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" + crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" @@ -17,7 +20,13 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/testutils/stub" ) -func MockCoreObserver(t *testing.T, evmChain, btcChain chains.Chain, evmChainParams, btcChainParams *observertypes.ChainParams) *CoreObserver { +// MockCoreObserver creates a mock core observer for testing +func MockCoreObserver( + t *testing.T, + bridge interfaces.ZetaCoreBridger, + evmChain, btcChain chains.Chain, + evmChainParams, btcChainParams *observertypes.ChainParams, +) *CoreObserver { // create mock signers and clients evmSigner := stub.NewEVMSigner( evmChain, @@ -30,6 +39,7 @@ func MockCoreObserver(t *testing.T, evmChain, btcChain chains.Chain, evmChainPar // create core observer observer := &CoreObserver{ + bridge: bridge, signerMap: map[int64]interfaces.ChainSigner{ evmChain.ChainId: evmSigner, btcChain.ChainId: btcSigner, @@ -92,14 +102,14 @@ func Test_GetUpdatedSigner(t *testing.T) { } t.Run("signer should not be found", func(t *testing.T) { - observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + observer := MockCoreObserver(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) // BSC signer should not be found _, err := observer.GetUpdatedSigner(coreContext, chains.BscMainnetChain.ChainId) require.ErrorContains(t, err, "signer not found") }) t.Run("should be able to update connector and erc20 custody address", func(t *testing.T) { - observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + observer := MockCoreObserver(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) // update signer with new connector and erc20 custody address signer, err := observer.GetUpdatedSigner(coreContext, evmChain.ChainId) @@ -157,14 +167,14 @@ func Test_GetUpdatedChainClient(t *testing.T) { } t.Run("evm chain client should not be found", func(t *testing.T) { - observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + observer := MockCoreObserver(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) // BSC chain client should not be found _, err := observer.GetUpdatedChainClient(coreContext, chains.BscMainnetChain.ChainId) require.ErrorContains(t, err, "chain client not found") }) t.Run("chain params in evm chain client should be updated successfully", func(t *testing.T) { - observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + observer := MockCoreObserver(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) coreContext := CreateCoreContext(evmChain, btcChain, evmChainParamsNew, btcChainParams) // update evm chain client with new chain params chainOb, err := observer.GetUpdatedChainClient(coreContext, evmChain.ChainId) @@ -173,14 +183,14 @@ func Test_GetUpdatedChainClient(t *testing.T) { require.True(t, observertypes.ChainParamsEqual(*evmChainParamsNew, chainOb.GetChainParams())) }) t.Run("btc chain client should not be found", func(t *testing.T) { - observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + observer := MockCoreObserver(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) coreContext := CreateCoreContext(btcChain, btcChain, evmChainParams, btcChainParamsNew) // BTC testnet chain client should not be found _, err := observer.GetUpdatedChainClient(coreContext, chains.BtcTestNetChain.ChainId) require.ErrorContains(t, err, "chain client not found") }) t.Run("chain params in btc chain client should be updated successfully", func(t *testing.T) { - observer := MockCoreObserver(t, evmChain, btcChain, evmChainParams, btcChainParams) + observer := MockCoreObserver(t, nil, evmChain, btcChain, evmChainParams, btcChainParams) coreContext := CreateCoreContext(btcChain, btcChain, evmChainParams, btcChainParamsNew) // update btc chain client with new chain params chainOb, err := observer.GetUpdatedChainClient(coreContext, btcChain.ChainId) @@ -189,3 +199,135 @@ func Test_GetUpdatedChainClient(t *testing.T) { require.True(t, observertypes.ChainParamsEqual(*btcChainParamsNew, chainOb.GetChainParams())) }) } + +func Test_GetPendingCctxsWithinRatelimit(t *testing.T) { + // define test foreign chains + ethChain := chains.EthChain + btcChain := chains.BtcMainnetChain + foreignChains := []chains.Chain{ + ethChain, + btcChain, + } + + // chain params + ethChainParams := &observertypes.ChainParams{ChainId: ethChain.ChainId} + btcChainParams := &observertypes.ChainParams{ChainId: btcChain.ChainId} + + // create 10 missed and 90 pending cctxs for eth chain, the coinType/amount does not matter for this test + ethCctxsMissed := sample.CustomCctxsInBlockRange(t, 1, 10, ethChain.ChainId, coin.CoinType_Gas, "", uint64(2e14), crosschaintypes.CctxStatus_PendingOutbound) + ethCctxsPending := sample.CustomCctxsInBlockRange(t, 11, 100, ethChain.ChainId, coin.CoinType_Gas, "", uint64(2e14), crosschaintypes.CctxStatus_PendingOutbound) + ethCctxsAll := append(append([]*crosschaintypes.CrossChainTx{}, ethCctxsMissed...), ethCctxsPending...) + + // create 10 missed and 90 pending cctxs for btc chain, the coinType/amount does not matter for this test + btcCctxsMissed := sample.CustomCctxsInBlockRange(t, 1, 10, btcChain.ChainId, coin.CoinType_Gas, "", 2000, crosschaintypes.CctxStatus_PendingOutbound) + btcCctxsPending := sample.CustomCctxsInBlockRange(t, 11, 100, btcChain.ChainId, coin.CoinType_Gas, "", 2000, crosschaintypes.CctxStatus_PendingOutbound) + btcCctxsAll := append(append([]*crosschaintypes.CrossChainTx{}, btcCctxsMissed...), btcCctxsPending...) + + // all missed cctxs and all pending cctxs across all foreign chains + allCctxsMissed := crosschainkeeper.SortCctxsByHeightAndChainID( + append(append([]*crosschaintypes.CrossChainTx{}, ethCctxsMissed...), btcCctxsMissed...)) + allCctxsPending := crosschainkeeper.SortCctxsByHeightAndChainID( + append(append([]*crosschaintypes.CrossChainTx{}, ethCctxsPending...), btcCctxsPending...)) + + // define test cases + tests := []struct { + name string + rateLimiterFlags *crosschaintypes.RateLimiterFlags + response *crosschaintypes.QueryRateLimiterInputResponse + ethCctxsFallback []*crosschaintypes.CrossChainTx + btcCctxsFallback []*crosschaintypes.CrossChainTx + + // expected result map + fail bool + expectedCctxsMap map[int64][]*crosschaintypes.CrossChainTx + }{ + { + name: "should return all missed and pending cctxs using fallback", + rateLimiterFlags: &crosschaintypes.RateLimiterFlags{Enabled: false}, + response: &crosschaintypes.QueryRateLimiterInputResponse{}, + ethCctxsFallback: ethCctxsAll, + btcCctxsFallback: btcCctxsAll, + expectedCctxsMap: map[int64][]*crosschaintypes.CrossChainTx{ + ethChain.ChainId: ethCctxsAll, + btcChain.ChainId: btcCctxsAll, + }, + }, + { + name: "should return all missed and pending cctxs without fallback", + rateLimiterFlags: &crosschaintypes.RateLimiterFlags{ + Enabled: true, + Window: 100, + Rate: sdk.NewUint(1e18), // 1 ZETA/block + }, + response: &crosschaintypes.QueryRateLimiterInputResponse{ + Height: 100, + CctxsMissed: allCctxsMissed, + CctxsPending: allCctxsPending, + // #nosec G701 len always positive + TotalPending: uint64(len(allCctxsPending) + len(allCctxsMissed)), + PastCctxsValue: sdk.NewInt(10).Mul(sdk.NewInt(1e18)).String(), // 10 ZETA + PendingCctxsValue: sdk.NewInt(90).Mul(sdk.NewInt(1e18)).String(), // 90 ZETA + LowestPendingCctxHeight: 11, + }, + ethCctxsFallback: nil, + btcCctxsFallback: nil, + expectedCctxsMap: map[int64][]*crosschaintypes.CrossChainTx{ + ethChain.ChainId: ethCctxsAll, + btcChain.ChainId: btcCctxsAll, + }, + }, + { + name: "should fail if cannot query rate limiter flags", + rateLimiterFlags: nil, + fail: true, + }, + { + name: "should fail if cannot query rate limiter input", + rateLimiterFlags: &crosschaintypes.RateLimiterFlags{ + Enabled: true, + Window: 100, + Rate: sdk.NewUint(1e18), // 1 ZETA/block + }, + response: nil, + fail: true, + }, + { + name: "should fail on invalid rate limiter input", + rateLimiterFlags: &crosschaintypes.RateLimiterFlags{ + Enabled: true, + Window: 100, + Rate: sdk.NewUint(1e18), // 1 ZETA/block + }, + response: &crosschaintypes.QueryRateLimiterInputResponse{ + PastCctxsValue: "invalid", + }, + fail: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // create mock bridge + bridge := stub.NewMockZetaCoreBridge() + + // load mock data + bridge.WithRateLimiterFlags(tt.rateLimiterFlags) + bridge.WithPendingCctx(ethChain.ChainId, tt.ethCctxsFallback) + bridge.WithPendingCctx(btcChain.ChainId, tt.btcCctxsFallback) + bridge.WithRateLimiterInput(tt.response) + + // create core observer + observer := MockCoreObserver(t, bridge, ethChain, btcChain, ethChainParams, btcChainParams) + + // run the test + cctxsMap, err := observer.GetPendingCctxsWithinRatelimit(foreignChains) + if tt.fail { + require.Error(t, err) + require.Nil(t, cctxsMap) + } else { + require.NoError(t, err) + require.Equal(t, tt.expectedCctxsMap, cctxsMap) + } + }) + } +}